Model Framework

All HRV models in HeartRateLab inherit from AbstractHRVModel and share a unified API for simulation, parameter fitting, and evaluation.

AbstractHRVModel Interface

Every model must implement simulate. The fit and parameter_space methods are optional: only models that support parameter inference implement them.

# Required for all models
simulate(model::AbstractHRVModel, params::NamedTuple, n_beats::Int) -> Vector{Float64}

# Optional: models that support fitting
fit(model::AbstractHRVModel, data::Vector{Float64}; method::Symbol, kwargs...) -> ModelFitResult
parameter_space(model::AbstractHRVModel) -> NamedTuple

simulate always returns a Vector{Float64} of IBI values in milliseconds. params is a NamedTuple of parameter values passed to the simulator; each model defines which keys it reads.

ModelFitResult

fit returns a ModelFitResult containing:

FieldTypeDescription
modelAbstractHRVModelThe model that was fitted
methodSymbolFitting method: :analytical, :gradient, or :bayesian
paramsNamedTuplePoint estimates (MAP for Bayesian, minimizer for gradient, mean for analytical)
posteriorDict or nothingPosterior samples (Bayesian) or per-beat series (analytical); nothing for gradient
diagnosticsDict{String,Any}Convergence info, iterations, loss, R-hat values, etc.
dataVector{Float64}Original IBI data used for fitting (milliseconds)
julia> using HeartRateLab

julia> vdp = VanDerPol()
VanDerPol()

julia> typeof(vdp) <: AbstractHRVModel
true

Fitting Methods

:analytical

Available on LIF only. Inverts the closed-form period formula for each measured IBI individually, yielding a per-beat current $I$ series. Instantaneous: no simulation or optimization is needed.

result = fit(LIF(), ibis; method=:analytical)
result.params.I          # mean I across all beats
result.posterior["I"]    # Vector{Float64} of per-beat I values (same length as ibis)
result.diagnostics["I_std"]  # standard deviation of per-beat I

:gradient

Minimizes a distance function (RMSE or feature-space distance) using gradient-free or gradient-based optimization via Optim.jl. Returns a scalar point estimate per parameter.

result = fit(VanDerPol(), ibis; method=:gradient)
result.params.μ                   # fitted μ
result.diagnostics["converged"]   # Bool
result.diagnostics["iterations"]  # Int
result.diagnostics["loss_final"]  # Float64
result.posterior                  # nothing

:bayesian

NUTS MCMC sampler via Turing.jl. Samples the full posterior distribution over parameters. Requires more computation but provides uncertainty estimates.

result = fit(LIF(), ibis; method=:bayesian, chains=4, samples=1000)
result.params.I               # posterior mean of I
result.posterior["I"]         # Vector of 4000 MCMC samples
result.diagnostics["rhat_I"]  # R-hat convergence statistic (target: < 1.1)

Accessing Posterior Samples

Use parameter_series to retrieve per-beat series or posterior samples from a fit result:

# Analytical fit: per-beat I series
result = fit(LIF(), ibis; method=:analytical)
I_series = parameter_series(result, :I)   # Vector{Float64}, one value per beat

# Bayesian fit: MCMC posterior samples
result_bayes = fit(LIF(), ibis; method=:bayesian)
I_samples = parameter_series(result_bayes, :I)  # Vector{Float64}, MCMC samples

# Gradient fit: returns nothing (no distribution)
result_grad = fit(LIF(), ibis; method=:gradient)
parameter_series(result_grad, :I)  # nothing

API Reference

HeartRateLab.Models.AbstractHRVModel — Type
AbstractHRVModel

Abstract base type for all HRV models (mechanistic, data-driven, or spectral).

All models must implement at minimum:

  • simulate(model::AbstractHRVModel, params::NamedTuple, n_beats::Int)::Vector{Float64}

Models may optionally implement:

  • fit(model::AbstractHRVModel, data::Vector{Float64}; method::Symbol, kwargs...)
  • parameter_space(model::AbstractHRVModel)
source
HeartRateLab.Models.ModelFitResult — Type
ModelFitResult

Result of fitting an HRV model to data. Stores both point estimates and uncertainty information.

Fields

  • model::AbstractHRVModel: The model type that was fitted
  • method::Symbol: Fitting method used (:analytical, :bayesian, or :gradient)
  • params::NamedTuple: Point estimate of parameters (MAP/MLE for Bayesian, best individual otherwise)
  • posterior: Posterior samples for Bayesian fitting (Turing.Chain), nothing otherwise
  • diagnostics::Dict: Convergence info, iteration count, loss history, etc.
  • data::Vector{Float64}: Original IBI data used for fitting (in milliseconds)

Fitting Methods

  • :analytical: Closed-form / per-beat inversion where available (e.g. LIF)
  • :bayesian: Markov Chain Monte Carlo via Turing.jl, produces full posterior over parameters
  • :gradient: Gradient-based optimization via Optim.jl, minimizes feature-space distance

Example

result = fit(LIF(), data; method=:bayesian)
posterior_samples = result.posterior  # Turing.Chain
point_estimate = result.params       # NamedTuple of parameter values
simulated = simulate(result.model, point_estimate, 100)  # Generate synthetic data
source
HeartRateLab.Models.parameter_series — Function
parameter_series(result::ModelFitResult, param::Symbol) -> Union{Vector{Float64}, Nothing}

Return the per-point array for a fitted parameter, where available.

The meaning of the returned array depends on the fitting method:

  • :analytical — per-beat trajectory from closed-form inversion (same length as result.data)
  • :bayesian — MCMC posterior samples
  • :gradient — nothing (scalar optimisation, no distribution available)

Example

result = fit(lif, ibis; method=:analytical)
I_series = parameter_series(result, :I)   # Vector{Float64}, one value per beat
source