epimodels.validation package

Submodules

epimodels.validation.specs module

Specification classes for parameters, state variables, and constraints.

class epimodels.validation.specs.DomainType(*values)[source]

Bases: Enum

Type of numeric domain for parameters and variables.

CATEGORICAL = 'categorical'
CONTINUOUS = 'continuous'
DISCRETE = 'discrete'
INTEGER = 'integer'
class epimodels.validation.specs.ModelConstraint(expression: str, description: str = '', severity: str = 'error', name: str | None = None)[source]

Bases: object

Cross-parameter or model-level constraint.

expression

Constraint expression (Python expression or SymPy-parseable) Can reference parameter names directly. Examples: “beta > gamma”, “p + q <= 1”, “R0 > 1”

Type:

str

description

Human-readable description of the constraint

Type:

str

severity

“error” (raises exception) or “warning” (logs warning)

Type:

str

name

Optional name for the constraint

Type:

str | None

Example

>>> constraint = ModelConstraint(
...     expression="beta / gamma > 1",
...     description="R0 > 1 required for epidemic",
...     severity="warning"
... )
description: str = ''
expression: str
name: str | None = None
severity: str = 'error'
class epimodels.validation.specs.ParameterSpec(name: str, symbol: str, description: str = '', domain_type: DomainType = DomainType.CONTINUOUS, bounds: tuple[float | None, float | None] | None=None, dtype: type = <class 'float'>, default: Any | None = None, required: bool = True, constraints: list[str] = <factory>, units: str | None = None, typical_range: tuple[float, float] | None=None)[source]

Bases: object

Rich specification for a model parameter.

name

Parameter identifier (used in code)

Type:

str

symbol

LaTeX representation for display

Type:

str

description

Human-readable description

Type:

str

domain_type

Type of numeric domain

Type:

epimodels.validation.specs.DomainType

bounds

Optional (min, max) tuple. Use None for unbounded. Example: (0, None) means positive, (0, 1) means probability

Type:

tuple[float | None, float | None] | None

dtype

Expected Python type (float, int, list, etc.)

Type:

type

default

Default value if parameter is optional

Type:

Any | None

required

Whether parameter must be provided

Type:

bool

constraints

List of constraint expressions (e.g., “value > 0”, “value < other_param”)

Type:

list[str]

units

Physical units (e.g., “1/day”, “individuals”)

Type:

str | None

typical_range

Typical range for documentation (min, max)

Type:

tuple[float, float] | None

Example

>>> spec = ParameterSpec(
...     name="beta",
...     symbol=r"$\beta$",
...     description="Transmission rate",
...     bounds=(0, None),
...     constraints=["value > 0"]
... )
bounds: tuple[float | None, float | None] | None = None
constraints: list[str]
default: Any | None = None
description: str = ''
domain_type: DomainType = 'continuous'
dtype

alias of float

name: str
required: bool = True
symbol: str
typical_range: tuple[float, float] | None = None
units: str | None = None
class epimodels.validation.specs.VariableSpec(name: str, symbol: str, description: str = '', bounds: tuple[float | None, float | None] | None=None, non_negative: bool = True, constraints: list[str] = <factory>, units: str | None = None)[source]

Bases: object

Specification for a state variable.

name

Variable identifier (used in code)

Type:

str

symbol

LaTeX representation

Type:

str

description

Human-readable description

Type:

str

bounds

Optional (min, max) tuple

Type:

tuple[float | None, float | None] | None

non_negative

Whether variable must be >= 0

Type:

bool

constraints

List of constraint expressions

Type:

list[str]

units

Physical units

Type:

str | None

Example

>>> spec = VariableSpec(
...     name="S",
...     symbol="S",
...     description="Susceptible individuals",
...     non_negative=True
... )
bounds: tuple[float | None, float | None] | None = None
constraints: list[str]
description: str = ''
name: str
non_negative: bool = True
symbol: str
units: str | None = None

epimodels.validation.symbolic module

Symbolic model representation and analysis using SymPy.

Provides symbolic computation for: - Parameter and variable symbols with assumptions - R0 (basic reproduction number) calculation - Equilibrium point analysis - Stability analysis

class epimodels.validation.symbolic.SymbolicModel[source]

Bases: object

Symbolic representation of an epidemiological model.

Enables symbolic analysis of model properties: - Compute R0 using next-generation matrix method - Find equilibrium points - Analyze stability

Example

