Skip to content

docs/doxy rewrite - #46

Merged
satfra merged 40 commits into
mainfrom
docs/doxy_rewrite
Oct 12, 2026
Merged

satfra merged 40 commits into
mainfrom
docs/doxy_rewrite

Conversation

@satfra

@satfra satfra commented Oct 12, 2026

Copy link
Copy Markdown
Owner
  • Fix physics function bugs found in the docs audit
  • docs: fix math and diagram rendering, hide internals
  • docs: rewrite the physics API reference
  • doc fixes and minor bugfixes
  • Fix timestepping/common bugs found in the docs audit
  • docs: rewrite the timestepping and common API reference
  • IDA: honour implicit/minimal_dt and maximal_dt; drop implicit/max_steps
  • Timesteppers refuse variables and adaptors they cannot handle
  • ImplicitEuler/TRBDF2: step control from local error estimates
  • Euler/TRBDF2 steppers interpolate their frames onto the output grid
  • IDA steppers: carry all spatial state through mesh adaptation
  • CLI overrides and ConfigTree getters: keep types, never fall back silently
  • Root finders: use the seeded exponent, restartable search, bisection fix
  • QuadratureProvider: fix cache lookups
  • CSV: keep infinities, make header names round-trip
  • timestepping/common fixes
  • Fix the LLF and KT dissipation sign and the LDG constant-levels
  • Fix minor discretization bugs
  • HAdaptivity: don't coarsen right behind a refinement front
  • CG: assemble from the constrained solution; constrained dofs are algebraic
  • Tests: hide the hybrid-stepper scans, trim the zero-dim KT Gaussian
  • Remove dead code, move discretization internals into internal::
  • docs: rewrite the discretization and model API reference
  • discretization fixes
  • Mesh default axis, size-checked HDF5Input::load_map, CsvOutput memory
  • KT: mesh adaptation in 1D
  • IDA steppers: own IDADriver instead of deal.II's SUNDIALS::IDA
  • IDA: continue warm across mesh adaptations
  • HAdaptivity: slope prolongation for KT, threshold marking
  • IDA: change the Jacobian to a new alpha instead of reassembling it
  • HAdaptivity: tolerance marking and a default curvature indicator
  • ONfiniteT batched model: non-negative refinement indicator
  • HAdaptivity: tolerance marking by default
  • HAdaptivity: predict, new defaults, adaptor state in snapshots
  • HAdaptivity: coarsen_age 10 by default
  • Tutorials, API overview: adaptivity keys of the tolerance marking
  • documentation fixes
  • update ONfiniteT example: split into two dirs (one batched, one standard)

satfra added 30 commits October 10, 2026 13:23
- SplineInterpolator1D(Stack): the second-derivative corrections were
  applied to the wrong grid points (lower/upper swapped).
- BosonicRegulator::dq2RB (and hence dq2RF): wrong derivative.
- dddcothS: missing factor 1/T.
- TFLitimSpatial::F<3>: cosh argument x/(2T) instead of x/T.
- PolynomialExpRegulator: Kokkos::exp instead of std::exp (device code).
- MathJax 3 with SVG output: MathJax 2's HTML-CSS output was misaligned
  (inline math raised) inside the pydata theme.
- Class diagrams via Graphviz as SVG, inverted in dark mode; drop the
  include, collaboration and directory graphs.
- Hide private members and all `internal` namespaces from the reference.
Integrators, interpolators, regulators, thermodynamic and threshold
functions now document what they compute (corrected formulas: the
vacuum integrators integrate dq q^{d-1} up to sqrt(x_extent) k, not
dp^2 p^{d-2} to infinity), the kernel interface for get/map, config
keys, setters, valid dimensions, and short usage examples. All math
uses Doxygen's \f$ / \f[ delimiters.

Small code changes alongside:
- include optimize.hh where optimize_x_extent is used;
- rename constructor parameters typical_E -> k where they set k; the
  Integrator_fT_p2* constructors now also start their radial cutoff at
  that k (default 1, so ConfigTree-built integrators are unchanged).
