Skip to content

Commit 44e58a3

Browse files
authored
Fix Q_heat always zero for isothermal cells; add unit tests (#9)
* Fix Q_heat always zero for isothermal cells; add unit tests - Enable 'calculate heat source for isothermal models' option for CellElectrical and CellCoSimElectrical via _thermal_extra_options class variable, wired into both _CellBase and _CoSimCellBase - Fix _CoSimCellBase._initial_outputs: bm.y0 is None in PyBaMM 26.x before the first step; return zero-filled placeholder with SOC set - Pass save=False to pybamm.Simulation.step() to prevent unbounded memory growth on long co-sim runs - Add tests: Q_heat nonzero during discharge, temperature input effectiveness — for all four cell classes * Fixes
1 parent 6578242 commit 44e58a3

2 files changed

Lines changed: 219 additions & 4 deletions

File tree

src/pathsim_batt/cells/pybamm_cell.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class _CellBase(DynamicalSystem):
6262
"""
6363

6464
_thermal_option: str = ""
65+
_thermal_extra_options: dict[str, str] = {}
6566
_pybamm_output_vars: list[str] = []
6667

6768
def __init__(
@@ -74,7 +75,9 @@ def __init__(
7475
self._initial_soc = float(initial_soc)
7576

7677
if model is None:
77-
model = pybamm.lithium_ion.SPMe(options={"thermal": self._thermal_option})
78+
model = pybamm.lithium_ion.SPMe(
79+
options={"thermal": self._thermal_option, **self._thermal_extra_options}
80+
)
7881

7982
self._parameter_values = _prepare_parameter_values(parameter_values)
8083

@@ -180,6 +183,7 @@ class _CoSimCellBase(Wrapper):
180183
"""
181184

182185
_thermal_option: str = ""
186+
_thermal_extra_options: dict[str, str] = {}
183187
_pybamm_output_vars: list[str] = []
184188

185189
def __init__(
@@ -196,7 +200,9 @@ def __init__(
196200
raise ValueError("dt must be positive")
197201

198202
if model is None:
199-
model = pybamm.lithium_ion.SPMe(options={"thermal": self._thermal_option})
203+
model = pybamm.lithium_ion.SPMe(
204+
options={"thermal": self._thermal_option, **self._thermal_extra_options}
205+
)
200206

201207
self._model = model
202208
self._parameter_values = _prepare_parameter_values(parameter_values)
@@ -278,7 +284,8 @@ class CellElectrical(_CellBase):
278284
Parameters
279285
----------
280286
model : pybamm.BaseBatteryModel or None
281-
PyBaMM lithium-ion model. Defaults to ``SPMe(thermal="isothermal")``.
287+
PyBaMM lithium-ion model. Defaults to isothermal SPMe with heat
288+
source calculation enabled.
282289
parameter_values : pybamm.ParameterValues or None
283290
PyBaMM parameter set. Defaults to ``Chen2020``.
284291
initial_soc : float
@@ -300,6 +307,7 @@ class CellElectrical(_CellBase):
300307
"""
301308

302309
_thermal_option = "isothermal"
310+
_thermal_extra_options = {"calculate heat source for isothermal models": "true"}
303311
_pybamm_output_vars = [
304312
"Terminal voltage [V]",
305313
"X-averaged total heating [W.m-3]",
@@ -371,7 +379,8 @@ class CellCoSimElectrical(_CoSimCellBase):
371379
Parameters
372380
----------
373381
model : pybamm.BaseBatteryModel or None
374-
PyBaMM lithium-ion model. Defaults to ``SPMe(thermal="isothermal")``.
382+
PyBaMM lithium-ion model. Defaults to isothermal SPMe with heat
383+
source calculation enabled.
375384
parameter_values : pybamm.ParameterValues or None
376385
PyBaMM parameter set. Defaults to ``Chen2020``.
377386
initial_soc : float
@@ -384,6 +393,7 @@ class CellCoSimElectrical(_CoSimCellBase):
384393
"""
385394

386395
_thermal_option = "isothermal"
396+
_thermal_extra_options = {"calculate heat source for isothermal models": "true"}
387397
_pybamm_output_vars = [
388398
"Terminal voltage [V]",
389399
"X-averaged total heating [W.m-3]",

tests/cells/test_pybamm_cell.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,64 @@ def test_pathsim_state_advances(self):
190190
self.sim.run(2)
191191
self.assertFalse(np.allclose(self.cell.engine.state, state_before))
192192

193+
def test_q_heat_nonzero_during_discharge(self):
194+
"""Q_heat must be strictly positive when a discharge current flows.
195+
196+
With thermal='isothermal' PyBaMM does not compute heat source terms,
197+
so Q_heat would be identically zero — this test guards against that.
198+
"""
199+
cell = CellElectrical(initial_soc=1.0)
200+
I_src = Constant(5.0) # 1C-ish discharge
201+
T_src = Constant(298.15)
202+
sim = Simulation(
203+
blocks=[I_src, T_src, cell],
204+
connections=[
205+
Connection(I_src, cell["I"]),
206+
Connection(T_src, cell["T_cell"]),
207+
],
208+
dt=10.0,
209+
Solver=ESDIRK43,
210+
)
211+
sim.run(60)
212+
self.assertGreater(
213+
cell.outputs[1],
214+
0.0,
215+
"Q_heat is zero — thermal model may not compute heat sources",
216+
)
217+
218+
def test_temperature_input_affects_voltage(self):
219+
"""T_cell must actually influence the electrochemistry.
220+
221+
Butler-Volmer kinetics are temperature-dependent, so discharging at
222+
a significantly higher temperature must yield a measurably different
223+
terminal voltage after the same duration.
224+
"""
225+
226+
def _run_and_get_voltage(T_cell):
227+
cell = CellElectrical(initial_soc=1.0)
228+
I_src = Constant(5.0)
229+
T_src = Constant(T_cell)
230+
sim = Simulation(
231+
blocks=[I_src, T_src, cell],
232+
connections=[
233+
Connection(I_src, cell["I"]),
234+
Connection(T_src, cell["T_cell"]),
235+
],
236+
dt=10.0,
237+
Solver=ESDIRK43,
238+
)
239+
sim.run(300)
240+
return cell.outputs[0] # terminal voltage [V]
241+
242+
V_cold = _run_and_get_voltage(278.15) # 5 °C
243+
V_hot = _run_and_get_voltage(318.15) # 45 °C
244+
self.assertNotAlmostEqual(
245+
V_cold,
246+
V_hot,
247+
places=3,
248+
msg="T_cell input has no effect on terminal voltage",
249+
)
250+
193251

194252
class TestElectrothermal(unittest.TestCase):
195253
"""Integration tests for CellElectrothermal — PathSim integrates the PyBaMM ODE."""
@@ -240,6 +298,58 @@ def test_pathsim_state_advances(self):
240298
self.sim.run(2)
241299
self.assertFalse(np.allclose(self.cell.engine.state, state_before))
242300

301+
def test_q_heat_nonzero_during_discharge(self):
302+
"""Q_heat must be strictly positive when a discharge current flows."""
303+
cell = CellElectrothermal(initial_soc=1.0)
304+
I_src = Constant(5.0)
305+
T_src = Constant(298.15)
306+
sim = Simulation(
307+
blocks=[I_src, T_src, cell],
308+
connections=[
309+
Connection(I_src, cell["I"]),
310+
Connection(T_src, cell["T_amb"]),
311+
],
312+
dt=10.0,
313+
Solver=ESDIRK43,
314+
)
315+
sim.run(60)
316+
self.assertGreater(
317+
cell.outputs[2],
318+
0.0,
319+
"Q_heat is zero — thermal model may not compute heat sources",
320+
)
321+
322+
def test_tamb_input_affects_cell_temperature(self):
323+
"""T_amb must influence the output cell temperature.
324+
325+
With a lower ambient temperature the cell should run cooler after
326+
the same discharge duration.
327+
"""
328+
329+
def _run_and_get_T_cell(T_amb):
330+
cell = CellElectrothermal(initial_soc=1.0)
331+
I_src = Constant(5.0)
332+
T_src = Constant(T_amb)
333+
sim = Simulation(
334+
blocks=[I_src, T_src, cell],
335+
connections=[
336+
Connection(I_src, cell["I"]),
337+
Connection(T_src, cell["T_amb"]),
338+
],
339+
dt=10.0,
340+
Solver=ESDIRK43,
341+
)
342+
sim.run(300)
343+
return cell.outputs[1] # cell temperature [K]
344+
345+
T_cell_cold_amb = _run_and_get_T_cell(278.15) # 5 °C ambient
346+
T_cell_hot_amb = _run_and_get_T_cell(318.15) # 45 °C ambient
347+
self.assertLess(
348+
T_cell_cold_amb,
349+
T_cell_hot_amb,
350+
msg="T_amb input has no effect on output cell temperature",
351+
)
352+
243353

244354
class TestCoSimulationElectrical(unittest.TestCase):
245355
"""Integration tests for CellCoSimElectrical — PyBaMM performs the stepping."""
@@ -293,6 +403,55 @@ def test_dfn_step_outputs_physical(self):
293403
self.assertGreater(cell.outputs[2], 0.0) # SOC
294404
self.assertLessEqual(cell.outputs[2], 1.0)
295405

406+
def test_q_heat_nonzero_during_discharge(self):
407+
"""Q_heat must be strictly positive when a discharge current flows."""
408+
cell = CellCoSimElectrical(initial_soc=1.0, dt=10.0)
409+
I_src = Constant(5.0)
410+
T_src = Constant(298.15)
411+
sim = Simulation(
412+
blocks=[I_src, T_src, cell],
413+
connections=[
414+
Connection(I_src, cell["I"]),
415+
Connection(T_src, cell["T_cell"]),
416+
],
417+
dt=5.0,
418+
Solver=ESDIRK43,
419+
)
420+
sim.run(60)
421+
self.assertGreater(
422+
cell.outputs[1],
423+
0.0,
424+
"Q_heat is zero — thermal model may not compute heat sources",
425+
)
426+
427+
def test_temperature_input_affects_voltage(self):
428+
"""T_cell must actually influence the electrochemistry."""
429+
430+
def _run_and_get_voltage(T_cell):
431+
cell = CellCoSimElectrical(initial_soc=1.0, dt=10.0)
432+
I_src = Constant(5.0)
433+
T_src = Constant(T_cell)
434+
sim = Simulation(
435+
blocks=[I_src, T_src, cell],
436+
connections=[
437+
Connection(I_src, cell["I"]),
438+
Connection(T_src, cell["T_cell"]),
439+
],
440+
dt=5.0,
441+
Solver=ESDIRK43,
442+
)
443+
sim.run(300)
444+
return cell.outputs[0] # terminal voltage [V]
445+
446+
V_cold = _run_and_get_voltage(278.15) # 5 °C
447+
V_hot = _run_and_get_voltage(318.15) # 45 °C
448+
self.assertNotAlmostEqual(
449+
float(V_cold),
450+
float(V_hot),
451+
places=3,
452+
msg="T_cell input has no effect on terminal voltage",
453+
)
454+
296455

297456
class TestCoSimulationElectrothermal(unittest.TestCase):
298457
"""Integration tests for CellCoSimElectrothermal — PyBaMM performs the stepping."""
@@ -349,6 +508,52 @@ def test_dfn_step_outputs_physical(self):
349508
self.assertGreater(cell.outputs[3], 0.0) # SOC
350509
self.assertLessEqual(cell.outputs[3], 1.0)
351510

511+
def test_q_heat_nonzero_during_discharge(self):
512+
"""Q_heat must be strictly positive when a discharge current flows."""
513+
cell = CellCoSimElectrothermal(initial_soc=1.0, dt=10.0)
514+
I_src = Constant(5.0)
515+
T_src = Constant(298.15)
516+
sim = Simulation(
517+
blocks=[I_src, T_src, cell],
518+
connections=[
519+
Connection(I_src, cell["I"]),
520+
Connection(T_src, cell["T_amb"]),
521+
],
522+
dt=5.0,
523+
Solver=ESDIRK43,
524+
)
525+
sim.run(60)
526+
self.assertGreater(
527+
cell.outputs[2],
528+
0.0,
529+
"Q_heat is zero — thermal model may not compute heat sources",
530+
)
531+
532+
def test_tamb_input_affects_cell_temperature(self):
533+
"""T_amb must influence the output cell temperature."""
534+
535+
def _run_and_get_T_cell(T_amb):
536+
cell = CellCoSimElectrothermal(initial_soc=1.0, dt=10.0)
537+
I_src = Constant(5.0)
538+
T_src = Constant(T_amb)
539+
sim = Simulation(
540+
blocks=[I_src, T_src, cell],
541+
connections=[
542+
Connection(I_src, cell["I"]),
543+
Connection(T_src, cell["T_amb"]),
544+
],
545+
dt=5.0,
546+
Solver=ESDIRK43,
547+
)
548+
sim.run(300)
549+
return cell.outputs[1] # cell temperature [K]
550+
551+
T_cold = _run_and_get_T_cell(278.15)
552+
T_hot = _run_and_get_T_cell(318.15)
553+
self.assertLess(
554+
T_cold, T_hot, msg="T_amb input has no effect on output cell temperature"
555+
)
556+
352557

353558
if __name__ == "__main__":
354559
unittest.main()

0 commit comments

Comments
 (0)