From 8aafcdc385dd6ffff9dd334fb4976d10df2e9e99 Mon Sep 17 00:00:00 2001 From: Pierfrancesco Ombrini <91598680+Ombrini@users.noreply.github.com> Date: Fri, 10 Jul 2026 10:50:21 +0200 Subject: [PATCH 1/6] implement three electrode eis simulation with test and example --- .../run_eis_three_electrode_simulation.py | 14 ++++ .../src/pybamm/simulation/eis_simulation.py | 81 ++++++++++++++++--- .../pybamm/tests/unit/test_eis_simulation.py | 48 +++++++++++ 3 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 examples/scripts/run_eis_three_electrode_simulation.py diff --git a/examples/scripts/run_eis_three_electrode_simulation.py b/examples/scripts/run_eis_three_electrode_simulation.py new file mode 100644 index 0000000000..9efd7ac4b7 --- /dev/null +++ b/examples/scripts/run_eis_three_electrode_simulation.py @@ -0,0 +1,14 @@ +import numpy as np + +import pybamm + +model = pybamm.lithium_ion.SPM(options={"surface form": "differential"}) + +frequencies = np.logspace(-4, 4, 50) + +eis_sim = pybamm.EISSimulation( + model, + three_electrodes=True, +) +eis_sim.solve(frequencies) +eis_sim.nyquist_plot() diff --git a/packages/pybamm/src/pybamm/simulation/eis_simulation.py b/packages/pybamm/src/pybamm/simulation/eis_simulation.py index 0c7ebdfdef..a9676ab303 100644 --- a/packages/pybamm/src/pybamm/simulation/eis_simulation.py +++ b/packages/pybamm/src/pybamm/simulation/eis_simulation.py @@ -29,6 +29,10 @@ class EISSimulation(BaseSimulation): A dictionary of the types of spatial method to use on each domain. skip_surface_form_check : bool, optional If True, skip the 'surface form' model option validation. Defaults to False. + three_electrodes: bool, optional + Whether to use a three-electrode model. If True, the model must have a + reference electrode inserted. Default is False. If True, returns the impedance + of both the working and counter electrodes, as well as the cell impedance. """ def __init__( @@ -40,17 +44,28 @@ def __init__( var_pts=None, spatial_methods=None, skip_surface_form_check=False, + three_electrodes=False, ): timer = pybamm.Timer() model_name = model.name parameter_values = parameter_values or model.default_parameter_values + self.three_electrodes = three_electrodes + if three_electrodes and ( + "Positive electrode 3E potential [V]" not in model.variables + or "Negative electrode 3E potential [V]" not in model.variables + ): + reference_position = ( + parameter_values["Negative electrode thickness [m]"] + + parameter_values["Separator thickness [m]"] / 2 + ) + model.insert_reference_electrode(reference_position) # Validate required variables and surface form before any processing - self._validate_model_for_eis(model, skip_surface_form_check) + self._validate_model_for_eis(model, skip_surface_form_check, three_electrodes) pybamm.logger.info(f"Setting up {model_name} for EIS") - eis_model = self._set_up_model_for_eis(model) + eis_model = self._set_up_model_for_eis(model, three_electrodes) # Compute impedance scale factor after model transformation: the # external-circuit FunctionControl swap rescales "Current [A]" by @@ -78,7 +93,7 @@ def __init__( pybamm.citations.register("Hallemans2025") @staticmethod - def _validate_model_for_eis(model, skip_surface_form_check=False): + def _validate_model_for_eis(model, skip_surface_form_check=False, three_electrodes=False): """Validate that a model is suitable for frequency-domain EIS. Raises @@ -87,6 +102,14 @@ def _validate_model_for_eis(model, skip_surface_form_check=False): If the model is missing required variables or options. """ required_vars = ["Voltage [V]", "Current [A]"] + if three_electrodes: + required_vars.extend( + [ + "Positive electrode 3E potential [V]", + "Negative electrode 3E potential [V]", + ] + ) + for var in required_vars: if var not in model.variables: raise ValueError( @@ -105,7 +128,7 @@ def _validate_model_for_eis(model, skip_surface_form_check=False): ) @staticmethod - def _set_up_model_for_eis(model): + def _set_up_model_for_eis(model, three_electrodes=False): """Prepare a model for frequency-domain EIS. Creates a copy of the model with voltage and current as algebraic @@ -132,6 +155,27 @@ def _set_up_model_for_eis(model): new_model.algebraic[V_cell] = V_cell - V new_model.initial_conditions[V_cell] = new_model.param.ocv_init + if three_electrodes: + V_pos_3e = new_model.variables["Positive electrode 3E potential [V]"] + V_pos_3e_var = pybamm.Variable( + "Positive electrode 3E potential variable [V]" + ) + new_model.variables["Positive electrode 3E potential variable [V]"] = ( + V_pos_3e_var + ) + new_model.algebraic[V_pos_3e_var] = V_pos_3e_var - V_pos_3e + new_model.initial_conditions[V_pos_3e_var] = new_model.param.p.prim.U_init + + V_neg_3e = new_model.variables["Negative electrode 3E potential [V]"] + V_neg_3e_var = pybamm.Variable( + "Negative electrode 3E potential variable [V]" + ) + new_model.variables["Negative electrode 3E potential variable [V]"] = ( + V_neg_3e_var + ) + new_model.algebraic[V_neg_3e_var] = V_neg_3e_var - V_neg_3e + new_model.initial_conditions[V_neg_3e_var] = new_model.param.n.prim.U_init + # Replace current with a FunctionControl variable external_circuit_variables = pybamm.external_circuit.FunctionControl( model.param, None, model.options, control="algebraic" @@ -212,7 +256,7 @@ def _build_matrix_problem(self, inputs_dict=None): return self._cached_M, neg_J, self._cached_b @staticmethod - def _calculate_impedance(frequency, M, neg_J, b): + def _calculate_impedance(frequency, M, neg_J, b, three_electrodes=False): """Calculate impedance at a single frequency. Parameters @@ -233,9 +277,15 @@ def _calculate_impedance(frequency, M, neg_J, b): """ A = 1.0j * 2 * np.pi * frequency * M + neg_J x = spsolve(A, b) - # Voltage is penultimate, current is last (by construction in - # _set_up_model_for_eis) - return -x[-2] / x[-1] + if three_electrodes: + z_cell = -x[-4] / x[-1] + z_pos = -x[-3] / x[-1] + z_neg = -x[-2] / x[-1] + return z_cell, z_pos, z_neg + else: + # Voltage is penultimate, current is last (by construction in + # _set_up_model_for_eis) + return -x[-2] / x[-1] def solve(self, frequencies, inputs=None, initial_soc=None): """Compute impedance at the given frequencies. @@ -269,9 +319,20 @@ def solve(self, frequencies, inputs=None, initial_soc=None): M, neg_J, b = self._build_matrix_problem(inputs_dict=inputs) - zs = [self._calculate_impedance(f, M, neg_J, b) for f in frequencies] + zs = [ + self._calculate_impedance(f, M, neg_J, b, self.three_electrodes) + for f in frequencies + ] impedance = np.array(zs) * self._z_scale - self._solution = pybamm.EISSolution(frequencies, impedance) + if self.three_electrodes: + self._solution = pybamm.EISSolution(frequencies, impedance[:, 0]) + self._solution._data["Cell impedance [Ohm]"] = impedance[:, 0] + self._solution._data["Positive electrode impedance [Ohm]"] = impedance[:, 1] + self._solution._data["Negative electrode impedance [Ohm]"] = -impedance[ + :, 2 + ] + else: + self._solution = pybamm.EISSolution(frequencies, impedance) self._solution.set_up_time = self.set_up_time self.solve_time = timer.time() diff --git a/packages/pybamm/tests/unit/test_eis_simulation.py b/packages/pybamm/tests/unit/test_eis_simulation.py index 9d30746f94..3ad58a4c0a 100644 --- a/packages/pybamm/tests/unit/test_eis_simulation.py +++ b/packages/pybamm/tests/unit/test_eis_simulation.py @@ -28,6 +28,14 @@ def test_skip_surface_form_check(self): pybamm.EISSimulation._validate_model_for_eis( model, skip_surface_form_check=True ) + + def test_three_electrode_inserts_reference_electrode(self): + model = pybamm.lithium_ion.SPM(options={"surface form": "differential"}) + pybamm.EISSimulation( + model, three_electrodes=True + ) + assert "Positive electrode 3E potential [V]" in model.variables + assert "Negative electrode 3E potential [V]" in model.variables class TestEISSolution: @@ -251,6 +259,25 @@ def test_high_freq_intercept_matches_contact_resistance(self): assert high_freq_z.real == pytest.approx(1.0, abs=1e-3) assert abs(high_freq_z.imag) < 1e-3 + @pytest.mark.parametrize( + "model_class", + [pybamm.lithium_ion.SPM, pybamm.lithium_ion.SPMe, pybamm.lithium_ion.DFN], + ) + def test_three_electrode_impedances_sum_to_cell_impedance(self, model_class): + model = model_class(options={"surface form": "differential"}) + eis_sim = pybamm.EISSimulation( + model, three_electrodes=True + ) + frequencies = np.logspace(-2, 2, 5) + + result = eis_sim.solve(frequencies) + z_cell = result["Cell impedance [Ohm]"] + z_pos = result["Positive electrode impedance [Ohm]"] + z_neg = result["Negative electrode impedance [Ohm]"] + + np.testing.assert_allclose(z_pos + z_neg, z_cell, rtol=1e-6, atol=1e-8) + np.testing.assert_allclose(result.impedance, z_cell) + class TestNyquistPlot: """Tests for Nyquist plotting.""" @@ -290,6 +317,27 @@ def test_eis_nyquist_plot(self): assert fig is not None assert ax is not None + def test_eis_nyquist_plot_components(self): + import matplotlib + + matplotlib.use("Agg") + + impedance = np.array([1 + 0.5j, 2 + 1j, 3 + 1.5j]) + solution = pybamm.EISSolution(np.array([1.0, 10.0, 100.0]), impedance) + solution._data["Cell impedance [Ohm]"] = impedance + solution._data["Positive electrode impedance [Ohm]"] = 0.4 * impedance + solution._data["Negative electrode impedance [Ohm]"] = 0.6 * impedance + + fig, ax = solution.nyquist_plot(show_plot=False) + + assert fig is not None + assert [line.get_label() for line in ax.get_lines()] == [ + "Cell", + "Positive electrode", + "Negative electrode", + ] + + def test_nyquist_plot_before_solve_raises(self): model = pybamm.lithium_ion.SPM(options={"surface form": "differential"}) eis_sim = pybamm.EISSimulation(model) From bcfc46d247c672d509d687263e337a072fdafd66 Mon Sep 17 00:00:00 2001 From: Pierfrancesco Ombrini <91598680+Ombrini@users.noreply.github.com> Date: Fri, 10 Jul 2026 11:41:34 +0200 Subject: [PATCH 2/6] add nyquist plot with 3 electrodes --- .../pybamm/src/pybamm/solvers/solution.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/pybamm/src/pybamm/solvers/solution.py b/packages/pybamm/src/pybamm/solvers/solution.py index 3a5119a6df..dbccd0a801 100644 --- a/packages/pybamm/src/pybamm/solvers/solution.py +++ b/packages/pybamm/src/pybamm/solvers/solution.py @@ -173,7 +173,35 @@ def nyquist_plot(self, **kwargs): """ from pybamm.plotting.nyquist_plot import nyquist_plot - return nyquist_plot(self.impedance, **kwargs) + component_keys = { + "Cell": "Cell impedance [Ohm]", + "Positive electrode": "Positive electrode impedance [Ohm]", + "Negative electrode": "Negative electrode impedance [Ohm]", + } + if not all(key in self._data for key in component_keys.values()): + return nyquist_plot(self.impedance, **kwargs) + + plot_kwargs = dict(kwargs) + ax = plot_kwargs.pop("ax", None) + show_plot = plot_kwargs.pop("show_plot", True) + plot_kwargs.pop("label", None) + fig = None + for label, key in component_keys.items(): + fig_i, ax = nyquist_plot( + self._data[key], + ax=ax, + show_plot=False, + label=label, + **plot_kwargs, + ) + fig = fig or fig_i + ax.legend() + + if show_plot: # pragma: no cover + plt = pybamm.import_optional_dependency("matplotlib.pyplot") + plt.show() + + return fig, ax _DEFAULT_SOLUTION_OPTIONS = { From 512f3fa0877a1df88b09de58aa72d90b3a7b5b77 Mon Sep 17 00:00:00 2001 From: Pierfrancesco Ombrini <91598680+Ombrini@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:22:43 +0200 Subject: [PATCH 3/6] draft or pull request --- PR_draft.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 PR_draft.md diff --git a/PR_draft.md b/PR_draft.md new file mode 100644 index 0000000000..f07b36953a --- /dev/null +++ b/PR_draft.md @@ -0,0 +1,30 @@ +# Description + +Adds three-electrode EIS support to `pybamm.EISSimulation`. + +When `three_electrodes=True`, `EISSimulation` now inserts a default reference electrode at the separator midpoint if one has not already been inserted. The EIS setup promotes the positive and negative 3E potentials to algebraic probe variables, returns named impedance components in the `EISSolution`, and keeps the default `solution.impedance` as the cell impedance. + +`EISSolution.nyquist_plot()` now detects three-electrode impedance components and plots the cell, positive electrode, and negative electrode curves together. + +Fixes # (issue) + +## Type of change + +Feature. + +Changelog entry required under `# [Unreleased]` / `## Features`: + +- Added three-electrode EIS support to `pybamm.EISSimulation`, including automatic default reference-electrode insertion, named positive/negative electrode impedance outputs, and component-aware Nyquist plotting. ([#XXXX](https://github.com/pybamm-team/PyBaMM/pull/XXXX)) + +# Important checks: + +Please confirm the following before marking the PR as ready for review: +- [ ] No style issues: `nox -s pre-commit` +- [ ] All tests pass: `nox -s tests` +- [ ] 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 + +Focused checks run locally: +- `.venv/bin/ruff check packages/pybamm/tests/unit/test_eis_simulation.py` +- `MPLBACKEND=Agg MPLCONFIGDIR=/tmp/mplcache .venv/bin/pytest packages/pybamm/tests/unit/test_eis_simulation.py::TestEISSimulationSolve::test_three_electrode_impedances_sum_to_cell_impedance` \ No newline at end of file From 3aea83a04a3b394c9d4cd0197e060d3631942086 Mon Sep 17 00:00:00 2001 From: Ombrini <91598680+Ombrini@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:16:13 +0200 Subject: [PATCH 4/6] update and changelog and relaxing tolerances of the test. --- CHANGELOG.md | 5 +++++ PR_draft.md | 2 +- .../pybamm/src/pybamm/simulation/eis_simulation.py | 6 ++++-- packages/pybamm/tests/unit/test_eis_simulation.py | 13 ++++--------- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b72352aae2..02e3f55869 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM/) +## Features + +- Added `three_electrodes=True` in `EISSimulation` to compute the EIS of both Negative and Positive electrode independently. This is done by using `model.insert_reference_electrode` inside the class and computing the mass matrix of both Positive and Negative electrode. The `.nyquist_plot()` is updated accordingly. + + ## Breaking changes - The "voltage as a state" model option now defaults to "true": voltage is solved as an algebraic state, making standard models DAEs. Reading `Voltage [V]` from a solution is now an O(1) state lookup instead of a post-solve expression evaluation (up to 73% faster end-to-end for dense output; 8-24% faster experiment cycling across SPM/SPMe/DFN; up to 48% faster with in-solver `output_variables=["Voltage [V]"]`). The default `IDAKLUSolver`, `CasadiSolver` (all modes), and `JaxSolver(method="BDF")` handle DAEs; ODE-only solvers (`ScipySolver`, `JaxSolver(method="RK45")`) require `{"voltage as a state": "false", "surface form": "false"}` for SPM/SPMe, which remains a supported configuration. Known trade-off: continuous solves of rapidly alternating current profiles (e.g. interpolant drive cycles with more than ~25 current reversals in one solve) can be slower, up to ~2x for SPM in the worst measured case; the legacy configuration above restores previous performance for these workloads. Experiment-driven cycling is unaffected. ([#5573](https://github.com/pybamm-team/PyBaMM/pull/5573)) diff --git a/PR_draft.md b/PR_draft.md index f07b36953a..53348db390 100644 --- a/PR_draft.md +++ b/PR_draft.md @@ -27,4 +27,4 @@ Please confirm the following before marking the PR as ready for review: Focused checks run locally: - `.venv/bin/ruff check packages/pybamm/tests/unit/test_eis_simulation.py` -- `MPLBACKEND=Agg MPLCONFIGDIR=/tmp/mplcache .venv/bin/pytest packages/pybamm/tests/unit/test_eis_simulation.py::TestEISSimulationSolve::test_three_electrode_impedances_sum_to_cell_impedance` \ No newline at end of file +- `MPLBACKEND=Agg MPLCONFIGDIR=/tmp/mplcache .venv/bin/pytest packages/pybamm/tests/unit/test_eis_simulation.py::TestEISSimulationSolve::test_three_electrode_impedances_sum_to_cell_impedance` diff --git a/packages/pybamm/src/pybamm/simulation/eis_simulation.py b/packages/pybamm/src/pybamm/simulation/eis_simulation.py index a9676ab303..4c8f676a4f 100644 --- a/packages/pybamm/src/pybamm/simulation/eis_simulation.py +++ b/packages/pybamm/src/pybamm/simulation/eis_simulation.py @@ -93,7 +93,9 @@ def __init__( pybamm.citations.register("Hallemans2025") @staticmethod - def _validate_model_for_eis(model, skip_surface_form_check=False, three_electrodes=False): + def _validate_model_for_eis( + model, skip_surface_form_check=False, three_electrodes=False + ): """Validate that a model is suitable for frequency-domain EIS. Raises @@ -109,7 +111,7 @@ def _validate_model_for_eis(model, skip_surface_form_check=False, three_electrod "Negative electrode 3E potential [V]", ] ) - + for var in required_vars: if var not in model.variables: raise ValueError( diff --git a/packages/pybamm/tests/unit/test_eis_simulation.py b/packages/pybamm/tests/unit/test_eis_simulation.py index 3ad58a4c0a..c6e4d1484f 100644 --- a/packages/pybamm/tests/unit/test_eis_simulation.py +++ b/packages/pybamm/tests/unit/test_eis_simulation.py @@ -28,12 +28,10 @@ def test_skip_surface_form_check(self): pybamm.EISSimulation._validate_model_for_eis( model, skip_surface_form_check=True ) - + def test_three_electrode_inserts_reference_electrode(self): model = pybamm.lithium_ion.SPM(options={"surface form": "differential"}) - pybamm.EISSimulation( - model, three_electrodes=True - ) + pybamm.EISSimulation(model, three_electrodes=True) assert "Positive electrode 3E potential [V]" in model.variables assert "Negative electrode 3E potential [V]" in model.variables @@ -265,9 +263,7 @@ def test_high_freq_intercept_matches_contact_resistance(self): ) def test_three_electrode_impedances_sum_to_cell_impedance(self, model_class): model = model_class(options={"surface form": "differential"}) - eis_sim = pybamm.EISSimulation( - model, three_electrodes=True - ) + eis_sim = pybamm.EISSimulation(model, three_electrodes=True) frequencies = np.logspace(-2, 2, 5) result = eis_sim.solve(frequencies) @@ -275,7 +271,7 @@ def test_three_electrode_impedances_sum_to_cell_impedance(self, model_class): z_pos = result["Positive electrode impedance [Ohm]"] z_neg = result["Negative electrode impedance [Ohm]"] - np.testing.assert_allclose(z_pos + z_neg, z_cell, rtol=1e-6, atol=1e-8) + np.testing.assert_allclose(z_pos + z_neg, z_cell, rtol=1e-5, atol=1e-5) np.testing.assert_allclose(result.impedance, z_cell) @@ -337,7 +333,6 @@ def test_eis_nyquist_plot_components(self): "Negative electrode", ] - def test_nyquist_plot_before_solve_raises(self): model = pybamm.lithium_ion.SPM(options={"surface form": "differential"}) eis_sim = pybamm.EISSimulation(model) From 614fb90ba5d7daf3dd5abc3a48aa17f1c329f888 Mon Sep 17 00:00:00 2001 From: Ombrini <91598680+Ombrini@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:19:52 +0200 Subject: [PATCH 5/6] updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02e3f55869..92e1184bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Features -- Added `three_electrodes=True` in `EISSimulation` to compute the EIS of both Negative and Positive electrode independently. This is done by using `model.insert_reference_electrode` inside the class and computing the mass matrix of both Positive and Negative electrode. The `.nyquist_plot()` is updated accordingly. +- Added three-electrode EIS support to `pybamm.EISSimulation`, including automatic default reference-electrode insertion, named positive/negative electrode impedance outputs, and component-aware Nyquist plotting. ([#XXXX](https://github.com/pybamm-team/PyBaMM/pull/XXXX)) ## Breaking changes From 80d3865c160ae9f50ca97f794a388c6615b0edf6 Mon Sep 17 00:00:00 2001 From: Ombrini <91598680+Ombrini@users.noreply.github.com> Date: Tue, 14 Jul 2026 19:24:19 +0200 Subject: [PATCH 6/6] add PR number to changelog --- CHANGELOG.md | 2 +- PR_draft.md | 30 ------------------------------ 2 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 PR_draft.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e1184bb8..d44e9528de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Features -- Added three-electrode EIS support to `pybamm.EISSimulation`, including automatic default reference-electrode insertion, named positive/negative electrode impedance outputs, and component-aware Nyquist plotting. ([#XXXX](https://github.com/pybamm-team/PyBaMM/pull/XXXX)) +- Added three-electrode EIS support to `pybamm.EISSimulation`, including automatic default reference-electrode insertion, named positive/negative electrode impedance outputs, and component-aware Nyquist plotting. ([#XXXX](https://github.com/pybamm-team/PyBaMM/pull/#5648)) ## Breaking changes diff --git a/PR_draft.md b/PR_draft.md deleted file mode 100644 index 53348db390..0000000000 --- a/PR_draft.md +++ /dev/null @@ -1,30 +0,0 @@ -# Description - -Adds three-electrode EIS support to `pybamm.EISSimulation`. - -When `three_electrodes=True`, `EISSimulation` now inserts a default reference electrode at the separator midpoint if one has not already been inserted. The EIS setup promotes the positive and negative 3E potentials to algebraic probe variables, returns named impedance components in the `EISSolution`, and keeps the default `solution.impedance` as the cell impedance. - -`EISSolution.nyquist_plot()` now detects three-electrode impedance components and plots the cell, positive electrode, and negative electrode curves together. - -Fixes # (issue) - -## Type of change - -Feature. - -Changelog entry required under `# [Unreleased]` / `## Features`: - -- Added three-electrode EIS support to `pybamm.EISSimulation`, including automatic default reference-electrode insertion, named positive/negative electrode impedance outputs, and component-aware Nyquist plotting. ([#XXXX](https://github.com/pybamm-team/PyBaMM/pull/XXXX)) - -# Important checks: - -Please confirm the following before marking the PR as ready for review: -- [ ] No style issues: `nox -s pre-commit` -- [ ] All tests pass: `nox -s tests` -- [ ] 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 - -Focused checks run locally: -- `.venv/bin/ruff check packages/pybamm/tests/unit/test_eis_simulation.py` -- `MPLBACKEND=Agg MPLCONFIGDIR=/tmp/mplcache .venv/bin/pytest packages/pybamm/tests/unit/test_eis_simulation.py::TestEISSimulationSolve::test_three_electrode_impedances_sum_to_cell_impedance`