Skip to content

Commit 4b73cba

Browse files
committed
Added missing lasym terms to VmecWout
1 parent c695032 commit 4b73cba

2 files changed

Lines changed: 145 additions & 60 deletions

File tree

src/vmecpp/__init__.py

Lines changed: 144 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,8 @@ class VmecWOut(BaseModelWithNumpy):
592592
"DGeod",
593593
"raxis_cc",
594594
"zaxis_cs",
595+
"raxis_cs",
596+
"zaxis_cc",
595597
"version_",
596598
"bvco",
597599
"buco",
@@ -617,6 +619,15 @@ class VmecWOut(BaseModelWithNumpy):
617619
"bsupumnc",
618620
"bsupvmnc",
619621
"gmnc",
622+
"rmns",
623+
"zmnc",
624+
"gmns",
625+
"bmns",
626+
"bsubumns",
627+
"bsubvmns",
628+
"bsubsmnc",
629+
"bsupumns",
630+
"bsupvmns",
620631
"restart_reason_timetrace",
621632
]
622633
"""If quantities are not exactly the same in C++ WoutFileContents and this class,
@@ -661,7 +672,10 @@ class VmecWOut(BaseModelWithNumpy):
661672
),
662673
pydantic.Field(alias="lasym__logical__"),
663674
]
664-
"""Flag indicating non-stellarator-symmetry."""
675+
"""Flag indicating non-stellarator-symmetry.
676+
677+
Non-stellarator symmetric fields are only populated if this is True.
678+
"""
665679

666680
lfreeb: typing.Annotated[
667681
bool,
@@ -859,76 +873,101 @@ class VmecWOut(BaseModelWithNumpy):
859873
grid."""
860874

861875
bmnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
862-
"""Fourier coefficients of the magnetic field strength ``|B|`` on the half-grid."""
876+
"""Fourier coefficients (cos) of the magnetic field strength ``|B|`` on the half-
877+
grid."""
863878

864879
gmnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
865-
r"""Fourier coefficients of the Jacobian :math:`\sqrt{g}` on the half-grid."""
880+
r"""Fourier coefficients (cos) of the Jacobian :math:`\sqrt{g}` on the half-grid."""
866881

867882
bsubumnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
868-
r"""Fourier coefficients of the covariant magnetic field component
883+
r"""Fourier coefficients (cos) of the covariant magnetic field component
869884
:math:`B_{\theta}` on the half-grid."""
870885

871886
bsubvmnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
872-
r"""Fourier coefficients of the covariant magnetic field component :math:`B_{\phi}`
873-
on the half-grid."""
887+
r"""Fourier coefficients (cos) of the covariant magnetic field component
888+
:math:`B_{\phi}` on the half-grid."""
874889

875890
bsubsmns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
876-
"""Fourier coefficients of the covariant magnetic field component :math:`B_{s}` on
877-
the full- grid."""
891+
"""Fourier coefficients (sin) of the covariant magnetic field component
892+
:math:`B_{s}` on the full- grid."""
878893

879894
bsupumnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
880-
r"""Fourier coefficients of the contravariant magnetic field component
895+
r"""Fourier coefficients (cos) of the contravariant magnetic field component
881896
:math:`B^{\theta}` on the half-grid."""
882897

883898
bsupvmnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"]
884-
r"""Fourier coefficients of the contravariant magnetic field component
899+
r"""Fourier coefficients (cos) of the contravariant magnetic field component
885900
:math:`B^{\phi}` on the half-grid."""
886901

887902
rmnc: jt.Float[np.ndarray, "mn_mode n_surfaces"]
888-
"""Fourier coefficients for ``R`` of the geometry of the flux surfaces on the full-
889-
grid."""
903+
"""Fourier coefficients (cos) for ``R`` of the geometry of the flux surfaces on the
904+
full- grid."""
890905

891906
zmns: jt.Float[np.ndarray, "mn_mode n_surfaces"]
892-
"""Fourier coefficients for ``Z`` of the geometry of the flux surfaces on the full-
893-
grid."""
907+
"""Fourier coefficients (sin) for ``Z`` of the geometry of the flux surfaces on the
908+
full- grid."""
894909

895910
lmns: jt.Float[np.ndarray, "mn_mode n_surfaces"]
896-
"""Fourier coefficients for ``lambda`` stream function on the half-grid."""
911+
"""Fourier coefficients (sin) for ``lambda`` stream function on the half-grid."""
897912

898913
lmns_full: jt.Float[np.ndarray, "mn_mode n_surfaces"]
899-
"""Fourier coefficients for ``lambda`` stream function on the full-grid.
914+
"""Fourier coefficients (sin) for ``lambda`` stream function on the full-grid.
900915
901916
This quantity is VMEC++ specific and required for hot-restart to work properly. We
902917
store it with the Fortran convention for the order of the dimensions for consistency
903918
with lmns.
904919
"""
905920

