Skip to content

Vacuum - FEATURE! - High-order singular quadrature for the 3D vacuum kernel - #673

Draft
jhalpern30 wants to merge 6 commits into
developfrom
feature/vacuum-3d-high-order
Draft

jhalpern30 wants to merge 6 commits into
developfrom
feature/vacuum-3d-high-order

Conversation

@jhalpern30

@jhalpern30 jhalpern30 commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Release note

  • Audience: users
  • Numerical impact: 3D-path vacuum response wv moves; 2D path unchanged (harness @ e0d1ee9)
  • Migration: rerun any 3D vacuum response (wv) used for results and re-check its grid convergence, since 3D values move; 3D grids now need mtheta ≥ 9 and at least 7 toroidal points over the full torus (smaller grids raise an error), and the new grid-aspect and wall-resolution warnings flag grids that cannot reach full accuracy

The 3D vacuum response now converges spectrally with mtheta and nzeta. On W7-X it self-converges to ~1e-11, where it used to stall at 1e-5 to 1e-4. A 3D run of an axisymmetric case now reaches the 2D code's answer to its floor (4e-7 without a wall, 6e-8 with one). New warnings flag grids that cannot reach that accuracy.

Summary

Review package with figures: https://claude.ai/artifact/4HaFFsohHYdSqRvuKTKyK6

The 3D kernel's singular correction had a floor that refining the grid did not remove. Three changes remove it, and all three are needed (the ablation figure in the package removes them one at a time):

  • Spectral tangents. PlasmaGeometry3D and WallGeometry3D take ∂r/∂θ and ∂r/∂ζ from the trigonometric interpolant (trig_tangents) instead of periodic bicubic splines, which were O(h⁴) and capped the whole scheme.
  • Grid-scaled patch. PATCH_RAD = mtheta ÷ 3, RAD_DIM = max(12, mtheta/2) (ANG_DIM = 2·RAD_DIM), INTERP_ORDER = 11. The patch is now a fixed physical region, so the smooth-remainder error decays spectrally. Malhotra's N^¼ growth is a cost choice for FMM codes; the dense build here has no FMM to protect.
  • 2× trigonometric upsampling of the patch. r and the tangents are upsampled once per surface (trig_upsample_matrix, trig_upsample). The 11-point Lagrange stencil runs on the fine patch, and the polar weights are folded back to the collocation nodes by the transposed upsampling, with field periods folded into Uζ (scatter_polar_to_row!). The far field stays the trapezoidal rule on the collocation grid.

Guards (new warnings and assert)

  • PlasmaGeometry3D: warns when the RMS spacing ratio hζ/hθ > 1.75 on a 3D boundary and suggests an nzeta. This replaces the old warning at > 10.
  • WallGeometry3D (conformal wall): warns when the wall and plasma normals differ by more than 2°. This is about 1e-4 in Wᵛ: a sharp convex plasma edge fans out over the gap faster than the grid can follow.
  • compute_3D_kernel_matrices!: asserts that observer and source share one grid.

Performance

  • Geometry per polar node is carried as one SVector{9}, so P2G is swept once. Per-thread scratch now lives in a parametric KernelWorkspace{T}.
  • BLAS is pinned to one thread around the kernel calls and restored in finally, because the per-row products run inside the threaded observer loop.
  • W7-X 82×108, m ≤ 16: 6.4 s on 4 threads. About 2× stock per grid point, but fewer points are needed for any given tolerance.

Tests

  • New Kernel3D manufactured exterior solution converges at high order. Point charges inside a rotating ellipse give a harmonic exterior field, and solving K χ = G b on the kernel's own normals measures its error. The test asserts e16 < 1e-3, e32 < 5e-6 and a rate faster than h⁶.
  • extract_patch! test rewritten for the element-generic signature, including the periodic wrap.

Regression report

Regression Report: solovev_n1
===================================================================================================================================
Ref 1: a1149c5c206706e5fcb1f2eb8f3f34f56e8a9fc2  @ a1149c5c2 (2026-11-07)
       env: julia 1.12.7, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 4 threads/4 BLAS, jit build, warm
Ref 2: e0d1ee998636841c651be3e8dd0ce13e7d2b4bf4  @ e0d1ee998 (2026-11-13)
       env: julia 1.12.7, x86_64-linux-gnu, manifest 4c9d54fa (pinned), 4 threads/4 BLAS, jit build, warm
-----------------------------------------------------------------------------------------------------------------------------------
Quantity                      a1149c5c206706e5fcb1f2eb8f3f34f56e8a9fc2  e0d1ee998636841c651be3e8dd0ce13e7d2b4bf4  Diff       Status
-----------------------------------------------------------------------------------------------------------------------------------
total energy Re(et[1])        7.362765e-03                              7.362765e-03                              0.0e+00    OK    
total energy Im(et[1])        2.139585e-10                              2.139585e-10                              0.0e+00    OK    
plasma energy Re(ep[1])       -1.042157e+01                             -1.042157e+01                             0.0e+00    OK    
vacuum energy Re(ev[1])       1.042894e+01                              1.042894e+01                              0.0e+00    OK    
vacuum matrix min eigenvalue  2.170703e+00                              2.170703e+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)             383                                       383                                       0.0e+00    OK    
ODE steps (total)             381                                       381                                       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.621027e-02                              4.621027e-02                              0.0e+00    OK    
beta_n                        3.217775e+00                              3.217775e+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)          2340b888d410...                           2340b888d410...                           identical  OK    
pressure profile (checksum)   108b947087e6...                           108b947087e6...                           identical  OK    
ca_left (checksum)            dec3aca30808...                           dec3aca30808...                           identical  OK    
Runtime (s)                   209.3s                                    202.4s                                               --    
===================================================================================================================================
Summary: 22 unchanged

