A 2-D diffusion eigenproblem with a two-parameter coefficient

This page treats scalar diffusion on the unit square whose matrix-valued coefficient depends on two scalar parameters, \mu = (\mu_1, \mu_2) \in [0.8, 1.05]^2. The spatial problem is two-dimensional; the parameter box it is swept over is also two-dimensional. It is the companion to the one-parameter sweep, with the same PDE generalized to a 2-D parameter box.

The page is built in the same order as the one-parameter page. First the shared groundwork: the model, the discretisation, and the two qualitatively different features that drive refinement. Then each of the two operating modes gets a full, self-contained treatment: Window mode — the operating mode closest to the reference setup — followed by FirstK mode — a fixed-band approximation. Both runs use the node-centric forward_points refinement — the dissertation’s own refinement rule (DIR-097) — so, like the MATLAB, they fill the 2-D interior. The page closes with a side-by-side comparison of the two and a reachability analysis of why the alternative edge-bisection rule cannot reach that interior fill (DIR-096) and how the admissible variant (DIR-105) fills it without the MATLAB’s virtual points.

Related pages

Why this example

The one-parameter case is the smallest problem that exposes the core difficulty — eigenvalue branches that approach and exchange character — in a setting dense enough to verify against ground truth. This two-parameter case answers the next question: does the same algorithm generalise to a higher-dimensional parameter box? The answer here is yes, and the extension requires no new ideas — the graph now has edges in two axis-aligned directions instead of one, and the same edge-by-edge matching and certification logic applies unchanged.

What the 2-D box adds is variety: two qualitatively different refinement drivers appear on the same run.

  1. A crossing (bands 3–4, left half of the box). The a-posteriori verifier detects the crossing at level 0 without needing to bisect through it — the cluster verdict certifies those edges directly. This is the easier case.
  2. A small-gap pair (bands 4–7, right half of the box). A sustained near-degeneracy over an extended region drives most of the L \geq 1 refinement and produces the asymmetric node concentration visible in the grid plots. This is the harder case — the gap never closes, so the verifier must accumulate enough nodes to resolve it.

The 2-D run also raises the propagation-order question: for d_{\text{param}} \geq 2 there is more than one admissible order to walk the initial seed lattice when propagating the band-matching identity from the anchor point outward. The initial-lattice section below shows the seed graph and three representative shortest paths.

The model

Find (\lambda, u) such that -\nabla\cdot\bigl(c(\mu)\nabla u\bigr) = \lambda\, u on \Omega = [0,1]^2 with u = 0 on \partial\Omega, where

c(\mu_1, \mu_2) = \begin{bmatrix} \mu_1^{-2} & 0.8\,\mu_2^{-1} \\ 0.8\,\mu_2^{-1} & \mu_2^{-2} \end{bmatrix}, \qquad \mu \in M = [0.8,\,1.05]^2.

The weak form is affine in the entries of c, so the stiffness matrix decomposes into fixed tensor-component matrices assembled once — the same affine structure used on the one-parameter page; only the coefficient and the parameter box change. Every parameter point costs four cached component contractions plus one eigensolve — no re-meshing, no per-\mu quadrature.

Discretisation

Both runs share the same spatial discretisation and parameter domain; only the spectral bookkeeping differs between modes.

Quantity Value Role
Mesh \texttt{mesh\_n} = 64, P1 triangulation spatial discretisation
Parameter box [0.8,\, 1.05]^2 parameter domain
Initial grid 3\times3 lattice (9 nodes, 12 axis-aligned edges) seed
Max refinement levels 8 (both runs terminate before this) iteration cap

The two operating modes split here:

  • Window mode keeps whichever eigenvalues fall inside a fixed spectral window I_\lambda = [0, 200], so per-node rank is ragged (varies across the parameter box as bands enter and leave the window). This is how the reference 2-D example was actually computed.
  • FirstK mode keeps a fixed number n_{\text{eigs}} = 20 of the smallest bands at every node. This is the rebuild’s fixed-band approximation.

Each mode is treated in full below, on its own honest tolerances, before they are compared.

