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 BPX support #246

Merged
merged 13 commits into from
Apr 5, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Features

- [#79](https://github.com/pybop-team/PyBOP/issues/79) - Adds BPX as a dependency and imports BPX support from PyBaMM.
- [#267](https://github.com/pybop-team/PyBOP/pull/267) - Add classifiers to pyproject.toml, update project.urls.
- [#195](https://github.com/pybop-team/PyBOP/issues/195) - Adds the Nelder-Mead optimiser from PINTS as another option.

Expand Down
9 changes: 5 additions & 4 deletions examples/notebooks/equivalent_circuit_identification.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,10 @@
"metadata": {},
"outputs": [],
"source": [
"# params = pybop.ParameterSet(\n",
"# parameter_set = pybop.ParameterSet(\n",
"# json_path=\"examples/scripts/parameters/initial_ecm_parameters.json\"\n",
"# )"
"# )\n",
"# parameter_set.import_parameters()"
]
},
{
Expand All @@ -116,7 +117,7 @@
"metadata": {},
"outputs": [],
"source": [
"params = pybop.ParameterSet(\n",
"parameter_set = pybop.ParameterSet(\n",
" params_dict={\n",
" \"chemistry\": \"ecm\",\n",
" \"Initial SoC\": 0.5,\n",
Expand Down Expand Up @@ -162,7 +163,7 @@
"outputs": [],
"source": [
"model = pybop.empirical.Thevenin(\n",
" parameter_set=params.import_parameters(), options={\"number of rc elements\": 2}\n",
" parameter_set=parameter_set, options={\"number of rc elements\": 2}\n",
")"
]
},
Expand Down
70 changes: 70 additions & 0 deletions examples/scripts/BPX_spm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import numpy as np

import pybop

# Define model
bpx_parameters = pybop.ParameterSet(
json_path="examples/scripts/parameters/example_BPX.json"
)
parameter_set = bpx_parameters.import_from_bpx()
model = pybop.lithium_ion.SPM(parameter_set=parameter_set)

# Fitting parameters
parameters = [
pybop.Parameter(
"Negative particle radius [m]",
prior=pybop.Gaussian(6e-06, 0.1e-6),
bounds=[1e-6, 9e-6],
true_value=parameter_set["Negative particle radius [m]"],
),
pybop.Parameter(
"Positive particle radius [m]",
prior=pybop.Gaussian(4.5e-07, 0.1e-6),
bounds=[1e-7, 9e-7],
true_value=parameter_set["Positive particle radius [m]"],
),
]

# Generate data
sigma = 0.001
t_eval = np.arange(0, 900, 2)
values = model.predict(t_eval=t_eval)
corrupt_values = values["Voltage [V]"].data + np.random.normal(0, sigma, len(t_eval))

# Form dataset
dataset = pybop.Dataset(
{
"Time [s]": t_eval,
"Current function [A]": values["Current [A]"].data,
"Voltage [V]": corrupt_values,
}
)

# Generate problem, cost function, and optimisation class
problem = pybop.FittingProblem(model, parameters, dataset)
cost = pybop.SumSquaredError(problem)
optim = pybop.Optimisation(cost, optimiser=pybop.CMAES)
optim.set_max_iterations(100)

# Run the optimisation
x, final_cost = optim.run()
print(
"True parameters:",
[
parameters[0].true_value,
parameters[1].true_value,
],
)
print("Estimated parameters:", x)

# Plot the timeseries output
pybop.quick_plot(problem, parameter_values=x, title="Optimised Comparison")

# Plot convergence
pybop.plot_convergence(optim)

# Plot the parameter traces
pybop.plot_parameters(optim)

# Plot the cost landscape with optimisation path and updated bounds
pybop.plot2d(optim, steps=15)
9 changes: 5 additions & 4 deletions examples/scripts/ecm_CMAES.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import pybop

# Import the ECM parameter set from JSON
params = pybop.ParameterSet(
parameter_set = pybop.ParameterSet(
json_path="examples/scripts/parameters/initial_ecm_parameters.json"
)
parameter_set.import_parameters()

# Alternatively, define the initial parameter set with a dictionary
# Add definitions for R's, C's, and initial overpotentials for any additional RC elements
# params = pybop.ParameterSet(
# parameter_set = pybop.ParameterSet(
# params_dict={
# "chemistry": "ecm",
# "Initial SoC": 0.5,
Expand Down Expand Up @@ -40,7 +41,7 @@

# Define the model
model = pybop.empirical.Thevenin(
parameter_set=params.import_parameters(), options={"number of rc elements": 2}
parameter_set=parameter_set, options={"number of rc elements": 2}
)

# Fitting parameters
Expand Down Expand Up @@ -81,7 +82,7 @@
print("Estimated parameters:", x)

# Export the parameters to JSON
params.export_parameters(
parameter_set.export_parameters(
"examples/scripts/parameters/fit_ecm_parameters.json", fit_params=parameters
)

Expand Down
78 changes: 78 additions & 0 deletions examples/scripts/parameters/example_BPX.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
{
"Header": {
"BPX": 0.1,
"Title": "Parameterisation example of an LFP|graphite 2 Ah cylindrical 18650 cell.",
"Description": "LFP|graphite 2 Ah cylindrical 18650 cell. Parameterisation by About:Energy Limited (aboutenergy.io), December 2022, based on cell cycling data, and electrode data gathered after cell teardown. Electrolyte properties from Nyman et al. 2008 (doi:10.1016/j.electacta.2008.04.023). Negative electrode entropic coefficient data are from O'Regan et al. 2022 (doi:10.1016/j.electacta.2022.140700). Positive electrode entropic coefficient data are from Gerver and Meyers 2011 (doi:10.1149/1.3591799). Other thermal properties are estimated.",
"Model": "DFN"
},
"Parameterisation": {
"Cell": {
"Ambient temperature [K]": 298.15,
"Initial temperature [K]": 298.15,
"Reference temperature [K]": 298.15,
"Lower voltage cut-off [V]": 2.0,
"Upper voltage cut-off [V]": 3.65,
"Nominal cell capacity [A.h]": 2,
"Specific heat capacity [J.K-1.kg-1]": 999,
"Thermal conductivity [W.m-1.K-1]": 1.89,
"Density [kg.m-3]": 1940,
"Electrode area [m2]": 0.08959998,
"Number of electrode pairs connected in parallel to make a cell": 1,
"External surface area [m2]": 0.00431,
"Volume [m3]": 1.7e-05
},
"Electrolyte": {
"Initial concentration [mol.m-3]": 1000,
"Cation transference number": 0.259,
"Conductivity [S.m-1]": "0.1297 * (x / 1000) ** 3 - 2.51 * (x / 1000) ** 1.5 + 3.329 * (x / 1000)",
"Diffusivity [m2.s-1]": "8.794e-11 * (x / 1000) ** 2 - 3.972e-10 * (x / 1000) + 4.862e-10",
"Conductivity activation energy [J.mol-1]": 17100,
"Diffusivity activation energy [J.mol-1]": 17100
},
"Negative electrode": {
"Particle radius [m]": 4.8e-06,
"Thickness [m]": 4.44e-05,
"Diffusivity [m2.s-1]": 9.6e-15,
"OCP [V]": "5.29210878e+01 * exp(-1.72699386e+02 * x) - 1.17963399e+03 + 1.20956356e+03 * tanh(6.72033948e+01 * (x + 2.44746396e-02)) + 4.52430314e-02 * tanh(-1.47542326e+01 * (x - 1.62746053e-01)) + 2.01855800e+01 * tanh(-2.46666302e+01 * (x - 1.12986136e+00)) + 2.01708039e-02 * tanh(-1.19900231e+01 * (x - 5.49773440e-01)) + 4.99616805e+01 * tanh(-6.11370883e+01 * (x + 4.69382558e-03))",
"Entropic change coefficient [V.K-1]": "(-0.1112 * x + 0.02914 + 0.3561 * exp(-((x - 0.08309) ** 2) / 0.004616)) / 1000",
"Conductivity [S.m-1]": 7.46,
"Surface area per unit volume [m-1]": 473004,
"Porosity": 0.20666,
"Transport efficiency": 0.09395,
"Reaction rate constant [mol.m-2.s-1]": 6.872e-06,
"Minimum stoichiometry": 0.0016261,
"Maximum stoichiometry": 0.82258,
"Maximum concentration [mol.m-3]": 31400,
"Diffusivity activation energy [J.mol-1]": 30000,
"Reaction rate constant activation energy [J.mol-1]": 55000
},
"Positive electrode": {
"Particle radius [m]": 5e-07,
"Thickness [m]": 6.43e-05,
"Diffusivity [m2.s-1]": 6.873e-17,
"OCP [V]": "3.41285712e+00 - 1.49721852e-02 * x + 3.54866018e+14 * exp(-3.95729493e+02 * x) - 1.45998465e+00 * exp(-1.10108622e+02 * (1 - x))",
"Entropic change coefficient [V.K-1]": {
"x": [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1],
"y": [0.0001, 4.7145e-05, 3.7666e-05, 2.0299e-05, 5.9833e-06, -4.6859e-06, -1.3966e-05, -2.3528e-05, -3.3593e-05, -4.3433e-05, -5.2311e-05, -6.0211e-05, -6.8006e-05, -7.6939e-05, -8.7641e-05, -9.913e-05, -0.00010855, -0.00011266, -0.00011238, -0.00010921, -0.00022539]
},
"Conductivity [S.m-1]": 0.80,
"Surface area per unit volume [m-1]": 4418460,
"Porosity": 0.20359,
"Transport efficiency": 0.09186,
"Reaction rate constant [mol.m-2.s-1]": 9.736e-07,
"Minimum stoichiometry": 0.0875,
"Maximum stoichiometry": 0.95038,
"Maximum concentration [mol.m-3]": 21200,
"Diffusivity activation energy [J.mol-1]": 80000,
"Reaction rate constant activation energy [J.mol-1]": 35000
},
"Separator": {
"Thickness [m]": 2e-05,
"Porosity": 0.47,
"Transport efficiency": 0.3222
},
"User-defined": {
"Source:": "An example BPX json file downloaded on 19/3/24 from https://github.com/FaradayInstitution/BPX/blob/main/examples/lfp_18650_cell_BPX.json"
}
}
}
13 changes: 11 additions & 2 deletions pybop/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@dataclass
class TimeSeriesState(object):
"""
The current state of a time series model that is a pybamm model
The current state of a time series model that is a pybamm model.
"""

sol: pybamm.Solution
Expand Down Expand Up @@ -46,7 +46,7 @@ class BaseModel:

"""

def __init__(self, name="Base Model"):
def __init__(self, name="Base Model", parameter_set=None):
"""
Initialize the BaseModel with an optional name.

Expand All @@ -56,6 +56,15 @@ def __init__(self, name="Base Model"):
The name given to the model instance.
"""
self.name = name
if parameter_set is None:
self._parameter_set = None
elif isinstance(parameter_set, dict):
self._parameter_set = pybamm.ParameterValues(parameter_set)
elif isinstance(parameter_set, pybamm.ParameterValues):
self._parameter_set = parameter_set
else: # a pybop parameter set
self._parameter_set = pybamm.ParameterValues(parameter_set.params)

self.pybamm_model = None
self.parameters = None
self.dataset = None
Expand Down
16 changes: 7 additions & 9 deletions pybop/models/empirical/ecm.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,21 @@
options=None,
**kwargs,
):
super().__init__()
super().__init__(name, parameter_set)
self.pybamm_model = pybamm.equivalent_circuit.Thevenin(
options=options, **kwargs
)
self._unprocessed_model = self.pybamm_model
self.name = name

if isinstance(parameter_set, dict):
self.default_parameter_values = pybamm.ParameterValues(parameter_set)
self._parameter_set = self.default_parameter_values
# Set parameters, using either the provided ones or the default
if isinstance(self._parameter_set, dict):
self.default_parameter_values = pybamm.ParameterValues(self._parameter_set)

Check warning on line 56 in pybop/models/empirical/ecm.py

View check run for this annotation

Codecov / codecov/patch

pybop/models/empirical/ecm.py#L56

Added line #L56 was not covered by tests
else:
self.default_parameter_values = self.pybamm_model.default_parameter_values
self._parameter_set = (
parameter_set or self.pybamm_model.default_parameter_values
)

self._parameter_set = self._parameter_set or self.default_parameter_values
self._unprocessed_parameter_set = self._parameter_set

# Define model geometry and discretization
self.geometry = geometry or self.pybamm_model.default_geometry
self.submesh_types = submesh_types or self.pybamm_model.default_submesh_types
self.var_pts = var_pts or self.pybamm_model.default_var_pts
Expand All @@ -71,6 +68,7 @@
)
self.solver = solver or self.pybamm_model.default_solver

# Internal attributes for the built model are initialized but not set
self._model_with_set_params = None
self._built_model = None
self._built_initial_soc = None
Expand Down
4 changes: 2 additions & 2 deletions pybop/models/empirical/ecm_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class ECircuitModel(BaseModel):
Overwrites and extends `BaseModel` class for circuit-based PyBaMM models.
"""

def __init__(self):
super().__init__()
def __init__(self, name, parameter_set):
super().__init__(name, parameter_set)

def _check_params(self, inputs=None, allow_infeasible_solutions=True):
"""
Expand Down
14 changes: 4 additions & 10 deletions pybop/models/lithium_ion/echem.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,13 @@ def __init__(
solver=None,
options=None,
):
super().__init__()
super().__init__(name, parameter_set)
self.pybamm_model = pybamm.lithium_ion.SPM(options=options)
self._unprocessed_model = self.pybamm_model
self.name = name

# Set parameters, using either the provided ones or the default
self.default_parameter_values = self.pybamm_model.default_parameter_values
self._parameter_set = (
parameter_set or self.pybamm_model.default_parameter_values
)
self._parameter_set = self._parameter_set or self.default_parameter_values
self._unprocessed_parameter_set = self._parameter_set

# Define model geometry and discretization
Expand Down Expand Up @@ -113,16 +110,13 @@ def __init__(
solver=None,
options=None,
):
super().__init__()
super().__init__(name, parameter_set)
self.pybamm_model = pybamm.lithium_ion.SPMe(options=options)
self._unprocessed_model = self.pybamm_model
self.name = name

# Set parameters, using either the provided ones or the default
self.default_parameter_values = self.pybamm_model.default_parameter_values
self._parameter_set = (
parameter_set or self.pybamm_model.default_parameter_values
)
self._parameter_set = self._parameter_set or self.default_parameter_values
self._unprocessed_parameter_set = self._parameter_set

# Define model geometry and discretization
Expand Down
4 changes: 2 additions & 2 deletions pybop/models/lithium_ion/echem_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ class EChemBaseModel(BaseModel):
Overwrites and extends `BaseModel` class for electrochemical PyBaMM models.
"""

def __init__(self):
super().__init__()
def __init__(self, name, parameter_set):
super().__init__(name, parameter_set)

def _check_params(
self, inputs=None, parameter_set=None, allow_infeasible_solutions=True
Expand Down
Loading
Loading