>>> model = SymbolicModel()
>>> model.add_parameter("beta", positive=True)
>>> model.add_parameter("gamma", positive=True)
>>> model.add_variable("S", positive=True)
>>> model.add_variable("I", positive=True)
>>> model.add_variable("R", positive=True)
>>> model.set_total_population("N")
>>> model.define_ode("S", "-beta*S*I/N")
>>> model.define_ode("I", "beta*S*I/N - gamma*I")
>>> model.define_ode("R", "gamma*I")
>>> R0 = model.compute_R0_next_generation()
add_parameter(name: str, positive: bool = False, negative: bool = False, real: bool = True, **assumptions) None[source]

Add a parameter as a symbolic variable.

Parameters:
  • name – Parameter name

  • positive – Whether parameter is positive

  • negative – Whether parameter is negative

  • real – Whether parameter is real (vs complex)

  • **assumptions – Additional SymPy assumptions

Returns:

SymPy Symbol object

add_variable(name: str, positive: bool = False, negative: bool = False, real: bool = True, **assumptions) None[source]

Add a state variable as a symbolic variable.

Parameters:
  • name – Variable name

  • positive – Whether variable is positive

  • negative – Whether variable is negative

  • real – Whether variable is real

  • **assumptions – Additional SymPy assumptions

Returns:

SymPy Symbol object

analyze_stability_full(equilibrium: dict[str, float | None], params: dict[str, float] | None = None, tolerance: float = 1e-10) dict[str, Any][source]

Perform full stability analysis at an equilibrium point.

Computes and analyzes: - Jacobian matrix - Eigenvalues - Stability classification - Bifurcation indicators - Detailed classification (node, focus, saddle, etc.)

Parameters:
  • equilibrium – Equilibrium point (variable names to values)

  • params – Parameter values for numeric evaluation

  • tolerance – Tolerance for eigenvalue zero detection

Returns:

  • ‘jacobian’: Jacobian matrix (symbolic or numeric)

  • ’eigenvalues’: List of eigenvalues

  • ’eigenvalues_numeric’: Numeric eigenvalues (if params provided)

  • ’stability’: ‘stable’, ‘unstable’, ‘neutral’, or ‘saddle’

  • ’classification’: Detailed type (e.g., ‘stable_node’, ‘unstable_focus’)

  • ’max_real_part’: Maximum real part of eigenvalues

  • ’min_real_part’: Minimum real part of eigenvalues

  • ’has_complex’: Boolean indicating complex eigenvalues

  • ’near_bifurcation’: Boolean indicating proximity to bifurcation

  • ’bifurcation_type’: Type of bifurcation if detected

Return type:

Dictionary with comprehensive stability information

Example

>>> result = model.analyze_stability_full(dfe, params={'beta': 0.3, 'gamma': 0.1})
>>> result['stability']
'unstable'
>>> result['classification']
'unstable_node'
>>> result['max_real_part']
0.2
check_stability_at_dfe(R0: Any) str[source]

Check stability of disease-free equilibrium based on R0.

Parameters:

R0 – Basic reproduction number (symbolic or numeric)

Returns:

“stable”, “unstable”, or “neutral”

Return type:

Stability classification

compute_R0_next_generation() Any[source]

Compute basic reproduction number R0 using next-generation matrix method.

This method: 1. Identifies infected compartments 2. Computes new infections matrix F and transitions matrix V 3. Computes R0 as spectral radius of F*V^(-1) evaluated at DFE

The next-generation matrix method: - For dI/dt = F - V (F = new infections, V = transitions out) - Compute Jacobians: dF = ∂F/∂X, dV = ∂V/∂X (where X = infected compartments) - Evaluate at DFE (disease-free equilibrium) - R0 = spectral_radius(dF * dV^(-1))

Returns:

Symbolic expression for R0 (evaluated at DFE)

Note

For single infected compartment: R0 = (∂F/∂I)|DFE / (∂V/∂I)|DFE

compute_eigenvalues(jacobian: MutableDenseMatrix, numeric: bool = False, params: dict[str, float] | None = None) list[Any][source]

Compute eigenvalues of the Jacobian matrix.

Eigenvalues determine local stability: - All Re(λ) < 0: stable equilibrium - Any Re(λ) > 0: unstable equilibrium - Re(λ) = 0: neutral or bifurcation point

Parameters:
  • jacobian – Jacobian matrix (from compute_jacobian)

  • numeric – Force numeric evaluation of eigenvalues

  • params – Parameter values for numeric evaluation

Returns:

