iteration: fix post-reguess lambda scaling divergence, add Python multigrid driver#560
Open
CharlesCNorton wants to merge 2 commits into
Open
iteration: fix post-reguess lambda scaling divergence, add Python multigrid driver#560CharlesCNorton wants to merge 2 commits into
CharlesCNorton wants to merge 2 commits into
Conversation
… Python multigrid driver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #555.
Root cause of the Python/C++ divergence
VmecConstants::rmsPhiPis accumulated byRadialProfiles::evalRadialProfiles, andVmec::runcallsconstants_.reset()before everyInitializeRadial(vmec.cc:302). TheVmecModelbindingsReinitialize()(the axis-reguess path) andRefineTo()(the multigrid step) re-ranInitializeRadialwithout that reset, so each re-initialization doubledrmsPhiPand rescaledlamscaleby sqrt(2).That rescales the entire lambda sector. The preconditioned residual
fsql1carries the factor directly (it has no normalization that cancels alamscalechange), while the invariantfsqlstays unchanged, so the damping average sees a differentfsq1and the post-reguess trajectory splits from the C++ inner solve from the second step onward. The converged physics is unaffected (lamscaleis an internal scaling), which is why this never showed in convergence tests, only in step-for-step traces. Onli383_low_resat ns=31 the signature is clean: bit-identicalfsqr/fsqr1/fsqz1withfsql1off by exactly 2.0 at the first post-reguess evaluation.A secondary, much smaller divergence channel was the damping average itself: numpy's pairwise summation associates differently from Eigen's sequential
VectorXd::sum(), and the last-bit difference grows through long chaotic transients. The Python loop now sums the ring buffer left to right, matching Eigen bit for bit.Changes
pybind_vmec.cc:constants_.reset()beforeInitializeRadialinReinitialize()andRefineTo(), matchingVmec::run._iteration.py: sequential left-to-right damping-average sum._iteration.py/__init__.py:solve_multigrid()— drives the full coarse-to-finens_arrayramp from Python, mirroring theVmec::runmulti-grid loop (skip-coarserns_minrule, per-stage ftol/niter,refine_tointerpolation between stages, C++ failure semantics). This covers the "multigrid logic isn't implemented yet" part of the issue.tests/test_iteration.py:reinitialize(),solve_multigridon cma's own two-stage input (ns_array[25, 51]) against the fullvmecpp.run, comparing the concatenated force-residual and restart-reason traces step for step,li383_low_resadded to the restart-path matrix (cold-start reguess with a clean descent — a sharp probe of the post-reguess state),rtol=0.5tortol=1e-3, with the first 50 iterations atrtol=1e-9.Validation
Full-length (to convergence) Python-vs-C++ parity sweep, comparing iteration counts, restart-reason traces, and residual traces:
Restart-reason traces are identical in every case; residual traces are tight for the whole run (the only remaining non-bit-exact effect is last-bit noise through the longest chaotic transients, staying below 1e-3 relative).
tests/test_iteration.py: 12 passed. Full Python suite: 144 passed, 4 skipped (thecth_like_free_bdywout-reference failure pre-exists atmainin this environment and is unrelated).