# Example 2 — Custom Python growth model Demonstrates defining a growth model as a Python function and fitting it via `NLModel`. Full script: `examples/scripts/03_custom_model.py` ## Define a Gompertz model ```python import numpy as np from pykinbiont import NLModel def gompertz(p, t): """ Modified Gompertz function. Parameters ---------- p : array-like, shape (3,) [A, mu, lam] — carrying capacity, max growth rate, lag time t : float or ndarray Time point(s) """ A, mu, lam = p[0], p[1], p[2] return A * np.exp(-np.exp(mu * np.e / A * (lam - t) + 1)) custom_gompertz = NLModel( name="gompertz", func=gompertz, param_names=["A", "mu", "lam"], ) ``` ## Synthetic data with a visible lag phase ```python import pykinbiont from pykinbiont import GrowthData pykinbiont.configure("/path/to/KinBiont.jl") def true_gompertz(t, A=1.1, mu=0.5, lam=2.0): return A * np.exp(-np.exp(mu * np.e / A * (lam - t) + 1)) rng = np.random.default_rng(42) times = np.linspace(0, 18, 60) curves = np.stack([ np.maximum( true_gompertz(times, A=1.0 + 0.1 * i, mu=0.4 + 0.05 * i, lam=2.0 + 0.3 * i) + rng.normal(0, 0.007, len(times)), 1e-4, ) for i in range(3) ]) data = GrowthData(curves=curves, times=times, labels=["G1", "G2", "G3"]) ``` ## Fit ```python from pykinbiont import ModelSpec, FitOptions, fit spec = ModelSpec( models=[custom_gompertz], params=[[1.0, 0.5, 2.0]], lower=[[0.0, 0.0, 0.0]], upper=[[3.0, 3.0, 10.0]], ) opts = FitOptions(multistart=True, n_restart=30) results = fit(data, spec, opts) for r in results: p = dict(zip(r.param_names, r.best_params)) print(f"{r.label}: A={p['A']:.3f} mu={p['mu']:.3f} lam={p['lam']:.3f}" f" AIC={r.best_aic:.2f}") ``` ## Compare custom vs built-in ```python from pykinbiont import MODEL_REGISTRY logistic_model = MODEL_REGISTRY["NL_logistic"] spec_both = ModelSpec( models=[custom_gompertz, logistic_model], params=[[1.0, 0.5, 2.0], [1.2, 0.5, 0.01]], lower=[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], upper=[[3.0, 3.0, 10.0], [5.0, 5.0, 1.0]], ) results_both = fit(data, spec_both, opts) df = results_both.to_dataframe() print(df[["label", "best_model", "aic", "loss"]]) ``` ## Custom ODE model For models defined as differential equations, use `ODEModel`: ```python from pykinbiont import ODEModel def logistic_ode(du, u, p, t): """Logistic growth ODE: dN/dt = mu*N*(1 - N/K)""" mu, K = p[0], p[1] du[0] = mu * u[0] * (1.0 - u[0] / K) ode_model = ODEModel( name="logistic_ode", func=logistic_ode, param_names=["mu", "K"], n_eq=1, ) spec_ode = ModelSpec( models=[ode_model], params=[[0.5, 1.2]], lower=[[0.0, 0.1]], upper=[[5.0, 5.0]], ) results_ode = fit(data, spec_ode, opts) ```