Timestepping:
- IDA callbacks: deal.II 9.7 callbacks return void, so the int returned on a
  recoverable failure was discarded and IDA carried on with garbage. They now
  throw RecoverableUserCallbackError (internal::throw_on_ida_failure). The
  FE-only residual also catches model exceptions like the other runs.
- Newton matrix of the variables was alpha - dD/dv instead of alpha + dD/dv.
- BoostRK/BoostABM skipped the frame at the final time (0.3/0.1 -> 2).
- ImplicitEuler wrote the Newton residual into the variables slot and kept a
  stale Jacobian after mesh adaptation.
- Newton ran max_steps + 1 iterations; RK divided by zero for output_dt < dt
  and missed the final frame; KINSOL statistics were uninitialised.
- Copy-pasted error messages, undeclared update_variables().

Common / model:
- def::AD::jacobian_variables did not compile for pure-variables models and
  used the wrong loop bound for the extractor Jacobian.
- GSLMinimizer1D: unchecked bracket setup, memory leaks, dead "stuck" check;
  GSLSimplexMinimizer leaked its step vector.
- ExternalDataInterpolator clamped wrongly when all x < 0.
- CubicSpline is no longer copyable (double free); undefined
  Barycentric::check_consistency removed.
- compute::conj return types, GLQuadrature static_assert that never fired,
  empty Polynomial underflows, make_folder(""), to_string_with_digits,
  uninitialised Quadrature/QuadratureType members, powr used before its
  declaration in complex_math.hh, dead concepts and commented-out code.
- *_or_warn getters now also warn when a present key can not be read.
- The generated parameter file now uses the defaults the code uses.
Timestepping: an overview in abstract_timestepper.hh with the equations
solved, a table for choosing a stepper (what it integrates, step control,
mesh adaptation, MPI) and every /timestepping key with its real default and
the steppers that use it. Each stepper, linear solver, Newton/KINSOL and the
step controllers now state what they compute; wrong statements (Boost ABM
"fixed steps", inverted Newton threshold, KINSOL doc copied from Newton, ...)
are fixed.
deal.II's IDA wrapper never set step-size bounds, so both keys only fed the
sanity checks. The deal.II patch now adds a custom_setup(ida_mem) hook, run
after every (re)initialization, through which all IDA steppers set
IDASetMinStep/IDASetMaxStep. Against an unpatched deal.II this is detected at
compile time and skipped with a one-time warning; the dependency bundle has to
be rebuilt for the bounds to take effect.

implicit/max_steps never had an effect (the patch sets IDASetMaxNumSteps to
LONG_MAX) and is removed from the code, the generated parameter file, tests
and examples.
ImplicitEuler, TRBDF2 and ExplicitEuler integrate only the FE/FV part and
silently kept the variables at their initial values; they now throw when the
flow has variables. BoostRK, BoostABM and RK accepted a mesh adaptor but
never called it; they now throw when given one other than NoAdaptivity.
Both steppers chose their step from Newton's convergence measure, so
abs_tol/rel_tol never controlled the time discretization error. They now
estimate the local error, damped in stiff directions with the Newton matrix
(Shampine's filter, no extra factorization):
- ImplicitEuler: (M + dt J) e = dt/2 (R(u_n) - R(u_{n+1}));
- TRBDF2: the embedded estimate of Bank et al. (1985).
A step whose weighted RMS error exceeds 1 is rejected and retried with a
smaller step (accepted regardless at minimal_dt).

TRBDF2's BDF2 stage is divided by (2 - gamma), so both stages have the
Newton matrix M + gamma dt J / 2; before, the shared Jacobian was off by a
factor sqrt(2) in the second stage.

Newton now tests convergence on the weighted RMS of its update (<= 0.1 of
the step tolerance) instead of the mass-weighted residual, whose size
depended on the mesh and left errors far above the tolerance.
ExplicitEuler, ImplicitEuler and TRBDF2 wrote a frame at the first step past
each multiple of output_dt, so frame times drifted with the step size. They
now write frames exactly at t_start + n output_dt, interpolated within the
step that reaches them: linearly for the Euler steppers, quadratically
through u_n, u_gamma, u_{n+1} for TRBDF2 (second order, like the method).
AbstractAdaptor::adapt_at(t, sol, also_transfer) adapts and moves further
vectors on the same mesh along with sol; HAdaptivity transfers them in one
SolutionTransfer pass. On a refinement:
- the IDA hybrids transfer their spatial bracket, pending state and
  checkpoints, which kept the old size and were read by the next
  dt_variables call;
- all IDA runs transfer sol_dot instead of zeroing it, so IDA's
  consistent-IC solve starts from the previous derivative;
- the plain IDA runs also re-initialize their solution views, and the run
  with variables updates its block sizes and n_FE_dofs.

The hybrids' variable buffer is pruned behind each output frame (nothing
earlier is queried again) and searched by bisection; it used to grow with the
run and be scanned linearly.
…ently

ConfigTree:
- get_X(key, def) and get_X_or_warn(key, def) fall back to def only for a
  missing key; a present entry that cannot be read as X throws. Before, e.g.
  fe_order = 2.0 silently became the default.

ConfigurationHelper:
- -sd/-si/-sb/-ss keep the type of an existing entry (an integer entry takes
  integral values from -sd or -si; booleans need -sb, strings -ss), values
  must parse completely, -sb takes true/false/1/0 in any case, and -ss splits
  at the first '=' only.
- A key that is not in the parameter file is created with the flag's type,
  with a warning naming it.
- --generate-parameter-file writes TOML; --gen-toml/--gen-json choose the
  format. The file is named like the parameter file, written by rank 0 only,
  never replaces an existing file, and -p may come anywhere on the line.
- append(), which never applied anything, is removed; the class is an
  ordinary copyable value (its copy constructor always threw).
ScalingRootFinder:
- seed_exponent(beta) had no effect on the search. With a seed, the
  convergent model is fitted from two points when the three-point fit is not
  yet available or not valid, which saves an evaluation when no divergent fit
  stands in.
- search() starts from scratch on every call (only the configuration is
  kept); a second call used to continue from the first one's samples.
- The deep-scaling approach step uses approach_factor instead of a hard-coded
  0.1.

BisectionRootFinder converges on the bracket width and returns the smallest
point known to succeed. It used to throw whenever the last probe failed and to
return unconverged when the last probe succeeded early.
- A key slightly above every cached key was never matched: lower_bound
  returns end() there and the predecessor was not checked, so a new rule was
  built instead. Both neighbours are now checked independently.
- Keys match to a relative 1e-6. is_close(a, b, 1e-6 T) used 1e-6 T as both
  the absolute and the relative tolerance (1e-3 relative at T = 1000), and
  never matched a negative key, whose entry was then re-initialized on every
  lookup.
- inf and -inf are read as infinities instead of NaN; ExternalDataInterpolator
  now drops rows with any non-finite value.
- write_csv_header turns line breaks in names into spaces (the reader splits
  records at line breaks) and quotes names that start with the comment
  character, which used to turn the whole header line into a comment.
- ExplicitEuler, ImplicitEuler and TRBDF2 also write a frame at the final
  time when it is not on the output grid (OutputGrid::finish).
- Newton decides on a Jacobian rebuild from the residual reduction of the
  previous iteration.
- The local-error solve of ImplicitEuler/TRBDF2 asks for 1e-3 relative
  accuracy instead of abs_tol, which an iterative solver may not reach.
- def::LLFFlux added -alpha/2 (u_n - u_s) to every Cartesian component of
  the numerical flux instead of -alpha/2 (u_n - u_s) n_d.
- The KT numerical flux had the same defect, H.n = avg.n - (a.n) du / 2:
  anti-dissipative on every lower boundary face. The dissipation is now
  a_d n_d du / 2 per direction, in the residual and the jacobian.
- LDG: a level declared constant was built as
  j_gu[k] u even if a level below it was nonlinear, and j_gu[k] was never
  rebuilt.
