Source code for pybop.costs.design_costs

import warnings

import numpy as np

from pybop import is_numeric
from pybop.costs.base_cost import BaseCost


[docs] class DesignCost(BaseCost): """ Overwrites and extends `BaseCost` class for design-related cost functions. Inherits all parameters and attributes from ``BaseCost``. Additional Attributes --------------------- problem : object The associated problem containing model and evaluation methods. parameter_set : object) The set of parameters from the problem's model. dt : float The time step size used in the simulation. """ def __init__(self, problem, update_capacity=False): """ Initialises the gravimetric energy density calculator with a problem. Parameters ---------- problem : object The problem instance containing the model and data. """ super(DesignCost, self).__init__(problem) self.problem = problem if update_capacity is True: nominal_capacity_warning = ( "The nominal capacity is approximated for each iteration." ) else: nominal_capacity_warning = ( "The nominal capacity is fixed at the initial model value." ) warnings.warn(nominal_capacity_warning, UserWarning) self.update_capacity = update_capacity self.parameter_set = problem.model.parameter_set self.update_simulation_data(problem.x0)
[docs] def update_simulation_data(self, initial_conditions): """ Updates the simulation data based on the initial conditions. Parameters ---------- initial_conditions : array The initial conditions for the simulation. """ if self.update_capacity: self.problem.model.approximate_capacity(self.problem.x0) solution = self.problem.evaluate(initial_conditions) if "Time [s]" not in solution: raise ValueError("The solution does not contain time data.") self.problem._time_data = solution["Time [s]"] self.problem._target = {key: solution[key] for key in self.problem.signal} self.dt = solution["Time [s]"][1] - solution["Time [s]"][0]
[docs] def _evaluate(self, x, grad=None): """ Computes the value of the cost function. This method must be implemented by subclasses. Parameters ---------- x : array The parameter set for which to compute the cost. grad : array, optional Gradient information, not used in this method. Raises ------ NotImplementedError If the method has not been implemented by the subclass. """ raise NotImplementedError
[docs] class GravimetricEnergyDensity(DesignCost): """ Represents the gravimetric energy density of a battery cell, calculated based on a normalised discharge from upper to lower voltage limits. The goal is to maximise the energy density, which is achieved by minimizing the negative energy density reported by this class. Inherits all parameters and attributes from ``DesignCost``. """ def __init__(self, problem, update_capacity=False): super(GravimetricEnergyDensity, self).__init__(problem, update_capacity)
[docs] def _evaluate(self, x, grad=None): """ Computes the cost function for the energy density. Parameters ---------- x : array The parameter set for which to compute the cost. grad : array, optional Gradient information, not used in this method. Returns ------- float The negative gravimetric energy density or infinity in case of infeasible parameters. """ if not all(is_numeric(i) for i in x): raise ValueError("Input must be a numeric array.") try: with warnings.catch_warnings(): # Convert UserWarning to an exception warnings.filterwarnings("error", category=UserWarning) if self.update_capacity: self.problem.model.approximate_capacity(x) solution = self.problem.evaluate(x) voltage, current = solution["Voltage [V]"], solution["Current [A]"] negative_energy_density = -np.trapz(voltage * current, dx=self.dt) / ( 3600 * self.problem.model.cell_mass(self.parameter_set) ) return negative_energy_density # Catch infeasible solutions and return infinity except UserWarning as e: print(f"Ignoring this sample due to: {e}") return np.inf # Catch any other exception and return infinity except Exception as e: print(f"An error occurred during the evaluation: {e}") return np.inf
[docs] class VolumetricEnergyDensity(DesignCost): """ Represents the volumetric energy density of a battery cell, calculated based on a normalised discharge from upper to lower voltage limits. The goal is to maximise the energy density, which is achieved by minimizing the negative energy density reported by this class. Inherits all parameters and attributes from ``DesignCost``. """ def __init__(self, problem, update_capacity=False): super(VolumetricEnergyDensity, self).__init__(problem, update_capacity)
[docs] def _evaluate(self, x, grad=None): """ Computes the cost function for the energy density. Parameters ---------- x : array The parameter set for which to compute the cost. grad : array, optional Gradient information, not used in this method. Returns ------- float The negative volumetric energy density or infinity in case of infeasible parameters. """ if not all(is_numeric(i) for i in x): raise ValueError("Input must be a numeric array.") try: with warnings.catch_warnings(): # Convert UserWarning to an exception warnings.filterwarnings("error", category=UserWarning) if self.update_capacity: self.problem.model.approximate_capacity(x) solution = self.problem.evaluate(x) voltage, current = solution["Voltage [V]"], solution["Current [A]"] negative_energy_density = -np.trapz(voltage * current, dx=self.dt) / ( 3600 * self.problem.model.cell_volume(self.parameter_set) ) return negative_energy_density # Catch infeasible solutions and return infinity except UserWarning as e: print(f"Ignoring this sample due to: {e}") return np.inf # Catch any other exception and return infinity except Exception as e: print(f"An error occurred during the evaluation: {e}") return np.inf