Skip to content

Parameter

abc

Abstract base class and runtime contracts for parameters.

ModelStateSpecification(parameter_names, axes=(), broadcast=False, labels=None) dataclass

Model State Specification.

Describe how configured parameter entries assemble the evolving model state.

Attributes:

Name Type Description
parameter_names tuple[IdentifierString, ...]

Ordered parameter names that define the model state.

axes tuple[IdentifierString, ...]

Named axes shared by each state entry.

broadcast bool

Whether scalar state entries may broadcast to the requested axes.

labels tuple[str, ...] | None

Optional human-readable labels such as compartment names.

Notes

ModelStateSpecification defines the semantic order of model-state entries, but it does not prescribe how an engine should convert them into its internal representation. An ODE engine might use np.stack(...), while another engine might preserve the dictionary form.

Each state entry currently maps to exactly one configured parameter name. Reusing the same parameter name for multiple state entries is not supported because it would collapse when requests are materialized into a dictionary.

For an age-by-region SEIR system with age labels ("age0_17", "age18_55", "age55_100") and region labels ("Region A", "Region B"), parameter_names could be ("S0", "E0", "I0", "R0") and axes could be ("region", "age"). An engine could then stack those entries into a (4, 2, 3) array ordered as (compartment, region, age).

Examples:

>>> from flepimop2.parameter.abc import ModelStateSpecification
>>> spec = ModelStateSpecification(
...     parameter_names=("s0", "i0", "r0"),
...     labels=("S", "I", "R"),
... )
>>> tuple(spec.requests())
('s0', 'i0', 'r0')
__post_init__()

Validate any provided state labels align with parameter names.

Raises:

Type Description
ValueError

If labels and parameter_names have different lengths.

ValueError

If parameter_names contains duplicates.

Examples:

>>> from flepimop2.parameter.abc import ModelStateSpecification
>>> spec = ModelStateSpecification(parameter_names=("s0",), labels=("S",))
>>> spec.labels
('S',)
>>> ModelStateSpecification(
...     parameter_names=("s0", "i0"),
...     labels=("S",),
... )
Traceback (most recent call last):
    ...
ValueError: ModelStateSpecification labels must match ...
>>> ModelStateSpecification(
...     parameter_names=("s0", "s0"),
... )
Traceback (most recent call last):
    ...
ValueError: ModelStateSpecification parameter_names must be unique ...
Source code in src/flepimop2/parameter/abc/__init__.py
def __post_init__(self) -> None:
    """
    Validate any provided state labels align with parameter names.

    Raises:
        ValueError: If `labels` and `parameter_names` have different lengths.
        ValueError: If `parameter_names` contains duplicates.

    Examples:
        >>> from flepimop2.parameter.abc import ModelStateSpecification
        >>> spec = ModelStateSpecification(parameter_names=("s0",), labels=("S",))
        >>> spec.labels
        ('S',)
        >>> ModelStateSpecification(
        ...     parameter_names=("s0", "i0"),
        ...     labels=("S",),
        ... )
        Traceback (most recent call last):
            ...
        ValueError: ModelStateSpecification labels must match ...
        >>> ModelStateSpecification(
        ...     parameter_names=("s0", "s0"),
        ... )
        Traceback (most recent call last):
            ...
        ValueError: ModelStateSpecification parameter_names must be unique ...
    """
    if self.labels is not None and len(self.labels) != len(self.parameter_names):
        msg = (
            "ModelStateSpecification labels must match parameter_names length; "
            f"got {len(self.labels)} labels and {len(self.parameter_names)} "
            "parameter names."
        )
        raise ValueError(msg)
    if len(set(self.parameter_names)) != len(self.parameter_names):
        msg = (
            "ModelStateSpecification parameter_names must be unique so each "
            "state entry maps to exactly one configured parameter."
        )
        raise ValueError(msg)
requests()

Convert this model-state specification into per-parameter requests.

Returns:

Type Description
dict[IdentifierString, ParameterRequest]

Sampling requests for each parameter used to initialize model state.

Source code in src/flepimop2/parameter/abc/__init__.py
def requests(self) -> dict[IdentifierString, ParameterRequest]:
    """
    Convert this model-state specification into per-parameter requests.

    Returns:
        Sampling requests for each parameter used to initialize model state.
    """
    return {
        name: ParameterRequest(
            name=name,
            axes=self.axes,
            broadcast=self.broadcast,
        )
        for name in self.parameter_names
    }

ParameterABC

Bases: ModuleBase

Abstract base class for parameter modules.

Notes

Concrete parameter implementations should use request to decide how to materialize their output for a particular system. For example, a fixed scalar parameter may broadcast to a requested age axis, while a data-backed parameter may validate that its loaded data already matches the requested shape.

sample(*, axes=None, request=None) abstractmethod

Sample a value from the parameter.

Parameters:

Name Type Description Default
axes AxisCollection | None

Resolved runtime axes available for this simulation.

None
request ParameterRequest | None

Optional system-declared request describing the expected shape and advisory type for the parameter.

None

Returns:

Type Description
ParameterValue

A sampled parameter value with resolved shape metadata.

Source code in src/flepimop2/parameter/abc/__init__.py
@abstractmethod
def sample(
    self,
    *,
    axes: AxisCollection | None = None,
    request: ParameterRequest | None = None,
) -> ParameterValue:
    """
    Sample a value from the parameter.

    Args:
        axes: Resolved runtime axes available for this simulation.
        request: Optional system-declared request describing the expected shape
            and advisory type for the parameter.

    Returns:
        A sampled parameter value with resolved shape metadata.
    """
    raise NotImplementedError

ParameterRequest(name, axes=(), broadcast=False, optional=False) dataclass

Request for a parameter value declared by a system.

Attributes:

Name Type Description
name IdentifierString

Parameter name expected by the system.

axes tuple[IdentifierString, ...]

Ordered named axes the resolved parameter should align with.

broadcast bool

Whether a scalar parameter may be broadcast to the requested shape.

optional bool

Whether the system can omit this parameter and rely on defaults.

Notes

Systems typically create ParameterRequest objects in SystemABC.requested_parameters(). Parameter modules receive the request in ParameterABC.sample() and can use the shape metadata to decide how to materialize their values.

Examples:

>>> from pprint import pp
>>> from flepimop2.parameter.abc import ParameterRequest
>>> request = ParameterRequest(
...     name="gamma",
...     axes=("age",),
...     broadcast=True,
... )
>>> pp(request)
ParameterRequest(name='gamma', axes=('age',), broadcast=True, optional=False)

ParameterValue(value, shape) dataclass

Sampled parameter value plus resolved runtime shape metadata.

Attributes:

Name Type Description
value Array

The realized array-API value for this parameter. Any object satisfying the Array protocol is accepted -- NumPy arrays, JAX arrays, PyTorch tensors, etc. -- which lets backend-specific drivers (e.g. a jax.vmap-wrapped engine) keep their tracers without an unwanted host round-trip. Producers remain responsible for handing in the dtype the downstream engine expects; ParameterValue does not coerce.

shape ResolvedShape

Named runtime shape resolved against a concrete axis collection.

Notes

ParameterValue intentionally keeps the payload small: it records the value itself and the resolved named shape. Richer provenance or caching metadata can be added later once those workflows are designed.

Examples:

>>> from pprint import pp
>>> import numpy as np
>>> from flepimop2.axis import ResolvedShape
>>> from flepimop2.parameter.abc import ParameterValue
>>> value = ParameterValue(
...     value=np.array([1.0, 2.0]),
...     shape=ResolvedShape(axis_names=("age",), sizes=(2,)),
... )
>>> pp(value)
ParameterValue(value=array([1., 2.]),
               shape=ResolvedShape(axis_names=('age',), sizes=(2,)))
__post_init__()

Validate the value's shape and that its dtype is numeric.

ParameterValue no longer coerces the underlying value. Producers (ParameterABC subclasses) hand in an Array-API value of the correct dtype; the static Array annotation expresses the contract and mypy enforces it. This leaves the value's array backend (NumPy, JAX, PyTorch, ...) untouched so consumers wrapped in jax.jit / jax.vmap see a tracer rather than a TracerArrayConversionError from an implicit np.asarray.

The Array Protocol is intentionally broad enough to cover every Array-API-compliant backend, which means it also nominally admits non-numeric NumPy arrays (e.g. string or object dtypes). Those have no meaningful place in a ParameterValue, so this check rejects them at construction time without forcing a host round-trip on tracer-bearing backends: it asks the value's own Array-API namespace via __array_namespace__().isdtype(..., "numeric"), falling back to dtype.kind for backends that predate the Array-API isdtype helper.

Raises:

Type Description
ValueError

If the array shape does not match the resolved named shape.

TypeError

If the array dtype is not numeric (boolean, integer, unsigned integer, floating, or complex floating).

Examples:

>>> import numpy as np
>>> from flepimop2.axis import ResolvedShape
>>> from flepimop2.parameter.abc import ParameterValue
>>> ParameterValue(value=np.array(42.0), shape=ResolvedShape()).item()
42.0
>>> ParameterValue(
...     value=np.array([1.0, 2.0]),
...     shape=ResolvedShape(axis_names=("age",), sizes=(3,)),
... )
Traceback (most recent call last):
    ...
ValueError: ParameterValue shape mismatch: array has shape ...
Source code in src/flepimop2/parameter/abc/__init__.py
def __post_init__(self) -> None:
    """
    Validate the value's shape and that its dtype is numeric.

    ``ParameterValue`` no longer coerces the underlying ``value``.
    Producers (`ParameterABC` subclasses) hand in an Array-API value
    of the correct dtype; the static ``Array`` annotation expresses
    the contract and ``mypy`` enforces it.  This leaves the value's
    array backend (NumPy, JAX, PyTorch, ...) untouched so consumers
    wrapped in ``jax.jit`` / ``jax.vmap`` see a tracer rather than a
    ``TracerArrayConversionError`` from an implicit ``np.asarray``.

    The `Array` `Protocol` is intentionally broad enough to cover
    every Array-API-compliant backend, which means it also nominally
    admits non-numeric NumPy arrays (e.g. string or object dtypes).
    Those have no meaningful place in a `ParameterValue`, so this
    check rejects them at construction time without forcing a host
    round-trip on tracer-bearing backends: it asks the value's own
    Array-API namespace via ``__array_namespace__().isdtype(...,
    "numeric")``, falling back to ``dtype.kind`` for backends that
    predate the Array-API ``isdtype`` helper.

    Raises:
        ValueError: If the array shape does not match the resolved named shape.
        TypeError: If the array dtype is not numeric (boolean, integer,
            unsigned integer, floating, or complex floating).

    Examples:
        >>> import numpy as np
        >>> from flepimop2.axis import ResolvedShape
        >>> from flepimop2.parameter.abc import ParameterValue
        >>> ParameterValue(value=np.array(42.0), shape=ResolvedShape()).item()
        42.0
        >>> ParameterValue(
        ...     value=np.array([1.0, 2.0]),
        ...     shape=ResolvedShape(axis_names=("age",), sizes=(3,)),
        ... )
        Traceback (most recent call last):
            ...
        ValueError: ParameterValue shape mismatch: array has shape ...
    """
    actual = tuple(self.value.shape)
    if actual != self.shape.sizes:
        msg = (
            "ParameterValue shape mismatch: array has shape "
            f"{actual}, but resolved shape is {self.shape.sizes} "
            f"for axes {self.shape.axis_names}."
        )
        raise ValueError(msg)
    try:
        xp: Any = self.value.__array_namespace__()
        is_numeric = bool(xp.isdtype(self.value.dtype, "numeric"))
    except (AttributeError, TypeError):
        kind = getattr(self.value.dtype, "kind", None)
        is_numeric = kind in {"b", "i", "u", "f", "c"}
    if not is_numeric:
        msg = (
            f"ParameterValue.value has non-numeric dtype {self.value.dtype!r}; "
            "expected a numeric Array-API array."
        )
        raise TypeError(msg)
