Source code for pbparam.optimisation_problems.base_optimisation_problem

#
# Base optimisation problem class
#

import pybamm
import pbparam
import numpy as np
import warnings


[docs] class BaseOptimisationProblem: """ Base optimisation problem class This class provides a base for defining optimization problems and contains methods that should be overridden in subclasses to provide specific implementations. This base class will always have the following properties, in subclasses they may be renamed to be more specific to the problem being solved but for internal use they will be renamed back to the generic names. Use in subclasses will use the generic names. Non generic names are for use in the constructor only as a communication to the user. Parameters (required) --------------------- model : :class:`pybamm.Simulation` (or pandas.DataFrame) The object to be used in optimisation of simulation parameters. data : :class:`pandas.DataFrame` The experimental or reference data to be used in optimisation of simulation parameters. Parameters (optional) --------------------- cost_function : :class:`pbparam.BaseCostFunction` Cost function class to be used in minimisation algorithm. 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. variables_to_fit : str or list of str The variable or variables to optimise in the cost function. weights : dict 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. Properties (non user settable defined in constructor) ----------------------------------------------------- x0 : array-like Initial guess for the optimization problem bounds : tuple bounds of the optimization problem scalings : array-like scalings for the optimization problem """ def __init__( self, *args, cost_function=None, data=None, model=None, parameters=None, variables_to_fit=None, weights=None, ): """ Initialize the class properties x0 : array-like Initial guess for the optimization problem bounds : tuple bounds of the optimization problem scalings : array-like scalings for the optimization problem 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 data : class:`pandas.DataFrame` Data object containing the data to be fitted model : :class:`pybamm.Simulation` (or pandas.DataFrame) Model object containing the model to be fitted parameters : dict Parameters object containing the parameters to be fitted variables_to_fit : str or list of str (optional) List of variables to be optimised in the cost function. weights : dict (optional) The weights of the variables_to_fit as a list. """ if len(args) > 0: # if positional arguments are passed raise an error, this enforces the use # of proper names for the arguments during subclass constructors making # inspection easier for future developers. raise NotImplementedError( "BaseOptimisationProblem does not take any positional arguments" ) # Parameters to be defined in constructor self.x0 = None self.bounds = None self.scalings = None # Parameters that can be set by the user self.cost_function = cost_function assert self.cost_function is not None, "cost_function must be defined" self.data = data self.model = model self.parameters = parameters self.variables_to_fit = variables_to_fit self.weights = weights self.map_inputs = {} # This should be a requirement to run if defined, if it's not defined in the # subclass it will pass self.setup_objective_function()
[docs] def process_and_clean_data(self): """ Placeholder for data cleaning and preperation routines This method should be overridden in subclasses to provide specific implementations. """ pass
def collect_parameters(self, solve_options=None): self.solve_options = solve_options or {} # Obtain the new parameters to optimise introduced by the cost function self.cost_function_parameters = self.cost_function._get_parameters( self.variables_to_fit ) self.joint_parameters = { **self.parameters, **self.cost_function_parameters, } # Initialise the parameters_values dictionary self.parameter_values = self.model.parameter_values.copy() # Initialise the dictionary to map each parameter to optimise to the index of x # it corresponds to self._process_parameters() # Update the simulation parameters to inputs for k in self.map_inputs.keys() & self.parameter_values.keys(): self.parameter_values[k] = "[input]" def _process_parameters(self): # Assemble the map_inputs dictionary, where the keys are the names parameters to # optimise and the values are their index in the x array self.map_inputs = { key: index for index, keys in enumerate(self.joint_parameters) for key in (keys if isinstance(keys, tuple) else [keys]) } # Initialise the initial guesses, bounds and scalings for the optimisation # TODO: allow these to not be passed at this stage self.x0 = np.empty([len(self.joint_parameters)]) self.bounds = [None] * len(self.joint_parameters) self.scalings = np.empty([len(self.joint_parameters)]) for i, value in enumerate(self.joint_parameters.values()): if value[0]: scaling = value[0] else: scaling = 1 self.scalings[i] = scaling self.x0[i] = value[0] / scaling self.bounds[i] = tuple(v / scaling for v in value[1])
[docs] def update_simulation_parameters(self, simulation): """ Update the simulation object with new parameter values """ # Remove integrator_specs from solver solver = simulation.solver if hasattr(solver, "integrator_specs"): solver.integrator_specs = {} # Why cant we do this? # self.model.parameter_values = self.parameter_values # self.model.solver = solver # If the simulation has been run then the discretisation already took place # and this would not update the parameters. PyBaMM PR #3267 # (https://github.com/pybamm-team/PyBaMM/pull/3267) no longer allows to change # such attributes # Updating sim params requires recreating the simulation new_simulation = pybamm.Simulation( simulation.model, experiment=getattr(simulation, "experiment", None), geometry=simulation.geometry, parameter_values=self.parameter_values, submesh_types=simulation.submesh_types, var_pts=simulation.var_pts, spatial_methods=simulation.spatial_methods, solver=solver, output_variables=simulation.output_variables, C_rate=getattr(simulation, "C_rate", None), ) self.model = new_simulation
def process_weights(self): if self.weights is None: # No weights provided, initialize with default values self.weights = { var: [1 / np.nanmean(self.data[var])] * len(self.data[var]) for var in self.variables_to_fit } else: for var in self.variables_to_fit: if var not in self.weights: raise ValueError( "Weights dictionary should contain all \ variables in variables_to_fit." ) if self.weights is not None: for var, weight in self.weights.items(): if len(weight) != 1 and len(weight) != len(self.data[var]): raise ValueError( f"Length of weights[{var}] should be 1 or \ same as the length of data." ) # Give warning if weights are given with MLE if isinstance(self.cost_function, pbparam.MLE) and self.weights is not None: warnings.warn("Weights are provided but not used in the MLE calculation.")
[docs] def objective_function(self, x): """ Placeholder method for the objective function Subclasses will override this method to provide specific implementations Parameters ---------- x : array-like independent variable for the objective function Returns ------- float the value of the objective function """ raise NotImplementedError( "objective_function not defined, setup_objective_function needs to" " be called first" )
[docs] def setup_objective_function(self): """ Placeholder method for setting up the objective function Subclasses will override this method to provide specific implementations """ pass
[docs] def calculate_solution(self): """ Placeholder method for calculating the solution of the optimization problem Subclasses will override this method to provide specific implementations """ pass
def _plot(self, x): """ Placeholder method for plotting the optimization problem Subclasses will override this method to provide specific implementations Parameters ---------- x : array-like independent variable for the plot """ pass