From c44df71d6371d7c4e970137822344f5bbf593f93 Mon Sep 17 00:00:00 2001 From: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:49:14 +0100 Subject: [PATCH 1/8] Add PowerDensity costs --- ...pme_max_energy.py => maximising_energy.py} | 11 +- examples/scripts/maximising_power.py | 62 ++++++++++ pybop/__init__.py | 2 + pybop/costs/design_costs.py | 110 ++++++++++++++++++ tests/unit/test_cost.py | 2 + 5 files changed, 183 insertions(+), 4 deletions(-) rename examples/scripts/{spme_max_energy.py => maximising_energy.py} (86%) create mode 100644 examples/scripts/maximising_power.py diff --git a/examples/scripts/spme_max_energy.py b/examples/scripts/maximising_energy.py similarity index 86% rename from examples/scripts/spme_max_energy.py rename to examples/scripts/maximising_energy.py index 51e5bf535..2a03639eb 100644 --- a/examples/scripts/spme_max_energy.py +++ b/examples/scripts/maximising_energy.py @@ -3,8 +3,8 @@ # A design optimisation example loosely based on work by L.D. Couto # available at https://doi.org/10.1016/j.energy.2022.125966. -# The target is to maximise the gravimetric energy density over a -# range of possible design parameter values, including for example: +# The target is to maximise the energy density over a range of +# possible design parameter values, including for example: # cross-sectional area = height x width (only need change one) # electrode widths, particle radii, volume fractions and # separator width. @@ -29,7 +29,10 @@ # Define test protocol experiment = pybop.Experiment( - ["Discharge at 1C until 2.5 V (5 seconds period)"], + [ + "Discharge at 1C until 2.5 V (5 seconds period)", + "Hold at 2.5 V for 30 minutes or until 10 mA (5 seconds period)", + ], ) signal = ["Voltage [V]", "Current [A]"] @@ -63,4 +66,4 @@ pybop.quick_plot(problem, problem_inputs=x, title="Optimised Comparison") # Plot the cost landscape with optimisation path -pybop.plot2d(optim, steps=5) +pybop.plot2d(optim, steps=15) diff --git a/examples/scripts/maximising_power.py b/examples/scripts/maximising_power.py new file mode 100644 index 000000000..b26535a70 --- /dev/null +++ b/examples/scripts/maximising_power.py @@ -0,0 +1,62 @@ +import pybop + +# Define parameter set and model +parameter_set = pybop.ParameterSet.pybamm("Chen2020", formation_concentrations=True) +model = pybop.lithium_ion.SPMe(parameter_set=parameter_set) + +# Define useful quantities +nominal_capacity = parameter_set["Nominal cell capacity [A.h]"] +target_c_rate = 2 +discharge_rate = target_c_rate * nominal_capacity + +# Fitting parameters +parameters = pybop.Parameters( + pybop.Parameter( + "Positive electrode thickness [m]", + prior=pybop.Gaussian(7.56e-05, 0.5e-05), + bounds=[65e-06, 10e-05], + ), + pybop.Parameter( + "Nominal cell capacity [A.h]", # controls the C-rate in the experiment + prior=pybop.Gaussian(discharge_rate, 0.2), + bounds=[0.8 * discharge_rate, 1.2 * discharge_rate], + ), +) + +# Define test protocol +experiment = pybop.Experiment( + ["Discharge at 1C for 30 minutes or until 2.5 V (5 seconds period)"], +) +signal = ["Voltage [V]", "Current [A]"] + +# Generate problem +problem = pybop.DesignProblem( + model, + parameters, + experiment, + signal=signal, + initial_state={"Initial SoC": 1.0}, +) + +# Generate multiple cost functions and combine them +cost1 = pybop.GravimetricPowerDensity(problem, target_time=3600/target_c_rate) +cost2 = pybop.VolumetricPowerDensity(problem, target_time=3600/target_c_rate) +cost = pybop.WeightedCost(cost1, cost2, weights=[1, 1e-3]) + +# Run optimisation +optim = pybop.XNES( + cost, verbose=True, allow_infeasible_solutions=False, max_iterations=10 +) +x, final_cost = optim.run() +print("Estimated parameters:", x) +print(f"Initial gravimetric power density: {cost1(optim.x0):.2f} W.kg-1") +print(f"Optimised gravimetric power density: {cost1(x):.2f} W.kg-1") +print(f"Initial volumetric power density: {cost2(optim.x0):.2f} W.m-3") +print(f"Optimised volumetric power density: {cost2(x):.2f} W.m-3") +print(f"Optimised discharge rate: {x[-1]:.2f} A = {x[-1]/nominal_capacity:.2f} C") + +# Plot the timeseries output +pybop.quick_plot(problem, problem_inputs=x, title="Optimised Comparison") + +# Plot the cost landscape with optimisation path +pybop.plot2d(optim, steps=15) diff --git a/pybop/__init__.py b/pybop/__init__.py index 6863ccf8d..f4420bb27 100644 --- a/pybop/__init__.py +++ b/pybop/__init__.py @@ -105,6 +105,8 @@ DesignCost, GravimetricEnergyDensity, VolumetricEnergyDensity, + GravimetricPowerDensity, + VolumetricPowerDensity, ) from .costs._likelihoods import ( BaseLikelihood, diff --git a/pybop/costs/design_costs.py b/pybop/costs/design_costs.py index 78d513f68..d868d9a79 100644 --- a/pybop/costs/design_costs.py +++ b/pybop/costs/design_costs.py @@ -124,3 +124,113 @@ def compute( ) return energy_density + + +class GravimetricPowerDensity(DesignCost): + """ + Represents the gravimetric power density of a battery cell, calculated based + on a normalised discharge from upper to lower voltage limits. The goal is to + maximise the power density, which is achieved by setting minimising = False + in the optimiser settings. + + Inherits all parameters and attributes from ``DesignCost``. + + Additional parameters + --------------------- + target_time : int + The length of time (seconds) over which the power should be sustained. + """ + + def __init__(self, problem, target_time: int = 3600): + super().__init__(problem) + self.target_time = target_time + + def compute( + self, + y: dict, + dy: np.ndarray = None, + calculate_grad: bool = False, + ) -> 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. + Note: not used in design optimisation classes. + calculate_grad : bool, optional + A bool condition designating whether to calculate the gradient. + + Returns + ------- + float + The gravimetric power density or -infinity in case of infeasible parameters. + """ + if not any(np.isfinite(y[signal][0]) for signal in self.signal): + return -np.inf + + voltage, current = y["Voltage [V]"], y["Current [A]"] + dt = y["Time [s]"][1] - y["Time [s]"][0] + time_averaged_power_density = np.trapz(voltage * current, dx=dt) / ( + self.target_time * 3600 * self.problem.model.cell_mass() + ) + + return time_averaged_power_density + + +class VolumetricPowerDensity(DesignCost): + """ + Represents the volumetric power density of a battery cell, calculated based + on a normalised discharge from upper to lower voltage limits. The goal is to + maximise the power density, which is achieved by setting minimising = False + in the optimiser settings. + + Inherits all parameters and attributes from ``DesignCost``. + + Additional parameters + --------------------- + target_time : int + The length of time (seconds) over which the power should be sustained. + """ + + def __init__(self, problem, target_time: int = 3600): + super().__init__(problem) + self.target_time = target_time + + def compute( + self, + y: dict, + dy: np.ndarray = None, + calculate_grad: bool = False, + ) -> 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. + Note: not used in design optimisation classes. + calculate_grad : bool, optional + A bool condition designating whether to calculate the gradient. + + Returns + ------- + float + The volumetric power density or -infinity in case of infeasible parameters. + """ + if not any(np.isfinite(y[signal][0]) for signal in self.signal): + return -np.inf + + voltage, current = y["Voltage [V]"], y["Current [A]"] + dt = y["Time [s]"][1] - y["Time [s]"][0] + time_averaged_power_density = np.trapz(voltage * current, dx=dt) / ( + self.target_time * 3600 * self.problem.model.cell_volume() + ) + + return time_averaged_power_density diff --git a/tests/unit/test_cost.py b/tests/unit/test_cost.py index 2aad7169b..a8247c5a5 100644 --- a/tests/unit/test_cost.py +++ b/tests/unit/test_cost.py @@ -219,6 +219,8 @@ def design_problem(self, parameters, experiment, signal): pybop.DesignCost, pybop.GravimetricEnergyDensity, pybop.VolumetricEnergyDensity, + pybop.GravimetricPowerDensity, + pybop.VolumetricPowerDensity, ], ) @pytest.mark.unit From 0e5caeb22c76c9ad71d9144edb5a19e10897a2fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:51:13 +0000 Subject: [PATCH 2/8] style: pre-commit fixes --- examples/scripts/maximising_power.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scripts/maximising_power.py b/examples/scripts/maximising_power.py index b26535a70..68d28b330 100644 --- a/examples/scripts/maximising_power.py +++ b/examples/scripts/maximising_power.py @@ -39,8 +39,8 @@ ) # Generate multiple cost functions and combine them -cost1 = pybop.GravimetricPowerDensity(problem, target_time=3600/target_c_rate) -cost2 = pybop.VolumetricPowerDensity(problem, target_time=3600/target_c_rate) +cost1 = pybop.GravimetricPowerDensity(problem, target_time=3600 / target_c_rate) +cost2 = pybop.VolumetricPowerDensity(problem, target_time=3600 / target_c_rate) cost = pybop.WeightedCost(cost1, cost2, weights=[1, 1e-3]) # Run optimisation From 3139504e1d2b5adce31a1d6ed51def3468b3e16a Mon Sep 17 00:00:00 2001 From: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:54:24 +0100 Subject: [PATCH 3/8] Reduce steps back to 5 --- examples/scripts/maximising_energy.py | 2 +- examples/scripts/maximising_power.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/scripts/maximising_energy.py b/examples/scripts/maximising_energy.py index 2a03639eb..cd5b1b60b 100644 --- a/examples/scripts/maximising_energy.py +++ b/examples/scripts/maximising_energy.py @@ -66,4 +66,4 @@ pybop.quick_plot(problem, problem_inputs=x, title="Optimised Comparison") # Plot the cost landscape with optimisation path -pybop.plot2d(optim, steps=15) +pybop.plot2d(optim, steps=5) diff --git a/examples/scripts/maximising_power.py b/examples/scripts/maximising_power.py index 68d28b330..411ae3d58 100644 --- a/examples/scripts/maximising_power.py +++ b/examples/scripts/maximising_power.py @@ -59,4 +59,4 @@ pybop.quick_plot(problem, problem_inputs=x, title="Optimised Comparison") # Plot the cost landscape with optimisation path -pybop.plot2d(optim, steps=15) +pybop.plot2d(optim, steps=5) From d011fc9871d2c50ce5d1bfa2d2411f03a3f40114 Mon Sep 17 00:00:00 2001 From: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:26:38 +0100 Subject: [PATCH 4/8] Add integrals to docstrings --- pybop/costs/design_costs.py | 61 ++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/pybop/costs/design_costs.py b/pybop/costs/design_costs.py index d868d9a79..7300ece4e 100644 --- a/pybop/costs/design_costs.py +++ b/pybop/costs/design_costs.py @@ -30,11 +30,19 @@ def __init__(self, problem): 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 setting minimising = False + Calculates the gravimetric energy density (specific energy) of a battery cell, + when applied to a normalised discharge from upper to lower voltage limits. The + goal of maximising the energy density is achieved by setting minimising = False in the optimiser settings. + The gravimetric energy density [Wh.kg-1] is calculated as + $$ + \displaystyle \frac{1}{3600 m} \int_{t=0}^{t=T} I(t) V(t) dt + $$ + where $m$ is the cell mass, $t$ is the time, $T$ is the total time, $I$ is the + current and $V$ is the voltage. The factor of 1/3600 is included to convert + from seconds to hours. + Inherits all parameters and attributes from ``DesignCost``. """ @@ -79,10 +87,18 @@ def compute( 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 setting minimising = False - in the optimiser settings. + Calculates the (volumetric) energy density of a battery cell, when applied to a + normalised discharge from upper to lower voltage limits. The goal of maximising + the energy density is achieved by setting minimising = False in the optimiser + settings. + + The volumetric energy density [Wh.m-3] is calculated as + $$ + \displaystyle \frac{1}{3600 v} \int_{t=0}^{t=T} I(t) V(t) dt + $$ + where $v$ is the cell volume, $t$ is the time, $T$ is the total time, $I$ is + the current and $V$ is the voltage. The factor of 1/3600 is included to convert + from seconds to hours. Inherits all parameters and attributes from ``DesignCost``. """ @@ -128,10 +144,18 @@ def compute( class GravimetricPowerDensity(DesignCost): """ - Represents the gravimetric power density of a battery cell, calculated based - on a normalised discharge from upper to lower voltage limits. The goal is to - maximise the power density, which is achieved by setting minimising = False - in the optimiser settings. + Calculates the gravimetric power density (specific power) of a battery cell, + when applied to a discharge from upper to lower voltage limits. The goal of + maximising the power density is achieved by setting minimising = False in the + optimiser settings. + + The time-averaged gravimetric power density [W.kg-1] is calculated as + $$ + \displaystyle \frac{1}{3600 m T} \int_{t=0}^{t=T} I(t) V(t) dt + $$ + where $m$ is the cell mass, $t$ is the time, $T$ is the total time, $I$ is the + current and $V$ is the voltage. The factor of 1/3600 is included to convert + from seconds to hours. Inherits all parameters and attributes from ``DesignCost``. @@ -183,10 +207,17 @@ def compute( class VolumetricPowerDensity(DesignCost): """ - Represents the volumetric power density of a battery cell, calculated based - on a normalised discharge from upper to lower voltage limits. The goal is to - maximise the power density, which is achieved by setting minimising = False - in the optimiser settings. + Calculates the (volumetric) power density of a battery cell, when applied to a + discharge from upper to lower voltage limits. The goal of maximising the power + density is achieved by setting minimising = False in the optimiser settings. + + The time-averaged volumetric power density [W.m-3] is calculated as + $$ + \displaystyle \frac{1}{3600 v T} \int_{t=0}^{t=T} I(t) V(t) dt + $$ + where $v$ is the cell volume, $t$ is the time, $T$ is the total time, $I$ is + the current and $V$ is the voltage. The factor of 1/3600 is included to convert + from seconds to hours. Inherits all parameters and attributes from ``DesignCost``. From 1244d9ecf91b62a8230d3851fc3d7fa5cbed7bca Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 11:28:25 +0000 Subject: [PATCH 5/8] style: pre-commit fixes --- pybop/costs/design_costs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pybop/costs/design_costs.py b/pybop/costs/design_costs.py index 7300ece4e..9e98b6889 100644 --- a/pybop/costs/design_costs.py +++ b/pybop/costs/design_costs.py @@ -37,7 +37,7 @@ class GravimetricEnergyDensity(DesignCost): The gravimetric energy density [Wh.kg-1] is calculated as $$ - \displaystyle \frac{1}{3600 m} \int_{t=0}^{t=T} I(t) V(t) dt + \\displaystyle \frac{1}{3600 m} \\int_{t=0}^{t=T} I(t) V(t) dt $$ where $m$ is the cell mass, $t$ is the time, $T$ is the total time, $I$ is the current and $V$ is the voltage. The factor of 1/3600 is included to convert @@ -94,7 +94,7 @@ class VolumetricEnergyDensity(DesignCost): The volumetric energy density [Wh.m-3] is calculated as $$ - \displaystyle \frac{1}{3600 v} \int_{t=0}^{t=T} I(t) V(t) dt + \\displaystyle \frac{1}{3600 v} \\int_{t=0}^{t=T} I(t) V(t) dt $$ where $v$ is the cell volume, $t$ is the time, $T$ is the total time, $I$ is the current and $V$ is the voltage. The factor of 1/3600 is included to convert @@ -151,7 +151,7 @@ class GravimetricPowerDensity(DesignCost): The time-averaged gravimetric power density [W.kg-1] is calculated as $$ - \displaystyle \frac{1}{3600 m T} \int_{t=0}^{t=T} I(t) V(t) dt + \\displaystyle \frac{1}{3600 m T} \\int_{t=0}^{t=T} I(t) V(t) dt $$ where $m$ is the cell mass, $t$ is the time, $T$ is the total time, $I$ is the current and $V$ is the voltage. The factor of 1/3600 is included to convert @@ -213,7 +213,7 @@ class VolumetricPowerDensity(DesignCost): The time-averaged volumetric power density [W.m-3] is calculated as $$ - \displaystyle \frac{1}{3600 v T} \int_{t=0}^{t=T} I(t) V(t) dt + \\displaystyle \frac{1}{3600 v T} \\int_{t=0}^{t=T} I(t) V(t) dt $$ where $v$ is the cell volume, $t$ is the time, $T$ is the total time, $I$ is the current and $V$ is the voltage. The factor of 1/3600 is included to convert From 63347f8d1d6435de618c0c87fea7b229c8717c37 Mon Sep 17 00:00:00 2001 From: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:03:27 +0100 Subject: [PATCH 6/8] Use mathjax for sphinx --- docs/conf.py | 3 ++- pybop/costs/design_costs.py | 48 ++++++++++++++++++------------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d7c54b116..e602d17f3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,12 +20,13 @@ "sphinx.ext.napoleon", "sphinx.ext.autodoc", "sphinx.ext.autosummary", + "sphinx.ext.mathjax", "sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx_design", "sphinx_copybutton", "autoapi.extension", - # custom extentions + # custom extensions "_extension.gallery_directive", # For extension examples and demos "myst_parser", diff --git a/pybop/costs/design_costs.py b/pybop/costs/design_costs.py index 9e98b6889..b543402d7 100644 --- a/pybop/costs/design_costs.py +++ b/pybop/costs/design_costs.py @@ -36,12 +36,12 @@ class GravimetricEnergyDensity(DesignCost): in the optimiser settings. The gravimetric energy density [Wh.kg-1] is calculated as - $$ - \\displaystyle \frac{1}{3600 m} \\int_{t=0}^{t=T} I(t) V(t) dt - $$ - where $m$ is the cell mass, $t$ is the time, $T$ is the total time, $I$ is the - current and $V$ is the voltage. The factor of 1/3600 is included to convert - from seconds to hours. + .. math:: + \\frac{1}{3600 m} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t + + where m is the cell mass, t is the time, T is the total time, I is the current + and V is the voltage. The factor of 1/3600 is included to convert from seconds + to hours. Inherits all parameters and attributes from ``DesignCost``. """ @@ -93,12 +93,12 @@ class VolumetricEnergyDensity(DesignCost): settings. The volumetric energy density [Wh.m-3] is calculated as - $$ - \\displaystyle \frac{1}{3600 v} \\int_{t=0}^{t=T} I(t) V(t) dt - $$ - where $v$ is the cell volume, $t$ is the time, $T$ is the total time, $I$ is - the current and $V$ is the voltage. The factor of 1/3600 is included to convert - from seconds to hours. + .. math:: + \\frac{1}{3600 v} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t + + where v is the cell volume, t is the time, T is the total time, I is the current + and V is the voltage. The factor of 1/3600 is included to convert from seconds + to hours. Inherits all parameters and attributes from ``DesignCost``. """ @@ -150,12 +150,12 @@ class GravimetricPowerDensity(DesignCost): optimiser settings. The time-averaged gravimetric power density [W.kg-1] is calculated as - $$ - \\displaystyle \frac{1}{3600 m T} \\int_{t=0}^{t=T} I(t) V(t) dt - $$ - where $m$ is the cell mass, $t$ is the time, $T$ is the total time, $I$ is the - current and $V$ is the voltage. The factor of 1/3600 is included to convert - from seconds to hours. + .. math:: + \\frac{1}{3600 m T} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t + + where m is the cell mass, t is the time, T is the total time, I is the current + and V is the voltage. The factor of 1/3600 is included to convert from seconds + to hours. Inherits all parameters and attributes from ``DesignCost``. @@ -212,12 +212,12 @@ class VolumetricPowerDensity(DesignCost): density is achieved by setting minimising = False in the optimiser settings. The time-averaged volumetric power density [W.m-3] is calculated as - $$ - \\displaystyle \frac{1}{3600 v T} \\int_{t=0}^{t=T} I(t) V(t) dt - $$ - where $v$ is the cell volume, $t$ is the time, $T$ is the total time, $I$ is - the current and $V$ is the voltage. The factor of 1/3600 is included to convert - from seconds to hours. + .. math:: + \\frac{1}{3600 v T} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t + + where v is the cell volume, t is the time, T is the total time, I is the current + and V is the voltage. The factor of 1/3600 is included to convert from seconds + to hours. Inherits all parameters and attributes from ``DesignCost``. From 4f3db76763b7bdcec36941800aaab5938339aba0 Mon Sep 17 00:00:00 2001 From: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:26:35 +0100 Subject: [PATCH 7/8] Insert line breaks --- pybop/costs/design_costs.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pybop/costs/design_costs.py b/pybop/costs/design_costs.py index b543402d7..fde33385d 100644 --- a/pybop/costs/design_costs.py +++ b/pybop/costs/design_costs.py @@ -36,6 +36,7 @@ class GravimetricEnergyDensity(DesignCost): in the optimiser settings. The gravimetric energy density [Wh.kg-1] is calculated as + .. math:: \\frac{1}{3600 m} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t @@ -93,6 +94,7 @@ class VolumetricEnergyDensity(DesignCost): settings. The volumetric energy density [Wh.m-3] is calculated as + .. math:: \\frac{1}{3600 v} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t @@ -150,6 +152,7 @@ class GravimetricPowerDensity(DesignCost): optimiser settings. The time-averaged gravimetric power density [W.kg-1] is calculated as + .. math:: \\frac{1}{3600 m T} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t @@ -212,6 +215,7 @@ class VolumetricPowerDensity(DesignCost): density is achieved by setting minimising = False in the optimiser settings. The time-averaged volumetric power density [W.m-3] is calculated as + .. math:: \\frac{1}{3600 v T} \\int_{t=0}^{t=T} I(t) V(t) \\mathrm{d}t From ec43450bde7daa5e434e22093048d405541c1f4a Mon Sep 17 00:00:00 2001 From: NicolaCourtier <45851982+NicolaCourtier@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:11:01 +0100 Subject: [PATCH 8/8] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e962f8c..d4e4d6e30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Features +- [#529](https://github.com/pybop-team/PyBOP/issues/529) - Adds `GravimetricPowerDensity` and `VolumetricPowerDensity` costs, along with the mathjax extension for Sphinx. + ## Optimisations - [#512](https://github.com/pybop-team/PyBOP/pull/513) - Refactors `LogPosterior` with attributes pointing to composed likelihood object. @@ -14,7 +16,6 @@ # [v24.9.1](https://github.com/pybop-team/PyBOP/tree/v24.9.0) - 2024-09-16 - ## Features ## Bug Fixes