| Title: | Template Unified Latent Process Architecture for Bayesian Hierarchical Models |
| Version: | 0.2.0 |
| Description: | A general-purpose engine for fitting Bayesian hierarchical models with spatial fields, temporal effects, spatially varying coefficients, and multiple inference backends. Scalable spatial structure includes Hilbert space approximate Gaussian processes (HSGP; Riutort-Mayol et al. 2023 <doi:10.1007/s11222-022-10167-2>), nearest-neighbor Gaussian processes (NNGP; Datta et al. 2016 <doi:10.1080/01621459.2015.1044091>), intrinsic conditional autoregressive models (ICAR; Besag, York, and Mollie 1991 <doi:10.1007/BF00116466>), the reparameterized Besag-York-Mollie model (BYM2; Riebler et al. 2016 <doi:10.1177/0962280216660421>), and stochastic partial differential equation fields (SPDE; Lindgren, Rue, and Lindstrom 2011 <doi:10.1111/j.1467-9868.2011.00777.x>). Temporal structure covers random walks, autoregressive processes, and Gaussian processes. Inference is tiered by correctness guarantee: exact Hamiltonian Monte Carlo with the No-U-Turn sampler, Laplace and nested Laplace approximations with hyperparameter integration (Rue, Martino, and Chopin 2009 <doi:10.1111/j.1467-9868.2008.00700.x>), and variational inference. Model-specific packages plug observation likelihoods into the engine through a templated C++ callback interface. |
| License: | MIT + file LICENSE |
| Encoding: | UTF-8 |
| Language: | en-US |
| SystemRequirements: | C++17 |
| Depends: | R (≥ 4.1.0) |
| Imports: | Rcpp (≥ 1.0.12), Matrix, tulpaMesh (≥ 0.1.3), generics, lifecycle, stats, tools, utils, methods, graphics, grDevices |
| Suggests: | testthat (≥ 3.0.0), bayesplot, ggplot2, sf, terra, knitr, rmarkdown, posterior (≥ 1.5.0), loo (≥ 2.7.0), rstantools, lme4, nlme, lmtest, numDeriv, spdep, MASS, betareg, glmmTMB, pscl, tweedie, fmesher, patchwork, stars, statmod, withr |
| LinkingTo: | Rcpp, RcppEigen, Matrix |
| Config/testthat/edition: | 3 |
| VignetteBuilder: | knitr |
| URL: | https://github.com/gcol33/tulpa, https://gillescolling.com/tulpa/ |
| BugReports: | https://github.com/gcol33/tulpa/issues |
| Config/roxygen2/version: | 8.0.0 |
| NeedsCompilation: | yes |
| Packaged: | 2026-08-27 23:30:17 UTC; Gilles Colling |
| Author: | Gilles Colling |
| Maintainer: | Gilles Colling <gilles.colling051@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-09-09 15:30:02 UTC |
tulpa: Template Unified Latent Process Architecture for Bayesian Hierarchical Models
Description
A general-purpose engine for fitting Bayesian hierarchical models with spatial fields, temporal effects, spatially varying coefficients, and multiple inference backends. Scalable spatial structure includes Hilbert space approximate Gaussian processes (HSGP; Riutort-Mayol et al. 2023 doi:10.1007/s11222-022-10167-2), nearest-neighbor Gaussian processes (NNGP; Datta et al. 2016 doi:10.1080/01621459.2015.1044091), intrinsic conditional autoregressive models (ICAR; Besag, York, and Mollie 1991 doi:10.1007/BF00116466), the reparameterized Besag-York-Mollie model (BYM2; Riebler et al. 2016 doi:10.1177/0962280216660421), and stochastic partial differential equation fields (SPDE; Lindgren, Rue, and Lindstrom 2011 doi:10.1111/j.1467-9868.2011.00777.x). Temporal structure covers random walks, autoregressive processes, and Gaussian processes. Inference is tiered by correctness guarantee: exact Hamiltonian Monte Carlo with the No-U-Turn sampler, Laplace and nested Laplace approximations with hyperparameter integration (Rue, Martino, and Chopin 2009 doi:10.1111/j.1467-9868.2008.00700.x), and variational inference. Model-specific packages plug observation likelihoods into the engine through a templated C++ callback interface.
Author(s)
Maintainer: Gilles Colling gilles.colling051@gmail.com (ORCID) [copyright holder]
Authors:
Gilles Colling gilles.colling051@gmail.com (ORCID) [copyright holder]
Other contributors:
Frances Y. Kuo (Sobol direction numbers in src/sobol_direction_numbers.h, BSD-3-clause) [contributor, copyright holder]
Stephen Joe (Sobol direction numbers in src/sobol_direction_numbers.h, BSD-3-clause) [contributor, copyright holder]
See Also
Useful links:
Report bugs at https://github.com/gcol33/tulpa/issues
Reject non-finite fitting inputs.
Description
The design is built with na.action = na.pass, so a missing predictor or
response survives into X / y. tulpa() does not drop incomplete cases, so
an NA/NaN/Inf would propagate into the C++ kernels as a NaN estimate. Fail
loudly with the offending row instead.
Usage
.assert_finite_model_inputs(X, y)
Resolve a supplied fixed-effect prior to its Gaussian (mean, sd) fields
Description
The one place that decides whether a beta_prior argument can be expressed
as a Gaussian prior on the fixed effects at all. .normalize_beta_prior()
(per-coefficient) and .beta_prior_ridge_sd() (scalar ridge) both resolve
through it and differ only in the shape they recycle the fields to, so a
prior one accepts is a prior the other accepts.
Usage
.beta_prior_fields(beta_prior)
Arguments
beta_prior |
A list (or |
Details
A prior with no sd is an input the fitters cannot express: substituting the
default for it would replace the user's modelling statement with a different
one and leave no trace on the posterior.
Value
list(mean, sd), unrecycled.
Resolve a beta_prior = list(mean, sd) to a single ridge SD.
Description
For the fitters whose fixed-effect prior is a mean-zero Gaussian ridge with a
scalar SD (EP, multinomial, ordinal): validates the unified beta_prior
object, enforces a mean of 0, and returns the positive scalar SD. Keeps the
shared prior interface (beta_prior = list(mean, sd)) while rejecting the
options those fitters do not implement (a non-zero mean, a per-coefficient
SD). Validation of the supplied object itself is .beta_prior_fields(),
shared with .normalize_beta_prior().
Usage
.beta_prior_ridge_sd(beta_prior, default_sd = .tulpa_prior_sd("ridge"))
Arguments
beta_prior |
|
default_sd |
SD used when |
Assemble NUTS initial positions and an inverse-mass diagonal
Description
Assemble NUTS initial positions and an inverse-mass diagonal
Usage
.build_warm_start(
fit,
layout,
re_terms,
n_chains,
sigma_re = NULL,
jitter = 1,
metric = FALSE
)
Arguments
fit |
Source fit ( |
layout |
The target sampler layout, from |
re_terms |
Random-effect terms of the model being sampled. |
n_chains |
Number of chains to initialise. |
sigma_re |
Random-effect SDs, when the source fit conditioned on them. |
jitter |
Scale of the between-chain dispersion, as a multiple of each
parameter's warm-start SD. |
metric |
Hand the assembled inverse-mass diagonal to the kernel as well
as the position. |
Value
list(init = <n_chains x D matrix>, inv_metric_diag = <D vector or NULL>).
Resolve a family spelling to its canonical registry name.
Description
A no-op for a canonical name, for a <family>_<link> code, and for anything
unrecognized, which reaches .family_or_stop() and errors there against the
canonical list.
Usage
.canonical_family(family)
Arguments
family |
Family identifier as supplied by the caller. |
Proper-CAR precision Q = tau * (D - rho W) at fixed (tau, rho).
Description
D is the diagonal degree matrix (neighbour counts) and W the adjacency, so
Q has the same nonzero structure as the ICAR precision with rho scaling the
off-diagonals. Matches tulpa::add_car_proper_prior / the nested CAR_proper
precision builder.
Usage
.car_proper_precision_Q(spatial, tau, rho)
Families whose dispersion can be estimated
Description
Families whose dispersion can be estimated
Usage
.dispersion_families()
Dispersion derivatives for a family, or NULL
Description
Dispersion derivatives for a family, or NULL
Usage
.family_dphi(family)
Arguments
family |
Family name, as registered in |
Value
A list with dloglik, dscore and dweight, or NULL when the
family has no free dispersion or none has been derived. NULL is a refusal
to estimate, never a signal to fall back to a fixed value silently.
Second-order dispersion derivatives for a family, or NULL
Description
Second-order dispersion derivatives for a family, or NULL
Usage
.family_dphi2(family)
Arguments
family |
Family name, as registered in |
Value
A list with dloglik2, dscore2, dweight2 and dweight_deta, or
NULL when the family's phi Hessian has not been derived. NULL is a
refusal, not a fixed-value fallback: it hands the phi Hessian to the
differencing stencil rather than reporting an inexact closed form.
Whether a family's phi Hessian additionally needs d(W_obs)/dphi
Description
TRUE whenever the Newton working weight is not the observed curvature, which
is exactly when the border's Hinv and the mode motion's Hinv_mode are
different matrices. For a family whose weight is also free of eta the two
inverses multiply a zero channel and the answer would come out the same
either way, but the entry is required regardless rather than resting on that
second coincidence.
Usage
.family_dphi2_needs_obs(family)
Observed curvature (-d^2 log-lik / d eta^2 at the realized y), elementwise.
Description
Falls back to the y-free weight for families whose curvature carries no
y – those where the response enters the log-likelihood linearly in eta
(Poisson, binomial, and the zero-truncated Poisson), for which the observed
and expected curvature coincide identically rather than only in expectation.
Usage
.family_obs_weight(eta, y, family, n_trials = NULL, phi = 1, phi2 = NULL)
Look up a family's operation set, with a clear error for unknown families.
Description
Look up a family's operation set, with a clear error for unknown families.
Usage
.family_ops(family)
Build a length-n_obs -> length-n_units indicator design matrix Z.
Description
The latent field lives on units – unique coordinates for an NNGP field,
adjacency units for an areal one – and each observation belongs to exactly
one, so Z[i, j] = 1 iff observation i sits on unit j.
Usage
.field_design_Z(obs_to_unit, n_units, n_obs)
Arguments
obs_to_unit |
Per-observation unit index, or empty for one observation per unit. |
Details
The map is passed in rather than read off the spec, because the two specs
that reach here name it differently: a GP spec carries obs_to_loc, an areal
spec carries spatial_idx. Reading one name served only one caller; for the
other the field was absent, the map silently became seq_len(n_obs), and the
column index ran past n_units as soon as a location carried more than one
observation.
Attach the standard tulpa_fit contract to a fitter's result.
Description
The dispatch layer and the fitters that return generic-accessor-facing
objects route their return value through this helper (some special-purpose
fitters – the logpost samplers, tulpa_ep, the categorical drivers – still
stamp their class by hand), so a directly-called fitter and a
tulpa()-dispatched one yield the same
enriched object: the tulpa_fit class (so the generic S3 methods –
coef / summary / vcov / confint / tidy / glance / ranef –
dispatch), the fixed-effect layout (n_fixed / fixed_names / param_names,
which the .fit_fixed_table summary path reads), and an explicit
posterior-provenance tag (draws_kind) the chain-vs-iid diagnostic gate
(.tulpa_draws_kind()) reads to decide whether Rhat/ESS apply. Each field is
filled only when the fitter did not already set it, so a fitter that knows
better wins and the helper is idempotent under tulpa_dispatch().
Usage
.finalize_fit(
fit,
backend = NULL,
draws_kind = NULL,
n_fixed = NULL,
fixed_names = NULL,
param_names = NULL,
extra_class = NULL
)
Arguments
fit |
The fitter result (a list); returned unchanged if not a list. |
backend |
Backend key (sets |
draws_kind |
Explicit |
n_fixed, fixed_names, param_names |
Fixed-effect layout, each filled only when the fitter left it unset. |
extra_class |
Subclass(es) to prepend before |
Details
draws_kind precedence is: a value already on the fit, then the explicit
draws_kind argument, then the registry emits property for backend.
Backends that are not registry keys (the tgmrf_* fitters) must pass
draws_kind explicitly, since their emits cannot be looked up.
Value
The enriched fit, classed c(extra_class, ..., "tulpa_fit").
Connected components of an adjacency graph, as a list of node-index vectors.
Description
Iterative depth-first search, so it carries no recursion-depth risk on a
large map. Returns what graph_partition (inst/include/tulpa/graph_components.h)
returns – the actual component MEMBERSHIP, not just a count – so a genuine
disconnected map (a mainland plus islands) pins each component's constant
over that component's real nodes rather than an equal-size contiguous split.
Takes the dense adjacency rather than a sparse one because symmetric sparse
storage (dsCMatrix) keeps a single triangle, which would put an edge's two
endpoints in different components.
Usage
.graph_components(adjacency)
Number of connected components of an adjacency graph.
Description
The count of .graph_components, single-sourcing the DFS.
Usage
.graph_n_components(adjacency)
Intrinsic ICAR field precision, augmented to full rank.
Description
L = D - W (degree minus adjacency) plus the sum-to-zero augmentation
sum_c 1_c 1_c' / J_c that identifies each component's constant null
direction (inst/include/tulpa/sum_to_zero.h). The components are the actual
connected components of the graph (.graph_components), each pinned over its
own node set of size J_c – matching for_each_icar_component
(src/icar_kernel.h), so an unequal / non-contiguous disconnected map
reconstructs the same precision the kernel penalizes with.
Usage
.icar_precision_Q(spatial)
Details
The conditional Laplace kernel passes tau_spatial = 1 on both the ICAR/CAR
path (R/fit_laplace.R) and the BYM2 structured block (which carries sigma
and rho in its d_fac instead), and the field enters the linear predictor
with d_fac = 1, so this is exactly the field block of that fit's joint
Hessian. test-marginal-se-areal.R pins it against the kernel's own
log_prior_icar.
Marginal H_beta for a BYM2 field. The latent is the two-block
[phi (structured), theta (unstructured)] convolution; each enters the
linear predictor through the field indicator scaled by its d_fac
(sigma * sqrt(rho) * scale_factor for phi, sigma * sqrt(1 - rho) for
theta), with phi carrying the augmented ICAR precision .icar_precision_Q
and theta ~ N(0, I). The conditional
Laplace kernel hardcodes sigma = 1, rho = 0.5.
Description
Marginal H_beta for a BYM2 field. The latent is the two-block
[phi (structured), theta (unstructured)] convolution; each enters the
linear predictor through the field indicator scaled by its d_fac
(sigma * sqrt(rho) * scale_factor for phi, sigma * sqrt(1 - rho) for
theta), with phi carrying the augmented ICAR precision .icar_precision_Q
and theta ~ N(0, I). The conditional
Laplace kernel hardcodes sigma = 1, rho = 0.5.
Usage
.marginal_H_beta_bym2(
mode,
X,
spatial,
family,
phi,
n_trials,
weights = NULL,
offset = NULL,
sigma_spatial = 1,
rho = 0.5,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1
)
Negate an additive term (numeric literal -> its negative; anything else ->
a unary-minus call), for flattening binary - in a bar LHS.
Description
Negate an additive term (numeric literal -> its negative; anything else ->
a unary-minus call), for flattening binary - in a bar LHS.
Usage
.negate_term(t)
Build NNGP precision Lambda = (I - A)' D^-1 (I - A) at given hyperparameters.
Description
Mirrors the algorithm in src/gpu_nngp_laplace.h::batch_nngp_scatter plus
apply_nngp_full_prior_dense – for each Vecchia row i (in NNGP order):
resolve conditioning set N(i) via
nn_idx/nn_orderform the n_nb x n_nb cov matrix C and the n_nb cov vector c
solve C alpha = c
v_i = sigma^2 - c' alpha
beta_i is 1 at i and -alpha_k at each neighbor -> Lambda += beta_i beta_i' / v_i
Usage
.nngp_precision_Q(spatial, sigma2_gp, phi_gp)
Details
Identifiers follow the C++ side: nn_idx[i, k] is a 1-based NNGP-order
index, nn_order[j] maps NNGP-order j (1-based) to obs idx (1-based).
Normalize an optional fixed-effect Gaussian prior
Description
Validates beta_prior and recycles scalar mean / sd to length p.
Returns NULL (use the built-in weak prior) or list(mean, sd) with both
vectors of length p. Shared by tulpa_laplace() and the EM driver so the
validation rules live in one place.
Usage
.normalize_beta_prior(beta_prior, p)
Arguments
beta_prior |
|
p |
Number of fixed effects ( |
Normalize a zero-inflation prior to a scalar SD
Description
The compiled zero-inflated kernels carry one mean-zero Gaussian prior over
the whole beta_zi block (ModelData::zi_prior_sd on the sampler paths,
the appended BetaPrior tail on the Laplace path), so the prior is a single
SD rather than the per-coefficient list(mean, sd) that beta_prior takes.
The list form is kept so the two priors read alike at the front door and so a
per-coefficient or non-zero-mean ZI prior can be added without a signature
change.
Usage
.normalize_zi_prior(zi_prior)
Arguments
zi_prior |
|
Value
A scalar prior SD; the engine default when zi_prior is NULL.
Interpret an RE term's covariance specification
Description
Maps one re_list element to the two representations the Laplace path needs:
pack, the value the C++ kernel consumes (a length-n_coefs marginal-SD
vector for a diagonal / uncorrelated term, or a packed lower-triangular
Cholesky of length n_coefs (n_coefs + 1) / 2 for a correlated one), and
Q, the n_coefs x n_coefs RE precision Sigma^{-1} used to build the
marginal fixed-effect SE. A correlated term is signalled by r$L (a lower-
triangular Cholesky factor, Sigma = L L') or r$cov (the covariance
matrix); when present these take precedence over r$sigma.
Usage
.re_cov_spec(r)
Arguments
r |
One element of a |
Value
list(pack, Q, diagonal).
n_obs x n_re_groups indicator design for an iid RE block.
Description
re_idx is the 1-based per-observation group index. Observations whose
group falls outside 1:n_re_groups contribute no RE column, matching the
g >= 0 && g < n_re_groups guard in the C++ kernels.
Usage
.re_design(re_idx, n_re_groups, n_obs)
Resolve a warm_start request into sampler init / inverse-mass vectors
Description
Accepts the front door's warm_start: "eb" or "laplace" to run that fit
first, or an already-fitted object from either. Returns NULL when no warm
start was asked for, so the caller passes nothing and the kernel keeps its
own defaults.
Usage
.resolve_warm_start(warm_start, args, re_terms, sigma_re, beta_prior, n_chains)
Arguments
warm_start |
|
args |
The assembled sampler arguments (the same values the kernel will receive), used both to run the source fit and to probe the target layout. |
re_terms |
Random-effect terms in |
sigma_re |
Random-effect SDs to condition on, for the Laplace source. |
beta_prior |
Optional fixed-effect prior, threaded into the source fit. |
n_chains |
Number of chains the sampler will run. |
Schur complement of the latent block out of the joint Hessian.
Description
Given the fixed-effect design X, the combined latent design D (the iid
RE indicator block stacked with the spatial-field design), the latent prior
precision Q_latent, and the GLM weights W, returns the marginal fixed-
effect precision H_beta = X'WX - (X'WD) (D'WD + Q_latent)^{-1} (X'WD)'.
Shared by the SPDE and NNGP marginal-SE paths.
Usage
.schur_H_beta(X, D, Q_latent, W)
Redirect a backend selection: swap the backend and restamp mode / tier / tier name from the registry, recording the reason shown on the fit.
Description
A redirect off an EXPLICIT request (anything but mode = "auto") is an
override, not a resolution: the fit does what the model structure requires
instead of what the caller asked for. That is recorded on the selection –
sel$overridden for callers, and a clause appended to the reason the fit
reports – so an overridden fit is distinguishable from one that was never
asked for a mode at all. Only the FIRST override is recorded: with several
redirects in a chain, the request the user actually made is the one worth
naming, not the intermediate backend a previous redirect chose.
Usage
.sel_redirect(sel, backend, reason, notify = TRUE)
Details
notify says whether tulpa() should additionally WARN. It is for the case
where the requested backend could have fitted the model and the redirect takes
a capability away – a smoother sending an eb / agq request to the nested
kernels loses the random-effect SD those two would have estimated. It is off
where the requested mode is not expressible for the structure at all and the
redirect is the documented resolution rather than a loss: a random slope has no
scalar sigma_re for mode = "laplace" to condition on, and an SPDE field
redirected from nested_laplace to spde is the same mode and tier reaching
its own integrator. Those are recorded, not warned about, so a documented route
does not warn on every fit.
Assemble the fractional rSPDE precision and obs map at a given (range, sigma)
Description
Shared by every fractional-nu fit path (Laplace single-point, nested,
NUTS, marginal SEs). Maps (range, sigma) to the SPDE hyperparameters
kappa = sqrt(8 nu) / range, builds the latent precision Q = Pl' C^{-1} Pl
and field shift Pr (field u = Pr x) from the validated R oracle
.spde_rational_assemble(), then normalizes the field to marginal variance
sigma^2. The rational construction is correct in spectral shape but its
overall scale carries kappa-dependent constants (the l_max normalization,
the per-factor conditioning rescalings); without the variance normalization
the implied field variance is not sigma^2 and varies with the range, which
biases the nested (range, sigma) integration. The normalization rescales
Pr (hence A_eff = A Pr) by sigma / sqrt(mean marginal variance); Q is
untouched, so logdet_Q remains the correct prior normalizer for the
variance-normalized model.
Usage
.spde_assemble_at(spatial, range, sigma, order = 2L)
Arguments
spatial |
A validated |
range, sigma |
Spatial range and marginal SD. |
order |
Rational approximation order. Default 2 (the rSPDE convention), which keeps the rational precision's condition number tractable. |
Value
A list with Q, Pr, A_eff, Pl (all CSC, sized to the non-orphan
submesh), keep (1-based indices of the retained mesh nodes), n_mesh_full
(the full mesh size), kappa, var_scale, l_max, and logdet_Q.
Build the sparse FEM stiffness / projector from a spatial_spde spec
Description
Prefers the stored Matrix objects (spatial$G, spatial$A); falls back to
rebuilding them from the pre-extracted CSC slots so a spec carrying only the
slots still assembles.
Usage
.spde_fem_matrices(spatial)
Fractional rSPDE single-point Laplace fit at a fixed (range, sigma)
Description
The fractional counterpart of the integer cpp_laplace_fit_spde branch in
laplace_spde_at(). Assembles (Q, A_eff) via .spde_assemble_at() and runs
the precomputed C++ solve, whose latent is the auxiliary weights x; the
returned mode mesh block is mapped back to the field u = Pr x so every
downstream consumer reads field-space mesh effects exactly as on the integer
path. The log-marginal is the well-conditioned B / determinant-lemma marginal
(cpp_spde_fractional_logmarginal()) for the RE-free case (comparable across
the (range, sigma) grid); a fit with an iid RE block keeps the precomputed
precision-space marginal.
Usage
.spde_laplace_fractional_at(
y,
n_trials,
X,
spatial,
family,
phi,
range,
sigma,
re_idx,
n_re_groups,
sigma_re,
max_iter,
tol,
n_threads,
offset,
weights = NULL,
order = 2L
)
Mean marginal variance of the rSPDE field u = Pr x, x ~ N(0, Q^-1)
Description
Estimates mean_i [Pr Q^{-1} Pr']_ii = tr(Pr Q^{-1} Pr') / n by Hutchinson
probing: for z ~ N(0, I), a = Pr' z, Q v = a, then E[a' v] = tr(...).
The solve is against Q through the SAME sparse Cholesky the precomputed C++
fit uses, so the normalization is consistent with the fit even when the wide
rational spectrum makes Q ill-conditioned: a shared solver makes the implied
field covariance identical between the normalization and the likelihood, which
is what the nested (range, sigma) integration needs (an inconsistent solver
breaks the cross-grid marginal). The probe matrix is fixed across calls for a
deterministic, grid-smooth normalization.
Usage
.spde_mean_marginal_var(Q, Pr, C0, n_probe = .SPDE_VARNORM_NPROBE)
Numerically stable Laplace log-marginal for a fractional rSPDE at (range, sigma)
Description
Delegates the well-conditioned B / matrix-determinant-lemma marginal to C++
(cpp_spde_fractional_logmarginal()). The precision-space 0.5(log|Q| - log|H|)
is corrupted in a range-dependent way by the rational precision's wide spectrum
(cond(Q) ~ 1e13+), so the marginal is formed through the obs-space
B = (A_eff Pl^{-1}) C (A_eff Pl^{-1})' + X X'/tau_beta, built through the
operator factor Pl (cond = sqrt cond(Q)), never an explicit Q inverse.
Gaussian is the exact conjugate marginal; non-gaussian uses the det-lemma at the
precomputed Laplace mode. phi is the Gaussian residual SD (variance phi^2),
consistent with the integer path and the engine family convention.
Usage
.spde_nested_logmarginal_at(
spatial,
range,
sigma,
y,
X,
family,
phi,
n_trials,
order,
max_iter,
tol,
n_threads,
offset,
tau_beta = 1e-04
)
Value
A list with log_marginal, n_iter, converged, and the assembly.
Is an SPDE smoothness nu fractional (rational path) or integer (exact path)?
Description
Is an SPDE smoothness nu fractional (rational path) or integer (exact path)?
Usage
.spde_nu_is_fractional(nu)
Arguments
nu |
Matern smoothness; |
Value
TRUE when alpha is non-integer (the rational SPDE path).
Backend names belonging to a tier (derived from the registry).
Description
Backend names belonging to a tier (derived from the registry).
Usage
.tier_backends(tier_key)
Reject families the compiled kernels do not implement.
Description
Reject families the compiled kernels do not implement.
Usage
.validate_family_compiled(family)
Validate the response y for a count family.
Description
Errors when family is a count family and y carries negative or
non-integer values, which the integer-casting kernels would silently floor
into a biased likelihood. A no-op for continuous families.
Usage
.validate_family_counts(family, y, zi = FALSE)
Arguments
zi |
Whether a zero-inflation component is being fitted alongside the
family. With a zero-truncated family this is the hurdle model, where the
zeros belong to the zero component rather than to the truncated count
density, so the |
Validate the dispersion parameter phi for a family.
Description
Errors when family carries a dispersion / precision parameter and phi is
not a positive finite scalar. A no-op for families without dispersion
(binomial, poisson) and for unknown / model-package families. Shared by the
tulpa() front door and tulpa_laplace() so the rule lives in one place
.
Usage
.validate_family_phi(family, phi)
Validate the response against a family's support.
Description
The one support rule set, shared by the tulpa() front door and by the
engine fitters (tulpa_laplace(), tulpa_laplace_beta(),
tulpa_nuts_beta()). Holding it in a single function is what keeps a
boundary response rejected with the same message wherever it enters: a
per-fitter copy is only as good as the set of fitters that carry it, and
tulpa() reaches beta-with-RE through a sampler that carried none.
Usage
.validate_family_support(family, y, n_trials = NULL, zi = FALSE)
Arguments
family |
Family identifier, canonical or aliased. |
y |
Response vector, or |
n_trials |
Binomial denominators, when known. Supplied, the binomial
rule is |
zi |
Whether a zero-inflation component is fitted alongside the family, passed through to the count rule for the hurdle case. |
Details
Counts delegate to .validate_family_counts(); the continuous families and
the binomial denominator add their rules here.
Reject a ZI request against a family with no atom at zero.
Description
Reject a ZI request against a family with no atom at zero.
Usage
.validate_family_zi(family)
Reject a ZI request the compiled kernels cannot fit.
Description
Reject a ZI request the compiled kernels cannot fit.
Usage
.validate_family_zi_compiled(family)
Validate a GLM design bundle (y, X, n_trials) at a fitter's front door.
Description
Shared by the flagship drivers (tulpa_nested_laplace(), tulpa_gibbs(), the
re_cov fitters) so nrow(X) == length(y) and length(n_trials) == length(y) are enforced in one place – otherwise a mismatched n_trials
(as.integer(NULL) -> integer(0)) reaches the C++ kernel silently.
Usage
.validate_glm_design(y, X, n_trials, where)
Arguments
y, X |
Response vector and design matrix. |
n_trials |
Binomial denominators, or |
where |
Caller name for the error message. |
Value
List list(N, n_trials) with n_trials coerced to a length-N
integer vector.
Validate a 0-based/1-based random-effect index against its group count.
Description
re_idx addresses one grouping factor: 0 marks "no random effect" and
1..n_re_groups a group. An out-of-range id would index out of bounds in the
C++ kernel, so bound it in R.
Usage
.validate_re_idx(re_idx, n_re_groups, N, where)
Arguments
re_idx |
Per-observation group index (length |
n_re_groups |
Number of groups. |
N |
Expected length. |
where |
Caller name for the error message. |
Value
re_idx coerced to an integer vector.
Validate the SPDE smoothness parameter
Description
nu must be a single finite positive number. Integer nu (1, 2, 3, ...)
gives an exact FEM construction; fractional nu uses the rational SPDE
approximation (see rational_spde_coefficients()).
Usage
.validate_spde_nu(nu)
Arguments
nu |
Candidate smoothness parameter. |
Value
Invisibly TRUE; raises an error on an invalid nu.
Split a Laplace / EB mode vector into its semantic blocks
Description
Returns the count-side fixed effects, the zero-inflation coefficients, and the per-term random-effect deviations, so the caller places each into the sampler's corresponding block rather than relying on the two flat vectors agreeing end to end (they do not, under zero inflation).
Usage
.warm_start_blocks(fit, re_terms)
Arguments
fit |
A fitted |
re_terms |
The random-effect terms of the model being sampled, in
|
Per-term random-effect SDs from a warm-start source fit
Description
An EB fit estimated them, so they are read off map. A Laplace fit
conditioned on values the caller supplied, so those are passed in.
Usage
.warm_start_sigmas(fit, sigma_re, n_terms)
Arguments
fit |
A fitted |
sigma_re |
Fallback SDs, used when the fit did not estimate them. |
n_terms |
Number of random-effect terms. |
Build the zero-inflation design matrix from ziformula.
Description
A one-sided formula giving the fixed effects of the structural-zero logit.
~ 1 is the constant-probability model. Random effects in the ZI predictor
are not supported: the compiled kernels share the random-effect block into
the count process only, so a bar here would be silently dropped.
Usage
.zi_design(ziformula, data, n_obs)
All registered backends across every tier (derived from the registry).
Description
All registered backends across every tier (derived from the registry).
Usage
ALL_BACKENDS
Backend -> supported families, derived from the registry.
Description
Backend -> supported families, derived from the registry.
Usage
BACKEND_FAMILY_SUPPORT
Backend registry – single source of truth for the inference backends.
Description
One entry per backend. Adding a backend is a single entry here; tier
membership, family support, the input contract, and R-level reachability
all derive from this list – the single source of truth for per-tier
backends and family support.
Fields:
-
emits– the kind of posterior representation the backend's draws hold, which fixes whether MCMC chain diagnostics apply (independent oftier: an exact SMC sampler emits"iid"particles, a Tier-3 VI fit also emits"iid", while a nested-Laplace Tier-2 fit emits"iid"too):-
"chain": autocorrelated MCMC output – Rhat and autocorrelation-ESS are meaningful (diagnostics()computes them). -
"iid": exchangeable draws from a deterministic approximation, or resampled particles – split-Rhat is vacuous and ESS = n by construction, so they say nothing about approximation bias. -
"point": no posterior sample (mode + covariance only).
-
-
tier– tier key ("exact","structured","optimized"). -
input– the input contract the backend's fitter consumes:-
"design": design-matrix bundle (y,n_trials,X, RE structure). -
"logpost": alog_posterior(theta)closure plus dimension/init. -
"modeldata": a tulpaModelData/LikelihoodSpec(C-ABI NUTS path). -
"nested": a design-matrix bundle plus one or more latent prior blocks (latent(tgmrf(...))), integrated over the block hyperparameters by the nested-Laplace driver. -
"spde": a design bundle plus a self-contained SPDE spec (mesh + FEM matrices); fit_spde()'s own CCD / grid hyperparameter engine.
-
-
fitter– name of the R function implementing the backend, orNULLwhen only a C++ kernel exists with no R entry point yet. Stored as a string (not the function object) so the registry is independent of source-load order; resolved lazily viaresolve_backend_fitter().NULL=> not selectable from R; dispatch fails loudly. -
families– character vector of supported family identifiers, orNULLfor an unrestricted backend. -
cabi– the registered C-ABI callable backing the backend (the symbol a model package reaches viaLinkingTo: tulpa, and the one an R wrapper would call), orNULL. -
note– optional human-readable note.
Family identity (for families) is checked against family$name,
family$distribution, and family$numerator$distribution (tulpaRatio-style
ratio families nest a per-process distribution).
Usage
BACKEND_REGISTRY
Inference tiers with their properties (derived from TIER_META +
BACKEND_REGISTRY). Preserves the INFERENCE_TIERS$<tier>$backends
interface relied on by downstream code and tests.
Description
Inference tiers with their properties (derived from TIER_META +
BACKEND_REGISTRY). Preserves the INFERENCE_TIERS$<tier>$backends
interface relied on by downstream code and tests.
Usage
INFERENCE_TIERS
Tier metadata: the epistemic guarantee attached to each tier.
Description
Tier membership (which backends live in a tier) is not stored here – it
is derived from BACKEND_REGISTRY. This table holds only the per-tier
description/guarantee so the two never drift.
Usage
TIER_META
Random-effect variances and correlations
Description
Summarize the random-effect covariance of a fit: the standard deviation of each random-effect coefficient, the correlations between them within a term, and the covariance matrix itself.
Whether the covariance was estimated, sampled, or merely conditioned on is
reported alongside it, because the three are different claims. A fit from
mode = "laplace" conditions on sigma_re, so its values are the ones
supplied; mode = "eb" estimates them; a sampler tier integrates them.
Usage
VarCorr(x, sigma = 1, ...)
## S3 method for class 'tulpa_fit'
VarCorr(x, sigma = 1, ...)
Arguments
x |
A |
sigma |
Ignored, for compatibility with the |
... |
Ignored. |
Value
A data frame with one row per random-effect coefficient: term,
coef, sd, and source (one of "estimated", "sampled",
"conditioned"). Correlated terms additionally carry the covariance
matrices in the "cov" attribute, one per term, each with a "correlation"
attribute. Returns an empty data frame when the fit has no random effects.
See Also
ranef() for the per-level deviations, tulpa_eb() to estimate the
covariance rather than condition on it.
Examples
set.seed(1)
G <- 30L; per <- 10L; n <- G * per
grp <- rep(seq_len(G), each = per); x <- rnorm(n)
b <- rnorm(G, 0, 0.8)
d <- data.frame(y = rpois(n, exp(0.3 + 0.5 * x + b[grp])), x = x,
g = factor(grp))
fit <- tulpa(y ~ x + (1 | g), data = d, family = "poisson", mode = "eb")
VarCorr(fit)
Construct a spatial adjacency graph for areal models
Description
Build the symmetric adjacency matrix that spatial() and spatial_car()
consume, from a common spatial layout, instead of hand-coding it. The model
still receives an explicit graph (spatial(graph = g$adjacency, ...)), so the
graph stays inspectable before fitting – the engine never guesses
connectivity from coordinates silently.
adjacency() is a single front-door verb that dispatches on the kind of
input:
- a
data.frame cell centroids on a regular grid – queen (edge or corner) or rook (edge only) contiguity over the lattice. This covers both plain coordinate grids and rasterised cells passed as a table of centres.
- an
sfobject polygon contiguity from shared boundaries (queen = share a point or edge; rook = share an edge). Requires sf.
- a
SpatRaster(terra) orstarsobject raster cells – the lattice of (non-
NA) cell centres, queen or rook. Requires terra or stars respectively.
The result is a tulpa_adjacency object holding the matrix plus the cell
identifier for each node, so a model can pass g$adjacency while the
observation data is remapped to 1-based node indices with node_index().
Usage
adjacency(x, ...)
## Default S3 method:
adjacency(x, ...)
## S3 method for class 'data.frame'
adjacency(
x,
type = c("queen", "rook"),
x_coord = "x",
y_coord = "y",
id = NULL,
order = 1L,
tolerance = 1.5,
offsets = NULL,
...
)
## S3 method for class 'sf'
adjacency(x, type = c("queen", "rook", "touches"), id = NULL, ...)
## S3 method for class 'SpatRaster'
adjacency(
x,
type = c("queen", "rook"),
order = 1L,
tolerance = 1.5,
offsets = NULL,
na_rm = TRUE,
...
)
## S3 method for class 'stars'
adjacency(
x,
type = c("queen", "rook"),
order = 1L,
tolerance = 1.5,
offsets = NULL,
na_rm = TRUE,
...
)
Arguments
x |
The spatial layout: a |
... |
Passed to methods. |
type |
Contiguity rule: |
x_coord, y_coord |
For the |
id |
For the |
order |
For grid / raster layouts, the neighbourhood order (ring count):
|
tolerance |
For grid / raster layouts, the per-offset neighbour distance
cut-off as a multiple of the inferred cell size: a candidate at a lattice
offset is kept when its true centre distance is at most |
offsets |
Advanced, grid / raster layouts only: a custom neighbour
stencil as a two-column integer matrix or a list of length-2 |
na_rm |
For raster layouts, drop cells whose value is |
Value
A tulpa_adjacency object: a list with
adjacencythe
[n x n]symmetric sparse adjacency matrix (dgCMatrix, 0/1, zero diagonal) to pass asgraph/adjacency.idsthe cell identifier for each node, in node order (length
n).nthe number of nodes.
cellsizethe inferred cell size
c(x, y)for grid / raster layouts, orNAfor polygons.typethe contiguity rule used.
See Also
node_index() to map cell identifiers to node indices,
check_adjacency() to validate a hand-built matrix, spatial() and
spatial_car() which consume the graph.
Examples
# A 3 x 3 regular grid of cell centres
grid <- expand.grid(x = 1:3, y = 1:3)
grid$cell <- paste0("c", seq_len(nrow(grid)))
g <- adjacency(grid, x_coord = "x", y_coord = "y", id = "cell",
type = "queen")
g
g$adjacency
# Second-order (24-neighbour) queen contiguity: any order is settable
g2 <- adjacency(grid, id = "cell", order = 2)
# Advanced: a custom stencil (symmetrized for the undirected field)
g3 <- adjacency(grid, id = "cell", offsets = list(c(1, 0), c(0, 1)))
# Use it in a model: graph stays explicit and inspectable
spatial(graph = g$adjacency, formula = ~ 1 || cell_idx)
# Remap observation data (original cell ids -> 1:n node indices) by key
obs <- data.frame(cell = c("c5", "c1", "c5", "c9"))
obs$cell_idx <- node_index(g, obs$cell)
obs
Convert adjacency matrix to CSR for tulpa C++
Description
Convert adjacency matrix to CSR for tulpa C++
Usage
adjacency_to_csr_tulpa(adj)
Adaptive Gauss-Hermite quadrature for one-RE GLMMs
Description
Marginal-likelihood approximation for a generalised linear mixed
model with one cluster-level intercept random effect. Adaptive
Gauss-Hermite quadrature (AGQ) generalises Laplace by replacing the
single-point Gaussian integral around the cluster's posterior mode
with n_quad-point quadrature. AGQ at n_quad = 1 recovers the
Laplace approximation; higher n_quad reduces approximation error,
especially for clusters with few observations or non-Gaussian
likelihoods.
This is an engine block, not a front door: its tuning knobs
(max_iter, tol, n_quad) sit in the signature rather than in a
control list.
Scoped to the lme4::glmer(..., nAGQ = N) use case: one intercept-
only RE term, families binomial, poisson, or gaussian. For
multi-RE or random-slope models, use tulpa_laplace() (Laplace at
the joint mode) or HMC.
Usage
agq_fit(
y,
X,
group,
n_groups = max(group),
family = c("binomial", "poisson", "gaussian"),
n_trials = NULL,
sigma_eps = 1,
n_quad = 7L,
beta_init = NULL,
sigma_init = 1,
max_iter = 200L,
tol = 1e-06,
verbose = FALSE
)
Arguments
y |
Response vector. |
X |
Fixed-effects design matrix ( |
group |
Integer cluster labels (1-based, length |
n_groups |
Number of clusters (default |
family |
One of |
n_trials |
Trial sizes (binomial only; default |
sigma_eps |
Residual SD (gaussian only; default |
n_quad |
Number of Gauss-Hermite quadrature nodes per cluster.
|
beta_init |
Initial fixed-effects (default zeros). |
sigma_init |
Initial RE SD (default |
max_iter |
Optimiser iteration cap (default |
tol |
Convergence tolerance (default |
verbose |
Print optimiser summary (default |
Value
A list with class tulpa_fit carrying:
-
means: named vectorc(beta, log_sigma_re). -
mode: same asmeans(Gaussian fit at the optimum). -
cov: covariance matrix of(beta, log_sigma_re)from inverse observed information. -
sigma_re: estimated RE SD. -
log_marginal: AGQ-approximated log marginal likelihood at the fitted values. -
n_quad: quadrature order used. -
n_iter,converged: optimiser diagnostics. -
inference_mode:"structured". -
inference_tier:2L. -
backend:"agq".
An optimum at which the AGHQ objective is undefined (some group's solve fails there, so its value is the failure sentinel rather than a marginal likelihood) is an error naming the groups, not a returned fit.
Tier
Tier 2 (Structured). AGQ is exact in the limit n_quad -> infinity
but at finite n_quad it is a controlled approximation – same
epistemic class as Laplace.
References
Pinheiro, J. C., & Bates, D. M. (1995). Approximations to the log-likelihood function in the nonlinear mixed-effects model. Journal of Computational and Graphical Statistics, 4(1), 12-35.
See Also
tulpa_laplace() for the joint-mode Laplace path
(handles multi-RE and spatial structure).
Examples
set.seed(1)
n_g <- 20; n_per <- 5; n <- n_g * n_per
group <- rep(seq_len(n_g), each = n_per)
x <- rnorm(n)
X <- cbind(1, x)
u <- rnorm(n_g, 0, 0.8)
eta <- 0.3 + 0.7 * x + u[group]
y <- rbinom(n, 1, plogis(eta))
# Compare Laplace (n_quad = 1) and AGQ-7.
fit_lap <- agq_fit(y, X, group, family = "binomial", n_quad = 1)
fit_agq <- agq_fit(y, X, group, family = "binomial", n_quad = 7)
c(fit_lap$log_marginal, fit_agq$log_marginal)
Apply RSR projection to spatial effect
Description
Project spatial effect into the space orthogonal to covariates. Called during posterior computation.
Usage
apply_rsr_projection(w, P_perp)
Arguments
w |
Spatial effect vector (length n) |
P_perp |
Projection matrix from compute_rsr_projection |
Value
Projected spatial effect (length n)
Posterior draws in the posterior package's format
Description
Convert a fit's posterior draws to a posterior draws object. as_draws()
returns the draws_array shape; as_draws_array(), as_draws_matrix(),
as_draws_df() and as_draws_rvars() return theirs. When the posterior
package is installed these are also registered against its generics, so
posterior::as_draws(fit) works.
Usage
as_draws(x, ...)
## S3 method for class 'tulpa_fit'
as_draws(x, n_draws = NULL, seed = NULL, ...)
as_draws_array(x, ...)
## S3 method for class 'tulpa_fit'
as_draws_array(x, n_draws = NULL, seed = NULL, ...)
as_draws_matrix(x, ...)
## S3 method for class 'tulpa_fit'
as_draws_matrix(x, n_draws = NULL, seed = NULL, ...)
as_draws_df(x, ...)
## S3 method for class 'tulpa_fit'
as_draws_df(x, n_draws = NULL, seed = NULL, ...)
as_draws_rvars(x, ...)
## S3 method for class 'tulpa_fit'
as_draws_rvars(x, n_draws = NULL, seed = NULL, ...)
Arguments
x |
A |
... |
Passed to the corresponding |
n_draws |
Number of draws to synthesize from the Gaussian approximation
for a fit that carries none. |
seed |
Optional integer seed for the synthesis. The RNG state is restored afterwards. |
Details
Fits differ in whether they carry draws at all. Sampler and nested-Laplace
fits do, and convert directly. A Gaussian-approximation fit (mode = "laplace", mode = "eb") carries a mode and a precision instead, and
converting it means drawing from the approximation – which is a modelling
decision, not a format change, because every downstream posterior summary
would then treat a normal approximation as a posterior sample. So it is
opt-in: pass n_draws to synthesize that many draws from
N(coef(object), vcov(object)), or get an error naming the alternative.
Synthesized draws form a single chain and cover the fixed effects only.
Value
A posterior draws object of the requested shape.
See Also
tulpa_draws_array() for the base R array without the dependency,
posterior_sample() for the raw matrix.
Examples
set.seed(1)
df <- data.frame(x = rnorm(80))
df$y <- rpois(80, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson")
if (requireNamespace("posterior", quietly = TRUE)) {
posterior::summarise_draws(as_draws(fit))
}
Error if a selected backend has no R-level fitter.
Description
Enforces the registry honesty contract: a backend may ship a C++ kernel
reachable from model packages (via LinkingTo: tulpa) yet have no R entry
point. Such a backend must never be silently selectable from R –
selecting it errors with a precise message naming the C-ABI symbol, rather
than pretending to dispatch.
Usage
assert_backend_reachable(backend)
Mark an outer-grid setting as a default rather than a pin
Description
Declares that a setting shaping the outer hyperparameter grid carries a
default the caller computed, not a choice the user made. The auto-recenter
pass (outer_grid_placement) leaves a user-pinned
setting exactly as given, and re-centres (or, for a prior, engages its own
regularizer over) a marked one when the fit rails against its ceiling.
Three kinds of setting take the mark:
a grid axis on a nested-Laplace
priorblock (sigma_grid,tau_grid, ...) – a numeric vector of nodes, or the[n_cells x k]matrix of pre-paired coordinates the families whose axis is a matrix take (mcar/miid'slogchol_grid,tgmrf'stheta_grid_built);a scalar grid-construction knob in
control, for a driver that builds its axes rather than taking them (fit_st_nested()'sn_grid_spatial,tau_upper, ...);a
prior_sigmahyperprior specification – a list, e.g.list("pc.prec", c(U = 3, alpha = 0.01)).
Wrapper packages are the intended caller: one that builds a default of its own – because it derives a second axis from it, hands the same vector to several blocks, or exposes its own argument with a default – would otherwise be indistinguishable from a user who pinned that setting deliberately. Mark it and the rescue stays live. A setting whose value is exactly the engine's own default is recognised without a mark; anything else needs one.
The mark is an attribute, so it is dropped by sort(), [, c() and
as.numeric(): build the value first, mark it last.
Usage
auto_grid(x)
Arguments
x |
Numeric vector or matrix of grid nodes, a numeric scalar knob, or a prior-specification list. |
Value
x carrying the marker attribute. Numeric input is coerced to
double IN PLACE, so everything else it carries – dim() and dimnames()
above all – survives the mark; a list is returned unchanged apart from
the attribute.
See Also
is_auto_grid(), tulpa_nested_laplace_joint(), fit_st_nested()
Examples
prior <- list(type = "icar", sigma_grid = auto_grid(c(0.1, 0.5, 1, 2, 3)))
is_auto_grid(prior$sigma_grid)
is_auto_grid(auto_grid(list("pc.prec", c(U = 3, alpha = 0.01))))
Auto-select mode (Tier 1 or Tier 2 only)
Description
Implements the "auto" mode selection. Chooses the most reliable method that is expected to finish for the given model.
Critical rule: Auto never selects Tier 3 (Optimized).
Usage
auto_select_mode(
family,
n_obs,
has_spatial,
has_temporal,
has_latent,
temporal = NULL,
spatial_type = NULL,
has_re = FALSE
)
Is a backend reachable from R (does it have an R-level fitter)?
Description
Is a backend reachable from R (does it have an R-level fitter)?
Usage
backend_is_reachable(backend)
Test whether a backend supports the given family.
Description
Test whether a backend supports the given family.
Usage
backend_supports_family(backend, family)
Bayesian R-squared
Description
Per posterior draw s, R2_s = Var_i(mu_si) / (Var_i(mu_si) + Var_res_s),
where mu_si are the response-scale fitted means and Var_res_s is the
family's residual variance averaged over observations (Gelman et al. 2019).
The linear predictor is rebuilt per draw exactly as in
posterior_predict() (fixed effects + random effects + offset at the
training data).
Usage
bayes_R2(object, ...)
## S3 method for class 'tulpa_fit'
bayes_R2(
object,
ndraws = NULL,
summary = TRUE,
probs = c(0.025, 0.975),
seed = NULL,
...
)
Arguments
object |
A |
... |
Passed to methods. |
ndraws |
Number of posterior draws to use. Defaults to all stored draws, or 400 on the draw-free Laplace tier. |
summary |
Summarize the per-draw values (default |
probs |
Quantiles reported by the summary (default 2.5% / 97.5%). |
seed |
Optional integer seed (RNG state is restored on exit), used by the Gaussian fixed-effect sampling on draw-free fits. |
Value
With summary = TRUE (default) a one-row data frame with
estimate (posterior median), std.error, and the probs quantiles;
with summary = FALSE the vector of per-draw R^2 values.
References
Gelman, Goodrich, Gabry & Vehtari (2019). R-squared for Bayesian regression models. The American Statistician 73(3):307-309.
Examples
set.seed(1)
d <- data.frame(x = rnorm(200))
d$y <- rnorm(200, 2 * d$x, 1)
fit <- tulpa(y ~ x, data = d, family = "gaussian", mode = "laplace", phi = 1)
bayes_R2(fit)
Bridge sampling for marginal likelihood
Description
Estimate the log marginal likelihood \log Z = \log p(y) from
posterior draws via the Meng-Wong / Gronau bridge-sampling identity.
Useful for Bayes factors, model comparison, and as a sanity check on
Laplace-approximated marginal likelihoods.
The classical iterative scheme (Meng & Wong 1996; Gronau et al. 2017)
is used with a multivariate-normal proposal fit to half of the
posterior draws – the other half is used for the bridge ratio so the
proposal is independent of the samples that score it. All
computations are done in log-space via logsumexp for numerical
stability.
Usage
bridge_sampling(
draws,
log_posterior,
n_proposal = nrow(draws),
max_iter = 1000L,
tol = 1e-10,
split = TRUE,
verbose = FALSE
)
Arguments
draws |
Numeric matrix of posterior draws, one parameter per
column. Typically |
log_posterior |
Function |
n_proposal |
Number of proposal draws. Default |
max_iter |
Maximum bridge iterations (default 1000). |
tol |
Convergence tolerance on |
split |
Logical: split the posterior draws in half so the
proposal is fit on one half and scored on the other (default
|
verbose |
Print iteration history (default |
Value
A list with:
-
log_marginal: the bridge-sampling estimate of\log Z. -
n_iter: bridge iterations to convergence. -
converged: logical. -
re_sd: relative MSE estimate (Fruehwirth-Schnatter 2004); a rough quality check, smaller is better. -
proposal: the fitted proposallist(mean, cov).
Tier
Bridge sampling is a post-hoc marginal-likelihood estimator, not a sampling backend, so it does not appear in the INFERENCE_TIERS registry. It operates on draws from any Tier-1 (Exact) backend.
References
Meng, X.-L., & Wong, W. H. (1996). Simulating ratios of normalizing constants via a simple identity: a theoretical exploration. Statistica Sinica, 6, 831-860.
Gronau, Q. F., Sarafoglou, A., Matzke, D., Ly, A., Boehm, U., Marsman, M., ... & Steingroever, H. (2017). A tutorial on bridge sampling. Journal of Mathematical Psychology, 81, 80-97.
Examples
# Toy: marginal likelihood of N(theta | 0, 1) under Y = theta + eps,
# eps ~ N(0, 1), prior theta ~ N(0, 10). Closed-form available.
y <- 1.5
log_post <- function(theta) {
dnorm(y, theta, 1, log = TRUE) + dnorm(theta, 0, 10, log = TRUE)
}
draws <- matrix(rnorm(2000, mean = y * 100 / 101, sd = sqrt(100 / 101)),
ncol = 1)
bs <- bridge_sampling(draws, log_post)
bs$log_marginal # should match dnorm(y, 0, sqrt(101), log = TRUE)
Build a GLMM log-posterior (and gradient) from a model-data bundle.
Description
Build a GLMM log-posterior (and gradient) from a model-data bundle.
Usage
build_glmm_logpost(
bundle,
family,
sigma_re = NULL,
n_trials = NULL,
phi = 1,
beta_prior = .tulpa_default_beta_prior("glmm_logpost"),
weights = NULL,
phi2 = NULL
)
Arguments
bundle |
Output of |
family |
Character family name (see |
sigma_re |
Numeric vector of random-effect SDs, one per RE term. Length
must equal |
n_trials |
Binomial denominators (or |
phi |
Dispersion/precision passed to the family. |
beta_prior |
|
weights |
Optional per-observation likelihood weights (length |
phi2 |
Optional second dispersion (Student-t degrees of freedom). |
Value
A list with:
-
log_posterior(theta)– scalar log-posterior (up to a constant). -
grad_log_posterior(theta)– gradient vector. -
dim– length oftheta. -
init– a zero starting vector of lengthdim. -
unpack(theta)– list(beta,u= list of n_groups x n_coefs matrices). -
param_names– character labels aligned withtheta.
Check if data is gridded (can use stars)
Description
Check if data is gridded (can use stars)
Usage
can_use_stars(plot_data)
Central Composite Design (CCD) grid for nested-Laplace integration
Description
Produces a structured set of standardised hyperparameter points
z \in \mathbb{R}^k for use as integration nodes in a nested
Laplace approximation when k \ge 3. CCD scales much better than
the full tensor expand.grid() used by the 1D and 2D backends:
a CCD has 1 + 2k + 2^{k - q} points (1 centre, 2k axial,
2^{k - q} factorial), versus m^k for an m-per-axis
tensor product.
Point layout (with centre+axial+factorial scaling f_0):
1 centre point at the origin;
2k axial points at
\pm f_0along each coordinate axis;-
2^{k - q}factorial points at corners of the hypercube, scaled to lie on a sphere of radiusf_0.
For k \le 6 the factorial portion is the full 2^k
design. For k \ge 7 a half-fraction (q = 1) using the
defining word x_1 \cdots x_k keeps the point count reasonable
while preserving Resolution V.
Used by tulpa_nested_laplace() for higher-dimensional hyperparameter
blocks. The standardised z-coordinates are mapped to physical
hyperparameters \theta via ccd_to_theta().
Usage
ccd_grid(k, f_0 = sqrt(k))
Arguments
k |
Number of hyperparameters (length of theta). Must be >= 1. |
f_0 |
Radius of the design sphere (default |
Value
A list with components:
-
z: numeric matrix[n_points x k]of standardised hyperparameter coordinates. -
n_points: integer; total grid size. -
kind: character vector labelling each point as"center","axial", or"factorial". -
f_0: the sphere radius used.
See Also
ccd_to_theta() to map z-coordinates to physical theta.
Map standardised CCD coordinates to physical hyperparameters
Description
Converts standardised CCD z-coordinates (in \mathbb{R}^k) to
physical hyperparameters \theta via the affine map
\theta = \hat\theta + L \cdot z with optional log-scale
transform per component. L is typically a Cholesky factor of the
negative Hessian inverse evaluated at the (working) mode \hat\theta
– i.e. it scales z to one posterior standard deviation per axis.
Usage
ccd_to_theta(z, theta_hat, L, log_scale = FALSE)
Arguments
z |
Matrix |
theta_hat |
Numeric vector of length |
L |
Numeric |
log_scale |
Logical (or logical vector of length k). If |
Value
Numeric matrix [n_points x k] of physical theta-values.
Corrected R-INLA CCD integration weights
Description
Per-point design weights for a ccd_grid() used as nested-Laplace integration
nodes. Implements the corrected R-INLA central-composite-design weights: the
formula in Rue, Martino and Chopin (2009) has a typo, corrected by the R-INLA
team to give a positive central weight. With m hyperparameters, np design
points and standardized scaling f0, every non-centre point gets
w = 1 / ((np - 1)(1 + e^{-m f0^2 / 2}(f0^2 - 1)))
and the centre point gets w_0 = 1 - (np - 1) w. Used as
\Delta_k: the integration weight of node k is
\Delta_k \exp(\log\,\mathrm{marginal}_k), renormalized. On a standardized
(whitened) hyperparameter posterior these reproduce the Gaussian moments
exactly.
Usage
ccd_weights(ccd)
Arguments
ccd |
A |
Value
Numeric vector of length ccd$n_points: the design weight for each
point (the centre weight w0 for the central point, w otherwise).
See Also
Validate a spatial adjacency matrix
Description
Check that a hand-built adjacency matrix is a well-formed spatial graph
before passing it to spatial() / spatial_car(): square, symmetric, zero
on the diagonal, 0/1 valued, and free of isolated nodes. adjacency() runs
the same checks on the graphs it constructs.
Usage
check_adjacency(adjacency, ids = NULL)
Arguments
adjacency |
A matrix (dense or sparse |
ids |
Optional cell identifiers; if supplied, their length must match
|
Value
Invisibly, a tulpa_adjacency_check list with the per-check results
(square, symmetric, zero_diag, binary, isolated-node indices, edge
count) and an overall ok flag. Issues are reported via warning() and
printed; the function does not stop, so every problem surfaces in one pass.
See Also
adjacency() to construct a graph, node_index().
Examples
adj <- matrix(0, 4, 4)
for (i in 1:3) adj[i, i + 1] <- adj[i + 1, i] <- 1
check_adjacency(adj)
Quick convergence check
Description
Runs the core convergence diagnostics on a fit and reports whether Rhat,
bulk-ESS, and divergence thresholds are all met. A terse companion to
diagnostic_summary() for use in scripts and tests.
Usage
check_diagnostics(
fit,
rhat_threshold = 1.01,
ess_threshold = 400,
quiet = FALSE
)
Arguments
fit |
A |
rhat_threshold |
Maximum acceptable Rhat (default 1.01). |
ess_threshold |
Minimum acceptable bulk-ESS (default 400). |
quiet |
Logical; if TRUE, suppress messages (default FALSE). |
Value
Invisibly, TRUE if all checks pass, FALSE if any fail, or NA
for a non-chain (approximation) fit where Rhat/ESS do not apply.
See Also
diagnostic_summary(), diagnostics(), n_divergent()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
check_diagnostics(fit)
Diagnostic panel plot
Description
Produces a 2x2 or 1x3 panel of diagnostic plots:
QQ plot of PIT residuals vs Uniform (with KS p-value)
Residuals vs fitted (with lowess smoother)
Dispersion histogram (observed variance vs simulated)
Spatial correlogram via Moran's I (if coords provided)
Usage
check_model(object, ...)
## Default S3 method:
check_model(object, coords = NULL, nsim = 250L, seed = 123L, ...)
Arguments
object |
A fitted model with |
... |
Passed to methods. |
coords |
Optional N x 2 coordinate matrix for spatial panel |
nsim |
Number of simulations (default 250) |
seed |
Random seed (default 123) |
Value
Invisible list with ks_p, disp_ratio, moran (if spatial)
Fixed-effect coefficients
Description
Fixed-effect coefficients
Usage
## S3 method for class 'tulpa_fit'
coef(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
Named numeric vector of fixed-effect posterior means (the Laplace
mode for the Laplace tier). Random effects come from ranef().
Collect additive terms from a + chain
Description
Recursively flattens a + b + c into list(a, b, c).
Usage
collect_additive_terms(expr)
Arguments
expr |
A language object |
Value
A list of language objects (individual terms)
Compare models by information criteria
Description
Rank fitted models best-first by an information criterion. "waic" and
"loo" use the native pointwise log-likelihood layer (tulpa_criteria() /
tulpa_psis()) – WAIC and PSIS-LOO respectively, computed from each fit's
[n_draws x n_obs] pointwise log-likelihood (fit$draws$log_lik), with no
loo package dependency. "loglik" returns the (integrated) joint
log-likelihood with the parameter count. A fit carrying no pointwise
log-likelihood (a deterministic / point approximation) yields NA criterion
columns rather than an error, so the table always has one row per model.
Usage
compare_models(..., criterion = c("waic", "loo", "loglik"))
Arguments
... |
Named |
criterion |
|
Value
A data frame. For "loglik": model, n_params, logLik. For
"waic" / "loo" (ranked best-first): model, elpd, se_elpd,
p_eff, ic (-2 * elpd), delta (elpd gap to the best model),
se_diff (SE of that pointwise elpd difference), and weight (the
Akaike-style weight on the criterion).
See Also
model_average() for model-averaged predictions, tulpa_criteria()
and tulpa_psis() for the native criteria layer.
Examples
set.seed(1)
df <- data.frame(x = rnorm(120))
df$y <- rpois(120, exp(0.4 + 0.5 * df$x))
f1 <- tulpa(y ~ x, data = df, family = "poisson")
f2 <- tulpa(y ~ 1, data = df, family = "poisson")
compare_models(full = f1, null = f2, criterion = "waic")
Compute BYM2 scaling factor
Description
Compute the scaling factor for BYM2 following Riebler et al. (2016). This makes the spatial fraction parameter interpretable.
Usage
compute_bym2_scale(adjacency)
Arguments
adjacency |
Adjacency matrix |
Value
Scaling factor (scalar)
Compute valid bounds for rho in proper CAR
Description
For proper CAR, rho must be in the range (1/lambda_min, 1/lambda_max) where lambda are the eigenvalues of D^(-1)W. In practice, we typically restrict to (0, 1) for interpretability (positive spatial autocorrelation).
Usage
compute_car_rho_bounds(adjacency)
Arguments
adjacency |
Adjacency matrix |
Value
Named vector with lower and upper bounds for rho
Compute nearest neighbors for NNGP
Description
Compute the k nearest neighbors for each observation using Euclidean distance. Returns in a format suitable for the NNGP likelihood.
Usage
compute_nngp_neighbors(coords, k)
Arguments
coords |
N x d matrix of coordinates |
k |
Number of nearest neighbors |
Value
List with:
-
nn_idx: N x k matrix of neighbor indices (0 for obs with fewer neighbors) -
nn_dist: N x k matrix of distances to neighbors -
nn_order: Ordering of observations for NNGP (by coordinate)
Compute RSR projection matrix
Description
Compute the orthogonal projection matrix P_perp = I - P_X that projects the spatial effect into the space orthogonal to the covariates.
Usage
compute_rsr_projection(X)
Arguments
X |
Design matrix of covariates to orthogonalize against |
Value
Projection matrix (n x n)
Credible intervals for the fixed effects
Description
Credible intervals for the fixed effects
Usage
## S3 method for class 'tulpa_fit'
confint(object, parm = NULL, level = 0.95, ...)
Arguments
object |
A |
parm |
Parameter names or indices (default: all fixed effects). |
level |
Interval level (default 0.95). |
... |
Ignored. |
Value
Matrix with lower and upper columns. A nested-Laplace fit carries
interval_source / interval_declined (which read produced the bounds –
by default the grid's Gaussian-mixture CDF), retained_mass (the share of
the grid weight the bounds are conditional on), and skew_applied, one
logical per reported coefficient saying whether its bounds are the
inner-Laplace skew-corrected quantiles or not. See summary.tulpa_fit().
Create the ggplot2 map
Description
Create the ggplot2 map
Usage
create_map(
plot_data,
obs_coords,
title,
palette,
point_color,
point_size,
na_color,
crs,
legend_title,
...
)
Create point-based map
Description
Create point-based map
Usage
create_point_map(plot_data, palette, na_color, legend_title)
Create raster-style map using stars
Description
Create raster-style map using stars
Usage
create_raster_map(plot_data, crs, palette, na_color, legend_title)
DIC and CPO
Description
Generic front doors onto the two criteria tulpa_criteria() computes that
the loo package owns no generic for. WAIC and PSIS-LOO have theirs
(loo::waic(), loo::loo()), so a model package registers methods on
those rather than on new names that would mask them.
Usage
dic(object, ...)
## Default S3 method:
dic(object, loglik_at_mean = NULL, ...)
cpo(object, ...)
## Default S3 method:
cpo(object, ...)
Arguments
object |
A pointwise log-likelihood matrix (draws x observations), or a fitted model object a method is registered for. |
... |
Passed to |
loglik_at_mean |
Length- |
Details
The default methods take a draws x observations pointwise log-likelihood
matrix, the same input tulpa_criteria() takes. A model package registers a
method taking its own fit object, builds the matrix from the posterior, and
delegates here.
Value
A tulpa_criteria object.
See Also
tulpa_criteria() for every criterion at once and for what the LOO
unit means.
Examples
set.seed(1)
y <- rnorm(40)
mu <- matrix(rnorm(200 * 40, sd = 0.2), 200, 40)
ll <- dnorm(matrix(y, 200, 40, byrow = TRUE), mean = mu, log = TRUE)
cpo(ll)
Decompose the LHS of a bar term into intercept flag + slope language objects
Description
Walks the additive chain of the LHS and separates numeric intercept indicators (0, 1, -1) from slope term expressions. Returns language objects, not deparsed strings.
Usage
decompose_bar_lhs(lhs)
Arguments
lhs |
A language object (LHS of a bar term) |
Value
A list with has_intercept (logical) and slope_terms (list of language objects)
Comprehensive Diagnostic Summary
Description
Provides a comprehensive diagnostic report for a tulpa model, combining convergence metrics, divergence information, and actionable recommendations.
Usage
diagnostic_summary(fit, quiet = FALSE)
Arguments
fit |
A |
quiet |
Logical; if TRUE, suppress printed output (default: FALSE). |
Value
A list with class tulpa_diagnostic_summary containing:
- status
Overall status: "PASS", "WARN", or "FAIL"
- n_divergent
Number of divergent transitions
- divergent_pct
Percentage of divergent transitions
- worst_rhat
Data frame of parameters with worst Rhat
- worst_ess
Data frame of parameters with worst ESS
- e_bfmi
E-BFMI value (HMC only)
- pareto_k, quad_ess
approximation fits only: the outer PSIS k-hat, or the grid quadrature ESS when no k-hat was produced
- pareto_k_declined
approximation fits only, and only when there is no k-hat: WHY –
"not_requested"and"unguessable_axis: <axis>"are benign or permanent,"degenerate_proposal"and"grid_too_small"are signals about the fit, and"internal_inconsistency"is an engine bug and raises the status to"WARN"- inner_skew_max, inner_skew_declined
approximation fits only: the largest scored inner-Laplace
|gamma_3|, or why nothing was scored ("coupled_arm"marks arms the inner layer could score neither per observation nor through the cell tensor)- axis_fields_dropped
data frame of grid axes the fit's own resolved path could not read and dropped as engine defaults: one row per field, with the block, family, path and the axis that path integrated instead. Absent whenever every supplied axis was used
- recommendations
Character vector of recommendations
See Also
check_diagnostics(), diagnostics(), plot_diagnostics()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
ds <- diagnostic_summary(fit)
print(ds)
Posterior diagnostics for a fitted model
Description
The diagnostic front door for any tulpa fit. What a fit's posterior draws can be asked depends on how they were produced, so this reads the draws provenance and returns the diagnostic that applies:
- MCMC chain draws
improved Rhat (the maximum of rank-normalized split-Rhat and folded split-Rhat), bulk / tail / mean / sd / quantile effective sample size, and Monte Carlo standard errors, following Vehtari et al. (2021). Split-Rhat is defined for a single chain, so a result is produced for any number of chains.
- i.i.d. approximation draws
the approximation-reliability table – the PSIS tail-shape
pareto_kscored against the exact inner-Laplace marginal and the outer-grid quadrature effective sample size (the OUTER hyperparameter-grid integration layer), the inner-Laplace skewness diagnosticgamma_3when computed (the INNER Gaussian approximation to the latent field, a separate layerpareto_kdoes not cover), a combined whole-fit verdict naming which layer degrades when one does, and a per-parameter posterior summary. Seelaplace_diagnostics()for the full description of this table and its attributes.- point summaries
no sample to diagnose; returns
NULLwith a message naming the backend.
Provenance is read from $draws_kind (stamped by tulpa_dispatch), falling
back to the backend registry's emits property and then to an inner
$joint_fit. A fit that predates the tag is treated as a chain, so an older
fit is never silently refused.
Usage
diagnostics(fit, ...)
## Default S3 method:
diagnostics(
fit,
pars = NULL,
measures = c("rhat", "ess_bulk", "ess_tail"),
probs = c(0.05, 0.95),
sbc = NULL,
...
)
## S3 method for class 'sbc'
diagnostics(fit, ...)
Arguments
fit |
A |
... |
Passed to the method. |
pars |
Optional character vector of parameter names to restrict to. |
measures |
Character vector selecting which diagnostics to compute, in
output-column order. Available: |
probs |
Numeric probabilities for the quantile-based measures
( |
sbc |
Optional |
Value
For a chain fit, a data frame with a parameter column followed by
one column per requested measure; entries are NA for parameters that are
constant or have too few draws. For an i.i.d. approximation fit, the
laplace_diagnostics table (see that function for its attributes). For a
point fit, NULL.
Extending
diagnostics() is an S3 generic so a model package can answer for its own
fit class (diagnostics.ratiod_fit(), say) rather than shadowing this
export with a same-named function. The default method does the provenance
routing described above and is what a method should delegate to once it has
assembled a draws array.
Calibration alongside reliability
The tables above score ONE fit's own internal reliability. Whether the
backend's posterior is CALIBRATED is a different question, answered over many
simulated data sets by sbc(), and the two disagree in both directions – a
fit whose reliability band is clean on both layers can still fail
calibration, and one whose outer k-hat is well past the escalation threshold
can pass. So the band is a screen, not a verdict. Pass an sbc result as
sbc = and the calibration verdict is attached to the table and printed
underneath it; diagnostics() called on the sbc result itself returns its
report table.
References
Vehtari, Gelman, Simpson, Carpenter & Burkner (2021). Rank-normalization, folding, and localization: an improved Rhat for assessing convergence of MCMC. Bayesian Analysis 16(2):667-718.
Vehtari, Simpson, Gelman, Yao & Gabry (2024). Pareto smoothed importance sampling. JMLR 25(72):1-58.
See Also
sbc() for calibration, laplace_diagnostics() for the
approximation-reliability table in full, tulpa_draws_array(),
plot_rhat(), plot_ess(), diagnostic_summary(),
check_diagnostics()
Examples
set.seed(1)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
# chain fit -> Rhat / ESS
hmc <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
diagnostics(hmc)
# deterministic fit -> PSIS approximation reliability
smc <- tulpa(y ~ x, data = df, family = "poisson", mode = "smc")
diagnostics(smc)
Dispatch a spatial Polya-Gamma Gibbs fit to the correct sampler
Description
The Gibbs analogue of dispatch_laplace_spatial(): routes on spatial$type
to the matching cpp_pg_<family>_gibbs_<structure> sampler, building the
neighbour-list / coordinate inputs each one needs. The binomial Polya-Gamma
augmentation backs the full areal (icar/bym2/rsr) + continuous (gp/nngp/
multiscale_gp) family; neg_binomial_2 is backed by the single areal ICAR
negbin sampler (cpp_pg_negbin_gibbs_spatial), the only negbin spatial kernel.
Usage
dispatch_gibbs_spatial(
y,
n_trials,
X,
re_group,
n_re_groups,
spatial,
family,
iter,
warmup,
thin = 1L,
prior_beta_sd = .tulpa_prior_sd("gibbs"),
prior_sigma_re_scale = 2.5,
verbose = FALSE,
n_threads = 1L
)
Dispatch a temporal Polya-Gamma Gibbs fit
Description
The temporal analogue of dispatch_gibbs_spatial(): maps a validated
temporal_multiscale() spec onto the multiscale temporal Polya-Gamma
sampler (cpp_pg_binomial_gibbs_temporal), which composes an additive
RW1 trend + cyclic-RW1 seasonal + AR1/IID short-term decomposition. Binomial
only. The C++ kernel implements an RW1 trend (rw2 is rejected here rather than
silently downgraded).
Usage
dispatch_gibbs_temporal(
y,
n_trials,
X,
re_group,
n_re_groups,
temporal,
family,
iter,
warmup,
thin = 1L,
prior_beta_sd = .tulpa_prior_sd("gibbs"),
prior_sigma_re_scale = 2.5,
verbose = FALSE,
n_threads = 1L
)
Dispatch spatial Laplace to the correct C++ backend
Description
weights is the per-observation likelihood weight. It reaches the same
BuiltinFamilyResponse::weights channel the non-spatial route uses, which
scales each row's log-density, score and Fisher curvature by the same w_i,
so the mode these kernels return and the marginal precision
.marginal_H_beta_*() builds at it describe one model.
Usage
dispatch_laplace_spatial(
y,
n_trials,
X,
re_idx,
n_re_groups,
sigma_re,
spatial,
family,
phi,
max_iter,
tol,
n_threads,
offset = NULL,
weights = NULL
)
Durbin-Watson test for temporal autocorrelation
Description
Tests first-order autocorrelation in temporally-ordered residuals.
Usage
durbin_watson(object, alternative = c("two.sided", "greater", "less"))
Arguments
object |
A numeric vector of temporally-ordered residuals |
alternative |
|
Value
An htest object with DW statistic, lag-1 r, and p-value
Extract coordinates from various formats
Description
Extract coordinates from various formats
Usage
extract_coords(x)
Extract coordinates from tulpa_fit object
Description
Extract coordinates from tulpa_fit object
Usage
extract_coords_from_fit(fit)
Elementwise log-likelihood for a family.
Description
Elementwise log-likelihood for a family.
Usage
family_loglik(eta, y, family, n_trials = NULL, phi = 1, phi2 = NULL)
Inverse-link mean for a family. phi reaches entries whose response mean
depends on the dispersion (lognormal); the others ignore it via ....
Description
Inverse-link mean for a family. phi reaches entries whose response mean
depends on the dispersion (lognormal); the others ignore it via ....
Usage
family_mean(eta, family, phi = 1)
Supported R-level family names.
Description
Supported R-level family names.
Usage
family_names()
Response-scale mean of y given eta for a family (trial-scaled where relevant).
Description
Response-scale mean of y given eta for a family (trial-scaled where relevant).
Usage
family_response_mean(eta, family, n_trials = NULL, phi = 1)
One response draw per element of eta (posterior predictive), elementwise.
Description
One response draw per element of eta (posterior predictive), elementwise.
Usage
family_sample(eta, family, n_trials = NULL, phi = 1, phi2 = NULL)
Score (d log-likelihood / d eta), elementwise.
Description
Score (d log-likelihood / d eta), elementwise.
Usage
family_score_eta(eta, y, family, n_trials = NULL, phi = 1, phi2 = NULL)
Response variance Var(y | eta) for a family, elementwise.
Description
Response variance Var(y | eta) for a family, elementwise.
Usage
family_variance(eta, family, n_trials = NULL, phi = 1, phi2 = NULL)
Laplace/IRLS working weight (-d^2 log-lik / d eta^2), elementwise.
Description
Laplace/IRLS working weight (-d^2 log-lik / d eta^2), elementwise.
Usage
family_weight(eta, family, n_trials = NULL, phi = 1, phi2 = NULL)
Find all latent(...) calls in a formula's parse tree
Description
Recursively walks the formula AST and collects all latent(...) calls.
The matched calls are returned unevaluated; tulpa_parse_formula()
resolves them in the formula's environment.
Usage
find_latent_terms(term)
Arguments
term |
A language object (formula term) |
Value
A list of language objects, each a latent(...) call
Find all bar terms in a formula's parse tree
Description
Recursively walks the formula AST and collects all | and || nodes
found inside parentheses. These are the random effect specifications.
Usage
findbars(term)
Arguments
term |
A language object (formula term) |
Value
A list of language objects, each a | or || call
Fit a Spatial Model using SPDE Laplace Approximation
Description
Fits a GLM with a Matern spatial field via the SPDE approach. Uses CHOLMOD sparse solver with optional nested Laplace for hyperparameter integration.
Usage
fit_spde(
y,
X,
spatial,
family = "binomial",
n_trials = NULL,
range = NULL,
sigma = NULL,
nested_laplace = is.null(range) || is.null(sigma),
phi = 1,
offset = NULL,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
mode = c("laplace", "nuts"),
control = list()
)
Arguments
y |
Integer response vector. |
X |
Design matrix. |
spatial |
A |
family |
Distribution family: |
n_trials |
Integer vector of trial sizes (binomial only). |
range |
Spatial range parameter. If NULL, uses nested Laplace to integrate over range and sigma. |
sigma |
Marginal standard deviation. If NULL, uses nested Laplace. |
nested_laplace |
Logical. If TRUE (default when range/sigma are NULL), use nested Laplace approximation over hyperparameters. |
phi |
Dispersion parameter (negbin only). |
offset |
Optional fixed additive term on the linear predictor
( |
re_idx, n_re_groups, sigma_re |
Optional single iid random-intercept
|
mode |
Inference method (the method is an argument, not a parallel
verb): |
control |
A named list of numerical / tuning knobs (statistical arguments stay in the signature above). Recognized entries:
|
Value
A list with:
-
mode: mode of the latent field (beta + mesh node effects) -
log_marginal: log marginal likelihood -
converged: convergence flag -
spatial: the spatial specification (for prediction) -
pareto_k,pareto_k_is_ess: outer Pareto-\hat{k}and its importance-sampling ESS (NAwhendiagnose_k = FALSE) -
nested: nested Laplace results (if used)
References
Lindgren, Rue & Lindstrom (2011). An explicit link between Gaussian fields and Gaussian Markov random fields: the stochastic partial differential equation approach. JRSS-B 73(4):423-498. Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392.
Examples
if (requireNamespace("fmesher", quietly = TRUE)) {
set.seed(1)
n <- 200L
coords <- cbind(runif(n), runif(n))
mesh <- fmesher::fm_mesh_2d(loc = coords, max.edge = c(0.15, 0.4), cutoff = 0.05)
fem <- fmesher::fm_fem(mesh)
A <- as(fmesher::fm_basis(mesh, loc = coords), "CsparseMatrix")
spec <- spatial_spde_custom(C = fem$c0, G = fem$g1, A = A, nu = 1,
prior_range = c(0.3, 0.5), prior_sigma = c(0.6, 0.05))
w <- as.numeric(rnorm(spec$n_mesh, 0, 0.6)); w <- w - mean(w)
x <- rnorm(n)
y <- rpois(n, exp(2.0 + 0.5 * x + as.numeric(spec$A %*% w)))
fit <- fit_spde(y = y, X = cbind(1, x), spatial = spec, family = "poisson")
fit$nested$range_mean
}
Fit an additive spatiotemporal GLM by nested Laplace
Description
Fits y ~ X beta + u_spatial[s] + v_temporal[t] with an areal spatial field
(icar / bym2 / car_proper) and a temporal field (rw1 / rw2 / ar1),
integrating the spatial precision, temporal precision, and (for ar1) the
temporal autocorrelation over a hyperparameter grid via the
cpp_nested_laplace_st_* kernels. The fixed-effect posterior is the
grid-marginalised mixture; the spatial and temporal field posterior means are
the grid-weighted latent modes.
Usage
fit_st_nested(
y,
X,
spatial_idx,
adjacency,
temporal_idx,
n_times,
spatial_type = c("icar", "bym2", "car_proper"),
temporal_type = c("ar1", "rw1", "rw2"),
family = "binomial",
n_trials = NULL,
phi = 1,
cyclic = FALSE,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
control = list()
)
Arguments
y |
Response vector. |
X |
Fixed-effects design matrix ( |
spatial_idx |
Integer per-observation spatial-unit index (1-based). |
adjacency |
Spatial adjacency (a symmetric 0/1 matrix or |
temporal_idx |
Integer per-observation time index (1-based). |
n_times |
Number of distinct time points. |
spatial_type |
|
temporal_type |
|
family |
Response family (see |
n_trials |
Binomial denominators, or |
phi |
Dispersion passed to the family. |
cyclic |
Logical; wrap the temporal field (seasonal). Default |
re_idx, n_re_groups, sigma_re |
Optional single iid random-intercept term
alongside the fields (conditioned on |
control |
A list of numerical / grid knobs: The A grid knob PINS the axes it shapes, and a pin always wins – but
pinning is decided by value, not by presence: a knob
set to the engine's own default, or marked with |
Value
A tulpa_fit (subclass tulpa_nested_laplace) carrying the
fixed-effect posterior (draws via the grid mixture), spatial_effects,
temporal_effects, log_marginal, weights, and theta_grid over
(tau_spatial, tau_temporal, rho). Also carries pareto_k_regime
("spread" / "collapsed_interior" / "collapsed_edge", see
tulpa_nested_laplace_joint()'s return docs for the definition) and
outer_grid_placement ("fixed" or "auto_recentered") plus, on a
"fixed" placement, outer_grid_recenter_declined
("grid_knobs_overridden" / "grid_not_collapsed" /
"no_usable_curvature" / "refit_failed" / "sd_ceiling_unresolved" /
"sd_floor_unresolved"). A recentred fit also carries
outer_grid_pinned_axes, the axes whose knobs were pinned and whose
nodes were therefore kept, and outer_grid_recenter_sd_clamp /
_sd_raw / _sd_used – per moved axis, which mode-SD bound the
placement hit, the SD the stencil measured, and the SD the axis was laid
from. A bound-decline is PER AXIS here: the axes the
mode-find did resolve are still re-placed, and
outer_grid_recenter_sd_declined names the ones that kept their incoming
nodes and on which bound, so a partially re-placed grid is not read as a
fully re-placed one. With every free axis declined the pass reports the
grid as the fixed one it still is.
See Also
tulpa() (front door), tulpa_nested_laplace() (single field).
Examples
set.seed(1)
n_s <- 16L; n_t <- 8L; N <- 400L
adj <- matrix(0, n_s, n_s)
for (i in 1:(n_s - 1)) adj[i, i + 1] <- adj[i + 1, i] <- 1
s <- sample(n_s, N, TRUE); tt <- sample(n_t, N, TRUE)
us <- as.numeric(scale(cumsum(rnorm(n_s)))); vt <- as.numeric(scale(cumsum(rnorm(n_t))))
x <- rnorm(N)
y <- rbinom(N, 1, plogis(0.2 + 0.5 * x + 0.7 * us[s] + 0.6 * vt[tt]))
fit <- fit_st_nested(y, cbind(1, x), s, adj, tt, n_t, family = "binomial")
Fitted values (population level)
Description
In-sample mean response from the fixed effects and the observation offset
(E[y] = g^{-1}(X beta + offset), trial-scaled for binomial). Random
effects are held at their prior mean of zero; group-level effects are in
ranef(). y - fitted(object) equals
residuals(object, type = "response").
Usage
## S3 method for class 'tulpa_fit'
fitted(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
Numeric vector of fitted mean responses, length nobs.
Fixed-effect coefficients (lme4-compatible)
Description
The fixed-effect point estimates, equivalent to coef() on a tulpa_fit.
Provided under the fixef name for code written against the lme4 / nlme
interface: lme4::fixef(fit) and nlme::fixef(fit) dispatch here too when
either package is installed, so a tulpa_fit can be dropped into an
lme4-shaped workflow.
Usage
fixef(object, ...)
## S3 method for class 'tulpa_fit'
fixef(object, ...)
Arguments
object |
A |
... |
Ignored. |
Details
Note the deliberate difference from lme4::coef.merMod, which returns
per-group sums of the fixed and random effects. On a tulpa_fit, coef()
returns the fixed effects alone and fixef() is its synonym; the random
effects are ranef().
Value
Named numeric vector of fixed-effect estimates.
See Also
Examples
set.seed(1)
df <- data.frame(x = rnorm(80))
df$y <- rpois(80, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson")
fixef(fit)
Format mode selection message
Description
Format mode selection message
Usage
format_mode_selection(selection)
Arguments
selection |
List from select_inference_mode() |
Value
Character string for display
Render a slope spec as a display string
Description
Used by the print method. Derives the textual form on demand from the stored language objects so we never store deparsed slope text.
Usage
format_re_lhs(re)
Format tier information for display
Description
Format tier information for display
Usage
format_tier_info(tier_info, verbose = FALSE)
Arguments
tier_info |
List from get_backend_tier() |
verbose |
Include full description |
Value
Character string
Gauss-Hermite quadrature (probabilist's, exp(-z^2/2) weight)
Description
Computes nodes and weights for \int f(z) (2\pi)^{-1/2} exp(-z^2/2) dz
via Golub-Welsch on the Hermite Jacobi matrix. Sums of w_k f(z_k)
approximate E_{Z ~ N(0,1)}[f(Z)].
Usage
gauss_hermite_prob(n)
Arguments
n |
Number of quadrature nodes. |
Value
List with nodes and weights (each length n).
Get tier for a backend
Description
Get tier for a backend
Usage
get_backend_tier(backend)
Arguments
backend |
Character string naming the backend |
Value
List with tier information
Map mode to valid backends
Description
Map mode to valid backends
Usage
get_mode_backends(mode)
Arguments
mode |
Character: "auto", "exact", "structured", or "optimized" |
Value
Character vector of valid backends for this mode
Get appropriate color scale
Description
Get appropriate color scale
Usage
get_palette_scale(palette, na_color, legend_title, geom = "fill")
Geweke Convergence Test
Description
Performs Geweke's convergence diagnostic, comparing the mean of the first portion of a chain to the last portion. Useful for single-chain diagnostics.
Usage
geweke_test(fit, frac1 = 0.1, frac2 = 0.5, pars = NULL)
Arguments
fit |
A |
frac1 |
Fraction of chain for first window (default: 0.1). |
frac2 |
Fraction of chain for second window (default: 0.5). |
pars |
Character vector of parameter names (default: all main params). |
Details
The Geweke test computes a z-score comparing the means of early and late portions of a chain. Large z-scores (|z| > 2) indicate the chain has not converged.
Value
A data frame with columns: parameter, z_score, p_value.
See Also
diagnostics(), check_diagnostics()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
geweke_test(fit)
Model-level summary statistics (broom-compatible)
Description
Model-level summary statistics (broom-compatible)
Usage
## S3 method for class 'tulpa_fit'
glance(x, ...)
Arguments
x |
A |
... |
Ignored. |
Value
Single-row data frame.
Examples
set.seed(1)
df <- data.frame(x = rnorm(100))
df$y <- rpois(100, exp(0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson")
glance(fit)
Compute GLM working weights for Laplace Hessian
Description
Thin wrapper over the family-ops registry (family_weight()) so the weight
formulas live in exactly one place (R/family_loglik.R).
Usage
glmm_weights(eta, family, n_trials = NULL, phi = 1, phi2 = NULL)
Map a spatial_gp covariance spec to the Laplace cov_type integer
Description
The Laplace NNGP kernel (laplace_core.cpp) supports three covariance
functions: 0 = exponential, 1 = Matern(nu=1.5), 2 = Matern(nu=2.5).
Anything else is rejected with a clear error rather than silently
falling back to a different covariance.
Usage
gp_cov_type_for_laplace(spatial)
Describe one outer-grid hyperparameter axis
Description
Builds a single axis spec for tulpa_hyper_grid(). Carries the candidate
values, the optional log-prior, and the metadata (log-scale, bounds,
refinable flag) that the generic refinement / consistency passes need.
Usage
hyper_axis_spec(
name,
grid,
log_prior = NULL,
log_scale = FALSE,
bounds = NULL,
refinable = FALSE
)
Arguments
name |
Character. Axis label, used as the column name of the grid matrix and in posterior summaries. |
grid |
Numeric vector of length >= 1. The per-axis candidate values (the outer integration nodes on this axis). The full outer grid is the Cartesian product across axes. |
log_prior |
Optional |
log_scale |
Logical. Does the axis live naturally on a log scale
( |
bounds |
Numeric vector of length 2 giving the natural support
|
refinable |
Logical. When |
Value
An object of class tulpa_hyper_axis_spec (a validated list with
the six fields above).
See Also
Independence Metropolis-Hastings with Laplace proposal
Description
Sample from a posterior using independence Metropolis-Hastings with a multivariate-normal proposal centred at the Laplace mode and scaled by the inverse Hessian. For posteriors that are well- approximated by a Gaussian near the mode this is dramatically cheaper than HMC: each iteration is one log-posterior evaluation plus one accept/reject step. Embarrassingly parallel across chains.
Use cases:
Quick draws when Laplace is "almost right" but you want asymptotically-correct samples.
Cross-validation, simulation studies, or other repeated-fit workflows where HMC startup cost dominates.
Sanity check on the Laplace approximation: low IMH acceptance means the posterior is far from Gaussian and Laplace is biased.
Usage
imh_laplace(
log_posterior,
mode,
hessian,
n_iter = 2000L,
warmup = n_iter%/%2L,
scale = 1,
init = NULL,
thin = 1L,
verbose = FALSE
)
Arguments
log_posterior |
Function |
mode |
Numeric vector of length |
hessian |
Symmetric positive-definite |
n_iter |
Total iterations including warmup (default 2000). |
warmup |
Warmup iterations to discard (default |
scale |
Optional inflation factor on the proposal covariance (default 1.0). Values slightly > 1 (e.g., 1.5) give heavier-tailed proposals that improve mixing when Laplace underestimates posterior spread. |
init |
Optional starting parameter vector (default = |
thin |
Keep every |
verbose |
Print acceptance rate at end (default |
Value
A list with class tulpa_fit carrying:
-
draws:(n_iter - warmup) / thinxdmatrix of draws. -
means: posterior means. -
accept_prob: per-iteration acceptance indicators. -
mean_accept: overall post-warmup acceptance rate. -
log_prob: per-draw log posterior values. -
inference_mode:"exact". -
inference_tier:1L. -
backend:"imh_laplace".
Tier
Tier 1 (Exact). The MH accept/reject step makes the chain asymptotically correct under the standard MH conditions. Tier status does not depend on Laplace's quality – only its quality affects efficiency.
See Also
tulpa_laplace() for the mode + Hessian,
bridge_sampling() for marginal-likelihood estimation on the
resulting draws.
Examples
# Toy: Bernoulli logistic with one covariate.
set.seed(1)
n <- 100
x <- rnorm(n)
eta <- 0.3 + 1.2 * x
y <- rbinom(n, 1, plogis(eta))
X <- cbind(1, x)
lap <- tulpa_laplace(y, n_trials = rep(1L, n), X = X,
family = "binomial")
log_post <- function(beta) {
eta <- as.numeric(X %*% beta)
sum(y * eta - log1p(exp(eta))) +
sum(dnorm(beta, 0, 10, log = TRUE))
}
fit <- imh_laplace(log_post, mode = lap$mode[1:2],
hessian = lap$H_beta, n_iter = 1000)
fit$mean_accept
colMeans(fit$draws)
Print inference mode information
Description
Displays information about the inference modes available in tulpa, including their tiers, guarantees, and appropriate use cases.
Usage
inference_mode_info()
Value
NULL, invisibly. Called for the side effect of printing the
available inference modes, their tiers, guarantees, and use cases to the
console.
Inference Mode System
Description
tulpa uses an explicit tier system for inference that encodes epistemic guarantees, not just runtime characteristics.
This design makes the difference between inference methods first-class and unavoidable, rather than hiding them as implementation details.
The Three Tiers
Tier 1 - Exact: Asymptotically correct posterior inference (up to Monte Carlo error). Credible intervals are interpretable as posterior uncertainty. This is the reference standard.
Tier 2 - Structured: Accurate inference conditional on explicit structural assumptions. Typically requires latent Gaussian structure, conditional independence, smooth posteriors. Very fast for the right model class, but can be wrong outside that class. Failure modes are predictable and explainable.
Tier 3 - Optimized: No general correctness guarantee beyond empirical usefulness. Point estimates usually good, but uncertainty often underestimated. Tails and correlations unreliable. Failure is usually silent. This is optimization, not sampling.
Auto Mode
mode = "auto" chooses between Tier 1 and Tier 2 only.
It will never silently choose Tier 3 (Optimized).
The contract: "Use the most reliable method that is expected to finish for this model."
Auto decisions are deterministic, explainable, and overrideable.
Implementation Rules
Modes change semantics, not just runtime. Intervals from Optimized do not mean the same as Exact.
The mode must always be visible in output.
No silent upgrading or downgrading. If Exact fails, we error - we do not switch to Structured.
Backends slot into tiers. Adding a backend never introduces a new epistemic promise.
Initialize latent factor parameters
Description
Initialize latent factor parameters
Usage
initialize_latent_params(latent_info, seed = NULL)
Arguments
latent_info |
Latent factor info from prepare_latent_for_hmc |
seed |
Optional random seed |
Value
Numeric vector of initial parameter values
Is an outer-grid setting marked as a default?
Description
Is an outer-grid setting marked as a default?
Usage
is_auto_grid(x)
Arguments
x |
Any object. |
Value
TRUE when x carries the auto_grid() marker.
See Also
Examples
is_auto_grid(auto_grid(c(0.5, 1, 2)))
is_auto_grid(c(0.5, 1, 2))
Check if adjacency matrix is connected
Description
Check if the spatial graph defined by the adjacency matrix is fully connected. A disconnected graph can cause identifiability issues.
Usage
is_connected(adjacency)
Arguments
adjacency |
Adjacency matrix |
Value
Logical; TRUE if connected
Proper-CAR Laplace at given hyperparameters
Description
Single-point Laplace for a proper-CAR areal field at a fixed (tau, rho),
the conditional counterpart of the nested CAR_proper integrator. Reuses the
shared make_car_proper_latent_blocks factory + dense spec solver via
cpp_laplace_fit_car_proper, so the mode + log-marginal equal the nested
kernel at that one grid cell. tau / rho default to the spec's fields (or
tau = 1, rho = midpoint of the eigenvalue-derived rho_bounds) and are
recorded on the result.
Usage
laplace_car_proper_at(
y,
n_trials,
X,
spatial,
family = "binomial",
phi = 1,
tau = NULL,
rho = NULL,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
max_iter = 100L,
tol = 1e-06,
n_threads = 1L,
offset = NULL,
weights = NULL
)
Approximation-reliability diagnostics for a deterministic nested-Laplace fit
Description
Use diagnostics(), which returns this table for any fit whose draws are an
i.i.d. approximation sample. The sections below document that table; they
remain the reference for its columns and attributes.
Per-parameter reliability diagnostics for a fit whose posterior draws are
i.i.d. samples from a deterministic approximation (the nested-Laplace
grid-mixture posterior sum_k w_k N(mode_k, V_k)), where the between-chain
Gelman-Rubin Rhat that diagnostics() reports for a chain fit does not apply. This is
the accessor that plays Rhat's role for the deterministic engine: it answers
"did the approximation work", not "did the chains mix".
The headline is a Pareto-smoothed importance-sampling (PSIS) reliability
diagnostic for the OUTER hyperparameter-grid integration. The nested
integrator scores its hyperparameter grid against the exact inner-Laplace
marginal posterior with a generalized-Pareto fit to the upper tail of the
importance ratios log p_target(theta) - log q_proposal(theta)
(see tulpa_psis()); the resulting tail-shape pareto_k is the "did the
outer integration work" number – k-hat < 0.5 good, 0.5-0.7 usable,
>= 0.7 unreliable (Vehtari et al. 2024; Yao et al. 2018). It is computed at
fit time and read back here; a fit that did not run the diagnostic, or whose
grid proposal degenerated, reports it as NA and is assessed on the grid
quadrature reliability instead.
pareto_k scores the outer integration only. A high pareto_k on a fit
whose grid quadrature is healthy (ess_grid well above 1, largest cell
weight modest) flags outer-integration (CI-width) calibration in the
right-skewed hyperparameter tail and does not by itself invalidate the
point estimates, which the grid quadrature governs.
outer_regime qualifies what a high pareto_k means, and is the reason a
bare threshold on pareto_k is not a reliability verdict. A sharp
hyperparameter posterior collapses the grid onto ~1 cell (ess_grid near
1); the outer integration has then degenerated to a point evaluation at the
modal hyperparameter, so pareto_k is scoring how well a Gaussian at that
mode stands in for the hyperparameter marginal, not how well a grid
integrated it. Where the dominant cell is INTERIOR to the grid the collapse
is benign – the grid bracketed the mode, the estimate is empirical Bayes
there, and only integrated hyperparameter uncertainty is missing. Where it
sits at a grid BOUNDARY the grid may simply be too narrow: grid_edge_axes
/ grid_edge_sides name the axes to widen. On a fit whose pareto_k
cleared the good band, the outer diagnostic also fits a skew-normal proposal
and reports the marginal's estimated skewness as outer_skew_max, so an
inflated k-hat that was purely the symmetric proposal's mismatch with a
skewed variance-component marginal is both corrected and explained. A
skew-normal has Gaussian tails, so this can never mask a genuinely
heavy-tailed target.
The grid quadrature reliability – the effective sample size
ess_grid = 1 / sum(w_k^2) of the outer integration weights and the largest
single cell weight – is always computed from the stored grid: a grid that
collapses onto one cell (ess_grid near 1) integrates no hyperparameter
uncertainty, while a spread grid does.
A SEPARATE layer – the inner Gaussian Laplace approximation to the
latent-field conditional posterior pi(x | theta, y), which pareto_k
does not cover – is scored by inner_skew: the leading-order Edgeworth
skewness estimate gamma_3 (Rue, Martino & Chopin 2009 Sec 3.2.3) at the
fitted MAP grid cell, computed when control$diagnose_skew = TRUE (the
default) on the fitting call. Reading a high pareto_k alone as "the fit
is broken" conflates the two layers: an occu_cover batch flagged 42/78
species "unreliable" on outer k-hat alone when their point estimates,
governed by the healthy inner layer, were fine – the
reliability attribute is the combined verdict that names which layer
degrades, if either does.
Each parameter row also carries the rank-normalized split-Rhat and bulk /
tail effective sample size of the draws (Vehtari et al. 2021). On i.i.d.
draws these sit at ~1.00 and ~n_draws by construction; they are reported,
clearly as i.i.d.-draw Monte-Carlo diagnostics and not chain mixing, to
document that the reported posterior summaries are not Monte-Carlo-limited.
A posterior sample is what those per-parameter rows are computed from, and
nothing else here needs one: every reliability quantity above is read off the
fit. So a fit that carries no draws – a default single-block nested-Laplace
fit, whose posterior is the retained outer-grid mixture rather than a sample
– reports the full band with an empty per-parameter body and n_draws = NA,
and records why in the param_table_declined attribute.
tulpa_posterior_draws() samples that mixture where the rows are wanted.
The one case that still returns NULL is a fit with neither draws nor any
reliability quantity, such as a plain Laplace fit with no outer grid to
score.
Usage
laplace_diagnostics(fit, pars = NULL)
Arguments
fit |
A |
pars |
Optional character vector of parameter names to restrict to. |
Value
A data frame with one row per parameter – parameter, mean, sd,
ess_bulk, ess_tail, rhat – carrying attributes:
pareto_kthe outer PSIS reliability k-hat (
NAif not computed).pareto_k_band"good"/"ok"/"unreliable"/NA.pareto_k_declined,pareto_k_declined_notewhen
pareto_kisNA, WHY:"not_requested","not_applicable","unguessable_axis"(naming the axis – a permanent limitation of that family, so readess_gridinstead),"draws_too_few","grid_too_small","no_varying_axis","degenerate_proposal", or"internal_inconsistency"(an engine bug worth reporting), plus a one-line reading of it.pareto_k_is_essimportance-sampling ESS on the smoothed weights.
ess_grid,n_grid,rel_ess_grid,max_weightgrid quadrature reliability.
outer_regime"spread"/"collapsed_interior"/"collapsed_edge"– whether the outer grid integrated hyperparameter uncertainty at all, and if not whether its dominant cell is interior (empirical Bayes at the mode: point estimates sound, hyperparameter uncertainty not integrated) or against a grid boundary (widen it).grid_edge_axes,grid_edge_sidesfor an edge collapse, the axes the dominant cell sits against and on which side.
outer_skew_maxlargest estimated |skewness| of the hyperparameter marginal, computed only when the k-hat triggered the skew-normal proposal rescue (
NAmeans the Gaussian proposal already fit, not "symmetric and unchecked").outer_regime_notea one-line reading of a collapsed regime, or absent on a spread grid.
grid_railed_axesouter axes whose OWN marginal is maximal at one of their own endpoints, as
axis:side– the span does not contain that axis's posterior mode, so its marginal is a truncated tail at any spacing. Reported whether or not the engine was allowed to, able to, or built to move the axis.grid_placement,grid_recentred_axes,grid_placement_declined,grid_placement_notewhether the outer grid was re-centred, on which axes, and – when it was not – why.
scopethe outer diagnostic's scope string.
inner_skew_maxthe largest
|gamma_3|among the scored latent indices (NAifcontrol$diagnose_skew = FALSEor nothing scored).inner_skew_band"good"/"ok"/"unreliable"/NA, banded oninner_skew_maxby the general skewness-magnitude convention (Bulmer 1979) – not a Rue-Martino-Chopin-specific cutoff.inner_skew_scored,inner_skew_probedhow many of the probed latent indices returned a finite
gamma_3vs how many were probed.inner_skew_declined,inner_skew_arms_declined,inner_skew_declined_notewhen nothing was scored, WHY:
"coupled_arm"(STRUCTURAL – the coupled arms have neither a per-observation sum nor a cell third-derivative tensor to read, so the outer k-hat is the only reliability number this fit has),"curvature3_unavailable","no_finite_contribution","no_probe_indices","not_requested","backend_unsupported","solve_failed", or"not_converged"(the probe re-solve stopped short of a mode, so neither inner score has a point to read); the arms (1-based) a joint fit had no oracle for, which is also set on a PARTIALLY scored fit; and a one-line reading.inner_pareto_k,inner_pareto_k_bandthe inner-Laplace importance k-hat over the probed subspace, and its band on the same convention as the outer k-hat. Available wherever a mode was found, including a fit
gamma_3cannot score.inner_pareto_k_rel_ess,inner_pareto_k_is_essthe smallest realized importance efficiency and effective sample size across the probed indices – how much correcting the inner Gaussian actually needs, which is what makes the scale-free shape above readable.
inner_pareto_k_uniformTRUEwhen no probed index carried a material correction, i.e. the inner Gaussian reproduces the conditional posterior over the sampled region.inner_pareto_k_scored,inner_pareto_k_probedhow many probed indices returned a finite k-hat vs how many were probed.
inner_pareto_k_declined,inner_pareto_k_declined_notewhen it is
NA, WHY, from the same closed vocabulary the outer k-hat uses.reliabilitythe combined whole-fit verdict:
"reliable"only when both layers are good; otherwise names which layer is scoped or flags both as unreliable. The inner layer enters through the worse of its two scores, so a fit whose cubic term declined is still assessed.
and a trailing summary attribute (a
one-row data frame of the headline numbers) for printing.
Scope
The PSIS pareto_k diagnoses the OUTER (hyperparameter) integration: whether
the Gaussian-proposal-over-grid approximation of the marginal hyperparameter
posterior p(theta | data) can be importance-corrected to the exact inner
marginal. This is the dominant approximation in nested Laplace and the one
with an exactly evaluable target. A full latent-space PSIS against the exact
joint posterior pi(x) is not computed: the latent prior marginal
p(x) = integral p(x | theta) p(theta) dtheta has no closed form, and for
the marginalized-occupancy / cover-hurdle likelihoods the exact joint density
is evaluable only inside the C++ kernel, so a stored fit cannot reconstruct
it. The grid quadrature reliability is the complementary stored-fit number.
The inner layer carries a SECOND score, inner_pareto_k, which needs no
likelihood derivative at all and therefore answers where inner_skew
declines. The inner Gaussian at the fitted hyperparameter is an importance
proposal for the exact conditional posterior, and the joint density is the
target, so PSIS on that ratio scores the inner approximation directly. It is
computed on the same probed indices along the same conditional-mean curve,
one dimension per index, since importance sampling degrades with dimension
and a k-hat over the whole latent field would report n_x rather than the
approximation. A Pareto shape index is scale-free, so it is banded only on
indices whose realized importance efficiency shows a correction worth
describing; inner_pareto_k_uniform records that none did, which is what a
well-approximated inner layer looks like.
inner_skew diagnoses the INNER (latent-field) Laplace: whether the
Gaussian approximation to pi(x_i | theta, y) is itself a good fit, at
each scored latent index i. gamma_3 is exact for a gaussian-family
coefficient (the log-likelihood is exactly quadratic in eta) and
declines to NA – never a silently-wrong 0 ("perfectly Gaussian") –
whenever no per-observation third-derivative oracle is available: a
coupled multi-process likelihood (e.g. tulpaObs's occu_cover, whose
arms combine non-separably through a CellCouplingSpec) or a family with
no registered third derivative. Only the requested latent indices are
scored (every arm's fixed-effects coefficients by default – see
control$skew_idx), since each index costs one extra linear solve; the
full latent field is not scored by default on a large spatial field.
References
Vehtari, Simpson, Gelman, Yao & Gabry (2024). Pareto smoothed importance sampling. JMLR 25(72):1-58.
Yao, Vehtari, Simpson & Gelman (2018). Yes, but did it work?: Evaluating variational inference. ICML, PMLR 80:5581-5590.
Vehtari, Gelman, Simpson, Carpenter & Burkner (2021). Rank-normalization, folding, and localization: an improved Rhat for assessing convergence of MCMC. Bayesian Analysis 16(2):667-718.
Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392.
See Also
diagnostics() (the front door, which returns this table for
i.i.d. fits), tulpa_psis().
Examples
set.seed(1)
n <- 200L; x <- rnorm(n)
y <- rbinom(n, 1, plogis(-0.2 + 0.6 * x))
# `mode = "laplace"` returns a mode + covariance and carries no draws; a
# sampled deterministic backend is what this table describes.
fit <- tulpa(y ~ x, data.frame(y = y, x = x), family = "binomial",
mode = "smc")
diagnostics(fit)
NNGP Laplace at given hyperparameters
Description
Single-point Laplace approximation for a Matern/exponential GP spatial
field at fixed (sigma2_gp, phi_gp). Used by dispatch_laplace_spatial
when spatial$type == "gp". The neighbor structure is read straight
off the validated spec – call validate_gp(spatial, data) first if
constructing manually.
Usage
laplace_gp_at(
y,
n_trials,
X,
spatial,
family = "binomial",
phi = 1,
sigma2_gp = NULL,
phi_gp = NULL,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
max_iter = 100L,
tol = 1e-06,
n_threads = 1L,
offset = NULL,
weights = NULL
)
Arguments
y |
Response vector. |
n_trials |
Trial sizes (binomial). |
X |
Fixed-effects design matrix. |
spatial |
A |
family |
Distribution family. |
phi |
Dispersion parameter (negbin / gamma only). |
sigma2_gp |
Marginal variance (NULL -> 1.0). |
phi_gp |
Range / decay parameter (NULL -> 1.0). |
max_iter |
Newton iterations. |
tol |
Newton tolerance. |
n_threads |
OpenMP threads. |
weights |
Optional per-observation likelihood weights (length
|
Value
The raw cpp_laplace_fit_gp result list, augmented with
sigma2_gp, phi_gp, and the spatial spec.
HSGP Laplace at given hyperparameters
Description
Single-point Laplace for a Hilbert-space GP field at a fixed
(sigma2, lengthscale), the conditional counterpart of the nested HSGP
integrator. Reuses the shared make_hsgp_block factory + dense spec solver
(DENSE_BASIS scatter) via cpp_laplace_fit_hsgp, so the mode equals the
nested kernel at that one grid cell. sigma2 / lengthscale default to the
spec's fields (or 1) and are recorded on the result.
Usage
laplace_hsgp_at(
y,
n_trials,
X,
spatial,
family = "binomial",
phi = 1,
sigma2 = NULL,
lengthscale = NULL,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
max_iter = 100L,
tol = 1e-06,
n_threads = 1L,
offset = NULL,
weights = NULL
)
SPDE Laplace at given hyperparameters
Description
Single-point Laplace approximation for an SPDE spatial field at a fixed
(range, sigma). Used by both dispatch_laplace_spatial (single-point
path) and fit_spde (single-point branch) so the call site stays a
single source of truth.
Usage
laplace_spde_at(
y,
n_trials,
X,
spatial,
family = "binomial",
phi = 1,
range = NULL,
sigma = NULL,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
max_iter = 100L,
tol = 1e-06,
n_threads = 1L,
offset = NULL,
weights = NULL
)
Arguments
y |
Response vector. |
n_trials |
Trial sizes (binomial). |
X |
Fixed-effects design matrix. |
spatial |
A |
family |
Distribution family. |
phi |
Dispersion parameter (negbin / gamma only). |
range |
Spatial range (NULL -> use |
sigma |
Marginal SD (NULL -> use |
max_iter |
Newton iterations. |
tol |
Newton tolerance. |
n_threads |
OpenMP threads. |
weights |
Optional per-observation likelihood weights (length
|
Value
The raw cpp_laplace_fit_spde result list (mode, log_det_Q,
log_marginal, n_iter, converged), augmented with range, sigma,
and the spatial spec for downstream prediction.
Mark an expression as a latent block in a tulpa formula
Description
Wraps a user-defined latent block (currently a tgmrf() object) so the
formula parser can recognise and route it to the inference layer. The
call is structural – it is never executed at fit time. The parser
evaluates the inner expression in the formula's environment, removes
the latent(...) term from the fixed-effects formula, and attaches the
resulting object to parsed$latent_blocks.
Usage
latent(block)
Arguments
block |
A |
Value
Returns block invisibly. Outside a formula context the call is
a no-op pass-through.
Create a latent factor specification
Description
Define latent factors for capturing unmeasured shared structure between
all processes. Factors are observation-level random effects
that enter both linear predictors when shared = TRUE (default).
Usage
latent_factor(
n_factors = 1L,
prior = NULL,
shared = TRUE,
constraint = c("sum_to_zero", "first_zero"),
scale = TRUE
)
Arguments
n_factors |
Integer; number of latent factors. Default is 1. More factors capture more complex unmeasured structure but increase computational cost and risk overfitting. |
prior |
Prior for factor standard deviations. Default is a PC prior with P(sigma > 1) = 0.01, which shrinks toward simpler models. |
shared |
Logical; if TRUE (default), latent factors enter both all process linear predictors identically. If FALSE, factors only affect the first process. |
constraint |
Identifiability constraint for factors:
|
scale |
Logical; if TRUE (default), factor loadings are standardized to have unit variance before applying sigma. |
Details
Why Use Latent Factors?
When modeling multiple processes, they often share unmeasured confounders. For example, in relative abundance data, both the focal species count and total count might be affected by:
Observer skill (unmeasured)
Local microhabitat conditions (unmeasured)
Weather on sampling day (unmeasured)
Without accounting for these shared drivers, estimates can be biased. Latent factors capture this shared structure without requiring the confounders to be measured.
Mathematical Model
For observation i with K latent factors, on each of two model arms (processes 1 and 2):
\eta^{(1)}_i = X^{(1)}_i \beta^{(1)} + \sum_{k=1}^{K} f_{ik} \sigma_k + \ldots
\eta^{(2)}_i = X^{(2)}_i \beta^{(2)} + \sum_{k=1}^{K} f_{ik} \sigma_k + \ldots
where:
-
f_{ik} \sim N(0, 1)are standardized factor scores -
\sigma_kare factor standard deviations with PC prior Identifiability:
\sum_i f_{ik} = 0for each k
Because factors enter both linear predictors identically (when shared), they cancel in derived quantities (e.g., ratios, differences):
\eta^{(1)}_i - \eta^{(2)}_i
This means factors capture shared effects that would otherwise bias derived quantities.
Choosing n_factors
Start with
n_factors = 1for simple unmeasured confoundingUse
n_factors = 2-3if you suspect multiple independent confoundersMore than 3 factors is rarely needed and risks overfitting
The PC prior provides regularization, shrinking unneeded factors toward zero
Relationship to Random Effects
Latent factors differ from random effects in several ways:
Random effects are grouped (e.g., site-level), factors are observation-level
Random effects require grouping structure, factors don't
Factors capture residual correlation not explained by observed predictors
You can use both together: random effects for known grouping, factors for residual unmeasured confounding.
Value
A tulpa_latent object consumed by ratio / multi-arm model
packages built on tulpa (e.g. tulpaRatio); not read by the single-response
tulpa() front door (use latent(tgmrf(...)) for a single-response
latent Gaussian block).
See Also
prior_pc() for prior specification; latent() / tgmrf() for a
single-response in-formula latent Gaussian block
Examples
# Basic latent factor (single shared factor)
latent_factor()
# Two latent factors
latent_factor(n_factors = 2)
# Custom prior (more regularization)
latent_factor(n_factors = 1, prior = prior_pc(U = 0.5, alpha = 0.01))
# Numerator-only factor (not shared)
latent_factor(n_factors = 1, shared = FALSE)
# The spec is consumed by a ratio / multi-arm model package (tulpaRatio owns
# the two-arm `species | total ~ ...` formula and the negbin/negbin ratio
# family); it is that package's fitter, not the single-response tulpa() front
# door, that reads the `latent_factor()` object.
Extract latent factor posteriors from fit
Description
Extract latent factor posteriors from fit
Usage
latent_factors(fit, summary = TRUE, probs = c(0.025, 0.5, 0.975))
Arguments
fit |
A tulpa_fit object |
summary |
Logical; if TRUE, return summary statistics. If FALSE, return full posterior draws. |
probs |
Quantiles for summary. Default is c(0.025, 0.5, 0.975). |
Value
If summary = TRUE, a data frame with columns for observation,
factor, and summary statistics. If summary = FALSE, a matrix of
posterior draws.
Examples
## Not run:
# `fit` is a ratio / multi-arm model fitted with latent factors by a consumer
# package (tulpaRatio owns the two-arm formula and the negbin/negbin ratio
# family and reads the latent_factor() spec):
# Get summary
factors <- latent_factors(fit)
head(factors)
# Get full posterior draws
factor_draws <- latent_factors(fit, summary = FALSE)
## End(Not run)
Links available as a <family>_<link> suffix.
Description
Links available as a <family>_<link> suffix.
Usage
link_names()
Family codes the link layer accepts beyond the bare registry names.
Description
The cross product of the link-capable base families and the links, minus each family's own canonical form (which is already a registry name).
Usage
linked_family_names()
Log-likelihood at the posterior mean
Description
Log-likelihood at the posterior mean
Usage
## S3 method for class 'tulpa_fit'
logLik(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
A logLik object.
Numerically stable log(mean(exp(x)))
Description
Numerically stable log(mean(exp(x)))
Usage
logmeanexp(x)
Numerically stable log(exp(a) + exp(b)) elementwise (vectors / scalars)
Description
Numerically stable log(exp(a) + exp(b)) elementwise (vectors / scalars)
Usage
logsumexp_pair(a, b)
Metropolis-Adjusted Langevin Algorithm (MALA)
Description
Sample from a posterior using MALA: a Langevin proposal driven by the gradient of the log posterior, corrected by a Metropolis- Hastings accept/reject step. Each iteration is one log-posterior + one gradient evaluation. The natural stepping stone between random-walk MH and HMC: cheaper per iteration than HMC (no leapfrog integration), better-mixing than RWMH because the drift term moves toward higher density.
Step size epsilon is adapted via dual averaging during warmup to
target an acceptance rate of 0.574 (Roberts & Rosenthal 1998 optimal
for high-dimensional Gaussians).
Usage
mala(
log_posterior,
grad_log_posterior,
init,
n_iter = 2000L,
warmup = n_iter%/%2L,
epsilon = 0.1,
target_accept = 0.574,
mass_diag = NULL,
thin = 1L,
verbose = FALSE
)
Arguments
log_posterior |
Function |
grad_log_posterior |
Function |
init |
Numeric vector: initial state (must give finite log_posterior). |
n_iter |
Total iterations including warmup (default 2000). |
warmup |
Warmup iterations (step-size adaptation + discarded;
default |
epsilon |
Initial step size (default 0.1). Adapted during warmup. |
target_accept |
Target acceptance during warmup adaptation (default 0.574, the Roberts & Rosenthal 1998 optimum). |
mass_diag |
Optional preconditioner: a length- |
thin |
Keep every |
verbose |
Print acceptance + step-size summary at end (default FALSE). |
Value
A list with class tulpa_fit carrying:
-
draws: post-warmup draws. -
means,n_params,n_samples,log_prob. -
accept_prob: per-iteration accepted indicators (post-warmup). -
mean_accept: post-warmup acceptance rate. -
epsilon: final adapted step size. -
inference_mode:"exact". -
inference_tier:1L. -
backend:"mala".
Tier
Tier 1 (Exact). The MH step makes the chain asymptotically correct. Mixing depends on whether the gradient gives useful local geometry – poor for posteriors with very different scales across dimensions (use a preconditioner or HMC instead).
References
Roberts, G. O., & Tweedie, R. L. (1996). Exponential convergence of Langevin distributions and their discrete approximations. Bernoulli, 2(4), 341-363.
Roberts, G. O., & Rosenthal, J. S. (1998). Optimal scaling of discrete approximations to Langevin diffusions. JRSS B, 60(1), 255-268.
Examples
log_post <- function(t) -0.5 * sum((t - c(1, 2))^2)
grad <- function(t) -(t - c(1, 2))
fit <- mala(log_post, grad, init = c(0, 0), n_iter = 1000)
colMeans(fit$draws) # near c(1, 2)
fit$mean_accept # should adapt toward 0.574
Marginal fixed-effect Hessian for spatial-field Laplace fits
Description
The raw fixed-effect block of the joint Hessian gives the conditional
precision on \beta \mid u^*, which under-states uncertainty; this
returns the marginal precision instead.
Details
The correct marginal precision comes from a Schur complement on the joint Hessian at the mode:
H_\beta^{\mathrm{marg}} = X'WX - X'WZ (Z'WZ + Q_u)^{-1} Z'WX
where Z is the obs->latent map (SPDE: projection matrix A;
NNGP: indicator from obs to unique-location field) and Q_u
is the spatial precision at the fitted hyperparameters.
These helpers rebuild the spatial precision in R from the spec and
fitted hyperparameters, then solve via sparse Cholesky. The shape
matches what cpp_laplace_fit_spde / cpp_laplace_fit_gp use
internally – Q construction here is a 1:1 port of spde_qbuilder.h
(orphan ridge included).
MCMC convergence diagnostics
Description
Use diagnostics(), which reads a fit's draws provenance and returns the
diagnostic that applies – chain mixing for MCMC draws, approximation
reliability for deterministic fits. The name mcmc_diagnostics() described
only one of the two branches it already routed between.
Usage
mcmc_diagnostics(
fit,
pars = NULL,
measures = c("rhat", "ess_bulk", "ess_tail"),
probs = c(0.05, 0.95)
)
Arguments
fit |
A |
pars |
Optional character vector of parameter names to restrict to. |
measures |
Character vector selecting which diagnostics to compute, in
output-column order. Available: |
probs |
Numeric probabilities for the quantile-based measures
( |
Value
The value of diagnostics() for fit.
MCMC chain draws from a fit
Description
Returns a fit's posterior draws only when they form a genuine MCMC chain
($draws_kind == "chain", or an untagged legacy fit); for an i.i.d. /
approximation fit (nested Laplace, VI, SMC, ...) it returns NULL, because
chain diagnostics do not apply. This is the accessor diagnostics()
gates on. For the provenance-agnostic posterior sample used by summaries,
see posterior_sample().
Usage
mcmc_draws(fit)
Arguments
fit |
A |
Value
The chain draws matrix/array, or NULL for a non-chain fit.
See Also
posterior_sample(), diagnostics()
Model-averaged predictions
Description
Combine fitted values from several models using native model weights computed
from the pointwise PSIS-LOO (or WAIC) elpd via tulpa_psis() – no loo
package dependency. "loo" / "waic" give stacking weights (the
simplex-optimal predictive combination); "pbma" / "pbma+" give
pseudo-BMA(+) weights. Every model must carry an [n_draws x n_obs] pointwise
log-likelihood (fit$draws$log_lik) over the same observations.
Usage
model_average(
...,
weights = c("loo", "waic", "pbma", "pbma+"),
fitted_fn = fitted
)
Arguments
... |
Named |
weights |
|
fitted_fn |
Function extracting a length- |
Value
A list with averaged (the weighted fitted vector), weights (the
named model weights), and comparison (the compare_models() table).
References
Yao, Vehtari, Simpson & Gelman (2018). Using stacking to average Bayesian predictive distributions. Bayesian Analysis 13(3):917-1007.
See Also
Examples
set.seed(1)
df <- data.frame(x = rnorm(120))
df$y <- rpois(120, exp(0.4 + 0.5 * df$x))
f1 <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, seed = 1L))
f2 <- tulpa(y ~ 1, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, seed = 1L))
ma <- model_average(full = f1, null = f2, weights = "waic")
ma$weights
Moran's I test for spatial autocorrelation in residuals
Description
Tests whether residuals exhibit spatial structure after model fitting. Supports inverse-distance and k-nearest-neighbour weight matrices. No external dependencies (uses normal approximation for inference).
Usage
moran_i(
object,
coords,
weights = c("inverse", "knn"),
k = 10L,
resid_type = "pearson",
alternative = c("two.sided", "greater", "less")
)
Arguments
object |
A fitted model, or a numeric vector of residuals |
coords |
N x 2 coordinate matrix (required) |
weights |
Weight scheme: |
k |
Number of neighbours for knn (default 10) |
resid_type |
Residual type if extracting from model (default |
alternative |
|
Value
An htest object with Moran's I, expected I, and p-value
Examples
set.seed(1)
coords <- cbind(runif(50), runif(50))
resid <- rnorm(50)
moran_i(resid, coords)
Number of divergent transitions
Description
Counts divergent transitions recorded by an HMC/NUTS fit, reading whichever
field the backend populated ($diagnostics$n_divergent,
$diagnostics$divergent_idx, $diagnostics$divergent, or the top-level
$n_divergent / $divergent).
Usage
n_divergent(fit)
Arguments
fit |
A |
Value
Integer count of divergent transitions (0 if none are recorded).
See Also
plot_divergences(), check_diagnostics()
Remove all latent(...) calls from a formula's parse tree
Description
Mirror of nobars() for latent(...) calls.
Usage
no_latent_terms(term)
Arguments
term |
A language object (formula term) |
Value
A language object with all latent(...) calls removed, or NULL
if nothing remains.
Remove all bar terms from a formula's parse tree
Description
Recursively rewrites the formula AST, removing any | or || nodes
found inside parentheses. Returns the fixed-effects-only formula.
Usage
nobars(term)
Arguments
term |
A language object (formula term) |
Value
A language object with all bar terms removed, or NULL if nothing remains
Number of observations in a tulpa fit
Description
Number of observations in a tulpa fit
Usage
## S3 method for class 'tulpa_fit'
nobs(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
Integer observation count.
Map cell identifiers to graph node indices
Description
Translate a vector of original cell identifiers into the 1-based node indices
of a tulpa_adjacency graph, by key (not by row order). Use it to add the
node-index column the model's spatial grouping bar needs to the observation
data, which typically has many rows per cell and a different row order than
the graph.
Usage
node_index(graph, ids)
Arguments
graph |
A |
ids |
A vector of cell identifiers to look up (matched against
|
Value
An integer vector the same length as ids, giving each one's node
index in graph (NA for identifiers absent from the graph).
See Also
Examples
grid <- expand.grid(x = 1:3, y = 1:3)
grid$cell <- paste0("c", seq_len(nrow(grid)))
g <- adjacency(grid, id = "cell")
obs <- data.frame(cell = c("c5", "c1", "c9"))
obs$cell_idx <- node_index(g, obs$cell)
obs
Parse a single random effect bar term
Description
Takes a | or || language object and extracts the grouping variable(s),
effect terms (intercept, slopes), and correlation structure. The bar
operator drives the correlated flag: | -> TRUE (LKJ-Cholesky on the
joint slope vector), || -> FALSE (diagonal covariance, one sigma per
coefficient). This matches lme4 / glmmTMB and lets downstream packages
branch on correlated to choose between independent-sigma and Cholesky
parameterizations.
Usage
parse_bar_term(bar_term)
Arguments
bar_term |
A language object: |
Details
Nested grouping (1 | a/b) is expanded into one spec per level
(a, then a:b). || is preserved as a single spec rather than split into
multiple | bars, so the original user intent (one logical RE term with
diagonal covariance) round-trips through the parsed object.
Value
A list of RE specs (one per grouping level). Each spec has:
-
group_var: character, display label (colon-joined for nested) -
group_vars: character vector, each element a column name -
group_expr: language or NULL (set when grouping is a non-name expr) -
slope_terms: list of language objects (slope LHS terms) -
has_intercept: logical -
correlated: logical (TRUE for|, FALSE for||) -
original: the original bar language object
Pathfinder: variational warm-start via L-BFGS + ELBO scoring
Description
Single-path Pathfinder (Zhang, Carpenter, Gelman, Vehtari 2022):
run L-BFGS toward the posterior mode, fit a Gaussian at the optimum
using the inverse-Hessian estimate, and report draws plus the ELBO.
Cheap, derivative-only, embarrassingly parallel – meant as an HMC
warm-start, an initialiser for imh_laplace(), or a quick
sanity check on the Laplace approximation.
This implementation is single-path only. The full multi-path Pathfinder (K parallel L-BFGS runs + mixture proposal + Pareto- smoothed importance reweighting) is a follow-on. The single-path version is what most users want as a Laplace-equivalent diagnostic.
Usage
pathfinder(
log_posterior,
init,
grad_log_posterior = NULL,
n_draws = 1000L,
max_iter = 100L,
tol = 1e-06,
verbose = FALSE
)
Arguments
log_posterior |
Function |
init |
Numeric vector: initial point for L-BFGS. Should be in the support of the posterior (finite log_posterior). |
grad_log_posterior |
Optional function returning the gradient
of |
n_draws |
Number of draws from the fitted Gaussian (default 1000). |
max_iter |
L-BFGS iteration cap (default 100). |
tol |
Gradient-norm tolerance for L-BFGS convergence (default 1e-6). |
verbose |
Print L-BFGS / ELBO summary at end (default FALSE). |
Value
A list with class tulpa_fit carrying:
-
draws:n_draws x dmatrix of Gaussian draws at the mode. -
means: posterior means (= mode for a Gaussian fit). -
mode: the L-BFGS mode. -
cov: the proposal covariance (solve(-hessian)). -
elbo: Monte-Carlo estimate ofE_q[log p - log q]. -
n_iter: L-BFGS iterations. -
converged: logical. -
inference_mode:"structured". -
inference_tier:2L. -
backend:"pathfinder".
Tier
Tier 2 (Structured). The output is a Gaussian approximation, not
samples from the exact posterior – same epistemic class as
tulpa_laplace(). Pair with imh_laplace() for an exact-tier
upgrade.
References
Zhang, L., Carpenter, B., Gelman, A., & Vehtari, A. (2022). Pathfinder: parallel quasi-Newton variational inference. Journal of Machine Learning Research, 23(306), 1-49.
See Also
imh_laplace() for an exact-tier MH using the Pathfinder
Gaussian as proposal; bridge_sampling() for marginal-likelihood
estimation on the resulting draws.
Examples
# Toy: 2-D conjugate normal.
y <- c(0.5, -0.7)
log_post <- function(t) {
sum(dnorm(y, t, 1, log = TRUE)) +
sum(dnorm(t, 0, sqrt(10), log = TRUE))
}
pf <- pathfinder(log_post, init = c(0, 0), n_draws = 2000)
pf$mode # near c(0.45, -0.64)
pf$elbo
PIT (Probability Integral Transform) residuals
Description
For each observation, computes the quantile of the observed value within the posterior predictive distribution. If the model is correct, PIT residuals follow Uniform(0, 1).
Usage
pit_residuals(object, ...)
## Default S3 method:
pit_residuals(object, observed = NULL, nsim = 250L, seed = 123L, ...)
Arguments
object |
A fitted model with a |
... |
Passed to methods. |
observed |
Observed response vector (required if |
nsim |
Number of simulations (default 250) |
seed |
Random seed (default 123) |
Details
For integer-valued responses, a randomisation step avoids discrete artefacts: the residual is drawn uniformly between P(sim < obs) and P(sim <= obs).
Value
Numeric vector of length n_obs with values in [0, 1]
Plot fixed-effect posteriors
Description
Plot fixed-effect posteriors
Usage
## S3 method for class 'tulpa_fit'
plot(x, type = c("density", "trace", "pairs", "smooth"), term = NULL, ...)
Arguments
x |
A |
type |
One of |
term |
For |
... |
Passed to plotting functions. |
Value
The input x, returned invisibly. Called for the side effect of
producing base-graphics plots of the fixed-effect posteriors.
Plot method for tulpa_prior_predict
Description
Density overlay of prior predictive draws. Requires bayesplot; falls back to base graphics matplot of a subset of draws otherwise.
Usage
## S3 method for class 'tulpa_prior_predict'
plot(x, process = 1L, max_draws = 50L, ...)
Arguments
x |
A tulpa_prior_predict object |
process |
Process index or name (multi-process families) |
max_draws |
Maximum draws to overlay. Default 50. |
... |
Passed through. |
Value
A ggplot object (via bayesplot) when bayesplot is installed;
otherwise NULL invisibly, after drawing a base-graphics overlay. Called
for the density overlay of the prior predictive draws.
Plot method for spatiotemporal effects
Description
Plot method for spatiotemporal effects
Usage
## S3 method for class 'tulpa_st_summary'
plot(x, type = "heatmap", ...)
Arguments
x |
Spatiotemporal effects object |
type |
Plot type: |
... |
Additional arguments passed to plotting functions |
Value
A ggplot object when ggplot2 is installed; otherwise NULL
invisibly, after drawing a base-graphics plot. Called for the side effect
of visualizing the spatiotemporal interaction effects.
Plot method for tulpa_svc_posterior
Description
Plot method for tulpa_svc_posterior
Usage
## S3 method for class 'tulpa_svc_posterior'
plot(x, term = 1, type = "mean", ...)
Arguments
x |
A tulpa_svc_posterior object |
term |
Which term to plot (name or index). Default: first term. |
type |
Plot type: "mean" (default), "sd", or quantile (e.g., "q50") |
... |
Additional arguments passed to plotting functions |
Value
A ggplot object when ggplot2 is installed; otherwise NULL
invisibly, after drawing a base-graphics map. Called for the side effect of
mapping the selected spatially-varying coefficient.
Plot method for tulpa_temporal_posterior
Description
Plot method for tulpa_temporal_posterior
Usage
## S3 method for class 'tulpa_temporal_posterior'
plot(x, component = NULL, type = "ribbon", ...)
Arguments
x |
A tulpa_temporal_posterior object |
component |
Which component to plot (for multi-scale). Default: first. |
type |
Plot type: "ribbon" (default) or "line" |
... |
Additional arguments passed to plotting functions |
Value
A ggplot object when ggplot2 is installed; otherwise NULL
invisibly, after drawing a base-graphics plot. Called for the side effect
of plotting the temporal-effect posterior.
Plot method for tulpa_tvc_posterior
Description
Plot method for tulpa_tvc_posterior
Usage
## S3 method for class 'tulpa_tvc_posterior'
plot(x, term = 1, type = "ribbon", ...)
Arguments
x |
A tulpa_tvc_posterior object |
term |
Which term to plot (name or index). Default: first term. |
type |
Plot type: "ribbon" (default) or "line" |
... |
Additional arguments passed to plotting functions |
Value
A ggplot object when ggplot2 is installed; otherwise NULL
invisibly, after drawing a base-graphics plot. Called for the side effect
of plotting the selected temporally-varying coefficient.
Plot Autocorrelation Functions
Description
Creates autocorrelation function (ACF) plots for selected parameters. High autocorrelation indicates slow mixing and low effective sample size.
Usage
plot_acf(fit, pars = NULL, lags = 25, n_pars = 6)
Arguments
fit |
A |
pars |
Character vector of parameter names. If NULL, selects worst-mixing parameters based on ESS. |
lags |
Maximum number of lags to compute (default: 25). |
n_pars |
Maximum number of parameters to plot (default: 6). |
Details
Ideal ACF plots show rapid decay to zero. Slow decay indicates high autocorrelation, which reduces effective sample size and may indicate poor mixing.
Value
A ggplot object (if ggplot2 available) or base R plot (invisible).
See Also
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_acf(fit)
plot_acf(fit, lags = 10)
Base R ACF plot
Description
Base R ACF plot
Usage
plot_acf_base(draws, pars, lags)
Diagnostic Plotting Functions for tulpa Models
Description
Visual diagnostic tools for MCMC convergence assessment. All functions provide base R fallbacks when ggplot2/bayesplot are unavailable.
Creates a combined diagnostic figure with Rhat, ESS, trace plot, and
energy/ACF panels. Requires the patchwork package for layout.
Usage
plot_diagnostics(fit, pars = NULL)
Arguments
fit |
A |
pars |
Character vector of parameter names for trace plot. If NULL, uses the parameter with worst Rhat. |
Details
Creates a 2x2 grid:
Top left: Rhat plot
Top right: ESS plot
Bottom left: Trace for worst parameter
Bottom right: Energy (HMC) or ACF (other)
Value
A combined plot (ggplot + patchwork) or NULL if requirements not met.
See Also
diagnostic_summary(), plot_rhat(), plot_ess()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_diagnostics(fit)
Plot Divergent Transitions
Description
Creates visualizations to investigate divergent transitions. Parallel coordinates and scatter plots highlight where in parameter space divergences occur.
Usage
plot_divergences(fit, pars = NULL, type = c("parcoord", "scatter"))
Arguments
fit |
A |
pars |
Character vector of parameter names. If NULL, uses variance parameters. |
type |
Plot type: "parcoord" (parallel coordinates) or "scatter". |
Details
Divergent transitions indicate regions of high posterior curvature that the sampler cannot efficiently explore. Common causes:
Very narrow funnels (hierarchical models)
Strong correlations
Multi-modality
Value
A ggplot object or base R plot (invisible).
See Also
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_divergences(fit)
plot_divergences(fit, type = "scatter")
Parallel coordinates plot for divergences
Description
Parallel coordinates plot for divergences
Usage
plot_divergences_parcoord(draws_df, pars)
Base R parallel coordinates
Description
Base R parallel coordinates
Usage
plot_divergences_parcoord_base(scaled, pars, divergent)
Scatter plot matrix for divergences
Description
Scatter plot matrix for divergences
Usage
plot_divergences_scatter(draws_df, pars)
Plot Energy Diagnostic (E-BFMI)
Description
Creates overlaid histograms of marginal energy and energy transition, along with the E-BFMI statistic. Low E-BFMI indicates poor exploration of the posterior.
Usage
plot_energy(fit)
Arguments
fit |
A |
Details
E-BFMI (Energy Bayesian Fraction of Missing Information) compares the distribution of energy levels to energy transitions. Values below 0.3 indicate the sampler may not be exploring the full posterior.
Value
A ggplot object (if ggplot2 available) or base R plot.
See Also
diagnostic_summary(), check_diagnostics()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_energy(fit)
Base R energy plot
Description
Base R energy plot
Usage
plot_energy_base(energy, energy_diff, e_bfmi, status)
Plot Effective Sample Size Diagnostic
Description
Creates a visual display of effective sample size (ESS) for all parameters, expressed as a ratio of ESS to total samples. Low ESS indicates high autocorrelation.
Usage
plot_ess(fit, type = c("bulk", "tail"), threshold = 400, pars = NULL)
Arguments
fit |
A |
type |
Type of ESS: "bulk" (default) or "tail". |
threshold |
Minimum acceptable ESS (default: 400). |
pars |
Character vector of parameter names to include. |
Details
ESS/iter ratio interpretation:
Green: ESS >= threshold
Yellow: threshold/2 <= ESS < threshold
Red: ESS < threshold/2
Value
A ggplot object (if ggplot2 available) or base R plot (invisible).
See Also
plot_rhat(), diagnostic_summary(), diagnostics()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_ess(fit)
plot_ess(fit, type = "tail")
Base R ESS plot
Description
Base R ESS plot
Usage
plot_ess_base(diag, threshold, type)
Plot spatial predictions as a map
Description
Create publication-ready maps from a tulpa spatial fit: the fitted response surface or the prediction uncertainty (credible-interval width), on the response scale.
Usage
plot_map(
x,
coords = NULL,
what = c("fitted", "uncertainty"),
summary = c("median", "mean", "q2.5", "q97.5", "sd"),
newdata = NULL,
title = NULL,
palette = "viridis",
points = FALSE,
point_color = "grey30",
point_size = 0.5,
na_color = "transparent",
crs = NULL,
legend_title = NULL,
...
)
Arguments
x |
A |
coords |
A data frame or matrix with spatial coordinates (columns
named 'x'/'y', 'X'/'Y', 'lon'/'lat', 'longitude'/'latitude', or
'Easting'/'Northing'). Required if |
what |
What to plot: "fitted" (default, the response-scale point prediction) or "uncertainty" (the 95% credible-interval width). |
summary |
Which summary statistic for |
newdata |
Optional data frame with prediction locations and covariates.
If NULL and |
title |
Plot title. If NULL, auto-generated based on |
palette |
Color palette: "viridis" (default), "magma", "plasma", "inferno", "cividis", "mako", "rocket", or a custom vector of colors. |
points |
Logical; if TRUE, overlay observation points. Default FALSE. |
point_color |
Color for observation points. Default "grey30". |
point_size |
Size for observation points. Default 0.5. |
na_color |
Color for NA values. Default "transparent". |
crs |
Coordinate reference system (proj4 string or EPSG code). If NULL, uses planar coordinates. |
legend_title |
Title for the color legend. If NULL, auto-generated. |
... |
Additional arguments passed to ggplot2 theme functions. |
Details
This function provides a streamlined workflow for visualizing spatial predictions from tulpa models. It handles:
Extracting predictions from tulpa_fit objects
Converting to appropriate spatial format (stars/sf)
Creating publication-quality maps with sensible defaults
Uncertainty visualization via credible interval width
For custom maps or more control, extract predictions using predict() and
use ggplot2 directly with geom_stars() or geom_sf().
Value
A ggplot2 object that can be further customized.
Required packages
This function requires ggplot2. For raster-style maps, stars and sf
are also needed. Install with:
install.packages(c("ggplot2", "stars", "sf"))
See Also
predict.tulpa_fit() for predictions at new locations
Examples
if (requireNamespace("ggplot2", quietly = TRUE)) {
set.seed(123)
n_sites <- 20
df <- data.frame(
y = rbinom(n_sites, 20, 0.4),
elevation = rnorm(n_sites),
site = factor(seq_len(n_sites)),
lon = runif(n_sites),
lat = runif(n_sites)
)
adj <- matrix(0, n_sites, n_sites)
for (i in 1:(n_sites - 1)) adj[i, i + 1] <- adj[i + 1, i] <- 1
fit <- tulpa(
y ~ elevation + spatial(site),
data = df,
family = "binomial",
n_trials = rep(20L, n_sites),
spatial = spatial_car(adj, group_var = "site"),
mode = "laplace"
)
# Areal (CAR/ICAR) fits carry no point coordinates, so pass `coords`:
cc <- df[, c("lon", "lat")]
plot_map(fit, coords = cc) # fitted response surface
plot_map(fit, what = "uncertainty", coords = cc)
plot_map_panel(fit, coords = cc) # both side by side
}
Plot multiple maps in a grid
Description
Create a multi-panel figure with the fitted-value map and the prediction-uncertainty map side by side.
Usage
plot_map_panel(x, newdata = NULL, ncol = 2, ...)
Arguments
x |
A |
newdata |
Optional data frame with prediction locations. |
ncol |
Number of columns in the grid. Default 2. |
... |
Additional arguments passed to |
Value
A patchwork object (if patchwork is installed) or a list of ggplots.
Examples
if (requireNamespace("ggplot2", quietly = TRUE)) {
set.seed(123)
n_sites <- 20
df <- data.frame(
y = rbinom(n_sites, 20, 0.4),
elevation = rnorm(n_sites),
site = factor(seq_len(n_sites)),
lon = runif(n_sites),
lat = runif(n_sites)
)
adj <- matrix(0, n_sites, n_sites)
for (i in 1:(n_sites - 1)) adj[i, i + 1] <- adj[i + 1, i] <- 1
fit <- tulpa(
y ~ elevation + spatial(site),
data = df,
family = "binomial",
n_trials = rep(20L, n_sites),
spatial = spatial_car(adj, group_var = "site"),
mode = "laplace"
)
cc <- df[, c("lon", "lat")]
plot_map_panel(fit, coords = cc)
plot_map_panel(fit, coords = cc, ncol = 1)
}
Plot Bivariate Parameter Posteriors (Pairs Plot)
Description
Creates a pairs plot showing bivariate relationships between parameters. Divergent transitions (if present) are highlighted to help identify problematic posterior regions.
Usage
plot_pairs(
fit,
pars = NULL,
highlight_divergent = TRUE,
n_pars = 5,
alpha = 0.3
)
Arguments
fit |
A |
pars |
Character vector of parameter names. If NULL, selects main variance parameters. |
highlight_divergent |
Logical; highlight divergent transitions in red (default: TRUE). |
n_pars |
Maximum number of parameters (default: 5). |
alpha |
Point transparency (default: 0.3). |
Details
Pairs plots help identify:
Strong correlations between parameters (potential non-identifiability)
Multimodality
Regions where divergences cluster (indicating problematic geometry)
Value
A ggplot object (if ggplot2/GGally available) or base R plot.
See Also
plot_divergences(), diagnostics()
Examples
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_pairs(fit)
plot_pairs(fit, n_pars = 2)
Base R pairs plot
Description
Base R pairs plot
Usage
plot_pairs_base(draws_df, pars, has_divergent, alpha)
Plot Rhat Convergence Diagnostic
Description
Creates a visual display of Rhat values for all parameters, with color-coding to highlight convergence issues. Rhat values > 1.01 indicate potential convergence problems.
Usage
plot_rhat(fit, threshold = 1.01, pars = NULL)
Arguments
fit |
A |
threshold |
Rhat threshold for warnings (default: 1.01). |
pars |
Character vector of parameter names to include. If NULL (default), includes all main parameters (excludes high-dimensional RE/spatial). |
Details
Color coding:
Green: Rhat < 1.01 (converged)
Yellow: 1.01 <= Rhat < 1.05 (borderline)
Red: Rhat >= 1.05 (not converged)
Value
A ggplot object (if ggplot2 available) or base R plot (invisible).
See Also
plot_ess(), diagnostic_summary(), diagnostics()
Examples
# Diagnostic plots require a fitted tulpa model
# See tulpa() examples for fitting models
set.seed(123)
df <- data.frame(x = rnorm(60))
df$y <- rpois(60, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson", mode = "hmc",
control = list(n_iter = 500L, warmup = 250L, n_chains = 2L,
seed = 1L))
plot_rhat(fit)
Base R Rhat plot
Description
Base R Rhat plot
Usage
plot_rhat_base(diag, threshold)
Fit a post-hoc linear model on estimated parameters
Description
Useful for exploring drivers of occupancy/detection/abundance variation after model fitting. Fits a weighted linear model and optionally generates bootstrap confidence intervals.
Usage
post_hoc_lm(
formula,
data,
weights = NULL,
n_boot = 1000L,
probs = c(0.025, 0.975)
)
Arguments
formula |
Model formula (e.g., |
data |
A data.frame with response and predictors. |
weights |
Optional weights (e.g., inverse of standard errors). |
n_boot |
Number of bootstrap replicates for CI (default 1000, 0 to skip). |
probs |
Quantile probabilities for bootstrap CI (default 0.025, 0.975). |
Value
A list of class "post_hoc_lm" with:
- summary
data.frame of coefficient estimates and CIs
- lm_fit
the underlying
lmobject- boot_coefs
matrix of bootstrap coefficient samples (if
n_boot > 0)- R2
R-squared from the fitted model
Examples
# Explore drivers of per-site estimates after fitting a model.
site <- data.frame(
psi_hat = c(0.2, 0.5, 0.8, 0.4, 0.6, 0.3),
se = c(0.05, 0.04, 0.06, 0.05, 0.03, 0.05),
trait = c(1.0, 2.5, 3.8, 1.9, 3.1, 1.2)
)
fit <- post_hoc_lm(psi_hat ~ trait, data = site,
weights = 1 / site$se^2, n_boot = 200L)
fit
Posterior predictive replicates
Description
Draw replicated responses from the posterior predictive distribution: the linear predictor is rebuilt per posterior draw (fixed effects, formula random effects, offset, and a posterior-mean SPDE field when present) and pushed through the family's sampling distribution.
Fits carrying posterior draws use them directly (fixed and random effects
jointly per draw). The Laplace tier samples the fixed effects from the
Gaussian approximation N(coef(fit), vcov(fit)) and holds the random
effects at their posterior mode, so its replicates understate the RE
posterior uncertainty. At newdata the prediction is population level
(random effects at zero), matching predict.tulpa_fit().
Usage
posterior_predict(object, ...)
## S3 method for class 'tulpa_fit'
posterior_predict(
object,
newdata = NULL,
ndraws = NULL,
n_trials = NULL,
seed = NULL,
...
)
Arguments
object |
A |
... |
Passed to methods. |
newdata |
Optional data frame of covariates to predict at. Population
level (fixed effects only); |
ndraws |
Number of posterior draws to use. Defaults to all stored draws, or 400 on the draw-free Laplace tier. |
n_trials |
Binomial / beta-binomial trial counts for the replicates.
Defaults to the training trials when |
seed |
Optional integer seed (RNG state is restored on exit). |
Value
A ndraws x n_obs numeric matrix of replicated responses.
See Also
pp_check(), which uses these replicates; simulate.tulpa_fit().
Examples
set.seed(1)
d <- data.frame(y = rpois(100, 4), x = rnorm(100))
fit <- tulpa(y ~ x, data = d, family = "poisson", mode = "laplace")
yrep <- posterior_predict(fit, ndraws = 100)
dim(yrep) # 100 x 100
Posterior parameter sample from a fit
Description
Returns a fit's posterior draws for summary purposes (quantiles, derived
quantities, density plots), regardless of how they were produced – an MCMC
chain, a nested-Laplace node mixture, or a variational sample all answer
here. For the chain-only view used by convergence diagnostics, see
mcmc_draws().
Usage
posterior_sample(fit)
Arguments
fit |
A |
Details
A fit that carries no draws returns NULL with a message naming its backend,
the posterior representation it carries instead, and the accessor that turns
that representation into draws where one exists – an
absent draws matrix is a property of the backend, not a failure of the
accessor, and saying which is the difference between a diagnosable answer and
a bare NULL.
Value
The posterior draws matrix/array, or NULL if the fit carries none.
See Also
mcmc_draws(), tulpa_posterior_draws(), diagnostics()
Examples
set.seed(1)
df <- data.frame(x = rnorm(80))
df$y <- rpois(80, exp(0.5 + 0.3 * df$x))
fit <- tulpa(y ~ x, data = df, family = "poisson")
dim(posterior_sample(fit))
Posterior predictive check
Description
Generate posterior predictive checks for a fitted tulpa model. Compares observed data to replicated data from the posterior predictive distribution.
Usage
pp_check(object, ...)
## S3 method for class 'tulpa_fit'
pp_check(
object,
type = c("dens_overlay", "scatter", "intervals", "stat"),
component = NULL,
stat = mean,
ndraws = 50,
...
)
Arguments
object |
A |
... |
Additional arguments passed to bayesplot functions |
type |
Type of check: "dens_overlay", "scatter", "intervals", "stat" |
component |
Which component: "numerator", "denominator", or "both" |
stat |
Function for "stat" type (default: mean) |
ndraws |
Number of posterior draws to use (default: 50 for plots) |
Value
A ggplot object
Examples
# pp_check is a generic; model packages (e.g. tulpaObs, tulpaRatio) provide
# the posterior-predictive method for their fits.
set.seed(123)
n <- 200L
df <- data.frame(y = rpois(n, 5), x = rnorm(n),
site = factor(rep(1:10, each = 20)))
fit <- tulpa(y ~ x + (1 | site), data = df, family = "poisson",
mode = "hmc", control = list(n_iter = 500, warmup = 250))
# Density overlay (dispatches to the model package's pp_check method).
if (requireNamespace("bayesplot", quietly = TRUE)) {
pp_check(fit, type = "dens_overlay")
}
Single component pp_check
Description
Single component pp_check
Usage
pp_check_single(y, yrep, type, stat, ndraws, title_suffix, ...)
Predict at new covariate values (population level)
Description
Prediction of the linear predictor at newdata, on the link or response
scale. The fixed-effect part is X beta with credible bounds from the
fixed-effect covariance (vcov()). For a fit carrying a continuous spatial
field, the posterior-mean field is interpolated (kriged) to the newdata
coordinates and added to the linear predictor by default, so predict()
gives the conditional (location-specific) prediction. Three continuous field
families are supported: an SPDE Matern field (spatial_spde()), projected
through the mesh; a Hilbert-space GP field (spatial_gp(approx = "hsgp")),
where the Laplacian basis is re-evaluated at the new coordinates (with the
training centring / boundary); and a GP / NNGP field (spatial_gp()),
interpolated by the NNGP conditional mean at each new location's nearest
training locations. The HSGP and GP/NNGP fields are marginalised over the
hyperparameter grid (not plugged in at the posterior mean). Ordinary random
effects are held at zero (population level); add group effects from ranef()
when needed.
Usage
## S3 method for class 'tulpa_fit'
predict(
object,
newdata = NULL,
type = c("link", "response"),
se.fit = FALSE,
level = 0.95,
include_field = TRUE,
...
)
Arguments
object |
A |
newdata |
Data frame of covariates (and, for an SPDE fit, the coordinate
columns named in the spec's coordinate formula). If |
type |
|
se.fit |
If |
level |
Credible-interval level (default 0.95). |
include_field |
For a continuous-spatial fit (SPDE, HSGP, or GP/NNGP),
add the kriged field to the prediction (default |
... |
Ignored. |
Value
If se.fit = FALSE, a numeric vector. If se.fit = TRUE, a data
frame with fit, se.fit (link scale), lower, upper on the requested
scale.
Prepare latent factor data for HMC
Description
Prepare latent factor data for HMC
Usage
prepare_latent_for_hmc(latent, N)
Arguments
latent |
A validated tulpa_latent object (or NULL) |
N |
Number of observations |
Value
List with latent factor data for C++
Prepare map data from data frame
Description
Prepare map data from data frame
Usage
prepare_map_data_from_df(x, coords, what, summary)
Prepare map data from tulpa_fit object
Description
Prepare map data from tulpa_fit object
Usage
prepare_map_data_from_fit(fit, what, summary, newdata, coords = NULL)
Print method for diagnostic summary
Description
Print method for diagnostic summary
Usage
## S3 method for class 'tulpa_diagnostic_summary'
print(x, ...)
Arguments
x |
A tulpa_diagnostic_summary object. |
... |
Ignored. |
Value
The input x, returned invisibly. Called for the side effect of
printing the diagnostic summary to the console.
Print method for Geweke test
Description
Print method for Geweke test
Usage
## S3 method for class 'tulpa_geweke'
print(x, ...)
Arguments
x |
A tulpa_geweke object. |
... |
Ignored. |
Value
The input x, returned invisibly. Called for the side effect of
printing the Geweke convergence diagnostic to the console.
Print method for tulpa_gp
Description
Print method for tulpa_gp
Usage
## S3 method for class 'tulpa_gp'
print(x, ...)
Arguments
x |
A tulpa_gp object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the Gaussian-process spatial specification to the console.
Print method for tulpa_hsgp
Description
Print method for tulpa_hsgp
Usage
## S3 method for class 'tulpa_hsgp'
print(x, ...)
Arguments
x |
A tulpa_hsgp object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the Hilbert-space GP spatial specification to the console.
Print method for tulpa_latent
Description
Print method for tulpa_latent
Usage
## S3 method for class 'tulpa_latent'
print(x, ...)
Arguments
x |
A tulpa_latent object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the latent factor specification to the console.
Print method for tulpa_multiscale
Description
Print method for tulpa_multiscale
Usage
## S3 method for class 'tulpa_multiscale'
print(x, ...)
Arguments
x |
A tulpa_multiscale object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the multi-scale spatial specification to the console.
Print method for nested-Laplace fits
Description
Compact one-screen summary of a tulpa_nested_laplace() or
tulpa_nested_laplace_joint() fit: the hyperparameters integrated over, the
outer-grid size, the outer Pareto-\hat{k} accuracy diagnostic when
present, and the wall-clock timing line ("fit in 5h 25m (grid 2h 09m)")
when fit$timing is attached. Inherited by the single-block joint and
multi-block joint subclasses.
Usage
## S3 method for class 'tulpa_nested_laplace'
print(x, ...)
Arguments
x |
A |
... |
Ignored. |
Value
x, invisibly.
Print method for tulpa_prior
Description
Print method for tulpa_prior
Usage
## S3 method for class 'tulpa_prior'
print(x, ...)
Arguments
x |
A tulpa_prior object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the prior specification to the console.
Print method for tulpa_prior_predict
Description
Print method for tulpa_prior_predict
Usage
## S3 method for class 'tulpa_prior_predict'
print(x, ...)
Arguments
x |
A tulpa_prior_predict object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing a summary of the prior predictive draws to the console.
Print method for tulpa_priors
Description
Print method for tulpa_priors
Usage
## S3 method for class 'tulpa_priors'
print(x, ...)
Arguments
x |
A tulpa_priors object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the full prior specification to the console.
Print method for tulpa_rsr
Description
Print method for tulpa_rsr
Usage
## S3 method for class 'tulpa_rsr'
print(x, ...)
Arguments
x |
A tulpa_rsr object |
... |
Passed to underlying print method |
Value
The input x, returned invisibly. Called for the side effect of
printing the spatial specification and its restricted-spatial-regression
modifier to the console.
Print method for tulpa_simulate
Description
Print method for tulpa_simulate
Usage
## S3 method for class 'tulpa_simulate'
print(x, ...)
Arguments
x |
A tulpa_simulate object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing a summary of the simulated datasets to the console.
Print method for tulpa_spatial
Description
Print method for tulpa_spatial
Usage
## S3 method for class 'tulpa_spatial'
print(x, ...)
Arguments
x |
A tulpa_spatial object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the spatial specification to the console.
Print method for tulpa_svc
Description
Print method for tulpa_svc
Usage
## S3 method for class 'tulpa_svc'
print(x, ...)
Arguments
x |
A tulpa_svc object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the spatially-varying-coefficient specification to the console.
Print method for tulpa_svc_posterior
Description
Print method for tulpa_svc_posterior
Usage
## S3 method for class 'tulpa_svc_posterior'
print(x, ...)
Arguments
x |
A tulpa_svc_posterior object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing a summary of the spatially-varying-coefficient posterior to the
console.
Print method for tulpa_temporal
Description
Print method for tulpa_temporal
Usage
## S3 method for class 'tulpa_temporal'
print(x, ...)
Arguments
x |
A tulpa_temporal object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the temporal specification to the console.
Print method for tulpa_temporal_gp
Description
Print method for tulpa_temporal_gp
Usage
## S3 method for class 'tulpa_temporal_gp'
print(x, ...)
Arguments
x |
A tulpa_temporal_gp object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the Gaussian-process temporal specification to the console.
Print method for tulpa_temporal_multiscale
Description
Print method for tulpa_temporal_multiscale
Usage
## S3 method for class 'tulpa_temporal_multiscale'
print(x, ...)
Arguments
x |
A tulpa_temporal_multiscale object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the multi-scale temporal specification to the console.
Print method for tulpa_temporal_posterior
Description
Print method for tulpa_temporal_posterior
Usage
## S3 method for class 'tulpa_temporal_posterior'
print(x, ...)
Arguments
x |
A tulpa_temporal_posterior object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing a summary of the temporal-effect posterior to the console.
Print method for tulpa_tvc
Description
Print method for tulpa_tvc
Usage
## S3 method for class 'tulpa_tvc'
print(x, ...)
Arguments
x |
A tulpa_tvc object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing the temporally-varying-coefficient specification to the console.
Print method for tulpa_tvc_posterior
Description
Print method for tulpa_tvc_posterior
Usage
## S3 method for class 'tulpa_tvc_posterior'
print(x, ...)
Arguments
x |
A tulpa_tvc_posterior object |
... |
Ignored |
Value
The input x, returned invisibly. Called for the side effect of
printing a summary of the temporally-varying-coefficient posterior to the
console.
Format a single prior for printing
Description
Thin wrapper around format() for tulpa_prior objects. Each
prior_*() constructor attaches a subclass (e.g. tulpa_prior_normal)
so adding a new distribution is one new format.tulpa_prior_<dist>()
method – no central if/else to extend.
Usage
print_prior(prior, indent = "")
Arguments
prior |
A tulpa_prior object |
indent |
Indentation string |
Beta prior
Description
Specify a beta prior for a parameter bounded in (0, 1).
Usage
prior_beta(alpha = 1, beta = 1)
Arguments
alpha |
First shape parameter. Must be positive. |
beta |
Second shape parameter. Must be positive. |
Details
alpha = beta = 1: Uniform(0, 1)
alpha = beta = 2: Symmetric, peaked at 0.5
alpha > beta: Skewed toward 1
alpha < beta: Skewed toward 0
Value
A tulpa_prior object
Examples
prior_beta(1, 1) # Uniform
prior_beta(2, 2) # Symmetric, centered at 0.5
prior_beta(5, 2) # Skewed toward 1 (for high autocorrelation)
Exponential prior
Description
Specify an exponential prior for a positive parameter.
Usage
prior_exponential(rate = 1)
Arguments
rate |
Rate parameter (lambda). Must be positive. |
Value
A tulpa_prior object
Examples
prior_exponential(1)
Build a prior list for tulpa_nested_laplace() from a tulpa spec object
Description
Validates a tulpa_temporal or tulpa_spatial specification against
data, then converts it to the prior list shape consumed by
tulpa_nested_laplace(). Mainly an internal helper for callers that already
have a fitted spec; users typically pass spec + data directly to
tulpa_nested_laplace() instead.
Supported spec types:
-
tulpa_temporalwithtype in {"rw1", "rw2", "ar1"} -
tulpa_spatial, arealtype in {"car", "icar", "car_proper", "bym2"}. An areal spec returns the graph fields only –type,spatial_idx,n_spatial_units,adj_row_ptr,adj_col_idx,n_neighbors– plusscale_factorfor bym2 and the eigenvalue-derivedrho_boundsfor proper CAR. No grid field is returned for any areal type: the outer axes are built later by the family's registrydefaults()closure, and proper CAR's(tau, rho)grid is built there from those bounds. -
tulpa_gp/tulpa_hsgp, continuoustype in {"gp", "nngp", "hsgp"}: validated againstdataand routed to thenngp/hsgpnested kernel through the shared converter.
SPDE is the one continuous field not built here – it carries its own
(range, sigma) FEM integrator; call fit_spde() with the spatial_spde()
spec.
Usage
prior_from_spec(spec, data)
Arguments
spec |
A |
data |
Data frame the spec resolves time/group/site indices against. |
Value
A prior list ready for tulpa_nested_laplace().
Gamma prior
Description
Specify a gamma prior for a positive parameter.
Usage
prior_gamma(shape = 2, rate = 0.1)
Arguments
shape |
Shape parameter (alpha). Must be positive. |
rate |
Rate parameter (beta). Must be positive. |
Details
Mean = shape/rate, Variance = shape/rate^2.
Value
A tulpa_prior object
Examples
prior_gamma(2, 0.1) # Mean = 20, weakly informative
prior_gamma(1, 1) # Exponential(1)
Half-Cauchy prior
Description
Specify a half-Cauchy prior for a positive parameter. Has heavier tails than half-normal, often used for variance parameters.
Usage
prior_half_cauchy(scale = 2.5)
Arguments
scale |
Scale parameter. Must be positive. |
Value
A tulpa_prior object
Examples
prior_half_cauchy(2.5)
Half-normal prior
Description
Specify a half-normal (truncated at 0) prior for a positive parameter.
Usage
prior_half_normal(sd = 1)
Arguments
sd |
Scale parameter. Must be positive. |
Value
A tulpa_prior object
Examples
prior_half_normal(1)
Normal prior
Description
Specify a normal prior for a parameter.
Usage
prior_normal(mean = 0, sd = 2.5)
Arguments
mean |
Prior mean. Default 0. |
sd |
Prior standard deviation. Must be positive. |
Value
A tulpa_prior object
Examples
prior_normal(0, 2.5)
prior_normal(0, 1)
Penalized complexity (PC) prior
Description
Specify a PC prior for a positive parameter (typically a standard deviation). PC priors shrink toward simpler models by penalizing deviation from a base model.
Usage
prior_pc(U = 1, alpha = 0.01)
Arguments
U |
Upper bound. P(x > U) = alpha. |
alpha |
Tail probability. Default 0.01. |
Details
The PC prior is specified via: P(sigma > U) = alpha
U is the upper bound you consider "large"
alpha is the probability of exceeding it
This implies an exponential prior with rate = -log(alpha) / U.
Value
A tulpa_prior object
References
Simpson, D., Rue, H., Riebler, A., Martins, T. G., & Sorbye, S. H. (2017). Penalising model component complexity: A principled, practical approach to constructing priors. Statistical Science, 32(1), 1-28.
Examples
prior_pc(U = 1, alpha = 0.01) # P(sigma > 1) = 0.01
prior_pc(U = 0.5, alpha = 0.05) # Tighter, P(sigma > 0.5) = 0.05
Prior predictive simulation
Description
Draw datasets from the prior predictive distribution: parameters are sampled from their priors (no data conditioning) and pushed through the model's linear predictor and the family's simulator.
Useful for checking whether priors imply plausible data ranges before fitting.
Usage
prior_predict(
formula,
family,
data,
priors = NULL,
n_draws = 100,
seed = NULL,
...
)
Arguments
formula |
A model formula (e.g., |
family |
A |
data |
Data frame containing covariates and grouping factors. Used for dimensions and design matrices; the response column may be absent or NA. |
priors |
Prior specification ( |
n_draws |
Number of prior parameter draws. Default 100. |
seed |
Optional integer seed for reproducibility. |
... |
Passed to |
Value
A tulpa_prior_predict object: a list with
-
y: list of lengthn_draws, each element the simulated response for that draw (matching whatever shapefamily$simulate_fnreturns). -
theta: list of lengthn_drawsof parameter draws (beta,sigma, RE coefficientsu, family-specific extras). -
linpred: list of lengthn_draws, each a list of linear predictor vectors per process. -
family: the family used. -
n_draws,n_obs.
Examples
# Toy Gaussian family for illustration
fam <- tulpa_family(
name = "gaussian",
simulate_fn = function(eta, params, n_obs, ...) {
rnorm(n_obs, eta[[1]], params$sigma_y)
},
extra_params = list(sigma_y = prior_half_normal(1))
)
df <- data.frame(y = rep(0, 20), x = rnorm(20))
pp <- prior_predict(y ~ x, fam, df, n_draws = 50, seed = 1)
length(pp$y) # 50
Show default priors for a tulpa family
Description
Display the default prior specifications used for each model family. Useful for understanding what priors are applied before fitting and as a starting point for customization.
Usage
priors_default(family = NULL, spatial = FALSE, temporal = FALSE)
Arguments
family |
A tulpa family object (e.g. a ratio family constructor from a model package such as tulpaRatio). If NULL (default), shows defaults for all families. |
spatial |
Logical; if TRUE, include spatial priors. Default FALSE. |
temporal |
Logical; if TRUE, include temporal priors. Default FALSE. |
Details
Default priors in tulpa follow these principles:
-
Fixed effects (beta): Normal(0, 2.5) - weakly informative, allows coefficients roughly in [-5, 5] on the link scale.
-
Random effect SD (sigma): PC prior with P(sigma > 1) = 0.01 - favors simpler models with smaller variance components.
-
Overdispersion (phi): PC prior with P(phi > 10) = 0.01 on the NB2 size
phi(largerphiis less overdispersion;phi -> Infis the Poisson limit). The default keepsphifinite, allowing overdispersion while penalising extreme values. -
Temporal correlation (rho): Beta(2, 2) - symmetric prior centered at 0.5, appropriate for AR(1) correlation.
-
Spatial mixing (rho_spatial): Beta(1, 1) = Uniform(0, 1) - no prior preference for structured vs. unstructured spatial variation.
Value
Invisibly returns a tulpa_priors object with the defaults.
Primarily called for its side effect of printing.
See Also
tulpa_priors() for creating custom priors
Examples
# Defaults for all families (no family argument)
priors_default()
# Family-specific defaults take a tulpa_family object. Model packages
# (e.g. tulpaRatio) register rich families; a minimal one is enough here.
fam <- tulpa_family(
name = "poisson_gamma",
simulate_fn = function(eta, params, n_obs, ...) rpois(n_obs, exp(eta[[1]]))
)
priors_default(fam)
# Including spatial parameters
priors_default(fam, spatial = TRUE)
# Use as a starting point for customization
my_priors <- priors_default(fam)
my_priors$beta <- prior_normal(0, 1) # Tighter prior on fixed effects
Random-effect summaries
Description
Random-effect summaries
Usage
ranef(object, ...)
## S3 method for class 'tulpa_fit'
ranef(object, ...)
Arguments
object |
A |
... |
Ignored. |
Details
What each backend reports for a group effect follows what it computes:
Sampler tier and the RE-covariance Gibbs debias (
tulpa_re_cov_gibbs(), reached bytulpa(..., control = list(re_cov = "gibbs"))) draw the random effects jointly with everything else, soestimate/sd/ bounds are the empirical posterior summaries.The RE-covariance integrator (
tulpa_re_cov_nested()) carries a Gaussian per-group posterior at eachSigmanode; the reported summaries are the exact moments and quantiles of the weighted mixture of those, so they carry both the within-node curvature and theSigmauncertainty. A group effect the subspace debias selected (control$subspace_debias) is moved by the Metropolis sampler at every node instead, and is reported from those draws.The Laplace tier reports the conditional mode with no spread (
sdand the bounds areNA), which is the only per-group quantity it forms.
The source column says per row which of these produced it: "sampled" for
a posterior draw summary, "mixture" for the node mixture, "mode" for a
conditional mode.
A fit whose backend never forms a per-group posterior at all (the adaptive
Gauss-Hermite inner marginal integrates each group out by quadrature) errors
with that reason rather than returning an empty table, which would be
indistinguishable from a model with no random effects. A model that genuinely
has none returns a zero-row data frame.
Value
Data frame with one row per random-effect coefficient: term (the
group level, and the coefficient for a random slope), estimate, sd,
the 2.5% / 97.5% bounds conf.low / conf.high, and source (which
construction the row came from). sd and the bounds are NA on a backend
that reports a point per group (see Details).
Examples
set.seed(1)
df <- data.frame(x = rnorm(100), g = factor(rep(1:10, 10)))
df$y <- rpois(100, exp(0.3 * df$x))
fit <- tulpa(y ~ x + (1 | g), data = df, family = "poisson")
ranef(fit)
Rational Approximation Coefficients for a Fractional SPDE
Description
Returns the coefficient descriptor for an SPDE Matern field of operator order
alpha = nu + 1 (in 2D). For integer nu the construction is exact and no
rational approximation is needed. For fractional nu it returns the
rational-SPDE roots from the BRASIL best-rational approximation (see Details).
Usage
rational_spde_coefficients(nu, m = 4L, lambda_range = c(1e-04, 10000))
Arguments
nu |
Matern smoothness parameter. A positive number; may be fractional (e.g. 0.5, 1.5, 2.5). |
m |
Rational approximation order (number of numerator / denominator
factors) used for fractional |
lambda_range |
The generalized-eigenvalue spectrum |
Details
For integer nu (1, 2, 3, ...) the operator order alpha = nu + 1 is an
integer and the precision is assembled directly from integer powers of the
FEM operator L = kappa^2 C + G – an exact construction.
For fractional nu the field uses the operator-based rational SPDE
approximation (Bolin & Kirchner 2020). With beta = alpha / 2 and
m_beta = max(1, floor(beta)), the field-mode variance of the assembled
precision Q = Pl' C^{-1} Pl (field u = Pr x, x ~ N(0, Q^{-1})) tracks
the Matern spectral density l^{-2 beta} when
prod(1 - l rc) / (l^{m_beta - 1} prod(1 - l rb)) approximates l^{-beta_rem}
on the scaled spectrum, beta_rem = beta - (m_beta - 1). The roots come from
the degree-(m, m) best uniform (minimax) rational approximation of
x^{-beta_rem}, computed by the BRASIL algorithm (Hofreither 2021); its
numerator zeros map to rc = 1 / zero and denominator poles to rb = 1 / pole.
The field assembly from these roots is .spde_rational_assemble(); it is
validated against the Matern spectral density in test-spde-rational.R.
Value
For integer nu, a list with is_integer = TRUE, the operator order
alpha, beta = alpha / 2, m = 0, and empty poles / weights. For
fractional nu, a list with is_integer = FALSE, alpha, beta, the
rSPDE roots rb (denominator factors, drive Pl) and rc (numerator
factors, drive Pr), the integer power m_beta, the remaining fractional
exponent beta_rem, the scale constant scale, the rational order m, and
the approximation error.
References
Bolin, D. & Kirchner, K. (2020). The rational SPDE approach for Gaussian random fields with general smoothness. Journal of Computational and Graphical Statistics, 29(2), 274-285.
Hofreither, C. (2021). An algorithm for best rational approximation based on barycentric rational interpolation. Numerical Algorithms, 88, 365-388.
PC + LKJ hyperprior for a random-effect covariance
Description
Construct the default weakly-informative hyperprior used by
tulpa_re_cov_nested() for one covariance block: independent
Penalized-Complexity (PC) priors on the marginal standard deviations
sigma_i together with an LKJ prior on the correlation matrix R
(correlated block) or no correlation (diagonal block), returned as a
log_prior_theta function in the block's integration coordinates.
Usage
re_cov_pc_lkj_prior(
n_coefs,
prior_sigma = c(3, 0.05),
eta = 2,
correlated = TRUE
)
Arguments
n_coefs |
Number of coefficients |
prior_sigma |
|
eta |
LKJ shape (default 2). |
correlated |
|
Details
For a correlated block the prior is specified on the natural scale,
p(sigma, R) = LKJ(R | eta) * prod_i PC(sigma_i), then pushed to the
log-Cholesky coordinates theta of Sigma = L L' by the exact
change-of-variables Jacobian. For a diagonal (uncorrelated) block the LKJ
factor drops and theta_i = log sigma_i with Jacobian sum_i theta_i.
PC prior (Simpson et al. 2017) on each marginal SD: exponential with rate
lambda = -log(alpha) / U, so P(sigma_i > U) = alpha – the
prior_sigma = c(U, alpha) convention also used by the SPDE prior in tulpa.
LKJ prior (Lewandowski et al. 2009) on the correlation matrix:
p(R) proportional to det(R)^(eta - 1). eta = 1 is uniform over
correlation matrices; eta > 1 concentrates toward the identity. The
normalizing constant is dropped (constant across the grid, so it cancels when
the integration weights are renormalized).
Jacobian (correlated block): with theta packing log L_ii on the diagonal
and the raw strict-lower entries of L, the change of variables from
(sigma, R) to theta adds sum_i (c + 2 - i) * log L_ii - c * sum_i log sigma_i to log p(sigma, R). (Composition of the log-diagonal map, the
standard Cholesky-to-covariance Jacobian 2^c prod_i L_ii^(c+1-i), and the
covariance-to-(sigma, R) Jacobian; verified against numerical
differentiation in test-re-cov-prior.R.)
Value
A function(theta) returning the scalar log prior density in the
block's integration coordinates, suitable for one block of the
log_prior_theta argument of tulpa_re_cov_nested().
See Also
Objects exported from other packages
Description
These objects are imported from other packages. Follow the links below to see their documentation.
Residuals from a tulpa fit
Description
Population-level residuals from the fixed-effect fitted mean: "response"
is y - E[y | eta] on the response scale (trial-scaled for binomial,
offset included); "pearson" additionally scales by the family standard
deviation sqrt(Var(y | eta)) at the fitted linear predictor. Random
effects are held at zero, matching fitted().
Usage
## S3 method for class 'tulpa_fit'
residuals(object, type = c("pearson", "response"), ...)
Arguments
object |
A |
type |
|
... |
Ignored. |
Value
Numeric vector of length nobs(object).
Resolve the R fitter function for a backend (errors if unreachable).
Description
Looks up the fitter name string in the registry and resolves it lazily, so the registry stays independent of source-file load order.
Usage
resolve_backend_fitter(backend)
Resolve a parsed RE spec to a grouping factor
Description
Three paths, mirroring resolve_group_rhs:
single column name: take
data[[name]].multiple column names (nested
a/b):interaction(data[names]).language expression: evaluate against
data(e.g.,factor(g)).
Usage
resolve_group_factor(re_spec, data, env)
Resolve the RHS of a bar term into one or more group specs
Description
Three cases:
simple name:
g-> one spec withgroup_vars = "g".nested
/:a/b-> two specs ("a",c("a","b")).other call:
factor(g)org1:g2-> one spec carrying the language object ingroup_exprso we evaluate it at build time instead of guessing column names.
Usage
resolve_group_rhs(rhs)
Arguments
rhs |
A language object (RHS of |
Value
List of group specs.
Pool multiple imputation draws via Rubin's rules
Description
Pools per-imputation block fits via Rubin's rules. When every draw also
carries a per-coefficient skewness vector $gamma, the third cumulant
is pooled by the law of total cumulants
\kappa_3 = E[\kappa_3(X | k)] + 3\,\mathrm{Cov}(\mu_k, \sigma_k^2) + \kappa_3(\mu_k),
giving a pooled skewness $gamma alongside the usual $mean / $se.
If any draw is missing $gamma, the third-cumulant path is skipped.
Usage
rubins_pool(draws)
Arguments
draws |
List of K draws. Each draw is a named list of submodel
results, each with |
Value
A named list of pooled submodel summaries, each with mean,
se, V_within, V_between, V_total, K (number of draws that
contributed), and when applicable gamma (pooled skewness) and
kappa3 (pooled third cumulant).
Simulation-based calibration
Description
Scores whether an inference algorithm's posterior is CALIBRATED, by reading the whole marginal CDF rather than one or two nominal levels. Draw a truth from the distribution the fit updates, simulate a data set at that truth, fit, and take the probability integral transform (PIT) of the truth under the reported posterior. Under exact inference those PIT values are exactly Uniform(0, 1), so the entire ECDF is the measurement (Talts et al. 2018).
Usage
sbc(object, ...)
## Default S3 method:
sbc(object, ...)
## S3 method for class 'character'
sbc(
object = c("prior_predictive", "posterior"),
simulator = NULL,
fitter = NULL,
model = NULL,
n_sim = 100L,
quantities = NULL,
flat_prior = character(),
level = 0.95,
seed = 0L,
n_ref = NULL,
control = list(),
...
)
## S3 method for class 'sbc'
summary(object, baseline = NULL, ...)
## S3 method for class 'sbc'
plot(x, arm = NULL, quantity = NULL, folded = FALSE, ...)
Arguments
object |
An |
... |
Ignored. |
simulator, fitter |
The prior-predictive callbacks; see the contract
above. Required for |
model |
The list of posterior-SBC callbacks. Required for
|
n_sim |
Number of simulations. |
quantities |
Optional character vector restricting which quantities are scored. Default scores every quantity every arm reports. |
flat_prior |
Character vector of scored quantities held FIXED under a flat prior, admitted by a structural argument the caller is asserting. Prior-predictive only. |
level |
Simultaneous band level. |
seed |
Offset added to the simulation index, so simulation |
n_ref |
Optional; when supplied it is passed to |
control |
List of knobs: |
baseline |
Optional arm name. When given, the summary also carries the
PAIRED CRPS differences of every other arm against it, seed by seed. A
negative |
x |
An |
arm, quantity |
Optional character vectors selecting which panels to draw. Default draws every (arm, quantity). |
folded |
Plot the folded PIT instead of the raw one. |
Value
An object of class sbc:
pitone row per (simulation, arm, quantity): the truth, the raw and folded PIT, the CRPS and the predictive kind.
reportone row per (arm, quantity): the KS distance, whether the ECDF stayed inside the simultaneous band, the exact uniformity p-value, the same three folded, and the mean CRPS with its standard error.
bandsthe calibrated simultaneous band, by sample size.
premiseswhat each guard concluded.
crps_rolethe role the CRPS column has under this experiment.
The two experiments
"prior_predictive"ordinary SBC.
theta ~ p(theta),y ~ p(y | theta), fit, PIT. It reports self-consistency AVERAGED over the whole generative distribution."posterior"calibration CONDITIONAL on an observed data set (Sailynoja et al. 2026, Algorithm 2).
theta' ~ pi(theta | y_obs),y ~ pi(y | theta'), and the PIT is taken under the AUGMENTED posteriorpi(theta | y, y_obs). That is ordinary SBC withpi(theta | y_obs)in the role of the prior, so the same band, the same folded read and the same proper score all carry over – and it needs no proper prior.
A fixed-truth sweep is not an SBC experiment and is not offered here: its PIT is not uniform under correct inference and its CRPS is a descriptive loss, not a proper posterior score.
What is reported
Three reads of the same PIT sample, per (arm, quantity):
- raw
the PIT ECDF against an exact SIMULTANEOUS band. A pointwise binomial band is not simultaneous – at n = 100, holding each order statistic at 95 percent holds all of them together at 0.4471 – so the band here is calibrated by bisection against the exact crossing probability of the uniform order statistics.
- folded
2 |u - 1/2|, also uniform, and where a symmetric over- or under-dispersion shows after cancelling in the raw ECDF.- CRPS
the strictly proper score, closed form for the nested tier's own Gaussian mixture, paired seed by seed through
summary(x, baseline = ).
Every discrete PIT (a rank, a grid axis, a draw set) is randomized within its
atom, u = F(theta^-) + V P(theta), so one uniform reference and one band
serve every quantity. Reading rank / n_ref against a continuous uniform is
the classic silent SBC bug.
The callback contract
For experiment = "prior_predictive":
simulator(seed)returns a list carrying
theta, a named numeric vector of the true values of the scored quantities, plus whatever the fitter needs. It must be a pure function of its seed.fitter(d)returns a named list of ARMS, each a named list over quantities, each entry a predictive built by
sbc_mixture()and its siblings. Several arms read off one solve per seed, so an arm-to-arm difference carries no fit-to-fit noise.
For experiment = "posterior", model is a list of six callbacks –
data_obs, fit(data), draw_theta(fit, seed), simulate(theta, seed),
pool(data_obs, replicate), arms(fit, data) – plus an optional
group_ids(data) used to verify the fresh-groups premise below. The driver
hands draw_theta and simulate DIFFERENT seeds, so set.seed(seed) at the
top of each is the correct fixture; sharing one makes the replicate's noise a
function of the truth, which is not p(y | theta').
Two guards
The prior-predictive experiment draws the truth from the prior, so an
IMPROPER prior cannot be used – and the nested-Laplace door puts no prior on
the fixed effects. A scored quantity whose truth does not move across
simulations was not drawn from a proper prior, and sbc() errors on it
before spending the fits, pointing at experiment = "posterior". A location
parameter whose flat prior leaves the PIT uniform by a structural argument is
admitted through flat_prior, which is itself checked and travels on the
result.
The posterior experiment rests on two premises, each of which silently
invalidates the result when broken. The augmented posterior must condition on
BOTH data sets, so a pool() returning no more than the replicate (or no
more than the observed data) is refused. And the replicate must be
conditionally independent of the observed data given theta, which for a
hierarchical model whose group effects are integrated out means FRESH groups.
Supply group_ids and the observable half of that is verified – the group
LABELS are disjoint – and omit it and the result records the premise as
unverified rather than assumed. The other half, that the simulator drew those
groups' effects from the prior rather than conditionally on the observed
data, is not visible from outside the callback and is not claimed.
References
Talts, Betancourt, Simpson, Vehtari & Gelman (2018). Validating Bayesian inference algorithms with simulation-based calibration. arXiv:1804.06788.
Sailynoja, Schmitt, Buerkner & Vehtari (2026). Posterior SBC: simulation-based calibration checking conditional on data. Statistics and Computing 36:78. doi:10.1007/s11222-026-10825-9
Grimit, Gneiting, Berrocal & Johnson (2006). The continuous ranked probability score for circular variables and its application to mesoscale forecast ensemble verification. QJRMS 132(621C):2925-2942.
See Also
sbc_mixture() for the predictive shapes a fitter reports,
diagnostics() for the single-fit reliability band that screens what this
measures, pit_residuals() for single-fit posterior-predictive PIT
residuals (a different quantity).
Examples
# A conjugate normal-normal model, whose posterior is exact, so its PIT must
# be uniform: the harness scoring itself.
sim <- function(seed) {
set.seed(seed)
mu <- rnorm(1)
list(y = rnorm(10L, mu, 1), theta = c(mu = mu))
}
fitter <- function(d) {
v <- 1 / (1 + length(d$y))
list(exact = list(mu = sbc_normal(v * sum(d$y), sqrt(v))),
narrow = list(mu = sbc_normal(v * sum(d$y), sqrt(v) / 2)))
}
res <- sbc("prior_predictive", simulator = sim, fitter = fitter, n_sim = 60L)
res
summary(res, baseline = "exact")
Predictive shapes an SBC fitter reports
Description
The tagged representations sbc() reads. A fitter (or a posterior-SBC
arms) callback returns a named list of ARMS, each a named list over
quantities, and each entry is one of these – the shape the backend actually
reports for that quantity. Everything downstream (the PIT, the CRPS, drawing
from a predictive) dispatches on the kind tag, so a new backend shape is
one entry in three switches rather than a parallel scorer.
Usage
sbc_mixture(mu, var, w = NULL)
sbc_normal(mean, sd)
sbc_discrete(support, probs)
sbc_rank(rank, n_ref)
sbc_draws(x)
Arguments
mu, var, w |
Component means, variances and weights. |
mean, sd |
Mean and standard deviation of a single Gaussian. |
support, probs |
Finite support and its probabilities, normalized. |
rank, n_ref |
The rank in |
x |
Posterior draws. |
Details
These are the extension point, not alternative front doors: sbc() is the
verb, and these are the argument type it consumes.
sbc_mixture() is what an outer hyperparameter grid defines for a fixed
effect – component k is N(mu_k, var_k) with weight w_k, which is
exactly the mixture a nested-Laplace fit reports. sbc_normal() is the
one-component case, with its own constructor so a collapsed-moment read says
what it is. sbc_discrete() is a distribution on a finite support, which is
what a discrete hyperparameter grid defines for its own axis. sbc_rank() is
a rank of the truth among n_ref reference values, which is what a joint
log-likelihood comparison against posterior draws produces – it needs no
entry in the simulator's theta, since the comparison against the truth
already happened when the rank was formed. sbc_draws() is for a backend
reporting no analytic marginal.
The last three have ATOMS, so their PIT is randomized within the atom by
sbc(); reading a rank against a continuous uniform is the classic silent
SBC bug.
Value
A list carrying a kind tag and that shape's parameters.
See Also
Examples
sbc_normal(0.3, 0.1)
sbc_mixture(mu = c(0, 1), var = c(1, 4), w = c(0.7, 0.3))
sbc_discrete(support = c(0.5, 1, 2), probs = c(0.2, 0.5, 0.3))
Select best backend within a mode
Description
Select best backend within a mode
Usage
select_backend_for_mode(
mode,
family,
n_obs,
has_spatial,
has_temporal,
has_latent = FALSE,
spatial_type = NULL
)
Select inference mode and backend
Description
Implements the mode selection logic for tulpa. Accepts either tier names (auto, exact, structured, optimized) or backend names (hmc, ess, pg, laplace, vi).
When mode is "auto", selects between Tier 1 (Exact) and Tier 2 (Structured) based on model characteristics. Never selects Tier 3 (Optimized) automatically.
Usage
select_inference_mode(
mode,
family,
n_obs,
has_spatial = FALSE,
has_temporal = FALSE,
has_latent = FALSE,
spatial_type = NULL,
temporal = NULL,
has_re = FALSE
)
Arguments
mode |
User-specified mode or backend name |
family |
Model family object |
n_obs |
Number of observations |
has_spatial |
Whether model has spatial effects |
has_temporal |
Whether model has temporal effects |
has_latent |
Whether model has latent factors |
has_re |
Whether the model has any random-effect term ( |
Value
List with:
mode: The selected mode name
backend: The selected backend
tier: The tier number (1, 2, or 3)
tier_name: The tier name
reason: Explanation for the selection
Select the "main" model parameters for diagnostic display
Description
Drops per-element latent-field entries (names ending in a bracketed index,
e.g. u[12], w[3]) so plots and summaries focus on scalar coefficients
and hyperparameters rather than thousands of latent values.
Usage
select_main_params(param_names)
Arguments
param_names |
Character vector of parameter names. |
Value
The subset of param_names that are not bracketed-index entries
(or the full vector if that would be empty).
Generate random effects for simulation
Description
Generate random effects for simulation
Usage
sim_random_effects(n_groups, sigma = 0.5, type = "iid")
Arguments
n_groups |
Number of groups |
sigma |
Standard deviation |
type |
Type of random effects: "iid" (default), "car", "ar1" |
Value
Numeric vector of random effects
Generate spatial random effects for simulation
Description
Generate spatial random effects for simulation
Usage
sim_spatial_effects(adjacency, sigma = 0.5, type = "icar")
Arguments
adjacency |
Adjacency matrix |
sigma |
Standard deviation |
type |
Spatial type: "icar" (default), "bym2" |
Value
Numeric vector of spatial effects
Generate temporal random effects for simulation
Description
Generate temporal random effects for simulation
Usage
sim_temporal_effects(n_times, sigma = 0.5, type = "rw1", rho = 0.7)
Arguments
n_times |
Number of time points |
sigma |
Standard deviation |
type |
Temporal type: "rw1", "rw2", "ar1" |
rho |
Autocorrelation for AR(1). Default 0.7. |
Value
Numeric vector of temporal effects
Simulate responses from a fitted tulpa model
Description
Base-R alias for posterior_predict(): each simulation is one posterior
predictive replicate at the training data.
Usage
## S3 method for class 'tulpa_fit'
simulate(object, nsim = 1, seed = NULL, ...)
Arguments
object |
A |
nsim |
Number of simulated datasets (default 1). |
seed |
Optional integer seed (RNG state is restored on exit). |
... |
Ignored. |
Value
A data frame with nsim columns (sim_1, ...), one row per
observation, following the stats::simulate() convention.
Extract fitted covariate smooths
Description
The grid-marginalized posterior mean of each s(x) term's latent values,
one estimate per node (bin midpoint or unique covariate value). The latent
block values are read from the fit's per-grid modes, weighted by the
hyperparameter grid weights.
Usage
smooth_effects(object, term = 1L)
Arguments
object |
A |
term |
Which smoother: index or covariate name. Default 1. |
Value
A data frame with columns x (node location) and estimate (the
posterior-mean smooth at that node), with the covariate name as an
attribute "var".
Examples
set.seed(1)
d <- data.frame(x = runif(300, -2, 2))
d$y <- rpois(300, exp(0.3 + sin(2 * d$x)))
fit <- tulpa(y ~ s(x), data = d, family = "poisson")
sm <- smooth_effects(fit)
plot(sm$x, sm$estimate, type = "l")
Skew-normal CDF
Description
Cumulative distribution function of the univariate skew-normal,
F(q; \xi, \omega, \alpha) = \Phi(z) - 2 T(z, \alpha),
where z = (q - \xi) / \omega and T is Owen's T function.
Owen's T is evaluated by base R numerical quadrature; no extra
dependencies are required.
Usage
sn_cdf(q, sn)
Arguments
q |
Numeric vector of quantiles. |
sn |
Skew-normal parameter list from |
Value
Numeric vector of CDF values, same length as q.
Match three cumulants to a skew-normal parameterisation
Description
Inverts the skew-normal moment formulas to convert
(mu, sigma, gamma) into (xi, omega, alpha) parameters.
Returns NULL with a warning when |gamma| exceeds the
skew-normal ceiling (~0.9953) – in that regime the third cumulant
cannot be matched by any skew-normal and the caller should fall back
to direct-quadrature quantiles.
Usage
sn_match(mu, sigma, gamma)
Arguments
mu |
Posterior mean (numeric, length 1). |
sigma |
Posterior standard deviation (positive numeric, length 1). |
gamma |
Posterior skewness (numeric, length 1). |
Value
Named list with elements xi, omega, alpha;
or NULL if |gamma| >= .SN_GAMMA_MAX.
References
Azzalini, A. (1985). A class of distributions which includes the normal ones. Scand. J. Statist. 12: 171-178.
Skew-normal quantile
Description
Inverse of the skew-normal CDF via Newton iteration on
sn_cdf() with a Brent-style bracket fallback when Newton fails to
converge.
Usage
sn_quantile(p, sn, tol = 1e-10, max_iter = 60L)
Arguments
p |
Numeric vector of probabilities in |
sn |
Skew-normal parameter list from |
tol |
Absolute tolerance on the CDF residual (default |
max_iter |
Maximum Newton iterations (default |
Value
Numeric vector of quantiles, same length as p.
Areal spatially varying coefficient field
Description
Declare one or more areal (CAR / Besag) fields over a graph from an lme4-style bar formula. The bar's left-hand side lists the coefficients that vary smoothly over the graph; the right-hand side names the graph-node index. Each coefficient becomes an independent CAR field, entering the linear predictor scaled by that coefficient's per-observation design value:
\eta_i = \ldots + \sum_c X_{ic}\, z^{(c)}_{g_i},
where g_i is the graph node of observation i, z^{(c)} is
the CAR field for design column c, and X_{ic} is that column's
value at observation i. The intercept column is all ones, so
~ 1 || cell is the ordinary spatial intercept field; a covariate column
(e.g. time) gives a spatially varying slope on that covariate (a
per-region trend).
Use it inline in a tulpa() model formula, the same way a random-effect
bar is written:
y ~ time + spatial(graph = adj, formula = ~ 1 + time || cell) + (1 | site)
Usage
spatial(graph, formula, proper = FALSE, shared = NULL, by = NULL)
Arguments
graph |
Symmetric adjacency matrix of the spatial graph
( |
formula |
One-sided formula carrying a grouping bar, e.g.
|
proper |
Logical; |
shared |
Optional shared-effect handle, passed through to the field
blocks (see the model docs). Default |
by |
Optional replicated-CAR factor: a bare column name (or a string)
naming a factor in the model data. With |
Details
Each field is independent (its own precision), matching INLA's two
separate f(cell, model = "besag") and f(cell.slope, time, model = "besag") fields. Nesting (a / b), interaction, or expression
grouping is rejected: the grouping must be a single graph node. Add
ordinary nested random effects, e.g. (1 | site), as separate terms.
Value
A tulpa_spatial_field object describing the field(s). It is
expanded into one CAR block per design column at fit time, when the data
is available.
See Also
spatial_car() for a single areal field passed via the
spatial = argument, spatial_svc() for the coordinate-based
(Gaussian-process) spatially varying coefficient.
Examples
# Chain graph over 10 cells
adj <- matrix(0, 10, 10)
for (i in 1:9) adj[i, i + 1] <- adj[i + 1, i] <- 1
# Spatial intercept plus a spatially varying time slope
f <- spatial(graph = adj, formula = ~ 1 + time || cell)
print(f)
BYM2 spatial structure
Description
Specify a Besag-York-Mollie 2 (BYM2) spatial random effect. BYM2 decomposes the spatial effect into a structured (ICAR) component and an unstructured (IID) component, with a mixing parameter controlling the proportion of variance attributable to spatial structure.
BYM2 is preferred over plain CAR when you want to:
Distinguish structured vs unstructured spatial variation
Have an interpretable spatial fraction parameter
Use the scaling from Riebler et al. (2016)
Usage
spatial_bym2(
adjacency,
level = c("group", "obs"),
group_var = NULL,
shared = NULL,
scale_factor = NULL,
parameterization = c("standard", "collapsed")
)
Arguments
adjacency |
Symmetric adjacency matrix ( |
level |
Either |
group_var |
Name of the grouping variable in the data; required
when |
shared |
Optional shared-effect handle (see model docs). |
scale_factor |
Scaling factor for the ICAR component. If NULL (default), computed from the adjacency matrix following Riebler et al. |
parameterization |
|
Value
A tulpa_spatial object
References
Riebler, A., Sorbye, S. H., Simpson, D., & Rue, H. (2016). An intuitive Bayesian spatial model for disease mapping that accounts for scaling. Statistical Methods in Medical Research, 25(4), 1145-1165.
Examples
# Create adjacency matrix for 10 regions (chain structure)
adj <- matrix(0, 10, 10)
for (i in 1:9) {
adj[i, i+1] <- adj[i+1, i] <- 1
}
# Create BYM2 spatial structure
bym2 <- spatial_bym2(adj, level = "group", group_var = "region")
print(bym2)
# Disease mapping with BYM2 spatial smoothing
set.seed(456)
n_regions <- 10
epi_data <- data.frame(
region = factor(rep(1:n_regions, each = 4)),
age = rnorm(n_regions * 4, 50, 10)
)
epi_data$cases <- rbinom(nrow(epi_data), size = 100, prob = 0.15)
fit <- tulpa(
cases ~ age + spatial(region),
spatial = spatial_bym2(adj, level = "group", group_var = "region"),
data = epi_data,
family = "binomial",
n_trials = rep(100L, nrow(epi_data)),
mode = "laplace"
)
summary(fit)
CAR / ICAR spatial structure
Description
Constructs a conditional autoregressive spatial random effect from an
adjacency matrix. With proper = FALSE (the default) this is the
improper CAR / ICAR (type = "car"); with proper = TRUE it returns
the same object as spatial_car_proper().
Usage
spatial_car(
adjacency,
level = c("group", "obs"),
group_var = NULL,
proper = FALSE,
shared = NULL,
parameterization = c("standard", "collapsed")
)
Arguments
adjacency |
Symmetric adjacency matrix ( |
level |
Either |
group_var |
Name of the grouping variable in the data; required
when |
proper |
If |
shared |
Optional shared-effect handle (see model docs). |
parameterization |
|
Value
A tulpa_spatial object with type = "car" (or
"car_proper" when proper = TRUE).
See Also
spatial_car_proper(), spatial_bym2().
Proper CAR spatial structure
Description
Convenience wrapper for spatial_car(..., proper = TRUE). Creates a
proper conditional autoregressive (CAR) spatial random effect with the
autocorrelation parameter rho estimated from the data.
Use this when you want spatial autocorrelation to be a parameter of the model rather than fixed at 1 (as in ICAR). rho ~= 0 collapses to IID, rho ~= 1 approaches ICAR.
Usage
spatial_car_proper(
adjacency,
level = c("group", "obs"),
group_var = NULL,
shared = NULL
)
Arguments
adjacency |
Symmetric adjacency matrix ( |
level |
Either |
group_var |
Name of the grouping variable in the data; required
when |
shared |
Optional shared-effect handle (see model docs). |
Value
A tulpa_spatial object with type = "car_proper".
See Also
spatial_car() for ICAR (rho fixed at 1),
spatial_bym2() for the BYM2 decomposition.
Examples
adj <- matrix(0, 10, 10)
for (i in 1:9) adj[i, i+1] <- adj[i+1, i] <- 1
spec <- spatial_car_proper(adj, level = "group", group_var = "site")
print(spec)
Gaussian process spatial structure (NNGP)
Description
Specify a Gaussian-process spatial random effect, approximated with a nearest-neighbour GP (NNGP) for scalability. Captures smooth spatial variation from point-referenced coordinates.
Usage
spatial_gp(
coords,
approx = c("nngp", "hsgp"),
cov = c("exponential", "matern"),
nu = 1.5,
nn = 15,
m = 6,
c = 1.5,
sigma_prior_U = 1,
sigma_prior_alpha = 0.01,
shared = NULL,
scale_coords = TRUE,
parameterization = c("noncentered", "centered", "collapsed")
)
Arguments
coords |
A formula ( |
approx |
GP approximation: |
cov |
Covariance function (NNGP only). One of |
nu |
Matern smoothness parameter, one of |
nn |
Number of nearest neighbours used in the NNGP approximation. |
m |
Number of HSGP basis functions per dimension ( |
c |
HSGP boundary factor, |
sigma_prior_U, sigma_prior_alpha |
Penalized-complexity prior on the
field's marginal standard deviation ( |
shared |
Whether the spatial effect is shared across processes in a
multi-process model. |
scale_coords |
Logical. Standardize coordinates before fitting
(default |
parameterization |
Latent parameterization for the exact-NUTS field.
One of |
Value
A tulpa_gp object (also of class tulpa_spatial).
See Also
spatial_car(), spatial_bym2() for areal spatial effects.
Examples
# GP spatial specification from coordinate columns
spatial_gp(~ lon + lat)
spatial_gp(~ lon + lat, cov = "matern", nu = 1.5)
Multi-Scale Gaussian Process spatial structure
Description
Specify a multi-scale spatial random effect that decomposes spatial variation into local (fine-scale) and regional (broad-scale) components. Each scale has its own range and variance parameters.
This is particularly useful for large datasets (>100k observations) where spatial patterns exist at multiple scales.
Usage
spatial_multiscale(
coords,
scales = c("local", "regional"),
approx = c("nngp", "hsgp"),
m = 6L,
c_boundary = 1.5,
range_local = c(0.01, 1),
range_regional = c(1, 10),
cov = c("exponential", "matern"),
nu = 1.5,
nn_local = 10,
nn_regional = 30,
shared = NULL,
scale_coords = TRUE,
sampler = c("auto", "noncentered", "centered", "interweaved")
)
Arguments
coords |
A one-sided formula specifying coordinate columns (e.g.,
|
scales |
Character vector specifying scale names. Default: |
approx |
Approximation method: |
m |
Number of HSGP basis functions per dimension (default 6). Only
used when |
c_boundary |
Boundary factor for HSGP domain extension (default 1.5).
Only used when |
range_local |
Plausible range interval for the local scale as
|
range_regional |
Plausible range interval for the regional scale, read
the same way. Default: |
cov |
Covariance function: |
nu |
Smoothness parameter for Matern covariance, one of |
nn_local |
Number of nearest neighbors for local scale. Default 10. |
nn_regional |
Number of nearest neighbors for regional scale. Default 30. |
shared |
Logical; if TRUE (default), spatial effects enter both all processes. |
scale_coords |
Logical; if TRUE (default), coordinates are scaled to unit variance before computing distances. |
sampler |
Latent parameterization for the exact-NUTS field. |
Details
The multi-scale model decomposes spatial variation additively:
\eta(s) = X\beta + w_{local}(s) + w_{regional}(s)
where each component follows an independent Gaussian process:
w_{local}(s) \sim GP(0, \sigma^2_{local} C(\phi_{local}))
w_{regional}(s) \sim GP(0, \sigma^2_{regional} C(\phi_{regional}))
Identifiability: With sufficient data (>500 locations), the two scales are typically well-identified when prior ranges are non-overlapping. PC priors on variance components help prevent overfitting.
Computational cost: Approximately 1.5-2x the cost of single-scale GP, as two NNGP likelihoods must be evaluated.
Value
A tulpa_multiscale object
See Also
spatial_gp() for single-scale GP, temporal_multiscale() for
multi-scale temporal effects
Examples
# Create multi-scale spatial structure
ms <- spatial_multiscale(
~ lon + lat,
range_local = c(0.1, 0.5),
range_regional = c(1, 5)
)
print(ms)
set.seed(101)
n <- 25
df <- data.frame(
lon = runif(n, 0, 10),
lat = runif(n, 0, 10),
depth = rnorm(n),
temp = rnorm(n)
)
df$count <- rpois(n, exp(1 + 0.2 * df$depth))
# Both scales are sampled by exact NUTS (mode = "exact"); the field is
# returned as gp_local[i] / gp_regional[i] draws.
fit <- tulpa(
count ~ depth + temp,
data = df,
family = "poisson",
spatial = spatial_multiscale(
~ lon + lat,
range_local = c(0.1, 0.5),
range_regional = c(1, 5),
nn_local = 5L,
nn_regional = 8L
),
mode = "exact",
control = list(n_iter = 60L, n_warmup = 30L, seed = 1L)
)
summary(fit)
Extract spatial range and variance from a fitted spatial model
Description
Summarises the posterior of spatial hyperparameters. For sampler-tier fits this reads the raw hyperparameter draws; for a nested-Laplace spatial fit it summarises the outer hyperparameter grid. Works with ICAR, BYM2, GP (NNGP), CAR, SPDE, and SVC spatial types.
Usage
spatial_range(object, probs = c(0.025, 0.975))
Arguments
object |
A |
probs |
Quantile probabilities for the summary (default 0.025, 0.975). |
Value
A data.frame with rows for each spatial hyperparameter and columns
mean, sd, and one quantile column per entry of probs (named from the
probability, e.g. q2.5, q97.5 for the defaults).
Restricted Spatial Regression (RSR)
Description
Apply Restricted Spatial Regression to mitigate spatial confounding. RSR orthogonalizes the spatial effect to the covariate space, preventing the spatial random effect from absorbing covariate information.
This is important when covariates are spatially smooth (e.g., climate variables, elevation) because the spatial random effect can "steal" variance from these covariates, leading to biased coefficient estimates.
Usage
spatial_rsr(spatial, restrict_to)
Arguments
spatial |
A spatial specification ( |
restrict_to |
Formula specifying which covariates to orthogonalize
against (e.g., |
Details
The RSR approach (Reich et al., 2006; Hodges & Reich, 2010) modifies the spatial random effect to be orthogonal to the fixed effect design matrix:
w_{RSR} = (I - P_X) w
where P_X = X(X'X)^{-1}X' is the projection matrix onto the column
space of X.
When to use RSR:
Covariates are spatially smooth (environmental gradients)
Interested in causal interpretation of covariate effects
Coefficients appear attenuated toward zero
When NOT to use RSR:
Covariates are spatially uncorrelated
Spatial effect is the primary quantity of interest
Prediction is the main goal (not causal inference)
Value
A modified spatial specification with RSR enabled
References
Reich, B. J., Hodges, J. S., & Zadnik, V. (2006). Effects of residual smoothing on the posterior of the fixed effects in disease-mapping models. Biometrics, 62(4), 1197-1206.
Hodges, J. S., & Reich, B. J. (2010). Adding spatially-correlated errors can mess up the fixed effect you love. The American Statistician, 64(4), 325-334.
See Also
Examples
# Create RSR spatial structure
rsr <- spatial_rsr(
spatial_gp(~ lon + lat),
restrict_to = ~ depth + temp
)
print(rsr)
# Areal binomial data on a chain of regions, covariate spatially confounded
set.seed(404)
n_regions <- 12
W <- matrix(0, n_regions, n_regions)
for (i in 1:(n_regions - 1)) W[i, i + 1] <- W[i + 1, i] <- 1
df <- data.frame(region = factor(rep(1:n_regions, each = 5)))
df$x <- as.integer(df$region) / 4 + rnorm(nrow(df), 0, 0.5)
df$y <- rbinom(nrow(df), 20, plogis(-0.5 + 0.6 * df$x))
# RSR orthogonalises the spatial field to x, protecting its coefficient
fit <- tulpa(
y ~ x + spatial(region),
data = df,
family = "binomial",
n_trials = rep(20L, nrow(df)),
spatial = spatial_rsr(spatial_car(W, level = "obs"), restrict_to = ~ x),
mode = "auto",
control = list(n_iter = 500L, warmup = 250L)
)
summary(fit)
SPDE Spatial Field (Matern via Triangular Mesh)
Description
Specify a continuous Matern spatial field using the SPDE approach (Lindgren, Rue & Lindstrom 2011). Builds a triangular mesh from observation coordinates, computes FEM matrices, and passes them to tulpa's SPDE Laplace engine with CHOLMOD sparse solver.
Usage
spatial_spde(
coords,
data = NULL,
mesh = NULL,
boundary = NULL,
max_edge = NULL,
cutoff = 0,
nu = 1,
prior_range = c(0.5, 0.5),
prior_sigma = c(1, 0.5)
)
Arguments
coords |
A formula |
data |
Optional data.frame for formula evaluation. |
mesh |
A pre-built |
boundary |
Optional boundary: a two-column matrix of polygon vertices, an sf polygon, or NULL for convex hull with extension. |
max_edge |
Maximum edge length for mesh refinement. A single value
or |
cutoff |
Minimum distance between mesh vertices. Default 0. |
nu |
Matern smoothness parameter. A positive number. Integer |
prior_range |
Prior for the spatial range. A numeric vector |
prior_sigma |
Prior for the marginal standard deviation. A numeric vector
|
Value
A tulpa_spatial object with type "spde".
Examples
set.seed(42)
coords <- cbind(runif(50), runif(50))
spec <- spatial_spde(coords)
print(spec)
SPDE Spatial Field from Custom Matrices
Description
Specify a continuous Matern spatial field using externally-provided FEM matrices. Use this with meshes from fmesher, rSPDE, or any other source.
Usage
spatial_spde_custom(
C,
G,
A,
nu = 1,
prior_range = c(0.5, 0.5),
prior_sigma = c(1, 0.5)
)
Arguments
C |
Mass matrix (n_mesh x n_mesh sparse matrix, e.g. from |
G |
Stiffness matrix (n_mesh x n_mesh sparse matrix, e.g. from |
A |
Projection matrix (n_obs x n_mesh sparse matrix, e.g. from |
nu |
Matern smoothness parameter. A positive number; integer values
give the exact FEM construction, fractional values the BRASIL rational SPDE
approximation (supported by |
prior_range |
Prior for the spatial range. Default |
prior_sigma |
Prior for the marginal standard deviation. Default |
Value
A tulpa_spatial object with type "spde".
Spatially varying coefficient structure
Description
Specify a spatially varying coefficient (SVC): one or more fixed-effect coefficients are allowed to vary smoothly over space, with the variation governed by a Gaussian process (NNGP or HSGP approximation).
Usage
spatial_svc(
coords,
terms = 1,
cov = c("exponential", "matern"),
nn = 15,
shared = NULL,
scale_coords = TRUE,
approx = c("nngp", "hsgp"),
m = 6,
c_boundary = 1.5,
parameterization = c("noncentered", "centered")
)
Arguments
coords |
A formula ( |
terms |
Which coefficients vary over space. A formula, an integer vector
of design-matrix column indices, or a character vector of term names.
Default |
cov |
Covariance function. One of |
nn |
Number of nearest neighbours used in the NNGP approximation
( |
shared |
Whether the effect is shared across processes in a
multi-process model. |
scale_coords |
Logical. Standardize coordinates before fitting
(default |
approx |
Spatial approximation. |
m |
Number of basis functions per dimension for the HSGP approximation
( |
c_boundary |
Boundary-extension factor for the HSGP domain
( |
parameterization |
Latent parameterization for the exact-NUTS field
( |
Value
A tulpa_svc object (also of class tulpa_spatial).
See Also
spatial_gp() for a spatial random effect (rather than a varying
coefficient).
Examples
# Intercept that varies smoothly over space
spatial_svc(~ lon + lat)
Spatiotemporal interaction specifications for tulpa
Description
Functions to specify spatiotemporal interaction effects for tulpa models. These capture dependencies that arise when spatial patterns vary over time, or when temporal trends differ across space.
Specify a spatiotemporal interaction effect for tulpa models. The interaction captures structured or unstructured deviation from the additive spatial + temporal model.
No tulpa backend fits an interaction term, so this constructor errors. The
additive space-time model is fitted by tulpa(spatial = , temporal = ) and
by fit_st_nested().
Usage
spatiotemporal(
spatial,
temporal,
type = c("I", "II", "III", "IV", "iid", "separable"),
shared = NULL
)
Arguments
spatial |
A spatial specification from |
temporal |
A temporal specification from |
type |
Interaction type:
|
shared |
Logical; if TRUE (default), spatiotemporal effect enters both all processes. Set to FALSE for process-specific effects (triggers warning about potential confounding). |
Details
Spatiotemporal interactions extend the basic additive model:
\eta_{st} = X\beta + f_s(space) + f_t(time)
to include interactions:
\eta_{st} = X\beta + f_s(space) + f_t(time) + \delta_{st}
where \delta_{st} captures space-time interactions.
Interaction Types (following Knorr-Held, 2000):
-
Type I: Unstructured interaction - IID
\delta_{st} \sim N(0, \sigma^2) -
Type II: Structured time, unstructured space - temporal structure at each location
-
Type III: Structured space, unstructured time - spatial structure at each time
-
Type IV: Structured space AND time - full Kronecker interaction
Separable Models:
-
Separable: Covariance is Kronecker product
C_{st} = C_s \otimes C_t -
Non-separable: GP with joint space-time metric
Type I (IID)
Independent random effect for each space-time combination:
\delta_{st} \stackrel{iid}{\sim} N(0, \sigma^2_\delta)
This is the simplest form, requiring S*T parameters but capturing no structured interaction.
Type II (Temporal structure per location)
Each location has its own temporal random effect:
\delta_{\cdot t}^{(s)} \sim RW(\sigma^2)
This captures location-specific temporal trends but assumes independence across locations.
Type III (Spatial structure per time point)
Each time point has its own spatial random effect:
\delta_{s \cdot}^{(t)} \sim ICAR(\tau)
This captures time-specific spatial patterns but assumes independence across time points.
Type IV (Full structure)
Kronecker product of spatial and temporal precision matrices:
Q_\delta = Q_s \otimes Q_t
This is the most constrained model, assuming the interaction has the same structure as the marginal effects.
Separable
For GP-based effects, assumes separable covariance:
C(\mathbf{s}_1, t_1; \mathbf{s}_2, t_2) = C_s(\mathbf{s}_1, \mathbf{s}_2) \cdot C_t(t_1, t_2)
Value
Nothing: the call always signals an error.
References
Knorr-Held, L. (2000). Bayesian modelling of inseparable space-time variation in disease risk. Statistics in Medicine, 19(17-18), 2555-2567.
See Also
spatial_car(), spatial_gp(), temporal_rw1(), temporal_ar1()
Extract spatiotemporal effects from fitted model
Description
Extract posterior distributions of spatiotemporal interaction effects from a fitted tulpa model.
Usage
spatiotemporal_effects(
object,
format = c("array", "long", "summary"),
probs = c(0.025, 0.5, 0.975),
...
)
## S3 method for class 'tulpa_fit'
spatiotemporal_effects(
object,
format = c("array", "long", "summary"),
probs = c(0.025, 0.5, 0.975),
...
)
Arguments
object |
A |
format |
Output format: |
probs |
Quantiles to compute if |
... |
Ignored |
Value
Spatiotemporal effects in requested format
Examples
## Not run:
# `fit` carries a Knorr-Held interaction block, which no tulpa backend
# fits: the spec comes from a model package that configures the
# interaction itself (see spatiotemporal() for what the engine does fit).
st_effects <- spatiotemporal_effects(fit, format = "summary")
head(st_effects)
## End(Not run)
Non-separable spatiotemporal GP
Description
Specify a non-separable Gaussian Process for spatiotemporal effects.
Unlike separable models where the covariance factors as C_s \otimes C_t,
non-separable models allow for direct space-time interaction in the covariance.
No tulpa backend fits a joint space-time covariance, so this constructor
errors. A spatial GP alongside a temporal field is fitted by
tulpa(spatial = spatial_gp(...), temporal = ...).
Usage
spatiotemporal_gp(
coords,
time_var,
cov_space = c("exponential", "matern", "gaussian", "spherical"),
cov_time = c("exponential", "matern", "gaussian"),
nonsep_type = c("product", "sum", "gneiting", "cressie_huang"),
nn = 15,
shared = NULL
)
Arguments
coords |
A one-sided formula specifying coordinate columns (e.g.,
|
time_var |
Name of the time variable in data. |
cov_space |
Spatial covariance: |
cov_time |
Temporal covariance: |
nonsep_type |
Non-separability type:
|
nn |
Number of nearest neighbors for NNGP approximation. Default 15. |
shared |
Logical; if TRUE (default), effect enters both processes. |
Details
The non-separable covariance functions allow for more flexible space-time dependence:
Gneiting class:
C(h, u) = \frac{\sigma^2}{(a|u|^{2\alpha} + 1)^{\tau}} \exp\left(-\frac{c\|h\|^{2\gamma}}{(a|u|^{2\alpha} + 1)^{\beta\gamma}}\right)
where h is spatial lag, u is temporal lag, and parameters control the space-time interaction.
Cressie-Huang class: Constructed via Fourier transform methods to ensure positive definiteness.
Value
Nothing: the call always signals an error.
References
Gneiting, T. (2002). Nonseparable, stationary covariance functions for space-time data. Journal of the American Statistical Association, 97(458), 590-600.
Cressie, N., & Huang, H. C. (1999). Classes of nonseparable, spatio-temporal stationary covariance functions. Journal of the American Statistical Association, 94(448), 1330-1340.
Spectrum at frequency zero (AR fit)
Description
Spectrum at frequency zero (AR fit)
Usage
spectrum0_ar(x)
Posterior summary of the fixed effects
Description
Posterior summary of the fixed effects
Usage
## S3 method for class 'tulpa_fit'
summary(object, level = 0.95, ...)
Arguments
object |
A |
level |
Credible-interval level (default 0.95). |
... |
Ignored. |
Value
Data frame: estimate, std.error, and lower/upper credible bounds, one row per fixed effect. Sampler tiers report empirical quantiles; the Laplace tier reports the Gaussian approximation.
On a nested-Laplace fit the estimate and standard error are the
hyperparameter-grid-marginalized moments, and the bounds invert the Gaussian
mixture sum_k w_k N(mu_kj, V_kjj) that grid defines, rather than reading
mu +/- z sigma off the single Gaussian matching those moments. An
interval_source attribute records which read produced them
("mixture_cdf", "gaussian_moment", "skew_map_cell", or
"skew_map_cell/mixture_cdf" when the two are in play on different
coefficients) and interval_declined says why, whenever the mixture read
did not run. A
retained_mass attribute gives the share of the grid weight whose cells
retained a fixed-effect block: 1 on a complete grid, and below 1 on one
that dropped a positive-weight cell, whose report is then the posterior
conditional on the cells that remain.
Where no per-cell block was retained the estimate falls back to the
grid-weighted average of the per-cell modes, restricted to the cells whose
inner solve reached a mode. A fit where none did reports NA with
interval_declined = "not_converged", rather than the vector its Newton
started from as an estimate.
With control$skew_correct = TRUE a coefficient whose inner-Laplace
gamma_3 is in the band it is valid on reports Cornish-Fisher quantiles
instead, and a skew_applied attribute names which coefficients took the
correction. That correction is measured at the MAP cell, so it is reported
on its own and is not composed with the mixture read; a coefficient it
declines keeps the mixture read rather than falling back further.
An axis_fields_dropped attribute carries the grid axes the fit's own
resolved path could not read, one row per dropped field (block, type,
field, path, integrates, reason). It is NULL whenever every supplied axis
was used, which is the ordinary case; diagnostic_summary() reads the same
record in sentences.
A beta_prior attribute carries the Gaussian fixed-effect prior the fit
ran under, as list(mean, sd). It is the engine default,
prior_normal(0, 2.5), whenever the caller supplied none, and NULL on
the paths that express no Gaussian prior on the fixed effects.
Summary method for tulpa_svc_posterior
Description
Summary method for tulpa_svc_posterior
Usage
## S3 method for class 'tulpa_svc_posterior'
summary(object, probs = c(0.025, 0.5, 0.975), ...)
Arguments
object |
A tulpa_svc_posterior object |
probs |
Quantiles to compute |
... |
Ignored |
Value
A tulpa_svc_summary data frame with one row per location and term,
holding the observation index, term name, coordinates, and the posterior
mean, SD, and requested quantiles of each spatially-varying coefficient.
Summary method for tulpa_temporal_posterior
Description
Summary method for tulpa_temporal_posterior
Usage
## S3 method for class 'tulpa_temporal_posterior'
summary(object, probs = c(0.025, 0.5, 0.975), ...)
Arguments
object |
A tulpa_temporal_posterior object |
probs |
Quantiles to compute |
... |
Ignored |
Value
A tulpa_temporal_summary data frame with one row per time point
(per component for multi-scale fits), holding the posterior mean, SD, and
requested quantiles of the temporal effect.
Summary method for tulpa_tvc_posterior
Description
Summary method for tulpa_tvc_posterior
Usage
## S3 method for class 'tulpa_tvc_posterior'
summary(object, probs = c(0.025, 0.5, 0.975), ...)
Arguments
object |
A tulpa_tvc_posterior object |
probs |
Quantiles to compute |
... |
Ignored |
Value
A tulpa_tvc_summary data frame with one row per time point and
term, holding the posterior mean, SD, and requested quantiles of each
temporally-varying coefficient.
Extract spatially-varying coefficients from a fitted model
Description
Extract posterior distributions of spatially-varying coefficients (SVCs) from a fitted tulpa model with SVC specification.
Usage
svc(object, terms = NULL, summary = FALSE, probs = c(0.025, 0.5, 0.975), ...)
## S3 method for class 'tulpa_fit'
svc(object, terms = NULL, summary = FALSE, probs = c(0.025, 0.5, 0.975), ...)
Arguments
object |
A |
terms |
Which SVC terms to extract. If NULL (default), extracts all. |
summary |
Logical; if TRUE, return summary statistics instead of full posterior draws. |
probs |
Quantiles to compute if |
... |
Ignored |
Value
A tulpa_svc_posterior object containing:
-
draws: Array of posterior draws (draws x locations x terms) -
coords: Coordinate matrix -
term_names: Names of SVC terms
See Also
spatial_svc(), plot.tulpa_svc_posterior()
Examples
set.seed(303)
n <- 25L
df <- data.frame(lon = runif(n), lat = runif(n), x = rnorm(n))
bsurf <- 0.9 * sin(2.8 * df$lon) + 0.7 * cos(2.2 * df$lat)
df$count <- rpois(n, exp(0.2 + (0.8 + bsurf) * df$x))
# The varying slope on `x` is a spatial field; SVC is exact-mode only.
fit <- tulpa(
count ~ x,
data = df,
family = "poisson",
spatial = spatial_svc(~ lon + lat, terms = ~ x - 1, nn = 5L),
mode = "exact",
control = list(n_iter = 80L, n_warmup = 40L, seed = 1L)
)
svc_post <- svc(fit)
summary(svc_post)
Extract temporal effects from a fitted model
Description
Extract posterior distributions of temporal effects from a fitted tulpa model with temporal specification.
Usage
temporal(
object,
component = "all",
summary = FALSE,
probs = c(0.025, 0.5, 0.975),
...
)
## S3 method for class 'tulpa_fit'
temporal(
object,
component = "all",
summary = FALSE,
probs = c(0.025, 0.5, 0.975),
...
)
Arguments
object |
A |
component |
Which component to extract for multi-scale models:
|
summary |
Logical; if TRUE, return summary statistics instead of full posterior draws. |
probs |
Quantiles to compute if |
... |
Ignored |
Details
temporal() is overloaded. Given a fitted model it is the accessor described
here. Given a one-sided formula (or a named formula = / structure =
argument) it is instead the inline varying-coefficient field constructor used
in a tulpa() model formula, the temporal mirror of spatial():
temporal(formula = ~ 1 + x || time, structure = "rw1") declares a smooth
temporal level (the intercept column) plus a temporally varying slope on each
covariate column. structure is one of "rw1" (default), "rw2", or
"ar1"; only the double bar || (independent fields) is supported.
Value
A tulpa_temporal_posterior object
See Also
temporal_multiscale(), temporal_rw1()
Examples
set.seed(131)
df <- data.frame(year = 1:40, x = rnorm(40))
df$count <- rpois(40, exp(1 + 0.2 * df$x))
fit <- tulpa(
count ~ x,
data = df,
family = "poisson",
temporal = temporal_multiscale("year", trend = "rw2", seasonal = 12),
mode = "exact",
control = list(n_iter = 200L, n_warmup = 100L, seed = 1L)
)
# Extract all temporal effects
temp_post <- temporal(fit)
summary(temp_post)
AR(p) temporal latent field (general-order autoregressive)
Description
A stationary autoregressive process of order p,
w_t = \sum_{j=1}^{p} \phi_j w_{t-j} + \varepsilon_t, as a
user-defined GMRF latent block for a model formula via
latent(temporal_ar(...)) – the general-order extension of
temporal_ar2(), sharing the same construction: the precision is the exact
stationary AR(p) GMRF (the banded inverse of the Yule-Walker Toeplitz
autocovariance), and stationarity is enforced by the partial-autocorrelation
parameterization (Levinson-Durbin map of p unconstrained
atanh_psi hyperparameters), so the hyperparameter integration never
leaves the stationary region.
The block's hyperparameters are log_tau (log innovation precision) and
atanh_psi1..atanh_psi<p>; the AR coefficients are recovered through
psi_j = tanh(atanh_psi_j) and the Levinson-Durbin recursion. Note the
outer integration cost grows with the hyperparameter count p + 1; orders
beyond 3-4 are better served by a sampler tier.
Usage
temporal_ar(
time_idx,
p = 2L,
n_times = NULL,
prior_tau_sd = 2,
prior_psi_sd = 1.5,
name = NULL
)
Arguments
time_idx |
Integer vector (1-based) of the time point for each observation. |
p |
Autoregressive order (>= 1). |
n_times |
Number of distinct time points; defaults to |
prior_tau_sd, prior_psi_sd |
Prior SDs for |
name |
Optional block label (default |
Value
A tgmrf / tulpa_latent_block object.
See Also
temporal_ar2() (the p = 2 shorthand), temporal_ar1() for the
first-order formula-integrated process, tgmrf() for the general
user-defined GMRF interface.
Examples
set.seed(1)
Tt <- 150L
w <- numeric(Tt); w[1:3] <- rnorm(3)
for (t in 4:Tt) w[t] <- 0.4 * w[t-1] + 0.2 * w[t-2] - 0.2 * w[t-3] + rnorm(1, 0, 0.4)
d <- data.frame(t = seq_len(Tt), y = w + rnorm(Tt, 0, 0.3))
fit <- tulpa(y ~ latent(temporal_ar(d$t, p = 3)), data = d,
family = "gaussian", mode = "nested_laplace")
AR1 temporal structure (First-order Autoregressive)
Description
Specify a first-order autoregressive temporal random effect.
AR1 models temporal correlation where each time point depends on
the previous one: phi[t] = rho * phi[t-1] + epsilon[t].
Unlike RW1/RW2, AR1 is a proper (stationary) model with an estimated autocorrelation parameter rho.
Usage
temporal_ar1(time_var, group_var = NULL, shared = NULL, rho_prior = NULL)
Arguments
time_var |
A formula ( |
group_var |
Optional formula ( |
shared |
Whether the temporal effect is shared across processes in a
multi-process model. |
rho_prior |
Prior for the AR(1) autocorrelation. Default |
Details
The AR1 process has marginal variance sigma^2 / (1 - rho^2) and correlation between time points t and s of rho^|t-s|.
The precision matrix is tridiagonal and full rank, so no constraints are needed.
Value
A tulpa_temporal object
Examples
# Create temporal AR1 specification
temporal_ar1("year")
temporal_ar1("year", group = "site")
# AR1 temporal correlation
set.seed(127)
df <- data.frame(
year = rep(1:20, each = 3),
x = rnorm(60)
)
f <- as.numeric(arima.sim(list(ar = 0.7), 20, sd = 0.4))
df$count <- rpois(60, exp(1 + 0.3 * df$x + f[df$year]))
fit <- tulpa(
count ~ x,
data = df,
family = "poisson",
temporal = temporal_ar1("year"),
mode = "auto"
)
summary(fit)
AR(2) temporal latent field (second-order autoregressive)
Description
A stationary second-order autoregressive process
w_t = \phi_1 w_{t-1} + \phi_2 w_{t-2} + \varepsilon_t as a user-defined
GMRF latent block, for use in a model formula via
latent(temporal_ar2(...)). It reuses tulpa's nested-Laplace / NUTS
machinery (no dedicated C++ kernel). The precision is the exact stationary
AR(2) GMRF; stationarity is enforced by the PACF parameterization, so the
hyperparameter grid never leaves the stationarity region.
The block's hyperparameters are log_tau (log innovation precision) and
atanh_psi1, atanh_psi2 (unconstrained partial autocorrelations); the AR
coefficients are recovered as phi2 = tanh(atanh_psi2),
phi1 = tanh(atanh_psi1) (1 - phi2).
Usage
temporal_ar2(
time_idx,
n_times = NULL,
prior_tau_sd = 2,
prior_psi_sd = 1.5,
name = "ar2"
)
Arguments
time_idx |
Integer vector (1-based) of the time point for each observation. |
n_times |
Number of distinct time points; defaults to |
prior_tau_sd, prior_psi_sd |
Prior SDs for |
name |
Optional block label. |
Value
A tgmrf / tulpa_latent_block object.
See Also
temporal_ar1() for the first-order (formula-integrated) process;
tgmrf() for the general user-defined GMRF interface.
Examples
set.seed(1)
Tt <- 120L
w <- numeric(Tt); w[1:2] <- rnorm(2)
for (t in 3:Tt) w[t] <- 0.5 * w[t-1] + 0.3 * w[t-2] + rnorm(1, 0, 0.4)
d <- data.frame(t = seq_len(Tt), y = w + rnorm(Tt, 0, 0.3))
fit <- tulpa(y ~ latent(temporal_ar2(d$t)), data = d, family = "gaussian",
mode = "nested_laplace")
Extract temporal correlation parameters from a fitted model
Description
Returns posterior summary for temporal hyperparameters (tau, rho for AR1, sigma/lengthscale for temporal GP).
Usage
temporal_corr(object, probs = c(0.025, 0.975))
Arguments
object |
A |
probs |
Quantile probabilities (default 0.025, 0.975). |
Value
A data.frame with rows for each temporal hyperparameter.
Gaussian Process temporal structure
Description
Specify a Gaussian Process (GP) temporal random effect for irregularly-spaced or continuous time points. Unlike RW1/RW2/AR1 which assume equally-spaced observations, GP temporal effects model correlation as a function of time distance.
This is particularly useful for:
Irregularly-spaced time series
Continuous time (e.g., exact timestamps)
Smooth temporal trends with uncertainty
Usage
temporal_gp(
time_var,
cov = c("exponential", "matern", "gaussian", "periodic"),
nu = 1.5,
period = NULL,
group_var = NULL,
shared = NULL,
scale_coords = TRUE,
parameterization = c("noncentered", "centered")
)
Arguments
time_var |
Name of the time variable in data. Can be a formula
(e.g., |
cov |
Covariance function: |
nu |
Smoothness parameter for Matern covariance, one of:
|
period |
Period for periodic covariance (e.g., 12 for monthly, 365 for
daily data with annual cycle). Only used when |
group_var |
Optional name of grouping variable for panel data. If provided, separate GPs are estimated for each group. |
shared |
Logical; if TRUE (default), temporal effect enters both all processes. |
scale_coords |
Logical; if TRUE (default), time values are scaled to unit variance before computing distances. |
parameterization |
Parameterization for GP effects:
|
Details
The GP temporal model adds a time-correlated random effect:
\eta(t) = X\beta + f(t)
where f(t) follows a Gaussian process:
f(t) \sim GP(0, \sigma^2 C(|t - t'|; \phi))
The correlation function C(d; \phi) depends on time distance d:
-
Exponential:
C(d) = \exp(-d/\phi)- continuous but not differentiable -
Matern: Smooth with tunable roughness via
\nu -
Gaussian:
C(d) = \exp(-(d/\phi)^2)- infinitely differentiable -
Periodic:
C(d) = \exp(-2\sin^2(\pi d/p)/\phi^2)- for seasonal data
Implementation: the exponential kernel (equivalently Matern with
nu = 0.5) is an Ornstein-Uhlenbeck process, so its joint density
factorizes into a first-order Markov chain and evaluates in O(T) with no
matrix. The other kernels have no finite-dimensional state-space form and
are evaluated from a dense T x T Cholesky, where T is the number of distinct
time points.
The field is fit on the sampler path: its two hyperparameters
(log_sigma2_temporal_gp, logit_phi_temporal_gp) are sampled jointly with
the field and the fixed effects, so pass a sampler mode such as
mode = "hmc". There is no nested-Laplace kernel for it.
Value
A tulpa_temporal_gp object
See Also
temporal_rw1(), temporal_ar1() for equally-spaced temporal effects,
spatial_gp() for spatial GP effects
Examples
# Create GP temporal specification
temporal_gp("timestamp")
temporal_gp("day", cov = "matern", nu = 1.5)
temporal_gp("month", cov = "periodic", period = 12)
# Irregularly-spaced time series
set.seed(140)
times <- sort(runif(40, 0, 10))
df <- data.frame(
time = times,
x = rnorm(40),
y = rbinom(40, 1, 0.5)
)
fit <- tulpa(
y ~ x,
data = df,
family = "binomial",
temporal = temporal_gp("time"),
mode = "hmc",
control = list(n_iter = 200, warmup = 100, n_chains = 1)
)
Multi-scale temporal structure
Description
Specify a temporal random effect that decomposes variation into separate scales: a smooth trend, an optional seasonal cycle, and a short-term component. Each scale uses its own prior, letting slow and fast dynamics be modelled jointly.
Usage
temporal_multiscale(
time_var,
trend = c("rw2", "rw1", "none"),
seasonal = NULL,
short_term = c("ar1", "iid", "none"),
group_var = NULL,
shared = NULL
)
Arguments
time_var |
Single character string naming the time variable in the data. |
trend |
Prior for the smooth long-term trend. One of |
seasonal |
Optional integer period ( |
short_term |
Prior for the short-term component. One of |
group_var |
Optional character string naming a grouping variable for group-specific temporal effects. |
shared |
Whether the effect is shared across processes in a
multi-process model. |
Details
At least one of trend, seasonal, or short_term must be active.
Value
A tulpa_temporal_multiscale object.
See Also
temporal_rw1(), temporal_rw2(), temporal_ar1() for
single-scale temporal priors.
Examples
# Trend + annual seasonal cycle + AR1 short-term component on monthly data
temporal_multiscale("month", trend = "rw2", seasonal = 12, short_term = "ar1")
Restricted temporal regression (RTR)
Description
The temporal analogue of spatial_rsr(): constrain a temporal random effect
to be orthogonal to a set of covariates, so a temporally smooth covariate does
not have its fixed-effect coefficient attenuated by a confounded temporal
field, u_{RTR} = (I - P_X) u.
No tulpa backend applies that projection to a temporal field, so this
constructor errors rather than returning a specification that would fit as the
unrestricted temporal model. spatial_rsr() is the wired spatial analogue.
Usage
temporal_rtr(temporal, restrict_to)
Arguments
temporal |
A |
restrict_to |
A one-sided formula giving the covariate space the temporal
effect is made orthogonal to, e.g. |
Value
Nothing: the call always signals an error.
See Also
spatial_rsr(), temporal_rw1(), temporal_ar1()
RW1 temporal structure (First-order Random Walk)
Description
Specify a first-order random walk temporal random effect. RW1 penalizes
first differences, so adjacent time points are smoothed toward each other:
phi[t] - phi[t-1] ~ N(0, sigma^2).
Usage
temporal_rw1(time_var, group_var = NULL, cyclic = FALSE, shared = NULL)
Arguments
time_var |
A formula ( |
group_var |
Optional formula ( |
cyclic |
Logical. If |
shared |
Whether the temporal effect is shared across processes in a
multi-process model. |
Details
The precision matrix is rank T - 1 (one constraint needed). RW1 is the
least smooth of the random-walk priors; for smoother trends see
temporal_rw2(), and for a stationary alternative see temporal_ar1().
Value
A tulpa_temporal object.
See Also
temporal_rw2(), temporal_ar1() for other temporal priors.
Examples
# Create temporal RW1 specification
temporal_rw1("year")
temporal_rw1("month", cyclic = TRUE)
RW2 temporal structure (Second-order Random Walk)
Description
Specify a second-order random walk temporal random effect.
RW2 penalizes deviations from linearity, resulting in smoother trends
than RW1: phi[t] - 2*phi[t-1] + phi[t-2] ~ N(0, sigma^2).
Usage
temporal_rw2(time_var, group_var = NULL, cyclic = FALSE, shared = NULL)
Arguments
time_var |
A formula ( |
group_var |
Optional formula ( |
cyclic |
Logical. If |
shared |
Whether the temporal effect is shared across processes in a
multi-process model. |
Details
RW2 produces smoother curves than RW1 because it penalizes the second derivative (curvature) rather than the first derivative (slope). It requires at least 3 time points.
The precision matrix is rank T-2 (two constraints needed).
Value
A tulpa_temporal object
Examples
# Create temporal RW2 specification
temporal_rw2("year")
# Smooth temporal trend
set.seed(126)
df <- data.frame(
year = rep(1:20, each = 4),
x = rnorm(80)
)
trend <- sin(seq(0, 2, length.out = 20))
df$count <- rpois(80, exp(1 + 0.3 * df$x + trend[df$year]))
fit <- tulpa(
count ~ x,
data = df,
family = "poisson",
temporal = temporal_rw2("year"),
mode = "auto"
)
summary(fit)
Time-varying coefficient structure
Description
Specify a time-varying coefficient (TVC): one or more fixed-effect
coefficients are allowed to evolve over time, with the evolution governed by
a temporal prior (rw1, rw2, ar1, or a GP).
Usage
temporal_tvc(
time_var,
terms = 1,
structure = c("rw1", "rw2", "ar1", "gp"),
group_var = NULL,
shared = NULL,
sigma_prior_U = 1,
sigma_prior_alpha = 0.01
)
Arguments
time_var |
Single character string naming the time variable in the data. |
terms |
Which coefficients vary over time. A formula, an integer vector
of design-matrix column indices, or a character vector of term names.
Default |
structure |
Temporal prior governing how the coefficients evolve. One of
|
group_var |
Optional character string naming a grouping variable for group-specific time-varying coefficients. |
shared |
Whether the effect is shared across processes in a
multi-process model. |
sigma_prior_U, sigma_prior_alpha |
Penalized-complexity prior on each
varying coefficient's marginal standard deviation, calibrated so that
|
Value
A tulpa_tvc object.
See Also
temporal_rw1(), temporal_rw2(), temporal_ar1() for the
underlying temporal priors.
Examples
# Intercept that drifts as a first-order random walk over year
temporal_tvc("year", structure = "rw1")
Test for over- or underdispersion
Description
Compares the variance of observed data to the variance expected under
the fitted model (via simulation). Ratio > 1 = overdispersion;
< 1 = underdispersion. Equivalent to DHARMa::testDispersion().
Usage
test_dispersion(object, ...)
## Default S3 method:
test_dispersion(
object,
observed = NULL,
nsim = 250L,
seed = 123L,
alternative = c("two.sided", "greater", "less"),
...
)
Arguments
object |
A fitted model with |
... |
Passed to methods. |
observed |
Observed response vector (optional – extracted from fit) |
nsim |
Number of simulations (default 250) |
seed |
Random seed (default 123) |
alternative |
|
Value
An htest object with dispersion ratio and p-value
Test for outliers (simulation envelope)
Description
Counts how many observations fall outside the min-to-max range of
all simulations. Under a correct model, the expected number is
approximately 2 * N / (nsim + 1). Equivalent to DHARMa::testOutliers().
Usage
test_outliers(object, ...)
## Default S3 method:
test_outliers(object, observed = NULL, nsim = 250L, seed = 123L, ...)
Arguments
object |
A fitted model with |
... |
Passed to methods. |
observed |
Observed response vector (optional) |
nsim |
Number of simulations (default 250) |
seed |
Random seed (default 123) |
Value
An htest object (binomial test)
Test uniformity of PIT residuals
Description
If the model is correct, PIT residuals should follow Uniform(0, 1).
Applies a Kolmogorov-Smirnov test against that null.
Equivalent to DHARMa::testUniformity().
Usage
test_uniformity(
object,
observed = NULL,
nsim = 250L,
seed = 123L,
plot = FALSE
)
Arguments
object |
A fitted model, a numeric vector of PIT residuals, or a matrix of simulations |
observed |
Observed data (if object is simulations matrix) |
nsim |
Number of simulations (default 250) |
seed |
Random seed (default 123) |
plot |
If TRUE, draws a QQ plot |
Value
An htest object (KS test result)
Test for zero inflation
Description
Compares the number of zeros in observed data to the distribution
expected under the fitted model. Equivalent to DHARMa::testZeroInflation().
Usage
test_zero_inflation(object, ...)
## Default S3 method:
test_zero_inflation(object, observed = NULL, nsim = 250L, seed = 123L, ...)
Arguments
object |
A fitted model with |
... |
Passed to methods. |
observed |
Observed response vector (optional) |
nsim |
Number of simulations (default 250) |
seed |
Random seed (default 123) |
Value
An htest object with zero-inflation ratio and p-value
User-defined GMRF latent block
Description
tgmrf() lets a user define a Gaussian Markov random field as a latent
block in a tulpa model by supplying two R closures: a precision-matrix
factory Q(theta) and a log-prior on theta prior(theta). The resulting
object plugs into a formula via latent(tgmrf(...)) and is consumed by
every tulpa inference tier (Laplace, EM+Laplace, VI, nested_laplace+CCD,
NUTS, IMH-Laplace).
This is the latent-side dual of LikelihoodSpec. LikelihoodSpec lets a
model package own the observation model; tgmrf() lets a script own a
single latent block.
Usage
tgmrf(
Q,
prior,
init,
mu = NULL,
graph = NULL,
bounds = NULL,
obs_idx = NULL,
name = NULL
)
Arguments
Q |
A function |
prior |
A function |
init |
Numeric vector of starting values for |
mu |
Optional function |
graph |
Optional sparse matrix specifying the upper bound on |
bounds |
Optional list with components |
obs_idx |
Optional integer vector mapping each observation to a
latent slot in |
name |
Optional character; cosmetic label used by |
Details
For a Gaussian latent block z ~ N(mu(theta), Q(theta)^{-1}):
\log p(z\mid\theta) = \tfrac{1}{2}\log\det Q(\theta) - \tfrac{1}{2}(z - \mu(\theta))^\top Q(\theta)(z - \mu(\theta)) + \mathrm{const}
the gradient -Q(z - mu) and Hessian -Q wrt z are closed form, so the
user never writes gradient code. The Laplace inner step needs only
Q(theta) and mu(theta) at numeric theta. NUTS and VI additionally
use a forward finite-difference gradient on theta, costing dim(theta)
extra Q calls per outer step – cheap for the typical dim(theta) <= 5.
Value
An object of class c("tgmrf", "tulpa_latent_block") with
components:
-
Q,prior,mu– the user closures (orNULLformu). -
init,theta_names,theta_dim– hyperparameter metadata. -
n_latent– inferred fromQ(init). -
pattern– captured sparsity pattern as adgCMatrixof 1s. -
graph– user-supplied pattern, validated againstpatternif given. -
bounds,name– passed through.
See Also
latent() for the formula slot that consumes a tgmrf block.
Examples
# Periodic AR(1) block, wrap-around tridiagonal precision.
periodic_ar1 <- function(n) {
tgmrf(
Q = function(theta) {
sigma <- exp(theta[1]); rho <- tanh(theta[2])
d <- rep((1 + rho^2) / sigma^2, n)
o <- rep(-rho / sigma^2, n)
M <- Matrix::bandSparse(
n, k = c(-1, 0, 1), diagonals = list(o, d, o)
)
M[1, n] <- M[n, 1] <- -rho / sigma^2
methods::as(M, "CsparseMatrix")
},
prior = function(theta) {
stats::dnorm(theta[1], 0, 1, log = TRUE) +
stats::dnorm(theta[2], 0, 1, log = TRUE)
},
init = c(log_sigma = 0, atanh_rho = 0),
name = "periodic_ar1"
)
}
blk <- periodic_ar1(20)
print(blk)
User-defined GMRF latent block, compiled C++ backend
Description
tgmrf_cpp() is the compiled-C++ analogue of tgmrf(). Where tgmrf()
takes R closures Q(theta) / prior(theta), tgmrf_cpp() takes a
user-written .cpp file that defines the same kernels as templated C++
(so the same source compiles against every AD type tulpa uses). The
returned object has the same S3 class as tgmrf() – c("tgmrf", "tulpa_latent_block") – so every downstream consumer (formula parser,
inference layers, S3 methods) treats the two paths identically. Only the
dispatch on Q(theta) differs: registry-stored function pointer
(tgmrf_cpp()) vs Rcpp::Function callback (tgmrf()).
The user .cpp file must include <tulpa/tgmrf.h> and call the
TULPA_REGISTER_TGMRF(id, Q_fn, mu_fn, log_prior_fn) macro once. See
inst/examples/tgmrf_periodic_ar1.cpp for a worked example.
Usage
tgmrf_cpp(
cpp_file,
id,
init,
mu = NULL,
graph = NULL,
bounds = NULL,
obs_idx = NULL,
name = NULL,
cache_dir = tulpa_cache_dir(),
rebuild = FALSE
)
Arguments
cpp_file |
Absolute path to the user's |
id |
Character: the stable id used as the registry key. Must match
the first argument of |
init, mu, graph, bounds, obs_idx, name |
Same arguments as |
cache_dir |
Directory for Rcpp's |
rebuild |
Force a recompile even if the cached DLL is up to date.
Default |
Value
An object of class c("tgmrf", "tulpa_latent_block") with the
same fields as tgmrf() plus backend = "cpp" and cpp_id = id. The
Q / prior fields are R wrapper closures around the registered C++
kernels, so callers (e.g. NUTS-over-theta) that hold the object can
still call block$Q(theta) directly.
See Also
tgmrf() for the R-closure path; latent() for the formula
slot that consumes a tgmrf block.
Examples
## Not run:
# Compile a user latent block against tulpa's AD types; see
# inst/examples/tgmrf_periodic_ar1.cpp for a complete block.
blk <- tgmrf_cpp("my_block.cpp", id = "ar1", init = c(0, 0), graph = my_graph)
## End(Not run)
Tidy fixed-effect table (broom-compatible)
Description
Tidy fixed-effect table (broom-compatible)
Usage
## S3 method for class 'tulpa_fit'
tidy(x, conf.level = 0.95, ...)
Arguments
x |
A |
conf.level |
Interval level (default 0.95). |
... |
Ignored. |
Value
Data frame: term, estimate, std.error, conf.low, conf.high.
Examples
set.seed(1)
df <- data.frame(x = rnorm(100), g = factor(rep(1:10, 10)))
df$y <- rpois(100, exp(0.3 * df$x))
fit <- tulpa(y ~ x + (1 | g), data = df, family = "poisson")
tidy(fit)
Fit a tulpa model
Description
Single entry point for fitting a Bayesian hierarchical model. tulpa()
parses the formula, builds the model matrices, selects an inference backend
through the tier/mode system (see inference_mode_info()), assembles the
arguments that backend needs, and dispatches.
The fit conditions on the random-effect standard deviations sigma_re (and,
for non-Gaussian dispersion, phi): both the Laplace (Tier 2) and the
sampler (Tier 1) paths target the posterior given these. Integrating over the
hyperparameters is the role of the nested-Laplace / EM layer.
Usage
tulpa(
formula,
data,
family = "gaussian",
mode = "auto",
sigma_re = NULL,
n_trials = NULL,
weights = NULL,
phi = 1,
estimate_phi = FALSE,
phi2 = NULL,
beta_prior = NULL,
re_prior = NULL,
ziformula = NULL,
zi_prior = NULL,
warm_start = NULL,
spatial = NULL,
temporal = NULL,
control = list(),
...
)
Arguments
formula |
A model formula. Fixed effects, |
data |
A data frame. |
family |
Character family name: one of |
mode |
Inference mode or backend. |
sigma_re |
Random-effect SDs to condition on: length 1 (recycled) or one
per RE term. Defaults to 1 per term with a message. Ignored by the backends
that determine the covariance themselves ( |
n_trials |
Binomial denominators (length |
weights |
Optional observation weights (non-negative numeric vector,
length |
phi |
Dispersion/precision passed to the family (residual variance for
gaussian and lognormal, size for neg_binomial_2, precision for beta,
scale for t). The variance convention holds across every backend; the
SD-parameterized compiled kernels receive |
estimate_phi |
Estimate the dispersion from the data instead of
conditioning on Available under |
phi2 |
Optional second dispersion: the Student-t degrees of freedom
( |
beta_prior |
Optional |
re_prior |
Optional |
ziformula |
Optional one-sided formula for the zero-inflation
probability, e.g. |
zi_prior |
Optional |
warm_start |
Optional starting point for the NUTS sampler, from a
cheaper fit of the same model: The variance-component slots take an adapting mass by default, because a
plug-in fit estimates no curvature for them. Passing a fit from
|
spatial |
Optional spatial-field spec. How it is addressed depends on the field family:
The mode selects how the spatial hyperparameter is handled:
|
temporal |
Optional temporal field spec ( |
control |
Optional list of backend tuning arguments (e.g. |
... |
Reserved for future statistical arguments. Nothing is read from
it today, so any entry errors: a stray name here is a misspelled argument
or a tuning knob that belongs in |
Value
A tulpa_fit object carrying the backend's output plus
inference_mode, inference_tier, backend, selection_reason,
formula, and family. Two field-name conventions to know when
reaching into the object directly (the generic accessors handle both):
on nested-Laplace fits $weights is the hyperparameter GRID weights;
user observation weights are stored as $obs_weights. $draws is a
draws matrix on engine fits, while model-package fits may carry a list
($y_rep, $log_lik) under the same name.
Coverage
-
No random effects and random intercepts (
(1 | g)) are supported on the design path (mode = "laplace") and the sampler path (mode = "mala","pathfinder","imh_laplace", and the ModelData kernels"hmc"/"sghmc"/"sgld"/"mclmc"/"smc"/"vi"/"ess"). -
Random slopes are supported on the Laplace (Tier 2) path: there is no scalar
sigma_reto condition on, so the RE covarianceSigmais integrated rather than fixed. This covers correlated terms ((1 + x | g), a fullSigma), uncorrelated terms ((1 + x || g), a diagonalSigma), and several terms together ((1 + x | g) + (1 | h)) – each term becomes a covariance block, and any accompanying(1 | g)term is integrated as a 1x1 block (nothing is silently conditioned atsigma_re = 1).mode = "laplace"routes to the nested-LaplaceSigmaintegrator (tulpa_re_cov_nested(), CCD design, flat-in-log hyperprior by default – seere_prior$hyperprior);control$re_cov = "gibbs"switches to the exact Metropolis-within-Gibbs debias (tulpa_re_cov_gibbs()), andcontrol$re_cov = "aghq"keeps the nested integrator but replaces the inner joint-Laplace marginal with adaptive Gauss-Hermite quadrature (control$n_quad, default 9 there; seen_quadintulpa_re_cov_nested()). Both also run on the sampler path (mode = "mala"/"pathfinder"). -
mode = "gibbs"(Polya-Gamma) fits a single random-intercept model forfamily = "binomial"or"neg_binomial_2", and samples the RE sd rather than conditioning onsigma_re; tune it viare_prior$prior_sigma_scaleand a mean-zerobeta_prior. -
Latent prior blocks (
latent(tgmrf(...))) route to the nested-Laplace path (Tier 2), which integrates over the block hyperparameters.mode = "auto"and"structured"select it automatically when latent blocks are present;mode = "nested_laplace"forces it. At most one random-intercept(1 | g)term may accompany the blocks (model richer grouping as aniidblock). Joint multi-arm nested models cannot be expressed by a single-response formula – calltulpa_nested_laplace_joint()directly. The ModelData sampler kernels (
"hmc","ess","sghmc","sgld","mclmc","smc","vi") thread the full latent vector – fixed effects, random effects (all forms), areal spatial (icar/bym2), and temporal (rw1/rw2/ar1) – through one ModelData builder and sample the variance components jointly with the field.esscarries random effects but declines a structured spatial / temporal block (its isotropic Gaussian-prior block cannot represent the graph precision); continuous-coordinate fields (gp/nngp/hsgp/spde),car_proper, and exotic latent blocks stay on the dedicated nested-Laplace / SPDE / Polya-Gamma paths.
References
Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392. Hoffman & Gelman (2014). The No-U-Turn Sampler: adaptively setting path lengths in Hamiltonian Monte Carlo. JMLR 15(47):1593-1623.
See Also
inference_mode_info(), tulpa_laplace(), mala(), pathfinder()
Examples
set.seed(1)
n <- 200L
g <- sample(letters[1:12], n, replace = TRUE)
d <- data.frame(
y = rbinom(n, 1, plogis(-0.3 + 0.6 * rnorm(n))),
x = rnorm(n),
g = g
)
# Random-intercept logistic GLMM, Laplace tier.
fit <- tulpa(y ~ x + (1 | g), data = d, family = "binomial", mode = "laplace")
coef(fit)
summary(fit)
Environment variables tulpa reads
Description
Switches read from the process environment rather than from a fitter's
control list, because each one changes how the compiled kernels partition
work rather than what model is fitted. Each is read once, when the package's
shared library loads, so it has to be set before library(tulpa) – setting
it later in a session has no effect.
Reproducibility of a parallel scatter
TULPA_GRID_WORKSTEAL=0Forces the serial per-cell coupling scatter. The work-stealing partition is pinned by the grid geometry, so a grid solved with several outer threads already reproduces the serial reduction; this is the escape hatch if that ever has to be verified against a plain sequential pass.
TULPA_COUPLING_FORCE_PARALLELSet (to any value) to take the chunked parallel reduce on every coupled cell instead of only where the cell count pays for the per-chunk partial gradient and Hessian buffers. A small grid then exercises the parallel path. The reduce runs in fixed chunk order, so the answer is identical either way, which is what
tests/testthat/test-coupling-force-parallel.Rasserts.
Test-suite tiers
TULPA_FAST, TULPA_SLOW_TESTS and TULPA_FULL_RECOVERY select which cost
tier of the test suite runs; they are read by the suite, not by the package,
and are described in tests/testthat/README.md.
Replicate an areal graph across the levels of a factor (replicated CAR)
Description
Build the block-diagonal Kronecker graph I_L (x) Q and the level-offset
node index for a replicated areal field: one independent copy of the graph
per level of a by factor, all sharing one precision. This is the
graph-side counterpart to tulpa_bar_field_specs() – that helper expands
the coefficient columns and is graph-agnostic, while replication needs the
graph, so it is a sibling rather than a new argument. A downstream package
composes the two (column expansion x replication) from the one
implementation rather than re-deriving the Kronecker remap.
Usage
tulpa_bar_field_replicate(adjacency, node, by)
Arguments
adjacency |
Symmetric adjacency matrix of the base graph
( |
node |
Integer vector of 1-based graph-node indices, one per observation (the resolved bar right-hand side). |
by |
A vector of the same length as |
Value
A list:
adjacencythe
[L*n_node x L*n_node]block-diagonal Kronecker adjacencyI_L (x) Q(the base graph forL == 1).indexinteger vector, one per observation: the node index offset into its level's copy (
node + (level - 1) * n_node).n_levelsthe number of replication levels
L.n_nodesthe base graph node count
n_node.levelsthe factor levels of
by, in replicate order.
See Also
tulpa_bar_field_specs() for the coefficient-column expansion,
spatial() for the inline areal field constructor whose by = argument
this powers.
Examples
adj <- matrix(0, 4, 4)
for (i in 1:3) adj[i, i + 1] <- adj[i + 1, i] <- 1
node <- rep(1:4, times = 2)
lev <- rep(c("a", "b"), each = 4)
rep_info <- tulpa_bar_field_replicate(adj, node, lev)
dim(rep_info$adjacency) # 8 x 8 (I_2 (x) Q)
rep_info$index # level b nodes offset by 4
Expand a varying-coefficient bar into per-column field specs
Description
Expand the left-hand side of an lme4-style varying-coefficient bar
(~ 1 + w || node) against a data frame into one descriptor per design
matrix column. This is the same expansion spatial() and the inline
temporal() field constructor use internally to turn a bar into one CAR /
temporal field per design column, exposed so a downstream package can reuse
the one implementation rather than re-parsing the bar grammar.
The bar's right-hand side names the node index (the graph node for a spatial
field, the time index for a temporal field); it is not expanded but is
returned as the node attribute. The left-hand side is expanded with
stats::model.matrix(): the intercept column (1) is the unweighted
(all-ones) field, and each covariate column is a varying coefficient whose
per-observation weight is that column's design value (0 + drops the
intercept).
Usage
tulpa_bar_field_specs(formula, data)
Arguments
formula |
A one-sided formula carrying a single grouping bar, e.g.
|
data |
A data frame whose columns the bar left-hand side and the node
index refer to. The per-column weight vectors are evaluated against it, so
they have |
Value
A list with one element per design-matrix column, each a list:
column_namecharacter;
"Intercept"for the intercept column, otherwise themodel.matrix()column name (e.g."w").weighta numeric vector of length
nrow(data)for a covariate column (the per-observation design value scaling that field), orNULLfor the intercept column (which is the all-ones, unweighted field).is_interceptlogical;
TRUEfor the intercept column.
The list carries two attributes: node (character, the node-index column
named by the bar right-hand side) and correlated (logical, TRUE for a
single |, FALSE for a double ||).
See Also
tulpa_is_spatial_bar() for the recognizer, spatial() for the
inline areal field constructor that consumes this expansion.
Examples
d <- data.frame(cell = rep(1:5, each = 4), w = rnorm(20))
# Intercept plus a varying slope on w
specs <- tulpa_bar_field_specs(~ 1 + w || cell, d)
length(specs) # 2
specs[[1]]$column_name # "Intercept"
is.null(specs[[1]]$weight) # TRUE (unweighted field)
specs[[2]]$column_name # "w"
identical(specs[[2]]$weight, d$w) # TRUE
attr(specs, "node") # "cell"
Build model matrices from a parsed formula
Description
Takes a parsed formula and data frame, and constructs:
The fixed-effects design matrix X
An offset vector (or NULL) extracted from
offset(...)termsRE group index vectors
RE slope matrices (if applicable)
Usage
tulpa_build_model_data(parsed, data)
Arguments
parsed |
A |
data |
A data frame |
Value
A list with:
-
y: response vector (or NULL). Acbind(successes, failures)response is resolved to the successes column. -
n_trials: binomial denominators when the response was written ascbind(successes, failures), otherwise NULL -
X: fixed-effects design matrix -
offset: numeric vector or NULL -
re_terms: list of RE data structures (group indices, slope matrices)
Remove compiled blocks from the tgmrf_cpp() cache
Description
Deletes cached compilation output under tulpa_cache_dir(). The cache holds
build artefacts only – a removed entry is rebuilt by the next tgmrf_cpp()
call on the same source – so it can be emptied at any time.
Usage
tulpa_cache_clear(older_than = 0)
Arguments
older_than |
Remove only entries whose last modification is more than
this many days ago. |
Value
The number of entries removed, invisibly.
See Also
tulpa_cache_dir() for the location.
Examples
## Not run:
# Deletes the caller's own cached builds, so it is not run on check.
tulpa_cache_clear() # empty the cache
tulpa_cache_clear(older_than = 30) # keep the last 30 days
## End(Not run)
Default cache directory for tgmrf_cpp()-compiled DLLs
Description
Reports the per-user directory under tools::R_user_dir("tulpa", "cache")
where tgmrf_cpp() keeps the objects it compiles. Reporting the path does
not create it: the directory appears the first time a compile needs it, and
tulpa_cache_clear() removes what it holds.
Usage
tulpa_cache_dir(create = FALSE)
Arguments
create |
Create the directory if it is missing. |
Value
Absolute path (character) to the cache directory.
See Also
tulpa_cache_clear() to remove cached objects.
Examples
# Where compiled tgmrf_cpp() blocks are cached. Reporting creates nothing.
tulpa_cache_dir()
Validate a control = list() surface against its canonical key set
Description
Front-door fitters across the tulpa* ecosystem carry their perf /
numerical / tuning knobs in a single control list (design principle 6).
Without a name check a misspelled knob is a silent no-op –
control$adaptve_grid fits the default and reports nothing. This validates
the names a caller supplied against the whitelist of what the target fitter
actually reads, and errors listing the allowed set.
Usage
tulpa_check_control(control, allowed, where)
Arguments
control |
The |
allowed |
Character vector of accepted knob names. |
where |
Name of the calling fitter, used in the error message. |
Details
Consumer packages (tulpaRatio, tulpaObs) call this with their own key
registry rather than reimplementing the check.
Value
invisible(NULL), called for the side effect of erroring on an
unknown or unnamed knob.
Examples
tulpa_check_control(list(max_iter = 50), c("max_iter", "tol"), "my_fit")
try(tulpa_check_control(list(max_itr = 50), c("max_iter", "tol"), "my_fit"))
Model criteria from a pointwise log-likelihood
Description
Turn an [n_draws x n_obs] pointwise log-likelihood into the standard
Bayesian goodness-of-fit currency: WAIC, DIC, conditional predictive
ordinates (CPO) and their sum LPML, PSIS-LOO, all from one matrix. PSIS-LOO
reuses the native tulpa_psis() smoothing, so the CPO and LOO numbers are
the same computation (CPO_i = exp(elpd_loo_i), LPML = elpd_loo). The
input may be a matrix or a streaming tulpa_loglik() so EVA-scale fits are
processed in observation blocks.
Usage
tulpa_criteria(
log_lik,
criteria = c("waic", "loo", "cpo", "lpml", "dic"),
loglik_at_mean = NULL,
group = NULL,
chunk_size = NULL,
pointwise = FALSE
)
Arguments
log_lik |
An |
criteria |
Which criteria to compute. Any of |
loglik_at_mean |
Optional length- |
group |
Optional length- |
chunk_size |
Number of observation columns to process per block. The default streams the whole matrix at once when materialized, else picks a block sized to a few million entries. |
pointwise |
If |
Details
p_waic is the well-known positively-biased variance estimator at low draw
counts; the result records n_draws and the count of observations with
p_waic_i > 0.4 (the loo heuristic for an unreliable WAIC), and the
PSIS-LOO elpd_loo is the more stable figure to report when that count is
non-trivial.
The LOO unit is whatever one column of log_lik holds. If the consumer
built the matrix with one column per row (plot / visit), the default is
per-row LOO; if a column already carries a whole group's compressed
likelihood, leaving it out drops that group and pareto_k can blow up by
construction. The group argument makes the unit explicit: supply it to
aggregate columns into folds and report leave-one-group-out CV (LOGO-CV)
instead, a different and deliberate estimand.
Value
A tulpa_criteria object: a list with the requested scalar scores
(each estimate paired with its standard error where defined),
n_draws / n_obs, the PSIS pareto_k summary, and – when
pointwise = TRUE – a pointwise data frame.
References
Vehtari, Gelman & Gabry (2017). Practical Bayesian model evaluation using leave-one-out cross-validation and WAIC. Statistics and Computing 27(5):1413-1432. Watanabe (2010). Spiegelhalter et al. (2002). Geisser & Eddy (1979).
See Also
tulpa_psis() for the smoothing core, tulpa_pit() for the
probability-integral-transform companion, compare_models() for
model comparison.
Examples
# A draws x observations log-likelihood matrix (here built directly;
# in practice extracted from a fitted model's posterior draws).
set.seed(1)
y <- rnorm(40)
mu <- matrix(rnorm(200 * 40, sd = 0.2), 200, 40)
ll <- dnorm(matrix(y, 200, 40, byrow = TRUE), mean = mu, log = TRUE)
tulpa_criteria(ll)
tulpa_criteria(ll, criteria = "waic", pointwise = TRUE)$pointwise[1:3, ]
Simulation-Based Diagnostics for tulpa Models
Description
Model-checking tools based on posterior predictive simulation.
These are native R implementations equivalent to DHARMa's test suite,
with no external dependencies. They work with any model that provides
simulate(), fitted(), and residuals() methods.
Value
The diagnostic functions documented in this family return their
individual results (a test-statistic object, a data frame of residuals, or
a check_model summary); see each function's own help page.
Dispatch a fit to the backend chosen by the mode/tier system.
Description
The routing spine. Resolves mode to a concrete backend via
select_inference_mode(), asserts the backend is R-reachable (fails loudly
otherwise), then calls its fitter with fitter_args. The caller supplies
fitter_args matching the backend's input contract
(BACKEND_REGISTRY$<backend>$input).
The selected mode/tier/backend are stamped onto the returned fit (without overwriting any the fitter already set), so the inference contract is always visible in the output.
Usage
tulpa_dispatch(
mode,
fitter_args = list(),
family = NULL,
n_obs = NULL,
has_spatial = FALSE,
has_temporal = FALSE,
has_latent = FALSE,
spatial_type = NULL,
temporal = NULL
)
Arguments
mode |
User-specified mode ( |
fitter_args |
Named list of arguments forwarded to the backend fitter. |
family, n_obs, has_spatial, has_temporal, has_latent, spatial_type, temporal |
Model characteristics forwarded to |
Value
The fitter's result, with inference_mode, inference_tier,
backend, and selection_reason ensured.
Posterior draws as a 3D array
Description
Assembles a fitted model's posterior draws into an [iteration, chain, parameter] array – the layout expected by bayesplot and analogous to
posterior::as_draws_array(). Multiple chains are recognised from a 3D
draws array, a $chain_id row map, or an $n_chains count over chain-major
rows; a single pooled chain yields a one-chain array.
Usage
tulpa_draws_array(fit)
Arguments
fit |
A |
Details
As with posterior_sample(), a fit that carries no draws returns NULL with
a message naming its backend and the representation it carries instead.
Value
A numeric array with dimensions [n_iter, n_chain, n_param] and the
parameter names on the third dimension, or NULL if the fit carries no
draws. Chains of unequal length are truncated to the shortest.
See Also
posterior_sample(), tulpa_posterior_draws(), diagnostics()
Empirical-Bayes random-effect covariances
Description
Estimate one or more random-effect covariances Sigma by maximizing the
Laplace marginal likelihood over them (plus the hyperprior), then report the
fixed effects conditional on the maximizer. This is the plug-in ("ML-II" /
empirical-Bayes) counterpart of tulpa_re_cov_nested(), which integrates over
Sigma instead of fixing it at the maximizer.
Usage
tulpa_eb(
y,
n_trials = NULL,
X,
re_terms,
family = "binomial",
phi = 1,
phi2 = NULL,
prior_sigma = c(3, 0.05),
eta = 2,
hyperprior = c("flat", "pc_lkj"),
log_prior_theta = NULL,
beta_prior = NULL,
offset = NULL,
n_quad = 1L,
marginal = FALSE,
estimate_phi = FALSE,
X_zi = NULL,
zi_prior_sd = 2.5,
control = list()
)
Arguments
y, n_trials, X, family, phi |
Passed to |
re_terms |
Either a single random-effect term or a list of them; see
|
phi2 |
Optional second dispersion, threaded into every inner
|
prior_sigma, eta |
Hyperparameters of the PC + LKJ prior used when
|
hyperprior |
|
log_prior_theta |
Optional |
beta_prior |
Optional Gaussian prior on the fixed effects, threaded into
every inner |
offset |
Optional observation-level offset on the linear predictor
(length |
n_quad |
Quadrature order for the inner marginal. |
marginal |
Report fixed-effect intervals that carry the hyperparameter
uncertainty, instead of the intervals conditional on |
estimate_phi |
Estimate the family's dispersion alongside the
random-effect covariances, instead of conditioning on The dispersion enters unpenalized – the hyperprior covers the covariance
coordinates only – so this is the ML-II estimate of Available for every family carrying a dispersion, which is every front-door
family except Alongside |
X_zi |
Optional zero-inflation design matrix ( |
zi_prior_sd |
Prior SD on |
control |
A named list of numerical knobs: |
Details
Blocks, coordinates and the default hyperprior are exactly those of
tulpa_re_cov_nested() – a correlated (1 + x | g) term is a full
Sigma = L L' in log-Cholesky coordinates, an uncorrelated (1 + x || g)
term is diagonal in log-SD coordinates, and a scalar (1 | g) term is the
degenerate one-coefficient block. Both functions call the same outer
objective and the same optimizer, so tulpa_eb()$theta_hat and
tulpa_re_cov_nested()$theta_hat are the same estimate on the same data –
which requires hyperprior to default the same way on both: "flat", the
zero function, matching the nested-Laplace convention on every other scale
axis in the engine (icar / rw1 / rw2 / ar1's tau / iid all lack a
hyperprior on their scale too; see vignette("priors")). Set
hyperprior = "pc_lkj" for the weakly-informative PC + LKJ prior instead
(see re_cov_pc_lkj_prior()) – the regularizer that, at small G, keeps a
block off the sigma = 0 boundary this maximizer would otherwise reach.
The reported fixed-effect covariance is the conditional one at theta_hat
(solve(H_beta)). It does not include the hyperparameter uncertainty that
tulpa_re_cov_nested() integrates over, so EB intervals are narrower –
increasingly so as the number of groups falls. Use the nested integrator when
the variance components themselves, or calibrated fixed-effect intervals, are
the target; use EB when the point estimate is, or as a fast starting fit.
Value
A tulpa_fit with:
-
mode,H_beta: the fixed-effect (and, on then_quad = 1path, random- effect) mode and the fixed-effect precision attheta_hat, drivingcoef/confint/vcov/summary. -
map: theSigma/sigma/rhosummary attheta_hat(a single list for one block, a named list of them for several). -
Sigma: the estimated covariance (a matrix for one block, a named list of matrices for several). -
theta_hat,log_marginal,layout,n_blocks,n_coefs. -
converged: whether the inner Newton solve attheta_hatconverged. -
outer_convergence:optim's code for the maximization overSigma(0on success). A non-zero value also warns. With
marginal = TRUEand a correction that formed:cov_marginal(the widened fixed-effect covariance, whichvcov/summary/confintthen report),cov_conditional(thesolve(H_beta)they would otherwise have reported, kept so the two are comparable on one fit),H_thetaandtheta_cov(the outer Hessian attheta_hatand its inverse, named bytheta_names), and themarginal_step/marginal_richardsonactually used. All absent when the correction was not requested or could not be formed, sois.null(fit$cov_marginal)tests whether the reported intervals are marginal. A requested correction that fails warns and leaves the conditional covariance in place.
References
Casella (1985). An introduction to empirical Bayes data analysis. The American Statistician 39(2):83-87. Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392.
See Also
tulpa_re_cov_nested() to integrate over Sigma rather than fix it;
tulpa_laplace() for the inner solve.
Examples
set.seed(1)
G <- 30L; per <- 10L; n <- G * per
grp <- rep(seq_len(G), each = per); x <- rnorm(n)
b <- rnorm(G, 0, 0.8)
y <- rpois(n, exp(0.3 + 0.5 * x + b[grp]))
re_term <- list(idx = grp, n_groups = G, n_coefs = 1L)
fit <- tulpa_eb(y, NULL, cbind(1, x), re_term, family = "poisson")
fit$map$sigma # empirical-Bayes RE standard deviation
Fit a latent-variable model via EM + Laplace approximation
Description
Generic EM engine. Model packages provide callbacks for the E-step
(latent variable posterior) and the M-step encoding (how to assemble
submodel data from weights). Each M-step submodel block is fit
independently via tulpa_laplace(); the engine reads family and
offset per block so heterogeneous-family mixtures (e.g. a binomial
zero submodel + poisson positive submodel for a hurdle model) work
without engine changes.
Usage
tulpa_em_laplace(
e_step,
m_step_encode,
spatial = NULL,
re_list = list(),
max_iter = 50L,
tol = 1e-04,
damping = 0.3,
correction = c("auto", "mi", "gibbs", "none"),
n_imputations = 20L,
n_gibbs = 10L,
draw_z = NULL,
m_step_extra = NULL,
beta_prior = NULL,
verbose = TRUE,
...
)
Arguments
e_step |
Callback: |
m_step_encode |
Callback:
|
spatial, re_list |
Not consumed at the driver level – latent structure
is per-submodel, set as a |
max_iter |
Maximum EM iterations. |
tol |
Convergence tolerance on max relative parameter change. |
damping |
EM damping factor in |
correction |
Post-EM correction. |
n_imputations |
Number of MI draws (default |
n_gibbs |
Length of the Gibbs chain (default |
draw_z |
Optional function |
m_step_extra |
Optional |
beta_prior |
Optional Gaussian prior on the fixed effects, applied to
every block fit via |
verbose |
Print per-iteration progress. |
... |
Forwarded to |
Details
This is an engine block, not a front door: model packages call it
programmatically, so its tuning knobs (max_iter, tol, damping)
sit in the signature rather than in a control list.
Value
A list with:
-
fits– named list oftulpa_laplace()results, one per block. -
weights– final E-step weights. -
n_iter– number of EM iterations actually run. -
converged– logical. -
history–data.frame(iter, delta)of max relative parameter change per iteration. -
correction– the resolved correction mode ("none","mi", or"gibbs"). -
pooled– present whencorrection %in% c("mi", "gibbs"). Named list of pooled per-submodel summaries fromrubins_pool(). -
draws– present whencorrection %in% c("mi", "gibbs"). List of per-draw fits withbeta/seattached.
Soft latent labels go in weights, not in y
The M-step maximizes the expected complete-data log-likelihood. For a
Bernoulli latent z_i carrying E-step posterior weight w_i that is
Q = \sum_i [\, w_i \log p_i + (1 - w_i) \log (1 - p_i) \,],
a weighted Bernoulli log-likelihood, which carries no binomial
coefficient. Encode it as two rows per unit – y = 1 at weight w_i and
y = 0 at weight 1 - w_i:
list(y = rep(c(1, 0), each = n),
X = rbind(X, X),
weights = c(w, 1 - w),
family = "binomial")
A fractional y on a binomial block is refused, and is not the same
objective: it asks for the exact binomial density at a non-integer
response, whose normalizer lchoose(n, y) is not zero there and depends on
w. On the weighted encoding the block's log_marginal is the Laplace
marginal of Q, an EM objective that increases across iterations.
Examples
# Zero-inflated Poisson via EM: the E-step scores the posterior probability
# that each zero is non-structural. Those are soft labels, so the occupancy
# arm is the weighted Bernoulli above -- two rows per unit -- and the
# abundance arm weights each count by the same w.
set.seed(1)
n <- 200
z <- rbinom(n, 1, 0.7)
y <- rpois(n, 4) * z
X <- cbind(1, rnorm(n))
e_step <- function(fits, ...) {
if (!length(fits)) return(list(weights = pmax(as.numeric(y > 0), 0.5)))
psi <- plogis(drop(X %*% fits$occ$mode))
lam <- exp(drop(X %*% fits$abund$mode))
w <- ifelse(y > 0, 1, psi * exp(-lam) / (psi * exp(-lam) + (1 - psi)))
list(weights = w)
}
m_step_encode <- function(weights, ...) {
list(
occ = list(y = rep(c(1, 0), each = n), X = rbind(X, X),
weights = c(weights, 1 - weights), family = "binomial"),
abund = list(y = y, X = X, family = "poisson", weights = weights)
)
}
res <- tulpa_em_laplace(e_step, m_step_encode, verbose = FALSE)
res$converged
plogis(res$fits$occ$mode[1]) # P(non-structural), truth 0.7
exp(res$fits$abund$mode[1]) # abundance mean, truth 4
Generic Monte-Carlo EM driver
Description
Same M-step plumbing as tulpa_em_laplace(): every iteration calls
m_step_encode(weights, ...) to assemble per-submodel blocks and
fits each block via tulpa_laplace(). The difference is the E-step:
instead of computing closed-form weights, an e_step_sample
callback returns n_mc weight draws per iteration. Each draw is
run through the M-step independently and the resulting parameter
estimates are pooled via rubins_pool().
Convergence criterion is the max relative change in pooled M-step
parameter estimates between iterations. To increase Monte-Carlo
accuracy as iterations progress (Booth-Hobert ascent-based MCEM),
set n_mc_growth > 1.
This is an engine block, not a front door: model packages call it
programmatically, so its tuning knobs (max_iter, tol, n_mc)
sit in the signature rather than in a control list.
Usage
tulpa_em_mc(
e_step_sample,
m_step_encode,
n_mc = 10L,
n_mc_growth = 1,
n_mc_max = 200L,
max_iter = 30L,
tol = 0.001,
verbose = TRUE,
...
)
Arguments
e_step_sample |
Function |
m_step_encode |
Function |
n_mc |
Initial number of Monte-Carlo draws per iteration
(default |
n_mc_growth |
Multiplicative growth of |
n_mc_max |
Cap on |
max_iter |
Maximum EM iterations (default |
tol |
Convergence tolerance on max relative change in pooled
parameter estimates (default |
verbose |
Print per-iteration progress (default |
... |
Forwarded to |
Value
A list with:
-
pooled– named list of pooled per-submodel summaries (mean,se,V_within,V_between,V_total); seerubins_pool(). -
fits– list of per-MC-draw fit lists from the final iteration, each indexed by submodel name. -
n_iter– iterations actually run. -
n_mc_final–n_mcvalue used in the last iteration. -
converged– logical. -
history–data.frame(iter, n_mc, delta).
Tier
Inherits the tier of the inner M-step (Laplace => Tier 2). The
Monte-Carlo E-step itself is exact in the limit n_mc -> infinity,
so as a full pipeline MCEM is asymptotically Tier 1 if
e_step_sample is exact.
References
Wei, G. C. G., & Tanner, M. A. (1990). A Monte Carlo implementation of the EM algorithm and the poor man's data augmentation algorithms. Journal of the American Statistical Association, 85(411), 699-704.
Booth, J. G., & Hobert, J. P. (1999). Maximizing generalized linear mixed model likelihoods with an automated Monte Carlo EM algorithm. JRSS B, 61(1), 265-285.
See Also
tulpa_em_laplace() for the closed-form-weights variant,
rubins_pool() for the pooling rule.
Examples
## Not run:
# Monte-Carlo EM from two callbacks: e_step_sample() draws the latent
# variables and m_step_encode() encodes the complete-data design for the inner
# Laplace M-step (model packages such as tulpaObs supply these). See
# ?tulpa_em_laplace for the deterministic-E-step analogue.
fit <- tulpa_em_mc(e_step_sample, m_step_encode)
## End(Not run)
Expectation-Propagation fit for a GLM
Description
Fits a generalized linear model with a Gaussian coefficient prior by Expectation Propagation: the posterior is approximated by a Gaussian whose per-observation sites match the moments of the tilted distribution (via Gauss-Hermite quadrature). EP is exact for a Gaussian likelihood and typically more accurate than Laplace on skewed likelihoods, since it matches marginal moments rather than the mode curvature.
Usage
tulpa_ep(
formula,
data,
family = "binomial",
phi = 1,
phi2 = NULL,
n_trials = NULL,
beta_prior = .tulpa_default_beta_prior("ep"),
control = list()
)
Arguments
formula |
Model formula. |
data |
A data frame. |
family |
Character family name (see |
phi |
Dispersion / precision passed to the family (held fixed). |
phi2 |
Optional second dispersion (Student-t degrees of freedom for
|
n_trials |
Binomial denominators (length |
beta_prior |
Fixed-effect prior as |
control |
List: |
Value
A tulpa_fit (subclass tulpa_ep) with coefficients (posterior
mean), vcov, draws, log_marginal (the EP approximation), converged.
References
Minka (2001). Expectation Propagation for approximate Bayesian inference. UAI. Rasmussen & Williams (2006). Gaussian Processes for Machine Learning, Algorithm 3.5.
See Also
tulpa() (Laplace / sampler tiers), pathfinder() (VI).
Examples
set.seed(1)
d <- data.frame(x = rnorm(200))
d$y <- rbinom(200, 1, plogis(-0.3 + 0.8 * d$x))
fit <- tulpa_ep(y ~ x, data = d, family = "binomial")
coef(fit)
Construct a minimal tulpa_family for simulation
Description
Lightweight constructor for a tulpa_family object that exposes the
contract required by prior_predict() and tulpa_simulate(). Model
packages (tulpaRatio, tulpaObs) register richer families that also link to
C++ likelihoods; this helper is for tests and simple custom families that
only need simulation.
Usage
tulpa_family(
name,
simulate_fn,
process_names = "y",
extra_params = list(),
link_inv = NULL
)
Arguments
name |
Family name (character, length 1). |
simulate_fn |
|
process_names |
Character vector. Defaults to |
extra_params |
Named list of |
link_inv |
List of inverse-link functions per process; defaults to
identity for every process. tulpa passes the raw linear predictor to
|
Value
A tulpa_family object.
Examples
fam <- tulpa_family(
name = "poisson",
simulate_fn = function(eta, params, n_obs, ...) {
rpois(n_obs, exp(eta[[1]]))
}
)
Formula parsing for tulpa models
Description
Parses mixed-model formulas by walking the formula's abstract syntax tree.
R formulas are already parse trees – we do structural recursion to find
random effect terms (| nodes) and separate them from fixed effects.
Value
The formula helpers documented in this family return parsed-formula structures (lists describing the fixed effects, random-effect terms, and latent blocks); see each function's own help page.
Fit a Gaussian linear model via tulpa's generic engine
Description
Proof-of-concept function demonstrating the tulpa generic interface. Fits y ~ Normal(X * beta, sigma) with HMC sampling.
Usage
tulpa_gaussian(
formula,
data,
beta_prior = .tulpa_default_beta_prior("gaussian"),
control = list()
)
Arguments
formula |
A formula (e.g., y ~ x1 + x2) |
data |
A data frame |
beta_prior |
Fixed-effect prior as |
control |
List of numerical / sampler knobs: |
Value
A list with draws matrix, posterior means, and metadata
Fit via Polya-Gamma Gibbs sampler
Description
Public API for PG Gibbs sampling. Used by model packages for binomial and negative binomial GLMMs.
Usage
tulpa_gibbs(
y,
n_trials,
X,
group,
n_groups,
family = "binomial",
beta_prior = .tulpa_default_beta_prior("gibbs"),
prior_sigma_scale = 2.5,
spatial = NULL,
temporal = NULL,
control = list()
)
Arguments
y |
Response vector |
n_trials |
Trial sizes (binomial) |
X |
Design matrix |
group |
Integer vector of group indices (1-based) |
n_groups |
Number of groups |
family |
Character: "binomial" or "neg_binomial_2" |
beta_prior |
Fixed-effect prior as |
prior_sigma_scale |
Prior scale for RE sigma (statistical; default 2.5). |
spatial |
Optional spatial spec. When supplied the fit routes to the
matching spatial Polya-Gamma Gibbs sampler via
|
temporal |
Optional temporal spec: a validated |
control |
A named list of numerical / tuning knobs (statistical
arguments stay in the signature above): |
Details
For family = "neg_binomial_2" the Polya-Gamma weights are drawn
at the exact real shape PG(y + r, eta) and the dispersion r is
updated by a random-walk Metropolis-Hastings step on log(r) whose
stationary support is bounded to r in [0.1, 500]; data favouring a
dispersion outside that range pile up at the boundary.
Every sampler that centres a latent effect – the negative-binomial
kernels, and the binomial kernels carrying a spatial or temporal
field – adds the removed level to the first coefficient, which leaves
eta unchanged only when the first column of X is an all-ones
intercept. Those routes error on a design without one.
Value
List with beta draws, RE draws, sigma_re draws (plus the spatial
field draws when spatial is supplied)
Examples
set.seed(1)
G <- 20L; npg <- 15L; n <- G * npg
grp <- rep(seq_len(G), each = npg)
X <- cbind(1, rnorm(n))
b <- rnorm(G, 0, 0.6)
y <- rbinom(n, 1, plogis(X %*% c(-0.2, 0.5) + b[grp]))
fit <- tulpa_gibbs(y, rep(1L, n), X, grp, G, family = "binomial",
control = list(n_iter = 500L, warmup = 250L))
colMeans(fit$beta)
Outer hyperparameter-grid integration with a user-supplied inner fit
Description
Generic driver for nested-Laplace-style outer integration over a small
hyperparameter block. The user supplies per-axis specs (values + optional
log-prior + log-scale / bounds / refinable metadata) and an inner_fit
callback that, at every hyperparameter cell, returns the inner log marginal
and optionally the fixed-effect posterior mode + marginal covariance.
The driver builds the Cartesian outer grid, normalises the log-marginals to
integration weights, reports per-axis posterior moments and weighted
quantiles, and (when the inner fit supplies them) law-of-total-covariance
fixed-effect posterior + mixture draws.
This factors the per-family outer-grid plumbing in tulpa_nested_laplace()
/ tulpa_nested_laplace_joint() / tulpa_re_cov_nested() into one
callback-driven entry point: downstream consumers (occupancy / N-mixture /
cover hurdle families in tulpaObs, custom user families) drop in their own
per-cell inner fit and get the outer integration for free.
Usage
tulpa_hyper_grid(
hyper_specs,
inner_fit,
combine = c("law_of_total_cov", "weighted_mean_only", "none"),
n_draws = 2000L,
seed = NULL,
beta_names = NULL,
control = list()
)
Arguments
hyper_specs |
A list of axis specs. Each spec is either a
|
inner_fit |
|
combine |
How to pool per-cell fixed-effect posteriors into a single posterior over the betas. One of:
|
n_draws |
Number of posterior draws of the fixed effects to
synthesise from the cell mixture (default 2000). Used only when
|
seed |
Optional integer seed for the draw synthesis. |
beta_names |
Optional character vector naming the fixed-effect
coordinates. When |
control |
Optional list of refinement / tuning knobs:
|
Value
A list of class c("tulpa_hyper_grid", "tulpa_fit", "list") with:
-
theta_grid– numeric matrix[n_cells x n_axes]of outer-grid hyperparameter values; columns named after the axes. -
theta_names– character vector of axis names. -
log_marginal– numeric[n_cells]; per-cell log integrandinner$log_marginal + log_prior(theta_cell). Non-finite / failed cells are-Inf. -
log_prior– numeric[n_cells]; per-cell log-prior contribution (the sum across axes ofaxis$log_prior(theta_cell[axis])).0when all axes havelog_prior = NULL. -
weights– numeric[n_cells]summing to 1 (or allNAwith a warning when no cell carries finite mass). -
theta_mean,theta_sd– named numeric vectors; weighted posterior mean and SD per axis. SDs refit via the 3-point Laplace-at-mode parabola where possible. -
theta_median,theta_ci_lo,theta_ci_hi– named numeric vectors; weighted-quantile median and 2.5 / 97.5\ (the recommended summary for right-skewed scale-like axes). -
beta,beta_cov,draws– fixed-effect posterior, present percombine:-
combine = "law_of_total_cov": weighted mean, total covariance (E[V] + V[E]),[n_draws x p]mixture draws. -
combine = "weighted_mean_only": weighted mean only;beta_covanddrawsareNULL. -
combine = "none": all three areNULL.
-
-
means,param_names,process_info,n_samples,n_params,N– thetulpa_fitaccessor surface, populated when a fixed-effect posterior is assembled. -
hyper_specs– echoed list ofhyper_axis_specobjects (the normalised form). -
combine,n_failed,n_grid,refining_axis– diagnostic fields. -
adaptive_grid_info,var_of_means_consistency_info– present when the corresponding refinement pass fired.
See Also
hyper_axis_spec() for the axis-spec constructor;
tulpa_nested_laplace_joint() for the family-specific outer-grid
driver that this helper generalises.
Examples
## Not run:
# Integrate an inner fit over a hyperparameter grid: inner_fit(theta) returns
# a per-cell fit and hyper_specs names the axes. tulpa_nested_laplace() is the
# packaged driver built on this.
res <- tulpa_hyper_grid(hyper_specs, inner_fit)
## End(Not run)
Select the symplectic integrator for HMC and NUTS
Description
Get or set the symplectic splitting integrator that the exact-MCMC tier (HMC / NUTS) uses to build its trajectory proposals. The integrator is backed by the SIMP library, which supplies leapfrog and the higher-order Yoshida members from one triple-jump composition.
Usage
tulpa_integrator(name, mts_substeps = 4L)
Arguments
name |
Integrator name: |
mts_substeps |
Number of inner prior-force substeps per trajectory step
for the |
Details
The default, "leapfrog", reproduces tulpa's historical trajectory step
exactly.
"minerror2" is a two-stage order-two scheme whose coefficient is tuned to
cancel the leading energy error on a Gaussian target. Since mass adaptation
drives a posterior toward an isotropic Gaussian, it conserves energy well
near the adapted optimum and adapts to larger step sizes, at two gradient
evaluations per step. It is the recommended choice when leapfrog's step size
is limited by energy error on a near-Gaussian posterior.
"yoshida4" is an order-4 scheme that also samples reliably (three gradient
evaluations per step). "yoshida6" and "yoshida8" are available but
experimental for NUTS: high-order composition integrators have a sharp
step-size stability threshold that the dual-averaging adaptation pushes
against, so "yoshida6" needs a high adapt_delta (0.95 or more) and
"yoshida8" often fails to adapt. For sampling, prefer "leapfrog",
"minerror2", or "yoshida4".
"adaptive2" and "adaptive3" are step-adapted versions of the two- and
three-stage schemes. Rather than fixing the coefficient in advance, each NUTS
chain resolves it at the end of warmup for its own operating point: the
coefficient that minimizes the worst-case energy error over the band of
dimensionless steps the chain actually takes, read off from the adapted mass
matrix and the local posterior curvature. Where "minerror2" is optimal only
in the small-step limit, "adaptive2" tracks the chain's realised step band;
"adaptive3" spends a third gradient per step to hold a small error over a
wider band. Both run a fixed placeholder of the same stage family during
warmup, so the dual-averaged step size carries over. Step-adaptation applies
to NUTS (the default sampler); the fixed-trajectory HMC path uses the
placeholder.
"mts" is a multiple-time-stepping (RESPA) integrator. It splits the force
into a stiff but cheap prior part – the Gaussian latent structure – and a
smooth but expensive likelihood part. Each NUTS trajectory step takes
mts_substeps inner leapfrog substeps against the prior force while
evaluating the likelihood gradient only once, so the outer step can be larger
without the stiff prior forcing it small. It pays one full gradient per step
(as leapfrog does) plus mts_substeps cheap prior gradients, and helps most
when the latent field is stiff relative to a comparatively flat likelihood.
Like the other schemes it applies to NUTS.
The choice is process-global (like the gradient mode): set it once before
fitting. It is read on the main thread at the start of sampling. Because it
is process-global, a caller that changes it owns restoring it – and an error
between the two calls would leave the process on the other integrator.
with_tulpa_integrator() does both, restoring on error as well as on
success.
Value
If name is omitted, the current integrator name. If name is
given, the previous name is returned invisibly.
Examples
tulpa_integrator() # current integrator
old <- tulpa_integrator("yoshida4")
tulpa_integrator(old) # restore
Recognize an inline varying-coefficient bar
Description
Test whether a one-sided formula carries a single varying-coefficient
grouping bar of the form ~ 1 + w || node (independent fields, ||) or
~ 1 + w | node (correlated fields, |). This is the same grammar
spatial() and the inline temporal() field constructor accept, and the
grammar tulpa_bar_field_specs() expands. A downstream package can use it to
branch on "is this term a spatial / temporal varying-coefficient bar?" before
calling tulpa_bar_field_specs().
Usage
tulpa_is_spatial_bar(x)
Arguments
x |
A one-sided formula (e.g. |
Value
A single logical: TRUE when x is (or wraps) a | / || bar,
FALSE otherwise (a plain formula such as ~ 1 + w, a non-bar term, or a
non-formula / non-language input).
See Also
tulpa_bar_field_specs() for expanding the bar into per-column
field specs.
Examples
tulpa_is_spatial_bar(~ 1 + w || cell) # TRUE
tulpa_is_spatial_bar(~ 1 + w | cell) # TRUE (correlated)
tulpa_is_spatial_bar(~ 1 + w) # FALSE (no bar)
K-fold cross-validation for a tulpa fit
Description
Splits the data into K folds, refits the model on each K - 1 training
partition (via the fit's stored tulpa() call), and accumulates the held-out
fold's pointwise log predictive density
\log \frac{1}{S}\sum_s p(y_i \mid \eta_i^{(s)}) over the training
posterior draws. The summed elpd_kfold is directly comparable to the
elpd_loo from tulpa_criteria() – the exact refit counterpart to the
PSIS-LOO approximation, for when the Pareto k-hat gate flags LOO as
unreliable.
Fixed-effect / GLMM fits only: subsetting the observations breaks a spatial or
temporal field, so those fits are rejected (use PSIS-LOO via
tulpa_criteria()). Held-out random-effect groups contribute at their prior
mean (population-level held-out prediction), matching predict().
Usage
tulpa_kfold(object, data, K = 10L, folds = NULL, n_trials = NULL, seed = NULL)
Arguments
object |
A |
data |
The data frame the model was fit to. |
K |
Number of folds (default 10). |
folds |
Optional integer vector of fold ids, length |
n_trials |
Optional binomial denominators (length |
seed |
Optional seed for the random partition. |
Value
A list with elpd_kfold (summed held-out elpd), se_elpd_kfold (its
standard error), pointwise (per-observation held-out elpd), folds, and
K.
See Also
tulpa_criteria() for PSIS-LOO / WAIC on a single fit.
Examples
set.seed(1)
d <- data.frame(x = rnorm(120))
d$y <- rpois(120, exp(0.4 + 0.6 * d$x))
fit <- tulpa(y ~ x, data = d, family = "poisson", mode = "laplace")
cv <- tulpa_kfold(fit, data = d, K = 5, seed = 1)
cv$elpd_kfold
Fit a model via Laplace approximation
Description
General-purpose Laplace approximation for latent Gaussian models. Finds the mode of the latent field (beta + random effects) and returns the Laplace-approximated marginal likelihood.
This is the public API for model packages (tulpaGlmm, tulpaObs, etc.)
to call tulpa's Laplace engine. As the low-level engine entry point (not a
front-door fitter), it keeps its numerical controls (max_iter, tol,
n_threads, return_hessian) inline in the signature rather than in a
control list, so callers assembling many solves pass them positionally.
Usage
tulpa_laplace(
y,
n_trials,
X,
re_list = list(),
family = "binomial",
phi = 1,
phi2 = NULL,
spatial = NULL,
weights = NULL,
offset = NULL,
max_iter = 100L,
tol = 1e-06,
n_threads = 1L,
return_hessian = TRUE,
beta_prior = NULL,
return_re_cov = FALSE,
X_zi = NULL,
zi_prior_sd = 2.5,
return_joint_hessian = FALSE,
compute_skew = FALSE,
skew_idx = NULL,
debias = NULL
)
Arguments
y |
Response vector (integer for binomial/poisson/negbin, numeric for gaussian) |
n_trials |
Trial sizes (integer vector, used for binomial only) |
X |
Fixed-effects design matrix |
re_list |
List of RE specifications. Each element is a list with:
|
family |
Character: |
phi |
Dispersion parameter. For |
phi2 |
Optional second dispersion: the Student-t degrees of freedom
( |
spatial |
Optional spatial specification (tulpa_spatial object) |
weights |
Optional observation weights (numeric vector, length |
offset |
Optional observation-level offset on the linear predictor
(numeric vector, length |
max_iter |
Maximum Newton iterations (default 100) |
tol |
Convergence tolerance (default 1e-6) |
n_threads |
Number of threads (default 1) |
return_hessian |
Logical: return the fixed-effect Hessian block? (default TRUE) |
beta_prior |
Optional Gaussian prior on the fixed effects. |
return_re_cov |
If |
X_zi |
Optional zero-inflation design matrix ( |
zi_prior_sd |
Prior SD on the zero-inflation coefficients,
|
return_joint_hessian |
If |
compute_skew |
If |
skew_idx |
1-based latent indices to probe (in the |
debias |
Subspace debias: a list with |
Value
A list with:
-
mode: full mode vector (beta, then RE values per term) -
log_marginal: Laplace-approximated log-marginal likelihood -
n_iter: number of Newton iterations -
converged: logical, whether the stopping rule was met -
score_max: largest absolute component of the joint penalized score at the returned mode – the residual the solve actually achieved, which is a different question fromconvergedand the one anything differentiating through the mode depends on -
log_det_Q: log-determinant of the Hessian -
H_beta: fixed-effect block of the Hessian (if return_hessian = TRUE) -
cov_blocks: list of per-group posterior covariance matrices, one per (RE term, group) in term-major then group order (if return_re_cov = TRUE)
Examples
set.seed(1)
n <- 200L
X <- cbind(1, rnorm(n))
eta <- X %*% c(-0.3, 0.8)
y <- rbinom(n, 1, plogis(eta))
fit <- tulpa_laplace(y, rep(1L, n), X, family = "binomial")
fit$mode # posterior mode of the fixed effects
Fit a beta-regression model via Laplace, estimating the precision
Description
Thin wrapper around tulpa_laplace() for family = "beta". The
Laplace engine treats phi as fixed per fit (same contract as gamma
and neg_binomial_2); this wrapper does an outer 1-D optimisation of
the Laplace-approximated log-marginal over phi, then refits at the
optimum to return betas and Hessian.
The mean-precision parameterisation is y ~ Beta(mu * phi, (1 - mu) * phi)
with default logit link; y must be strictly in (0, 1).
Usage
tulpa_laplace_beta(
y,
X,
re_list = list(),
spatial = NULL,
weights = NULL,
offset = NULL,
max_iter = 100L,
tol = 1e-06,
n_threads = 1L,
beta_prior = NULL,
phi_init = NULL,
phi_bounds = c(0.1, 10000),
outer_tol = 1e-04,
mode = c("laplace", "nuts"),
control = list()
)
Arguments
y |
Response in |
X |
Fixed-effects design matrix. |
re_list, spatial, weights, offset, max_iter, tol, n_threads, beta_prior |
Passed to |
phi_init |
Optional starting value for the precision. If |
phi_bounds |
Numeric length-2 vector with lower/upper bounds on
|
outer_tol |
Tolerance for the outer optimisation. Default 1e-4. |
mode |
Inference method (the method is an argument, not a parallel
verb): |
control |
Passed to |
Value
For mode = "laplace", the list returned by tulpa_laplace() at the
optimum, augmented with phi (the optimised precision) and
phi_log_marginal (the optimisation trace). For mode = "nuts", the draws
object returned by tulpa_nuts_beta().
Examples
set.seed(1)
n <- 200L
X <- cbind(1, rnorm(n))
mu <- plogis(X %*% c(0.2, 0.7)); phi <- 8
y <- rbeta(n, mu * phi, (1 - mu) * phi)
fit <- tulpa_laplace_beta(y, X)
fit$mode
Latent Factor Specification for Unmeasured Confounders
Description
Specify latent factors to capture shared unmeasured confounders between model processes. Latent factors are particularly useful when you suspect that both processes are driven by common unmeasured variables.
Value
The constructor documented in this family (latent_factor())
returns a tulpa_latent specification object consumed by ratio /
multi-arm model packages built on tulpa (e.g. tulpaRatio), where the
shared factor enters two or more linear predictors and cancels in the
derived ratio. It is a multi-arm construct: the single-response tulpa()
front door does not read it (for an in-formula latent Gaussian block on a
single response, use latent(tgmrf(...))).
Streaming pointwise log-likelihood
Description
Wrap a pointwise log-likelihood for tulpa_criteria() without materializing
the whole [n_draws x n_obs] matrix. A plain matrix is wrapped directly; a
block generator (a function of an integer column vector returning the
[n_draws x length(cols)] submatrix) lets the criteria accumulators stream
over observation blocks, so an EVA-scale [200 x 1.16M] log-likelihood is
consumed a few thousand columns at a time.
Usage
tulpa_loglik(x, n_obs = NULL, n_draws = NULL)
Arguments
x |
Either a numeric |
n_obs, n_draws |
Required when |
Value
A tulpa_loglik object: a list with get(cols), n_obs,
n_draws, and materialized.
See Also
Multinomial (nominal K-class) logistic regression via Laplace
Description
Fits a baseline-category multinomial logit model: for a K-level unordered
response, classes 1..K-1 each get their own linear predictor and class K
is the baseline. The coupled multinomial likelihood is solved by a Newton
step to the penalized mode (a Gaussian ridge prior on the coefficients) and
summarized by a Laplace approximation, reusing the native multinomial kernel.
Usage
tulpa_multinomial(
formula,
data,
beta_prior = .tulpa_default_beta_prior("multinomial"),
control = list()
)
Arguments
formula |
Model formula; the response must be a factor (or coercible to one) with >= 3 levels. The baseline is the last level. |
data |
A data frame. |
beta_prior |
Fixed-effect prior as |
control |
List of numerical knobs: |
Value
A tulpa_fit (subclass tulpa_multinomial) with coef (named
class:term), vcov, draws, log_marginal, classes, baseline, and
the standard generic-method support.
See Also
tulpa() for single-process GLMMs.
Examples
set.seed(1)
n <- 300L; x <- rnorm(n)
eta <- cbind(0.5 + 1.0 * x, -0.3 - 0.8 * x) # classes 1, 2 vs baseline 3
P <- cbind(exp(eta), 1); P <- P / rowSums(P)
y <- factor(apply(P, 1, function(pr) sample.int(3L, 1L, prob = pr)))
fit <- tulpa_multinomial(y ~ x, data = data.frame(y = y, x = x))
coef(fit)
Nested Laplace approximation for latent Gaussian models
Description
Generic outer-grid nested Laplace driver. Builds a grid over the hyperparameters of a single latent prior block (spatial or temporal), runs an inner Laplace at each grid point with warm-starting, and integrates over the grid to give proper hyperparameter marginals.
Supported priors:
Spatial (areal):
"icar"(1D grid on tau),"bym2"(2D on (sigma, rho)),"car_proper"(2D on (tau, rho); rho lives in the eigenvalue interval (1/lambda_min, 1/lambda_max) ofD^{-1} W).Spatial (continuous):
"nngp"(2D on (sigma2, phi_gp)),"hsgp"(2D on (sigma2, lengthscale)).Temporal:
"rw1","rw2"(1D grid on tau),"ar1"(2D on (tau, rho))SPDE continuous spatial: see
cpp_nested_laplace_spde()(separate entry, rebuilds Q via SPDE Q-builder).
Usage
tulpa_nested_laplace(
y,
n_trials,
X,
prior = NULL,
spec = NULL,
data = NULL,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
family = "binomial",
phi = 1,
likelihood = NULL,
control = list()
)
Arguments
y |
Response vector. |
n_trials |
Trial sizes (binomial). Pass |
X |
Fixed-effects design matrix. |
prior |
A list describing the latent prior block. Required field
Any block accepts an optional
A default grid axis is a starting axis, not a hard ceiling: for |
spec |
Optional |
data |
Data frame used to validate |
re_idx |
Optional 1-based RE group index per obs (defaults to no RE). |
n_re_groups |
RE group count (default 0). |
sigma_re |
RE standard deviation (default 1). |
family |
|
phi |
Dispersion (negbin/gamma). |
likelihood |
Optional model-supplied likelihood, replacing the built-in
|
control |
Optional list of perf/numerical tuning knobs (statistical
arguments stay top-level), following the
|
Value
A list with:
-
theta_grid: matrix or vector of grid hyperparameter values. -
log_marginal: log p(y, mode | theta_k) at each grid point. -
weights: integration weights normalising to sum 1. -
theta_mean,theta_sd: posterior moments per hyperparameter. -
n_iter: inner Newton iterations per grid point. -
modes: matrix[n_grid x n_x]of inner modes, when stored. -
pareto_k,pareto_k_is_ess: outer Pareto-\hat{k}and its importance-sampling ESS (NAwhen not computed for the grid; seecontrol$diagnose_k). -
inner_skew,inner_skew_idx,inner_skew_dropped: the inner-Laplace skewness diagnostic (gamma_3) at each scored latent index and its 1-based index, plus a count of (index, observation) contributions dropped for a non-finite third derivative (seecontrol$diagnose_skew). -
timing: named numeric of wall-clock seconds (total,setup,grid,postproc,diagnostics); thegridphase is the inner Laplace pass that scales with grid size. Surfaced one-line inprint. -
prior: echoed input.
References
Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392.
Examples
set.seed(1)
S <- 30L # spatial units arranged in a chain
nb <- lapply(seq_len(S), function(s) setdiff(c(s - 1L, s + 1L), c(0L, S + 1L)))
nn <- lengths(nb)
field <- as.numeric(scale(cumsum(rnorm(S, 0, 0.4)))) # smooth spatial field
idx <- rep(seq_len(S), each = 6L); n <- length(idx); x <- rnorm(n)
y <- rbinom(n, 1L, plogis(-0.2 + 0.6 * x + field[idx]))
prior <- list(type = "icar", n_spatial_units = S, spatial_idx = idx,
adj_row_ptr = c(0L, cumsum(nn)), adj_col_idx = unlist(nb) - 1L,
n_neighbors = nn, tau_grid = c(0.5, 1, 2, 4, 8))
fit <- tulpa_nested_laplace(y, rep(1L, n), cbind(1, x), prior = prior,
family = "binomial")
fit$theta_mean # marginalized ICAR precision
Joint multi-likelihood nested Laplace approximation
Description
Outer-grid nested Laplace driver for joint models – multiple response arms sharing one latent prior block, parameterized as a per-arm field amplitude (sigma) on a unit-precision latent.
Supported priors:
-
"bym2"– outer grid over(sigma, rho [, alpha]). Latent:phi (n_s) | theta (n_s)with unit-precision ICAR + iid components. -
"icar"– outer grid over(sigma [, alpha]). Latent:phi (n_s)with unit-precision ICAR. -
"car_proper"– outer grid over(sigma, rho_car [, alpha]). Latent:phi (n_s)withQ = D - rho_car * W.
Other backends (NNGP, HSGP, RW1/2, AR1) follow the same interface and land under Phase 3.
Usage
tulpa_nested_laplace_joint(
responses,
prior,
copy = NULL,
phi_grid = NULL,
prior_sigma = NULL,
prior_alpha = NULL,
prior_phi = NULL,
cell_coupling = "separable",
control = list()
)
Arguments
responses |
A named list of arm specs (length >= 1). Each arm:
|
prior |
A list describing the shared latent prior block. Required
field
|
copy |
Multi-block copy specification (multi-block |
phi_grid |
Optional list specifying per-arm dispersion axes on the
outer grid. Accepts either a named list (keys = arm names) or a
positional list of length
Family-specific interpretation of
Each |
prior_sigma, prior_alpha |
Optional regularizing hyperpriors on the
donor field amplitude
|
prior_phi |
Optional regularizing hyperprior on the per-arm
dispersion axes declared through |
cell_coupling |
Character scalar naming a per-cell coupled likelihood
registered against tulpa's process-global registry (see
|
control |
Optional list of perf/numerical tuning knobs (statistical
arguments stay top-level), following the
|
Value
A list of class c("tulpa_nested_laplace_joint", "tulpa_nested_laplace", "list") with:
-
theta_grid,theta_names– outer-grid hyperparameter values (includes thealphaaxis whencopyis set). -
log_marginal,weights– per-grid-point log-marginal and integration weights (sum to 1). -
theta_mean,theta_sd– posterior moments per hyperparameter, includingalphawhencopyis set. -
theta_median,theta_ci_lo,theta_ci_hi– weighted-quantile median and 2.5/97.5 empirical CI per hyperparameter axis (same names astheta_mean). Recommended summary for right-skewed scale-like axes (alpha at small n_pos, sigma/range/phi near a boundary), where the posterior mean is pulled by the right tail away from the bulk andmean +/- 1.96 sdmis-states the uncertainty. -
modes–[n_grid x n_x]matrix of inner modes. -
n_iter– inner Newton iterations per grid point. -
arm_layout– list with per-armbeta_start,re_start, spatial offset(s) andn_xfor decoding modes. -
prior,responses,copy– echoed inputs. -
timing– named numeric of wall-clock seconds:totalplus thesetup(validation / encoding / grid construction),grid(inner Laplace solves, including adaptive-refinement and consistency passes),postproc(weight / moment / marginal assembly) anddiagnostics(outer Pareto-\hat{k}) phases. Thegridphase is the one that scales with grid size and core count. Surfaced one-line inprint. -
pareto_k,pareto_k_is_ess,pareto_k_scope– outer Pareto-\hat{k}accuracy diagnostic and its importance-sampling ESS (bothNAwhencontrol$diagnose_k = FALSEor the fit declines; see thediagnose_kcontrol knob).pareto_k < 0.7indicates the nested integration is reliable;>= 0.7that the (skewed / heavy- tailed) hyperparameter posterior is misfit by the Gaussian grid and the fit should escalate to an exact debias. -
pareto_k_declined– whenpareto_kisNA, WHY:"not_requested"(control$diagnose_k = FALSE; nothing is wrong),"unguessable_axis: <axis>"(a support the engine will not guess, e.g. car_proper'srho_car– a PERMANENT limitation of that family, so read the quadrature ESS instead),"draws_too_few","grid_too_small","no_varying_axis","degenerate_proposal"(the outer mode curvature came back non-finite – a signal about the fit),"not_applicable", or"internal_inconsistency"(an engine bug worth reporting).NAon a fit whose\hat{k}WAS computed. -
pareto_k_proposal_source– how the outer importance proposal the\hat{k}scores was built:"mode_hessian"from the Laplace curvature at the hyperparameter mode (the CCD design's, or a finite-difference Hessian when a sharp posterior collapses the grid),"grid_moment"from the grid-weighted covariance,"moment_matched"when the moment-matching refinement improved on either,"grid_mixture"when the faithful mixture-over-grid-cells proposal won, or"skew_normal"when the skew-normal rescue did.NAwhen the diagnostic is off or declines. The mode-Hessian source keeps the\hat{k}meaningful when the grid concentrates on ~1 cell. -
pareto_k_regime– whether the outer grid actually integrated hyperparameter uncertainty:"spread", or"collapsed_interior"/"collapsed_edge"when the quadrature effective sample size falls below two cells so the integration has degenerated to a point evaluation at the modal hyperparameter. An interior collapse is benign (the grid bracketed the mode; the fit is empirical Bayes at that mode and only the integrated hyperparameter uncertainty is missing); an edge collapse means the mode sits at an extreme node, so the grid may be too narrow. Attached from the stored weights, so it is present even withcontrol$diagnose_k = FALSE. -
pareto_k_grid_edge_axes,pareto_k_grid_edge_sides– for an edge collapse, which axes the dominant cell sits against and on which side ("lower"/"upper"); widen those axes and refit to confirm the mode is bracketed. Axes the grid pins to one value are excluded (pinned, not at a boundary). -
outer_grid_placement–"fixed"(the defaultsigma_gridaxis was used as-is) or"auto_recentered"when acollapsed_edgeonsigmatriggered the mode-Hessian recenter-and-refit (see thepriorargument above).outer_grid_recenter_attempts(integer) andouter_grid_prior_added(logical: whether the light default PC(U=3, alpha=0.01) sigma prior was applied) are set alongside it, plusouter_grid_prior_declined = "prior_pinned"when a second attempt ran but the caller's pinnedprior_sigmaheld that prior back. -
outer_grid_recenter_declined– on a"fixed"placement, why the recenter did not run:"grid_not_collapsed"(the grid already brackets the mode – the common case, no refit needed),"axis_pinned"(the caller pinnedsigma_grid; mark it withauto_grid()if it is a default rather than a choice), or"no_usable_curvature"(the mode-Hessian the recenter needs was unavailable or degenerate, e.g. a car_proper grid whoserho_caraxis has unguessable support). Absent when the fit WAS recentred. -
outer_grid_recenter_sd_clamp,outer_grid_recenter_sd_raw,outer_grid_recenter_sd_used– on a recentred fit, one entry per moved axis: which mode-SD bound the placement hit ("none"/"floor"/"ceiling"), the SD the finite-difference stencil MEASURED, and the SD the axis was actually laid from. A clamped axis was laid from a substituted spread rather than a measured one, and the reported interval is read off that span; the two used to be indistinguishable on the fit. -
pareto_k_outer_skew– per-axis skewness of the hyperparameter marginal in the proposal's whitened coordinate, present only when a\hat{k}above the good band triggered the skew-normal rescue pass. It is the EXPLANATION for an inflated\hat{k}: a symmetric Gaussian proposal against a right-skewed variance-component marginal has a heavy importance-ratio tail whatever the integration's quality.NULLwhen the Gaussian proposal already fit. -
pareto_k_se_boot,pareto_k_ci_low,pareto_k_ci_high,pareto_k_se_formula,pareto_k_tail_points,pareto_k_tail_points_requested,pareto_k_band_confident– the outer Pareto-\hat{k}uncertainty, present whenever the diagnostic ran.pareto_k_se_bootis the bootstrap SE of the k-hat andpareto_k_ci_low/pareto_k_ci_highits 2.5\ quantiles – the estimator's sampling spread GIVEN the proposal, not a posterior credible interval;pareto_k_se_formulais the closed-form GPD-shape MLE asymptotic SE cross-check.pareto_k_tail_pointsis the GPD tail size used andpareto_k_tail_points_requestedthek_tail_pointsrequest (NAwhen automatic).pareto_k_band_confidentis TRUE iff the bootstrap CI lies within one reliability band,NAwhen the bootstrap was off or could not fit.diagnose_drawsanddiagnose_cost_ratio(the diagnostic's draw budget and its wall-clock cost relative to the fit) are attached at the top level. -
pareto_k_by_arm,pareto_k_by_arm_is_ess,pareto_k_by_arm_scope– present only withcontrol$diagnose_k = "by_arm". Named (by arm) outer Pareto-\hat{k}restricted to each arm's hyperparameter axes, the other arms held at their posterior mean, so a tail-heavy joint k can be localised to one arm. A per-arm entry isNAwhen that arm carries no varying axis. Each arm carries its own bootstrap uncertainty:pareto_k_by_arm_se_boot,pareto_k_by_arm_ci_low/pareto_k_by_arm_ci_high,pareto_k_by_arm_se_formula,pareto_k_by_arm_tail_pointsandpareto_k_by_arm_band_confident. -
k_quality_requested,k_quality_reached,k_quality_best,k_quality_reason,k_quality_rounds– the reliability verdict for thecontrol$k_qualityintent.k_quality_requestedechoes the intent;k_quality_bestis the band actually achieved ("good"/"ok"/"unreliable", or"uncertain"when the bootstrap CI crosses a boundary);k_quality_reachedisTRUE/FALSEfor an"ok"/"good"target (NAfor"report"/"none"), never silently downgraded;k_quality_reasonrecords why it stopped; andk_quality_roundsis the number of escalation re-fits performed (0when the first fit sufficed or escalation was off). -
adaptive_grid_info– whenadaptive_grid = TRUE, a list withtriggered_axes(character) andn_points_added(integer) describing the refinement passes. NULL otherwise. -
local_ccd_info– whenlocal_ccdengaged (ork_refine = "ccd"), a list withn_cells_refined,n_nodes_added, the refinedcells, then_cells_before/n_cells_aftergrid sizes,n_design_nodes(the cells that carry an in-cell design weight rather than their own mass),design_mass(the share of the integration weight sitting on them) andcell_share(the share each refined cell held on the BASE grid, before any node was placed). The two shares differ: the replacement nodes sit nearer the peak than the cell's own coordinate did, so refining raises the share the refined region holds, and reading both separates how concentrated the base grid already was from how much the refinement concentrated it. Three per-cell readings come with it, on three orthogonal axes, none of them gating anything. Shape:misfit(the standardized cubic magnitude of the part of the cell the design cannot represent, the one readingskew_maxgates on, withmisfit_declined/cells_declinedfor the cells put back as their own mass atoms). Centring:offset(the norm of the whitened gradient, i.e. how far the cell's own peak sat from the cell's coordinate in units of its marginal spread) andmode_gain(the same displacement in the cell's own curvature units,0.5 * g' (-H)^-1 gin nats, read off the same fit's quadratic coefficients and comparable across cells whose curvature differs;NAwhere-His not positive definite, a cell with no interior peak to be displaced from). Mass:log_mass_ratio(the cell's refined mass over the coarse atom it replaced,log_mass_refined - log_mass_coarse, both carried on the log scale with the cell's outer design weight in them) andmax_node_weight(the share the single largest node takes of its own cell's refined mass). Each carries a_declinedtwin for the cells the gate put back, whose nodes were evaluated before the score was read.misfitcertifies that the design can represent the cell's shape and nothing more: a cell can be exactly quadratic and still be read at a point that is not representative of it (offset/mode_gain), and refining it can still move how much mass it competes for against its unrefined neighbours (log_mass_ratio, the embedded-rule local error indicator of adaptive cubature). NULL when local CCD was off or declined (single-block,< 4axes, an activephi_grid, or no peaked interior cell). -
integration_requested,integration_declined– whatintegrationasked for, and why the CCD did not run.$integrationnames the integrator that RAN, and.nl_node_support()keys the interval construction off it, so a caller who asked for a moment rule and received a density grid reads the reason here rather than inferring it.integration_declinedisNA_character_when nothing was declined, and otherwise one of"axis_count"(fewer transformable latent axes than the requested mode's threshold),"unguessable_axis"(a CAR_properrho_caror a non-BYM2rho),"degenerate_axis"(a single-valued axis),"modefind_ridge"/"modefind_boundary"/"modefind_degenerate"/"modefind_failed","hessian_singular"or"hessian_not_pd". Multi-block fits only. -
weight_kind– one entry per outer-grid cell,"mass"or"design". A tensor cell holds the mass of its own cell and a CCD node holds a design weight, so a fit integrated by one rule reports one value throughout; a locally refined grid carries both, and this says which per cell instead of leaving it to be read offintegration. -
theta_interval_read,theta_interval_design_mass– what the reported per-axistheta_median/theta_ci_lo/theta_ci_hiwere read off, and how much of the integration weight sits on nodes whose cumulative sum is not a CDF."density"is a weighted quantile over cells that discretize the posterior (design_mass0),"moment_rule"an interval from the moments a central-composite design delivers (design_mass1), and"mixed"the locally CCD-refined grid, which carries both kinds at once. A mixed support still reports the weighted quantile, which measured best against a converged reference in bothdesign_massregimes; the share is the regime variable, since on that part of the support the quantile is bounded by the refined cells' own grid neighbourhoods rather than by the posterior. Every nested path stamps the pair, not only the multi-block driver. -
within_cell_requested,theta_within_cell,theta_within_cell_declined– the WITHIN-CELL construction the same intervals were read with. The kind above says what the integrator left; this says how each cell's mass was spread inside its own box when the grid was read back."box_uniform"is the default and puts the cumulative full mass at each cell edge;"chord"(control$within_cell) puts the cumulative mid-mass at each cell coordinate, the same masses over the same boxes with the knots moved half a cell. The construction is recorded per axis, and an axis whose cell partition could not be built falls back to"chord"on its own with the reason intheta_within_cell_declined("support_<kind>","single_node","boxes_do_not_tile","no_usable_node"). -
theta_cell_edge_coord,theta_cell_edge_declined– per axis, the COORDINATE that axis's outer cell edges were mirrored in, and why the support the axis declares did not produce them. A declared support is authoritative: its mirrored edge is used when finite and inside it, and otherwise the axis reports its extreme grid coordinate – which the containment test has already placed inside the support – rather than falling through to a coordinate guessed from the node values, which is what put a lower bound of 0 on apositiveaxis and a bound above 1 on aunitone. An axis that declares no support at all keeps the guess by design, but its mirrored edge is checked for being a representable double that brackets the coordinates, and falls back to the extreme grid coordinate with"mirrored_edge_not_representable"when it is not.theta_cell_edge_declinedisNAwhere nothing was declined. -
outer_grid_cell_width,outer_grid_axis_sd,outer_grid_h_over_sd– per axis, the cell width and the posterior SD in that axis's own coordinate, and their ratio. A within-cell reconstruction resolves an interval endpoint to within one cell, so how much of the reported width and of its realized coverage is a property of where the grid fell rather than of the posterior is governed by this ratio; below.nl_diag("grid_resolved")the cells are narrower than the posterior they discretize and the two constructions converge to each other. NA on an axis whose own marginal is maximal at an endpoint, which is the placement questionouter_grid_railed_axesreports. -
outer_grid_resolution_declined– per axis, why that axis carries no ratio, from the closed vocabularytoo_few_nodes,mode_at_edge,coord_not_finite,spacing_not_finite,stencil_degenerate,curvature_not_negative;NAon an axis that scored. The reasons are not interchangeable:mode_at_edgesays the grid does not contain that axis's own posterior mode, which is a stronger statement about the fit than any ratio, and the whole-gridresolvedverdict is withheld while one is present rather than being read off the axes that did score. -
prune_cheap_log_marginal,prune_mask,prune_n_pruned,prune_tol– present only whenprune = TRUEand the safety gate did not fall back. Cheap-pass log-marginals at every cell, a logical mask of pruned cells, the pruned-cell count, and the threshold actually applied. Pruned cells havelog_marginal = -Infso they get zero weight under.nl_normalise_weights_safe. -
prune_fallback_triggered,prune_fallback_reason– present only when the safety gate fell back to the full grid. The returned fit is the full-grid (unpruned) result; the reason string records which gate condition tripped.
(sigma, alpha) parameterization
Each arm's linear predictor reads
\eta_{arm} = X_{arm} \beta_{arm} + \sigma_{arm} \cdot z_s,
where z_s is a unit-precision latent (ICAR(tau=1) for ICAR/BYM2,
or the BYM2 mix; CAR_proper uses the structure of
D - \rho_{car} W). All arms share the donor amplitude
\sigma from the outer-grid sigma_grid axis. The copy arm's
amplitude is \sigma_{arm} = \alpha \cdot \sigma, where \alpha
is a direct outer-grid axis taken from copy$alpha_grid. The Cartesian
product is over (sigma, [rho/rho_car,] alpha[, phi]). Direct \alpha
as an outer-grid axis (rather than a post-hoc ratio
\alpha = \sigma_{pos} / \sigma_{occ}) avoids plug-in bias on the
weakly-identified ratio at small n_pos and lets a regularizing
hyperprior land on \alpha directly.
References
Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392.
See Also
tulpa_nested_laplace() for the single-arm engine.
Examples
set.seed(1)
S <- 25L # shared spatial units (chain graph)
nb <- lapply(seq_len(S), function(s) setdiff(c(s - 1L, s + 1L), c(0L, S + 1L)))
nn <- lengths(nb)
field <- as.numeric(scale(cumsum(rnorm(S, 0, 0.4))))
mk_arm <- function(m, fam) {
si <- sample(S, m, replace = TRUE); x <- rnorm(m)
lin <- 0.2 + 0.5 * x + 0.8 * field[si]
y <- if (fam == "binomial") rbinom(m, 1L, plogis(lin)) else lin + rnorm(m, 0, 0.5)
list(y = as.numeric(y), n_trials = rep(1L, m), X = cbind(1, x),
spatial_idx = si, family = fam, phi = if (fam == "gaussian") 0.5 else 1)
}
prior <- list(type = "icar", n_spatial_units = S,
adj_row_ptr = c(0L, cumsum(nn)), adj_col_idx = unlist(nb) - 1L,
n_neighbors = nn, sigma_grid = c(0.3, 0.7, 1.4))
fit <- tulpa_nested_laplace_joint(
responses = list(occ = mk_arm(200L, "binomial"), pos = mk_arm(200L, "gaussian")),
prior = prior)
fit$theta_mean # shared field amplitude, integrated across both arms
Fit a beta-regression model via NUTS (joint sampling of beta + log_phi)
Description
Bayesian counterpart to tulpa_laplace_beta(). Routes the Beta GLM
through tulpa's full NUTS backend via the generic LikelihoodSpec
interface, sampling the precision phi jointly with the regression
coefficients on the log scale. NUTS performs exact marginalisation
over phi, replacing the Brent outer-opt in tulpa_laplace_beta()
and the deterministic nested-Laplace grid that would otherwise
integrate over the Beta dispersion.
Mean-precision parameterisation, default logit link:
y_i ~ Beta(mu_i * phi, (1 - mu_i) * phi) with
mu_i = 1 / (1 + exp(-eta_i)) and eta_i = X_i %*% beta.
Usage
tulpa_nuts_beta(
y,
X,
beta_prior = .tulpa_default_beta_prior("beta_nuts"),
log_phi_prior_sd = 3,
log_phi_init = 0,
control = list()
)
Arguments
y |
Response vector, strictly in |
X |
Fixed-effects design matrix. |
beta_prior |
Fixed-effect prior as |
log_phi_prior_sd |
Prior SD on |
log_phi_init |
Starting value for |
control |
A named list of numerical / sampler knobs (statistical
arguments stay in the signature): |
Value
A list with:
-
draws–n_samples x (p + 1)matrix of post-warmup draws, columnsbeta[1] ... beta[p], log_phi. -
means– posterior means. -
phi_summary– posterior mean / median / quantiles ofphi = exp(log_phi). -
accept_prob,divergent,treedepth,epsilon– NUTS diagnostics from the underlying chain.
See Also
tulpa_laplace_beta() for the Laplace + Brent point
estimate; tulpa_laplace() for the underlying Laplace engine.
Examples
set.seed(1)
n <- 150L
X <- cbind(1, rnorm(n))
mu <- plogis(X %*% c(0.2, 0.7)); phi <- 8
y <- rbeta(n, mu * phi, (1 - mu) * phi)
fit <- tulpa_nuts_beta(y, X, control = list(n_iter = 500L, n_warmup = 250L))
colMeans(fit$draws)
Sample an SPDE GLM via NUTS, optionally jointly over Matern hypers
Description
Bayesian counterpart to fit_spde() / laplace_spde_at(). Routes the
SPDE-augmented GLM through tulpa's full NUTS backend via the generic
LikelihoodSpec interface. Two modes:
-
Legacy / fixed-hyper (
joint = FALSE, the default): conditions on the supplied(range, sigma). Samples the latent block(beta, w_mesh, log_phi)jointly. Useful as the inner step of an outer CCD / nested-Laplace integration over hypers. -
Joint hypers (
joint = TRUE): samples(beta, z_mesh, log_kappa, log_tau, log_phi)jointly via the non-centered transformw = L(theta)^{-T} z. A PC prior (Fuglstad et al. 2019 JASA) on(range, sigma)is taken fromprior_range,prior_sigma. Returns additionalw_draws,range_draws,sigma_draws,kappa_draws,tau_drawscolumns/vectors on top of the raw parameter draws.
Usage
tulpa_nuts_spde(
y,
X,
spatial,
family = c("gaussian", "poisson", "binomial", "gamma", "neg_binomial_2", "beta"),
n_trials = NULL,
joint = FALSE,
range = NULL,
sigma = NULL,
prior_range = NULL,
prior_sigma = NULL,
log_kappa_init = NULL,
log_tau_init = NULL,
beta_prior = .tulpa_default_beta_prior("spde_nuts"),
log_phi_prior_sd = 3,
log_phi_init = 0,
control = list()
)
Arguments
y |
Response vector. Family-specific:
|
X |
Fixed-effects design matrix. |
spatial |
A |
family |
One of |
n_trials |
Integer vector for |
joint |
Logical. |
range, sigma |
Fixed-hyper mode only. Matern range and marginal SD
on the field. Default to the SPDE prior medians
( |
prior_range, prior_sigma |
Joint mode only. PC prior anchors as
|
log_kappa_init, log_tau_init |
Joint mode only. Initial values for
the hyper slots. Default to the value implied by the PC anchor's
|
beta_prior |
Fixed-effect prior as |
log_phi_prior_sd |
Prior SD on
|
log_phi_init |
Starting value for |
control |
A named list of numerical / sampler knobs (statistical
arguments stay in the signature): |
Value
A list with draws (matrix n_samples x n_params), means,
phi_summary (where applicable), accept_prob, divergent,
treedepth, epsilon, joint_hypers, plus the supplied spatial
spec. In joint = TRUE mode additionally:
w_draws (transformed z -> w per draw), range_draws,
sigma_draws, kappa_draws, tau_draws, and range_summary,
sigma_summary (mean/median/5%/95% quantiles).
See Also
fit_spde() for the Laplace counterpart and the nested-Laplace
path over (range, sigma).
Examples
## Not run:
# SPDE-field NUTS. `spatial` is an SPDE spec built from a mesh, e.g.
# spatial_spde(~ x + y, df). See fit_spde() for the Laplace analogue.
fit <- tulpa_nuts_spde(y, X, spatial = spatial_spde(~ x + y, df))
## End(Not run)
Ordinal (ordered K-class) cumulative-logit regression via Laplace
Description
Fits a proportional-odds cumulative-logit model for an ordered factor
response: P(y <= j | x) = plogis(c_j - x'beta) with ordered cutpoints
c_1 < ... < c_{K-1}. The penalized mode (a ridge prior on the coefficients
and cutpoints) is found by L-BFGS and summarized by a Laplace approximation.
There is no separate intercept – the cutpoints carry the baseline levels.
Usage
tulpa_ordinal(
formula,
data,
link = c("logit", "probit"),
beta_prior = .tulpa_default_beta_prior("ordinal"),
cut_prior_sd = 10,
control = list()
)
Arguments
formula |
Model formula; the response must be an ordered (or coercible)
factor with >= 3 levels. An intercept in |
data |
A data frame. |
link |
Cumulative link: |
beta_prior |
Fixed-effect prior as |
cut_prior_sd |
SD of the mean-zero Gaussian ridge prior on the cutpoint parameters (default 10). |
control |
List of numerical knobs: |
Value
A tulpa_fit (subclass tulpa_ordinal) with coef (covariate
effects), cutpoints, vcov, draws, log_marginal, levels.
See Also
tulpa_multinomial() for the nominal (unordered) case.
Examples
set.seed(1)
n <- 400L; x <- rnorm(n)
cuts <- c(-1, 0.5, 2); eta <- 0.8 * x
Fm <- plogis(outer(-eta, cuts, "+")); P <- cbind(Fm, 1) - cbind(0, Fm)
y <- ordered(apply(P, 1, function(pr) sample.int(4L, 1L, prob = pr)))
fit <- tulpa_ordinal(y ~ x, data = data.frame(y = y, x = x))
fit$coefficients; fit$cutpoints
Parse a mixed-model formula
Description
Decomposes a formula into fixed effects and random effects by walking the formula's abstract syntax tree. This is structural recursion with pattern matching on bar terms – no string manipulation.
Usage
tulpa_parse_formula(formula)
Arguments
formula |
A formula object (e.g., |
Value
A list with:
-
response: character, the response variable label (NULLif none) -
response_expr: language object, the LHS of~(NULLif none) -
fixed_formula: formula, the fixed-effects-only formula -
random_effects: list of parsed RE specifications -
n_re_terms: integer, number of RE terms -
latent_blocks: list of evaluated user-defined latent block objects (typicallytgmrfS3 objects), one perlatent(...)term in the original formula. Empty list when nolatent(...)terms are present. -
n_latent_blocks: integer, number oflatent(...)terms -
original: the original formula
Examples
pf <- tulpa_parse_formula(y ~ x1 + x2 + (1 | group) + (x1 || site))
pf$response # "y"
pf$fixed_formula # y ~ x1 + x2
Probability integral transform from a predictive CDF
Description
The generic, family-agnostic half of a PIT residual check: the model package
supplies the posterior-predictive CDF evaluated at each observation (a
[n_draws x n_obs] matrix, or a draw-averaged [n_obs] vector), and this
returns the PIT value per observation. For a discrete or mixed response
(a hurdle has a point mass at zero) supply the left limit cdf_lower
(P(Y < y)); the randomized PIT then draws one uniform per observation and
interpolates F(y^-) + U (F(y) - F(y^-)), which is uniform under a correct
model. With cdf_lower = NULL the response is treated as continuous and the
PIT is the draw-averaged CDF.
Usage
tulpa_pit(cdf, cdf_lower = NULL, jitter = TRUE)
Arguments
cdf |
Posterior-predictive CDF at the observed value, |
cdf_lower |
Optional left-limit CDF |
jitter |
If |
Value
Numeric vector of length n_obs of PIT values in [0, 1].
See Also
Posterior draws from a nested-Laplace fit
Description
Draw from the outer-grid mixture posterior of a nested-Laplace fit – the
engine analogue of inla.posterior.sample(). Each draw picks an outer-grid
cell k ~ Categorical(weights) and then samples that cell's inner Gaussian,
so the draws are i.i.d. samples from sum_k w_k N(m_k, V_k).
Sampling the mixture is the faithful primitive for marginalizing nonlinear
derived quantities (e.g. plogis(eta_2) - plogis(eta_1), expected-cover
products p * mu): compute the derived quantity per draw, then summarize.
Collapsing the grid to a single moment-matched Gaussian biases skewed or
multimodal-over-grid posteriors.
Usage
tulpa_posterior_draws(fit, idx = NULL, n = 1000, ...)
Arguments
fit |
A nested-Laplace fit ( |
idx |
Optional integer vector of 1-based indices into whatever a draw
covers for this backend (see above); |
n |
Number of posterior draws (default 1000). |
... |
Unused; for S3 compatibility. |
Value
A numeric matrix [n x length(idx)], one row per draw. Carries
attr(., "draws_kind") = "iid" (consistent with the draws-provenance
gate), attr(., "cells") – the outer-grid cell index each row was drawn
from – and attr(., "scope"), which of the two representations above the
columns are.
What a draw covers
It depends on which representation the backend retained, and the returned
matrix says so in its scope attribute.
tulpa_nested_laplace_jointthe FULL latent vector – per-arm fixed effects, per-arm random effects, then the latent field(s) – because the joint fit retains each cell's sparse precision over that vector (
control$store_Q = TRUE).scopeis"latent".tulpa_nested_laplace(single-block)the FIXED-EFFECT block. This backend inverts each cell's precision into the marginal fixed-effect block and releases the precision itself, so the latent field is not part of the retained per-cell Gaussian and cannot be sampled from the fit.
scopeis"fixed".
See Also
tulpa_nested_laplace(), tulpa_nested_laplace_joint(),
posterior_sample()
Posterior draws from a joint nested-Laplace fit
Description
The tulpa_posterior_draws() method for a joint nested-Laplace fit
(tulpa_nested_laplace_joint()). Each draw picks an outer-grid cell
k ~ Categorical(weights) and then samples the inner latent vector from the
constrained Gaussian N(m_k, V_k) at that cell, where m_k is the cell's
inner mode and V_k is the inner-Laplace covariance with the ICAR / BYM2
sum-to-zero field constraint imposed (conditioning by kriging). The draws
are i.i.d. samples from sum_k weights_k * N(m_k, V_k).
Usage
## S3 method for class 'tulpa_nested_laplace_joint'
tulpa_posterior_draws(fit, idx = NULL, n = 1000, ...)
Arguments
fit |
A |
idx |
Optional integer vector of 1-based latent indices to return.
|
n |
Number of posterior draws (default 1000). |
... |
Unused; for S3 compatibility. |
Details
The constrained draw at cell k uses the sparse Cholesky of the stored
precision Q_k and the conditioning-by-kriging correction
z_c = z - Q_k^{-1} A^\top (A Q_k^{-1} A^\top)^{-1} A z,
where z ~ N(0, Q_k^{-1}) and A stacks the field sum-to-zero rows (one for
an ICAR / CAR field; two – structured and unstructured – for a BYM2 field;
one per spatial block in a multi-block fit). The returned draw is
m_k + z_c, restricted to idx. Because m_k already satisfies the
constraint (the inner solve centres the field), the mean is left unchanged
and only the covariance is constrained, so the per-cell marginal matches the
inner-Laplace constrained covariance exactly.
Cells with zero outer-grid weight (e.g. pruned cells) or no stored Q are
dropped and the remaining weights renormalized. A degenerate single-cell
grid (quadrature ESS 1) returns draws from that cell's N(m_1, V_1).
Value
A numeric matrix [n x length(idx)] of latent draws, one row per
draw, columns named x<idx>. Carries attr(., "draws_kind") = "iid"
(consistent with the draws-provenance gate), attr(., "cells") – the
outer-grid cell index each row was drawn from – and
attr(., "scope") = "latent".
See Also
tulpa_posterior_draws(), tulpa_nested_laplace_joint(),
posterior_sample()
Power-scaling prior / likelihood sensitivity
Description
Local power-scaling sensitivity (Kallioinen et al. 2024): how much each
fixed-effect posterior moves when the prior or the likelihood is raised to a
power alpha near 1. The existing draws are importance-reweighted by
exp((alpha - 1) * log_component) (PSIS-smoothed via tulpa_psis(); no
refits), and the sensitivity is the gradient of the cumulative
Jensen-Shannon distance between the base and power-scaled posteriors with
respect to log2(alpha).
Values above threshold flag sensitivity. High on both the prior and
likelihood components indicates potential prior-data conflict; high prior
with low likelihood indicates a strong prior / weak likelihood.
When the fit recorded per-draw hyperparameter log-prior values at
draw-synthesis time ($hyper_log_prior_draws, stored by the nested-Laplace
mixture paths such as tulpa_re_cov_nested() and the tulpa() random-slope
redirect), a hyperparameter column reports the power-scaling sensitivity
of the hyperparameter prior by the same reweighting; NA otherwise.
Usage
tulpa_powerscale_sensitivity(
fit,
data,
prior = NULL,
lower_alpha = 0.99,
upper_alpha = 1.01,
threshold = 0.05
)
Arguments
fit |
A |
data |
The data frame the model was fit to. |
prior |
The Gaussian fixed-effect prior the fit used, as
|
lower_alpha, upper_alpha |
Power-scaling grid endpoints for the gradient (defaults 0.99 / 1.01, as in priorsense). |
threshold |
Sensitivity flag threshold (default 0.05). |
Value
A data frame with one row per fixed-effect parameter and columns
variable, prior, hyperparameter, likelihood, diagnosis.
References
Kallioinen, Paananen, Buerkner & Vehtari (2024). Detecting and diagnosing prior and likelihood sensitivity with power-scaling. Statistics and Computing 34:57. Nguyen & Vreeken (2015). Non-parametric Jensen-Shannon divergence. ECML PKDD.
See Also
tulpa_psis(), tulpa_criteria().
Examples
set.seed(1)
d <- data.frame(x = rnorm(150))
d$y <- rpois(150, exp(0.5 + 0.7 * d$x))
fit <- tulpa(y ~ x, data = d, family = "poisson", mode = "laplace",
beta_prior = list(mean = 0, sd = 5))
tulpa_powerscale_sensitivity(fit, data = d, prior = list(mean = 0, sd = 5))
Prior specification for tulpa models
Description
Specify priors for model parameters. Supports both PC (penalized complexity) priors for variance components and standard distributions for other parameters.
Usage
tulpa_priors(
beta = NULL,
sigma = NULL,
phi = NULL,
rho_temporal = NULL,
rho_spatial = NULL
)
Arguments
beta |
Prior for fixed effects. Default: |
sigma |
Prior for random effect SDs. Default: PC prior with P(sigma > 1) = 0.01. |
phi |
Prior for overdispersion parameter. Default: PC prior with P(phi > 10) = 0.01. |
rho_temporal |
Prior for temporal autocorrelation. Default:
|
rho_spatial |
Prior for spatial proportion (BYM2). Default:
|
Details
PC priors (Simpson et al., 2017) provide principled regularization that:
Favors simpler models (smaller variance components)
Has interpretable parameters (tail probabilities)
Prevents overfitting with sparse data
For other parameters, standard distributions are available via helper
functions: prior_normal(), prior_half_normal(), prior_half_cauchy(),
prior_gamma(), prior_beta(), prior_exponential().
Value
A tulpa_priors object
References
Simpson, D., Rue, H., Riebler, A., Martins, T. G., & Sorbye, S. H. (2017). Penalising model component complexity: A principled, practical approach to constructing priors. Statistical Science, 32(1), 1-28.
Examples
# Default priors
tulpa_priors()
# Custom fixed effect prior
tulpa_priors(beta = prior_normal(0, 1))
# Tighter random effect prior
tulpa_priors(sigma = prior_pc(U = 0.5, alpha = 0.01))
# Half-Cauchy for random effect SD
tulpa_priors(sigma = prior_half_cauchy(2.5))
# Informative prior for temporal correlation
tulpa_priors(rho_temporal = prior_beta(5, 2)) # Prior mode at ~0.8
Profile the inner Laplace solve by phase
Description
Times the sparse joint Laplace solver one phase at a time – scatter (the Hessian and gradient assembly), factorize (numeric Cholesky), eta, line search, and the rest – and returns the breakdown as a data frame. The accumulator aggregates across the parallel outer-grid worker threads, so the reported times cover the whole fit rather than only the calling thread.
Usage
tulpa_profile(expr, sort = TRUE)
Arguments
expr |
An expression that runs a fit (for example a call to
|
sort |
Logical; order rows by descending time. Default |
Details
Use it to settle where a per-cell solve spends its time, e.g. whether a slow
joint occu_cover() fit is bound by the assembly scatter or the
Cholesky factorize:
p <- tulpa_profile(
tulpa_nested_laplace_joint(..., control = list(integration = "ccd"))
)
print(p) # rows ordered by time; scatter vs factorize at top
fit <- attr(p, "value")
Value
A data frame with one row per phase and columns phase,
seconds, calls, ms_per_call (mean wall time per phase
call), and share (fraction of total timed seconds). The fit result
is attached as the "value" attribute.
Examples
set.seed(1)
n <- 200L; X <- cbind(1, rnorm(n))
y <- rbinom(n, 1, plogis(X %*% c(0, 0.5)))
tulpa_profile(tulpa_laplace(y, rep(1L, n), X, family = "binomial"))
Pareto-smoothed importance sampling
Description
Smooths a set of importance ratios by replacing the largest weights with the
order statistics of a generalized-Pareto fit to their upper tail, and
returns the Pareto shape diagnostic pareto_k together with the smoothed
(normalized) log weights and their importance-sampling effective sample
size. pareto_k estimates the number of finite moments of the raw weight
distribution: < 0.5 is good (finite variance). The usable upper boundary is
sample-size dependent, min(1 - 1/log10(S), 0.7) for S draws (Vehtari et al.
2024): about 0.565 at S = 200, reaching the 0.7 cap only past S ~ 2154.
Above it the proposal cannot be reliably corrected to the target.
Usage
tulpa_psis(log_ratios, tail_points = NULL)
Arguments
log_ratios |
Numeric vector of (unnormalized) log importance ratios
|
tail_points |
Number of upper-tail order statistics for the
generalized-Pareto fit, or |
Value
A list with pareto_k (the tail shape, NA if the sample is too
small to fit), is_ess (importance-sampling effective sample size,
1 / sum(w^2) on the normalized smoothed weights), log_weights
(the normalized smoothed log weights), tail_len (the tail size used), and
tail_smoothed (FALSE when the tail kept its raw log ratios because the
generalized-Pareto fit was not attempted or returned a shape / scale the
quantile function is undefined at; pareto_k then reports the attempted fit
and does not describe the returned weights).
References
Vehtari, Simpson, Gelman, Yao & Gabry (2024). Pareto smoothed importance sampling. JMLR 25(72):1-58.
See Also
diagnostics() for the fit-level diagnostic front door.
Examples
set.seed(1)
# Well-behaved importance ratios: k-hat is small.
ps <- tulpa_psis(rnorm(2000))
ps$pareto_k
ps$is_ess
Adaptive Gauss-Hermite refinement of a grouped random-effect covariance
Description
Refines a generalized linear mixed model's fixed effects and random-effect
covariance by replacing the per-group Laplace integral with n_quad-point
adaptive Gauss-Hermite quadrature (AGHQ). At n_quad = 1 this is the joint
Laplace (glmer nAGQ = 1); higher n_quad reduces the small-cluster
attenuation of the variance components for binary / count data. Unlike
agq_fit() (intercept-only RE, built-in binomial/poisson/gaussian
likelihoods), this engine is callback-driven: the caller supplies the
per-group conditional likelihood, so a custom marginal (e.g. a
latent-state-integrated occupancy / detection likelihood, or the
latent-abundance-integrated N-mixture marginal) refines through the same
quadrature.
This is an engine block, not a front door: model packages call it
programmatically, so its tuning knobs (max_iter, n_quad, keep)
sit in the signature rather than in a control list.
The engine is structure-agnostic. It integrates the per-group marginal
M_g = \int \exp\{\ell_g(b_g)\}\, N(b_g; 0, \Sigma)\, db_g,
where b_g is the group's random-effect vector (dimension
\sum_m c_m over the RE terms) and \ell_g(b_g) is the group's
conditional log-likelihood when its linear predictors are perturbed by
b_g. How b_g enters the likelihood – through one linear
predictor, or through several coupled arms at different observation
granularities (e.g. a per-site abundance arm and a per-visit detection arm
sharing a species grouping) – lives entirely in the callback. The engine
only needs, per group, the value / gradient / Hessian of \ell_g in
b_g (for the mode) and \ell_g at the quadrature nodes (for the
sum). The fixed parameters theta and the log-Cholesky coordinates of
\Sigma are optimized jointly on \sum_g \log M_g; standard errors
come from the exact-marginal Hessian.
Two callback forms select the structure (supply exactly one):
-
make_site– the common single-arm, per-row-separable case (\ell_g(b_g) = \sum_{i \in g} \log f_i(\eta_i + Z_i b_g)for one linear predictor\eta). The engine builds the oracle from the per-row marginal and the RE designZitself. -
make_group– the general / multi-arm case. The caller supplies the per-groupb-space oracle directly, so non-separable units (e.g. the visits of an N-mixture site coupled through the shared latent count) and random effects on several arms at once are handled with no engine change.
Scope: one shared grouping factor across all RE terms (the per-group integral
factorizes). The total RE dimension per group should be small (the quadrature
grid is n_quad^dim).
Usage
tulpa_re_aghq(
theta0,
re_terms,
Sigma0,
make_site = NULL,
make_group = NULL,
oracle = NULL,
n_obs = NULL,
keep = NULL,
n_quad = 9L,
lkj_eta = 1,
theta_prior_sd = Inf,
sigma_prior = NULL,
gradient = c("fd", "analytic"),
max_iter = 200L
)
Arguments
theta0 |
Initial fixed-parameter vector. The engine optimizes these jointly with the RE covariance; the callback interprets them. |
re_terms |
A list of RE term specs (or one spec), each defining a
covariance block: |
Sigma0 |
List of initial per-term covariance matrices (the EM estimate). |
make_site |
|
make_group |
|
oracle |
Optional prebuilt native (compiled) oracle, an external pointer
to a |
n_obs |
Number of observations (length of each term's |
keep |
Optional logical/integer mask of observations to include
(default all; |
n_quad |
Quadrature nodes per RE dimension. Either a single integer
(default 9; |
lkj_eta |
LKJ shape for an optional correlation penalty on each
correlated block (log-density |
theta_prior_sd |
Optional Gaussian ridge SD on the fixed parameters
|
sigma_prior |
Optional Penalized-Complexity prior on the marginal
standard deviations of one or more RE covariance blocks, added to the
objective (and hence the marginal Hessian). |
gradient |
How |
max_iter |
Optimizer iteration cap (default 200). |
Value
A list with: theta (refined fixed parameters), Sigma_list
(refined per-term covariance), blup / blup_var (per-term n_groups x n_coefs posterior mean / variance of the RE), group_ok (logical, length
n_groups: FALSE where that group's mode search or precision
factorization failed, which is what the NA rows of blup, blup_var,
blup_cov_g and blup_cross_g mean – a caller conditions its per-group
reads on this rather than on trapping the accompanying warning),
blup_cross (per-term
n_groups x n_theta x n_coefs array: the mode/theta cross-Hessian block
Bf, -d^2 ell_g / d theta db at each group's mode, in the same
negative-Hessian sign convention as the posterior precision underlying
blup_var – so a joint draw of theta and a group's RE b_g uses
b_g | theta ~ N(blup_g - Cinv_g %*% t(Bf_g) %*% (theta_draw - theta), Cinv_g) with Cinv_g the group's n_coefs x n_coefs posterior
covariance block. NA throughout when blup_cross_available is FALSE:
the cross-Hessian needs the oracle's analytic theta_score, which the
R-closure bridge (make_site / make_group) does not supply – only a
prebuilt native oracle carries it), blup_cov_g / blup_cross_g
(per-group lists, length n_groups, of the FULL joint posterior
covariance (d x d, d = every RE term's width combined) and mode/theta
cross-Hessian (n_theta x d) across ALL RE terms sharing that group –
the superset blup_var/blup_cross reduce to a per-term diagonal block
of when a group carries more than one term, since a group's terms are
found jointly and can carry real posterior covariance BETWEEN terms
(e.g. an abundance-arm and a detection-arm term sharing one grouping
factor); same NA-when-unavailable rule as blup_cross), theta_cov /
theta_se
(fixed-parameter covariance / SE from the marginal Hessian), re_par /
re_par_cov / re_par_se (the RE-covariance coordinates the optimizer
carried – log-Cholesky for a full block, log-SD for a diagonal one – with
their block of the same inverse Hessian, so SE(log sigma) is available
for a boundary test on a weakly-identified variance component),
re_par_layout (per block: label, nc, full, the index range into
re_par and the coord names, so a caller does not reconstruct the
packing), joint_cov (the whole (n_theta + n_chol) inverse Hessian),
log_marginal
(the AGHQ marginal log-likelihood at the optimum, excluding any ridge),
n_quad, lkj_eta, converged, and counts (stats::optim's own
function / gradient evaluation counts, so a caller reporting how much
work the fit took has a number to report rather than NA). RE terms that
do not share one
grouping factor are an input error and stop. Three conditions warn and
return NULL (caller keeps its prior fit): a singular / non-finite
optimum, an objective that is already undefined at the starting parameters
(some group's solve fails there, so there is nothing to descend), and an
optimum whose objective is the failure sentinel rather than an attained
marginal likelihood – the last two report which groups failed.
References
Pinheiro & Bates (1995). Approximations to the log-likelihood function in the nonlinear mixed-effects model. Journal of Computational and Graphical Statistics 4(1):12-35. Lewandowski, Kurowicka & Joe (2009). Generating random correlation matrices based on vines and extended onion method. Journal of Multivariate Analysis 100(9):1989-2001.
Examples
# A per-row-separable binomial GLMM marginal supplied through `make_site`.
l1pe <- function(x) ifelse(x > 0, x + log1p(exp(-x)), log1p(exp(x)))
make_binom_site <- function(X, y, nt) function(theta) {
eta_fixed <- as.numeric(X %*% theta)
list(eta_re = eta_fixed,
deriv = function(rows, eta) {
p <- plogis(eta)
list(logL = y[rows] * eta - nt[rows] * l1pe(eta),
d1 = y[rows] - nt[rows] * p, d2 = -nt[rows] * p * (1 - p))
},
lmat = function(rows, ETA) y[rows] * ETA - nt[rows] * l1pe(ETA))
}
set.seed(1)
ng <- 30L; npg <- 8L; n <- ng * npg
g <- rep(seq_len(ng), each = npg); x <- rnorm(n)
X <- cbind(1, x); nt <- rep(3L, n); u <- rnorm(ng, 0, 0.9)
y <- rbinom(n, nt, plogis(0.3 + 0.7 * x + u[g]))
fit <- tulpa_re_aghq(theta0 = c(0, 0),
re_terms = list(list(idx = g, n_groups = ng, n_coefs = 1L)),
Sigma0 = list(matrix(0.25, 1, 1)),
make_site = make_binom_site(X, y, nt), n_obs = n, n_quad = 5L)
sqrt(fit$Sigma_list[[1]][1, 1]) # adaptive-GHQ RE standard deviation
Gibbs estimation of random-effect covariances (exact-target debias)
Description
For one or more random-effects terms (e.g. (1 + x | g), (1 + x || g), or
several terms together), estimate the random-effect covariances Sigma by
sampling the exact joint posterior p(beta, {b}, {Sigma} | y) rather than
fixing each Sigma at the Laplace mode. This removes the Laplace / PQL
"approximation" bias that shrinks variance components low for binary and
low-count responses with small groups.
Usage
tulpa_re_cov_gibbs(
y,
n_trials = NULL,
X,
re_terms,
family = "binomial",
phi = 1,
prior_df = NULL,
prior_scale = NULL,
beta_prior = .tulpa_default_beta_prior("re_cov_gibbs"),
control = list()
)
Arguments
y, n_trials, X, family, phi |
Passed to the likelihood and to
|
re_terms |
Either a single random-effect term or a list of them. Each
term is a list with |
prior_df |
Inverse-Wishart prior degrees of freedom. Applied to every
correlated block (default |
prior_scale |
Inverse-Wishart prior scale matrix. Used for a block when
its dimension matches (default |
beta_prior |
Gaussian fixed-effect prior as |
control |
A named list of numerical / tuning knobs (statistical arguments stay in the signature above). Recognized entries:
|
Details
Metropolis-within-Gibbs targeting p(beta, {b_m}, {Sigma_m} | y):
-
b_{m,g} | beta, Sigma, y– random-walk Metropolis per (term, group) (groups are conditionally independent givenbetaand the covariances). The proposal shape is the Laplace per-group posterior covariance block (return_re_covfromtulpa_laplace()); the Metropolis acceptance is computed against the exact group log-likelihood plus the Gaussian RE log-prior, which corrects the non-Gaussianity the Laplace approximation misses. The linear predictor holds every other term's contribution fixed. -
beta | b, Sigma, y– random-walk Metropolis, proposal shape from the Laplace fixed-effect Hessian blockH_beta. -
Sigma_m | b_m– exact conjugate draw. A correlated block draws the full matrix fromIW(prior_df + G_m, prior_scale + sum_g b_{m,g} b_{m,g}'); an uncorrelated (diagonal) block draws each variance from its scalar conjugate (inverse-gamma). No linearization enters this step.
A single Laplace solve provides the starting values (beta, b) and the
proposal shapes; the random-walk scales for the beta block and the per-term
b blocks are adapted toward their target acceptance during burn-in
(Robbins-Monro), then held fixed for the recorded sweeps. The covariance
summary marginalizes the derived scale / correlation parameters over the
posterior draws via the same machinery as tulpa_re_cov_nested().
For family = "gaussian" the response is already conditionally Gaussian, so
there is no Laplace bias to remove; phi (the residual variance) is treated
as known. The sampler still runs and is useful as a reference / for Sigma
uncertainty.
Value
A list with:
-
posterior: data frame with one row per parameter (sigma_i,rho_ij,Sigma_ij; prefixed by block label when there are several terms; diagonal blocks report norho) and columnsmean,sd,median,ci_lo,ci_hi. -
Sigma_mean: the posterior mean ofSigma(a matrix for one block, a named list of matrices for several). -
Sigma_draws: list of recorded draws; each element is the per-block list ofSigmamatrices for that sweep. -
beta_draws/draws: matrix of recordedbetadraws;draws(withmeans,param_names,process_info) drives the generictulpa_fitmethods. -
re: matrix of recorded random-effect draws,n_keptrows by one column per (block, group, coefficient) in that order – the exact per-group posterior the sweep samples. Row-aligned withbeta_draws, so a draw is a joint(beta, b)state. This is whatranef()summarizes and whatposterior_predict()adds to the linear predictor. -
accept: list withbetaandbacceptance rates over recorded sweeps. -
n_kept,n_coefs(vector of per-blockc),prior: bookkeeping.
References
Lewandowski, Kurowicka & Joe (2009). Generating random correlation matrices based on vines and extended onion method. Journal of Multivariate Analysis 100(9):1989-2001.
See Also
tulpa_re_cov_nested() for the grid-integration (summary-bias) fix;
tulpa_laplace() for the pilot solve and per-group covariance blocks.
Examples
set.seed(1)
G <- 20L; per <- 12L; n <- G * per
grp <- rep(seq_len(G), each = per); x <- rnorm(n)
b <- cbind(rnorm(G, 0, 0.7), rnorm(G, 0, 0.5)) # random intercept + slope
eta <- -0.2 + 0.5 * x + b[grp, 1] + b[grp, 2] * x
y <- rbinom(n, 1L, plogis(eta))
re_term <- list(idx = grp, n_groups = G, n_coefs = 2L, Z = cbind(1, x),
correlated = TRUE)
fit <- tulpa_re_cov_gibbs(y, rep(1L, n), cbind(1, x), re_term,
family = "binomial",
control = list(n_iter = 300L, warmup = 150L))
fit$Sigma_mean # exact-debias RE covariance posterior mean
Nested-Laplace integration over random-effect covariances
Description
For one or more random-effects terms (e.g. (1 + x | g), (1 + x || g), or
several terms together), integrate the Laplace marginal likelihood over the
random-effect covariances Sigma instead of fixing them at point estimates.
Reports weighted posterior summaries (mean, SD, median, 2.5\
Sigma and its derived scale (sigma_i) and correlation (rho_ij)
parameters, marginalizing the joint posterior over a Sigma-grid.
This corrects the plug-in-MAP ("summary") bias: the mode of a skewed variance-component marginal is biased low relative to its median, so the headline summary should be the marginalized median, not the mode.
Usage
tulpa_re_cov_nested(
y,
n_trials = NULL,
X,
re_terms,
family = "binomial",
phi = 1,
phi2 = NULL,
prior_sigma = c(3, 0.05),
eta = 2,
hyperprior = c("flat", "pc_lkj"),
log_prior_theta = NULL,
beta_prior = NULL,
offset = NULL,
n_quad = 1L,
X_zi = NULL,
zi_prior_sd = 2.5,
control = list()
)
Arguments
y, n_trials, X, family, phi |
Passed to |
re_terms |
Either a single random-effect term or a list of them. Each
term is a list with |
phi2 |
Optional second dispersion, threaded into every inner
|
prior_sigma, eta |
Hyperparameters of the PC + LKJ prior used when
|
hyperprior |
|
log_prior_theta |
Optional |
beta_prior |
Optional Gaussian prior on the fixed effects, threaded into
every inner |
offset |
Optional observation-level offset on the linear predictor
(length |
n_quad |
Quadrature order for the inner marginal. |
X_zi |
Optional zero-inflation design matrix ( |
zi_prior_sd |
Prior SD on |
control |
A named list of numerical / tuning knobs (statistical arguments stay in the signature above). Recognized entries:
|
Details
Each term is one covariance block. A correlated block ((1 + x | g)) is
a full Sigma = L L' parameterized by its lower Cholesky factor in
log-Cholesky coordinates (the log-diagonal and the strictly-lower entries of
L, c(c+1)/2 values for a c-coefficient block), which keeps Sigma
positive definite for every coordinate. An uncorrelated block
((1 + x || g)) is a diagonal Sigma parameterized by its c log standard
deviations. A scalar (1 | g) term is the degenerate c = 1 block. Several
blocks stack their parameters into one integration vector; a single-term
model is the length-1 case.
Integration nodes live in the whitened stacked-parameter space, centred at
the joint marginal-likelihood mode and rotated/scaled by the Cholesky of the
mode's posterior covariance (solve(Hessian)), so points track the posterior
ridge. Two node layouts are available via integration:
-
"ccd"(default): a central-composite design (ccd_grid()) of1 + 2k + 2^(k-q)points for the totalk = sum_blocksparameters, with the corrected R-INLA design weights (ccd_weights()). Scales polynomially ink, where the tensor grid is exponential. -
"grid": the fulln_per_axis^ktensor product with uniform cell weights – denser and more robust to a non-Gaussian whitened posterior, but only tractable for smallk.
Each node k contributes integration weight proportional to
Delta_k * exp(log_marginal(Sigma_k) + log_prior_theta(theta_k)), following
the INLA convention int ~ sum_k Delta_k pi(theta_k).
The two layouts also decide how the reported median and 2.5\
of each derived quantity are read off the nodes. A tensor grid's uniform
cells discretize the posterior density, so the cumulative node weights are a
CDF and the summary is the weighted quantile. A CCD is a moment rule: its
nodes sit where they reproduce the integrand's first two moments and carry no
probability mass of their own, so the summary is moment-matched instead –
the first two weighted moments on each quantity's own coordinate (log for a
scale or a variance, atanh for a correlation, the identity for a covariance)
define a Gaussian there whose quantiles are mapped back. Scale intervals are
therefore positive and asymmetric, and correlation intervals stay inside
(-1, 1). The mean and sd columns are the weighted moments under either
layout.
By default (hyperprior = "flat") log_prior_theta is the zero function:
flat in log(theta), the same convention the nested-Laplace spatial /
temporal / RE-scale axes use (icar / rw1 / rw2 / ar1's tau / iid, none of
which carry a hyperprior on their scale either – see vignette("priors")).
Set hyperprior = "pc_lkj" to use the weakly-informative PC + LKJ hyperprior
instead, built per block by re_cov_pc_lkj_prior() and summed over blocks
(PC prior on each marginal SD via prior_sigma, LKJ prior on each correlated
block's correlation matrix via eta), expressed in the same parameterization
with the exact change-of-variables Jacobian. Supply a custom log_prior_theta
function to override either default (then prior_sigma / eta /
hyperprior are ignored); it must act on the full stacked parameter vector.
tulpa_eb() shares this same objective and the same default, so
tulpa_eb()$theta_hat and tulpa_re_cov_nested()$theta_hat stay the same
estimate on the same data under either setting.
Value
A list with:
-
posterior: data frame with one row per parameter and columnsmean,sd,median,ci_lo,ci_hi. Parameter names aresigma_i,rho_ij,Sigma_ijfor a single block, prefixed by the block label (g.sigma_1, ...) when there are several blocks. Diagonal blocks report norho. -
map: the plug-in-mode summary attheta_hat(a singlelist(Sigma, sigma, rho)for one block, or a named list of them). -
Sigma_mean: the weighted posterior mean ofSigma(a matrix for one block, or a named list of matrices). -
beta,draws,means,param_names,process_info: the fixed-effect posterior from the node mixture (drivescoef/confint/vcov/summary). -
re_nodes,re_var_nodes: the per-group random-effect posterior at each integration node – conditional mean and marginal variance of every (block, group, coefficient), one row per node.ranef()reports theweights-mixture of them.NULLatn_quad > 1, whose inner marginal integrates each group out instead of conditioning on it; that fit carriesranef_unavailable(the reason) in their place. -
re_debias_draws,re_debias_idx: present when the subspace debias selected a random-effect coordinate. The sampled draws of those coordinates on the node mixture the fixed-effect draws use, and their positions within the random-effect block.ranef()reports those rows empirically and the rest from the Gaussian mixture, recording which in itssourcecolumn. -
theta_hat,theta_grid,weights,log_marginal,n_grid,layout,n_blocks,n_coefs(vector of per-blockc). -
subspace_debias: present only whencontrol$subspace_debiaswas set.idxare the corrected latent coordinates,bandsthe per-probed-index reliability table they were read from,closure_addedwhat the coupling closure added, andacceptthe per-node Metropolis acceptance rate.
References
Rue, Martino & Chopin (2009). Approximate Bayesian inference for latent Gaussian models by using integrated nested Laplace approximations. JRSS-B 71(2):319-392. Lewandowski, Kurowicka & Joe (2009). Generating random correlation matrices based on vines and extended onion method. Journal of Multivariate Analysis 100(9):1989-2001.
See Also
tulpa_laplace() for the inner solve; tulpa_nested_laplace() for
the analogous outer integration over spatial / temporal prior
hyperparameters.
Examples
set.seed(1)
G <- 20L; per <- 12L; n <- G * per
grp <- rep(seq_len(G), each = per); x <- rnorm(n)
b <- cbind(rnorm(G, 0, 0.7), rnorm(G, 0, 0.5)) # random intercept + slope
eta <- -0.2 + 0.5 * x + b[grp, 1] + b[grp, 2] * x
y <- rbinom(n, 1L, plogis(eta))
re_term <- list(idx = grp, n_groups = G, n_coefs = 2L, Z = cbind(1, x),
correlated = TRUE)
fit <- tulpa_re_cov_nested(y, rep(1L, n), cbind(1, x), re_term,
family = "binomial")
fit$Sigma_mean # marginalized RE covariance
Selective refit of high-Pareto-k observations (reloo)
Description
PSIS-LOO with exact refits where the importance sampling is unreliable:
observations whose Pareto k-hat exceeds k_threshold are re-scored by
refitting the model without that observation (through the fit's stored
tulpa() call, as in tulpa_kfold()) and evaluating the exact held-out
log predictive density. All other observations keep their PSIS-LOO value,
so the cost is one refit per flagged observation rather than per fold.
The same restrictions as tulpa_kfold() apply: fixed-effect / GLMM fits
only (subsetting breaks a spatial / temporal field), and held-out
random-effect groups contribute at their prior mean.
Usage
tulpa_reloo(
object,
data,
k_threshold = .nl_diag("k_usable"),
n_trials = NULL,
ndraws = NULL
)
Arguments
object |
A |
data |
The data frame the model was fit to. |
k_threshold |
Pareto k-hat above which an observation is refit (default 0.7, the standard PSIS reliability gate). |
n_trials |
Optional binomial denominators (length |
ndraws |
Number of posterior draws used for the PSIS-LOO baseline (defaults to all stored draws, or 400 on the draw-free Laplace tier). |
Value
A list with elpd_loo (corrected), se_elpd_loo, looic,
pointwise (per-observation elpd, exact at the refit observations),
reloo_idx (indices refit), pareto_k (the original k-hat values), and
k_threshold.
See Also
tulpa_kfold() for the full refit-CV; tulpa_criteria() for
PSIS-LOO / WAIC without refits.
Examples
set.seed(1)
d <- data.frame(x = rnorm(120))
d$y <- rpois(120, exp(0.4 + 0.6 * d$x))
fit <- tulpa(y ~ x, data = d, family = "poisson", mode = "laplace")
rl <- tulpa_reloo(fit, data = d)
rl$elpd_loo
Fit a fixed-effect GLM with a model-agnostic sampler kernel
Description
Drives one of tulpa's ModelData sampler kernels – NUTS ("hmc"), elliptical
slice sampling ("ess"), SGHMC ("sghmc"), SGLD ("sgld"), MCLMC
("mclmc"), sequential Monte Carlo ("smc"), or variational inference
("vi") – on a fixed-effect GLM. The model (design + per-observation
likelihood) is built once through the same built-in-family scaffold the
single-point Laplace fit uses, so no likelihood / link logic is duplicated.
Usage
tulpa_sample_glmm(
y,
n_trials,
X,
family,
backend,
phi = 1,
phi2 = NULL,
offset = NULL,
fixed_names = NULL,
re_spec = NULL,
spatial_spec = NULL,
temporal_spec = NULL,
svc_spec = NULL,
tvc_spec = NULL,
zi_spec = NULL,
sigma_re_scale = 2.5,
sigma_beta = .tulpa_prior_sd("sample_glmm"),
warm_start = NULL,
control = list()
)
Arguments
y |
Response vector. |
n_trials |
Binomial denominators (or |
X |
Fixed-effect design matrix ( |
family |
Character family name (see |
backend |
One of |
phi |
Dispersion/precision passed to the family (held fixed). The
kernel parameterization: for |
phi2 |
Optional second dispersion: the Student-t degrees of freedom
( |
offset |
Optional fixed additive term on the linear predictor
( |
fixed_names |
Optional fixed-effect names for the draw columns. |
re_spec |
Optional random-effect spec: a list with |
spatial_spec |
Optional areal spatial spec: a list with |
temporal_spec |
Optional temporal spec: a list with |
sigma_re_scale |
Half-Cauchy scale for the RE / BYM2 standard-deviation hyperprior (sampled jointly with the latent effects). |
warm_start |
Optional |
control |
List of kernel tuning knobs (
The SGHMC discretisation is calibrated for small The elliptical-slice kernel takes four more, all prefixed The elliptical-slice kernel draws from R's own RNG, so |
Value
A tulpa_fit with draws, means, param_names, the kernel's
diagnostics, and (for "hmc") chain_id / n_chains so chain diagnostics
apply.
Simulate data from a tulpa model
Description
Generic simulator that dispatches through a tulpa_family's simulate_fn.
Given a model spec (formula + family + data) and a parameter vector or a
fitted model, generate one or more synthetic response datasets.
Used internally by prior_predict() and exposed for posterior predictive
checks, simulation-based calibration, and what-if analyses with fixed
parameters.
Usage
tulpa_simulate(
formula,
family,
data,
theta = NULL,
n_sims = 1L,
priors = NULL,
seed = NULL,
...
)
Arguments
formula |
A model formula, or list of formulas keyed by process name. |
family |
A |
data |
Data frame with covariates and grouping factors. |
theta |
One of:
|
n_sims |
Number of simulated datasets. When |
priors |
Used only if |
seed |
Optional integer seed. |
... |
Passed to |
Value
A tulpa_simulate object: list with y (length-n_sims list of
simulated responses), theta (parameters used per sim), linpred,
family, n_sims, n_obs.
Examples
fam <- tulpa_family(
name = "gaussian",
simulate_fn = function(eta, params, n_obs, ...) {
rnorm(n_obs, eta[[1]], params$sigma_y)
},
extra_params = list(sigma_y = prior_half_normal(1))
)
df <- data.frame(y = rep(0, 20), x = rnorm(20))
theta <- list(
beta = list(y = c(0.5, 1.0)),
u = list(y = list()),
extras = list(sigma_y = 0.5)
)
sim <- tulpa_simulate(y ~ x, fam, df, theta = theta, n_sims = 3, seed = 1)
length(sim$y) # 3
Spatial structure specifications for tulpa
Description
Functions to specify spatial random effects for tulpa models. Spatial effects are shared between processes by default, which helps prevent bias from spatially-structured unmeasured confounders.
Value
The spatial constructors documented in this family
(spatial_car(), spatial_bym2(), spatial_gp(), and the others) each
return a tulpa_spatial (or related) specification object to pass to the
spatial argument of tulpa().
Temporal structure specifications for tulpa
Description
Functions to specify temporal random effects for tulpa models. Temporal effects are shared between processes by default, which helps prevent bias from temporally-structured unmeasured confounders.
Value
The temporal constructors documented in this family
(temporal_rw1(), temporal_rw2(), temporal_ar1(), and the others) each
return a tulpa_temporal specification object to pass to the temporal
argument of tulpa().
Fit a custom tgmrf latent block
Description
One front door for inference over a user-defined tgmrf() latent block's
hyperparameter vector theta. mode selects the inference engine, all of
which share the same Laplace body for (beta, z) | theta:
-
"imh"(default) – Tier-1 exact MCMC: the Laplace body plus an independence-Metropolis bias correction overtheta(the "Laplace + MH debias" composition). -
"nuts"– Tier-1 exact MCMC: NUTS over the marginalthetaposterior. -
"vi"– Tier-2 structured: a single-path Pathfinder Gaussian fit of thethetaposterior (no bias correction). -
"nuts_joint"– Tier-1 exact MCMC sampling the FULL joint(beta, z, theta)rather than the Laplace-marginalizedtheta(requires a C++-backend block, seetgmrf_cpp()).
The inference method is an argument, not a parallel verb.
Usage
tulpa_tgmrf(
y,
n_trials,
X,
block,
family = "binomial",
phi = 1,
re_idx = NULL,
n_re_groups = 0L,
sigma_re = 1,
mode = c("imh", "nuts", "vi", "nuts_joint"),
control = list(),
...
)
Arguments
y, n_trials, X |
Response, binomial trial counts (or |
block |
A |
family, phi |
Observation family and its dispersion. |
re_idx, n_re_groups, sigma_re |
Optional scalar random-intercept structure. |
mode |
Inference engine: |
control |
A list of numerical / tuning knobs for the chosen |
... |
Individual |
Value
A tulpa_tgmrf / tulpa_fit object; $backend and $mode record
the engine used.
See Also
tgmrf() for the block, tgmrf_cpp() for the compiled-block form.
Examples
## Not run:
# `block` is a tgmrf latent block (from tgmrf() / tgmrf_cpp()); the inference
# method is an argument, not a parallel verb. See vignette("tgmrf").
fit <- tulpa_tgmrf(y, rep(1L, length(y)), X, block = blk, mode = "imh")
## End(Not run)
Posterior predictive checks for tulpa models
Description
Visual and numerical checks comparing observed data to posterior predictive distributions. Essential for assessing model fit.
Value
The functions documented in this family return a ggplot object
(pp_check()) or a tulpa_prior_predict object (prior_predict()); see
each function's own help page.
Empirical semivariogram of residuals
Description
Computes the empirical semivariogram in distance bins for visual assessment of remaining spatial structure in residuals.
Usage
tulpa_variogram(
object,
coords,
n_bins = 15L,
max_dist = NULL,
resid_type = "pearson"
)
Arguments
object |
A fitted model, or a numeric vector of residuals |
coords |
N x 2 coordinate matrix (required) |
n_bins |
Number of distance bins (default 15) |
max_dist |
Maximum distance (default: half the maximum pairwise distance) |
resid_type |
Residual type if extracting from model (default |
Value
A tulpa_variogram data.frame with columns dist, gamma, n_pairs
Extract temporally-varying coefficients from a fitted model
Description
Extract posterior distributions of temporally-varying coefficients (TVCs) from a fitted tulpa model with TVC specification.
Usage
tvc(object, terms = NULL, summary = FALSE, probs = c(0.025, 0.5, 0.975), ...)
## S3 method for class 'tulpa_fit'
tvc(object, terms = NULL, summary = FALSE, probs = c(0.025, 0.5, 0.975), ...)
Arguments
object |
A |
terms |
Which TVC terms to extract. If NULL (default), extracts all. |
summary |
Logical; if TRUE, return summary statistics instead of full posterior draws. |
probs |
Quantiles to compute if |
... |
Ignored |
Value
A tulpa_tvc_posterior object containing:
-
draws: Array of posterior draws (draws x times x terms) -
time_levels: Time point labels -
term_names: Names of TVC terms
See Also
temporal_tvc(), plot.tulpa_tvc_posterior()
Examples
set.seed(160)
n_t <- 10L; reps <- 5L
walk <- cumsum(rnorm(n_t, 0, 0.35)); walk <- walk - mean(walk)
year <- rep(seq_len(n_t), each = reps)
df <- data.frame(year = year, x = rnorm(length(year)))
df$count <- rpois(nrow(df), exp(0.3 + (0.5 + walk[year]) * df$x))
# The slope on `x` walks in time; TVC is exact-mode only.
fit <- tulpa(
count ~ x,
data = df,
family = "poisson",
temporal = temporal_tvc("year", terms = ~ x - 1, structure = "rw1"),
mode = "exact",
control = list(n_iter = 200L, n_warmup = 100L, seed = 1L)
)
tvc_post <- tvc(fit)
summary(tvc_post)
plot(tvc_post, "x")
Validate a tulpa_family
Description
Validate a tulpa_family
Usage
validate_family(family)
Arguments
family |
Object to validate |
Validate GP spatial specification against data
Description
Validate GP spatial specification against data
Usage
validate_gp(gp, data)
Arguments
gp |
tulpa_gp or tulpa_multiscale object |
data |
Data frame |
Value
Updated spatial object with computed neighbor structure
Internal validation helpers
Description
Small shared helpers used by validate_*() functions across the
spatial / temporal / SVC / TVC specs. Centralised here to keep the
per-spec validators thin and prevent drift between near-identical
column-existence checks and coordinate preparation blocks.
Validate HSGP spatial structure
Description
Validate HSGP spatial structure
Usage
validate_hsgp(spatial, data)
Arguments
spatial |
A tulpa_hsgp object |
data |
The data frame |
Value
A validated tulpa_hsgp object with coords_matrix filled in
Validate latent factor specification against data
Description
Validate latent factor specification against data
Usage
validate_latent(latent, N)
Arguments
latent |
A tulpa_latent object |
N |
Number of observations |
Value
Validated latent specification with additional computed fields
Validate that a fit used the expected mode
Description
Ensures the fit object was created with the expected inference mode. Useful for enforcing mode requirements in downstream analysis.
Usage
validate_mode(fit, expected_mode, error = TRUE)
Arguments
fit |
A tulpa_fit object |
expected_mode |
Mode that should have been used |
error |
If TRUE (default), error on mismatch. If FALSE, return logical. |
Value
If error = FALSE, returns TRUE/FALSE. Otherwise errors on mismatch.
Validate a prior specification
Description
Validate a prior specification
Usage
validate_prior(prior, name)
Arguments
prior |
A prior object |
name |
Parameter name for error messages |
Validate RSR specification
Description
Validate RSR specification
Usage
validate_rsr(spatial, data, formula)
Arguments
spatial |
tulpa_rsr object |
data |
Data frame |
formula |
Model formula (to extract design matrix) |
Value
Updated spatial object with projection matrix
Validate spatial specification against data
Description
Validate spatial specification against data
Usage
validate_spatial(spatial, data)
Arguments
spatial |
tulpa_spatial object |
data |
Data frame |
Value
NULL (invisibly); errors if validation fails
Validate SVC specification against data and design matrix
Description
Validate SVC specification against data and design matrix
Usage
validate_svc(svc, data, X)
Arguments
svc |
tulpa_svc object |
data |
Data frame |
X |
Design matrix (to resolve term names) |
Value
Updated tulpa_svc object with computed neighbor structure
Validate temporal specification against data
Description
Validate temporal specification against data
Usage
validate_temporal(temporal, data)
Arguments
temporal |
tulpa_temporal object |
data |
Data frame |
Value
Updated tulpa_temporal object with indices computed
Validate temporal GP specification against data
Description
Validate temporal GP specification against data
Usage
validate_temporal_gp(temporal, data)
Arguments
temporal |
tulpa_temporal_gp object |
data |
Data frame |
Value
Updated object with computed time structure
Validate multi-scale temporal specification
Description
Validate multi-scale temporal specification
Usage
validate_temporal_multiscale(temporal, data)
Arguments
temporal |
tulpa_temporal_multiscale object |
data |
Data frame |
Value
Updated object with indices computed
Validate TVC specification against data and design matrix
Description
Validate TVC specification against data and design matrix
Usage
validate_tvc(tvc, data, X)
Arguments
tvc |
tulpa_tvc object |
data |
Data frame |
X |
Design matrix (to resolve term names) |
Value
Updated tulpa_tvc object with computed structure
Variance-covariance matrix of the fixed effects
Description
Variance-covariance matrix of the fixed effects
Usage
## S3 method for class 'tulpa_fit'
vcov(object, ...)
Arguments
object |
A |
... |
Ignored. |
Value
Fixed-effect variance-covariance matrix (empirical for sampler tiers,
H_beta^-1 for the Laplace tier).
Run an expression under a chosen symplectic integrator
Description
Evaluate expr with the integrator set to name, then restore whatever was
selected before – on an error as well as on success. The integrator
selection is process-global, so a bare tulpa_integrator() call leaves every
later fit in the session on the new scheme, and an error between setting and
restoring leaves it there permanently.
Usage
with_tulpa_integrator(name, expr, mts_substeps = 4L)
Arguments
name |
Integrator name, as for |
expr |
Expression to evaluate. Evaluated in the caller's environment. |
mts_substeps |
Inner prior-force substeps for |
Value
The value of expr.
See Also
Examples
with_tulpa_integrator("yoshida4", tulpa_integrator())
tulpa_integrator() # unchanged
Elementwise log-likelihood of the zero-inflated mixture.
Description
Elementwise log-likelihood of the zero-inflated mixture.
Usage
zi_loglik(eta, logit_zi, y, family, n_trials = NULL, phi = 1, phi2 = NULL)
Negative Hessian of the zero-inflated mixture in eta space.
Description
Returns an n x 3 matrix with columns count (-d2/d eta_count^2), zi
(-d2/d logit_zi^2) and cross (-d2/d eta_count d logit_zi), i.e. the
distinct entries of the symmetric 2 x 2 block per observation.
Usage
zi_neg_hessian(eta, logit_zi, y, family, n_trials = NULL, phi = 1, phi2 = NULL)
Details
This is the observed negative Hessian, exact wherever the base family
registers obs_weight. At y > 0 the mixture is additively separable, so the
count block is the base family's own curvature and the cross term is zero;
the coupling lives entirely in the y = 0 branch, where both components can
explain the zero.
Response-scale mean of the zero-inflated mixture.
Description
Response-scale mean of the zero-inflated mixture.
Usage
zi_response_mean(eta, logit_zi, family, n_trials = NULL, phi = 1)
One draw per element from the zero-inflated mixture.
Description
One draw per element from the zero-inflated mixture.
Usage
zi_sample(eta, logit_zi, family, n_trials = NULL, phi = 1, phi2 = NULL)
Score of the zero-inflated mixture in the two-process eta ordering.
Description
Returns an n x 2 matrix with columns count (d loglik / d eta_count) and
zi (d loglik / d logit_zi).
Usage
zi_score_eta(eta, logit_zi, y, family, n_trials = NULL, phi = 1, phi2 = NULL)
Response variance of the zero-inflated mixture.
Description
Law of total variance over the mixture indicator: with probability pi the
response is a structural zero, otherwise it is a base-family draw.
Usage
zi_variance(eta, logit_zi, family, n_trials = NULL, phi = 1, phi2 = NULL)