Skip to content

Equilibrium - FEATURE - 🌱 Add the Cerfon-Freidberg diverted analytic equilibrium - #495

Draft
d-burg wants to merge 11 commits into
developfrom
feature/cerfon-freidberg-equilibrium
Draft

d-burg wants to merge 11 commits into
developfrom
feature/cerfon-freidberg-equilibrium

Conversation

@d-burg

@d-burg d-burg commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Release note

  • Audience: users
  • Numerical impact: none on existing cases: this adds a new eq_type. (harness @ e65792b)
  • Migration: none

Adds the Cerfon-Freidberg diverted analytic equilibrium, eq_type = "cerfon", a generalization of the Solovev model with a magnetic null on the plasma boundary (lower single null or double null). Because q diverges logarithmically as ψ_N → 1, no psihigh converges, which makes it a fixture for truncation sensitivity that the limited Solovev case cannot provide.

Regression report

regress --cases cerfon_lsn_n1,diiid_n1,solovev_n1 --refs cb18e386e,e65792bb1 --force on feynman (SLURM, 8 threads; current develop vs head after merging develop, both fresh, manifest pinned).

  • diiid_n1: 53 unchanged. solovev_n1: 22 unchanged.
  • cerfon_lsn_n1 (new in this PR): runs on the branch and extracts its 20 quantities. It has no develop baseline, because develop has no examples/Cerfon_diverted_example.
  • test/runtests_cerfon.jl: 365/365.
diiid_n1
Regression Report: diiid_n1
=================================================================================================
Ref 1: cb18e386e  @ cb18e386e (2026-10-02)
       env: julia 1.11.9, x86_64-linux-gnu, manifest f1e0eb38 (pinned), 8 threads/8 BLAS
Ref 2: e65792bb1  @ e65792bb1 (2026-10-03)
       env: julia 1.11.9, x86_64-linux-gnu, manifest f1e0eb38 (pinned), 8 threads/8 BLAS
