Skip to content

Expose MH acceptance rate and fix truncated normal tails - #188

Merged
Gattocrucco merged 8 commits into
mainfrom
neurips-2
Aug 2, 2026
Merged

Expose MH acceptance rate and fix truncated normal tails#188
Gattocrucco merged 8 commits into
mainfrom
neurips-2

Conversation

@Gattocrucco

Copy link
Copy Markdown
Collaborator

Adds an accept property to Bart and mc_gbart with the per-iteration fraction of trees with an accepted grow/prune move, including burn-in and keeping chains separate to allow convergence checks.

Alongside, it fixes truncated_normal_onesided for bounds deep into a tail: the uniform to invert could underflow to 0 (especially on cpu, where xla flushes subnormals), producing infinities, and the inversion saturates at |x| ~ 12.9 in float32, which can leave the sample outside the truncation region. The sample is now clipped to the smallest normal number and collapsed onto the boundary when it falls short of it. This makes step_z valid when the latent is truncated far into a tail by an offset that contradicts the binary outcome.

Minor: gen_params now converts the default offset to a jax array when called directly (outside gen_data it was left an untraced Python float).

Test suite: test_mcmcstep shares one NUM_TREES constant and trims parametrized variants to cut recompilations, and its toy dataset is reworked so every variable is actually splittable.

🤖 Generated with Claude Code

Gattocrucco and others added 8 commits July 25, 2026 19:25
gen_params is jitted and its body assumed every scalar argument
arrives as a traced array. jax does not trace an argument left at
its default, so an omitted offset reached check_offset as a plain
Python float and raised AttributeError. Convert it explicitly, in
the same place the other scalars are cast, so the array also
propagates to the returned Params.

offset is the only argument with a non-None scalar default.
gen_data was unaffected because it always forwards offset to the
jitted gen_params, which traces it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`truncated_normal_onesided` could return ±inf: the lower target of
the clip guard was the smallest subnormal float, which xla flushes
to zero on cpu, and which ndtri maps to -inf anyway. Clip to the
smallest normal float instead, which ndtri maps to ~-12.9.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Past |bound| ~ 12.9 in float32 the inversion of the normal cdf
saturates, so the sample fell short of the truncation region; in probit
this made the latent contradict the outcome it was conditioned on. Clamp
the sample onto the bound, where the distribution concentrates for such
bounds anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Most tests in the module picked an arbitrary forest size (2, 3, 4, 5, 6,
10, 20). Route them all through a single NUM_TREES global so jax can
reuse compiled code instead of recompiling per shape.

The uv/mv equivalence check needs its rtol relaxed from 1e-6 to 1e-5,
matching the error_cov_inv check next to it: with fewer trees the larger
leaves push the float32 reduction noise just past the old threshold.

test_blocked_mass_matches_reference keeps its own tree count, which is
part of the tuning that makes the blocked mass nonzero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The (n, p), (k, df) and k values were not picked for coverage: no
library code branches on them in those ranges. The var dtype is uint8
up to p=256, prec_count_num_trees resolves to None for all these n,
and the wishart and leaf term samplers are fully vmapped over k.

Keep only the values with a role: k=1, which feeds the exactly zero
matrix to chol_with_gersh; one wide and one tall data shape; and dof
both tight and loose relative to k. Drop n=3, which left the mask in
test_error_cov_inv_missing_equals_drop empty, and k=5, which equals
NUM_TREES and so hides a trees/k axis swap.

290 -> 250 tests, 184s -> 163s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`X` was a plain `arange`, so every row past the first was above the
cutpoint range and sent all datapoints to the same child. The trees
grew internal nodes but partitioned nothing: averaged over trees, all
datapoints sat in a single leaf. Bin the index into the cutpoint range
instead, rotating the phase per variable, which takes the occupied
leaves per tree from 1.0 to 2.8 (n=10) and from 1.4 to 3.2 (n=50).

Also assert that the mask in test_error_cov_inv_missing_equals_drop is
neither empty nor full, since either way the two states it compares
coincide and both assertions hold trivially.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add `Bart.accept` and `mc_gbart.accept`, the per-iteration fraction of
trees with an accepted grow or prune move, matching the `accept`
attribute of R BART3. Burn-in samples are included; unlike BART3, the
iterations thinned away by `n_skip`/`keepevery` are not recorded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Gattocrucco
Gattocrucco merged commit 7b3f44f into main Aug 2, 2026
27 checks passed
@Gattocrucco
Gattocrucco deleted the neurips-2 branch August 2, 2026 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant