owalnuts 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- owalnuts-0.2.0/CHANGELOG.md +808 -0
- owalnuts-0.2.0/Cargo.lock +305 -0
- owalnuts-0.2.0/Cargo.toml +71 -0
- owalnuts-0.2.0/LICENSE +21 -0
- owalnuts-0.2.0/LICENSE.txt +21 -0
- owalnuts-0.2.0/NOTICE +8 -0
- owalnuts-0.2.0/PKG-INFO +285 -0
- owalnuts-0.2.0/README.md +464 -0
- owalnuts-0.2.0/SECURITY.md +11 -0
- owalnuts-0.2.0/THIRD_PARTY.md +29 -0
- owalnuts-0.2.0/examples/funnel_kernel_options.rs +168 -0
- owalnuts-0.2.0/examples/funnel_orbit_trace.rs +510 -0
- owalnuts-0.2.0/examples/funnel_paper_adaptation.rs +132 -0
- owalnuts-0.2.0/examples/funnel_replay.rs +533 -0
- owalnuts-0.2.0/examples/gaussian.rs +90 -0
- owalnuts-0.2.0/examples/kernel_bench.rs +350 -0
- owalnuts-0.2.0/examples/kernel_efficiency.rs +557 -0
- owalnuts-0.2.0/examples/readme_quick_start.rs +35 -0
- owalnuts-0.2.0/examples/state_space_path_metric.rs +222 -0
- owalnuts-0.2.0/examples/structured_metric_benchmark.rs +57 -0
- owalnuts-0.2.0/examples/support/reference_nuts.rs +338 -0
- owalnuts-0.2.0/integrations/bridgestan/Cargo.lock +454 -0
- owalnuts-0.2.0/integrations/bridgestan/Cargo.toml +33 -0
- owalnuts-0.2.0/integrations/bridgestan/README.md +164 -0
- owalnuts-0.2.0/integrations/bridgestan/artifacts/bridgestan-benchmark.json +271 -0
- owalnuts-0.2.0/integrations/bridgestan/models/eight_schools.data.json +5 -0
- owalnuts-0.2.0/integrations/bridgestan/models/eight_schools.stan +19 -0
- owalnuts-0.2.0/integrations/bridgestan/models/local_level.stan +18 -0
- owalnuts-0.2.0/integrations/bridgestan/src/bin/bench.rs +408 -0
- owalnuts-0.2.0/integrations/bridgestan/src/bin/wallgap.rs +192 -0
- owalnuts-0.2.0/integrations/bridgestan/src/lib.rs +2199 -0
- owalnuts-0.2.0/integrations/bridgestan/tests/load.rs +373 -0
- owalnuts-0.2.0/integrations/bridgestan/tests/nonfinite.rs +117 -0
- owalnuts-0.2.0/integrations/python/.gitignore +6 -0
- owalnuts-0.2.0/integrations/python/Cargo.lock +640 -0
- owalnuts-0.2.0/integrations/python/Cargo.toml +33 -0
- owalnuts-0.2.0/integrations/python/LICENSE.txt +21 -0
- owalnuts-0.2.0/integrations/python/README.md +236 -0
- owalnuts-0.2.0/integrations/python/src/lib.rs +1616 -0
- owalnuts-0.2.0/integrations/python/tests/check_cmdstan_export.py +68 -0
- owalnuts-0.2.0/integrations/python/tests/test_adapters.py +496 -0
- owalnuts-0.2.0/integrations/python/tests/test_stan.py +143 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365/README.md +33 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365/SHA256SUMS.txt +4 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365/gaussian_cases.json +195 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365/span_traces.json +14 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365/transition_sequence_traces.json +4 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365/transition_traces.json +9 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_funnel_leaves/README.md +40 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_funnel_leaves/SHA256SUMS.txt +2 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_funnel_leaves/funnel_leaf_cases.json +1 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_funnel_leaves/generate_funnel_leaves.cpp +126 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_invalid_leaves/README.md +46 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_invalid_leaves/SHA256SUMS.txt +2 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_invalid_leaves/generate_invalid_leaves.cpp +166 -0
- owalnuts-0.2.0/oracle/walnutpie/f5bba365_invalid_leaves/invalid_leaf_cases.json +1 -0
- owalnuts-0.2.0/pyproject.toml +66 -0
- owalnuts-0.2.0/python/owalnuts/__init__.py +1072 -0
- owalnuts-0.2.0/src/diagnostics.rs +1027 -0
- owalnuts-0.2.0/src/export.rs +318 -0
- owalnuts-0.2.0/src/kernel.rs +6167 -0
- owalnuts-0.2.0/src/lib.rs +24 -0
- owalnuts-0.2.0/src/oracle_tests/funnel_leaf.rs +162 -0
- owalnuts-0.2.0/src/oracle_tests/invalid_leaf.rs +234 -0
- owalnuts-0.2.0/src/oracle_tests/macro_leaf.rs +216 -0
- owalnuts-0.2.0/src/oracle_tests/mod.rs +6 -0
- owalnuts-0.2.0/src/oracle_tests/sequence.rs +478 -0
- owalnuts-0.2.0/src/oracle_tests/span.rs +329 -0
- owalnuts-0.2.0/src/oracle_tests/transition.rs +350 -0
- owalnuts-0.2.0/src/sampler.rs +1221 -0
- owalnuts-0.2.0/src/types.rs +20 -0
- owalnuts-0.2.0/src/walnutpie/research.rs +1226 -0
- owalnuts-0.2.0/src/walnutpie.rs +13654 -0
- owalnuts-0.2.0/tests/chain_rescue.rs +915 -0
- owalnuts-0.2.0/tests/data/arviz_fixture.json +6506 -0
- owalnuts-0.2.0/tests/data/generate_arviz_fixture.py +74 -0
- owalnuts-0.2.0/tests/diagnostics_arviz.rs +64 -0
- owalnuts-0.2.0/tests/export_cmdstan.rs +212 -0
- owalnuts-0.2.0/tests/freeze_mode.rs +177 -0
- owalnuts-0.2.0/tests/kernel_fingerprint.rs +227 -0
- owalnuts-0.2.0/tests/nonfinite_position_policy.rs +142 -0
- owalnuts-0.2.0/tests/public_facade.rs +2705 -0
- owalnuts-0.2.0/tests/sampler_api.rs +883 -0
- owalnuts-0.2.0/tests/structured_refresh.rs +633 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to oWALNUTS are documented here. Kernel behaviour is
|
|
4
|
+
identified by `owalnuts::walnutpie::ALGORITHM_REVISION`; a seed reproduces a
|
|
5
|
+
run only under the same revision, crate build, lock file, target
|
|
6
|
+
architecture and operating system (see the `walnutpie` module documentation).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-09-04
|
|
9
|
+
|
|
10
|
+
The kernel is at revision `walnutpie-warmup-telemetry-tau0.6-m1-r2-e1-d3-v10`
|
|
11
|
+
(unchanged since 0.1.0-beta.2: every pinned fingerprint and oracle still
|
|
12
|
+
holds); the paper adaptation mode advances to
|
|
13
|
+
`walnutpie-paper-adaptation-kquantile-gamma-v4`. Evidence for every claim is a
|
|
14
|
+
checksummed study under `STUDIES/` (study codes in brackets). Summary in
|
|
15
|
+
`wiki/release-0.2.0.md`.
|
|
16
|
+
|
|
17
|
+
### Upgrading from 0.1.0-beta
|
|
18
|
+
|
|
19
|
+
- The `walnutpie` facade is still exported unchanged; every 0.1 call site
|
|
20
|
+
compiles. New code should use `owalnuts::sampler`, whose `run` paths are
|
|
21
|
+
thin wrappers over the same entry points and produce bit-identical draws.
|
|
22
|
+
- Research-only items (`OuterOrbitSelection`,
|
|
23
|
+
`ResearchTargetEvaluationLimit`, `ResearchRestartReferenceMultiplier`,
|
|
24
|
+
`DualAveragingAcceptance::AcceptedTrajectory`,
|
|
25
|
+
`TargetEvaluationLimitProvenance::ExplicitResearchOptIn`, the
|
|
26
|
+
`direct_original_q` family, the projected/pooled arrowhead warmup) now need
|
|
27
|
+
`features = ["research"]`.
|
|
28
|
+
- `sampler::Tuning::default()` is **not** `walnutpie::KernelTuning::default()`,
|
|
29
|
+
and `RunConfig` keeps the frozen legacy defaults: the sampler defaults are
|
|
30
|
+
max depth 10, macro step `h = 0.5` and `delta = 1`, with eight refinement
|
|
31
|
+
levels; `KernelTuning::default()` remains the frozen replay tuning of
|
|
32
|
+
`ALGORITHM_REVISION` (depth 3). Runs configured through `RunConfig` are
|
|
33
|
+
unaffected; runs configured through `Sampler` without an explicit
|
|
34
|
+
`.tuning(..)` use the new defaults.
|
|
35
|
+
- `PaperAdaptationConfig::default()` changed behaviour (v4, see Changed). The
|
|
36
|
+
v3 behaviour is one builder call away.
|
|
37
|
+
- `Sampler` caches the initial evaluation by default (see Changed): draws
|
|
38
|
+
are bit-identical but every transition makes one target call fewer, so
|
|
39
|
+
target-call totals and ESS-per-call figures are not comparable with 0.1
|
|
40
|
+
runs; `Sampler::cache_initial_evaluation(false)` restores the old
|
|
41
|
+
accounting, and `RunConfig` keeps the cache off.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- **Warmup-time chain rescue** (`STUDIES/chain_rescue_v1`,
|
|
46
|
+
[WP33-CHAIN-RESCUE-V1]). `walnutpie::WarmupConfig::with_chain_rescue(ChainRescueConfig)`
|
|
47
|
+
makes the multi-chain diagonal driver (`sample_chains_with_control` and
|
|
48
|
+
its wrappers; the `sampler` identity and diagonal paths) advance the
|
|
49
|
+
chains window by window and meet at the end of every slow metric window.
|
|
50
|
+
`ChainRescueConfig::restart_from_best()` scores every chain on the window
|
|
51
|
+
just completed (post-boundary step; median and IQR of the selected
|
|
52
|
+
states' log density) and re-seeds an outlier — step below 0.1x the
|
|
53
|
+
chains' median, or median log density more than three within-chain IQRs
|
|
54
|
+
below the chains' median — from the largest-step non-outlier chain's
|
|
55
|
+
window: one of its positions (drawn with the outlier's own RNG stream),
|
|
56
|
+
its metric, step and dual-averaging state. `pool_at_boundaries()` merges
|
|
57
|
+
the chains' window variances exactly and installs the pooled metric and
|
|
58
|
+
the median step everywhere. Both act only on discarded transitions;
|
|
59
|
+
retained draws come from the unchanged per-chain kernel, and a boundary
|
|
60
|
+
with no outlier changes nothing. Every decision is a `ChainRescueUpdate`
|
|
61
|
+
(`window_index`, `transition`, `chain`, scores, `ChainRescueOutcome::{Kept,
|
|
62
|
+
Skipped, Restarted { source, criterion, source_position, step_after },
|
|
63
|
+
Pooled }`) in `RunTelemetry::chain_rescues`. Internally `run_chain` is
|
|
64
|
+
now `ChainRun::start / advance / finish`; the plain path runs the three
|
|
65
|
+
back to back on one thread and is bit-identical (every fingerprint and
|
|
66
|
+
test unchanged). Single-chain runs ignore the option; the dense and
|
|
67
|
+
structured-refresh facades reject it. Tests: `tests/chain_rescue.rs`
|
|
68
|
+
(determinism across seeds and thread counts, the density rule fires on a
|
|
69
|
+
chain started in a trap and the rescued chain samples the main mode,
|
|
70
|
+
never fires on a Gaussian and the draws are the plain run's, every
|
|
71
|
+
record lies in warmup, skipped boundaries and one chain are the plain
|
|
72
|
+
run, dense rejects).
|
|
73
|
+
|
|
74
|
+
- **Orbit-position diagnostics** (`STUDIES/kernel_gap_v1`,
|
|
75
|
+
[WP30-KERNEL-GAP-V1]). `walnutpie::TransitionDiagnostics` gains
|
|
76
|
+
`orbit_states` (states in the final orbit), `selected_index` and
|
|
77
|
+
`initial_index` (positions of the selected and the initial state within
|
|
78
|
+
it, from the backward end); `WorkTotals` gains
|
|
79
|
+
`accepted_forward_micro_steps` (target calls attached to a built leaf)
|
|
80
|
+
and `refinement_level_built` (built leaves by refinement level). Counters
|
|
81
|
+
only: draws, call counts and fingerprints are unchanged. The kernel's
|
|
82
|
+
`Span` carries `states` and `selected_offset` for them. The reference
|
|
83
|
+
NUTS of `examples/kernel_efficiency.rs` moved to
|
|
84
|
+
`examples/support/reference_nuts.rs` (any `Target`, the same orbit
|
|
85
|
+
statistics); `examples/funnel_kernel_options.rs` takes `--seed` and
|
|
86
|
+
`--sampler-defaults`. The study decomposes the per-gradient gap to NUTS
|
|
87
|
+
on six posteriordb models at CmdStan's adapted step, metric and starts:
|
|
88
|
+
gradients per leaf 1.01x and no selection difference; leaves per orbit
|
|
89
|
+
0.60x under the endpoint U-turn statistic (41-69 % recursive U-turns);
|
|
90
|
+
`UTurnRule::MomentumSum` restores 0.97x leaves and 0.90x ESS per gradient
|
|
91
|
+
(default 0.77x), with the residual refinement's reverse-coarser stops.
|
|
92
|
+
No new option; the recommendation is to re-decide `MomentumSum` as the
|
|
93
|
+
default jointly with `DiagonalMetricRegularization::Stan`.
|
|
94
|
+
- **Per-chain R-hat attribution** (`STUDIES/step_collapse_v1`,
|
|
95
|
+
[WP27-STEP-COLLAPSE-V1]). `diagnostics::Summary` carries
|
|
96
|
+
`chain_disagreement: Option<ChainDisagreement>`: when the maximum rank
|
|
97
|
+
R-hat over parameters exceeds `diagnostics::RHAT_DISAGREEMENT_THRESHOLD`
|
|
98
|
+
(1.01) and there are at least three chains, the maximum R-hat is
|
|
99
|
+
recomputed with each chain left out and the chains whose removal alone
|
|
100
|
+
brings it below the threshold are named (a chain in a second mode, a
|
|
101
|
+
chain that never left its start); an empty list says no single chain
|
|
102
|
+
explains the failure. `Summary`'s `Display` prints the line.
|
|
103
|
+
- **Two opt-in warmup step floors**, off by default, measured and not
|
|
104
|
+
recommended (`STUDIES/step_collapse_v1`): `WarmupConfig::with_step_floor_relative_to_search`
|
|
105
|
+
(the adapted step is at least a fraction of the latest initial-step
|
|
106
|
+
search result; requires a search) and `WarmupConfig::with_max_window_shrink`
|
|
107
|
+
(the adapted step never falls below the step its dual-averaging stream
|
|
108
|
+
started from divided by a factor). Neither moves a collapsed step, the
|
|
109
|
+
search floor loses 30 % on the controls, and the shrink bound pins
|
|
110
|
+
`arma11` chains that need to slide through thirty orders of magnitude.
|
|
111
|
+
- **The frozen-chain escape rule** (`STUDIES/freeze_mode_v1`,
|
|
112
|
+
[WP24-FREEZE-MODE-V1]). `walnutpie::ExhaustionRule::AcceptUnlessDivergent`
|
|
113
|
+
keeps the finest attempt of a leaf that failed `delta` at every refinement
|
|
114
|
+
level unless it is divergent in Stan's sense, `H_end - H_0 >
|
|
115
|
+
divergence_threshold` with `H_0` the transition's initial Hamiltonian:
|
|
116
|
+
one-sided and relative to the transition, so an energy drop of any size is
|
|
117
|
+
accepted and, once the trajectory sits below `H_0`, a leaf's own error no
|
|
118
|
+
longer ends the orbit. Rises below the rounding noise of `H_0`
|
|
119
|
+
(`|H_0| * 2^-40`, `walnutpie::HAMILTONIAN_NOISE_RELATIVE`) count as zero,
|
|
120
|
+
and such a leaf feeds Stan's `min(1, exp(H_0 - H_end))` to the step
|
|
121
|
+
statistic. `WarmupConfig::with_warmup_exhaustion_rule` applies a rule to
|
|
122
|
+
the discarded transitions only. This is what lets a chain slide out of a
|
|
123
|
+
start where the leapfrog is unstable at every step size: the `arma11`
|
|
124
|
+
starts of `posteriordb_bench_v2` (log density -4.5e19 to -1.8e115) froze
|
|
125
|
+
every oWALNUTS arm because WALNUTS' two-sided `|H_end - H_start| <= delta`
|
|
126
|
+
turns every downhill step into an exhaustion and the coarse-endpoint
|
|
127
|
+
statistic then drives dual averaging into a floating-point no-op
|
|
128
|
+
equilibrium (`q + h v == q`); CmdStan escapes the same starts through its
|
|
129
|
+
one-sided test. The one-sided rule is *not* reversible beyond the
|
|
130
|
+
threshold (the funnel tail mass at the sampler defaults drops from 0.024
|
|
131
|
+
to 0.014 when it is applied to retained draws — as does the existing
|
|
132
|
+
two-sided `AcceptBelowDivergenceThreshold`, 0.013), hence the warmup-only
|
|
133
|
+
option.
|
|
134
|
+
- `WarmupConfig::with_minimum_step`: a floor on the adapted step (a
|
|
135
|
+
negative control of the study; a chain whose every leaf fails at every
|
|
136
|
+
step size is not helped by it).
|
|
137
|
+
- `walnutpie::TransitionDiagnostics::{step_size, position_changed,
|
|
138
|
+
acceptance_statistic}`: the macro step the transition ran with, whether
|
|
139
|
+
the selected position differs from the initial one, and the statistic
|
|
140
|
+
fed to dual averaging.
|
|
141
|
+
- `tests/freeze_mode.rs`: the pin and the escape on a synthetic Gaussian
|
|
142
|
+
with an exponential wall, without BridgeStan.
|
|
143
|
+
|
|
144
|
+
- **`owalnuts::sampler`, the 0.2 public API.** One builder, `Sampler`
|
|
145
|
+
(`warmup`, `draws`, `chains`, `seed`, `threads`, `metric`, `adaptation`,
|
|
146
|
+
`tuning`, `limits`, `run`, `run_with_init`, `run_from_random_starts`), one
|
|
147
|
+
result, `Posterior` (chains, flat and per-draw access, telemetry, metadata,
|
|
148
|
+
refresh records), and five small option types: `Metric` (`Identity`,
|
|
149
|
+
`Diagonal`, `Dense`, `Structured`, `StructuredRefresh`), `Adaptation`
|
|
150
|
+
(`None`, `DualAveraging` — the default — `Paper`, and
|
|
151
|
+
`Custom(WarmupConfig)` for the opt-in Stan-parity controls below), `Tuning`,
|
|
152
|
+
`Limits` (target-evaluation budget, worst-case admission, deadline, timeout,
|
|
153
|
+
cancellation, depth-stop limit) and `Init`. Every `run` path is a thin
|
|
154
|
+
wrapper over one `walnutpie` entry point and produces bit-identical draws to
|
|
155
|
+
calling it directly (`tests/sampler_api.rs`); kernel behaviour is unchanged.
|
|
156
|
+
The README quick start and the `gaussian` and `funnel_paper_adaptation`
|
|
157
|
+
examples use it.
|
|
158
|
+
- **Uniform starts with retries.** `sampler::Init` (`Given`, `Uniform {
|
|
159
|
+
radius, max_attempts }`, default `Init::uniform()` = Stan's uniform(-2, 2)
|
|
160
|
+
with 100 attempts), `uniform_starts`, `Sampler::run_with_init` and
|
|
161
|
+
`Sampler::run_from_random_starts`: a start is redrawn until the log density
|
|
162
|
+
and gradient are finite, deterministically given the seed (start RNG
|
|
163
|
+
`splitmix64(seed ^ INIT_SEED_TAG)`, independent of the chain seeds).
|
|
164
|
+
`STUDIES/posteriordb_bench_v1` aborted two `lotka_volterra` seeds on an
|
|
165
|
+
unevaluable single draw; with this rule they proceed as CmdStan's do.
|
|
166
|
+
[WP22-POSTERIORDB-BENCH-V1 follow-up]
|
|
167
|
+
- **`research` Cargo feature (off by default).** The research-only items
|
|
168
|
+
listed under Upgrading are exported from `walnutpie` only with the feature.
|
|
169
|
+
They are still compiled (`src/walnutpie/research.rs`), so no kernel path
|
|
170
|
+
changed; the `STUDIES/` crates and the Python integration enable the
|
|
171
|
+
feature.
|
|
172
|
+
- **Research-only `NonfinitePositionPolicy`.** `KernelTuning::
|
|
173
|
+
with_nonfinite_position` / `sampler::Tuning::nonfinite_position` select
|
|
174
|
+
what a transition does when the integrator hands the target a nonfinite
|
|
175
|
+
position. The default `Abort` is the frozen `v10` behaviour (the run ends
|
|
176
|
+
with `ErrorKind::Numerical`); the opt-in `RejectLeaf` treats the point as a
|
|
177
|
+
zero-density leaf with a zero gradient, like a recoverable target failure,
|
|
178
|
+
and counts it in `WorkTotals::nonfinite_position_rejections`. Runs in which
|
|
179
|
+
the event never occurs are bit-identical under either policy; fingerprints
|
|
180
|
+
unchanged. Motivated by `STUDIES/sspd_target_fatal_diag_v1`; qualified as
|
|
181
|
+
an opt-in only by `STUDIES/nonfinite_position_policy_v1` (WP38).
|
|
182
|
+
- **Diagnostics and CmdStan export.** `owalnuts::diagnostics` computes
|
|
183
|
+
rank-normalised folded split R-hat, bulk/tail/quantile/mean ESS, MCSE of
|
|
184
|
+
the mean, and type-7 quantiles per parameter from `&[&[f64]]` chain views
|
|
185
|
+
(Vehtari et al. 2021; every estimator matches `az.rhat`, `az.ess` and
|
|
186
|
+
`az.mcse` to 1e-6 relative on the committed
|
|
187
|
+
`tests/data/arviz_fixture.json`), and `Summary::from_output` builds a
|
|
188
|
+
Stan-style table for a `MultiChainOutput` with per-chain and pooled
|
|
189
|
+
`SamplerHealth` (divergences, invalid-evaluation, depth-cap and
|
|
190
|
+
refinement-exhaustion stops, mean tree depth, target calls, step size);
|
|
191
|
+
`Summary` implements `Display` as an aligned table.
|
|
192
|
+
`owalnuts::export::CmdStanCsv` writes one CmdStan-format CSV per chain
|
|
193
|
+
(`lp__` recomputed from the target when supplied, `stepsize__`,
|
|
194
|
+
`treedepth__`, `n_leapfrog__` as fused target calls, `divergent__`,
|
|
195
|
+
`energy__` as the transition's initial Hamiltonian, then the draws);
|
|
196
|
+
`arviz.from_cmdstan` loads the files and its `az.summary` agrees with the
|
|
197
|
+
Rust `Summary` (`tests/export_cmdstan.rs`, opt-in via
|
|
198
|
+
`OWALNUTS_ARVIZ_PYTHON`). `accept_stat__` is not emitted because the kernel
|
|
199
|
+
captures acceptance only during warmup. No new dependencies.
|
|
200
|
+
- **Opt-in Stan-parity warmup controls** on `walnutpie::WarmupConfig`:
|
|
201
|
+
`DualAveragingAcceptance::MeanTrajectoryAcceptance` (Stan's
|
|
202
|
+
`accept_stat__`), `InitialStepSearchConfig::stan()` (Stan's
|
|
203
|
+
`init_stepsize`, at the start and after every metric update),
|
|
204
|
+
`DiagonalMetricRegularization::{TowardUnit, Stan}`,
|
|
205
|
+
`with_stan_restart_reference` (`mu = ln(10 h)` on restart),
|
|
206
|
+
`with_initial_phase_max_error` (a different `delta` for the initial fast
|
|
207
|
+
phase), and the preset `WarmupConfig::stan_style(target)`;
|
|
208
|
+
`sampler::Adaptation::Custom(WarmupConfig)` passes any of them through the
|
|
209
|
+
builder. Evidence in `STUDIES/adaptation_parity_v1`: alone, none of the
|
|
210
|
+
four Stan warmup differences helps (Stan's metric prior freezes chains
|
|
211
|
+
started in a tail under `delta = 1`); with the initial-phase `delta` the
|
|
212
|
+
full preset at depth 10 is 2.0x the default's ESS per gradient (0.68x
|
|
213
|
+
CmdStan) but loses 12-16 % on `kidiq`, `mesquite`, `garch11` and fails
|
|
214
|
+
R-hat on `kidiq`, `earnings`, so it stays opt-in.
|
|
215
|
+
- **Opt-in kernel rule variants** (`STUDIES/kernel_efficiency_v1`).
|
|
216
|
+
`walnutpie::KernelOptions` (`KernelTuning::with_options`,
|
|
217
|
+
`sampler::Tuning::kernel_options`) selects the no-U-turn predicate
|
|
218
|
+
(`UTurnRule::{Endpoints, EndpointsWithCross, MomentumSum}`; `MomentumSum`
|
|
219
|
+
is Stan's generalised criterion on the sum of the leaf momenta with the
|
|
220
|
+
2.21+ cross checks) and the treatment of a leaf that fails `delta` at
|
|
221
|
+
every refinement level (`ExhaustionRule::{Stop,
|
|
222
|
+
AcceptBelowDivergenceThreshold}`; the latter is Stan's rule, subject to
|
|
223
|
+
the usual reverse coarsening check so the leaf stays reversible).
|
|
224
|
+
`RunConfig::with_cached_initial_evaluation` reuses the previous
|
|
225
|
+
transition's selected log density and gradient instead of re-evaluating
|
|
226
|
+
the current position at the start of every transition. `KernelOptions::
|
|
227
|
+
default()` and the cache off reproduce the frozen fingerprints; on the
|
|
228
|
+
100-D Gaussian `MomentumSum` with the cache takes the kernel from 0.81x to
|
|
229
|
+
1.09x reference NUTS ESS per gradient, is neutral within seed noise on the
|
|
230
|
+
correlated Gaussian and Eight Schools, and preserves the funnel tail mass
|
|
231
|
+
(`examples/funnel_kernel_options.rs`). The preregistered posteriordb
|
|
232
|
+
decision (`STUDIES/uturn_default_v1`, [WP26-UTURN-DEFAULT-V1]) kept
|
|
233
|
+
`Endpoints` as the default: `MomentumSum` is 1.06x geomean over the 17
|
|
234
|
+
models (1.18–2.14x on six, 0.78–0.93x on seven), passes one cell fewer,
|
|
235
|
+
and the funnel at the sampler defaults is biased under every rule.
|
|
236
|
+
- **Additive, off-by-default Appendix C guards** on `PaperAdaptationConfig`:
|
|
237
|
+
`with_min_max_error`, `with_first_update_after`,
|
|
238
|
+
`with_metric_update_required`, `with_unhealthy_orbits_excluded`,
|
|
239
|
+
`with_trim_fraction`, `with_exhausted_transitions_as_zero` (a default since
|
|
240
|
+
v4, see Changed), plus `PaperAdaptationOutcome::Deferred`. Measured in
|
|
241
|
+
`STUDIES/paper_adaptation_robust_v1`; kernel fingerprints unchanged.
|
|
242
|
+
- **FFI and autodiff backend support.** `RawTarget` wraps a C-ABI fused
|
|
243
|
+
log-density/gradient callback (`RawTargetFn`) so compiled gradients —
|
|
244
|
+
numba/Cython `cfunc`s, BridgeStan-style entry points — run from parallel
|
|
245
|
+
chains with no interpreter lock; `-inf` returns follow the v10 recoverable
|
|
246
|
+
zero-density path and any other nonfinite output is fatal. References,
|
|
247
|
+
boxes, and `Arc`s of targets are now targets (`&dyn Target` works
|
|
248
|
+
everywhere). Fatal target error messages are carried into `Error` and shown
|
|
249
|
+
by its `Display`. `Target::parameter_names` (default `None`) labels
|
|
250
|
+
unconstrained coordinates for diagnostics export. Motivated by the autodiff
|
|
251
|
+
track's measurement that GIL-free callback transport, not sampler
|
|
252
|
+
efficiency, was the remaining gap to nutpie on PyMC models.
|
|
253
|
+
[WP15a-AUTODIFF-BRIDGESTAN-ENZYME-V1, WP15B-PYTHON-TARGETS-V1]
|
|
254
|
+
- **`owalnuts-autodiff` (`integrations/autodiff`, unpublished).** Write a
|
|
255
|
+
log density once as `fn log_density<S: Scalar>(&self, q: &[S])`, evaluate
|
|
256
|
+
it with `f64` or with `Var` on a reusable thread-local arena tape, and get
|
|
257
|
+
an `AutodiffTarget<M>` implementing `walnutpie::Target`. Fused primitives
|
|
258
|
+
(`normal_lpdf`/`lupdf` with broadcasting, Student-t, Cauchy, lognormal,
|
|
259
|
+
exponential, gamma, half-normal, `bernoulli_logit`, `poisson_log`, `dot`,
|
|
260
|
+
`sum`, `log_sum_exp`, `cumsum`, `softplus`, and the exp/logistic/interval/
|
|
261
|
+
ordered constraints with log-Jacobians) with hand-gradient and
|
|
262
|
+
finite-difference oracles; numbers in `integrations/AUTODIFF-RESEARCH.md`.
|
|
263
|
+
- **`owalnuts-bridgestan` (`integrations/bridgestan`, unpublished):**
|
|
264
|
+
`ReplicatedStanTarget` (one library copy per thread, `try_lock` dispatch)
|
|
265
|
+
and a per-library-file serialising mutex for `StanTarget`, after the
|
|
266
|
+
measurement that `STAN_THREADS` on mingw-w64 costs 9-16x per gradient
|
|
267
|
+
(emulated TLS); the non-threaded build matches CmdStan's per-gradient
|
|
268
|
+
cost (`arK` wall 10.5 s -> 1.5 s against CmdStan's 1.0 s, trajectories
|
|
269
|
+
bit-identical). [WP22-POSTERIORDB-BENCH-V1 follow-up]
|
|
270
|
+
- **Boundary-refreshed structured metrics.** `sample_structured_refresh`,
|
|
271
|
+
`sample_chains_structured_refresh`, and
|
|
272
|
+
`preflight_chains_structured_refresh` run the fixed kernel directly in
|
|
273
|
+
original coordinates through a `StructuredBlockMass` operator and rebuild it
|
|
274
|
+
with a caller-supplied `StructuredMetricRefresh` at every completed slow
|
|
275
|
+
warmup-window boundary (per-chain Welford `WindowSummary`; typed
|
|
276
|
+
`StructuredRefreshUpdate` telemetry; optional boundary step re-search;
|
|
277
|
+
`StructuredRefreshRestartPolicy` for dual averaging; failures keep the
|
|
278
|
+
previous metric installed; the metric freezes before the first retained
|
|
279
|
+
transition). Execution identity `STRUCTURED_REFRESH_REVISION`
|
|
280
|
+
(`walnutpie-structured-metric-refresh-v1`); an identity refresh is
|
|
281
|
+
bit-identical to the fixed direct driver, and `ALGORITHM_REVISION` is
|
|
282
|
+
unchanged. Motivated by the T=1000 state-space result that the posterior-
|
|
283
|
+
precision path block depends on global parameters best estimated during
|
|
284
|
+
warmup. [WP4B-REAL-TARGET-PATH-METRIC-V1, WP12-SSPD11-CONFIRMATION-V1]
|
|
285
|
+
- **Python package `owalnuts` 0.2.0** (`integrations/python`, unpublished):
|
|
286
|
+
every run builds an `owalnuts::sampler::Sampler`, so the package inherits
|
|
287
|
+
the sampler defaults (see Fixed); `owalnuts.DEFAULTS` reports them
|
|
288
|
+
read-only from the Rust constants; `init="uniform"` start rule
|
|
289
|
+
(`Init::uniform`), `Tuning(u_turn_rule=, exhaustion_rule=)` and
|
|
290
|
+
`Adaptation(metric_regularization=)` overrides, `SampleResult.summary()`
|
|
291
|
+
backed by `owalnuts::diagnostics`, plus the `from_cfunc` /
|
|
292
|
+
`from_pymc(gil_free=True)` GIL-free transport and the structured-metric
|
|
293
|
+
refresh callback. See its README.
|
|
294
|
+
- **Python package on PyPI.** `pip install owalnuts` (`owalnuts[stan]`,
|
|
295
|
+
`[jax]`, `[torch]`, `[pymc]`, `[numba]`, `[arviz]`): abi3 wheels
|
|
296
|
+
(CPython 3.10+, one per platform) for Linux x86_64/aarch64 (manylinux),
|
|
297
|
+
macOS x86_64/arm64 and Windows x86_64 plus a self-contained sdist (maturin
|
|
298
|
+
vendors the root crate and `integrations/bridgestan` into it), built and
|
|
299
|
+
tested against a fresh interpreter by `.github/workflows/wheels.yml` and
|
|
300
|
+
published from `v*` tags through PyPI trusted publishing.
|
|
301
|
+
- **Stan models from Python.** `owalnuts.from_stan(stan_file, data, seed=,
|
|
302
|
+
make_args=)` compiles a Stan program with the `bridgestan` package on Linux
|
|
303
|
+
and macOS (no `STAN_THREADS` by default) and
|
|
304
|
+
returns a `StanTarget` that `owalnuts.sample` runs through the Rust
|
|
305
|
+
`owalnuts_bridgestan::ReplicatedStanTarget` — snapshotted library replicas,
|
|
306
|
+
GIL-free — with the model's
|
|
307
|
+
unconstrained parameter names on the result (`bs_param_unc_names`, new in
|
|
308
|
+
the bridgestan crate) and `StanTarget.constrain(result)` for the
|
|
309
|
+
constrained draws (`bs_param_constrain`). Cargo feature `stan` of the
|
|
310
|
+
extension (default on); the bridgestan crate's tape-backend `bench` binary
|
|
311
|
+
moved behind its own `bench` feature. Windows 0.2 disables `from_stan` and
|
|
312
|
+
direct Python model operations; use the Rust owned-worker API.
|
|
313
|
+
- `examples/kernel_bench.rs` (kernel hot-path microbenchmark) and
|
|
314
|
+
`tests/kernel_fingerprint.rs` (bit-exact run fingerprints in both build
|
|
315
|
+
profiles).
|
|
316
|
+
|
|
317
|
+
### Changed
|
|
318
|
+
|
|
319
|
+
- **POST-STUDY DEFAULT CHANGE (WP36): chain rescue is disabled by default.**
|
|
320
|
+
`Adaptation::DualAveraging` and `Adaptation::Paper` no longer install a
|
|
321
|
+
rescue on identity or diagonal multi-chain runs, and
|
|
322
|
+
`sampler::DEFAULT_CHAIN_RESCUE` is now the truthful
|
|
323
|
+
`Option<ChainRescueConfig> = None` (`STUDIES/chain_rescue_v2`,
|
|
324
|
+
[WP36-CHAIN-RESCUE-V2]). WP36 launched all 288 frozen one-shot cells; 281
|
|
325
|
+
were process-valid, while six Windows heap-corruption exits and one
|
|
326
|
+
post-result timeout left six paired triplets invalid. The registered
|
|
327
|
+
completeness gate therefore failed. Two-hit reduced nuisance unique-chain
|
|
328
|
+
actions from 35 to 14, but `two_hit` failed its conjunctive gates, including
|
|
329
|
+
the registered minimum sample size, efficacy, funnel, origin-safety and
|
|
330
|
+
efficiency requirements. That failure alone did not select `no_rescue`; it
|
|
331
|
+
advanced the mechanical rule to the `current` fallback check. `current`
|
|
332
|
+
separately had registered red lines in four origin-overwrite cells (five
|
|
333
|
+
events) plus unknown run history for HMM/92104, so the fallback selected
|
|
334
|
+
`no_rescue`. The classifier found pathological/frozen ARMA and
|
|
335
|
+
Lotka-Volterra origins and zero HMM origins, so WP36 does not establish
|
|
336
|
+
genuine posterior-mode destruction. Immediate
|
|
337
|
+
`restart_from_best` retains WP33's strong bad-start efficacy and remains
|
|
338
|
+
fully opt-in, together with observe-only, two-hit and pooling, through
|
|
339
|
+
`Adaptation::Custom(WarmupConfig::with_chain_rescue(...))`. A restart that
|
|
340
|
+
acts copies state between chains and therefore invalidates the
|
|
341
|
+
independent-start interpretation of ordinary R-hat. Historical WP35 replay
|
|
342
|
+
must use study revision `8d3a7b5` and its recorded source under test
|
|
343
|
+
`aa4510f`: running `STUDIES/posteriordb_bench_v6` at current HEAD would use
|
|
344
|
+
the intentional no-rescue `Adaptation::default()` and would not reproduce
|
|
345
|
+
WP35. Default sampler output is bit-identical to an otherwise identical
|
|
346
|
+
explicit custom no-rescue warmup, and its rescue telemetry is empty. This is
|
|
347
|
+
the post-study default commit, not an algorithm revision: the retained kernel,
|
|
348
|
+
`walnutpie::WarmupConfig` / `RunConfig` defaults, walnutpie facade outputs,
|
|
349
|
+
and all core fingerprints are unchanged.
|
|
350
|
+
|
|
351
|
+
- **HISTORICAL TEMPORARY DEFAULT CHANGE (WP33, by the preregistered rule): the sampler's own
|
|
352
|
+
adaptation modes apply `sampler::DEFAULT_CHAIN_RESCUE =
|
|
353
|
+
ChainRescueConfig::restart_from_best()` on the identity and diagonal
|
|
354
|
+
metrics with at least two chains** (`STUDIES/chain_rescue_v1`,
|
|
355
|
+
[WP33-CHAIN-RESCUE-V1]). The study preregistered the rule — a candidate
|
|
356
|
+
flips if it passes >= 3 more of 27 cells (8 posteriordb models x 3 fresh
|
|
357
|
+
seeds + 3 funnel cells) than the plain driver, no model below 0.9x
|
|
358
|
+
ESS/gradient, no new reference disagreement |z| > 3.5, funnel |z| <= 2 on
|
|
359
|
+
every seed — before the implementation existed, and restart-from-best
|
|
360
|
+
met every part: 25 vs 21 cells (`lotka_volterra` 0/3 -> 3/3, the frozen
|
|
361
|
+
chain caught at the first boundary, 25 min -> 5 min on one seed; `arma11`
|
|
362
|
+
2/3 -> 3/3, the crawl cell R-hat 1.60 -> 1.003), per-model ESS/gradient
|
|
363
|
+
1.00–289x (geomean 2.72), `mesquite` and `nes2000` byte-identical,
|
|
364
|
+
funnel z +0.94 / −0.77 / −1.02, max |z| unchanged within noise. Pooling
|
|
365
|
+
(`pool_at_boundaries`) gained two cells and fell to 0.42x on
|
|
366
|
+
`lotka_volterra`; it stays opt-in. Draws of every multi-chain `Sampler`
|
|
367
|
+
run on an identity or diagonal metric with `Adaptation::DualAveraging`
|
|
368
|
+
or `Adaptation::Paper` can change (they are the plain draws whenever no
|
|
369
|
+
chain is an outlier at any boundary); `Adaptation::Custom` is used as
|
|
370
|
+
given (the opt-out), dense and structured metrics, single-chain runs,
|
|
371
|
+
`walnutpie::WarmupConfig::default()`, `RunConfig`, `ALGORITHM_REVISION`
|
|
372
|
+
and the kernel fingerprints are unchanged. Read
|
|
373
|
+
`RunTelemetry::chain_rescues` before trusting R-hat on a multimodal
|
|
374
|
+
target: a `LogDensity` rescue merges a chain into the others and hides
|
|
375
|
+
the mode it had found. Three of six predictions failed and are reported
|
|
376
|
+
(the density rule also fires on chains merely late at the first
|
|
377
|
+
boundary; the funnel 88101 R-hat is not a one-bad-chain failure; the
|
|
378
|
+
rule was predicted not to be met). `tests/sampler_api.rs` mirrors the
|
|
379
|
+
new default.
|
|
380
|
+
|
|
381
|
+
- **DEFAULT CHANGE (post-hoc after WP31): `sampler::Tuning::default()`
|
|
382
|
+
U-turn rule `Endpoints` -> `MomentumSum`, and the sampler's own
|
|
383
|
+
adaptation modes regularise the diagonal metric with Stan's prior**
|
|
384
|
+
(`STUDIES/joint_default_v1`, [WP31-JOINT-DEFAULT-V1]; validated by
|
|
385
|
+
`STUDIES/posteriordb_bench_v5`, [WP32-POSTERIORDB-BENCH-V5]). New
|
|
386
|
+
constants `sampler::DEFAULT_U_TURN_RULE = UTurnRule::MomentumSum` and
|
|
387
|
+
`sampler::DEFAULT_METRIC_REGULARIZATION = DiagonalMetricRegularization::Stan`;
|
|
388
|
+
`Adaptation::DualAveraging` and `Adaptation::Paper` apply the latter to
|
|
389
|
+
the `WarmupConfig` they build (as they apply `DEFAULT_WARMUP_EXHAUSTION`);
|
|
390
|
+
`Adaptation::Custom` is used as given. This is a **post-hoc decision**:
|
|
391
|
+
WP31 preregistered a flip rule (geomean >= 1.15x, no model < 0.85x, gates
|
|
392
|
+
>= the default's, funnel |z| <= 2 at both tunings, Eight Schools >= 0.9x)
|
|
393
|
+
and the pair met four of five — 1.51x geomean over the 17 posteriordb
|
|
394
|
+
models, 41 vs 35 cells, funnel exact with zero divergences, Eight Schools
|
|
395
|
+
1.29x — but failed the per-model floor on the two cells no option passes
|
|
396
|
+
(`hmm_drive_0`'s arm-dependent second-mode draw at 0.005x, the centered
|
|
397
|
+
eight schools at 0.79x and 0/3 everywhere), so the study did not flip.
|
|
398
|
+
The flip was decided afterwards on that evidence and validated on fresh
|
|
399
|
+
seeds 87101–87103 against CmdStan 2.39.0 and nutpie 0.16.8 with
|
|
400
|
+
preregistered predictions (WP32; numbers in `wiki/release-0.2.0.md`).
|
|
401
|
+
Draws of every `Sampler` run that adapts a diagonal metric or uses
|
|
402
|
+
`Tuning::default()` change; `walnutpie::KernelOptions::default()`,
|
|
403
|
+
`walnutpie::WarmupConfig::default()`, `RunConfig`, `ALGORITHM_REVISION`
|
|
404
|
+
and the kernel fingerprints are unchanged, and the old behaviour is
|
|
405
|
+
`Tuning::new().kernel_options(KernelOptions::default())` with
|
|
406
|
+
`Adaptation::Custom(WarmupConfig::new(0.8).with_warmup_exhaustion_rule(DEFAULT_WARMUP_EXHAUSTION).with_metric_regularization(DiagonalMetricRegularization::TowardUnit))`.
|
|
407
|
+
`tests/sampler_api.rs` mirrors the new defaults, and the Python package
|
|
408
|
+
inherits them through `Sampler` (see Fixed).
|
|
409
|
+
- **`sampler::Tuning::default()` refinement levels 4 -> 8**
|
|
410
|
+
(`STUDIES/funnel_defaults_v1`, [WP28-FUNNEL-DEFAULTS-V1]). At four
|
|
411
|
+
levels the sampler defaults (adapted diagonal metric, dual averaging,
|
|
412
|
+
`h0 = 0.5`) put half the exact mass below `omega = -5` on Neal's funnel
|
|
413
|
+
(0.0203 / 0.0242 / 0.0625 on three fresh seeds against 0.0478, z -3.5 /
|
|
414
|
+
-3.8 / +0.3, ~1,000 retained refinement exhaustions per cell): the
|
|
415
|
+
adapted step cannot enter the neck with micro-steps no finer than
|
|
416
|
+
`h / 16`. At eight levels (`h / 256`) the tail mass is 0.0412 / 0.0346 /
|
|
417
|
+
0.0897 (|z| <= 1.43), exhaustions fall 26x and divergences 7x, at 1.08x
|
|
418
|
+
the funnel's target calls, 1.05x the ESS per call on the noncentered
|
|
419
|
+
Eight Schools and 1.00x on a 100-D Gaussian (two of three seeds
|
|
420
|
+
call-for-call identical: the cap never engages there). The preregistered
|
|
421
|
+
grid also rejected `delta` 0.5 / 0.25 alone (more exhaustions, worse
|
|
422
|
+
bias), `Adaptation::Paper` from `h0 = 0.5` (one seed at z -2.7; 0.4x on
|
|
423
|
+
the Gaussian) and the `stan_style` preset (biased; one cell errored on a
|
|
424
|
+
nonfinite density). Eight levels remove the bias, not the funnel's poor
|
|
425
|
+
mixing under dual averaging (one chain per seed at `h ~ 0.01`, `omega`
|
|
426
|
+
R-hat 1.01-1.04); `max_error(0.5)` with eight levels mixes better there
|
|
427
|
+
at 0.79x on the Gaussian and stays opt-in. `tests/sampler_api.rs` and the
|
|
428
|
+
Python package default follow; `walnutpie::KernelTuning::default()`,
|
|
429
|
+
`RunConfig`, `ALGORITHM_REVISION` and the kernel fingerprints are
|
|
430
|
+
unchanged.
|
|
431
|
+
|
|
432
|
+
- **`sampler` default warmup exhaustion rule.** `Adaptation::DualAveraging`
|
|
433
|
+
and `Adaptation::Paper` now apply `sampler::DEFAULT_WARMUP_EXHAUSTION`
|
|
434
|
+
(`ExhaustionRule::AcceptUnlessDivergent`, below) to the discarded
|
|
435
|
+
transitions; retained transitions keep `Tuning::kernel_options` (the
|
|
436
|
+
frozen two-sided rule), so no retained draw of the frozen kernel changes
|
|
437
|
+
and warmup differs only where a leaf exhausts at every level.
|
|
438
|
+
`Adaptation::Custom` configurations are used as given. On the
|
|
439
|
+
`posteriordb_bench_v2` starts this turns `arma11` from 7 frozen chains of
|
|
440
|
+
12 into 0 (min bulk ESS 1,290-1,460 against 4-7) and passes two of three
|
|
441
|
+
`lotka_volterra` seeds against one, with the funnel tail mass and the
|
|
442
|
+
centered Eight Schools unchanged or better (`STUDIES/freeze_mode_v1`).
|
|
443
|
+
`tests/sampler_api.rs` mirrors the new default; `ALGORITHM_REVISION` and
|
|
444
|
+
the kernel fingerprints are unchanged.
|
|
445
|
+
|
|
446
|
+
- **`sampler::Limits` admits the exact worst case by default.** The sampler's
|
|
447
|
+
own defaults (depth 10, eight refinement levels) exceed the conservative
|
|
448
|
+
`walnutpie` admission ceiling for ordinary 4 x 1,000/2,000 runs, which made
|
|
449
|
+
`Sampler::run` fail with a resource-limit error. The worst case is an exact
|
|
450
|
+
bound the run cannot exceed, so admitting against it costs nothing; draws are
|
|
451
|
+
unchanged. `Limits::admit_conservative` restores the 0.1.x check.
|
|
452
|
+
- **`sampler::Tuning::default()` max depth 8 -> 10** (Stan's default). Chosen
|
|
453
|
+
by the preregistered ablation `STUDIES/adaptation_parity_v1` (nine
|
|
454
|
+
posteriordb models, two seeds): 1.45x geometric-mean minimum bulk ESS per
|
|
455
|
+
gradient over the 0.1 defaults, 17/18 gate passes versus 12/18, no model
|
|
456
|
+
worse beyond seed noise; the correlated regressions `diamonds`, `earnings`
|
|
457
|
+
and `sblrc` capped 55-85 % of transitions at depth 8 and failed every gate.
|
|
458
|
+
Geomean versus CmdStan on the nine models: 0.34x -> 0.49x. Only the
|
|
459
|
+
`sampler` default changes; `walnutpie::KernelTuning::default()` and
|
|
460
|
+
`WarmupConfig::default()` are the frozen `v10` legacy, `ALGORITHM_REVISION`
|
|
461
|
+
is unchanged and the kernel fingerprints still hold.
|
|
462
|
+
- **`sampler::Sampler` caches the initial evaluation by default.** Draws are
|
|
463
|
+
bit-identical to the uncached run; one target call per transition is saved
|
|
464
|
+
(11 % of the gradients on Eight Schools and the 100-D Gaussian at 8-9
|
|
465
|
+
leaves per orbit, `STUDIES/kernel_efficiency_v1`). `walnutpie::RunConfig`
|
|
466
|
+
keeps the cache off so the frozen target-call fingerprints hold;
|
|
467
|
+
`Sampler::cache_initial_evaluation(false)` restores the old accounting.
|
|
468
|
+
- **`PaperAdaptationConfig::default()` is `walnutpie-paper-adaptation-kquantile-gamma-v4`.**
|
|
469
|
+
`with_exhausted_transitions_as_zero(true)` and
|
|
470
|
+
`with_step_relative_bound(DEFAULT_PAPER_STEP_RELATIVE_BOUND = 1e6)` are
|
|
471
|
+
now the defaults. `STUDIES/paper_adaptation_robust_v1` traced the
|
|
472
|
+
posteriordb freezes to leaf-less transitions producing no `h` statistic
|
|
473
|
+
and then to the `1e3` step band, not to the `delta` rule; with both
|
|
474
|
+
guards the default is robust on all 14 freeze-model cells and
|
|
475
|
+
0.90-1.35x dual averaging's min bulk ESS per gradient (geomean 1.04).
|
|
476
|
+
The `v3` behaviour is
|
|
477
|
+
`.with_exhausted_transitions_as_zero(false).with_step_relative_bound(PAPER_STEP_RELATIVE_BOUND)`.
|
|
478
|
+
Acceptance-driven warmup, `ALGORITHM_REVISION` and the kernel
|
|
479
|
+
fingerprints are unchanged.
|
|
480
|
+
- **Allocation-free kernel hot path, bit-identical.** Micro-steps write the
|
|
481
|
+
gradient into the state's own buffer (`FusedEval`), a per-transition
|
|
482
|
+
workspace replaces the clones in refinement and reverse coarsening, span
|
|
483
|
+
endpoints share `Rc` states, leaf states live in a per-thread ring, the
|
|
484
|
+
endpoint joint log density is reused for the reverse check, and the
|
|
485
|
+
per-call kinetic energy is skipped when no proposal observer is attached.
|
|
486
|
+
Kernel overhead per fused target call (single thread, best of 3,
|
|
487
|
+
`examples/kernel_bench.rs`): funnel 480 -> 255 ns, Gaussian-100D 2002 ->
|
|
488
|
+
936 ns, Eight Schools 889 -> 510 ns; allocations per call 8.5 -> 0.19,
|
|
489
|
+
14 -> 0.56 and 14.4 -> 1.27. Every fingerprint, oracle and facade test is
|
|
490
|
+
unchanged in both build profiles.
|
|
491
|
+
- Crate description, README and module docs point first-time readers at
|
|
492
|
+
`owalnuts::sampler`; the `walnutpie` docs describe it as the complete
|
|
493
|
+
facade underneath.
|
|
494
|
+
|
|
495
|
+
### Fixed
|
|
496
|
+
|
|
497
|
+
- **Windows BridgeStan native lifetime is isolated on one owned OS thread.**
|
|
498
|
+
Every library/symbol, construct, metadata/name, gradient/error-free, and
|
|
499
|
+
model-destruct call now runs on that owner; bounded-channel callers never
|
|
500
|
+
enter model code, and drop joins through native TLS teardown. Windows
|
|
501
|
+
`ReplicatedStanTarget::requested_replicas()` preserves the caller's request
|
|
502
|
+
while `effective_replicas()` returns one; `threading()` reports serialized
|
|
503
|
+
effective execution, `compiled_threading()` preserves the DLL capability,
|
|
504
|
+
and `execution()` names the owned-serialized backend. Model bytes use a
|
|
505
|
+
verified SHA-256 cache and
|
|
506
|
+
module-global setup is locked; a process-global Windows native-call mutex
|
|
507
|
+
additionally serializes all owner threads/models through setup, evaluation,
|
|
508
|
+
error free, and destruction. Non-Windows replica 0 preserves the original
|
|
509
|
+
`$ORIGIN`/`@loader_path` path while replicas 1..n use one source snapshot.
|
|
510
|
+
The first resident-DLL/scoped-pool mitigation was rejected because its fixed
|
|
511
|
+
arm still faulted in 8/180 children. The owned-one follow-up had zero faults
|
|
512
|
+
in 540 children against 19/180 event-inclusive comparator faults. A fresh
|
|
513
|
+
fixed-only qualification of the final process-global implementation then
|
|
514
|
+
completed all 540 ordinary and 180 concurrent-four-target children with
|
|
515
|
+
zero process faults, timeouts, missing/incomplete outputs, invariant
|
|
516
|
+
mismatches, or correlated Event 1000 records: 0/720, with a one-sided 95%
|
|
517
|
+
upper bound of 0.415210%. Sampling medians remained 3.1–5.1x the
|
|
518
|
+
four-replica comparator. The historical root cause is not proven, and this
|
|
519
|
+
mitigation is qualified only on one Windows GNU host, three model binaries,
|
|
520
|
+
short runs, and one effective worker per target. Windows MSVC,
|
|
521
|
+
Linux/macOS, the package/wheel matrix, and multi-worker Windows execution
|
|
522
|
+
remain unqualified, so release publication stays blocked.
|
|
523
|
+
Python `from_stan` and direct Python `bridgestan.StanModel` call/name/
|
|
524
|
+
constrain paths are disabled on Windows 0.2 because they bypass this Rust
|
|
525
|
+
owner backend. Python `StanTarget.probe_*` metadata now names its one-load
|
|
526
|
+
scope; `SampleResult` alone reports the target actually used for sampling.
|
|
527
|
+
(`STUDIES/bridgestan_lifetime_v1`,
|
|
528
|
+
`STUDIES/bridgestan_owned_worker_v1`)
|
|
529
|
+
- **The Python package goes through `owalnuts::sampler` and inherits its
|
|
530
|
+
defaults.** `integrations/python` built `KernelTuning` / `WarmupConfig` on
|
|
531
|
+
the `walnutpie` facade directly and restated the sampler defaults, so it
|
|
532
|
+
silently missed the ones changed during 0.2.0 (depth 10 and eight
|
|
533
|
+
refinement levels were copied by hand; `DEFAULT_U_TURN_RULE`,
|
|
534
|
+
`DEFAULT_METRIC_REGULARIZATION`, `DEFAULT_WARMUP_EXHAUSTION`, the cached
|
|
535
|
+
initial evaluation and the worst-case admission were not). The PyO3
|
|
536
|
+
`sample` / `preflight` paths now construct `Sampler` with `Tuning`,
|
|
537
|
+
`Adaptation`, `Metric`, `Limits` and `Init` from the Python arguments and
|
|
538
|
+
send only what the caller set, `owalnuts.DEFAULTS` reads the defaults
|
|
539
|
+
from the Rust values, and a test checks that `owalnuts.sample` with
|
|
540
|
+
explicit arguments equal to `DEFAULTS` reproduces a Rust `Sampler` run bit
|
|
541
|
+
for bit on a 3-D Gaussian. Two consequences on the Python surface: an
|
|
542
|
+
explicit `max_target_evaluations` is also the admission ceiling (the
|
|
543
|
+
package used to admit budgeted runs against a 2^50 dummy ceiling), and a
|
|
544
|
+
structured-metric run at the defaults must keep its worst case under the
|
|
545
|
+
hard 1e9 research ceiling, as it always had to.
|
|
546
|
+
- **`Sampler` admits structured metrics with their worst case under the
|
|
547
|
+
`research` feature.** The structured facade paths have no budgeted
|
|
548
|
+
admission variant, so `Limits::admit_worst_case` (the default) left them
|
|
549
|
+
rejected at the conservative 113M ceiling whenever the sampler defaults
|
|
550
|
+
exceeded it. With `features = ["research"]` the sampler now raises the
|
|
551
|
+
`RunConfig` admission ceiling to the budget (capped at the research
|
|
552
|
+
maximum) when, and only when, the worst case exceeds the conservative
|
|
553
|
+
ceiling; runs it already admitted are configured exactly as before, and
|
|
554
|
+
without the feature the facade's conservative admission still applies.
|
|
555
|
+
`tests/sampler_api.rs` covers the admission and the rejection under
|
|
556
|
+
`Limits::admit_conservative`.
|
|
557
|
+
- **BridgeStan targets no longer abort on `NaN`/`+inf` log densities.**
|
|
558
|
+
`owalnuts-bridgestan` maps a `NaN`/`+inf` log density and a finite log
|
|
559
|
+
density with a nonfinite gradient to the recoverable zero-density path
|
|
560
|
+
(`map_evaluation`), as CmdStan and nutpie reject such proposals; only a
|
|
561
|
+
dimension mismatch stays fatal. [WP22-POSTERIORDB-BENCH-V1 follow-up]
|
|
562
|
+
- `sample_chains_structured` and `sample_chains_structured_with_control`
|
|
563
|
+
reject a target, mass, or initial position whose dimensions differ with a
|
|
564
|
+
configuration error instead of panicking.
|
|
565
|
+
|
|
566
|
+
### Validation (2026-09-01 program)
|
|
567
|
+
|
|
568
|
+
- **Step collapse and the post-escape crawl (WP27-STEP-COLLAPSE-V1).** The
|
|
569
|
+
default step collapse on `sblrc` (h 0.003 against CmdStan's 0.10) and
|
|
570
|
+
`earnings` (0.003 against 0.017) is the `v10` diagonal regularisation
|
|
571
|
+
`(n/(n+5)) var + 5/(n+5)`, whose additive term floors every variance at
|
|
572
|
+
0.0099 for `n = 500` while those coefficients have posterior variances
|
|
573
|
+
1e-5 to 5e-4; dual averaging's statistic sits on target throughout and
|
|
574
|
+
holds `h` at the step the 30x-too-wide metric allows. Stan's statistic,
|
|
575
|
+
Stan's `init_stepsize` at every window, the delta ramp, the restart
|
|
576
|
+
reference and two step floors leave `h` unchanged;
|
|
577
|
+
`DiagonalMetricRegularization::Stan` alone restores CmdStan's step (`sblrc`
|
|
578
|
+
9.7x ESS/gradient, `earnings` 1.9x with zero depth-cap draws, `arma11`'s
|
|
579
|
+
healthy chains 2.9x, controls 1.08x) but `earnings` then fails R-hat
|
|
580
|
+
(1.02, min bulk ESS 164–179: at CmdStan's step and metric the WALNUTS
|
|
581
|
+
orbit is 49 leaves against NUTS's 163, a retained-kernel U-turn matter),
|
|
582
|
+
so the preregistered flip rule is not met and the regularisation stays
|
|
583
|
+
opt-in, recommended for regressions with coefficient scales below ~0.1.
|
|
584
|
+
The `arma11` 79103 crawl (chain 3 at `h` 5e-8 after escaping the wall) is
|
|
585
|
+
a start CmdStan cannot leave in 1,000 iterations either (same log
|
|
586
|
+
density plateau at −3.7e5 from iteration 250, final `h` 1.7e-6, R-hat
|
|
587
|
+
1.6); no warmup option changes it. Seeds 80101–80102, seven models, ten
|
|
588
|
+
arms, CmdStan 2.39.0 from the same starts.
|
|
589
|
+
- **posteriordb v3 (WP25-POSTERIORDB-BENCH-V3).** With the WP24 warmup rule, the
|
|
590
|
+
dual-averaging arm passes 35/51 cells (CmdStan 37, nutpie 31), zero frozen
|
|
591
|
+
chains, geomean 0.34x CmdStan ESS per gradient, 0.75x wall per gradient,
|
|
592
|
+
1.35x nutpie ESS per second. `STUDIES/posteriordb_bench_v3`.
|
|
593
|
+
|
|
594
|
+
- posteriordb benchmark against CmdStan and nutpie
|
|
595
|
+
(`STUDIES/posteriordb_bench_v1`, 17 posteriors x 4 arms x 3 seeds, 204
|
|
596
|
+
cells): with the 0.1 defaults the dual-averaging arm was 0.32x CmdStan and
|
|
597
|
+
0.25x nutpie on minimum bulk ESS per gradient over 14 models (gate passes
|
|
598
|
+
26/51 versus CmdStan 34/51, nutpie 29/51), refinement engaged on 1 % of
|
|
599
|
+
retained leaves, and the v3 paper arm froze on nine models. This study
|
|
600
|
+
motivated the depth-10 default, the start retries, the BridgeStan fixes and
|
|
601
|
+
the Appendix C v4 defaults above.
|
|
602
|
+
[WP22-POSTERIORDB-BENCH-V1]
|
|
603
|
+
- posteriordb v2 on the 0.2.0 defaults (`STUDIES/posteriordb_bench_v2`, the
|
|
604
|
+
same 17 posteriors, five arms, three fresh seeds, all 255 cells present):
|
|
605
|
+
the dual-averaging arm passes 32/51 cells (CmdStan 35, nutpie 27; the
|
|
606
|
+
`stan_style` arm 32, the Appendix C v4 arm 29 versus 8 in v1) with zero
|
|
607
|
+
cells lost to a fatal evaluation or start failure; geomean 0.233x CmdStan
|
|
608
|
+
bulk ESS per gradient over 17 models (0.447x over the 15 models without a
|
|
609
|
+
frozen chain), 0.771x CmdStan wall per gradient and 1.108x nutpie ESS per
|
|
610
|
+
second. `arma11` and `lotka_volterra` chains freeze from uniform starts
|
|
611
|
+
where every leaf fails at every refinement level; the Appendix C v4 arm is
|
|
612
|
+
at parity with dual averaging (geomean 0.995), so it stays opt-in. The
|
|
613
|
+
breadth-throughput release gate (P1–P3, P5) is not met.
|
|
614
|
+
[WP23-POSTERIORDB-BENCH-V2]
|
|
615
|
+
- Adaptation ablation (`STUDIES/adaptation_parity_v1`, nine posteriordb
|
|
616
|
+
models, two seeds): depth 10 over depth 8 gives 1.45x geomean minimum bulk
|
|
617
|
+
ESS per gradient and 17/18 gate passes; geomean versus CmdStan 0.49x; the
|
|
618
|
+
full `stan_style` preset reaches 0.68x but regresses four models and fails
|
|
619
|
+
R-hat on two, so it is opt-in.
|
|
620
|
+
- Appendix C robustness (`STUDIES/paper_adaptation_robust_v1`): the v4
|
|
621
|
+
default is robust on all 14 previously freezing posteriordb cells and
|
|
622
|
+
0.90-1.35x dual averaging's minimum bulk ESS per gradient (geomean 1.04).
|
|
623
|
+
- Kernel efficiency against a clean-room reference NUTS
|
|
624
|
+
(`STUDIES/kernel_efficiency_v1`, ESS per gradient, seed medians): the
|
|
625
|
+
default kernel is 0.81x on Eight Schools, 0.75x on the 100-D Gaussian and
|
|
626
|
+
1.03x on the 50-D correlated Gaussian; with the initial-evaluation cache
|
|
627
|
+
0.91x / 0.81x / 1.06x, and with Stan's momentum-sum U-turn rule as well
|
|
628
|
+
0.86x / 1.09x / 1.07x. The gap decomposes into the wasted re-evaluation
|
|
629
|
+
(exact, now cached by default), the endpoint U-turn rule (0.75x on the
|
|
630
|
+
isotropic Gaussian, 1.0x on the correlated one) and refinement rejections
|
|
631
|
+
(0.85-0.95x where refinement engages), consistent with the posteriordb
|
|
632
|
+
0.7-0.8x.
|
|
633
|
+
- Sampler API parity: every `Sampler::run` path is bit-identical to the
|
|
634
|
+
`walnutpie` entry point it wraps (`tests/sampler_api.rs`); the
|
|
635
|
+
allocation-free kernel reproduces the pinned run fingerprints in debug and
|
|
636
|
+
release (`tests/kernel_fingerprint.rs`).
|
|
637
|
+
- Diagnostics: R-hat, ESS (bulk, tail, quantile, mean) and MCSE match ArviZ
|
|
638
|
+
to 1e-6 relative on `tests/data/arviz_fixture.json`; `az.summary` over the
|
|
639
|
+
exported CmdStan CSV agrees with the Rust `Summary`.
|
|
640
|
+
|
|
641
|
+
## [0.1.0-beta.2] - 2026-08-31
|
|
642
|
+
|
|
643
|
+
First release candidate. The kernel is at revision
|
|
644
|
+
`walnutpie-warmup-telemetry-tau0.6-m1-r2-e1-d3-v10`; the paper adaptation mode
|
|
645
|
+
is at `walnutpie-paper-adaptation-kquantile-gamma-v3`. Evidence for every claim
|
|
646
|
+
below is a checksummed study under `STUDIES/` with an entry in
|
|
647
|
+
`wiki/research-ledger-2026-08-31.md` (entry ids in brackets).
|
|
648
|
+
|
|
649
|
+
### Fixed
|
|
650
|
+
|
|
651
|
+
- **Micro-step acceptance statistic (kernel `v9`).** Through `v8` a refinement
|
|
652
|
+
level was accepted when the largest Hamiltonian departure of *any* visited
|
|
653
|
+
micro-step from the start state was within `max_error`. That statistic is
|
|
654
|
+
not symmetric under time reversal, so the deterministic reverse selection
|
|
655
|
+
could disagree with the forward selection and non-reversible leaves were
|
|
656
|
+
accepted. On Neal's 10-D funnel at the paper's tuning the kernel placed about
|
|
657
|
+
twice the correct mass below `omega = -5` (0.0971 vs the exact 0.0478).
|
|
658
|
+
Acceptance now uses the endpoint departure `|H(end) - H(start)|` exactly as
|
|
659
|
+
upstream `walnutpie::macro_step`/`within_tolerance`. Verified by a
|
|
660
|
+
4,000-leaf funnel differential oracle generated from the unmodified upstream
|
|
661
|
+
headers (`oracle/walnutpie/f5bba365_funnel_leaves`; `v8` disagreed on 1,555
|
|
662
|
+
leaves, `v9` agrees on all 4,000 to 1e-11) and by
|
|
663
|
+
`STUDIES/funnel_bias_fix_v1` (P(omega<-5) 0.0474 at 4×50,000 draws).
|
|
664
|
+
Runs that never refine are unchanged; every earlier result from a
|
|
665
|
+
refinement-active run is provisional until re-run.
|
|
666
|
+
[WP2-FUNNEL-REPRO-V1, WP6-FUNNEL-BIAS-FIX-V9]
|
|
667
|
+
- **Recoverable target failures refine instead of stopping (kernel `v10`).**
|
|
668
|
+
Through `v9` a `TargetError::recoverable` result stopped the whole transition
|
|
669
|
+
(`StopReason::InvalidEvaluation`), so a stiff model whose coarse micro-steps
|
|
670
|
+
overshoot into a non-representable region produced no-op transitions (65–100%
|
|
671
|
+
of transitions on the Stock–Watson study). Upstream maps a failed evaluation
|
|
672
|
+
to `logp = -inf`, `grad = 0` (`walnutpie/util.hpp`), so the micro-step merely
|
|
673
|
+
fails the endpoint tolerance and the leaf refines; only when every level
|
|
674
|
+
fails is the leaf rejected by refinement exhaustion. `v10` does exactly that,
|
|
675
|
+
excludes zero-density points from the Hamiltonian extrema and divergence
|
|
676
|
+
statistic, and reports them as `zero_density_evaluations` per transition and
|
|
677
|
+
per work partition. A successful run can no longer report
|
|
678
|
+
`StopReason::InvalidEvaluation` (retained in the enum). Nonfinite *returned*
|
|
679
|
+
values (`NaN`, `+inf`, nonfinite gradient) remain fatal. Verified by a
|
|
680
|
+
4,000-leaf differential oracle with a throwing wall target
|
|
681
|
+
(`oracle/walnutpie/f5bba365_invalid_leaves`) and by
|
|
682
|
+
`STUDIES/invalid_evaluation_parity_v1` (0 invalid-evaluation stops across
|
|
683
|
+
216,000 retained transitions and 8.5M recoverable evaluations; truncated
|
|
684
|
+
Gaussian stationary; funnel unchanged). [WP2b-SW-REPRO-V1,
|
|
685
|
+
WP10-INVALID-EVALUATION-PARITY-V10]
|
|
686
|
+
- **Paper-mode `h` statistic (paper adaptation `v2`).** The unrefined fraction
|
|
687
|
+
is now taken over *built* leaves, a transition without a built leaf
|
|
688
|
+
contributes no sample and no step update
|
|
689
|
+
(`PaperAdaptationUpdate::transitions_without_statistic`), and the installed
|
|
690
|
+
paper-mode step is bounded to `PAPER_STEP_RELATIVE_BOUND` (1e3) times the
|
|
691
|
+
configured initial step. Under `v1`, all-invalid transitions counted as fully
|
|
692
|
+
unrefined and could drive `h` to its 1e6 ceiling. [WP9]
|
|
693
|
+
- The pinned upstream macro-leaf oracle no longer carves out
|
|
694
|
+
`forward_refinement`, `backward_refinement`, and
|
|
695
|
+
`multi_level_reverse_coarsening`; all pinned cases pass unmodified.
|
|
696
|
+
|
|
697
|
+
### Added
|
|
698
|
+
|
|
699
|
+
- **JMLR Appendix C adaptation**, opt-in through
|
|
700
|
+
`WarmupConfig::with_paper_adaptation(PaperAdaptationConfig)`: the K-quantile
|
|
701
|
+
rule for `delta` (`max_error`) and `Gamma`-targeted dual averaging for `h`
|
|
702
|
+
(`step_size`), with `PaperStepStatistic`, `PaperRestartPolicy`,
|
|
703
|
+
`PaperAdaptationUpdate` telemetry, `WarmupCheckpointTelemetry::
|
|
704
|
+
unrefined_fraction`/`max_error_after`, and `PAPER_ADAPTATION_REVISION`.
|
|
705
|
+
On Neal's funnel from a conservative start it is unbiased and, with the `v3`
|
|
706
|
+
default, 1.41×/1.61× (bulk/tail ESS per target call) more efficient than the
|
|
707
|
+
paper's fixed funnel tuning. [WP7-FUNNEL-ADAPTIVE-V1,
|
|
708
|
+
WP9-PAPER-H-RULE-STABILISATION-V2]
|
|
709
|
+
- Research-only outer-orbit selection switch
|
|
710
|
+
`RunConfig::with_research_outer_orbit_selection` (`OuterOrbitSelection::
|
|
711
|
+
{BiasedProgressive, ExactNormalizedMultinomial}`); the default is unchanged.
|
|
712
|
+
- Fixed-metric facades: `DenseMass`, `BlockDiagonalMass`,
|
|
713
|
+
`StructuredBlockMass` (`StructuredCovarianceBlock::{BidiagonalCholesky,
|
|
714
|
+
ScaledAr1}`, linear time), `LowRankArrowheadMass`, and the versioned
|
|
715
|
+
`sample_direct_original_q` family (`DirectOriginalQMass`,
|
|
716
|
+
`DIRECT_ORIGINAL_Q_REVISION`) executing dynamics in target coordinates.
|
|
717
|
+
- Research-only projected/pooled arrowhead warmup (`sample_projected_arrowhead`,
|
|
718
|
+
`sample_chains_projected_arrowhead`, `PROJECTED_ARROWHEAD_REVISION`); frozen
|
|
719
|
+
at its stage-7 state and not recommended for use (see
|
|
720
|
+
`wiki/sampler-path-ledger.md`).
|
|
721
|
+
- `RunConfig::with_research_target_evaluation_limit`
|
|
722
|
+
(`ResearchTargetEvaluationLimit`, `RESEARCH_MAX_TARGET_EVALUATIONS`) and the
|
|
723
|
+
explicitly budgeted entry points `sample_chains_with_target_budget` /
|
|
724
|
+
`preflight_chains_with_target_budget` for deep-refinement runs that exceed
|
|
725
|
+
the conservative admission ceiling.
|
|
726
|
+
- Warmup telemetry checkpoints, `DualAveragingAcceptance::AcceptedTrajectory`
|
|
727
|
+
(research-only), `InitialStepSearchConfig`, and trace-only depth diagnostics
|
|
728
|
+
(final U-turn predicate dots, margin, physical trajectory length).
|
|
729
|
+
- Examples `funnel_paper_adaptation` and `state_space_path_metric`; CI on
|
|
730
|
+
Linux/Windows GNU 1.88 and Linux stable.
|
|
731
|
+
|
|
732
|
+
### Changed
|
|
733
|
+
|
|
734
|
+
- **Paper-mode default restart policy (paper adaptation `v3`).**
|
|
735
|
+
`PaperAdaptationConfig::default()` continues one dual-averaging stream
|
|
736
|
+
across `delta` installations (`PaperRestartPolicy::
|
|
737
|
+
ContinueThroughLocalErrorInstall`); restarting left chain-specific final
|
|
738
|
+
steps (spread 1.7–2.8×) while continuing gave ≤ 1.3× with equal or better
|
|
739
|
+
efficiency in both tested `Delta` families. Select
|
|
740
|
+
`RestartOnLocalErrorInstall` to reproduce the `v1`/`v2` behaviour.
|
|
741
|
+
[WP9-PAPER-H-RULE-STABILISATION-V2]
|
|
742
|
+
- `ALGORITHM_REVISION` advanced `v8 → v9 → v10`. Seeds are not portable across
|
|
743
|
+
revisions; both changes leave the frozen default tuning bit-identical (no
|
|
744
|
+
pinned fingerprint changed) because it never reaches a leaf where the old and
|
|
745
|
+
new rules differ.
|
|
746
|
+
- Crate description and README rewritten for a first-time reader; the
|
|
747
|
+
"internal beta" framing is replaced by an explicit validated-results table
|
|
748
|
+
and known-limitations list.
|
|
749
|
+
|
|
750
|
+
### Validation (2026-08-31 program)
|
|
751
|
+
|
|
752
|
+
- Neal's funnel, paper tuning (δ = 0.21, h = 0.36, WALNUTS-D, depth 10, 10
|
|
753
|
+
levels), 4×50,000 draws: P(omega<-5) 0.0474 vs exact 0.0478 (z −0.08),
|
|
754
|
+
var(omega) 9.04 vs 9, zero divergences/invalid/exhaustions; the upstream
|
|
755
|
+
reference at identical tuning gives 0.0477. [WP6-FUNNEL-BIAS-FIX-V9]
|
|
756
|
+
- Neal's funnel, Appendix C warmup from δ = 1, h = 0.1 (`v3` default): all
|
|
757
|
+
gates pass, final-step spread ≤ 1.27× across chains, 1.41×/1.61× bulk/tail
|
|
758
|
+
ESS per call versus fixed paper tuning. [WP9-PAPER-H-RULE-STABILISATION-V2]
|
|
759
|
+
- Noncentered Eight Schools, v38 strict track, kernel `v9`: conservative
|
|
760
|
+
minimum over seven seeds and six functionals 12,830 bulk / 10,346 tail ESS/s
|
|
761
|
+
(CmdStan 6,290 / 3,951; BlackJAX 5,645 / 4,195; NumPyro 5,241 / 4,050); ESS
|
|
762
|
+
per target call unchanged from `v7` (0.96 / 0.99). Walls were measured on a
|
|
763
|
+
loaded machine. [WP8-EIGHT-SCHOOLS-V9-REBENCH-V1]
|
|
764
|
+
- Outer-selection reverse ablation on Eight Schools: biased progressive
|
|
765
|
+
selection (the default) gives 1.75× bulk ESS per target call over exact
|
|
766
|
+
normalized multinomial with no tail penalty. [WP3-1]
|
|
767
|
+
- Exact Gaussian state-space ground truth, T ∈ {100, 1000}: the
|
|
768
|
+
posterior-precision tridiagonal path metric mixes at depth 3–4 at
|
|
769
|
+
Monte-Carlo accuracy (ESS/call 4.8× identity, ~1,000× a prior-based metric,
|
|
770
|
+
which caps at depth 8 in 92% of transitions at T = 1000). [WP4-ESSGT-V1]
|
|
771
|
+
- Real Polyscope state-space target at T = 1000 (non-pathological fixture):
|
|
772
|
+
oWALNUTS with an adapted diagonal in centered coordinates passes every gate
|
|
773
|
+
NumPyro NUTS passes, confirmed on three fresh seeds; the posterior-precision
|
|
774
|
+
path block agrees on every seed and gives 2.7× ESS per call but passed the
|
|
775
|
+
strict conjunctive gate on 2/3 seeds (not yet confirmed)
|
|
776
|
+
[WP12-SSPD11-CONFIRMATION-V1]. On the σ_x → 0
|
|
777
|
+
funnel fixture no tested Euclidean sampler passes, NumPyro included (1,510
|
|
778
|
+
divergences). [WP4B-REAL-TARGET-PATH-METRIC-V1]
|
|
779
|
+
- Stock–Watson stochastic volatility (simulated series): the paper's fixed
|
|
780
|
+
tuning does not reproduce the paper's energy-error contrast on this series;
|
|
781
|
+
the Appendix C adaptation arm passes every gate on 2/3 fresh seeds at
|
|
782
|
+
4×2,000 draws (miss: R-hat 1.0101, clean health) and is 2.0× more efficient
|
|
783
|
+
per call than the fixed tuning. [WP2b-SW-REPRO-V1,
|
|
784
|
+
WP12-SSPD11-CONFIRMATION-V1]
|
|
785
|
+
|
|
786
|
+
### Errata
|
|
787
|
+
|
|
788
|
+
- The Eight Schools figures previously circulated as oWALNUTS's "conservative
|
|
789
|
+
minimum across seeds and six functionals" (19,054.65 bulk / 14,494.34 tail
|
|
790
|
+
ESS/s) were the minimum over functionals of the across-seed *median*, while
|
|
791
|
+
competitor figures in the same table were true minima. The like-for-like
|
|
792
|
+
`v7` minimum was 8,634 / 5,949 ESS/s (still fastest among the strict matched
|
|
793
|
+
competitors, 1.37× / 1.42×). The corrected `v9` numbers are above.
|
|
794
|
+
[WP8-EIGHT-SCHOOLS-V9-REBENCH-V1]
|
|
795
|
+
|
|
796
|
+
## [0.1.0-beta.1] - internal, never published
|
|
797
|
+
|
|
798
|
+
### Added
|
|
799
|
+
|
|
800
|
+
- Minimal standalone `owalnuts` crate containing the fixed-diagonal internal-beta facade.
|
|
801
|
+
- Bounded Gaussian example, facade tests, resource controls, telemetry, and run identity metadata.
|
|
802
|
+
- Private parity tests and pinned upstream oracle fixtures with provenance.
|
|
803
|
+
- Opt-in acceptance-driven warmup (dual-averaged step, Welford diagonal mass)
|
|
804
|
+
and configurable `KernelTuning`.
|
|
805
|
+
|
|
806
|
+
### Removed
|
|
807
|
+
|
|
808
|
+
- Legacy weighted sampler, NUTS, Python, benchmark, and prototype public surfaces.
|