Release Notes
All notable changes to GeometricIntegrators.jl.
This package is pre-1.0, so every minor release is potentially breaking in the sense of SemVer for 0.x versions. The sections below name what actually changed, so that a compat-only bump can be told apart from a rename or a change in results.
Entries for 0.15.0 and later were written from the git history; the 0.4.0 – 0.11.0 entries below are the original release notes, kept verbatim. Versions 0.12 – 0.14 were never written up and remain a gap.
0.18.3
New Features
CGVINodal, a continuous Galerkin variational integrator on a nodal basis. WhereCGVIimposes the continuity of the trajectory across the interval boundaries weakly, through Lagrange multipliers added to the action,CGVINodalbuilds it into the basis, following [47]. The basis must be interpolatory with nodes at both ends of the interval — a Lagrange basis on Lobatto-Legendre nodes — so the coefficients are nodal values: the first is pinned to the knownqₙ, the last isqₙ₊₁, and the momentum is computed explicitly rather than solved for. That leavesD*(S-1)unknowns againstCGVI'sD*(S+1).Measured convergence order is 2s-2 in the position on Lobatto(s) nodes, the same as
CGVIon Gauss(s), and the two are of comparable accuracy at equals.The constructor rejects a basis whose nodes do not include the endpoints. The formulation reads
q(0)offX[1]andq(1)offX[S]rather than reconstructing them, so a Gauss basis builds fine and then integrates a wrong trajectory — the same class of silent errorDGVIP0already asserts against.The integrator comes from NonlinearIntegrators.jl, where it was the linear reference the network integrators were compared against, and is removed there in favour of this one.
Changes
CGVIandCGVINodalshare one core,src/integrators/cgvi/integrators_cgvi_common.jl, in the shape the DGVI family already uses: an abstractCGVIMethodsupertype carrying the traits, the solver defaults, the accessors,cgvi_coefficients,show, the cache, the initial-guess helpers,components_q!/components_v!/components_p!, and theresidual!/update!/integrate_step!entry points. Each variant supplies onlydescription,solversize,initial_guess!,components!,residual!andupdate!.CGVI's results are unchanged, bit for bit.CGVICacheis now shared by both variants. It lost thes̃field, which was allocated and never read, and takes the number of degrees of freedom and the solver size as constructor arguments. It is not exported and no user code should name it.CacheTypedeliberately stays a function of the method alone:CacheDicttype-asserts itsgetindexon it, and aCacheTypethat reads a value off the problem does not constant-fold, which leaves the cache inferred abstractly and makes the Newton hot path box on every stage access.Both variants now define
solversize, as every other implicit method here does. That is what sizes the nonlinear solution vector and whatinitsolverscales the defaultf_abstolby; previously the CGVI family took thesolversize == 0fallback.
Documentation
docs/src/integrators/cgvi.mdgains a section on the nodal formulation and the [47] reference.
0.18.2
Breaking Changes
Requires SimpleSolvers 0.12.1 and GeometricIntegratorsBase 0.6.3. Coupled as before: GeometricIntegratorsBase 0.6.3 requires SimpleSolvers 0.12.1, so neither range is satisfiable with the old pin on the other.
Of the three breaks in SimpleSolvers 0.12, one reaches this package: a
NonlinearSolverno longer emits line-search warnings from inside its iteration.solver_step!stopped callinglinesearch_warnings, so a rejected line search now reports to its caller through the returned status and to nobody at all if the caller drops it. Every solve here dropped it, because every solve here calledsolve!. That is what the change below is for. The other two — a new field onNonlinearSolverStatus, and aLinesearchMethodimplementingsolve_with_statusrather thansolve— do not reach a package that constructs neither.Bisection's two fixes in 0.12 (it no longer claims success when it cannot bracket, and it bisects toward a minimum rather than toward whichever root the value bracket held) reach no line search here:default_linesearchisBacktrackingand every method solves withNewton(). The one place this package does reach forBisectionis not a line search at all — PGLRK bisects the energy residual over λ throughSimpleSolvers.bisection. That call does take the no-bracket branch, on every step of everyλmax = 1E-300run in the test suite, and it is unaffected all the same: the endpoint returned there is bit-for-bit the one 0.11 returned (abs(y₀) ≤ abs(y₁) ? α₀ : α₁), the warning that accompanies it is gated behind theverbosity = 0thatsolve_λ!passes and predates 0.12 anyway, and the success flag that 0.12 corrected is the one thingbisectiondoes not hand back to its caller —solve_λ!re-derives the failure itself, fromabs(λ) ≥ λmax.
New Features
Every nonlinear solve goes through
solve_with_status!, and the status it returns is handed to GeometricIntegratorsBase 0.6.3'scheck_solver_status. Nineteen call sites: fourteenintegrate_step!methods across the RK, VI, CGVI, DGVI, DVI, HPI and SPARK families, PGLRK'senergy_residual!— a stage solve inside a step rather than a step, which is why it is counted apart — the three projection integrators, and DIRK's per-stage loop. It replaces the pair of commented-out stubs (# println(status(solver))/# println(meets_stopping_criteria(status(solver))), and the older# print_solver_status(int.solver.status, …)spelling) that had sat under the solve in most of those files.The fifteen that own a persistent
solverstate(int)use the state-taking form ofsolve_with_status!, added in SimpleSolvers 0.12.1 at GeometricIntegratorsBase's request; the other four have no state to reuse and take the state-building form. DIRK's per-stage solvers get aNullSolverStatefrom theSingleStageSolverswrapper, and aProjectionIntegratorhas nosolverstatefield at all.check_solver_statusis silent by default — SimpleSolvers remains the one voice that reports a failed solve, and warning here as well would say the same thing twice per time step. So no run changes what it prints. See GeometricIntegratorsBase's notes for what the hook is for and how to override it, and for why reading the status now costs nothing per solve.PGLRK is the one method where the status needed a decision rather than a rewrite. Its stage solves happen inside
energy_residual!, whichSimpleSolvers.bisectioncalls once per trial λ — including at the endpoints±λmax, where a solve that struggles is expected rather than exceptional. Passing every probe's status tocheck_solver_statuswould hand a caller who had overridden it a bisection probe to reject.solve_λ!checks the accepted λ once instead, reading its outcome off the persistent solver state that the finalenergy_residual!call leaves behind — which costs nothing and cannot describe a different solve than the one the step keeps. This is the one place here that still callsstatus(s, state)by hand, and it is sound because SimpleSolvers 0.12.1 states the property it relies on outright: nothing touches the state between the end of the solve loop and the caller, so rebuilding the status from it afterwards gives the same value. Its own test suite pins that.The seven
solve!calls undersrc/integrators/vprk/are deliberately left alone. That whole directory is dead: it is reached only throughsrc/integrators/VPRK.jl, whose everyincludeis commented out and which is itself never included. Converting it would imply it is live.
Tests
test/verification/pglrk_convergence_tests.jlcounts the statuses PGLRK hands tocheck_solver_statusand requires exactly one per time step. This is the one decision in this release that no other assertion could catch: the hook's default returns its argument, so calling it once per bisection probe instead of once per step is invisible to every other test in the suite, and would only surface for a caller who had overridden it to reject a non-converged step — who would then find bisection probes being rejected.solve_λ!has three ways through it, each with its owncheck_solver_statuscall site, and the test drives all three at 5 checks over 5 steps. Which route a run took is asserted and not assumed, from the count of fallback@debuglines captured off aTestLogger, because a hook count ofnstepsis what every route is supposed to produce and so cannot by itself tell them apart:route problem, λmaxfallbacks bisection locates a root Lotka-Volterra, default 0 bisection finds no sign change, step falls back to plain Gauss Lotka-Volterra, 1E-3005 early return, the unperturbed method already conserves the energy harmonic oscillator, 1E-3000 The third row needs a problem where plain Gauss conserves
hto theftolof the early return, which the harmonic oscillator is and Lotka-Volterra is not: Gauss preserves the quadratic invariant of a linear system exactly (|h − h₀| ≈ 7E-18 against ftol ≈ 1.8E-15), whereas on Lotka-Volterra at Δt = 0.1 it drifts to ≈ 6E-11 against ftol ≈ 3.6E-15, four orders too far. Note thatλmaxdoes not reach the early return, whose condition is on the λ = 0 residual alone — which is why the third row keeps theλmaxof the second: had the early return not been taken, the bisection would have run in [±1E-300] and fallen back on every step exactly as it does in row two, sonfallback == 0there is what says the bisection never ran at all.VSPARK(SPARKLobattoIIIBIIIA(2))accepts either outcome, which is what CI on Julia 1.13 and nightly was failing on. That case raisesSingularException: Zero pivot found at index 25on 1.13 and nightly under Linux and Windows, while returning a ~1E-6 answer on 1.10 and 1.12 everywhere and on macOS throughout. The failure is older than this release — it is onmainat the previous commit with the identical signature — and it is not a regression to chase: the stage system is numerically singular, cond ≈ 5.6E16 with σmin = 5.7E-17 against σmax = 3.2, which is the same matrix to within a factor of 1.5 as theSPARKLobattoIIIAIIIB(2)andSPARKGLRK(2)siblings the suite already assertsSingularExceptionfor. Its σmin sits an order below then·eps·σmax ≈ 1.8E-14at which a 26×26 system stops having a numerical rank.Whether LAPACK's
getrflands on an exact zero pivot or on one of ~1E-17 is a rounding accident of the BLAS kernel, so the outcome is genuinely platform-dependent. The@teston this case was promoted from@test_brokenunder SimpleSolvers 0.10 on the strength of one platform where it survived; that promotion is retracted, and the test now asserts the answer where there is one anderr isa SingularExceptionwhere there is not. Audit finding S8 always covered the case — see the S8 retraction indocs/src/audit.md, which carries the singular-value measurements.
Documentation
docs/src/audit.mdcarries the re-measured warning census. This closes the item that stood under## Open Issuesas "docs/src/audit.mdstill carries the pre-0.12 warning census": the page now has its ownUpdate (SimpleSolvers 0.12.1 / GeometricIntegratorsBase 0.6.3)block with the 14 → 8 figures recorded under Findings below, and with the one thing 0.12 changes about the reasoning already on that page — themaxlogcaps having become a back-off, the arithmetic by which three saturating sites gave exactly 9 warnings no longer describes the mechanism, and neither does the@test_nowarn-goes-blind caveat that followed from it.The same page records the S8 retraction for
VSPARK(SPARKLobattoIIIBIIIA(2))described under Tests above, with the singular values behind it, and strikes the 0.17.0 promotion where it was originally recorded rather than leaving two entries that disagree.
Findings
Test outcomes are unchanged across this bump, and this was measured rather than assumed: the suite was run at HEAD~ against SimpleSolvers 0.11.0 and GeometricIntegratorsBase 0.6.2 in a separate worktree, and all 28 testsets report identical pass and broken counts — the six route assertions added above excepted, since they do not exist there.
The warning census moved, and this is what it moved to. A full run emitted 14 warnings before and emits 8 now. Unchanged: the seven Nonlinear solver failed at timestep n=…: non-finite direction vector (GeometricIntegratorsBase's own, at n=5, 7, 9×3, 10×2) and the one stagnation warning from VSPARK(SPARKLobattoIIIBIIIA(2)) — the one entry here that is platform-dependent, since it is emitted exactly on the runs where that solve returns rather than raising SingularException, as the Tests entry above describes. Gone: six Backtracking line search: warnings — three no step satisfied the sufficient decrease condition in 13/14 trials and three φ'(0) = <tiny positive> (with φ(0) = …). Those are exactly the per-iteration line-search warnings SimpleSolvers 0.12 stopped emitting from inside solver_step!.
Nothing was lost with them. The stagnation warning reports the same residual to the bit (rfₐ = 3.571488053775618e-5) and the same diagnosis, and 0.12 appends the cause to it:
The line search reported
LINESEARCH_NO_DESCENTon 2 of the 3 step(s), i.e. φ'(0) > 0 — the direction was not a descent direction at all, which points at the Jacobian rather than at the tolerance: a stale one underrefactorize > 1, a nonzeroregularization_factor, or an inexact linear solve.
That is the same three φ'(0) > 0 events the deleted per-iteration warnings were reporting one at a time, now counted and named once at the end of the solve. docs/src/audit.md, where the census lives, carries these numbers as its own update — together with the one thing they change about the reasoning there: SimpleSolvers 0.12 replaced the maxlog caps with a back-off (occurrences 1, 2, 4, 8, …), so the "three maxlog = 3 sites saturate at exactly 9" arithmetic of the earlier census no longer describes the mechanism. This suite never reaches the back-off regime, the repeating diagnosis now firing once per run, so it is inherited but not exercised from here.
0.18.1
Breaking Changes
Requires SimpleSolvers 0.11 and GeometricIntegratorsBase 0.6. The two bumps are coupled: GeometricIntegratorsBase 0.6 requires SimpleSolvers 0.11, so neither range is satisfiable with the old pin on the other. Nothing this package calls was removed by either.
A non-finite direction now aborts the integration. SimpleSolvers 0.11 tests
all(isfinite, …)where 0.10 tested onlyisnan, so an overflowed direction — which used to pass every guard and stall silently,Inf * nan_factorbeingInf— raisesNonlinearSolverException. GeometricIntegratorsBase catches it in the time-stepping loop, warns naming the timestep, and breaks before thecopy!, so the solution holds valid data up ton-1and zeros after it.That warning is GeometricIntegratorsBase's own: the
verbosity = 0, warn_iterations = 0pair the SPARK suites pass does not reach it, and it carries nomaxlog, so its count is the true number of events. Seven fire per test run, all from methods already recorded@test_brokenfor diverging. They are left unsuppressed deliberately — each is a correct symptom, and a more informative one than the silent stall it replaces, since it names the step at which the trajectory stops meaning anything.default_optionsinheritsf_stall_window = 50from GeometricIntegratorsBase, SimpleSolvers 0.11's criterion for a solve whose residual sits on a floor abovef_abstolwhile the iterate keeps moving — the casemax_stallscannot see. No measured effect on this test suite. Options are merged rather than replaced, so a method needing a longer window overrides it with one keyword.
New Features
- DIRK calls the typed
default_linesearch(eltype(x), method)that GeometricIntegratorsBase 0.6 added alongside the untyped hook.
Test outcomes are unchanged across this bump: all 24 testsets report identical pass and broken counts, and Aqua passes.
0.18.0
Breaking Changes
Four Runge-Kutta method types are renamed, to avoid a collision with the methods of the same name that GeometricIntegratorsBase 0.5.2 now exports.
Integrators.jlreexports that package, and two distinct types sharing a name resolve to nothing once both are in scope. The Runge-Kutta side takes the suffix, followingImplicitEulerRK, which was renamed the same way and for the same reason:old new CrankNicolsonCrankNicolsonRKImplicitMidpointImplicitMidpointRKSymplecticEulerASymplecticEulerARKSymplecticEulerBSymplecticEulerBRKOnly the Julia types are renamed. The tableaus keep their names, so
TableauCrankNicolsonandTableauImplicitMidpointare untouched, the partitioned tableaus built inline stay:SymplecticEulerAand:SymplecticEulerB, and the bibliography keys behindreference(Val(:CrankNicolson))andreference(Val(:ImplicitMidpoint))are unchanged — those name the methods, not the wrappers. The unsuffixed names now resolve to GeometricIntegratorsBase.CGVI and DGVI results change. CompactBasisFunctions 0.3 fixes the Legendre derivative, which had been missing the √(2j+1) normalisation of its own basis functions.
Requires RungeKutta 0.6, which dropped the
get_prefix from its accessors and then removed the node and weight ones altogether, having become one-line forwardings to QuadratureRules. The quadrature data is now taken from QuadratureRules directly, which is one indirection fewer and needs no new dependency.lobatto_nullvectorstays and is imported explicitly, in bothIntegratorsandSPARK, as it is no longer exported.Requires QuadratureRules 0.2, CompactBasisFunctions 0.3, GeometricBase 0.14.8 and GeometricIntegratorsBase 0.5.2.
basisandnnodesare extended from GeometricBase rather than defined here.basis(::CGVI),basis(::DGVIMethod)and theirnnodescounterparts were bare definitions, so they created GeometricIntegrators-local functions rather than extending the ones CompactBasisFunctions and QuadratureRules use — and distinct functions sharing a name resolve to nothing when two of them are in scope together.nbasisstays imported from CompactBasisFunctions, which owns it.The
GenericLinearAlgebradependency is dropped. It was never called by name; theusinginsrc/SPARK.jlwas left over from 2020, when tableau construction still lived here and needed genericeigvalsfor BigFloat companion matrices. One call did rely on it, in the DVI integrators rather than in SPARK:rank(Matrix(tableau.a))routes throughsvdvals, and there is no stdlibsvdvals!(::Matrix{BigFloat}), soDVRK(TableauGauss(BigFloat, s))only worked because RungeKutta happened to load GenericLinearAlgebra for its own use. The check is a diagnostic@warn, so the rank is now taken in Float64.Motivation: GenericLinearAlgebra 0.4.0 cannot precompile on Julia 1.13, where overwriting its
LinearAlgebra.eigencopy_oftypemethod forUpperHessenbergis a hard error.Documenter, SharedArrays, Test and Parameters are removed as dependencies; none is used by the loaded sources. The inert
[extras]block goes with them — there is no[targets]section, so it has had no effect since the move to a workspace layout.ProgressMeteris deliberately kept, as a marker for the commented-out simulation code.
Fixes
R∞operator precedence in sixteen Lobatto-pair projection constructors. They passedR∞=-1^(s+1), which Julia parses as-(1^(s+1))and is therefore −1 at every s, where the intended(-1)^(s+1)is +1 at odd s. The Gauss siblings in the same functions already wrote(-1)^swith parentheses, so the two forms sat side by side in one file.R∞sets β, dλ, δλ and βq/βp, and at odd s the wrong sign zeroes β. All sixteen change at odd s, none at even s (even-s results are bit-identical). Two methods change order:method order error VSPARKLobattoIIIBIIIApSymmetric(3)measurement failed → 4.000 4.39e-07 → 1.89e-11 VSPARKLobattoIIIAIIIBpSymmetric(3)3.094 → 3.999 4.17e-11 → 1.21e-11 VSPARKLobattoIIIBIIIApSymmetric(3)is promoted from broken to a real convergence assertion. The finding previously filed against it in the verification report as inherent divergence is retracted and re-filed as a fixed bug.The Vandermonde solve of
lobatto_gauss_coefficientsis pinned to BigFloat. The RungeKutta 0.6 migration had to spell this out once the quadrature data started coming from QuadratureRules, whose accessors default to Float64 rather than BigFloat; solving in Float64 instead moves the coefficients by 2.0e-15 in relative norm at s = 4 and by 9.3e-13 at s = 8, and no test would have caught it, since the result is converted to Float64 either way.
Tests
- Aqua's stale-dependency check is enabled, now that the dead
[compat]entries are gone. It would not have caught Documenter or ProgressMeter, both of which have a liveusing.
0.17.0
Breaking Changes
- Drops GeometricIntegratorsBase 0.3/0.4 and SimpleSolvers 0.9 from compat, so environments that resolved against those can no longer resolve GeometricIntegrators.
- The framework
f_abstoldefault changes from a flat8eps()tomax(8, solversize(method, problem)) * eps(datatype(problem)), which comes from GeometricIntegratorsBase 0.5.1. This shifts the machine-precision error floors of the high-dimensional SPARK methods, whosef_abstolnow resolves to 1.8e-15 … 1.1e-14 rather than 1.8e-15 throughout. The affected test thresholds are re-measured. solversizeandnullvectorsizetake their arguments in(method, problem)order, following GeometricIntegratorsBase 0.5.1 — 21 definitions plus every call site. The two ad-hocf_abstoloverrides this package carried (a flat8eps()insrc/Integrators.jland SPARK's8e-15) are removed in favour of the framework default.
Fixes
HSPARKsecondarywarning suppression restored on the@test_throwsloop, where it had been dropped rather than substituted when those cases were rewritten from@test_broken.
Tests
- The
mufflelog-suppression scaffolding is removed throughout — six definitions in two variants across 42 call sites — in favour ofverbosity = 0, warn_iterations = 0on the genuinely divergent cases. SimpleSolvers 0.10 makes this possible: a line search now shares its solver'sOptions, soverbosityfinally reaches it.verbosity = 0silences only SimpleSolvers, where aNullLoggeralso swallowed unrelated failures. @test_nowarntripwires are added around the converging variational and Hamilton-Pontryagin convergence calls, now that which orbit warns no longer depends on platform floating-point details.- The SPARK
@test_brokencases now assert the failure mechanism where it is unambiguous: structurally singular stage systems assert@test_throws SingularException, order-deficient and unstable methods assertconverged, stalling solves assertstalled. Marginally singular cases, which converge or zero-pivot depending on rounding, stay@test_broken. VSPARK(SPARKLobattoIIIBIIIA(2)), formerly a singular@test_broken, converges under SimpleSolvers 0.10 and is promoted to@test. See Open Issues — its solve still stalls well above the tolerance it was asked for.- Every magnitude and error tolerance is normalised to the tightest {1,2,4,8}×10ⁿ strictly above the freshly measured value; dropping the "5" mantissa makes the powers-of-two grid guarantee measured < TOL ≤ 2 × measured.
0.16.10
Breaking Changes
- Requires GeometricProblems 0.8. Every dependency gains a compat entry, and the test and docs dependencies are bounded where they are resolved.
Fixes
- SLRK null-vector term. The null-vector multiplier was added to both the momentum-stage row and the primary-constraint row. The first lives in Z-space (P = p + h·Z) so it carries an extra factor h, and the two combined to (1-h)·μ·di/bi. The stage Jacobian was therefore exactly singular at Δt = 1 and ill-conditioned near it — cond(J) 1.5e2 → 2.2e3 → 2.3e4 → 1.6e11 as Δt → 0.9, 0.99, 0.999, 1.0, now flat at ~47. No other SPARK integrator does this. Present since the original implementation, and behaviour-neutral at usable step sizes: trajectories unchanged to ≤ 2 ulp.
SLRKLobattoIIICbarCwas registered under the same name symbol asSLRKLobattoIIICCbar.- The SLRK docstring was a verbatim copy of the
VSPARKsecondaryone and described a different scheme. Rewritten from the code and the manuscript, including the requirement that theLDAEProblem'sfbe the full dL/dq — the opposite convention toVSPARKsecondary, previously undocumented and a silent source of wrong results. - DVRK traits.
order,issymmetricandissymplecticcalled an undefinedtableaus(method)and threwUndefVarErroron everyDVRKinstance. This is why the method table reportedmissingfor DVRK and why its convergence tests had to passexpected =explicitly. - The DVRK tableau invertibility check false-positived on well-conditioned tableaus.
detis not scale-invariant and the determinant of an RK coefficient matrix shrinks rapidly with the number of stages, soabs(det(a)) ≤ eps^(3/4)flaggedGauss(10)and up as singular (det = 1.5e-12 at cond = 1.2e2). Replaced byrank(Matrix(a)) < s, which separates Gauss (full rank) from LobattoIIIA/IIIB (rank s-1, cond ≥ 6e17) cleanly and works for Float32 and BigFloat tableaus too. - The even-dimension guard is widened.
DVIA,DVIB,CMDVIandCTDVIfail on odd D exactly asDVRKdid —div(D,2)dropped a component silently, and for D = 1 left the nonlinear system underdetermined. The guard moves tocheck_dvi_dimensionand is called from all three cache constructors, so all five methods now give the sameArgumentErrorinstead of aSingularException. - Minor fix in the initial guess for
CTDVI; the SPARKshowmethods are fixed and the show tests now assert their titles. solversizebecomes one function with one definition.
Documentation
- DVRK's order-2s claim holds only within a gauge class. DVRK requires L = ϑ(q)·q̇ − H(q) with d/2 components of ϑ vanishing identically; ϑ is only defined up to an exact one-form, and the gauge decides whether a given problem satisfies that hypothesis. Every existing DVRK test used
LotkaVolterra2d, whose gauge has ϑ₂ ≠ 0 and is therefore outside the class. In classDVRK(Gauss(s))attains the full order 2s; out of class it stays convergent at s. The same split shows up in symplecticity: the defect sits at the finite-difference floor in class, and grows with h out of class. The "order reduction on degenerate Lagrangians" previously recorded as a property of the method was a testing artefact. - The hypotheses under which DVRK is symplectic are documented and checked: the tableau symplecticity condition and an invertible coefficient matrix, an even-dimensional configuration space, and p₀ = ϑ(q₀).
DVRK(RadauIIA(s))andDVRK(LobattoIIIA(s))now warn rather than silently producing a non-symplectic method. Note that the vanishing-components condition and the invertibility of ∂ϑ_μ/∂q^ν are separate conditions; earlier prose folded one into the other. - A full SLRK section in
docs/src/integrators/spark.md: scheme, ω construction, null vector, constructor table, gauge caveat, and what the family does and does not preserve. - The verification report is published as a documentation page (
docs/src/audit.md) rather than living in the repository root. scripts/gainsdvrk_convergence.jl,dvrk_symplecticity.jl,projected_vprk_symplecticity.jl,slrk_verification.jlandvspark_projection_symplecticity.jl, which reproduce every number in the report.
Findings
Three negative results, recorded in docs/src/audit.md and carried into Open Issues below:
- SLRK is not symplectic.
- The VSPARK projection methods are not empirically symplectic either.
- The midpoint projection is not symplectic, and
VPRKpSymplecticis the same map asVPRKpStandard.
Continuous Integration
- Coverage is computed in a single job rather than in all twelve matrix jobs.
0.16.9
New Features
IRK3, a fully implicit Runge-Kutta method.
0.16.8
Breaking Changes
- Requires GeometricProblems 0.7.
- The five DGVI variants share one abstract method type, one superset coefficient block, one pruned cache and a common integrator layer, which removes 1714 lines net while taking the family from zero working variants to five.
New Features
- DGVI, FLRK and PGLRK are ported to the method-based
GeometricIntegratorarchitecture, registered as methods, documented, and covered by the same static-plus-dynamic verification standard as the rest of the package.DiscontinuityandPathIntegralare documented.
Fixes
Nine defects, four of them numerical:
CoefficientsPGLRKcould not construct at all:new(...)passed 11 arguments to a 14-field struct, since@CoefficientsRKgained â/b̂/ĉ, so P landed in â and the Q matrix inb̂::Vector.Legendrewas also never imported.- FLRK took its stage force from the projection field g = (∇ϑ)ᵀv instead of f = (∇ϑ)ᵀv − ∇H, so the momentum update was missing −∇H. Reverting the fix degrades max|p − ϑ(q)| from 1E-15 to 9.9E-2 while leaving q correct, which is why the old q-only test never caught it.
- FLRK as an
RKMethodwould have been silently replaced by a plain IRK byinitmethod(::RKMethod, …); it is now anLODEMethod. VPRKpTableaureferenced unbound names and needed a non-dependency (NLsolve); rewritten with the multipliers folded into one coupled Newton system.CoefficientsPGLRK(2)is inconsistent by construction and is now rejected.VPRKpTableauneeds s ≥ D+1 to fit its multipliers and s ≥ D+2 for full order.DGVI/DGVIP0/DGVIP1are capped at order 2⌊s/2⌋ by their nodal-value flux.- The disabled DGVI tests used a regular Lagrangian, on which DGVI's closure row collapses to q − p and the Jacobian is singular; they now use degenerate ones.
- The PGLRK symplecticity claim is corrected.
Verified afterwards: FLRK reaches order 2s in both q and p; PGLRK conserves energy to 3.6E-15 against Gauss's 7.3E-10 on a nonlinear problem; VPRKpTableau enforces the Dirac constraint to 1E-15 and recovers the order plain VPRK loses on degenerate Lagrangians; DGVIPI and DGVIEXP reach the full order 2s.
0.16.7
Fixes
McLachlan4was order 2, not 4. The coefficients were composed as φ then adjoint, where McLachlan 1995 eq. 1.8 requires the adjoint first. The coefficients themselves are correct. The existing accuracy tolerance tightens 5E-4 → 5E-8.- HSPARK-secondary momentum coefficients
a_p_2/a_p_3were s×s but indexed over the σ projective stages, giving aBoundsError. Rebuilt as the s×σ conjugate-symplectic partners ofα_q_2/α_q_3. - HSPARK-secondary null-vector residual and component code was commented out while the cache still allocated the μ unknown, leaving an unconstrained Jacobian row and a
SingularException. Re-enabled, matching the working VSPARK-secondary. A deeper singularity remains in its ω secondary-constraint block, so the EXPERIMENTAL family stays@test_broken. - HSPARKsecondary initial guess built its NamedTuple with
v/frather than the solution derivative fieldsq̇/ṗthat every other SPARK integrator uses, sosolutionstep!could not consume it (FieldErroron hdae problems). - δ-constraint residual row index R-1 → i in
integrators_hspark.jlandintegrators_hspark_primary.jl— neutral for P = 1, correct for P > 1. - The momentum (p) error checks were NaN because
relative_maximum_error(…).pdivided 0/0 at the harmonic oscillator's p(0) = 0. Fixed upstream in GeometricSolutions 0.6.5; the checks are re-enabled. KraaijevangerSpijkeris order 1, not 2 (RungeKutta 0.5.22 corrects the attribute; the method genuinely satisfies only the order-1 conditions, Σᵢbᵢcᵢ = 2 ≠ 1/2).- The VPRK docstring's symplecticity condition is corrected to the general barred form.
New Features
Yoshida6(6th order, SS m=7) andYoshida8(8th order, SS m=15) symmetric composition methods, extending the splitting methods beyond 4th order. Coefficients verified to high precision against Yoshida 1990 and McLachlan 1995 Table 2.
Documentation
- New pages for the degenerate variational integrators (
dvi.md: DVIA/DVIB/CMDVI/CTDVI + DVRK) and the Hamilton-Pontryagin-Galerkin framework (hpg.md), the latter re-enabled inmake.jl; the CGVI page is enabled too.spark.mdis expanded with the construction and the order/stability caveats. Stage-time and summation-index typos fixed inrk.md.
Tests
test/verification/: a reusable convergence and energy harness plus per-family convergence tests (rk, prk, splitting, variational, galerkin, dvi, hpi, projection, spark), wired intoruntests.jl— 64 SPARK order assertions and 11 recorded deficiencies among them.- Roughly 30 previously-commented VSPARK/HSPARK cases are re-enabled with honest tolerances, and the genuinely broken ones recorded as
@test_brokenwith root causes rather than tuned away. Most SPARK non-convergence is an inherent method property, not a bug: symplectic plus constraint-at-solution reduces order or diverges; R(∞) = (-1)^(s+1) ≠ 1 drops GLVPRK and HPARKGLRK from 2s to 2 at s = 2; coinciding tableau pairs give a singular stage system at s = 2. - Error bounds more than an order of magnitude looser than the measured error are tightened, from measurement.
0.16.6
Breaking Changes
- Updates for GeometricIntegratorsBase 0.4.0, whose
SolutionStepreset!takes a time rather than a timestep.
0.16.5
- GeometricProblems 0.7 in the test extras.
0.16.4
Breaking Changes
- Requires SimpleSolvers 0.9:
NewtonMethodis replaced byNewton, following the new naming upstream, and the default linesearch becomesStrongWolfe.
Tests
- Some problematic SPARK tests are disabled and the remainder switched to the default solver settings.
0.16.3
Breaking Changes
- Adapts to the removal of the dimension type parameter in
Cache(GeometricIntegratorsBase 0.3); requires Parameters 0.12 or 0.13.
Fixes
- An
IntegratorCacheSPARKtype parameter mismatch inintegrators_spark_parameters.jl. - The
cache.t̄/q̄/p̄assignments are removed fromGeometricBase.reset!forIntegratorCacheSPARK.
Tests
- Tests for the Radau and Lobatto integrators.
0.16.2
Documentation
- Cleanup in the documentation.
0.16.1
Documentation
- Documentation fixes.
0.16.0
Breaking Changes
- Requires GeometricBase 0.14, GeometricEquations 0.21, GeometricSolutions 0.6, GeometricIntegratorsBase 0.2 and PrettyTables 2 or 3.
- Runge-Kutta methods account for the precision of the solution, and the tableaus become type-dependent.
Documentation
- Plots in the documentation are replaced by Makie.
Tests
- Test dependencies are reorganised into workspaces, and
test/Project.tomlis checked in.
0.15.5
Breaking Changes
- Requires SimpleSolvers 0.7.5 and the corresponding GeometricIntegratorsBase release.
0.15.4
Breaking Changes
- Admits PrettyTables 3 alongside 2.
Continuous Integration
- CI updated for Julia 1.13.
0.15.3
Breaking Changes
- Requires SimpleSolvers 0.7.
0.15.2
Breaking Changes
- Requires SimpleSolvers 0.6.
Fixes
- Imports and dependencies cleaned up; a compat entry added for GeometricProblems; the missing
base.mdadded to the documentation.
0.15.1
Fixes
- Adapts to the initial guess fixes in GeometricIntegratorsBase, and fixes the initial guess for the IRK implicit method.
- Removes the obsolete
solvers.jl.
0.11.0
Breaking Changes
- Rename
AtomicSolutiontoSolutionStep - Disable
Simulationfunctionality untilEnsembleSolutionis added to GeometricEquations.jl
New Features
Documentation
- Include documentation of GeometricEquations.jl and GeometricSolutions.jl
0.10.0
Breaking Changes
- Refactor
TimeSeries,DataSeriesandSolutionand move to GeometricSolutions.jl - Adapt Solution HDF5 interface to default Julia argument order and naming conventions
- Extract HDF5 functionality from Solutions into separate data structure
- Remove parallel Solution types
0.9.0
Breaking Changes
- Move
HermiteInterpolationto Integrators and removeInterpolationsub-package - Move
Equationssubmodule to GeometricEquations.jl - Move
Common,ConfigandUtilssubmodules to GeometricBase.jl - Move
TimeSeries,DataSeriesandSolutionfromSolutionstypes to GeometricBase.jl - Remove parallel DataSeries and Solution types
New Features
- Implement first and second order Degenerate Variational Integrators (DVIs)
- Add tests for extrapolation methods
Fixes
- Bugfixes in implicit equations
- Bugfixes in extrapolation methods
- Bugfixes in initial guesses
- Bugfixes in VPRK and VSPARK initialisation
- Bugfixes in
TimeSeriesgetindexmethods
Documentation
- Add missing docstrings in various places and remove superficial docstrings
0.8.0
Breaking Changes
- Use RungeKutta.jl for most tableaus and coefficients
- Move stochastic integrators to separate package
- Rewrite of most equation types
- Rename
VODEandVDAEtoLODEandLDAEfor consistency withHODEandHDAE - Add optional fields for the secondary constraint to all *DAE equations
New Features
- Allow for arbitrary data structures as states (still experimental and not fully supported)
- Add
convertmethods forPODEandHODEtoODEandSODE
Fixes
- Countless minor bugfixes
Documentation
- Add theoretical background for variational integrators, Runge-Kutta and splitting methods
- Add references for most methods
0.7.0
- Use CompactBasisFunctions.jl instead of BasisFunctions submodule
- Use QuadratureRules.jl instead of Quadratures submodule
- Use SimpleSolvers.jl instead of Solvers submodule
- Use GeometricProblems.jl instead of TestProblems submodule
0.6.2
- Bugfix release
0.6.1
- Bugfix release
0.6.0
Breaking Changes
- Revise tableaus: align constructor names with RungeKutta.jl
New Features
- Add new Runge-Kutta tableaus
- Generalise Lobatto and Radau tableaus to arbitrary number of stages
- Extend documentation on integrators and tableaus
0.5.1
- Update documentation
- Fix HDF5 v0.14 deprecations
0.5.0
- Moved repository to JuliaGNI
- Moved CI from Travis to GitHub
Breaking Changes
- Functions for initial guesses are now called v̄ and f̄ and can be prescribed separately from v and f in PDAE, HDAE, etc.
- Rename SPARK tableau constructors and unify distinct constructors for Lobatto tableaus with different number of stages
New Features
- Implement SPARK integrator for index-two DAEs
- Implement infrastructure for storing internal variables and solver output to atomic solutions
- Store internal variables of SPARK and VPRK integrators in atomic solution
- Add various five-stage Lobatto tableaus
- Add and clean up SPARK tableaus and add docstrings
- Add functions for checking symplecticity conditions of SPARK tableaus
- Add Aqua.jl tests
Fixes
- Fix initial guess warnings in tests by prescribing proper functions for v̄ and f̄ in example problems
- Fix update_multiplier() method for SPARK integrators
0.4.1
New Features
- Atomic solutions can now store a NamedTuple of internal variables of the integrator, including nonlinear solver output
- Output of internal variables has been added to VPRK integrators
- Add Gauss-Legendre tableaus for implicit partitioned Runge-Kutta methods
Fixes
- Revision of integrator type hierarchy
0.4.0
New Integrators
- Runge-Kutta integrators for implicit ODEs (
FIRKimplicitandSRKimplicit) - Variational Partitioned Runge-Kutta integrator with projection based on internal stages
Fixes
- Computation of initial guess in all implicit integrators
Open Issues
The migration checklist in todo.md is fully worked off — all thirteen items are closed and the two trailing sections are post-mortems rather than tasks. What follows is what is genuinely open, taken from the verification report (docs/src/audit.md) and the test suite.
The solver status is available but not acted on
0.18.2 routes every solve through solve_with_status! and hands the status to check_solver_status, whose default returns it and does nothing else. That was the deliberate choice — SimpleSolvers stays the single reporting voice, so no run changes what it prints — but it means a step that did not converge is still only reported, never acted on, and the trajectory continues past the point where it stopped meaning anything with nothing in sol to mark it.
The place to act is GeometricIntegratorsBase's integrate!, which already handles two of the three ways a step can go wrong (a NonlinearSolverException, and NaNs in the iterate) by warning with the time step and returning what was computed so far. This is tracked in that package's ## Open Issues rather than here, since the hook and the loop both live there; it is named here because this package is where the consequences would show — see the @test_broken methods below, several of which reach max_iterations on every step.
SLRK is not symplectic (audit finding S15)
The manuscript's proof leaves an uncontrolled term in the multiplier block: the null-vector condition that kills the velocity term has no counterpart for Λ, and the proof defers to a label that is never defined. Measured with PoincareInvariants.jl, the first invariant drifts secularly at O(h^(p+1)) per step, growing linearly with the step count, while a genuinely symplectic variational integrator holds it to round-off.
Within this ansatz the defect cannot be removed: exact symplecticity, the primary constraint at every stage, and the constraint at the solution cannot be had together. The defect is gauge invariant; solvability is not — the two Lobatto IIIA-IIIB pairs go singular on a gauge-equivalent one-form with a vanishing component.
The family remains useful — it preserves the constraints — but it should not be described as symplectic.
The VSPARK projection methods are not symplectic either (S17)
On the massless charged particle, every one of the thirty Gauss-inner methods (ten projections × s = 1,2,3) shows a clean O(h^k) defect with k ∈ {3,4} and 100-step drifts up to 1.2e-05. Nothing is at round-off. Which tableau condition fails is per-projection, not universal.
A sufficient criterion for a false pass is established: if every component of ϑ is at most linear in q, an unprojected variational Runge-Kutta method already lands on the constraint manifold, so the projection is inert and any projection built on such an inner method comes out symplectic whichever condition it violates. That explains PointVorticesLinear.
Still unexplained: why LotkaVolterra2d and the two singular gauges nevertheless come out at round-off. None of them is in the linear-ϑ class — each has a nonlinear first component — and the projective multipliers are measured between 2.4e-03 and 2.6e+01, so the projection is demonstrably active. Only the negative half is established.
What a proof would need is the two-form condition Σᵢ b₄ᵢ dΦ̃ᵢ ∧ dΛ̃ᵢ = 0 rather than the pointwise Φ̃ᵢ = 0; measurement shows neither factor is the mechanism.
The midpoint projection is not symplectic
Its proof needs R(∞) = +1, while requiring the midpoint to be an internal stage forces R(∞) = −1, since a midpoint stage means e_mᵀA = bᵀ/2 and hence bᵀA⁻¹e = 2. With the sign that makes the method solvable, the defect is O(h³); dropping the R(∞) factor instead makes the step equations unsolvable, the irreducible residual being the midpoint/trapezoidal discrepancy. For a tableau without a midpoint stage the equations are solvable but the defect is O(h³) as well, so that sign does not rescue the method either.
Two consequences for the code: VPRKpSymplectic is a post projection whose R(∞) is absorbed by the multiplier and is therefore the same map as VPRKpStandard; and the mixed term of the modified two-form as printed in the source manuscript has the wrong sign (with the sign corrected the form is preserved to round-off).
Note also that both Lotka-Volterra models are unusable for testing symplecticity here: their multiplier has a single nonzero component whose ϑ component is affine, so every projection method, including the standard one, comes out exactly symplectic on them.
VPRKpInternal and VPRKpSecondary do not run
Both are exported, and both appear in test/integrators/test_show.jl (which never solves), but neither has an initial_guess! method for its state layout. Their tests are commented out at test/projections/projections_vprk_tests.jl:109 and :127. The verdict recorded above on the internal projection is therefore one about the method, not about executable code.
VSPARK(SPARKLobattoIIIBIIIA(2)) has a singular stage system
Restated in 0.18.2, having been recorded here since 0.17.0 as a case that "stalls". It does stall — the solve stagnates after 3 iterations at rfa = 3.57e-5 against the `fabstol = 5.77e-15it was asked for, and where it returns it is the one stagnation warning a full test run still prints — but the stall is a symptom. The stage system is **numerically singular**: cond ≈ 5.6E16, σmin = 5.7E-17 against σmax = 3.2, with σmin an order below then·eps·σmax ≈ 1.8E-14at which a 26×26 system stops having a numerical rank. That is the same matrix, to within a factor of 1.5 in σmin, as theSPARKLobattoIIIAIIIB(2)andSPARKGLRK(2)siblings the suite assertsSingularException` for.
The 0.17.0 promotion from @test_broken to @test is therefore retracted: it read one platform's luck as a property of the method. Whether LAPACK's getrf lands on an exact zero pivot or on one of ~1E-17 decides the outcome, so the same call raises on Julia 1.13 and nightly under Linux and Windows and returns a ~1E-6 answer on 1.10 and 1.12 everywhere and on macOS throughout. The test now accepts both. What stays open is the method: s = 2 is audit finding S8, degenerate at the lowest stage count, and the answer it sometimes returns is one produced by a Newton direction solved out of a rank-deficient matrix. It should not be treated as a supported configuration.
A rank-deficient stage system is diagnosed by luck rather than by design
The entry above is one case of a general gap, and the Known-broken cases remain entry below already names the class — the marginally singular methods "which converge or zero-pivot depending on rounding, so that no single assertion is reliable for them". What makes them unassertable is that nothing on the path distinguishes rank deficiency from a hard problem. SimpleSolvers' LU linear solver raises SingularException only on an exact zero pivot; a pivot of 1E-17 is accepted, and the Newton direction that comes back out of it is arbitrary in magnitude and direction. The integrator sees a solve that stalls, not a matrix that has no rank, and the two call for opposite responses.
A rank-revealing factorization, or simply a pivot-magnitude threshold relative to σmax, would make the outcome deterministic and let a caller distinguish "this method is degenerate here" from "this step is hard". Both belong in SimpleSolvers rather than here, and neither is a compat-bump change. Until then the SPARK suite has to accept two outcomes for at least one case, and this package cannot tell a caller which of the two it got.
The four state-building solve_with_status! sites allocate a state per call
0.18.2 leaves DIRK's per-stage loop and the three projection integrators on the state-building form of solve_with_status!, which constructs a NonlinearSolverState on every call — once per stage per step for DIRK, once per step for each projection. This is not a regression: the solve!(x, s, params) they replaced went through the same NonlinearSolverState(x, value(cache(s))) convenience path, so nothing got slower. It is simply now visible, and it is the objection SimpleSolvers 0.12.1's own docstring raises against that form — "a caller stepping through time should build one NonlinearSolver and one NonlinearSolverState and reuse both".
Closing it means giving ProjectionIntegrator a solverstate field and SingleStageSolvers one state per stage, both structural changes to types that GeometricIntegratorsBase and this package own respectively. Out of a compat bump, and worth doing together rather than one at a time.
check_solver_status cannot tell a caller which solve it is being asked about
The hook takes (status, int), which is the right signature for the fifteen methods that solve once per step. It is thinner than it should be for the other four. DIRK calls it once per stage with the same int every time, so an override cannot tell which of the s stage solves failed, and sees s calls per step where every other method produces one. The three projections pass a ProjectionIntegrator, so an override written as GeometricIntegratorsBase's documentation suggests — on GeometricIntegrator{<:MyMethod} — never sees the projection solve at all, only the inner integrator's.
Neither is wrong as far as it goes, and no test here depends on the distinction. But a caller overriding the hook to reject a non-converged step gets a coarser instrument than the call sites could support, and widening the signature is a GeometricIntegratorsBase decision.
RungeKutta's barred Lobatto tableaus carry 1E-77 where the plain ones carry exact zeros
Noticed while measuring the stage Jacobians above. TableauLobattoIIIA(s).a and TableauLobattoIIIB(s).a have an exactly zero first row, as they should; the adjoint variants do not:
julia> TableauLobattoIIIB̄(3).a[1,:]
3-element Vector{Float64}:
-2.1590421387736112e-78
8.636168555094445e-78
-2.1590421387736112e-78The magnitude is eps(BigFloat) at the default 256-bit precision, so these are rounding residue from a coefficient solve carried out in BigFloat and surviving the conversion to Float64. They reach this package through every SPARK tableau built on the barred pairs — SPARKLobattoIIIBIIIA(s) for s ≥ 3 shows them in tableau.p.a.
Numerically inert: 1E-77 against coefficients of order 1 changes no arithmetic here. What it does change is that the structural zeros are no longer detectable — iszero, count(iszero, …) and anything asking "is the first stage explicit?" answer wrongly on these tableaus. Nothing in this package asks today. The fix is upstream in RungeKutta.jl, where rounding the solve back to exact zeros costs nothing.
The PGLRK status-hook override in the test suite is session-global
test/verification/pglrk_convergence_tests.jl adds a counting method to GeometricIntegratorsBase.check_solver_status for GeometricIntegrator{<:PGLRK}. A method is global to the session and runtests.jl drives its files with @safetestset — a fresh module in the same process — so it also counts every PGLRK integration in methods_tests.jl, test_show.jl and spark_tableaus_tests.jl, which run after it. Harmless today: it returns its argument unchanged and nothing there reads the counter. It is recorded because a second counting override, for another method or another hook, would silently collide with this one, and because there is no way to scope a method to a file.
Known-broken cases remain
A full run reports 26 broken assertions, concentrated in SPARK: 10 in the SPARK convergence suite and 6 in the SPARK integrator suite, with 3 variational, 3 DGVI, 2 PRK, 1 RK and 1 PGLRK/VPRKpTableau making up the rest. (The suite contains 24 @test_broken statements; the two tallies differ because several sit inside loops and others are not reached.)
Most are inherent properties of the methods rather than implementation defects: symplectic plus constraint-at-solution reduces order or diverges for the Lobatto IIIA-IIIB and IIIB-IIIA SPARK/HPARK families; R(∞) = (-1)^(s+1) ≠ 1 drops GLVPRK and HPARKGLRK from order 2s to 2 at s = 2; coinciding tableau pairs give a singular stage system at s = 2. The SPARK cases still left as @test_broken, rather than asserting a specific failure mechanism, are the marginally singular ones, which converge or zero-pivot depending on rounding, so that no single assertion is reliable for them.
order(VPRKGauss(s)) and order(VPSRK3()) report wrong orders
Inherited from RungeKutta metadata rather than computed here.
HSPARKsecondary remains EXPERIMENTAL
The BoundsError and SingularException fixed in 0.16.7 got the family as far as the solver, but a deeper singularity remains in its ω secondary-constraint block.