906921
rmns: jt.Float[np.ndarray, "mn_mode n_surfaces"] | None = None
907-
"""Fourier coefficients for `R` ~ sin(m*theta - n*zeta) of the geometry of the flux surfaces on the full-grid.
908-
909-
Only populated when lasym=True (non-stellarator-symmetric configurations).
910-
"""
922+
"""Fourier coefficients (sin) for `R` of the geometry of the flux surfaces on the
923+
full-grid; non-stellarator-symmetric."""
911924

912925
zmnc: jt.Float[np.ndarray, "mn_mode n_surfaces"] | None = None
913-
"""Fourier coefficients for `Z` ~ cos(m*theta - n*zeta) of the geometry of the flux surfaces on the full-grid.
914-
915-
Only populated when lasym=True (non-stellarator-symmetric configurations).
916-
"""
926+
"""Fourier coefficients (cos) for `Z` of the geometry of the flux surfaces on the
927+
full-grid; non-stellarator-symmetric."""
917928

918929
lmnc: jt.Float[np.ndarray, "mn_mode n_surfaces"] | None = None
919-
"""Fourier coefficients for `lambda` ~ cos(m*theta - n*zeta) stream function on the half-grid.
920-
921-
Only populated when lasym=True (non-stellarator-symmetric configurations).
922-
"""
930+
"""Fourier coefficients (cos) for `lambda` stream function on the half-grid; non-
931+
stellarator-symmetric."""
923932

924933
lmnc_full: jt.Float[np.ndarray, "mn_mode n_surfaces"] | None = None
925-
"""Fourier coefficients for `lambda` ~ cos(m*theta - n*zeta) stream function on the full-grid.
934+
"""Fourier coefficients (cos) for `lambda` stream function on the full-grid; non-
935+
stellarator-symmetric.
926936
927937
This quantity is VMEC++ specific and required for hot-restart to work properly. We
928938
store it with the Fortran convention for the order of the dimensions for consistency
929-
with lmnc. Only populated when lasym=True (non-stellarator-symmetric configurations).
939+
with lmnc. Only populated when lasym=True (non-stellarator-symmetric
940+
configurations).
930941
"""
931942

943+
gmns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
944+
r"""Fourier coefficients (sin) of the Jacobian :math:`\sqrt{g}` on the half-grid;
945+
non-stellarator-symmetric."""
946+
947+
bmns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
948+
"""Fourier coefficients (sin) of the magnetic field strength ``|B|`` on the half-
949+
grid; non-stellarator-symmetric."""
950+
951+
bsubumns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
952+
r"""Fourier coefficients (sin) of the covariant magnetic field component
953+
:math:`B_{\theta}` on the half-grid; non-stellarator-symmetric."""
954+
955+
bsubvmns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
956+
r"""Fourier coefficients (sin) of the covariant magnetic field component
957+
:math:`B_{\phi}` on the half-grid; non-stellarator-symmetric."""
958+
959+
bsubsmnc: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
960+
"""Fourier coefficients (cos) of the covariant magnetic field component
961+
:math:`B_{s}` on the full- grid; non-stellarator-symmetric."""
962+
963+
bsupumns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
964+
r"""Fourier coefficients (sin) of the contravariant magnetic field component
965+
:math:`B^{\theta}` on the half-grid; non-stellarator-symmetric."""
966+
967+
bsupvmns: jt.Float[np.ndarray, "mn_mode_nyq n_surfaces"] | None = None
968+
r"""Fourier coefficients (sin) of the contravariant magnetic field component
969+
:math:`B^{\phi}` on the half-grid; non-stellarator-symmetric."""
970+
932971
pcurr_type: ProfileType
933972
"""Parametrization of toroidal current profile (copied from input)."""
934973

@@ -1017,6 +1056,16 @@ class VmecWOut(BaseModelWithNumpy):
10171056
zaxis_cs: jt.Float[np.ndarray, "ntor_plus_1"]
10181057
"""Fourier coefficients of :math:`Z(phi)` of the magnetic axis geometry."""
10191058

1059+
# In the C++ WOutFileContents this is called raxis_s.
1060+
raxis_cs: jt.Float[np.ndarray, "ntor_plus_1"] | None = None
1061+
"""Fourier coefficients of :math:`R(phi)` of the magnetic axis geometry; non-
1062+
stellarator-symmetric."""
1063+
1064+
# In the C++ WOutFileContents this is called zaxis_c.
1065+
zaxis_cc: jt.Float[np.ndarray, "ntor_plus_1"] | None = None
1066+
"""Fourier coefficients of :math:`Z(phi)` of the magnetic axis geometry; non-
1067+
stellarator-symmetric."""
1068+
10201069
# In the C++ WOutFileContents this is called dVds.
10211070
vp: jt.Float[np.ndarray, "n_surfaces"]
10221071
r"""Differential volume :math:`V' = \frac{\partial V}{\partial s}` on half-grid.
@@ -1329,24 +1378,37 @@ def _from_cpp_wout(cpp_wout: _vmecpp.VmecppWOut) -> VmecWOut:
13291378
attrs["lmns_full"] = cpp_wout.lmns_full.T
13301379

