Source code for pybop.costs.weighted_cost

import numpy as np

from pybop.costs.base_cost import BaseCost
from pybop.costs.design_cost import DesignCost
from pybop.costs.evaluation import Evaluation
from pybop.parameters.parameter import Inputs
from pybop.processing.dataset import Dataset
from pybop.simulators.solution import Solution


[docs] class WeightedCost(BaseCost): """ A subclass for constructing a linear combination of cost functions as a single weighted cost function. Parameters ---------- costs : pybop.BaseCost The individual PyBOP cost objects. weights : list[float] A list of values with which to weight the cost values. """ def __init__(self, *costs, weights: list[float] | None = None): if not all(isinstance(cost, BaseCost) for cost in costs): raise TypeError("All costs must be instances of BaseCost.") if len(set(isinstance(cost, DesignCost) for cost in costs)) > 1: raise TypeError( "Costs must be either all design costs or all error measures." ) self.costs = [cost for cost in costs] if len(set(cost.domain for cost in self.costs)) > 1: raise ValueError("All costs must have the same domain.") super().__init__() self._domain = self.costs[0].domain for cost in self.costs: self.parameters.join(cost.parameters) target_dataset = self.costs[0]._dataset # noqa: SLF001 self.set_target( [cost.target for cost in self.costs], dataset=None if target_dataset is None else Dataset(target_dataset, domain=self.costs[0].domain), ) # 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)) # Apply the minimising property from each cost for i, cost in enumerate(self.costs): self.weights[i] = self.weights[i] * (1 if cost.minimising else -1) if all(not cost.minimising for cost in self.costs): # If all costs are maximising, convert the weighted cost to maximising self.weights = -self.weights self.minimising = False
[docs] def evaluate_batch( self, solution: list[Solution], inputs: list[Inputs], calculate_sensitivities: bool = False, ) -> Evaluation: """ Computes the cost function for the given predictions. Parameters ---------- solution : list[Solution] A list of simulation results. inputs : list[Inputs] The corresponding list of input parameters. calculate_sensitivities : bool Whether to also return the sensitivities (default: False). """ # Preallocate the evaluation results weighted_evaluation = Evaluation() weighted_evaluation.preallocate( inputs=inputs, calculate_sensitivities=calculate_sensitivities ) for j, (sol, x) in enumerate(zip(solution, inputs, strict=False)): e = np.empty(len(self.costs)) de = ( {key: np.zeros(len(self.costs)) for key in x.keys()} if calculate_sensitivities else None ) for i, cost in enumerate(self.costs): evaluation = cost.evaluate( sol, inputs=x, calculate_sensitivities=calculate_sensitivities ) e[i] = evaluation.values.item() if calculate_sensitivities: for key, value in evaluation.sensitivities.items(): de[key][i] = value.item() # Sum with weighting e = np.dot(e, self.weights) if calculate_sensitivities: for key in de.keys(): de[key] = np.dot(de[key], self.weights) weighted_evaluation.insert_result(i=j, value=e, sensitivities=de) return weighted_evaluation
[docs] def set_target( self, target: list[list[str]] | list[str] | str | None = None, dataset: Dataset | None = None, ): """Set the target variable for all costs. Expecting a list of list[str] the same length as self.costs.""" target = [target] if isinstance(target, str) else target or self._target if isinstance(target[0], str): target = [target] * len(self.costs) self._target = [] for i, cost in enumerate(self.costs): cost.set_target(target[i], dataset=dataset) self._target.extend(cost.target) super().set_target(target=self.target, dataset=dataset)
@property def target(self): return list(set(self._target))