A drift-diffusion model for a two-choice decision: a noisy evidence
accumulator starts between two boundaries and the response time is
the first time it touches one of them. The family models the response
time; which boundary was touched is data, supplied through the
dec() addition term.
Arguments
- link
Link for the drift rate
mu:"identity"(the default),"log","softplus"or"squareplus", the set brms allows. An unquoted name works too, as in brms:wiener(log). The drift rate is signed, so a link other than the identity is a model in which the accumulator can only drift up.- link_bs
Link for the boundary separation
bs:"log"(the default),"identity","softplus"or"squareplus".- link_ndt
Link for the non-decision time
ndt.NULL, the default, is the bounded link of "Non-decision time" above. Give"log", brms's default, or"identity","softplus"or"squareplus", for brms's own parameterization; see "Links, and the one difference from brms".- link_bias
Link for the start point
bias:"logit"(the default) or"identity".- max_ndt
Upper bound for the non-decision time, in the units of the response, applied to every row.
NULL, the default, takes the fastest response of each row'sndt_group(), or of the whole data set when the model has nondt_group(). It cannot be combined withndt_group(). Give it when a component of afrmtmb::mixture()needs the bound up front.- variability
Which across-trial variability parameters to estimate: any of
"sv"(drift rate),"sz"(start point) and"st"(non-decision time). The default estimates none, which is the plain Wiener model.- nodes
Gauss-Legendre node counts for the two quadratures, as a named vector. Only the entries for the
variabilityparameters in use are read. The defaults are measured rather than chosen:szreaches 1e-10 in the log density by 7 nodes everywhere it was probed, andstneeds more because a response time belowndt + st / 2cuts the range and the integrand turns on sharply at the cut. Seevignette("ddm")for the measurement.- allow_unreachable
Permit a
max_ndtabove the smallest response time. Only correct inside a mixture, where another component carries the rows the Wiener density cannot reach.- contaminant
Mix a uniform contaminant into the density, with its mixing proportion estimated as the distributional parameter
lambdaon a logit link.FALSE, the default, is the plain model. See Contaminant trials.- contaminant_range
The response times the uniform contaminant spreads over, as
c(lower, upper)in the units of the response: the task's response window.NULL, the default, is allowed only when the model declares a deadline withtrunc(ub = ), and is then the fastest response to that deadline; otherwise it is refused. See Contaminant trials for why.
Details
The parameterization is brms's wiener family, name for name:
muDrift rate, the mean rate of evidence accumulation (brms and the literature also call it
v). Identity link by default, so it is signed: positive drift favors the upper boundary.bsBoundary separation, the distance between the two boundaries (
a). Log link by default.ndtNon-decision time, the part of the response time spent encoding and moving rather than deciding (
t0ortau). Bounded link by default; see Non-decision time below.biasRelative start point in (0, 1), the fraction of the boundary separation the accumulator starts at (
w). Logit link by default. 0.5 is unbiased.
Each of the four takes the links brms allows for it; see Links, and the one difference from brms.
variability adds Ratcliff's three across-trial variability
parameters to that set; see Across-trial variability below.
The decision indicator
The boundary a trial ended at is data, and it reaches the density as an addition term:
dec() is spelled as brms spells it and takes what brms takes: a
factor or character vector whose SECOND level is the upper boundary
(so "lower"/"upper" and c(FALSE, TRUE) both work as they read),
or a numeric 0/1 column. The package contributes the term to frmtmb's
addition-term registry when it loads.
vint() also still works, and carries the indicator as a plain 0/1
integer column:
Supplying neither is refused with a message that says so, because the failure is otherwise silent.
The unit of the response
SECONDS. Every default in this package assumes it: the starting
values, the bound the ndt link is scaled onto, and
gddm_control(dt = ) and the window it takes from the data. Nothing
in any of the likelihoods refuses milliseconds, and a fit to
millisecond data converges and reports a boundary separation three
orders of magnitude out.
So every family here warns when the fastest response in the data is above 20, names milliseconds as the likely cause and says to divide by 1000. It is a ceiling on the fastest response in the WHOLE data set rather than on any one trial, so one slow trial does not reach it.
It is a warning rather than a refusal because it CAN fire on a
correct model: a slow task with a short session. Measured, the rate
is zero over 192 designs at the parameters a two-choice task
produces, and rises to 0.07 at 20 trials of a task whose median
response is 39 seconds, 0.56 at 60 seconds and 1.00 at 100 seconds.
A deliberation or matrix-reasoning design is where that lands. The
warning carries the class frmtmb_eam_units_warning so that such a
design can silence this one condition and keep the rest. NEWS.md
carries the full tables.
Non-decision time, and what its coefficients mean
The density is zero for a response time at or below ndt, so the
likelihood has a hard edge at the fastest response and an ordinary
log link would let the optimizer walk straight over it. The ndt
link makes the constraint structural instead, and it does so in one
of two ways.
Without ndt_group(), ndt is a TIME, on a logit scaled onto
(0, ub) with ub the fastest response in the whole data set, or
max_ndt when you give one. This is the parameterization the family
has always had. frm_linpred(dpar = "ndt", type = "response") reports
seconds, a prior(class = "ndt") is a density on those seconds, and
a bf(ndt = 0.2) constant is 0.2 seconds.
With ndt_group(), ndt is a FRACTION of the row's own bound,
on a plain logit, and the density multiplies it by that bound. The
bound is the fastest response of the row's group. Write
frm(bf(rt | dec(response) + ndt_group(subject) ~ coherence,
ndt ~ 1 + (1 | subject), bias = 0.5),
family = wiener(), data = dat)and each subject's non-decision time is bounded by its own fastest
response. That is what a random effect on ndt needs; the next
section is what one global bound does to it. The price is that ndt
is on a different scale: frm_linpred(dpar = "ndt", type = "response")
reports the fraction, and ndt_time() reports the time for either
parameterization. A prior(class = "ndt") and a bf(ndt = )
constant are fractions under a grouping too.
Give the grouping as a factor, a character vector, a logical or
integer codes. It is keyed on the group's LABEL, so subsetting,
droplevels(), relevel() and a prediction grid you build yourself
all pair a row with the same bound the fit used. max_ndt and
ndt_group() cannot be combined, because they set the same bound to
different things, and an ndt_group() no family reads is refused
rather than carried into the fit unused.
A max_ndt above the smallest response time is refused, because for
this family alone it admits parameter values at which some observed
row has no likelihood. Inside a frmtmb::mixture() that is exactly
what the other component is for, so allow_unreachable = TRUE lifts
the refusal; see Mixtures. A mixture never finalizes its components,
so a component needs max_ndt given up front and cannot use
ndt_group().
Whichever parameterization a model is in, the bound is fixed when the model frame is assembled and is a property of the FITTED data, so a prediction on new rows is scaled by the bound the fit used rather than by one re-derived from the new rows. A group the fit never saw has no bound and is refused.
Both are the default link. brms's own link, a log link with no bound,
is link_ndt = "log"; see Links, and the one difference from brms.
Past a linear predictor of about 37 the logit saturates in double precision. Nothing guards that, and nothing needs to: the density falls off a cliff as the decision time goes to zero, so the log likelihood is already unreachable long before the link runs out of digits.
Why one bound is the wrong constraint under a random effect
A single bound is the GLOBAL fastest response, so a subject whose
true ndt is above it cannot be represented at any value of the
random effect. At 30 subjects by 400 trials with a between-subject
spread of 26 ms on a mean of 250 ms, 20 of the 30 subjects are in
that position and NONE is inconsistent with its own data. Without
variability such a fit does not converge; with
variability = "sv" it converges, reports nothing from diagnose(),
and returns a population ndt pinned at the bound and wrong by ten
percent with a standard error of 7.2e-06 on it.
What settles it is what happens as data accumulates. On that design, the per-subject root mean squared error of the fitted non-decision times:
| trials | with ndt_group(s) | one global bound |
| 100 | 20.91 ms | 27.23 ms |
| 200 | 14.08 ms | 20.51 ms |
| 400 | 7.67 ms | 30.37 ms |
The per-group bound converges on the truth and the global bound does not, because more data lowers the global minimum and tightens the ceiling on every subject at once.
What sd(ndt) does and does not tell you
A grouped model estimates ndt as a fraction of each group's own
floor, so the fitted per-subject times vary with those floors even
when the variance component is exactly zero. On the design above an
estimator with NO random effect on ndt returns a between-subject
standard deviation of 0.02748 against a truth of 0.02629, where the
full model returns 0.02543, and at 100 trials per subject the two
are identical in every digit.
The variance component is not empty: at 400 trials it buys 8.44
log-likelihood units and cuts the per-subject error from 11.90 ms to
7.67 ms. But a between-subject standard deviation is the wrong
statistic to read that off. Compare the per-subject non-decision
times from ndt_time() against what you believe, and compare
log-likelihoods, rather than reading VarCorr()'s ndt entry as
evidence that the component was estimated.
The bound's own quality is a function of the group's trial count.
The group's fastest response overshoots its true non-decision time
by about 47 ms at 400 trials and 78 ms at 50, and a group of one
trial gets that trial's own response time as its bound. Nothing
refuses a small group, because any threshold would fire on correct
models; the trial counts are recorded on the fitted family, at
family(fit)$ndt_bound$sizes.
Recovery at 30 subjects by 400 trials
Measured on 60 replicate data sets at the design named in the
realistic-scale table of dev/extension-gaps-plan.md: 30 subjects,
400 trials each, two conditions, mu ~ cond + (1 | s),
bs ~ 1 + (1 | s) and ndt ~ 1 + (1 | s) with ndt_group(s). The
truths are a drift of 0.4 and 1.3, a boundary separation of 1.4, a
non-decision time of 250 ms, and between-subject standard deviations
of 0.35 on the drift, 0.20 on the log boundary and 0.12 on the log
non-decision time. Seeds 20260910 to 20260969.
dev/eamhier-findings.md holds the tables and
dev/eamhier-scripts/ the harness.
All 60 fits converged with a positive definite Hessian, no NaN
standard error and nothing from diagnose(), and every subject's
fitted non-decision time stayed below its own fastest response.
quantity truth mean mcse Wald coverage
mu intercept 0.4 0.4019 0.0094 54/60 90.0%
mu condition effect 0.9 0.9004 0.0035 57/60 95.0%
bs 1.4 1.3900 0.0067 56/60 93.3%
sd(mu | s) 0.35 0.3474 0.0059 55/60 91.7%
sd(log bs | s) 0.20 0.1898 0.0037 54/60 90.0%
What 60 replicates can resolve. The rule applied is that a coverage misses when its Wilson interval excludes 0.95, which at n = 60 rejects on 53 or fewer. Against a nominal rate that is known rather than estimated, the exact binomial power of that rule is 0.82 at a true 85 percent, 0.59 at 88 and 0.39 at 90, with a size of 0.030. So the table can show a ten-point shortfall and cannot show seven. No coefficient's interval excludes 95.
Both variance components come back about as low as maximum
likelihood is expected to put them. The shrinkage at 30 groups,
including the term for reporting a standard deviation rather than a
variance, is sqrt(1 - 1/30) * (1 - 1/116) = 0.9747, that is 2.5
percent low. Against that expectation sd(mu | s) sits 1.06 Monte
Carlo standard errors high and sd(log bs | s) 1.39 low: one common
shrinkage covers both, and neither is a separate finding. Read a
variance component from 30 groups as a slight underestimate.
The non-decision time is read per subject rather than as a spread, for the reason the section above gives. Its root mean squared error is 7.9 ms against a between-subject spread of 30.2 ms, it is smaller than that spread on 60 of 60 replicates, and its correlation with the drawn values is 0.967. The population value comes back about 2 ms high.
The same 60 draws with variability = "sv" recover the same
quantities to the same accuracy. What they add is in the next
section.
Links, and the one difference from brms
Each parameter takes the links brms 2.23.0 allows for it, through the
argument brms uses, in brms's order: link for mu, then link_bs,
link_ndt and link_bias. A positional call written for brms means
the same thing here. A link outside brms's set is refused.
The defaults are brms's, except for ndt. brms puts ndt on a log
link. Without a formula on ndt, brms bounds it in its parameter
block, real<lower=0,upper=min_Y> ndt, and gives it a
uniform(0, min_Y) prior. With a formula, brms has no bound: each
row's density rejects a non-decision time at or above that row's
response time. This package has no parameter block, so its default
link carries the bound instead.
link_ndt = "log" gives brms's parameterization. The coefficients of
ndt ~ ... are then on brms's log scale, and a prior written for
brms means the same thing here. A row whose response time is at or
below its non-decision time has a log density of minus infinity, and
the optimizer treats that as a rejected step. The start is half the
fastest response, which every row can reach.
Measured on 100 data sets of 30 trials and 100 of 400 (seeds
20261101 to 20261200, dev/famuni-wiener-ndtrep.R): every fit
converged under both links, and on this scalar ndt, where the two
links are the same model, the two maxima agreed to 6e-10 in the
log-likelihood and to 1.6e-05 standard errors in ndt. Against brms
on one data set of 400 trials with ndt ~ 1, the maximum-likelihood
estimates sat within 0.15 posterior standard deviations of brms's
posterior medians, and the standard errors within 5 percent of the
posterior standard deviations.
Under ndt ~ 1 + (1 | s) the per-row rejection is a per-subject
bound, which is the constraint ndt_group() gives the default link.
Measured on 20 data sets of 20 subjects by 50 trials, with a
between-subject standard deviation of 0.12 on the log non-decision
time around 250 ms (seeds 20261101 to 20261120,
dev/famuni-wiener-ndtrep.R): the log link converged on 20 of 20
with no warning, ndt_group() on 20 of 20, and the one global bound
on 12 of 20. The median per-subject root mean squared error of the
non-decision time was 13.8 ms under the log link, 16.4 ms under
ndt_group() and 24.4 ms under the global bound, and the log link
had the smallest error on 20 of the 20 data sets. Its
between-subject standard deviation came back at a median of 0.115,
where ndt_group() put it at zero for the reason "What sd(ndt) does
and does not tell you" gives.
An unbounded ndt link cannot be combined with max_ndt,
ndt_group(), variability, contaminant = TRUE or
allow_unreachable = TRUE. The first two set the bound that the
link does not have. Each of the other three holds a row past the
non-decision time at a small positive decision time, and without a
bound every row can be held there at the same time, where the
density of one point has no upper limit as bs goes to zero. A
mixture with a lognormal reached a log-likelihood of +7713.9 on 400
rows that way, with ndt at 1.8e+308 and bs at 7.05e-05
(dev/famuni-wiener-mix2.R).
Under an identity link nothing keeps bs above zero, bias inside
(0, 1) or ndt above zero. The density is NaN outside those
ranges, on the tape the optimizer evaluates as well as on doubles, and
the optimizer treats it as a rejected step. On data with a bias of
0.97 (seeds 20261101 to 20261105, n = 80), link_bias = "identity"
fits reach the logit fits' maxima on all five.
A brms family object is read with its links. brms::wiener() and
brms::brmsfamily("wiener") carry brms's link_ndt = "log", so
family = brms::wiener() fits brms's log link for ndt, while
family = "wiener" and this package's wiener() fit the bounded
default. With both packages attached, a bare wiener() is the one
attached last: brms's gives the log link, this package's the bounded
one. On a scalar ndt the two are the same model with the same
maximum; under ndt ~ x the coefficients are on different scales, and
the log link refuses max_ndt, ndt_group(), variability and
contaminant = TRUE. Write frmtmb.eam::wiener() to be sure which
you have.
Post-fit quantities against brms
The log-likelihood of a row is the same quantity in both packages:
the log density of its response time at the boundary it ended at. On
50 posterior draws of a 400-row brms fit, this density and brms's
log_lik() agree to 1.1e-14.
fitted() and simulate() differ from brms on purpose. Both condition
on the boundary each row ended at. brms's posterior_predict() draws
the boundary as well and returns only the time, or, with
negative_rt = TRUE, the time signed by the DRAWN boundary, so a row
observed at the upper boundary gets draws at the lower one.
predict(negative_rt = TRUE) is refused here. brms's
posterior_epred() is the mean response time over both boundaries,
from Grasman, Wagenmakers and van der Maas (2009). brms 2.23.0 passes
that formula the relative start point bias where it takes the
absolute one, bias * bs, so its value is correct only at bs = 1:
at bs = 2.5 it is wrong by up to 64 percent, and at a drift of
exactly zero it is NaN. The formula with bias * bs agrees with
this package's conditional means, weighted by the boundary
probabilities, to 1.04e-15 relative (dev/famuni-wiener-fit2.R).
Across-trial variability
Ratcliff's full diffusion model draws three of the four parameters
afresh on every trial. variability names which of those to
estimate, and each one it names becomes an ordinary distributional
parameter that takes its own formula:
frm(bf(rt | dec(response) ~ coherence, bias = 0.5),
family = wiener(variability = c("sv", "sz", "st")), data = dat)svStandard deviation of a normal drift rate. Log link.
szWidth of a uniform relative start point, centered on
bias, on the same (0, 1) scale asbias. Logit link, so the width is below 1 and the start point stays inside the boundaries wheneverbiasis 0.5.stWidth of a uniform non-decision time, centered on
ndt, and on whichever scalendtis on: a duration in the units of the response, on a logit scaled onto(0, 2 * bound), or a FRACTION of twice the row's own bound underndt_group().
The likelihood is the analytic Wiener density averaged over those
distributions, and the three are done three different ways because
they are three different integrals. The drift integral is Gaussian
against an exponential-quadratic and is evaluated in CLOSED FORM: it
is exact, it takes no nodes, and there is nothing to tune. The other
two are uniform and are evaluated by fixed-node Gauss-Legendre
quadrature, whose node counts are the nodes argument.
The node positions and counts are decided when the family object is built and are constants from then on, because a node count that moved with a parameter would be a branch on a parameter and an automatic-differentiation tape cannot record one. A parameter only rescales the interval the fixed nodes are mapped onto.
fitted() and simulate() follow the variability rather than
ignoring it. Both condition on the boundary a row ended at, and
conditioning reweights which per-trial parameters that row could have
had: the trials that reached a boundary are not a fair sample of the
drift rates that could have produced them. So the fitted mean is a
ratio of two quadratures and the simulator accepts a drawn drift rate
and start point with the boundary probability they imply. The plain
closed-form mean is not a usable approximation here: at an unbiased
start point it returns the same number for both boundaries, and the
full model does not.
Two limits of the parameterization are worth knowing. The uniform
start point stays inside the boundaries by construction only when
bias is 0.5, which is the usual case and the one the logit link on
sz is scaled for; at a strongly biased start a wide sz can push
the range past a boundary, where the density is near zero and the
likelihood is a barrier rather than a cliff. And nothing holds
ndt - st / 2 above zero, so a fit is free to report a range that
includes a negative non-decision time. Neither can be made structural
from outside frmtmb: both are joint constraints on two distributional
parameters, and a link is a property of one.
How well sv is identified, measured. On 60 replicates of one
subject with 12,000 trials, no random effects, and a true sv of
0.4, the estimate ranges from 0.194 to 0.599 and its Wald interval
covers on 57 of the 59 fits that report a usable one. The estimated
sv and an estimated drift contrast are correlated, 0.59 by rank: a
draw that returns a low sv returns a low contrast with it, and
every replicate that missed on the contrast had an sv below the
truth. Read the two together rather than one at a time. In the
hierarchical design of "Recovery at 30 subjects by 400 trials" both
cover at 57 of 60.
One fit in 60 lost sv entirely and nothing reported it. At one
seed the estimate ran to the log link's floor, 4.4e-04, with a Wald
interval of (-1163, 1147) on the log scale, convergence code 0, a
positive definite Hessian and diagnose() printing "No convergence
problems detected". The likelihood is genuinely flat there on that
draw: dropping the sv term moves the log-likelihood by 7e-07,
while holding sv at its true value costs 4.05 units. So the fit is
not wrong, but the interval it prints is not a statement about
anything. Read the interval confint() gives sv before you read
the estimate. The signature is inside that one fit and needs no
replicates: the standard error on log sv was 589.25 where the
largest of the other four was 0.0501, a ratio of 11,763. Nothing
else in the fit reports it.
Mixtures
A contaminant component covers the trials the diffusion process cannot produce, which is the standard treatment for fast guesses. The Wiener component then wants a non-decision time that some observed rows fall below, so pass the bound and lift the refusal:
frm(bf(rt | dec(response) ~ 1, bias1 = 0.5),
family = mixture(wiener(max_ndt = 0.4, allow_unreachable = TRUE),
lognormal()),
data = dat)A row below the non-decision time gets a log density of about
-1 / delta where delta is a billionth of the smallest response
time: it exponentiates to exactly zero, which is the right likelihood
for the component, and it differentiates to exactly zero, which a
true -Inf would not.
Censoring and truncation
cens() and trunc() work on the response time, on the plain model.
What a censored row means depends on its code, because the boundary
is known on some and not on others:
- right
The trial had not reached EITHER boundary by the recorded time: a deadline passed with no response. Its probability is the survival over both boundaries,
P(T > t). Itsdec()value is required, since a declaration cannot depend on a censoring code, and is NOT read: any 0 or 1 gives the same fit, bit for bit.- left and interval
The trial DID reach a boundary, and
dec()says which; only its time is coarse. Its probability is that boundary's defective distribution function,P(T <= t, boundary b), or its difference across the interval.dec()is read. A row whosedec()is missing is dropped by frmtmb'sna.actionwith a message, before this family sees it; this family cannot refuse it by name.
A deadline design is
with censored set to "right" on the trials with no response and
rt set to the deadline there.
trunc() divides by the window's mass over both boundaries. Left or
interval censoring cannot be combined with trunc(): frmtmb forms
the censored rows and the truncation window from the one
distribution-function slot a family has, and the two need different
functions here, so the pair is refused by name.
Every quantity is computed as a LOG and never as a complement: the
survival from the method of images for small times and from the
eigenfunction series for large ones, and each defective function the
same way. Against a 700-bit reference over drift -20 to 20, boundary
separation 0.3 to 6, relative start 0.001 to 0.999 and normalized
time 1e-3 to 10, 3150 points, the worst relative error is 4.3e-14 on
the distribution function over both boundaries, 1.2e-12 on either
defective one, and on the survival 1.7e-12 where |v| a is at most 1,
7.4e-12 up to 24, 4.6e-11 up to 72 and 8.0e-11 up to 120. These
hold on that grid only. The review of 2026-09-24 measured 798 more
points at 1200 bits, to |v| a = 250 and a relative start of 0.9999,
and there the survival's relative error is 1.7e-11 where |v| a is
at most 1, 1.5e-9 at 120 and 1.6e-9 at 250, the worst at relative
start 0.9999 and normalized time 0.01. 1.6e-9 is the worst measured
anywhere. A survival
of exp(-72063) comes back as that log. RWiener::pwiener() agrees to
4.5e-11 absolute on the grid the density is pinned on, and all of
that difference is RWiener's own error.
An interval's mass is formed as a difference of logs, never of probabilities: F(t2) (1 - F(t1) / F(t2)), or the same with the mass still to come after each edge, whichever keeps more digits, or a quadrature of the density where the interval holds a tiny share of the mass on both sides. Against 751 intervals at 1200 bits, 511 of them the review's, with the lower edge at normalized time 1e-3 to 3, |v| a up to 80 and masses down to 1e-99 of F(t1), the worst error of the log mass is 4.9e-11. The probability of a left-censored row, or of an interval, is held at 1e-300 or above, because frmtmb takes it on the probability scale: a row whose true probability is smaller than that contributes a flat barrier rather than its value.
What it recovers, at 30 subjects by 400 trials (drift 0.4 and 0.9 by condition with a subject deviation of 0.35, boundary separation 1.4 with a subject deviation of 0.20 on its log, non-decision time 0.25 s). With a 1 s deadline, 16 percent of trials right-censored, 40 replicates: every fixed effect and both variance components covered on 33 to 38 of 38 fits with an interval. With trials faster than 0.45 s recorded as left-censored and every fifth other trial as interval-censored into its 100 ms bin, 29 and 14 percent of trials, 60 replicates: they covered on 47 to 55 of 55, the lowest being the drift intercept at 85.5 percent, Wilson interval 73.8 to 92.4. Every fit converged. The arms draw from the same seeds, so their coverages are not independent of each other.
The plain model only. Under variability the distribution function
is the same series averaged over the per-trial parameters, and the
drift average has no closed form there, so the pair is refused.
Contaminant trials
contaminant = TRUE mixes a second process into every row: a
response time uniform over a window, with a boundary that is a coin
flip. Its share is the distributional parameter lambda, on a logit
link, which takes a formula like any other:
frm(bf(rt | dec(response) ~ condition, lambda ~ 1, bias = 0.5),
family = wiener(contaminant = TRUE, contaminant_range = c(0, 3)),
data = dat)It needs no frmtmb::mixture(): the uniform has no free parameter,
so it is one more term in the density rather than a second family. A
right-censored row enters through both parts' survivals; a left- or
interval-censored row through the diffusion's defective function at
its boundary and half of the uniform's, the coin flip landing there.
The window. Give it as contaminant_range =: the task's
response window, such as c(0, deadline). Without it, a model that
declares its deadline through trunc(ub = ) uses the fastest
response to that deadline, and any other model is refused, with the
class frmtmb_eam_contaminant_range_error. A response outside the
window is refused with the same class, since the contaminant has
density zero there. The refusal of a missing window is the
measured reason: without a deadline bounding both processes, the
slowest diffusion trial sets the top of the observed range, the
window grows with the sample, and lambda collapses. On one subject
of 4000 trials, 25 seeds, true share 0.0495, contaminants uniform on
0 to 5 s: the observed range gave lambda 0.0212 and covered on 2 of
25, the true window 0.0498 and 25 of 25. With a 5 s deadline declared
by trunc(ub = 5), the window from the fastest response to the
deadline gave 0.0508 against a recorded share of 0.0496, and the one
to the slowest response 0.0518, both covering on 25 of 25; the
deadline is the top used, being the one that does not move with the
sample (dev/phase3b-cont-window.R). The review of 2026-09-24
measured the same pattern on its own design: 0.0050 and 5 of 25
without a deadline, 0.0545 and 22 of 25 with one.
Where the practice comes from. Ratcliff and Tuerlinckx (2002) model the contaminant as uniform over the range of the observed response times, and the DMAT toolbox follows them; HDDM uses a uniform of fixed density instead. None of the three was checked against its source or text for this page; the attributions are the review's of 2026-09-24, recorded as such.
What it recovers, at 30 subjects by 400 trials. Drift 0.4 and
0.9 by condition with a subject deviation of 0.35, boundary
separation 1.4 with a subject deviation of 0.20 on its log,
non-decision time 0.25 s, 5 percent contaminants uniform on 0.1 to
5 s, max_ndt = 0.5. With contaminant_range = c(0.1, 5), over 40
replicates, lambda came back at 0.0499 and its Wald interval
covered on 39 of 40; every fixed effect and both variance components
covered on 34 to 40 of 40, and every fit converged. The same draws
with a 3 s deadline declared by trunc(ub = 3) and the default
window, 44 replicates: lambda 0.0306 against a recorded share of
0.0307, covering on 35 of 36. On data with no contaminant and the
window given, 30 replicates: see the collapse case below.
A guess faster than the non-decision time. The non-decision time
is bounded by the fastest response, so a contaminant faster than the
true non-decision time pulls the bound under it and the fit cannot
reach the truth. On the design above at the default bound, 20
replicates on the first round's build, the non-decision time came
back at 0.111 s against 0.25 and the drift effect covered on 0 of
20. Under the contaminant such a row
has a likelihood, so max_ndt above the fastest response is allowed
with contaminant = TRUE: give a bound you know the non-decision time
is under.
The collapse case. Maximum likelihood has no prior to hold
lambda off its edge. When the data give it nothing, it runs down
the logit: the estimate is a logit near -20, its standard error is in
the thousands, and the log-likelihood is the plain family's.
frmtmb::diagnose() reports it as a parameter at the end of its
link. On the design above with no contaminant and the window
c(0, 5) given, 30 replicates: lambda reached the edge on 25,
diagnose() named it on 27, all 25 at the edge among them, and at
the edge the log-likelihood was the plain family's to within
8.9e-07 on every one.
At lambda = 0 the density is the plain family's: bit for bit on
every row whose diffusion density is at least the contaminant's, and
to the rounding of one log-sum anchor on the others.
Accuracy
The density is the Navarro and Fuss (2009) pair of series, both
evaluated at a fixed truncation and combined with a smooth weight,
because an automatic-differentiation tape cannot choose between them
on a parameter. It agrees with RWiener::dwiener() to better than
1e-12 relative on the log scale over normalized times from 1e-3 to
50. See vignette("ddm").
References
Grasman, R. P. P. P., Wagenmakers, E.-J. and van der Maas, H. L. J. (2009). On the mean and variance of response times under the diffusion model with an application to parameter estimation. Journal of Mathematical Psychology, 53(2), 55-68.
Navarro, D. J. and Fuss, I. G. (2009). Fast and accurate calculations for first-passage times in Wiener diffusion models. Journal of Mathematical Psychology, 53(4), 222-230.
Ratcliff, R. and Tuerlinckx, F. (2002). Estimating parameters of the diffusion model: approaches to dealing with contaminant reaction times and parameter variability. Psychonomic Bulletin & Review, 9(3), 438-481.
Examples
set.seed(1)
dat <- ddm_simulate(300, mu = 0.8, bs = 1.4, ndt = 0.3, bias = 0.5)
fit <- frm(bf(rt | dec(upper) ~ 1, bias = 0.5),
family = wiener(), data = dat)
fixef(fit)
#> Estimate Est.Error Q2.5 Q97.5
#> Intercept 0.6956708 0.08770048 0.523781 0.8675606