Skip to content

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:

  1. a hyperparameter prior, declared with @hyperparams;

  2. a latent field prior — a function of the hyperparameters returning a mean and a precision matrix, wrapped in FunctionLatentModel;

  3. 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.

julia
using Latte, Distributions

spec = @hyperparams begin
~ Exponential(1.0), transform = log, space = natural)
~ Beta(2, 2),       transform = logit, space = natural)
end

Each ~ 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 transform field is the bijector mapping natural → working (log for positive parameters, logit for the unit interval, identity for 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:

julia
θ_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:

logpworking(η)=logpnatural(θ)+log|dθdη|,

where η is the working value and θ=g(η) the corresponding natural value. The log-Jacobian term is supplied by 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.

julia
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
julia
@hyperparams begin ... end

Convenient macro for creating HyperparameterSpec objects with clean, declarative syntax.

Syntax

Free parameters use the ~ operator:

julia
name ~ prior                                         # Identity transform, working space (default)
(name ~ prior, transform = transform_expr)           # With custom transform
(name ~ prior, transform = log, space = natural)     # With options

Fixed parameters use =:

julia
name = value

Builtin Transforms

The macro provides convenient shortcuts for common transformations:

  • logelementwise(log) for positive parameters (σ, τ, κ, etc.)

  • logitBijectors.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)

  • space or prior_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

julia
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)
end

Comparison with Manual Construction

The macro provides a much more concise syntax compared to manual construction:

julia
# 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 required

  • When specifying options, use parentheses: (name ~ prior, option = value)

  • Options use unquoted symbols: space = natural, not space = :natural

  • Parameters cannot be specified multiple times

  • The space and prior_space keywords are synonyms - use whichever is more natural

See Also

source
Latte.HyperparameterSpec Type
julia
HyperparameterSpec{Free, Fixed}

Complete specification of hyperparameters with both free and fixed parameters.

Type Parameters

  • Free: Concrete NamedTuple type for free parameters

  • Fixed: 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

julia
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,)
)
source
Latte.Hyperparameter Type
julia
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 (:natural or :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

julia
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,)
)
source
Latte.WorkingHyperparameters Type
julia
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 vector

  • Spec: HyperparameterSpec type

Fields

  • θ::Vector{T}: Parameter values in working space

  • spec::Spec: Hyperparameter specification

Example

julia
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)         # reduction
source
Latte.NaturalHyperparameters Type
julia
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 vector

  • Spec: HyperparameterSpec type

Fields

  • θ::Vector{T}: Parameter values in natural space

  • spec::Spec: Hyperparameter specification

Example

julia
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)     # reduction
source
Latte.logpdf_prior Function
julia
logpdf_prior::WorkingHyperparameters) -> Float64

Evaluate 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

julia
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 space
source
julia
logpdf_prior::NaturalHyperparameters) -> Float64

Evaluate 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

julia
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 space
source
julia
logpdf_prior(θ_natural::NamedTuple, spec::HyperparameterSpec) -> Float64

Evaluate the log prior density in natural space (legacy interface).

Arguments

  • θ_natural::NamedTuple: Free parameters in natural space

  • spec::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

julia
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 π(σ)
source
Latte.logdetjac Function
julia
logdetjac::WorkingHyperparameters) -> Float64

Compute 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:

julia
log p_natural(θ) = log p_working(η) + logdetjac(θ_working)

where η are working values, θ = g(η) are the corresponding natural values.

Example

julia
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)
source
julia
logdetjac::NaturalHyperparameters) -> Float64

Compute 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:

julia
log p_working(η) = log p_natural(θ) + logdetjac(θ_natural)

where θ are natural values, η = f(θ) are the corresponding working values.

Example

julia
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)
source

Building a model directly: LatentGaussianModel

LatentGaussianModel combines the three components. It factors the joint as p(θ)p(xθ)p(yx,θ) with p(xθ) Gaussian.

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.

julia
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:

julia
# 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
julia
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 Gaussian LatentModel (wrap a function with FunctionLatentModel), or a NonGaussianLatentPrior (e.g. AutoDiffLatentPrior) for nonlinear state-space priors

  • O <: ObservationModel: Type of the observation model

Fields

  • hyperparameter_spec::HP: Hyperparameter specification with transformations

  • latent_prior::F: The latent field prior as a LatentModel, receiving natural-space hyperparameters as keyword arguments

  • observation_model::O: Observation model linking observations to latent field

Example

julia
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)
source
Latte.latent_gmrf Function
julia
latent_gmrf(model::LatentGaussianModel, θ_named)

Get the latent field GMRF for given hyperparameters θ_named.

source
julia
latent_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.

source
Latte.log_joint_density Function
julia
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 model

  • x: Latent field values

  • θ_w: Hyperparameters in working space

  • y: Observations

This is the main implementation. Creates the latent GMRF and observation likelihood internally.

source
julia
log_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 model

  • x: Latent field values

  • θ_n: Hyperparameters in natural space

  • y: Observations

source