def test_well_posed_irreversible_plating_with_porosity(self): options = { "lithium plating": "irreversible", "lithium plating porosity change": "true", } model = pybamm.lithium_ion.SPM(options) param = pybamm.ParameterValues( chemistry=pybamm.parameter_sets.Yang2017) modeltest = tests.StandardModelTest(model, parameter_values=param) modeltest.test_all()
def test_update_parameters_eqn(self): a = pybamm.Scalar(1) b = pybamm.Scalar(2, name="test parameter") c = pybamm.Scalar(3) eqn = a + b * c self.assertEqual(eqn.evaluate(), 7) parameter_values = pybamm.ParameterValues({"test parameter": 3}) eqn_changed = parameter_values.update_scalars(eqn) self.assertEqual(eqn_changed.evaluate(), 10)
def test_loss_active_material(self): options = { "loss of active material": "none", } model = pybamm.lithium_ion.SPM(options) chemistry = pybamm.parameter_sets.Ai2020 parameter_values = pybamm.ParameterValues(chemistry=chemistry) modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) modeltest.test_all()
def test_load_params(self): anode = pybamm.ParameterValues({}).read_parameters_csv( "input/parameters/lithium-ion/anodes/graphite_Kim2011/parameters.csv" ) self.assertEqual(anode["Reference temperature [K]"], "298.15") cathode = pybamm.ParameterValues({}).read_parameters_csv( "input/parameters/lithium-ion/cathodes/nca_Kim2011/parameters.csv") self.assertEqual(cathode["Reference temperature [K]"], "298.15") electrolyte = pybamm.ParameterValues({}).read_parameters_csv( "input/parameters/lithium-ion/electrolytes/lipf6_Kim2011/parameters.csv" ) self.assertEqual(electrolyte["Reference temperature [K]"], "298.15") cell = pybamm.ParameterValues({}).read_parameters_csv( "input/parameters/lithium-ion/cells/Kim2011/parameters.csv") self.assertAlmostEqual( cell["Negative current collector thickness [m]"], 10**(-5))
def test_mesh_creation(self): param = pybamm.ParameterValues( values={ "Electrode width [m]": 0.4, "Electrode height [m]": 0.5, "Negative tab width [m]": 0.1, "Negative tab centre y-coordinate [m]": 0.1, "Negative tab centre z-coordinate [m]": 0.5, "Positive tab width [m]": 0.1, "Positive tab centre y-coordinate [m]": 0.3, "Positive tab centre z-coordinate [m]": 0.5, "Negative electrode thickness [m]": 0.3, "Separator thickness [m]": 0.3, "Positive electrode thickness [m]": 0.3, }) geometry = pybamm.Geometryxp1DMacro(cc_dimension=2) param.process_geometry(geometry) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 10, var.x_s: 7, var.x_p: 12, var.y: 16, var.z: 24} submesh_types = { "negative electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "current collector": pybamm.MeshGenerator(pybamm.ScikitUniform2DSubMesh), } mesh_type = pybamm.Mesh # create mesh mesh = mesh_type(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative electrode"][0].edges[0], 0) self.assertEqual(mesh["positive electrode"][0].edges[-1], 1) # check internal boundary locations self.assertEqual(mesh["negative electrode"][0].edges[-1], mesh["separator"][0].edges[0]) self.assertEqual(mesh["positive electrode"][0].edges[0], mesh["separator"][0].edges[-1]) for domain in mesh: if domain == "current collector": # NOTE: only for degree 1 npts = var_pts[var.y] * var_pts[var.z] self.assertEqual(mesh[domain][0].npts, npts) else: self.assertEqual(len(mesh[domain][0].edges), len(mesh[domain][0].nodes) + 1)
def test_loss_active_material_both(self): options = { "particle cracking": "no cracking", "loss of active material": "both", } model = pybamm.lithium_ion.SPMe(options) chemistry = pybamm.parameter_sets.Ai2020 parameter_values = pybamm.ParameterValues(chemistry=chemistry) modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) modeltest.test_all()
def test_well_posed_both_cracking(self): options = { "particle": "Fickian diffusion", "particle cracking": "both" } model = pybamm.lithium_ion.SPMe(options) chemistry = pybamm.parameter_sets.Ai2020 parameter_values = pybamm.ParameterValues(chemistry=chemistry) modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) modeltest.test_all()
def test_constant_current(self): # test simplify param = pybamm.ElectricalParameters() current = param.current_with_time parameter_values = pybamm.ParameterValues({ "Typical current [A]": 2, "Typical timescale [s]": 1, "Current function [A]": 2, }) processed_current = parameter_values.process_symbol(current) self.assertIsInstance(processed_current.simplify(), pybamm.Scalar)
def test_deprecate_anode_cathode(self): chemistry = copy.deepcopy(pybamm.parameter_sets.Ecker2015) chemistry["anode"] = chemistry.pop("negative electrode") with self.assertWarnsRegex(DeprecationWarning, "anode"): pybamm.ParameterValues(chemistry=chemistry) chemistry = copy.deepcopy(pybamm.parameter_sets.Ecker2015) chemistry["cathode"] = chemistry.pop("positive electrode") with self.assertWarnsRegex(DeprecationWarning, "cathode"): pybamm.ParameterValues(chemistry=chemistry) chemistry = copy.deepcopy(pybamm.parameter_sets.Ecker2015) chemistry["anode"] = None with self.assertRaisesRegex(KeyError, "both 'anode' and 'negative"): pybamm.ParameterValues(chemistry=chemistry) chemistry = copy.deepcopy(pybamm.parameter_sets.Ecker2015) chemistry["cathode"] = None with self.assertRaisesRegex(KeyError, "both 'cathode' and 'positive"): pybamm.ParameterValues(chemistry=chemistry)
def test_read_parameters_csv(self): data = pybamm.ParameterValues({}).read_parameters_csv( pybamm.get_parameters_filepath( os.path.join( "input", "parameters", "lithium-ion", "cathodes", "lico2_Marquis2019", "parameters.csv", ))) self.assertEqual(data["Positive electrode porosity"], "0.3")
def test_standard_lithium_parameters(self): chemistry = pybamm.parameter_sets.Ai2020 parameter_values = pybamm.ParameterValues(chemistry=chemistry) options = { "particle": "Fickian diffusion", "particle cracking": "both" } model = pybamm.lithium_ion.DFN(options) sim = pybamm.Simulation(model, parameter_values=parameter_values) sim.set_parameters() sim.build()
def test_read_parameters_csv(self): data = pybamm.ParameterValues({}).read_parameters_csv( pybamm.get_parameters_filepath( os.path.join( "input", "parameters", "lithium-ion", "cathodes", "lico2_Marquis2019", "parameters.csv", ))) self.assertEqual(data["Reference temperature [K]"], "298.15")
def test_simple_model(self): model = pybamm.BaseModel() v = pybamm.Variable("v") a = pybamm.Parameter("a") model.rhs = {v: -a * v} model.initial_conditions = {v: 1} param = pybamm.ParameterValues({"a": 1}) sim = pybamm.Simulation(model, parameter_values=param) sol = sim.solve([0, 1]) np.testing.assert_array_almost_equal(sol.y.full()[0], np.exp(-sol.t), decimal=5)
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"): parameter_values.process_model(model)
def test_multi_var_function_parameter(self): def D(a, b): return a * pybamm.exp(b) parameter_values = pybamm.ParameterValues({"a": 3, "b": 0, "Diffusivity": D}) a = pybamm.Parameter("a") b = pybamm.Parameter("b") func = pybamm.FunctionParameter("Diffusivity", {"a": a, "b": b}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 3)
def test_multi_var_function_with_parameters(self): def D(a, b): return a * np.exp(b) parameter_values = pybamm.ParameterValues({"a": 3, "b": 0}) a = pybamm.Parameter("a") b = pybamm.Parameter("b") func = pybamm.Function(D, a, b) processed_func = parameter_values.process_symbol(func) self.assertIsInstance(processed_func, pybamm.Function) self.assertEqual(processed_func.evaluate(), 3)
def test_init(self): # from dict param = pybamm.ParameterValues({"a": 1}) self.assertEqual(param["a"], 1) self.assertEqual(list(param.keys())[0], "a") self.assertEqual(list(param.values())[0], 1) self.assertEqual(list(param.items())[0], ("a", 1)) # from file param = pybamm.ParameterValues( "lithium-ion/cathodes/lico2_Marquis2019/" + "parameters.csv") self.assertEqual(param["Positive electrode porosity"], 0.3) # values vs chemistry with self.assertRaisesRegex( ValueError, "values and chemistry cannot both be None"): pybamm.ParameterValues() with self.assertRaisesRegex( ValueError, "Only one of values and chemistry can be provided."): pybamm.ParameterValues(values=1, chemistry={})
def test_process_function_parameter(self): parameter_values = pybamm.ParameterValues({ "a": 3, "func": pybamm.load_function("process_symbol_test_function.py"), "const": 254, "float_func": lambda x: 42, }) a = pybamm.InputParameter("a") # process function func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"a": 3}), 369) # process constant function const = pybamm.FunctionParameter("const", {"a": a}) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Scalar) self.assertEqual(processed_const.evaluate(), 254) # 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) # 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) # function itself as input (different to the variable being an input) parameter_values = pybamm.ParameterValues({"func": "[input]"}) 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)
def get_mesh_for_testing(xpts=None, rpts=10, ypts=15, zpts=15, geometry=None, cc_submesh=None): param = pybamm.ParameterValues( values={ "Electrode width [m]": 0.4, "Electrode height [m]": 0.5, "Negative tab width [m]": 0.1, "Negative tab centre y-coordinate [m]": 0.1, "Negative tab centre z-coordinate [m]": 0.0, "Positive tab width [m]": 0.1, "Positive tab centre y-coordinate [m]": 0.3, "Positive tab centre z-coordinate [m]": 0.5, "Negative electrode thickness [m]": 0.3, "Separator thickness [m]": 0.3, "Positive electrode thickness [m]": 0.3, }) if geometry is None: geometry = pybamm.battery_geometry() param.process_geometry(geometry) submesh_types = { "negative electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D), } if cc_submesh: submesh_types["current collector"] = cc_submesh if xpts is None: xn_pts, xs_pts, xp_pts = 40, 25, 35 else: xn_pts, xs_pts, xp_pts = xpts, xpts, xpts var = pybamm.standard_spatial_vars var_pts = { var.x_n: xn_pts, var.x_s: xs_pts, var.x_p: xp_pts, var.r_n: rpts, var.r_p: rpts, var.y: ypts, var.z: zpts, } return pybamm.Mesh(geometry, submesh_types, var_pts)
def test_mesh_sizes(self): param = pybamm.ParameterValues( values={ "Negative electrode thickness [m]": 0.1, "Separator thickness [m]": 0.2, "Positive electrode thickness [m]": 0.3, }) geometry = pybamm.Geometry1DMacro() param.process_geometry(geometry) # provide mesh properties var = pybamm.standard_spatial_vars var_pts = { var.x_n: 10, var.x_s: 10, var.x_p: 12, var.r_n: 5, var.r_p: 6 } submesh_types = { "negative electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D), } mesh_type = pybamm.Mesh # create mesh mesh = mesh_type(geometry, submesh_types, var_pts) var_id_pts = {var.id: pts for var, pts in var_pts.items()} self.assertEqual(mesh["negative electrode"][0].npts, var_id_pts[var.x_n.id]) self.assertEqual(mesh["separator"][0].npts, var_id_pts[var.x_s.id]) self.assertEqual(mesh["positive electrode"][0].npts, var_id_pts[var.x_p.id]) self.assertEqual( len(mesh["negative electrode"][0].edges) - 1, var_id_pts[var.x_n.id]) self.assertEqual( len(mesh["separator"][0].edges) - 1, var_id_pts[var.x_s.id]) self.assertEqual( len(mesh["positive electrode"][0].edges) - 1, var_id_pts[var.x_p.id])
def setUp(self): self.model = pybamm.lithium_ion.SPM( options={"SEI": "electron-migration limited"}) self.cycle = [( "Discharge at 2 C until 3.6 V", "Charge at 3 C until 3.8 V", "Hold at 3.8 V until 98 mA", "Rest for 4 minutes", )] self.number = 2 self.experiment = pybamm.Experiment(self.cycle * self.number) self.is_experiment = False self.degradation_parameter = "Inner SEI open-circuit potential [V]" self.varied_values = [0.09, 0.05] self.param_values_mohtat = [] for i in range(2): self.param_values_mohtat.append( pybamm.ParameterValues(pybamm.parameter_sets.Mohtat2020)) self.param_values_mohtat[i][ "Inner SEI open-circuit potential [V]"] = self.varied_values[i] self.chemistry = pybamm.parameter_sets.Mohtat2020 self.parameter_values = pybamm.ParameterValues(self.chemistry)
def test_read_parameters_csv(self): data = pybamm.ParameterValues({}).read_parameters_csv( os.path.join( pybamm.root_dir(), "pybamm", "input", "parameters", "lithium-ion", "positive_electrodes", "lico2_Marquis2019", "parameters.csv", )) self.assertEqual(data["Positive electrode porosity"], "0.3")
def test_init(self): # from dict param = pybamm.ParameterValues({"a": 1}) self.assertEqual(param["a"], 1) self.assertEqual(list(param.keys())[0], "a") self.assertEqual(list(param.values())[0], 1) self.assertEqual(list(param.items())[0], ("a", 1)) # from file param = pybamm.ParameterValues( values="input/parameters/lithium-ion/cathodes/lico2_Marquis2019/" + "parameters.csv") self.assertEqual(param["Reference temperature [K]"], 298.15) # values vs chemistry with self.assertRaisesRegex( ValueError, "values and chemistry cannot both be None"): pybamm.ParameterValues() with self.assertRaisesRegex( ValueError, "Only one of values and chemistry can be provided."): pybamm.ParameterValues(values=1, chemistry={})
def test_evaluate(self): parameter_values = pybamm.ParameterValues({"a": 1, "b": 2, "c": 3}) 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) y = pybamm.StateVector(slice(0, 1)) with self.assertRaises(ValueError): parameter_values.evaluate(y) array = pybamm.Array(np.array([1, 2, 3])) with self.assertRaises(ValueError): parameter_values.evaluate(array)
def test_process_parameter_in_parameter(self): parameter_values = pybamm.ParameterValues( {"a": 2, "2a": pybamm.Parameter("a") * 2, "b": np.array([1, 2, 3])} ) # process 2a parameter a = pybamm.Parameter("2a") processed_a = parameter_values.process_symbol(a) self.assertEqual(processed_a.evaluate(), 4) # case where parameter can't be processed b = pybamm.Parameter("b") with self.assertRaisesRegex(TypeError, "Cannot process parameter"): parameter_values.process_symbol(b)
def test_update(self): param = pybamm.ParameterValues({"a": 1}) self.assertEqual(param["a"], 1) # no conflict param.update({"a": 2}) self.assertEqual(param["a"], 2) param.update({"a": 2}, check_conflict=True) self.assertEqual(param["a"], 2) # with conflict param.update({"a": 3}) self.assertEqual(param["a"], 3) with self.assertRaisesRegex( ValueError, "parameter 'a' already defined with value '3'"): param.update({"a": 4}, check_conflict=True)
def get_max_error(current): pybamm.logger.info("current = {}".format(current)) # Update current (and hence C_e) in the parameters param = pybamm.ParameterValues( chemistry=pybamm.parameter_sets.Sulzer2019) param.update({"Typical current [A]": current}) param.update_model(leading_order_model, loqs_disc) param.update_model(composite_model, comp_disc) param.update_model(full_model, full_disc) # Solve, make sure times are the same and use tight tolerances t_eval = np.linspace(0, 0.6) solver_loqs = leading_order_model.default_solver solver_loqs.rtol = 1e-8 solver_loqs.atol = 1e-8 solution_loqs = solver_loqs.solve(leading_order_model, t_eval) solver_comp = composite_model.default_solver solver_comp.rtol = 1e-8 solver_comp.atol = 1e-8 solution_comp = solver_comp.solve(composite_model, t_eval) solver_full = full_model.default_solver solver_full.rtol = 1e-8 solver_full.atol = 1e-8 solution_full = solver_full.solve(full_model, t_eval) # Post-process variables t_loqs, y_loqs = solution_loqs.t, solution_loqs.y t_comp, y_comp = solution_comp.t, solution_comp.y t_full, y_full = solution_full.t, solution_full.y voltage_loqs = pybamm.ProcessedVariable( leading_order_model.variables["Terminal voltage"], t_loqs, y_loqs, loqs_disc.mesh, ) voltage_comp = pybamm.ProcessedVariable( composite_model.variables["Terminal voltage"], t_comp, y_comp, comp_disc.mesh, ) voltage_full = pybamm.ProcessedVariable( full_model.variables["Terminal voltage"], t_full, y_full, full_disc.mesh) # Compare t = t_full[:np.min([len(t_loqs), len(t_comp), len(t_full)])] loqs_error = np.max(np.abs(voltage_loqs(t) - voltage_full(t))) comp_error = np.max(np.abs(voltage_comp(t) - voltage_full(t))) return (loqs_error, comp_error)
def test_mesh_creation(self): param = pybamm.ParameterValues( values={ "Negative electrode thickness [m]": 0.1, "Separator thickness [m]": 0.2, "Positive electrode thickness [m]": 0.3, }) geometry = pybamm.Geometry1DMacro() param.process_geometry(geometry) var = pybamm.standard_spatial_vars var_pts = { var.x_n: 10, var.x_s: 10, var.x_p: 12, var.r_n: 5, var.r_p: 6 } submesh_types = { "negative electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "current collector": pybamm.MeshGenerator(pybamm.SubMesh0D), } mesh_type = pybamm.Mesh # create mesh mesh = mesh_type(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative electrode"][0].edges[0], 0) self.assertEqual(mesh["positive electrode"][0].edges[-1], 1) # check internal boundary locations self.assertEqual(mesh["negative electrode"][0].edges[-1], mesh["separator"][0].edges[0]) self.assertEqual(mesh["positive electrode"][0].edges[0], mesh["separator"][0].edges[-1]) for domain in mesh: if domain != "current collector": self.assertEqual(len(mesh[domain][0].edges), len(mesh[domain][0].nodes) + 1)
def test_evaluate(self): parameter_values = pybamm.ParameterValues({"a": 1, "b": 2, "c": 3}) 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) 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): parameter_values.evaluate(y)
def test_parameter_citations(self): citations = pybamm.citations citations._reset() pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Chen2020) self.assertIn("Chen2020", citations._papers_to_cite) citations._reset() pybamm.ParameterValues(chemistry=pybamm.parameter_sets.NCA_Kim2011) self.assertIn("kim2011multi", citations._papers_to_cite) citations._reset() pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Marquis2019) self.assertIn("marquis2019asymptotic", citations._papers_to_cite) citations._reset() pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Sulzer2019) self.assertIn("sulzer2019physical", citations._papers_to_cite) citations._reset() pybamm.ParameterValues(chemistry=pybamm.parameter_sets.Ecker2015) self.assertIn("ecker2015i", citations._papers_to_cite) self.assertIn("ecker2015ii", citations._papers_to_cite) self.assertIn("richardson2020", citations._papers_to_cite)