Source code for pybamm.solvers.summary_variable
#
# Summary Variable class
#
from __future__ import annotations
from typing import Any
import numpy as np
import pybamm
[docs]
class SummaryVariables:
"""
Class for managing and calculating summary variables from a PyBaMM solution.
Summary variables are only calculated when simulations are run with PyBaMM
Experiments.
Parameters
----------
solution : :class:`pybamm.Solution`
The solution object to be used for creating the processed variables.
cycle_summary_variables : list[pybamm.SummaryVariables], optional
A list of cycle summary variables.
esoh_solver : :class:`pybamm.lithium_ion.ElectrodeSOHSolver`, optional
Solver for electrode state-of-health (eSOH) calculations.
user_inputs : dict, optional
Additional user inputs for calculations.
Attributes
----------
cycle_number : array[int]
Stores the cycle number for each saved cycle, for use when plotting.
Length is equal to the number of cycles in a solution.
"""
def __init__(
self,
solution: pybamm.Solution,
cycle_summary_variables: list[SummaryVariables] | None = None,
esoh_solver: pybamm.lithium_ion.ElectrodeSOHSolver | None = None,
user_inputs: dict[str, Any] | None = None,
):
self.user_inputs = user_inputs or {}
self.esoh_solver = esoh_solver
self._variables = {} # Store computed variables
self.cycle_number = np.array([])
model = solution.all_models[0]
self._possible_variables = model.summary_variables # minus esoh variables
self._esoh_variables = None # Store eSOH variable names
# Flag if eSOH calculations are needed
self.calc_esoh = (
self.esoh_solver is not None
and isinstance(model, pybamm.lithium_ion.BaseModel)
and model.options.electrode_types["negative"] == "porous"
and "Negative electrode capacity [A.h]" in model.variables
and "Positive electrode capacity [A.h]" in model.variables
)
# Initialize based on cycle information
if cycle_summary_variables:
self._initialize_for_cycles(cycle_summary_variables)
else:
self.first_state = solution.first_state
self.last_state = solution.last_state
self.cycles = None
def _initialize_for_cycles(self, cycle_summary_variables: list[SummaryVariables]):
"""Initialize attributes for when multiple cycles are provided."""
self.first_state = None
self.last_state = None
self.cycles = cycle_summary_variables
self.cycle_number = np.arange(1, len(self.cycles) + 1)
first_cycle = self.cycles[0]
self.calc_esoh = first_cycle.calc_esoh
self.esoh_solver = first_cycle.esoh_solver
self.user_inputs = first_cycle.user_inputs
@property
def all_variables(self) -> list[str]:
"""
Return names of all possible summary variables, including eSOH variables
if appropriate.
"""
try:
return self._all_variables
except AttributeError:
base_vars = self._possible_variables.copy()
base_vars.extend(
f"Change in {var[0].lower() + var[1:]}"
for var in self._possible_variables
)
if self.calc_esoh:
base_vars.extend(self.esoh_variables)
self._all_variables = base_vars
return self._all_variables
@property
def esoh_variables(self) -> list[str] | None:
"""Return names of all eSOH variables."""
if self.calc_esoh and self._esoh_variables is None:
esoh_model = self.esoh_solver._get_electrode_soh_sims_full().model
esoh_vars = list(esoh_model.variables.keys())
self._esoh_variables = esoh_vars
return self._esoh_variables
def __getitem__(self, key: str) -> float | list[float]:
"""
Access or compute a summary variable by its name.
Parameters
----------
key : str
The name of the variable
Returns
-------
float or list[float]
"""
if key in self._variables:
# return it if it exists
return self._variables[key]
elif key == "Cycle number":
return self.cycle_number
elif key not in self.all_variables:
# check it's listed as a summary variable
raise KeyError(f"Variable '{key}' is not a summary variable.")
else:
# otherwise create it, save it and then return it
if self.calc_esoh and key in self._esoh_variables:
self.update_esoh()
else:
base_key = key.removeprefix("Change in ")
base_key = base_key[0].upper() + base_key[1:]
# this will create 'X' and 'Change in x' at the same time
self.update(base_key)
return self._variables[key]
[docs]
def update(self, var: str):
"""Compute and store a variable and its change."""
var_lowercase = var[0].lower() + var[1:]
if self.cycles:
self._update_multiple_cycles(var, var_lowercase)
else:
self._update(var, var_lowercase)
def _update_multiple_cycles(self, var: str, var_lowercase: str):
"""Creates aggregated summary variables for where more than one cycle exists."""
var_cycle = [cycle[var] for cycle in self.cycles]
change_var_cycle = [
cycle[f"Change in {var_lowercase}"] for cycle in self.cycles
]
self._variables[var] = var_cycle
self._variables[f"Change in {var_lowercase}"] = change_var_cycle
def _update(self, var: str, var_lowercase: str):
"""Create variable `var` for a single cycle."""
data_first = self.first_state[var].data
data_last = self.last_state[var].data
self._variables[var] = data_last[0]
self._variables[f"Change in {var_lowercase}"] = data_last[0] - data_first[0]
[docs]
def update_esoh(self):
"""Create all aggregated eSOH variables"""
if self.cycles is not None:
var_cycle = [cycle._get_esoh_variables() for cycle in self.cycles]
aggregated_vars = {k: [] for k in var_cycle[0].keys()}
for cycle in var_cycle:
for k, v in cycle.items():
aggregated_vars[k].append(v)
self._variables.update(aggregated_vars)
else:
self._variables.update(self._get_esoh_variables())
def _get_esoh_variables(self) -> dict[str, float]:
"""Compute eSOH variables for a single solution."""
Q_n = self.last_state["Negative electrode capacity [A.h]"].data[0]
Q_p = self.last_state["Positive electrode capacity [A.h]"].data[0]
Q_Li = self.last_state["Total lithium capacity in particles [A.h]"].data[0]
all_inputs = {**self.user_inputs, "Q_n": Q_n, "Q_p": Q_p, "Q_Li": Q_Li}
try:
esoh_sol = self.esoh_solver.solve(inputs=all_inputs)
except pybamm.SolverError as error: # pragma: no cover
raise pybamm.SolverError(
"Could not solve for eSOH summary variables"
) from error
return esoh_sol
[docs]
def get_summary_variables(self):
"""
Computes and returns all the summary values + cycle number, as a dictionary
"""
return {k: self[k] for k in [*self.all_variables, "Cycle number"]}