Reference eigenvalue surfaces

The dense reference sweep is a 65\times 65 Cartesian grid over M, using n_{\text{eigs}} = 20 to cover the full window [0, 200]. The first eight eigenvalue surfaces are stacked in a single 3-D view so the full spectrum’s shape — and the crossings and small-gap regions that drive refinement — are visible at a glance.

Reference eigenvalue surfaces — the first eight eigenvalue surfaces from the dense 65\times 65 truth sweep over \mu \in [0.8, 1.05]^2, stacked in a single 3-D view. Colour encodes \lambda globally.

The stacked view shows what is there; the next two surface views isolate the two specific features that drive refinement.

Bands 3–4 crossing. The two surfaces cross over a curve in the left half of M. The a-posteriori projection matrix at the box midpoint detects this crossing at level 0; no refinement is needed through the crossing itself.

Small-gap region, bands 4–7 (side 1). In the right half of M, bands 4 and 7 sit very close in value over an extended region. This small-gap region is what drives most of the L \geq 1 refinement.

Small-gap region, bands 4–7 (side 2). Same surfaces, different viewing angle.

Propagation path on the initial lattice

For d_{\text{param}} \geq 2 the matching propagation has more than one admissible order to walk the initial grid. One way to picture the choice is a minimum spanning tree over the initial 3\times3 lattice with three shortest paths from (0.8, 0.8) to (0.8, 1.05), (0.925, 1.05), and (1.05, 1.05) overlaid. The refinement loop propagates matching directly along the graph edges without an explicit spanning-tree construction, so the figure below is a reference visual built on the same 9-node lattice.

Initial lattice — the 3\times3 seed lattice with a minimum spanning tree (blue) and three shortest paths overlaid (red, green, purple).

Window mode — the reference operating mode

The §3.3.3 example was computed in window mode (CodeSeptember condition=Inf, r=[0,200]), so this is the run that most closely mirrors the reference operating mode — though it does not reproduce the reference grid, which fills the 2-D interior in a way the default refinement structurally cannot (see Reachability & interior fill below). Snapshots are ragged: each parameter point keeps whatever eigenpairs fall inside the fixed window

I_\lambda = [0,\, 200]

(matching the MATLAB r=[0,200] / CodeSeptember condition=Inf). The verdicts use the one a-posteriori (Hungarian) path with anchored-absolute pruning at (t_\pi = 0.57,\, t_\lambda = 0.015) — the §3.3.3 stated values. A surplus band that merely enters or leaves the window between adjacent nodes is certified by the row-\ell_2-norm test (Algorithm 6) rather than forcing refinement.

Threshold provenance note. The reference’s §3.3.3 prose states t_\lambda = 0.015; both MATLAB sources (CodeSeptember Main.m and ExampleAug2124NoVirt.m) use t_\lambda = 0.085 for the 2-D case. The window run here uses the prose value; see the one-parameter page for the analogous provenance discussion in the 1-D case.

Adaptive grid evolution

One panel per refinement level. Nodes are coloured by insertion level; surviving edges shown as grey lines. As the iteration proceeds, new nodes concentrate in the right half of the box — driven by the small-gap pair above.

Drag the slider (or use ←/→ when focused) to step through the levels. L = 0 is the 3\times3 lattice seed.

Adaptive 2D grid (Window) per refinement level
Adaptive 2D grid (Window) at L=0

A-posteriori certification map

Each panel shows the edges tested during refinement level L, coloured by their dominant a-posteriori verdict: green = CERTIFIED, red = REFINE, orange = CLUSTER. Grey edges are certified edges from earlier levels shown for spatial context.

A-posteriori verdict map (Window) per refinement level
A-posteriori verdicts (Window) at L=0

Verdict map

Initial seed-grid edges coloured by the dominant a-posteriori verdict. Green = certified, red = refine, orange = cluster.

Verdict map (Window) — the 12 initial 3\times3-lattice edges at level 0, coloured by dominant a-posteriori verdict.

Error table