-------------------------------------------------------------------------------------------------
Quantity                                      cb18e386e        e65792bb1        Diff       Status
-------------------------------------------------------------------------------------------------
total energy Re(et[1])                        8.012318e-01     8.012318e-01     0.0e+00    OK    
total energy Im(et[1])                        4.142529e-05     4.142529e-05     0.0e+00    OK    
plasma energy Re(ep[1])                       -1.348486e+00    -1.348486e+00    0.0e+00    OK    
vacuum energy Re(ev[1])                       2.149718e+00     2.149718e+00     0.0e+00    OK    
vacuum matrix min eigenvalue                  1.873976e-01     1.873976e-01     0.0e+00    OK    
plasma energy (all)                           [35 elem]        [35 elem]        0.0e+00    OK    
vacuum energy (all)                           [35 elem]        [35 elem]        0.0e+00    OK    
total energy (all)                            [35 elem]        [35 elem]        0.0e+00    OK    
ODE steps (saved)                             2576             2576             0.0e+00    OK    
ODE steps (total)                             4572             4572             0.0e+00    OK    
q0                                            1.204212e+00     1.204212e+00     0.0e+00    OK    
q95                                           4.781723e+00     4.781723e+00     0.0e+00    OK    
beta_t                                        1.327024e-02     1.327024e-02     0.0e+00    OK    
beta_n                                        1.372511e+00     1.372511e+00     0.0e+00    OK    
internal inductance li1                       8.842392e-01     8.842392e-01     0.0e+00    OK    
internal inductance li2                       7.080847e-01     7.080847e-01     0.0e+00    OK    
internal inductance li3                       7.304433e-01     7.304433e-01     0.0e+00    OK    
poloidal beta betap1                          6.680744e-01     6.680744e-01     0.0e+00    OK    
poloidal beta betap2                          5.349834e-01     5.349834e-01     0.0e+00    OK    
poloidal beta betap3                          5.518761e-01     5.518761e-01     0.0e+00    OK    
# singular surfaces                           5                5                0.0e+00    OK    
singular psi locations                        [5 elem]         [5 elem]         0.0e+00    OK    
singular q values                             [5 elem]         [5 elem]         0.0e+00    OK    
current beta betaj                            4.236478e-01     4.236478e-01     0.0e+00    OK    
plasma volume                                 1.829472e+01     1.829472e+01     0.0e+00    OK    
plasma current                                1.152130e+00     1.152130e+00     0.0e+00    OK    
mpert                                         35               35               0.0e+00    OK    
npert                                         1                1                0.0e+00    OK    
toroidal field bt0                            2.006573e+00     2.006573e+00     0.0e+00    OK    
wall field bwall                              3.880145e-01     3.880145e-01     0.0e+00    OK    
aspect ratio                                  2.845746e+00     2.845746e+00     0.0e+00    OK    
elongation kappa                              1.708350e+00     1.708350e+00     0.0e+00    OK    
q profile (checksum)                          0cd285cea88d...  0cd285cea88d...  identical  OK    
pressure profile (checksum)                   a1c48b266622...  a1c48b266622...  identical  OK    
Mercier D_I profile (checksum)                eeb06744e795...  eeb06744e795...  identical  OK    
resistive interchange D_R profile (checksum)  fa37296851f6...  fa37296851f6...  identical  OK    
ballooning Delta' profile (checksum)          6eb0ea075ccf...  6eb0ea075ccf...  identical  OK    
island half-widths                            [5 elem]         [5 elem]         0.0e+00    OK    
Chirikov parameter                            [5 elem]         [5 elem]         0.0e+00    OK    
||resonant area-weighted field||              5.189179e-04     5.189179e-04     0.0e+00    OK    
dominant-coupling singular values             [3 elem]         [3 elem]         0.0e+00    OK    
|forcing overlap with dominant mode|          1.415683e-04     1.415683e-04     0.0e+00    OK    
|delta_nominal| of coil set 1                 7.055228e-05     7.055228e-05     0.0e+00    OK    
||ddelta/d(shift)|| over coil sets            1.246233e-04     1.246233e-04     0.0e+00    OK    
||ddelta/d(tilt)|| over coil sets             4.180643e-07     4.180643e-07     0.0e+00    OK    
PE plasma energy                              3.422677e+00     3.422677e+00     0.0e+00    OK    
PE vacuum energy                              3.174510e+00     3.174510e+00     0.0e+00    OK    
PE surface energy                             5.826684e+00     5.826684e+00     0.0e+00    OK    
PE toroidal torque                            5.087465e-02     5.087465e-02     0.0e+00    OK    
NTV torque FGAR [N·m]                         5.296762e-01     5.296762e-01     0.0e+00    OK    
NTV kinetic energy dW FGAR [J]                7.924971e-02     7.924971e-02     0.0e+00    OK    
Runtime (s)                                   297.9s           293.8s                      --    
||forcing b~|| (root-area-weighted)           4.683328e-04     4.683328e-04     0.0e+00    OK    
resonant area-weighted field b^r              [5 elem]         [5 elem]         0.0e+00    OK    
=================================================================================================
Summary: 53 unchanged
solovev_n1
Regression Report: solovev_n1
=================================================================================
Ref 1: cb18e386e  @ cb18e386e (2026-10-02)
       env: julia 1.11.9, x86_64-linux-gnu, manifest f1e0eb38 (pinned), 8 threads/8 BLAS
Ref 2: e65792bb1  @ e65792bb1 (2026-10-03)
       env: julia 1.11.9, x86_64-linux-gnu, manifest f1e0eb38 (pinned), 8 threads/8 BLAS
---------------------------------------------------------------------------------
Quantity                      cb18e386e        e65792bb1        Diff       Status
---------------------------------------------------------------------------------
total energy Re(et[1])        6.773398e-01     6.773398e-01     0.0e+00    OK    
total energy Im(et[1])        -1.099802e-03    -1.099802e-03    0.0e+00    OK    
plasma energy Re(ep[1])       -9.736133e+00    -9.736133e+00    0.0e+00    OK    
vacuum energy Re(ev[1])       1.041347e+01     1.041347e+01     0.0e+00    OK    
vacuum matrix min eigenvalue  2.171581e+00     2.171581e+00     0.0e+00    OK    
plasma energy (all)           [32 elem]        [32 elem]        0.0e+00    OK    
vacuum energy (all)           [32 elem]        [32 elem]        0.0e+00    OK    
total energy (all)            [32 elem]        [32 elem]        0.0e+00    OK    
ODE steps (saved)             388              388              0.0e+00    OK    
ODE steps (total)             618              618              0.0e+00    OK    
q0                            1.900006e+00     1.900006e+00     0.0e+00    OK    
q95                           3.147422e+00     3.147422e+00     0.0e+00    OK    
beta_t                        4.620277e-02     4.620277e-02     0.0e+00    OK    
beta_n                        3.215290e+00     3.215290e+00     0.0e+00    OK    
# singular surfaces           2                2                0.0e+00    OK    
singular psi locations        [2 elem]         [2 elem]         0.0e+00    OK    
singular q values             [2 elem]         [2 elem]         0.0e+00    OK    
mpert                         32               32               0.0e+00    OK    
npert                         1                1                0.0e+00    OK    
q profile (checksum)          049799b65749...  049799b65749...  identical  OK    
pressure profile (checksum)   46c4ecb65021...  46c4ecb65021...  identical  OK    
ca_left (checksum)            320262e6cac8...  320262e6cac8...  identical  OK    
Runtime (s)                   213.6s           210.9s                      --    
=================================================================================
Summary: 22 unchanged