- ComponentDescriptor: couplings between non-adjacent levels are the
  closure of the adjacent ones, independent of the order of add_dependency
  calls; jacobians_constant(k, j) is true iff all levels in between are.
Mesh and coordinates
- HAdaptivity never refines past adaptivity/level and never coarsens below
  the initial mesh.
- LogarithmicCoordinates1D::backward_derivative: denominator b + y - c.
- Linear/LogarithmicCoordinates1D reject grid_extent < 2 (and bias == 0).
- to_string() of all 1D grids and Matsubara values: grids differing below 1e-6
  no longer share an HDF5 coordinate key.
- CoordinatePackND::operator== is false for packs of different axes
  instead of failing to compile.

FV
- TVDReconstructor::compute_gradient_at_point compares the point with the
  cell centre up to rounding, so a source point at the centre gets the
  limited slope even if FEValues and the cell disagree in the last bit.
- van Albada: sign test and denominator without underflow (float and tiny
  slopes gave 0 or 0/0).

Model / assembly / output
- Readouts locate their EoM with EoM_postprocess, as extract() does.
- The readout raw potential (a global solve per output frame) is skipped
  if no sink writes it and the model opts out with readouts_use_potential
  = false (and its extract() does not read it).
- OutputSession(config) without /output/folder writes to ./ like
  OutputPath(config) instead of a temporary folder deleted at exit.
- FlowingVariables::variable_data() throws if there are no variables.
- ADjacobian_extractors<2> checks the hessian size.
- 1D EoM root polish: moves the reconstructed minimum onto the zero
  of the EoM field near it, picking the candidate with the lowest
  potential, so it never jumps to a metastable minimum.
Coarsening re-interpolates the solution onto the parent cell. Right behind a
moving front that overshoots: in ONfiniteT (CG) the flat, convexity-restoring
region sits ~1e-8 above the propagator pole m2 = -k^2, the interpolated
state crossed it, and IDA's restart failed with a NaN residual at t ~ 3.5.

A cell is now never coarsened if it lies within
/discretization/adaptivity/coarsen_buffer (default 2) layers of active
neighbours of a cell flagged for refinement, or if one of the last
/discretization/adaptivity/coarsen_age (default 2) adaptations created it.
ONfiniteT CG now runs to t = 5 and follows a uniform mesh of the same
resolution.
…braic

The timesteppers never apply the hanging-node (and model affine) constraints,
and the constrained rows of the residual are empty, so after a refinement in
2D/3D the hanging-node entries of IDA's vector stayed at their transferred
values while their masters evolved -- and the neighbouring cells read them.

- CG mass, residual and jacobians, the refinement indicator, the readouts and
  output, and the variables' residual read u with the constraints applied
  and u_dot with their homogeneous part (D(u + u_dot) - D(u), D affine).
- get_differential_indices excludes constrained dofs: they are fixed by the
  constraints, not by an equation.
- HAdaptivity applies the constraints before transferring vectors (that
  hunk went into e7abe1f).
- sundials_ida_boost: the six cur_dt / coupling-mode scans (46 full runs that
  assert nothing and only print errors) are hidden ([.][scan]); run them with
  "[scan]". The seven gating cases are unchanged. 125 s -> 18 s.
- zero_dim_2d_kt gaussian: drop the 400x400 run and the separate EoM case;
  the 40x40 matrix of 16 scenarios checks the same observables (including
  that scenario). The model skips the readout raw potential, which only the
  output would read. Still [slow]. 429 s -> ~85 s.
Removed
- AbstractModel::mass(m_ij, x).
- ADjacobian_variables::jacobian_variables<1> (never called; <0> asserts).
- SubDescriptor::names_tuple, ComponentDescriptor::get_component_block_structure.

Renamed
- GridAxis::get_stepwiths -> get_step_widths,
  ConfigurationMesh::get_step_withs_for_triangulation ->
  get_step_widths_for_triangulation.

