Repository navigation
Add the Log-Normal Race Model (lnr) on the shared race core (#352) - #501
Open
GidonFrischkorn wants to merge 21 commits into
Open
GidonFrischkorn wants to merge 21 commits into
GidonFrischkorn wants to merge 21 commits into
Conversation
…cture (#352) Rebuilt from 352-log-normal-race-model (c01cfd1) on race-model-core: the model, its distribution functions (now R/distributions-lnr.R), tests, article and fixtures, with the coding, validation, revert, observables and epred integrator taken from the shared race helpers. The model registers its shared-test cases in helper-model-lnr.R and its column roles and response annotations in its own file. Changes against the branch: accumulator columns may give a category zero accumulators on a trial it does not win (the likelihood and simulators already skipped them); error messages of the accumulator checks follow the shared wording; a category named Intercept, or after an identifier of the generated likelihood (rt, t, m, n, lp, ...), is refused instead of failing in stanc.
…ect the accumulators docs and article Categories named start, end, seq, ptarget or nn are refused: brms declares them in threaded programs and stanc rejects the program. plnr(q, response) no longer returns NaN near the lower support edge and no longer warns for a vector ndt. plnr(response = NA) fails with the dlnr message. qlnr() works on the log survivor, so NA propagates and tiny upper-tail probabilities invert. The accumulators docs say a column may hold 0 on trials the category does not win. The lnr() examples run as written. The article gains a custom hierarchical example, a prior override example and reader-facing definitions. New tests cover the Stan likelihood with zero counts (including a trial where the loser's log survivor underflows), the reserved names, log_lik with zero counts and the distribution fixes.
# Conflicts: # NAMESPACE # NEWS.md
race-model-core now uses develop's test-case registry, which removed load_ppcheck_fit(); the two tests that read the file by path load it the same way (R5).
…ures.R Rule R4 now requires sd_default, init and links cases; the values are the ones default_prior() and settable_links() give, written out. The stored fit becomes a fixture case and is made by tests/local/make-fixtures.R with one chain and 150 draws (R5); the old file had two chains and 400 draws.
The core now lists brms's threading identifiers among the Stan reserved words, so check_model() refuses them before it reaches the names of the lnr likelihood.
The file's own skip_unless_cmdstan() did not honour BMM_TEST_STAN=false.
The five cached fits of the LNR article move to the article-fits release (ADR 0003): the article reads them from fits/, which the website build downloads, instead of from tracked files in assets/.
`seq`, `start` and `end` are in the shared list of Stan reserved names, which the category check reads before the model's own list, so the three entries in the LNR list could never be reached. `ptarget` and `nn` stay: a threaded program declares them and the shared list does not hold them. The test that expects the core's message for the three names is unchanged.
The fixture had 5 divergent transitions in its 150 draws at the default adapt_delta of 0.8. Refit at 0.95 with the same data, seed, chain and draws, it has none (max Rhat 1.017).
The likelihood declared t, m, n, win, lp, reps, i and j, so a custom-version category with one of those names was refused. The locals are now lnr_dt, lnr_m, lnr_cnt, lnr_win, lnr_lp, lnr_reps, lnr_i and lnr_j; no category can carry an underscore, so none can shadow them. The generated programs are identical up to these names in eight configurations (simple and custom, with random effects, threading, a fixed s, count columns, one category). t, m, i, j, win, lp and reps are accepted as categories now. n stays refused: brms adds random effects in a loop over n, and stanc then refuses a category of that name.
ndt and s had exponential(4) on their group-level standard deviations, while lba(), cswald() and ezdm() give the same parameters exponential(2) (ndt also in ddm()). One rate per parameter meaning across the response-time models: ndt and s now get exponential(2) too; the meanlogs keep it. The "Default priors" section said the rates were shared with the other racing models, which was not true against lba(); it now states the common rate and that the between-subject SDs of 0.17 to 0.19 estimated for ndt and s on simulated data lie well inside it.
n, ptarget and nn are now refused for every race model by the shared list, so the LNR list keeps only the generated likelihood's own arguments.
GidonFrischkorn
force-pushed
the
race-lnr
branch
from
October 18, 2026 15:12
8cec21b to
2b23f1d
Compare
…o race-lnr # Conflicts: # tests/testthat/test-conventions.R
The tests below draw random numbers and had no seed, so each CI run tested different values. They now open with withr::local_seed(445), as develop's tests do since the seeding change (ADR 0004, R8).
Resolved tests/local/make-fixtures.R as the union of the lnr and the mpt fixture blocks; NAMESPACE and man/ regenerated with devtools::document().
built_data_columns() names the columns a model's check_data() adds, so standata() routes newdata in the layout given to bmm() through check_data() and pp_check(group = ) hides them (#550, #552). lnr declares .lnr_cat and .lnr_n1 to .lnr_n<k>, two counts for the simple version and one per category for the custom one, as race_bf() hands them to vint().
This branch has not been deployed
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.
Add the Log-Normal Race Model (lnr) on the shared race core (#352)
The Log-Normal Race Model (Rouder et al., 2015) for choice response times with two or more options. Each option has an accumulator whose finishing time is lognormal; the first to finish gives the response, and a non-decision time is added.
lnr()has two versions:simple(one meanlog for the correct response and one shared by the errors, responses coded as integers) andcustom(one meanlog per named response category, with optional per-categoryaccumulators). The sdlogsis shared by all accumulators. Also addsdlnr(),plnr(),qlnr(),rlnr()and an article.This PR now targets
dev-1.6.0, since the race core (#499) is merged. It adds only the model's own files, plus its NEWS bullet, the article, its_pkgdown.ymlentry and its references.What changed since the last description
file-sizecheck) moved to thearticle-fitsrelease, as ADR 0003 asks for new fits. The article reads them asfile = "fits/<name>.rds". I rewrote the branch so that no commit adds them, because the check looks at every commit, not only the tip. Patches, messages and authors of all other commits are unchanged.lnr_prefix, so categories such ast,mornno longer collide with them. The model itself reserves onlyrtandresponse. The names brms declares (nin the random-effects loop,seq/start/end/ptarget/nnunder threading) are refused for every race model by the race core. That needs the small core PR that addsn,ptargetandnn, which is merged into this branch: merge that PR first. The generated Stan programs are identical to the old ones after mapping the names back.ndtandsnow getexponential(2)instead ofexponential(4). That is the ratelba(),rdm(),ddm(),cswald()andezdm()give the same parameters (one rate per parameter meaning). The meanlogs keepexponential(2).pp_checkfixture had 5 of 150 divergent transitions. It is refitted withadapt_delta = 0.95and now has none; its design is unchanged.Checks (local, macOS, R 4.6.1)
NOT_CRAN=true, Stan tests on, 4 workers): 2012 tests, 0 failed, 0 skipped.R CMD check --no-manual: 0 errors, 0 warnings, 1 NOTE. The NOTE ("Cannot extract version info" for NEWS.md) comes from the two development headings that ADR 0002 puts ondev-*branches. Everydev-*branch has it.file-sizecheck failed on this PR after the retarget, because of the old fit. It passes on the rewritten branch: no commit adds a file above 1 MiB.Not run: a render of the article (the fits must be in the release first), and the recovery and cross-validation scripts of the original branch. The likelihood has not changed since they ran.
Merging next to #500 and #502
The three model PRs merge in any order. The second and third conflict in
NEWS.md(keep both bullets),NAMESPACEandman/bmm.Rd(rundevtools::document()), andtests/testthat/test-conventions.R(keep both files' entries). This PR and #502 also both add a fixture block totests/local/make-fixtures.R; keep both, each with its own closing brace.For the reviewer
Most of the code was written with AI assistance and has had AI-assisted reviews. What it needs from a person is judgement on the model: the likelihood against the source, the parameterisation and defaults, and whether the documentation makes sense to a user. About 3 hours.
Pass 1: likelihood and Stan (about 1 h).
R/model_lnr.R: the generated likelihood (.lnr_stan_code()) andconfigure_model(). The simple version's likelihood is the probability of the observed category (correct, or any error). It therefore differs from a per-accumulator likelihood bylog(K - 1)per error trial, which shiftsloo()but not the posterior. Is that the right choice for the default?Pass 2: R distribution functions and postprocessing (about 1 h).
R/distributions-lnr.R(dlnr(),plnr(),qlnr(),rlnr()), thenlog_lik,posterior_predictandposterior_epredinR/model_lnr.R. Tests:tests/testthat/test-distributions-lnr.R,test-model_lnr.R,test-model_lnr-stan.R(the Stan/R parity tests).Pass 3: docs and article (about 1 h).
?lnr,?lnr_dist,vignettes/articles/bmm_lnr.Rmd.Skim or skip:
man/,NAMESPACE,tests/testthat/helper-model-lnr.R(cases for the cross-model tests), thetest-conventions.Rcounts.Questions where an outside view helps most:
sis shared by all accumulators. The distribution functions accept one per accumulator. Should the model offer per-categorys, or say plainly that it does not?normal(0, 0.5)for its meanlog. Should a fast category get a different default, or should the article show how to set one?accumulators = c(a = 0)) but allowed in a column. Is the asymmetry right?Already checked, so no need to repeat: the rebuild from the original branch is bit-identical (
dlnr/plnr/qlnr/rlnr,log_lik,posterior_predict,posterior_epred,pp_simulate, and the generated Stan code in six configurations). The generated Stan likelihood matches an independent R implementation to 9e-16 on trials with zero counts. Every identifier in the generated program was tested as a category name against stanc. The citation was checked.