Models

BayesBiont fits the same model definitions Kinbiont uses. Pull them from Kinbiont.MODEL_REGISTRY and pass them to a BayesianModelSpec.

Closed-form NL models

The full list is in Kinbiont.MODEL_REGISTRY. The ones most commonly used:

Registry keyFunctionParameters
"NL_Gompertz"N_max * exp(-exp(-r * (t - λ)))N_max, growth_rate, lag
"NL_logistic"K / (1 + (K/N_0 - 1) * exp(-r * t))N_max, growth_rate, lag (Kinbiont labels — see note below)
"NL_exponential"N_0 * exp(r * t)N_0, growth_rate
"NL_Richards"Richards generalised logistic4 params
"NL_Bertalanffy"Von Bertalanffy4 params
"NL_Morgan"Morgan-Mercer-Flodin4 params
"NL_Weibull"Weibull4 params

Naming caveat for NL_logistic

Kinbiont labels the three positional parameters of NL_logistic as [N_max, growth_rate, lag], but the underlying function K / (1 + (K/N_0 - 1) * exp(-r * t)) is actually parameterised as [K, N_0, r]. BayesBiont passes the names through verbatim; r.growth_rate therefore returns posterior samples of N_0 (the initial-population parameter), and r.lag returns the rate. This is upstream and tracked in Kinbiont; until it's fixed, treat the positional semantics, not the label, as authoritative for NL_logistic.

ODE models

ODE models from Kinbiont's MODEL_REGISTRY (e.g. "aHPM", "HPM", "HPM_3_death", "logistic", "gompertz", "baranyi_richards") work with bayesfit too. The ODE is integrated inside the Turing model at each NUTS step.

spec = BayesianModelSpec([MODEL_REGISTRY["aHPM"]])
opts = BayesFitOptions(n_chains=2, n_warmup=500, n_samples=500)
post = bayesfit(data, spec, opts)

Default observation convention for multi-state ODE models: the predicted OD/cell concentration at each timepoint is the sum of all state variables (matches Kinbiont's sum_fin convention). Initial condition is u0 = [y[1], 0, ..., 0] — all observed mass placed in the first state, others at zero.

ODE + ReverseDiff (optional speedup)

For hierarchical fits with many curves, the ForwardDiff backend can become expensive (it scales as the square of the parameter count). ReverseDiff with a compiled tape is roughly constant per-parameter:

using SciMLSensitivity        # required for ODE + reverse-mode AD
opts = BayesFitOptions(adbackend=:reversediff, n_chains=2, n_warmup=500, n_samples=500)
post = bayesfit(data, spec, opts)

Measured: 6-well hierarchical aHPM took ~21 min with ReverseDiff vs ~40+ min with ForwardDiff on the same data.

Likelihoods

Set with BayesFitOptions(likelihood=...):

ValueWhat it isWhen to use
:lognormal (default)Multiplicative log-normal noise on OD. Equivalent to constant relative error.OD data; matches Kinbiont's RE loss semantically.
:normalAdditive Normal noise.Linear-scale data; when noise is independent of magnitude.
:proportionalHeteroscedastic Normal with σ_i = σ · pred_i.Pure relative-error noise model; behaves like :lognormal but on raw scale.

The :lognormal default requires strictly positive data. If your input has zeros or negatives (e.g. blank-subtraction artefacts), either preprocess with Kinbiont's correct_negatives=true or set likelihood=:normal.

Custom models

Any Kinbiont.NLModel or Kinbiont.ODEModel you build yourself works:

my_curve(p, t) = p[1] .* exp.(p[2] .* t .- p[3] .* t.^2)
my_model = NLModel(
    "my_quadratic_exp",
    my_curve,
    ["scale", "rate", "saturation"],
    nothing,
)
spec = BayesianModelSpec([my_model])
post = bayesfit(data, spec)    # falls back to empirical priors from model.guess (or prior medians if no guess)

You'll typically want to supply explicit priors for custom models — see Priors.