Skip to content

Calibrator API

Calibrator

A Facade for running parameter calibration from a defined CalibrationProblem.

Source code in commol/api/calibrator.py
class Calibrator:
    """
    A Facade for running parameter calibration from a defined CalibrationProblem.
    """

    def __init__(
        self,
        simulation: Simulation,
        problem: CalibrationProblem,
    ):
        """
        Initializes the calibration from a Simulation and CalibrationProblem.

        Parameters
        ----------
        simulation : Simulation
            A fully initialized Simulation object with the model to calibrate.
        problem : CalibrationProblem
            A fully constructed and validated calibration problem definition.
        """
        logger.info(
            f"Initializing Calibration for model: '{simulation.model_definition.name}'"
        )
        self.simulation: Simulation = simulation
        self.problem: CalibrationProblem = problem
        self._engine: "DifferenceEquationsProtocol" = simulation.engine

        logger.info(
            (
                f"Calibration initialized with {len(problem.parameters)} parameters "
                f"and {len(problem.observed_data)} observed data points."
            )
        )

    def run(self) -> CalibrationResult:
        """
        Runs the calibration optimization.

        Returns
        -------
        CalibrationResult
            Object containing the optimized parameter values, final loss,
            convergence status, and other optimization statistics.

        Raises
        ------
        ImportError
            If Rust extension is not available.
        ValueError
            If calibration problem setup is invalid.
        RuntimeError
            If optimization fails.
        """
        logger.info(
            (
                f"Starting calibration with "
                f"{self.problem.optimization_config.algorithm.value} algorithm and "
                f"{self.problem.loss_config.function.value} loss function."
            )
        )

        # Convert observed data to Rust types
        rust_observed_data = [
            rust_calibration.ObservedDataPoint(
                step=point.step,
                compartment=point.compartment,
                value=point.value,
                weight=point.weight,
            )
            for point in self.problem.observed_data
        ]

        # Convert parameters to Rust types
        rust_parameters = [
            rust_calibration.CalibrationParameter(
                id=param.id,
                min_bound=param.min_bound,
                max_bound=param.max_bound,
                initial_guess=param.initial_guess,
            )
            for param in self.problem.parameters
        ]

        # Convert loss config to Rust type
        rust_loss_config = self._build_loss_config()

        # Convert optimization config to Rust type
        rust_optimization_config = self._build_optimization_config()

        logger.info("Converted problem definition to Rust types.")
        logger.info("Running optimization...")

        # Call the Rust calibrate function
        rust_result = rust_calibration.calibrate(
            self._engine,
            rust_observed_data,
            rust_parameters,
            rust_loss_config,
            rust_optimization_config,
        )

        # Convert result back to Python CalibrationResult
        result = CalibrationResult(
            best_parameters=rust_result.best_parameters,
            parameter_names=rust_result.parameter_names,
            best_parameters_list=rust_result.best_parameters_list,
            final_loss=rust_result.final_loss,
            iterations=rust_result.iterations,
            converged=rust_result.converged,
            termination_reason=rust_result.termination_reason,
        )

        logger.info(
            (
                f"Calibration finished after {result.iterations} iterations. "
                f"Final loss: {result.final_loss:.6f}"
            )
        )

        return result

    def _build_loss_config(self) -> "LossConfigProtocol":
        """Convert Python LossConfig to Rust LossConfig."""
        loss_func = self.problem.loss_config.function

        if loss_func == LossFunction.SSE:
            return rust_calibration.LossConfig.sum_squared_error()
        elif loss_func == LossFunction.RMSE:
            return rust_calibration.LossConfig.root_mean_squared_error()
        elif loss_func == LossFunction.MAE:
            return rust_calibration.LossConfig.mean_absolute_error()
        elif loss_func == LossFunction.WEIGHTED_SSE:
            return rust_calibration.LossConfig.weighted_sse()
        else:
            raise ValueError(f"Unsupported loss function: {loss_func}.")

    def _build_optimization_config(self) -> "OptimizationConfigProtocol":
        """Convert Python OptimizationConfig to Rust OptimizationConfig."""
        opt_config = self.problem.optimization_config

        if opt_config.algorithm == OptimizationAlgorithm.NELDER_MEAD:
            if not isinstance(opt_config.config, NelderMeadConfig):
                raise ValueError(
                    (
                        f"Expected NelderMeadConfig for Nelder-Mead algorithm, "
                        f"got {type(opt_config.config).__name__}"
                    )
                )

            nm_config = rust_calibration.NelderMeadConfig(
                max_iterations=opt_config.config.max_iterations,
                sd_tolerance=opt_config.config.sd_tolerance,
                alpha=opt_config.config.alpha,
                gamma=opt_config.config.gamma,
                rho=opt_config.config.rho,
                sigma=opt_config.config.sigma,
                verbose=opt_config.config.verbose,
                header_interval=opt_config.config.header_interval,
            )
            return rust_calibration.OptimizationConfig.nelder_mead(nm_config)

        elif opt_config.algorithm == OptimizationAlgorithm.PARTICLE_SWARM:
            if not isinstance(opt_config.config, ParticleSwarmConfig):
                raise ValueError(
                    (
                        f"Expected ParticleSwarmConfig for Particle Swarm algorithm, "
                        f"got {type(opt_config.config).__name__}"
                    )
                )

            ps_config = rust_calibration.ParticleSwarmConfig(
                num_particles=opt_config.config.num_particles,
                max_iterations=opt_config.config.max_iterations,
                target_cost=opt_config.config.target_cost,
                inertia_factor=opt_config.config.inertia_factor,
                cognitive_factor=opt_config.config.cognitive_factor,
                social_factor=opt_config.config.social_factor,
                verbose=opt_config.config.verbose,
                header_interval=opt_config.config.header_interval,
            )
            return rust_calibration.OptimizationConfig.particle_swarm(ps_config)

        else:
            raise ValueError(
                f"Unsupported optimization algorithm: {opt_config.algorithm}"
            )

    @property
    def num_parameters(self) -> int:
        """Number of parameters being calibrated."""
        return len(self.problem.parameters)

    @property
    def num_observations(self) -> int:
        """Number of observed data points."""
        return len(self.problem.observed_data)

    @property
    def parameter_names(self) -> list[str]:
        """Names of parameters being calibrated."""
        return [param.id for param in self.problem.parameters]

