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

Add GroupedSPMe model #584

Merged
merged 63 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
963b550
Add first go at grouped SPMe
NicolaCourtier Oct 20, 2024
fdabd64
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Oct 24, 2024
e059cb9
Update thicknesses
NicolaCourtier Oct 24, 2024
d1375f0
Pre-commit styling
NicolaCourtier Oct 24, 2024
0fd41e2
Update overpotentials
NicolaCourtier Oct 24, 2024
5a4b77b
Add double-layer
NicolaCourtier Oct 24, 2024
bfe24c4
Update example
NicolaCourtier Oct 24, 2024
fdb5fc1
Add missing line
NicolaCourtier Oct 25, 2024
6022ab6
Remove factors of length
NicolaCourtier Oct 25, 2024
46dd920
Fix domain parameters
NicolaCourtier Oct 25, 2024
738150f
Update series resistance and etas
NicolaCourtier Oct 28, 2024
3a9d6bf
Set constant conductivity
NicolaCourtier Oct 28, 2024
9923d39
Remove factor of 3
NicolaCourtier Oct 28, 2024
6663892
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Oct 31, 2024
5f2e92b
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Nov 1, 2024
4b828ea
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Nov 1, 2024
d78e495
Add parameter conversion function
NicolaCourtier Nov 3, 2024
c1bde00
Combine plots
NicolaCourtier Nov 3, 2024
2c364a9
Update model options
NicolaCourtier Nov 3, 2024
5cb497d
Rename variables
NicolaCourtier Nov 3, 2024
2808721
Add conductivities and init_soc
NicolaCourtier Nov 4, 2024
24e10a2
Update averaging
NicolaCourtier Nov 4, 2024
dc88e85
Move timescales
NicolaCourtier Nov 4, 2024
c758689
Rescale electrolyte source
NicolaCourtier Nov 4, 2024
b89ad94
Assume high conductivities
NicolaCourtier Nov 4, 2024
3036431
Add electrolyte flux
NicolaCourtier Nov 4, 2024
dd24448
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Nov 8, 2024
965175e
Fix typo
NicolaCourtier Nov 8, 2024
5eb8ff3
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Nov 13, 2024
cad0a03
Add transfer coefficient
NicolaCourtier Nov 14, 2024
f116d15
Update grouped_SPMe
NicolaCourtier Nov 15, 2024
82f0bfb
Revert to sto_e, make double layer optional
NicolaCourtier Nov 18, 2024
1579f8b
Add target continuity conditions
NicolaCourtier Nov 18, 2024
80c6bcc
Simplify ocv_init setting
NicolaCourtier Nov 20, 2024
acf95bd
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Nov 21, 2024
6ccfd63
Remove concatenations
NicolaCourtier Nov 21, 2024
1ed841c
Switch from theoretical to measured capacity
NicolaCourtier Nov 23, 2024
601c92a
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Nov 26, 2024
808b32c
Move example into subfolder
NicolaCourtier Nov 26, 2024
1a578f8
Fix electrolyte scaling
NicolaCourtier Nov 29, 2024
f1e96f4
Add potentials to quick plot
NicolaCourtier Dec 2, 2024
d566801
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Dec 3, 2024
2e46801
Update option setting
NicolaCourtier Dec 4, 2024
ff0422c
Grouped SPMe edit (#577)
noelhallemans Dec 4, 2024
8b822ce
Create grouped_SPMe_experiment.py
NicolaCourtier Dec 4, 2024
aa29725
Move set_initial_state to base models
NicolaCourtier Dec 6, 2024
bb887b2
Remove testing script
NicolaCourtier Dec 6, 2024
8ba44ec
Pre-commit
NicolaCourtier Dec 6, 2024
93ee714
Add tests on GroupedSPMe
NicolaCourtier Dec 6, 2024
20c8809
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Dec 6, 2024
7272454
Test differential surface form
NicolaCourtier Dec 6, 2024
ffdd56b
Fix option setting
NicolaCourtier Dec 6, 2024
3273fa3
Update README.md
NicolaCourtier Dec 6, 2024
8b819db
Add test_grouped_SPMe
NicolaCourtier Dec 6, 2024
e5ad2b3
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Dec 6, 2024
8ccc856
Increase coverage
NicolaCourtier Dec 6, 2024
5b3a6cd
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Dec 6, 2024
c02fda8
Update name and option setting
NicolaCourtier Dec 6, 2024
8603405
Remove comments
NicolaCourtier Dec 11, 2024
cfd8f76
Combine if statements
NicolaCourtier Dec 11, 2024
cee4cfe
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Dec 11, 2024
3646f81
Merge branch 'develop' into grouped-SPMe
NicolaCourtier Dec 11, 2024
87abcc3
Update CHANGELOG.md
NicolaCourtier Dec 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ The table below lists the currently supported [models](https://github.com/pybop-
| Single Particle Model with Electrolyte (SPMe) | Particle Swarm Optimization (PSO) | Root Mean Squared Error (RMSE) <tr></tr> |
| Doyle-Fuller-Newman (DFN) | Exponential Natural Evolution Strategy (xNES) | Minkowski <tr></tr> |
| Many Particle Model (MPM) | Separable Natural Evolution Strategy (sNES) | Sum of Power <tr></tr> |
| Multi-Species Multi-Reactants (MSMR) | Adaptive Moment Estimation with Weight Decay (AdamW) | Gaussian Log Likelihood <tr></tr> |
| Multi-Species Multi-Reaction (MSMR) | Adaptive Moment Estimation with Weight Decay (AdamW) | Gaussian Log Likelihood <tr></tr> |
| Weppner-Huggins | Improved Resilient Backpropagation (iRProp-) | Log Posterior <tr></tr> |
| Equivalent Circuit Models (ECM) | SciPy Minimize & Differential Evolution | Unscented Kalman Filter (UKF) <tr></tr> |
| | Cuckoo Search | Gravimetric Energy Density <tr></tr> |
| Grouped-parameter SPMe (GroupedSPMe) | Cuckoo Search | Gravimetric Energy Density <tr></tr> |
| | Gradient Descent | Volumetric Energy Density<tr></tr> |
| | Nelder-Mead | <tr></tr> |

Expand Down
120 changes: 120 additions & 0 deletions examples/scripts/comparison_examples/grouped_SPMe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import matplotlib.pyplot as plt
import numpy as np

import pybop
from pybop.models.lithium_ion.basic_SPMe import convert_physical_to_grouped_parameters

# Prepare figure
layout_options = dict(
xaxis_title="Time / s",
yaxis_title="Voltage / V",
)
plot_dict = pybop.plot.StandardPlot(layout_options=layout_options)

# Unpack parameter values from Chen2020
parameter_set = pybop.ParameterSet.pybamm("Chen2020")
ce0 = parameter_set["Initial concentration in electrolyte [mol.m-3]"]
T = parameter_set["Ambient temperature [K]"]
parameter_set["Electrolyte diffusivity [m2.s-1]"] = parameter_set[
"Electrolyte diffusivity [m2.s-1]"
](ce0, T)
parameter_set["Electrolyte conductivity [S.m-1]"] = parameter_set[
"Electrolyte conductivity [S.m-1]"
](ce0, T)

# Make the conductivities artificially large
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
parameter_set["Electrolyte conductivity [S.m-1]"] = 1e16 # 0.9487
parameter_set["Negative electrode conductivity [S.m-1]"] = 1e16
parameter_set["Positive electrode conductivity [S.m-1]"] = 1e16

# Define a test protocol
initial_state = {"Initial SoC": 0.9}
experiment = pybop.Experiment(
[
"Discharge at 1C until 2.5 V (5 seconds period)",
"Rest for 30 minutes (5 seconds period)",
# "Charge at 2C until 4.1 V (5 seconds period)",
# "Rest for 30 minutes (5 seconds period)",
],
)

# Run an example SPMe simulation
model_options = {"surface form": "differential", "contact resistance": "true"}
time_domain_SPMe = pybop.lithium_ion.SPMe(
parameter_set=parameter_set,
options=model_options,
)
simulation = time_domain_SPMe.predict(
initial_state=initial_state, experiment=experiment
)
dataset = pybop.Dataset(
{
"Time [s]": simulation["Time [s]"].data,
"Current function [A]": simulation["Current [A]"].data,
"Voltage [V]": simulation["Voltage [V]"].data,
}
)
plot_dict.add_traces(dataset["Time [s]"], dataset["Voltage [V]"])

# Test model in the time domain
grouped_parameter_set = convert_physical_to_grouped_parameters(parameter_set)
time_domain_grouped = pybop.lithium_ion.GroupedSPMe(
parameter_set=grouped_parameter_set,
options=model_options,
build=True,
)
time_domain_grouped.set_initial_state(initial_state)
time_domain_grouped.set_current_function(dataset)
simulation = time_domain_grouped.predict(t_eval=dataset["Time [s]"])
dataset = pybop.Dataset(
{
"Time [s]": simulation["Time [s]"].data,
"Current function [A]": simulation["Current [A]"].data,
"Voltage [V]": simulation["Voltage [V]"].data,
}
)
plot_dict.add_traces(dataset["Time [s]"], dataset["Voltage [V]"], line_dash="dash")
plot_dict()

# Set up figure
fig, ax = plt.subplots()
ax.grid()

# Compare models in the frequency domain
freq_domain_SPMe = pybop.lithium_ion.SPMe(
parameter_set=parameter_set, options=model_options, eis=True
)
freq_domain_grouped = pybop.lithium_ion.GroupedSPMe(
parameter_set=grouped_parameter_set,
options=model_options,
eis=True,
build=True,
)

for i, model in enumerate([freq_domain_SPMe, freq_domain_grouped]):
NSOC = 11
Nfreq = 60
fmin = 4e-4
fmax = 1e3
SOCs = np.linspace(0, 1, NSOC)
frequencies = np.logspace(np.log10(fmin), np.log10(fmax), Nfreq)

impedances = 1j * np.zeros((Nfreq, NSOC))
for ii, SOC in enumerate(SOCs):
model.set_initial_state({"Initial SoC": SOC})
simulation = model.simulateEIS(inputs=None, f_eval=frequencies)
impedances[:, ii] = simulation["Impedance"]

if i == 0:
ax.plot(np.real(impedances[:, ii]), -np.imag(impedances[:, ii]), "b")
if i == 1:
ax.plot(np.real(impedances[:, ii]), -np.imag(impedances[:, ii]), "r--")

# Show figure
ax.set(xlabel=r"$Z_r(\omega)$ [$\Omega$]", ylabel=r"$-Z_j(\omega)$ [$\Omega$]")
ax.set_aspect("equal", "box")
plt.show()
fig.savefig("Nyquist.png")

# mdic = {"Z": impedances, "f": frequencies, "SOC": SOCs}
# savemat("Simulated data SPMe/Z_SPMe_SOC_Pybop_chen2020.mat", mdic)
NicolaCourtier marked this conversation as resolved.
Show resolved Hide resolved
35 changes: 6 additions & 29 deletions pybop/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,30 +241,7 @@ def set_initial_state(self, initial_state: dict, inputs: Optional[Inputs] = None
"""
self.clear()

initial_state = self.convert_to_pybamm_initial_state(initial_state)

if isinstance(self.pybamm_model, pybamm.equivalent_circuit.Thevenin):
initial_state = self.get_initial_state(initial_state, inputs=inputs)
self._unprocessed_parameter_set.update({"Initial SoC": initial_state})

else:
if not self.pybamm_model._built: # noqa: SLF001
self.pybamm_model.build_model()

# Temporary construction of attributes for PyBaMM
self._model = self.pybamm_model
self._unprocessed_parameter_values = self._unprocessed_parameter_set

# Set initial state via PyBaMM's Simulation class
pybamm.Simulation.set_initial_soc(self, initial_state, inputs=inputs)

# Update the default parameter set for consistency
self._unprocessed_parameter_set = self._parameter_values

# Clear the pybamm objects
del self._model
del self._unprocessed_parameter_values
del self._parameter_values
self._set_initial_state(initial_state=initial_state, inputs=inputs)

# Use a copy of the updated default parameter set
self._parameter_set = self._unprocessed_parameter_set.copy()
Expand Down Expand Up @@ -331,11 +308,11 @@ def set_up_for_eis(self, model):
model.param, None, model.options, control="algebraic"
).get_fundamental_variables()

# Perform the replacement
symbol_replacement_map = {
model.variables[name]: variable
for name, variable in external_circuit_variables.items()
}
# Define the variables to replace
symbol_replacement_map = {}
for name, variable in external_circuit_variables.items():
if name in model.variables.keys():
symbol_replacement_map[model.variables[name]] = variable

# Don't replace initial conditions, as these should not contain
# Variable objects
Expand Down
18 changes: 18 additions & 0 deletions pybop/models/empirical/base_ecm.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

import numpy as np
import pybamm

Expand Down Expand Up @@ -121,6 +123,22 @@ def _check_params(
return self.param_checker(inputs, allow_infeasible_solutions)
return True

def _set_initial_state(self, initial_state: dict, inputs: Optional[Inputs] = None):
"""
Set the initial state of charge or concentrations for the battery model.
Parameters
----------
initial_state : dict
A valid initial state, e.g. the initial state of charge or open-circuit voltage.
inputs : Inputs
The input parameters to be used when building the model.
"""
initial_state = self.convert_to_pybamm_initial_state(initial_state)

initial_state = self.get_initial_state(initial_state, inputs=inputs)
self._unprocessed_parameter_set.update({"Initial SoC": initial_state})

def get_initial_state(
self,
initial_value,
Expand Down
2 changes: 1 addition & 1 deletion pybop/models/lithium_ion/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# Import lithium ion based models
#
from .base_echem import EChemBaseModel
from .echem import SPM, SPMe, DFN, MPM, MSMR, WeppnerHuggins
from .echem import SPM, SPMe, DFN, MPM, MSMR, WeppnerHuggins, GroupedSPMe
33 changes: 32 additions & 1 deletion pybop/models/lithium_ion/base_echem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import warnings
from typing import Optional

from pybamm import LithiumIonParameters
from pybamm import LithiumIonParameters, Simulation
from pybamm import lithium_ion as pybamm_lithium_ion

from pybop.models.base_model import BaseModel, Inputs
Expand Down Expand Up @@ -154,6 +154,37 @@ def _check_params(

return True

def _set_initial_state(self, initial_state: dict, inputs: Optional[Inputs] = None):
"""
Set the initial state of charge or concentrations for the battery model.
Parameters
----------
initial_state : dict
A valid initial state, e.g. the initial state of charge or open-circuit voltage.
inputs : Inputs
The input parameters to be used when building the model.
"""
initial_state = self.convert_to_pybamm_initial_state(initial_state)

if not self.pybamm_model._built: # noqa: SLF001
self.pybamm_model.build_model()

# Temporary construction of attributes for PyBaMM
self._model = self.pybamm_model
self._unprocessed_parameter_values = self._unprocessed_parameter_set

# Set initial state via PyBaMM's Simulation class
Simulation.set_initial_soc(self, initial_state, inputs=inputs)

# Update the default parameter set for consistency
self._unprocessed_parameter_set = self._parameter_values

# Clear the pybamm objects
del self._model
del self._unprocessed_parameter_values
del self._parameter_values

def cell_volume(self, parameter_set: Optional[ParameterSet] = None):
"""
Calculate the total cell volume in m3.
Expand Down
Loading
Loading