The whole of what a per-group bound costs a consumer's density, and
it is one multiplication and one refusal. Call it wherever the
density, the mean or the simulator would have read dpars$ndt.
Arguments
- dpars
The distributional parameters, as the density receives them. Only
ndtis read.- aterms
The addition-term values. Only
ndt_floorandndt_groupare read. Defaults todpars, for a family whose engine merges the two into one list, which is whatfrmtmb.learn::rlddm()does.- what
The family's name, so that the refusal names the family the user wrote.
Value
The non-decision time in the response's own units, recycled
to the length the arguments imply. An 'RTMB' advector passes
through: there is no comparison and no branch on a parameter here,
only two is.null() tests on data.
Details
With a per-group bound dpars$ndt is a FRACTION and the row's own
bound arrives in the addition-term values as ndt_floor, so this
returns ndt * ndt_floor. With a scalar bound ndt_floor is absent,
dpars$ndt is already a time, and this returns it untouched, so a
model that did not opt in is the arithmetic it always was.
Why this is exported rather than documented
The arithmetic is three lines. The REFUSAL is not, and it is the part
that matters: a grouped model whose density is reached without
ndt_floor would read the fraction as seconds and report a converged
fit at a non-decision time several times too small. That is the
silent wrong answer the per-group bound exists to remove, and a
consumer who copies the arithmetic and not the refusal reintroduces
it. The grouping itself is in the addition-term values on every path
that can reach a likelihood, so ndt_group present with ndt_floor
absent is exactly that condition, and this refuses on it.
What happens if a consumer does not call this
Nothing refuses, and the fit is silently wrong. This package cannot
check another package's arithmetic: a family that attaches a
per-group bound through ndt_bound_attach() and then reads
dpars[["ndt"]] directly is reading a fraction as a time, and
everything downstream still looks right.
Measured, on two families identical in every line but this one, a
shifted gamma over four groups of 300 rows with true shifts 0.60 to
0.90: both fit, both report convergence code 0, a positive definite
Hessian and no non-finite standard errors, both give the IDENTICAL
log-likelihood 1345.58425, and the one that skipped this call has an
ndt_time() up to 37.5 percent too small. Every fitted time is
still below its own group's floor on both, which is why that check
cannot catch it: it tests the floor lookup, not the arithmetic.
The fraction one half that ndt_bound_attach() sets as the ndt
starting value is a partial guard and only by luck. On a faster
design the same broken family died at once with NA/NaN gradient evaluation, because half a second is past most of the data; on a
slower one it converged silently. A guard that works only when the
responses are fast is not a guard.
What it does NOT do
It returns a value rather than a modified dpars, because where the
value goes is the consumer's own layout. Two consequences a consumer
owns. A family with an across-trial RANGE on the non-decision time
scales that separately; this package's own families use twice the
bound. And a family that reads a linear predictor back through
frmtmb's robust dpar accessors must drop dpars$.eta_ndt itself
after calling this, because the linear predictor beside ndt no
longer maps to the value that was used.
See also
ndt_bound_attach(), which puts ndt_floor where this
reads it.