Changelog
Source:NEWS.md
frmtmb.learn 0.7.4
Needs frmtmb 0.79.0 for the numbers of frm(importance =) it documents and for the discrete-likelihood check.
Bug fixes
-
rlddm()is a density, and declarestype = "continuous". A correct fit with tight response times, logLik +666.77, was told “a probability mass is at most 1”. DHARMa’sintegerResponsereads the same type, and is right on it now.
Other changes
The documented numbers for
frm(importance =)are measured again on frmtmb’s corrected proposal. At 100 trials, the correction movessd(alpha)by 0.02 to 0.05 log units, not 0.11 to 0.18. No dataset is refused, and a collapsed component no longer walks. Needs frmtmb 0.79.0 for these numbers. Apart fromrlddm()’stype, the package code is unchanged but for the note on itsfrm_compat()row.frm_compat()bynlis “refused” on these families:nl = TRUEneeds a family with a singlemu.nlf()on one parameter is the route.frm_compat():ar(),ma(),arma(),cosy()andunstr()by these families are “refused”, asfrm()and brms refuse them: they have nomu.
frmtmb.learn 0.7.2
No change to the package’s code. Needs frmtmb 0.68.0 for its tests.
-
test-engine.R,test-reference.Randtest-stan-identity.Rallow frmtmb 0.68.0’s warning that standard errors are not available, andtest-engine.Rrequires it on its fit with a constant likelihood, where no parameter has a standard error (lane nanse,dev/nanse-findings.md).
frmtmb.learn 0.7.1
No change to the package’s code.
-
test-stan-identity.Rlets no warning escape. Two of its fits stop short of the optimum and warn twice each, “false convergence (8)” and a large gradient; the identity compares both sides at the point where the fit stopped, so the test allows those two messages by name and no other (allow_warnings(), now inhelper-warnings.Rhere as in frmtmb’s own tests).
frmtmb.learn 0.7.0
The duplicated-payoff refusal and
?bandit2arm_deltano longer say thatsimulate()takes nonewdata: frmtmb’ssimulate()takes it now, and refuses it for a learning family, whose draw walks the fitted trial sequence.frm_compat()recordsautoscaleas working for the learning families, which frmtmb’s default now engages below a column spread of 1e-3: measured onbandit2arm_delta()at 1e-6, it reaches the scale-1 log-likelihood on 3 of 3 seeds (dev/predfix-p1-ext.Rin the frmtmb repository).Every family takes
session =(item 3.3 of the extension plan). At the first trial of each session the value store goes back to its initial values, and trial numbers need be unique only within a session. The subject stays the unit that random effects andfrm(importance =)group on, so a subject’s sessions share its effects and are resampled together. A two-session fit’s objective is the sum of the two single-session objectives at the same parameters.frm_value_trace()gains asessioncolumn, andfrm_task_simulate()readssession =too. See?bandit2arm_delta, “Sessions”.A session label reused in two runs that are not adjacent in trial order is refused by name: the runs would join into one sequence and the value store would carry across the trials between them. It is checked where the trial column orders a subject’s sessions, that is, where its numbers are unique across them. Interleaved contexts, one value store per context kept across its runs, are not built.
No single-session fit moved: every family’s fit without
session =is bit-identical to 0.6.0.
frmtmb.learn 0.6.0
- Documentation only: the compatibility notes and the family refusals name
frm_linpred(type = )where they namedpredict(type = ). frmtmb’spredict()is brms’s predictive summary in the development version and the linear predictor moved tofrm_linpred().
frmtmb.learn 0.5.0
Requires frmtmb 0.60.0, for
frm_stop()and the other condition helpers.Requires the frmtmb release that exports
frm_stop(); thefrmtmb (>= 0.59.0)floor must move to it.BREAKING: every error, warning and message that frmtmb.learn raises is classed. An error has the class
c("frmtmb_learn_error", "frmtmb_error", "error", "condition"), and warnings and messages follow the same pattern, sotryCatch(frmtmb_error = )catches any refusal. The class vector no longer containssimpleError,simpleWarningorsimpleMessage. See?frmtmb::frmtmb-conditions.BREAKING: a value that matches none of the choices of
bandit2arm_dual(split =)andfrm_task_design(task =)is refused with afrmtmb_learn_errorthat names the argument, the value and the choices. The old text was'arg' should be one of .... A partial value still matches.frm_task_simulate()refuses afamilythat is not a family object by name.frm_task_simulate(1, d)used to fail withsubscript out of bounds.The refusals about
rlddm()’s non-decision-time bound, which frmtmb.eam raises, and the refusals frmtmb raises about this package’s families, such asdeclares no mean, are afrmtmb_learn_error.
frmtmb.learn 0.4.2
Requires frmtmb 0.59.0.
The
reward()refusal on a duplicated payoff column no longer says thatnewdata“cannot supply” the missing columns, which impliedsimulate()readsnewdata. Since frmtmb 0.58.0simulate()refusesnewdataby name. The message now says thatsimulate()takes nonewdataand that no other data set drawn through the same formula can supply the columns either.
frmtmb.learn 0.4.1
-
A correlated block over every parameter is validated at the population scale, and one of its components turns out not to mean what it looks like. Item 2.2 of
dev/extension-gaps-plan.md:(1 | p | id)on every parameter ofbandit2arm_delta()andrlddm(), 100 learners by 200 trials, 60 replicates per arm.dev/learnhier-findings.mdhas the construction, the seeds and the scripts.bandit2arm_delta()recovers everything. With a true correlation of 0.5 the block returns 0.5322 with a Monte Carlo error of 0.0181 and coverage 0.97; with a true correlation of 0 it returns 0.0214, so the estimator neither misses a correlation nor invents one. 120 of 120 fits converged with a positive definite Hessian and no component collapsed.rlddm()recovers every parameter that HAS a population truth on the scale it is fitted on: nine of nine components cover between 0.90 and 0.98. -
sd(ndt)underndt_group()is about 94 percent a property of the data rather than of the learners, and?rlddmnow says why in one line of algebra. A learner’s floor is its own non-decision time plus the fastest decision it happened to make, soqlogis(ndt / floor) = log(ndt) - log(m)identically, wheremis an observed minimum. Over 60 replicates the design’s ownsd(log ndt)is 0.1494 whilesd(log m)is 0.4404. More trials do not repair it: the nuisance share reads 107.9, 106.7 and 108.2 percent at 100, 200 and 400 trials.The same term drives
cor(bs, ndt)toward -1, becauselog(m)correlates 0.872 with a learner’s ownbsdeviation. The correlation the design drew is recovered exactly once that term is removed,cor(bs, log ndt)of +0.2989 against a drawn 0.3.Nothing about the family’s behaviour changed; this is a measurement and a documentation change. The per-learner non-decision times read through
frmtmb.eam::ndt_time()are unaffected and remain what?rlddmtells you to read: 12.3 ms of error against the drawn truths at a correlation of 0.947, where the best constant fraction of each learner’s own floor manages 20.4 ms and 0.871. The identity tier gains the correlated block.
ln_stan_code_delta_cor()andln_stan_code_rlddm_cor()carryuas a subject-by-parameter matrix with the block’s covariance as DATA, so the map to frmtmb’s parameters stays the identity and the comparison carries no Jacobian. Agreement is 2.6e-15 relative for the two-by-two block, 4.9e-15 at the full 100 by 200 design, and 1.7e-11 for the four-by-four block underndt_group(id).
frmtmb.learn 0.4.0
-
rlddm()can bound the non-decision time PER GROUP. Writert | dec(choice) + reward(pay1, pay2) + ndt_group(id) ~ ...and each row’sndtis bounded by its own learner’s fastest response instead of by the fastest response of every learner in the data set. That is what a random effect onndtneeds. On the scale tier’s own design, 100 learners by 200 trials, the same data and the same model:0.3.0, one global bound with ndt_group(id)convergence code 1 0 maximum absolute gradient 6.36e+09 3.12e-03 positive definite Hessian no yes standard errors that are NaN4 0 log-likelihood -8333.1 -7781.2 sd(ndt)on the link2.236 0.3261 learners below their own fastest response (n/a) 100 of 100 per-learner ndterror against the drawn truths(n/a) 14.0 ms 551.9 log-likelihood units at the same fourteen parameters. The
ndtvariance component is the clearest single symptom: under one bound it is a random effect running away on a scaled logit against a wall.Under the grouping
ndtis a FRACTION of the row’s own bound andpredict(dpar = "ndt", type = "response")reports that fraction;frmtmb.eam::ndt_time()reports the non-decision time in the response’s own units under either parameterization.max_ndtandndt_group()together are refused, because they set one bound to two different things. A model that does not writendt_group()is unchanged: the log-likelihood, every coefficient, every fitted value and the value trace are identical in every digit to 0.3.0’s. The bound, its refusals and the
ndt_group()coercion now come fromfrmtmb.eam::ndt_bound()rather than from a second copy of the scaled logit written here. The copy is what made this family inherit the one-global-bound defect in the first place.-
BREAKING:
bf(ndt = )on a barerlddm()is refused and needsrlddm(max_ndt = ).bf()transforms a pinned constant at PARSE time, before the response has been seen, so a family with no bound yet has no scale to transform it on. Pass the bound instead:rlddm(subject = id, trial = trial, max_ndt = 0.26). This is the contractfrmtmb.eam::wiener()and its siblings have had since frmtmb.eam 0.7.0, andrlddm()now shares it rather than being the exception.The capability is respelled, not lost. The same model under the new spelling is the same fit:
bf(ndt = 0.2)withrlddm(max_ndt = 0.26)gives a log-likelihood of -86.3374111 on this version and -86.3374111 on 0.3.0, which is also whatbf(ndt = 0.2)on a bare 0.3.0 family gave. Add the argument and the numbers do not move.Nothing that worked was wrong, and no fitted model needs redoing. Through 0.3.0 an in-range
bf(ndt = 0.2)was fitted at exactly 0.2: the parse-time call only range-checks the constant, and the transform that reaches the parameter runs later, against the settled link. What the old behavior did NOT do is check the constant against the bound the fit would actually use.bf(ndt = 0.3)withmax_ndt = 0.26was accepted, reached the objective asNaN, and surfaced asNA/NaN gradient evaluationfrom the optimizer, naming neither the parameter nor the constant. That case is now refused before the formula is parsed, by name:Constant ndt = 0.3 is not in the range of the scaled_logit link. summary()on an ungroupedrlddm()fit prints thendtlink asscaled_logitwhere 0.3.0 printedscaled_logit(0, 0.2615). The link’s arithmetic is unchanged and the bound is now on the fitted family atfamily(fit)$ndt_bound$ub, which 0.3.0 did not carry.THIS PACKAGE’S FLOOR ON frmtmb.eam HAS TO RISE.
rlddm()now callsndt_bound(),ndt_bound_attach(),ndt_bound_of(),ndt_bound_pending()andndt_apply(), which frmtmb.eam gains in the same round.DESCRIPTIONstill saysfrmtmb.eam (>= 0.6.0)and is not edited here, because the version those exports ship in is not this lane’s to choose.library(frmtmb.learn)against frmtmb.eam 0.6.0 fails at namespace load, so the floor must rise in the same commit that lands the exports.
frmtmb.learn 0.3.0
A duplicated reward schedule fitted correctly and then simulated a different experiment. reward(pay1, pay2) and reward(rec, rec) give a bitwise identical log-likelihood and identical fixef(), because the density reads only the chosen arm, while the simulator drew a P(better arm) of 0.5470 against 0.8616 with 0.8633 observed. Exact chance, from a task nobody ran, with no error anywhere.
The guard is DERIVED rather than declared: the schedule columns are read off the addition terms a family already names, so all eight families are covered and one that gains a
reward()term later is covered without anyone remembering. What that costs is written down: a schedule under a third name is invisible, so a test asserts every multi-column term is classified or excluded with a reason.A compatibility row said
prl_fictitious()reads both columns. It does not: its update takes the chosen payoff and flips the sign. That row has been false since 0.2.0.rlddm()inherits frmtmb.eam’s non-decision-time bound and the defect that comes with it. See that package’s 0.6.0 notes; do not put a random effect onndtyet.-
A draw needs a column a fit does not, and
simulate()now refuses when the data does not carry it. ALL EIGHT families read only the CHOSEN option’s payoff. That is what lets a record of the received outcome alone be fitted, by passing the one column once per option, and it is exact rather than approximate: each option’s prediction error is multiplied by a 0/1 chosen indicator, and zero times a finite number is exactly zero, so the objective is bitwise the same function of the parameters whatever the unchosen entry holds. Measured by replacing the unchosen entries withN(100, 50)noise and comparing the objective at one parameter vector: bitwise identical.A DRAW is a different question, and the difference had been silent. A simulated subject chooses for itself, so paying it needs the schedule of every option, and with the columns duplicated there is no schedule. Measured on the shipped two-armed design, 30 subjects by 100 trials, where arm 1 pays with probability 0.7 and arm 2 with 0.3:
family, route real duplicated bandit2arm_delta, simulate 0.7444 (0.0024) 0.5004 (0.0035) prl_fictitious, simulate 0.8616 (0.0027) 0.5470 (0.0090) prl_fictitious, task_simulate 0.8367 (0.0037) 0.5072 (0.0083) rlddm, task_simulate 0.1443 (0.0036) 0.5040 (0.0071) The statistic is the proportion of trials in the second half that took arm 1, over 40 draws (20 for
rlddm()); the observed proportions in the data being fitted are 0.7593 and 0.8633. Every duplicated column gives exact chance, because with both arms paying the same on every trial there is nothing to learn, and each came back as a plausible-looking vector of option codes with no error and no warning.ts_par7()drew 800 rows the same way.The signature is EVERY column of
reward()orpayoff()identical on every row. Some columns identical is not it, because the data then still says what at least one option not taken would have paid, and the test file pins that case as accepted.stage2()is not read as a schedule at all: its two columns are the observed state and choice.The guard is DERIVED from the terms a family names rather than declared per family, so a ninth family cannot be added without the question being asked.
ln_family(counterfactual =)takesNULLto derive,identical(FALSE)to opt out, or the columns themselves, and refuses anything else while the family is being built rather than when a draw is asked for. It does not takeTRUE: deriving is already the default, so there is nothing forTRUEto mean, and reading it as an opt-in would make the one spelling that says yes the one that turned the guard off.What the derivation is weaker at than a declaration is a NAME it does not know. A schedule arriving through a term that is not
reward()orpayoff()is not guarded, and nothing says so; a forgotten declaration would at least sit in the family’s source. The package’s own registration table is the only place that is visible, so the test suite now asserts that every term it registers at arity 2 or more is classified as a payoff schedule or explicitly excluded with a reason. Registering a fourth term without answering the question turns the suite red. All four routes to a draw are covered:simulate(),frm_simulate()andfrmtmb.sample::posterior_predict()through the family’ssim_ctxslot, andfrm_task_simulate()on the design it is handed, which is the only routerlddm()andts_par7()have becausesimulate()already refuses them and their own refusal messages send users there.newdatais not a way round the refusal and the message says so: the formula names one column twice andnewdatais read through that same formula, so a user whose record holds the received outcome alone has to refit with a column per option, or build a schedule withfrm_task_design(). -
A released compatibility row was wrong, and it is corrected. Since 0.1.0 the
prl_fictitious()/reward()row has said “Both columns are READ here rather than only carried: counterfactual updating moves the unchosen option’s value too, so the second column enters the likelihood and not just the simulator”. It does not. The update forms the outcome asc1 * reward1 + c2 * reward2with the CHOSEN indicators and then FLIPS ITS SIGN, so what moves the unchosen value is the negative of the realized outcome and the second column never enters. Measured: replacing the unchosen entries withN(100, 50)noise leaves the log-likelihood bitwise unchanged at -937.55332137794574.?prl_fictitioushas always said this correctly, that the counterfactual outcome is the negative of the realized one “rather than a reading of the secondreward()column”. Two documents in one package said opposite things about the same family, and a user choosing a family on that table would have chosen this one for a property it does not have. An earlier draft of the release above exemptedprl_fictitious()from the new guard on the strength of the wrong one. -
reward(pay)with one argument is still refused, and the refusal belongs to frmtmb rather than to this package. An addition term’s arity is fixed when it is registered:frmtmb_register_aterm(arity =)takes one whole number and the parser refuses any other argument count, soreward(pay1)stops at “reward()takes 2 arguments, not 1” before this package sees anything. Registeringrewardat arity 1 instead would refusereward(pay1, pay2), which every example, vignette and test writes.What the core seam buys is the SPELLING and not the capability. A one-column route needs no core change at all: a second term name registered at arity 1 keys its value at
aterms[["reward1"]], which is the key the two-column spelling’s first argument already uses, so a family that readsreward2with a fallback takes both spellings. Verified as far as the parser:reward1(rec)is routed to that key and the shipped family then refuses on its ownrequired_atermsdeclaration, which is this package’s to change and not core’s.reward(rec, rec)remains the spelling that works today and is exactly right for the likelihood.
frmtmb.learn 0.2.1
Requires frmtmb 0.55.0. The hazard-container lint runs in this package’s own check, and frm(importance = ) now says when the correction has stalled rather than advising more rounds, which this package’s own documentation had to say in prose instead.
frmtmb.learn 0.2.0
The three things 0.1.0 named as left out are in, and the seam it was waiting on has closed. Requires frmtmb 0.53.0 for the two factorization slots, and frmtmb.eam 0.5.0 for the Wiener density.
frm(importance = ) works
-
Every family declares
frmtmb_structure(loglik_row =)and(loglik_group =)besideloglik, sofrm(importance = )is admitted instead of refused by name. A subject is the group and a trial is the row: two subjects share nothing but the parameters, and a subject’s likelihood is a product over trials given the value trajectory. Both facts are properties of the recursion rather than of any one learning rule, so both hold for all eight families.All three quantities come off ONE call to the recursion, which now yields per-trial factors rather than a running total. The change costs nothing: measured at 40 subjects by 100 trials, tape build 0.250 s against 0.260 s and one gradient 98.5 ms against 103.0 ms, with the objective identical to full printed precision.
unitis unchanged, “one subject’s trial sequence”. These slots say how finely the likelihood FACTORIZES;unitsays what may honestly be left OUT, and dropping a trial changes every later trial’s value store. -
What the correction is worth, measured, and it depends on the design.
bandit2arm_delta()on a reversal task, 40 subjects, one scalar random intercept, truthsd(alpha) = 0.5, six datasets per cell. At 100 trials, on the three of six datasets whose variance component the Laplace fit had not collapsed, the correction movessd(alpha)from 0.38-0.45 to 0.45-0.51, that is up and toward the truth, by 0.11 to 0.18 log units, with a Monte Carlo standard error of 0.05-0.06 and a smallest effective sample size per draw of 0.87-0.93. Of the other three, two refused on the correction’s own convergence guard and ONE HAD ALREADY COLLAPSED to 0.0004 and reported the same spurious +1.822 the short-session cell does, so a collapsed component is not a short-session problem andsqrt(VarCorr(fit))is worth checking at every trial count. The fixed effects move by less than 0.02.At 20 trials it is not usable, and the reason is worth knowing: five of six Laplace fits have already collapsed
sd(alpha)to 1e-4, so there is nothing to reweight, and the correction then walks at its own step cap for every round while WARNING that it did not converge. A shift reported withfit$importance$cappedtrue and every entry offit$importance$movesequal is that step cap, not an estimate, and the warning’s advice to raiseimportance_roundsenlarges it in exact proportion: five rounds of a 0.3645 cap give 1.822 and ten give 3.645. Checksqrt(VarCorr(fit))before believing a correction.The model must be grouped on the family’s own subject.
(1 | other)gives a per-subject likelihood against a per-other proposal, and the core refuses it by name. residuals(type = "deviance")still refuses, and for a different reason than before. The magnitude is now available, becauseloglik_row()carries each trial’s own log-likelihood and, for the seven option-code families, its saturated value of zero. What is missing is the SIGN: the response is nominal and has no mean to depart from.rlddm()’s row has no saturated value at all, because a density’s supremum at a fixed response time is unbounded, and its refusal says so.
Two new families
-
rlddm(), a delta learning rule whose value difference drives the drift rate of a Wiener first-passage density, so choices and response times are one likelihood (Pedersen, Frank and Biele 2017). The response is the response TIME and the boundary reached travels indec(). hBayesDM does not carry this model: it ships the two halves separately asbandit2arm_deltaandchoiceRT_ddm, and?rlddmmaps the parameters onto the second of those rather than naming a counterpart that does not exist.The density comes from
frmtmb.eam::wiener_lpdf(), newly exported for the purpose, rather than through a colon. This is the only dependency one extension of frmtmb has on another and it is one function.Recovered at 30 subjects by 100 trials, 60 replicates: every bias inside its Monte Carlo error, coverage 0.95 to 0.98 on
drift,bs,ndtandbiasand 0.87 onalpha, 60 of 60 usable. The pair that trades off is not the obvious one:driftwithbscorrelate at 0.17 across replicates, whilebswithndtreach -0.57 anddriftwithbias+0.56.simulate()refuses, asts_par7()does: one trial’s draw is two numbers and a response vector holds one.frm_task_simulate()returns whole data frames with both columns. -
igt_orl(), outcome-representation learning for the Iowa gambling task (Haines, Vassileva and Ahn 2018). Three value stores per deck, separate learning rates for gains and losses that SWAP between the played deck and the three that were not, and a perseverance term.0.1.0 left it out on the grounds that it is weakly identified. Measured, that is half right. Every parameter recovers at 30 subjects by 100 trials: biases at or inside their Monte Carlo error, coverage 0.92 to 0.98, 60 of 60 usable. What is weak is the SEPARATION of the three choice-rule parameters, visible in the correlation of the estimates rather than in their bias:
betaFwithbetaPat -0.77,kwithbetaPat -0.53,kwithbetaFat +0.45. ReportbetaFandbetaPtogether rather than one alone.
The Kalman filter’s exploration bonus
-
bandit4arm2_kalman_filter(bonus = TRUE)adds Daw and others’ exploration bonus,tau * (mu + phi * sqrt(s)), so an arm the subject is uncertain about is worth more than its posterior mean.phiis an ordinary distributional parameter and takes a formula like any other.bonus = FALSEis the default and fits exactly the model 0.1.0 fitted; the suite pins that by holdingphiat zero withbf(phi = 0)and requiring the two log-likelihoods to agree.
Checks
Three new rows in the Stan identity tier, all written from the published equations: the Kalman filter with the bonus at -2.8e-13,
igt_orlat -6.8e-13, andrlddmat 3.2e-09. The last one is not exact and that makes it the strongest row. Every other program in that file evaluates the same arithmetic as the engine in a different order; Stan implements the Wiener density itself, so this row compares two independent implementations of the density as well as two of the recursion. Which side the difference is on is settled by a third implementation rather than by the size of the residual: measured over 4000 rows spanning the region the fixture visits,frmtmb.eam’s density differs fromRWiener’s by a median of 0 and a 99th percentile of 3.6e-15 per row, so a per-row difference of about 4e-12 against Stan sits on Stan’s side.Longhand references for
igt_orl()andrlddm()join the six that 0.1.0 shipped, intest-reference.R: one subject and one trial at a time,ifand[for selection, the opposite spelling to the engine’s.test-factorization.Ris new: the three likelihood slots agree with each other and withlogLik(), a subject’s value is the sum of its own rows in the order the core fixes, the slots honor the stacked design the correction calls them with, and a replicate whose parameters differ gives different values, without which the stacking check would pass on a family that ignored the stacking entirely.
frmtmb.learn 0.1.0
First release. The value-learning models of the reinforcement-learning and computational-psychiatry literature, written as frmtmb families.
What the grammar buys
- Every parameter of every family is an ordinary distributional parameter with its own linear predictor. A learning rate takes a condition effect, a smooth term or a correlated per-subject random effect the way a mean does, and the effect comes back with a standard error. A reversal task fitted as
alpha ~ after_reversal + (1 | id)gives the CHANGE in learning rate with an interval, which two separately fitted models do not. There is no separate reversal family here for that reason.
One recursion, six families
bandit2arm_delta(), the Rescorla-Wagner delta rule on two arms.bandit2arm_dual(), separate rates for gains and losses.splitchooses whether the split is on the sign of the PREDICTION ERROR (the default) or of the OUTCOME, which is hBayesDM’sprl_rp. Measured: on binary payoffs the two are the SAME model, because the value store stays inside the payoff range and the two signs agree on every trial; they differ only on graded payoffs.prl_fictitious(), counterfactual updating, where the option that was not chosen moves in the opposite direction.bandit4arm2_kalman_filter(), the restless four-armed bandit of Daw and others (2006). Its learning rate is not a parameter: the Kalman gain is derived from a posterior variance the filter carries, so it falls as a subject learns.ts_par7(), the two-stage model-based and model-free hybrid of Daw and others (2011), withw,lambda, two learning rates, two sensitivities and perseveration.igt_pvl_delta(), prospect-valence learning for the Iowa gambling task. ORL is not here;?igt_pvl_deltasays why.The engine is internal and is the point of the package. It walks trials once and updates every subject at each step, taking the learning rule and the choice rule as tape-safe functions, with padding and a mask for unequal trial counts. A new family is the model and nothing else. It runs on the tape and off it, so the likelihood, the fitted trajectory and the simulator are ONE recursion rather than three copies that have to be kept in step.
Reading a fit
-
frm_value_trace()returns the per-trial value estimates each choice was made on, the prediction error the outcome produced, and the fitted probability of the choice that was made. These are the quantities papers plot. -
frm_task_design()builds a trial-level design for each task with the payoff schedule of every option fixed in advance, which is what makes a draw coherent: a simulated subject that takes the arm the real one did not still has to be paid. -
frm_task_simulate()draws whole datasets from the generative process, taking parameters directly rather than through a formula.frm_simulate()is the other route and goes through the fitted grammar; the two share the recursion and are checked against each other. -
frm_learn_families()is the reference table, including the map from each parameter to hBayesDM’s spelling of it.
What these families refuse, and why
-
fitted(),predict(type = "response")and every kind of residual refuse. The response is the option a subject took, coded 1 to K, and it is nominal: arm 2 is not twice arm 1 and the Iowa gambling task’s four decks have no order at all. Core forms a fitted value as the conditional mean of the response and a residual asy - mean, so a mean declared here would make three methods return arithmetic on a category code.frm_value_trace()returns everything a mean would have carried and more. Note that frmtmb’s ownrw_deltaexample DOES havefitted(): it codes a two-armed choice 0 and 1 and returns P(arm 1), which does not survive a fourth arm. -
frm(importance =)is refused by name for every family, and the refusal is wider than the mathematics. The correction needs one log-likelihood value per group and these likelihoods have one, butfrmtmb_structure(loglik =)returns a single AD scalar and there is nowhere to put the factors.frm_compat("bandit2arm_delta", "importance")names the seam at the console. The Laplace error is measured by simulation instead. -
simulate()is refused forts_par7()alone: one two-step trial’s draw is three numbers and a response vector holds one. Usefrm_task_simulate(). -
weights(),cens(),trunc()andse()refuse in the family’s own words: each reshapes a per-row likelihood contribution, and a trial’s contribution here is conditional on every earlier trial of the same subject.
Validation
- Every family is checked by an IDENTITY against an independent Stan program of the same model, written from the published equations, evaluated at frmtmb’s own estimates. The programs declare exactly the parameters frmtmb estimates on the scales frmtmb estimates them on, so the map is the identity and the comparison carries no Jacobian. The tier is gated on
FRMTMB_BRMS_FIT_TESTSandNOT_CRAN, like the brms comparison tier, because it compiles Stan programs. - Parameter recovery at a realistic scale, with bias and coverage.
- The Laplace caveat is measured by simulation at few trials per subject, and the numbers are in
vignette("learning")and in?frmtmb.learn. - No hBayesDM code or data is used anywhere. Its models are published equations and the families here are written from them. A cross-check against it lives in
dev/, is never run by the suite, and has not been run at all: hBayesDM 2.0.0 fits through cmdstanr, and CmdStan’s vendored TBB does not build under this machine’s compiler. The script’s header records the whole chain. So this release has no check against an outside implementation; what it has instead is an exact identity against its own independent Stan programs and a longhand reference per family.
Deliberate omissions
- RLDDM, a learning rule feeding a drift-diffusion choice rule, is the next family and is not built: the choice rule belongs to
frmtmb.eam, and building it here would make this package import a sibling extension. The engine already has the shape it needs. - No exploration bonus on the Kalman filter, and no ORL for the Iowa gambling task. Both are short additions on this engine and both are left out for stated reasons in their families’ help.