Repository: pybamm-team/PyBaMM (python)

Issue:
Create "Properly Lumped" thermal model
### Description

This would implement an alternative battery model where the heat capacity is given as a lumped parameter rather than for each cell component.

### Motivation

While the lumped thermal model in PyBaMM is physically informative and accurate, it poses a problem for practicing battery modelers who most of the time do not have detailed thermal characterization of each component of the cell.

### Possible Implementation

Rather than implementing a new model, I propose that we just add an option "use lumped thermal capacity" which is default "false", and just alter the existing lumped submodel code.

### Additional context

_No response_

Pull request description:
Added "use lumped thermal capacity" option in lumped thermal model.
# Description

Added "use lumped thermal capacity" option in lumped thermal model so that we can simulate properly for those who do not have detailed thermal characterization of each component of the cell.

Fixes #4612 

## Type of change

Please add a line in the relevant section of [CHANGELOG.md](https://github.com/pybamm-team/PyBaMM/blob/develop/CHANGELOG.md) to document the change (include PR #)

# Important checks:

Please confirm the following before marking the PR as ready for review:
- [x] No style issues: `nox -s pre-commit`
- [x] All tests pass: `nox -s tests`
- [x] The documentation builds: `nox -s doctests`
- [x] Code is commented for hard-to-understand areas
- [x] Tests added that prove fix is effective or that feature works


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3647546d6..36017478d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -55,6 +55,7 @@
 - Added `Symbol.post_order()` method to return an iterable that steps through the tree in post-order fashion. ([#4684](https://github.com/pybamm-team/PyBaMM/pull/4684))
 - Porosity change now works for composite electrode ([#4417](https://github.com/pybamm-team/PyBaMM/pull/4417))
 - Added two more submodels (options) for the SEI: Lars von Kolzenberg (2020) model and Tunneling Limit model ([#4394](https://github.com/pybamm-team/PyBaMM/pull/4394))
+- Added "use lumped thermal capacity" option in lumped thermal model ([#4968](https://github.com/pybamm-team/PyBaMM/pull/4968))
 
 ## Breaking changes
 
diff --git a/src/pybamm/models/full_battery_models/base_battery_model.py b/src/pybamm/models/full_battery_models/base_battery_model.py
index 956d44d00..2e8ccdb2b 100644
--- a/src/pybamm/models/full_battery_models/base_battery_model.py
+++ b/src/pybamm/models/full_battery_models/base_battery_model.py
@@ -224,6 +224,10 @@ class BatteryModelOptions(pybamm.FuzzyDict):
                 the respective porosity change) over the x-axis in Single Particle
                 Models, can be "false" or "true". Default is "false" for SPMe and
                 "true" for SPM.
+            * "use lumped thermal capacity" : str
+                Whether to use a lumped capacity model for the thermal model. Can be
+                "false" (default) or "true". This is only available for the lumped
+                thermal model.
     """
 
     def __init__(self, extra_options):
@@ -337,6 +341,7 @@ class BatteryModelOptions(pybamm.FuzzyDict):
             "voltage as a state": ["false", "true"],
             "working electrode": ["both", "positive"],
             "x-average side reactions": ["false", "true"],
+            "use lumped thermal capacity": ["false", "true"],
         }
 
         default_options = {
@@ -596,6 +601,15 @@ class BatteryModelOptions(pybamm.FuzzyDict):
                 f"X-full thermal submodels do not yet support {n}D current collectors"
             )
 
+        if (
+            options["use lumped thermal capacity"] == "true"
+            and "lumped" not in options["thermal"]
+        ):
+            raise pybamm.OptionError(
+                "Lumped thermal capacity model only compatible with lumped thermal "
+                "models"
+            )
+
         if isinstance(options["stress-induced diffusion"], str):
             if (
                 options["stress-induced diffusion"] == "true"
diff --git a/src/pybamm/models/submodels/thermal/base_thermal.py b/src/pybamm/models/submodels/thermal/base_thermal.py
index 1040227de..2ba37678e 100644
--- a/src/pybamm/models/submodels/thermal/base_thermal.py
+++ b/src/pybamm/models/submodels/thermal/base_thermal.py
@@ -19,6 +19,9 @@ class BaseThermal(pybamm.BaseSubModel):
 
     def __init__(self, param, options=None, x_average=False):
         super().__init__(param, options=options)
+        self.use_lumped_thermal_capacity = self.options.get(
+            "use lumped thermal capacity", "false"
+        )
         self.x_average = x_average
 
         if self.options["heat of mixing"] == "true":
@@ -240,9 +243,13 @@ class BaseThermal(pybamm.BaseSubModel):
         Q_mix_vol_av = Q_mix_W / V
         Q_vol_av = Q_W / V
 
-        # Effective heat capacity
-        T_vol_av = variables["Volume-averaged cell temperature [K]"]
-        rho_c_p_eff_av = self.param.rho_c_p_eff(T_vol_av)
+        # Add lumped heat capacity if option is enabled
+        if self.use_lumped_thermal_capacity == "true":
+            rho_c_p_eff_av = self.param.cell_heat_capacity
+        else:
+            # Effective heat capacity
+            T_vol_av = variables["Volume-averaged cell temperature [K]"]
+            rho_c_p_eff_av = self.param.rho_c_p_eff(T_vol_av)
 
         variables.update(
             {
diff --git a/src/pybamm/parameters/lithium_ion_parameters.py b/src/pybamm/parameters/lithium_ion_parameters.py
index 6485047bf..ee3c01d0a 100644
--- a/src/pybamm/parameters/lithium_ion_parameters.py
+++ b/src/pybamm/parameters/lithium_ion_parameters.py
@@ -56,6 +56,7 @@ class LithiumIonParameters(BaseParameters):
         self.h_total = self.therm.h_total
         self.rho_c_p_eff = self.therm.rho_c_p_eff
         self.lambda_eff = self.therm.lambda_eff
+        self.cell_heat_capacity = self.therm.cell_heat_capacity
 
         # Macroscale geometry
         self.L_x = self.geo.L_x
diff --git a/src/pybamm/parameters/thermal_parameters.py b/src/pybamm/parameters/thermal_parameters.py
index cf714d9c0..cebb64df6 100644
--- a/src/pybamm/parameters/thermal_parameters.py
+++ b/src/pybamm/parameters/thermal_parameters.py
@@ -40,6 +40,9 @@ class ThermalParameters(BaseParameters):
         # Initial temperature
         self.T_init = pybamm.Parameter("Initial temperature [K]")
 
+        # Lumped model heat capacity (volumetric)
+        self.cell_heat_capacity = pybamm.Parameter("Cell heat capacity [J.K-1.m-3]")
+
     def T_amb(self, y, z, t):
         """Ambient temperature [K]"""
         return pybamm.FunctionParameter(


Test patch:
diff --git a/tests/integration/test_models/test_submodels/test_thermal/test_thermal_lumped.py b/tests/integration/test_models/test_submodels/test_thermal/test_thermal_lumped.py
new file mode 100644
index 000000000..38800aaba
--- /dev/null
+++ b/tests/integration/test_models/test_submodels/test_thermal/test_thermal_lumped.py
@@ -0,0 +1,37 @@
+import pybamm
+import numpy as np
+
+
+class TestLumpedThermalModel:
+    def test_lumped_model_matches_standard(self):
+        options_standard = {"thermal": "lumped"}
+        model_standard = pybamm.lithium_ion.SPM(options=options_standard)
+        param_standard = pybamm.ParameterValues("Chen2020")
+
+        options_lumped = {"thermal": "lumped", "use lumped thermal capacity": "true"}
+        model_lumped = pybamm.lithium_ion.SPM(options=options_lumped)
+        param_lumped = pybamm.ParameterValues("Chen2020")
+
+        sim_standard = pybamm.Simulation(
+            model_standard, parameter_values=param_standard
+        )
+        solution_standard = sim_standard.solve([0, 3600])
+
+        heat_capacity_standard = solution_standard[
+            "Volume-averaged effective heat capacity [J.K-1.m-3]"
+        ].data[0]
+
+        param_lumped.update(
+            {
+                "Cell heat capacity [J.K-1.m-3]": heat_capacity_standard,
+            },
+            check_already_exists=False,
+        )
+
+        sim_lumped = pybamm.Simulation(model_lumped, parameter_values=param_lumped)
+        solution_lumped = sim_lumped.solve([0, 3600])
+
+        T_standard = solution_standard["Volume-averaged cell temperature [K]"].data
+        T_lumped = solution_lumped["Volume-averaged cell temperature [K]"].data
+
+        np.testing.assert_allclose(T_standard, T_lumped, rtol=1e-3)
diff --git a/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py b/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py
index 84aeec622..00b0be42b 100644
--- a/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py
+++ b/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py
@@ -54,6 +54,7 @@ PRINT_OPTIONS_OUTPUT = """\
 'voltage as a state': 'false' (possible: ['false', 'true'])
 'working electrode': 'both' (possible: ['both', 'positive'])
 'x-average side reactions': 'false' (possible: ['false', 'true'])
+'use lumped thermal capacity': 'false' (possible: ['false', 'true'])
 """
 
 
diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_half_cell_tests.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_half_cell_tests.py
index 50fac7a04..808afdab4 100644
--- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_half_cell_tests.py
+++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_half_cell_tests.py
@@ -2,6 +2,8 @@
 # Base unit tests for lithium-ion half-cell models
 # This is achieved by using the {"working electrdode": "positive"} option
 #
+import pybamm
+import pytest
 
 
 class BaseUnitTestLithiumIonHalfCell:
@@ -83,6 +85,18 @@ class BaseUnitTestLithiumIonHalfCell:
         options = {"thermal": "lumped"}
         self.check_well_posedness(options)
 
+    def test_well_posed_lumped_thermal_capacity_model(self):
+        options = {"thermal": "lumped", "use lumped thermal capacity": "true"}
+        self.check_well_posedness(options)
+
+    def test_incompatible_lumped_thermal_capacity_option(self):
+        options = {"thermal": "x-full", "use lumped thermal capacity": "true"}
+        with pytest.raises(
+            pybamm.OptionError,
+            match="Lumped thermal capacity model only compatible with lumped thermal models",
+        ):
+            self.check_well_posedness(options)
+
     def test_well_posed_lumped_thermal_hom(self):
         options = {"thermal": "lumped", "heat of mixing": "true"}
         self.check_well_posedness(options)
diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py
index 010e59aa6..a00d535ae 100644
--- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py
+++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py
@@ -2,6 +2,7 @@
 # Base unit tests for the lithium-ion models
 #
 import pybamm
+import pytest
 
 
 class BaseUnitTestLithiumIon:
@@ -55,6 +56,18 @@ class BaseUnitTestLithiumIon:
         }
         self.check_well_posedness(options)
 
+    def test_well_posed_lumped_thermal_capacity_model(self):
+        options = {"thermal": "lumped", "use lumped thermal capacity": "true"}
+        self.check_well_posedness(options)
+
+    def test_incompatible_lumped_thermal_capacity_option(self):
+        options = {"thermal": "x-full", "use lumped thermal capacity": "true"}
+        with pytest.raises(
+            pybamm.OptionError,
+            match="Lumped thermal capacity model only compatible with lumped thermal models",
+        ):
+            self.check_well_posedness(options)
+
     def test_well_posed_thermal_1plus1D(self):
         options = {
             "current collector": "potential pair",
diff --git a/tests/unit/test_parameters/test_lithium_ion_parameters.py b/tests/unit/test_parameters/test_lithium_ion_parameters.py
index ba0dfe5fc..17d305801 100644
--- a/tests/unit/test_parameters/test_lithium_ion_parameters.py
+++ b/tests/unit/test_parameters/test_lithium_ion_parameters.py
@@ -105,6 +105,12 @@ class TestLithiumIonParameterValues:
 
     def test_thermal_parameters(self):
         values = pybamm.lithium_ion.BaseModel().default_parameter_values
+        values.update(
+            {
+                "Cell heat capacity [J.K-1.m-3]": 2.5e6,
+            },
+            check_already_exists=False,
+        )
         param = pybamm.LithiumIonParameters()
         T = param.T_ref
 
@@ -124,6 +130,7 @@ class TestLithiumIonParameterValues:
 
         # other thermal parameters
         np.testing.assert_equal(values.evaluate(param.T_init), 298.15)
+        np.testing.assert_equal(values.evaluate(param.cell_heat_capacity), 2.5e6)
 
     def test_parameter_functions(self):
         values = pybamm.lithium_ion.BaseModel().default_parameter_values