Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/esoh model update #2109

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4de6073
ElectrodeSOH with two stage solving
anoushka2000 Jun 16, 2022
8603def
update notebook for eSOH model
anoushka2000 Jun 16, 2022
44fc92c
Merge remote-tracking branch 'origin/develop' into feature/esoh_model…
anoushka2000 Jun 16, 2022
9025dac
update notebook
anoushka2000 Jun 16, 2022
d353660
Merge remote-tracking branch 'origin/develop' into feature/esoh_model…
anoushka2000 Jun 22, 2022
fce929f
solve eSOH as ElectrodeSOHx100 and ElectrodeSOHC models
anoushka2000 Jun 24, 2022
60ff1d0
update electrode soh test
anoushka2000 Jun 24, 2022
b302bfc
Cn, Cp and n_Li from inputs
anoushka2000 Jun 24, 2022
97ee9d1
formatting
anoushka2000 Jun 24, 2022
2c582bf
update simulation to set up both esoh models if calc_esoh
anoushka2000 Jun 24, 2022
45947d4
Merge branch 'pybamm-team:develop' into feature/esoh_model_update
anoushka2000 Jun 27, 2022
8948968
modify get_cycle_summary_variables for two eSOH models
anoushka2000 Jun 29, 2022
d59390d
Merge remote-tracking branch 'origin/feature/esoh_model_update' into …
anoushka2000 Jun 29, 2022
7f1a238
Merge remote-tracking branch 'origin/develop' into feature/esoh_model…
anoushka2000 Jun 30, 2022
321a740
update simulation, notebook and tests
anoushka2000 Jun 30, 2022
fe8ba6f
black and flake8
anoushka2000 Jun 30, 2022
3066711
line length
anoushka2000 Jul 8, 2022
6697b37
Merge branch 'develop' into feature/esoh_model_update
anoushka2000 Jul 8, 2022
d74b661
fix upstream merge
anoushka2000 Jul 8, 2022
cf532ca
get value from Scalar x_100_upper_limit
anoushka2000 Jul 8, 2022
88447e0
allow data for both electrodes OCP and fix get_initial_stoichiometries
anoushka2000 Jul 9, 2022
c687aec
fix get_initial_stoichiometries (parameter_values as kwarg)
anoushka2000 Jul 10, 2022
af3578e
correct case where only OCPn_data
anoushka2000 Jul 10, 2022
74704a6
correct model order in get_initial_stoichiometries
anoushka2000 Jul 10, 2022
4e93bcb
flake8
anoushka2000 Jul 10, 2022
7163ed9
case for both OCPn_data and OCPp_data
anoushka2000 Jul 11, 2022
79ec839
check if initial values are float or pybamm.Scalar
anoushka2000 Jul 12, 2022
dea700c
update docs
anoushka2000 Jul 13, 2022
6d69982
update notebooks
anoushka2000 Jul 14, 2022
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
7 changes: 6 additions & 1 deletion docs/source/models/lithium_ion/electrode_soh.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
Electrode SOH models
====================

.. autoclass:: pybamm.lithium_ion.ElectrodeSOH
.. autoclass:: pybamm.lithium_ion.ElectrodeSOHx100
:members:

.. autoclass:: pybamm.lithium_ion.ElectrodeSOHC
:members:

.. autofunction:: pybamm.lithium_ion.solve_electrode_soh

.. autoclass:: pybamm.lithium_ion.ElectrodeSOHHalfCell
:members:

Expand Down
153 changes: 100 additions & 53 deletions examples/notebooks/models/electrode-state-of-health.ipynb

Large diffs are not rendered by default.

692 changes: 347 additions & 345 deletions examples/notebooks/simulating-long-experiments.ipynb

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion pybamm/models/full_battery_models/lithium_ion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
# Root of the lithium-ion models module.
#
from .base_lithium_ion_model import BaseModel
from .electrode_soh import ElectrodeSOH, get_initial_stoichiometries
from .electrode_soh import (
ElectrodeSOHx100,
ElectrodeSOHC,
solve_electrode_soh,
get_initial_stoichiometries,
)
from .electrode_soh_half_cell import ElectrodeSOHHalfCell
from .spm import SPM
from .spme import SPMe
Expand Down
238 changes: 168 additions & 70 deletions pybamm/models/full_battery_models/lithium_ion/electrode_soh.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,87 +5,84 @@
import numpy as np


