bmm (development version)
New models
- Add Yes/No Signal Detection Theory (
sdt_yn) for detection and old/new recognition tasks with aggregated response counts. It estimates sensitivity (d) and response bias (criterion), and optionally the unequal-variance ratio (sdratio). Also addsdsdt_yn(),rsdt_yn(),sdt_d()andsdt_criterion(). See?sdt_ynfor the parameters, links, default priors and the designs that identifysdratio. Thanks to @GidonFrischkorn - Add m-Alternative Forced Choice Signal Detection Theory (
sdt_mafc) for accuracy-only m-AFC tasks, where one ofmalternatives carries the signal (DeCarlo, 2012). It estimates sensitivity (d) from response counts and has no bias parameter.mmay be a constant or the name of a data column, so trials with different numbers of alternatives can be fit jointly. Also addsdsdt_mafc()andrsdt_mafc(). See?sdt_mafcfor the parameters, the noise distributions, the default priors and howdrelates to the sensitivitysdt_yn()reports. Thanks to @GidonFrischkorn - Add Ranking Signal Detection Theory (
sdt_ranking) for tasks where observers rankmitems, one of them a studied target, by how strongly they recognise them (Meyer-Grant et al., 2026). It estimates sensitivity (d) from the target’s rank counts, with Gumbel ("gumbel_min") or Gaussian ("normal") noise.mmay be a constant or the name of a data column, so trials with different set sizes can be fit jointly. Also addsdsdt_ranking()andrsdt_ranking(). See?sdt_rankingfor the parameters, the response format and the default priors. Thanks to @GidonFrischkorn
New datasets
- Add
broeder_schuetz_2009_e3, binary old/new recognition data from Broeder & Schuetz (2009, Exp. 3), with five base-rate conditions from 40 subjects. See?broeder_schuetz_2009_e3. - Add
meyer_grant_jakob_2025, ranking data from Meyer-Grant & Jakob (2025): 60 subjects ranking 3, 4 or 5 faces, aggregated to the target’s rank counts per subject and set size. See?meyer_grant_jakob_2025.
New features
- New function
bmm_setup()checks whether your machine can fit models: the C++ toolchain,cmdstanrwith CmdStan,rstan, and the backendbmm()will use. It then compiles and samples a small test model and gives one fix for every check that failed. Whenbmm()fails because the compiler or the Stan backend is missing or broken, the error now points tobmm_setup()(#431). -
bmm(file_refit = "on_change")is now implemented and no longer warns and falls back to"never". The cached fit saved underfileis returned only while the Stan code, the Stan data, the factor levels of the model variables and the algorithm are unchanged; any change refits. The comparison happens wherebrmsmakes it — after the bmm configuration pipeline has produced the Stan code and data, before compilation — so a cache hit costs one run of the pipeline plusstandata()andstancode(): about 0.4 s rather than 0.02 s for an sdm model ofoberauer_lin_2017, far less than compiling and sampling but not free. As inbrms, only those four things are compared, so sampler settings do not force a refit —control = list(adapt_delta = )in particular, and alsoiter,warmup,chains,seed,initandsave_pars: rerunning with a higheradapt_deltaafter divergent transitions, or withsave_pars(all = TRUE)forloo(), returns the cached fit unchanged (#411). -
bmm_options(file_refit = )accepts the same values asbmm(). It previously required a logical, so the string forms could be set only throughoptions(bmm.file_refit = )(#411). -
update()gained thefileandfile_compressarguments.filewrites the updated fit in the same orderbmm()does, i.e. after the bmm postprocessing (#411). - New function
report_priors()reports, for each parameter of a fitted model, its link function, the prior actually used on the sampling scale, and whether it was a bmm default, a brms default, or user-specified; parameters left with improper flat priors are flagged.format = "text"produces methods-section-ready sentences. Parameters that exist only because the family machinery requires them are omitted, so the report lists the model’s own vocabulary: themubrms forces on the response-time custom families, themu2/kappa2second component of abrms::mixture()family, and thetheta2mixture-weight reference the sampler holds at zero (#391). - New function native_parameters() returns posterior draws of the model parameters on their native scale, evaluated over a grid of predictor values. Because draws are returned rather than summaries, contrasts between conditions are ordinary arithmetic on the draws. The inverse link transformation is applied to the draws before any summary is computed, so quantiles and credible intervals are exact. Mixture weights are transformed jointly through their softmax (#388).
- New developer generic native_transform() defines how a model maps its parameters from the sampling scale to the native scale. The default method covers every transformation that can be expressed through an elementwise
linksdeclaration in a.model_*()constructor, or through a single softmax group that is active on every row of the data, so most new models are supported without writing a method.native_transform.non_targets()ships as the worked example of a design-dependent method (#388). - New function
bmm_data_check()prints a human-readable pre-fit data report. It runs the same validation pipeline asbmm()without compiling the model, captures all errors, warnings and messages, and summarizes the response variables (with the coding the model expects), the data columns each parameter formula uses (including factor coding), the number of observations per design cell, and model-specific diagnostics for common data mistakes — e.g. circular responses in degrees or coded on [0, 2*pi), and misplacedNAvalues innt_features/nt_distancesfor set size varying designs. Developers can extend the model-specific diagnostics by addingdata_check_findings()methods, building each finding with the newdata_check_finding()constructor (#389). - Random-effects standard deviations now get domain-informed default priors instead of the
student_t(3, 0, 2.5)default ofbrms, which on a log link lets individual parameters vary by a factor of 12 around the group value. Every model parameter declares ansdentry in itsdefault_priors(next tomainandeffects), applied as a blanketclass = "sd"prior whenever the parameter has random effects. The priors areexponential()on the link scale: rate 1 for memory-strength, mixing-weight and identity-linked drift parameters, rate 2 for boundary, non-decision time, start point, contaminant and log-linked drift parameters, rate 4 for the circular biasmu/mu1. Override by addressing the parameter withdparornlpar, e.g.set_prior("exponential(2)", class = "sd", nlpar = "kappa")(#342). - Correlations among random effects now get an
lkj(2)default prior instead of the uniformlkj(1)ofbrms. For two correlated effects this lowers the prior probability of a correlation beyond ±.9 from 10% to 1.45%, so estimated correlations shrink slightly towards zero. The default applies only to models that estimate a correlation matrix, not to(1 | ID)or(x || ID). To return to the previous behaviour, passprior = set_prior("lkj(1)", class = "cor").report_priors()shows the prior aslkj(2), classcor, as written inset_prior()(#417). -
bmm()now starts every model from tight initial values for its random effects, and mixture2p, mixture3p and imm start their population-level parameters inside the central 50% of their default priors, wherever the predictors can hold the parameter at one value there — a design that cannot, such as~ 0 + poly(x, 2), starts as close as it can; these models and m3 previously usedinit = 1. This reduces thelkj_corr_cholesky_lpdf: Random variable[k] is 0,von_mises_lpdf: Scale parameter is infandRejecting initial valuemessages at the start of warmup. Posteriors are unaffected. A user-suppliedinitstill replaces the bmm default. -
bmm()andupdate()now start Stan’s step-size search at 0.01 instead of 1, which further reduces thelkj_corr_cholesky_lpdf: Random variable[k] is 0andvon_mises_lpdf: Scale parameter is infmessages at the start of warmup. The adapted step size and the posterior do not change. Astep_size(orstepsize) in your owncontrollist wins, andbmm_options(step_size = FALSE)restores Stan’s default for new fits;update()keeps the step size a fit was run with.
Bug fixes
- A name in a model’s
linksargument that names no parameter of that model is now an error instead of being added:sdm(resp_error = "y", links = list(kapa = "identity"))reported a parameterkapawhilekappakept its default link. A name one edit from a parameter is read as that parameter, with a warning; a link allowing values the default excludes warns once. A link the model cannot apply is refused too, naming those it can:links = list(bound = "loglog")used to fail withargument is of length zero. sdm, mixture2p, mixture3p and imm take none (#420). -
sdm no longer fails before sampling when
muis predicted without an intercept (Initial values for vectors are only specified for b-coefficients, sd and z parameters). sdm, ddm, ezdm and cswald no longer fail with the rstan backend when a parameter has exactly one slope (no more scalars to read), nor when their random effects usegr(..., by = ). -
report_priors()no longer fails for a fit whose formula needsdata2, e.g.(1 | gr(ID, cov = A))(Object 'A' was not found in 'data2'). -
report_priors()now names every kind of correlation prior the wayset_prior()documents it, not only the one on group-level effects. A fit with two or moreme()terms was reported as classLmewithlkj_corr_cholesky(1), brms’s internal spelling, instead ofcormewithlkj(1). The same applied torescor,lncorandcortime. -
update()withnewdataor a newformula.no longer fails before sampling for a model that starts from bmm’s initial values (no more scalars to readon rstan,Fitting failedon cmdstanr). The initial values are now built for the data and formula the update fits; aninitpassed toupdate()still wins (#415). - Formulas with
mo()ors()terms no longer start with a partial init list, which made cmdstanr printInit values were only set for a subset of parameters.extract_parameter_dimensions()now reads a declaration sized by an element of a data array, such assimplex[Jmo_c[1]], and simplex parameters get an initial value. -
file_refitno longer accepts a logical that is not a singleTRUE/FALSE.NA,logical(0)and multi-element logicals were coerced to"never", sofile_refit = cfg$refitwith a missing orNAconfig key silently returned the cached fit instead of erroring as it did before"on_change"was added (#411). - Under
file_refit = "on_change", a cached fit thatrestructure()cannot bring forward to the current bmm version is now refitted rather than abortingbmm()with “Unable to restructure the object… Please refit” — that is the one thing"on_change"exists to do. A cached fit with noalgorithmfield also no longer aborts onbrms’s barestopifnot(); the comparison falls back to the Stan code, Stan data and factor levels (#411). - Fix
file_refitsilently falling back to"never"for any capitalisation other than all-lowercase."Always"passed validation, which lowercases, but the coercion that followed it did not, sobmm(..., file = , file_refit = "Always")returned the cached fit instead of refitting (#411). -
update()no longer returns a fit whosefilefield has been dropped, and no longer letsbrmshandle the file at all.brms::update.brmsfit()clearsfileandbrms::brm()writes it before any bmm postprocessing has run, soupdate(fit, file = )stored a plainbrmsfitthatbmm()then refused to read back (Object loaded via 'file' is not of class 'bmmfit'). Worse, when the file already existed,brms::brm()read it and returned its contents instead of fitting, soupdate(fit, newdata = , file = )— rerunning a script that caches its fits — silently discarded the update and returned the old fit with no message.update()now writes the file itself, after the postprocessing, asbmm()does (#411). - Fix
options(bmm.default_priors = FALSE)makingbmm()fail for every model instead of fitting with flat priors.set_default_prior()returnedNULL, whichcombine_prior()indexed unconditionally (second argument must be a list);set_default_prior()now returns an empty prior, andcombine_prior()passes aNULLargument through (#391). -
update()now configures the likelihood for the threading spec that will actually be used.brms::update.brmsfitfalls back to the original fit’sthreadswhen the argument is not passed, but bmm only inspected the new request, so updating a threaded fit without repeatingthreadsemitted the serial likelihood chunk into threaded Stan code — for the sdm model a compile error (Identifier 'COSN' not in scope), and for anyloop = FALSEcustom family a silently mis-sliced likelihood. The fallback follows brms in distinguishing an absentthreadsargument from an explicitthreads = NULL, which turns threading off. -
update()no longer lets a globaloptions(brms.threads = )reach the likelihood configuration. Updating an unthreaded fit under a session-wide threading option emitted the sliced likelihood chunk while brms generated serial Stan code (Identifier 'start' not in scope); the spec of the fit being updated now always wins, as it does in brms. -
update()now re-resolves every parameter whose constant the new formula changes.update()never calledcheck_model(), so the constant was never resolved again and the original fit’s prior overrode the freshly configured one:update(fit, bmf(..., mu ~ 1))returned a model in whichmuwas still pinned,update(fit, bmf(..., kappa = 5))one that reportedkappa = 5while Stan estimatedkappafreely, andupdate(fit, bmf(..., mu = 0.5))one that reportedmu = 0.5while Stan keptmuat the original value — all three with no error or warning. -
ezdm(version = "4par")no longer drops cells in which a boundary was reached fewer than twice or has no RT summaries.brmsexcluded the whole row, response counts included, so the cells with the most extreme accuracy were missing.bmm()now keeps them and warns how many there are; refit such models.newdataforlog_lik()orpredict()now needs the columnsrt_used_upperandrt_used_lower, as infit$data. Where some cells have no usable RT summaries at the upper boundary,pp_check()withoutresp_varleaves those cells out and no longer acceptsnewdataor theloo_*types (#430). -
dezdm(version = "4par")now returns the density without a boundary’s response-time terms where that boundary’s summaries areNA, instead ofNA(#430). -
ezdm_summary_stats(method = "mixture")now returnsn_upperandn_trialsfor the responses itsmean_rt/var_rtare based on. It previously returned the raw counts beside cleaned moments, so every cell told ezdm it rested on more responses than it had and its posterior came out too narrow. Summary statistics change and fits are not reproduced; recompute them and refit. Useguess_rateto set the accuracy expected of a contaminant response (version = "3par"only). A cell whose accuracyguess_ratecannot explain is corrected without it, and warns.adjust_ezdm_accuracy()is deprecated — applying it now removes the same contaminants twice (#423). - The ezdm likelihood no longer assumes that reaction times are normally distributed. Because response times are right-skewed, it treated each cell as more informative than it is, and posteriors, especially for
bound, came out too narrow. Existing ezdm fits are not reproduced: in our simulations, credible intervals for subject-levelboundestimates widen by 20% to 75%, and fitting takes longer (see?ezdm_dist). A fit cached withbmm(file = )is reused unlessfile_refit = "on_change".rezdm()no longer truncatesmean_rtatndt, so with very few trials it can returnmean_rt <= 0, whichbmm()rejects (#407). - Fix numerical failures in ezdm. With large drift rates, the 4-parameter model returned
NaNindezdm(),rezdm(),log_lik()andpp_check(), and both models could return a log-likelihood of-Infwhen the predicted accuracy was very close to 1 but a cell contained errors. Near zero drift, the likelihood jumped where the code switched between formulas, and the response counts said nothing about the direction of drift.dezdm()now rejects counts that are not whole numbers (#407). - The mixing weights
thetat(mixture2p) andthetat/thetant(mixture3p) had noeffectsprior, so any regression coefficient on them was flat. They now getnormal(0, 0.5)on the logit/softmax scale (#305). - The circular bias parameters
mu(sdm) andmu1(mixture2p, mixture3p, imm) had astudent_t(1, 0, 1)intercept prior, uniform over the circle under thetan_halflink, and noeffectsprior, which gave non-reference levels a bimodal prior on the native scale. They now getnormal(0, 0.5)on the intercept (95% of the prior bias within 89 degrees of the target),normal(0, 0.25)on effects (a condition difference with SD 24 degrees) andexponential(4)on random-effects SDs.mu/mu1stay fixed at 0 by default, so this only affects models that free them (#306). - The cswald model no longer rejects extreme response times at high drift rates. Its likelihood returned
NaNthere, which the sampler reported as “Log probability evaluates to log(0)” or as divergent transitions, and could bias the posterior for data with long tails. Fits of such data change; refit to benefit (#387). - Passing
threads = NULLwhileoptions(brms.threads = )is set no longer produces Stan code that fails to compile (Identifier 'start' not in scope) for the sdm and cswald models.threads = NULLnow turns parallelization off, as it does inbrms. -
rsdm()andrmixture2p()now draw each value from its own parameter values when a parameter has one value per draw. Before, every value came from the mix of all parameter values, soposterior_predict()andpp_check()for sdm fits were too wide. Fits are unaffected; rerunposterior_predict()orpp_check().rsdm()no longer fails for largecandkappa, andrmixture2p()accepts vectors ofkappaandp_mem.rejection_sampling()takes arguments of lengthnin...per draw and requires a whole-numbern. The same seed now gives different draws fromrsdm(),rmixture2p(),rmixture3p()andrimm()(#445). -
update()withoutnewdatano longer fails for m3 fits (The response variable(s) corr, other, dist, npl missing in the data) or for mixture3p and imm fits whose formulas do not useset_size(The set_size variable ‘set_size’ must be either a variable in your data or a single numeric value), and no longer warns for sdt_yn fits that the reserved columndist_typewill be overwritten (#429). -
m3 no longer ignores
num_optionswhen its numbers are named after the response categories, as innum_options = c(corr = 1, other = 4, dist = 5, npl = 5). Each category’s activation was combined with itself instead of its number of options, with no error or warning. Such names are now matched to the categories, in any order; names already used by a data column or a parameter are an error. Iffit$formula$formulashows a category twice, as inlog(corr * corr), refit the model, e.g. withupdate(fit)(#449). -
dm3()andrm3()no longer mix up the response categories when the activation formulas are not listed in the order ofresp_cats. Each number of options was applied to whichever activation stood at its position, so densities and simulated counts were wrong; rerun such calls. Fitting withbmm()was not affected.
Other changes
- bmm’s own links now point at popov-lab.github.io/bmm instead of venpopov.com/bmm, which currently redirects to the new address. Update bookmarks when convenient (#433).
- The cswald likelihood now evaluates all observations in one call instead of one at a time. This makes fitting faster, improves the accuracy of the gradients the sampler uses, and adds support for within-chain parallelization:
bmm(..., threads = 2)now works for cswald as it does for sdm. The posterior is unchanged (#387).
bmm 1.3.2
CRAN release: 2026-09-16
Changes to default priors
- Recalibrated the default priors for the m3 activation parameters (
a,c) so that thesimpleandsoftmaxchoice rules imply a comparable, broad prior-predictive range of average performance, and so that thesoftmaxdefaults place equal prior means on general (a) and context (c) activation — centering the impliedc - aprior at zero for fair comparisons under cell-means coding. The mis-scalednormal(0, 2)effect prior oncis replaced by the sharednormal(0, 0.5). Because thesimplerule requiresc > ato predict accurate recall, its defaults remain asymmetric; direct comparisons of context and general activation should use thesoftmaxchoice rule (#364).
New features
- The sdm model now supports within-chain parallelization via the
threadsargument (e.g.bmm(..., threads = 2)), reducing fitting time by up to ~45% in benchmarks (#374). - Add
softplusas an opt-in link function for positively-bounded parameters, as an alternative to the defaultloglink.softplus(x) = log(1 + exp(x))keeps parameters positive while growing linearly for large values, avoiding the numerical blow-up ofexp()and giving predictor effects an additive (rather than multiplicative) interpretation on the natural scale. Enable it per parameter via the model’slinkslist, e.g.m3(...)$links <- list(c = "softplus", a = "softplus")orddm(rt, response, links = list(bound = "softplus"))(#363). -
pp_check()can now check every observable of a model’s likelihood, not just the primary response. brms’spp_check()only plots the brmsYvariable, so the ddm and cswald responses and the ezdm RT variance and accuracy went unchecked. The newresp_varargument selects the observable (e.g.pp_check(fit, resp_var = "response")), including derived ones ("signed_rt"for the RT models;"mean_pc", the proportion of upper-boundary responses, forezdm), andresp_var = "all"returns a panel of all checks drawn from one shared joint simulation.pp_check_vars(fit)lists the available checks, the plot type each uses by default, and the brms standata slots it reads; model authors declare observables via thepp_observables()/pp_simulate()S3 generics. The ezdm checks show how each statistic is distributed across design cells: density overlays for the RT statistics and a histogram for the proportion of upper responses (the newtype = "bars_binned");type = "intervals"compares each cell with its own predictive interval instead. Where an observable is undefined for some cells (an ezdm boundary reached by fewer than two responses), observations are dropped only when the observed value is undefined; undefined simulated values are absorbed by dropping those posterior draws, so the number of observations checked does not depend onndraws(#401).
Bug fixes
- Fix
pp_check()for the RT models silently producing a misleading plot withnegative_rt = TRUE: brms forwarded the argument toposterior_predict()(signed predicted RTs) while the observed response times stayed unsigned, so the plot looked like severe misfit.pp_check()now checks the"signed_rt"observable (with a message) so both halves are signed, and errors for models without signed RTs (#401). - Remove the unreachable
dvargument of the internal ezdmposterior_predictfunctions and its documentation.posterior_predict(fit, dv = "var_rt")silently returnedmean_rt: brms forwardsposterior_predict()dots toprepare_predictions()only, never to the family’s prediction function, sodvwas dropped without a warning and has never worked. Usepp_check(fit, resp_var = ...)instead (#401). - Fix
rezdm(version = "4par")crashing (recycling errors) when some but not all simulated cells produced fewer than 2 responses at the upper boundary — the upper-boundary branch did not subset its moments and non-decision time by the affected cells, while the lower-boundary branch did. The bug was invisible whenever every cell shared the same parameters (the unindexed vectors are then constant), and is reachable throughrezdm()directly; fixing it is a prerequisite for the multi-observablepp_check(), which callsrezdm()with per-draw posterior parameters (#401). - Fix the grouped plot type auto-selected by
pp_check(group = )(e.g.dens_overlay_grouped) being silently dropped whentypewas not supplied, so that brms fell back to the ungrouped type and bayesplot warned about an unrecognizedgroupargument. - Fix initial values being set in two places, where the
initreturned byconfigure_model()was silently overwritten bycreate_initfun(). This caused the m3 model’s intendedinit = 0(needed for stable sampling with thesimplechoice rule and anidentitylink) to be lost, and left deadinitcode in the sdm model.create_initfun()is now the single source of truth for initial values, with model-specific behaviour expressed through S3 methods (#375). - Fix
.pwald()returningNaN/-Infin the upper tail of the shifted-Wald survival function, which propagated todcswald()(and thereforelog_lik/posterior_predict) for the cswald model at extreme reaction times. The R-side survival now uses the stablelog_diff_expform already used by the Stan likelihood (swald_lccdf) (#376). - Fix
print()for model summaries selecting regression-coefficient rows by an unanchored substring match, so a parameter such asacould pull in rows of another parameter likekappa(e.g. in the imm model). Rows are now matched on the exact parameter prefix. This also fixes a crash when only a single coefficient row is shown (#379, #369). -
create_initfun()now matches Stan parameters to model parameters with a word-boundary regex ((^|_)param(_|$)) instead of a substring match, preventing collisions in models with short parameter names (e.g.s,c,a) that are substrings of longer ones (sim,correct,activation); the longest (most specific) match is selected when several apply (#354, #355). -
create_initfun()now resolves initialization terms fromnlparswhen a model parameter is not a distributional parameter, so models built as non-linear brms formulas (e.g. native-multinomial models whose parameters live inbterms$nlpars) no longer error withno applicable method for 'has_intercept' applied to an object of class "NULL"(#362). - The sdm model now emits its serial likelihood under
threading(n, force = TRUE), matching the unsliced Stan code brms generates in that case. Previously the threaded chunk was emitted and the model failed to compile. -
bmm()now warns when a predictor in the formula shares its name with both a predicted parameter and a column in the data. Such a predictor was silently treated as a non-linear term (emitted vianlf()instead oflf()), changing the likelihood without any error. Short parameter names (c,a,s,b) collide naturally with condition codes or columns likeaccuracy/stimulus(#378).
Other changes
- Added an internal consistency check in
configure_prior(): if a model’sfixed_parametersincludes a parameter that itsconfigure_model()never wires into the formula (neither a dpar nor an nlpar), bmm now fails with a clear model-definition error instead of letting a malformedb_Intercept ~ constant()prior reachbrm(). This is a safety net for model development; it cannot be reached through the normalbmm()interface, where an unrecognized parameter is already caught earlier bycheck_formula()(#377). -
print.bmmodel()now also displays the required response variables (one per line, annotated with the expected coding — e.g. radians in [-pi, pi] for the circular models, seconds and 0/1 responses for the trial-wise RT models) and the default parameter links, answering “what does my data frame need to look like?” directly at the console (#392). - The package maintainer contact is now Gidon T. Frischkorn, and the repository URLs point to
https://github.com/popov-lab/bmm.
Developer-facing changes
-
use_model_template()now scaffolds the current model-specification patterns. It generates a flat.{model}_defaultsblock for unversioned models (likeddm) or, with the newversionsargument, a.{model}_version_tableblock for versioned models (likecswald). The generated constructor spells out every field of the model object inline (referencing the defaults/version table forparameters,links,fixed_parameters,default_priors, andinit_ranges), and versioned aliases validateversionwithmatch.arg()(#350). -
Removed the unused
void_mufield from all model definitions and the template. It was assigned but never read anywhere — response-mean suppression is already handled viafixed_parametersand the family’sdpars(#350).
bmm 1.3.1
CRAN release: 2026-06-05
Bug fixes
- Fix
swald_lccdf()returning incorrect log-survival probability when response time equals non-decision time in the cswald model. Previously returned-Infinstead of0(log of survival = 1) (#348).
bmm 1.3.0
CRAN release: 2026-03-30
New models
- Add the Diffusion Decision Model (
ddm) for speeded decision-making tasks with trial-level RT and response data. The model estimates drift rate, boundary separation, non-decision time, and (optionally) relative starting point. Includes distribution functionsdddm()andrddm()(#280). - Add the EZ-Diffusion Model (
ezdm) for speeded decision-making tasks. The model estimates drift rate, boundary separation, and non-decision time from aggregated summary statistics (mean RT, variance of RT, accuracy) using the closed-form equations derived by Wagenmakers et al. (2007). Supports both 3-parameter (symmetric starting point) and 4-parameter (asymmetric starting point) versions based on Srivastava et al. (2016). Implements Bayesian hierarchical estimation following Chavez & Vandekerckhove (2025). Includes distribution functionsdezdm()andrezdm()(#281). - Add the Censored Shifted Wald Model (
cswald) for choice reaction time tasks with two response boundaries. The model estimates drift rate, boundary separation, and non-decision time from trial-level RT and response data. Implements two versions: simple (treats errors as censored correct responses, appropriate for high-accuracy tasks) and crisk (competing risks version with separate accumulators for each response, suitable for balanced accuracy). Includes distribution functionsdcswald(),pcswald(),qcswald(), andrcswald(). Thanks to @GidonFrischkorn
New features
- New S3 method conditional_effects() for
bmmfitobjects. Provides an intuitive interface for visualizing predictor effects on model parameters, with automatic routing between distributional and non-linear parameters, inverse link transformations to show parameters on their natural scale (scale = "native"), softmax handling for mixture3p weight parameters, and filtering of internal model variables (#203). - New S3 methods for emmeans support on
bmmfitobjects. Users can now callemmeans(fit, ~ condition, dpar = "kappa")for any bmmodel (#323). - New pp_check() method for multinomial models (e.g.,
m3). Sincebrms::pp_check()does not support the multinomial family,bmmnow provides a custom method that compares observed and predicted response proportions in theppc_barsstyle frombayesplot. The method supports faceting by experimental conditions viagroup, configurable credible intervals viaprobs, and population-level predictions viare_formula = NA. For non-multinomial models,pp_check()delegates tobrms::pp_check()and auto-selects the grouped plot variant whengroupis specified (#324). - New function parameters() lists all parameters of a
bmmodelorbmmfitobject with descriptions, link functions, and fixed values (#329). - New function extract_stan_blocks() extracts individual program blocks (functions, data, parameters, etc.) from compiled Stan code (#286).
- New function extract_parameter_dimensions() extracts parameter names, dimensions, and types from a Stan parameters block.
- New functions ezdm_summary_stats() and adjust_ezdm_accuracy() to compute and pre-process summary statistics from trial-level RT data for the EZ-Diffusion Model (#291).
- New functions flag_contaminant_rts() and validate_fast_guesses() for trial-level contamination detection in RT data. Identifies fast guesses and attention lapses using mixture modeling and provides Bayesian validation of fast guess assumptions (#307).
- New function create_initfun() creates initialization functions for models that benefit from or require initial values for MCMC sampling (#285).
bmm 1.2.0
CRAN release: 2025-07-24
New models
- Add the Memory Measurement Model (Oberauer & Lewandowsky, 2019) and its generalization as the Multinomial Measurement Model for categorical decision tasks as new model class m3 with three versions: simple span (ss), complex span (cs), and custom. For details, see the article on the
bmmwebsite (#237). Thanks to @GidonFrischkorn and @chenyu-psy
New features
- Updates to the
bmf2bfS3 methods for more flexible translation ofbmmformulasintobrmsformulas(#227). - New function apply_links adds link functions to all non-linear formulas in a bmmformula object.
- New example data set oberauer_lewandowsky_2019_e1 for exploring the m3 model.
- The
file_refitargument of thebmmfunction now accepts character strings likebrms. A warning is given when “on_change” is specified, as this is not currently implemented forbmmodels(#228). (The warning was removed and “on_change” implemented in the development version, see #411.) - New function rejection_sampling
Bug fixes
- Fix conflict in setting default priors when model parameters were transformed in a non-linear formula (#232).
- Allow a NULL formula (
formula(NULL)) to be added to a bmmformula for consistentcy with brms (#264) - Improve error messages when attempting to construct bmmformulas without a left-hand-side variable
Documentation
- Add documentation to the continuous reproduction task article for pre-processing half-circular stimulus spaces when using
bmmodelsof thecircularmodel class (#229, #233). - New online article to accompany the m3 model
bmm 1.0.0
First version of the package on published on CRAN!
New features
- you can now specify to save the bmmfit object generated by bmm() to a file with the file argument, similarly to brms::brm() (#190)
- the parameterization of the imm was adapted to accurately reflect the model as implemented by Oberauer et al. (2017)
- prepare package for CRAN submission
Bug fixes
- fix incorrect specification of default priors when only an interaction is specified (#201)
- the random generation function for the mixture3p and imm returned incorrect samples for some rare parameter combinations, this has now been fixed, so that the functions now return correct samples for all parameter combinations.
Deprecated functions and arguments
- BREAKING CHANGE: the arguments for the distribution functions of the mixture2p and mixture3p model have been change to match the snake_case coding scheme. Instead of pMem and pNT these are now p_mem and p_nt. The old names are deprecated and are no longer supported
bmm 0.5.0
New features
- add a summary() method for bmmfit objects (#144)
- add a global option bmm.summary_backend to control the backend used for the summary() method (choices are “bmm” and “brms”)
- function restructure() now allows to apply methods introduced in newer bmm versions to bmmfit objects created by older bmm versions
- you can now specify any model parameter to be a constant by using an equal sign in the bmmformula (#142)
- you can now choose to estimate parameters that are fixed to a constant by default for all models (#145)
- default priors for all models are now specified via the configure_prior() S3 method (#145)
- cmdstanr will be used as the default backend for brms if the user has it installed (#145)
- various updates to the documentation and data sets
Documentation
- two new online articles that introduce the bmmformula syntax and explain how to extract information from bmmodels such as the generated Stan code and Stan data for each model
Deprecated functions and arguments
- BREAKING CHANGE: remove get_model_prior(), get_stancode() and get_standata(). Due to recent changes in brms version 2.21.0, you can now use the brms functions default_prior, stancode and standata directly with bmm models.
- the function fit_model() is deprecated in favor of bmm() and will be removed in a future version (#163)
- the argument setsize for the mixture3p and IMM models is now called set_size for consistency. The old argument name is deprecated and will be removed in a future version (#163)
- the distributions functions for the imm model are renamed from dIMM, pIMM, rIMM and qIMM to dimm, pimm, rimm and qimm (#163)
- the argument parallel for the bmm() function is deprecated and will be removed in a future version. Use cores instead, as for brms::brm() (#163)
- the models IMMfull(), IMMabc() and IMMbsc() are now called via imm(), imm(version = “abc”) or imm(version = “bsc”). The old names are deprecated and will be removed in a future version (#163)
- the sdmSimple() model is now called sdm(). The old name is deprecated and will be removed in a future version (#163)
bmm 0.4.0
New features
- add a check for the sdmSimple model if the data is sorted by predictors. This leads to much faster sampling. The user can control the default behavior with the sort_data argument (#72)
- the mixture3p and IMM models now require that the intercept must be suppressed when set size is used as a predictor (#96).
- add postprocessing methods for sdmSimple to allow the use of pp_check(), conditional_effects and bridgesampling with the model (#30)
- add informed default priors for all models. You can always use the get_model_prior() function to see the default priors for a model
- add a new function set_default_prior for developers, which allows them to more easily set default priors on new models regardless of the user-specified formula
- you can now specify variables for models via regular expressions rather than character vectors (#102)
- you can now view and set all bmm global options via bmm_options(). See ?bmm_options for more information
- add a start-up message upon loading the package
Bug fixes
- fix a bug in the mixture3p and IMM models which caused an error when intercept was not suppressed and set size was used as predictor
- update() now works properly with bmmfit objects (#95)
- fix a bug in the sort_data check which caused an error when using grouped covariance structure in random effects across different parameters
bmm 0.3.0
New features
- BREAKING CHANGE: The fit_model function now requires a bmmformula to be passed. The syntax of the bmmformula or its short form bmf is equal to specifying a brmsformula. However, as of this version the bmmformula only specifies how parameters of a bmmodel change across experimental conditions or continuous predictors. The response variables that the model is fit to now have to be specified when the model is defined using model = bmmodel(). (#79)
- BREAKING CHANGE: The non_target and spaPos variables for the mixture3p and IMM models were relabeled to nt_features and nt_distances for consistency. This is also to communicate that distance is not limited to spatial distance but distances on any feature dimensions of the retrieval cues. Currently, still only a single generalization gradient for the cue features is possible.
- This release includes reference fits for all implemented models to ensure that future changes to the package do not compromise the included models and change the results that their implementations produce.
- The check_formula methods have been adapted to match the new bmmformula syntax. It now evaluates if formulas have been specified using the bmmformula function, if formulas for all parameters of a bmmodel have been specified and warns the user that only a fixed intercept will be estimated if no formula for one of the parameters was provided. Additionally, check_formula throws an error should formulas be provided that do not match a parameter of the called bmmodel unless they are part of a non-linear transformation.
- You can now specify formulas for internally fixed parameters such as mu in all visual working memory models. This allows you to predict if there is response bias in the data. If a formula is not provided for mu, the model will assume that the mean of the response distribution is fixed to zero.
- there is now an option bmm.silent that allows to suppress messages
- the baseline activation b was removed from the IMM models, as this is internally fixed to zero for scaling and as of now cannot be predicted by independent variables because the model would be unidentifiable.
- the arguments used to fit the bmmodel are now accessible in the bmmfit object via the
fit$bmm$fit_argslist. - add class(‘bmmfit’) to the object returned from fit_model() allowing for more flexible postprocessing of the underlying brmsfit object. The object is now of class(‘bmmfit’, ‘brmsfit’)
- changes to column names of datasets zhang_luck_2008 and oberauer_lin_2017 to make them more consistent
Bug Fixes
- an error with the treatment of distances in the IMMfull and the IMMbsc has been corrected. This versions ensures that only positive distances can be passed to any of the two models.
- removed a warning regarding the scaling of the distances in the IMMfull and the IMMbsc that was specific only for circular distances.
bmm 0.2.2
Bug Fixes
- fixed a bug where passing a character vector or negative values to set_size argument of visual working memory models caused an error or incorrect behavior (#97)
bmm 0.2.0
New features
- New model available - The Signal Discrimination Model by Oberauer (2023) for visual working memory continuous reproduction tasks. See ?sdmSimple. The current version does not take into account non-target activation
- Add ability to extract information about the default priors in bmm models with get_model_prior() (#53)
- Add ability to generate stan code and stan data for each model with get_model_stancode() and get_model_standata() (#81)
- BREAKING CHANGE: Add distribution functions for likelihood (e.g. dIMM()) and random variate generation rIMM()) for all models in the package. Remove deprecated gen_3p_data() and gen_imm_data() functions (#69)
- Two new data sets are available: zhang_luck_2008 and oberauer_lin_2017 (#22)
Documentation
- Website for the development version of the package is now available at https://popov-lab.github.io/bmm/dev/ (#18)
- Add articles for each model to the website at https://popov-lab.github.io/bmm/dev/articles/
- Add a detailed developer’s guide to the website at https://popov-lab.github.io/bmm/dev/dev-notes (#21)
- Improve README with more detailed information about the package’s goals and its models (#21)
Other changes
- Save bmm package version in the brmsfit object for reproducibility - e.g.
fit$version$bmm(#88)
bmm 0.1.1
New features
- BREAKING CHANGE: Improve user interface to fit_model() ensures package stability and future development. Model specific arguments are now passed to the model functions as named arguments (e.g. mixture3p(non_targets, setsize)). This allows for a more flexible and intuitive way to specify model arguments. Passing model specific arguments directly to the fit_model() function is now deprecated (#43).
- Add information about each model such as domain, task, name, version, citation, requirements and parameters (#42)
- Add ability to generate a template file for adding new models to the package with use_model_template() (for developers) (#39)
bmm 0.1.0
A major restructuring of the package to support stable and generalizable development of future models (#41).
New Features
- Refactor the fit_model() function to be generic and independent of the model being fit (#20)
- Transform models to be S3 objects. (#41).
- View currently supported models with new function supported_models(). Currently supported models are: mixture2p(), mixture3p(), IMMabc(), IMMbsc(), IMMfull()
- Add S3 methods for checking the data, formula, model and priors (#41)
- Add distribution functions for the Signal Discrimination Model. See ?SDM for usage (#27)
- Add softmax and softmaxinv functions
Bug Fixes
- Change default prior on log(kappa) to Normal(2,1) for the mixture3p() model (#15)
Other changes
- BREAKING CHANGE: deprecate model_type argument in fit_model(). Models must now be specified with S3 functions passed to argument model rather than model names as strings passed to argument model_type (#41)
- Add extensive unit testing
