From ba2aa674244a247bc47b6ae67b5f6bf43e55f335 Mon Sep 17 00:00:00 2001 From: Pradyot Ranjan <99216956+prady0t@users.noreply.github.com> Date: Fri, 13 Sep 2024 18:46:55 +0530 Subject: [PATCH] Migrating unittest to pytest (Part 7) (#4431) * Migrating unittest to pytest (Part 7) Signed-off-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> * style: pre-commit fixes * Removing style failures Signed-off-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> * Update tests/unit/test_parameters/test_parameter_values.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_parameters/test_process_parameter_data.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_parameters/test_process_parameter_data.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_parameters/test_process_parameter_data.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_plotting/test_quick_plot.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_plotting/test_quick_plot.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_solvers/test_casadi_algebraic_solver.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_solvers/test_casadi_algebraic_solver.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Update tests/unit/test_spatial_methods/test_spectral_volume.py Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> * Removing DepricatioWarning failure Signed-off-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> --------- Signed-off-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> Co-authored-by: Pradyot Ranjan <99216956+pradyotRanjan@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Agriya Khetarpal <74401230+agriyakhetarpal@users.noreply.github.com> --- .../test_parameters/test_parameter_values.py | 366 ++++++++---------- .../test_process_parameter_data.py | 56 ++- tests/unit/test_plotting/test_quick_plot.py | 113 +++--- .../test_serialisation/test_serialisation.py | 153 ++++---- tests/unit/test_simulation.py | 163 ++++---- .../test_solvers/test_algebraic_solver.py | 39 +- tests/unit/test_solvers/test_base_solver.py | 105 +++-- .../test_casadi_algebraic_solver.py | 34 +- tests/unit/test_solvers/test_casadi_solver.py | 56 ++- tests/unit/test_solvers/test_idaklu_solver.py | 60 ++- .../unit/test_solvers/test_jax_bdf_solver.py | 20 +- tests/unit/test_solvers/test_jax_solver.py | 38 +- .../test_solvers/test_processed_variable.py | 64 +-- .../test_processed_variable_computed.py | 22 +- .../test_base_spatial_method.py | 72 ++-- .../test_finite_volume/test_extrapolation.py | 27 +- .../test_finite_volume/test_finite_volume.py | 88 ++--- .../test_ghost_nodes_and_neumann.py | 60 +-- .../test_grad_div_shapes.py | 13 +- .../test_finite_volume/test_integration.py | 46 +-- .../test_scikit_finite_element.py | 38 +- .../test_spectral_volume.py | 24 +- 22 files changed, 693 insertions(+), 964 deletions(-) diff --git a/tests/unit/test_parameters/test_parameter_values.py b/tests/unit/test_parameters/test_parameter_values.py index eaeb4a5a42..4086abea6d 100644 --- a/tests/unit/test_parameters/test_parameter_values.py +++ b/tests/unit/test_parameters/test_parameter_values.py @@ -3,8 +3,8 @@ # +import pytest import os -import unittest import numpy as np import pandas as pd @@ -19,88 +19,82 @@ import casadi -class TestParameterValues(unittest.TestCase): +class TestParameterValues: def test_init(self): # from dict param = pybamm.ParameterValues({"a": 1}) - self.assertEqual(param["a"], 1) - self.assertIn("a", param.keys()) - self.assertIn(1, param.values()) - self.assertIn(("a", 1), param.items()) + assert param["a"] == 1 + assert "a" in param.keys() + assert 1 in param.values() + assert ("a", 1) in param.items() # from dict with strings param = pybamm.ParameterValues({"a": "1"}) - self.assertEqual(param["a"], 1) + assert param["a"] == 1 # from dict "chemistry" key gets removed param = pybamm.ParameterValues({"a": 1, "chemistry": "lithium-ion"}) - self.assertNotIn("chemistry", param.keys()) + assert "chemistry" not in param.keys() # chemistry kwarg removed - with self.assertRaisesRegex( - ValueError, "'chemistry' keyword argument has been deprecated" + with pytest.raises( + ValueError, match="'chemistry' keyword argument has been deprecated" ): pybamm.ParameterValues(None, chemistry="lithium-ion") # junk param values rejected - with self.assertRaisesRegex(ValueError, "'Junk' is not a valid parameter set."): + with pytest.raises(ValueError, match="'Junk' is not a valid parameter set."): pybamm.ParameterValues("Junk") def test_repr(self): param = pybamm.ParameterValues({"a": 1}) - self.assertEqual( - repr(param), - "{'Boltzmann constant [J.K-1]': 1.380649e-23,\n" + assert ( + repr(param) == "{'Boltzmann constant [J.K-1]': 1.380649e-23,\n" " 'Electron charge [C]': 1.602176634e-19,\n" " 'Faraday constant [C.mol-1]': 96485.33212,\n" " 'Ideal gas constant [J.K-1.mol-1]': 8.314462618,\n" - " 'a': 1}", - ) - self.assertEqual( - param._ipython_key_completions_(), - [ - "Ideal gas constant [J.K-1.mol-1]", - "Faraday constant [C.mol-1]", - "Boltzmann constant [J.K-1]", - "Electron charge [C]", - "a", - ], + " 'a': 1}" ) + assert param._ipython_key_completions_() == [ + "Ideal gas constant [J.K-1.mol-1]", + "Faraday constant [C.mol-1]", + "Boltzmann constant [J.K-1]", + "Electron charge [C]", + "a", + ] def test_eq(self): - self.assertEqual( - pybamm.ParameterValues({"a": 1}), pybamm.ParameterValues({"a": 1}) - ) + assert pybamm.ParameterValues({"a": 1}) == pybamm.ParameterValues({"a": 1}) def test_update(self): # equate values param = pybamm.ParameterValues({"a": 1}) - self.assertEqual(param["a"], 1) + assert param["a"] == 1 # no conflict param.update({"a": 2}) - self.assertEqual(param["a"], 2) + assert param["a"] == 2 param.update({"a": 2}, check_conflict=True) - self.assertEqual(param["a"], 2) + assert param["a"] == 2 # with conflict param.update({"a": 3}) # via __setitem__ param["a"] = 2 - self.assertEqual(param["a"], 2) - with self.assertRaisesRegex( - ValueError, "parameter 'a' already defined with value '2'" + assert param["a"] == 2 + with pytest.raises( + ValueError, match="parameter 'a' already defined with value '2'" ): param.update({"a": 4}, check_conflict=True) # with parameter not existing yet - with self.assertRaisesRegex(KeyError, "Cannot update parameter"): + with pytest.raises(KeyError, match="Cannot update parameter"): param.update({"b": 1}) # update with a ParameterValues object new_param = pybamm.ParameterValues(param) - self.assertEqual(new_param["a"], 2) + assert new_param["a"] == 2 # test deleting a parameter del param["a"] - self.assertNotIn("a", param.keys()) + assert "a" not in param.keys() def test_set_initial_stoichiometries(self): param = pybamm.ParameterValues("Chen2020") @@ -113,12 +107,12 @@ def test_set_initial_stoichiometries(self): x = param["Initial concentration in negative electrode [mol.m-3]"] x_0 = param_0["Initial concentration in negative electrode [mol.m-3]"] x_100 = param_100["Initial concentration in negative electrode [mol.m-3]"] - self.assertAlmostEqual(x, x_0 + 0.4 * (x_100 - x_0)) + assert x == pytest.approx(x_0 + 0.4 * (x_100 - x_0)) y = param["Initial concentration in positive electrode [mol.m-3]"] y_0 = param_0["Initial concentration in positive electrode [mol.m-3]"] y_100 = param_100["Initial concentration in positive electrode [mol.m-3]"] - self.assertAlmostEqual(y, y_0 - 0.4 * (y_0 - y_100)) + assert y == pytest.approx(y_0 - 0.4 * (y_0 - y_100)) def test_set_initial_stoichiometry_half_cell(self): param = pybamm.lithium_ion.DFN( @@ -137,7 +131,7 @@ def test_set_initial_stoichiometry_half_cell(self): y = param["Initial concentration in positive electrode [mol.m-3]"] y_0 = param_0["Initial concentration in positive electrode [mol.m-3]"] y_100 = param_100["Initial concentration in positive electrode [mol.m-3]"] - self.assertAlmostEqual(y, y_0 - 0.4 * (y_0 - y_100)) + assert y == pytest.approx(y_0 - 0.4 * (y_0 - y_100)) # inplace for 100% coverage param_t = pybamm.lithium_ion.DFN( @@ -161,11 +155,11 @@ def test_set_initial_stoichiometry_half_cell(self): 1, inplace=True, options={"working electrode": "positive"} ) y_100 = param_100["Initial concentration in positive electrode [mol.m-3]"] - self.assertAlmostEqual(y, y_0 - 0.4 * (y_0 - y_100)) + assert y == pytest.approx(y_0 - 0.4 * (y_0 - y_100)) # test error param = pybamm.ParameterValues("Chen2020") - with self.assertRaisesRegex(OptionError, "working electrode"): + with pytest.raises(OptionError, match="working electrode"): param.set_initial_stoichiometry_half_cell( 0.1, options={"working electrode": "negative"} ) @@ -183,20 +177,20 @@ def test_set_initial_ocps(self): Un_0 = param_0["Initial voltage in negative electrode [V]"] Up_0 = param_0["Initial voltage in positive electrode [V]"] - self.assertAlmostEqual(Up_0 - Un_0, 2.8) + assert Up_0 - Un_0 == pytest.approx(2.8) Un_100 = param_100["Initial voltage in negative electrode [V]"] Up_100 = param_100["Initial voltage in positive electrode [V]"] - self.assertAlmostEqual(Up_100 - Un_100, 4.2) + assert Up_100 - Un_100 == pytest.approx(4.2) def test_check_parameter_values(self): - with self.assertRaisesRegex(ValueError, "propotional term"): + with pytest.raises(ValueError, match="propotional term"): pybamm.ParameterValues( {"Negative electrode LAM constant propotional term": 1} ) # The + character in "1 + dlnf/dlnc" is appended with a backslash (\+), # since + has other meanings in regex - with self.assertRaisesRegex(ValueError, "Thermodynamic factor"): + with pytest.raises(ValueError, match="Thermodynamic factor"): pybamm.ParameterValues({"1 + dlnf/dlnc": 1}) def test_process_symbol(self): @@ -204,86 +198,86 @@ def test_process_symbol(self): # process parameter a = pybamm.Parameter("a") processed_a = parameter_values.process_symbol(a) - self.assertIsInstance(processed_a, pybamm.Scalar) - self.assertEqual(processed_a.value, 4) + assert isinstance(processed_a, pybamm.Scalar) + assert processed_a.value == 4 # process binary operation var = pybamm.Variable("var") add = a + var processed_add = parameter_values.process_symbol(add) - self.assertIsInstance(processed_add, pybamm.Addition) - self.assertIsInstance(processed_add.children[0], pybamm.Scalar) - self.assertIsInstance(processed_add.children[1], pybamm.Variable) - self.assertEqual(processed_add.children[0].value, 4) + assert isinstance(processed_add, pybamm.Addition) + assert isinstance(processed_add.children[0], pybamm.Scalar) + assert isinstance(processed_add.children[1], pybamm.Variable) + assert processed_add.children[0].value == 4 b = pybamm.Parameter("b") add = a + b processed_add = parameter_values.process_symbol(add) - self.assertIsInstance(processed_add, pybamm.Scalar) - self.assertEqual(processed_add.value, 6) + assert isinstance(processed_add, pybamm.Scalar) + assert processed_add.value == 6 scal = pybamm.Scalar(34) mul = a * scal processed_mul = parameter_values.process_symbol(mul) - self.assertIsInstance(processed_mul, pybamm.Scalar) - self.assertEqual(processed_mul.value, 136) + assert isinstance(processed_mul, pybamm.Scalar) + assert processed_mul.value == 136 # process integral aa = pybamm.PrimaryBroadcast(pybamm.Parameter("a"), "negative electrode") x = pybamm.SpatialVariable("x", domain=["negative electrode"]) integ = pybamm.Integral(aa, x) processed_integ = parameter_values.process_symbol(integ) - self.assertIsInstance(processed_integ, pybamm.Integral) - self.assertIsInstance(processed_integ.children[0], pybamm.PrimaryBroadcast) - self.assertEqual(processed_integ.children[0].child.value, 4) - self.assertEqual(processed_integ.integration_variable[0], x) + assert isinstance(processed_integ, pybamm.Integral) + assert isinstance(processed_integ.children[0], pybamm.PrimaryBroadcast) + assert processed_integ.children[0].child.value == 4 + assert processed_integ.integration_variable[0] == x # process unary operation v = pybamm.Variable("v", domain="test") grad = pybamm.Gradient(v) processed_grad = parameter_values.process_symbol(grad) - self.assertIsInstance(processed_grad, pybamm.Gradient) - self.assertIsInstance(processed_grad.children[0], pybamm.Variable) + assert isinstance(processed_grad, pybamm.Gradient) + assert isinstance(processed_grad.children[0], pybamm.Variable) # process delta function aa = pybamm.Parameter("a") delta_aa = pybamm.DeltaFunction(aa, "left", "some domain") processed_delta_aa = parameter_values.process_symbol(delta_aa) - self.assertIsInstance(processed_delta_aa, pybamm.DeltaFunction) - self.assertEqual(processed_delta_aa.side, "left") + assert isinstance(processed_delta_aa, pybamm.DeltaFunction) + assert processed_delta_aa.side == "left" processed_a = processed_delta_aa.children[0] - self.assertIsInstance(processed_a, pybamm.Scalar) - self.assertEqual(processed_a.value, 4) + assert isinstance(processed_a, pybamm.Scalar) + assert processed_a.value == 4 # process boundary operator (test for BoundaryValue) aa = pybamm.Parameter("a") x = pybamm.SpatialVariable("x", domain=["negative electrode"]) boundary_op = pybamm.BoundaryValue(aa * x, "left") processed_boundary_op = parameter_values.process_symbol(boundary_op) - self.assertIsInstance(processed_boundary_op, pybamm.BoundaryOperator) + assert isinstance(processed_boundary_op, pybamm.BoundaryOperator) processed_a = processed_boundary_op.children[0].children[0] processed_x = processed_boundary_op.children[0].children[1] - self.assertIsInstance(processed_a, pybamm.Scalar) - self.assertEqual(processed_a.value, 4) - self.assertEqual(processed_x, x) + assert isinstance(processed_a, pybamm.Scalar) + assert processed_a.value == 4 + assert processed_x == x # process EvaluateAt evaluate_at = pybamm.EvaluateAt(x, aa) processed_evaluate_at = parameter_values.process_symbol(evaluate_at) - self.assertIsInstance(processed_evaluate_at, pybamm.EvaluateAt) - self.assertEqual(processed_evaluate_at.children[0], x) - self.assertEqual(processed_evaluate_at.position, 4) - with self.assertRaisesRegex(ValueError, "'position' in 'EvaluateAt'"): + assert isinstance(processed_evaluate_at, pybamm.EvaluateAt) + assert processed_evaluate_at.children[0] == x + assert processed_evaluate_at.position == 4 + with pytest.raises(ValueError, match="'position' in 'EvaluateAt'"): parameter_values.process_symbol(pybamm.EvaluateAt(x, x)) # process broadcast whole_cell = ["negative electrode", "separator", "positive electrode"] broad = pybamm.PrimaryBroadcast(a, whole_cell) processed_broad = parameter_values.process_symbol(broad) - self.assertIsInstance(processed_broad, pybamm.Broadcast) - self.assertEqual(processed_broad.domain, whole_cell) - self.assertIsInstance(processed_broad.children[0], pybamm.Scalar) - self.assertEqual(processed_broad.children[0].evaluate(), 4) + assert isinstance(processed_broad, pybamm.Broadcast) + assert processed_broad.domain == whole_cell + assert isinstance(processed_broad.children[0], pybamm.Scalar) + assert processed_broad.children[0].evaluate() == 4 # process concatenation conc = pybamm.concatenation( @@ -291,8 +285,8 @@ def test_process_symbol(self): pybamm.Vector(2 * np.ones(15), domain="test 2"), ) processed_conc = parameter_values.process_symbol(conc) - self.assertIsInstance(processed_conc.children[0], pybamm.Vector) - self.assertIsInstance(processed_conc.children[1], pybamm.Vector) + assert isinstance(processed_conc.children[0], pybamm.Vector) + assert isinstance(processed_conc.children[1], pybamm.Vector) np.testing.assert_array_equal(processed_conc.children[0].entries, 1) np.testing.assert_array_equal(processed_conc.children[1].entries, 2) @@ -304,52 +298,52 @@ def test_process_symbol(self): processed_dom_con = parameter_values.process_symbol(dom_con) a_proc = processed_dom_con.children[0].children[0] b_proc = processed_dom_con.children[1].children[0] - self.assertIsInstance(a_proc, pybamm.Scalar) - self.assertIsInstance(b_proc, pybamm.Scalar) - self.assertEqual(a_proc.value, 4) - self.assertEqual(b_proc.value, 2) + assert isinstance(a_proc, pybamm.Scalar) + assert isinstance(b_proc, pybamm.Scalar) + assert a_proc.value == 4 + assert b_proc.value == 2 # process variable c = pybamm.Variable("c") processed_c = parameter_values.process_symbol(c) - self.assertIsInstance(processed_c, pybamm.Variable) - self.assertEqual(processed_c.name, "c") + assert isinstance(processed_c, pybamm.Variable) + assert processed_c.name == "c" # process scalar d = pybamm.Scalar(14) processed_d = parameter_values.process_symbol(d) - self.assertIsInstance(processed_d, pybamm.Scalar) - self.assertEqual(processed_d.value, 14) + assert isinstance(processed_d, pybamm.Scalar) + assert processed_d.value == 14 # process array types e = pybamm.Vector(np.ones(4)) processed_e = parameter_values.process_symbol(e) - self.assertIsInstance(processed_e, pybamm.Vector) + assert isinstance(processed_e, pybamm.Vector) np.testing.assert_array_equal(processed_e.evaluate(), np.ones((4, 1))) f = pybamm.Matrix(np.ones((5, 6))) processed_f = parameter_values.process_symbol(f) - self.assertIsInstance(processed_f, pybamm.Matrix) + assert isinstance(processed_f, pybamm.Matrix) np.testing.assert_array_equal(processed_f.evaluate(), np.ones((5, 6))) # process statevector g = pybamm.StateVector(slice(0, 10)) processed_g = parameter_values.process_symbol(g) - self.assertIsInstance(processed_g, pybamm.StateVector) + assert isinstance(processed_g, pybamm.StateVector) np.testing.assert_array_equal( processed_g.evaluate(y=np.ones(10)), np.ones((10, 1)) ) # not found - with self.assertRaises(KeyError): + with pytest.raises(KeyError): x = pybamm.Parameter("x") parameter_values.process_symbol(x) parameter_values = pybamm.ParameterValues({"x": np.nan}) - with self.assertRaisesRegex(ValueError, "Parameter 'x' not found"): + with pytest.raises(ValueError, match="Parameter 'x' not found"): x = pybamm.Parameter("x") parameter_values.process_symbol(x) - with self.assertRaisesRegex(ValueError, "possibly a function"): + with pytest.raises(ValueError, match="possibly a function"): x = pybamm.FunctionParameter("x", {}) parameter_values.process_symbol(x) @@ -361,11 +355,11 @@ def test_process_parameter_in_parameter(self): # process 2a parameter a = pybamm.Parameter("2a") processed_a = parameter_values.process_symbol(a) - self.assertEqual(processed_a.evaluate(), 4) + assert processed_a.evaluate() == 4 # case where parameter can't be processed b = pybamm.Parameter("b") - with self.assertRaisesRegex(TypeError, "Cannot process parameter"): + with pytest.raises(TypeError, match="Cannot process parameter"): parameter_values.process_symbol(b) def test_process_input_parameter(self): @@ -375,19 +369,19 @@ def test_process_input_parameter(self): # process input parameter a = pybamm.Parameter("a") processed_a = parameter_values.process_symbol(a) - self.assertIsInstance(processed_a, pybamm.InputParameter) - self.assertEqual(processed_a.evaluate(inputs={"a": 5}), 5) + assert isinstance(processed_a, pybamm.InputParameter) + assert processed_a.evaluate(inputs={"a": 5}) == 5 # process binary operation b = pybamm.Parameter("b") add = a + b processed_add = parameter_values.process_symbol(add) - self.assertEqual(processed_add, 3 + pybamm.InputParameter("a")) + assert processed_add == 3 + pybamm.InputParameter("a") # process complex input parameter c = pybamm.Parameter("c times 2") processed_c = parameter_values.process_symbol(c) - self.assertEqual(processed_c.evaluate(inputs={"c": 5}), 10) + assert processed_c.evaluate(inputs={"c": 5}) == 10 def test_process_function_parameter(self): def test_function(var): @@ -408,7 +402,7 @@ def test_function(var): # process function func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) - self.assertEqual(processed_func.evaluate(inputs={"a": 3}), 369) + assert processed_func.evaluate(inputs={"a": 3}) == 369 # process constant function # this should work even if the parameter in the function is not provided @@ -416,35 +410,35 @@ def test_function(var): "const", {"a": pybamm.Parameter("not provided")} ) processed_const = parameter_values.process_symbol(const) - self.assertIsInstance(processed_const, pybamm.Scalar) - self.assertEqual(processed_const.evaluate(), 254) + assert isinstance(processed_const, pybamm.Scalar) + assert processed_const.evaluate() == 254 # process case where parameter provided is a pybamm symbol # (e.g. a multiplication) mult = pybamm.FunctionParameter("mult", {"a": a}) processed_mult = parameter_values.process_symbol(mult) - self.assertEqual(processed_mult.evaluate(inputs={"a": 14, "b": 63}), 63 * 5) + assert processed_mult.evaluate(inputs={"a": 14, "b": 63}) == 63 * 5 # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) - self.assertEqual(processed_diff_func.evaluate(inputs={"a": 3}), 123) + assert processed_diff_func.evaluate(inputs={"a": 3}) == 123 # make sure diff works, despite simplifications, when the child is constant a_const = pybamm.Scalar(3) func_const = pybamm.FunctionParameter("func", {"a": a_const}) diff_func_const = func_const.diff(a_const) processed_diff_func_const = parameter_values.process_symbol(diff_func_const) - self.assertEqual(processed_diff_func_const.evaluate(), 123) + assert processed_diff_func_const.evaluate() == 123 # function parameter that returns a python float func = pybamm.FunctionParameter("float_func", {"a": a}) processed_func = parameter_values.process_symbol(func) - self.assertEqual(processed_func.evaluate(), 42) + assert processed_func.evaluate() == 42 # weird type raises error func = pybamm.FunctionParameter("bad type", {"a": a}) - with self.assertRaisesRegex(TypeError, "Parameter provided for"): + with pytest.raises(TypeError, match="Parameter provided for"): parameter_values.process_symbol(func) # function itself as input (different to the variable being an input) @@ -452,7 +446,7 @@ def test_function(var): a = pybamm.Scalar(3) func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) - self.assertEqual(processed_func.evaluate(inputs={"func": 13}), 13) + assert processed_func.evaluate(inputs={"func": 13}) == 13 # make sure function keeps the domain of the original function @@ -473,11 +467,11 @@ def my_func(x): ) func3 = parameter_values.process_symbol(func) - self.assertEqual(func1.domains, func2.domains) - self.assertEqual(func1.domains, func3.domains) + assert func1.domains == func2.domains + assert func1.domains == func3.domains # [function] is deprecated - with self.assertRaisesRegex(ValueError, "[function]"): + with pytest.raises(ValueError, match="[function]"): pybamm.ParameterValues({"func": "[function]something"}) def test_process_inline_function_parameters(self): @@ -490,12 +484,12 @@ def D(c): func = pybamm.FunctionParameter("Diffusivity", {"a": a}) processed_func = parameter_values.process_symbol(func) - self.assertEqual(processed_func.evaluate(), 9) + assert processed_func.evaluate() == 9 # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) - self.assertEqual(processed_diff_func.evaluate(), 6) + assert processed_diff_func.evaluate() == 6 def test_multi_var_function_with_parameters(self): def D(a, b): @@ -508,8 +502,8 @@ def D(a, b): processed_func = parameter_values.process_symbol(func) # Function of scalars gets automatically simplified - self.assertIsInstance(processed_func, pybamm.Scalar) - self.assertEqual(processed_func.evaluate(), 3) + assert isinstance(processed_func, pybamm.Scalar) + assert processed_func.evaluate() == 3 def test_multi_var_function_parameter(self): def D(a, b): @@ -522,7 +516,7 @@ def D(a, b): func = pybamm.FunctionParameter("Diffusivity", {"a": a, "b": b}) processed_func = parameter_values.process_symbol(func) - self.assertEqual(processed_func.evaluate(), 3) + assert processed_func.evaluate() == 3 def test_process_interpolant(self): x = np.linspace(0, 10)[:, np.newaxis] @@ -533,18 +527,18 @@ def test_process_interpolant(self): func = pybamm.FunctionParameter("Times two", {"a": a}) processed_func = parameter_values.process_symbol(func) - self.assertIsInstance(processed_func, pybamm.Interpolant) - self.assertEqual(processed_func.evaluate(inputs={"a": 3.01}), 6.02) + assert isinstance(processed_func, pybamm.Interpolant) + assert processed_func.evaluate(inputs={"a": 3.01}) == 6.02 # interpolant defined up front interp = pybamm.Interpolant(data[:, 0], data[:, 1], a, interpolator="cubic") processed_interp = parameter_values.process_symbol(interp) - self.assertEqual(processed_interp.evaluate(inputs={"a": 3.01}), 6.02) + assert processed_interp.evaluate(inputs={"a": 3.01}) == 6.02 # process differentiated function parameter diff_interp = interp.diff(a) processed_diff_interp = parameter_values.process_symbol(diff_interp) - self.assertEqual(processed_diff_interp.evaluate(inputs={"a": 3.01}), 2) + assert processed_diff_interp.evaluate(inputs={"a": 3.01}) == 2 def test_process_interpolant_2d(self): x_ = [np.linspace(0, 10), np.linspace(0, 20)] @@ -566,9 +560,9 @@ def test_process_interpolant_2d(self): func = pybamm.FunctionParameter("Times two", {"a": a, "b": b}) processed_func = parameter_values.process_symbol(func) - self.assertIsInstance(processed_func, pybamm.Interpolant) - self.assertAlmostEqual( - processed_func.evaluate(inputs={"a": 3.01, "b": 4.4}), 14.82 + assert isinstance(processed_func, pybamm.Interpolant) + assert processed_func.evaluate(inputs={"a": 3.01, "b": 4.4}) == pytest.approx( + 14.82 ) # process differentiated function parameter @@ -579,9 +573,7 @@ def test_process_interpolant_2d(self): # interpolant defined up front interp2 = pybamm.Interpolant(data[0], data[1], children=(a, b)) processed_interp2 = parameter_values.process_symbol(interp2) - self.assertEqual( - processed_interp2.evaluate(inputs={"a": 3.01, "b": 4.4}), 14.82 - ) + assert processed_interp2.evaluate(inputs={"a": 3.01, "b": 4.4}) == 14.82 y3 = (3 * x).sum(axis=1) @@ -598,7 +590,7 @@ def test_process_interpolant_2d(self): func = pybamm.FunctionParameter("Times three", {"a": a, "b": b}) processed_func = parameter_values.process_symbol(func) - self.assertIsInstance(processed_func, pybamm.Interpolant) + assert isinstance(processed_func, pybamm.Interpolant) # self.assertEqual(processed_func.evaluate().flatten()[0], 22.23) np.testing.assert_almost_equal( processed_func.evaluate(inputs={"a": 3.01, "b": 4.4}).flatten()[0], @@ -765,7 +757,7 @@ def test_process_integral_broadcast(self): param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) - self.assertEqual(func_proc, pybamm.Scalar(2, name="func")) + assert func_proc == pybamm.Scalar(2, name="func") # test with auxiliary domains @@ -780,9 +772,8 @@ def test_process_integral_broadcast(self): param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) - self.assertEqual( - func_proc, - pybamm.PrimaryBroadcast(pybamm.Scalar(2, name="func"), "current collector"), + assert func_proc == pybamm.PrimaryBroadcast( + pybamm.Scalar(2, name="func"), "current collector" ) # secondary and tertiary @@ -799,11 +790,8 @@ def test_process_integral_broadcast(self): param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) - self.assertEqual( - func_proc, - pybamm.FullBroadcast( - pybamm.Scalar(2, name="func"), "negative particle", "current collector" - ), + assert func_proc == pybamm.FullBroadcast( + pybamm.Scalar(2, name="func"), "negative particle", "current collector" ) # secondary, tertiary and quaternary @@ -821,16 +809,13 @@ def test_process_integral_broadcast(self): param = pybamm.ParameterValues({"func": 2}) func_proc = param.process_symbol(func) - self.assertEqual( - func_proc, - pybamm.FullBroadcast( - pybamm.Scalar(2, name="func"), - "negative particle", - { - "secondary": "negative particle size", - "tertiary": "current collector", - }, - ), + assert func_proc == pybamm.FullBroadcast( + pybamm.Scalar(2, name="func"), + "negative particle", + { + "secondary": "negative particle size", + "tertiary": "current collector", + }, ) # special case for integral of concatenations of broadcasts @@ -854,7 +839,7 @@ def test_process_integral_broadcast(self): ) func_proc = param.process_symbol(func) - self.assertEqual(func_proc, pybamm.Scalar(3)) + assert func_proc == pybamm.Scalar(3) # with auxiliary domains var_n = pybamm.Variable( @@ -889,9 +874,8 @@ def test_process_integral_broadcast(self): ) func_proc = param.process_symbol(func) - self.assertEqual( - func_proc, - pybamm.PrimaryBroadcast(pybamm.Scalar(3), "current collector"), + assert func_proc == pybamm.PrimaryBroadcast( + pybamm.Scalar(3), "current collector" ) def test_process_size_average(self): @@ -913,16 +897,16 @@ def dist(R): ) var_av_proc = param.process_symbol(var_av) - self.assertIsInstance(var_av_proc, pybamm.SizeAverage) + assert isinstance(var_av_proc, pybamm.SizeAverage) R = pybamm.SpatialVariable("R", "negative particle size") - self.assertEqual(var_av_proc.f_a_dist, R**2) + assert var_av_proc.f_a_dist == R**2 def test_process_not_constant(self): param = pybamm.ParameterValues({"a": 4}) a = pybamm.NotConstant(pybamm.Parameter("a")) - self.assertIsInstance(param.process_symbol(a), pybamm.NotConstant) - self.assertEqual(param.process_symbol(a).evaluate(), 4) + assert isinstance(param.process_symbol(a), pybamm.NotConstant) + assert param.process_symbol(a).evaluate() == 4 def test_process_complex_expression(self): var1 = pybamm.Variable("var1") @@ -933,12 +917,12 @@ def test_process_complex_expression(self): param = pybamm.ParameterValues({"par1": 2, "par2": 4}) exp_param = param.process_symbol(expression) - self.assertEqual(exp_param, 3.0 * (2.0**var2) / ((-4.0 + var1) + var2)) + assert exp_param == 3.0 * (2.0**var2) / ((-4.0 + var1) + var2) def test_process_geometry(self): var = pybamm.Variable("var") geometry = {"negative electrode": {"x": {"min": 0, "max": var}}} - with self.assertRaisesRegex(ValueError, "Geometry parameters must be Scalars"): + with pytest.raises(ValueError, match="Geometry parameters must be Scalars"): pybamm.ParameterValues({}).process_geometry(geometry) def test_process_model(self): @@ -965,39 +949,37 @@ def test_process_model(self): parameter_values = pybamm.ParameterValues({"a": 1, "b": 2, "c": 3, "d": 42}) parameter_values.process_model(model) # rhs - self.assertIsInstance(model.rhs[var1], pybamm.Gradient) + assert isinstance(model.rhs[var1], pybamm.Gradient) # algebraic - self.assertIsInstance(model.algebraic[var2], pybamm.Multiplication) - self.assertIsInstance(model.algebraic[var2].children[0], pybamm.Scalar) - self.assertIsInstance(model.algebraic[var2].children[1], pybamm.Variable) - self.assertEqual(model.algebraic[var2].children[0].value, 3) + assert isinstance(model.algebraic[var2], pybamm.Multiplication) + assert isinstance(model.algebraic[var2].children[0], pybamm.Scalar) + assert isinstance(model.algebraic[var2].children[1], pybamm.Variable) + assert model.algebraic[var2].children[0].value == 3 # initial conditions - self.assertIsInstance(model.initial_conditions[var1], pybamm.Scalar) - self.assertEqual(model.initial_conditions[var1].value, 2) + assert isinstance(model.initial_conditions[var1], pybamm.Scalar) + assert model.initial_conditions[var1].value == 2 # boundary conditions bc_key = next(iter(model.boundary_conditions.keys())) - self.assertIsInstance(bc_key, pybamm.Variable) + assert isinstance(bc_key, pybamm.Variable) bc_value = next(iter(model.boundary_conditions.values())) - self.assertIsInstance(bc_value["left"][0], pybamm.Scalar) - self.assertEqual(bc_value["left"][0].value, 3) - self.assertIsInstance(bc_value["right"][0], pybamm.Scalar) - self.assertEqual(bc_value["right"][0].value, 42) + assert isinstance(bc_value["left"][0], pybamm.Scalar) + assert bc_value["left"][0].value == 3 + assert isinstance(bc_value["right"][0], pybamm.Scalar) + assert bc_value["right"][0].value == 42 # variables - self.assertEqual(model.variables["var1"], var1) - self.assertIsInstance(model.variables["grad_var1"], pybamm.Gradient) - self.assertIsInstance(model.variables["grad_var1"].children[0], pybamm.Variable) - self.assertEqual( - model.variables["d_var1"], (pybamm.Scalar(42, name="d") * var1) - ) - self.assertIsInstance(model.variables["d_var1"].children[0], pybamm.Scalar) - self.assertIsInstance(model.variables["d_var1"].children[1], pybamm.Variable) + assert model.variables["var1"] == var1 + assert isinstance(model.variables["grad_var1"], pybamm.Gradient) + assert isinstance(model.variables["grad_var1"].children[0], pybamm.Variable) + assert model.variables["d_var1"] == (pybamm.Scalar(42, name="d") * var1) + assert isinstance(model.variables["d_var1"].children[0], pybamm.Scalar) + assert isinstance(model.variables["d_var1"].children[1], pybamm.Variable) # bad boundary conditions model = pybamm.BaseModel() model.algebraic = {var1: var1} x = pybamm.Parameter("x") model.boundary_conditions = {var1: {"left": (x, "Dirichlet")}} - with self.assertRaises(KeyError): + with pytest.raises(KeyError): parameter_values.process_model(model) def test_inplace(self): @@ -1006,16 +988,16 @@ def test_inplace(self): new_model = param.process_model(model, inplace=False) V = model.variables["Voltage [V]"] - self.assertTrue(V.has_symbol_of_classes(pybamm.Parameter)) + assert V.has_symbol_of_classes(pybamm.Parameter) V = new_model.variables["Voltage [V]"] - self.assertFalse(V.has_symbol_of_classes(pybamm.Parameter)) + assert not V.has_symbol_of_classes(pybamm.Parameter) def test_process_empty_model(self): model = pybamm.BaseModel() parameter_values = pybamm.ParameterValues({"a": 1, "b": 2, "c": 3, "d": 42}) - with self.assertRaisesRegex( - pybamm.ModelError, "Cannot process parameters for empty model" + with pytest.raises( + pybamm.ModelError, match="Cannot process parameters for empty model" ): parameter_values.process_model(model) @@ -1024,15 +1006,15 @@ def test_evaluate(self): a = pybamm.Parameter("a") b = pybamm.Parameter("b") c = pybamm.Parameter("c") - self.assertEqual(parameter_values.evaluate(a), 1) - self.assertEqual(parameter_values.evaluate(a + (b * c)), 7) + assert parameter_values.evaluate(a) == 1 + assert parameter_values.evaluate(a + (b * c)) == 7 d = pybamm.Parameter("a") + pybamm.Parameter("b") * pybamm.Array([4, 5]) np.testing.assert_array_equal( parameter_values.evaluate(d), np.array([9, 11])[:, np.newaxis] ) y = pybamm.StateVector(slice(0, 1)) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): parameter_values.evaluate(y) def test_exchange_current_density_plating(self): @@ -1042,18 +1024,8 @@ def test_exchange_current_density_plating(self): param = pybamm.Parameter( "Exchange-current density for lithium metal electrode [A.m-2]" ) - with self.assertRaisesRegex( + with pytest.raises( KeyError, - "referring to the reaction at the surface of a lithium metal electrode", + match="referring to the reaction at the surface of a lithium metal electrode", ): parameter_values.evaluate(param) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_parameters/test_process_parameter_data.py b/tests/unit/test_parameters/test_process_parameter_data.py index 3230f374f2..9352894c5c 100644 --- a/tests/unit/test_parameters/test_process_parameter_data.py +++ b/tests/unit/test_parameters/test_process_parameter_data.py @@ -7,62 +7,52 @@ import numpy as np import pybamm -import unittest +import pytest -class TestProcessParameterData(unittest.TestCase): +class TestProcessParameterData: def test_process_1D_data(self): name = "lico2_ocv_example" path = os.path.abspath(os.path.dirname(__file__)) processed = pybamm.parameters.process_1D_data(name, path) - self.assertEqual(processed[0], name) - self.assertIsInstance(processed[1], tuple) - self.assertIsInstance(processed[1][0][0], np.ndarray) - self.assertIsInstance(processed[1][1], np.ndarray) + assert processed[0] == name + assert isinstance(processed[1], tuple) + assert isinstance(processed[1][0][0], np.ndarray) + assert isinstance(processed[1][1], np.ndarray) def test_process_2D_data(self): name = "lico2_diffusivity_Dualfoil1998_2D" path = os.path.abspath(os.path.dirname(__file__)) processed = pybamm.parameters.process_2D_data(name, path) - self.assertEqual(processed[0], name) - self.assertIsInstance(processed[1], tuple) - self.assertIsInstance(processed[1][0][0], np.ndarray) - self.assertIsInstance(processed[1][0][1], np.ndarray) - self.assertIsInstance(processed[1][1], np.ndarray) + assert processed[0] == name + assert isinstance(processed[1], tuple) + assert isinstance(processed[1][0][0], np.ndarray) + assert isinstance(processed[1][0][1], np.ndarray) + assert isinstance(processed[1][1], np.ndarray) def test_process_2D_data_csv(self): name = "data_for_testing_2D" path = os.path.abspath(os.path.dirname(__file__)) processed = pybamm.parameters.process_2D_data_csv(name, path) - self.assertEqual(processed[0], name) - self.assertIsInstance(processed[1], tuple) - self.assertIsInstance(processed[1][0][0], np.ndarray) - self.assertIsInstance(processed[1][0][1], np.ndarray) - self.assertIsInstance(processed[1][1], np.ndarray) + assert processed[0] == name + assert isinstance(processed[1], tuple) + assert isinstance(processed[1][0][0], np.ndarray) + assert isinstance(processed[1][0][1], np.ndarray) + assert isinstance(processed[1][1], np.ndarray) def test_process_3D_data_csv(self): name = "data_for_testing_3D" path = os.path.abspath(os.path.dirname(__file__)) processed = pybamm.parameters.process_3D_data_csv(name, path) - self.assertEqual(processed[0], name) - self.assertIsInstance(processed[1], tuple) - self.assertIsInstance(processed[1][0][0], np.ndarray) - self.assertIsInstance(processed[1][0][1], np.ndarray) - self.assertIsInstance(processed[1][0][2], np.ndarray) - self.assertIsInstance(processed[1][1], np.ndarray) + assert processed[0] == name + assert isinstance(processed[1], tuple) + assert isinstance(processed[1][0][0], np.ndarray) + assert isinstance(processed[1][0][1], np.ndarray) + assert isinstance(processed[1][0][2], np.ndarray) + assert isinstance(processed[1][1], np.ndarray) def test_error(self): - with self.assertRaisesRegex(FileNotFoundError, "Could not find file"): + with pytest.raises(FileNotFoundError, match="Could not find file"): pybamm.parameters.process_1D_data("not_a_real_file", "not_a_real_path") - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_plotting/test_quick_plot.py b/tests/unit/test_plotting/test_quick_plot.py index eb6c0607e3..d5d994117d 100644 --- a/tests/unit/test_plotting/test_quick_plot.py +++ b/tests/unit/test_plotting/test_quick_plot.py @@ -1,12 +1,12 @@ import os import pybamm -import unittest +import pytest import numpy as np from tempfile import TemporaryDirectory -class TestQuickPlot(unittest.TestCase): +class TestQuickPlot: def test_simple_ode_model(self): model = pybamm.lithium_ion.BaseModel(name="Simple ODE Model") @@ -77,11 +77,11 @@ def test_simple_ode_model(self): # update the axis new_axis = [0, 0.5, 0, 1] quick_plot.axis_limits.update({("a",): new_axis}) - self.assertEqual(quick_plot.axis_limits[("a",)], new_axis) + assert quick_plot.axis_limits[("a",)] == new_axis # and now reset them quick_plot.reset_axis() - self.assertNotEqual(quick_plot.axis_limits[("a",)], new_axis) + assert quick_plot.axis_limits[("a",)] != new_axis # check dynamic plot loads quick_plot.dynamic_plot(show_plot=False) @@ -90,7 +90,7 @@ def test_simple_ode_model(self): # Test with different output variables quick_plot = pybamm.QuickPlot(solution, ["b broadcasted"]) - self.assertEqual(len(quick_plot.axis_limits), 1) + assert len(quick_plot.axis_limits) == 1 quick_plot.plot(0) quick_plot = pybamm.QuickPlot( @@ -103,18 +103,18 @@ def test_simple_ode_model(self): "c broadcasted positive electrode", ], ) - self.assertEqual(len(quick_plot.axis_limits), 5) + assert len(quick_plot.axis_limits) == 5 quick_plot.plot(0) # update the axis new_axis = [0, 0.5, 0, 1] var_key = ("c broadcasted",) quick_plot.axis_limits.update({var_key: new_axis}) - self.assertEqual(quick_plot.axis_limits[var_key], new_axis) + assert quick_plot.axis_limits[var_key] == new_axis # and now reset them quick_plot.reset_axis() - self.assertNotEqual(quick_plot.axis_limits[var_key], new_axis) + assert quick_plot.axis_limits[var_key] != new_axis # check dynamic plot loads quick_plot.dynamic_plot(show_plot=False) @@ -135,19 +135,19 @@ def test_simple_ode_model(self): labels=["sol 1", "sol 2"], n_rows=2, ) - self.assertEqual(quick_plot.colors, ["r", "g", "b"]) - self.assertEqual(quick_plot.linestyles, ["-", "--"]) - self.assertEqual(quick_plot.figsize, (1, 2)) - self.assertEqual(quick_plot.labels, ["sol 1", "sol 2"]) - self.assertEqual(quick_plot.n_rows, 2) - self.assertEqual(quick_plot.n_cols, 1) + assert quick_plot.colors == ["r", "g", "b"] + assert quick_plot.linestyles == ["-", "--"] + assert quick_plot.figsize == (1, 2) + assert quick_plot.labels == ["sol 1", "sol 2"] + assert quick_plot.n_rows == 2 + assert quick_plot.n_cols == 1 # Test different time units quick_plot = pybamm.QuickPlot(solution, ["a"]) - self.assertEqual(quick_plot.time_scaling_factor, 1) + assert quick_plot.time_scaling_factor == 1 quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="seconds") quick_plot.plot(0) - self.assertEqual(quick_plot.time_scaling_factor, 1) + assert quick_plot.time_scaling_factor == 1 np.testing.assert_array_almost_equal( quick_plot.plots[("a",)][0][0].get_xdata(), t_eval ) @@ -156,7 +156,7 @@ def test_simple_ode_model(self): ) quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="minutes") quick_plot.plot(0) - self.assertEqual(quick_plot.time_scaling_factor, 60) + assert quick_plot.time_scaling_factor == 60 np.testing.assert_array_almost_equal( quick_plot.plots[("a",)][0][0].get_xdata(), t_eval / 60 ) @@ -165,30 +165,30 @@ def test_simple_ode_model(self): ) quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="hours") quick_plot.plot(0) - self.assertEqual(quick_plot.time_scaling_factor, 3600) + assert quick_plot.time_scaling_factor == 3600 np.testing.assert_array_almost_equal( quick_plot.plots[("a",)][0][0].get_xdata(), t_eval / 3600 ) np.testing.assert_array_almost_equal( quick_plot.plots[("a",)][0][0].get_ydata(), 0.2 * t_eval ) - with self.assertRaisesRegex(ValueError, "time unit"): + with pytest.raises(ValueError, match="time unit"): pybamm.QuickPlot(solution, ["a"], time_unit="bad unit") # long solution defaults to hours instead of seconds solution_long = solver.solve(model, np.linspace(0, 1e5)) quick_plot = pybamm.QuickPlot(solution_long, ["a"]) - self.assertEqual(quick_plot.time_scaling_factor, 3600) + assert quick_plot.time_scaling_factor == 3600 # Test different spatial units quick_plot = pybamm.QuickPlot(solution, ["a"]) - self.assertEqual(quick_plot.spatial_unit, r"$\mu$m") + assert quick_plot.spatial_unit == r"$\mu$m" quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="m") - self.assertEqual(quick_plot.spatial_unit, "m") + assert quick_plot.spatial_unit == "m" quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="mm") - self.assertEqual(quick_plot.spatial_unit, "mm") + assert quick_plot.spatial_unit == "mm" quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="um") - self.assertEqual(quick_plot.spatial_unit, r"$\mu$m") - with self.assertRaisesRegex(ValueError, "spatial unit"): + assert quick_plot.spatial_unit == r"$\mu$m" + with pytest.raises(ValueError, match="spatial unit"): pybamm.QuickPlot(solution, ["a"], spatial_unit="bad unit") # Test 2D variables @@ -197,24 +197,25 @@ def test_simple_ode_model(self): quick_plot.dynamic_plot(show_plot=False) quick_plot.slider_update(0.01) - with self.assertRaisesRegex(NotImplementedError, "Cannot plot 2D variables"): + with pytest.raises(NotImplementedError, match="Cannot plot 2D variables"): pybamm.QuickPlot([solution, solution], ["2D variable"]) # Test different variable limits quick_plot = pybamm.QuickPlot( solution, ["a", ["c broadcasted", "c broadcasted"]], variable_limits="tight" ) - self.assertEqual(quick_plot.axis_limits[("a",)][2:], [None, None]) - self.assertEqual( - quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:], [None, None] - ) + assert quick_plot.axis_limits[("a",)][2:] == [None, None] + assert quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:] == [ + None, + None, + ] quick_plot.plot(0) quick_plot.slider_update(1) quick_plot = pybamm.QuickPlot( solution, ["2D variable"], variable_limits="tight" ) - self.assertEqual(quick_plot.variable_limits[("2D variable",)], (None, None)) + assert quick_plot.variable_limits[("2D variable",)] == (None, None) quick_plot.plot(0) quick_plot.slider_update(1) @@ -223,41 +224,37 @@ def test_simple_ode_model(self): ["a", ["c broadcasted", "c broadcasted"]], variable_limits={"a": [1, 2], ("c broadcasted", "c broadcasted"): [3, 4]}, ) - self.assertEqual(quick_plot.axis_limits[("a",)][2:], [1, 2]) - self.assertEqual( - quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:], [3, 4] - ) + assert quick_plot.axis_limits[("a",)][2:] == [1, 2] + assert quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:] == [3, 4] quick_plot.plot(0) quick_plot.slider_update(1) quick_plot = pybamm.QuickPlot( solution, ["a", "b broadcasted"], variable_limits={"a": "tight"} ) - self.assertEqual(quick_plot.axis_limits[("a",)][2:], [None, None]) - self.assertNotEqual( - quick_plot.axis_limits[("b broadcasted",)][2:], [None, None] - ) + assert quick_plot.axis_limits[("a",)][2:] == [None, None] + assert quick_plot.axis_limits[("b broadcasted",)][2:] != [None, None] quick_plot.plot(0) quick_plot.slider_update(1) - with self.assertRaisesRegex( - TypeError, "variable_limits must be 'fixed', 'tight', or a dict" + with pytest.raises( + TypeError, match="variable_limits must be 'fixed', 'tight', or a dict" ): pybamm.QuickPlot( solution, ["a", "b broadcasted"], variable_limits="bad variable limits" ) # Test errors - with self.assertRaisesRegex(ValueError, "Mismatching variable domains"): + with pytest.raises(ValueError, match="Mismatching variable domains"): pybamm.QuickPlot(solution, [["a", "b broadcasted"]]) - with self.assertRaisesRegex(ValueError, "labels"): + with pytest.raises(ValueError, match="labels"): pybamm.QuickPlot( [solution, solution], ["a"], labels=["sol 1", "sol 2", "sol 3"] ) # No variable can be NaN - with self.assertRaisesRegex( - ValueError, "All-NaN variable 'NaN variable' provided" + with pytest.raises( + ValueError, match="All-NaN variable 'NaN variable' provided" ): pybamm.QuickPlot(solution, ["NaN variable"]) @@ -269,7 +266,7 @@ def test_plot_with_different_models(self): model.rhs = {a: pybamm.Scalar(0)} model.initial_conditions = {a: pybamm.Scalar(0)} solution = pybamm.CasadiSolver("fast").solve(model, [0, 1]) - with self.assertRaisesRegex(ValueError, "No default output variables"): + with pytest.raises(ValueError, match="No default output variables"): pybamm.QuickPlot(solution) def test_spm_simulation(self): @@ -462,17 +459,17 @@ def test_plot_2plus1D_spm(self): ][1] np.testing.assert_array_almost_equal(qp_data.T, phi_n[:, :, -1]) - with self.assertRaisesRegex(NotImplementedError, "Shape not recognized for"): + with pytest.raises(NotImplementedError, match="Shape not recognized for"): pybamm.QuickPlot(solution, ["Negative particle concentration [mol.m-3]"]) pybamm.close_plots() def test_invalid_input_type_failure(self): - with self.assertRaisesRegex(TypeError, "Solutions must be"): + with pytest.raises(TypeError, match="Solutions must be"): pybamm.QuickPlot(1) def test_empty_list_failure(self): - with self.assertRaisesRegex(TypeError, "QuickPlot requires at least 1"): + with pytest.raises(TypeError, match="QuickPlot requires at least 1"): pybamm.QuickPlot([]) def test_model_with_inputs(self): @@ -509,20 +506,10 @@ def test_model_with_inputs(self): pybamm.close_plots() -class TestQuickPlotAxes(unittest.TestCase): +class TestQuickPlotAxes: def test_quick_plot_axes(self): axes = pybamm.QuickPlotAxes() axes.add(("test 1", "test 2"), 1) - self.assertEqual(axes[0], 1) - self.assertEqual(axes.by_variable("test 1"), 1) - self.assertEqual(axes.by_variable("test 2"), 1) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() + assert axes[0] == 1 + assert axes.by_variable("test 1") == 1 + assert axes.by_variable("test 2") == 1 diff --git a/tests/unit/test_serialisation/test_serialisation.py b/tests/unit/test_serialisation/test_serialisation.py index a1286cad26..adf53b7b46 100644 --- a/tests/unit/test_serialisation/test_serialisation.py +++ b/tests/unit/test_serialisation/test_serialisation.py @@ -4,8 +4,7 @@ import json import os -import unittest -import unittest.mock as mock +import pytest from datetime import datetime import numpy as np import pybamm @@ -14,14 +13,14 @@ from pybamm.expression_tree.operations.serialise import Serialise -def scalar_var_dict(): +def scalar_var_dict(mocker): """variable, json pair for a pybamm.Scalar instance""" a = pybamm.Scalar(5) a_dict = { - "py/id": mock.ANY, + "py/id": mocker.ANY, "py/object": "pybamm.expression_tree.scalar.Scalar", "name": "5.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 5.0, "children": [], } @@ -29,7 +28,7 @@ def scalar_var_dict(): return a, a_dict -def mesh_var_dict(): +def mesh_var_dict(mocker): """mesh, json pair for a pybamm.Mesh instance""" r = pybamm.SpatialVariable( @@ -48,13 +47,13 @@ def mesh_var_dict(): mesh_json = { "py/object": "pybamm.meshes.meshes.Mesh", - "py/id": mock.ANY, + "py/id": mocker.ANY, "submesh_pts": {"negative particle": {"r": 20}}, "base_domains": ["negative particle"], "sub_meshes": { "negative particle": { "py/object": "pybamm.meshes.one_dimensional_submeshes.Uniform1DSubMesh", - "py/id": mock.ANY, + "py/id": mocker.ANY, "edges": [ 0.0, 0.05, @@ -86,7 +85,7 @@ def mesh_var_dict(): return mesh, mesh_json -class TestSerialiseModels(unittest.TestCase): +class TestSerialiseModels: def test_user_defined_model_recreaction(self): # Start with a base model model = pybamm.BaseModel() @@ -146,26 +145,26 @@ def test_user_defined_model_recreaction(self): os.remove("heat_equation.json") -class TestSerialise(unittest.TestCase): +class TestSerialise: # test the symbol encoder - def test_symbol_encoder_symbol(self): + def test_symbol_encoder_symbol(self, mocker): """test basic symbol encoder with & without children""" # without children - a, a_dict = scalar_var_dict() + a, a_dict = scalar_var_dict(mocker) a_ser_json = Serialise._SymbolEncoder().default(a) - self.assertEqual(a_ser_json, a_dict) + assert a_ser_json == a_dict # with children add = pybamm.Addition(2, 4) add_json = { - "py/id": mock.ANY, + "py/id": mocker.ANY, "py/object": "pybamm.expression_tree.binary_operators.Addition", "name": "+", - "id": mock.ANY, + "id": mocker.ANY, "domains": { "primary": [], "secondary": [], @@ -174,18 +173,18 @@ def test_symbol_encoder_symbol(self): }, "children": [ { - "py/id": mock.ANY, + "py/id": mocker.ANY, "py/object": "pybamm.expression_tree.scalar.Scalar", "name": "2.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 2.0, "children": [], }, { - "py/id": mock.ANY, + "py/id": mocker.ANY, "py/object": "pybamm.expression_tree.scalar.Scalar", "name": "4.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 4.0, "children": [], }, @@ -194,32 +193,32 @@ def test_symbol_encoder_symbol(self): add_ser_json = Serialise._SymbolEncoder().default(add) - self.assertEqual(add_ser_json, add_json) + assert add_ser_json == add_json - def test_symbol_encoder_explicitTimeIntegral(self): + def test_symbol_encoder_explicit_time_integral(self, mocker): """test symbol encoder with initial conditions""" expr = pybamm.ExplicitTimeIntegral(pybamm.Scalar(5), pybamm.Scalar(1)) expr_json = { "py/object": "pybamm.expression_tree.unary_operators.ExplicitTimeIntegral", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "explicit time integral", - "id": mock.ANY, + "id": mocker.ANY, "children": [ { "py/object": "pybamm.expression_tree.scalar.Scalar", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "5.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 5.0, "children": [], } ], "initial_condition": { "py/object": "pybamm.expression_tree.scalar.Scalar", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "1.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 1.0, "children": [], }, @@ -227,9 +226,9 @@ def test_symbol_encoder_explicitTimeIntegral(self): expr_ser_json = Serialise._SymbolEncoder().default(expr) - self.assertEqual(expr_json, expr_ser_json) + assert expr_json == expr_ser_json - def test_symbol_encoder_event(self): + def test_symbol_encoder_event(self, mocker): """test symbol encoder with event""" expression = pybamm.Scalar(1) @@ -237,32 +236,32 @@ def test_symbol_encoder_event(self): event_json = { "py/object": "pybamm.models.event.Event", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "my event", "event_type": ["EventType.TERMINATION", 0], "expression": { "py/object": "pybamm.expression_tree.scalar.Scalar", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "1.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 1.0, "children": [], }, } event_ser_json = Serialise._SymbolEncoder().default(event) - self.assertEqual(event_ser_json, event_json) + assert event_ser_json == event_json # test the mesh encoder - def test_mesh_encoder(self): - mesh, mesh_json = mesh_var_dict() + def test_mesh_encoder(self, mocker): + mesh, mesh_json = mesh_var_dict(mocker) # serialise mesh mesh_ser_json = Serialise._MeshEncoder().default(mesh) - self.assertEqual(mesh_ser_json, mesh_json) + assert mesh_ser_json == mesh_json - def test_deconstruct_pybamm_dicts(self): + def test_deconstruct_pybamm_dicts(self, mocker): """tests serialisation of dictionaries with pybamm classes as keys""" x = pybamm.SpatialVariable("x", "negative electrode") @@ -273,9 +272,9 @@ def test_deconstruct_pybamm_dicts(self): "rod": { "symbol_x": { "py/object": "pybamm.expression_tree.independent_variable.SpatialVariable", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "x", - "id": mock.ANY, + "id": mocker.ANY, "domains": { "primary": ["negative electrode"], "secondary": [], @@ -288,40 +287,40 @@ def test_deconstruct_pybamm_dicts(self): } } - self.assertEqual(Serialise()._deconstruct_pybamm_dicts(test_dict), ser_dict) + assert Serialise()._deconstruct_pybamm_dicts(test_dict) == ser_dict - def test_get_pybamm_class(self): + def test_get_pybamm_class(self, mocker): # symbol - _, scalar_dict = scalar_var_dict() + _, scalar_dict = scalar_var_dict(mocker) scalar_class = Serialise()._get_pybamm_class(scalar_dict) - self.assertIsInstance(scalar_class, pybamm.Scalar) + assert isinstance(scalar_class, pybamm.Scalar) # mesh - _, mesh_dict = mesh_var_dict() + _, mesh_dict = mesh_var_dict(mocker) mesh_class = Serialise()._get_pybamm_class(mesh_dict) - self.assertIsInstance(mesh_class, pybamm.Mesh) + assert isinstance(mesh_class, pybamm.Mesh) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): unrecognised_symbol = { - "py/id": mock.ANY, + "py/id": mocker.ANY, "py/object": "pybamm.expression_tree.scalar.Scale", "name": "5.0", - "id": mock.ANY, + "id": mocker.ANY, "value": 5.0, "children": [], } Serialise()._get_pybamm_class(unrecognised_symbol) - def test_reconstruct_symbol(self): - scalar, scalar_dict = scalar_var_dict() + def test_reconstruct_symbol(self, mocker): + scalar, scalar_dict = scalar_var_dict(mocker) new_scalar = Serialise()._reconstruct_symbol(scalar_dict) - self.assertEqual(new_scalar, scalar) + assert new_scalar == scalar def test_reconstruct_expression_tree(self): y = pybamm.StateVector(slice(0, 1)) @@ -395,10 +394,10 @@ def test_reconstruct_expression_tree(self): new_equation = Serialise()._reconstruct_expression_tree(equation_json) - self.assertEqual(new_equation, equation) + assert new_equation == equation - def test_reconstruct_mesh(self): - mesh, mesh_dict = mesh_var_dict() + def test_reconstruct_mesh(self, mocker): + mesh, mesh_dict = mesh_var_dict(mocker) new_mesh = Serialise()._reconstruct_mesh(mesh_dict) @@ -410,12 +409,12 @@ def test_reconstruct_mesh(self): ) # reconstructed meshes are only used for plotting, geometry not reconstructed. - with self.assertRaisesRegex( - AttributeError, "'Mesh' object has no attribute '_geometry'" + with pytest.raises( + AttributeError, match="'Mesh' object has no attribute '_geometry'" ): - self.assertEqual(new_mesh.geometry, mesh.geometry) + assert new_mesh.geometry == mesh.geometry - def test_reconstruct_pybamm_dict(self): + def test_reconstruct_pybamm_dict(self, mocker): x = pybamm.SpatialVariable("x", "negative electrode") test_dict = {"rod": {x: {"min": 0.0, "max": 2.0}}} @@ -424,9 +423,9 @@ def test_reconstruct_pybamm_dict(self): "rod": { "symbol_x": { "py/object": "pybamm.expression_tree.independent_variable.SpatialVariable", - "py/id": mock.ANY, + "py/id": mocker.ANY, "name": "x", - "id": mock.ANY, + "id": mocker.ANY, "domains": { "primary": ["negative electrode"], "secondary": [], @@ -441,13 +440,13 @@ def test_reconstruct_pybamm_dict(self): new_dict = Serialise()._reconstruct_pybamm_dict(ser_dict) - self.assertEqual(new_dict, test_dict) + assert new_dict == test_dict # test recreation if not passed a dict test_list = ["left", "right"] new_list = Serialise()._reconstruct_pybamm_dict(test_list) - self.assertEqual(test_list, new_list) + assert test_list == new_list def test_convert_options(self): options_dict = { @@ -462,7 +461,7 @@ def test_convert_options(self): "open-circuit potential": (("single", "current sigmoid"), "single"), } - self.assertEqual(Serialise()._convert_options(options_dict), options_result) + assert Serialise()._convert_options(options_dict) == options_result def test_save_load_model(self): model = pybamm.lithium_ion.SPM(name="test_spm") @@ -473,9 +472,9 @@ def test_save_load_model(self): mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) # test error if not discretised - with self.assertRaisesRegex( + with pytest.raises( NotImplementedError, - "PyBaMM can only serialise a discretised, ready-to-solve model", + match="PyBaMM can only serialise a discretised, ready-to-solve model", ): Serialise().save_model(model, filename="test_model") @@ -484,12 +483,12 @@ def test_save_load_model(self): # default save Serialise().save_model(model, filename="test_model") - self.assertTrue(os.path.exists("test_model.json")) + assert os.path.exists("test_model.json") # default save where filename isn't provided Serialise().save_model(model) filename = "test_spm_" + datetime.now().strftime("%Y_%m_%d-%p%I_%M") + ".json" - self.assertTrue(os.path.exists(filename)) + assert os.path.exists(filename) os.remove(filename) # default load @@ -500,9 +499,9 @@ def test_save_load_model(self): new_solution = new_solver.solve(new_model, [0, 3600]) # check an error is raised when plotting the solution - with self.assertRaisesRegex( + with pytest.raises( AttributeError, - "No variables to plot", + match="No variables to plot", ): new_solution.plot() @@ -519,7 +518,7 @@ def test_save_load_model(self): with open("test_model.json", "w") as f: json.dump(model_data, f) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): Serialise().load_model("test_model.json") os.remove("test_model.json") @@ -534,9 +533,9 @@ def test_save_experiment_model_error(self): sim = pybamm.Simulation(model, experiment=experiment) sim.solve() - with self.assertRaisesRegex( + with pytest.raises( NotImplementedError, - "Serialising models coupled to experiments is not yet supported.", + match="Serialising models coupled to experiments is not yet supported.", ): sim.save_model("spm_experiment", mesh=False, variables=False) @@ -591,13 +590,3 @@ def test_serialised_model_plotting(self): # check dynamic plot loads new_solution.plot(show_plot=False) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_simulation.py b/tests/unit/test_simulation.py index 744ea2457c..fc9fec9745 100644 --- a/tests/unit/test_simulation.py +++ b/tests/unit/test_simulation.py @@ -3,8 +3,6 @@ import pandas as pd import os -import sys -import unittest import uuid import pytest from tempfile import TemporaryDirectory @@ -12,7 +10,7 @@ from tests import no_internet_connection -class TestSimulation(unittest.TestCase): +class TestSimulation: def test_simple_model(self): model = pybamm.BaseModel() v = pybamm.Variable("v") @@ -27,49 +25,49 @@ def test_basic_ops(self): sim = pybamm.Simulation(model) # check that the model is unprocessed - self.assertEqual(sim._mesh, None) - self.assertEqual(sim._disc, None) + assert sim._mesh is None + assert sim._disc is None V = sim.model.variables["Voltage [V]"] - self.assertTrue(V.has_symbol_of_classes(pybamm.Parameter)) - self.assertFalse(V.has_symbol_of_classes(pybamm.Matrix)) + assert V.has_symbol_of_classes(pybamm.Parameter) + assert not V.has_symbol_of_classes(pybamm.Matrix) sim.set_parameters() - self.assertEqual(sim._mesh, None) - self.assertEqual(sim._disc, None) + assert sim._mesh is None + assert sim._disc is None V = sim.model_with_set_params.variables["Voltage [V]"] - self.assertFalse(V.has_symbol_of_classes(pybamm.Parameter)) - self.assertFalse(V.has_symbol_of_classes(pybamm.Matrix)) + assert not V.has_symbol_of_classes(pybamm.Parameter) + assert not V.has_symbol_of_classes(pybamm.Matrix) # Make sure model is unchanged - self.assertNotEqual(sim.model, model) + assert sim.model != model V = model.variables["Voltage [V]"] - self.assertTrue(V.has_symbol_of_classes(pybamm.Parameter)) - self.assertFalse(V.has_symbol_of_classes(pybamm.Matrix)) + assert V.has_symbol_of_classes(pybamm.Parameter) + assert not V.has_symbol_of_classes(pybamm.Matrix) - self.assertEqual(sim.submesh_types, model.default_submesh_types) - self.assertEqual(sim.var_pts, model.default_var_pts) - self.assertIsNone(sim.mesh) + assert sim.submesh_types == model.default_submesh_types + assert sim.var_pts == model.default_var_pts + assert sim.mesh is None for key in sim.spatial_methods.keys(): - self.assertEqual( - sim.spatial_methods[key].__class__, - model.default_spatial_methods[key].__class__, + assert ( + sim.spatial_methods[key].__class__ + == model.default_spatial_methods[key].__class__ ) sim.build() - self.assertFalse(sim._mesh is None) - self.assertFalse(sim._disc is None) + assert sim._mesh is not None + assert sim._disc is not None V = sim.built_model.variables["Voltage [V]"] - self.assertFalse(V.has_symbol_of_classes(pybamm.Parameter)) - self.assertTrue(V.has_symbol_of_classes(pybamm.Matrix)) + assert not V.has_symbol_of_classes(pybamm.Parameter) + assert V.has_symbol_of_classes(pybamm.Matrix) def test_solve(self): sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) sim.solve([0, 600]) - self.assertFalse(sim._solution is None) + assert sim._solution is not None for val in list(sim.built_model.rhs.values()): - self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + assert not val.has_symbol_of_classes(pybamm.Parameter) # skip test for scalar variables (e.g. discharge capacity) if val.size > 1: - self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) + assert val.has_symbol_of_classes(pybamm.Matrix) # test solve without check sim = pybamm.Simulation( @@ -77,15 +75,15 @@ def test_solve(self): ) sol = sim.solve(t_eval=[0, 600]) for val in list(sim.built_model.rhs.values()): - self.assertFalse(val.has_symbol_of_classes(pybamm.Parameter)) + assert not val.has_symbol_of_classes(pybamm.Parameter) # skip test for scalar variables (e.g. discharge capacity) if val.size > 1: - self.assertTrue(val.has_symbol_of_classes(pybamm.Matrix)) + assert val.has_symbol_of_classes(pybamm.Matrix) # Test options that are only available when simulating an experiment - with self.assertRaisesRegex(ValueError, "save_at_cycles"): + with pytest.raises(ValueError, match="save_at_cycles"): sim.solve(save_at_cycles=2) - with self.assertRaisesRegex(ValueError, "starting_solution"): + with pytest.raises(ValueError, match="starting_solution"): sim.solve(starting_solution=sol) def test_solve_remove_independent_variables_from_rhs(self): @@ -157,8 +155,8 @@ def test_set_crate(self): model = pybamm.lithium_ion.SPM() current_1C = model.default_parameter_values["Current function [A]"] sim = pybamm.Simulation(model, C_rate=2) - self.assertEqual(sim.parameter_values["Current function [A]"], 2 * current_1C) - self.assertEqual(sim.C_rate, 2) + assert sim.parameter_values["Current function [A]"] == 2 * current_1C + assert sim.C_rate == 2 def test_step(self): dt = 0.001 @@ -166,24 +164,24 @@ def test_step(self): sim = pybamm.Simulation(model) sim.step(dt) # 1 step stores first two points - self.assertEqual(sim.solution.y.full()[0, :].size, 2) + assert sim.solution.y.full()[0, :].size == 2 np.testing.assert_array_almost_equal(sim.solution.t, np.array([0, dt])) saved_sol = sim.solution sim.step(dt) # automatically append the next step - self.assertEqual(sim.solution.y.full()[0, :].size, 4) + assert sim.solution.y.full()[0, :].size == 4 np.testing.assert_array_almost_equal( sim.solution.t, np.array([0, dt, dt + 1e-9, 2 * dt]) ) sim.step(dt, save=False) # now only store the two end step points - self.assertEqual(sim.solution.y.full()[0, :].size, 2) + assert sim.solution.y.full()[0, :].size == 2 np.testing.assert_array_almost_equal( sim.solution.t, np.array([2 * dt + 1e-9, 3 * dt]) ) # Start from saved solution sim.step(dt, starting_solution=saved_sol) - self.assertEqual(sim.solution.y.full()[0, :].size, 4) + assert sim.solution.y.full()[0, :].size == 4 np.testing.assert_array_almost_equal( sim.solution.t, np.array([0, dt, dt + 1e-9, 2 * dt]) ) @@ -197,15 +195,15 @@ def test_solve_with_initial_soc(self): param = model.default_parameter_values sim = pybamm.Simulation(model, parameter_values=param) sim.solve(t_eval=[0, 600], initial_soc=1) - self.assertEqual(sim._built_initial_soc, 1) + assert sim._built_initial_soc == 1 sim.solve(t_eval=[0, 600], initial_soc=0.5) - self.assertEqual(sim._built_initial_soc, 0.5) + assert sim._built_initial_soc == 0.5 exp = pybamm.Experiment( [pybamm.step.string("Discharge at 1C until 3.6V", period="1 minute")] ) sim = pybamm.Simulation(model, parameter_values=param, experiment=exp) sim.solve(initial_soc=0.8) - self.assertEqual(sim._built_initial_soc, 0.8) + assert sim._built_initial_soc == 0.8 # test with drive cycle data_loader = pybamm.DataLoader() @@ -220,12 +218,12 @@ def test_solve_with_initial_soc(self): param["Current function [A]"] = current_interpolant sim = pybamm.Simulation(model, parameter_values=param) sim.solve(initial_soc=0.8) - self.assertEqual(sim._built_initial_soc, 0.8) + assert sim._built_initial_soc == 0.8 # Test that build works with initial_soc sim = pybamm.Simulation(model, parameter_values=param) sim.build(initial_soc=0.5) - self.assertEqual(sim._built_initial_soc, 0.5) + assert sim._built_initial_soc == 0.5 # Test that initial soc works with a relevant input parameter model = pybamm.lithium_ion.DFN() @@ -236,7 +234,7 @@ def test_solve_with_initial_soc(self): ) sim = pybamm.Simulation(model, parameter_values=param) sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": og_eps_p}) - self.assertEqual(sim._built_initial_soc, 0.8) + assert sim._built_initial_soc == 0.8 # test having an input parameter in the ocv function model = pybamm.lithium_ion.SPM() @@ -264,14 +262,14 @@ def ocv_with_parameter(sto): model = pybamm.lithium_ion.DFN(options) sim = pybamm.Simulation(model) sim.solve([0, 1], initial_soc=0.9) - self.assertEqual(sim._built_initial_soc, 0.9) + assert sim._built_initial_soc == 0.9 # Test whether initial_soc works with half cell (build) options = {"working electrode": "positive"} model = pybamm.lithium_ion.DFN(options) sim = pybamm.Simulation(model) sim.build(initial_soc=0.9) - self.assertEqual(sim._built_initial_soc, 0.9) + assert sim._built_initial_soc == 0.9 # Test whether initial_soc works with half cell when it is a voltage model = pybamm.lithium_ion.SPM({"working electrode": "positive"}) @@ -284,14 +282,14 @@ def ocv_with_parameter(sto): sim = pybamm.Simulation(model, parameter_values=parameter_values) sol = sim.solve([0, 1], initial_soc=f"{ucv} V") voltage = sol["Terminal voltage [V]"].entries - self.assertAlmostEqual(voltage[0], ucv, places=5) + assert voltage[0] == pytest.approx(ucv, abs=1e-05) # test with MSMR model = pybamm.lithium_ion.MSMR({"number of MSMR reactions": ("6", "4")}) param = pybamm.ParameterValues("MSMR_Example") sim = pybamm.Simulation(model, parameter_values=param) sim.build(initial_soc=0.5) - self.assertEqual(sim._built_initial_soc, 0.5) + assert sim._built_initial_soc == 0.5 def test_solve_with_initial_soc_with_input_param_in_ocv(self): # test having an input parameter in the ocv function @@ -314,7 +312,7 @@ def ocv_with_parameter(sto): model, parameter_values=parameter_values, experiment=experiment ) sim.solve([0, 3600], inputs={"a": 1}, initial_soc=0.8) - self.assertEqual(sim._built_initial_soc, 0.8) + assert sim._built_initial_soc == 0.8 def test_esoh_with_input_param(self): # Test that initial soc works with a relevant input parameter @@ -326,7 +324,7 @@ def test_esoh_with_input_param(self): ) sim = pybamm.Simulation(model, parameter_values=param) sim.solve(t_eval=[0, 1], initial_soc=0.8, inputs={"eps_p": original_eps_p}) - self.assertEqual(sim._built_initial_soc, 0.8) + assert sim._built_initial_soc == 0.8 def test_solve_with_inputs(self): model = pybamm.lithium_ion.SPM() @@ -347,17 +345,17 @@ def test_step_with_inputs(self): sim.step( dt, inputs={"Current function [A]": 1} ) # 1 step stores first two points - self.assertEqual(sim.solution.t.size, 2) - self.assertEqual(sim.solution.y.full()[0, :].size, 2) - self.assertEqual(sim.solution.t[0], 0) - self.assertEqual(sim.solution.t[1], dt) + assert sim.solution.t.size == 2 + assert sim.solution.y.full()[0, :].size == 2 + assert sim.solution.t[0] == 0 + assert sim.solution.t[1] == dt np.testing.assert_array_equal( sim.solution.all_inputs[0]["Current function [A]"], 1 ) sim.step( dt, inputs={"Current function [A]": 2} ) # automatically append the next step - self.assertEqual(sim.solution.y.full()[0, :].size, 4) + assert sim.solution.y.full()[0, :].size == 4 np.testing.assert_array_almost_equal( sim.solution.t, np.array([0, dt, dt + 1e-9, 2 * dt]) ) @@ -399,13 +397,13 @@ def oscillating(t): def f(t, x=x): return x + t - with self.assertRaises(ValueError): + with pytest.raises(ValueError): operating_mode(f) def g(t, y): return t - with self.assertRaises(TypeError): + with pytest.raises(TypeError): operating_mode(g) def test_save_load(self): @@ -418,13 +416,13 @@ def test_save_load(self): sim.save(test_name) sim_load = pybamm.load_sim(test_name) - self.assertEqual(sim.model.name, sim_load.model.name) + assert sim.model.name == sim_load.model.name # save after solving sim.solve([0, 600]) sim.save(test_name) sim_load = pybamm.load_sim(test_name) - self.assertEqual(sim.model.name, sim_load.model.name) + assert sim.model.name == sim_load.model.name # with python formats model.convert_to_format = None @@ -434,8 +432,9 @@ def test_save_load(self): model.convert_to_format = "python" sim = pybamm.Simulation(model) sim.solve([0, 600]) - with self.assertRaisesRegex( - NotImplementedError, "Cannot save simulation if model format is python" + with pytest.raises( + NotImplementedError, + match="Cannot save simulation if model format is python", ): sim.save(test_name) @@ -454,11 +453,11 @@ def test_load_param(self): os.remove(filename) raise excep - self.assertEqual( - "graphite_LGM50_electrolyte_exchange_current_density_Chen2020", - pkl_obj.parameter_values[ + assert ( + "graphite_LGM50_electrolyte_exchange_current_density_Chen2020" + == pkl_obj.parameter_values[ "Negative electrode exchange-current density [A.m-2]" - ].__name__, + ].__name__ ) os.remove(filename) @@ -474,7 +473,7 @@ def test_save_load_dae(self): sim.solve([0, 600]) sim.save(test_name) sim_load = pybamm.load_sim(test_name) - self.assertEqual(sim.model.name, sim_load.model.name) + assert sim.model.name == sim_load.model.name # with python format model.convert_to_format = None @@ -492,7 +491,7 @@ def test_save_load_dae(self): sim.solve([0, 600]) sim.save(test_name) sim_load = pybamm.load_sim(test_name) - self.assertEqual(sim.model.name, sim_load.model.name) + assert sim.model.name == sim_load.model.name def test_save_load_model(self): model = pybamm.lead_acid.LOQS({"surface form": "algebraic"}) @@ -500,7 +499,7 @@ def test_save_load_model(self): sim = pybamm.Simulation(model) # test exception if not discretised - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): sim.save_model("sim_save") # save after solving @@ -510,7 +509,7 @@ def test_save_load_model(self): # load model saved_model = pybamm.load_model("sim_save.json") - self.assertEqual(model.options, saved_model.options) + assert model.options == saved_model.options os.remove("sim_save.json") @@ -518,7 +517,7 @@ def test_plot(self): sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) # test exception if not solved - with self.assertRaises(ValueError): + with pytest.raises(ValueError): sim.plot() # now solve and plot @@ -529,8 +528,8 @@ def test_plot(self): def test_create_gif(self): with TemporaryDirectory() as dir_name: sim = pybamm.Simulation(pybamm.lithium_ion.SPM()) - with self.assertRaisesRegex( - ValueError, "The simulation has not been solved yet." + with pytest.raises( + ValueError, match="The simulation has not been solved yet." ): sim.create_gif() sim.solve(t_eval=[0, 10]) @@ -577,13 +576,13 @@ def test_drive_cycle_interpolant(self): # check warning raised if the largest gap in t_eval is bigger than the # smallest gap in the data - with self.assertWarns(pybamm.SolverWarning): + with pytest.warns(pybamm.SolverWarning): sim.solve(t_eval=np.linspace(0, 10, 3)) # check warning raised if t_eval doesnt contain time_data , but has a finer # resolution (can still solve, but good for users to know they dont have # the solution returned at the data points) - with self.assertWarns(pybamm.SolverWarning): + with pytest.warns(pybamm.SolverWarning): sim.solve(t_eval=np.linspace(0, time_data[-1], 800)) def test_discontinuous_current(self): @@ -604,20 +603,20 @@ def car_current(t): ) sim.solve([0, 3600]) current = sim.solution["Current [A]"] - self.assertEqual(current(0), 1) - self.assertEqual(current(1500), -0.5) - self.assertEqual(current(3000), 0.5) + assert current(0) == 1 + assert current(1500) == -0.5 + assert current(3000) == 0.5 def test_t_eval(self): model = pybamm.lithium_ion.SPM() sim = pybamm.Simulation(model) # test no t_eval - with self.assertRaisesRegex(pybamm.SolverError, "'t_eval' must be provided"): + with pytest.raises(pybamm.SolverError, match="'t_eval' must be provided"): sim.solve() # test t_eval list of length != 2 - with self.assertRaisesRegex(pybamm.SolverError, "'t_eval' can be provided"): + with pytest.raises(pybamm.SolverError, match="'t_eval' can be provided"): sim.solve(t_eval=[0, 1, 2]) # tets list gets turned into np.linspace(t0, tf, 100) @@ -633,11 +632,3 @@ def test_battery_model_with_input_height(self): inputs = {"Electrode height [m]": 0.2} sim = pybamm.Simulation(model=model, parameter_values=parameter_values) sim.solve(t_eval=t_eval, inputs=inputs) - - -if __name__ == "__main__": - print("Add -v for more debug output") - - if "-v" in sys.argv: - debug = True - unittest.main() diff --git a/tests/unit/test_solvers/test_algebraic_solver.py b/tests/unit/test_solvers/test_algebraic_solver.py index 6e8b3a3d80..89ca7d750c 100644 --- a/tests/unit/test_solvers/test_algebraic_solver.py +++ b/tests/unit/test_solvers/test_algebraic_solver.py @@ -3,24 +3,24 @@ # import pybamm -import unittest +import pytest import numpy as np from tests import get_discretisation_for_testing -class TestAlgebraicSolver(unittest.TestCase): +class TestAlgebraicSolver: def test_algebraic_solver_init(self): solver = pybamm.AlgebraicSolver( method="hybr", tol=1e-4, extra_options={"maxfev": 100} ) - self.assertEqual(solver.method, "hybr") - self.assertEqual(solver.extra_options, {"maxfev": 100}) - self.assertEqual(solver.tol, 1e-4) + assert solver.method == "hybr" + assert solver.extra_options == {"maxfev": 100} + assert solver.tol == 1e-4 solver.method = "krylov" - self.assertEqual(solver.method, "krylov") + assert solver.method == "krylov" solver.tol = 1e-5 - self.assertEqual(solver.tol, 1e-5) + assert solver.tol == 1e-5 def test_wrong_solver(self): # Create model @@ -31,9 +31,9 @@ def test_wrong_solver(self): # test errors solver = pybamm.AlgebraicSolver() - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Cannot use algebraic solver to solve model with time derivatives", + match="Cannot use algebraic solver to solve model with time derivatives", ): solver.solve(model) @@ -61,7 +61,7 @@ def algebraic_eval(self, t, y, inputs): # Relax options and see worse results solver = pybamm.AlgebraicSolver(extra_options={"ftol": 1}) solution = solver._integrate(model, np.array([0])) - self.assertNotEqual(solution.y, -2) + assert solution.y != -2 def test_root_find_fail(self): class Model(pybamm.BaseModel): @@ -81,15 +81,16 @@ def algebraic_eval(self, t, y, inputs): model = Model() solver = pybamm.AlgebraicSolver(method="hybr") - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Could not find acceptable solution: The iteration is not making", + match="Could not find acceptable solution: The iteration is not making", ): solver._integrate(model, np.array([0])) solver = pybamm.AlgebraicSolver() - with self.assertRaisesRegex( - pybamm.SolverError, "Could not find acceptable solution: solver terminated" + with pytest.raises( + pybamm.SolverError, + match="Could not find acceptable solution: solver terminated", ): solver._integrate(model, np.array([0])) @@ -303,13 +304,3 @@ def test_solve_with_input(self): solver = pybamm.AlgebraicSolver() solution = solver.solve(model, np.linspace(0, 1, 10), inputs={"value": 7}) np.testing.assert_array_equal(solution.y, -7) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_base_solver.py b/tests/unit/test_solvers/test_base_solver.py index 9a1e87acec..6753513e72 100644 --- a/tests/unit/test_solvers/test_base_solver.py +++ b/tests/unit/test_solvers/test_base_solver.py @@ -2,39 +2,38 @@ # Tests for the Base Solver class # +import pytest import casadi import pybamm import numpy as np from scipy.sparse import csr_matrix -import unittest - -class TestBaseSolver(unittest.TestCase): +class TestBaseSolver: def test_base_solver_init(self): solver = pybamm.BaseSolver(rtol=1e-2, atol=1e-4) - self.assertEqual(solver.rtol, 1e-2) - self.assertEqual(solver.atol, 1e-4) + assert solver.rtol == 1e-2 + assert solver.atol == 1e-4 solver.rtol = 1e-5 - self.assertEqual(solver.rtol, 1e-5) + assert solver.rtol == 1e-5 solver.rtol = 1e-7 - self.assertEqual(solver.rtol, 1e-7) + assert solver.rtol == 1e-7 def test_root_method_init(self): solver = pybamm.BaseSolver(root_method="casadi") - self.assertIsInstance(solver.root_method, pybamm.CasadiAlgebraicSolver) + assert isinstance(solver.root_method, pybamm.CasadiAlgebraicSolver) solver = pybamm.BaseSolver(root_method="lm") - self.assertIsInstance(solver.root_method, pybamm.AlgebraicSolver) - self.assertEqual(solver.root_method.method, "lm") + assert isinstance(solver.root_method, pybamm.AlgebraicSolver) + assert solver.root_method.method == "lm" root_solver = pybamm.AlgebraicSolver() solver = pybamm.BaseSolver(root_method=root_solver) - self.assertEqual(solver.root_method, root_solver) + assert solver.root_method == root_solver - with self.assertRaisesRegex( - pybamm.SolverError, "Root method must be an algebraic solver" + with pytest.raises( + pybamm.SolverError, match="Root method must be an algebraic solver" ): pybamm.BaseSolver(root_method=pybamm.ScipySolver()) @@ -42,9 +41,9 @@ def test_step_or_solve_empty_model(self): model = pybamm.BaseModel() solver = pybamm.BaseSolver() error = "Cannot simulate an empty model" - with self.assertRaisesRegex(pybamm.ModelError, error): + with pytest.raises(pybamm.ModelError, match=error): solver.step(None, model, None) - with self.assertRaisesRegex(pybamm.ModelError, error): + with pytest.raises(pybamm.ModelError, match=error): solver.solve(model, None) def test_t_eval_none(self): @@ -56,7 +55,7 @@ def test_t_eval_none(self): disc.process_model(model) solver = pybamm.BaseSolver() - with self.assertRaisesRegex(ValueError, "t_eval cannot be None"): + with pytest.raises(ValueError, match="t_eval cannot be None"): solver.solve(model, None) def test_nonmonotonic_teval(self): @@ -64,29 +63,29 @@ def test_nonmonotonic_teval(self): model = pybamm.BaseModel() a = pybamm.Scalar(0) model.rhs = {a: a} - with self.assertRaisesRegex( - pybamm.SolverError, "t_eval must increase monotonically" + with pytest.raises( + pybamm.SolverError, match="t_eval must increase monotonically" ): solver.solve(model, np.array([1, 2, 3, 2])) # Check stepping with step size too small dt = -1e-9 - with self.assertRaisesRegex(pybamm.SolverError, "Step time must be >0"): + with pytest.raises(pybamm.SolverError, match="Step time must be >0"): solver.step(None, model, dt) # Checking if array t_eval lies within range dt = 2 t_eval = np.array([0, 1]) - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Elements inside array t_eval must lie in the closed interval 0 to dt", + match="Elements inside array t_eval must lie in the closed interval 0 to dt", ): solver.step(None, model, dt, t_eval=t_eval) t_eval = np.array([1, dt]) - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Elements inside array t_eval must lie in the closed interval 0 to dt", + match="Elements inside array t_eval must lie in the closed interval 0 to dt", ): solver.step(None, model, dt, t_eval=t_eval) @@ -96,8 +95,8 @@ def test_solution_time_length_fail(self): model.variables = {"v": v} solver = pybamm.DummySolver() t_eval = np.array([0]) - with self.assertRaisesRegex( - pybamm.SolverError, "Solution time vector has length 1" + with pytest.raises( + pybamm.SolverError, match="Solution time vector has length 1" ): solver.solve(model, t_eval) @@ -107,9 +106,7 @@ def test_block_symbolic_inputs(self): a = pybamm.Variable("a") p = pybamm.InputParameter("p") model.rhs = {a: a * p} - with self.assertRaisesRegex( - pybamm.SolverError, "No value provided for input 'p'" - ): + with pytest.raises(pybamm.SolverError, match="No value provided for input 'p'"): solver.solve(model, np.array([1, 2, 3])) def test_ode_solver_fail_with_dae(self): @@ -118,7 +115,7 @@ def test_ode_solver_fail_with_dae(self): model.algebraic = {a: a} model.concatenated_initial_conditions = pybamm.Scalar(0) solver = pybamm.ScipySolver() - with self.assertRaisesRegex(pybamm.SolverError, "Cannot use ODE solver"): + with pytest.raises(pybamm.SolverError, match="Cannot use ODE solver"): solver.set_up(model) def test_find_consistent_initialization(self): @@ -231,20 +228,22 @@ def algebraic_eval(self, t, y, inputs): solver = pybamm.BaseSolver(root_method="hybr") - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Could not find acceptable solution: The iteration is not making", + match="Could not find acceptable solution: The iteration is not making", ): solver.calculate_consistent_state(Model()) solver = pybamm.BaseSolver(root_method="lm") - with self.assertRaisesRegex( - pybamm.SolverError, "Could not find acceptable solution: solver terminated" + with pytest.raises( + pybamm.SolverError, + match="Could not find acceptable solution: solver terminated", ): solver.calculate_consistent_state(Model()) # with casadi solver = pybamm.BaseSolver(root_method="casadi") - with self.assertRaisesRegex( - pybamm.SolverError, "Could not find acceptable solution: Error in Function" + with pytest.raises( + pybamm.SolverError, + match="Could not find acceptable solution: Error in Function", ): solver.calculate_consistent_state(Model()) @@ -256,9 +255,9 @@ def test_discretise_model(self): model.initial_conditions = {v: 1} solver = pybamm.BaseSolver() - self.assertFalse(model.is_discretised) + assert not model.is_discretised solver.set_up(model, {}) - self.assertTrue(model.is_discretised) + assert model.is_discretised # 1D model cannot be automatically discretised model = pybamm.BaseModel() @@ -266,8 +265,8 @@ def test_discretise_model(self): model.rhs = {v: -1} model.initial_conditions = {v: 1} - with self.assertRaisesRegex( - pybamm.DiscretisationError, "Cannot automatically discretise model" + with pytest.raises( + pybamm.DiscretisationError, match="Cannot automatically discretise model" ): solver.set_up(model, {}) @@ -285,7 +284,7 @@ def test_convert_to_casadi_format(self): solver = pybamm.BaseSolver(root_method="casadi") pybamm.set_logging_level("ERROR") solver.set_up(model, {}) - self.assertEqual(model.convert_to_format, "casadi") + assert model.convert_to_format == "casadi" pybamm.set_logging_level("WARNING") def test_inputs_step(self): @@ -301,7 +300,7 @@ def test_inputs_step(self): sol = solver.step( old_solution=None, model=model, dt=1.0, inputs={input_key: interp} ) - self.assertFalse(input_key in sol.all_inputs[0]) + assert input_key not in sol.all_inputs[0] def test_extrapolation_warnings(self): # Make sure the extrapolation warnings work @@ -326,10 +325,10 @@ def test_extrapolation_warnings(self): solver = pybamm.ScipySolver() solver.set_up(model) - with self.assertWarns(pybamm.SolverWarning): + with pytest.warns(pybamm.SolverWarning): solver.step(old_solution=None, model=model, dt=1.0) - with self.assertWarns(pybamm.SolverWarning): + with pytest.warns(pybamm.SolverWarning): solver.solve(model, t_eval=[0, 1]) def test_multiple_models_error(self): @@ -344,7 +343,7 @@ def test_multiple_models_error(self): solver = pybamm.ScipySolver() solver.solve(model, t_eval=[0, 1]) - with self.assertRaisesRegex(RuntimeError, "already been initialised"): + with pytest.raises(RuntimeError, match="already been initialised"): solver.solve(model2, t_eval=[0, 1]) def test_multiprocess_context(self): @@ -353,12 +352,16 @@ def test_multiprocess_context(self): assert solver.get_platform_context("Linux") == "fork" assert solver.get_platform_context("Darwin") == "fork" - @unittest.skipIf(not pybamm.has_idaklu(), "idaklu solver is not installed") + @pytest.mark.skipif( + not pybamm.has_idaklu(), reason="idaklu solver is not installed" + ) def test_sensitivities(self): def exact_diff_a(y, a, b): return np.array([[y[0] ** 2 + 2 * a], [y[0]]]) - @unittest.skipIf(not pybamm.has_jax(), "jax or jaxlib is not installed") + @pytest.mark.skipif( + not pybamm.has_jax(), reason="jax or jaxlib is not installed" + ) def exact_diff_b(y, a, b): return np.array([[y[0]], [0]]) @@ -397,13 +400,3 @@ def exact_diff_b(y, a, b): np.testing.assert_allclose( sens_b, exact_diff_b(y, inputs["a"], inputs["b"]) ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_casadi_algebraic_solver.py b/tests/unit/test_solvers/test_casadi_algebraic_solver.py index b85f4292b9..b2dc92d25b 100644 --- a/tests/unit/test_solvers/test_casadi_algebraic_solver.py +++ b/tests/unit/test_solvers/test_casadi_algebraic_solver.py @@ -1,18 +1,18 @@ import casadi import pybamm -import unittest +import pytest import numpy as np from scipy.optimize import least_squares import tests -class TestCasadiAlgebraicSolver(unittest.TestCase): +class TestCasadiAlgebraicSolver: def test_algebraic_solver_init(self): solver = pybamm.CasadiAlgebraicSolver(tol=1e-4) - self.assertEqual(solver.tol, 1e-4) + assert solver.tol == 1e-4 solver.tol = 1e-5 - self.assertEqual(solver.tol, 1e-5) + assert solver.tol == 1e-5 def test_simple_root_find(self): # Simple system: a single algebraic equation @@ -65,13 +65,15 @@ def algebraic_eval(self, t, y, inputs): model = Model() solver = pybamm.CasadiAlgebraicSolver() - with self.assertRaisesRegex( - pybamm.SolverError, "Could not find acceptable solution: Error in Function" + with pytest.raises( + pybamm.SolverError, + match="Could not find acceptable solution: Error in Function", ): solver._integrate(model, np.array([0]), {}) solver = pybamm.CasadiAlgebraicSolver(extra_options={"error_on_fail": False}) - with self.assertRaisesRegex( - pybamm.SolverError, "Could not find acceptable solution: solver terminated" + with pytest.raises( + pybamm.SolverError, + match="Could not find acceptable solution: solver terminated", ): solver._integrate(model, np.array([0]), {}) @@ -91,9 +93,9 @@ def algebraic_eval(self, t, y, inputs): return y**0.5 model = NaNModel() - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Could not find acceptable solution: solver returned NaNs", + match="Could not find acceptable solution: solver returned NaNs", ): solver._integrate(model, np.array([0]), {}) @@ -170,7 +172,7 @@ def test_solve_with_input(self): np.testing.assert_array_equal(solution.y, -7) -class TestCasadiAlgebraicSolverSensitivity(unittest.TestCase): +class TestCasadiAlgebraicSolverSensitivity: def test_solve_with_symbolic_input(self): # Simple system: a single algebraic equation var = pybamm.Variable("var") @@ -344,13 +346,3 @@ def objective(x): # without Jacobian lsq_sol = least_squares(objective, [2, 2], method="lm") np.testing.assert_array_almost_equal(lsq_sol.x, [3, 3], decimal=3) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_casadi_solver.py b/tests/unit/test_solvers/test_casadi_solver.py index 76f5a6c9dc..3e1023c7d4 100644 --- a/tests/unit/test_solvers/test_casadi_solver.py +++ b/tests/unit/test_solvers/test_casadi_solver.py @@ -1,13 +1,13 @@ +import pytest import pybamm -import unittest import numpy as np from tests import get_mesh_for_testing, get_discretisation_for_testing from scipy.sparse import eye -class TestCasadiSolver(unittest.TestCase): +class TestCasadiSolver: def test_bad_mode(self): - with self.assertRaisesRegex(ValueError, "invalid mode"): + with pytest.raises(ValueError, match="invalid mode"): pybamm.CasadiSolver(mode="bad mode") def test_model_solver(self): @@ -102,7 +102,7 @@ def test_without_grid(self): # Safe mode, without grid (enforce events that won't be triggered) solver = pybamm.CasadiSolver(mode="safe without grid", rtol=1e-8, atol=1e-8) - with self.assertRaisesRegex(pybamm.SolverError, "Maximum number of decreased"): + with pytest.raises(pybamm.SolverError, match="Maximum number of decreased"): solver.solve(model, [0, 10]) def test_model_solver_python(self): @@ -143,9 +143,9 @@ def test_model_solver_failure(self): # Solution fails early but manages to take some steps so we return it anyway # Check that the final solution does indeed stop before t=20 t_eval = np.linspace(0, 20, 100) - with self.assertWarns(pybamm.SolverWarning): + with pytest.warns(pybamm.SolverWarning): solution = solver.solve(model_disc, t_eval) - self.assertLess(solution.t[-1], 20) + assert solution.t[-1] < 20 # Solve with failure at t=0 solver = pybamm.CasadiSolver( dt_max=1e-3, return_solution_if_failed_early=True, max_step_decrease_count=2 @@ -155,7 +155,7 @@ def test_model_solver_failure(self): t_eval = np.linspace(0, 20, 100) # This one should fail immediately and throw a `SolverError` # since no progress can be made from the first timestep - with self.assertRaisesRegex(pybamm.SolverError, "Maximum number of decreased"): + with pytest.raises(pybamm.SolverError, match="Maximum number of decreased"): solver.solve(model, t_eval) def test_model_solver_events(self): @@ -390,7 +390,7 @@ def test_model_solver_with_inputs(self): solver = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 10, 100) solution = solver.solve(model, t_eval, inputs={"rate": 0.1}) - self.assertLess(len(solution.t), len(t_eval)) + assert len(solution.t) < len(t_eval) np.testing.assert_allclose( solution.y.full()[0], np.exp(-0.1 * solution.t), rtol=1e-04 ) @@ -399,12 +399,12 @@ def test_model_solver_with_inputs(self): solver = pybamm.CasadiSolver(mode="safe without grid", rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 10, 100) solution = solver.solve(model, t_eval, inputs={"rate": 0.1}) - self.assertLess(len(solution.t), len(t_eval)) + assert len(solution.t) < len(t_eval) np.testing.assert_allclose( solution.y.full()[0], np.exp(-0.1 * solution.t), rtol=1e-04 ) solution = solver.solve(model, t_eval, inputs={"rate": 1.1}) - self.assertLess(len(solution.t), len(t_eval)) + assert len(solution.t) < len(t_eval) np.testing.assert_allclose( solution.y.full()[0], np.exp(-1.1 * solution.t), rtol=1e-04 ) @@ -482,8 +482,8 @@ def test_dae_solver_algebraic_model(self): solver = pybamm.CasadiSolver() t_eval = np.linspace(0, 1) - with self.assertRaisesRegex( - pybamm.SolverError, "Cannot use CasadiSolver to solve algebraic model" + with pytest.raises( + pybamm.SolverError, match="Cannot use CasadiSolver to solve algebraic model" ): solver.solve(model, t_eval) @@ -507,7 +507,7 @@ def func(var): solver = pybamm.CasadiSolver() t_eval = [0, 5] - with self.assertRaisesRegex(pybamm.SolverError, "interpolation bounds"): + with pytest.raises(pybamm.SolverError, match="interpolation bounds"): solver.solve(model, t_eval) def test_casadi_safe_no_termination(self): @@ -532,7 +532,7 @@ def test_casadi_safe_no_termination(self): solver = pybamm.CasadiSolver(mode="safe") solver.set_up(model) - with self.assertRaisesRegex(pybamm.SolverError, "interpolation bounds"): + with pytest.raises(pybamm.SolverError, match="interpolation bounds"): solver.solve(model, t_eval=[0, 1]) def test_modulo_non_smooth_events(self): @@ -581,7 +581,7 @@ def test_modulo_non_smooth_events(self): ) -class TestCasadiSolverODEsWithForwardSensitivityEquations(unittest.TestCase): +class TestCasadiSolverODEsWithForwardSensitivityEquations: def test_solve_sensitivity_scalar_var_scalar_input(self): # Create model model = pybamm.BaseModel() @@ -931,7 +931,7 @@ def test_solve_sensitivity_subset(self): solution.sensitivities["q"], (0.1 * solution.t)[:, np.newaxis], ) - self.assertTrue("r" not in solution.sensitivities) + assert "r" not in solution.sensitivities np.testing.assert_allclose( solution.sensitivities["all"], np.hstack( @@ -949,8 +949,8 @@ def test_solve_sensitivity_subset(self): calculate_sensitivities=["r"], ) np.testing.assert_allclose(solution.y[0], -1 + 0.2 * solution.t) - self.assertTrue("p" not in solution.sensitivities) - self.assertTrue("q" not in solution.sensitivities) + assert "p" not in solution.sensitivities + assert "q" not in solution.sensitivities np.testing.assert_allclose(solution.sensitivities["r"], 1) np.testing.assert_allclose( solution.sensitivities["all"], @@ -962,7 +962,7 @@ def test_solve_sensitivity_subset(self): ) -class TestCasadiSolverDAEsWithForwardSensitivityEquations(unittest.TestCase): +class TestCasadiSolverDAEsWithForwardSensitivityEquations: def test_solve_sensitivity_scalar_var_scalar_input(self): # Create model model = pybamm.BaseModel() @@ -1066,8 +1066,8 @@ def test_solve_sensitivity_subset(self): solution.sensitivities["q"][::2], (0.1 * solution.t)[:, np.newaxis], ) - self.assertTrue("r" not in solution.sensitivities) - self.assertTrue("s" not in solution.sensitivities) + assert "r" not in solution.sensitivities + assert "s" not in solution.sensitivities np.testing.assert_allclose( solution.sensitivities["all"], np.hstack( @@ -1096,18 +1096,8 @@ def test_solver_interpolation_warning(self): # Check for warning with t_interp t_eval = np.linspace(0, 1, 10) t_interp = t_eval - with self.assertWarns( + with pytest.warns( pybamm.SolverWarning, - msg=f"Explicit interpolation times not implemented for {solver.name}", + match=f"Explicit interpolation times not implemented for {solver.name}", ): solver.solve(model, t_eval, t_interp=t_interp) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_idaklu_solver.py b/tests/unit/test_solvers/test_idaklu_solver.py index 3f9dcf0508..32e289b3e0 100644 --- a/tests/unit/test_solvers/test_idaklu_solver.py +++ b/tests/unit/test_solvers/test_idaklu_solver.py @@ -4,7 +4,6 @@ from contextlib import redirect_stdout import io -import unittest import pytest import numpy as np @@ -13,8 +12,8 @@ @pytest.mark.cibw -@unittest.skipIf(not pybamm.has_idaklu(), "idaklu solver is not installed") -class TestIDAKLUSolver(unittest.TestCase): +@pytest.mark.skipif(not pybamm.has_idaklu(), reason="idaklu solver is not installed") +class TestIDAKLUSolver: def test_ida_roberts_klu(self): # this test implements a python version of the ida Roberts # example provided in sundials @@ -105,7 +104,7 @@ def test_model_events(self): ) # Check invalid atol type raises an error - with self.assertRaises(pybamm.SolverError): + with pytest.raises(pybamm.SolverError): solver._check_atol_type({"key": "value"}, []) # enforce events that won't be triggered @@ -136,7 +135,7 @@ def test_model_events(self): options={"jax_evaluator": "iree"} if form == "iree" else {}, ) solution = solver.solve(model_disc, t_eval, t_interp=t_interp) - self.assertLess(len(solution.t), len(t_interp)) + assert len(solution.t) < len(t_interp) np.testing.assert_array_almost_equal( solution.y[0], np.exp(0.1 * solution.t), @@ -344,7 +343,7 @@ def test_ida_roberts_klu_sensitivities(self): ) # should be no sensitivities calculated - with self.assertRaises(KeyError): + with pytest.raises(KeyError): print(sol.sensitivities["a"]) # now solve with sensitivities (this should cause set_up to be run again) @@ -566,7 +565,7 @@ def test_failures(self): solver = pybamm.IDAKLUSolver() t_eval = [0, 3] - with self.assertRaisesRegex(pybamm.SolverError, "KLU requires the Jacobian"): + with pytest.raises(pybamm.SolverError, match="KLU requires the Jacobian"): solver.solve(model, t_eval) model = pybamm.BaseModel() @@ -581,8 +580,8 @@ def test_failures(self): # will give solver error t_eval = [0, -3] - with self.assertRaisesRegex( - pybamm.SolverError, "t_eval must increase monotonically" + with pytest.raises( + pybamm.SolverError, match="t_eval must increase monotonically" ): solver.solve(model, t_eval) @@ -598,7 +597,7 @@ def test_failures(self): solver = pybamm.IDAKLUSolver() t_eval = [0, 3] - with self.assertRaisesRegex(pybamm.SolverError, "FAILURE IDA"): + with pytest.raises(pybamm.SolverError, match="FAILURE IDA"): solver.solve(model, t_eval) def test_dae_solver_algebraic_model(self): @@ -677,14 +676,14 @@ def test_setup_options(self): with redirect_stdout(f): solver.solve(model, t_eval, t_interp=t_interp) s = f.getvalue() - self.assertIn("Solver Stats", s) + assert "Solver Stats" in s solver = pybamm.IDAKLUSolver(options={"print_stats": False}) f = io.StringIO() with redirect_stdout(f): solver.solve(model, t_eval, t_interp=t_interp) s = f.getvalue() - self.assertEqual(len(s), 0) + assert len(s) == 0 # test everything else for jacobian in ["none", "dense", "sparse", "matrix-free", "garbage"]: @@ -731,7 +730,7 @@ def test_setup_options(self): soln = solver.solve(model, t_eval, t_interp=t_interp) np.testing.assert_array_almost_equal(soln.y, soln_base.y, 4) else: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): soln = solver.solve(model, t_eval, t_interp=t_interp) def test_solver_options(self): @@ -796,7 +795,7 @@ def test_solver_options(self): options = {option: options_fail[option]} solver = pybamm.IDAKLUSolver(options=options) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): solver.solve(model, t_eval) def test_with_output_variables(self): @@ -899,7 +898,7 @@ def construct_model(): # Check that the missing variables are not available in the solution for varname in inaccessible_vars: - with self.assertRaises(KeyError): + with pytest.raises(KeyError): sol[varname].data # Mock a 1D current collector and initialise (none in the model) @@ -1005,13 +1004,13 @@ def test_with_output_variables_and_sensitivities(self): def test_bad_jax_evaluator(self): model = pybamm.lithium_ion.DFN() model.convert_to_format = "jax" - with self.assertRaises(pybamm.SolverError): + with pytest.raises(pybamm.SolverError): pybamm.IDAKLUSolver(options={"jax_evaluator": "bad_evaluator"}) def test_bad_jax_evaluator_output_variables(self): model = pybamm.lithium_ion.DFN() model.convert_to_format = "jax" - with self.assertRaises(pybamm.SolverError): + with pytest.raises(pybamm.SolverError): pybamm.IDAKLUSolver( options={"jax_evaluator": "bad_evaluator"}, output_variables=["Terminal voltage [V]"], @@ -1027,7 +1026,7 @@ def test_with_output_variables_and_event_termination(self): solver=pybamm.IDAKLUSolver(output_variables=["Terminal voltage [V]"]), ) sol = sim.solve(np.linspace(0, 3600, 2)) - self.assertEqual(sol.termination, "event: Minimum voltage [V]") + assert sol.termination == "event: Minimum voltage [V]" # create an event that doesn't require the state vector eps_p = model.variables["Positive electrode porosity"] @@ -1045,7 +1044,7 @@ def test_with_output_variables_and_event_termination(self): solver=pybamm.IDAKLUSolver(output_variables=["Terminal voltage [V]"]), ) sol3 = sim3.solve(np.linspace(0, 3600, 2)) - self.assertEqual(sol3.termination, "event: Minimum voltage [V]") + assert sol3.termination == "event: Minimum voltage [V]" def test_simulation_period(self): model = pybamm.lithium_ion.DFN() @@ -1107,29 +1106,18 @@ def test_python_idaklu_deprecation_errors(self): ) if form == "python": - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Unsupported option for convert_to_format=python", + match="Unsupported option for convert_to_format=python", ): - with self.assertWarnsRegex( + with pytest.raises( DeprecationWarning, - "The python-idaklu solver has been deprecated.", + match="The python-idaklu solver has been deprecated.", ): _ = solver.solve(model, t_eval) elif form == "jax": - with self.assertRaisesRegex( + with pytest.raises( pybamm.SolverError, - "Unsupported evaluation engine for convert_to_format=jax", + match="Unsupported evaluation engine for convert_to_format=jax", ): _ = solver.solve(model, t_eval) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - - unittest.main() diff --git a/tests/unit/test_solvers/test_jax_bdf_solver.py b/tests/unit/test_solvers/test_jax_bdf_solver.py index e0064ae463..98eaed8e6a 100644 --- a/tests/unit/test_solvers/test_jax_bdf_solver.py +++ b/tests/unit/test_solvers/test_jax_bdf_solver.py @@ -1,16 +1,15 @@ +import pytest import pybamm -import unittest from tests import get_mesh_for_testing -import sys import numpy as np if pybamm.has_jax(): import jax -@unittest.skipIf(not pybamm.has_jax(), "jax or jaxlib is not installed") -class TestJaxBDFSolver(unittest.TestCase): +@pytest.mark.skipif(not pybamm.has_jax(), reason="jax or jaxlib is not installed") +class TestJaxBDFSolver: def test_solver_(self): # Trailing _ manipulates the random seed # Create model model = pybamm.BaseModel() @@ -113,7 +112,7 @@ def solve_bdf(rate): grad_solve_bdf = jax.jit(jax.grad(solve_bdf)) grad_bdf = grad_solve_bdf(rate) - self.assertAlmostEqual(grad_bdf, grad_num, places=3) + assert grad_bdf == pytest.approx(grad_num, abs=0.001) def test_mass_matrix_with_sensitivities(self): # Solve @@ -146,7 +145,7 @@ def solve_bdf(rate): grad_solve_bdf = jax.jit(jax.grad(solve_bdf)) grad_bdf = grad_solve_bdf(rate) - self.assertAlmostEqual(grad_bdf, grad_num, places=3) + assert grad_bdf == pytest.approx(grad_num, abs=0.001) def test_solver_with_inputs(self): # Create model @@ -176,12 +175,3 @@ def fun(y, t, inputs): ) np.testing.assert_allclose(y[:, 0].reshape(-1), np.exp(-0.1 * t_eval)) - - -if __name__ == "__main__": - print("Add -v for more debug output") - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_jax_solver.py b/tests/unit/test_solvers/test_jax_solver.py index b1c293c2f2..f7e5b8d3b6 100644 --- a/tests/unit/test_solvers/test_jax_solver.py +++ b/tests/unit/test_solvers/test_jax_solver.py @@ -1,16 +1,15 @@ +import pytest import pybamm -import unittest from tests import get_mesh_for_testing -import sys import numpy as np if pybamm.has_jax(): import jax -@unittest.skipIf(not pybamm.has_jax(), "jax or jaxlib is not installed") -class TestJaxSolver(unittest.TestCase): +@pytest.mark.skipif(not pybamm.has_jax(), reason="jax or jaxlib is not installed") +class TestJaxSolver: def test_model_solver(self): # Create model model = pybamm.BaseModel() @@ -38,10 +37,8 @@ def test_model_solver(self): ) # Test time - self.assertEqual( - solution.total_time, solution.solve_time + solution.set_up_time - ) - self.assertEqual(solution.termination, "final time") + assert solution.total_time == solution.solve_time + solution.set_up_time + assert solution.termination == "final time" second_solution = solver.solve(model, t_eval) @@ -76,10 +73,8 @@ def test_semi_explicit_model(self): np.testing.assert_allclose(solution.y[-1], 2 * soln, rtol=1e-7, atol=1e-7) # Test time - self.assertEqual( - solution.total_time, solution.solve_time + solution.set_up_time - ) - self.assertEqual(solution.termination, "final time") + assert solution.total_time == solution.solve_time + solution.set_up_time + assert solution.termination == "final time" second_solution = solver.solve(model, t_eval) np.testing.assert_array_equal(second_solution.y, solution.y) @@ -124,7 +119,7 @@ def solve_model(rate, solve=solve): grad_solve = jax.jit(jax.grad(solve_model)) grad = grad_solve(rate) - self.assertAlmostEqual(grad, grad_num, places=1) + assert grad == pytest.approx(grad_num, abs=0.1) def test_solver_only_works_with_jax(self): model = pybamm.BaseModel() @@ -144,7 +139,7 @@ def test_solver_only_works_with_jax(self): model.convert_to_format = convert_to_format solver = pybamm.JaxSolver() - with self.assertRaisesRegex(RuntimeError, "must be converted to JAX"): + with pytest.raises(RuntimeError, match="must be converted to JAX"): solver.solve(model, t_eval) def test_solver_doesnt_support_events(self): @@ -171,7 +166,7 @@ def test_solver_doesnt_support_events(self): # Solve solver = pybamm.JaxSolver() t_eval = np.linspace(0, 10, 100) - with self.assertRaisesRegex(RuntimeError, "Terminate events not supported"): + with pytest.raises(RuntimeError, match="Terminate events not supported"): solver.solve(model, t_eval) def test_model_solver_with_inputs(self): @@ -223,14 +218,14 @@ def test_get_solve(self): disc.process_model(model) # test that another method string gives error - with self.assertRaises(ValueError): + with pytest.raises(ValueError): solver = pybamm.JaxSolver(method="not_real") # Solve solver = pybamm.JaxSolver(rtol=1e-8, atol=1e-8) t_eval = np.linspace(0, 5, 80) - with self.assertRaisesRegex(RuntimeError, "Model is not set up for solving"): + with pytest.raises(RuntimeError, match="Model is not set up for solving"): solver.get_solve(model, t_eval) solver.solve(model, t_eval, inputs={"rate": 0.1}) @@ -242,12 +237,3 @@ def test_get_solve(self): y = solver({"rate": 0.2}) np.testing.assert_allclose(y[0], np.exp(-0.2 * t_eval), rtol=1e-6, atol=1e-6) - - -if __name__ == "__main__": - print("Add -v for more debug output") - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_processed_variable.py b/tests/unit/test_solvers/test_processed_variable.py index b6ae669878..6cd456347d 100644 --- a/tests/unit/test_solvers/test_processed_variable.py +++ b/tests/unit/test_solvers/test_processed_variable.py @@ -7,7 +7,7 @@ import tests import numpy as np -import unittest +import pytest def to_casadi(var_pybamm, y, inputs=None): @@ -61,7 +61,7 @@ def process_and_check_2D_variable( return y_sol, first_sol, second_sol, t_sol -class TestProcessedVariable(unittest.TestCase): +class TestProcessedVariable: def test_processed_variable_0D(self): # without space t = pybamm.t @@ -112,7 +112,7 @@ def test_processed_variable_0D_no_sensitivity(self): ) # test no inputs (i.e. no sensitivity) - self.assertDictEqual(processed_var.sensitivities, {}) + assert processed_var.sensitivities == {} # with parameter t = pybamm.t @@ -132,7 +132,7 @@ def test_processed_variable_0D_no_sensitivity(self): ) # test no sensitivity raises error - with self.assertRaisesRegex(ValueError, "Cannot compute sensitivities"): + with pytest.raises(ValueError, match="Cannot compute sensitivities"): print(processed_var.sensitivities) def test_processed_variable_1D(self): @@ -562,10 +562,10 @@ def test_processed_var_1D_interpolation(self): processed_eqn(t_sol, x_sol), t_sol * y_sol + x_sol[:, np.newaxis] ) # 1 vector, 1 scalar - self.assertEqual(processed_eqn(0.5, x_sol[10:30]).shape, (20,)) - self.assertEqual(processed_eqn(t_sol[4:9], x_sol[-1]).shape, (5,)) + assert processed_eqn(0.5, x_sol[10:30]).shape == (20,) + assert processed_eqn(t_sol[4:9], x_sol[-1]).shape == (5,) # 2 scalars - self.assertEqual(processed_eqn(0.5, x_sol[-1]).shape, ()) + assert processed_eqn(0.5, x_sol[-1]).shape == () # test x x_disc = disc.process_symbol(x) @@ -686,7 +686,7 @@ def test_processed_var_wrong_spatial_variable_names(self): "domain B": {b: {"min": 0, "max": 1}}, } ) - with self.assertRaisesRegex(NotImplementedError, "Spatial variable name"): + with pytest.raises(NotImplementedError, match="Spatial variable name"): pybamm.ProcessedVariable( [var_sol], [var_casadi], @@ -892,7 +892,7 @@ def test_processed_var_2D_secondary_broadcast(self): processed_var(t_sol, x_sol, r_sol).shape, (10, 35, 50) ) - def test_processed_var_2D_scikit_interpolation(self): + def test_processed_var_2_d_scikit_interpolation(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() @@ -1061,7 +1061,7 @@ def test_3D_raises_error(self): u_sol = np.ones(var_sol.shape[0] * 3)[:, np.newaxis] var_casadi = to_casadi(var_sol, u_sol) - with self.assertRaisesRegex(NotImplementedError, "Shape not recognized"): + with pytest.raises(NotImplementedError, match="Shape not recognized"): pybamm.ProcessedVariable( [var_sol], [var_casadi], @@ -1088,51 +1088,23 @@ def test_process_spatial_variable_names(self): ) # Test empty list returns None - self.assertIsNone(processed_var._process_spatial_variable_names([])) + assert processed_var._process_spatial_variable_names([]) is None # Test tabs is ignored - self.assertEqual( - processed_var._process_spatial_variable_names(["tabs", "var"]), - "var", - ) + assert processed_var._process_spatial_variable_names(["tabs", "var"]) == "var" # Test strings stay strings - self.assertEqual( - processed_var._process_spatial_variable_names(["y"]), - "y", - ) + assert processed_var._process_spatial_variable_names(["y"]) == "y" # Test spatial variables are converted to strings x = pybamm.SpatialVariable("x", domain=["domain"]) - self.assertEqual( - processed_var._process_spatial_variable_names([x]), - "x", - ) + assert processed_var._process_spatial_variable_names([x]) == "x" # Test renaming for PyBaMM convention - self.assertEqual( - processed_var._process_spatial_variable_names(["x_a", "x_b"]), - "x", - ) - self.assertEqual( - processed_var._process_spatial_variable_names(["r_a", "r_b"]), - "r", - ) - self.assertEqual( - processed_var._process_spatial_variable_names(["R_a", "R_b"]), - "R", - ) + assert processed_var._process_spatial_variable_names(["x_a", "x_b"]) == "x" + assert processed_var._process_spatial_variable_names(["r_a", "r_b"]) == "r" + assert processed_var._process_spatial_variable_names(["R_a", "R_b"]) == "R" # Test error raised if spatial variable name not recognised - with self.assertRaisesRegex(NotImplementedError, "Spatial variable name"): + with pytest.raises(NotImplementedError, match="Spatial variable name"): processed_var._process_spatial_variable_names(["var1", "var2"]) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_solvers/test_processed_variable_computed.py b/tests/unit/test_solvers/test_processed_variable_computed.py index 407d422e4c..59a062b199 100644 --- a/tests/unit/test_solvers/test_processed_variable_computed.py +++ b/tests/unit/test_solvers/test_processed_variable_computed.py @@ -6,12 +6,12 @@ # values itself since it does not have access to the full state vector # +import pytest import casadi import pybamm import tests import numpy as np -import unittest def to_casadi(var_pybamm, y, inputs=None): @@ -68,7 +68,7 @@ def process_and_check_2D_variable( return y_sol, first_sol, second_sol, t_sol -class TestProcessedVariableComputed(unittest.TestCase): +class TestProcessedVariableComputed: def test_processed_variable_0D(self): # without space y = pybamm.StateVector(slice(0, 1)) @@ -115,7 +115,7 @@ def test_processed_variable_0D_no_sensitivity(self): ) # test no inputs (i.e. no sensitivity) - self.assertDictEqual(processed_var.sensitivities, {}) + assert processed_var.sensitivities == {} # with parameter t = pybamm.t @@ -136,7 +136,7 @@ def test_processed_variable_0D_no_sensitivity(self): ) # test no sensitivity raises error - self.assertIsNone(processed_var.sensitivities) + assert processed_var.sensitivities is None def test_processed_variable_1D(self): var = pybamm.Variable("var", domain=["negative electrode", "separator"]) @@ -386,7 +386,7 @@ def test_processed_variable_2D_space_only(self): np.testing.assert_array_equal(processed_var.unroll(), y_sol.reshape(10, 40, 1)) # Check unroll function (3D) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): processed_var.dimensions = 3 processed_var.unroll() @@ -428,7 +428,7 @@ def test_3D_raises_error(self): u_sol = np.ones(var_sol.shape[0] * 3)[:, np.newaxis] var_casadi = to_casadi(var_sol, u_sol) - with self.assertRaisesRegex(NotImplementedError, "Shape not recognized"): + with pytest.raises(NotImplementedError, match="Shape not recognized"): pybamm.ProcessedVariableComputed( [var_sol], [var_casadi], @@ -436,13 +436,3 @@ def test_3D_raises_error(self): pybamm.Solution(t_sol, u_sol, pybamm.BaseModel(), {}), warn=False, ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_base_spatial_method.py b/tests/unit/test_spatial_methods/test_base_spatial_method.py index 647616c924..190fbd8f94 100644 --- a/tests/unit/test_spatial_methods/test_base_spatial_method.py +++ b/tests/unit/test_spatial_methods/test_base_spatial_method.py @@ -2,9 +2,9 @@ # Test for the base Spatial Method class # +import pytest import numpy as np import pybamm -import unittest from tests import ( get_mesh_for_testing, get_1p1d_mesh_for_testing, @@ -12,31 +12,31 @@ ) -class TestSpatialMethod(unittest.TestCase): +class TestSpatialMethod: def test_basics(self): mesh = get_mesh_for_testing() spatial_method = pybamm.SpatialMethod() spatial_method.build(mesh) - self.assertEqual(spatial_method.mesh, mesh) - with self.assertRaises(NotImplementedError): + assert spatial_method.mesh == mesh + with pytest.raises(NotImplementedError): spatial_method.gradient(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.divergence(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.laplacian(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.gradient_squared(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.integral(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.indefinite_integral(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.boundary_integral(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.delta_function(None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.internal_neumann_condition(None, None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.evaluate_at(None, None, None) def test_get_auxiliary_domain_repeats(self): @@ -47,20 +47,18 @@ def test_get_auxiliary_domain_repeats(self): # No auxiliary domains repeats = spatial_method._get_auxiliary_domain_repeats({}) - self.assertEqual(repeats, 1) + assert repeats == 1 # Just secondary domain repeats = spatial_method._get_auxiliary_domain_repeats( {"secondary": ["negative electrode"]} ) - self.assertEqual(repeats, mesh["negative electrode"].npts) + assert repeats == mesh["negative electrode"].npts repeats = spatial_method._get_auxiliary_domain_repeats( {"secondary": ["negative electrode", "separator"]} ) - self.assertEqual( - repeats, mesh["negative electrode"].npts + mesh["separator"].npts - ) + assert repeats == mesh["negative electrode"].npts + mesh["separator"].npts # With tertiary domain repeats = spatial_method._get_auxiliary_domain_repeats( @@ -69,17 +67,17 @@ def test_get_auxiliary_domain_repeats(self): "tertiary": ["current collector"], } ) - self.assertEqual( - repeats, - (mesh["negative electrode"].npts + mesh["separator"].npts) - * mesh["current collector"].npts, + assert ( + repeats + == (mesh["negative electrode"].npts + mesh["separator"].npts) + * mesh["current collector"].npts ) # Just tertiary domain repeats = spatial_method._get_auxiliary_domain_repeats( {"tertiary": ["current collector"]}, ) - self.assertEqual(repeats, mesh["current collector"].npts) + assert repeats == mesh["current collector"].npts # With quaternary domain repeats = spatial_method._get_auxiliary_domain_repeats( @@ -89,11 +87,11 @@ def test_get_auxiliary_domain_repeats(self): "quaternary": ["current collector"], } ) - self.assertEqual( - repeats, - mesh["negative particle size"].npts + assert ( + repeats + == mesh["negative particle size"].npts * (mesh["negative electrode"].npts + mesh["separator"].npts) - * mesh["current collector"].npts, + * mesh["current collector"].npts ) def test_discretise_spatial_variable(self): @@ -108,7 +106,7 @@ def test_discretise_spatial_variable(self): r = pybamm.SpatialVariable("r", ["negative particle"]) for var in [x1, x2, r]: var_disc = spatial_method.spatial_variable(var) - self.assertIsInstance(var_disc, pybamm.Vector) + assert isinstance(var_disc, pybamm.Vector) np.testing.assert_array_equal( var_disc.evaluate()[:, 0], mesh[var.domain].nodes ) @@ -119,7 +117,7 @@ def test_discretise_spatial_variable(self): r_edge = pybamm.SpatialVariableEdge("r", ["negative particle"]) for var in [x1_edge, x2_edge, r_edge]: var_disc = spatial_method.spatial_variable(var) - self.assertIsInstance(var_disc, pybamm.Vector) + assert isinstance(var_disc, pybamm.Vector) np.testing.assert_array_equal( var_disc.evaluate()[:, 0], mesh[var.domain].edges ) @@ -130,12 +128,12 @@ def test_boundary_value_checks(self): mesh = get_mesh_for_testing() spatial_method = pybamm.SpatialMethod() spatial_method.build(mesh) - with self.assertRaisesRegex(TypeError, "Cannot process BoundaryGradient"): + with pytest.raises(TypeError, match="Cannot process BoundaryGradient"): spatial_method.boundary_value_or_flux(symbol, child) # test also symbol "right" symbol = pybamm.BoundaryGradient(child, "right") - with self.assertRaisesRegex(TypeError, "Cannot process BoundaryGradient"): + with pytest.raises(TypeError, match="Cannot process BoundaryGradient"): spatial_method.boundary_value_or_flux(symbol, child) mesh = get_1p1d_mesh_for_testing() @@ -147,15 +145,5 @@ def test_boundary_value_checks(self): auxiliary_domains={"secondary": "current collector"}, ) symbol = pybamm.BoundaryGradient(child, "left") - with self.assertRaisesRegex(NotImplementedError, "Cannot process 2D symbol"): + with pytest.raises(NotImplementedError, match="Cannot process 2D symbol"): spatial_method.boundary_value_or_flux(symbol, child) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_extrapolation.py b/tests/unit/test_spatial_methods/test_finite_volume/test_extrapolation.py index c51e2d9a13..8479097031 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_extrapolation.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_extrapolation.py @@ -9,7 +9,6 @@ get_1p1d_mesh_for_testing, ) import numpy as np -import unittest def errors(pts, function, method_options, bcs=None): @@ -57,7 +56,7 @@ def get_errors(function, method_options, pts, bcs=None): return l_errors, r_errors -class TestExtrapolation(unittest.TestCase): +class TestExtrapolation: def test_convergence_without_bcs(self): # all tests are performed on x in [0, 1] linear = {"extrapolation": {"order": "linear"}} @@ -262,8 +261,8 @@ def test_linear_extrapolate_left_right(self): # check constant extrapolates to constant constant_y = np.ones_like(macro_submesh.nodes[:, np.newaxis]) - self.assertEqual(extrap_left_disc.evaluate(None, constant_y), 2) - self.assertEqual(extrap_right_disc.evaluate(None, constant_y), 3) + assert extrap_left_disc.evaluate(None, constant_y) == 2 + assert extrap_right_disc.evaluate(None, constant_y) == 3 # check linear variable extrapolates correctly linear_y = macro_submesh.nodes @@ -297,7 +296,7 @@ def test_linear_extrapolate_left_right(self): # check constant extrapolates to constant constant_y = np.ones_like(micro_submesh.nodes[:, np.newaxis]) - self.assertEqual(surf_eqn_disc.evaluate(None, constant_y), 1.0) + assert surf_eqn_disc.evaluate(None, constant_y) == 1.0 # check linear variable extrapolates correctly linear_y = micro_submesh.nodes @@ -359,7 +358,7 @@ def test_quadratic_extrapolate_left_right(self): np.testing.assert_array_almost_equal( extrap_flux_left_disc.evaluate(None, constant_y), 0 ) - self.assertEqual(extrap_flux_right_disc.evaluate(None, constant_y), 0) + assert extrap_flux_right_disc.evaluate(None, constant_y) == 0 # check linear variable extrapolates correctly np.testing.assert_array_almost_equal( @@ -448,7 +447,7 @@ def test_extrapolate_2d_models(self): extrap_right = pybamm.BoundaryValue(var, "right") disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) - self.assertEqual(extrap_right_disc.domain, ["negative electrode"]) + assert extrap_right_disc.domain == ["negative electrode"] # evaluate y_macro = mesh["negative electrode"].nodes y_micro = mesh["negative particle"].nodes @@ -462,7 +461,7 @@ def test_extrapolate_2d_models(self): extrap_right = pybamm.BoundaryValue(var, "right") disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) - self.assertEqual(extrap_right_disc.domain, []) + assert extrap_right_disc.domain == [] # 2d macroscale mesh = get_1p1d_mesh_for_testing() @@ -471,7 +470,7 @@ def test_extrapolate_2d_models(self): extrap_right = pybamm.BoundaryValue(var, "right") disc.set_variable_slices([var]) extrap_right_disc = disc.process_symbol(extrap_right) - self.assertEqual(extrap_right_disc.domain, []) + assert extrap_right_disc.domain == [] # test extrapolate to "negative tab" gives same as "left" and # "positive tab" gives same "right" (see get_mesh_for_testing) @@ -497,13 +496,3 @@ def test_extrapolate_2d_models(self): extrap_pos_disc.evaluate(None, constant_y), extrap_right_disc.evaluate(None, constant_y), ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py index de31b770ff..204e831855 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_finite_volume.py @@ -10,10 +10,10 @@ ) import numpy as np from scipy.sparse import kron, eye -import unittest +import pytest -class TestFiniteVolume(unittest.TestCase): +class TestFiniteVolume: def test_node_to_edge_to_node(self): # Create discretisation mesh = get_mesh_for_testing() @@ -46,14 +46,14 @@ def test_node_to_edge_to_node(self): ) # bad shift key - with self.assertRaisesRegex(ValueError, "shift key"): + with pytest.raises(ValueError, match="shift key"): fin_vol.shift(c, "bad shift key", "arithmetic") - with self.assertRaisesRegex(ValueError, "shift key"): + with pytest.raises(ValueError, match="shift key"): fin_vol.shift(c, "bad shift key", "harmonic") # bad method - with self.assertRaisesRegex(ValueError, "method"): + with pytest.raises(ValueError, match="method"): fin_vol.shift(c, "shift key", "bad method") def test_concatenation(self): @@ -71,7 +71,7 @@ def test_concatenation(self): edges = [ pybamm.Vector(np.ones(mesh[dom].npts + 2), domain=dom) for dom in whole_cell ] - with self.assertRaisesRegex(pybamm.ShapeError, "child must have size n_nodes"): + with pytest.raises(pybamm.ShapeError, match="child must have size n_nodes"): fin_vol.concatenation(edges) def test_discretise_diffusivity_times_spatial_operator(self): @@ -154,14 +154,14 @@ def test_discretise_spatial_variable(self): # macroscale x1 = pybamm.SpatialVariable("x", ["negative electrode"]) x1_disc = disc.process_symbol(x1) - self.assertIsInstance(x1_disc, pybamm.Vector) + assert isinstance(x1_disc, pybamm.Vector) np.testing.assert_array_equal( x1_disc.evaluate(), disc.mesh["negative electrode"].nodes[:, np.newaxis] ) # macroscale with concatenation x2 = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) x2_disc = disc.process_symbol(x2) - self.assertIsInstance(x2_disc, pybamm.Vector) + assert isinstance(x2_disc, pybamm.Vector) np.testing.assert_array_equal( x2_disc.evaluate(), disc.mesh[("negative electrode", "separator")].nodes[:, np.newaxis], @@ -169,7 +169,7 @@ def test_discretise_spatial_variable(self): # microscale r = 3 * pybamm.SpatialVariable("r", ["negative particle"]) r_disc = disc.process_symbol(r) - self.assertIsInstance(r_disc, pybamm.Vector) + assert isinstance(r_disc, pybamm.Vector) np.testing.assert_array_equal( r_disc.evaluate(), 3 * disc.mesh["negative particle"].nodes[:, np.newaxis] ) @@ -326,8 +326,8 @@ def test_boundary_value_domain(self): c_s_p_surf = pybamm.surf(c_s_p) c_s_n_surf_disc = disc.process_symbol(c_s_n_surf) c_s_p_surf_disc = disc.process_symbol(c_s_p_surf) - self.assertEqual(c_s_n_surf_disc.domain, ["negative electrode"]) - self.assertEqual(c_s_p_surf_disc.domain, ["positive electrode"]) + assert c_s_n_surf_disc.domain == ["negative electrode"] + assert c_s_p_surf_disc.domain == ["positive electrode"] def test_delta_function(self): mesh = get_mesh_for_testing() @@ -344,17 +344,17 @@ def test_delta_function(self): # Basic shape and type tests y = np.ones_like(mesh["negative electrode"].nodes[:, np.newaxis]) # Left - self.assertEqual(delta_fn_left_disc.domains, delta_fn_left.domains) - self.assertIsInstance(delta_fn_left_disc, pybamm.Multiplication) - self.assertIsInstance(delta_fn_left_disc.left, pybamm.Matrix) + assert delta_fn_left_disc.domains == delta_fn_left.domains + assert isinstance(delta_fn_left_disc, pybamm.Multiplication) + assert isinstance(delta_fn_left_disc.left, pybamm.Matrix) np.testing.assert_array_equal(delta_fn_left_disc.left.evaluate()[:, 1:], 0) - self.assertEqual(delta_fn_left_disc.shape, y.shape) + assert delta_fn_left_disc.shape == y.shape # Right - self.assertEqual(delta_fn_right_disc.domains, delta_fn_right.domains) - self.assertIsInstance(delta_fn_right_disc, pybamm.Multiplication) - self.assertIsInstance(delta_fn_right_disc.left, pybamm.Matrix) + assert delta_fn_right_disc.domains == delta_fn_right.domains + assert isinstance(delta_fn_right_disc, pybamm.Multiplication) + assert isinstance(delta_fn_right_disc.left, pybamm.Matrix) np.testing.assert_array_equal(delta_fn_right_disc.left.evaluate()[:, :-1], 0) - self.assertEqual(delta_fn_right_disc.shape, y.shape) + assert delta_fn_right_disc.shape == y.shape # Value tests # Delta function should integrate to the same thing as variable @@ -378,7 +378,7 @@ def test_heaviside(self): # process_binary_operators should work with heaviside disc_heav = disc.process_symbol(heav * var) nodes = mesh["negative electrode"].nodes - self.assertEqual(disc_heav.size, nodes.size) + assert disc_heav.size == nodes.size np.testing.assert_array_equal(disc_heav.evaluate(y=2 * np.ones_like(nodes)), 2) np.testing.assert_array_equal(disc_heav.evaluate(y=-2 * np.ones_like(nodes)), 0) @@ -404,8 +404,8 @@ def test_upwind_downwind(self): nodes = mesh["negative electrode"].nodes n = mesh["negative electrode"].npts - self.assertEqual(disc_upwind.size, nodes.size + 1) - self.assertEqual(disc_downwind.size, nodes.size + 1) + assert disc_upwind.size == nodes.size + 1 + assert disc_downwind.size == nodes.size + 1 y_test = 2 * np.ones_like(nodes) np.testing.assert_array_equal( @@ -420,7 +420,7 @@ def test_upwind_downwind(self): # Remove boundary conditions and check error is raised disc.bcs = {} disc._discretised_symbols = {} - with self.assertRaisesRegex(pybamm.ModelError, "Boundary conditions"): + with pytest.raises(pybamm.ModelError, match="Boundary conditions"): disc.process_symbol(upwind) # Set wrong boundary conditions and check error is raised @@ -430,9 +430,9 @@ def test_upwind_downwind(self): "right": (pybamm.Scalar(3), "Neumann"), } } - with self.assertRaisesRegex(pybamm.ModelError, "Dirichlet boundary conditions"): + with pytest.raises(pybamm.ModelError, match="Dirichlet boundary conditions"): disc.process_symbol(upwind) - with self.assertRaisesRegex(pybamm.ModelError, "Dirichlet boundary conditions"): + with pytest.raises(pybamm.ModelError, match="Dirichlet boundary conditions"): disc.process_symbol(downwind) def test_grad_div_with_bcs_on_tab(self): @@ -525,10 +525,10 @@ def test_neg_pos_bcs(self): # check after disc that negative tab goes to left and positive tab goes # to right disc.process_symbol(grad_eqn) - self.assertEqual(disc.bcs[var]["left"][0], pybamm.Scalar(1)) - self.assertEqual(disc.bcs[var]["left"][1], "Dirichlet") - self.assertEqual(disc.bcs[var]["right"][0], pybamm.Scalar(0)) - self.assertEqual(disc.bcs[var]["right"][1], "Neumann") + assert disc.bcs[var]["left"][0] == pybamm.Scalar(1) + assert disc.bcs[var]["left"][1] == "Dirichlet" + assert disc.bcs[var]["right"][0] == pybamm.Scalar(0) + assert disc.bcs[var]["right"][1] == "Neumann" def test_full_broadcast_domains(self): model = pybamm.BaseModel() @@ -568,12 +568,12 @@ def test_evaluate_at(self): evaluate_at = pybamm.EvaluateAt(var, position) evaluate_at_disc = disc.process_symbol(evaluate_at) - self.assertIsInstance(evaluate_at_disc, pybamm.MatrixMultiplication) - self.assertIsInstance(evaluate_at_disc.left, pybamm.Matrix) - self.assertIsInstance(evaluate_at_disc.right, pybamm.StateVector) + assert isinstance(evaluate_at_disc, pybamm.MatrixMultiplication) + assert isinstance(evaluate_at_disc.left, pybamm.Matrix) + assert isinstance(evaluate_at_disc.right, pybamm.StateVector) y = np.arange(n)[:, np.newaxis] - self.assertEqual(evaluate_at_disc.evaluate(y=y), y[idx]) + assert evaluate_at_disc.evaluate(y=y) == y[idx] def test_inner(self): # standard @@ -598,9 +598,9 @@ def test_inner(self): disc.bcs = boundary_conditions inner_disc = disc.process_symbol(inner) - self.assertIsInstance(inner_disc, pybamm.Inner) - self.assertIsInstance(inner_disc.left, pybamm.MatrixMultiplication) - self.assertIsInstance(inner_disc.right, pybamm.MatrixMultiplication) + assert isinstance(inner_disc, pybamm.Inner) + assert isinstance(inner_disc.left, pybamm.MatrixMultiplication) + assert isinstance(inner_disc.right, pybamm.MatrixMultiplication) n = mesh["negative particle"].npts y = np.ones(n)[:, np.newaxis] @@ -613,19 +613,9 @@ def test_inner(self): inner_disc = disc.process_symbol(inner) - self.assertIsInstance(inner_disc, pybamm.Inner) - self.assertIsInstance(inner_disc.left, pybamm.MatrixMultiplication) - self.assertIsInstance(inner_disc.right, pybamm.MatrixMultiplication) + assert isinstance(inner_disc, pybamm.Inner) + assert isinstance(inner_disc.left, pybamm.MatrixMultiplication) + assert isinstance(inner_disc.right, pybamm.MatrixMultiplication) m = mesh["negative electrode"].npts np.testing.assert_array_equal(inner_disc.evaluate(y=y), np.zeros((n * m, 1))) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_ghost_nodes_and_neumann.py b/tests/unit/test_spatial_methods/test_finite_volume/test_ghost_nodes_and_neumann.py index ba82f2fb09..0044d20c0a 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_ghost_nodes_and_neumann.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_ghost_nodes_and_neumann.py @@ -5,10 +5,10 @@ import pybamm from tests import get_mesh_for_testing, get_p2d_mesh_for_testing import numpy as np -import unittest +import pytest -class TestGhostNodes(unittest.TestCase): +class TestGhostNodes: def test_add_ghost_nodes(self): # Set up @@ -36,25 +36,25 @@ def test_add_ghost_nodes(self): np.testing.assert_array_equal( sym_ghost.evaluate(y=y_test)[1:-1], discretised_symbol.evaluate(y=y_test) ) - self.assertEqual( - (sym_ghost.evaluate(y=y_test)[0] + sym_ghost.evaluate(y=y_test)[1]) / 2, 0 - ) - self.assertEqual( - (sym_ghost.evaluate(y=y_test)[-2] + sym_ghost.evaluate(y=y_test)[-1]) / 2, 3 - ) + assert ( + sym_ghost.evaluate(y=y_test)[0] + sym_ghost.evaluate(y=y_test)[1] + ) / 2 == 0 + assert ( + sym_ghost.evaluate(y=y_test)[-2] + sym_ghost.evaluate(y=y_test)[-1] + ) / 2 == 3 # test errors bcs = {"left": (pybamm.Scalar(0), "x"), "right": (pybamm.Scalar(3), "Neumann")} - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.add_ghost_nodes(var, discretised_symbol, bcs) - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.add_neumann_values(var, discretised_symbol, bcs, var.domain) bcs = {"left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(3), "x")} - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.add_ghost_nodes(var, discretised_symbol, bcs) - with self.assertRaisesRegex(ValueError, "No boundary conditions"): + with pytest.raises(ValueError, match="No boundary conditions"): sp_meth.add_ghost_nodes(var, discretised_symbol, {}) - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.add_neumann_values(var, discretised_symbol, bcs, var.domain) def test_add_ghost_nodes_concatenation(self): @@ -92,22 +92,14 @@ def test_add_ghost_nodes_concatenation(self): symbol_plus_ghost_both.evaluate(None, y_test)[1:-1], discretised_symbol.evaluate(None, y_test), ) - self.assertEqual( - ( - symbol_plus_ghost_both.evaluate(None, y_test)[0] - + symbol_plus_ghost_both.evaluate(None, y_test)[1] - ) - / 2, - 0, - ) - self.assertEqual( - ( - symbol_plus_ghost_both.evaluate(None, y_test)[-2] - + symbol_plus_ghost_both.evaluate(None, y_test)[-1] - ) - / 2, - 3, - ) + assert ( + symbol_plus_ghost_both.evaluate(None, y_test)[0] + + symbol_plus_ghost_both.evaluate(None, y_test)[1] + ) / 2 == 0 + assert ( + symbol_plus_ghost_both.evaluate(None, y_test)[-2] + + symbol_plus_ghost_both.evaluate(None, y_test)[-1] + ) / 2 == 3 def test_p2d_add_ghost_nodes(self): # create discretisation @@ -187,13 +179,3 @@ def test_p2d_add_ghost_nodes(self): np.testing.assert_array_equal( (c_s_p_ghost_eval[:, -2] + c_s_p_ghost_eval[:, -1]) / 2, 3 ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_grad_div_shapes.py b/tests/unit/test_spatial_methods/test_finite_volume/test_grad_div_shapes.py index a1dd402f56..9e7f993e2a 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_grad_div_shapes.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_grad_div_shapes.py @@ -10,10 +10,9 @@ get_cylindrical_mesh_for_testing, ) import numpy as np -import unittest -class TestFiniteVolumeGradDiv(unittest.TestCase): +class TestFiniteVolumeGradDiv: def test_grad_div_shapes_Dirichlet_bcs(self): """ Test grad and div with Dirichlet boundary conditions in Cartesian coordinates @@ -637,13 +636,3 @@ def test_grad_1plus1d(self): np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, linear_y), expected ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_finite_volume/test_integration.py b/tests/unit/test_spatial_methods/test_finite_volume/test_integration.py index e9730a8eb7..bf6f44059a 100644 --- a/tests/unit/test_spatial_methods/test_finite_volume/test_integration.py +++ b/tests/unit/test_spatial_methods/test_finite_volume/test_integration.py @@ -2,6 +2,7 @@ # Tests for integration using Finite Volume method # +import pytest import pybamm from tests import ( get_mesh_for_testing, @@ -9,10 +10,9 @@ get_cylindrical_mesh_for_testing, ) import numpy as np -import unittest -class TestFiniteVolumeIntegration(unittest.TestCase): +class TestFiniteVolumeIntegration: def test_definite_integral(self): # create discretisation mesh = get_mesh_for_testing(xpts=200, rpts=200) @@ -37,7 +37,7 @@ def test_definite_integral(self): submesh = mesh[("negative electrode", "separator")] constant_y = np.ones_like(submesh.nodes[:, np.newaxis]) - self.assertEqual(integral_eqn_disc.evaluate(None, constant_y), ln + ls) + assert integral_eqn_disc.evaluate(None, constant_y) == ln + ls linear_y = submesh.nodes np.testing.assert_array_almost_equal( integral_eqn_disc.evaluate(None, linear_y), (ln + ls) ** 2 / 2 @@ -56,10 +56,10 @@ def test_definite_integral(self): submesh = mesh[("separator", "positive electrode")] constant_y = np.ones_like(submesh.nodes[:, np.newaxis]) - self.assertEqual(integral_eqn_disc.evaluate(None, constant_y), ls + lp) + assert integral_eqn_disc.evaluate(None, constant_y) == ls + lp linear_y = submesh.nodes - self.assertAlmostEqual( - integral_eqn_disc.evaluate(None, linear_y)[0][0], (1 - (ln) ** 2) / 2 + assert integral_eqn_disc.evaluate(None, linear_y)[0][0] == pytest.approx( + (1 - (ln) ** 2) / 2 ) cos_y = np.cos(submesh.nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( @@ -122,9 +122,9 @@ def test_definite_integral(self): # test failure for secondary dimension column form finite_volume = pybamm.FiniteVolume() finite_volume.build(mesh) - with self.assertRaisesRegex( + with pytest.raises( NotImplementedError, - "Integral in secondary vector only implemented in 'row' form", + match="Integral in secondary vector only implemented in 'row' form", ): finite_volume.definite_integral_matrix(var, "column", "secondary") @@ -293,14 +293,14 @@ def test_definite_integral_vector(self): # row (default) vec = pybamm.DefiniteIntegralVector(var) vec_disc = disc.process_symbol(vec) - self.assertEqual(vec_disc.shape[0], 1) - self.assertEqual(vec_disc.shape[1], mesh["negative electrode"].npts) + assert vec_disc.shape[0] == 1 + assert vec_disc.shape[1] == mesh["negative electrode"].npts # column vec = pybamm.DefiniteIntegralVector(var, vector_type="column") vec_disc = disc.process_symbol(vec) - self.assertEqual(vec_disc.shape[0], mesh["negative electrode"].npts) - self.assertEqual(vec_disc.shape[1], 1) + assert vec_disc.shape[0] == mesh["negative electrode"].npts + assert vec_disc.shape[1] == 1 def test_indefinite_integral(self): # create discretisation @@ -340,7 +340,7 @@ def test_indefinite_integral(self): phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) phi_approx += 1 # add constant of integration np.testing.assert_array_almost_equal(phi_exact, phi_approx) - self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0) + assert left_boundary_value_disc.evaluate(y=phi_exact) == 0 # linear case phi_exact = submesh.nodes[:, np.newaxis] phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) @@ -380,7 +380,7 @@ def test_indefinite_integral(self): phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) phi_approx += 1 # add constant of integration np.testing.assert_array_almost_equal(phi_exact, phi_approx) - self.assertEqual(left_boundary_value_disc.evaluate(y=phi_exact), 0) + assert left_boundary_value_disc.evaluate(y=phi_exact) == 0 # linear case phi_exact = submesh.nodes[:, np.newaxis] - submesh.edges[0] @@ -441,7 +441,7 @@ def test_indefinite_integral(self): c_approx = c_integral_disc.evaluate(None, c_exact) c_approx += 1 # add constant of integration np.testing.assert_array_almost_equal(c_exact, c_approx) - self.assertEqual(left_boundary_value_disc.evaluate(y=c_exact), 0) + assert left_boundary_value_disc.evaluate(y=c_exact) == 0 # linear case c_exact = submesh.nodes[:, np.newaxis] @@ -489,7 +489,7 @@ def test_backward_indefinite_integral(self): phi_approx = int_grad_phi_disc.evaluate(None, phi_exact) phi_approx += 1 # add constant of integration np.testing.assert_array_almost_equal(phi_exact, phi_approx) - self.assertEqual(right_boundary_value_disc.evaluate(y=phi_exact), 0) + assert right_boundary_value_disc.evaluate(y=phi_exact) == 0 # linear case phi_exact = submesh.nodes - submesh.edges[-1] @@ -583,9 +583,9 @@ def test_indefinite_integral_on_nodes(self): int_c = pybamm.IndefiniteIntegral(c, r) disc.set_variable_slices([c]) - with self.assertRaisesRegex( + with pytest.raises( NotImplementedError, - "Indefinite integral on a spherical polar domain is not implemented", + match="Indefinite integral on a spherical polar domain is not implemented", ): disc.process_symbol(int_c) @@ -655,13 +655,3 @@ def test_forward_plus_backward_integral(self): full_int_phi_disc.evaluate(y=phi_exact).flatten(), int_plus_back_int_phi_disc.evaluate(y=phi_exact).flatten(), ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_scikit_finite_element.py b/tests/unit/test_spatial_methods/test_scikit_finite_element.py index 18c941517b..42b282e08a 100644 --- a/tests/unit/test_spatial_methods/test_scikit_finite_element.py +++ b/tests/unit/test_spatial_methods/test_scikit_finite_element.py @@ -2,21 +2,21 @@ # Test for the operator class # +import pytest import pybamm from tests import get_2p1d_mesh_for_testing, get_unit_2p1D_mesh_for_testing import numpy as np -import unittest -class TestScikitFiniteElement(unittest.TestCase): +class TestScikitFiniteElement: def test_not_implemented(self): mesh = get_2p1d_mesh_for_testing(include_particles=False) spatial_method = pybamm.ScikitFiniteElement() spatial_method.build(mesh) - self.assertEqual(spatial_method.mesh, mesh) - with self.assertRaises(NotImplementedError): + assert spatial_method.mesh == mesh + with pytest.raises(NotImplementedError): spatial_method.divergence(None, None, None) - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): spatial_method.indefinite_integral(None, None, None) def test_discretise_equations(self): @@ -100,7 +100,7 @@ def test_discretise_equations(self): "positive tab": (pybamm.Scalar(1), "Other BC"), } } - with self.assertRaises(ValueError): + with pytest.raises(ValueError): eqn_disc = disc.process_symbol(eqn) disc.bcs = { var: { @@ -108,19 +108,19 @@ def test_discretise_equations(self): "positive tab": (pybamm.Scalar(1), "Neumann"), } } - with self.assertRaises(ValueError): + with pytest.raises(ValueError): eqn_disc = disc.process_symbol(eqn) # raise ModelError if no BCs provided new_var = pybamm.Variable("new_var", domain="current collector") disc.set_variable_slices([new_var]) eqn = pybamm.laplacian(new_var) - with self.assertRaises(pybamm.ModelError): + with pytest.raises(pybamm.ModelError): eqn_disc = disc.process_symbol(eqn) # check GeometryError if using scikit-fem not in y or z x = pybamm.SpatialVariable("x", ["current collector"]) - with self.assertRaises(pybamm.GeometryError): + with pytest.raises(pybamm.GeometryError): disc.process_symbol(x) def test_gradient(self): @@ -389,14 +389,14 @@ def test_definite_integral_vector(self): # row (default) vec = pybamm.DefiniteIntegralVector(var) vec_disc = disc.process_symbol(vec) - self.assertEqual(vec_disc.shape[0], 1) - self.assertEqual(vec_disc.shape[1], mesh["current collector"].npts) + assert vec_disc.shape[0] == 1 + assert vec_disc.shape[1] == mesh["current collector"].npts # column vec = pybamm.DefiniteIntegralVector(var, vector_type="column") vec_disc = disc.process_symbol(vec) - self.assertEqual(vec_disc.shape[0], mesh["current collector"].npts) - self.assertEqual(vec_disc.shape[1], 1) + assert vec_disc.shape[0] == mesh["current collector"].npts + assert vec_disc.shape[1] == 1 def test_neg_pos(self): mesh = get_2p1d_mesh_for_testing(include_particles=False) @@ -423,7 +423,7 @@ def test_neg_pos(self): # test BoundaryGradient not implemented extrap_neg = pybamm.BoundaryGradient(var, "negative tab") - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): disc.process_symbol(extrap_neg) def test_boundary_integral(self): @@ -562,13 +562,3 @@ def test_disc_spatial_var(self): # spatial vars should discretise to the flattend meshgrid np.testing.assert_array_equal(y_disc.evaluate(), y_actual) np.testing.assert_array_equal(z_disc.evaluate(), z_actual) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main() diff --git a/tests/unit/test_spatial_methods/test_spectral_volume.py b/tests/unit/test_spatial_methods/test_spectral_volume.py index f6a631e84c..1fd97c2ebd 100644 --- a/tests/unit/test_spatial_methods/test_spectral_volume.py +++ b/tests/unit/test_spatial_methods/test_spectral_volume.py @@ -2,9 +2,9 @@ # Test for the operator class # +import pytest import pybamm import numpy as np -import unittest def get_mesh_for_testing( @@ -87,10 +87,10 @@ def get_1p1d_mesh_for_testing( ) -class TestSpectralVolume(unittest.TestCase): +class TestSpectralVolume: def test_exceptions(self): sp_meth = pybamm.SpectralVolume() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): sp_meth.chebyshev_differentiation_matrices(3, 3) mesh = get_mesh_for_testing() @@ -104,14 +104,14 @@ def test_exceptions(self): sp_meth.build(mesh) bcs = {"left": (pybamm.Scalar(0), "x"), "right": (pybamm.Scalar(3), "Neumann")} - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.replace_dirichlet_values(var, discretised_symbol, bcs) - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.replace_neumann_values(var, discretised_symbol, bcs) bcs = {"left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(3), "x")} - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.replace_dirichlet_values(var, discretised_symbol, bcs) - with self.assertRaisesRegex(ValueError, "boundary condition must be"): + with pytest.raises(ValueError, match="boundary condition must be"): sp_meth.replace_neumann_values(var, discretised_symbol, bcs) def test_grad_div_shapes_Dirichlet_bcs(self): @@ -628,13 +628,3 @@ def test_grad_1plus1d(self): np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, linear_y), expected ) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - pybamm.settings.debug_mode = True - unittest.main()