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:

\[\sigma = \mathbf{B}\cdot\mathbf{n}, \qquad \mathbf{K} = \mathbf{B}\times\mathbf{n}.\]

For off-surface targets x, the jump term is absent. The external and internal fields are

\[\mathbf{B}_{\mathrm{ext}}(x) = \nabla G[\sigma](x) + \mathrm{BiotSavart}[\mathbf{K}](x),\]
\[\mathbf{B}_{\mathrm{int}}(x) = -\nabla G[\sigma](x) - \mathrm{BiotSavart}[\mathbf{K}](x),\]

which matches the reference BIEST implementation [MCO2019] and virtual-casing C++ API.

The off-surface gradient is

\[(\nabla \mathbf{B}_{\mathrm{ext}})_{k i} = \varepsilon_{k \ell m}\,\partial_i\partial_\ell G[K_m] + \partial_i\partial_k G[\sigma],\]

and \nabla \mathbf{B}_{\mathrm{int}} = - \nabla \mathbf{B}_{\mathrm{ext}}.

Algorithm

Off-surface evaluation is performed in two stages:

  1. 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 setup

    • a minimum patch size (13x13) to match the BIEST off-surface singular-correction requirements.

  2. Evaluation:

    • ComputeBOffSurf uses a combination of LaplaceFxdU and BiotSavartFxU.

    • ComputeGradBOffSurf uses LaplaceFxd2U (second derivatives) for both \mathbf{K} and \sigma and assembles the curl term.

Adaptive Refinement

The off-surface field evaluator supports adaptive refinement, matching the strategy in BIEST:

  • Evaluate the Laplace double-layer DxU on the target set using a constant density.

  • Define an error metric:

    \[\epsilon = \max_i \min(|1+U_i|, |U_i|),\]

    where U_i is the double-layer potential at target i. With the outward-normal SCTL/BIEST convention, the exact values are -1 inside and 0 outside the surface.

  • Refine the source grid by doubling (Nt, Np) until \epsilon \le 10^{-digits}, a max_Nt/max_Np cap is reached, or max_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_schedule

  • VirtualCasingJAX.compute_internal_B_offsurf_schedule

  • VirtualCasingJAX.compute_external_gradB_offsurf_schedule

  • VirtualCasingJAX.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.py

  • tests/test_virtual_casing_gradb_offsurf_fd.py

Performance Notes

  • The direct quadrature path is memory-intensive; use chunk_size to control memory.

  • Adaptive refinement can upsample aggressively for small surfaces; use max_Nt and max_Np to 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

\[\mathbf B(\mathbf x_\Gamma+\delta\mathbf x) = \mathbf B_\Gamma + \nabla\mathbf B_\Gamma\,\delta\mathbf x + O(|\delta\mathbf x|^2).\]

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].