API
Frontend macros
BayesianRegressionModels.@brm Macro
@brm formula_block
@brm df formula_blockParse a brms-style formula block into a BRMI (BRM Intermediate).
The one-argument form returns a function model(df) -> BRMI (the model name is gensym-ed). The two-argument form bakes df in and returns the BRMI directly. Inside formula_block, each line is one of:
lhs ~ rhs— a sampling statement (likelihood or linear predictor).(x, y, z) ~ rhs— broadcast one RHS over several sampling LHSs, sugar for repeating the~row once per name. Each slot takes one ordinary sampling LHS;[y1, y2] ~ ...stays the joint multivariate response.lhs = rhs— a literal binding (named intermediate).ragged(y, group) ~ rhs— group a flat observed response at the formula boundary, aligned to thekernel(...)result referenced byrhs.effect(lp, coefficient) ~ Normal(location, scale)— an SBBRMI population-coefficient prior override.sd(lp, ID[, coefficient]) ~ Exponential(scale)andcor(:, ID) ~ LKJCholesky(K, eta)— SBBRMI priors for a shared|ID|random-effect block.
Multiple ~ lines produce a multi-response / distributional model. Inside an observed family's argument expression, nested @brm(expr) is the explicit opt-in for a coefficient-bearing predictor formula. Unmarked family arguments remain ordinary expressions.
brmi = @brm df begin
y ~ Normal(loc, err)
loc ~ 1 + age + (1 | subj)
err ~ Exponential(1)
endThe resulting BRMI feeds either backend: VBRMI(brmi) (vectorised Julia) or SBBRMI(brmi) (StanBlocks → Stan).
BayesianRegressionModels.@n Macro
@n lhs = rhsLower a parsed-formula LHS into a NamedColumn binding. Used internally by @brm; not normally called by hand.
BayesianRegressionModels.@x Macro
@x exprLower a parsed-formula RHS into the column-type tree (ExprColumn / NamedColumn). Used internally by @brm; not normally called by hand.
BayesianRegressionModels.@getproperty Macro
@getproperty obj.fieldExpands to hasproperty(obj, :field) ? obj.field : :field — used by the @brm df-baked path to fall back from "column from the dataframe" to "bare symbol" without erroring on missing columns.
BayesianRegressionModels._brm Function
_brm(formula::Union{Expr,AbstractString}; df=nothing) -> ExprThe macro-free entry point behind @brm. Parses a formula block and returns either a function definition (df=nothing, default) or a let-wrapped BRMI construction (df supplied). Useful when you want to construct the formula expression programmatically rather than via the macro.
BayesianRegressionModels.parse! Function
parse!(x; info) -> ExprWalk a formula expression once, classifying every bare Symbol into info.alllocals (as :nonlocal for data columns, :local for literal bindings, :maybelocal for response-side names that may or may not be observed). Returns the rewritten expression with ~/= statements lowered into @n/@x calls. Used internally by _brm.
Intermediate representation
BayesianRegressionModels.BRMI Type
BRMI(; ops...)
BRMI(operations::NamedTuple)BRM Intermediate — the parsed formula. operations is a NamedTuple mapping each LHS name to its NamedColumn. The two backends VBRMI and SBBRMI walk this representation.
A BRMI is what @brm ultimately returns (either directly, when df is baked in, or by calling the gensym'd model function on a dataframe). Introspect via outcomes / linear_predictors / predictors / grouping_factors / dependencies / data_columns.
BayesianRegressionModels.AbstractColumn Type
AbstractColumnSupertype of every column-shape token the @brm parser produces. Subtypes include MissingColumn, DataColumn, NamedColumn, ExprColumn, MultiMembershipTerm, LikelihoodColumn, and MaterializedColumn.
Backend dispatchers (vmeta_sampling_rhs, _sb_emit!, the introspection walkers) branch on this hierarchy rather than on Symbol tags.
BayesianRegressionModels.MissingColumn Type
MissingColumn()LHS marker for response columns that aren't in the dataframe — the backend treats them as parameters rather than observations.
sourceBayesianRegressionModels.DataColumn Type
DataColumn(vec)Wraps an observed vector from the dataframe. Surfaces as the leaf of a NamedColumn for any data-backed predictor or response.
BayesianRegressionModels.NamedColumn Type
NamedColumn(name, parent)Assigns a formula-local symbol name to its parent column. Every lhs ~ rhs and lhs = rhs line produces a NamedColumn keyed by lhs. Access via name / parent.
BayesianRegressionModels.ExprColumn Type
ExprColumn(f, args...; kwargs...)Represents an f(args...; kwargs...) formula RHS — the leaves the @brm parser builds for every :call Expr (e.g. Normal(loc, err), 1 + a + (1|g), mo(c)). Access via getf / getargs / getkwargs / getop.
Construction runs _check_term_kwargs so a term can reject a retired keyword at the @brm call site rather than in the backend.
BayesianRegressionModels.LikelihoodColumn Type
LikelihoodColumn(parent, rhs)Pairs an observed-response parent (NamedColumn over a DataColumn) with its distributional rhs. vimpl's llikelihood! walks these.
BayesianRegressionModels.MaterializedColumn Type
MaterializedColumn(parent, broadcast)vimpl's already-materialised storage: parent is the live buffer refreshed each draw, broadcast is the lazy expression that fills it. Used inside VBRMI.meta.materialized to thread per-draw recomputation of named intermediates (loc ~ 1 + a, etc.).
BayesianRegressionModels.Data Type
Data(df)Wrapper that exposes every column of the underlying DataFrame as a NamedColumn over a DataColumn. Used by @brm to lift df columns into the formula's column-type universe.
BayesianRegressionModels.MaybeData Type
MaybeData(df)Like Data, but for columns that may not be present in the dataframe. Missing columns surface as NamedColumn wrapping a MissingColumn — i.e. a parameter to be sampled rather than observed.
BayesianRegressionModels.maybedata Function
maybedata(df) -> MaybeDataConvenience constructor for MaybeData. Used by @brm to surface response-side names that may be data (observed) or parameters (sampled).
Column-tree accessors
BayesianRegressionModels.name Function
name(x::NamedColumn) -> SymbolReturn the formula-local name assigned to x (i.e. the LHS of the ~ / = line that produced it).
BayesianRegressionModels.getf Function
getf(x::ExprColumn) -> FThe callable head of an ExprColumn — e.g. Normal, +, ~, assign, mo. Sole dispatch tag for backend emitters.
BayesianRegressionModels.getargs Function
getargs(x::ExprColumn) -> Tuple
getargs(x::ExprColumn, n) -> TupleThe positional args of an ExprColumn. The two-arg form asserts length == n. Plus helpers for +-flattening: getargs(+, x::ExprColumn{typeof(+)}) returns the summands; getargs(+, x) wraps non-+ shapes as a 1-tuple.
BayesianRegressionModels.getkwargs Function
getkwargs(x::ExprColumn) -> NamedTupleThe keyword args of an ExprColumn. E.g. hsgp(x; k=20, c=1.5) exposes (; k=20, c=1.5).
BayesianRegressionModels.getop Function
getop(x) -> Symbol_or_FunctionSymbolic operator name for an ExprColumn: :|| for the doublepipe marker, :(=) for assign, otherwise falls through to getf. Used by the HTML renderer to print formula RHS with the original operator surface.
BayesianRegressionModels.getbroadcast Function
getbroadcast(x::MaterializedColumn) -> BroadcastedAccess the lazy broadcast expression that refreshes MaterializedColumn(x)'s parent buffer each draw.
Formula-term markers
BayesianRegressionModels.assign Function
Marker function for literal-binding (lhs = rhs) operations in a @brm block. Dispatch tag only — never called.
BayesianRegressionModels.effect Function
effect(linear_predictor, coefficient)
sd(linear_predictor, id[, coefficient])
cor(:, id)Address a prior BRM owns, by naming the parameter as the head and the target in the slots. Every slot is one name or :, and : means the default — the base layer that a more specific statement overrides:
@brm begin
log_ka ~ 1 + weight + (1 | pk | subject)
effect(log_ka, Intercept) ~ Normal(log(1 / 8), 0.8)
effect(log_ka, weight) ~ Normal(0, 0.1)
sd(:, pk) ~ Exponential(2 / 3)
cor(:, pk) ~ LKJCholesky(2, 2)
endThe grammar is <quantity>(<linear predictor | :>, <target…>). effect addresses a population coefficient, a categorical column's K-1 treatment contrasts, or — for the first categorical term of a predictor without an intercept — its K cell means, each also addressable as <column>_lvl_<k>; sd and cor address a shared |ID| random-effect covariance block. sd's trailing coefficient slot may be omitted and means :, so sd(:, pk) is the block-wide scale and sd(log_ka, pk, weight) is one margin. Because : is positional rather than a trailing omission, sd(:, pk, weight) addresses the weight margin across every predictor slicing the block. Correlation priors are block-wide by construction — one shared |ID| covariance spans every predictor that slices it — so cor takes : in the predictor slot.
An interaction term is addressed the way the formula spells it: effect(mu, gen & mathz) sets one shared Normal prior over every beta_pop column the gen & mathz term emits — one column for a continuous × continuous interaction, K-1 for a continuous × categorical one. Operand order does not matter (a & b and b & a are the same address). Each emitted column keeps its own int_… label (see popcoefnames), and addressing one of those labels directly refines the whole-term address on that column alone.
There is deliberately no concise, predictor-inferring form: name the linear predictor or write :. Two statements reaching the same parameter resolve most-specific-wins — fewer : slots wins — and an exact tie is an error.
The whole-predictor Colon address is also how the joint variance-decomposition family r2d2 attaches: effect(lp, :) ~ r2d2(...) decomposes every population coefficient of lp at once, while effect(lp, :) ~ Normal(...) is simply the default layer for those columns.
effect is an address marker, not a callable function; sd and cor are rewritten onto it by the macro and are not exported. Use effect_priors, ranef_effect_priors, r2d2_priors, and ranefcoefnames to inspect the captured statements and margin labels.
BayesianRegressionModels.r2d2 Function
r2d2(; R2=Beta(1, 1), tau_bsv=nothing, alpha=1)R²-induced Dirichlet Decomposition prior for a whole linear predictor, applied through a effect Colon address:
@brm begin
log_CL ~ 1 + weight + age + (1 | p | subject)
effect(log_CL, :) ~ r2d2(R2 = Beta(1, 1), tau_bsv = 0.5)
endOne total between-subject scale tau_bsv is split by R2 into an explained and a residual part. The explained part is allocated across the predictor's population columns by a Dirichlet simplex phi ~ Dirichlet(alpha), giving column k the derived prior scale
beta_scale[k] = sqrt(phi[k] * R2 * tau_bsv^2 / Var(x_k))while the predictor's random-effect margins take the residual scale sqrt((1 - R2) * tau_bsv^2) — for a latent per-subject parameter the random effect is the residual, which is the motivating PK reading (decision kx8wkd).
Keywords:
R2— aBetaprior on the explained fraction. Defaults toBeta(1, 1).tau_bsv— the total scale. A positive number fixes it; omitted, it becomes a sampled half-standard-normal parameter, which is the only option for a latent predictor with no observed response to anchor it.alpha— the symmetric Dirichlet concentration over population columns.
Within a shared brms-style |ID| bucket the decomposition is all-or-nothing: if any linear predictor slicing the bucket is r2d2-scoped, all of them must be, so the bucket's tau is one wholly derived vector rather than a part-sampled hybrid (decision 1db6zkr). The correlation factor L stays free and shared — r2d2 constrains marginal variances and says nothing about cross-predictor correlation.
r2d2 is a formula marker, not a callable function; the SBBRMI backend lowers it. Inspect captured statements with r2d2_priors.
The same marker on a random-effect SD address is the partial-R2D2M2 form:
@brm begin
sigma_pk ~ Exponential(1)
sigma_qt ~ Exponential(1)
log_CL ~ 1 + (1 | p | subject)
qt_base ~ 1 + (1 | p | subject)
sd(:, p) ~ r2d2(mean_R2=0.5, prec_R2=2,
concentration=1, reference_scale=sigma_pk)
sd(qt_base, p) ~ r2d2(reference_scale=sigma_qt)
cor(:, p) ~ LKJCholesky(2, 2)
endThis spelling samples one block-wide R2, allocates its odds R2 / (1 - R2) across the addressed marginal variances with a Dirichlet simplex, and derives margin j as tau[j] = reference_scale[j] * sqrt(phi[j] * R2 / (1 - R2)). R2=Beta(a,b) and the equivalent mean/precision spelling are both accepted; alpha= and concentration= are aliases. A more-specific sd(lp, id[, coefficient]) statement under a block-wide decomposition may override only reference_scale, which is how one correlated block can span observation channels with different residual units. cor(:, id) remains an independent LKJ prior and the non-centred draw geometry is unchanged.
Without a block-wide sd(:, id) statement, every R2D2 SD address owns its own R2 and simplex. A one-margin address has a deterministic simplex[1] and is therefore the per-margin ICC construction. Unaddressed margins retain their ordinary half-standard-Normal scale prior. Random-effect R2D2 is SBBRMI-only; centered and stratified buckets remain unsupported, while non-centred resample_groups replay redraws only the standardised group coordinates and transports the fitted R2, simplex, reference scales, and correlation factor.
Adding include= to the block-wide statement makes it the JOINT R2D2M2 budget: the block's one R2 and one Dirichlet simplex also allocate population components of every linear predictor slicing the block —
@brm begin
sigma_pk ~ Exponential(1)
sigma_qt ~ Exponential(1)
log_CL ~ 1 + wt + indication + (1 | p | subject)
qt_base ~ 1 + wt + indication + (1 | p | subject)
sd(:, p) ~ r2d2(mean_R2=0.5, prec_R2=2, concentration=1,
reference_scale=sigma_pk,
include=(:population, :contrasts))
sd(qt_base, p) ~ r2d2(reference_scale=sigma_qt)
cor(:, p) ~ LKJCholesky(2, 2)
end:population adds each predictor's non-intercept beta_pop columns, :contrasts its categorical treatment-contrast coefficients; :ranef names the margins, which are always allocated. A component of predictor m is scaled by m's own margin reference: a margin keeps tau[j] = reference_scale[m] * sqrt(phi[j] * R2 / (1 - R2)) and a coefficient or contrast takes reference_scale[m] * sqrt(phi[k] * R2 / ((1 - R2) * Var(x_k))), the design-column variance adjustment of the whole-predictor form (a contrast's dummy variance is its level frequency's p * (1 - p)). Intercepts stay outside. With include=, reference_scale= is optional: an omitted margin reference is a sampled half-standard-normal, so the statement then allocates LATENT between-group variation rather than an observed-outcome R². Per-column effect(lp, coef) ~ Normal(...) and effect(lp, categorical) ~ Normal(...) statements inside the scope are refused, as is a scoped predictor that also carries effect(lp, :) ~ r2d2(...); include= belongs on the block-wide statement only. The emitted carriers (pop_<lp>_beta_pop, cat_<lp>_<col>_beta, the block's derived tau) and every descriptor reader are unchanged.
BayesianRegressionModels.doublepipe Function
Marker function for the brms || zero-correlation random-effects operator. Dispatch tag only — never called.
BayesianRegressionModels.weighted Function
weighted(distribution, weights)Formula marker for a typed observation weight. The second argument is a StatsBase weight constructor over a dataframe column, for example aweights(replicate_k), fweights(repeats), or weights(power).
The weight type is semantic. Analytic weights modify a supported observation distribution's precision (currently Normal); frequency and generic weights scale model and pointwise log-likelihood contributions while preserving the base distribution's predictive RNG. Lowering is implemented by the StanBlocks backend; this marker is never called directly.
BayesianRegressionModels.gr Function
gr(group; by=strata)brms-style stratified grouping marker. (1 | gr(subj, by=diagnosis)) gives each level of by its own random-effect covariance structure. Dispatch tag only — backend interpretation lives in vmeta_sampling_rhs (vimpl) / the sbimpl ranef walker.
BayesianRegressionModels.mm Function
mm(group1, group2, ...; weights=nothing, normalize=true)Multi-membership grouping marker for random effects. Each observation belongs to two or more grouping levels and receives the weighted sum of their shared random-effect coefficients. weights is either omitted (equal 1/M weights) or a tuple of one data column per group. Supplied weights must be finite, nonnegative, and have a positive row total; by default each row is normalized to sum to one. Set normalize=false to preserve valid supplied magnitudes.
@brm lowers this surface immediately to a typed MultiMembershipTerm; the Stan backend validates and materializes it, including when data are replayed through reprocess.
BayesianRegressionModels.gp Function
gp(x...; cov=:exp_quad, iso=true, jitter=1e-9)
gp(x; cov=:periodic, period, jitter=1e-9)Exact latent Gaussian-process predictor for the StanBlocks backend. Accepts one-or-more real-valued axes, with an isotropic squared-exponential kernel by default; set iso=false for one length scale per axis. cov=:periodic selects Stan's periodic kernel sigma^2 exp(-2 sin^2(pi |x - x'| / period) / rho^2) over exactly one axis and requires the numeric formula constant period. jitter stabilizes the covariance Cholesky factor. Dispatch tag — lowering lives in _sb_gp / _sb_gp_aniso / _sb_gp_periodic (sbimpl).
BayesianRegressionModels.hsgp Function
Hilbert-space approx GP marker; population-level, 1D squared-exp kernel.
sourceBayesianRegressionModels.offset Function
offset(x)Fixed-slope (no beta) contribution to a linear predictor. brms-style offset(...) — adds x directly to the predictor without allocating any parameters. Dispatch tag only.
BayesianRegressionModels.zscale Function
Marker for zscale(x) — z-standardise x (subtract mean, divide by SD). Dispatch tag only.
BayesianRegressionModels.center Function
Marker for center(x) — mean-centre x. Dispatch tag only.
BayesianRegressionModels.standardize Function
Marker for standardize(x) — alias-style mean/SD standardisation. Dispatch tag only.
BayesianRegressionModels.protect Function
protect(x)brms I(...) analogue: literal-escape wrapper around an expression so the formula parser doesn't try to re-interpret it. In formula-eval contexts it's a no-op on Real — generic broadcast/materialise paths handle protect(expr) without a dedicated dispatch.
BayesianRegressionModels.factor Function
factor(x; ref=k, cmc=true)Treat x as a categorical predictor with optional reference level ref. Dispatch tag — backend buckets it into the categorical-predictor path in introspection.jl and _sb_cat (sbimpl).
Under an intercept the term is K−1 treatment contrasts against ref. As the first categorical term of a predictor WITHOUT an intercept it is cell-mean coded (K coefficients, no reference) — brms / R model.matrix semantics. cmc=false (brms' name, "cell-mean coding") switches that off and keeps the treatment contrasts, e.g. to pin one group of an intercept-free predictor at zero.
BayesianRegressionModels.mi Function
mi(y)Missing-data marker. Wrap a response LHS (mi(y) ~ Normal(...)) to opt into the brms-style observed/imputed split: observed rows feed the likelihood, missing rows become parameters drawn from the same family. See _sb_emit_mi! (sbimpl) for backend dispatch.
BayesianRegressionModels.me Function
me(x_obs, sd_x)brms-style measurement-error predictor marker. me(x_obs, sd_x) declares that the observed x_obs has Gaussian measurement error with SD sd_x; the backend allocates a latent x_true and emits the observation likelihood x_obs ~ Normal(x_true, sd_x). Dispatch tag — see _sb_me.
BayesianRegressionModels.s Function
s(x)Add a penalized one-dimensional thin-plate regression spline to an SBBRMI linear predictor. The fixed rank-10 basis contains the unpenalized null space {1, x} and eight penalty-whitened range columns whose shared smoothing standard deviation has a standard half-normal prior.
The public syntax is exactly one finite numeric predictor, s(x). At least ten unique training values are required; keyword arguments such as k= and knots= are not supported. The term owns its complete smooth contribution and does not receive an additional population coefficient.
Prediction and replay through reprocess or restan_data use the frozen training basis by default. This marker is implemented only by the StanBlocks backend; it is not a deterministic B-spline expansion and is not available to VBRMI.
See Formula terms for an example and a comparison with bs(...) formulas. Dispatch tag — see _sb_s.
BayesianRegressionModels.t2 Function
t2(x, z; k=(5, 5), basis=(:cr, :cr), full=false)Tensor-product smooth marker. The StanBlocks backend builds cubic-regression- spline margins, separates their null and penalized spaces, and gives the RR, RN, and NR tensor blocks independent smoothing scales. The current surface is two-dimensional, supports only basis=(:cr, :cr), and requires full=false. Prediction/replay freezes the training knots and penalty decomposition by default. See Formula terms for the full contract. Dispatch tag — see _sb_t2.
BayesianRegressionModels.ar Function
ar(time; p=1)Autoregressive-noise predictor marker. Adds an AR(p) noise process ordered by time. Only p=1 is supported in the current sbimpl emitter. Dispatch tag — see _sb_ar1.
BayesianRegressionModels.dar Function
dar(time; p=1)Differenced-autoregressive trajectory marker. The StanBlocks backend emits the weekly path
x[1] = 0, d[t] = beta * d[t-1] + sigma * z[t], x[t+1] = x[t] + d[t],
with 0 <= beta <= 1, sigma > 0, and standardized innovations z. It is a direct predictor summand, so a formula intercept supplies the initial level and no additional population coefficient multiplies the path. The time values must be finite and strictly increasing. Only p=1 is supported. Dispatch tag — see _sb_dar1.
BayesianRegressionModels.rw Function
rw(time)Random-walk trajectory marker. The StanBlocks backend emits the path
x[1] = 0, x[t+1] = x[t] + sigma * z[t],
over the sorted distinct values of time — i.e. dar(time) with the differences' persistence fixed at zero — with sigma > 0 and standardized innovations z; each row reads the point of its own time value, so rows may share a time (several groups per day get one shared walk). It is a direct predictor summand: a formula intercept supplies the initial level and no additional population coefficient multiplies the path. sd(lp, rw(time)) addresses sigma; the term has no persistence, so ar(lp, rw(time)) is refused. On replay the grid may gain new times (a forecast). Dispatch tag — see _sb_rw1.
BayesianRegressionModels.cdar Function
cdar(step; by=group, cor=C)Grouped, correlated, damped random-walk deviation marker. For every level g of the by group column and every distinct value w of the step column (sorted), the StanBlocks backend emits
delta[:, 1] = sigma * L * eta[:, 1], delta[:, w] = rho * delta[:, w-1] + sigma * sqrt(1 - rho^2) * L * eta[:, w],
with L L' = C the Cholesky factor of the group correlation (or covariance) matrix C — a P × P matrix given as a data field or a literal, P the number of group levels — 0 <= rho <= 1, sigma > 0, and standardized innovations eta. Each row contributes delta[group(row), step(row)] directly, with no additional population coefficient. sd(lp, cdar(step)) addresses sigma and ar(lp, cdar(step)) addresses rho. On replay the group levels and C are frozen from the fit while the step grid may grow (a forecast). Dispatch tag — see _sb_cdar.
BayesianRegressionModels.mo Function
Monotonic effect with free β coefficient (brms-style mo); reuses mo1 + one normal slot.
BayesianRegressionModels.mo1 Function
Monotonic effect with β fixed to 1 (brms-style mo, β=1); shares a simplex sibling.
BayesianRegressionModels.kernel Function
pred ~ kernel(data..., per_subject_lps...) do slices..., lp_values...
...
endGeneral group-local submodel term: broadcast an inline cell over the groups of the linear predictors it is given.
The per-subject LP formulas own the population effects, covariates, links and random-effect buckets — they are ordinary @brm formulas declared in the same block. kernel derives ONE shared grouping from those LPs and evaluates the do block once per group, with each positional passed in as that group's slice:
log_CL ~ 1 + weight + (1 | p | subject)
log_Vc ~ 1 + (1 | p | subject)
conc ~ kernel(t_obs, ragged(dose, dose_subject), log_CL, log_Vc) do ts, doses, lCL, lVc
... # runs per subject, in Stan
endPositionals split by kind. A raw data column on the kernel's own one-row-per-subject frame is gathered into a per-group slice. A column living on a DIFFERENT frame — a dose-event table, say — must declare its grouping with ragged. A linear predictor is passed as that group's scalar value.
Dispatch tag only — lowering lives in _sb_kernel_doblock! (sbimpl). The legacy model= / obs= spelling and its anonymous n_eta block were removed by user decision 130c904; the do-block form above is the only one. A name such as eta_CL is merely a user-chosen ordinary linear-predictor name—there is no kernel-owned eta vector or positional eta-index contract in the current API.
BayesianRegressionModels.ragged Function
ragged(x, group)Group a FLAT secondary row axis by a raw data column that names the subject of every row. The marker has two formula positions:
As a
kernel(...)positional,ragged(x, group)gives the cell a ragged per-subject vector.xmay be a flat data column or an event-axis linear predictor.As an observation LHS,
ragged(y, group) ~ Family(pred, ...)groups a flat response before applying the top-level likelihood. The referencedkernel(...)result supplies the authoritative subject row order, so labels are joined rather than sorted or inferred from first occurrence. The emitted observation keeps the logical nameyand therefore uses StanBlocks' normal top-level ragged outputs: flaty_gen, group-aggregatey_likelihood, and descriptorsegments. This formula-boundary grouping is SBBRMI/sbimpl-only.
For the kernel-positional form, x lives on some frame other than the kernel's one-row-per-subject frame — a dose-event table, say — and group names, for every row of that frame, which subject it belongs to. The cell receives x as a RAGGED per-subject vector.
x may be either a linear predictor declared in the same @brm block, or a raw flat data column. It is the same grouping either way:
log_F ~ 1 + vessel + mo(diet) + hsgp(log_dose) # rows = dose events
log_CL ~ 1 + weight + (1 | p | subject) # rows = subjects
pred ~ kernel(t_obs, dv,
ragged(dose_amount, dose_subject), # flat column -> grouped here
ragged(log_F, dose_subject), # predictor -> indexed in Stan
log_CL) do ts, yy, doses, lF, lCL
effective_dose = doses .* exp(lF)
...
endOnly the REALIZATION differs, and the difference is forced: a linear predictor is a Stan parameter, so it cannot be gathered on the Julia side — the plate takes an index column and the cell fancy-indexes the unsliced predictor (log_F[rows]). A data column is Julia data, so it is gathered directly into a Vector{Vector{T}}, which StanBlocks ingests as a ragged column natively — exactly what a hand-prepared per-subject view would have been. Wrapping a column does not consume the flat original: a term that names it on its own axis (hsgp(log_dose) above) still sees it.
Why the grouping is an explicit argument rather than derived: an ordinary per-subject LP is grouped by the ranef bucket it already carries (_sb_kernel_lp_bucket), but a secondary-axis population LP like the one above has no random-effect term at all, and a raw column carries no grouping whatsoever. The axis has to be declared, and group is that declaration.
ONE VALUE PER ROW OF x's OWN FRAME — no expansion happens anywhere. If the event table stores a compact schedule (one row per dose OP, carrying an interval and a count) then x has one value per OP. Constructing ragged(x, group) requires one group key per row of x and joins those keys to the kernel's outer subject labels. That is local validation of the grouping operation, not an inner-shape contract between cell arguments. Kernel compares no totals or per-subject inner lengths across positionals; once each positional supplies one outer cell per subject, relationships among the values inside a cell belong to the cell body.
Dispatch tag only — lowering lives in _sb_kernel_doblock! (sbimpl). Observation-LHS lowering lives in _sb_sampling!.
truncated and censored are re-exported unchanged from Distributions.jl and documented there; only interval_censored below is BRM's own. See Likelihoods for how the three compose on a response.
Likelihood distributions / prior markers
BayesianRegressionModels.OrderedLogistic Type
OrderedLogistic(eta, cutpoints)Ordered-logistic distribution with location eta and strictly increasing cutpoints. The support is 1:(length(cutpoints) + 1).
Inside @brm, y ~ OrderedLogistic(eta) remains the cumulative-link formula shorthand: BRM prepares and estimates the cutpoints. A standalone numeric distribution must supply them explicitly.
BayesianRegressionModels.Ordinal Type
Ordinal(structure, link, eta, thresholds; discrimination=1)Executable ordinal distribution with independently typed probability structure and inverse link. structure is Cumulative or StoppingRatio; link is LogitLink, ProbitLink, or CloglogLink. discrimination is finite and strictly positive.
For a cumulative model, thresholds must be strictly increasing and eta must be scalar. For a stopping-ratio model the thresholds are stage-specific intercepts and need not be ordered; eta may be either one shared scalar or a vector with one value per non-terminal stage. The support is 1:(length(thresholds) + 1).
Inside @brm, omit thresholds: for example, y ~ Ordinal(Cumulative(), ProbitLink(), eta). BRM prepares the fitted outcome levels and threshold parameters. Formula-only keywords discrimination= and per_threshold= are documented in the likelihood guide.
BayesianRegressionModels.OrdinalStructure Type
Supertype for the ordinal probability construction used by Ordinal.
BayesianRegressionModels.Cumulative Type
Cumulative-link ordinal probabilities, P(Y <= k) = F(d * (cut[k] - eta)).
BayesianRegressionModels.StoppingRatio Type
Stopping-ratio ordinal probabilities, P(Y = k | Y >= k) = F(d * (cut[k] - eta[k])).
BayesianRegressionModels.OrdinalLink Type
Supertype for typed ordinal inverse-link choices used by Ordinal.
BayesianRegressionModels.CategoricalLogit Type
CategoricalLogit(eta2, eta3, ...)
CategoricalLogit(@brm(formula))Reference-class categorical distribution. Class 1 has logit zero and each argument is the logit for one subsequent class. The support is 1:(length(params(d)) + 1).
Inside an outer @brm model, the concise nested-@brm form is normalised to one ordinary scalar linear predictor per non-reference outcome class. Those predictors share formula structure while fitting distinct coefficients.
BayesianRegressionModels.Horseshoe Type
HorseshoeCarvalho-Polson-Scott horseshoe shrinkage prior marker. Use as a prior on a coefficient:
coef ~ Horseshoe()
coef ~ Horseshoe(local_scale=0.5, global_scale=0.1)local_scale and global_scale are positive finite formula constants and default to one. sbimpl emits the standard reparameterised hierarchy beta = raw * lambda * tau. Each scalar call owns its own tau; "global" means global only within that call, not shared across several coefficients. Marker struct only — the @brm parser never constructs an instance.
BayesianRegressionModels.ZeroInflatedPoisson Type
ZeroInflatedPoisson(lambda, zi)Mixture of a point mass at zero (probability zi) and Poisson(lambda).
Missing docstring.
Missing docstring for BayesianRegressionModels.HurdlePoisson. Check Documenter's build log for details.
BayesianRegressionModels.NegativeBinomial2 Type
NegativeBinomial2(mu, phi)Negative-binomial distribution parameterised by positive mean mu and positive shape/precision phi, matching Stan's neg_binomial_2(mu, phi).
BayesianRegressionModels.BetaBinomial2 Type
BetaBinomial2(trials, mean, precision)Beta-binomial distribution parameterised by success probability mean and positive concentration precision.
BayesianRegressionModels.BinomialLogit Type
BinomialLogit(n, logitp)Binomial distribution parameterised on the logit scale. Both BRM backends use a logit-native log-pmf and avoid an inverse-logit round trip for density evaluation.
sourceBayesianRegressionModels.interval_censored Function
interval_censored(base; upper)
interval_censored(x; upper, lower=0)Formula marker for genuine interval evidence. On a response RHS, the observed response column supplies each interval's lower endpoint and upper supplies its upper endpoint:
y_lower ~ interval_censored(Normal(mu, sigma); upper=y_upper)Each row contributes the base family's probability over (y_lower, y_upper], exactly CDF(y_upper) - CDF(y_lower). The lower endpoint is open for discrete families too; unlike inclusive truncation it receives no predecessor shift. Posterior prediction remains on the uncoarsened base-response scale. This is separate from Distributions.jl's censored, which is the distribution of clamp(X, lower, upper) and therefore has atoms at its thresholds.
Inside a linear predictor, x is the observed covariate and upper is its row-specific quantification limit:
mu ~ 1 + interval_censored(concentration; upper=lloq)By convention, concentration == lloq marks a BLOQ row; values above lloq are exact. BLOQ rows allocate latent predictor values between lower (zero by default) and lloq with a truncated Normal prior; configure that prior with latent(<lp|:>, interval_censored(concentration)) ~ Normal(...). This predictor form is implemented by SBBRMI.
The marker is intentionally formula-local: unlike truncated and censored, there is no existing Distributions.jl value with these per-row evidence semantics for BRM to construct outside @brm. It lives in the backend-neutral formula layer so weak-dependency backends can inspect it without loading StanBlocks.
BayesianRegressionModels.CircularVonMises Type
CircularVonMises(mu, kappa; interval=(-pi, pi))Von-Mises distribution represented on one fixed half-open principal interval. interval must be a finite pair (lo, hi) with hi - lo == 2pi. The density is exactly Distributions.jl's VonMises(mu, kappa) after mapping both the mean and observation to equivalent angles, while rand maps native von-Mises draws back into [lo, hi).
This surface is deliberately distinct from Distributions' VonMises, whose support moves with mu. In an @brm formula the StanBlocks backend uses the same fixed-interval contract and lowers density and RNG evaluation to Stan's native von_mises_lpdf and von_mises_rng.
BayesianRegressionModels.SkewDoubleExponential Type
SkewDoubleExponential(mu, sigma, tau)Asymmetric double-exponential distribution with the exact parameterization of Stan's native skew_double_exponential(mu, sigma, tau). sigma is the native Stan scale and tau is the probability mass at or below mu, so cdf(d, mu) == tau. At tau == 0.5, this is exactly Laplace(mu, sigma).
Distributions.jl represents the same family as SkewedExponentialPower(mu, sigma_sepd, 1, tau), with sigma == 4sigma_sepd*tau*(1-tau). BRM uses that identity as the executable Julia receipt while lowering this type directly to Stan's native family.
BayesianRegressionModels.LKJCovarianceFactor Function
LKJCovarianceFactor(K; scale_prior=Exponential(1), shape=1)Formula-only composite prior for the Cholesky factor of a K-dimensional covariance matrix. The StanBlocks backend samples positive marginal scales and an LKJ correlation Cholesky factor, then binds diag_pre_multiply(scales, L_corr) to the declaration's left-hand side.
Use the result with MvNormalCholesky. This is a formula marker rather than a Distributions.jl constructor.
BayesianRegressionModels.MvNormalCholesky Function
MvNormalCholesky(mean, factor)Formula-only multivariate Normal family whose second argument is already the lower Cholesky factor of the covariance. A vector response such as [y1, y2] ~ MvNormalCholesky([mu1, mu2], L_res) is one row-wise joint likelihood, not two conditionally independent scalar likelihoods.
BetaBinomial is re-exported unchanged from Distributions.jl and documented there; BetaBinomial2 above is BRM's own mean/precision parameterization.
Backends
BayesianRegressionModels.VBRMI Type
VBRMI(brmi::BRMI) -> VBRMIVectorised-Julia backend: materialises brmi into a LogDensityProblems-compatible struct over a flat unconstrained vector. Holds meta.blocks (per-block tuples of Parts) and meta.materialized (per-name materialised columns).
brmi = @brm df (y ~ 1 + a)
vbrmi = VBRMI(brmi)
d = LogDensityProblems.dimension(vbrmi)
ld = LogDensityProblems.logdensity(vbrmi, randn(d))For Stan-grade sampling use SBBRMI instead; VBRMI is the pure-Julia path (Mooncake AD compatible, no BridgeStan required).
BayesianRegressionModels.SBBRMI Type
SBBRMI(brmi::BRMI; mod=@__MODULE__, cv_groups=Set{Symbol}(),
centered_groups=Set{Symbol}(), total_groups=:auto,
s2z_groups=(), s2z_rho=nothing, s2z_coordinates=:contrasts,
held_out=()) -> SBBRMIStanBlocks backend: walks brmi, emits a StanBlocks.SlicModel, and materialises the data dict. Pass mod if you're constructing the model from a module other than BayesianRegressionModels so SLIC's symbol resolver finds your locally-defined submodels.
total_groups=:auto integrates matching population coefficients into group totals when the exact Gaussian conditional construction is available. It supports one grouping structure per predictor, independent random-effect margins, and Normal, Flat, or Student-t population priors (Student-t uses its exact Gaussian scale mixture). Unmatched fixed effects remain explicit. Original population coefficients and deviations are recovered in generated quantities. Inspect total_effect_blocks, or use total_groups=() for the conventional representation. Naming a group explicitly requires eligibility and errors otherwise. Correlated, crossed, stratified, multi-membership and R2D2 blocks retain conventional emission under :auto.
s2z_groups is an opt-in collection of grouping-factor names (default (), disabled) emitted in the posterior-preserving sum-to-zero parameterization: J-1 free orthonormal contrast coordinates per coefficient with a fixed projected partial map, exact Gaussian marginalization of the omitted block means into the sampled population coefficients theta, and generated recovery quantities. s2z_rho is required whenever s2z_groups is nonempty (no public default yet): a scalar in [0,1] or one weight per coefficient (0 = noncentered contrasts, 1 = centered). First scope is scalar independent blocks with J >= 2, fully matched population design and Flat/Normal population priors; anything else errors loudly. Inspect s2z_effect_blocks. S2Z groups are excluded from automatic totals and cannot overlap centered_groups or cv_groups.
s2z_coordinates=:groups instead samples J group coordinates per coefficient: independent s_j ~ N(0, tau^(2c_j)) cells with deviations tau * (w - mean(w)), w = s ./ tau.^c. The extra dimension mean(w) is an independent auxiliary that leaves the posterior unchanged. There s2z_rho is each group's power-interpolation centeredness c (default 0), and every group is one scalar cell for adaptive_centering_problem.
Deprecated
:groups is unrequested and unvalidated, kept working only pending further exploration/research; prefer the default :contrasts.
cv_groups is an opt-in set of grouping-factor names (e.g. [:subject]) whose per-group random effect should be emitted with cv-contagious sizing – the std-normal draw is sized from maximum(<g>_idx) instead of the standalone data scalar n_<g>, so marking the group index with maybecv(:<g>_idx) at trace time flips that RE to a generated-quantities population re-draw (leave-all-out / out-of-sample). This is used when generating a CV model artifact; the default (empty cv_groups) sizes the same submodel from n_<g> and leaves the RE a fitted parameter. There are no separate _cv submodels — the size expression passed at the call site is the entire difference. Plain (… | g) ranefs and cross-formula (… |ID| g) buckets are both supported; stratified gr(g, by=b) errors if opted-in.
centered_groups is an opt-in set of grouping-factor names whose per-group random effect should be emitted in the centered parameterization – the per-group effect itself is the sampled parameter, with the covariance as its prior (bc ~ multi_normal_cholesky(0, diag_pre_multiply(tau, L))). Naming a group here explicitly selects conventional centered deviations and excludes it from automatic totals. Other conventional blocks use noncentered draws. Plain (… | g) ranefs and (… |ID| g) buckets are supported; stratified gr(g, by=b) is not.
The two sets are mutually exclusive per group: a centered block's sampled parameter is the per-group effect itself, and no available spelling of that can carry a cv taint in its size, so SBBRMI rejects a group named in both rather than silently emitting an in-sample block. Note also that centered and non-centered emissions use different unconstrained coordinates, so fitted draws are not interchangeable between them.
held_out names one response or a collection of responses — a strict subset. Holding out every observation is refused: there would be nothing to fit, and held-out likelihoods are not the prior mechanism. Each named observation is emitted through StanBlocks' cv activity analysis: its likelihood is removed while its predictive draw remains in generated quantities. Other likelihoods remain active, so held_out=:qt_y fits the rest of a joint model while drawing QT-only parameters from their priors. Names resolve against both top-level responses and data-backed observations inside kernel(...) cells. For prior draws, keep the model identical and omit the response column from the data — the program lowers to generated quantities automatically.
Formula statements sd(:, ID) ~ Exponential(scale) and cor(:, ID) ~ LKJCholesky(K, eta) configure a shared |ID| block. An SD statement can instead select one emitted margin with sd(predictor, ID, coefficient), or write sd(predictor, ID) when that predictor contributes exactly one margin. See ranefcoefnames for the authoritative ordered addresses. Omitted statements retain historical defaults; Julia's Exponential scale is converted to Stan's rate.
Use stan_code to extract the transpiled Stan source. For sampling, load StanLogDensityProblems + BridgeStan and wrap the emitted SlicModel in a StanProblem.
brmi = @brm df (y ~ 1 + a + (1|g))
sbbrmi = SBBRMI(brmi)
src = stan_code(sbbrmi)BayesianRegressionModels.TuringBRMI Type
TuringBRMI(brmi; centered_groups=(), cv_groups=())A BRMI lowered to the Turing backend. plan is the strict, Stan-independent semantic plan; model is the concrete DynamicPPL model provided by BayesianRegressionModelsTuringExt when Turing is loaded.
Groups named in centered_groups sample their model-scale effects directly; all others use the default non-centered geometry. cv_groups is accepted only to give a loud boundary: it controls emitted Stan artifact sizing and therefore does not apply to the dynamic Turing model. Use reprocess(backend, new_data; resample_groups=...) for new group populations.
BayesianRegressionModels.GenerativeDeclaration Type
GenerativeDeclarationOne emitted sampling declaration in a GenerativePlan.
roleis:prioror:observation. A:priorincludes structured latent submodels such aspopefs,ranef_correlated, andplate, not only scalar distribution calls.targetandfamilyare the emitted SLIC binding and RHS head.data_sourceis the original data key for an observation (including a plate-local alias such askernel_y => dv), otherwisenothing.drawis the canonical binding a generative executor should use for an observation, otherwisenothing. For top-level observations this matches StanBlocks' current posterior-predictive*_genname. Nested plate observations are inventoried too, but consumers must discover their actual executable twins throughstan_descriptor: the emitted names are based ondata_source, not this plate-local target. A ragged base has a flat draw and a group-aggregate likelihood, both carrying the observed group boundaries.contextnames enclosing plate results, outermost first.expressionis an exact snapshot of the emitted~expression.
The remaining fields decompose that RHS call so an executor never has to parse the snapshot itself:
argumentsare the RHS call's positional arguments, in order (the rate ofexponential(1), the eta oflkj_corr_cholesky(1.)).keywordsis aNamedTupleof every RHS keyword argument, verbatim.annotationis the LHS type annotation (:(vector[3])forkernel_z::vector[3] ~ ...), ornothingfor a bare LHS.dimensionnormalises the two ways a declaration can spell its size into one tuple: them/n/o(orsize) keyword that StanBlocks'autotypereads, and the LHS::T[s...]annotation, which wins when both are present.()means the declaration spells no size of its own — it is a scalar, or its extent comes from the data or from a submodel's internals (popefs,ranef_correlated) rather than from this declaration.constraintsis the subset ofkeywordsStanBlocks folds into the declared type:lower,upper,offset,multiplier. This is the field that makes a half-normal visible:std_normal(; n=3, lower=0.)andstd_normal(; n=3)differ only here.
Entries of arguments, dimension, and constraints are the emitted expressions, not evaluated values: an entry may be a literal, or a Symbol that is a key of the plan's data, or a larger Expr. Resolve symbolic sizes against plan.data.
constraints reports only the constraints spelled on this declaration. Families carry their own implied support (exponential is positive, beta lives on [0, 1]) — that is a property of family, held in StanBlocks' autokwargs table or a ValueFamily's intrinsic support, and BRM deliberately does not duplicate it here.
The declaration is intentionally backend-level: it describes what BRM really emitted after formula terms introduced their latent blocks, rather than a second model-specific interpretation of the @brm source.
BayesianRegressionModels.GenerativePlan Type
GenerativePlanAn introspectable, replayable snapshot of an SBBRMI's actual emitted declarations. model, data, and preproc are copied together at plan construction, while declarations inventories every emitted sampling site, including priors introduced inside kernel(...) plates and every observation in a multi-output model.
Construct with generative_plan. stan_code accepts a plan, and reprocess preserves the existing SBBRMI replay contract (including its correct-or-loud unsupported cases). Plans constructed from a reusable @brm builder also retain that builder so generative_plan(plan, new_df) can rebuild the same declarations for genuinely new groups.
This is a declaration plan, not an RNG executor: it does not claim that a component-wise consumer draw is prior-predictive. Its purpose is to expose the one authoritative program and provenance an executor must consume.
sourceBayesianRegressionModels.generative_plan Function
generative_plan(sb::SBBRMI) -> GenerativePlan
generative_plan(builder::Function, df; mod=@__MODULE__, cv_groups=Set(), centered_groups=Set(), held_out=()) -> GenerativePlan
generative_plan(plan::GenerativePlan, new_df; cv_groups=Set(), centered_groups=nothing, held_out=()) -> GenerativePlanSnapshot the declarations BRM actually emitted. The inventory is derived from sb.model.model, so auto-introduced population coefficients, random-effect blocks, named linear predictors consumed by kernel(...), observation families, and multiple outputs cannot drift from the fitted model.
Use the reusable-builder form when future schedules may contain new groups:
builder = @brm begin
sigma ~ Exponential(1)
mu ~ 1 + x + (1 | subject)
y ~ Normal(mu, sigma)
end
plan = generative_plan(builder, schedule; mod=@__MODULE__)
new_population_plan = generative_plan(plan, new_schedule)The SBBRMI form has no reusable formula builder to apply to genuinely new groups; use reprocess on that plan for the existing frozen-constant replay semantics instead.
centered_groups selects the centered parameterization per grouping factor, exactly as in SBBRMI. The builder form defaults to empty; the plan form defaults to nothing, which infers the source plan's own centered groups — read off its emitted declarations, so a centered fit rebuilds centered unless explicitly overridden. A group named in both cv_groups and centered_groups is refused by the SBBRMI constructor, as at fit time.
BayesianRegressionModels.reprocess Function
reprocess(sb::SBBRMI, new_df; freeze_constants=true,
resample_groups=()) -> SBBRMIRe-materialise the SBBRMI's Stan data dict against new_df, re-running the Julia-side preprocessing (decision nr3v8n A) — so the silent-stale-constant bug of a naive per-column sb.model(; col=…) rebind is avoided. Returns a NEW SBBRMI that REUSES the already-transpiled Stan model (byte-identical stan_code when shapes are stable) with the new data dict.
freeze_constants=true(default — prediction / replay): apply the training constant tonew_df(z-score with training mean/sd, map factor codes via the training level set, rebuild the spline/HSGP basis on the training eigenbasis/(mean, L)). This is the operation the downstream PKPD app hand-rolls outside BRM.freeze_constants=false(fresh-fit semantics): re-derive each constant fromnew_df, then apply.resample_groups=()(default): retain the fitted random-effect coordinates. Naming one or more ordinary grouping factors (for example[:subject]) re-emits the BRMI with cv-contagious sizing, derives those groups' levels fromnew_df, and marks their indices so their standardized effects are re-drawn in generated quantities from the fitted covariance. Predictor constants remain frozen unlessfreeze_constants=falseis also requested. This changes Stan source by construction; it is the new-population/CV twin of the default same-group replay.
Covered: the Julia-side transforms (zscale/standardize/center/factor/ mo/s/t2/gp/hsgp), interval-censored predictor splits, protect/implicit-fn columns (re-materialised on new_df), typed mm(...) and ordinary plain/|ID| random-effect group indices, kernel(...) subject counts and ragged(x, group) columns, formula-boundary ragged(response, group) observations and their data-backed censored/truncated/interval_censored bounds (both re-gathered from the flat source column into the kernel's per-subject row order), continuous × continuous interaction columns, typed observation weights, categorical outcomes, and pass-through raw columns (plain data, me obs values, ar time). A derived non-vector key with no preprocessing record is carried through when the replay DataFrame supplies its raw column; StanBlocks then retraces the derived carrier (for example a vector-of-vectors column becomes the ragged mem/ends data). Frozen replay errors loudly on an unseen fitted level. Stratified gr(g, by=b) group-index replay remains correct-or-loud unsupported rather than silently copying stale structure.
BayesianRegressionModels.restan_data Function
restan_data(sb::SBBRMI, new_df; freeze_constants=true,
resample_groups=()) -> DictThin convenience over reprocess: the prepared Stan data dict for new_df, ready for a param_constrain! replay. Equivalent to stan_data(reprocess(sb, new_df; freeze_constants, resample_groups)). Same freeze_constants semantics (default true = training constants applied to new data), and accepts the same resample_groups keyword. A non-empty resample_groups also changes the Stan program; call reprocess when you need the corresponding SBBRMI/source as well as this data-only convenience result. See reprocess for the covered-terms list and error cases.
BayesianRegressionModels.population_draws Function
population_draws(model, draws, unc_names; groups, rng=Random.default_rng()) -> Matrix{Float64}Population-level ("no random effects") draws: a copy of draws with every random-effect coordinate of the named grouping factors set to zero, so the model evaluates at those factors' population mean.
model— theSBBRMI/GenerativePlanthe draws were fitted under.draws— draws × coordinates, inunc_namesorder (an unconstrained posterior draw matrix).unc_names— that model'sparam_unc_names.groups— a grouping-factor symbol or a collection of them. A factor naming no emitted block is an error.
Every other coordinate — population coefficients, residual scales, and the random effects' own L / tau hyperparameters — is left untouched: only the per-group effect coordinates are zeroed.
Why zeroing is the population mean under both emissions
BRM's default emission is non-centered: the sampled coordinate is z ~ std_normal() and the effect is b = diag_pre_multiply(tau, L) * z. Zero z is therefore exactly b = 0, the population mean. Under the opt-in centered emission (SBBRMI(...; centered_groups = [...])) the same coordinates hold the effects themselves — unconstrained, so the unconstrained value IS the effect — and 0 is their prior mean. Either way the model's per-row contribution of that factor reads exactly zero (ranef_blocks).
A brms-style (… |ID| g) bucket is shared across sub-formulas and is zeroed as one block: selecting its grouping factor zeroes that factor's effects in every sub-formula that shares the bucket, which is what "population-level for g" means.
For exact total-coefficient blocks, each posterior draw instead recovers one population coefficient vector conditionally and replaces the selected group totals with its implied population means. rng controls this recovery. The input and returned draws are both in the compiled model frame.
Example
unc = BridgeStan.param_unc_names(stan_model)
pop = population_draws(sb, draws, unc; groups = :subject)BayesianRegressionModels.transport_draws Function
transport_draws(from, to, draws, unc_from, unc_to;
resample = (), rng = Random.default_rng()) -> Matrix{Float64}Transport fitted draws from model from onto model to — the same @brm declaration re-instantiated at new covariates and/or new group levels — so the fit can be evaluated on new data without refitting.
from/to—SBBRMI/GenerativePlanvalues.tois typicallygenerative_plan(plan, new_df)(the builder form, which rebuilds the same declarations for genuinely new groups),reprocess(sb, new_df)when the groups are unchanged, orreprocess(sb, new_df; resample_groups = [g])— see the resample-target paragraph below.draws— draws × coordinates inunc_fromorder;unc_from/unc_toare the two models'param_unc_names.resample— grouping factors whose EXISTING levels should also be re-drawn rather than reused (leave-all-out / out-of-sample semantics). For conventional plain and|ID|blocks this is REFUSED — re-draw Stan-side viareprocess(...; resample_groups=...)instead (rule 2). It is honoured for total-coefficient blocks (conditional recovery, the sanctioned totals route) and for the block kinds with no GQ emission.rng— the source for the surviving Julia-side draws: total-coefficient recovery and the rule-2 exception kinds.
Returns a draws × length(unc_to) matrix aligned to to.
The alignment contract
Every coordinate of to is accounted for exactly once, by NAME:
A random-effect coordinate whose group LEVEL also exists in
from(and whose factor is not inresample) is copied from that level's coordinate — by level label, so a reordering, an insertion, or a dropped level cannot misalign it.A random-effect coordinate for a level
fromdoes not have, or for a factor inresample, is drawn fresh — subject to the emission. Under a CENTERED emission (centered_groups) the coordinate holds the effect itself, so the fresh draw isb = C * zwithz ~ N(0, 1)andCrebuilt per draw from that draw's fittedtau/L— a draw from the fitted covariance through the model's own transformation (a bareN(0, 1)would be the wrong scale). No StanBlocks generated-quantities re-draw exists for centered blocks, so this Julia-side draw is the route. Under the NON-CENTERED emission, plain(… | g)and shared(… |ID| g)blocks REFUSE fresh draws: population prediction for the Sb backend goes through the StanBlocks model (reprocess(fit, new_df; resample_groups=[g]), whose block isgenerated=trueand skipped here), never through Julia-side randomness. Julia-sideN(0, 1)survives only for block kinds with no GQ re-draw: stratifiedgr(g, by=b)blocks, typedmm(...)blocks, plain (non-bucket) R2D2 blocks, and zerocorr||synthetic margins — exactly a draw from the fitted covariance, because theL/tauhyperparameters are copied per draw by rule 3.Every other coordinate must exist in
fromunder the same name and is copied.
Anything that does not fit those three rules raises. In particular a coordinate of to that is absent from from and is not a random effect, a random-effect block to has and from does not, and a block whose term count changed, are all errors — those are the cases where a positional splice would have produced plausible, wrong numbers.
Coordinates that exist in from but not in to (a dropped group level) are simply not carried over.
Resample targets (reprocess(sb, new_df; resample_groups = [g]))
resample_groups moves factor g's standardised draws to the target's GENERATED QUANTITIES, so they are re-drawn Stan-side per draw and are NOT in to's param_unc_names. ranef_blocks(to) reports such a block with generated = true; transport_draws SKIPS it — there is no coordinate to transport. What IS transported is everything that remains a parameter: the population coefficients and, critically, the resampled factor's own L / tau covariance hyperparameters, all copied by NAME (rule 3). The net effect is exact out-of-sample semantics — the target re-draws g's per-level effects from the FITTED covariance, because that covariance is transported. from's per-level draws for g are dropped (they are from-only coordinates), which is correct: the new levels are not the fitted ones. This is the intended consumption path for a resample target; a positional splice of the fitted levels' draws would be the exact wrong answer.
Example
same_plan = generative_plan(plan, same_df) # same subjects, new schedule
moved = transport_draws(sb, same_plan, draws,
unc_old, unc_same) # levels copied by label
pop = reprocess(sb, new_df; # NEW subjects: Stan-side
resample_groups=[:subject])
moved_pop = transport_draws(sb, pop, draws, unc_old, unc_pop) # GQ re-drawBayesianRegressionModels.hsgp_population_curve Function
hsgp_population_curve(d::BRMDescriptor, draws, constrained_names, grid;
predictor::Symbol, coefficient::Symbol,
term::Symbol)Evaluate the population exposure contribution of one model-derived, one-dimensional hsgp(...; orthogonal_to=:linear) term on a fixed grid. The returned named tuple contains grid and three draws × grid matrices:
linear— the population coefficient contributionbeta * grid;hsgp— the residual nonlinear HSGP contribution;total—linear + hsgp, the quantity consumers normally want.
draws must contain CONSTRAINED posterior draws as rows, in constrained_names order. The names must include transformed parameters: the sampled model-derived axis is required to reconstruct the fitted projection for each draw. With BridgeStan, obtain both with include_tp=true, include_gq=false.
The public addresses come from the formula. For mu ~ ... + x + hsgp(x; orthogonal_to=:linear, ...), use predictor=:mu, coefficient=:x, and term=:hsgp_x. Compiler-owned carrier names are resolved through the descriptor and never form part of this API.
The curve is population-only: it includes no intercept, other covariates, or group-specific random slopes. The grid must lie inside the fixed domain fitted by the model. Raw-data, grouped, multidimensional, non-orthogonal, and non-HSGP terms fail closed rather than returning a quantity with different semantics.
BayesianRegressionModels.select_hsgp_centeredness Function
select_hsgp_centeredness(unit_weights, log_scales;
candidates=0:0.1:1,
underflow_log=log(floatmin(Float64)))Choose one fixed partial-centering exponent per HSGP basis weight from a pilot fit. Rows are pilot draws and columns are basis frequencies. unit_weights contains the noncentered standard-normal coordinates and log_scales contains the corresponding log spectral standard deviations.
For candidate c, the loss is
log(std(unit_weights .* exp.(c .* log_scales))) - mean(c .* log_scales)evaluated with a per-column exponent shift. A candidate is inadmissible when its centered coordinate scale would fall below underflow_log; importantly, the c=0 endpoint is evaluated without multiplying by log_scales, so it remains valid even when a physical high-frequency scale is -Inf.
The result is a named tuple with centeredness, the candidate losses, an admissible mask, and the normalized candidates. This is an offline pilot-then-refit rule. It does not update geometry during warmup.
BayesianRegressionModels.select_ranef_centeredness Function
select_ranef_centeredness(unit_effects, log_scales;
candidates=0:0.1:1,
underflow_log=log(floatmin(Float64)))Apply the scalar offline partial-centering criterion to ordinary random-effect coordinates. Rows are pilot draws; columns are one scalar (term, group) cell. unit_effects holds noncentered standardized effects and log_scales the matching log standard deviations. The criterion and stable shifted-exponent evaluation are identical to select_hsgp_centeredness; only the coordinate interpretation differs. The result is a fixed-partial specification for a fresh fit and does not adapt during warmup.
BayesianRegressionModels.RanefBlock Type
RanefBlockOne random-effect block BRM emitted, described well enough that a consumer can address its draws without reading the generated Stan.
binding— the emitted submodel binding (:b_p_subjectfor a(… |p| g)bucket,:r_<lhs>_<g>for a plain(… | g)term).family— the emittingranef_*submodel (:ranef_correlated_draws, …).group— the grouping-factor dataframe column, or the tuple of membership columns for a typedmm(...)block. Selecting any member names that shared block.id— the brms-style|ID|bucket symbol, ornothingfor a plain ranef. A bucket is shared across sub-formulas, so it is addressed as ONE block.by— thegr(g, by=b)stratifying column, ornothing.levels— the training level labels ofgroup, in the index order the emitter assigned (sort(unique(raw)), orlevels()for aCategoricalVector).levels[g]is the label of columngof the block.n_terms/n_groups— the block's shape.z— the emitted Stan parameter carrying the block's effect coordinates: the STANDARDISED draw under the default emission, the effects themselves under a centered one.noncentered— seepopulation_draws. Not always true, and not a formality:centered_groups = [:g]emits the*_centeredfamilies withnoncentered = false. Both emissions are fully supported:population_drawszeroes either (a centered coordinate IS the effect, so0is its mean), andtransport_drawscopies retained levels by label under either while drawing fresh centered levels through the fitted covariance (b = C * zper draw) rather than bareN(0, 1).generated— true iffresample_groupsmoved this block's standardised draws to GENERATED QUANTITIES (areprocess(model, new_df; resample_groups = [g])re-draw target;transport_draws). Such a block is re-drawn Stan-side per draw, so it has NO coordinate inparam_unc_names: it is DESCRIBED (shape,levels, group count all read off the preprocessing record) butranef_coordinatesrefuses it andtransport_drawsskips it — itsL/tauhyperparameters are the transportable state, and they are copied by name.falsefor an ordinary fit (including a plaincv_groupsbuild, whose block is still a sampled parameter).
Obtain with ranef_blocks; resolve to unconstrained coordinates with ranef_coordinates.
BayesianRegressionModels.ranef_blocks Function
ranef_blocks(model) -> Vector{RanefBlock}Every random-effect block model emitted, in declaration order. model is an SBBRMI or a GenerativePlan.
This is the authoritative answer to "which parameters carry grouping factor g's effects, and is the emission non-centered?" — read off the emitted declarations, not off the @brm source and not off the generated Stan text.
Coverage and the loud edges
Recognised families are the ranef_* submodels in sbimpl.jl. A declaration whose family name begins with ranef_ but is absent from the emission table raises: that is a new parameterization no consumer of this API can be assumed to handle, and the whole point of routing through here is that such a change cannot pass silently.
Structured-latent blocks whose prior is :iid_normal or an element-wise non-normal (_sb_emit_block_draw!) emit an ordinary std_normal / dist declaration rather than a ranef_* submodel, and are deliberately NOT reported — there is nothing in the emission that distinguishes them from any other vector prior. hsgp(x, by=g) and any other structured field whose prior is :correlated_normal goes through ranef_correlated_draws and IS reported, with id === nothing.
Example
sb = SBBRMI(builder(df); mod=@__MODULE__)
for b in ranef_blocks(sb)
println(b.group, " → ", b.z, " ", b.n_terms, "×", b.n_groups)
endBayesianRegressionModels.ranef_coordinates Function
ranef_coordinates(block::RanefBlock, unc_names) -> Matrix{Int}Resolve block to positions in the unconstrained parameter vector, returned as an n_terms × n_groups matrix of 1-based indices into unc_names.
unc_names is the compiled model's unconstrained parameter names — BridgeStan's param_unc_names(model), in its order. Each coordinate is located by its Stan NAME. Any coordinate the block expects and unc_names does not carry is an error listing the missing names, because that is exactly the shape a silent misalignment takes (a template that dropped a covariate level, or a model rebuilt with a different parameterization).
One carrier breaks pure name lookup: for an array[n_groups] vector[n_terms] Stan parameter (:group_term), Stan's unconstrained_param_names lists names array-index-fastest (.1.1, .2.1, …) while the unconstrained vector itself reads array-element-major ([s][t] at (s-1)*n_terms + t) — a stanc3 asymmetry, measured against param_constrain, not against the name list. There the names locate only the block's contiguous SEGMENT (its start is their minimum resolved position, and a non-contiguous name set is a loud error); position (t, g) within it is start + (g-1)*n_terms + (t-1). This holds under either name order, so a Stan that one day lists these names in layout order needs no change here.
ranef_coordinates(blocks, unc_names) -> Vector{Matrix{Int}}maps over several blocks.
sourceBayesianRegressionModels.Flat Type
An improper uniform prior on the real line. Prior-only simulation is undefined.
sourceBayesianRegressionModels.TotalEffectBlock Type
Fitted design, prior and coordinate identities for one exact total-coefficient block.
sourceBayesianRegressionModels.total_effect_blocks Function
Return the exact total-coefficient blocks selected during model construction.
sourceBayesianRegressionModels.recover_population_draws Function
recover_population_draws(model, draws, unc_names; rng=Random.default_rng())Draw the original population coefficients conditional on each saved total- coefficient draw. Returns one record per predictor with population, totals, and deviations arrays. This exact recovery adds conditional randomness; totals are unchanged. Input draws must be in the compiled model's coordinate frame.
BayesianRegressionModels.select_total_centeredness Function
select_total_centeredness(model, draws, unc_names;
criterion=:position, gradients=nothing, grid=0:0.1:1)Select each total coefficient's centering independently from a saved pilot. draws and optional gradients are draws × coordinates in the COMPILED model frame. :position minimizes log SD minus mean log Jacobian; :gradient minimizes position-gradient correlation and requires matching exact gradients. No new density/gradient calls are made. The returned centeredness vector can be passed to adaptive_centering_problem; losses preserves every grid score.
BayesianRegressionModels.S2ZEffectBlock Type
Fitted design, prior and coordinate identities for one S2Z block.
sourceBayesianRegressionModels.s2z_effect_blocks Function
Return the S2Z blocks selected during model construction.
sourceBayesianRegressionModels.recover_s2z_draws Function
recover_s2z_draws(model, draws, unc_names; rng=Random.default_rng())Draw the omitted block means conditional on each saved S2Z draw and recover the original population coefficients (beta = theta - B*m) and group effects (b = r + m). Returns one record per predictor with population, means, deviations and effects arrays. Input draws must be in the compiled model's coordinate frame.
BayesianRegressionModels.select_s2z_rho Function
select_s2z_rho(s2z_model, pilot_model, draws, names; obs_prec, group,
predictor=nothing, aggregate=:median)Select per-cell S2Z centering weights from a saved pilot fit. draws/names are the pilot's posterior draws in the COMPILED (unconstrained) frame with one column per name; obs_prec is the draws-by-observations expected observation precision matrix (obs_prec[d, n], caller-computed from the pilot draws, e.g. 1 ./ sigma[d, n].^2 for Gaussian location). No density or gradient calls are made.
For each draw and coefficient, group information matrices are rebuilt from obs_prec and the S2Z block's retained design, per-draw group scales come from the pilot's <binding>_log_scale / <binding>_tau coordinates, and raw reliabilities come from _s2z_fisher_raw. Cells aggregate across draws (aggregate, :median or :mean) and rescale ONCE at the aggregated scale — never median-of-rescaled-rho, which mixes the chart nonlinearity with per-draw scale variation and is fragile under approximate pilots.
The pilot must cover the same grouping with identical levels and row order (checked against the pilot's preprocessing record, loudly refused otherwise). Returns (; rho, raw, sd, carriers, n_draws, group, columns): rho feeds SBBRMI(...; s2z_rho=rho) directly.
BayesianRegressionModels.select_s2z_centeredness Function
select_s2z_centeredness(model, draws, unc_names;
criterion=:position, gradients=nothing, grid=0:0.1:1)Select one centering per S2Z cell from a saved pilot. With s2z_coordinates=:groups the cells are groups (any compiled centeredness); with contrast coordinates they are the free Helmert contrasts of an endpoint model (s2z_rho=0 or 1). draws and optional gradients are draws × coordinates in the COMPILED model frame; no density or gradient calls are made. Each cell tau_k^c * w has location zero: :position minimizes log SD minus mean log Jacobian, :gradient minimizes the position-gradient correlation and needs matching exact gradients.
The returned centeredness vector is ordered by block, coefficient and then group or contrast. Pass it to adaptive_centering_problem(model, problem, backend; centeredness), with nonlinear_adapt=false for a fixed post-hoc refit. For group coordinates, reshape(centeredness, J, K) is also a valid s2z_rho for a recompiled fixed model. When the model also has total-coefficient blocks, their cells come first: vcat(select_total_centeredness(...).centeredness, s2z.centeredness).
select_s2z_rho instead gives Sean's Fisher-rule weights for the linear interpolation of contrast coordinates.
stan_code
stan_code is re-exported from StanBlocks.jl. BRM extends that binding with stan_code(sb::SBBRMI), which returns the transpiled Stan source for sb.model.
Concurrent construction and ownership
After loading the backend packages and defining custom distribution families, tasks can independently construct TuringBRMI(builder(data)) and SBBRMI(builder(data)). A formula builder and read-only input arrays may be shared; custom callables used during construction must support concurrent calls. Keep each build's backend and mutable preparation state private to its task, and use BRM's stan_code(sb), stan_data(sb), and stan_model(sb) entries when immediately consuming an SBBRMI inside the same compiled caller.
SBBRMI requires StanBlocks' ValueFamily API. Its generated vector priors and mixtures carry density, pointwise, RNG and intrinsic-support definitions as values. Synchronized caches share these read-only families; model construction does not install Julia methods or module bindings for them. Use the StanBlocks revision recorded in test/setup_env.jl when preparing a downstream environment.
RKBRMI follows the same ownership rules. Concurrent builds require the RK fix for generated model bindings, included in revision cb9f9ab75c65df33c9c23e175f5fbdbb521c5fa4 and pinned in the test environment. Prepare a separate rk_logdensity_problem for each task; that interface also provides the world-age boundary for immediate first execution.
turing_model_source(backend) returns an independent expression that can be edited without changing any backend. Give each inference task its own sampler state, mutable density/AD workspace, and explicit RNG; keep input arrays unchanged while another task uses them.
Executable descriptors
BayesianRegressionModels.BRMDescriptor Type
BRMDescriptorThe one authoritative, executable, reflectable value for a @brm declaration. Construct with brm_descriptor.
| field | what it is |
|---|---|
id | stable content identity — StanBlocks' descriptor id, the hash of the generated Stan source, which is also the key instantiate caches the compiled artifact under. Stable across processes; independent of name |
name | informational label (name= at construction, else the plan's own) |
formula | the canonical rendering of the parsed declaration (show of the BRMI). Not the literal characters you typed — it is derived from the declaration, so unlike a hand-kept formula_src string it cannot drift from the model that runs |
plan | the GenerativePlan — every emitted ~ site, in order |
stan | StanBlocks' ModelDescriptor for the same model, if you need the Stan-level view verbatim |
highlights | ordered caller-selected BRMHighlights, resolved by stable name against stan.definitions; the full included-definition inventory remains on stan |
inputs | Tuple of BRMInput — the data block plus its dataframe provenance |
outputs | Tuple of BRMOutput — everything the model produces, with BRM roles |
operations | Tuple of BRMOperation — derived, not listed |
columns | the dataframe columns the declaration reads. This is the schema a form should collect for a replay |
unpredictable | observation targets whose predictive draw the emitted Stan program does not produce. Empty in the ordinary case; see below |
brm_columns(d), brm_operation(d, name), brm_execute(d, name; …) are the accessors.
BayesianRegressionModels.BRMInput Type
BRMInputOne entry of the emitted Stan data block, with its BRM provenance attached.
The first eight fields mirror StanBlocks' ModelInput (stanblocks-use §30) — name, type (the Stan center type), size (the declared size expressions, not values), constraints, and the four flags:
observed— the model conditions on this input (it is the LHS of a~).held_out— this input is cv-marked, so its likelihood contribution is dropped and it re-draws in generated quantities.derived— this input is another input's declared size (y_nforvector[y_n] y). Re-bindingyre-derives it; never ask a user for it.inlined— folded into the generated source, never reaches the data JSON.
The last two are BRM's addition — the schema link back to the dataframe:
column— the dataframe column this Stan input was built from, ornothingwhen it has no single raw column (a design matrix, a level count, a size).transform— the BRM preprocessing kind applied on the way (:zscale,:center,:standardize,:factor,:mo,:spline,:gp,:hsgp,:static,:group_index,:ranef_factor_dummy,:multi_membership,:kernel_subject_count,:kernel_ragged,:protect), ornothingwhen the column is passed through untransformed.
column/transform are read from the plan's preproc record and the BRMI's own data columns — they are never inferred from the input's name.
BayesianRegressionModels.BRMOutput Type
BRMOutputOne thing the compiled model produces, with its BRM meaning attached.
Fields name … source mirror StanBlocks' ModelOutput (stanblocks-use §30): kind is :parameter / :transformed_parameter / :generated_quantity, and generative is :posterior / :draw / :pointwise_loglik / :derived with source naming the observation a :draw or :pointwise_loglik belongs to.
BRM adds:
role— what this output means in the formula:role what it is :population_effecta popefsblock — the population-level design and its coefficients:random_effecta ranef_*block and its internals:group_blocka kernel(...)/plateresult and everything declared per cell:parameteran ordinary declared prior ( sigma ~ Exponential(1)):linear_predictora formula linear predictor ( mu ~ 1 + x + (1|g)) — an assignment, so no~declaration binds it:posterior_predictivea predictive draw of an observation :pointwise_loglikan observation's per-element log-likelihood :stan_deriveda Stan-level output no BRM declaration owns declaration— theGenerativeDeclarationthis output came from, ornothingfor:linear_predictor/:stan_derived. Its twelve fields (family, dimension, constraints, the verbatim expression, …) are documented onGenerativeDeclaration; reach for them instead of re-parsing. An output whosenamediffers fromdeclaration.targetis one of that block's internals (pop_mu_beta_popunderpop_mu).logical— the BRM-level quantity whose value this emitted Stan output physically carries, ornothingfor an internal. It is narrower thandeclaration:pop_mu_beta_popbelongs to thepop_mudeclaration but is one of its internals, so it carries no logical target. For a raggedkernel(...)result,logical == :loccan accompany an emittednamesuch as:loc__pl_mem_1; consumers keep the logical identity while addressing BridgeStan through the emitted name. Every named value akernel(...)cell assigns carries one too — no annotation. The author bound the name and the value is in every posterior draw, so it is addressable by that name:juliapk_loc ~ kernel(t, dv, qt_y, log_CL, qt_base) do ts, yy, qy, lCL, qbase conc = exp(-exp(lCL) .* ts) qt_loc = qbase .+ slope .* conc # a cell value, not an observation qy ~ normal(qt_loc, qt_sigma) conc # the RETURN end brm_output(d, :qt_loc) # BRMOutput(name = :pk_loc_qt_loc__pl_mem_1, # logical = :qt_loc, role = :group_block, …) brm_output(d, :conc) # the same cell's other named value brm_output(d, :pk_loc) # the collected returnCell values are ordinary
:group_blockoutputs of the plate declaration and compose withbrm_outputs/brm_output_coordinates/segmentslike any other; their emittednamestays compiler-owned and unparsed. Only a top-levelname = ...counts: a slice parameter is an argument (address the positional it came from), and an in-cell~is an observation (addressable through its own column's twins, below). Two consequences are deliberate. Scratch is addressable too —CL = exp(lCL)giveslogical == :CL— sobrm_outputs(d)is wider than the set of quantities an author would call primary. And two cells may name a value the same thing; both are claimed,brm_output(d, :mu)then refuses and names the candidates with their owners, andbrm_outputs(d; logical=:mu)returns both for selection ondeclaration.target. That is the same one-logical-many-carriers contract an observation's two twins already use; the model still builds and unambiguous names still resolve directly. A formula-authored top-level=assignment is claimed the same unconditional way, under its own name:qt_scale = sqrt((1 - r2_qt) * V)giveslogical == :qt_scale(role stays:stan_derived) whenever the assignment survives to a posterior-draw carrier. Data-folded assignments never reach the outputs so they stay unclaimed, and an emitted name an earlier claim already owns keeps its existing logical. ⚠ A predictive twin is not a substitute for the location it was drawn from. In the model aboveqt_y_genisnormal_..._rng(qt_loc, qt_sigma)— noise ADDED — so it answers a different question thanqt_locdoes. For a predictive or pointwise-loglik twin it is StanBlocks'source— the OBSERVED QUANTITY — which is not always a declaration target. A top-levely ~ Normal(mu, sigma)givesy_genlogical == :y, where:yis also the declaration; a plate-nestedyy ~ normal(...)over columndvgivesdv_genlogical == :dv, where the declaration is the cell-localyyand:dvis a data column. Both twins of one observation share it, sobrm_outputneedsrole=to pick between them.labels— per-element labels when BRM can name them (population coefficients, viapopcoefnames), otherwisenothing. A UI that would otherwise printbeta_pop.1,beta_pop.2can printx,g.segments— group boundaries for a RAGGED quantity, carried through from StanBlocks'ModelOutput.segments, otherwisenothing. Groupgoccupiessegments[g-1]+1 : segments[g](withsegments[0] ≡ 0) within this output's own coordinates — so it composes directly with the vectorbrm_output_coordinatesreturns, and a consumer never re-derives per-subject boundaries from a length column.
role is derived from the declaration, never from the output's name — a user variable may legitimately be called beta_pop or end in _gen.
BayesianRegressionModels.BRMOperation Type
BRMOperationOne executable operation the declaration supports. Operations are derived, never listed: each appears exactly when the model actually supports it, so a consumer can render one button per operation without maintaining its own list and without rendering a button that will fail.
name/title— the identifier and a human label.inputs— the names this operation needs. Readoriginto know which namespace they live in::stanoperations take Stan data keys (the neither-derived-nor-inlined subset),:brmoperations take dataframe columns.outputs— the descriptor output names it produces (empty when it produces a new descriptor or the Stan source rather than draws).origin—:stan(delegated to StanBlocks'stan_execute),:brm(BRM-level: replay the declaration on new data), or:override(supplied by the consumer throughbrm_descriptor(...; operations=…)).run— the callablebrm_executeinvokes,(descriptor; kwargs...) -> result.
See brm_descriptor for the derivation table and the extension points.
BayesianRegressionModels.BRMHighlight Type
BRMHighlightOne caller-selected Stan definition to feature when presenting a BRMDescriptor.
nameis the stable definition name resolved by StanBlocks.captionis optional presentation text supplied by the caller.definitionis StanBlocks' authoritative definition descriptor, including its emitted source and direct dependency links.closureis that definition plus its exact transitive included dependencies in StanBlocks' authoritative emission order.
BRM never copies or parses the generated Stan source to construct either.
Pass symbols and/or name => caption pairs through the ordered highlights= keyword of brm_descriptor. The full executable definition inventory remains available on d.stan.definitions; d.highlights is only the selected, ordered presentation layer.
BayesianRegressionModels.brm_descriptor Function
brm_descriptor(sb::SBBRMI; name=nothing, operations=Dict(), titles=Dict(), highlights=())
brm_descriptor(plan::GenerativePlan; …)
brm_descriptor(builder::Function, df; mod=@__MODULE__, cv_groups=Set(), held_out=(), …)Derive the one authoritative executable semantic descriptor for a @brm declaration. See BRMDescriptor for the fields.
The builder form is the one to prefer — it keeps the @brm builder, so the descriptor can also offer :replay (rebuild the declaration for genuinely new groups):
builder = @brm begin
sigma ~ Exponential(1)
mu ~ 1 + x + (1 | subject)
y ~ Normal(mu, sigma)
end
d = brm_descriptor(builder, df; mod=@__MODULE__, name=:my_model)The derived operations
Each is offered exactly when the declaration supports it. Nothing is listed by hand, so an operation that appears can be executed.
| operation | origin | offered when |
|---|---|---|
:transpile | :stan | always |
:instantiate | :stan | always |
:fit | :stan | the traced model has ≥1 parameter and ≥1 likelihood term |
:predict | :stan | the Stan program emits ≥1 posterior-predictive draw and ≥1 BRM observation resolves to it |
:pointwise_loglik | :stan | the Stan program emits ≥1 pointwise log-likelihood |
:replay | :brm | the descriptor was built from a @brm builder (rebuild on a new dataframe, e.g. new subjects) |
:reprocess | :brm | the declaration has no random-effect block, or every random-effect block has frozen same-group preprocessing (plain/` |
:replay / :reprocess take the new dataframe positionally and return a NEW BRMDescriptor; their inputs are the dataframe columns in d.columns, not Stan data keys. :reprocess forwards both freeze_constants= and the checked new-population resample_groups= CV/GQ re-emission described by reprocess.
The builder form's held_out keyword names one response or a collection of responses — a strict subset; holding out every observation is refused. A partial selection removes only those likelihoods; the remaining observations still offer :fit. For prior draws there is no separate operation: keep the model identical, omit the response column from the data, and sample the :instantiate problem (fixed_param) — the program lowers to generated quantities automatically.
Extension points
These three keywords let a consumer extend presentation and operations rather than fork the descriptor:
operations— aname => …mapping applied AFTER derivation:a callable
(d; kwargs...) -> resultadds a new operation (or replaces an existing one'srun), recorded withorigin = :override;a full
BRMOperationreplaces the entry outright;nothingsuppresses a derived operation (hide a button the surface should not show).
titles— aname => Stringmapping relabelling any operation.highlights— an ordered collection of included Stan definition names, orname => captionpairs. Names resolve through StanBlocks' authoritative definition inventory and fail closed when absent; order and captions are presentation metadata only and do not changed.idor the executable model.
Overriding a name the model does not offer is allowed (that is how you add one); suppressing a name that was never derived is a loud error, because it means the caller is holding a stale operation list — exactly the parallel registry this type exists to remove.
Failing closed
Five cases raise rather than degrade, because each is unrecoverable in a consumer that keys on names:
A declaration target that is also a Stan data input. Every consumer keys on the name (form field, result target, BridgeStan lookup), so this cannot be auto-resolved. Rename the binding.
Two declarations resolving to the same Stan output. Ambiguous provenance; the descriptor refuses to pick one.
brm_operation(d, name)for an operation the model does not offer — errors and names the operations it does offer, so the discovery never moves into the consumer.Suppressing an operation that was not derived (see above).
Selecting an absent or duplicate Stan definition highlight. BRM accepts only names that StanBlocks reports in the executable definition inventory.
One case deliberately does not raise, because it is a legitimate model: an observation whose predictive draw the Stan program does not emit is recorded in d.unpredictable and simply excluded from :predict's outputs. If that leaves no draws at all, :predict is not offered. The consumer never sees a predict button that would fail — which is the point.
BayesianRegressionModels.brm_columns Function
brm_columns(d::BRMDescriptor) -> Tuple{Vararg{Symbol}}The dataframe columns this declaration reads — the schema a form should collect for a :replay / :reprocess. Equivalent to d.columns.
BayesianRegressionModels.brm_output Function
brm_output(d::BRMDescriptor, logical::Symbol; role=nothing) -> BRMOutputReturn the unique emitted output that physically carries the BRM quantity logical. This resolves BRM meaning to Stan representation without parsing an emitter-owned name. For example, a ragged loc ~ kernel(...) result can resolve to an output named loc__pl_mem_1 while retaining logical == :loc.
logical is normally a declaration target. It is also the name of any value a kernel(...) cell assigns — see the logical field on BRMOutput — which is how to address a fitted noise-free in-cell location, with no annotation on the term.
One logical target legitimately has several carriers, and role is how you pick one. An observation pk_conc ~ Normal(loc, sigma) emits a posterior-predictive twin and a pointwise-log-likelihood twin, both carrying logical === :pk_conc. Ask for the one you mean:
brm_output(d, :pk_conc; role=:posterior_predictive) # the *_gen carrier
brm_output(d, :pk_conc; role=:pointwise_loglik) # the per-element loglikThe emitted names (pk_conc_gen, …) are StanBlocks' to choose and are NOT part of this contract; that is the whole point of asking by role.
Fails loudly when the target has no emitted carrier or still maps to several; the caller must never guess from descriptor order in either case. The several-carriers message lists each candidate WITH its role, so the fix is the role= to add rather than a name to hardcode.
BayesianRegressionModels.brm_outputs Function
brm_outputs(d::BRMDescriptor; logical=nothing, role=nothing, kind=nothing)
-> Vector{BRMOutput}Every emitted output matching the given semantic filters, in descriptor order. Each filter accepts a Symbol or any collection of them, and an omitted filter does not constrain. This is the discovery query — it can legitimately return zero, one, or many; use brm_output when exactly one is required.
brm_outputs(d; role=:posterior_predictive) # every predictive carrier
brm_outputs(d; logical=:pk_conc) # every carrier of one target
brm_outputs(d; role=(:parameter, :random_effect)) # either roleFiltering here is on BRM meaning (logical / role) or Stan representation (kind) — never on the emitted NAME, which the compiler owns.
BayesianRegressionModels.brm_output_coordinates Function
brm_output_coordinates(d::BRMDescriptor, logical::Symbol, constrained_names;
role=nothing) -> Vector{Int}Resolve a logical BRM output to its columns in BridgeStan's constrained param_names (or an equivalent posterior name vector). Resolution first uses brm_output to obtain the descriptor's emitted name, then matches only that exact name or its documented container coordinates (name.1, name.1.1, …). It never parses a compiler-owned plate suffix.
role disambiguates a target with several carriers, exactly as on brm_output — a posterior-predictive slice of an observation is
brm_output_coordinates(d, :pk_conc, param_names; role=:posterior_predictive)The returned integers index constrained_names in their existing order. For a ragged carrier they compose with that output's segments: group g is coordinates[segments[g-1]+1 : segments[g]]. This axis-order preservation is deliberate, and is the one ordering contract that differs from the label-indexed resolvers (brm_population_effect_coordinates, brm_term_coordinates, brm_ranef_sd_coordinates), which return carrier element order under any axis permutation.
A missing carrier is an error, which catches descriptor/artifact drift instead of returning an empty posterior slice.
sourcebrm_output_coordinates(output::BRMOutput, constrained_names) -> Vector{Int}Resolve an already-discovered emitted carrier to its columns in BridgeStan's constrained param_names (or an equivalent posterior name vector). Matching is exactly the logical resolver's: only output.name and its documented container coordinates (name.1, name.1.1, …) match; compiler-owned plate suffixes are never parsed.
This overload is for internal carriers that have no public logical target, such as the Cholesky factor inside a correlated random-effect block. A missing carrier remains an error rather than an empty posterior slice.
sourceBayesianRegressionModels.brm_operation Function
brm_operation(d::BRMDescriptor, name::Symbol) -> BRMOperationThe named operation. Fails closed: an operation this model does not offer errors and names the ones it does, so the discovery never moves into the consumer.
sourceBayesianRegressionModels.brm_execute Function
brm_execute(d::BRMDescriptor, name::Symbol, args...; kwargs...)Run a derived operation.
brm_execute(d, :transpile) # the Stan source
prob = brm_execute(d, :fit) # a BridgeStan-backed StanProblem
brm_execute(d, :predict; problem=prob, draws=theta_unc, seed=1234)
brm_execute(d, :replay, new_df) # a NEW BRMDescriptor:stan-origin operations forward to StanBlocks' stan_execute (data keywords re-bind inputs; :predict requires draws and seed). :brm-origin operations take the new dataframe positionally and return a new descriptor. For prior draws, build the descriptor with the response column omitted and sample the :instantiate problem (fixed_param); there is no separate prior operation. Unknown names fail closed via brm_operation.
BayesianRegressionModels.required_brm_inputs Function
required_brm_inputs(d::BRMDescriptor) -> Vector{Symbol}The Stan data keys a consumer must actually supply — the neither-derived-nor-inlined subset of d.inputs. The BRM analogue of StanBlocks' required_inputs; for the dataframe schema use brm_columns.
Introspection
BayesianRegressionModels.outcomes Function
outcomes(brmi::BRMI) -> Vector{NamedTuple}Return one entry per <response> ~ Family(args...) likelihood (i.e. every ~ op whose LHS is a NamedColumn over a DataColumn):
(; response::Symbol, family, args::Vector{NamedTuple})args is the family-arg list, classified per arg into one of:
(; role=:data, name)– e.g.Binomial's trials column.(; role=:linear_predictor, link_fn, link_lp)– a latent LP referenced bare (link_fn=identity) or via a unary wrap likeexp(eta)/logistic(odds). Distributional models with multiple LPs (e.g.Normal(loc, err)where bothloc ~ ...andlog(err) ~ ...) produce one entry per LP.(; role=:joint_means, names)– the ordered row-aligned predictors of anMvNormalCholeskyjoint response.(; role=:covariance_factor, name)– that joint response's declaredLKJCovarianceFactor.(; role=:constant, value)– numeric literal.(; role=:expression, expr)– catch-all opaque ExprColumn.
BayesianRegressionModels.linear_predictor_op Function
linear_predictor_op(brmi::BRMI, name::Symbol) -> Union{ExprColumn,Nothing}The ~ ExprColumn that defines <name> ~ <rhs>, or nothing if name isn't a ~-bound entry in brmi.operations.
BayesianRegressionModels.linear_predictors Function
linear_predictors(brmi::BRMI) -> Vector{NamedTuple}One entry per ~ op whose LHS is NOT a data column – the intermediate linear-predictor binds (loc ~ 1 + a, log(err) ~ 1 + b, ...). Each entry is (; name::Symbol, link_lhs_fn::Function) where name is the underlying parameter name (e.g. :err for log(err) ~ ...) and link_lhs_fn is the link applied to the LHS (e.g. log; identity when bare).
BayesianRegressionModels.predictors Function
predictors(brmi::BRMI, lhs::Symbol) -> Union{NamedTuple,Nothing}For a linear-predictor LHS, classify the RHS into:
intercept::Boolcontinuous::Vector{Symbol}– bare predictors overRealdata columns.categorical::Vector{Symbol}– bare predictors overIntegerdata columns.re_terms::Vector{(; group, inner)}– random-effects blocks, withinnerrecursively the same shape so callers can reach RE-internal predictors. A typedmm(...)term additionally carriesgroups, the tuple of all membership columns, whilegroupremains its first column for compatibility with scalar-axis consumers.
Works for both bare-LHS LPs (loc ~ 1 + a) and link-transformed-LHS LPs (log(err) ~ 1 + b); the link is captured separately via linear_predictors. nothing only when the RHS is malformed.
BayesianRegressionModels.grouping_factors Function
grouping_factors(brmi::BRMI, lhs::Symbol) -> Vector{Symbol}The grouping-factor symbols in <lhs> ~ ...: the last arg of every (... | g) term plus the by= group of every hsgp(x, by=g) term. Empty if no grouping term or the LP isn't introspectable.
BayesianRegressionModels.column_data Function
column_data(brmi::BRMI, name::Symbol)Underlying data vector for a NamedColumn-over-DataColumn entry. nothing if name doesn't resolve to a DataColumn.
BayesianRegressionModels.data_columns Function
data_columns(brmi::BRMI) -> Vector{Symbol}Every name in brmi.operations that is a NamedColumn over a DataColumn (i.e. every actual data column referenced by the formula).
BayesianRegressionModels.dependencies Function
dependencies(brmi::BRMI, name::Symbol) -> NamedTupleRecursively walk the ~ body of name, collecting every data column
- intermediate LP it transitively references: (; data::Vector{Symbol}, intermediates::Vector{Symbol})
For y1 ~ Normal(loc, err) with loc ~ 1 + a + (1|g1) and log(err) ~ 1 + b, dependencies(brmi, :y1) returns (; data=[:a, :g1, :b], intermediates=[:loc, :err]). RE grouping factors count as data deps.
BayesianRegressionModels.hierarchical_outcomes Function
hierarchical_outcomes(brmi::BRMI)outcomes(brmi) filtered to those whose linear predictor has any RE term.
BayesianRegressionModels.linear_predictor_args Function
All :linear_predictor args from an outcome (>1 means distributional).
BayesianRegressionModels.data_args Function
All :data args from an outcome (e.g. Binomial trials column).
BayesianRegressionModels.primary_lp Function
The first :linear_predictor arg from an outcome, or nothing.
BayesianRegressionModels.popcoefnames Function
popcoefnames(brmi::BRMI, lhs::Symbol) -> Union{Vector{Symbol}, Nothing}Ordered labels of the population-level beta_pop coefficient columns for linear predictor lhs, as emitted by the sbimpl backend (SBBRMI). The k-th returned symbol labels the parameter column pop_<lhs>_beta_pop.k 1:1, so a consumer can turn raw pop_<lhs>_beta_pop.N posterior columns into human-readable names without re-parsing the formula.
Only terms that sbimpl folds into the popefs design matrix appear, in formula (left-to-right) order:
the intercept
1is INCLUDED (labelled:Intercept), at its formula position — conventionally first,1 + …; there is NO separatepop_<lhs>_Interceptparameter;plain continuous (
Real, non-integer) predictors — one column each, labelled by the column name;single-
betawrapped terms (mo,me,protect,log(x),x^2, …) — one column each, labelled by the emitted design key;an interaction
a & b— one label per expanded treatment-contrast column:int_a_x_bfor continuous × continuous;int_c_x_g_lvl_kfor continuousc× categoricalg(levels2..K— the continuous operand first, whatever the surface order);int_g_lvl_j_x_h_lvl_kfor categorical × categorical.
EXCLUDED — emitted as their OWN parameters, NOT beta_pop, so they never appear in pop_<lhs>_beta_pop:
integer- or
CategoricalVector-typed bare predictors →cat_<lhs>_<name>(K−1 treatment contrasts; K cell means for the first categorical term of a predictor without an intercept);offset(x)→xitself, with fixed coefficient one;mo1(c)→mo1_<c>;s(x)andt2(x,z)→ their own fixed/range coefficients and smoothing scales;explicit-coefficient
coef * a(own scalar);random-effects blocks
(… | g)→ per-group ranef parameters.
Needs a data-bound BRMI (any fitted model has one): the population-vs- categorical split reads each predictor's element type. Returns Symbol[] when lhs has no population columns (e.g. loc ~ (1 | g)), and nothing when lhs is not a linear predictor of brmi.
These are the labels the effect(lhs, label) ~ Normal(...) prior address resolves against — with ONE addition that is deliberately not listed here, because it is not a beta_pop column: a categorical / integer-coded predictor (bare, or wrapped in factor(...)) is addressable by its COLUMN name, which sets one shared Normal prior over its K-1 treatment contrasts (cat_<lhs>_<c>_beta). A predictor without an intercept codes its first categorical term by K cell means instead; the column name then covers all K, and each is also addressable on its own as <c>_lvl_<k> (k = the level's position in the fitted level order). Everything else in the EXCLUDED list above still owns parameters no effect(...) address reaches.
An interaction term is also addressable WHOLE, the way the formula spells it: effect(lhs, a & b) sets one shared Normal prior over every beta_pop column the term emits, in either operand order; a direct int_… label address refines it on that column alone.
BayesianRegressionModels.effect_priors Function
effect_priors(brmi::BRMI) -> Vector{NamedTuple}Return the population-coefficient prior statements captured by @brm, in formula order. Each entry has
(; predictor, coefficient, family, arguments, keywords, expression)Both slots are always present: predictor and coefficient are each either a Symbol or Symbol(":"), the wildcard meaning every value of this slot. coefficient uses the same labels as popcoefnames, including :Intercept — or a whole-interaction a&b key for an effect(lp, a & b) statement, which the backends fan out over every column the term emits. expression is the exact parsed RHS ExprColumn; family, arguments, and keywords are its decomposed, directly inspectable parts.
A Symbol(":") slot is the default layer: the SBBRMI backend applies such a statement to every parameter it reaches, and lets a more specific statement — one with fewer wildcard slots — override it. Two statements of equal specificity reaching one parameter have no winner and error, as do addresses matching no parameter at all.
BayesianRegressionModels.term_priors Function
term_priors(brmi::BRMI) -> Vector{NamedTuple}Return the prior statements captured by @brm that address a term's own parameters — the sd, ar, simplex, latent and length_scale heads applied to a term rather than to a coefficient or a grouping factor — in formula order. Each entry has
(; class, term, predictor, component, family, arguments, keywords, expression)class is :term_sd, :term_ar, :term_simplex, :term_latent or :term_length_scale. term is the canonicalised term key: the term as the formula spells it, minus numeric and keyword arguments, so me(x, 0.5) and me(x) both key as Symbol("me(x)"), interval_censored(x; upper=lloq) keys as Symbol("interval_censored(x)"), and hsgp(x; k=20) keys as Symbol("hsgp(x)"). predictor === nothing means the statement is the default layer for every linear predictor carrying that term; a Symbol restricts it to one. component is nothing except for sd(lp, t2(x, z), rr), where it names one of the tensor smooth's three penalty blocks.
Which parameter each class reaches:
| spelling | parameter |
|---|---|
sd(lp, s(x)) | the smoothing SD sds |
sd(lp, t2(x, z), rr|rn|nr) | one of the three tensor smoothing SDs |
simplex(lp, mo1(c)) | the monotonic-increment Dirichlet concentration |
latent(lp, me(x)) | the latent true covariate x_true |
latent(lp, interval_censored(x)) | the covariate values on BLOQ rows |
sd(lp, gp(x)) / sd(lp, hsgp(x)) | the GP marginal amplitude sigma |
length_scale(lp, gp(x)) / length_scale(lp, hsgp(x)) | the GP length scale rho |
ar(lp, dar(time)) | the differenced-AR persistence coefficient beta |
sd(lp, rw(time)) | the random walk's innovation scale sigma |
sd(lp, cdar(step)) / ar(lp, cdar(step)) | the grouped damped walk's innovation scale sigma / persistence rho |
Standardized raw innovations (b_pen_raw, z, beta_raw, epsilon) are deliberately NOT addressable: they carry no independent scale, and giving them a prior would duplicate or confound the model-scale parameter above (decision 145tp0o).
BayesianRegressionModels.ranef_effect_priors Function
ranef_effect_priors(brmi::BRMI) -> Vector{NamedTuple}Return random-effect covariance-prior statements captured by @brm, in formula order. Each entry has
(; class, id, predictor, coefficient, family, arguments, keywords, expression)class is :sd or :cor. predictor === nothing means the statement is not predictor-specific — sd(:, ID), and always cor(:, ID), since a shared |ID| covariance block spans every predictor that slices it. coefficient === nothing means the whole block rather than one margin. sd(lp, ID, coef) carries both symbols. Use ranefcoefnames for the authoritative ordered margin addresses of a shared |ID| block.
BayesianRegressionModels.r2d2_priors Function
r2d2_priors(brmi::BRMI) -> Vector{NamedTuple}Return the whole-predictor variance-decomposition statements captured by @brm — the effect(lp, :) ~ r2d2(...) / effect(:, :) ~ r2d2(...) family — in formula order. Each entry has
(; predictor, family, arguments, keywords, expression)predictor is a Symbol for the explicit effect(lp, :) form and nothing for the wildcard effect(:, :) form, which the SBBRMI backend resolves only when the model has exactly one population predictor — a decomposition is per-predictor, so there is nothing to share. family is the marker function (r2d2); keywords carries the decomposition's R2 / tau_bsv / alpha settings.
These statements are deliberately separate from effect_priors: a Colon address configures a joint prior over every population column of its predictor plus that predictor's random-effect margins, rather than overriding one labelled coefficient.
BayesianRegressionModels.ranefcoefnames Function
ranefcoefnames(brmi::BRMI, id::Symbol) -> Union{Vector{NamedTuple},Nothing}Ordered (predictor, coefficient) addresses of the marginal SDs in the shared random-effect block selected by public |ID| symbol id. The k-th entry labels the k-th tau element emitted by the SBBRMI backend. Categorical random slopes use the exact dummy-column symbols emitted into the random-effect design matrix: <c>_dummy_2 … <c>_dummy_K under a random intercept, and <c>_dummy_1 … <c>_dummy_K for the first categorical term of an intercept-free block such as (0 + c | ID | g) (every level owns a group-level effect; opt out with factor(c; cmc=false)). Interaction slopes (a & b) use the exact emitted int_… design-column symbols shared with the population path: int_a_x_b for continuous × continuous, int_c_x_g_lvl_k for continuous × categorical, int_g_lvl_j_x_h_lvl_k for categorical × categorical.
Returns nothing when id is absent. Reusing one ID with multiple grouping factors is ambiguous on the public ID-only surface and raises.
Extension API
For downstream packages adding their own formula terms (e.g. via a gitignored -ext.jl extension):
BayesianRegressionModels.Part Type
Part{F<:Function,D<:NamedTuple}Bundles a marker function func (the sole dispatch tag for per-part behavior — nparams, lprior!, Base.show) with a NamedTuple data of per-kind state. Each part owns its buffer and knows how to consume a slice of the unconstrained vector, constrain it into the buffer, and return its log-prior contribution.
meta.blocks is a NamedTuple keyed by coalescing Symbol (:__population__, or a grouping-factor name like :g1); each value is a tuple of parts.
BayesianRegressionModels.push_parts!! Function
push_parts!!(meta, group::Symbol, parts::Part...) -> metaAppend parts to meta.blocks[group], creating the block if it didn't exist.
BayesianRegressionModels.nparams Function
nparams(x) -> IntNumber of unconstrained reals consumed by x (a Part, a tuple/NamedTuple of parts, or meta.blocks). Defines the slice length each part owns in the flat unconstrained vector. Extension methods register the dimension of a new marker function's parameter block.
BayesianRegressionModels.lprior! Function
lprior!(container, x) -> lpRecursive shell: each child (block-container, Part, …) is handed a view of exactly nparams(child) unconstrained reals; bottoms out on Part methods, each of which writes its constrained buffer and returns its log-prior + Jacobian contribution.
lprior!(p::Part{typeof(chol)}, x; eta=1.0) -> lpEither wrong or better LKJCholesky unconstraining + prior. Writes p.data.L in place from x (length n(n+1)/2) and returns the log-prior + Jacobian contribution. eta is the LKJ shape parameter.
lprior!(p::Part{typeof(simplex)}, x; alpha=1.0) -> lpLog-scale stick-breaking with logistic slices: constrain length(values)-1 unconstrained reals x into the simplex p.data.values in place and return log|J| + logpdf(Dirichlet(K, α), values) in one pass. Fully log-scale — never forms 1 - Σ values_<i on the linear scale, so it stays stable even when simplex entries get tiny. α == 1.0 reduces to the Jacobian-only transform (plus the constant loggamma(K)).
Ported from blog/posts/simplex/transforms/stickbreakingLogistic.stan.
lprior!(p::Part{typeof(mo1)}, _) -> 0.0Zero-parameter follow-up to a simplex sibling: refresh contrast = vcat(0, cumsum(values)) in place so downstream broadcasts see up-to-date contrast values. Contributes nothing to the log-prior.
lprior!(p::Part{typeof(hsgp)}, x) -> lpRefresh sqrt_spd + contrib in place from the K+2 unconstrained reals (ordering: log_rho, log_sigma, beta_1..beta_K). Returns Normal(0,1) log-priors on all three component groups (no Jacobian because we parameterize on the log-scale directly).
BayesianRegressionModels.vbroadcasted Function
vbroadcasted(x; meta) -> Broadcastedvimpl's column materialiser. Resolves a column-tree value to a broadcastable expression by walking NamedColumn, DataColumn, ExprColumn leaves. Used inside vmeta_sampling_rhs method bodies to resolve argument columns before scaling by a fresh β or wrapping in a likelihood. Add a method for a custom term to expose it under generic broadcast/arithmetic.
BayesianRegressionModels.vmeta_sampling_rhs Function
vmeta_sampling_rhs(meta, rhs; group) -> (meta', broadcasted)vimpl's per-term sampling-RHS walker. Dispatches on the type of rhs (ExprColumn of + / | / & / custom term, scalar Int, NamedColumn, …) and returns an updated meta plus a broadcasted expression that feeds the linear predictor. group is the current ranef-grouping symbol (:__population__ for fixed effects).
Extension hook: add a method on this function (typically dispatched on ::ExprColumn{typeof(your_term)}) to register a new formula term with the vimpl backend. The method allocates any Parts into meta.blocks via push_parts!! and returns the per-row contribution. See mo / mo1 / gp / offset in vimpl.jl for worked examples.
vmeta_sampling_rhs(meta, x::ExprColumn{typeof(mo1)}; group) -> (meta, broadcasted)Parse a mo1(c) term: brms-style monotonic effect with β fixed to 1. Population-level only for now.
vmeta_sampling_rhs(meta, x::ExprColumn{typeof(mo)}; group) -> (meta, broadcasted)Parse a mo(c) term: brms-style monotonic effect with free β. Delegates to _mo_contrast! for the monotonic contrast and multiplies by a fresh β slot allocated via growblock!!. Population-level only for now.
vmeta_sampling_rhs(meta, x::ExprColumn{typeof(offset)}; group) -> (meta, broadcasted)Parse an offset(x) term: fixed-slope (no beta) contribution to the linear predictor. brms-compatible — adds x directly to the predictor without allocating any parameters. Population-level only.
vmeta_sampling_rhs(meta, x::ExprColumn{typeof(gp)}; group) -> (meta, broadcasted)Parse a gp(x; k=K, c=C) term. Precomputes PHI + lambda from raw data, pushes a Part(hsgp, …) into the population block, and returns a broadcast wrapper over the per-draw contrib buffer (refreshed in lprior!).
BayesianRegressionModels._sb_submodel_rhs! Function
_sb_submodel_rhs!(stmts, data, target, f, rhs)sbimpl extension hook. Override (e.g. in a downstream -ext.jl) to route target ~ f(...) where f is a known SLIC submodel family (a dose-response or time-course submodel, …) straight to target ~ <slic>(; kwargs), bypassing the population-linear-predictor wrap (which would otherwise multiply the submodel output by a fresh β).
Return anything non-nothing to claim the binding; return nothing to fall through to the default linear-predictor path. The default method (this one) returns nothing.
Use import BayesianRegressionModels: _sb_submodel_rhs! (not using) when adding methods from a downstream module so the binding is extended rather than shadowed.
Reserved data keys — BRM owns raw grouping-column names. Any raw column used as a ranef grouping factor (the g in (… | g), (… | ID | g), or gr(g, by=…)) is reclaimed by BRM's ranef pre-pass: the raw labels are DELETED from data and replaced by a dense integer index (g_idx) and level count (n_g), because Stan cannot consume raw (possibly string) labels. So a hook must NOT stash its own vector under a raw column name that the same model also uses as a grouping factor, nor emit a reference to it — the reclaim will delete it out from under the emitted statement, and the failure surfaces only much later, in StanBlocks tracing, as an unresolvable-symbol error. Key consumer-owned data PER TARGET instead, e.g. Symbol(col_key, :_, target), which is self-owned and order-independent. (_sb_reclaim_group_col! now turns a collision into an immediate, correctly-attributed error at the delete site rather than a late one in a third package.)
BayesianRegressionModels._sb_emit_vector_prior! Function
_sb_emit_vector_prior!(stmts, data, target, f, op)sbimpl extension hook for a VECTOR-valued parameter prior on a non-data LHS (diet_share ~ Dirichlet(3, 1.0)). Return true to claim the binding, false to fall through to the scalar _sb_emit_prior! seam and then to the linear-predictor path.
It exists alongside _sb_emit_prior! rather than inside it because a multivariate family's shape comes from its hyperparameters: Stan sizes simplex[K] from the Dirichlet concentration, so the concentration has to be registered in data — which the four-argument scalar seam has no access to. Keeping the two seams separate also leaves every existing downstream _sb_emit_prior! method's arity untouched.
Use import BayesianRegressionModels: _sb_emit_vector_prior! (not using) when adding methods from a downstream module.