V09 — P1 vs P2 convergence study
Tier S - Self-consistent
Tier S (self-consistent) - priority P0
Reference frozen on 2026-08-14. Nabla 0.1.0,
solver licence mode enforcing,
run in 104.3 s.
1. Problem
Three problems whose exact solutions are already references of this dossier, each meshed at five densities and solved at both element orders, plus a fourth that exists only to close a question V01 left open. Thirty-six sub-models, and one question: does Nabla's discretization converge at the rate the theory requires, and does 2nd order earn what it costs?
| Sub-model family | Physics | Exact solution first used by | Levels x orders |
|---|---|---|---|
coax_p*_L* |
planar magnetostatic, uniform Jz in a Dirichlet box |
V01 (Ampère's law) | 5 x 2 |
magnet_p*_L* |
planar magnetostatic, permanent-magnet source | V13 (magnetized cylinder) | 5 x 2 |
annulus_p*_L* |
axisymmetric heat conduction, both walls held | V08 (logarithmic wall) | 5 x 2 |
axis_p*_L* |
axisymmetric magnetostatic solenoid | V01's open finding | 3 x 2 |
The three are not interchangeable, and each is here because it can fail on its own:
coax- a conductor of radiusa = 4 mmcarryingJz = 5 MA/m^2inside anA = 0circle atb = 12 mm. ItsBis continuous across the conductor surface, so the inscribed polygon Nabla draws for that circle costs nothing: the error a straight-sided element makes on a curved interface is proportional to the jump across it, and here there is none. The norm is taken over the whole model.magnet- a cylinder of radiusa = 10 mm,Br = 1.2 T,mu_r = 1, magnetized along+x, inside anA = 0circle at3a. Its surface does carry a jump inB(a bound surface current), so the norm is taken over the exterior air ringr > 1.3a, where the same global solution is smooth. §6 says what happens if that cut is not made, and it is the practical cost of Nabla having no curved (isoparametric) element.annulus- the control. V08's hollow-cylinder wall,r1 = 10 mm,r2 = 40 mm, held at 100 and 20 degC. It is a rectangle in (r, z), so there is no polygon and no geometric error anywhere in it, and its solution is logarithmic, which is genuinely outside both element spaces. That second property matters more than it sounds: V08 found P2 exact on its three parabolic problems, and an exact answer has no slope to fit.
Each family's five levels scale the region area constraint and every drawn
circle's segment length together, so the mesh family is geometrically similar.
Measured across the five levels, h spans 3.3x and the element count
10-16x - Triangle's quality constraint gives a coarse mesh more elements
than the area constraint asks for, so the achieved h is measured from the mesh
rather than assumed from the request (see §6).

2. Reference
FEM theory, and nothing else. This is the register's tier-S case: it has no
external reference by design, and it does not need one, because a convergence
order is not a number a code can be tuned to. For a piecewise-smooth solution
on a conforming mesh of degree-p Lagrange triangles,
|| u - u_h ||_L2 = O(h^(p+1)) P1 -> 2 P2 -> 3
|| grad u - grad u_h ||_L2 = O(h^p) P1 -> 1 P2 -> 2
(Ciarlet Thm 3.2.1 for the second; Brenner & Scott Thm 4.4.20 for the extra
order the first gets from duality). The tolerance is +/-0.15 on the fitted
slope, which is the validation plan §3.4's own band, transcribed rather than
chosen. The exact solutions are in harness/analytic.py as
coax_A, magnetized_cylinder_A and annulus_T_field, each returning
(u, du/dx, du/dy); for the magnetic ones B = (dA/dy, -dA/dx), so
||grad A - grad A_h|| is ||B - B_h|| and the two theory orders are being
fitted on one field and its own derivative rather than on two separately
post-processed quantities.
3. Nabla model and the instrument
The models are ordinary: Planar 2D / Axisymmetric 2D, SimplicialLDLT,
static, linear, no motion, no circuit. solverParameters[2] carries the element
order and the mesh is regenerated for every one of the thirty-six, because the
order is baked into the Triangle command line.
What is not ordinary is how the error is measured, and it is the whole reason this case can say anything.
The judged quantity is a finite-element norm, not a reading.
harness/fem_norms.py reads the mesh Triangle wrote (.1.node, .1.ele; six
nodes per element at -o2) and the nodal degrees of freedom the solution
carries, rebuilds the finite-element function with the shape functions
solver/poly_2nd_order.hpp documents - including the opposite-vertex midside
convention - and integrates both norms with a degree-5, 7-point quadrature on
every element. Nothing is smoothed, averaged or interpolated onto anything.
Two rows exist to prove the ruler before it is used, and both are judged:
| Check | Result | Why it has to pass |
|---|---|---|
reconstruction_vs_probe_rel |
1.73665e-16 | At P1 Nabla's own getFieldInPoint("A") interpolates A linearly over the containing element, which IS the finite-element function. If the reconstruction were a different function the two could not agree to round-off. |
node_coordinate_mismatch_mm |
0 | The mesh file's node list and the solver's own solution records have to be the same list in the same order, or every degree of freedom would be attached to the wrong node and every norm here would be meaningless while still looking like a number. |
harness/fem_norms.py is also tested on its own, on structured meshes and
nodal interpolants of a smooth function, with no Nabla and no JVM anywhere
(validation/tests/test_fem_norms.py, 31 tests): a slope that came out right
there and wrong here is the solver, and one that came out wrong there is the
ruler.
Both circles are corrected before they are drawn. An inscribed regular
polygon is smaller than its circle by O(1/N^2) = O(h^2) - the same order as
P1's own error in B - so left alone it would flatten P2's slope to 2 while a
reader watched the mesh being refined. At the magnet's coarsest level that
deficit is +0.19 % of the
magnet's area, and it goes straight into the dipole moment. So a circle carrying
a source is drawn with the circumradius whose polygon has the circle's area
(the enclosed current and the dipole moment are then exact), and a circle
carrying a Dirichlet boundary with the circumradius whose polygon has the
circle's logarithmic capacity (the radius the exterior solution actually
sees). What is left is an N-th order multipole, which decays as (r/R)^N and
is below 1e-6 at every station these norms integrate. Both corrections are
derived and unit-tested against their own asymptotics.
4. Results
| Quantity | Metric | Nabla | Reference | Error | Tolerance | Verdict |
|---|---|---|---|---|---|---|
slope_u_coax_p1 |
slope | 2.03369 - | 2 - | 0.0337 | +/-0.15 | PASS |
slope_grad_coax_p1 |
slope | 0.993814 - | 1 - | -0.00619 | +/-0.15 | PASS |
slope_u_coax_p2 |
slope | 3.03682 - | 3 - | 0.0368 | +/-0.15 | PASS |
slope_grad_coax_p2 |
slope | 1.93592 - | 2 - | -0.0641 | +/-0.15 | PASS |
slope_u_magnet_p1 |
slope | 2.03393 - | 2 - | 0.0339 | +/-0.15 | PASS |
slope_grad_magnet_p1 |
slope | 0.969675 - | 1 - | -0.0303 | +/-0.15 | PASS |
slope_u_magnet_p2 |
slope | 2.98344 - | 3 - | -0.0166 | +/-0.15 | PASS |
slope_grad_magnet_p2 |
slope | 1.91795 - | 2 - | -0.082 | +/-0.15 | PASS |
slope_u_annulus_p1 |
slope | 2.01645 - | 2 - | 0.0164 | +/-0.15 | PASS |
slope_grad_annulus_p1 |
slope | 1.00755 - | 1 - | 0.00755 | +/-0.15 | PASS |
slope_u_annulus_p2 |
slope | 3.02302 - | 3 - | 0.023 | +/-0.15 | PASS |
slope_grad_annulus_p2 |
slope | 2.01289 - | 2 - | 0.0129 | +/-0.15 | PASS |
reconstruction_vs_probe_rel |
abs | 1.73665e-16 - | 0 - | 1.737e-16 | 1e-09 | PASS |
node_coordinate_mismatch_mm |
abs | 0 mm | 0 mm | 0 | 1e-06 | PASS |
axis_off_axis_Bz_err_p1 |
report | (series) | - | - | - | REPORT |
axis_off_axis_Bz_err_p2 |
report | (series) | - | - | - | REPORT |
axis_on_axis_Bz_err_p1 |
report | (series) | - | - | - | REPORT |
axis_on_axis_Bz_err_p2 |
report | (series) | - | - | - | REPORT |
axis_reference_Bz_T |
report | 0.0202676 | - | - | - | REPORT |
axis_reference_Bz_offaxis_T |
report | 0.0202829 | - | - | - | REPORT |
err_grad_annulus_p1 |
report | (series) | - | - | - | REPORT |
err_grad_annulus_p2 |
report | (series) | - | - | - | REPORT |
err_grad_coax_p1 |
report | (series) | - | - | - | REPORT |
err_grad_coax_p2 |
report | (series) | - | - | - | REPORT |
err_grad_magnet_p1 |
report | (series) | - | - | - | REPORT |
err_grad_magnet_p2 |
report | (series) | - | - | - | REPORT |
err_u_annulus_p1 |
report | (series) | - | - | - | REPORT |
err_u_annulus_p2 |
report | (series) | - | - | - | REPORT |
err_u_coax_p1 |
report | (series) | - | - | - | REPORT |
err_u_coax_p2 |
report | (series) | - | - | - | REPORT |
err_u_magnet_p1 |
report | (series) | - | - | - | REPORT |
err_u_magnet_p2 |
report | (series) | - | - | - | REPORT |
nodes_annulus_p1 |
report | (series) | - | - | - | REPORT |
nodes_annulus_p2 |
report | (series) | - | - | - | REPORT |
nodes_coax_p1 |
report | (series) | - | - | - | REPORT |
nodes_coax_p2 |
report | (series) | - | - | - | REPORT |
nodes_magnet_p1 |
report | (series) | - | - | - | REPORT |
nodes_magnet_p2 |
report | (series) | - | - | - | REPORT |
p2_over_p1_per_dof_annulus |
report | 0.0236574 | - | - | - | REPORT |
p2_over_p1_per_dof_coax |
report | 0.0572534 | - | - | - | REPORT |
p2_over_p1_per_dof_magnet |
report | 0.0726495 | - | - | - | REPORT |
p2_over_p1_per_second_annulus |
report | 0.0168403 | - | - | - | REPORT |
p2_over_p1_per_second_coax |
report | 0.0383848 | - | - | - | REPORT |
p2_over_p1_per_second_magnet |
report | 0.0387911 | - | - | - | REPORT |
polygon_area_deficit_uncorrected_rel |
report | 0.00188911 | - | - | - | REPORT |
probe_err_grad_annulus_p1 |
report | (series) | - | - | - | REPORT |
probe_err_grad_annulus_p2 |
report | (series) | - | - | - | REPORT |
probe_err_grad_coax_p1 |
report | (series) | - | - | - | REPORT |
probe_err_grad_coax_p2 |
report | (series) | - | - | - | REPORT |
probe_err_grad_magnet_p1 |
report | (series) | - | - | - | REPORT |
probe_err_grad_magnet_p2 |
report | (series) | - | - | - | REPORT |
probe_err_u_annulus_p1 |
report | (series) | - | - | - | REPORT |
probe_err_u_annulus_p2 |
report | (series) | - | - | - | REPORT |
probe_err_u_coax_p1 |
report | (series) | - | - | - | REPORT |
probe_err_u_coax_p2 |
report | (series) | - | - | - | REPORT |
probe_err_u_magnet_p1 |
report | (series) | - | - | - | REPORT |
probe_err_u_magnet_p2 |
report | (series) | - | - | - | REPORT |
probe_slope_grad_annulus_p1 |
report | 0.985285 | - | - | - | REPORT |
probe_slope_grad_annulus_p2 |
report | 1.9255 | - | - | - | REPORT |
probe_slope_grad_coax_p1 |
report | 1.43414 | - | - | - | REPORT |
probe_slope_grad_coax_p2 |
report | 2.07299 | - | - | - | REPORT |
probe_slope_grad_magnet_p1 |
report | 0.948602 | - | - | - | REPORT |
probe_slope_grad_magnet_p2 |
report | 1.89605 | - | - | - | REPORT |
probe_slope_u_annulus_p1 |
report | 2.00449 | - | - | - | REPORT |
probe_slope_u_annulus_p2 |
report | 2.04019 | - | - | - | REPORT |
probe_slope_u_coax_p1 |
report | 2.09032 | - | - | - | REPORT |
probe_slope_u_coax_p2 |
report | 2.05735 | - | - | - | REPORT |
probe_slope_u_magnet_p1 |
report | 1.99476 | - | - | - | REPORT |
probe_slope_u_magnet_p2 |
report | 1.90964 | - | - | - | REPORT |
slope_grad_annulus_p1_finest3 |
report | 1.00704 | - | - | - | REPORT |
slope_grad_annulus_p2_finest3 |
report | 2.00046 | - | - | - | REPORT |
slope_grad_coax_p1_finest3 |
report | 1.02267 | - | - | - | REPORT |
slope_grad_coax_p2_finest3 |
report | 1.8673 | - | - | - | REPORT |
slope_grad_magnet_p1_finest3 |
report | 0.97559 | - | - | - | REPORT |
slope_grad_magnet_p2_finest3 |
report | 1.92282 | - | - | - | REPORT |
slope_u_annulus_p1_finest3 |
report | 2.01098 | - | - | - | REPORT |
slope_u_annulus_p2_finest3 |
report | 2.99627 | - | - | - | REPORT |
slope_u_coax_p1_finest3 |
report | 2.07736 | - | - | - | REPORT |
slope_u_coax_p2_finest3 |
report | 2.83745 | - | - | - | REPORT |
slope_u_magnet_p1_finest3 |
report | 2.06431 | - | - | - | REPORT |
slope_u_magnet_p2_finest3 |
report | 3.00025 | - | - | - | REPORT |
wall_s_annulus_p1 |
report | (series) | - | - | - | REPORT |
wall_s_annulus_p2 |
report | (series) | - | - | - | REPORT |
wall_s_coax_p1 |
report | (series) | - | - | - | REPORT |
wall_s_coax_p2 |
report | (series) | - | - | - | REPORT |
wall_s_magnet_p1 |
report | (series) | - | - | - | REPORT |
wall_s_magnet_p2 |
report | (series) | - | - | - | REPORT |
axis_off_axis_Bz_err_p1: extracted but not frozen in expected.json - reported onlyaxis_off_axis_Bz_err_p2: extracted but not frozen in expected.json - reported onlyaxis_on_axis_Bz_err_p1: extracted but not frozen in expected.json - reported onlyaxis_on_axis_Bz_err_p2: extracted but not frozen in expected.json - reported onlyaxis_reference_Bz_T: extracted but not frozen in expected.json - reported onlyaxis_reference_Bz_offaxis_T: extracted but not frozen in expected.json - reported onlyerr_grad_annulus_p1: extracted but not frozen in expected.json - reported onlyerr_grad_annulus_p2: extracted but not frozen in expected.json - reported onlyerr_grad_coax_p1: extracted but not frozen in expected.json - reported onlyerr_grad_coax_p2: extracted but not frozen in expected.json - reported onlyerr_grad_magnet_p1: extracted but not frozen in expected.json - reported onlyerr_grad_magnet_p2: extracted but not frozen in expected.json - reported onlyerr_u_annulus_p1: extracted but not frozen in expected.json - reported onlyerr_u_annulus_p2: extracted but not frozen in expected.json - reported onlyerr_u_coax_p1: extracted but not frozen in expected.json - reported onlyerr_u_coax_p2: extracted but not frozen in expected.json - reported onlyerr_u_magnet_p1: extracted but not frozen in expected.json - reported onlyerr_u_magnet_p2: extracted but not frozen in expected.json - reported onlynodes_annulus_p1: extracted but not frozen in expected.json - reported onlynodes_annulus_p2: extracted but not frozen in expected.json - reported onlynodes_coax_p1: extracted but not frozen in expected.json - reported onlynodes_coax_p2: extracted but not frozen in expected.json - reported onlynodes_magnet_p1: extracted but not frozen in expected.json - reported onlynodes_magnet_p2: extracted but not frozen in expected.json - reported onlyp2_over_p1_per_dof_annulus: extracted but not frozen in expected.json - reported onlyp2_over_p1_per_dof_coax: extracted but not frozen in expected.json - reported onlyp2_over_p1_per_dof_magnet: extracted but not frozen in expected.json - reported onlyp2_over_p1_per_second_annulus: extracted but not frozen in expected.json - reported onlyp2_over_p1_per_second_coax: extracted but not frozen in expected.json - reported onlyp2_over_p1_per_second_magnet: extracted but not frozen in expected.json - reported onlypolygon_area_deficit_uncorrected_rel: extracted but not frozen in expected.json - reported onlyprobe_err_grad_annulus_p1: extracted but not frozen in expected.json - reported onlyprobe_err_grad_annulus_p2: extracted but not frozen in expected.json - reported onlyprobe_err_grad_coax_p1: extracted but not frozen in expected.json - reported onlyprobe_err_grad_coax_p2: extracted but not frozen in expected.json - reported onlyprobe_err_grad_magnet_p1: extracted but not frozen in expected.json - reported onlyprobe_err_grad_magnet_p2: extracted but not frozen in expected.json - reported onlyprobe_err_u_annulus_p1: extracted but not frozen in expected.json - reported onlyprobe_err_u_annulus_p2: extracted but not frozen in expected.json - reported onlyprobe_err_u_coax_p1: extracted but not frozen in expected.json - reported onlyprobe_err_u_coax_p2: extracted but not frozen in expected.json - reported onlyprobe_err_u_magnet_p1: extracted but not frozen in expected.json - reported onlyprobe_err_u_magnet_p2: extracted but not frozen in expected.json - reported onlyprobe_slope_grad_annulus_p1: extracted but not frozen in expected.json - reported onlyprobe_slope_grad_annulus_p2: extracted but not frozen in expected.json - reported onlyprobe_slope_grad_coax_p1: extracted but not frozen in expected.json - reported onlyprobe_slope_grad_coax_p2: extracted but not frozen in expected.json - reported onlyprobe_slope_grad_magnet_p1: extracted but not frozen in expected.json - reported onlyprobe_slope_grad_magnet_p2: extracted but not frozen in expected.json - reported onlyprobe_slope_u_annulus_p1: extracted but not frozen in expected.json - reported onlyprobe_slope_u_annulus_p2: extracted but not frozen in expected.json - reported onlyprobe_slope_u_coax_p1: extracted but not frozen in expected.json - reported onlyprobe_slope_u_coax_p2: extracted but not frozen in expected.json - reported onlyprobe_slope_u_magnet_p1: extracted but not frozen in expected.json - reported onlyprobe_slope_u_magnet_p2: extracted but not frozen in expected.json - reported onlyslope_grad_annulus_p1_finest3: extracted but not frozen in expected.json - reported onlyslope_grad_annulus_p2_finest3: extracted but not frozen in expected.json - reported onlyslope_grad_coax_p1_finest3: extracted but not frozen in expected.json - reported onlyslope_grad_coax_p2_finest3: extracted but not frozen in expected.json - reported onlyslope_grad_magnet_p1_finest3: extracted but not frozen in expected.json - reported onlyslope_grad_magnet_p2_finest3: extracted but not frozen in expected.json - reported onlyslope_u_annulus_p1_finest3: extracted but not frozen in expected.json - reported onlyslope_u_annulus_p2_finest3: extracted but not frozen in expected.json - reported onlyslope_u_coax_p1_finest3: extracted but not frozen in expected.json - reported onlyslope_u_coax_p2_finest3: extracted but not frozen in expected.json - reported onlyslope_u_magnet_p1_finest3: extracted but not frozen in expected.json - reported onlyslope_u_magnet_p2_finest3: extracted but not frozen in expected.json - reported onlywall_s_annulus_p1: extracted but not frozen in expected.json - reported onlywall_s_annulus_p2: extracted but not frozen in expected.json - reported onlywall_s_coax_p1: extracted but not frozen in expected.json - reported onlywall_s_coax_p2: extracted but not frozen in expected.json - reported onlywall_s_magnet_p1: extracted but not frozen in expected.json - reported onlywall_s_magnet_p2: extracted but not frozen in expected.json - reported only
All twelve slopes land on theory, and the worst of them is 0.082 off an
integer against a +/-0.15 band. Read as a block:
| Problem | P1 u (2) |
P1 grad (1) |
P2 u (3) |
P2 grad (2) |
|---|---|---|---|---|
| coax | 2.03369 | 0.993814 | 3.03682 | 1.93592 |
| magnet | 2.03393 | 0.969675 | 2.98344 | 1.91795 |
| annulus | 2.01645 | 1.00755 | 3.02302 | 2.01289 |
Three problems, three different physics, two element orders, four theoretical orders - twelve independent fits, no free parameter anywhere, and the largest deviation is 4 % of the quantity being fitted.
Does P2 pay for itself?
That is the question the plan actually asks, and "is it more accurate on the same mesh" is not it - P2 is more accurate on the same mesh and it is also more expensive on it. The honest comparison is at equal cost, so each order's own error curve is interpolated in log-log to the other's finest budget:
| Problem | error ratio P2/P1 at equal unknowns | at equal wall time |
|---|---|---|
| coax | 0.0572534 | 0.0383848 |
| magnet | 0.0726495 | 0.0387911 |
| annulus | 0.0236574 | 0.0168403 |
So on these three smooth problems P2 is 14 to 42 times more accurate per unknown, and 26 to 59 times more accurate per second of solver time. It is worth being precise about what that does and does not license: all three solutions here are smooth, all three geometries are convex or annular, and none of them has a re-entrant corner, a sliding band or a saturating material. The plan predicted "for smooth problems yes, per unknown and per second; near re-entrant corners the advantage shrinks", and this case measures the first half and does not test the second. §7 says so again.
5. Discussion - the ruler is not the result
The validation plan V09 was written after V05 spent a chapter measuring a Maxwell-stress line integral rather than the field it was applied to, and it carries the lesson forward as an instruction: fit slopes on a proven estimator, and publish the estimator's own convergence beside the solution's.
The estimator a user actually has is getFieldInPoint. Two predictions about it
were written down before this case was run, from reading the code rather than
from any measurement:
Results.calcAauxinterpolatesAlinearly over the triangle the probe search returns. At 1st order that triangle is the element and the interpolation is the finite-element function - so the probe is exact. At 2nd order the search runs overP2Render.displayElements(), the four-way split of each quadratic element, so the probe returns a piecewise-linear interpolant of a quadratic solution on sub-triangles of sizeh/2. That costsO(h^2), which is below theO(h^3)being measured. The probe should report slope 2 for a perfect P2 solver.calcBxAuxinterpolates the nodally averagedBx/By, which is a recovery operator, and a recovery operator is superconvergent at interior nodes at P1. The probe should report a slope above 1, and an error below the true one, at P1.
Both hold, and the numbers are worth quoting:
probe slope, u |
probe slope, grad |
probe error / true error, finest level | |
|---|---|---|---|
| coax P1 | 2.09032 | 1.43414 | A 1.01x, B 0.25x |
| coax P2 | 2.05735 | 2.07299 | A 55x, B 0.66x |
| magnet P1 | 1.99476 | 0.948602 | A 0.99x, B 0.22x |
| magnet P2 | 1.90964 | 1.89605 | A 19x, B 0.88x |
| annulus P1 | 2.00449 | 0.985285 | T 1.10x, grad T 0.38x |
| annulus P2 | 2.04019 | 1.9255 | T 104x, grad T 1.32x |
Three things follow, and they are the practical output of this chapter.
At 1st order the point probe is the solution. Its A/T error reproduces
the finite-element norm to 1 % (1.01x, 0.99x, 1.10x) and its slope is 1.99-2.09.
Nothing is lost by reading a P1 solution through it.
At 2nd order the A/T probe has a floor of its own, and it is large. On
the finest annulus mesh the true error is 7.1e-8 and the probe reads
7.4e-6 - 104 times bigger - and its fitted slope is
2.04019 where the solution's is
3.02302. A user who refines a P2 model and watches the
probe will conclude their solver is 2nd order in A when it is 3rd, and will
stop refining a decade too early. This is a display-mesh limitation, not a
solver one - the degrees of freedom are right, and every integral
post-processing path already reads them properly - but it is the number to know
before quoting a P2 point probe.
The B probe is better than the field it comes from, at P1. It reads 0.22x
to 0.38x the true element-wise error, because nodal averaging is a recovery
operator; its fitted slope runs 0.95 to 1.43 rather than a clean 1. That is good
news for a user and bad news for anyone fitting a convergence order through it:
it is a different approximation, not a measurement of the one being studied.
It is also the reason this case does not judge a single row on a probe.
6. What had to be got right, and what it cost
h is measured, not requested. The five levels ask for element sizes in the
ratio 1 : 1/sqrt2 : 1/2 : ..., but Triangle's quality constraint gives a coarse
mesh more elements than the area constraint alone would, so the achieved sizes
span 3.3x where the request spans 4x. Every slope here is fitted against
h = sqrt(4 A / (sqrt3 E)) computed from the mesh that was actually generated.
Fitting against the requested h instead would have moved every slope by
about 15 % and turned four passing rows into failures - a case that measured its
own request rather than its own mesh.
The magnet's norm is taken outside the magnet, and that is not a convenience.
Nabla has no isoparametric element: a P2 triangle is straight-sided, and the
magnet surface reaches it as a polygon. Where B jumps across that surface, the
sliver between polygon and circle is wrong by O(1) over a width O(h^2),
which contributes O(h) to a relative L2 error in B at every element
order. Fitted through the magnet surface, P2's grad slope would be measuring
the polygon. The cut is placed on a region boundary at 1.3a, so it runs
between elements and never through one. This is a real limitation and it
belongs in §5 of the manual: on a curved material interface, Nabla's
practical order in B is set by how finely the interface is polygonised, not by
the element order - which is one more reason the airgap of a machine model wants
segments, not just area constraints.
Both problems with circles need their circumradius corrected. See §3. Had
they not been, the coarsest magnet model would have carried
+0.19 % too little magnet, falling
as h^2 - indistinguishable from a discretization error, and exactly the size
that caps a 3rd-order method at 2nd.
The probe takes single-precision coordinates. getFieldInPointAPI declares
float x, float y, so a station is answered at the float32 rounding of the
point it was given - a 6e-8 relative shift, which on a field gradient is the
whole difference between two readings that should be identical. The
reconstruction check rounds its stations to float32 before asking either
instrument, so what it reports is the reconstruction and not the coordinate
conversion. Worth knowing before comparing two probe readings that ought to
agree exactly.
7. V01's open question, answered
V01 published, without a verdict, that B_z probed exactly on the symmetry
axis of an axisymmetric model reads +40 % at P1 against -0.08 % at P2, that
refining does not help, and that V09 should measure it as a convergence study.
Here it is: an axisymmetric solenoid (r = 20..24 mm, +/-30 mm long,
NI = 1200 A), three mesh levels, both orders, read twice per solve - once at
r = 0 and once at r = 2 mm. The reference is the exact on-axis field of a
rectangular-section coil, 0.0202676 T, and the exact
off-axis value from the same current distribution.
| Level (coarse -> fine) | P1 on the axis | P1 at r = 2 mm |
P2 on the axis | P2 at r = 2 mm |
|---|---|---|---|---|
| 1 | +5.63 % | +3.66 % | -0.81 % | -0.85 % |
| 2 | -2.56 % | -1.22 % | -0.83 % | -0.82 % |
| 3 | +28.98 % | -1.70 % | -0.82 % | -0.83 % |
The P1 on-axis reading does not converge - it gets worse. The same solve read
two millimetres off the axis converges normally, and P2 is flat to 0.02 % across
the sweep at both stations. The common -0.8 % in the P2 column is the Dirichlet
box this model is truncated with, which is the same for every row and cancels out
of the comparison entirely: what is left in the P1 column is the recovery.
The mechanism is the one V01 named. An axisymmetric magnetic solution stores
u = r*A_phi, and B_z = (1/r) du/dr at r = 0 is a 0/0 that only P2's
axis-node Hessian recovery resolves; at P1 the answer depends on which element
the probe happened to land in and how close its nodes are to the axis, which is
why a finer mesh can be worse. Guidance: never read B_z on the axis of a
P1 axisymmetric model. Read it a millimetre or two off, or use P2. Integral
quantities are unaffected at either order (V01 measured that separately).
8. Limitations
- Three smooth problems on three well-behaved geometries. No re-entrant corner, no saturating material, no sliding band, no transient. The "P2 pays for itself" numbers in §4 are for smooth problems and the chapter says so; the plan's own expectation is that the advantage shrinks near a re-entrant corner, and this case does not test that.
- Element order is not the only P2 cost, and the others are not measured
here. The second-order element specification fixes what P2 gives up: the averaging sliding
band only - the legacy stitch band is rejected outright - axisymmetric + skew
rejected at both orders, and rotor-bar/stator skew at P2 costing about
6.6x the nnz and 11x the wall time per step against the same skewed P1 run.
A P2 machine model also needs its sliding-band ring subdivided more finely
than the shipped
arcLength / (0.5 * airGap)default, which is a P1 default: V05 measured the element layer touching the band ring carrying 38x and 81x the numerical noise of a layer one region away until the ring was refined 4x. None of that is exercised here, and a reader weighing P2 should read the accuracy claim above together with that list. - The norms are
L2norms over a domain. They say nothing about a local quantity at a corner, a force on a contour, or a torque - each of which has its own estimator, and V02, V05 and V15 are the chapters about those. - Only the magnetic and thermal static kernels. Time-harmonic P2 and
transient P2 exist (
time_harmonic_p2, the thermal transient) and are not measured here; V19 is the time-integration order study.
9. Reproduce
cd validation
python -m harness.run_case V09
python -m pytest tests/test_fem_norms.py # the ruler, without Nabla
The eighteen 1st-order sub-models are reproducible on the free tier; the
2nd-order ones need Feature.CORE_PRO. All thirty-six run in 104.3 s.
Every series quoted in the tables above - the five-level error curves, the probe
curves, the node counts, the wall times and the axis sweep - is written out in
full to artifacts/series/*.json, and the per-sub-model mesh statistics are in
artifacts/results.json under provenance.sub_models.
| Sub-model | Element order | Nodes | Elements |
|---|---|---|---|
coax_p1_L0 |
P1 | 634 | 1125 |
coax_p1_L1 |
P1 | 1077 | 1952 |
coax_p1_L2 |
P1 | 1935 | 3585 |
coax_p1_L3 |
P1 | 3441 | 6480 |
coax_p1_L4 |
P1 | 6407 | 12247 |
coax_p2_L0 |
P2 | 2392 | 1125 |
coax_p2_L1 |
P2 | 4105 | 1952 |
coax_p2_L2 |
P2 | 7454 | 3585 |
coax_p2_L3 |
P2 | 13361 | 6480 |
coax_p2_L4 |
P2 | 25060 | 12247 |
magnet_p1_L0 |
P1 | 1021 | 1863 |
magnet_p1_L1 |
P1 | 1749 | 3246 |
magnet_p1_L2 |
P1 | 3125 | 5895 |
magnet_p1_L3 |
P1 | 5622 | 10742 |
magnet_p1_L4 |
P1 | 10282 | 19855 |
magnet_p2_L0 |
P2 | 3904 | 1863 |
magnet_p2_L1 |
P2 | 6743 | 3246 |
magnet_p2_L2 |
P2 | 12144 | 5895 |
magnet_p2_L3 |
P2 | 21985 | 10742 |
magnet_p2_L4 |
P2 | 40418 | 19855 |
annulus_p1_L0 |
P1 | 463 | 856 |
annulus_p1_L1 |
P1 | 914 | 1723 |
annulus_p1_L2 |
P1 | 1770 | 3400 |
annulus_p1_L3 |
P1 | 3540 | 6877 |
annulus_p1_L4 |
P1 | 6982 | 13675 |
annulus_p2_L0 |
P2 | 1781 | 856 |
annulus_p2_L1 |
P2 | 3550 | 1723 |
annulus_p2_L2 |
P2 | 6939 | 3400 |
annulus_p2_L3 |
P2 | 13956 | 6877 |
annulus_p2_L4 |
P2 | 27638 | 13675 |
axis_p1_L0 |
P1 | 1682 | 3226 |
axis_p1_L1 |
P1 | 3220 | 6269 |
axis_p1_L2 |
P1 | 6304 | 12341 |
axis_p2_L0 |
P2 | 6589 | 3226 |
axis_p2_L1 |
P2 | 12708 | 6269 |
axis_p2_L2 |
P2 | 24948 | 12341 |
| Date | Change | Reason |
|---|---|---|
| 2026-08-14 | frozen at creation | Every reference here is an integer from FEM theory and every tolerance is the plan's own +/-0.15 slope band. Both were written down before the case was run, and neither depends on anything Nabla returned. |