class ElectrodeSOH(pybamm.BaseModel):
"""Model to calculate electrode-specific SOH, from [1]_.
This model is mainly for internal use, to calculate summary variables in a
simulation.

.. math::
n_{Li} = \\frac{3600}{F}(y_{100}C_p + x_{100}C_n),
.. math::
V_{max} = U_p(y_{100}) - U_n(x_{100}),
.. math::
V_{min} = U_p(y_{0}) - U_n(x_{0}),
.. math::
x_0 = x_{100} - \\frac{C}{C_n},
.. math::
y_0 = y_{100} + \\frac{C}{C_p}.

References
----------
.. [1] Mohtat, P., Lee, S., Siegel, J. B., & Stefanopoulou, A. G. (2019). Towards
better estimability of electrode-specific state of health: Decoding the cell
expansion. Journal of Power Sources, 427, 101-111.

**Extends:** :class:`pybamm.BaseModel`
"""

def __init__(self, name="Electrode-specific SOH model"):
class ElectrodeSOHx100(pybamm.BaseModel):
def __init__(self, name="ElectrodeSOHx100 model"):
pybamm.citations.register("Mohtat2019")
super().__init__(name)

param = pybamm.LithiumIonParameters()

Un = param.n.U_dimensional
Up = param.p.U_dimensional
T_ref = param.T_ref

x_100 = pybamm.Variable("x_100", bounds=(0, 1))
C = pybamm.Variable("C", bounds=(0, np.inf))

V_max = pybamm.InputParameter("V_max")
V_min = pybamm.InputParameter("V_min")
C_n = pybamm.InputParameter("C_n")
C_p = pybamm.InputParameter("C_p")
n_Li = pybamm.InputParameter("n_Li")
V_max = pybamm.InputParameter("V_max")
Cn = pybamm.InputParameter("C_n")
Cp = pybamm.InputParameter("C_p")
x_100_init = pybamm.InputParameter("x_100_init")

y_100 = (n_Li * param.F / 3600 - x_100 * C_n) / C_p
x_0 = x_100 - C / C_n
y_0 = y_100 + C / C_p
x_100 = pybamm.Variable("x_100")

y_100 = (n_Li * param.F / 3600 - x_100 * Cn) / Cp

self.algebraic = {
x_100: Up(y_100, T_ref) - Un(x_100, T_ref) - V_max,
C: Up(y_0, T_ref) - Un(x_0, T_ref) - V_min,
}

# initial guess must be chosen such that 0 < x_0, y_0, x_100, y_100 < 1
# First guess for x_100
x_100_init = 0.85
# Make sure x_0 = x_100 - C/C_n > 0
C_init = param.Q
C_init = pybamm.minimum(C_n * x_100_init - 0.1, C_init)
# Make sure y_100 > 0
# x_100_init = pybamm.minimum(n_Li * param.F / 3600 / C_n - 0.01, x_100_init)
self.initial_conditions = {x_100: x_100_init, C: C_init}
self.initial_conditions = {x_100: x_100_init}

self.variables = {"x_100": x_100, "y_100": y_100}

@property
def default_solver(self):
# Use AlgebraicSolver as CasadiAlgebraicSolver gives unnecessary warnings
return pybamm.AlgebraicSolver()


class ElectrodeSOHC(pybamm.BaseModel):
def __init__(self, name="ElectrodeSOHC model"):
pybamm.citations.register("Mohtat2019")
super().__init__(name)

param = pybamm.LithiumIonParameters()

Un = param.n.U_dimensional
Up = param.p.U_dimensional
T_ref = param.T_ref

n_Li = pybamm.InputParameter("n_Li")
V_min = pybamm.InputParameter("V_min")
Cn = pybamm.InputParameter("C_n")
Cp = pybamm.InputParameter("C_p")
x_100 = pybamm.InputParameter("x_100")
y_100 = pybamm.InputParameter("y_100")

C = pybamm.Variable("C")
x_0 = x_100 - C / Cn
y_0 = y_100 + C / Cp

self.algebraic = {C: Up(y_0, T_ref) - Un(x_0, T_ref) - V_min}

self.initial_conditions = {C: pybamm.minimum(Cn * x_100 - 0.1, param.Q)}

