diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index baae32b..0eb4048 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -31,17 +31,18 @@ jobs: matrix: include: # All Python versions against the latest PyBaMM - - {python-version: "3.10", pybamm-version: "26.7"} - - {python-version: "3.11", pybamm-version: "26.7"} - - {python-version: "3.12", pybamm-version: "26.7"} - - {python-version: "3.13", pybamm-version: "26.7"} - - {python-version: "3.14", pybamm-version: "26.7"} + - {python-version: "3.10", pybamm-version: "26.8"} + - {python-version: "3.11", pybamm-version: "26.8"} + - {python-version: "3.12", pybamm-version: "26.8"} + - {python-version: "3.13", pybamm-version: "26.8"} + - {python-version: "3.14", pybamm-version: "26.8"} # Older PyBaMM versions against the latest Python they support - {python-version: "3.13", pybamm-version: "25.12"} - {python-version: "3.14", pybamm-version: "26.3"} - {python-version: "3.14", pybamm-version: "26.4"} - {python-version: "3.14", pybamm-version: "26.5"} - {python-version: "3.14", pybamm-version: "26.6"} + - {python-version: "3.14", pybamm-version: "26.7"} steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index cdba83b..0ae2f3d 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ Thermal sub-model and heat-source options are injected automatically — pass th | `lead_acid.Full` | `Sulzer2019` | ❌ DAE | ❌ DAE | ✅ | ✅ | | `equivalent_circuit.Thevenin` | `ECM_Example` | ✅ | ✅ | ✅ ³ | ✅ ³ | -¹ PyBaMM < 26.7 only — from 26.7 on `LOQS` is a DAE, use a `CellCoSim*` block instead. +¹ Not on PyBaMM 26.7 — there `LOQS` is a DAE, use a `CellCoSim*` block instead. It is an ODE again from 26.8 on. ² PyBaMM < 26.7 only — pass `pybamm_solver=pybamm.CasadiSolver(mode="safe")`; the default `IDAKLUSolver` errors on `LOQS`. Fixed in 26.7. diff --git a/pyproject.toml b/pyproject.toml index c690a31..3f78a32 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ # is skipped on Emscripten so the pure-Python parts of the toolbox # (`pathsim_batt.thermal`) still install in the browser. # Upper-bounded: each new minor release is vetted before being allowed. - "pybamm>=25.12,<26.8; sys_platform != 'emscripten'", + "pybamm>=25.12,<26.9;sys_platform != 'emscripten'", ] [project.optional-dependencies] diff --git a/tests/cells/test_lead_acid.py b/tests/cells/test_lead_acid.py index c76b45d..c0ee3e9 100644 --- a/tests/cells/test_lead_acid.py +++ b/tests/cells/test_lead_acid.py @@ -2,7 +2,8 @@ Block / model matrix covered ----------------------------- -lead_acid.LOQS — ODE on PyBaMM < 26.7, DAE from 26.7 on → CoSim blocks only there +lead_acid.LOQS — ODE (all 4 blocks), except on PyBaMM 26.7 where it is a DAE + → CoSim blocks only there lead_acid.Full — DAE → CoSim blocks only """ @@ -31,10 +32,10 @@ # PyBaMM 26.7 registers "voltage as a state" centrally on every # BaseBatteryModel (default "true"), and lead-acid models don't support -# disabling it, so LOQS is a DAE from 26.7 on and can no longer run in the -# monolithic (ODE-only) blocks. -_PYBAMM_VERSION = tuple(int(x) for x in pybamm.__version__.split(".")[:2]) -_LOQS_IS_ODE = _PYBAMM_VERSION < (26, 7) +# disabling it, so LOQS is a DAE there and can't run in the monolithic +# (ODE-only) blocks. 26.8 defaults it back to "false", making LOQS an ODE +# again. Detect this from the model itself rather than the version number. +_LOQS_IS_ODE = not pybamm.lead_acid.LOQS().algebraic # --------------------------------------------------------------------------- # lead_acid.LOQS (ODE — all 4 blocks) @@ -44,8 +45,8 @@ class TestLeadAcidLOQS(unittest.TestCase): """lead_acid.LOQS with Sulzer2019 parameters. - ODE model (all 4 blocks) on PyBaMM < 26.7; DAE (CoSim blocks only) from - 26.7 on. Sulzer2019 cutoffs: lower 1.75 V, upper 2.42 V, nominal capacity + ODE model (all 4 blocks), except on PyBaMM 26.7 where it is a DAE (CoSim + blocks only). Sulzer2019 cutoffs: lower 1.75 V, upper 2.42 V, nominal capacity 17 A·h. """ @@ -57,32 +58,32 @@ def setUp(self): def _model(self): return pybamm.lead_acid.LOQS() - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_electrical_smoke(self): cell = run_electrical(self._model(), self.pv, current=17.0) assert_electrical_outputs(self, cell, self.v_lo, self.v_hi) - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_electrothermal_smoke(self): cell = run_electrothermal(self._model(), self.pv, current=17.0) assert_electrothermal_outputs(self, cell, self.v_lo, self.v_hi) - @unittest.skipIf(_LOQS_IS_ODE, "LOQS is a pure ODE on PyBaMM < 26.7") + @unittest.skipIf(_LOQS_IS_ODE, "LOQS is a pure ODE on this PyBaMM version") def test_monolithic_electrical_raises(self): - """LOQS is a DAE on PyBaMM >= 26.7 — CellElectrical must raise.""" + """Where LOQS is a DAE (PyBaMM 26.7), CellElectrical must raise.""" with self.assertRaises(NotImplementedError): CellElectrical(model=self._model(), parameter_values=self.pv) - @unittest.skipIf(_LOQS_IS_ODE, "LOQS is a pure ODE on PyBaMM < 26.7") + @unittest.skipIf(_LOQS_IS_ODE, "LOQS is a pure ODE on this PyBaMM version") def test_monolithic_electrothermal_raises(self): - """LOQS is a DAE on PyBaMM >= 26.7 — CellElectrothermal must raise.""" + """Where LOQS is a DAE (PyBaMM 26.7), CellElectrothermal must raise.""" with self.assertRaises(NotImplementedError): CellElectrothermal(model=self._model(), parameter_values=self.pv) def test_cosim_electrical_smoke(self): # On PyBaMM < 26.7, LOQS disables its Jacobian and IDAKLUSolver (the # co-sim default) errors without one; CasadiSolver works on every - # pinned version (25.12-26.7), so use it explicitly here. + # pinned version (25.12-26.8), so use it explicitly here. solver = pybamm.CasadiSolver(mode="safe") cell = CellCoSimElectrical( model=self._model(), @@ -126,19 +127,19 @@ def test_cosim_electrothermal_smoke(self): sim.run(2) assert_electrothermal_outputs(self, cell, self.v_lo, self.v_hi) - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_electrical_soc_decreases(self): """SOC must decrease under discharge current.""" cell = run_electrical(self._model(), self.pv, current=17.0, duration=60) self.assertLess(float(cell.outputs[2]), 1.0) - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_cutoff_values_match_parameter_set(self): cell = CellElectrical(model=self._model(), parameter_values=self.pv) self.assertAlmostEqual(cell._v_lower, self.v_lo) self.assertAlmostEqual(cell._v_upper, self.v_hi) - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_q_dot_nonzero_during_discharge(self): """Q_dot must be strictly positive during discharge (isothermal LOQS). @@ -152,7 +153,7 @@ def test_q_dot_nonzero_during_discharge(self): "Q_dot is zero — thermal model may not compute heat sources", ) - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_tamb_affects_temperature(self): """A warmer ambient temperature must yield a higher output cell temperature.""" solver = pybamm.CasadiSolver(mode="safe") @@ -183,7 +184,7 @@ def test_tamb_affects_temperature(self): ), ) - @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on PyBaMM >= 26.7") + @unittest.skipUnless(_LOQS_IS_ODE, "LOQS is a DAE on this PyBaMM version") def test_soc_scale_factor(self): """SOC must be well below 1.0 after sustained discharge.