Notes for reviewers

  • Tests: a Grad-Shafranov residual test checks that the flux map and the tabulated profiles satisfy the same equation. That test caught a sign error in F(ψ), fixed on this branch.
  • Input key: the CERFON_INPUT key for Cerfon & Freidberg's A is A. An ill-conditioned boundary-condition solve is an error.
  • Commit subjects: the four oldest commits use the retired EQUIL area; they will be retagged at squash.
  • Develop is merged in (465216bfa). DirectRunInput had gained a plasma-current sign field since the branch was written; cerfon_run now passes +1, as the Solovev model does.

NO MERGE WITHOUT HUMAN REVIEW. This PR is a draft and needs a named human reviewer before it can be considered for merge.

🤖 Generated with Claude Code

d-burg and others added 8 commits August 14, 2026 23:21
…(eq_type = "cerfon")

Adds a diverted generalization of the Solovev analytic model: the Solov'ev source
Δ*ψ̂ = (1−A)x² + A is solved with a particular solution plus the twelve-term homogeneous
basis, and point-wise conditions on the ψ̂ = 0 surface place a magnetic null on the plasma
boundary. Because |∇ψ| vanishes at that null, q diverges logarithmically as ψ_N → 1, so no
choice of psihigh converges — the equilibrium exists to expose truncation sensitivity that
the limited Solovev fixture (finite q_edge) cannot.

The existing "sol" model is the A = 0, no-null member of the same family.

Topologies: "lsn" (lower single null) imposes ε, κ and δ exactly; "dn" (double null) has two
nulls on the boundary and its q climbs about twice as fast. Measured end to end through the
field-line integration at q0 = 1.1: q_edge rises 0.232 per decade of (1 − psihigh) for the
single null and 0.448 for the double null.

No snowflake topology is offered. An exact second-order null additionally forces the
Grad-Shafranov source to vanish at the null, pinning it to the surface where the toroidal
current density reverses sign; the closed flux surfaces then detach from it entirely. A
snowflake-minus does solve and shapes well, but its near-separatrix surfaces are not
star-shaped about the axis and the ray-Newton tracer in direct_fieldline_int fails on them.
Both findings are recorded in the cerfon_solve_coeffs docstring.

Includes examples/Cerfon_diverted_example, a regression-harness case, and tests covering
Δ*ψᵢ = 0 for the basis, every hand-coded derivative against finite differences, the boundary
conditions on the solved coefficients, and the q divergence law.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ion F(psi)

`cerfon_run` tabulated F² = F_edge² − 2APψ/R₀² where matching Δ*ψ = −μ₀R²p′ − FF′ against
the Solov'ev source gives F² = F_edge² − 2CAψ/R₀² with C = −P, i.e. **+**2APψ/R₀². The two
halves of the module disagreed: `cerfon_flux_scale` derives the flux scale from the correct
form, while the profile table used the opposite sign.

The flux map, plasma shape, null placement and q divergence were all unaffected — which is
why nothing caught it. What was wrong is that the tabulated F did not reproduce the source
of the flux map it was paired with, leaving a 19-46% Grad-Shafranov residual, and the sign
of the diamagnetic correction was inverted (the equilibrium read as paramagnetic for A < 0).

Fixing it also removes a 3% offset in the realized on-axis q that had been attributed to
spline error: q0 now lands at 1.1000 against a requested 1.1, where it previously came out
1.1337 (LSN) and 1.1309 (DN). q at the truncation surface is unchanged, since F → F_edge
there regardless of the sign, so the divergence rates of 0.232 and 0.448 per decade stand.

