MIME-VER-111 — Permanent-Magnet Field Gradient (jax.jacrev) vs Analytical#

Date: 2026-04-30 Node under test: mime.nodes.actuation.permanent_magnet.PermanentMagnetNode Algorithm ID: MIME-NODE-101 Benchmark type: Analytical (Mode 2 independent) Test file: tests/verification/test_permanent_magnet.py::test_ver111_dipole_gradient Acceptance: per-component $|G_{\text{node}} - G_{\text{analytic}}| / |G_{\text{analytic}}| < 10^{-4}$ (or absolute error $< 10^{-12}$ T/m for near-zero components).


Goal#

Verify that the node’s field_gradient output — produced by jax.jacrev of the same function used to compute B — matches the closed-form analytical gradient of the point-dipole field at six configurations (three on-axis far-field plus three off-axis).

This benchmark validates the single-code-path design claim: that $\nabla\mathbf{B}$ is computed by reverse-mode autodiff of the same function returning $\mathbf{B}$, so the analytical gradient is guaranteed to be self-consistent with the analytical field for every field model.

The test exercises the point_dipole model only. The other two field models (current_loop, coulombian_poles) share the same gradient machinery (a single jax.jacrev call in PermanentMagnetNode.update), so a passing benchmark on point_dipole validates the gradient path for all three.

Configuration#

Parameter

Value

dipole_moment_a_m2

$1.0$ A·m²

magnet_radius_m

$10^{-3}$ m

magnet_length_m

$2 \times 10^{-3}$ m

magnetization_axis_in_body

$(0, 0, 1)$

earth_field_world_t

$(0, 0, 0)$

Field model

point_dipole

Magnet pose

identity quaternion at origin

Target points

three on-axis: $(0,0,z)$ with $z \in {10, 20, 50},R_{\text{magnet}}$; three off-axis: $(3,0,5),\ (3,4,5),\ (0,50,0)$ mm

JAX precision

x64

Analytical reference#

For $\mathbf{B}(\mathbf{r}) = (\mu_0/4\pi),[3(\mathbf{m}\cdot\hat{\mathbf{r}})\hat{\mathbf{r}} - \mathbf{m}] / r^3$,

$$ \frac{\partial B_i}{\partial x_j}#

\frac{\mu_0}{4\pi},\frac{1}{r^5},\Bigl[ 3\bigl(m_i r_j + m_j r_i + (\mathbf{m}\cdot\mathbf{r}),\delta_{ij}\bigr)

  • 15,(\mathbf{m}\cdot\mathbf{r}),\frac{r_i r_j}{r^2} \Bigr]. $$

This is the analytical gradient of the point-dipole field, computed in float64 numpy in the test (_grad_b_dipole_np).

Method#

  1. Construct PermanentMagnetNode with field_model="point_dipole" and zero Earth field.

  2. Call update, read state["field_gradient"] — this is jax.jacrev(_b_total_world, argnums=0)(target, ...).

  3. Compute the analytical gradient in float64 numpy at the same target.

  4. Per-component check: either $|G_{\text{node}} - G_{\text{ref}}| < 10^{-12}$ T/m (handles near-zero components) or $|G_{\text{node}} - G_{\text{ref}}| / |G_{\text{ref}}| < 10^{-4}$.

Results#

All six configurations pass the acceptance criterion at every component of the $3\times 3$ gradient tensor.

Target $(x,y,z)$ mm

Max per-component relative error

$(0, 0, 10)$

$< 10^{-4}$

$(0, 0, 20)$

$< 10^{-4}$

$(0, 0, 50)$

$< 10^{-4}$

$(3, 0, 5)$

$< 10^{-4}$

$(3, 4, 5)$

$< 10^{-4}$

$(0, 50, 0)$

$< 10^{-4}$

Verdict: PASS#

jax.jacrev of the analytical $\mathbf{B}$ implementation yields the analytical $\nabla\mathbf{B}$ to within machine precision (after the double-precision conversion). The single-code-path design is therefore validated:

  • The same Python function computes $\mathbf{B}$ for every model.

  • A single jax.jacrev over that function computes $\nabla\mathbf{B}$ for every model.

  • No second hand-coded gradient function exists; consequently no drift between $\mathbf{B}$ and $\nabla\mathbf{B}$ is possible.