lnccbrown / lnccbrown/HSSM

Lapse mixture drops all lapse mass on missing-RT and omission rows

Open
#1,322 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
Python
Stars
124
Forks
24
Avg merge
19h 32m
Merged PRs (30d)
60

Description

## Describe the bug

With `p_outlier > 0` (the default is 0.05), the lapse mixture in `src/hssm/distribution_utils/dist.py` (the block around `lapse_logp = lapse_func(data[:, 0].eval())`, ~L615-634) evaluates the lapse **RT density at the row's `rt` column for every row** and mixes `log((1-p)·exp(logp) + p·exp(lapse_logp) + 1e-29)`.

For a missing-RT row (`rt = -999.0`, choice observed → CPN) or an omission row (`rt = -999.0`, deadline column → OPN) the correct lapse term is not a density at `rt`; it is the lapse component's marginal of what the row observed:

| row type | observed | likelihood under the mixture `f = (1-p)·s + p·l(rt)·q(c)` | lapse term |
|---|---|---|---|
| observed | `(rt, c)` | `f(rt, c)` | `l(rt)·q(c)` |
| missing RT (CPN) | `c` only | `(1-p)·P_s(c) + p·q(c)` | `q(c) = 1/n_choices` |
| omission (OPN) | `rt > d` | `(1-p)·S_s(d) + p·S_l(d)` | `S_l(d) = 1 - CDF_l(d)` |

Today the term is `l(-999)`, which is `-inf` for the default `Uniform(0, 20)` lapse (so the lapse mass is silently dropped and the row collapses to `log(1-p) + logp_missing`) and a finite, meaningless number for a `Normal` lapse.

## Consequences

- Score for `p`: every missing row contributes `-1/(1-p)` regardless of the data, pushing a fitted `p_outlier` toward 0.
- Score for θ: the SSM gets responsibility 1 for every omission (correct weight is `(1-p)·m_s / ((1-p)·m_s + p·m_l)`), so θ drifts toward parameters that predict more omissions.
- Magnitude at the defaults: an omission row at `d = 2 s` with `S_s = 0.05` should be worth `0.95·0.05 + 0.05·0.90 = 0.0925`; today it is `0.0475` — 0.67 nats per omission row.

## To Reproduce

```python
import numpy as np, hssm
# any deadline dataset with omissions and p_outlier=0.05 (default);
# compare model.pymc_model.compile_logp() on an omission row against
# log((1-p)*exp(opn(theta, d)) + p*(1 - d/20))
```

## Expected behavior

`lapse_logp` is a row-typed vector: observed rows `logp(lapse, rt)`; missing-RT rows `-log(n_choices)`; omission rows `log1mexp(logcdf(lapse, d))`. The mixture line is then correct unchanged, and the row likelihoods satisfy `Σ_c P(c) = 1` and `∫_0^d Σ_c f + P(rt > d) = 1`.

Observed rows and choice-only models have a related, separate normalisation issue (tracked separately) — this issue is only about rows with `rt = -999.0`.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/hssm/distribution_utils/dist.py around the lapse_logp block at approximately lines 615-634, then use the issue's omission and missing-RT reproduction with compile_logp(). Done means lapse_logp is row-typed for observed, missing-RT, and omission rows, while the stated normalization checks hold.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.