feat: runtime reversibility check for constrained leapfrog integrator (RATTLE)#89
Draft
MaartenMarsman wants to merge 3 commits intomainfrom
Draft
feat: runtime reversibility check for constrained leapfrog integrator (RATTLE)#89MaartenMarsman wants to merge 3 commits intomainfrom
MaartenMarsman wants to merge 3 commits intomainfrom
Conversation
Phase-aware reverse check: observe during warmup, enforce during sampling. Forward+backward leapfrog in leapfrog_constrained_checked detects non-reversible RATTLE/SHAKE projections. Non-reversible steps terminate the NUTS tree and reject the proposal (same as divergence handling). Internal tolerance fixed at 0.5 * eps^2 (not exposed in R API). Non-reversible counts stored per iteration and surfaced through nuts_diagnostics alongside divergence counts. R layer: reverse_check parameter in bgm(), threaded through spec, validation, and run_sampler dispatch to C++ backends (GGM, OMRF, mixed). C++ layer: SamplerConfig, sampler_base, nuts_sampler, hmc_sampler, leapfrog, nuts, hmc, step_result, chain_result all extended. Tests: 6 tests covering parameter validation, output structure, and integration with continuous GGM edge selection.
The reversibility check is now always on (hardcoded in SamplerConfig). Removed the parameter from bgm(), bgm_spec(), validate_sampler(), run_sampler dispatchers, and the three C++ Rcpp::export entry points. Updated test-validate-sampler expected return list.
Five new scenarios exercising the RATTLE reverse check: - Large step sizes (eps=0.5) with near-machine-epsilon tolerance - Long trajectories (200 constrained leapfrog steps) - Rank-deficient regime (n=5, p=8) - Ill-conditioned data (condition number > 1e3) - High-dimensional GGM integration (p=20, edge selection)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #89 +/- ##
==========================================
+ Coverage 88.54% 88.70% +0.15%
==========================================
Files 72 72
Lines 12406 12518 +112
==========================================
+ Hits 10985 11104 +119
+ Misses 1421 1414 -7 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Add a forward-backward reversibility check to the constrained leapfrog
integrator (RATTLE/SHAKE). Each constrained leapfrog step runs a reverse
half-step and compares the result to the original position. Steps that fail
the round-trip within a tolerance of 0.5 * eps^2 are flagged as
non-reversible. Non-reversible steps terminate the NUTS tree and reject the
proposal, matching the existing divergence handling.
Problem / Motivation
The RATTLE/SHAKE constrained integrator projects positions and momenta onto
the constraint manifold (positive-definite Cholesky factor with structural
zeros for absent edges). These projections are iterative and can accumulate
numerical error, especially when edge indicators change between iterations.
Unlike unconstrained leapfrog, there is no guarantee that the constrained
integrator is time-reversible to machine precision. Without a runtime check,
non-reversible trajectories silently corrupt the detailed-balance condition
of the Markov chain.
Diagnostic analysis on the Wenchuan dataset (p=17, 136 possible edges)
showed that non-reversible steps co-occur with divergent trajectories and
are not caused by intrinsic RATTLE/SHAKE projection failures. The
dimensionless coupling constant C = max_rev_diff / eps^2 has a typical
range of 0.001-0.14 (median 0.002) for non-divergent steps, well below
the 0.5 threshold.
Proposed Changes / New Functionality
C++ sampling layer:
leapfrog_constrained_checked()inleapfrog.h/.cpp: runs forward step,then reverse half-step, compares positions. Returns a
reversibleflagalongside the usual position/momentum.
nuts_step()andhmc_step()innuts.h/.cppandhmc.h/.cpp: acceptreverse_checkandreverse_check_tolparameters. Non-reversible stepsset
s_prime = 0, n_prime = 0(tree terminates, proposal rejected).StepResultandChainResultextended withnon_reversiblefield.SamplerConfigstoresreverse_check = trueandreverse_check_tol = 0.5as internal defaults (not exposed to R).sampler_base.h: the check observes duringwarmup and enforces (rejects) during sampling.
R output layer:
non_reversiblecounts stored per iteration inchain_result.nuts_diagnosticsasnuts_diag$non_reversibleandnuts_diag$summary$total_non_reversible.The reversibility check is always on. There is no user-facing parameter to
disable it.
Type of Change
The constrained leapfrog now runs a forward + backward step, approximately
doubling the leapfrog cost. On the Wenchuan dataset (p=17), elapsed time
increases from ~3s to ~6s compared to main. This is the expected cost of
the round-trip check.
Documentation and Release Notes
man/*.Rdfiles if roxygen comments changedNEWS.mdentry if the change affects usersTesting and Validation
14 tests in
test-reversibility-check.R:Unit-level (C++ test interface):
round-trip drift
normal tolerance
Integration-level (bgm()):
non_reversible diagnostic field present in bgm() output
High-dimensional GGM (p=20, edge selection) completes without crash
Added or updated tests in
tests/testthat/when neededRan the project styler
Ran
lintr::lint_package()Ran
roxygen2::roxygenise()if roxygen comments changedRan
rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"))Verified numerical behaviour where relevant
R CMD check: 0 errors, 0 warnings, 1 note (pre-existing CODE_OF_CONDUCT.md).
Additional Notes
bgmComparesampler (bgmCompare_sampler.cpp) still uses the oldnuts_step/hmc_stepsignatures. It compiles via C++ default parametersbut does not pass
reverse_checkexplicitly. This is fine for now sincebgmComparedoes not use constrained integration, but should be updatedwhen that code is migrated to the unified runner.
ggm_test_leapfrog_constrained_checkedC++ test interface exposesreverse_check_factorfor unit testing with arbitrary tolerances. This isnot part of the public R API.
A future optimization could skip the reverse check for unconstrained
leapfrog steps (where reversibility is guaranteed by the symplectic
integrator), but this is not implemented since unconstrained steps are
already fast.