Skip to content

Fix ensure_positive_ndt for models with trial-to-trial ndt variability - #1292

Open
EItanm1999 wants to merge 1 commit into
lnccbrown:mainfrom
EItanm1999:fix/ensure-positive-ndt-st-aware
Open

Fix ensure_positive_ndt for models with trial-to-trial ndt variability#1292
EItanm1999 wants to merge 1 commit into
lnccbrown:mainfrom
EItanm1999:fix/ensure-positive-ndt-st-aware

Conversation

@EItanm1999

@EItanm1999 EItanm1999 commented Aug 31, 2026

Copy link
Copy Markdown

ensure_positive_ndt floors the log-likelihood wherever rt - t <= 1e-15. That is the correct support edge for a fixed non-decision time, but not for any model carrying st: there t is drawn per trial from Uniform(t - st, t + st), so the fastest admissible response time is t - st and the band [t - st, t] carries real density. The guard replaces that entire band with LOGP_LB regardless of what the likelihood returned.

The comment above the call site states the assumption ("Assuming that the non-decision time parameter is always named t") but nothing checks whether the model also has st. Affects every *_st model, applied unconditionally at dist.py:607 and :617.

Measured on ddm_st: compiling HSSM's observed-RV logp for a model whose likelihood IS the exact ddm_st quadrature and comparing against the same quadrature called directly gave differences of -5.3 to -12586.8 nats, varying with theta. With the guard skipped the two agree to exactly 0.0000 on 7 of 8 parameter vectors (the 8th had a subject z outside its bound). So this guard accounts for the entire discrepancy while parameters stay in bounds.

p_outlier partially masks it: the floored value is wrapped in the lapse mixture, so affected trials emerge at log(0.05/20) = -5.99 rather than at LOGP_LB, which is why this presented as bad geometry rather than an obvious -inf.

Only st moves the response-time support edge. sz (starting point) and sv (drift) do not, and are deliberately not consulted; a regression test pins that down.

Summary by CodeRabbit

  • Bug Fixes

    • Improved response-time boundary handling for models with variable non-decision time.
    • Preserved valid likelihood values while correctly replacing invalid values below the model’s support boundary.
    • Ensured other variability parameters do not incorrectly shift this boundary.
    • Kept full-model boundary behavior unchanged.
  • Tests

    • Added coverage for variable and fixed non-decision-time scenarios.
  • Documentation

    • Documented the corrected support-boundary behavior.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 80781531-7658-41cc-8285-1b27869e4f1d

📥 Commits

Reviewing files that changed from the base of the PR and between 3834b46 and 25a487f.

📒 Files selected for processing (3)
  • docs/changelog.md
  • src/hssm/distribution_utils/dist.py
  • tests/distribution_utils/test_distribution_utils.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

ensure_positive_ndt now derives the response-time support boundary from t and optional st. Tests verify boundary behavior for st, sz, and sv.

Changes

Non-decision time support boundary

Layer / File(s) Summary
Boundary calculation and flooring
src/hssm/distribution_utils/dist.py
ensure_positive_ndt uses t - st when st is present and t otherwise. Its annotations and documentation describe the updated boundary.
Support boundary validation
tests/distribution_utils/test_distribution_utils.py, docs/changelog.md
Parametrized tests verify sentinel replacement at or below the applicable boundary and preservation of valid values. The tests also confirm that sz and sv do not shift the boundary. The changelog documents the correction.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 25a48

This localized change corrects response-time support handling for models with trial-level non-decision-time variability and adds focused regression coverage; no actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: digicosmos86, krishnbera, alexanderfengler

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: correcting ensure_positive_ndt for models with trial-to-trial non-decision-time variability.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. (1 skipped: 1 …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 2 files. (1 skipped: 1 unsupported.)

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ensure_positive_ndt floors the log-likelihood wherever rt - t <= 1e-15. That is
the correct support edge for a fixed non-decision time, but not for any model
carrying st: there t is drawn per trial from Uniform(t - st, t + st), so the
fastest admissible response time is t - st and the band [t - st, t] carries real
density. The guard replaced that entire band with LOGP_LB regardless of what the
likelihood returned. Applied unconditionally at both call sites, affecting every
*_st model.

Measured on ddm_st: compiling HSSM's observed-RV logp for a model whose
likelihood IS the exact ddm_st quadrature and comparing against the same
quadrature called directly gave differences of -5.3 to -12586.8 nats, varying
with theta. With the guard skipped the two agree to exactly 0.0000 on 7 of 8
parameter vectors (the 8th had a subject z outside its bound). So this guard
accounts for the entire discrepancy while parameters stay in bounds.

p_outlier partially masks it: the floored value is wrapped in the lapse mixture,
so affected trials emerge at log(0.05/20) = -5.99 rather than at LOGP_LB, which
is why this presented as bad geometry rather than an obvious -inf.

The t - st edge is stated as correct under the half-width convention of
ssms/cssm, which every LAN *_st model follows, rather than as a universal law.
full_ddm is the one bundled model on the other convention: its only likelihood
is the blackbox wrapper around hddm_wfpt, which reads st as a full width and
puts its own edge at t - st/2. No separate factor is needed, because that edge
sits above t - st and hddm_wfpt already returns zero density across
[t - st, t - st/2), which the wrapper maps to the same lower bound. Verified:
across a 0.28-0.46 RT scan the raw likelihood's highest floored rt is exactly
t - st/2, and the guard changes no value at any rt (max |delta| 0.0).

Only st moves the response-time support edge. sz (starting point) and sv (drift)
do not, and are deliberately not consulted; a regression test pins that down.

Tests are one parametrized case per convention (fixed t, st moves the edge,
sz/sv leave it) over an explicit RT vector that straddles all three edges, so
the in-band assertion no longer depends on an unseeded draw. They compare
against the imported LOGP_LB rather than a -66.1 literal: under
PYTENSOR_FLAGS=floatX=float32 the bound is -66.0999984741211, and wrapping it in
np.array defeats NumPy's weak promotion, so the literal form failed there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@EItanm1999
EItanm1999 force-pushed the fix/ensure-positive-ndt-st-aware branch from 3834b46 to 25a487f Compare September 2, 2026 20:58
@EItanm1999

Copy link
Copy Markdown
Author

@AlexanderFengler

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