List of eigenvalues (symbolic or numeric complex numbers)

Example

>>> J = model.compute_jacobian(dfe, substitute_values=True)
>>> eigenvalues = model.compute_eigenvalues(J)
>>> eigenvalues
[0, -gamma, -beta + gamma]
compute_elasticity_indices(params: dict[str, float], output_vars: list[str] | None = None) dict[str, dict[str, float]][source]

Compute elasticity indices (normalized sensitivity).

E_ij = (∂y_i/y_i) / (∂p_j/p_j) = (p_j/y_i) * (∂y_i/∂p_j)

Interpreted as: percentage change in output per 1% change in parameter.

Parameters:
  • params – Parameter values (numeric)

  • output_vars – Variables to analyze

Returns:

{output_var: {param: elasticity}}

Return type:

Nested dictionary

Example

>>> E = model.compute_elasticity_indices({'beta': 0.3, 'gamma': 0.1, 'N': 1000})
>>> E['I']['beta']
1.0  # 1% increase in beta → 1% increase in I*
compute_jacobian(equilibrium: dict[str, float | None], substitute_values: bool = False) MutableDenseMatrix[source]

Compute Jacobian matrix at an equilibrium point.

The Jacobian matrix J has entries J_ij = ∂f_i/∂x_j where: - f_i is the ODE for variable i - x_j is the j-th state variable

Parameters:
  • equilibrium – Dictionary mapping variable names to values. Can be symbolic or numeric values.

  • substitute_values – If True, substitute equilibrium values into Jacobian. If False, keep symbolic form.

Returns:

SymPy Matrix representing the Jacobian

Example

>>> dfe = model.find_disease_free_equilibrium()
>>> J = model.compute_jacobian(dfe)
>>> J
Matrix([
    [0, -beta, 0],
    [0, beta - gamma, 0],
    [0, gamma, 0]
])
compute_sensitivity_matrix(params: dict[str, float] | None = None, output_vars: list[str] | None = None, param_list: list[str] | None = None, perturbation: float = 0.01) dict[str, dict[str, Any]][source]

Compute sensitivity of equilibrium values to parameters.

S_ij = ∂y_i/∂p_j where y is equilibrium value and p is parameter. Uses numerical perturbation when symbolic computation is not feasible.

Parameters:
  • params – Parameter values dict (required for numeric computation)

  • output_vars – Variables to analyze (default: all)

  • param_list – Parameters to analyze (default: all from params)

  • perturbation – Relative perturbation size for numerical derivatives (default: 1%)

Returns:

{output_var: {param: sensitivity_value}}

Return type:

Nested dictionary

Example

>>> S = model.compute_sensitivity_matrix({'beta': 0.3, 'gamma': 0.1, 'N': 1000})
>>> S['I']['beta']
100.0  # ∂I*/∂beta at equilibrium
define_difference_equation(variable: str, rhs: str) None[source]

Define a difference equation for a discrete-time model.

Parameters:
  • variable – Variable name

  • rhs – Right-hand side expression

Example

>>> model.define_difference_equation("S", "S - beta*S*I/N + gamma*I")
define_ode(variable: str, rhs: str) None[source]

Define an ODE for a state variable.

Parameters:
  • variable – Variable name (e.g., “S”, “I”, “R”)

  • rhs – Right-hand side expression as string (e.g., “-beta*S*I/N”)

Example

>>> model.define_ode("S", "-beta*S*I/N")
find_all_equilibria(params: dict[str, float] | None = None, numeric_fallback: bool = True, max_solutions: int = 10) list[dict[str, Any]][source]

Find all equilibrium points of the model.

Solves the system of equations f(x) = 0 where f is the vector field. Includes both disease-free and endemic equilibria.

Parameters:
  • params – Parameter values for numeric solving (optional)

  • numeric_fallback – If True, use numerical solver when symbolic fails

  • max_solutions – Maximum number of solutions to return

Returns:

  • Variable names mapped to equilibrium values

  • ’type’: ‘dfe’ or ‘endemic’

  • ’method’: ‘symbolic’ or ‘numeric’

Return type:

List of equilibrium dictionaries. Each dictionary contains

Example

>>> model.find_all_equilibria(params={'beta': 0.3, 'gamma': 0.1, 'N': 1000})
[
    {'S': N, 'I': 0, 'R': 0, 'type': 'dfe', 'method': 'symbolic'},
    {'S': N*gamma/beta, 'I': ..., 'R': ..., 'type': 'endemic', 'method': 'symbolic'}
]
find_disease_free_equilibrium() dict[str, Any][source]

