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) -> NamedTuplesimulate 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:
| Field | Type | Description |
|---|---|---|
model | AbstractHRVModel | The model that was fitted |
method | Symbol | Fitting method: :analytical, :gradient, or :bayesian |
params | NamedTuple | Point estimates (MAP for Bayesian, minimizer for gradient, mean for analytical) |
posterior | Dict or nothing | Posterior samples (Bayesian) or per-beat series (analytical); nothing for gradient |
diagnostics | Dict{String,Any} | Convergence info, iterations, loss, R-hat values, etc. |
data | Vector{Float64} | Original IBI data used for fitting (milliseconds) |
julia> using HeartRateLab
julia> vdp = VanDerPol()
VanDerPol()
julia> typeof(vdp) <: AbstractHRVModel
trueFitting 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) # nothingAPI Reference
HeartRateLab.Models.AbstractHRVModel — Type
AbstractHRVModelAbstract 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)
HeartRateLab.Models.ModelFitResult — Type
ModelFitResultResult of fitting an HRV model to data. Stores both point estimates and uncertainty information.
Fields
model::AbstractHRVModel: The model type that was fittedmethod::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 otherwisediagnostics::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 dataHeartRateLab.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 asresult.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