Lower-level construction
The @latte macro is the primary way to define a model: you write the latent field and likelihood once, and Latte assembles the LatentGaussianModel and its hyperparameter prior for you. This page documents the secondary path — building an LatentGaussianModel by hand from its three components. Reach for it when @latte cannot express your model, for example when you want full control over the latent precision matrix and assemble it yourself.
Prefer @latte for most models
Most models are written with the @latte macro, which builds a LatentGaussianModel for you (see Defining models: the @latte macro and the tutorials). Construct one directly, as shown below, when you need full control over the latent prior.
A hand-built model has three parts:
a hyperparameter prior, declared with
@hyperparams;a latent field prior — a function of the hyperparameters returning a mean and a precision matrix, wrapped in
FunctionLatentModel;an observation model linking observations to the latent field (see Observation Models).
Hyperparameters: @hyperparams
Hyperparameters control the latent prior (e.g. marginal variance, correlation) and the observation model (e.g. noise level). The @hyperparams macro declares them, each with a prior distribution, a transformation, and the space in which the prior is written.
using Latte, Distributions
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
(ρ ~ Beta(2, 2), transform = logit, space = natural)
endEach ~ line introduces a free parameter; a plain name = value line fixes a parameter to a constant. Only free parameters appear in the parameter vector.
Natural and working space
Latte distinguishes two spaces:
Natural space is where parameters live for the user and for the model functions: a standard deviation
σ > 0, a correlationρ ∈ (0, 1).Working space is the unconstrained space the inference engines optimise and explore in. The
transformfield is the bijector mapping natural → working (logfor positive parameters,logitfor the unit interval,identityfor unbounded ones).
The two parameter wrappers, WorkingHyperparameters and NaturalHyperparameters, carry a vector together with the spec, and convert moves between them and to a NamedTuple:
θ_w = WorkingHyperparameters([0.5, -1.2], spec) # working space (log σ, logit ρ)
θ_n = convert(NaturalHyperparameters, θ_w) # natural space (σ, ρ)
θ_nt = convert(NamedTuple, θ_n) # (ρ = logistic(-1.2), σ = exp(0.5))Model functions receive parameters in natural space as keyword arguments, even though optimisation happens in working space.
Prior space and the Jacobian correction
The space field records where the prior is written. With space = natural, the prior is a density over the natural parameter, and Latte applies the change of variables to evaluate it in working space:
where logdetjac. With space = working, the prior is already a density over the working parameter and no correction is added. logpdf_prior evaluates the prior in working space — including this correction when the prior was specified in natural space.
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
end
# Equivalent to a prior on log(σ) in working space:
# p(log σ) = Exponential(1.0)(σ) · |dσ/d(log σ)| = Exponential(1.0)(σ) · σLatte.@hyperparams Macro
@hyperparams begin ... endConvenient macro for creating HyperparameterSpec objects with clean, declarative syntax.
Syntax
Free parameters use the ~ operator:
name ~ prior # Identity transform, working space (default)
(name ~ prior, transform = transform_expr) # With custom transform
(name ~ prior, transform = log, space = natural) # With optionsFixed parameters use =:
name = valueBuiltin Transforms
The macro provides convenient shortcuts for common transformations:
log→elementwise(log)for positive parameters (σ, τ, κ, etc.)logit→Bijectors.Logit(0.0, 1.0)for parameters in (0,1) (ρ, correlation, etc.)identity→ no transformation (default for parameters already unconstrained)
Call aliases are also supported: Log(), Logit(), Identity()
For maximum flexibility, you can also provide any custom bijector expression directly.
Options
transform: Bijector mapping natural → working space (default:identity)spaceorprior_space: Space in which the prior is specified:natural- Prior is on the natural (constrained) parameter space:working- Prior is already on the working (unconstrained) space (default)
Important: When specifying options, wrap the entire clause in parentheses!
Examples
using Distributions, Bijectors
# Example 1: PC priors (Penalizing Complexity priors)
# Exponential prior on σ, optimized on log(σ)
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
(ρ ~ Beta(2, 2), transform = logit, space = natural)
μ = 0.0 # Fixed parameter
end
# Example 2: Using call aliases
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = Log(), space = natural)
(ρ ~ Beta(2, 2), transform = Logit(), space = natural)
end
# Example 3: Custom bijector expression
spec = @hyperparams begin
(τ ~ Gamma(2, 1), transform = elementwise(x -> log(x + 1)), space = natural)
end
# Example 4: Identity transform (default) - prior already in working space
spec = @hyperparams begin
μ ~ Normal(0, 10) # No transformation needed
σ ~ Gamma(2, 1) # Prior specified directly in working space
end
# Example 5: Mixed free and fixed parameters
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
(ρ ~ Beta(2, 2), transform = logit, space = natural)
μ = 0.0 # Fixed intercept
n = 100 # Fixed sample size
end
# Example 6: Using prior_space keyword (synonym for space)
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, prior_space = natural)
endComparison with Manual Construction
The macro provides a much more concise syntax compared to manual construction:
# With macro (recommended)
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
(ρ ~ Beta(2, 2), transform = logit, space = natural)
μ = 0.0
end
# Manual construction (verbose)
spec = HyperparameterSpec(
free = (
σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),
ρ = Hyperparameter(Beta(2, 2), transform=Bijectors.Logit(0.0, 1.0), prior_space=:natural)
),
fixed = (μ = 0.0,)
)Notes
At least one free parameter (using
~) is requiredWhen specifying options, use parentheses:
(name ~ prior, option = value)Options use unquoted symbols:
space = natural, notspace = :naturalParameters cannot be specified multiple times
The
spaceandprior_spacekeywords are synonyms - use whichever is more natural
See Also
HyperparameterSpec: The underlying type created by this macroHyperparameter: Individual hyperparameter specification
Latte.HyperparameterSpec Type
HyperparameterSpec{Free, Fixed}Complete specification of hyperparameters with both free and fixed parameters.
Type Parameters
Free: Concrete NamedTuple type for free parametersFixed: Concrete NamedTuple type for fixed parameter values
Fields
free::Free: Free parameters to be estimated (NamedTuple of Hyperparameter objects)fixed::Fixed: Fixed parameter values (NamedTuple of scalar values)
Example
using Bijectors
spec = HyperparameterSpec(
free = (
σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),
ρ = Hyperparameter(Beta(2, 2), transform=Bijectors.Logit(0.0, 1.0), prior_space=:natural)
),
fixed = (μ = 0.0,)
)Latte.Hyperparameter Type
Hyperparameter{T, S}A hyperparameter specification with an explicit transformation between working and natural spaces.
Type Parameters
T: The transformation type (Bijector or identity function)S: The space in which the prior was originally specified (:naturalor:working)
Fields
prior::Distribution: Prior distribution in working space (always stored in working space for numerical stability)transform::T: Bijector mapping natural → working (constrained → unconstrained)
Transformation Convention
Follows Bijectors.jl/Turing.jl convention:
transform: natural (constrained) → working (unconstrained)inverse(transform): working (unconstrained) → natural (constrained)
For example, for σ ∈ (0,∞):
Natural space: σ > 0
Working space: log(σ) ∈ ℝ
Transform:
elementwise(log)maps σ → log(σ)
Spaces
Working space (η): Unconstrained space used for optimization/exploration
Natural space (θ): Natural parameter space used in user model functions
Example
using Bijectors
# Used within a HyperparameterSpec, not standalone
spec = HyperparameterSpec(
free = (
σ = Hyperparameter(Exponential(1.0), elementwise(log), Val(:natural)),
ρ = Hyperparameter(Beta(2, 2), Bijectors.Logit(0.0, 1.0), Val(:natural))
),
fixed = (μ = 0.0,)
)Latte.WorkingHyperparameters Type
WorkingHyperparameters{T, Spec} <: AbstractVector{T}Hyperparameters in working (unconstrained) space.
Behaves like a vector while preserving semantic meaning. Supports indexing, iteration, broadcasting, and other vector operations.
Type Parameters
T: Element type of the parameter vectorSpec: HyperparameterSpec type
Fields
θ::Vector{T}: Parameter values in working spacespec::Spec: Hyperparameter specification
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_w = WorkingHyperparameters([0.5], spec)
# Vector-like operations
θ_w[1] # indexing
θ_w .+ 0.1 # broadcasting
sum(θ_w) # reductionLatte.NaturalHyperparameters Type
NaturalHyperparameters{T, Spec} <: AbstractVector{T}Hyperparameters in natural (constrained) space.
Behaves like a vector while preserving semantic meaning. Supports indexing, iteration, broadcasting, and other vector operations.
Type Parameters
T: Element type of the parameter vectorSpec: HyperparameterSpec type
Fields
θ::Vector{T}: Parameter values in natural spacespec::Spec: Hyperparameter specification
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_n = NaturalHyperparameters([2.0], spec)
# Vector-like operations
θ_n[1] # indexing
θ_n .* 2 # broadcasting
maximum(θ_n) # reductionLatte.logpdf_prior Function
logpdf_prior(θ::WorkingHyperparameters) -> Float64Evaluate the log prior density in working space.
Arguments
θ::WorkingHyperparameters: Hyperparameters in working space
Returns
Float64: Log prior density in working space
Details
Since priors are stored in working space internally, this directly evaluates the prior at the working-space values.
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_w = WorkingHyperparameters([0.5], spec)
log_p = logpdf_prior(θ_w) # Evaluates log p(η) in working spacelogpdf_prior(θ::NaturalHyperparameters) -> Float64Evaluate the log prior density in natural space.
Arguments
θ::NaturalHyperparameters: Hyperparameters in natural space
Returns
Float64: Log prior density in natural space
Details
Converts to working space, evaluates the working-space prior, then adds the Jacobian correction to obtain the natural-space density.
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_n = NaturalHyperparameters([2.0], spec)
log_p = logpdf_prior(θ_n) # Evaluates log π(θ) in natural spacelogpdf_prior(θ_natural::NamedTuple, spec::HyperparameterSpec) -> Float64Evaluate the log prior density in natural space (legacy interface).
Arguments
θ_natural::NamedTuple: Free parameters in natural spacespec::HyperparameterSpec: Hyperparameter specification
Returns
Float64: Log prior density in natural space
Details
Legacy interface that constructs NaturalHyperparameters internally and dispatches to the modern implementation.
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_natural = (σ = 2.0,) # σ in natural space
log_p = logpdf_prior(θ_natural, spec) # Evaluates log π(σ)Latte.logdetjac Function
logdetjac(θ::WorkingHyperparameters) -> Float64Compute the log absolute determinant of the Jacobian for the working → natural transformation.
This is the correction term to add when converting a density from working space to natural space:
log p_natural(θ) = log p_working(η) + logdetjac(θ_working)where η are working values, θ = g(η) are the corresponding natural values.
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_w = WorkingHyperparameters([0.5], spec)
# Have a density in working space, want natural space
log_density_working = some_function(θ_w)
log_density_natural = log_density_working + logdetjac(θ_w)logdetjac(θ::NaturalHyperparameters) -> Float64Compute the log absolute determinant of the Jacobian for the natural → working transformation.
This is the correction term to add when converting a density from natural space to working space:
log p_working(η) = log p_natural(θ) + logdetjac(θ_natural)where θ are natural values, η = f(θ) are the corresponding working values.
Example
spec = HyperparameterSpec(
free = (σ = Hyperparameter(Exponential(1.0), transform=elementwise(log), prior_space=:natural),)
)
θ_n = NaturalHyperparameters([2.0], spec)
# Prior is in natural space, want working space density
log_prior_natural = logpdf(Exponential(1.0), θ_n.σ)
log_density_working = log_prior_natural + logdetjac(θ_n)Building a model directly: LatentGaussianModel
LatentGaussianModel combines the three components. It factors the joint as
The latent field prior is a function of the (keyword) hyperparameters that returns a (mean, precision) tuple. Wrap it with FunctionLatentModel(f, n), passing the latent dimension n; a raw function is rejected with an error asking you to wrap it. The function receives every hyperparameter — free and fixed — by name in natural space, so accept kwargs... to ignore the ones it does not use.
using Latte, GaussianMarkovRandomFields, Distributions, SparseArrays
spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
(ρ ~ Beta(2, 2), transform = logit, space = natural)
end
# Latent GMRF: an AR(1) precision built by hand
function ar1_latent(; σ, ρ, kwargs...)
n = 100
Q = spdiagm(
-1 => -ρ * ones(n - 1),
0 => (1 + ρ^2) * ones(n),
1 => -ρ * ones(n - 1),
)
return (zeros(n), Q / σ^2) # FunctionLatentModel expects (mean, precision)
end
obs_model = ExponentialFamily(Normal)
model = LatentGaussianModel(spec, FunctionLatentModel(ar1_latent, 100), obs_model)Validation
The constructor checks that every hyperparameter required by the observation model is present in the spec, as a free or fixed parameter. A missing one is an error:
# Errors: Normal requires a σ hyperparameter, but the spec only declares τ
spec = @hyperparams begin
(τ ~ Gamma(2, 1), transform = log, space = natural)
end
obs_model = ExponentialFamily(Normal)
LatentGaussianModel(spec, FunctionLatentModel(latent_fn, 50), obs_model) # ERROR: missing σExtra hyperparameters beyond those the observation model needs are allowed — they are typically consumed by the latent field prior function.
Once built, the model feeds the inference engines directly; pass it to inla (see the INLA engine) and work with the result using the result accessors.
Latte.LatentGaussianModel Type
LatentGaussianModel{HP, F, O}A latent Gaussian model specification with hyperparameter prior, latent field prior, and observation model.
Factors as p(θ) · p(x | θ) · p(y | x, θ) with p(x | θ) Gaussian. This is the shared structure that INLA, TMB, HMC-on-Laplace, and variational approximations all operate on.
The alias LGM is available for brevity.
Type Parameters
HP: Type of the hyperparameter specification (HyperparameterSpec)F <: AbstractLatentPrior: Type of the latent prior — a GaussianLatentModel(wrap a function withFunctionLatentModel), or aNonGaussianLatentPrior(e.g.AutoDiffLatentPrior) for nonlinear state-space priorsO <: ObservationModel: Type of the observation model
Fields
hyperparameter_spec::HP: Hyperparameter specification with transformationslatent_prior::F: The latent field prior as aLatentModel, receiving natural-space hyperparameters as keyword argumentsobservation_model::O: Observation model linking observations to latent field
Example
using SparseArrays
# Hyperparameter specification (free parameters via `~`)
hp_spec = @hyperparams begin
(σ ~ Exponential(1.0), transform = log, space = natural)
end
# Latent field prior: a function of the (keyword) hyperparameters returning (mean, precision)
function latent_gmrf(; σ, kwargs...)
n = 100
Q = spdiagm(0 => fill(1 / σ^2, n)) # Simple white noise
μ = zeros(n)
return (μ, Q)
end
# Observation model
obs_model = ExponentialFamily(Normal)
# Create the latent Gaussian model — wrap the latent function with its dimension
model = LatentGaussianModel(hp_spec, FunctionLatentModel(latent_gmrf, 100), obs_model)Latte.latent_gmrf Function
latent_gmrf(model::LatentGaussianModel, θ_named)Get the latent field GMRF for given hyperparameters θ_named.
sourcelatent_gmrf(model::LatentGaussianModel, ws, θ_named)Construct the latent GMRF through a persistent workspace ws. Reuses the workspace's Cholesky symbolic factorization; only the numeric values are refactorized. Use this form inside hot loops over hyperparameters.
Latte.log_joint_density Function
log_joint_density(model::LatentGaussianModel, x, θ_w::WorkingHyperparameters, y)Evaluate the joint log-density log π(x, θ, y) for the INLA model in working space.
This computes: log π(θ) + log π(x | θ) + log π(y | x, θ)
Arguments
model::LatentGaussianModel: The INLA modelx: Latent field valuesθ_w: Hyperparameters in working spacey: Observations
This is the main implementation. Creates the latent GMRF and observation likelihood internally.
sourcelog_joint_density(model::LatentGaussianModel, x, θ_n::NaturalHyperparameters, y)Evaluate the joint log-density log π(x, θ, y) for the INLA model in natural space.
Converts to working space and adds Jacobian correction term.
Arguments
model::LatentGaussianModel: The INLA modelx: Latent field valuesθ_n: Hyperparameters in natural spacey: Observations