Find the disease-free equilibrium (DFE) of the model.

At DFE: - All infected compartments are zero - Susceptible compartment equals total population

Returns:

Dictionary mapping variable names to equilibrium values

find_endemic_equilibrium(params: dict[str, float] | None = None, numeric_fallback: bool = True) dict[str, Any] | None[source]

Find endemic equilibrium point.

At endemic equilibrium, disease persists (I* > 0). Only exists when R0 > 1.

Parameters:
  • params – Parameter values for numeric solving and R0 calculation

  • numeric_fallback – If True, use numerical solver when symbolic fails

Returns:

Equilibrium dictionary or None if no endemic equilibrium exists

Example

>>> model.find_endemic_equilibrium({'beta': 0.3, 'gamma': 0.1, 'N': 1000})
{'S': 333.33, 'I': 666.67, 'R': 0, 'type': 'endemic', 'method': 'symbolic'}
get_parameter_symbol(name: str) None[source]

Get SymPy Symbol for a parameter.

get_variable_symbol(name: str) None[source]

Get SymPy Symbol for a variable.

perform_perturbation_analysis(params: dict[str, float], equilibrium: dict[str, float], perturbation: float = 0.01, output_vars: list[str] | None = None) dict[str, dict[str, float]][source]

Numerical perturbation analysis.

For each parameter p: 1. Perturb p by (1 + perturbation) 2. Recompute equilibrium 3. Calculate % change in outputs

Parameters:
  • params – Base parameter values

  • equilibrium – Base equilibrium values

  • perturbation – Perturbation size (default: 1%)

  • output_vars – Variables to analyze

Returns:

{output_var: {param: percent_change}}

Return type:

Nested dictionary

Example

>>> PA = model.perform_perturbation_analysis(params, dfe, 0.01)
>>> PA['I']['beta']
0.98  # 1% increase in beta → 0.98% increase in I
rank_parameter_importance(params: dict[str, float], output_var: str, method: str = 'elasticity') list[tuple[str, float]][source]

Rank parameters by importance for a given output.

Parameters:
  • params – Parameter values

  • output_var – Variable to analyze

  • method – ‘elasticity’ or ‘perturbation’

Returns:

List of (parameter, importance_score) tuples, sorted by absolute importance

Example

>>> model.rank_parameter_importance(params, 'I')
[('beta', 1.0), ('gamma', -1.0), ('N', 0.0)]
# beta most important, gamma second, N has no effect
set_total_population(name: str = 'N') None[source]

Set the symbol representing total population.

Parameters:

name – Symbol name for total population (default: “N”)

Returns:

SymPy Symbol for total population

substitute_values(expression: Any, values: dict[str, float]) Any[source]

Substitute numeric values into a symbolic expression.

Parameters:
  • expression – SymPy expression

  • values – Dictionary mapping parameter/variable names to values

Returns:

Expression with substitutions applied

to_latex(expression: Any) str[source]

Convert a SymPy expression to LaTeX.

Parameters:

expression – SymPy expression

Returns:

LaTeX string

epimodels.validation.validators module

Validation functions for parameter values, initial conditions, and constraints.

epimodels.validation.validators.evaluate_constraint(expression: str, context: dict[str, Any]) tuple[bool, str | None][source]

Evaluate a constraint expression in the given context.

Parameters:
  • expression – Constraint expression (e.g., “beta > gamma”)

  • context – Dictionary mapping names to values

Returns:

Tuple of (is_satisfied, error_message)

Example

>>> satisfied, msg = evaluate_constraint("x > y", {"x": 5, "y": 3})
>>> satisfied
True
epimodels.validation.validators.validate_initial_condition(name: str, value: float, spec: VariableSpec, all_values: dict[str, float] | None = None) list[str][source]

Validate an initial condition value against its specification.

Parameters:
  • name – Variable name

  • value – Initial condition value

  • spec – Variable specification

  • all_values – All initial condition values (for cross-variable validation)

Returns:

List of error messages (empty if valid)

epimodels.validation.validators.validate_parameter_value(name: str, value: Any, spec: ParameterSpec, all_params: dict[str, Any] | None = None) list[str][source]

Validate a parameter value against its specification.

Parameters:
  • name – Parameter name

  • value – Parameter value to validate

  • spec – Parameter specification

  • all_params – All parameter values (for cross-parameter validation)

Returns:

