#
# Data fit class
#
import pbparam
import pybamm
[docs]
class DataFit(pbparam.BaseOptimisationProblem):
"""
A class to define an optimisation problem.
Parameters
----------
simulation : :class:`pybamm.Simulation`
The simulation to be run to fit to data
data : :class:`pandas.DataFrame`
The experimental or reference data to be used in optimisation
of simulation parameters.
parameters : dict
The parameters to be optimised. They should be provided as a dictionary where
the keys are the names of the variables to be optimised and the values are a
tuple with the initial guesses and the lower and upper bounds of the
optimisation. If a key is a list of strings then all the variables in the list
will take the same value.
variables_to_fit : str or list of str (optional)
The variable or variables to optimise in the cost function. The default is
"Voltage [V]". It can be a string or a list of strings.
weights : dict (optional)
The custom weights of individual variables. Default is 1 for all variables.
It can be int or list of int that has same length with the data.
cost_function : :class:`pbparam.BaseCostFunction`
Cost function class to be used in minimisation algorithm. The default
is Root-Mean Square Error. It can be selected from pre-defined built-in
functions or defined explicitly.
solve_options : dict (optional)
A dictionary of options to pass to the simulation. The default is None.
"""
def __init__(
self,
simulation,
data,
parameters,
variables_to_fit=["Voltage [V]"],
cost_function=pbparam.RMSE(),
weights=None,
solve_options=None,
):
super().__init__(
model=simulation,
cost_function=cost_function,
data=data,
parameters=parameters,
variables_to_fit=variables_to_fit,
weights=weights,
)
self.collect_parameters(solve_options)
self.update_simulation_parameters(simulation)
self.process_weights()
[docs]
def objective_function(self, x):
"""
Calculate the cost function given the current values of the parameters
Parameters
----------
x : array-like
The current values of the parameters
Returns
-------
cost : float
The calculated cost of the simulation with the current parameters
"""
# Update the parameter values and solve the simulation using PyBaMM
input_dict = {
param: self.scalings[i] * x[i] for param, i in self.map_inputs.items()
}
t_end = self.data["Time [s]"].iloc[-1]
self.solution = self.model.solve([0, t_end], inputs=input_dict)
# Get the new y values from the simulation
y_sim = [self.solution[v](self.data["Time [s]"]) for v in self.variables_to_fit]
y_data = [self.data[v] for v in self.variables_to_fit]
weights = [self.weights[v] for v in self.variables_to_fit]
sd = [x[self.map_inputs[k]] for k in self.cost_function_parameters]
return self.cost_function.evaluate(y_sim, y_data, weights, sd)
[docs]
def calculate_solution(self, parameters=None):
"""
Calculate solution of model.
Parameters
----------
parameters : array-like (optional)
Parameters to calcualte the solution for. If not provided, it uses the
original parameters.
Returns
-------
solution : pybamm.Solution
The solution for the given inputs.
"""
if parameters is None:
# Create a dictionary of inputs using the original parameters
inputs = {
param: self.x0[i] * self.scalings[i]
for param, i in self.map_inputs.items()
}
else:
# Create a dictionary of inputs using the provided parameters
inputs = {param: parameters[i] for param, i in self.map_inputs.items()}
# Check if the simulation has an attribute "experiment"
if getattr(self.model, "experiment", None):
t_eval = None
else:
# Use the final time from the data as t_eval if experiment is not present
t_eval = [0, self.data["Time [s]"].iloc[-1]]
# Solve the simulation with the given inputs and t_eval
solution = self.model.solve(t_eval=t_eval, inputs=inputs, **self.solve_options)
return solution
def _plot(self, x_optimal):
"""
Plot the optimization result. Should be accessed through the OptimizationResult
plot method.
Parameters
----------
x_optimal : array-like
Optimal values of the parameters found by the optimizer
Returns
-------
plot : pybamm.QuickPlot
The plot of the optimization result
"""
# calculate the solution for the initial and optimal parameters
initial_solution = self.calculate_solution()
optimal_solution = self.calculate_solution(x_optimal)
# create a quick plot
plot = pybamm.QuickPlot(
[initial_solution, optimal_solution],
output_variables=self.variables_to_fit,
labels=["Initial values", "Optimal values"],
)
# plot the result
plot.plot(0)
# plot the data on the same plot
for ax, var in zip(plot.axes, self.variables_to_fit):
data = self.data
ax.plot(
data["Time [s]"] / plot.time_scaling_factor,
data[var],
"k:",
label="Data",
)
return plot