The run, level by level — the reference Table 3.2. There is no n_{\text{wrong\_match}} column: Window mode’s ragged ranks have no fixed band index to compare pipeline labels against a fixed-band truth sweep.

Level n_{\text{points}} n_{\text{subintervals}} n_{\text{uncertified}}
0 9 12 12
1 20 22 18
2 33 26 17
3 48 30 10
4 52 8 1
5 53 2 0

(Values from the rebuild’s window/error_table.csv.)

Note

Parity gap vs reference Table 3.2. The reference reports the Window run terminating at L=6 with 64 nodes and the per-level counts (n_{\text{points}}, n_{\text{subintervals}}, n_{\text{uncertified}}) = (9,12,4), (13,10,9), (22,22,11), (36,36,10), (49,39,4), (59,34,2), (64,20,0). The rebuild converges one level earlier (L=5, 66 nodes, node-centric forward_points). The per-level node counts also diverge from L=1 onward. This is the same Hypothesis D parity gap documented for §3.3.3 in DIR-026: the rebuild’s verdict pipeline and the reference MATLAB publishing driver produce different per-level refinement decisions even when the tolerance values match. The rebuild’s Window run does correctly certify the grid to zero uncertified edges — the algorithm terminates validly, just on a different node distribution.

Summary. Window mode is the closest rebuild reproduction of the reference §3.3.3 spectral window, using the same tolerance values (t_\pi=0.57,\, t_\lambda=0.015) and the same I_\lambda=[0,200] window. The rebuild certifies the 2-D parameter box at L=5 (66 nodes) vs the reference L=6 (64 nodes), a known parity difference.

FirstK mode — the fixed-band approximation

The same problem, with different spectral bookkeeping: keep a fixed n_{\text{eigs}} = 20 smallest bands at every node, regardless of where the spectrum sits. Twenty bands is enough to span the [0, 200] window at every \mu, which makes this a clean fixed-band counterpart to the ragged window run. It is a deliberately chosen comparison count — the generic fixed-band preset keeps far fewer bands — picked so the two modes cover the same spectral region.

Quantity Value Role
Eigenvalues per point n_{\text{eigs}} = 20 fixed band count (chosen to span [0,200])
Pruning threshold t_\pi = 0.57 relative cutoff for off-pair projection mass
Eigenvalue cluster threshold t_\lambda = 0.015 groups near-degenerate bands

Adaptive grid evolution

Drag the slider to step through the refinement levels.

Adaptive 2D grid (FirstK) per refinement level
Adaptive 2D grid (FirstK) at L=0

Per-level surrogate

As the adaptive grid refines, the band-matched snapshot eigenvalues accumulate at the active nodes. At each level the surrogate is the griddata interpolation of those values onto a 129 \times 129 dense grid.

Surrogate eigenvalue surfaces (FirstK) per refinement level
Surrogate surfaces (FirstK) at L=0

A-posteriori certification map

A-posteriori verdict map (FirstK) per refinement level
A-posteriori verdicts (FirstK) at L=0

Verdict map

Verdict map (FirstK) — the 12 initial 3\times3-lattice edges at level 0, coloured by dominant a-posteriori verdict.

Error table

Level n_{\text{points}} n_{\text{subintervals}} n_{\text{wrong\_match}} n_{\text{uncertified}}
0 9 12 8 12
1 21 24 17 19
2 40 38 32 17
3 57 34 46 7
4 63 12 52 4
5 67 8 56 0

(Values from the rebuild’s firstk/error_table.csv.)

Note

On n_{\text{wrong\_match}}. This column compares the pipeline’s propagated band labels against an independent direct match at the seed midpoint — a strict consistency check. It can be positive even when the grid topology is correct; see the knob-sweep page.

Surrogate eigenvalue surfaces

The algorithm’s output visualised as a surrogate eigenvalue surface per band: scattered-data linear interpolation of the band-matched snapshot eigenvalues onto a dense grid, overlaid with markers at the actual snapshot eigenvalues.

Surrogate eigenvalue surfaces (FirstK) — linear interpolation of the band-matched snapshot eigenvalues onto a 129 \times 129 dense grid; red markers are the actual snapshot eigenvalues at each graph node.

Comparison: Window vs FirstK

The two modes solve the same PDE with different spectral bookkeeping, and they answer different questions:

  • Window asks “track every band inside [0, 200]” — this benchmark’s actual question, ragged ranks and all.
  • FirstK asks “track the 20 smallest bands everywhere” — a uniform approximation that needs no window.
Window FirstK
Bands per node ragged (varies with \mu) fixed (20)
Spectral region I_\lambda = [0, 200] 20 smallest bands
Tolerances t_\pi = 0.57,\ t_\lambda = 0.015 t_\pi = 0.57,\ t_\lambda = 0.015
Terminal level 5 8
Grid nodes 66 121
Reference Table 3.2 (target) L=6, 64 nodes

Both runs use node-centric forward_points refinement and fill the interior, but on different grids: Window settles at 66 nodes (L=5), FirstK at 121 nodes (L=8). Here — unlike the 1-D example, where a 6-band FirstK cutoff sits below the crossing and under-refines — the 20-band FirstK spans the same [0, 200] window as the ragged run, and under the reference-faithful additive pruning rule it over-refines relative to Window: carrying every one of 20 bands at every node surfaces more marginal off-pair projection mass to flag than the ragged run’s variable, mostly-smaller rank does. Neither run reproduces the reference 64-node L=6 grid exactly — the same Hypothesis D parity difference documented in DIR-026 for §3.3.3: the rebuild and the MATLAB publishing driver produce different per-level refinement decisions at otherwise-matching tolerances.

Adaptive grid (Window, forward_points) — 66 nodes, L=5; interior filled.

Adaptive grid (FirstK, forward_points) — 121 nodes, L=8; interior filled.

Reachability & interior fill

The adaptive grids above fill the interior of the four quarters — because this page runs the dissertation’s own node-centric forward_points refinement. That was not true of the rebuild’s original default, edge_bisection, and the reason it failed is structural, deterministic, and worth seeing: it is exactly the reachability gap DIR-096 measured and the motivation for making forward_points the refinement this page renders.

On the 3\times3 seed lattice (\mu \in \{0.8, 0.925, 1.05\}^2) the edge-bisection strategy (refinement_strategy="edge_bisection") inserts each new node at the midpoint of an axis-aligned edge. Every seed edge lies along a grid line, and the midpoint of a segment on a line is still on that line — so the four interior quarters of [0.8, 1.05]^2 can never receive a node. The reference MATLAB avoids this because it refines node-centrically (CodeSeptember/Main.m:329, forward_points): every node spawns forward neighbours in every axis direction, so a node steps off the seed lines and the interior fills in 2-D. This is the “empty quarter” reachability failure written up under DIR-050 (refinement-balance-and-reachability.md). The overlay below shows the gap with the edge-bisection strategy specifically — the trap this page’s forward_points figures escape.

Interior-fill reachability — the edge-bisection trap. Left: the reference terminal grid (2DNLevel7, 64 nodes) — the 30 nodes that lie off every seed grid line are drawn as red diamonds. Middle/right: the rebuild under the edge_bisection strategy (Window, FirstK-20) — zero interior nodes; every node sits on the boundary or the central cross. This is the strategy this page no longer uses; the forward_points grids above fill the interior. Reference coordinates are extracted directly from the frozen dissertation EPS.

Do the rebuild’s node-centric strategies close the gap? (DIR-096)

The rebuild ships two non-default strategies built for exactly this gap — refinement_strategy="hierarchical" (the spawn_hierarchical_child node-centric path) and refinement_topology="smolyak_orthogonal". DIR-096 measured all three against the reference at the real mesh (mesh_n=64), in FirstK and Window modes (scripts/dir_096_reachability_3_3_3.py, artifact tests/artifacts/dir_096/reachability.json). The verdict is a clean negative:

Strategy Interior nodes (best) Terminal Verdict
edge_bisection (old default) 0 L=5, 53–67 nodes Structural trap — stays on seed lines
hierarchical (1- & 2-sided) 0 L=1–5, 12–67 nodes Behaves like edge-bisection here †
smolyak_orthogonal 1 L=3, 18 nodes Places one interior node; unusable otherwise ‡
reference target 30 L=6, 64 nodes

† The hierarchical strategy’s node-centric reach only helps in an anisotropic pocket (a node with neighbours on one axis but not another — the DIR-066D dead_zone_probe). On the isotropic §3.3.3 seed the flagged edges are seed-lattice edges with same-level brackets, so the strategy falls back to plain bisection and reproduces the trap. Two-sided is byte-identical to edge-bisection here.

smolyak_orthogonal is the only strategy that places any interior node, but it places just one (in the admissible FirstK-5 config). In the FirstK-20 and Window configs it violates the no-hanging-node admissibility invariant; with that guard disabled it fills the interior only by runaway, non-terminating refinement (521 / 232 nodes, 445 / 177 interior at the level cap) — nothing like the reference’s controlled 64-node grid.

Conclusion (DIR-096). No edge-centric rebuild strategy reproduces the reference’s controlled 2-D interior fill. The fill in the MATLAB comes from forward_pointsomnidirectional node-centric spawning combined with an a-posteriori certification that still terminates at 64 nodes; the edge-centric strategies replicate neither the omnidirectional spawn (the hierarchical strategy spawns only along a flagged edge’s own axis) nor a controlled node-centric termination. This is why edge_bisection was not kept as the reproduction: it is an honest but different algorithm from the dissertation’s.

The forward_points port closes the gap — and is now what this page renders (DIR-097)

DIR-097 ports the missing mechanism exactly: refinement_strategy="forward_points" is the MATLAB node-centric driver loop (per-node level vectors, omnidirectional spawn, coordinate dedup, child-active-iff-REFINE through the unchanged one-verdict path). Rerunning the DIR-096 harness with it in the matrix flips the verdict — forward_points is the only strategy that reproduces the interior fill (Window [0,200]: L=5, 88 nodes, 39 interior; FirstK-20: 68 interior; every edge-centric strategy: 0). The faithful-config sweep (scripts/dir_097_faithful_sweep.py) lands closest to the published grid at t_lambda=0.015 (the prose value — the MATLAB source 0.085 terminates far too early), projection=off, InitialLevel=1, packaged as RefinementConfig.dissertation_3_3_3_forward_points_preset(): terminal L=5, 66 nodes, 21 interior vs the reference L=6, 64, 30. The remaining per-level residual (one level early; 21 vs 30 interior) is the flag-count facet of Q11 — the publishing driver is still missing from both MATLAB dumps, so exact Table 2D Error parity remains a stretch goal. Details + measured tables: strategy-comparison §6. The Window and FirstK figures on this page are this forward_points refinement — the dissertation’s own rule — which is why they fill the interior.

But forward_points is not admissible — the Pflüger fix (DIR-105)

forward_points reaches the interior, but on a non-admissible grid: its per-node level-vector spawn drops off-grid-line interior nodes whose coarser bracket-parents are absent — virtual points (a fabricated missing bracket). That is why it must switch off assert_admissible, π⋆, regions, and the parallel pool. DIR-105 ships the third pole that keeps both omnidirectionality and bracket-admissibility: refinement_strategy="admissible_spawn". It spawns the canonical axis_child from each active node’s dyadic index in every axis, and — before adding a child — back-fills that child’s missing bracket-ancestors (the Pflüger downward-closure completion) as real solved support nodes, so no virtual point can arise and Graph.assert_admissible() holds at every level.

The interior fill on a bracket-admissible grid (DIR-105). §3.3.3 Window [0,200], mesh_n=64. Interior-fill nodes (off both seed grid lines) are red diamonds. Left edge_bisection: 0 interior — the edge-centric reachability ceiling. Middle forward_points: 21 interior but 14 orphans (non-admissible — the virtual points). Right admissible_spawn: 48 interior with 0 orphans, assert_admissible passing; the admissibility tax (back-filled support solves) is 50, only 4.6 % of the full tensor grid at that depth — sub-tensorial, so the admissible grid is still genuinely sparse.

So the interior fill no longer requires the non-admissible n-spawn: it is reachable on a downward-closed grid. The cloud admissible_spawn produces differs from forward_points’ (canonical axis_child vs the per-lineage n), so this route does not reproduce the frozen MATLAB grid node-for-node — it is an admissible fill of the same interior, arguably more correct than the original (whose two extra nodes are the virtual points). Measured tables: strategy-comparison §7; the ε-trap it avoids is written up in matlab-virtual-points.md (DIR-104). Like forward_points, admissible_spawn is opt-in — the figures above render the faithful forward_points refinement, and admissible_spawn is the admissible variant of that same node-centric idea.

Side-by-side with the original MATLAB

The panels below place the Python rebuild next to a freshly generated figure from the dissertation’s own MATLAB code — here the frozen CodeSeptember choice1='1' case, which is the §3.3.3 2-parameter diffusion example, run unchanged. The MATLAB figures are produced headlessly by scripts/matlab_figures.py under MATLAB R2026a and committed as artifacts.

  • Source: GreedyAlgorithm/CodeSeptember/Main.m, choice1='1', InitialLevel=2 — the frozen 2-parameter case, faithful with no coefficient adaptation: \Omega=[0.8,1.05]\times[0.9,1.1], coefficient [µ₁⁻²; 0.8·µ₂⁻¹; µ₂⁻²], t_lambda=0.085, t_pi=0.57, window I_\lambda=[0,200].
  • Headless-safe patching: scripts/matlab_figures.py neutralises the interactive prompts, VideoWriter, and Windows-only .mat paths, and makes the reference-surface plot span the real (rectangular) parameter box — it never edits the frozen dump on disk. The grid-helpers lev2knots_doubling/tensor_grid are addpath’d from the Clean dump.

Rebuild — the first eight reference eigenvalue surfaces, stacked (dense truth sweep).

MATLAB — a reference eigenvalue surface (band 12) over \Omega from the original code, with the computed adaptive-grid points overlaid as red circles. Same interpolated-surface + sampled-point construction the rebuild mirrors.

Rebuild — adaptive grid (Window, forward_points), 66 nodes, L=5. The interior of the four quarters is populated — the node-centric spawn steps off the seed grid lines, exactly as the MATLAB does.

MATLAB — adaptive grid from the original code, 41 nodes, L=5. Refinement clusters in the right/upper small-gap region — the same bands-4–7 region the rebuild refines.

Both grids seed the same 3\times3 lattice, run the same node-centric forward_points refinement, and drive it into the same small-gap region. The remaining node-count gap (66 vs 41) is the projection/tolerance residual (Q11 — the publishing driver is missing from both MATLAB dumps), not an algorithm difference: the rebuild now uses the dissertation’s own refinement rule, so both fill the interior.

Separately from the forward_points figures above, DIR-103 ran a distinct bit-exact parity experiment against the frozen MATLAB run — deliberately using edge_bisection (plus squared Π and MATLAB’s exact mesh) to isolate the verdict pipeline from the spawn geometry. It measured how close the rebuild gets when the discretisation is made identical: RefinementConfig.dissertation_3_3_3_frozen_preset() (edge-bisection, squared Π, t_lambda=0.085, 5×5 seed) run via Diffusion2D.from_mesh(load_matlab_mesh(),…) on MATLAB’s exact P2 mesh reproduces the frozen run’s per-edge certify/refine verdict on 76/76 tested edges and 39 of 41 grid nodes with zero spurious. The 2 unreproduced nodes (µ_1\approx1.019) are node-centric n-vector forward spawns against a virtual point (empty space) that edge-bisection cannot place — a spawn-geometry difference, not a numeric or verdict gap. See the parity-ledger workflow, Q11, and DVG-SPAWN-01.

The 6-D elasticity example (§4.4) is not paired: its MATLAB run is the unbounded-memory path that OOMs on the available hardware (see the workspace OOM analysis), so a fresh MATLAB figure cannot be generated there. Rebuild-only figures on this page have no MATLAB counterpart.