Source code for pybop.costs.fitting_costs

from typing import Optional, Union

import numpy as np

from pybop.costs.base_cost import BaseCost
from pybop.observers.observer import Observer


[docs] class FittingCost(BaseCost): """ Overwrites and extends `BaseCost` class for fitting-type cost functions. This class is intended to be subclassed to create specific cost functions for evaluating model predictions against a set of data. The cost function quantifies the goodness-of-fit between the model predictions and the observed data, with a lower cost value indicating a better fit. Additional Parameters --------------------- weighting : Union[str, np.ndarray], optional The type of weighting to use when taking the sum or mean of the error measure. """ def __init__(self, problem, weighting: Union[str, np.ndarray] = None): super().__init__(problem)
[docs] self.weighting = None
if weighting == "equal" or weighting is None: self.weighting = 1.0 elif weighting == "domain": # Normalise the residuals by the domain spacing (for a uniform domain, # this is the same as a uniform weighting) domain_data = self.problem.domain_data domain_spacing = domain_data[1:] - domain_data[:-1] mean_spacing = np.mean(domain_spacing) self.weighting = np.concatenate( ( [(mean_spacing + domain_spacing[0]) / 2], (domain_spacing[1:] + domain_spacing[:-1]) / 2, [(domain_spacing[-1] + mean_spacing) / 2], ) ) * ((len(domain_data) - 1) / (domain_data[-1] - domain_data[0])) else: self.weighting = np.asarray(weighting)
[docs] def compute( self, y: dict, dy: Optional[dict] = None, ) -> Union[float, tuple[float, np.ndarray]]: """ Computes the cost function for the given predictions. Parameters ---------- y : dict[str, np.ndarray[np.float64]] The dictionary of predictions with keys designating the signals for fitting. dy : dict[str, dict[str, np.ndarray]], optional The corresponding sensitivities to each parameter for each signal. Returns ------- np.float64 or tuple[np.float64, np.ndarray[np.float64]] If dy is not None, returns a tuple containing the cost (float) and the gradient with dimension (len(parameters)), otherwise returns only the cost. """ # Early return if the prediction is not verified if not self.verify_prediction(y): return (np.inf, self.grad_fail) if dy is not None else np.inf # Compute the residual for all signals r = np.asarray([y[signal] - self._target[signal] for signal in self.signal]) # Extract the sensitivities for all signals and parameters if dy is not None: dy = self.stack_sensitivities(dy) return self._error_measure(r=r, dy=dy)
[docs] def _error_measure( self, r: np.ndarray, dy: Optional[np.ndarray] = None, ) -> Union[float, tuple[float, np.ndarray]]: """ Computes the cost function for the given predictions. Parameters ---------- r : np.ndarray The residual difference between the model prediction and the target. The dimensions of r are (len(signal), len(domain_data)). dy : np.ndarray, optional The corresponding gradient with respect to the parameters for each signal. The dimensions of dy are (len(parameters), len(signal), len(domain_data)). Returns ------- np.float64 or tuple[np.float64, np.ndarray[np.float64]] If dy is not None, returns a tuple containing the cost (float) and the gradient with dimension (len(parameters)), otherwise returns only the cost. """ raise NotImplementedError
[docs] class ObserverCost(BaseCost): """ Observer cost function. Computes the cost function for an observer model, which is log likelihood of the data points given the model parameters. Inherits all parameters and attributes from ``BaseCost``. """ def __init__(self, observer: Observer): super().__init__(problem=observer)
[docs] self._observer = observer
[docs] self._has_separable_problem = False
[docs] def compute( self, y: dict, dy: Optional[np.ndarray] = None, ) -> float: """ Computes the cost function for the given predictions. Parameters ---------- y : dict The dictionary of predictions with keys designating the signals for fitting. dy : np.ndarray, optional The corresponding gradient with respect to the parameters for each signal. Returns ------- float The observer cost (negative of the log likelihood). """ inputs = self._parameters.as_dict() log_likelihood = self._observer.log_likelihood( self._target, self._observer.domain_data, inputs ) return -log_likelihood