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.