List of error messages (empty if valid)

Example

>>> spec = ParameterSpec(name="beta", symbol="β", bounds=(0, None))
>>> errors = validate_parameter_value("beta", -0.5, spec)
>>> len(errors)
1

Module contents

Validation module for epimodels.

Provides rich parameter and state variable specification and validation.

class epimodels.validation.DomainType(*values)[source]

Bases: Enum

Type of numeric domain for parameters and variables.

CATEGORICAL = 'categorical'
CONTINUOUS = 'continuous'
DISCRETE = 'discrete'
INTEGER = 'integer'
class epimodels.validation.ModelConstraint(expression: str, description: str = '', severity: str = 'error', name: str | None = None)[source]

Bases: object

Cross-parameter or model-level constraint.

expression

Constraint expression (Python expression or SymPy-parseable) Can reference parameter names directly. Examples: “beta > gamma”, “p + q <= 1”, “R0 > 1”

Type:

str

description

Human-readable description of the constraint

Type:

str

severity

“error” (raises exception) or “warning” (logs warning)

Type:

str

name

Optional name for the constraint

Type:

str | None

Example

>>> constraint = ModelConstraint(
...     expression="beta / gamma > 1",
...     description="R0 > 1 required for epidemic",
...     severity="warning"
... )
description: str = ''
expression: str
name: str | None = None
severity: str = 'error'
class epimodels.validation.ParameterSpec(name: str, symbol: str, description: str = '', domain_type: DomainType = DomainType.CONTINUOUS, bounds: tuple[float | None, float | None] | None=None, dtype: type = <class 'float'>, default: Any | None = None, required: bool = True, constraints: list[str] = <factory>, units: str | None = None, typical_range: tuple[float, float] | None=None)[source]

Bases: object

Rich specification for a model parameter.

name

Parameter identifier (used in code)

Type:

str

symbol

LaTeX representation for display

Type:

str

description

Human-readable description

Type:

str

domain_type

Type of numeric domain

Type:

epimodels.validation.specs.DomainType

bounds

Optional (min, max) tuple. Use None for unbounded. Example: (0, None) means positive, (0, 1) means probability

Type:

tuple[float | None, float | None] | None

dtype

Expected Python type (float, int, list, etc.)

Type:

type

default

Default value if parameter is optional

Type:

Any | None

required

Whether parameter must be provided

Type:

bool

constraints

List of constraint expressions (e.g., “value > 0”, “value < other_param”)

Type:

list[str]

units

Physical units (e.g., “1/day”, “individuals”)

Type:

str | None

typical_range

Typical range for documentation (min, max)

Type:

tuple[float, float] | None

Example

>>> spec = ParameterSpec(
...     name="beta",
...     symbol=r"$\beta$",
...     description="Transmission rate",
...     bounds=(0, None),
...     constraints=["value > 0"]
... )
bounds: tuple[float | None, float | None] | None = None
constraints: list[str]
default: Any | None = None
description: str = ''
domain_type: DomainType = 'continuous'
dtype

alias of float

name: str
required: bool = True
symbol: str
typical_range: tuple[float, float] | None = None
units: str | None = None
class epimodels.validation.SymbolicModel[source]

Bases: object

Symbolic representation of an epidemiological model.

Enables symbolic analysis of model properties: - Compute R0 using next-generation matrix method - Find equilibrium points - Analyze stability

Example

>>> model = SymbolicModel()
>>> model.add_parameter("beta", positive=True)
>>> model.add_parameter("gamma", positive=True)
>>> model.add_variable("S", positive=True)
>>> model.add_variable("I", positive=True)
>>> model.add_variable("R", positive=True)
>>> model.set_total_population("N")
>>> model.define_ode("S", "-beta*S*I/N")
>>> model.define_ode("I", "beta*S*I/N - gamma*I")
>>> model.define_ode("R", "gamma*I")
>>> R0 = model.compute_R0_next_generation()
add_parameter(name: str, positive: bool = False, negative: bool = False, real: bool = True, **assumptions) None[source]

Add a parameter as a symbolic variable.

Parameters:
  • name – Parameter name

  • positive – Whether parameter is positive

  • negative – Whether parameter is negative

  • real – Whether parameter is real (vs complex)

  • **assumptions – Additional SymPy assumptions

Returns:

SymPy Symbol object

add_variable(name: str, positive: bool = False, negative: bool = False, real: bool = True, **assumptions) None[source]

Add a state variable as a symbolic variable.

Parameters:
  • name – Variable name

  • positive – Whether variable is positive

  • negative – Whether variable is negative

  • real – Whether variable is real

  • **assumptions – Additional SymPy assumptions

Returns:

SymPy Symbol object

analyze_stability_full(equilibrium: dict[str, float | None], params: dict[str, float] | None = None, tolerance: float = 1e-10) dict[str, Any][source]

Perform full stability analysis at an equilibrium point.

Computes and analyzes: - Jacobian matrix - Eigenvalues - Stability classification - Bifurcation indicators - Detailed classification (node, focus, saddle, etc.)

Parameters:
  • equilibrium – Equilibrium point (variable names to values)

  • params – Parameter values for numeric evaluation

  • tolerance – Tolerance for eigenvalue zero detection

Returns:

  • ‘jacobian’: Jacobian matrix (symbolic or numeric)

  • ’eigenvalues’: List of eigenvalues

  • ’eigenvalues_numeric’: Numeric eigenvalues (if params provided)

  • ’stability’: ‘stable’, ‘unstable’, ‘neutral’, or ‘saddle’

  • ’classification’: Detailed type (e.g., ‘stable_node’, ‘unstable_focus’)

  • ’max_real_part’: Maximum real part of eigenvalues

  • ’min_real_part’: Minimum real part of eigenvalues

  • ’has_complex’: Boolean indicating complex eigenvalues

  • ’near_bifurcation’: Boolean indicating proximity to bifurcation

  • ’bifurcation_type’: Type of bifurcation if detected

Return type:

Dictionary with comprehensive stability information

Example

>>> result = model.analyze_stability_full(dfe, params={'beta': 0.3, 'gamma': 0.1})
>>> result['stability']
'unstable'
>>> result['classification']
'unstable_node'
>>> result['max_real_part']
0.2
check_stability_at_dfe(R0: Any) str[source]

Check stability of disease-free equilibrium based on R0.

Parameters:

R0 – Basic reproduction number (symbolic or numeric)

Returns:

“stable”, “unstable”, or “neutral”

Return type:

Stability classification

compute_R0_next_generation() Any[source]

Compute basic reproduction number R0 using next-generation matrix method.

This method: 1. Identifies infected compartments 2. Computes new infections matrix F and transitions matrix V 3. Computes R0 as spectral radius of F*V^(-1) evaluated at DFE

The next-generation matrix method: - For dI/dt = F - V (F = new infections, V = transitions out) - Compute Jacobians: dF = ∂F/∂X, dV = ∂V/∂X (where X = infected compartments) - Evaluate at DFE (disease-free equilibrium) - R0 = spectral_radius(dF * dV^(-1))

Returns:

Symbolic expression for R0 (evaluated at DFE)

Note

For single infected compartment: R0 = (∂F/∂I)|DFE / (∂V/∂I)|DFE

compute_eigenvalues(jacobian: MutableDenseMatrix, numeric: bool = False, params: dict[str, float] | None = None) list[Any][source]

Compute eigenvalues of the Jacobian matrix.

Eigenvalues determine local stability: - All Re(λ) < 0: stable equilibrium - Any Re(λ) > 0: unstable equilibrium - Re(λ) = 0: neutral or bifurcation point

Parameters:
  • jacobian – Jacobian matrix (from compute_jacobian)

  • numeric – Force numeric evaluation of eigenvalues

  • params – Parameter values for numeric evaluation

Returns:

List of eigenvalues (symbolic or numeric complex numbers)

Example

>>> J = model.compute_jacobian(dfe, substitute_values=True)
>>> eigenvalues = model.compute_eigenvalues(J)
>>> eigenvalues
[0, -gamma, -beta + gamma]
compute_elasticity_indices(params: dict[str, float], output_vars: list[str] | None = None) dict[str, dict[str, float]][source]

Compute elasticity indices (normalized sensitivity).

E_ij = (∂y_i/y_i) / (∂p_j/p_j) = (p_j/y_i) * (∂y_i/∂p_j)

Interpreted as: percentage change in output per 1% change in parameter.

Parameters:
  • params – Parameter values (numeric)

  • output_vars – Variables to analyze

Returns:

{output_var: {param: elasticity}}

Return type:

Nested dictionary

Example

>>> E = model.compute_elasticity_indices({'beta': 0.3, 'gamma': 0.1, 'N': 1000})
>>> E['I']['beta']
1.0  # 1% increase in beta → 1% increase in I*
compute_jacobian(equilibrium: dict[str, float | None], substitute_values: bool = False) MutableDenseMatrix[source]

Compute Jacobian matrix at an equilibrium point.

The Jacobian matrix J has entries J_ij = ∂f_i/∂x_j where: - f_i is the ODE for variable i - x_j is the j-th state variable

Parameters:
  • equilibrium – Dictionary mapping variable names to values. Can be symbolic or numeric values.

  • substitute_values – If True, substitute equilibrium values into Jacobian. If False, keep symbolic form.

Returns:

SymPy Matrix representing the Jacobian

Example

>>> dfe = model.find_disease_free_equilibrium()
>>> J = model.compute_jacobian(dfe)
>>> J
Matrix([
    [0, -beta, 0],
    [0, beta - gamma, 0],
    [0, gamma, 0]
])
compute_sensitivity_matrix(params: dict[str, float] | None = None, output_vars: list[str] | None = None, param_list: list[str] | None = None, perturbation: float = 0.01) dict[str, dict[str, Any]][source]

Compute sensitivity of equilibrium values to parameters.

S_ij = ∂y_i/∂p_j where y is equilibrium value and p is parameter. Uses numerical perturbation when symbolic computation is not feasible.

Parameters:
  • params – Parameter values dict (required for numeric computation)

  • output_vars – Variables to analyze (default: all)

  • param_list – Parameters to analyze (default: all from params)

  • perturbation – Relative perturbation size for numerical derivatives (default: 1%)

Returns:

{output_var: {param: sensitivity_value}}

Return type:

Nested dictionary

Example

>>> S = model.compute_sensitivity_matrix({'beta': 0.3, 'gamma': 0.1, 'N': 1000})
>>> S['I']['beta']
100.0  # ∂I*/∂beta at equilibrium
define_difference_equation(variable: str, rhs: str) None[source]

Define a difference equation for a discrete-time model.

Parameters:
  • variable – Variable name

  • rhs – Right-hand side expression

Example

>>> model.define_difference_equation("S", "S - beta*S*I/N + gamma*I")
define_ode(variable: str, rhs: str) None[source]

Define an ODE for a state variable.

Parameters:
  • variable – Variable name (e.g., “S”, “I”, “R”)

  • rhs – Right-hand side expression as string (e.g., “-beta*S*I/N”)

Example

>>> model.define_ode("S", "-beta*S*I/N")
find_all_equilibria(params: dict[str, float] | None = None, numeric_fallback: bool = True, max_solutions: int = 10) list[dict[str, Any]][source]

Find all equilibrium points of the model.

Solves the system of equations f(x) = 0 where f is the vector field. Includes both disease-free and endemic equilibria.

Parameters:
  • params – Parameter values for numeric solving (optional)

  • numeric_fallback – If True, use numerical solver when symbolic fails

  • max_solutions – Maximum number of solutions to return

Returns:

  • Variable names mapped to equilibrium values

  • ’type’: ‘dfe’ or ‘endemic’

  • ’method’: ‘symbolic’ or ‘numeric’

Return type:

List of equilibrium dictionaries. Each dictionary contains

Example

>>> model.find_all_equilibria(params={'beta': 0.3, 'gamma': 0.1, 'N': 1000})
[
    {'S': N, 'I': 0, 'R': 0, 'type': 'dfe', 'method': 'symbolic'},
    {'S': N*gamma/beta, 'I': ..., 'R': ..., 'type': 'endemic', 'method': 'symbolic'}
]
find_disease_free_equilibrium() dict[str, Any][source]

Find the disease-free equilibrium (DFE) of the model.

At DFE: - All infected compartments are zero - Susceptible compartment equals total population

Returns:

Dictionary mapping variable names to equilibrium values

find_endemic_equilibrium(params: dict[str, float] | None = None, numeric_fallback: bool = True) dict[str, Any] | None[source]

Find endemic equilibrium point.

At endemic equilibrium, disease persists (I* > 0). Only exists when R0 > 1.

Parameters:
  • params – Parameter values for numeric solving and R0 calculation

  • numeric_fallback – If True, use numerical solver when symbolic fails

Returns:

Equilibrium dictionary or None if no endemic equilibrium exists

Example

>>> model.find_endemic_equilibrium({'beta': 0.3, 'gamma': 0.1, 'N': 1000})
{'S': 333.33, 'I': 666.67, 'R': 0, 'type': 'endemic', 'method': 'symbolic'}
get_parameter_symbol(name: str) None[source]