self.variables = {
"x_100": x_100,
"y_100": y_100,
"C": C,
"x_0": x_0,
"y_0": y_0,
"Un(x_100)": Un(x_100, T_ref),
"Un(x_0)": Un(x_0, T_ref),
"Up(y_100)": Up(y_100, T_ref),
"Up(y_0)": Up(y_0, T_ref),
"Up(y_100) - Un(x_100)": Up(y_100, T_ref) - Un(x_100, T_ref),
"Up(y_0) - Un(x_0)": Up(y_0, T_ref) - Un(x_0, T_ref),
"n_Li_100": 3600 / param.F * (y_100 * C_p + x_100 * C_n),
"n_Li_0": 3600 / param.F * (y_0 * C_p + x_0 * C_n),
"Up(y_100) - Un(x_100)": Up(y_100, T_ref) - Un(x_100, T_ref),
"n_Li_100": 3600 / param.F * (y_100 * Cp + x_100 * Cn),
"n_Li_0": 3600 / param.F * (y_0 * Cp + x_0 * Cn),
"n_Li": n_Li,
"C_n": C_n,
"C_p": C_p,
"C_n * (x_100 - x_0)": C_n * (x_100 - x_0),
"C_p * (y_100 - y_0)": C_p * (y_0 - y_100),
"x_100": x_100,
"y_100": y_100,
"C_n": Cn,
"C_p": Cp,
"C_n * (x_100 - x_0)": Cn * (x_100 - x_0),
"C_p * (y_100 - y_0)": Cp * (y_0 - y_100),
}

@property
Expand All @@ -94,6 +91,105 @@ def default_solver(self):
return pybamm.AlgebraicSolver()


def solve_electrode_soh(x100_sim, C_sim, inputs, parameter_values):
param = pybamm.LithiumIonParameters()

Vmax = inputs["V_max"]
Vmin = inputs["V_min"]
Cp = inputs["C_p"]
Cn = inputs["C_n"]
n_Li = inputs["n_Li"]

y_100_min = 1e-6
x_100_upper_limit = ((n_Li * param.F) / 3600 - y_100_min * Cp) / Cn

OCPp_data = isinstance(parameter_values["Positive electrode OCP [V]"], tuple)
OCPn_data = isinstance(parameter_values["Negative electrode OCP [V]"], tuple)

if OCPp_data:
y_100_min = np.min(parameter_values["Positive electrode OCP [V]"][1][0])
y_100_max = np.max(parameter_values["Positive electrode OCP [V]"][1][0])

x_100_upper_limit = (
n_Li * pybamm.constants.F.value / 3600 - y_100_min * Cp
) / Cn

x_100_lower_limit = (
n_Li * pybamm.constants.F.value / 3600 - y_100_max * Cp
) / Cn

if OCPn_data:
V_lower_bound = min(
parameter_values["Positive electrode OCP [V]"][1][1]
) - max(parameter_values["Negative electrode OCP [V]"][1][1])

V_upper_bound = max(
parameter_values["Positive electrode OCP [V]"][1][1]
) - min(parameter_values["Negative electrode OCP [V]"][1][1])
else:

V_lower_bound = (
min(parameter_values["Positive electrode OCP [V]"][1][1])
- parameter_values["Negative electrode OCP [V]"](
x_100_upper_limit
).evaluate()
)

V_upper_bound = (
max(parameter_values["Positive electrode OCP [V]"][1][1])
- parameter_values["Negative electrode OCP [V]"](
x_100_lower_limit
).evaluate()
)

elif OCPn_data:
x_100_min = np.min(parameter_values["Negative electrode OCP [V]"][1][0])
x_100_max = np.max(parameter_values["Negative electrode OCP [V]"][1][0])

y_100_upper_limit = (
n_Li * pybamm.constants.F.value / 3600 - x_100_min * Cp
) / Cn

y_100_lower_limit = (
n_Li * pybamm.constants.F.value / 3600 - x_100_max * Cp
) / Cn

V_lower_bound = parameter_values["Positive electrode OCP [V]"](
y_100_lower_limit
).evaluate() - max(parameter_values["Negative electrode OCP [V]"][1][1])

V_upper_bound = parameter_values["Positive electrode OCP [V]"](
y_100_upper_limit
).evaluate() - min(parameter_values["Negative electrode OCP [V]"][1][1])

if OCPp_data or OCPn_data:

if V_lower_bound > Vmin:
raise (
ValueError(
"Initial values are outside bounds of OCP data in parameters."
)
)

if V_upper_bound < Vmax:
raise (
ValueError(
"Initial values are outside bounds of OCP data in parameters."
)
)

if not isinstance(x_100_upper_limit, float):
x_100_upper_limit = x_100_upper_limit.value

inputs.update({"x_100_init": min(0.99 * x_100_upper_limit, 1 - 1e-6)})

x100_sol = x100_sim.solve([0], inputs=inputs)
inputs["x_100"] = x100_sol["x_100"].data[0]
inputs["y_100"] = x100_sol["y_100"].data[0]
C_sol = C_sim.solve([0], inputs=inputs)
return C_sol


def get_initial_stoichiometries(initial_soc, parameter_values):
"""
Calculate initial stoichiometries to start off the simulation at a particular
Expand All @@ -116,28 +212,30 @@ def get_initial_stoichiometries(initial_soc, parameter_values):
if initial_soc < 0 or initial_soc > 1:
raise ValueError("Initial SOC should be between 0 and 1")

model = pybamm.lithium_ion.ElectrodeSOH()

param = pybamm.LithiumIonParameters()
sim = pybamm.Simulation(model, parameter_values=parameter_values)

V_min = parameter_values.evaluate(param.voltage_low_cut_dimensional)
V_max = parameter_values.evaluate(param.voltage_high_cut_dimensional)
C_n = parameter_values.evaluate(param.n.cap_init)
C_p = parameter_values.evaluate(param.p.cap_init)
n_Li = parameter_values.evaluate(param.n_Li_particles_init)

model_x100 = ElectrodeSOHx100()
model_C = ElectrodeSOHC()

x100_sim = pybamm.Simulation(model_x100, parameter_values=parameter_values)
C_sim = pybamm.Simulation(model_C, parameter_values=parameter_values)

inputs = {
"V_min": V_min,
"V_max": V_max,
"C_n": C_n,
"C_p": C_p,
"n_Li": n_Li,
}

# Solve the model and check outputs
sol = sim.solve(
[0],
inputs={
"V_min": V_min,
"V_max": V_max,
"C_n": C_n,
"C_p": C_p,
"n_Li": n_Li,
},
)
sol = solve_electrode_soh(x100_sim, C_sim, inputs, parameter_values)

x_0 = sol["x_0"].data[0]
y_0 = sol["y_0"].data[0]
Expand Down
23 changes: 15 additions & 8 deletions pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,8 +651,10 @@ def solve(
raise ValueError(
"starting_solution can only be provided if simulating an Experiment"
)
if self.operating_mode == "without experiment" or isinstance(
self.model, pybamm.lithium_ion.ElectrodeSOH
if (
self.operating_mode == "without experiment"
or isinstance(self.model, pybamm.lithium_ion.ElectrodeSOHx100)
or isinstance(self.model, pybamm.lithium_ion.ElectrodeSOHC)
):
if t_eval is None:
raise pybamm.SolverError(
Expand Down Expand Up @@ -729,12 +731,17 @@ def solve(

# Set up eSOH model (for summary variables)
if calc_esoh is True:
esoh_model = pybamm.lithium_ion.ElectrodeSOH()
esoh_sim = pybamm.Simulation(
esoh_model, parameter_values=self.parameter_values
x100_model = pybamm.lithium_ion.ElectrodeSOHx100()
x100_sim = pybamm.Simulation(
x100_model, parameter_values=self.parameter_values
)
C_model = pybamm.lithium_ion.ElectrodeSOHC()
C_sim = pybamm.Simulation(
C_model, parameter_values=self.parameter_values
)
esoh_sims = [x100_sim, C_sim]
else:
esoh_sim = None
esoh_sims = None

if starting_solution is None:
starting_solution_cycles = []
Expand All @@ -745,7 +752,7 @@ def solve(
cycle_solution,
cycle_sum_vars,
cycle_first_state,
) = pybamm.make_cycle_solution(starting_solution.steps, esoh_sim, True)
) = pybamm.make_cycle_solution(starting_solution.steps, esoh_sims, True)
starting_solution_cycles = [cycle_solution]
starting_solution_summary_variables = [cycle_sum_vars]
starting_solution_first_states = [cycle_first_state]
Expand Down Expand Up @@ -870,7 +877,7 @@ def solve(
if len(steps) > 0:
cycle_sol = pybamm.make_cycle_solution(
anoushka2000 marked this conversation as resolved.
Show resolved Hide resolved
steps,
esoh_sim,
esoh_sims,
save_this_cycle=save_this_cycle,
)
cycle_solution, cycle_sum_vars, cycle_first_state = cycle_sol
Expand Down
Loading