Adds the Grad-Shafranov residual test that catches this class of error directly — the flux
map and the profiles tabulated alongside it must satisfy the same equation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
B0 is a pure similarity scaling at fixed q0: psi and j_phi scale with B0, p scales with B0²,
and beta_t, beta_N and the whole q profile are invariant (verified across B0 = 1, 2, 4 T,
where I_p/(a·B0) = 2.0413 identically). So this moves the absolute scale of the example into
a more device-like range without touching any normalized result or the truncation behaviour
the deck exists to exercise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d operating point

The deck previously sat at q0 = 1.1, which is n=1 unstable both with and without a wall
(dW = -11.03 no wall, -2.32 with the conformal wall at a = 0.25). q0 = 1.5 is stable with
the wall and unstable without it (dW = -5.98 plasma, +9.89 vacuum, +3.92 total), so the
shipped fixture now exercises wall stabilization with real margin rather than sitting on the
unstable branch. A scan of the wall gap puts the ideal-wall limit at a ~ 0.46, so the deck's
a = 0.25 is at roughly half the critical gap.

The verdict is robust to truncation over psihigh = 0.99 to 0.9999, unlike the with-wall
stability threshold in q0 itself, which moves with psihigh because it tracks when the q = 3
surface crosses inside. That sensitivity is the fixture working as intended; picking an
operating point well away from the threshold keeps the regression signal clean.

Also corrects the header's "q at the edge" to "q at the truncation surface". On this
equilibrium q at psi_N = 1 is infinite by construction and never reached, so the old wording
invited exactly the wrong reading of every number the deck produces. The quoted divergence
rate is updated to the measured q0 = 1.5 values: +0.314 per decade for the single null and
+0.607 for the double null.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e cerfon_run's profile table

The previous residual test retyped the mu0*p' and FF' formulas inside the test and never
called cerfon_run, so it could not catch a sign error in the tabulated F^2. The test now
builds the equilibrium with cerfon_run, differentiates the F and mu0*p columns of its sq_in
table in psi_N, and checks Delta*psi (evaluated analytically from cerfon_grad and
cerfon_hessian) against -mu0 R^2 p' - FF' at four interior points, each asserted to lie
inside the plasma (0 < psi_N < 1). The relative bound is unchanged at 1e-5.

Also asserts that the realized on-axis q matches the requested q0 (rtol 1e-3, the bound
runtests_tj_analytic.jl uses) for both lsn and dn, replaces the tautological null-point
check with a sign check on the null's height, uses the config's A instead of a hardcoded
-0.155, and drops the unused Printf import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-search controls

- One cerfon_null_point returns (1 - xsep*delta*eps, +/-xsep*kappa*eps) with the sign set
  by the null type; cerfon_shape_points no longer carries a second copy whose ynull was
  positive even for the lower single null. The null x is now computed directly rather
  than through cos(pi/2 + asin(delta)), so it can differ in the last bit.
- Names the axis-search and conditioning literals (scan size, Newton cap and step
  tolerance, Hessian floor, condition-number warning) without changing their values; the
  Newton step cap is derived from the scan size instead of a separate /80.
- cerfon_solve_coeffs validates the null type itself and asserts all 12 rows are set.
- cerfon_psihat evaluates the basis once per call instead of once per term.
- Fills sq_in column 4 with sqrt(psi_N), as the DirectRunInput docstring specifies.
- Shortens the snowflake note to one sentence, corrects the F^2 comment in cerfon_run,
  and documents the box_margin limitation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se an ill-conditioned boundary-condition solve

The CERFON_INPUT key a_solovev is Cerfon & Freidberg's A (the FF' fraction of the Solov'ev source), not a minor radius; it is now A in CerfonConfig, the example deck and the tests. Clean break: the old key is rejected.

A boundary-condition matrix with cond > CERFON_BC_COND_MAX (1e12, unchanged) is now an error instead of a warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@d-burg d-burg self-assigned this Oct 2, 2026
@github-actions github-actions Bot added the feature New capability label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

This pull request is missing a reviewer.

If you are not ready to name them, mark this pull request as a draft.
docs/development/contributors.md suggests lead developers to ask.
Merging is not blocked here, but no pull request may be merged without human review.

d-burg and others added 3 commits October 3, 2026 14:31
… Cerfon direct input

DirectRunInput gained ip_sign after this branch was written, so cerfon_run's positional call was one argument short once merged with develop. Analytic equilibria use +1 for both signs, as the Solovev model does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@d-burg d-burg changed the title Equilibrium - FEATURE - Add the Cerfon-Freidberg diverted analytic equilibrium Equilibrium - FEATURE - 🌱 Add the Cerfon-Freidberg diverted analytic equilibrium Oct 5, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant