Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# [Unreleased](https://github.com/pybamm-team/PyBaMM/)

## Breaking changes

- The composite electrode-SOH model's phase-capacity inputs are renamed `Q_n_1` -> `Primary: Negative electrode capacity [A.h]`, `Q_n_2` -> `Secondary: Negative electrode capacity [A.h]`, `Q_p_1` -> `Primary: Positive electrode capacity [A.h]`, and `Q_p_2` -> `Secondary: Positive electrode capacity [A.h]`. The numbered names matched the deprecated-MSMR pattern, so every composite solve emitted a `DeprecationWarning` for an internal input. Code solving a standalone `ElectrodeSOHComposite` simulation must use the new names. ([#5738](https://github.com/pybamm-team/PyBaMM/pull/5738))

## Bug fixes

- The `integration` nox session no longer installs the `pydiffsol` extra on macOS Intel CI runners, where it has no working build. ([#5726](https://github.com/pybamm-team/PyBaMM/pull/5726))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,25 +133,34 @@ def _get_prefix(electrode):
return "x"


def _get_phase_capacity_name(electrode, phase):
"""Get the input name for an electrode phase capacity."""
return f"{phase.capitalize()}: {electrode.capitalize()} electrode capacity [A.h]"


def _get_phase_capacity_input(electrode, phase):
"""Get the input parameter for an electrode phase capacity."""
return pybamm.InputParameter(_get_phase_capacity_name(electrode, phase))


def _get_electrode_capacity_equation(options, electrode):
"""
Build equation for electrode capacity in composite electrodes.

Returns Q = sum_i Q_i * (stoich_100_i - stoich_0_i) for all phases.
"""
prefix = _get_prefix(electrode)
e = electrode[0]
i_am_composite = check_if_composite(options, electrode)
stoich_variables = _get_stoich_variables(options)
direction = _get_direction(electrode)
Q_1 = pybamm.InputParameter(f"Q_{e}_1")
Q_1 = _get_phase_capacity_input(electrode, "primary")
Q = (
direction
* (stoich_variables[f"{prefix}_100_1"] - stoich_variables[f"{prefix}_0_1"])
* Q_1
)
if i_am_composite:
Q_2 = pybamm.InputParameter(f"Q_{e}_2")
Q_2 = _get_phase_capacity_input(electrode, "secondary")
Q += (
direction
* (stoich_variables[f"{prefix}_100_2"] - stoich_variables[f"{prefix}_0_2"])
Expand All @@ -168,20 +177,20 @@ def _get_cyclable_lithium_equation(options, soc="100"):
"""
x_soc_1 = pybamm.Variable(f"x_{soc}_1", bounds=(0, 1))
y_soc_1 = pybamm.Variable(f"y_{soc}_1", bounds=(0, 1))
Q_n_1 = pybamm.InputParameter("Q_n_1")
Q_p_1 = pybamm.InputParameter("Q_p_1")
lithium_primary_phases = Q_n_1 * x_soc_1 + Q_p_1 * y_soc_1
Q_n_prim = _get_phase_capacity_input("negative", "primary")
Q_p_prim = _get_phase_capacity_input("positive", "primary")
lithium_primary_phases = Q_n_prim * x_soc_1 + Q_p_prim * y_soc_1
lithium_secondary_phases = 0.0
is_positive_composite = check_if_composite(options, "positive")
is_negative_composite = check_if_composite(options, "negative")
if is_positive_composite:
Q_p_2 = pybamm.InputParameter("Q_p_2")
Q_p_sec = _get_phase_capacity_input("positive", "secondary")
y_soc_2 = pybamm.Variable(f"y_{soc}_2", bounds=(0, 1))
lithium_secondary_phases += Q_p_2 * y_soc_2
lithium_secondary_phases += Q_p_sec * y_soc_2
if is_negative_composite:
Q_n_2 = pybamm.InputParameter("Q_n_2")
Q_n_sec = _get_phase_capacity_input("negative", "secondary")
x_soc_2 = pybamm.Variable(f"x_{soc}_2", bounds=(0, 1))
lithium_secondary_phases += Q_n_2 * x_soc_2
lithium_secondary_phases += Q_n_sec * x_soc_2
return lithium_primary_phases + lithium_secondary_phases


Expand Down Expand Up @@ -470,18 +479,26 @@ def __init__(
)
elif initialization_method == "SOC":
soc_init = pybamm.InputParameter("SOC_init")
negative_soc = x_init_1 * pybamm.InputParameter("Q_n_1")
negative_soc = x_init_1 * _get_phase_capacity_input("negative", "primary")
if is_negative_composite:
x_init_2 = variables["x_init_2"]
negative_soc += x_init_2 * pybamm.InputParameter("Q_n_2")
negative_soc += x_init_2 * _get_phase_capacity_input(
"negative", "secondary"
)

negative_0_soc = x_0_1 * pybamm.InputParameter("Q_n_1")
negative_0_soc = x_0_1 * _get_phase_capacity_input("negative", "primary")
if is_negative_composite:
negative_0_soc += x_0_2 * pybamm.InputParameter("Q_n_2")
negative_0_soc += x_0_2 * _get_phase_capacity_input(
"negative", "secondary"
)

negative_100_soc = x_100_1 * pybamm.InputParameter("Q_n_1")
negative_100_soc = x_100_1 * _get_phase_capacity_input(
"negative", "primary"
)
if is_negative_composite:
negative_100_soc += x_100_2 * pybamm.InputParameter("Q_n_2")
negative_100_soc += x_100_2 * _get_phase_capacity_input(
"negative", "secondary"
)
self.algebraic[x_init_1] = (
(negative_soc - negative_0_soc) / (negative_100_soc - negative_0_soc)
) - soc_init
Expand Down Expand Up @@ -584,15 +601,18 @@ def solve_split(
is_positive_composite = check_if_composite(options, "positive")
is_negative_composite = check_if_composite(options, "negative")

Q_n_1 = parameter_values.evaluate(param.n.prim.Q_init, inputs=inputs)
Q_p_1 = parameter_values.evaluate(param.p.prim.Q_init, inputs=inputs)
Qs = {"Q_n_1": Q_n_1, "Q_p_1": Q_p_1}
Q_n_prim = parameter_values.evaluate(param.n.prim.Q_init, inputs=inputs)
Q_p_prim = parameter_values.evaluate(param.p.prim.Q_init, inputs=inputs)
Qs = {
_get_phase_capacity_name("negative", "primary"): Q_n_prim,
_get_phase_capacity_name("positive", "primary"): Q_p_prim,
}
if is_positive_composite:
Q_p_2 = parameter_values.evaluate(param.p.sec.Q_init, inputs=inputs)
Qs["Q_p_2"] = Q_p_2
Q_p_sec = parameter_values.evaluate(param.p.sec.Q_init, inputs=inputs)
Qs[_get_phase_capacity_name("positive", "secondary")] = Q_p_sec
if is_negative_composite:
Q_n_2 = parameter_values.evaluate(param.n.sec.Q_init, inputs=inputs)
Qs["Q_n_2"] = Q_n_2
Q_n_sec = parameter_values.evaluate(param.n.sec.Q_init, inputs=inputs)
Qs[_get_phase_capacity_name("negative", "secondary")] = Q_n_sec

Q_Li = parameter_values.evaluate(param.Q_Li_particles_init, inputs=inputs)

Expand Down Expand Up @@ -621,8 +641,12 @@ def solve_split(
f"{initial_value!r} of type {type(initial_value).__name__}"
)

Q_n_total = Q_n_1 + (Qs.get("Q_n_2", 0))
Q_p_total = Q_p_1 + (Qs.get("Q_p_2", 0))
Q_n_total = Q_n_prim + (
Qs.get(_get_phase_capacity_name("negative", "secondary"), 0)
)
Q_p_total = Q_p_prim + (
Qs.get(_get_phase_capacity_name("positive", "secondary"), 0)
)

primary_options = _get_primary_only_options(options)
# _ElectrodeSOH uses get_equilibrium_direction internally for the equilibrium
Expand Down Expand Up @@ -819,15 +843,18 @@ def solve_full(
is_positive_composite = check_if_composite(options, "positive")
is_negative_composite = check_if_composite(options, "negative")

Q_n_1 = parameter_values.evaluate(param.n.prim.Q_init, inputs=inputs)
Q_p_1 = parameter_values.evaluate(param.p.prim.Q_init, inputs=inputs)
Qs = {"Q_n_1": Q_n_1, "Q_p_1": Q_p_1}
Q_n_prim = parameter_values.evaluate(param.n.prim.Q_init, inputs=inputs)
Q_p_prim = parameter_values.evaluate(param.p.prim.Q_init, inputs=inputs)
Qs = {
_get_phase_capacity_name("negative", "primary"): Q_n_prim,
_get_phase_capacity_name("positive", "primary"): Q_p_prim,
}
if is_positive_composite:
Q_p_2 = parameter_values.evaluate(param.p.sec.Q_init, inputs=inputs)
Qs["Q_p_2"] = Q_p_2
Q_p_sec = parameter_values.evaluate(param.p.sec.Q_init, inputs=inputs)
Qs[_get_phase_capacity_name("positive", "secondary")] = Q_p_sec
if is_negative_composite:
Q_n_2 = parameter_values.evaluate(param.n.sec.Q_init, inputs=inputs)
Qs["Q_n_2"] = Q_n_2
Q_n_sec = parameter_values.evaluate(param.n.sec.Q_init, inputs=inputs)
Qs[_get_phase_capacity_name("negative", "secondary")] = Q_n_sec

Q_Li = parameter_values.evaluate(param.Q_Li_particles_init, inputs=inputs)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

import contextlib
import warnings

import pytest

Expand Down Expand Up @@ -552,6 +553,41 @@ def test_chen2020_composite_solve_with_hysteresis(self):
f"Stoichiometry {key} out of bounds: {results[key]}"
)

def test_phase_capacity_input_names(self):
options = {"particle phases": ("2", "1")}
simulation = pybamm.Simulation(
pybamm.lithium_ion.ElectrodeSOHComposite(options),
parameter_values=pybamm.ParameterValues("Chen2020_composite"),
)
simulation.build()

capacity_names = {
parameter.name
for parameter in simulation.built_model.input_parameters
if parameter.name.endswith("electrode capacity [A.h]")
}

assert capacity_names == {
"Primary: Negative electrode capacity [A.h]",
"Secondary: Negative electrode capacity [A.h]",
"Primary: Positive electrode capacity [A.h]",
}

def test_phase_capacity_inputs_are_not_deprecated_msmr_names(self):
pvals = pybamm.ParameterValues("Chen2020_composite")
options = {"particle phases": ("2", "1")}

with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
pybamm.lithium_ion.get_initial_stoichiometries_composite(
0.5, pvals, options=options
)

renamed = [
w for w in caught if "host site occupancy capacity" in str(w.message)
]
assert renamed == []


class TestElectrodeSOHMSMR:
def test_known_solution(self, options):
Expand Down