This notebook provides example usage for estimating stationary parameters for a two RC branch Thevenin model using multi-pulse HPPC data.
Before we begin, we need to ensure that we have all the necessary tools. We will install PyBOP from its development branch and upgrade some dependencies:
%pip install --upgrade pip ipywidgets openpyxl pandas -q
%pip install pybop -q
import pandas as pd
import plotly.graph_objects as go
import pybamm
import pybop
pybop.plot.PlotlyManager().pio.renderers.default = "notebook_connected"
/Users/engs2510/Documents/Git/Second_PyBOP/.nox/notebooks-overwrite/bin/python3: No module named pip Note: you may need to restart the kernel to use updated packages. /Users/engs2510/Documents/Git/Second_PyBOP/.nox/notebooks-overwrite/bin/python3: No module named pip Note: you may need to restart the kernel to use updated packages.
In this example, we use the default parameter value for the "Open-circuit voltage [V] as provided by the original PyBaMM class. The other relevant parameters for the ECM model implementation are updated as per the cell specification.
# Load the parameters
parameter_set = pybop.ParameterSet.pybamm("ECM_Example")
parameter_set.update(
{
"Cell capacity [A.h]": 3,
"Nominal cell capacity [A.h]": 3,
"Element-1 initial overpotential [V]": 0,
"Upper voltage cut-off [V]": 4.2,
"Lower voltage cut-off [V]": 2.5,
"R0 [Ohm]": 1e-3,
"R1 [Ohm]": 3e-3,
"C1 [F]": 5e2,
"Open-circuit voltage [V]": pybop.empirical.Thevenin().default_parameter_values[
"Open-circuit voltage [V]"
],
}
)
# Optional arguments - only needed for two RC pairs
parameter_set.update(
{
"R2 [Ohm]": 2e-3,
"C2 [F]": 3e4,
"Element-2 initial overpotential [V]": 0,
},
check_already_exists=False,
)
Now that the initial parameter set is constructed, we can start the PyBOP fitting process. First, we define the model class with two RC elements. One important thing here to note is "maximum solver timestep" (dt_max) needs to be set correctly to have a good fit.
model = pybop.empirical.Thevenin(
parameter_set=parameter_set,
options={"number of rc elements": 2},
solver=pybamm.CasadiSolver(mode="safe", dt_max=40),
)
We use multiple HPPC pulses from the dataset: Kollmeyer, Phillip; Skells, Michael (2020), “Samsung INR21700 30T 3Ah Li-ion Battery Data”, Mendeley Data, V1, doi: 10.17632/9xyvy2njj3.1
file_loc = r"../data/Samsung_INR21700/multipulse_hppc.xlsx"
df = pd.read_excel(file_loc, index_col=None, na_values=["NA"])
df = df.drop_duplicates(subset=["Time"], keep="first")
dataset = pybop.Dataset(
{
"Time [s]": df["Time"].to_numpy(),
"Current function [A]": df["Current"].to_numpy(),
"Voltage [V]": df["Voltage"].to_numpy(),
}
)
r0_guess = 0.005
parameters = pybop.Parameters(
pybop.Parameter(
"R0 [Ohm]",
prior=pybop.Gaussian(r0_guess, r0_guess / 10),
bounds=[0, 0.1],
),
pybop.Parameter(
"R1 [Ohm]",
prior=pybop.Gaussian(r0_guess, r0_guess / 10),
bounds=[0, 0.1],
),
pybop.Parameter(
"R2 [Ohm]",
prior=pybop.Gaussian(r0_guess, r0_guess / 10),
bounds=[0, 0.1],
),
pybop.Parameter(
"C1 [F]",
prior=pybop.Gaussian(500, 100),
bounds=[100, 1000],
),
pybop.Parameter(
"C2 [F]",
prior=pybop.Gaussian(2000, 500),
bounds=[1000, 10000],
),
)
# To see current vs time profile.
fig1 = go.Figure()
# Add a line trace for current vs. time
fig1.add_trace(
go.Scatter(
x=df["Time"].to_numpy(),
y=df["Current"].to_numpy(),
mode="lines", # 'lines', 'markers', or 'lines+markers'
name="Current vs Time",
)
)
# Customize layout
fig1.update_layout(
title="Current vs Time",
xaxis_title="Time (s)",
yaxis_title="Current (A)",
template="plotly", # Use a Plotly template (optional)
)
# Show the plot
fig1.show()
# To see voltage vs time profile.
fig2 = go.Figure()
# Add a line trace for current vs. time
fig2.add_trace(
go.Scatter(
x=df["Time"].to_numpy(),
y=df["Voltage"].to_numpy(),
mode="lines", # 'lines', 'markers', or 'lines+markers'
name="Voltage vs Time",
)
)
# Customize layout
fig2.update_layout(
title="Voltage vs Time",
xaxis_title="Time (s)",
yaxis_title="Voltage (V)",
template="plotly", # Use a Plotly template (optional)
)
# Show the plot
fig2.show()
The FittingProblem
class provides us with a single class that holds all of the objects we need to evaluate our selected SumSquaredError
cost function.
Initial state can be either "Initial SoC" or "Initial open-circuit voltage [V]". In this example, we get the initial OCV by accessing the voltage data. However, user can simply use a value instead, e.g., {"Initial open-circuit voltage [V]": 4.1}. Similarly, if SOC input is required, {"Initial SoC": 0.95}.
model.build(
initial_state={"Initial open-circuit voltage [V]": df["Voltage"].to_numpy()[0]}
)
problem = pybop.FittingProblem(
model,
parameters,
dataset,
)
cost = pybop.SumSquaredError(problem)
Next, we construct the optimisation class with our algorithm of choice and run it. In this case, we select the CMA-ES method as it provides global optimisation capability. For the sake of reducing the runtime of this example, we limit the maximum iterations to 100; however, feel free to update this value.
optim = pybop.XNES(
cost,
sigma0=[1e-3, 1e-3, 1e-3, 10, 10],
max_unchanged_iterations=20,
max_iterations=100,
)
results = optim.run()
/Users/engs2510/Documents/Git/Second_PyBOP/.nox/notebooks-overwrite/lib/python3.12/site-packages/pybamm/solvers/base_solver.py:762: SolverWarning: Explicit interpolation times not implemented for CasADi solver with 'safe' mode
Halt: Maximum number of iterations (100) reached. OptimisationResult: Initial parameters: [3.97387634e-03 4.25500910e-03 3.85039109e-03 4.55295623e+02 1.93038193e+03] Optimised parameters: [9.55811673e-03 3.31919040e-03 1.76755904e-02 2.41500501e+02 1.46563700e+03] Final cost: 0.00186863751963031 Optimisation time: 35.69897198677063 seconds Number of iterations: 100 SciPy result available: No
PyBOP provides various plotting utilities to visualize the results of the optimisation.
pybop.plot.quick(problem, problem_inputs=results.x, title="Optimised Comparison");
To assess the optimisation process, we can plot the convergence of the cost function and the trajectories of the parameters:
pybop.plot.convergence(optim)
pybop.plot.parameters(optim);
This notebook illustrates how to perform parameter estimation for multi-pulse HPPC data, providing insights into the optimisation process through various visualisations.