Curve models
Gompertz, Logistic, Baranyi, Richards, and any nonlinear model from Kinbiont's NL_MODEL_REGISTRY. User-defined models drop in as a function plus a guess heuristic.
BayesBiont.jl is the Bayesian companion to
Kinbiont.jl.
Where Kinbiont fits growth-curve models by maximum likelihood,
BayesBiont samples the full posterior with NUTS via
Turing.jl1,
supports hierarchical pooling across replicates with a
non-centered reparameterisation2,
and compares competing models with PSIS-LOO3.
What's in the box
Gompertz, Logistic, Baranyi, Richards, and any nonlinear model from Kinbiont's NL_MODEL_REGISTRY. User-defined models drop in as a function plus a guess heuristic.
aHPM and arbitrary multi-state ODEs from Kinbiont's MODEL_REGISTRY. Solved with OrdinaryDiffEq.jl; reverse-mode AD via SciMLSensitivity.jl for hierarchical fits.
:lognormal (default; matches Kinbiont's RE loss), :normal, :proportional. Defensive error on non-positive data under lognormal — no silent epsilon shift.
Curated DEFAULT_PRIORS for canonical models calibrated to plate-reader OD; empirical fallback via model.guess(data) for user-defined models. All overridable per parameter.
Non-centered reparameterisation across replicates by group2. Users see LogNormal priors going in and native-scale posteriors coming out; the log-space trick is internal.
PSIS-LOO and WAIC via PSIS.jl3. compare(r₁, r₂) returns ELPD differences with standard errors; Pareto-k diagnostics flag misspecification.
Why Bayesian
Maximum-likelihood fits give you a point estimate and a Hessian-derived standard error. The standard error is asymptotically valid under regularity conditions that growth-curve experiments routinely violate (sparse data, non-identifiability of the lag, weak constraints on the asymptote). Posterior credible intervals make no such assumptions and degrade gracefully as the data weakens — they widen to match what the data actually says.
With replicates of the same condition, neither "average then fit" nor "fit independently then average" is the right thing. Partial pooling estimates a population distribution over the per-well parameters and shares strength across replicates without forcing them to be identical. BayesBiont uses the non-centered reparameterisation2 to sidestep the funnel geometry that breaks centred hierarchical models under NUTS.
compare(result₁, result₂) returns the
expected log pointwise predictive density (ELPD)
difference under PSIS-LOO with its standard error,
plus per-observation Pareto-k diagnostics. The latter
flag observations the importance sampling can't
reweight reliably — a built-in misspecification check
that doesn't depend on noticing a bad fit by eye.
Kinbiont & BayesBiont
BayesBiont depends on Kinbiont and reuses its GrowthData
container and MODEL_REGISTRY. The model definitions are
the same; what differs is the inference and what you do with the
result.
Reach for Kinbiont when: you need throughput, you're screening, you're prototyping a new model, or uncertainty isn't the deliverable.
Reach for BayesBiont when: you have replicates, you want credible intervals you can publish, you're choosing between competing kinetic models, or the inference is the deliverable.
Example
Six wells from a plate reader, three replicates each for
wild-type and a mutant. Fit a hierarchical Gompertz model,
then ask the posterior whether the wild-type grows faster.
The same script can swap the model and call compare
to decide between candidates.
See the
Hierarchical pooling and
Model comparison
chapters of the documentation for the full walkthrough,
including how to interpret the Pareto-k diagnostic and how
to handle non-positive data under :lognormal.
using BayesBiont
# Six wells, three replicates per condition
data = GrowthData(curve_matrix, times, well_labels)
group = ["WT", "WT", "WT", "mut", "mut", "mut"]
# Hierarchical Bayesian fit on a non-linear Gompertz
spec = BayesianModelSpec([MODEL_REGISTRY["NL_Gompertz"]])
post = bayesfit(data, spec; group = group)
# Posterior contrast on the growth rate
Δ = contrast(post, "WT", "mut"; param = :growth_rate)
println("P(WT > mut) = ", mean(Δ .> 0))
println("Δ 95% CI = ", quantile(Δ, [0.025, 0.975]))
Installation
Both Kinbiont and BayesBiont are registered. Pkg resolves the rest of the dependency graph.
julia> ] add BayesBiont
Cite
@software{bayesbiont,
author = {Alvarenga, Edgar Zanella},
title = {BayesBiont.jl: Bayesian inference
for microbial growth curves},
publisher = {Fuzue Tech},
url = {https://github.com/fuzue/BayesBiont.jl},
year = {2026}
}
References