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

Plett OCP Hysteresis Model #3593

Merged
merged 58 commits into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
01dc550
first tests passed
mleot Dec 5, 2023
809af01
updated citations
mleot Dec 6, 2023
a9ce212
clean up ocp script
mleot Dec 6, 2023
13875ce
might be step in wrong direction
mleot Dec 7, 2023
80a03d6
now passes TestDFN unit test well posed
tanner-leo Dec 16, 2023
7d2026c
now passes TestDFN unit test well posed
tanner-leo Dec 16, 2023
551b023
passing MPM integration test
tanner-leo Dec 21, 2023
ab3f87d
nearly passing MPM + DFN tests
tanner-leo Dec 21, 2023
a9f09d0
Merge branch 'pr/mleot/3593' into pr/mleot/3593-1
tanner-leo Dec 21, 2023
fc91c08
Merge branch 'pybamm-team:develop' into plett-ocp-hysteresis
mleot Apr 5, 2024
9388adf
style: pre-commit fixes
pre-commit-ci[bot] Apr 5, 2024
7d4aa19
add citations
mleot Apr 17, 2024
2463e2a
style: pre-commit fixes
pre-commit-ci[bot] Apr 17, 2024
1bcb524
remove unessesary citation
mleot Apr 17, 2024
3e89fb4
passes all unit tests
mleot Apr 17, 2024
27e075f
style: pre-commit fixes
pre-commit-ci[bot] Apr 17, 2024
2e7d81b
move Q from lithium ion function to submodel coupled variable
mleot Apr 17, 2024
5ae648b
Merge branch 'plett-ocp-hysteresis' of https://github.com/mleot/pybam…
mleot Apr 17, 2024
e2fa1ed
clean up changes
mleot Apr 17, 2024
9b0a4aa
style: pre-commit fixes
pre-commit-ci[bot] Apr 17, 2024
e37031c
Merge branch 'develop' into plett-ocp-hysteresis
mleot Apr 17, 2024
2ed9dea
working example notebook and temporary revert to dQ as a function
mleot Apr 18, 2024
c4f96c6
style: pre-commit fixes
pre-commit-ci[bot] Apr 18, 2024
5fd1555
revert to functional form of Q
mleot Apr 18, 2024
957755e
renaming to dchs
mleot Apr 18, 2024
3324f40
Merge branch 'develop' into plett-ocp-hysteresis
mleot Apr 18, 2024
d0be637
clean up
mleot Apr 18, 2024
c2825aa
update docs build script
mleot Apr 19, 2024
a16283a
update notebook
mleot Apr 19, 2024
cc64fb4
compatabilize composite model
mleot Apr 19, 2024
7952dbd
add test
mleot Apr 19, 2024
0ad1869
style: pre-commit fixes
pre-commit-ci[bot] Apr 19, 2024
9e46865
remove unessesary statements
mleot Apr 19, 2024
746f811
style: pre-commit fixes
pre-commit-ci[bot] Apr 19, 2024
e9dd55f
remove unused
mleot Apr 19, 2024
4f8bbbc
Merge branch 'develop' into plett-ocp-hysteresis
mleot Apr 19, 2024
69beb4e
Merge branch 'develop' into plett-ocp-hysteresis
mleot Apr 30, 2024
d379fb7
pre-compatibilize psd+composite electrode model
mleot Apr 30, 2024
00a2e8e
style: pre-commit fixes
pre-commit-ci[bot] Apr 30, 2024
8ad8214
update notebook name
mleot May 2, 2024
2f947d9
rename to Wycisk from DCHS
mleot May 2, 2024
9566f50
update parameters required & names
mleot May 2, 2024
57240b8
Merge remote-tracking branch 'upstream/develop' into pr/mleot/3593
mleot May 2, 2024
ca414cb
Merge branch 'develop' into plett-ocp-hysteresis
mleot May 2, 2024
668c5fe
Merge branch 'plett-ocp-hysteresis' of https://github.com/mleot/pybam…
mleot May 2, 2024
eb2fe95
change if statement for code cov
mleot May 2, 2024
647be7b
add docs
mleot May 3, 2024
ec466f0
style: pre-commit fixes
pre-commit-ci[bot] May 3, 2024
6cab440
Merge branch 'develop' into plett-ocp-hysteresis
mleot May 3, 2024
e72f799
update toctree
mleot May 3, 2024
f02551e
add to changelog
mleot May 5, 2024
48fe678
Merge branch 'develop' into plett-ocp-hysteresis
mleot May 6, 2024
31b7115
move functions inside submodel
mleot May 7, 2024
66f0ef1
Merge branch 'develop' into plett-ocp-hysteresis
mleot May 7, 2024
1a8268c
fix change requests
mleot May 9, 2024
5c169e2
Merge branch 'develop' into plett-ocp-hysteresis
mleot May 9, 2024
f6fc3ad
getting rid of functional form Q
mleot May 11, 2024
d8f451e
Merge branch 'develop' into plett-ocp-hysteresis
mleot May 11, 2024
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Renamed "electrode diffusivity" to "particle diffusivity" as a non-breaking change with a deprecation warning ([#3624](https://github.com/pybamm-team/PyBaMM/pull/3624))
- Add support for BPX version 0.4.0 which allows for blended electrodes and user-defined parameters in BPX([#3414](https://github.com/pybamm-team/PyBaMM/pull/3414))
- Added `by_submodel` feature in `print_parameter_info` method to allow users to print parameters and types of submodels in a tabular and readable format ([#3628](https://github.com/pybamm-team/PyBaMM/pull/3628))
- Added `WyciskOpenCircuitPotential` for differential capacity hysteresis state open-circuit potential submodel ([#3593](https://github.com/pybamm-team/PyBaMM/pull/3593))

## Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ Open-circuit potential models
current_sigmoid_ocp
single_ocp
msmr_ocp
wycisk_ocp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Wycisk Open Circuit Potential
=============================

.. autoclass:: pybamm.open_circuit_potential.WyciskOpenCircuitPotential
:members:
1 change: 1 addition & 0 deletions docs/source/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ The notebooks are organised into subfolders, and can be viewed in the galleries
notebooks/models/compare-particle-diffusion-models.ipynb
notebooks/models/composite_particle.ipynb
notebooks/models/coupled-degradation.ipynb
notebooks/models/differential-capacity-hysteresis-state.ipynb
notebooks/models/DFN-with-particle-size-distributions.ipynb
notebooks/models/DFN.ipynb
notebooks/models/electrode-state-of-health.ipynb
Expand Down

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions pybamm/CITATIONS.bib
Original file line number Diff line number Diff line change
Expand Up @@ -691,3 +691,16 @@ @article{landesfeind2019temperature
year={2019},
publisher={The Electrochemical Society}
}

@article{Wycisk2022,
title = {Modified Plett-model for modeling voltage hysteresis in lithium-ion cells},
journal = {Journal of Energy Storage},
volume = {52},
pages = {105016},
year = {2022},
issn = {2352-152X},
doi = {https://doi.org/10.1016/j.est.2022.105016},
url = {https://www.sciencedirect.com/science/article/pii/S2352152X22010192},
author = {Dominik Wycisk and Marc Oldenburger and Marc Gerry Stoye and Toni Mrkonjic and Arnulf Latz},
keywords = {Lithium-ion battery, Voltage hysteresis, Plett-model, Silicon–graphite anode},
}
4 changes: 2 additions & 2 deletions pybamm/models/full_battery_models/base_battery_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class BatteryModelOptions(pybamm.FuzzyDict):
reactions.
* "open-circuit potential" : str
Sets the model for the open circuit potential. Can be "single"
(default), "current sigmoid", or "MSMR". If "MSMR" then the "particle"
(default), "current sigmoid", "Wycisk", or "MSMR". If "MSMR" then the "particle"
option must also be "MSMR". A 2-tuple can be provided for different
behaviour in negative and positive electrodes.
* "operating mode" : str
Expand Down Expand Up @@ -263,7 +263,7 @@ def __init__(self, extra_options):
"stress and reaction-driven",
],
"number of MSMR reactions": ["none"],
"open-circuit potential": ["single", "current sigmoid", "MSMR"],
"open-circuit potential": ["single", "current sigmoid", "MSMR", "Wycisk"],
"operating mode": [
"current",
"voltage",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ def set_open_circuit_potential_submodel(self):
ocp_model = ocp_submodels.SingleOpenCircuitPotential
elif ocp_option == "current sigmoid":
ocp_model = ocp_submodels.CurrentSigmoidOpenCircuitPotential
elif ocp_option == "Wycisk":
pybamm.citations.register("Wycisk2022")
ocp_model = ocp_submodels.WyciskOpenCircuitPotential
elif ocp_option == "MSMR":
ocp_model = ocp_submodels.MSMROpenCircuitPotential
self.submodels[f"{domain} {phase} open-circuit potential"] = ocp_model(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from .single_ocp import SingleOpenCircuitPotential
from .current_sigmoid_ocp import CurrentSigmoidOpenCircuitPotential
from .msmr_ocp import MSMROpenCircuitPotential
from .wycisk_ocp import WyciskOpenCircuitPotential
160 changes: 160 additions & 0 deletions pybamm/models/submodels/interface/open_circuit_potential/wycisk_ocp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#
# from Wycisk 2022
#
import pybamm
from . import BaseOpenCircuitPotential


class WyciskOpenCircuitPotential(BaseOpenCircuitPotential):
mleot marked this conversation as resolved.
Show resolved Hide resolved
"""
Class for open-circuit potential with hysteresis based on the approach outlined by Wycisk :footcite:t:'Wycisk2022'.
This approach employs a differential capacity hysteresis state variable. The decay and switching of the hysteresis state
is tunable via two additional parameters. The hysteresis state is updated based on the current and the differential capacity.
"""

def get_fundamental_variables(self):
domain, Domain = self.domain_Domain
phase_name = self.phase_name
h = pybamm.Variable(
f"{Domain} electrode {phase_name}hysteresis state",
domains={
"primary": f"{domain} electrode",
"secondary": "current collector",
},
)
return {
f"{Domain} electrode {phase_name}hysteresis state": h,
}

def get_coupled_variables(self, variables):
domain, Domain = self.domain_Domain
phase_name = self.phase_name
phase = self.phase

if self.reaction == "lithium-ion main":
T = variables[f"{Domain} electrode temperature [K]"]
h = variables[f"{Domain} electrode {phase_name}hysteresis state"]

# For "particle-size distribution" models, take distribution version
# of c_s_surf that depends on particle size.
domain_options = getattr(self.options, domain)
if domain_options["particle size"] == "distribution":
sto_surf = variables[
f"{Domain} {phase_name}particle surface stoichiometry distribution"
]
# If variable was broadcast, take only the orphan
if isinstance(sto_surf, pybamm.Broadcast) and isinstance(
T, pybamm.Broadcast
):
sto_surf = sto_surf.orphans[0]
T = T.orphans[0]
T = pybamm.PrimaryBroadcast(T, [f"{domain} {phase_name}particle size"])
h = pybamm.PrimaryBroadcast(h, [f"{domain} {phase_name}particle size"])
else:
sto_surf = variables[
f"{Domain} {phase_name}particle surface stoichiometry"
]
# If variable was broadcast, take only the orphan
if isinstance(sto_surf, pybamm.Broadcast) and isinstance(
T, pybamm.Broadcast
):
sto_surf = sto_surf.orphans[0]
T = T.orphans[0]

variables[
f"{Domain} electrode {phase_name}hysteresis state distribution"
] = h

# Bulk OCP is from the average SOC and temperature
sto_bulk = variables[f"{Domain} electrode {phase_name}stoichiometry"]
c_scale = self.phase_param.c_max
variables[f"Total lithium in {phase} phase in {domain} electrode [mol]"] = (
sto_bulk * c_scale
) # c_s_vol * L * A

ocp_surf_eq = self.phase_param.U(sto_surf, T)
variables[f"{Domain} electrode {phase_name}equilibrium OCP [V]"] = (
ocp_surf_eq
)

T_bulk = pybamm.xyz_average(pybamm.size_average(T))
ocp_bulk_eq = self.phase_param.U(sto_bulk, T_bulk)
variables[f"{Domain} electrode {phase_name}bulk equilibrium OCP [V]"] = (
ocp_bulk_eq
)

H = self.phase_param.hysteresis(sto_surf)
variables[f"{Domain} electrode {phase_name}OCP hysteresis [V]"] = H

dU = self.phase_param.U(sto_surf, T_bulk).diff(sto_surf)
dQ = self.phase_param.Q(sto_surf).diff(sto_surf)
dQdU = dQ / dU
variables[
f"{Domain} electrode {phase_name}differential capacity [A.s.V-1]"
] = dQdU

H_x_av = pybamm.x_average(H)
h_x_av = pybamm.x_average(h)
variables[f"X-averaged {domain} electrode {phase_name}hysteresis state"] = (
h_x_av
)

# check if psd
if domain_options["particle size"] == "distribution":
# should always be true
if f"{domain} particle size" in sto_surf.domains["primary"]:
# check if MPM Model
if "current collector" in sto_surf.domains["secondary"]:
ocp_surf = ocp_surf_eq + H_x_av * h_x_av
# must be DFN with PSD model
elif (
f"{domain} electrode" in sto_surf.domains["secondary"]
or f"{domain} {phase_name}particle size"
in sto_surf.domains["primary"]
):
ocp_surf = ocp_surf_eq + H * h
# must not be a psd
else:
ocp_surf = ocp_surf_eq + H * h

H_s_av = pybamm.size_average(H_x_av)
h_s_av = pybamm.size_average(h_x_av)

ocp_bulk = ocp_bulk_eq + H_s_av * h_s_av

dUdT = self.phase_param.dUdT(sto_surf)

variables.update(self._get_standard_ocp_variables(ocp_surf, ocp_bulk, dUdT))
return variables

def set_rhs(self, variables):
domain, Domain = self.domain_Domain
phase_name = self.phase_name

current = variables[
f"{Domain} electrode {phase_name}interfacial current density [A.m-2]"
]
# check if composite or not
if phase_name != "":
Q_cell = variables[f"{Domain} electrode {phase_name}phase capacity [A.h]"]
else:
Q_cell = variables[f"{Domain} electrode capacity [A.h]"]

dQdU = variables[
f"{Domain} electrode {phase_name}differential capacity [A.s.V-1]"
]
dQdU = dQdU.orphans[0]
K = self.phase_param.hysteresis_decay
K_x = self.phase_param.hysteresis_switch
mleot marked this conversation as resolved.
Show resolved Hide resolved
h = variables[f"{Domain} electrode {phase_name}hysteresis state"]

dhdt = (
K * (current / (Q_cell * (dQdU**K_x))) * (1 - pybamm.sign(current) * h)
) #! current is backwards for a halfcell
self.rhs[h] = dhdt

def set_initial_conditions(self, variables):
domain, Domain = self.domain_Domain
phase_name = self.phase_name
h = variables[f"{Domain} electrode {phase_name}hysteresis state"]
self.initial_conditions[h] = pybamm.Scalar(0)
35 changes: 35 additions & 0 deletions pybamm/parameters/lithium_ion_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,14 @@ def _set_parameters(self):
eps_c_init_av = pybamm.xyz_average(
self.epsilon_s * pybamm.r_average(self.c_init)
)
# if self.options['open-circuit potential'] == 'Plett':
self.hysteresis_decay = pybamm.Parameter(
f"{pref}{Domain} particle hysteresis decay rate"
)
self.hysteresis_switch = pybamm.Parameter(
f"{pref}{Domain} particle hysteresis switching factor"
)
self.h_init = pybamm.Scalar(0)

if self.options["open-circuit potential"] != "MSMR":
self.U_init = self.U(self.sto_init_av, main.T_init)
Expand Down Expand Up @@ -641,6 +649,33 @@ def U(self, sto, T, lithiation=None):
out.print_name = r"U_\mathrm{p}(c^\mathrm{surf}_\mathrm{s,p}, T)"
return out

def hysteresis(self, sto):
mleot marked this conversation as resolved.
Show resolved Hide resolved
"""Dimensional hysteresis [V]"""
Domain = self.domain.capitalize()
# tol = pybamm.settings.tolerances["U__c_s"]
# sto = pybamm.maximum(pybamm.minimum(sto,1-tol),tol)
inputs = {f"{self.phase_prefactor}{Domain} particle stoichiometry": sto}
lith_ref = pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode lithiation OCP [V]", inputs
)
delith_ref = pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode delithiation OCP [V]", inputs
)
h_ref = abs(delith_ref - lith_ref) / 2
# h_ref = pybamm.FunctionParameter(
# f"{self.phase_prefactor}{Domain} electrode OCP hysteresis [V]", inputs
# )
return h_ref

def Q(self, sto):
mleot marked this conversation as resolved.
Show resolved Hide resolved
"""Capacity change as a function of stoichiometry"""
c_max = self.c_max
epsilon_s_av = self.epsilon_s_av
V_electrode = self.main_param.A_cc * self.domain_param.L
Li_max = c_max * V_electrode * epsilon_s_av
Q_max = Li_max * self.main_param.F / 3600
return Q_max * sto

mleot marked this conversation as resolved.
Show resolved Hide resolved
def dUdT(self, sto):
"""
Dimensional entropic change of the open-circuit potential [V.K-1]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,28 @@ def test_current_sigmoid_ocp(self):
modeltest = tests.StandardModelTest(model, parameter_values=parameter_values)
modeltest.test_all(skip_output_tests=True)

def test_wycisk_ocp(self):
options = {"open-circuit potential": ("Wycisk", "single")}
model = pybamm.lithium_ion.MPM(options)
parameter_values = pybamm.ParameterValues("Chen2020")
parameter_values = pybamm.get_size_distribution_parameters(parameter_values)
parameter_values.update(
{
"Negative electrode lithiation OCP [V]"
"": lambda sto: parameter_values["Negative electrode OCP [V]"](sto)
- 0.1,
"Negative electrode delithiation OCP [V]"
"": lambda sto: parameter_values["Negative electrode OCP [V]"](sto)
+ 0.1,
"Negative particle hysteresis decay rate": 1,
"Negative particle hysteresis switching factor": 1,
# "Negative electrode OCP hysteresis [V]": lambda sto: 1,
},
check_already_exists=False,
)
modeltest = tests.StandardModelTest(model, parameter_values=parameter_values)
modeltest.test_all(skip_output_tests=True)

def test_voltage_control(self):
options = {"operating mode": "voltage"}
model = pybamm.lithium_ion.MPM(options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
'lithium plating porosity change': 'false' (possible: ['false', 'true'])
'loss of active material': 'stress-driven' (possible: ['none', 'stress-driven', 'reaction-driven', 'current-driven', 'stress and reaction-driven'])
'number of MSMR reactions': 'none' (possible: ['none'])
'open-circuit potential': 'single' (possible: ['single', 'current sigmoid', 'MSMR'])
'open-circuit potential': 'single' (possible: ['single', 'current sigmoid', 'MSMR', 'Wycisk'])
'operating mode': 'current' (possible: ['current', 'voltage', 'power', 'differential power', 'explicit power', 'resistance', 'differential resistance', 'explicit resistance', 'CCCV'])
'particle': 'Fickian diffusion' (possible: ['Fickian diffusion', 'fast diffusion', 'uniform profile', 'quadratic profile', 'quartic profile', 'MSMR'])
'particle mechanics': 'swelling only' (possible: ['none', 'swelling only', 'swelling and cracking'])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,10 @@ def test_well_posed_current_sigmoid_ocp(self):
options = {"open-circuit potential": "current sigmoid"}
self.check_well_posedness(options)

def test_well_posed_wycisk_ocp(self):
options = {"open-circuit potential": "Wycisk"}
self.check_well_posedness(options)

def test_well_posed_msmr(self):
options = {
"open-circuit potential": "MSMR",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,20 @@ def test_well_posed_current_sigmoid_ocp_with_psd(self):
}
self.check_well_posedness(options)

def test_well_posed_wycisk_ocp_with_psd(self):
options = {
"open-circuit potential": "Wycisk",
"particle size": "distribution",
}
self.check_well_posedness(options)

def test_well_posed_wycisk_ocp_with_composite(self):
options = {
"open-circuit potential": (("Wycisk", "single"), "single"),
"particle phases": ("2", "1"),
}
self.check_well_posedness(options)

def test_well_posed_external_circuit_explicit_power(self):
options = {"operating mode": "explicit power"}
self.check_well_posedness(options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ def test_msmr(self):
model = pybamm.lithium_ion.MPM(options)
model.check_well_posedness()

def test_wycisk_ocp(self):
options = {
"open-circuit potential": "Wycisk",
}
model = pybamm.lithium_ion.MPM(options)
model.check_well_posedness()


class TestMPMExternalCircuits(TestCase):
def test_well_posed_voltage(self):
Expand Down
Loading