Off-Surface Evaluation¶
This section describes the off-surface evaluation paths for B and
GradB in virtual_casing_jax, including the adaptive refinement
logic and validation strategy. The implementation mirrors the reference
virtual-casing/BIEST flow and is designed to be both accurate and
efficient for small to moderate target sets.
Equations¶
Let \Gamma be the surface with outward normal n. Define the
surface densities:
For off-surface targets x, the jump term is absent. The external and
internal fields are
which matches the reference BIEST implementation [MCO2019] and
virtual-casing C++ API.
The off-surface gradient is
and \nabla \mathbf{B}_{\mathrm{int}} = - \nabla \mathbf{B}_{\mathrm{ext}}.
Algorithm¶
Off-surface evaluation is performed in two stages:
Base resampling: the surface and field are resampled to a quadrature grid that is at least the maximum of:
NFP * src_nt(toroidal modes from the source field)surface resolution from
setupa minimum patch size (
13x13) to match the BIEST off-surface singular-correction requirements.
Evaluation:
ComputeBOffSurfuses a combination ofLaplaceFxdUandBiotSavartFxU.ComputeGradBOffSurfusesLaplaceFxd2U(second derivatives) for both\mathbf{K}and\sigmaand assembles the curl term.
Adaptive Refinement¶
The off-surface field evaluator supports adaptive refinement, matching the strategy in BIEST:
Evaluate the Laplace double-layer
DxUon the target set using a constant density.Define an error metric:
\[\epsilon = \max_i \min(|1+U_i|, |U_i|),\]where
U_iis the double-layer potential at targeti. With the outward-normal SCTL/BIEST convention, the exact values are-1inside and0outside the surface.Refine the source grid by doubling
(Nt, Np)until\epsilon \le 10^{-digits}, amax_Nt/max_Npcap is reached, ormax_levels(six by default) is exhausted.
This logic is implemented in
virtual_casing_jax.integrals.computeB_offsurface_adaptive and
_offsurface_adapt_grid, mirroring
ExtVacuumField::EvalOffSurface in the C++ code.
The eager adaptive methods raise OffsurfaceConvergenceError instead of
silently returning an unconverged value. The exception reports the requested
tolerance, achieved error, final grid, and attempted level count. Fixed
schedule methods remain finite by construction and return the best result
from the supplied schedule.
JIT-Friendly Schedule¶
The Python adaptive loop changes grid sizes dynamically, which is not compatible with JIT compilation. For JIT-friendly workflows, a fixed refinement schedule can be provided:
computeB_offsurface_adaptive_schedule and
computeGradB_offsurface_adaptive_schedule accept a static tuple of
(Nt, Np) pairs (e.g. ((24, 24), (48, 48), (96, 96))). The
implementation evaluates each level and updates only targets whose
double-layer error is above the tolerance. Refinement is therefore independent
of target batching while every array shape remains static for JIT.
High-level wrappers are exposed as:
VirtualCasingJAX.compute_external_B_offsurf_scheduleVirtualCasingJAX.compute_internal_B_offsurf_scheduleVirtualCasingJAX.compute_external_gradB_offsurf_scheduleVirtualCasingJAX.compute_internal_gradB_offsurf_schedule
These wrappers reuse the same density construction as the base off-surface
path, and support JIT when levels is passed as a static argument.
Off-Surface GradB (Base vs Adaptive)¶
The reference C++ code evaluates off-surface GradB on the base grid (no adaptive refinement). The default JAX path follows this behavior to maintain parity with the C++ dumps.
An optional adaptive GradB path is available via
adaptive=True in compute_external_gradB_offsurf. This uses the
same adaptive grid selection as the off-surface field evaluator, and is
validated against a finite-difference gradient of
compute_external_B_offsurf.
Validation¶
Off-surface validation includes:
Parity tests against the C++ dumps for: -
ComputeBOffSurf(external/internal) -ComputeGradBOffSurf(external)A finite-difference test comparing adaptive GradB against the gradient of
compute_external_B_offsurf.
See the tests:
tests/test_virtual_casing_offsurf_parity.pytests/test_virtual_casing_gradb_offsurf_fd.py
Performance Notes¶
The direct quadrature path is memory-intensive; use
chunk_sizeto control memory.Adaptive refinement can upsample aggressively for small surfaces; use
max_Ntandmax_Npto cap grid sizes if needed.Off-surface GradB is more expensive than off-surface B because it evaluates second-derivative kernels.
Near-surface continuation¶
Periodic trapezoidal quadrature becomes prohibitively fine when a target is
very close to the source surface. For dense near-LCFS grids and field-line
tracing, VirtualCasingExteriorField
can instead prepare the accurate singular on-surface field and gradient once:
plan = field.plan_near_surface(digits=4)
B = field.B_plasma_near_surface_xyz(points, plan)
The plan reparameterizes each star-shaped poloidal section by its geometric angle and evaluates the first-order continuation
The on-surface values use the same POU/polar and one-sided Hedgehog quadratures as the parity-tested boundary API. Target evaluation is then a batched periodic interpolation with constant memory per point. Use direct off-surface quadrature farther from the LCFS or whenever a case-specific convergence check shows that the neglected quadratic term is material.
Implementation Mapping¶
JAX entry points and their reference equivalents:
VirtualCasingJAX.compute_external_B_offsurf→VirtualCasing::ComputeBextOffSurf(C++)VirtualCasingJAX.compute_internal_B_offsurf→VirtualCasing::ComputeBintOffSurf(C++)VirtualCasingJAX.compute_external_gradB_offsurf→VirtualCasing::ComputeGradBOffSurf(C++ local parity extension)
All kernel normalizations and sign conventions follow BIEST [BIEST] and the formulas in [MCO2019].