calibration Module#

Parameter calibration for compartmental models.

This module provides tools to fit model parameters to observed incidence or mortality data using scipy.optimize.minimize.

Classes#

class episia.models.calibration.ModelCalibrator(model_class, param_class, fixed_params, fit_params, loss='mse')[source]#

Bases: object

Fit a compartmental model to observed time-series data.

Supports simultaneous fitting to multiple compartments (e.g. infected + deaths for SEIRD).

Example:

from episia.models.calibration import ModelCalibrator
from episia.models.sir import SIRModel
from episia.models.parameters import SIRParameters

calibrator = ModelCalibrator(
    model_class=SIRModel,
    param_class=SIRParameters,
    fixed_params=dict(N=1_000_000, I0=1, t_span=(0, 60)),
    fit_params={
        "beta":  (0.05, 1.0),   # (lower_bound, upper_bound)
        "gamma": (0.01, 0.5),
    },
)

result = calibrator.fit(
    t_observed=days,
    observed={"I": infected_counts},
)
print(result)
Parameters:
__init__(model_class, param_class, fixed_params, fit_params, loss='mse')[source]#
Parameters:
  • model_class – CompartmentalModel subclass (SIRModel, SEIRModel…).

  • param_class – Matching parameters class.

  • fixed_params (Dict[str, Any]) – Parameters held constant during optimisation.

  • fit_params (Dict[str, Tuple[float, float]]) – Parameters to fit; values are (lower, upper) bounds.

  • loss (str) – Loss function: ‘mse’, ‘rmse’, ‘mae’, ‘poisson’.

fit(t_observed, observed, method='L-BFGS-B', options=None)[source]#

Fit model parameters to observed data.

Parameters:
  • t_observed (ndarray) – Time points of observations (days).

  • observed (Dict[str, ndarray]) – Dict mapping compartment name → array of observations. E.g. {‘I’: infected_array} or {‘I’: …, ‘D’: …}.

  • method (str) – scipy.optimize.minimize method (default L-BFGS-B).

  • options (Dict | None) – Extra options for scipy.optimize.minimize.

Returns:

CalibrationResult with best-fit parameters and diagnostics.

Return type:

CalibrationResult

fit_and_apply(t_observed, observed, **fit_kwargs)[source]#

Fit and immediately run the calibrated model.

Returns:

(CalibrationResult, ModelResult) tuple.

Parameters:
Return type:

Tuple[CalibrationResult, Any]

class episia.models.calibration.CalibrationResult(parameters, loss, success, message, n_iterations, residuals=None)[source]#

Bases: object

Result of a parameter calibration run.

Parameters:
parameters#

Best-fit parameter dict.

Type:

Dict[str, float]

loss#

Final loss value.

Type:

float

success#

True if optimiser converged.

Type:

bool

message#

Optimiser message.

Type:

str

n_iterations#

Number of function evaluations.

Type:

int

residuals#

Observed − predicted array.

Type:

numpy.ndarray | None

__repr__()[source]#

Return repr(self).

Return type:

str

loss: float#
message: str#
n_iterations: int#
parameters: Dict[str, float]#
residuals: ndarray | None = None#
success: bool#

Examples#

Fitting an SIR model to incidence data:

import numpy as np
from episia.models import SIRModel
from episia.models.parameters import SIRParameters
from episia.models.calibration import ModelCalibrator

# Observed weekly cases
days = np.arange(0, 140, 7)
observed_cases = np.array([10, 25, 60, 120, 200, 280, 350, 380, 370, 300, 210, 130, 70, 30, 15, 8, 4, 2, 1, 0])

calibrator = ModelCalibrator(
    model_class=SIRModel,
    param_class=SIRParameters,
    fixed_params={
        'N': 1_000_000,
        'I0': observed_cases[0],
        't_span': (0, 140)
    },
    fit_params={
        'beta': (0.1, 1.0),    # Search bounds
        'gamma': (0.05, 0.5)
    },
    loss='rmse'
)

result = calibrator.fit(
    t_observed=days,
    observed={'I': observed_cases}
)

print(f"Best fit: β={result.parameters['beta']:.3f}, γ={result.parameters['gamma']:.3f}")
print(f"R₀={result.parameters['beta']/result.parameters['gamma']:.2f}")
print(f"Loss: {result.loss:.2f}")

# Run calibrated model
cal_result, model_result = calibrator.fit_and_apply(days, {'I': observed_cases})
model_result.plot().show()

Fitting multiple compartments (SEIRD):

# Fit to both cases and deaths
calibrator = ModelCalibrator(
    model_class=SEIRDModel,
    param_class=SEIRDParameters,
    fixed_params={'N': 1e6, 'I0': 10, 'E0': 50, 't_span': (0, 200)},
    fit_params={
        'beta': (0.1, 1.0),
        'mu': (0.001, 0.1)    # Mortality rate
    }
)

result = calibrator.fit(
    t_observed=days,
    observed={
        'I': observed_cases,
        'D': observed_deaths
    }
)