From 1765fcc5b8e0f27e796d6ed173f8d7f472381dad Mon Sep 17 00:00:00 2001 From: Jung Dae Suh Date: Thu, 5 Feb 2026 14:22:52 +0900 Subject: [PATCH 1/3] Fix #164: rebind spans after copy/move/assignment --- .../fourier_coefficients.cc | 37 ++++++++++ .../fourier_coefficients.h | 4 ++ .../vmec/fourier_forces/fourier_forces.cc | 65 +++++++++++++++++ .../vmec/fourier_forces/fourier_forces.h | 7 ++ .../vmec/fourier_geometry/fourier_geometry.cc | 69 +++++++++++++++++++ .../vmec/fourier_geometry/fourier_geometry.h | 7 ++ .../vmec/fourier_velocity/fourier_velocity.cc | 66 ++++++++++++++++++ .../vmec/fourier_velocity/fourier_velocity.h | 7 ++ 8 files changed, 262 insertions(+) diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.cc index 8620e320b..93a31929f 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.cc @@ -6,6 +6,7 @@ #include #include +#include #include #include "absl/algorithm/container.h" @@ -45,6 +46,42 @@ FourierCoeffs::FourierCoeffs(const Sizes* s, const RadialPartitioning* r, } } +FourierCoeffs& FourierCoeffs::operator=(const FourierCoeffs& other) { + if (this != &other) { + rcc = other.rcc; + rss = other.rss; + rsc = other.rsc; + rcs = other.rcs; + zsc = other.zsc; + zcs = other.zcs; + zcc = other.zcc; + zss = other.zss; + lsc = other.lsc; + lcs = other.lcs; + lcc = other.lcc; + lss = other.lss; + } + return *this; +} + +FourierCoeffs& FourierCoeffs::operator=(FourierCoeffs&& other) noexcept { + if (this != &other) { + rcc = std::move(other.rcc); + rss = std::move(other.rss); + rsc = std::move(other.rsc); + rcs = std::move(other.rcs); + zsc = std::move(other.zsc); + zcs = std::move(other.zcs); + zcc = std::move(other.zcc); + zss = std::move(other.zss); + lsc = std::move(other.lsc); + lcs = std::move(other.lcs); + lcc = std::move(other.lcc); + lss = std::move(other.lss); + } + return *this; +} + int FourierCoeffs::nsMin() const { return nsMin_; } int FourierCoeffs::nsMax() const { return nsMax_; } diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.h b/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.h index ba38b8ae3..9986e57a9 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.h +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_coefficients/fourier_coefficients.h @@ -19,6 +19,10 @@ class FourierCoeffs { public: FourierCoeffs(const Sizes* s, const RadialPartitioning* r, int nsMin, int nsMax, int ns); + FourierCoeffs(const FourierCoeffs& other) = default; + FourierCoeffs(FourierCoeffs&& other) noexcept = default; + FourierCoeffs& operator=(const FourierCoeffs& other); + FourierCoeffs& operator=(FourierCoeffs&& other) noexcept; void setZero(); diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc index 3bd20d80a..019d8f86a 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc @@ -5,6 +5,7 @@ #include "vmecpp/vmec/fourier_forces/fourier_forces.h" #include +#include #include namespace vmecpp { @@ -25,6 +26,70 @@ FourierForces::FourierForces(const Sizes* s, const RadialPartitioning* r, flcc(lcc), flss(lss) {} +FourierForces::FourierForces(const FourierForces& other) + : FourierCoeffs(other), + frcc(rcc), + frss(rss), + frsc(rsc), + frcs(rcs), + fzsc(zsc), + fzcs(zcs), + fzcc(zcc), + fzss(zss), + flsc(lsc), + flcs(lcs), + flcc(lcc), + flss(lss) {} + +void FourierForces::BindSpans() { + frcc = rcc; + frss = rss; + frsc = rsc; + frcs = rcs; + fzsc = zsc; + fzcs = zcs; + fzcc = zcc; + fzss = zss; + flsc = lsc; + flcs = lcs; + flcc = lcc; + flss = lss; +} + +FourierForces& FourierForces::operator=(const FourierForces& other) { + if (this != &other) { + FourierCoeffs::operator=(other); + BindSpans(); + } + return *this; +} + +FourierForces::FourierForces(FourierForces&& other) noexcept + : FourierCoeffs(std::move(other)), + frcc(rcc), + frss(rss), + frsc(rsc), + frcs(rcs), + fzsc(zsc), + fzcs(zcs), + fzcc(zcc), + fzss(zss), + flsc(lsc), + flcs(lcs), + flcc(lcc), + flss(lss) { + other.BindSpans(); +} + +FourierForces& FourierForces::operator=(FourierForces&& other) noexcept { + if (this != &other) { + FourierCoeffs::operator=(std::move(other)); + BindSpans(); + other.BindSpans(); + } + return *this; +} + void FourierForces::zeroZForceForM1() { for (int jF = nsMin_; jF < nsMax_; ++jF) { for (int n = 0; n < s_.ntor + 1; ++n) { diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.h b/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.h index 3c5074248..12615dcec 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.h +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.h @@ -15,6 +15,10 @@ namespace vmecpp { class FourierForces : public FourierCoeffs { public: FourierForces(const Sizes* s, const RadialPartitioning* r, int ns); + FourierForces(const FourierForces& other); + FourierForces& operator=(const FourierForces& other); + FourierForces(FourierForces&& other) noexcept; + FourierForces& operator=(FourierForces&& other) noexcept; void zeroZForceForM1(); void residuals(std::vector& fRes, bool includeEdgeRZ) const; @@ -34,6 +38,9 @@ class FourierForces : public FourierCoeffs { std::span flcs; std::span flcc; std::span flss; + + private: + void BindSpans(); }; } // namespace vmecpp diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc index 5b017f257..632caefbc 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc @@ -5,6 +5,7 @@ #include "vmecpp/vmec/fourier_geometry/fourier_geometry.h" #include +#include #include #include "vmecpp/common/util/util.h" @@ -29,6 +30,74 @@ FourierGeometry::FourierGeometry(const Sizes* s, const RadialPartitioning* r, lmncc(lcc), lmnss(lss) {} +FourierGeometry::FourierGeometry(const FourierGeometry& other) + : FourierCoeffs(other), + rmncc(rcc), + rmnss(rss), + rmnsc(rsc), + rmncs(rcs), + + zmnsc(zsc), + zmncs(zcs), + zmncc(zcc), + zmnss(zss), + + lmnsc(lsc), + lmncs(lcs), + lmncc(lcc), + lmnss(lss) {} + +void FourierGeometry::BindSpans() { + rmncc = rcc; + rmnss = rss; + rmnsc = rsc; + rmncs = rcs; + zmnsc = zsc; + zmncs = zcs; + zmncc = zcc; + zmnss = zss; + lmnsc = lsc; + lmncs = lcs; + lmncc = lcc; + lmnss = lss; +} + +FourierGeometry& FourierGeometry::operator=(const FourierGeometry& other) { + if (this != &other) { + FourierCoeffs::operator=(other); + BindSpans(); + } + return *this; +} + +FourierGeometry::FourierGeometry(FourierGeometry&& other) noexcept + : FourierCoeffs(std::move(other)), + rmncc(rcc), + rmnss(rss), + rmnsc(rsc), + rmncs(rcs), + + zmnsc(zsc), + zmncs(zcs), + zmncc(zcc), + zmnss(zss), + + lmnsc(lsc), + lmncs(lcs), + lmncc(lcc), + lmnss(lss) { + other.BindSpans(); +} + +FourierGeometry& FourierGeometry::operator=(FourierGeometry&& other) noexcept { + if (this != &other) { + FourierCoeffs::operator=(std::move(other)); + BindSpans(); + other.BindSpans(); + } + return *this; +} + void FourierGeometry::interpFromBoundaryAndAxis( const FourierBasisFastPoloidal& t, const Boundaries& b, const RadialProfiles& p) { diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.h b/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.h index d17be05b6..7e6cd3321 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.h +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.h @@ -18,6 +18,10 @@ namespace vmecpp { class FourierGeometry : public FourierCoeffs { public: FourierGeometry(const Sizes *s, const RadialPartitioning *r, int ns); + FourierGeometry(const FourierGeometry &other); + FourierGeometry &operator=(const FourierGeometry &other); + FourierGeometry(FourierGeometry &&other) noexcept; + FourierGeometry &operator=(FourierGeometry &&other) noexcept; void interpFromBoundaryAndAxis(const FourierBasisFastPoloidal &t, const Boundaries &b, const RadialProfiles &p); @@ -87,6 +91,9 @@ class FourierGeometry : public FourierCoeffs { // contrib to lambda ~ sin(m * theta) * sin(n * zeta) std::span lmnss; + + private: + void BindSpans(); }; } // namespace vmecpp diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc index 6de18c391..b248c1904 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc @@ -4,6 +4,8 @@ // SPDX-License-Identifier: MIT #include "vmecpp/vmec/fourier_velocity/fourier_velocity.h" +#include + namespace vmecpp { FourierVelocity::FourierVelocity(const Sizes* s, const RadialPartitioning* r, @@ -22,4 +24,68 @@ FourierVelocity::FourierVelocity(const Sizes* s, const RadialPartitioning* r, vlcc(lcc), vlss(lss) {} +FourierVelocity::FourierVelocity(const FourierVelocity& other) + : FourierCoeffs(other), + vrcc(rcc), + vrss(rss), + vrsc(rsc), + vrcs(rcs), + vzsc(zsc), + vzcs(zcs), + vzcc(zcc), + vzss(zss), + vlsc(lsc), + vlcs(lcs), + vlcc(lcc), + vlss(lss) {} + +void FourierVelocity::BindSpans() { + vrcc = rcc; + vrss = rss; + vrsc = rsc; + vrcs = rcs; + vzsc = zsc; + vzcs = zcs; + vzcc = zcc; + vzss = zss; + vlsc = lsc; + vlcs = lcs; + vlcc = lcc; + vlss = lss; +} + +FourierVelocity& FourierVelocity::operator=(const FourierVelocity& other) { + if (this != &other) { + FourierCoeffs::operator=(other); + BindSpans(); + } + return *this; +} + +FourierVelocity::FourierVelocity(FourierVelocity&& other) noexcept + : FourierCoeffs(std::move(other)), + vrcc(rcc), + vrss(rss), + vrsc(rsc), + vrcs(rcs), + vzsc(zsc), + vzcs(zcs), + vzcc(zcc), + vzss(zss), + vlsc(lsc), + vlcs(lcs), + vlcc(lcc), + vlss(lss) { + other.BindSpans(); +} + +FourierVelocity& FourierVelocity::operator=(FourierVelocity&& other) noexcept { + if (this != &other) { + FourierCoeffs::operator=(std::move(other)); + BindSpans(); + other.BindSpans(); + } + return *this; +} + } // namespace vmecpp diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.h b/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.h index 1b922eb6a..4d6bcd60a 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.h +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.h @@ -14,6 +14,10 @@ namespace vmecpp { class FourierVelocity : public FourierCoeffs { public: FourierVelocity(const Sizes* s, const RadialPartitioning* r, int ns); + FourierVelocity(const FourierVelocity& other); + FourierVelocity& operator=(const FourierVelocity& other); + FourierVelocity(FourierVelocity&& other) noexcept; + FourierVelocity& operator=(FourierVelocity&& other) noexcept; // appropriately-named variables for the data in FourierCoeffs std::span vrcc; @@ -30,6 +34,9 @@ class FourierVelocity : public FourierCoeffs { std::span vlcs; std::span vlcc; std::span vlss; + + private: + void BindSpans(); }; } // namespace vmecpp From 07741285cdf86392e03287b0c5c13bb171d87c32 Mon Sep 17 00:00:00 2001 From: Jung Dae Suh Date: Thu, 5 Feb 2026 23:03:13 +0900 Subject: [PATCH 2/3] Fix CI: simsopt resize return + move warnings --- .../vmec/fourier_forces/fourier_forces.cc | 5 +---- .../vmec/fourier_geometry/fourier_geometry.cc | 5 +---- .../vmec/fourier_velocity/fourier_velocity.cc | 5 +---- src/vmecpp/simsopt_compat.py | 17 +++++++++++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc index 019d8f86a..50088aec1 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_forces/fourier_forces.cc @@ -77,15 +77,12 @@ FourierForces::FourierForces(FourierForces&& other) noexcept flsc(lsc), flcs(lcs), flcc(lcc), - flss(lss) { - other.BindSpans(); -} + flss(lss) {} FourierForces& FourierForces::operator=(FourierForces&& other) noexcept { if (this != &other) { FourierCoeffs::operator=(std::move(other)); BindSpans(); - other.BindSpans(); } return *this; } diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc index 632caefbc..a98a5eb1d 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_geometry/fourier_geometry.cc @@ -85,15 +85,12 @@ FourierGeometry::FourierGeometry(FourierGeometry&& other) noexcept lmnsc(lsc), lmncs(lcs), lmncc(lcc), - lmnss(lss) { - other.BindSpans(); -} + lmnss(lss) {} FourierGeometry& FourierGeometry::operator=(FourierGeometry&& other) noexcept { if (this != &other) { FourierCoeffs::operator=(std::move(other)); BindSpans(); - other.BindSpans(); } return *this; } diff --git a/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc b/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc index b248c1904..f3b2f4537 100644 --- a/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc +++ b/src/vmecpp/cpp/vmecpp/vmec/fourier_velocity/fourier_velocity.cc @@ -75,15 +75,12 @@ FourierVelocity::FourierVelocity(FourierVelocity&& other) noexcept vlsc(lsc), vlcs(lcs), vlcc(lcc), - vlss(lss) { - other.BindSpans(); -} + vlss(lss) {} FourierVelocity& FourierVelocity::operator=(FourierVelocity&& other) noexcept { if (this != &other) { FourierCoeffs::operator=(std::move(other)); BindSpans(); - other.BindSpans(); } return *this; } diff --git a/src/vmecpp/simsopt_compat.py b/src/vmecpp/simsopt_compat.py index 4c7cd4059..40b5b60c6 100644 --- a/src/vmecpp/simsopt_compat.py +++ b/src/vmecpp/simsopt_compat.py @@ -259,7 +259,8 @@ def run( verbose=self.verbose, restart_from=restart_from, ) - self.wout = self.output_quantities.wout + wout = self.output_quantities.wout + self.wout = wout except RuntimeError as e: msg = f"Error while running VMEC++: {e}" raise ObjectiveFailure(msg) from e @@ -267,7 +268,7 @@ def run( if self._should_save_outputs: assert self.input_file is not None wout_fname = _make_wout_filename(self.input_file) - self.wout.save(Path(wout_fname)) + wout.save(Path(wout_fname)) self.output_file = str(wout_fname) logger.debug("VMEC++ run complete. Now loading output.") @@ -452,7 +453,11 @@ def set_indata(self) -> None: vi = self.indata # Shorthand # Convert boundary to RZFourier if needed: boundary_RZFourier = self.boundary.to_RZFourier() - boundary_RZFourier.change_resolution(self.indata.mpol, self.indata.ntor) + resized_boundary = boundary_RZFourier.change_resolution( + self.indata.mpol, self.indata.ntor + ) + if resized_boundary is not None: + boundary_RZFourier = resized_boundary vi.rbc.fill(0.0) vi.zbs.fill(0.0) @@ -521,7 +526,11 @@ def set_mpol_ntor(self, new_mpol: int, new_ntor: int): # NOTE: SurfaceRZFourier uses m up to mpol _inclusive_, # differently from VMEC++, so have to manually reduce the range by one. mpol_for_surfacerzfourier = new_mpol - 1 - self.boundary.change_resolution(mpol_for_surfacerzfourier, new_ntor) + resized_boundary = self.boundary.change_resolution( + mpol_for_surfacerzfourier, new_ntor + ) + if resized_boundary is not None: + self.boundary = resized_boundary self.recompute_bell() From 7c5cb68c3f1c54ccba6f279781f3ad5b2836188d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20Jura=C5=A1i=C4=87?= <166746189+jurasic-pf@users.noreply.github.com> Date: Wed, 11 Feb 2026 11:55:30 +0100 Subject: [PATCH 3/3] Update simsopt_compat.py --- src/vmecpp/simsopt_compat.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vmecpp/simsopt_compat.py b/src/vmecpp/simsopt_compat.py index 40b5b60c6..42f4e2a9a 100644 --- a/src/vmecpp/simsopt_compat.py +++ b/src/vmecpp/simsopt_compat.py @@ -259,8 +259,7 @@ def run( verbose=self.verbose, restart_from=restart_from, ) - wout = self.output_quantities.wout - self.wout = wout + self.wout = self.output_quantities.wout except RuntimeError as e: msg = f"Error while running VMEC++: {e}" raise ObjectiveFailure(msg) from e @@ -268,7 +267,7 @@ def run( if self._should_save_outputs: assert self.input_file is not None wout_fname = _make_wout_filename(self.input_file) - wout.save(Path(wout_fname)) + self.wout.save(Path(wout_fname)) self.output_file = str(wout_fname) logger.debug("VMEC++ run complete. Now loading output.") @@ -453,11 +452,12 @@ def set_indata(self) -> None: vi = self.indata # Shorthand # Convert boundary to RZFourier if needed: boundary_RZFourier = self.boundary.to_RZFourier() - resized_boundary = boundary_RZFourier.change_resolution( + updated_boundary = boundary_RZFourier.change_resolution( self.indata.mpol, self.indata.ntor ) - if resized_boundary is not None: - boundary_RZFourier = resized_boundary + if updated_boundary is not None: + boundary_RZFourier = updated_boundary + self.boundary = updated_boundary vi.rbc.fill(0.0) vi.zbs.fill(0.0) @@ -526,11 +526,11 @@ def set_mpol_ntor(self, new_mpol: int, new_ntor: int): # NOTE: SurfaceRZFourier uses m up to mpol _inclusive_, # differently from VMEC++, so have to manually reduce the range by one. mpol_for_surfacerzfourier = new_mpol - 1 - resized_boundary = self.boundary.change_resolution( + updated_boundary = self.boundary.change_resolution( mpol_for_surfacerzfourier, new_ntor ) - if resized_boundary is not None: - self.boundary = resized_boundary + if updated_boundary is not None: + self.boundary = updated_boundary self.recompute_bell()