Source code for pybop.costs._weighted_cost

from typing import Optional, Union

import numpy as np

from pybop import BaseCost, BaseLikelihood, DesignCost


[docs] class WeightedCost(BaseCost): """ A subclass for constructing a linear combination of cost functions as a single weighted cost function. Inherits all parameters and attributes from ``BaseCost``. Attributes --------------------- costs : pybop.BaseCost The individual PyBOP cost objects. weights : list[float] A list of values with which to weight the cost values. has_identical_problems : bool If True, the shared problem will be evaluated once and saved before the self.compute() method of each cost is called (default: False). has_separable_problem: bool This attribute must be set to False for WeightedCost objects. If the corresponding attribute of an individual cost is True, the problem is separable from the cost function and will be evaluated before the individual cost evaluation is called. """ def __init__(self, *costs, weights: Optional[list[float]] = None): if not all(isinstance(cost, BaseCost) for cost in costs): raise TypeError("All costs must be instances of BaseCost.")
[docs] self.costs = [cost for cost in costs]
if len(set(type(cost.problem) for cost in self.costs)) > 1: raise TypeError("All problems must be of the same class type.")
[docs] self.minimising = not any( isinstance(cost, (BaseLikelihood, DesignCost)) for cost in self.costs )
# Check if weights are provided if weights is not None: try: self.weights = np.asarray(weights, dtype=float) except ValueError: raise ValueError("Weights must be numeric values.") from None if self.weights.size != len(self.costs): raise ValueError("Number of weights must match number of costs.") else: self.weights = np.ones(len(self.costs)) # Check if all costs depend on the same problem
[docs] self._has_identical_problems = all( cost.has_separable_problem and cost.problem is self.costs[0].problem for cost in self.costs )
if self._has_identical_problems:
[docs] super().__init__(self.costs[0].problem) else:
super().__init__() for cost in self.costs: self.join_parameters(cost.parameters) # Weighted costs do not use this functionality
[docs] self._has_separable_problem = False
[docs] def compute( self, y: dict, dy: np.ndarray = None, calculate_grad: bool = False, ) -> Union[float, tuple[float, np.ndarray]]: """ 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. calculate_grad : bool, optional A bool condition designating whether to calculate the gradient. Returns ------- float The weighted cost value. """ if self._has_identical_problems: inputs = self.problem.parameters.as_dict() if calculate_grad: y, dy = self.problem.evaluateS1(inputs) else: y = self.problem.evaluate(inputs) e = np.empty_like(self.costs) de = np.empty((len(self.parameters), len(self.costs))) for i, cost in enumerate(self.costs): inputs = cost.parameters.as_dict() if self._has_identical_problems: y, dy = (y, dy) elif cost.has_separable_problem: if calculate_grad: y, dy = cost.problem.evaluateS1(inputs) else: y = cost.problem.evaluate(inputs) if calculate_grad: e[i], de[:, i] = cost.compute(y, dy=dy, calculate_grad=True) else: e[i] = cost.compute(y) e = np.dot(e, self.weights) if calculate_grad: de = np.dot(de, self.weights) return e, de return e
@property
[docs] def has_identical_problems(self): return self._has_identical_problems