Skip to content

Commit

Permalink
Merge branch 'master' into dp/vmecio-asym
Browse files Browse the repository at this point in the history
  • Loading branch information
dpanici authored Aug 28, 2024
2 parents 8efaf20 + 301a894 commit 7d0e31d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 9 deletions.
6 changes: 5 additions & 1 deletion desc/geometry/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,16 @@ def Z_n(self, new):
)

@classmethod
def from_input_file(cls, path):
def from_input_file(cls, path, **kwargs):
"""Create a axis curve from Fourier coefficients in a DESC or VMEC input file.
Parameters
----------
path : Path-like or str
Path to DESC or VMEC input file.
**kwargs : dict, optional
keyword arguments to pass to the constructor of the
FourierRZCurve being created.
Returns
-------
Expand All @@ -227,6 +230,7 @@ def from_input_file(cls, path):
inputs["axis"][:, 0].astype(int),
inputs["NFP"],
inputs["sym"],
**kwargs,
)
return curve

Expand Down
6 changes: 5 additions & 1 deletion desc/geometry/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,13 +298,16 @@ def set_coeffs(self, m, n=0, R=None, Z=None):
self.Z_lmn = put(self.Z_lmn, idxZ, ZZ)

@classmethod
def from_input_file(cls, path):
def from_input_file(cls, path, **kwargs):
"""Create a surface from Fourier coefficients in a DESC or VMEC input file.
Parameters
----------
path : Path-like or str
Path to DESC or VMEC input file.
**kwargs : dict, optional
keyword arguments to pass to the constructor of the
FourierRZToroidalSurface being created.
Returns
-------
Expand All @@ -328,6 +331,7 @@ def from_input_file(cls, path):
inputs["surface"][:, 1:3].astype(int),
inputs["NFP"],
inputs["sym"],
**kwargs,
)
return surf