13311380
# Asymmetric attributes are transposed and only populated when lasym=True
1381+
# All of them are defaulted to None when lasym=False
13321382
if cpp_wout.lasym:
1383+
attrs["raxis_cs"] = cpp_wout.raxis_s
1384+
attrs["zaxis_cc"] = cpp_wout.zaxis_c
1385+
1386+
attrs["bsubsmnc"] = cpp_wout.bsubsmnc.T
13331387
attrs["rmns"] = cpp_wout.rmns.T
13341388
attrs["zmnc"] = cpp_wout.zmnc.T
13351389
attrs["lmnc_full"] = cpp_wout.lmnc_full.T
1336-
else:
1337-
attrs["rmns"] = None
1338-
attrs["zmnc"] = None
1339-
attrs["lmnc_full"] = None
1390+
1391+
attrs["lmnc"] = _pad_and_transpose(cpp_wout.lmnc, attrs["mnmax"])
1392+
1393+
attrs["bmns"] = _pad_and_transpose(cpp_wout.bmns, attrs["mnmax_nyq"])
1394+
attrs["bsubumns"] = _pad_and_transpose(
1395+
cpp_wout.bsubumns, attrs["mnmax_nyq"]
1396+
)
1397+
attrs["bsubvmns"] = _pad_and_transpose(
1398+
cpp_wout.bsubvmns, attrs["mnmax_nyq"]
1399+
)
1400+
attrs["bsupumns"] = _pad_and_transpose(
1401+
cpp_wout.bsupumns, attrs["mnmax_nyq"]
1402+
)
1403+
attrs["bsupvmns"] = _pad_and_transpose(
1404+
cpp_wout.bsupvmns, attrs["mnmax_nyq"]
1405+
)
1406+
attrs["gmns"] = _pad_and_transpose(cpp_wout.gmns, attrs["mnmax_nyq"])
13401407

13411408
# These attributes have one column less and their elements are transposed
13421409
# in VMEC++ with respect to SIMSOPT/VMEC2000
13431410
attrs["lmns"] = _pad_and_transpose(cpp_wout.lmns, attrs["mnmax"])
13441411

1345-
# Asymmetric lambda coefficients on half-grid (only when lasym=True)
1346-
if cpp_wout.lasym:
1347-
attrs["lmnc"] = _pad_and_transpose(cpp_wout.lmnc, attrs["mnmax"])
1348-
else:
1349-
attrs["lmnc"] = None
13501412
attrs["bmnc"] = _pad_and_transpose(cpp_wout.bmnc, attrs["mnmax_nyq"])
13511413
attrs["bsubumnc"] = _pad_and_transpose(cpp_wout.bsubumnc, attrs["mnmax_nyq"])
13521414
attrs["bsubvmnc"] = _pad_and_transpose(cpp_wout.bsubvmnc, attrs["mnmax_nyq"])
@@ -1453,30 +1515,49 @@ def _to_cpp_wout(self) -> _vmecpp.WOutFileContents:
14531515
# stored in a wout file for consistency with lmns.
14541516
cpp_wout.lmns_full = self.lmns_full.T
14551517

1518+
if self.lasym:
1519+
cpp_wout.raxis_s = self.raxis_cs
1520+
cpp_wout.zaxis_c = self.zaxis_cc
14561521
# Asymmetric attributes are transposed and only set when lasym=True
1457-
if self.lasym and self.rmns is not None:
1458-
cpp_wout.rmns = self.rmns.T
1459-
if self.lasym and self.zmnc is not None:
1460-
cpp_wout.zmnc = self.zmnc.T
1461-
if self.lasym and self.lmnc_full is not None:
1462-
cpp_wout.lmnc_full = self.lmnc_full.T
1522+
for field in [
1523+
"bsubsmnc",
1524+
"rmns",
1525+
"zmnc",
1526+
"lmnc_full",
1527+
"lmnc",
1528+
"bmns",
1529+
]:
1530+
value = getattr(self, field)
1531+
if value is not None:
1532+
setattr(cpp_wout, field, value.T)
14631533