Moved into internal::
- la_policy.hh free functions, ParallelDoFs, serial_mirror, MeshIsRectangular.
- batch.hh: PointState, PointJacobian, FaceJacobian, SeedStackWorkspace,
  batch_terms, contains(Term, Term) and the evaluate_* dispatchers. Models
  keep PointBatch, LDGPointBatch, BatchOutput, Term, batch_tie.
- CG/DG i_tie, Variables fe_tie, def::BoundaryStencilDetail.
- HDF5 frame/writer types and the output timing helpers.
- FV::KurganovTadmor::Assembler: helpers private; public are the
  AbstractAssembler interface, the readout/extractor hooks the core calls and
  the few members the KT unit tests use.
- Constrained solution: internal::ConstrainedSolution applies the constraints
  once per entry point (u and C u_dot) and copies nothing when there are no
  constraints; replaces the constrained()/constrained_dot() pair, which made
  three copies and three distribute() calls per evaluation.
- LDG: a level declared constant in the level below, while a lower level is
  not constant, reuses its own jacobian; that reuse is now checked against the
  assembled level on first use as well (the docs promised it). Test added.
- EoM polish: locate the polished point with the mapping, as everywhere else.
- Cleanups: jacobian_variables without the leftover block, one shared
  variables_block() helper, clearer names and comments in HAdaptivity and KT,
  no stale diagnostic prints in the o2 tests.
- A missing /discretization/grid/{x,y,z}_grid falls back to the axis that
  ConfigurationMesh::get_defaults() writes (GridAxis::get_default_range())
  instead of a different hard-coded "0:0.1:1".
- HDF5Input::load_map takes the buffer as std::span<double> and throws if its
  size differs from the map's; the raw-pointer overloads remain, deprecated.
  Examples and tut4 pass their vectors directly.
- CsvOutput keeps only the current frame's values instead of every value
  ever written.
- Drop the declared-but-undefined FEOutput() default constructor.
- The KT assembler finds the active cell across a face also when it is
  refined (FV::KurganovTadmor::internal::active_face_neighbor: its child at
  the face, 1D). Stencils, face descriptors, traces and the jacobian sparsity
  all go through it; the reconstruction and diffusion gradients already use
  the true cell centres.
- refinement_indicator: the model's cell_indicator at each cell centre, with
  the cell average and first/second derivatives from the averages of the cell
  and its neighbours, times the cell width.
- Assembler::supports_mesh_adaptation (true in 1D); HAdaptivity refuses at
  compile time an assembler that sets it false, i.e. KT in 2D.
- Test kt_adaptivity: on a 1D mesh with level jumps in both directions the
  KT residual is conservative, no less accurate than on the unrefined mesh,
  and its jacobian matches finite differences; HAdaptivity refines where the
  indicator is large and conserves the integral of u.
- Examples/ONfiniteT: KT.cc uses HAdaptivity, model_KT.hh gets a
  cell_indicator; both models use |m2''| (the CG one was signed, which the
  fixed-fraction marking does not expect).
The IDA timesteppers drive SUNDIALS IDA through DiFfRG::IDADriver
(timestepping/ida_driver.hh), which follows deal.II's interface but
- bounds the step size to [minimal_dt, maximal_dt] directly (the deal.II
  patch for step bounds, ida_step_bounds.hh, is no longer needed);
- uses its own Newton convergence test that measures the rate in every step
  instead of carrying it over;
- exposes IDA's statistics and error test vectors.
The default implicit minimal_dt is 1e-14.
After a restart (mesh adaptation, tolerance refresh) IDADriver now restores
IDA's step history instead of starting over at order 1 with IDACalcIC: the
divided-difference vectors phi are handed to the adaptor (history()) and
transferred to the new mesh with the solution, IDA's step-control scalars are
saved and put back. Setting /timestepping/implicit/warm_restart = false
restores the cold restart.
KT (1D): the children of a refined cell now get the cell average plus a slope
times the offset of their centre, instead of the bare average. The slope is
that of the parabola through the cell's and its neighbours' averages (exact
child averages for a monotone quadratic profile), scaled down where needed so
that the children stay within the range of these averages.