Attributes

num_parameters property

num_parameters: int

Number of parameters being calibrated.

num_observations property

num_observations: int

Number of observed data points.

parameter_names property

parameter_names: list[str]

Names of parameters being calibrated.

Functions

__init__

__init__(simulation: Simulation, problem: CalibrationProblem)

Initializes the calibration from a Simulation and CalibrationProblem.

Parameters:

Name Type Description Default
simulation Simulation

A fully initialized Simulation object with the model to calibrate.

required
problem CalibrationProblem

A fully constructed and validated calibration problem definition.

required
Source code in commol/api/calibrator.py
def __init__(
    self,
    simulation: Simulation,
    problem: CalibrationProblem,
):
    """
    Initializes the calibration from a Simulation and CalibrationProblem.

    Parameters
    ----------
    simulation : Simulation
        A fully initialized Simulation object with the model to calibrate.
    problem : CalibrationProblem
        A fully constructed and validated calibration problem definition.
    """
    logger.info(
        f"Initializing Calibration for model: '{simulation.model_definition.name}'"
    )
    self.simulation: Simulation = simulation
    self.problem: CalibrationProblem = problem
    self._engine: "DifferenceEquationsProtocol" = simulation.engine

    logger.info(
        (
            f"Calibration initialized with {len(problem.parameters)} parameters "
            f"and {len(problem.observed_data)} observed data points."
        )
    )

run

Runs the calibration optimization.

Returns:

Type Description
CalibrationResult

Object containing the optimized parameter values, final loss, convergence status, and other optimization statistics.

Raises:

Type Description
ImportError

