Working with results
Every engine — inla, tmb, hmc_laplace — returns a result that implements one accessor protocol. This page collects those accessors in one place. The examples use INLA, but the named-block and marginal accessors below work the same way on any engine's result.
Use the accessor functions, not the fields
Reach for the accessor functions — latent_marginals(result), linear_predictor_marginals(result), base_latent_marginals(result) — rather than the matching result.… fields.
Under the default (compact) latent parameterization the model does not materialize the augmented blocks, so result.linear_predictor_marginals and result.base_latent_marginals are nothing. The functions handle this: they return the stored block when it exists and derive it from the latent posterior otherwise. A compact result derives the linear predictor η = A·x from the latent posterior through the design map; an augmented result slices the stored η-block. Either way you get the same vector of marginals back, so code written against the functions keeps working if the parameterization changes.
result = inla(model, y)
ηs = linear_predictor_marginals(result) # works in both compact and augmented mode
xs = base_latent_marginals(result) # the original model's latent componentsLatent marginals by name
When the model is written with @latte (or converted with latte_from_dppl), each ~-bound latent term gets a name. Rather than indexing the flat marginal vector with hand-computed offsets, ask for a named block with latent_marginals(result, name::Symbol). It returns the slice of marginals for that term — a Vector of Distributions, length 1 for a scalar term:
# Model with `β ~ MvNormal(...)` (regression coefficients) and a
# `u ~ BesagModel(W)(τ = τ)` spatial effect.
β_marginals = latent_marginals(result, :β) # one marginal per coefficient
u_marginals = latent_marginals(result, :u) # one marginal per spatial unit
β_means = [mean(m) for m in β_marginals]
β_stds = [std(m) for m in β_marginals]This is the recommended way to pull out a specific term: it stays correct when the latent layout changes (extra covariates, a different number of spatial units) and reads more clearly than positional indexing. The available names are those of the ~-bound latent terms in the model; latent_groups(result) returns the full name => index-range map. The same name-keyed form works for hyperparameters via hyperparameter_marginals(result, name), with hyperparameter_groups(result) exposing their layout.
The returned marginals are Distributions.jl distributions, so mean, var, std, quantile, pdf, and rand work on them directly.
Hyperparameter marginals are in natural space
hyperparameter_marginals(result) returns the hyperparameter posteriors in natural space — the scale the prior was declared on — not the internal working (transformed) space the optimizer uses. So no back-transformation is needed before computing summaries:
τ_marginal = hyperparameter_marginals(result, :τ)[1] # precision, natural space
precision_mean = mean(τ_marginal)
precision_ci = quantile(τ_marginal, [0.025, 0.975])hyperparameter_mode(result) likewise reports the posterior mode in natural space.
Derived quantities
A declared hyperparameter's transform is inferred from its prior's support, so declaring the prior on the parameter you care about is usually all you need. A positive parameter is cleanest as a positive prior — write α ~ LogNormal(0, 1) rather than log_α ~ Normal(0, 1) with α = exp(log_α) (an identical prior): then α is a declared hyperparameter and mean(hyperparameter_marginals(result, :α)[1]) is the natural-space E[α], directly.
For a quantity that genuinely can't be written as a single declared prior — a function of two or more hyperparameters, say — use pushforward to map a marginal through a transform. Its mean, quantile, etc. are computed by integration, so mean(pushforward(m, exp)) is the true E[exp X], not the Jensen-biased exp(E[X]):
m = hyperparameter_marginals(result, :log_β)[1]
derived = pushforward(m, exp)
mean(derived) # true E[exp(log_β)], integrated
quantile(derived, [0.025, 0.975])Fitted values on the response scale
observation_marginals(result) transforms the linear-predictor marginals through the inverse link to give marginals for the expected observation μ = g⁻¹(η), one per observation. For a Poisson log-link these are the rates λ; for a logit link they are the success probabilities p. The returned distributions support the full Distributions.jl interface:
fitted = observation_marginals(result)
μ_mean = mean(fitted[1])
μ_ci = (quantile(fitted[1], 0.025), quantile(fitted[1], 0.975))This requires an ExponentialFamily observation model (possibly wrapped) whose link function can be extracted.
Convergence and timing
converged(result) # did the optimization / exploration converge?
time_elapsed(result) # total wall-clock time, in secondslog_marginal_likelihood(result) returns the engine's approximation to log p(y), or nothing when the method has no natural estimate.
Approximation quality
diagnose(result) runs a PSIS-k̂ check on the inner Laplace approximation q(x | θ) ≈ p(x | y, θ) at the hyperparameter mode and returns a NamedTuple with a relative effective sample size, the GPD shape pareto_k, and a qualitative interpretation (:excellent / :acceptable / :unreliable):
d = diagnose(result)
d.rel_ess # relative effective sample size, in (0, 1]
d.interpretation # :excellent / :acceptable / :unreliable
d.obs_hessian # :exact, or :gauss_newton for a NonlinearLeastSquares obsAn :unreliable verdict means the Gaussian inner approximation is a poor fit at the mode, so the marginals downstream of it should be treated with caution.
obs_hessian reports whether the inner approximation used the exact observation Hessian or the Gauss–Newton approximation that a NonlinearLeastSquaresModel applies — the default for a Gaussian observation with a nonlinear-in-x mean. When it is :gauss_newton, the latent marginals and log p(y) carry that approximation; pass nls = false to the model constructor to force the exact path instead.
Reference
Latte.INLAResult Type
INLAResult{HM, LM, Mode, Expl, Conv, Time, Model, Opts, Acc}Results structure for INLA inference containing all outputs from the inference process.
This structure provides organized access to all results from INLA inference, including hyperparameter marginals, latent marginals, diagnostic information, and model comparison metrics.
Type Parameters
All fields are fully typed for type stability and performance.
Fields
hyperparameter_marginals::HM: NamedTuple mapping parameter names to marginal distributions for each hyperparameterlatent_marginals::LM: Vector of marginal distributions for latent variables (WeightedMixture)hyperparameter_mode::Mode: Mode of the hyperparameter posterior (WorkingHyperparameters)exploration::Expl: Results from posterior exploration (HyperparameterExploration)convergence::Conv: Convergence diagnostics and information (NamedTuple)computation_time::Time: Timing breakdown by computation phase (NamedTuple)model::Model: Original INLA model specification (LatentGaussianModel)options::Opts: Options used for inference (NamedTuple)accumulators::Acc: Tuple of PosteriorAccumulator objects with computed metrics (e.g., DIC, marginal likelihood)linear_predictor_marginals::Union{Nothing, Vector}: Marginals for linear predictors η (if augmented model)base_latent_marginals::Union{Nothing, Vector}: Marginals for base latent components (if augmented model)augmentation_info::Union{Nothing, AugmentationInfo}: Metadata about latent field augmentation
Usage
result = inla_inference(model, y)
# Access hyperparameter marginals (by name)
result.hyperparameter_marginals.τ # Marginal for τ hyperparameter
mean(result.hyperparameter_marginals.τ) # Mean of τ hyperparameter
# Access latent marginals
result.latent_marginals[1] # First latent variable marginal (WeightedMixture)
# Access mode (WorkingHyperparameters)
result.hyperparameter_mode # WorkingHyperparameters
convert(NamedTuple, convert(NaturalHyperparameters, result.hyperparameter_mode)) # Convert to NamedTuple in natural space
# Access diagnostics
result.convergence.mode_converged # Did mode finding converge?
result.computation_time.total # Total computation time
result.computation_time.mode_finding # Time spent finding mode
# Access model comparison metrics
result.accumulators[1] # First accumulator (e.g., DICAccumulator)
result.accumulators[1].DIC # DIC value
result.accumulators[1].p_D # Effective parametersLatte.latent_marginals Function
latent_marginals(r::InferenceResult) -> Vector{<:Distribution}
latent_marginals(r::InferenceResult, name::Symbol) -> Vector{<:Distribution}Marginal posterior distributions for the latent field. The vector form returns all marginals positionally. The name-keyed form returns the slice corresponding to that latent-field group (e.g. :β, :u); returns a 1-element vector for scalar groups.
Latte.base_latent_marginals Function
base_latent_marginals(result::INLAResult)Marginals of the original model's base latent components, uniform across result modes.
The augmented representation slices the base block out of the full latent vector; the compact default stores only the base latent, so its latent marginals already are these. Prefer this accessor to the result.base_latent_marginals field, which is nothing under the compact default.
See also latent_marginals, linear_predictor_marginals.
GaussianMarkovRandomFields.linear_predictor_marginals Function
linear_predictor_marginals(ga, obs_lik) -> (μ_η, v_η, eta_likelihood)Posterior marginals of the per-observation linear predictor η_i under a Gaussian approximation ga to the latent posterior.
For each observation i the result contains (μ_η[i], v_η[i]) — the mean and variance of the scalar predictor η_i that obs_lik consumes. The third return value is an observation likelihood whose own indexing matches μ_η's layout: feeding μ_η directly into loglik, pointwise_loglik, loggrad, or loghessian yields per-observation outputs in the same order as μ_η.
The dispatch recurses on the observation likelihood structure:
ExponentialFamilyLikelihood:η_i = x[indices[i]](orη_i = x_iwhenindices === nothing).μ_ηandv_ηare the corresponding slices ofmean(ga)/var(ga);eta_likelihoodis the same likelihood withindices === nothingso it consumes the returned (smaller)μ_ηdirectly.LinearlyTransformedLikelihood: affine predictorη = A x + b(the offsetb = lik.offsetis omitted whennothing), givingμ_η = A · mean(ga) + bandv_η = diag(A · Σ · Aᵀ)from the posterior's selected-inversion output (the constantbdoes not affect the variance).eta_likelihood = lik.base_likelihood. Assumes the base's ownindicesfield isnothing(the standard wrapping pattern); an indexed base is unusual and not specially handled.CompositeLikelihood: per-component results concatenated.eta_likelihoodis a freshCompositeLikelihoodwhose components are the per-component stripped likelihoods withindicesre-assigned to their slice of the concatenatedη, so the result can be evaluated againstμ_ηdirectly. Composite-of-composite is not specially handled — flatten upstream.
Hard constraints
If ga carries a hard linear constraint A_c x = e (either a ConstrainedGMRF or a WorkspaceGMRF with constraint info populated), v_η subtracts the standard correction diag(A · A_tilde_T · L_c⁻ᵀ · L_c⁻¹ · A_tilde_Tᵀ · Aᵀ) from the unconstrained diag(A · Σ · Aᵀ), reusing the cached A_tilde_T = Σ A_cᵀ and L_c = chol(A_c Σ A_cᵀ). The mean is the constrained mean returned by mean(ga).
Sparse-pattern assumption
For LinearlyTransformedLikelihood, the variance computation reads Σ from the posterior's selected inversion, which only fills entries at the Cholesky factor pattern of the precision matrix Q. When Q's pattern subsumes that of Aᵀ A — automatic when the posterior comes out of gaussian_approximation with a LinearlyTransformedObservationModel obs side — every entry Σ[j, k] needed by diag(A Σ Aᵀ) is present and the result is exact. With a hand-rolled prior whose pattern is too narrow, missing Σ entries silently contribute zero and v_η underestimates; arrange Q's pattern to include Aᵀ A if you build the posterior outside the package's standard flow.
Latte.hyperparameter_marginals Function
hyperparameter_marginals(r::InferenceResult) -> Vector{<:Distribution}
hyperparameter_marginals(r::InferenceResult, name::Symbol) -> Vector{<:Distribution}Marginal posterior distributions for the hyperparameters, analogous to latent_marginals. Semantics depend on the method — see concrete implementations' docstrings (e.g. INLA returns natural-space spline marginals, TMB returns working-space Gaussian approximations).
Latte.observation_marginals Function
observation_marginals(result::INLAResult; rtol::Real = 1.0e-3, atol::Real = 1.0e-6)Compute marginal distributions for observations (fitted values) by transforming linear predictor marginals through the inverse link function.
Mathematical Background
For an observation model with link function g:
Linear predictor: η (in ℝ, typically Gaussian-like)
Expected observation: μ = g⁻¹(η) (in observation space)
This function transforms the marginal distributions for η to obtain marginal distributions for μ = g⁻¹(η), which represent the fitted values or expected observations under the model.
Requirements
This function requires:
Augmented latent model: The model must have been created with automatic augmentation (default for LinearlyTransformedObservationModel).
ExponentialFamily observation model: Currently only supported for ExponentialFamily models with extractable link functions.
Arguments
result::INLAResult: INLA inference results with linear predictor marginalsrtol::Real = 1.0e-3: Relative tolerance for numerical integration in moment calculationsatol::Real = 1.0e-6: Absolute tolerance for numerical integration in moment calculations
Returns
A vector of TransformedWeightedMixture distributions, one for each observation, representing the marginal distribution of the expected observation μᵢ = g⁻¹(ηᵢ).
Each distribution supports the full Distributions.jl interface:
mean(obs_marginal): Expected value of the observationvar(obs_marginal): Variance of the observationquantile(obs_marginal, p): Quantiles for credible intervalspdf(obs_marginal, y): Density evaluationrand(obs_marginal): Sampling
Examples
# After running INLA with augmented model
result = inla(model, y)
# Get observation marginals (fitted values)
obs_marginals = observation_marginals(result)
# Access statistics for each observation
for i in 1:length(obs_marginals)
μ_mean = mean(obs_marginals[i])
μ_std = std(obs_marginals[i])
μ_ci = (quantile(obs_marginals[i], 0.025), quantile(obs_marginals[i], 0.975))
println("Observation $i: μ = $μ_mean ± $μ_std, 95% CI: $μ_ci")
end
# For Poisson regression with log link, these represent the rate parameter λ
# For logistic regression, these represent the probability pCommon Link Functions and Their Interpretations
LogLink (Poisson, Gamma, etc.): μ = exp(η) represents the rate/scale parameter
LogitLink (Binomial, Bernoulli): μ = logistic(η) represents the success probability
IdentityLink (Gaussian): μ = η is the mean directly
Implementation Notes
Uses
TransformedWeightedMixturewhich applies change of variables to the linear predictor marginals.Moments (mean, variance) computed via numerical integration (1D quadrature).
The bijector stored internally is the link function g; inverse is taken when needed.
Error Handling
Throws an error if
result.linear_predictor_marginalsisnothing(augmentation not used).Throws an error if the observation model is not an
ExponentialFamily.Throws an error if the link function is not supported by
get_bijector.
Latte.latent_groups Function
latent_groups(model::LatentGaussianModel) -> OrderedDict{Symbol, UnitRange{Int}}Name → augmented-latent-range mapping for a DPPL-built LGM (empty for hand-built LGMs). Matches latent_groups(::INLAResult) so lookup by name works interchangeably on the model and its inference result.
latent_groups(r::InferenceResult) -> OrderedDict{Symbol, UnitRange{Int}}Name → index-range mapping for the latent field. Scalar group :τ maps to i:i; vector group :β of length p maps to a:(a+p-1). When no naming exists (manually-constructed LGM), returns an empty OrderedDict.
Populated by DSL / formula layers; empty otherwise.
sourceLatte.hyperparameter_groups Function
hyperparameter_groups(r::InferenceResult) -> OrderedDict{Symbol, UnitRange{Int}}Name → index-range mapping for the hyperparameters, analogous to latent_groups.
Latte.hyperparameter_mode Function
hyperparameter_mode(r::InferenceResult) -> NaturalHyperparametersMode of the hyperparameter posterior in natural space. For INLA this centres the grid; for TMB this is the MAP (the answer); for HMC-Laplace this is the warm-start used before sampling.
sourceLatte.log_marginal_likelihood Function
log_marginal_likelihood(r::InferenceResult) -> Union{Float64, Nothing}Approximation to log p(y). Each method produces a different approximation (INLA: grid integral of Laplace-approx integrand; TMB: Laplace at MAP; HMC: requires bridge sampling). Returns nothing when the method has no natural way to produce an estimate. Concrete implementations' docstrings spell out which approximation is computed.
Latte.converged Function
converged(r::InferenceResult) -> BoolWhether the underlying optimisation / exploration converged.
sourceLatte.time_elapsed Function
time_elapsed(r::InferenceResult) -> Float64Total wall-clock time for inference, in seconds.
sourceLatte.pushforward Function
pushforward(marginal, g)The distribution of g(X) where X ~ marginal. Moments are computed by integration, so mean(pushforward(m, exp)) is the true E[exp(X)] — not exp(E[X]) (the Jensen-inequality trap).
g may be exp, log, identity, or any Bijectors bijector. This is the way to recover a derived hyperparameter's posterior: e.g. when a model declares log_α ~ Normal(...) and uses α = exp(log_α), the posterior of α is pushforward(result.hyperparameter_marginals.log_α, exp). (A declared hyperparameter's marginal is already in natural space, so this isn't needed there.)
Latte.diagnose Function
diagnose(r::InferenceResult; M = 500, rng = Random.default_rng())Run the PSIS-k̂ diagnostic at the hyperparameter mode of a Laplace-based inference result. Works uniformly over INLAResult, TMBResult, and HMCLaplaceResult — all three have a well-defined inner Laplace at their MAP (INLA uses it for grid centering; TMB reports it as the answer; HMC-Laplace uses it per-sample).
Returns
NamedTuple{(:rel_ess, :ess, :pareto_k, :interpretation, :obs_hessian, :M)}:
rel_ess ∈ (0, 1]— relative effective sample size (primary metric)ess— absolute ESS of the importance weightspareto_k— Zhang-Stephens GPD shape parameterinterpretation—:excellent/:acceptable/:unreliableobs_hessian—:gauss_newtonif the observation Hessian is the Gauss–Newton approximation (NonlinearLeastSquaresModel), else:exactM— number of Gaussian samples used
:unreliable suggests switching to a method that doesn't rely on the Laplace approximation being exact (e.g., a future sparsenuts(lgm, y) that samples the joint (θ, x) directly).