New /discretization/adaptivity/marking = "threshold" (default "fraction"):
refine above refine_threshold * max indicator, coarsen below
coarsen_threshold * max. A mesh that resolves the solution is then kept, and
adaptations that only coarsen are carried out.
IDA asks for a new Jacobian F_u + alpha F_udot whenever a change of step size
or order moves alpha out of [0.6, 1.67] times its value at the last setup.
IDADriver now calls the optional update_jacobian_alpha for such requests,
which adds (alpha_new - alpha) * F_udot (AbstractAssembler::jacobian_mass, at
the state of the last assembly) and refactorizes. It still reassembles after a
Newton failure since the last assembly, inside IDACalcIC, and 5 steps after
the last assembly. JacobianAlphaUpdate implements it for
TimeStepperSUNDIALS_IDA without variables and both IDA hybrids.
/timestepping/implicit/reuse_jacobian = false turns it off.
marking = "tolerance" refines cells with an indicator above tolerance and
coarsens those below coarsen_tolerance (default tolerance / 8). With it, a
mesh that resolves the solution is kept, and refinement starts only where a
feature becomes unresolved. It replaces the relative "threshold" marking,
which always refined the cells near the largest indicator.

indicator = "curvature" uses eta_K = max_c h_K^2 |u_c''| / range(u_c), about
8 times the relative deviation of u from its linear interpolant on the cell.
It grows where a kink, shock or narrow extremum forms and drops by 4 per
refinement. FV: the parabola curvature of the KT assembler (cell_curvatures;
boundary cells take their neighbour's), FEM: DerivativeApproximation. With
the default indicator = "auto" it is used for models that define neither
cell_indicator nor face_indicator; AbstractModel's defaults now return
def::DefaultIndicator so that HAdaptivity can tell.
/discretization/adaptivity/marking now defaults to "tolerance" (1e-3);
refine_percent and coarsen_percent only apply with marking = "fraction".
Since the tolerance is absolute, a model's own indicator needs a known scale.

Tests about the transfer and prolongation, not the marking, keep "fraction".
tut3's indicator was signed; now its absolute value.
predict (default on) adds each cell's indicator growth since the previous
adaptation, so a forming kink is refined one adaptation earlier. Defaults now:
tolerance 3e-4, coarsen_tolerance tolerance / 8, coarsen_age 5 (counted in
mesh changes), adapt_dt 0.2.

Snapshots now also store the adaptor's state (AbstractAdaptor::save_state /
load_state; HAdaptivity: mesh-change count, cell ages, last indicators), in
an optional "adaptor" group. Without it, a restarted adaptive flow made
different coarsening decisions than the uninterrupted one.
At the default adapt_dt 0.2, coarsen_age 5 still coarsened the flat region of
fRG potentials that is refined again shortly after: tut7 CG needed 2507
instead of 1302 residual evaluations, DG 3306 instead of 2160, ONfiniteT
10-15% more. With 10, tolerance marking needs fewer residuals than fraction
marking in 4 of 5 measured cases (tut7 CG +12%) and still coarsens (ONfiniteT
meshes 12-17% smaller than without coarsening).
The tutorial parameter files list the current adaptivity defaults (adapt_dt
0.2, tolerance 3e-4) instead of refine_percent/coarsen_percent, and the
discretization overview describes the tolerance marking.
@satfra
satfra force-pushed the docs/doxy_rewrite branch from 8a22dc8 to cdb063b Compare October 12, 2026 16:23
…ialExpRegulator

Kokkos' overflow-safe complex division costs ~6 real divisions per reciprocal; conj(z)/|z|^2 needs one.
The regulator series sum_m x^m/m becomes Horner with compile-time reciprocals.
@satfra
satfra force-pushed the docs/doxy_rewrite branch from cdb063b to 6235ed2 Compare October 12, 2026 16:51
@satfra
satfra merged commit 67e3c65 into main Oct 12, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant