Metrics

The metrics module provides classes for computing specific metrics from PyBaMM solutions at particular operating conditions.

Base Class

class ionworkspipeline.data_fits.models.metrics.BaseMetric(variable: str | None = None, step: int | None = None)

A base class for computing a metric given a specific operating condition.

Metrics extract values from PyBaMM solutions. Use Action classes (Maximize, Minimize, GreaterThan, LessThan) to define how metrics contribute to optimization objectives.

Metrics support arithmetic operations (+, -, *, /) to create composed metrics:

>>> # Pulse resistance = (V_after - V_before) / I_mean
>>> v_after = Time("Voltage [V]", value=1.0, step=2)
>>> v_before = Time("Voltage [V]", value=0.0, step=2)
>>> i_mean = Mean("Current [A]", step=2)
>>> resistance = (v_after - v_before) / i_mean

Metrics also support iteration over steps or cycles using fluent methods:

>>> Mean("Voltage [V]").by_step()  # evaluate for each step
<...IterativeMetric object at ...>
>>> Mean("Voltage [V]").by_cycle(step=1)  # evaluate step 1 of each cycle
<...IterativeMetric object at ...>

Parameters

variablestr or None

The variable name on which to compute the metric. May be None for wrapper metrics (ComposedMetric, IterativeMetric) that delegate to inner metrics.

stepint, optional

If provided, filters the solution to the specified step (0-indexed) using solution.sub_solutions[step] before computing the metric.

Extends: abc.ABC

by_cycle(step: int | None = None, cycles: list[int] | None = None) IterativeMetric

Evaluate this metric for each cycle.

Parameters

stepint, optional

The step index (0-indexed) within each cycle to evaluate. If None, evaluates the metric across the entire cycle (all steps).

cycleslist of int, optional

Specific cycle indices (0-indexed). If None, evaluates all cycles.

Returns

IterativeMetric

A metric that returns an array with one value per cycle.

Example

>>> Mean("Voltage [V]").by_cycle()  # all steps of all cycles
<...IterativeMetric object at ...>
>>> Mean("Voltage [V]").by_cycle(step=1)  # step 1 of all cycles
<...IterativeMetric object at ...>
>>> Maximum("Capacity [A.h]").by_cycle(step=1, cycles=[0, 5, 10])
<...IterativeMetric object at ...>
by_step(steps: list[int] | None = None) IterativeMetric

Evaluate this metric for each step, returning an array of results.

Parameters

stepslist of int, optional

Specific step indices (0-indexed). If None, evaluates all steps.

Returns

IterativeMetric

A metric that returns an array with one value per step.

Example

>>> Mean("Voltage [V]").by_step()  # all steps
<...IterativeMetric object at ...>
>>> Mean("Voltage [V]").by_step(steps=[0, 2, 4])  # specific steps
<...IterativeMetric object at ...>
property name: str

The name of this metric, used for identification.

run(solution: Solution, model: BaseModel = None) float | ndarray

Compute the metric from the solution.

If a step filter is specified, the solution is filtered to that step’s sub-solution before computing the metric.

Parameters

solutionpybamm.Solution

The solved PyBaMM solution.

modelpybamm.BaseModel, optional

The PyBaMM model instance.

Returns

float or np.ndarray

The computed metric value(s).

to_config() dict

Convert the metric to a configuration dictionary.

Returns

dict

Parsed configuration dictionary for this metric.

property variable: str

The variable name for this metric.

Raises NotImplementedError for wrapper metrics that must override this.

Time-Based Metrics

class ionworkspipeline.data_fits.models.metrics.Time(variable: str, value: float, step: int | None = None)

A metric computed at a specific time.

Parameters

variablestr

The variable name on which to compute the metric.

valuefloat

The time value (in seconds) to evaluate, also accepts -1 for final time value.

stepint, optional

If provided, filters the solution to the specified step (0-indexed).

Extends: ionworkspipeline.data_fits.models.metrics.metrics.BaseMetric

Condition-Based Metrics

class ionworkspipeline.data_fits.models.metrics.SOC(variable: str, soc_variable: str, value: float, step: int | None = None)

Metric computed at a specific state-of-charge using root finding for SOC crossings.

Identifies SOC crossings and extracts variable values at those times using scipy.optimize.root_scalar with linear interpolation fallback.

Uses “State of charge” as the SOC variable name.

Parameters

variablestr

The variable name on which to compute the metric.

soc_variablestr

The variable name for the SOC variable.

valuefloat

The state-of-charge value (between 0 and 1).

stepint, optional

If provided, filters the solution to the specified step (0-indexed).

Extends: ionworkspipeline.data_fits.models.metrics.metrics.BaseMetric

class ionworkspipeline.data_fits.models.metrics.Voltage(variable: str, value: float, step: int | None = None)

Metric computed at a specific voltage using root finding for voltage crossings.

Identifies voltage crossings and extracts variable values at those times using scipy.optimize.root_scalar with linear interpolation fallback.

Parameters

variablestr

The variable name on which to compute the metric.

valuefloat

The voltage value in Volts.

stepint, optional

If provided, filters the solution to the specified step (0-indexed).

Extends: ionworkspipeline.data_fits.models.metrics.metrics.BaseMetric

Aggregation Metrics

class ionworkspipeline.data_fits.models.metrics.Maximum(variable: str, step: int | None = None)

A maximum metric computed across multiple solution points.

Parameters

variablestr

The variable name on which to compute the metric.

Extends: ionworkspipeline.data_fits.models.metrics.metrics.AggregationMetric

class ionworkspipeline.data_fits.models.metrics.Minimum(variable: str, step: int | None = None)

A minimum metric computed across multiple solution points.

Parameters

variablestr

The variable name on which to compute the metric.

Extends: ionworkspipeline.data_fits.models.metrics.metrics.AggregationMetric

class ionworkspipeline.data_fits.models.metrics.Mean(variable: str, step: int | None = None)

A mean metric computed across multiple solution points.

Parameters

variablestr

The variable name on which to compute the metric.

Extends: ionworkspipeline.data_fits.models.metrics.metrics.AggregationMetric

class ionworkspipeline.data_fits.models.metrics.Sum(variable: str, step: int | None = None)

A summation metric computed across multiple solution points.

Parameters

variablestr

The variable name on which to compute the metric.

Extends: ionworkspipeline.data_fits.models.metrics.metrics.AggregationMetric

Point-Based Metrics

class ionworkspipeline.data_fits.models.metrics.PointBased(variable: str, step: int | None = None)

A metric that extracts a single constant value from a solution variable.

This metric is designed for variables that have a constant value throughout the solution (e.g., design parameters like electrode thickness, cell cost). It extracts the value at the final time point.

Useful for optimization objectives that involve parameters defined via pybamm.Parameter expressions which don’t vary over time.

Parameters

variablestr

The variable name on which to compute the metric.

stepint, optional

If provided, filters the solution to the specified step (0-indexed).

Example

Define a cost variable in the model and use PointBased to extract it:

model.variables["Cell cost"] = (
    pybamm.Parameter("Total cell area [m]")
    * pybamm.Parameter("Areal cost density [$/m^2]")
)
cost_metric = PointBased("Cell cost")

Extends: ionworkspipeline.data_fits.models.metrics.metrics.BaseMetric

Action Classes

Actions wrap metrics and define how they contribute to the optimization cost landscape.

class ionworkspipeline.data_fits.models.metrics.BaseAction(metric: BaseMetric, weight: float = 1.0)

Base class for optimization actions that wrap metrics.

Actions define how a metric contributes to the optimization cost landscape. Each action wraps a metric and transforms its output appropriately for the optimizer (which minimizes).

Parameters

metricBaseMetric

The metric to wrap with this action.

weightfloat, optional

Weight to apply to this metric in the objective function. Default is 1.0.

Examples

>>> from ionworkspipeline.data_fits.models.metrics import Maximum, Minimum
>>> from ionworkspipeline.data_fits.models.metrics.actions import (
...     Maximize, Minimize, LessThan, GreaterThan,
... )

Define objective actions: >>> actions = { … “Cell capacity”: Maximize(Maximum(“Discharge capacity [A.h]”), weight=1.5), … }

Define constraint actions: >>> constraints = { … “Max temperature”: LessThan(Maximum(“Temperature [K]”), 323.15), … “Min voltage”: GreaterThan(Minimum(“Voltage [V]”), 2.5), … }

Extends: abc.ABC

abstractmethod apply(metric_value: float | ndarray) float | ndarray

Apply the action transformation to a metric value.

This method applies the action’s transformation (e.g., negation for Maximize) without running the underlying metric. Use this when you already have the raw metric value and want to avoid running the metric twice.

Parameters

metric_valuefloat or np.ndarray

The raw metric value to transform.

Returns

float or np.ndarray

The transformed metric value for the optimizer.

property name: str

Return a descriptive name for this action.

run(solution: Solution, model: BaseModel | None = None) float | ndarray

Execute the action on a solution.

This is a convenience method that runs the metric and applies the action transformation in one step.

Parameters

solutionpybamm.Solution

The solved PyBaMM solution.

modelpybamm.BaseModel, optional

The PyBaMM model instance.

Returns

float or np.ndarray

The transformed metric value for the optimizer.

property variable: str

Return the underlying metric’s variable name.

class ionworkspipeline.data_fits.models.metrics.Maximize(metric: BaseMetric, weight: float = 1.0)

Maximize the metric value during optimization.

Since optimizers typically minimize, this action negates the metric value so that minimizing the negative is equivalent to maximizing the original.

Parameters

metricBaseMetric

The metric to maximize.

Example

Maximize discharge capacity:

Maximize(Maximum("Discharge capacity [A.h]"))

Extends: ionworkspipeline.data_fits.models.metrics.actions.BaseAction

apply(metric_value: float | ndarray) float | ndarray

Return negated metric value (optimizer minimizes, so we negate to maximize).

to_config() dict

Convert to configuration dict with explicit action type.

class ionworkspipeline.data_fits.models.metrics.Minimize(metric: BaseMetric, weight: float = 1.0)

Minimize the metric value during optimization.

This action passes through the metric value unchanged since optimizers already minimize by default.

Parameters

metricBaseMetric

The metric to minimize.

Example

Minimize cell cost:

Minimize(PointBased("Cell cost"))

Extends: ionworkspipeline.data_fits.models.metrics.actions.BaseAction

apply(metric_value: float | ndarray) float | ndarray

Return metric value as-is (optimizer already minimizes).

to_config() dict

Convert to configuration dict with explicit action type.

class ionworkspipeline.data_fits.models.metrics.GreaterThan(metric: BaseMetric, value: float, penalty: float = 1000000.0)

Constraint that the metric value should be greater than or equal to a threshold.

Returns 0 when the constraint is satisfied (value >= threshold), and a large penalty value when violated (value < threshold).

Parameters

metricBaseMetric

The metric to constrain.

valuefloat

The lower bound threshold. Values below this trigger penalties.

penaltyfloat, optional

The penalty value to return when the constraint is violated. This contribution is penalty * (1 + violation) to include both a steep violation and a proportion term to directionally inform the optimiser. Default is 1e6.

Example

Constrain minimum anode potential to be >= 0V (prevent lithium plating):

GreaterThan(Minimum("Negative electrode potential [V]"), 0.0)

Extends: ionworkspipeline.data_fits.models.metrics.actions.BaseAction

apply(metric_value: float | ndarray) float

Apply the constraint transformation to a metric value.

Returns

float

0.0 if constraint is satisfied (value >= threshold), penalty * (1 + violation) if violated (value < threshold), np.inf if the metric value is empty or contains NaN.

property name: str

Return a descriptive name for this constraint.

to_config() dict

Convert to configuration dict with explicit action type.

class ionworkspipeline.data_fits.models.metrics.LessThan(metric: BaseMetric, value: float, penalty: float = 1000000.0)

Constraint that the metric value should be less than or equal to a threshold.

Returns 0 when the constraint is satisfied (value <= threshold), and a large penalty value when violated (value > threshold).

Parameters

metricBaseMetric

The metric to constrain.

valuefloat

The upper bound threshold. Values above this trigger penalties.

penaltyfloat, optional

The penalty value to scale the metric by when the constraint is violated. This contribution is penalty * (1 + violation) to include both a steep violation and a proportion term to directionally inform the optimiser. Default is 1e6.

Example

Constrain maximum temperature to <= 50°C:

LessThan(Maximum("Temperature [K]"), 323.15)

Extends: ionworkspipeline.data_fits.models.metrics.actions.BaseAction

apply(metric_value: float | ndarray) float

Apply the constraint transformation to a metric value.

Returns

float

0.0 if constraint is satisfied (value <= threshold), penalty * (1 + violation) if violated (value > threshold), np.inf if the metric value is empty or contains NaN.

property name: str

Return a descriptive name for this constraint.

to_config() dict

Convert to configuration dict with explicit action type.