Source code for pybop.problems.base_problem

from typing import Optional

import numpy as np
from pybamm import IDAKLUSolver

from pybop import BaseModel, Dataset, Parameter, Parameters
from pybop.parameters.parameter import Inputs


[docs] class BaseProblem: """ Base class for defining a problem within the PyBOP framework, compatible with PINTS. Parameters ---------- parameters : pybop.Parameter or pybop.Parameters An object or list of the parameters for the problem. model : object, optional The model to be used for the problem (default: None). check_model : bool, optional Flag to indicate if the model should be checked (default: True). signal: list[str], optional A list of variables to analyse (default: ["Voltage [V]"]). domain : str, optional The name of the domain (default: "Time [s]"). additional_variables : list[str], optional Additional variables to observe and store in the solution (default: []). initial_state : dict, optional A valid initial state (default: None). """ def __init__( self, parameters: Parameters, model: Optional[BaseModel] = None, check_model: bool = True, signal: Optional[list[str]] = None, domain: Optional[str] = None, additional_variables: Optional[list[str]] = None, initial_state: Optional[dict] = None, ): signal = signal or ["Voltage [V]"] if isinstance(signal, str): signal = [signal] elif not all(isinstance(item, str) for item in signal): raise ValueError("Signal should be either a string or list of strings.") # Check if parameters is a list of pybop.Parameter objects if isinstance(parameters, list): if all(isinstance(param, Parameter) for param in parameters): parameters = Parameters(*parameters) else: raise TypeError( "All elements in the list must be pybop.Parameter objects." ) # Check if parameters is a single pybop.Parameter object elif isinstance(parameters, Parameter): parameters = Parameters(parameters) # Check if parameters is already a pybop.Parameters object elif not isinstance(parameters, Parameters): raise TypeError( "The input parameters must be a pybop Parameter, a list of pybop.Parameter objects, or a pybop Parameters object." )
[docs] self.parameters = parameters
self.parameters.reset_initial_value()
[docs] self._model = model.copy() if model is not None else None
[docs] self.eis = False
[docs] self.domain = domain or "Time [s]"
[docs] self.check_model = check_model
[docs] self.signal = signal or ["Voltage [V]"]
[docs] self.additional_variables = additional_variables or []
self.set_initial_state(initial_state)
[docs] self._dataset = None
[docs] self._target = None
[docs] self.verbose = False
[docs] self.failure_output = np.asarray([np.inf])
[docs] self.exception = [ "These parameter values are infeasible." ] # TODO: Update to a utility function and add to it on exception creation
if isinstance(self._model, BaseModel): self.eis = self.model.eis self.domain = "Frequency [Hz]" if self.eis else "Time [s]" # If model.solver is IDAKLU, set output vars for improved performance
[docs] self.output_vars = tuple(self.signal + self.additional_variables)
if self._model is not None and isinstance(self._model.solver, IDAKLUSolver): self._solver_copy = self._model.solver.copy() self._model.solver = IDAKLUSolver( atol=self._solver_copy.atol, rtol=self._solver_copy.rtol, root_method=self._solver_copy.root_method, root_tol=self._solver_copy.root_tol, extrap_tol=self._solver_copy.extrap_tol, options=self._solver_copy._options, # noqa: SLF001 output_variables=self.output_vars, )
[docs] def set_initial_state(self, initial_state: Optional[dict] = None): """ Set the initial state to be applied to evaluations of the problem. Parameters ---------- initial_state : dict, optional A valid initial state (default: None). """ self.initial_state = initial_state
@property
[docs] def n_parameters(self): return len(self.parameters)
@property
[docs] def n_outputs(self): return len(self.signal)
[docs] def evaluate(self, inputs: Inputs, eis=False): """ Evaluate the model with the given parameters and return the signal. Parameters ---------- inputs : Inputs Parameters for evaluation of the model. Raises ------ NotImplementedError This method must be implemented by subclasses. """ raise NotImplementedError
[docs] def evaluateS1(self, inputs: Inputs): """ Evaluate the model with the given parameters and return the signal and its derivatives. Parameters ---------- inputs : Inputs Parameters for evaluation of the model. Raises ------ NotImplementedError This method must be implemented by subclasses. """ raise NotImplementedError
[docs] def get_target(self): """ Return the target dataset. Returns ------- np.ndarray The target dataset array. """ return self._target
[docs] def set_target(self, dataset: Dataset): """ Set the target dataset. Parameters ---------- target : Dataset The target dataset array. """ if self.signal is None: raise ValueError("Signal must be defined to set target.") if not isinstance(dataset, Dataset): raise ValueError("Dataset must be a pybop Dataset object.") self._domain_data = dataset[self.domain] self._target = {signal: dataset[signal] for signal in self.signal}
@property
[docs] def model(self): return self._model
@property
[docs] def sensitivities_available(self): return self._model.sensitivities_available
@property
[docs] def target(self): return self._target
@property
[docs] def domain_data(self): return self._domain_data
@domain_data.setter def domain_data(self, domain_data): self._domain_data = domain_data @property
[docs] def dataset(self): return self._dataset
@property
[docs] def pybamm_solution(self): return self.model.pybamm_solution if self.model is not None else None