Repository navigation
VMEX 0.10.0 — the free-boundary and exterior-field release
VMEX 0.10.0 — the free-boundary and exterior-field release.
Free-boundary optimization did not work before this release, and the reason was not the optimizer. The forward solve returned a history-dependent root: five calls at identical coils gave objectives spanning 57 %, because each call restarted from the previous call's stopping point and the seed random-walked inside the ftol ball. L-BFGS-B was line-searching noise and stopped after one or four iterations. Solves now start from one converged reference per configuration, and a trial whose restart misses ftol is solved cold instead: five calls at one point are bit-identical, a warm trial costs 1.5-1.6 s against 21-26 s, and a deck that never converged at any budget now certifies in 1677 iterations (#383). max_fsq_ratio defaults to 1 rather than 1e6, which had been certifying roots 49x past their own tolerance — including a shipped test deck that had never converged and was being differentiated anyway.
With the optimizer iterating, a second defect surfaced: the boundary-Schur adjoint stranded a block system per gradient, 2218 live arrays and 0.29 GiB each time, because it built its jitted closures per gradient and JAX's trace cache kept every trial's arrays as jaxpr constants. Hoisting them, with the configuration as the only static key and every per-trial array an argument, takes the slope to 0.00 GiB per trial — and takes that lane from slower-warm-than-the-default to 1.86 s against 14.92 s at mpol 3 and 14.02 s against 65.87 s at the deck's own resolution (#397).
The free-boundary gradient itself went from 47.0x the cost of a value to 11.1x on the F0 deck, by building NESTOR's dense edge response once per gradient so the Krylov matvec contains no NESTOR call at all (#380), then solving the edge system directly (#395). Every result stays certified on the exact coupled transpose, with automatic continuation on it when the certificate misses, now counted. A predicted precision cliff at mpol 8 was tested and did not reproduce: zero fallbacks, lane JVP 1.41e-13. The remaining gap to the three-forward-solve target is bulk block assembly, measured and recorded rather than chased.
The interior field was wrong at second and third derivative and nothing could see it. Its geometry table multiplied every odd-m coefficient by a spurious sqrt(s) — exactly 1 at the boundary, which is why every exterior and virtual-casing path was always correct and no test caught it; in between it pulled each surface in towards the axis. The inversion is now seeded so it converges from its first guess, a point well outside the plasma returns NaN rather than raising, and a query exactly on the magnetic axis no longer returns NaN (#378).
Cold runs are 60-66 % XLA compilation, and 40-50 % of those seconds were single-op programs rather than solver lanes. Folding the pre-iteration setup into one compiled lane, and building the WOUT export's profiles from that same lane instead of a second untouched copy, takes li383's cold CLI from 187 compiled programs and 7.35 s to 49 and 6.07 s, and the QA ladder from 413 programs to 66 (#396, #400). Every physics scalar is bit-identical on both supported JAX versions, and the written WOUT is byte-identical field by field including dtypes and attribute order.
Optimization loops: a max_mode ladder compiled once per stage even though every array shape was identical, because the compile key held the starting point's bytes. Compiling once and masking the inactive dofs removes 276 s of recompilation from a QA run at per-stage costs identical to eleven significant figures (#390). The Jacobian batch width now defaults to "auto" — the batch dependence that kept it off was measured at 6e-11, not the 0.5-2 % on record — which takes QI's Jacobians from 18 s to 4-9 s (#392).
Exterior field: the level choice, its error estimate and the closed-form derivative kernels moved upstream into virtual-casing-jax 0.0.7, which is now the floor (#376, #405). B's achieved error has been reported since 0.9.0; its derivatives never were, and they lose accuracy fastest — at half a minor radius the same grid that gives B to 1e-06 gives its third derivative to 2e-02. They now carry per-order accuracy checks (#399). The LCFS source data can be projected onto surface gradients, off by default, which takes |curl B|/|grad B| from 2.3e-03 to 4.2e-09 at one minor radius (#381).
Examples: eight asymmetric examples had never run, because the coverage guard matched basenames and their symmetric namesakes masked them. Two of those shipped an inert smoke budget — a reduced grid overwritten three statements later — so every trial hit the iteration cap and the script exited 1 while reporting zero iterations and zero optimality. The guard is now a declared partition that refuses an undeclared script, a parser test forbids the overwritten-budget shape, and 46 examples across five families share one template with their parameters as named constants (#386, #394, #398, #402, #404, #391, #389). Two scipy examples declared transform and elongation limits and then hard-coded different values; one commented-out objective row referenced a constant defined nowhere; one example silently overwrote another's outputs.
Testing: the weekly workflow now runs every full lane that no workflow ran before, sharded to fit its caps (#373), and the two failures it immediately found are fixed — a field builder deleted as orphaned that a gate imported by name (#369), and a test double that went stale when the library gained a keyword (#370). The profiling harness could not distinguish "nothing compiled" from "the counter is broken", which made main intermittently red and would have corrupted committed benchmark records (#393).