If Rust extension is not available.

ValueError

If calibration problem setup is invalid.

RuntimeError

If optimization fails.

Source code in commol/api/calibrator.py
def run(self) -> CalibrationResult:
    """
    Runs the calibration optimization.

    Returns
    -------
    CalibrationResult
        Object containing the optimized parameter values, final loss,
        convergence status, and other optimization statistics.

    Raises
    ------
    ImportError
        If Rust extension is not available.
    ValueError
        If calibration problem setup is invalid.
    RuntimeError
        If optimization fails.
    """
    logger.info(
        (
            f"Starting calibration with "
            f"{self.problem.optimization_config.algorithm.value} algorithm and "
            f"{self.problem.loss_config.function.value} loss function."
        )
    )

    # Convert observed data to Rust types
    rust_observed_data = [
        rust_calibration.ObservedDataPoint(
            step=point.step,
            compartment=point.compartment,
            value=point.value,
            weight=point.weight,
        )
        for point in self.problem.observed_data
    ]

    # Convert parameters to Rust types
    rust_parameters = [
        rust_calibration.CalibrationParameter(
            id=param.id,
            min_bound=param.min_bound,
            max_bound=param.max_bound,
            initial_guess=param.initial_guess,
        )
        for param in self.problem.parameters
    ]

    # Convert loss config to Rust type
    rust_loss_config = self._build_loss_config()

    # Convert optimization config to Rust type
    rust_optimization_config = self._build_optimization_config()

    logger.info("Converted problem definition to Rust types.")
    logger.info("Running optimization...")

    # Call the Rust calibrate function
    rust_result = rust_calibration.calibrate(
        self._engine,
        rust_observed_data,
        rust_parameters,
        rust_loss_config,
        rust_optimization_config,
    )

    # Convert result back to Python CalibrationResult
    result = CalibrationResult(
        best_parameters=rust_result.best_parameters,
        parameter_names=rust_result.parameter_names,
        best_parameters_list=rust_result.best_parameters_list,
        final_loss=rust_result.final_loss,
        iterations=rust_result.iterations,
        converged=rust_result.converged,
        termination_reason=rust_result.termination_reason,
    )

    logger.info(
        (
            f"Calibration finished after {result.iterations} iterations. "
            f"Final loss: {result.final_loss:.6f}"
        )
    )

    return result

CalibrationProblem

CalibrationProblem

Bases: BaseModel

Defines a complete calibration problem.

This class encapsulates all the information needed to calibrate model parameters against observed data. It provides validation of the calibration setup but delegates the actual optimization to the Rust backend.

Attributes:

Name Type Description
observed_data list[ObservedDataPoint]

List of observed data points to fit against

parameters list[CalibrationParameter]

List of parameters to calibrate with their bounds

loss_config LossConfig

Configuration for the loss function

optimization_config OptimizationConfig

Configuration for the optimization algorithm

Functions

validate_unique_parameter_ids
validate_unique_parameter_ids() -> Self

Ensure parameter IDs are unique.

CalibrationResult

CalibrationResult

Bases: BaseModel

Result of a calibration run.

This is a simple data class that holds the results returned from the Rust calibration function.

Attributes:

Name Type Description
best_parameters dict[str, float]

Dictionary mapping parameter IDs to their calibrated values

parameter_names list[str]

Ordered list of parameter names

best_parameters_list list[float]

Ordered list of parameter values (matches parameter_names order)

final_loss float

Final loss value achieved

iterations int

Number of iterations performed

converged bool

Whether the optimization converged

termination_reason str

Explanation of why optimization terminated

Functions

__str__
__str__() -> str

String representation of calibration result.

CalibrationParameter

CalibrationParameter

Bases: BaseModel

Defines a parameter to be calibrated with its bounds.

Attributes:

Name Type Description
id str

Parameter identifier (must match a model parameter ID)

min_bound float

Minimum allowed value for this parameter

max_bound float

Maximum allowed value for this parameter

initial_guess float | None

