diff --git a/src/pathsim_batt/cells/pybamm_cell.py b/src/pathsim_batt/cells/pybamm_cell.py index c8080cf..40ce974 100644 --- a/src/pathsim_batt/cells/pybamm_cell.py +++ b/src/pathsim_batt/cells/pybamm_cell.py @@ -271,10 +271,10 @@ def reset(self) -> None: class CellElectrical(_CellBase): """Cell block — electrical outputs only, external thermal coupling. - PathSim integrates both the electrochemical state (via the discretised - PyBaMM ODE) and the cell temperature ODE. Wire ``Q_heat`` to a - ``LumpedThermal`` (or similar) block and feed its temperature output - back to ``T_cell``. + PathSim integrates the electrochemical state via the discretised PyBaMM + ODE. Temperature dynamics live outside this block: wire ``Q_dot`` to a + ``LumpedThermal`` (or similar) block and feed its temperature output back + to ``T_cell``. .. note:: The SPMe/SPM ODE is stiff. Use an implicit solver (e.g. @@ -302,7 +302,7 @@ class CellElectrical(_CellBase): Outputs ------- V (0) : terminal voltage [V] - Q_heat (1) : X-averaged volumetric heat generation [W m⁻³] + Q_dot (1) : total heat generation [W] SOC (2) : state of charge (0–1) """ @@ -310,11 +310,11 @@ class CellElectrical(_CellBase): _thermal_extra_options = {"calculate heat source for isothermal models": "true"} _pybamm_output_vars = [ "Terminal voltage [V]", - "X-averaged total heating [W.m-3]", + "Total heating [W]", ] input_port_labels = {"I": 0, "T_cell": 1} - output_port_labels = {"V": 0, "Q_heat": 1, "SOC": 2} + output_port_labels = {"V": 0, "Q_dot": 1, "SOC": 2} class CellElectrothermal(_CellBase): @@ -352,7 +352,7 @@ class CellElectrothermal(_CellBase): ------- V (0) : terminal voltage [V] T (1) : cell temperature [K] (part of PyBaMM state) - Q_heat (2) : X-averaged volumetric heat generation [W m⁻³] + Q_dot (2) : total heat generation [W] SOC (3) : state of charge (0–1) """ @@ -360,11 +360,11 @@ class CellElectrothermal(_CellBase): _pybamm_output_vars = [ "Terminal voltage [V]", "X-averaged cell temperature [K]", - "X-averaged total heating [W.m-3]", + "Total heating [W]", ] input_port_labels = {"I": 0, "T_amb": 1} - output_port_labels = {"V": 0, "T": 1, "Q_heat": 2, "SOC": 3} + output_port_labels = {"V": 0, "T": 1, "Q_dot": 2, "SOC": 3} class CellCoSimElectrical(_CoSimCellBase): @@ -396,11 +396,11 @@ class CellCoSimElectrical(_CoSimCellBase): _thermal_extra_options = {"calculate heat source for isothermal models": "true"} _pybamm_output_vars = [ "Terminal voltage [V]", - "X-averaged total heating [W.m-3]", + "Total heating [W]", ] input_port_labels = {"I": 0, "T_cell": 1} - output_port_labels = {"V": 0, "Q_heat": 1, "SOC": 2} + output_port_labels = {"V": 0, "Q_dot": 1, "SOC": 2} class CellCoSimElectrothermal(_CoSimCellBase): @@ -431,8 +431,8 @@ class CellCoSimElectrothermal(_CoSimCellBase): _pybamm_output_vars = [ "Terminal voltage [V]", "X-averaged cell temperature [K]", - "X-averaged total heating [W.m-3]", + "Total heating [W]", ] input_port_labels = {"I": 0, "T_amb": 1} - output_port_labels = {"V": 0, "T": 1, "Q_heat": 2, "SOC": 3} + output_port_labels = {"V": 0, "T": 1, "Q_dot": 2, "SOC": 3} diff --git a/tests/cells/test_pybamm_cell.py b/tests/cells/test_pybamm_cell.py index d48cc6d..681efe1 100644 --- a/tests/cells/test_pybamm_cell.py +++ b/tests/cells/test_pybamm_cell.py @@ -21,7 +21,7 @@ def test_electrical_input_labels(self): def test_electrical_output_labels(self): self.assertEqual(CellElectrical.output_port_labels["V"], 0) - self.assertEqual(CellElectrical.output_port_labels["Q_heat"], 1) + self.assertEqual(CellElectrical.output_port_labels["Q_dot"], 1) self.assertEqual(CellElectrical.output_port_labels["SOC"], 2) def test_electrothermal_input_labels(self): @@ -31,7 +31,7 @@ def test_electrothermal_input_labels(self): def test_electrothermal_output_labels(self): self.assertEqual(CellElectrothermal.output_port_labels["V"], 0) self.assertEqual(CellElectrothermal.output_port_labels["T"], 1) - self.assertEqual(CellElectrothermal.output_port_labels["Q_heat"], 2) + self.assertEqual(CellElectrothermal.output_port_labels["Q_dot"], 2) self.assertEqual(CellElectrothermal.output_port_labels["SOC"], 3) def test_is_dynamic(self): @@ -40,17 +40,17 @@ def test_is_dynamic(self): def test_cosim_len_zero(self): cell_e = CellCoSimElectrical(dt=1.0) - self.assertEqual(len(cell_e), 3) # V, Q_heat, SOC + self.assertEqual(len(cell_e), 3) # V, Q_dot, SOC cell_et = CellCoSimElectrothermal(dt=1.0) - self.assertEqual(len(cell_et), 4) # V, T, Q_heat, SOC + self.assertEqual(len(cell_et), 4) # V, T, Q_dot, SOC def test_len_zero(self): cell_e = CellElectrical() cell_e.set_solver(ESDIRK43, None) - self.assertEqual(len(cell_e), 3) # V, Q_heat, SOC + self.assertEqual(len(cell_e), 3) # V, Q_dot, SOC cell_et = CellElectrothermal() cell_et.set_solver(ESDIRK43, None) - self.assertEqual(len(cell_et), 4) # V, T, Q_heat, SOC + self.assertEqual(len(cell_et), 4) # V, T, Q_dot, SOC def test_current_always_input(self): pv = pybamm.ParameterValues("Chen2020") @@ -168,7 +168,7 @@ def test_outputs_in_range(self): self.sim.run(1) self.assertGreater(self.cell.outputs[0], 3.0) # V self.assertLess(self.cell.outputs[0], 4.3) - self.assertGreaterEqual(self.cell.outputs[1], 0.0) # Q_heat + self.assertGreaterEqual(self.cell.outputs[1], 0.0) # Q_dot self.assertGreater(self.cell.outputs[2], 0.0) # SOC self.assertLessEqual(self.cell.outputs[2], 1.0) @@ -190,11 +190,11 @@ def test_pathsim_state_advances(self): self.sim.run(2) self.assertFalse(np.allclose(self.cell.engine.state, state_before)) - def test_q_heat_nonzero_during_discharge(self): - """Q_heat must be strictly positive when a discharge current flows. + def test_q_dot_nonzero_during_discharge(self): + """Q_dot must be strictly positive when a discharge current flows. With thermal='isothermal' PyBaMM does not compute heat source terms, - so Q_heat would be identically zero — this test guards against that. + so Q_dot would be identically zero — this test guards against that. """ cell = CellElectrical(initial_soc=1.0) I_src = Constant(5.0) # 1C-ish discharge @@ -212,7 +212,7 @@ def test_q_heat_nonzero_during_discharge(self): self.assertGreater( cell.outputs[1], 0.0, - "Q_heat is zero — thermal model may not compute heat sources", + "Q_dot is zero — thermal model may not compute heat sources", ) def test_temperature_input_affects_voltage(self): @@ -276,7 +276,7 @@ def test_outputs_in_range(self): self.assertLess(self.cell.outputs[0], 4.3) self.assertGreater(self.cell.outputs[1], 250.0) # T self.assertLess(self.cell.outputs[1], 400.0) - self.assertGreaterEqual(self.cell.outputs[2], 0.0) # Q_heat + self.assertGreaterEqual(self.cell.outputs[2], 0.0) # Q_dot self.assertGreater(self.cell.outputs[3], 0.0) # SOC self.assertLessEqual(self.cell.outputs[3], 1.0) @@ -298,8 +298,8 @@ def test_pathsim_state_advances(self): self.sim.run(2) self.assertFalse(np.allclose(self.cell.engine.state, state_before)) - def test_q_heat_nonzero_during_discharge(self): - """Q_heat must be strictly positive when a discharge current flows.""" + def test_q_dot_nonzero_during_discharge(self): + """Q_dot must be strictly positive when a discharge current flows.""" cell = CellElectrothermal(initial_soc=1.0) I_src = Constant(5.0) T_src = Constant(298.15) @@ -316,7 +316,7 @@ def test_q_heat_nonzero_during_discharge(self): self.assertGreater( cell.outputs[2], 0.0, - "Q_heat is zero — thermal model may not compute heat sources", + "Q_dot is zero — thermal model may not compute heat sources", ) def test_tamb_input_affects_cell_temperature(self): @@ -375,7 +375,7 @@ def test_outputs_in_range(self): self.sim.run(2) self.assertGreater(self.cell.outputs[0], 2.0) # V self.assertLess(self.cell.outputs[0], 5.0) - self.assertGreaterEqual(self.cell.outputs[1], 0.0) # Q_heat + self.assertGreaterEqual(self.cell.outputs[1], 0.0) # Q_dot self.assertGreater(self.cell.outputs[2], 0.0) # SOC self.assertLessEqual(self.cell.outputs[2], 1.0) @@ -403,8 +403,8 @@ def test_dfn_step_outputs_physical(self): self.assertGreater(cell.outputs[2], 0.0) # SOC self.assertLessEqual(cell.outputs[2], 1.0) - def test_q_heat_nonzero_during_discharge(self): - """Q_heat must be strictly positive when a discharge current flows.""" + def test_q_dot_nonzero_during_discharge(self): + """Q_dot must be strictly positive when a discharge current flows.""" cell = CellCoSimElectrical(initial_soc=1.0, dt=10.0) I_src = Constant(5.0) T_src = Constant(298.15) @@ -421,7 +421,7 @@ def test_q_heat_nonzero_during_discharge(self): self.assertGreater( cell.outputs[1], 0.0, - "Q_heat is zero — thermal model may not compute heat sources", + "Q_dot is zero — thermal model may not compute heat sources", ) def test_temperature_input_affects_voltage(self): @@ -479,7 +479,7 @@ def test_outputs_in_range(self): self.assertLess(self.cell.outputs[0], 5.0) self.assertGreater(self.cell.outputs[1], 250.0) # T self.assertLess(self.cell.outputs[1], 400.0) - self.assertGreaterEqual(self.cell.outputs[2], 0.0) # Q_heat + self.assertGreaterEqual(self.cell.outputs[2], 0.0) # Q_dot self.assertGreater(self.cell.outputs[3], 0.0) # SOC self.assertLessEqual(self.cell.outputs[3], 1.0) @@ -508,8 +508,8 @@ def test_dfn_step_outputs_physical(self): self.assertGreater(cell.outputs[3], 0.0) # SOC self.assertLessEqual(cell.outputs[3], 1.0) - def test_q_heat_nonzero_during_discharge(self): - """Q_heat must be strictly positive when a discharge current flows.""" + def test_q_dot_nonzero_during_discharge(self): + """Q_dot must be strictly positive when a discharge current flows.""" cell = CellCoSimElectrothermal(initial_soc=1.0, dt=10.0) I_src = Constant(5.0) T_src = Constant(298.15) @@ -526,7 +526,7 @@ def test_q_heat_nonzero_during_discharge(self): self.assertGreater( cell.outputs[2], 0.0, - "Q_heat is zero — thermal model may not compute heat sources", + "Q_dot is zero — thermal model may not compute heat sources", ) def test_tamb_input_affects_cell_temperature(self):