Baseline is the branch point a1149c5. The harness has no 3D vacuum case, so this checks only that 2D results are unchanged. Vacuum unit tests: runtests_vacuum.jl 431/431 at 3e4ba53.

Notes for reviewers

  • The non-obvious part is the phase wrap in scatter_polar_to_row!. The folded patch is built once about node 1 and shifted to each observer. Columns pushed into the next field period pick up one more phase step, which assumes geometric phases (phases[d] = wrap_phase^(d-1), as compute_vacuum_response builds them).
  • The trig matrices use the symmetric offset range so that sin(πr/n) never approaches π. Both agree with a BigFloat reference to machine precision for odd and even n.
  • Known limits, not addressed here: (1) plasma–wall gaps below about one cell need a graded radial rule on the cross rows, which comes in a separate stacked PR; (2) a conformal wall around a sharp stellarator edge (ARIES-CS) needs Nθ ≈ 230, which the new 2° warning reports.
  • Paper Fig 2 and Fig 4 were produced with this branch; the review package shows both.

…l surface tangents for the 3D kernel

The 3D singular correction keeps its algorithm and code; only its parameters change. The patch half-width is
mtheta/3 grid points (was 11), the radial Gauss order max(12, mtheta/2) (was 20, angular twice that) and the Lagrange
order 11 (was 5). A fixed patch shrinks in physical size as the grid is refined and floors the error; growing it with
the grid keeps the high-order convergence (W7-X |m|,|n| <= 8: 8e-4 at 26x34 to 6e-9 at 100x132 per period).

PlasmaGeometry3D and WallGeometry3D take their tangents and normals from the trigonometric interpolant of the grid
samples (spectral differentiation matrix) in place of bicubic splines. Spline tangents converge as h^4 and cap the
kernel there (W7-X 1.9e-6 at 100x132).
Point charges inside a rotating ellipse give a harmonic exterior field; solving K chi = G b with the kernel's own normals measures its error (3.9e-4 at mtheta 16, 2.8e-6 at 32).
…rrection patch in the 3D kernel

- Interpolate the source geometry onto the polar nodes from a 2× trig-upsampled patch
  (`trig_upsample_matrix`, `trig_upsample`), and fold the scattered weights back to the
  collocation nodes through the restricted upsampling matrices, with field periods folded into Uζ
- Carry (r, ∂r/∂θ, ∂r/∂ζ) as SVector{9} per node; normals are formed from the interpolated
  tangents, replacing `compute_polar_normal!`
- Thread-local `KernelWorkspace` holds all per-observer scratch
- Bind the clamped patch radius to a new name so it is not boxed in the threaded closure
- Map trig-matrix offsets to the symmetric range so sin(πr/n) never rounds near π;
  both matrices now agree with a BigFloat reference to machine precision, odd and even n
- Pin BLAS to one thread around the kernel calls (restored in `finally`): its small
  products run inside the threaded observer loop
- `extract_patch!` is element-generic; test covers periodic wrap

W7-X m16: errors unchanged by the SVector, BLAS and trig changes (2.427e-6 at 56×74,
1.495e-8 at 82×108); 82×108 runs in 6.4 s vs 8.9 s before this change (-t 4).
The 24 x 24 periodic test grid has toroidal spacing 5.7x the poloidal one, so PlasmaGeometry3D
now warns about it inside @test_logs. The check is about the wall's sub-cell and near-fold
warnings only. Vacuum unit tests 431/431 (Feynman).
@jhalpern30 jhalpern30 self-assigned this Nov 13, 2026
@github-actions github-actions Bot added changed-results Results move or an interface breaks - read before upgrading feature New capability labels Nov 13, 2026
@github-actions

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.

@jhalpern30

Copy link
Copy Markdown
Collaborator Author

@logan-nc going to wait on merging this in until I have finalized the reviewer responses, but this is the code update motivated by the reviewer comments + APS discussions I was talking about. The TL/DR is the before and after for the paper convergence figures - we actually achieve spectral convergence now instead of ~h^4 and error absolutely TANKS. Can get down to almost machine precision on some cases on a laptop.

Basically required me going error-hunting, since even though I had a theoretically spectrally convergent scheme there were specific implementation details (both in the singular quadrature and also relying on bicubic derivatives) limiting my convergence.

The funny thing is for Fig. 2 below, the 3D code actually beats the 2D runtime for some low error rates because it converges so much faster

image image

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

changed-results Results move or an interface breaks - read before upgrading feature New capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant