WaveletAdaptiveNode#
Module: maddening.nodes.adaptive.wavelet
Stability: experimental
Algorithm ID: MADD-NODE-010
Version: 1.1.0
Summary#
WaveletAdaptiveNode is the first concrete
AdaptiveNode subclass with a real basis. It solves the
steady elliptic problem \((-\Delta + m)\,u = f\) on the unit cube in one, two or
three dimensions, periodic or with homogeneous Dirichlet walls, in an
interpolating (Deslauriers–Dubuc) wavelet basis [@DeslauriersDubuc1989],
selects the active set by Cohen–Dahmen–DeVore bulk chasing
[@CohenDahmenDeVore2001; @Doerfler1996] up to a budget \(k\), solves on that
set through a gathered dense block, and returns the sensor reading
\(J = u(x_s)\) with the frozen-active-set adjoint the base class provides:
plain reverse mode through the gathered solve, or the implicit-function
rule of [@Blondel2022] on the masked-CG path.
Governing Equations#
with \(m > 0\) and the isotropic Gaussian source
periodic on every axis or \(u = 0\) on every wall. On a periodic domain the source is periodised – summed over its images, \(f_{\text{per}}(x) = \prod_i \sum_{n=-2}^{2} \exp(-(d_i + n)^2/\sigma^2)\) with \(d_i\) the distance to the centre on axis \(i\) wrapped into \([-\tfrac12, \tfrac12)\) – so the problem is translation-invariant on the torus like the operator; the omitted images contribute less than \(2\,e^{-6.25/\sigma^2}\) of the peak per axis (\(< 10^{-16}\) for \(\sigma \le 0.41\)). The objective is the point reading \(J(\theta, \sigma) = u(x_s)\) at the grid point nearest the sensor \(x_s\). In the basis \(u = \sum_j c_j \psi_j\) this is
and, restricted to an active set \(M\) with \(|M| \le k\) chosen by
the frozen problem is \(A_M c_M = b_M\), \(c_j = 0\) for \(j \notin M\), with the
adjoint of the base class (adaptive_node.md Governing
Equations): exact within a region of constant \(M\), blind to the region’s
boundary (MADD-ANO-003).
Discretization#
Physical operator. Second-order central differences for \(-\Delta\) with lumped mass \(h^d\) on a uniform dyadic grid of \(n_c\,2^{J}\) points per axis (periodic) or \((n_c + 1)\,2^{J} - 1\) interior points (Dirichlet), \(h = 1/n\) or \(1/(n+1)\).
Basis. The Deslauriers–Dubuc interpolating wavelet basis of order 4 on the isotropic Mallat multiresolution (one level per refinement, \(2^d - 1\) detail subbands per level), built by a lifting scheme (predict the midpoint from the four nearest coarse samples, add the detail), matrix-free in JAX. The Dirichlet basis pins the wall values to zero and shrinks the stencil to linear at the walls; it is built dense at construction and the multi-D Dirichlet basis is the tensor product of the 1-D one. Columns are \(L^2\)-normalised (\(W_n\)).
Exact change of basis. \(A = W_n^{\top} A_{\text{phys}} W_n\) is assembled once, in NumPy, checked for symmetry and only then symmetrised: the correct product measures \(\max|A - A^{\top}| / \max|A| \approx 10^{-16}\) (15 configurations, 1-D to 3-D, both boundaries), a one-sided \([-1, 2, -1]/h\) stencil measures \(1.07\), and
SYMMETRY_TOL = 10^{-12}refuses anything above it. Symmetrising unconditionally would have turned that first-order stencil into a consistent second-order one and passed the order gate against the defect (measured 2.04). Because it is the finite-difference operator in another basis, the full-basis solve reproduces the finite-difference solution to round-off and the order of accuracy is the stencil’s: 2 in \(h\), for both boundary types. Declared asDiscretizationOrder(spatial=2.0, temporal=None)and measured by MMS (MADD-VER-014).Preconditioning. Symmetric diagonal scaling \(\hat A = D^{-1} A D^{-1}\) with \(D\) the hybrid-Jacobi scaling (per-entry \(\sqrt{A_{jj}}\) on the coarse level, the level mean of it on finer levels), which matches full Jacobi to four figures. \(\kappa(\hat A) \approx 20\) (1-D, 256 points), \(\approx 38\) (2-D, \(32^2\)), \(\approx 3.8\) (1-D Dirichlet). The Dahmen–Kunoth \(2^{t\,\text{level}}\) scaling [@DahmenKunoth1992] is available and weaker here.
Selection. CDD: start from the coarse level, estimate the residual \(\hat r = \hat b - \hat A \hat c\), mark the smallest set of inactive functions carrying \(\theta_D^2 = 0.25\) of the squared residual (Dörfler marking), capped at the room left under \(k\), re-solve, repeat until \(|M| \ge k\), 30 iterations, or a step with nothing left to mark, as a
lax.while_loop. The marking reads no difference below the rounding floor \(\tau = 16\,\varepsilon\,(\max|\hat b| + \max|\hat c|)\) (rounding_floor; the residual’s rounding error measured at most \(1.05\,\varepsilon(\max|\hat b| + \max|\hat c|)\)): a residual at or below \(\tau\) is never marked, and residual magnitudes within \(\tau\) of the cutoff (the \(n\)-th largest, \(n\) the Dörfler count capped at the room left) are tied and taken in ascending basis index order. Without it the mirror symmetry of the source (centred on every axis but the first) made residual pairs equal to rounding, and the last bits of \(\hat b\) decided which member survived the cap – so the eager diagnostics, a jittedupdateand the compiled graph selected different sets at the same parameters (2-D Dirichlet: 2 functions, \(J\) 4.7e-3 apart), a Python float and its float32-array spelling did too, and a 1e-6 sweep of \(\theta\) on the 2-D periodic problem switched set on 15 of 39 steps. Now the set changes only where a magnitude gap crosses \(\tau\) or the Dörfler bulk falls on a cumulative sum – isolated parameter values (no switch in the same sweep) – and every parameter leaf is cast to the node’s dtype before the source reads it, so spellings give the same bits. The seed is every level-0 function – the coarse block plus the first detail band, \((2 n_c)^d\) periodic or \((2 n_c + 1)^d\) Dirichlet – and \(k\) is validated to hold it. The selection is a function of the parameters alone (never of the previous state), so it never empties, never exceeds \(k\), and does not change at fixed parameters. For \(k\) above about \(n_{\max}/2\) the iteration bound is the exit in float64, not the budget: on 128 points \(k = 64\) and \(k = 96\) both stop at \(|M| = 54\) (sensor-reading error \(3 \times 10^{-11}\); 200 iterations reach 64), because each Dörfler step marks a fixed fraction of the remaining residual. In float32 the rounding floor usually ends it first (the reading moved by at most \(1.6 \times 10^{-7}\) against marking to the bound). The mask is valid at every exit;selection_diagnostics()reportsouter_iterations,budget_reachedandresolved.Frozen solve. The \(k\) active functions gathered into a dense \(k \times k\) block and solved directly (
frozen_solver="gather", \(O(k^3)\)), or the masked full-size operator (identity off the mask) handed toift_linear_solvewith CG (frozen_solver="cg"). On the 128-point basis at \(k = 8\) the two agree to \(3 \times 10^{-17}\) in \(\max|\Delta c|\); the property claimed and pinned is agreement to the CG tolerance, \(10^{-10}\). Differentiation differs between them: the gathered path is plain reverse mode throughjnp.linalg.solve; only the CG path uses the implicit-function rule ofift_linear_solve[@Blondel2022]. A mask with more than \(k\) functions cannot be solved in the gathered block: a concrete one is refused with a message, a traced one poisons the block with NaN rather than silently dropping the excess.Full-basis gradient. A dense solve on \(A\), overriding the base default: the gathered solve holds exactly \(k\) functions and an all-true mask would be silently truncated.
Conditioning guard. The constructor bounds the relative solve error by \(\kappa(\hat A)\,\varepsilon_{\text{dtype}} + \kappa_h\,\varepsilon_{64}\) and refuses a configuration above
CONDITION_LIMIT\(= 10^{-3}\), naming both terms, the dtype that would carry it and the smallest mass that would. \(\kappa(\hat A)\) iscondition_estimate: the largest Ritz value of 64 Lanczos steps over the smaller of the smallest Ritz value and the closed-form Rayleigh quotient of the constant function, \(m\,h^d n / \lVert D W_n^{-1}\mathbf 1\rVert^2\) (periodic) – within 0.03% beloweigvalshover 70 configurations, 0.6 s at 4096 functions. \(\kappa_h = (\lambda_{\max} + m)/(\lambda_{\min} + m)\) of the grid operator is closed form (physical_condition_number): periodic \(\lambda_{\min} = 0\), \(\lambda_{\max} = 4d/h^2\); Dirichlet \(\lambda = d\,(2 \mp 2\cos \pi h)/h^2\). The first term is the node’s own solve (float32 sensor-reading error measured at 0.002 – 0.58 of it); the second the float64 formation of \(W_n^{\top} A_{\text{phys}} W_n\), in which the Laplacian annihilates the constant only through cancellation (float64 error measured at 0.005 – 0.25 of it). The periodic operator’s smallest eigenvalue is \(m\), so both grow like \(1/m\): float32 refuses a periodic \(m\) below about \(2 \times 10^{-3}\) (1-D) to \(4 \times 10^{-3}\) (3-D, order 6), float64 below about \(10^{-8}\) (128 points) to \(6 \times 10^{-8}\) (256 points). Unchecked, float32 on 128 points read \(J = 6.7 \times 10^5\) against \(1.77 \times 10^5\) at \(m = 10^{-6}\) and \(-2.9 \times 10^{16}\) at \(m = 10^{-8}\), silently.What the bound covers. \(\kappa\varepsilon\) models a direct solve – the gathered block and the dense full-basis gradient. The masked CG (
frozen_solver="cg") stops at a relative residual ofrtol(\(10^{-6}\) in float32, \(10^{-10}\) in float64), which \(\kappa\) amplifies in the same way, so on that path the first term is \(\kappa(\hat A)\max(\varepsilon, \texttt{rtol})\): float32 refuses a periodic \(m\) below about \(2 \times 10^{-2}\), float64 below about \(2 \times 10^{-6}\). That bounds the accuracy of a CG solve that converges; it does not say whether CG converges. Measured on the 1-D periodic node at the full budget (jaxlib 0.11.0; lineax 0.0.7 and 0.1.1 alike), CG stagnates or breaks down below its tolerance at conditionings the bound accepts – float32 at 256 points already at \(m = 0.3\) (\(\kappa = 65\)), at 64 or 128 points at \(3 \times 10^{-2}\); float64 at 128 or 256 points at \(10^{-5}\) – and 4x or 16x the step budget does not help, so no condition-number-versus-max_stepsbound predicts it. The failure is loud, never a wrong number: on an eager call (the cold start ininitial_state, a diagnostic) the node re-raises it as aValueErrornaming \(\kappa\), the tolerance, the budget and the fixes (frozen_solver="gather", float64, a larger mass); under a trace it surfaces from lineax when the step executes. The bound is also conservative for the"level"and"dk"preconditioners: periodic \(m = 3 \times 10^{-3}\) in float32 is refused under both (bound \(1.4\) – \(1.9 \times 10^{-3}\)) while the full-basis solve is measured \(1.5 \times 10^{-4}\) ("level") and \(8 \times 10^{-5}\) ("dk") off, about 12x inside the limit; the default"hybrid"accepts it (bound \(7.7 \times 10^{-4}\)). The limit is not loosened per preconditioner on three measured points.
Implementation Mapping#
Equation Term |
Implementation |
Notes |
|---|---|---|
Basis synthesis \(u = W_n c\) |
|
Lifting scheme, matrix-free; |
Analysis \(c = W^{-1} u\) |
|
Exact inverse of the lifting scheme |
Dirichlet basis |
|
Dense, walls pinned to zero, tensor product in multi-D |
Operator assembly \(A = W_n^{\top} A_{\text{phys}} W_n\) |
|
NumPy, once per node; returns the dense \(A\), a sparse copy, \(W_n\), levels |
Diagonal scaling \(D\) |
|
Hybrid Jacobi by default; Jacobi, level-mean and Dahmen–Kunoth selectable |
Source \(f(x;\theta,\sigma)\) |
|
Reads |
Right-hand side \(b = h^d W_n^{\top} f\) |
|
Recomputed on every call, so the tangent flows; every parameter leaf cast to the node’s dtype first |
Active set \(M\) by CDD |
|
|
Rounding floor \(\tau\) and the index-order tie band |
|
$16,\varepsilon(\max |
\(\kappa(\hat A)\) |
|
Lanczos (64 steps, full reorthogonalisation) plus the periodic constant-mode Rayleigh quotient; computed by |
\(\kappa_h\) of the grid operator |
|
Closed form of the central-difference spectrum |
Solve error bound vs |
|
\(\kappa(\hat A)\varepsilon_{\text{dtype}} + \kappa_h\varepsilon_{64}\), with \(\max(\varepsilon, \texttt{rtol})\) on the masked-CG path; the constructor refuses above \(10^{-3}\) |
Frozen solve \(A_M c_M = b_M\) (gathered) |
|
Dense \(k \times k\) block; JAX primitive |
Selection diagnostics (iterations, budget reached) |
|
Host-side; |
Frozen solve (masked CG) |
|
Identity off the mask; |
Non-converging masked CG named |
|
Eager calls only: lineax’s error re-raised as a |
\(c_j = 0\) for \(j \notin M\) |
|
Inherited: the base class zeroes off the mask after every solve |
Sensor functional \(J = W_n[s,:]\, c\) |
|
Nearest grid point to |
Full-basis gradient \(\nabla J_{\text{full}}\) |
|
Dense solve on \(A\); overrides the base default |
Baked constants and their provenance |
|
|
Assumptions and Simplifications#
Unit cube, uniform dyadic grid, constant coefficient \(m > 0\): the operator is assembled once from the structural settings and
mass, somassisParamSpec(trainable=False). Variable coefficients are not supported.The source is the isotropic Gaussian above (periodised on a periodic domain) unless
source_fieldis overridden;thetaandsigmaare the trainable leaves.\(\text{seed} \le k \le n_{\max}\), validated at construction, where the seed is every level-0 function – the coarse block plus the first detail band, \((2 n_c)^d\) periodic or \((2 n_c + 1)^d\) Dirichlet, counted from the assembled basis – so the CDD seed fits in the gathered buffer and the set never exceeds it. The default \(k = \min(n_{\max}, \max(\text{seed}, 8, n_{\max}/16))\) always satisfies the bound. \(k = n_{\max}\) turns adaptivity off.
The base-class assumptions: the returned gradient is exact within a region of constant \(M\) and ignores the set’s dependence on \(\theta\) (
MADD-ANO-003); the objective is a scalar function of \(c\).The sensor is read at the nearest grid point, not interpolated.
The solve’s relative error bound \(\kappa(\hat A)\varepsilon_{\text{dtype}} + \kappa_h\varepsilon_{64}\) (\(\max(\varepsilon, \texttt{rtol})\) in place of \(\varepsilon\) on the masked-CG path) is at most
CONDITION_LIMIT\(= 10^{-3}\), validated at construction. It bounds a solve that completes, not whether the masked CG converges (see Discretization).Structural counts (
dim,n_levels,n_coarse,order,k) are whole numbers – an integral float from a JSON round trip is accepted,6.9orTrueis refused – andupdate/selection_diagnosticsrefuse a parameter key the node does not have.
Validated Physical Regimes#
Parameter |
Verified Range |
Notes |
|---|---|---|
Grid, order of accuracy |
16 – 256 points (1-D), \(8^2\) – \(32^2\) (2-D) |
MMS order 2.000 on the 1-D periodic ladder 16/32/64/128/256; 2.000 on the Dirichlet ladder 23 – 191 and 2.022 on the 2-D periodic ladder \(8^2\) – \(32^2\) ( |
Grid, adaptive solve and frozen gradient |
up to 256 (1-D), \(64^2\) (2-D), \(16^3\) (3-D); Dirichlet \(23^2\) and \(7^3\) |
At every size: the seed fits the default budget, the gathered solve equals the masked dense solve to \(10^{-12}\), |
Default budget vs seed |
all default |
The seed (level 0) is inside |
Budget \(k\) |
\(n_{\max}/16\) (default) – \(n_{\max}\) |
At \(k = n_{\max}/16\) on 128 points the sensor reading is within \(4.5 \times 10^{-4}\) (relative, float64) of the full-basis one over \(\theta \in \{0.04, 0.30, 0.42, 0.50, 0.92\}\) and never changes sign ( |
Gradient-capture ratio |
0.99 – 1.01 |
\(\theta = 0.42\), 1-D 128 points, 2-D \(16^2\), 3-D \(8^3\), Dirichlet 95 points: the local basis reproduces the full-basis gradient at the default budget, so the cold-start check passes silently |
|
\(1.5 \times 10^{-9}\) (θ), \(4 \times 10^{-11}\) (σ) relative |
Active set asserted unchanged across the step; through |
Order of the basis |
4 |
2 and 6 construct and round-trip; the operator’s order stays 2 regardless |
Known Limitations and Failure Modes#
Jump discontinuities at active-set switches (
MADD-ANO-003), inherited. At the default budget the ratio is ≈ 1, so the omitted first-order term is small, but a step across a switch still misses a jump.The adaptive solution is a truncation. At a small \(k\) the sensor reading can be off by more than the \(10^{-2}\) measured at \(n_{\max}/16\); the error is not monotone in \(k\).
massandsensorare baked. Changing either on a built node leaves the operator and the sensor row stale; rebuild the node.compile()refuses a graph that unfreezes them.Steady only.
updateignoresdtandboundary_inputsbecause the base class’s hooks do not receive them, so the node is an edge source and cannot time-step or consume an edge; a time-dependent wavelet node needs the freeze question in the developer guide answered.Dense operator. \(A\) is an \(n_{\max} \times n_{\max}\) constant of the step; sizes beyond the validated range need a matrix-free assembly.
Cost per update is \(O(30\,k^3 + 30\,\mathrm{nnz})\) for the selection plus one gathered solve, whether or not the budget is reached early.
Singular at \(m = 0\) and ill-conditioned like \(1/m\) near it (periodic): the constructor refuses a non-positive
mass, and one whose solve error bound exceedsCONDITION_LIMIT– in float32 a periodic \(m\) below about \(2 \times 10^{-3}\) (1-D) to \(4 \times 10^{-3}\) (3-D, order 6); in float64 below about \(10^{-8}\) to \(6 \times 10^{-8}\), where the float64 assembly is the limit. Build in float64 for a smaller mass; below the float64 limit no dtype carries the operator.The iteration bound, not the budget, ends the selection for \(k \gtrsim n_{\max}/2\) (in float64; in float32 the rounding floor usually does,
resolved). Measured on 128 points: \(k = 64\) and \(k = 96\) both stop at \(|M| = 54\) after 30 iterations (200 reach 64); the sensor error there is \(3 \times 10^{-11}\), so the objective is unaffected, butbudget_reachedisFalse. The bound is deliberately not raised – each iteration is a \(k \times k\) solve on every update; if the budget matters,selection_diagnostics()says whether it was met.A mask larger than \(k\) is not a valid input to the gathered solve. The node never produces one (the seed is validated, the marking is capped); one supplied from outside is refused eagerly and NaN-poisoned under
jit.frozen_solver="cg"accepts any mask.The masked CG may not converge where the bound accepts. In float32 it stagnates at 256 points from \(m = 0.3\), and at 64 or 128 points at \(3 \times 10^{-2}\); in float64 at 128 or 256 points at \(10^{-5}\) (full budget, periodic). More steps do not help. An eager solve raises a
ValueErrornaming the conditioning and the fixes; under a trace the error comes from lineax.frozen_solver="gather"(the default) carries every configuration the bound accepts.The selection is deterministic under rounding but still discrete. It changes where two residual magnitudes differ by about the rounding floor or the Dörfler bulk falls on a cumulative sum – isolated parameter values, not every rounding perturbation – and a gradient step across one misses a jump (
MADD-ANO-003). Where ties are broken, the lower basis index is kept, so a mirror-symmetric problem can get a set that is not mirror-symmetric; it is the same set on every evaluation path.
Stability Conditions#
The frozen solve is a linear solve on a symmetric positive-definite block;
its conditioning is that of \(\hat A_M\), bounded by \(\kappa(\hat A)\) above
(eigenvalue interlacing), and the constructor refuses an operator whose
solve error bound exceeds CONDITION_LIMIT (see Discretization).
There is no time-stepping and no stability limit.
State Variables#
Field |
Shape |
Units |
Description |
|---|---|---|---|
|
|
problem-defined |
Wavelet coefficients of \(u\) (physical, \(u = W_n c\)); zero off the active set |
|
|
— |
Boolean active set the current |
No extra state: nothing can carry a stale per-mode value across a change of active set.
Parameters#
Parameter |
Type |
Default |
Units |
Description |
|---|---|---|---|---|
|
int |
1 |
— |
Spatial dimension (structural) |
|
int |
6, 2 |
— |
Refinements and coarse points per axis (structural); together with |
|
int |
4 |
— |
Interpolating order, one of 2, 4, 6 (structural) |
|
int |
|
— |
Active-set budget (structural); |
|
float |
0.42 |
— |
Source centre on axis 0; trainable, bounds \((0, 1)\), logit |
|
float |
0.10 |
— |
Source width; trainable, positive, log |
|
float |
1.0 |
— |
\(m > 0\); |
|
tuple of float |
|
— |
Sensor location, snapped to the nearest grid point – on the circle for a periodic axis ( |
|
str |
|
— |
|
|
str |
|
— |
|
|
str |
|
— |
|
base-class settings |
— |
— |
— |
|
Every entry is stored in self.params, so cls(name=..., timestep=..., **node.params) rebuilds the node; n_max is derived and not stored.
Boundary Inputs#
Field |
Shape |
Default |
Description |
|---|---|---|---|
— |
— |
— |
None. The node is an edge source only (see Known Limitations 4) |
References#
[@DeslauriersDubuc1989] Deslauriers, G., Dubuc, S. (1989). Symmetric iterative interpolation processes. Constructive Approximation, 5(1), 49–68. — The interpolating subdivision the basis is built from.
[@CohenDahmenDeVore2001] Cohen, A., Dahmen, W., DeVore, R. (2001). Adaptive wavelet methods for elliptic operator equations: convergence rates. Mathematics of Computation, 70(233), 27–75. — Bulk chasing on the residual; the selection rule.
[@Doerfler1996] Dörfler, W. (1996). A convergent adaptive algorithm for Poisson’s equation. SIAM Journal on Numerical Analysis, 33(3), 1106–1124. — The bulk marking criterion inside the selection.
[@DahmenKunoth1992] Dahmen, W., Kunoth, A. (1992). Multilevel preconditioning. Numerische Mathematik, 63(1), 315–344. — The level-based diagonal scaling (
preconditioner="dk") and why a diagonal scaling suffices.[@Blondel2022] Blondel, M. et al. (2022). Efficient and modular implicit differentiation. NeurIPS 35. — The implicit-function rule used by
ift_linear_solveon the masked-CG path (frozen_solver="cg"); the default gathered path is plain reverse mode throughjnp.linalg.solveand gives the same adjoint.[@Roache2002] Roache, P. J. (2002). Code verification by the method of manufactured solutions. Journal of Fluids Engineering, 124(1), 4–10. — The order study in
MADD-VER-014.
Verification Evidence#
MADD-VER-014(manufactured solution): observed spatial order over the finest pair of the 16/32/64/128/256 periodic 1-D ladder within \([-0.25, +1.0]\) of the declared 2 (measured 2.000); the Dirichlet (23 – 191) ladder measures 2.000 and the 2-D (\(8^2\) – \(32^2\)) one 2.022 in the same test module, and a mis-specified source is shown to fail the gate.MADD-VER-015(regression against the full-basis solve): adaptive sensor reading within \(10^{-2}\) of the full one and of the same sign across the source sweep at \(k = n_{\max}/16\); full budget equals the dense solve to \(10^{-10}\).Test files:
tests/nodes/adaptive/test_wavelet_node.py(the seven-item author-facing contract, traceability, gradients with the active set held fixed, round trips, graph integration, the explicit-float32 dtype measurement, the seed-fits-the-budget pins in every dimension and both boundaries, refusal of an oversized set, construction inside a trace, the selection diagnostics, the sensor at the periodic seam, \(64^2\) and \(16^3\)),tests/nodes/adaptive/test_wavelet_engine.py(transforms, assembly and its symmetry check, conditioning, both frozen solves, the NaN poison on an oversized mask, CDD and its iteration count),tests/verification/test_wavelet_mms_order.py(order studies and the two benchmarks).1.1.0 pins (
test_wavelet_node.py,test_wavelet_engine.py): the auditor’s float32 masses are refused naming the cause, the dtype and the mass that work, with no warning; every accepted mass reads a float64 FFT solve toCONDITION_LIMITin float32 and float64; the estimate againsteigvalshand the closed form against the grid spectrum; eager, jit and graph select identical sets on the mirror-symmetric problems; a 1e-6 \(\theta\) sweep does not switch set; four spellings give identical source bits and sets; the periodic source is translation-invariant across the seam; index-order ties at the cap; non-integral counts and unknown keys refused;selection_diagnosticshonours itsparams.Masked-CG conditioning (
test_wavelet_cg_conditioning.py): the CG bound uses the CG tolerance and refuses what it cannot carry while the gathered solve accepts it; a converged CG reading is within the bound; a non-converging eager CG solve is refused with the node’s message (never lineax’s), and only lineax’s convergence errors are translated.
Changelog#
Version |
Date |
Change |
|---|---|---|
1.0.0 |
2026-09-21 |
Ported onto the 0.4.0 |
1.1.0 |
2026-09-24 |
Phase-3 audit: conditioning guard ( |
1.0.1 |
2026-09-22 |
Merged-tree audit: |