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 |
|---|---|
|
$1.0$ A·m² |
|
$10^{-3}$ m |
|
$2 \times 10^{-3}$ m |
|
$(0, 0, 1)$ |
|
$(0, 0, 0)$ |
Field model |
|
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#
Construct
PermanentMagnetNodewithfield_model="point_dipole"and zero Earth field.Call
update, readstate["field_gradient"]— this isjax.jacrev(_b_total_world, argnums=0)(target, ...).Compute the analytical gradient in float64 numpy at the same target.
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.jacrevover 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.