14641534
# This is a VMEC++ only quantity
14651535
cpp_wout.restart_reasons = self.restart_reason_timetrace
14661536

1537+
# coefficients on half-grid
14671538
# These attributes have one column less and their elements are transposed
14681539
# in VMEC++ with respect to SIMSOPT/VMEC2000
1469-
cpp_wout.lmns = self.lmns.T[1:, :]
1470-
1471-
# Asymmetric lambda coefficients on half-grid (only when lasym=True)
1472-
if self.lasym and self.lmnc is not None:
1473-
cpp_wout.lmnc = self.lmnc.T[1:, :]
1474-
cpp_wout.bmnc = self.bmnc.T[1:, :]
1475-
cpp_wout.bsubumnc = self.bsubumnc.T[1:, :]
1476-
cpp_wout.bsubvmnc = self.bsubvmnc.T[1:, :]
1477-
cpp_wout.bsupumnc = self.bsupumnc.T[1:, :]
1478-
cpp_wout.bsupvmnc = self.bsupvmnc.T[1:, :]
1479-
cpp_wout.gmnc = self.gmnc.T[1:, :]
1540+
for field in [
1541+
"lmns",
1542+
"gmnc",
1543+
"bmnc",
1544+
"bsubumnc",
1545+
"bsubvmnc",
1546+
"bsupumnc",
1547+
"bsupvmnc",
1548+
# Asymmetric coefficients (only when lasym=True)
1549+
"lmnc",
1550+
"gmns",
1551+
"bmns",
1552+
"bsubumns",
1553+
"bsubvmns",
1554+
"bsupumns",
1555+
"bsupvmns",
1556+
]:
1557+
value = getattr(self, field)
1558+
# Asymmetric coefficients may be None when lasym=False
1559+
if value is not None:
1560+
setattr(cpp_wout, field, value.T[1:, :])
14801561

14811562
return cpp_wout
14821563

@@ -1511,10 +1592,12 @@ def from_wout_file(wout_filename: str | Path) -> VmecWOut:
15111592

15121593
# Special handling for variables only present in VMEC++
15131594
# For now, only special case for lambda coefficients: lambda = 0 is a physically meaningful fall-back value
1514-
if "lmns_full" not in attrs:
1595+
if "lmns_full" not in attrs or "lmnc_full" not in attrs:
15151596
mnmax = attrs["mnmax"]
15161597
ns = attrs["ns"]
15171598
attrs["lmns_full"] = np.zeros([mnmax, ns])
1599+
if attrs["lasym__logical__"]:
1600+
attrs["lmnc_full"] = np.zeros([mnmax, ns])
15181601

15191602
return VmecWOut.model_validate(attrs, by_alias=True)
15201603

@@ -1928,8 +2011,10 @@ def ensure_vmec2000_input(input_path: Path) -> Generator[Path, None, None]:
19282011

19292012

19302013
def _pad_and_transpose(
1931-
arr: jt.Float[np.ndarray, "ns_minus_one mn"], mnsize: int
1932-
) -> jt.Float[np.ndarray, "mn ns"]:
2014+
arr: jt.Float[np.ndarray, "ns_minus_one mn"] | None, mnsize: int
2015+
) -> jt.Float[np.ndarray, "mn ns"] | None:
2016+
if arr is None:
2017+
return None
19332018
stacked = np.vstack((np.zeros(mnsize), arr)).T
19342019
assert stacked.shape[1] == arr.shape[0] + 1
19352020
assert stacked.shape[0] == arr.shape[1]

src/vmecpp/cpp/vmecpp/vmec/pybind11/pybind_vmec.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ PYBIND11_MODULE(_vmecpp, m) {
592592
//
593593
.def_readwrite("raxis_s", &vmecpp::WOutFileContents::raxis_s)
594594
.def_readwrite("zaxis_c", &vmecpp::WOutFileContents::zaxis_c)
595-
//
595+
// non-stellarator symmetric
596596
.def_readwrite("rmns", &vmecpp::WOutFileContents::rmns)
597597
.def_readwrite("zmnc", &vmecpp::WOutFileContents::zmnc)
598598
.def_readwrite("lmnc_full", &vmecpp::WOutFileContents::lmnc_full)

0 commit comments

Comments
 (0)