item()

Return the scalar item from a scalar parameter value.

Returns:

Type Description
float

The scalar value as a Python float.

Examples:

>>> import numpy as np
>>> from flepimop2.axis import ResolvedShape
>>> from flepimop2.parameter.abc import ParameterValue
>>> ParameterValue(value=np.array(3.14), shape=ResolvedShape()).item()
3.14
Source code in src/flepimop2/parameter/abc/__init__.py
def item(self) -> float:
    """
    Return the scalar item from a scalar parameter value.

    Returns:
        The scalar value as a Python float.

    Examples:
        >>> import numpy as np
        >>> from flepimop2.axis import ResolvedShape
        >>> from flepimop2.parameter.abc import ParameterValue
        >>> ParameterValue(value=np.array(3.14), shape=ResolvedShape()).item()
        3.14
    """
    return float(self.value.item())

build(config)

Build a ParameterABC from a configuration dictionary.

Parameters:

Name Type Description Default
config dict[str, Any] | ModuleBase | str

Configuration dictionary or a ModuleBase instance to construct the parameter from. The configuration must define a module.

required

Returns:

Type Description
ParameterABC

The constructed parameter instance.

Source code in src/flepimop2/parameter/abc/__init__.py
def build(config: dict[str, Any] | ModuleBase | str) -> ParameterABC:
    """Build a `ParameterABC` from a configuration dictionary.

    Args:
        config: Configuration dictionary or a `ModuleBase` instance to construct the
            parameter from. The configuration must define a `module`.

    Returns:
        The constructed parameter instance.
    """
    return _build(config, "parameter", ParameterABC)  # type: ignore[type-abstract]

fixed

Fixed parameter implementation.

FixedParameter

Bases: ParameterABC

Parameter with a fixed value.

Examples:

>>> from flepimop2.parameter.fixed import FixedParameter
>>> param = FixedParameter(value=42.0)
>>> param.sample().item()
42.0
from_shorthand(shorthand) classmethod

Build a fixed parameter from shorthand argument text.

Parameters:

Name Type Description Default
shorthand str

The text inside the shorthand parentheses.

required

Returns:

Type Description
Self

A fixed parameter with the parsed numeric value.

Raises:

Type Description
ValueError

If the shorthand cannot be parsed as a float.

Examples:

>>> from flepimop2.parameter.fixed import FixedParameter
>>> FixedParameter.from_shorthand("3").value
3.0
>>> FixedParameter.from_shorthand("0.25").value
0.25
>>> FixedParameter.from_shorthand("-7").value
-7.0
>>> FixedParameter.from_shorthand('''
...   3.1415
... ''').value
3.1415
>>> try:
...     FixedParameter.from_shorthand("'x'")
... except ValueError as exc:
...     print(exc)
FixedParameter shorthand must contain exactly one numeric value, got "'x'".
Source code in src/flepimop2/parameter/fixed/__init__.py
@classmethod
def from_shorthand(cls, shorthand: str) -> Self:
    r"""
    Build a fixed parameter from shorthand argument text.

    Args:
        shorthand: The text inside the shorthand parentheses.

    Returns:
        A fixed parameter with the parsed numeric value.

    Raises:
        ValueError: If the shorthand cannot be parsed as a float.

    Examples:
        >>> from flepimop2.parameter.fixed import FixedParameter
        >>> FixedParameter.from_shorthand("3").value
        3.0
        >>> FixedParameter.from_shorthand("0.25").value
        0.25
        >>> FixedParameter.from_shorthand("-7").value
        -7.0
        >>> FixedParameter.from_shorthand('''
        ...   3.1415
        ... ''').value
        3.1415
        >>> try:
        ...     FixedParameter.from_shorthand("'x'")
        ... except ValueError as exc:
        ...     print(exc)
        FixedParameter shorthand must contain exactly one numeric value, got "'x'".
    """
    try:
        return cls(value=float(shorthand.strip()))
    except ValueError as exc:
        msg = (
            "FixedParameter shorthand must contain exactly one numeric value, "
            f"got {shorthand!r}."
        )
        raise ValueError(msg) from exc
sample(*, axes=None, request=None)

Return the fixed value of the parameter.

Parameters:

Name Type Description Default
axes AxisCollection | None

Resolved runtime axes available for the current simulation.

None
request ParameterRequest | None

Optional system request describing the desired shape.

None

Returns:

Type Description
ParameterValue

The fixed parameter value with runtime shape metadata.

Raises:

Type Description
ValueError

If the configured shape conflicts with the system request.

ValueError

If a non-scalar fixed value has no named shape context.

ValueError

If the configured value cannot satisfy the resolved shape.

Source code in src/flepimop2/parameter/fixed/__init__.py
def sample(
    self,
    *,
    axes: AxisCollection | None = None,
    request: ParameterRequest | None = None,
) -> ParameterValue:
    """
    Return the fixed value of the parameter.

    Args:
        axes: Resolved runtime axes available for the current simulation.
        request: Optional system request describing the desired shape.

    Returns:
        The fixed parameter value with runtime shape metadata.

    Raises:
        ValueError: If the configured shape conflicts with the system request.
        ValueError: If a non-scalar fixed value has no named shape context.
        ValueError: If the configured value cannot satisfy the resolved shape.
    """
    axes = axes or AxisCollection()
    value = np.asarray(self.value, dtype=np.float64)

    configured_shape = axes.resolve_shape(self.shape) if self.shape else None
    requested_shape = (
        axes.resolve_shape(request.axes) if request is not None else None
    )

    if (
        configured_shape is not None
        and requested_shape is not None
        and configured_shape != requested_shape
    ):
        if request is None:
            msg = "request must be provided when validating a requested shape."
            raise ValueError(msg)
        msg = (
            f"FixedParameter shape {configured_shape.axis_names} does not match "
            f"requested shape {requested_shape.axis_names} for parameter "
            f"'{request.name}'."
        )
        raise ValueError(msg)

    target_shape = configured_shape or requested_shape
    if target_shape is None:
        if value.ndim > 0:
            msg = (
                "Non-scalar FixedParameter values require either an explicit "
                "'shape' configuration or a system request."
            )
            raise ValueError(msg)
        return ParameterValue(
            value=value,
            shape=ResolvedShape(),
        )

    if value.shape == target_shape.sizes:
        return ParameterValue(value=value, shape=target_shape)

    if value.ndim == 0 and (
        configured_shape is not None or (request is not None and request.broadcast)
    ):
        broadcast = np.broadcast_to(value, target_shape.sizes).astype(np.float64)
        return ParameterValue(value=broadcast, shape=target_shape)

    msg = (
        f"FixedParameter value shape {value.shape} is not compatible with "
        f"resolved shape {target_shape.sizes} for axes {target_shape.axis_names}."
    )
    raise ValueError(msg)
to_yaml_data()

Convert the fixed parameter into YAML-ready Python objects.

Returns:

Type Description
object

A bare scalar for simple fixed parameters, otherwise the expanded

object

module configuration mapping.

Source code in src/flepimop2/parameter/fixed/__init__.py
def to_yaml_data(self) -> object:
    """
    Convert the fixed parameter into YAML-ready Python objects.

    Returns:
        A bare scalar for simple fixed parameters, otherwise the expanded
        module configuration mapping.
    """
    if (
        isinstance(self.value, int | float)
        and not isinstance(self.value, bool)
        and not self.shape
        and not self.options
    ):
        return self.value
    return super().to_yaml_data()