Expand Down
50 changes: 44 additions & 6 deletions desc/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -1352,10 +1352,9 @@ def plot_section(
phi = np.atleast_1d(phi)
nphi = len(phi)
if grid is None:
nfp = eq.NFP
grid_kwargs = {
"L": 25,
"NFP": nfp,
"NFP": 1,
"axis": False,
"theta": np.linspace(0, 2 * np.pi, 91, endpoint=True),
"zeta": phi,
Expand Down Expand Up @@ -1610,9 +1609,14 @@ def plot_surfaces(eq, rho=8, theta=8, phi=None, ax=None, return_data=False, **kw
phi = np.atleast_1d(phi)
nphi = len(phi)

# do not need NFP supplied to these grids as
# the above logic takes care of the correct phi range
# if defaults are requested. Setting NFP here instead
# can create reshaping issues when phi is supplied and gets
# truncated by 2pi/NFP. See PR #1204
grid_kwargs = {
"rho": rho,
"NFP": nfp,
"NFP": 1,
"theta": np.linspace(0, 2 * np.pi, NT, endpoint=True),
"zeta": phi,
}
Expand All @@ -1631,7 +1635,7 @@ def plot_surfaces(eq, rho=8, theta=8, phi=None, ax=None, return_data=False, **kw
)
grid_kwargs = {
"rho": np.linspace(0, 1, NR),
"NFP": nfp,
"NFP": 1,
"theta": theta,
"zeta": phi,
}
Expand Down Expand Up @@ -1960,7 +1964,7 @@ def plot_boundary(eq, phi=None, plot_axis=True, ax=None, return_data=False, **kw
plot_axis = plot_axis and eq.L > 0
rho = np.array([0.0, 1.0]) if plot_axis else np.array([1.0])

grid_kwargs = {"NFP": eq.NFP, "rho": rho, "theta": 100, "zeta": phi}
grid_kwargs = {"NFP": 1, "rho": rho, "theta": 100, "zeta": phi}
grid = _get_grid(**grid_kwargs)
nr, nt, nz = grid.num_rho, grid.num_theta, grid.num_zeta
grid = Grid(
Expand Down Expand Up @@ -2030,6 +2034,9 @@ def plot_boundaries(
):
"""Plot stellarator boundaries at multiple toroidal coordinates.
NOTE: If attempting to plot objects with differing NFP, `phi` must
be given explicitly.
Parameters
----------
eqs : array-like of Equilibrium, Surface or EquilibriaFamily
Expand Down Expand Up @@ -2085,7 +2092,21 @@ def plot_boundaries(
fig, ax = plot_boundaries((eq1, eq2, eq3))
"""
# if NFPs are not all equal, means there are
# objects with differing NFPs, which it is not clear
# how to choose the phis for by default, so we will throw an error
# unless phi was given.
phi = parse_argname_change(phi, kwargs, "zeta", "phi")
errorif(
not np.allclose([thing.NFP for thing in eqs], eqs[0].NFP) and phi is None,
ValueError,
"supplied objects must have the same number of field periods, "
"or if there are differing field periods, `phi` must be given explicitly."
f" Instead, supplied objects have NFPs {[t.NFP for t in eqs]}."
" If attempting to plot an axisymmetric object with non-axisymmetric objects,"
" you must use the `change_resolution` method to make the axisymmetric "
"object have the same NFP as the non-axisymmetric objects.",
)

figsize = kwargs.pop("figsize", None)
cmap = kwargs.pop("cmap", "rainbow")
Expand Down Expand Up @@ -2129,7 +2150,7 @@ def plot_boundaries(
plot_axis_i = plot_axis and eqs[i].L > 0
rho = np.array([0.0, 1.0]) if plot_axis_i else np.array([1.0])

grid_kwargs = {"NFP": eqs[i].NFP, "theta": 100, "zeta": phi, "rho": rho}
grid_kwargs = {"NFP": 1, "theta": 100, "zeta": phi, "rho": rho}
grid = _get_grid(**grid_kwargs)
nr, nt, nz = grid.num_rho, grid.num_theta, grid.num_zeta
grid = Grid(
Expand Down Expand Up @@ -2198,6 +2219,9 @@ def plot_comparison(
):
"""Plot comparison between flux surfaces of multiple equilibria.
NOTE: If attempting to plot objects with differing NFP, `phi` must
be given explicitly.
Parameters
----------
eqs : array-like of Equilibrium or EquilibriaFamily
Expand Down Expand Up @@ -2266,7 +2290,21 @@ def plot_comparison(
)
"""
# if NFPs are not all equal, means there are
# objects with differing NFPs, which it is not clear
# how to choose the phis for by default, so we will throw an error
# unless phi was given.
phi = parse_argname_change(phi, kwargs, "zeta", "phi")
errorif(
not np.allclose([thing.NFP for thing in eqs], eqs[0].NFP) and phi is None,
ValueError,
"supplied objects must have the same number of field periods, "
"or if there are differing field periods, `phi` must be given explicitly."
f" Instead, supplied objects have NFPs {[t.NFP for t in eqs]}."
" If attempting to plot an axisymmetric object with non-axisymmetric objects,"
" you must use the `change_resolution` method to make the axisymmetric "
"object have the same NFP as the non-axisymmetric objects.",
)
color = parse_argname_change(color, kwargs, "colors", "color")
ls = parse_argname_change(ls, kwargs, "linestyles", "ls")
lw = parse_argname_change(lw, kwargs, "lws", "lw")
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 24 additions & 1 deletion tests/test_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,14 @@ def test_plot_boundaries(self):
eq1 = get("SOLOVEV")
eq2 = get("DSHAPE")
eq3 = get("W7-X")
fig, ax, data = plot_boundaries((eq1, eq2, eq3), return_data=True)
eq4 = get("ESTELL")
with pytest.raises(ValueError, match="differing field periods"):
fig, ax = plot_boundaries([eq3, eq4], theta=0)
fig, ax, data = plot_boundaries(
(eq1, eq2, eq3),
phi=np.linspace(0, 2 * np.pi / eq3.NFP, 4, endpoint=False),
return_data=True,
)
assert "R" in data.keys()
assert "Z" in data.keys()
assert len(data["R"]) == 3
Expand Down Expand Up @@ -550,6 +557,22 @@ def test_plot_comparison_no_theta(self):
fig, ax = plot_comparison(eqf, theta=0)
return fig

@pytest.mark.unit
@pytest.mark.mpl_image_compare(remove_text=True, tolerance=tol_2d)
def test_plot_comparison_different_NFPs(self):
"""Test plotting comparison of flux surfaces with differing NFPs."""
eq = get("SOLOVEV")
eq_nonax = get("HELIOTRON")
eq_nonax2 = get("ESTELL")
with pytest.raises(ValueError, match="differing field periods"):
fig, ax = plot_comparison([eq_nonax, eq_nonax2], theta=0)
fig, ax = plot_comparison(
[eq, eq_nonax],
phi=np.linspace(0, 2 * np.pi / eq_nonax.NFP, 6, endpoint=False),
theta=0,
)
return fig


class TestPlotGrid:
"""Tests for the plot_grid function."""
Expand Down

0 comments on commit 7d0e31d

Please sign in to comment.