def test_identity_ops(self): test_mesh = np.array([1, 2, 3]) spatial_method = pybamm.ZeroDimensionalSpatialMethod() spatial_method.build(test_mesh) np.testing.assert_array_equal(spatial_method._mesh, test_mesh) a = pybamm.Symbol("a") self.assertEqual(a, spatial_method.integral(None, a, "primary")) self.assertEqual( a, spatial_method.indefinite_integral(None, a, "forward")) self.assertEqual(a, spatial_method.boundary_value_or_flux(None, a)) self.assertEqual( (-a).id, spatial_method.indefinite_integral(None, a, "backward").id) mass_matrix = spatial_method.mass_matrix(None, None) self.assertIsInstance(mass_matrix, pybamm.Matrix) self.assertEqual(mass_matrix.shape, (1, 1)) np.testing.assert_array_equal(mass_matrix.entries, 1)
var_pts, ) for geometry in geometries ] # discretise model disc_fv = pybamm.Discretisation(meshes[0], models[0].default_spatial_methods) disc_sv = pybamm.Discretisation( meshes[1], { "negative particle": pybamm.SpectralVolume(order=order), "positive particle": pybamm.SpectralVolume(order=order), "negative electrode": pybamm.SpectralVolume(order=order), "separator": pybamm.SpectralVolume(order=order), "positive electrode": pybamm.SpectralVolume(order=order), "current collector": pybamm.ZeroDimensionalSpatialMethod(), }, ) disc_fv.process_model(models[0]) disc_sv.process_model(models[1]) # solve model t_eval = np.linspace(0, 3600, 100) casadi_fv = pybamm.CasadiSolver(atol=1e-8, rtol=1e-8).solve(models[0], t_eval) casadi_sv = pybamm.CasadiSolver(atol=1e-8, rtol=1e-8).solve(models[1], t_eval) solutions = [casadi_fv, casadi_sv] # plot plot = pybamm.QuickPlot(solutions)
def test_quadratic_extrapolate_left_right(self): # create discretisation mesh = get_mesh_for_testing() method_options = {"extrapolation": {"order": "quadratic", "use bcs": False}} spatial_methods = { "macroscale": pybamm.FiniteVolume(method_options), "negative particle": pybamm.FiniteVolume(method_options), "current collector": pybamm.ZeroDimensionalSpatialMethod(method_options), } disc = pybamm.Discretisation(mesh, spatial_methods) whole_cell = ["negative electrode", "separator", "positive electrode"] macro_submesh = mesh.combine_submeshes(*whole_cell) micro_submesh = mesh["negative particle"] # Macroscale # create variable var = pybamm.Variable("var", domain=whole_cell) # boundary value should work with something more complicated than a variable extrap_left = pybamm.BoundaryValue(2 * var, "left") extrap_right = pybamm.BoundaryValue(4 - var, "right") disc.set_variable_slices([var]) extrap_left_disc = disc.process_symbol(extrap_left) extrap_right_disc = disc.process_symbol(extrap_right) # check constant extrapolates to constant constant_y = np.ones_like(macro_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( extrap_left_disc.evaluate(None, constant_y), 2.0 ) np.testing.assert_array_almost_equal( extrap_right_disc.evaluate(None, constant_y), 3.0 ) # check linear variable extrapolates correctly linear_y = macro_submesh[0].nodes np.testing.assert_array_almost_equal( extrap_left_disc.evaluate(None, linear_y), 0 ) np.testing.assert_array_almost_equal( extrap_right_disc.evaluate(None, linear_y), 3 ) # Fluxes extrap_flux_left = pybamm.BoundaryGradient(2 * var, "left") extrap_flux_right = pybamm.BoundaryGradient(1 - var, "right") extrap_flux_left_disc = disc.process_symbol(extrap_flux_left) extrap_flux_right_disc = disc.process_symbol(extrap_flux_right) # check constant extrapolates to constant 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) # check linear variable extrapolates correctly np.testing.assert_array_almost_equal( extrap_flux_left_disc.evaluate(None, linear_y), 2 ) np.testing.assert_array_almost_equal( extrap_flux_right_disc.evaluate(None, linear_y), -1 ) # Microscale # create variable var = pybamm.Variable("var", domain="negative particle") surf_eqn = pybamm.surf(var) disc.set_variable_slices([var]) surf_eqn_disc = disc.process_symbol(surf_eqn) # check constant extrapolates to constant constant_y = np.ones_like(micro_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( surf_eqn_disc.evaluate(None, constant_y), 1 ) # check linear variable extrapolates correctly linear_y = micro_submesh[0].nodes y_surf = micro_submesh[0].edges[-1] np.testing.assert_array_almost_equal( surf_eqn_disc.evaluate(None, linear_y), y_surf )
def test_leading_order_convergence(self): """ Check that the leading-order model solution converges linearly in C_e to the full model solution """ # Create models leading_order_model = pybamm.lead_acid.LOQS() composite_model = pybamm.lead_acid.Composite() full_model = pybamm.lead_acid.Full() # Same parameters, same geometry parameter_values = full_model.default_parameter_values parameter_values["Current function [A]"] = "[input]" parameter_values.process_model(leading_order_model) parameter_values.process_model(composite_model) parameter_values.process_model(full_model) geometry = full_model.default_geometry parameter_values.process_geometry(geometry) # Discretise (same mesh, create different discretisations) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 3, var.x_s: 3, var.x_p: 3} mesh = pybamm.Mesh(geometry, full_model.default_submesh_types, var_pts) method_options = { "extrapolation": { "order": "linear", "use bcs": False } } spatial_methods = { "macroscale": pybamm.FiniteVolume(method_options), "current collector": pybamm.ZeroDimensionalSpatialMethod(method_options), } loqs_disc = pybamm.Discretisation(mesh, spatial_methods) loqs_disc.process_model(leading_order_model) comp_disc = pybamm.Discretisation(mesh, spatial_methods) comp_disc.process_model(composite_model) full_disc = pybamm.Discretisation(mesh, spatial_methods) full_disc.process_model(full_model) def get_max_error(current): pybamm.logger.info("current = {}".format(current)) # Solve, make sure times are the same and use tight tolerances t_eval = np.linspace(0, 3600 * 17 / current) solver = pybamm.CasadiSolver() solver.rtol = 1e-8 solver.atol = 1e-8 solution_loqs = solver.solve( leading_order_model, t_eval, inputs={"Current function [A]": current}) solution_comp = solver.solve( composite_model, t_eval, inputs={"Current function [A]": current}) solution_full = solver.solve( full_model, t_eval, inputs={"Current function [A]": current}) # Post-process variables voltage_loqs = solution_loqs["Terminal voltage"] voltage_comp = solution_comp["Terminal voltage"] voltage_full = solution_full["Terminal voltage"] # Compare t_loqs = solution_loqs.t t_comp = solution_comp.t t_full = solution_full.t 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) # Get errors currents = 0.5 / (2**np.arange(3)) errs = np.array([get_max_error(current) for current in currents]) loqs_errs, comp_errs = [np.array(err) for err in zip(*errs)] # Get rates: expect linear convergence for loqs, quadratic for composite loqs_rates = np.log2(loqs_errs[:-1] / loqs_errs[1:]) np.testing.assert_array_less(0.99 * np.ones_like(loqs_rates), loqs_rates) # Composite not converging as expected comp_rates = np.log2(comp_errs[:-1] / comp_errs[1:]) np.testing.assert_array_less(0.99 * np.ones_like(comp_rates), comp_rates) # Check composite more accurate than loqs np.testing.assert_array_less(comp_errs, loqs_errs)