Optional starting value for optimization (if None, midpoint is used)

Functions

validate_bounds
validate_bounds() -> Self

Validate that max_bound > min_bound and initial_guess is within bounds.

ObservedDataPoint

ObservedDataPoint

Bases: BaseModel

Represents a single observed data point for calibration.

Attributes:

Name Type Description
step int

Time step of the observation

compartment str

Name of the compartment being observed

value float

Observed value

weight float

Weight for this observation in the loss function (default: 1.0)

LossConfig

LossConfig

Bases: BaseModel

Configuration for the loss function used in calibration.

Attributes:

Name Type Description
function LossFunction

The loss function to use for measuring fit quality

OptimizationConfig

OptimizationConfig

Bases: BaseModel

Configuration for the optimization algorithm.

Attributes:

Name Type Description
algorithm Literal[nelder_mead, particle_swarm]

The optimization algorithm to use

config NelderMeadConfig | ParticleSwarmConfig

Configuration for the selected algorithm

Functions

validate_algorithm_config
validate_algorithm_config() -> Self

Ensure the config type matches the selected algorithm.

NelderMeadConfig

NelderMeadConfig

Bases: BaseModel

Configuration for the Nelder-Mead optimization algorithm.

The Nelder-Mead method is a simplex-based derivative-free optimization algorithm, suitable for problems where gradients are not available.

Attributes:

Name Type Description
max_iterations int

Maximum number of iterations (default: 1000)

sd_tolerance float

Convergence tolerance for standard deviation (default: 1e-6)

alpha float | None

Reflection coefficient (default: None, uses argmin's default)

gamma float | None

Expansion coefficient (default: None, uses argmin's default)

rho float | None

Contraction coefficient (default: None, uses argmin's default)

sigma float | None

Shrink coefficient (default: None, uses argmin's default)

verbose bool

Enable verbose output during optimization (default: False)

header_interval int

Number of iterations between table header repeats in verbose output (default: 100)

ParticleSwarmConfig

ParticleSwarmConfig

Bases: BaseModel

Configuration for the Particle Swarm Optimization algorithm.

Particle Swarm Optimization (PSO) is a population-based metaheuristic inspired by social behavior of bird flocking or fish schooling.

Attributes:

Name Type Description
num_particles int

Number of particles in the swarm (default: 40)

max_iterations int

Maximum number of iterations (default: 1000)

target_cost float | None

Target cost for early stopping (optional)

inertia_factor float | None

Inertia weight applied to velocity (default: None, uses argmin's default)

cognitive_factor float | None

Attraction to personal best (default: None, uses argmin's default)

social_factor float | None

Attraction to swarm best (default: None, uses argmin's default)

verbose bool

Enable verbose output during optimization (default: False)

header_interval int

Number of iterations between table header repeats in verbose output (default: 100)

Enumerations

LossFunction

LossFunction

Bases: str, Enum

Available loss functions for calibration.

Attributes:

Name Type Description
SSE str

Sum of Squared Errors

RMSE str

Root Mean Squared Error

MAE str

Mean Absolute Error

WEIGHTED_SSE str

Weighted Sum of Squared Errors

Attributes

SSE class-attribute instance-attribute
SSE = 'sum_squared_error'
RMSE class-attribute instance-attribute
RMSE = 'root_mean_squared_error'
MAE class-attribute instance-attribute
MAE = 'mean_absolute_error'
WEIGHTED_SSE class-attribute instance-attribute
WEIGHTED_SSE = 'weighted_sse'

OptimizationAlgorithm

OptimizationAlgorithm

Bases: str, Enum

Available optimization algorithms.

Attributes:

Name Type Description
NELDER_MEAD str

Nelder-Mead simplex algorithm

PARTICLE_SWARM str

Particle Swarm Optimization

Attributes

NELDER_MEAD class-attribute instance-attribute
NELDER_MEAD = 'nelder_mead'
PARTICLE_SWARM class-attribute instance-attribute
PARTICLE_SWARM = 'particle_swarm'