Get SymPy Symbol for a parameter.

get_variable_symbol(name: str) None[source]

Get SymPy Symbol for a variable.

perform_perturbation_analysis(params: dict[str, float], equilibrium: dict[str, float], perturbation: float = 0.01, output_vars: list[str] | None = None) dict[str, dict[str, float]][source]

Numerical perturbation analysis.

For each parameter p: 1. Perturb p by (1 + perturbation) 2. Recompute equilibrium 3. Calculate % change in outputs

Parameters:
  • params – Base parameter values

  • equilibrium – Base equilibrium values

  • perturbation – Perturbation size (default: 1%)

  • output_vars – Variables to analyze

Returns:

{output_var: {param: percent_change}}

Return type:

Nested dictionary

Example

>>> PA = model.perform_perturbation_analysis(params, dfe, 0.01)
>>> PA['I']['beta']
0.98  # 1% increase in beta → 0.98% increase in I
rank_parameter_importance(params: dict[str, float], output_var: str, method: str = 'elasticity') list[tuple[str, float]][source]

Rank parameters by importance for a given output.

Parameters:
  • params – Parameter values

  • output_var – Variable to analyze

  • method – ‘elasticity’ or ‘perturbation’

Returns:

List of (parameter, importance_score) tuples, sorted by absolute importance

Example

>>> model.rank_parameter_importance(params, 'I')
[('beta', 1.0), ('gamma', -1.0), ('N', 0.0)]
# beta most important, gamma second, N has no effect
set_total_population(name: str = 'N') None[source]

Set the symbol representing total population.

Parameters:

name – Symbol name for total population (default: “N”)

Returns:

SymPy Symbol for total population

substitute_values(expression: Any, values: dict[str, float]) Any[source]

Substitute numeric values into a symbolic expression.

Parameters:
  • expression – SymPy expression

  • values – Dictionary mapping parameter/variable names to values

Returns:

Expression with substitutions applied

to_latex(expression: Any) str[source]

Convert a SymPy expression to LaTeX.

Parameters:

expression – SymPy expression

Returns:

LaTeX string

class epimodels.validation.VariableSpec(name: str, symbol: str, description: str = '', bounds: tuple[float | None, float | None] | None=None, non_negative: bool = True, constraints: list[str] = <factory>, units: str | None = None)[source]

Bases: object

Specification for a state variable.

name

Variable identifier (used in code)

Type:

str

symbol

LaTeX representation

Type:

str

description

Human-readable description

Type:

str

bounds

Optional (min, max) tuple

Type:

tuple[float | None, float | None] | None

non_negative

Whether variable must be >= 0

Type:

bool

constraints

List of constraint expressions

Type:

list[str]

units

Physical units

Type:

str | None

Example

>>> spec = VariableSpec(
...     name="S",
...     symbol="S",
...     description="Susceptible individuals",
...     non_negative=True
... )
bounds: tuple[float | None, float | None] | None = None
constraints: list[str]
description: str = ''
name: str
non_negative: bool = True
symbol: str
units: str | None = None
epimodels.validation.evaluate_constraint(expression: str, context: dict[str, Any]) tuple[bool, str | None][source]

Evaluate a constraint expression in the given context.

Parameters:
  • expression – Constraint expression (e.g., “beta > gamma”)

  • context – Dictionary mapping names to values

Returns:

Tuple of (is_satisfied, error_message)

Example

>>> satisfied, msg = evaluate_constraint("x > y", {"x": 5, "y": 3})
>>> satisfied
True
epimodels.validation.validate_initial_condition(name: str, value: float, spec: VariableSpec, all_values: dict[str, float] | None = None) list[str][source]

Validate an initial condition value against its specification.

Parameters:
  • name – Variable name

  • value – Initial condition value

  • spec – Variable specification

  • all_values – All initial condition values (for cross-variable validation)

Returns:

List of error messages (empty if valid)

epimodels.validation.validate_parameter_value(name: str, value: Any, spec: ParameterSpec, all_params: dict[str, Any] | None = None) list[str][source]

Validate a parameter value against its specification.

Parameters:
  • name – Parameter name

  • value – Parameter value to validate

  • spec – Parameter specification

  • all_params – All parameter values (for cross-parameter validation)

Returns:

List of error messages (empty if valid)

Example

>>> spec = ParameterSpec(name="beta", symbol="β", bounds=(0, None))
>>> errors = validate_parameter_value("beta", -0.5, spec)
>>> len(errors)
1