def test_processed_variable_2Dspace_scikit(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() disc.set_variable_slices([var]) y = disc.mesh["current collector"][0].edges["y"] z = disc.mesh["current collector"][0].edges["z"] var_sol = disc.process_symbol(var) var_sol.mesh = disc.mesh["current collector"] t_sol = np.array([0]) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] processed_var = pybamm.ProcessedVariable(var_sol, pybamm.Solution(t_sol, u_sol)) np.testing.assert_array_equal(processed_var.entries, np.reshape(u_sol, [len(y), len(z)]))
def get_fundamental_variables(self): phi_s_cn = pybamm.standard_variables.phi_s_cn variables = self._get_standard_negative_potential_variables(phi_s_cn) # TODO: grad not implemented for 2D yet i_cc = pybamm.Scalar(0) i_boundary_cc = pybamm.standard_variables.i_boundary_cc variables.update(self._get_standard_current_variables(i_cc, i_boundary_cc)) # Lagrange multiplier for the composite current (enforce average) c = pybamm.Variable("Lagrange multiplier") variables.update({"Lagrange multiplier": c}) return variables
def test_solve_ode_model_with_dae_solver_python(self): model = pybamm.BaseModel() model.convert_to_format = "python" var = pybamm.Variable("var") model.rhs = {var: 0.1 * var} model.initial_conditions = {var: 1} disc = get_discretisation_for_testing() disc.process_model(model) # Solve solver = pybamm.ScikitsDaeSolver(rtol=1e-8, atol=1e-8, root_method="lm") t_eval = np.linspace(0, 1, 100) solution = solver.solve(model, t_eval) np.testing.assert_array_equal(solution.t, t_eval) np.testing.assert_allclose(solution.y[0], np.exp(0.1 * solution.t))
def test_spherical_grad_div_shapes_Neumann_bcs(self): """Test grad and div with Neumann boundary conditions (applied by div on N)""" # create discretisation mesh = get_mesh_for_testing() spatial_methods = {"negative particle": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) combined_submesh = mesh.combine_submeshes("negative particle") # grad var = pybamm.Variable("var", domain="negative particle") grad_eqn = pybamm.grad(var) disc.set_variable_slices([var]) grad_eqn_disc = disc.process_symbol(grad_eqn) constant_y = np.ones_like(combined_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_equal( grad_eqn_disc.evaluate(None, constant_y), np.zeros_like(combined_submesh[0].edges[1:-1][:, np.newaxis]), ) linear_y = combined_submesh[0].nodes np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, linear_y), np.ones_like(combined_submesh[0].edges[1:-1][:, np.newaxis]), ) # div # div ( grad(r^2) ) == 6 , N_left = N_right = 0 N = pybamm.grad(var) div_eqn = pybamm.div(N) boundary_conditions = { var.id: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } disc.bcs = boundary_conditions div_eqn_disc = disc.process_symbol(div_eqn) linear_y = combined_submesh[0].nodes const = 6 * np.ones(combined_submesh[0].npts) np.testing.assert_array_almost_equal( div_eqn_disc.evaluate(None, const), np.zeros((combined_submesh[0].npts, 1)))
def test_solver_sensitivities(self): # Create model model = pybamm.BaseModel() model.convert_to_format = "jax" domain = ["negative electrode", "separator", "positive electrode"] var = pybamm.Variable("var", domain=domain) model.rhs = {var: -pybamm.InputParameter("rate") * var} model.initial_conditions = {var: 1} # create discretisation mesh = get_mesh_for_testing(xpts=10) spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) disc.process_model(model) # Solve t_eval = np.linspace(0, 10, 4) y0 = model.concatenated_initial_conditions.evaluate().reshape(-1) rhs = pybamm.EvaluatorJax(model.concatenated_rhs) def fun(y, t, inputs): return rhs.evaluate(t=t, y=y, inputs=inputs).reshape(-1) h = 0.0001 rate = 0.1 # create a dummy "model" where we calculate the sum of the time series @jax.jit def solve_bdf(rate): return jax.numpy.sum( pybamm.jax_bdf_integrate(fun, y0, t_eval, {'rate': rate}, rtol=1e-9, atol=1e-9)) # check answers with finite difference eval_plus = solve_bdf(rate + h) eval_neg = solve_bdf(rate - h) grad_num = (eval_plus - eval_neg) / (2 * h) grad_solve_bdf = jax.jit(jax.grad(solve_bdf)) grad_bdf = grad_solve_bdf(rate) self.assertAlmostEqual(grad_bdf, grad_num, places=3)
def test_processed_var_2D_scikit_interpolation(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() disc.set_variable_slices([var]) y_sol = disc.mesh["current collector"].edges["y"] z_sol = disc.mesh["current collector"].edges["z"] var_sol = disc.process_symbol(var) var_sol.mesh = disc.mesh["current collector"] t_sol = np.linspace(0, 1) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] * np.linspace(0, 5) var_casadi = to_casadi(var_sol, u_sol) processed_var = pybamm.ProcessedVariable( [var_sol], [var_casadi], pybamm.Solution(t_sol, u_sol, pybamm.BaseModel(), {}), warn=False, ) # 3 vectors np.testing.assert_array_equal( processed_var(t_sol, y=y_sol, z=z_sol).shape, (15, 15, 50)) np.testing.assert_array_equal( processed_var(t_sol, y=y_sol, z=z_sol), np.reshape( u_sol, [len(y_sol), len(z_sol), len(t_sol)]), ) # 2 vectors, 1 scalar np.testing.assert_array_equal( processed_var(0.5, y=y_sol, z=z_sol).shape, (15, 15)) np.testing.assert_array_equal( processed_var(t_sol, y=0.2, z=z_sol).shape, (15, 50)) np.testing.assert_array_equal( processed_var(t_sol, y=y_sol, z=0.5).shape, (15, 50)) # 1 vectors, 2 scalar np.testing.assert_array_equal( processed_var(0.5, y=0.2, z=z_sol).shape, (15, )) np.testing.assert_array_equal( processed_var(0.5, y=y_sol, z=0.5).shape, (15, )) np.testing.assert_array_equal( processed_var(t_sol, y=0.2, z=0.5).shape, (50, )) # 3 scalars np.testing.assert_array_equal( processed_var(0.2, y=0.2, z=0.2).shape, ())
def test_extrapolate_on_nonuniform_grid(self): geometry = pybamm.Geometry("1D micro") submesh_types = { "negative particle": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh), "positive particle": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh), } var = pybamm.standard_spatial_vars rpts = 10 var_pts = { var.r_n: rpts, var.r_p: rpts, } mesh = pybamm.Mesh(geometry, submesh_types, var_pts) method_options = { "extrapolation": { "order": "linear", "use bcs": False } } spatial_methods = { "negative particle": pybamm.FiniteVolume(method_options), } disc = pybamm.Discretisation(mesh, spatial_methods) 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) micro_submesh = mesh["negative particle"] # 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_grad_div_shapes_Neumann_bcs(self): """Test grad and div with Neumann boundary conditions (applied by div on N)""" whole_cell = ["negative electrode", "separator", "positive electrode"] # create discretisation mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.SpectralVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) combined_submesh = mesh.combine_submeshes(*whole_cell) # grad var = pybamm.Variable("var", domain=whole_cell) grad_eqn = pybamm.grad(var) disc.set_variable_slices([var]) grad_eqn_disc = disc.process_symbol(grad_eqn) constant_y = np.ones_like(combined_submesh.nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, constant_y), np.zeros_like(combined_submesh.edges[:][:, np.newaxis]), ) # div N = pybamm.grad(var) div_eqn = pybamm.div(N) boundary_conditions = { var.id: { "left": (pybamm.Scalar(1), "Neumann"), "right": (pybamm.Scalar(1), "Neumann"), } } disc.bcs = boundary_conditions div_eqn_disc = disc.process_symbol(div_eqn) # Linear y should have laplacian zero linear_y = combined_submesh.nodes np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, linear_y), np.ones_like(combined_submesh.edges[:][:, np.newaxis]), ) np.testing.assert_array_almost_equal( div_eqn_disc.evaluate(None, linear_y), np.zeros_like(combined_submesh.nodes[:, np.newaxis]), )
def test_processed_variable_2D_x_z(self): var = pybamm.Variable( "var", domain=["negative electrode", "separator"], auxiliary_domains={"secondary": "current collector"}, ) x = pybamm.SpatialVariable( "x", domain=["negative electrode", "separator"], auxiliary_domains={"secondary": "current collector"}, ) z = pybamm.SpatialVariable("z", domain=["current collector"]) disc = tests.get_1p1d_discretisation_for_testing() disc.set_variable_slices([var]) z_sol = disc.process_symbol(z).entries[:, 0] x_sol = disc.process_symbol(x).entries[:, 0] # Keep only the first iteration of entries x_sol = x_sol[: len(x_sol) // len(z_sol)] var_sol = disc.process_symbol(var) t_sol = np.linspace(0, 1) y_sol = np.ones(len(x_sol) * len(z_sol))[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable( var_sol, pybamm.Solution(t_sol, y_sol), warn=False ) np.testing.assert_array_equal( processed_var.entries, np.reshape(y_sol, [len(x_sol), len(z_sol), len(t_sol)]), ) # On edges x_s_edge = pybamm.Matrix( np.tile(disc.mesh["separator"].edges, len(z_sol)), domain="separator", auxiliary_domains={"secondary": "current collector"}, ) x_s_edge.mesh = disc.mesh["separator"] x_s_edge.secondary_mesh = disc.mesh["current collector"] processed_x_s_edge = pybamm.ProcessedVariable( x_s_edge, pybamm.Solution(t_sol, y_sol), warn=False ) np.testing.assert_array_equal( x_s_edge.entries.flatten(), processed_x_s_edge.entries[:, :, 0].T.flatten() )
def test_add_ghost_nodes(self): # Set up # create discretisation mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) # Add ghost nodes whole_cell = ["negative electrode", "separator", "positive electrode"] var = pybamm.Variable("var", domain=whole_cell) disc.set_variable_slices([var]) discretised_symbol = pybamm.StateVector(*disc.y_slices[var.id]) bcs = { "left": (pybamm.Scalar(0), "Dirichlet"), "right": (pybamm.Scalar(3), "Dirichlet"), } # Test sp_meth = pybamm.FiniteVolume() sp_meth.build(mesh) sym_ghost, _ = sp_meth.add_ghost_nodes(var, discretised_symbol, bcs) combined_submesh = mesh.combine_submeshes(*whole_cell) y_test = np.linspace(0, 1, combined_submesh[0].npts) 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 ) # test errors bcs = {"left": (pybamm.Scalar(0), "x"), "right": (pybamm.Scalar(3), "Neumann")} with self.assertRaisesRegex(ValueError, "boundary condition must be"): sp_meth.add_ghost_nodes(var, discretised_symbol, bcs) with self.assertRaisesRegex(ValueError, "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"): sp_meth.add_ghost_nodes(var, discretised_symbol, bcs) with self.assertRaisesRegex(ValueError, "boundary condition must be"): sp_meth.add_neumann_values(var, discretised_symbol, bcs, var.domain)
def setUp(self): self.delta_phi_s_n = pybamm.Variable( "surface potential difference", ["negative electrode"], auxiliary_domains={"secondary": "current collector"}, ) self.delta_phi_s_p = pybamm.Variable( "surface potential difference", ["positive electrode"], auxiliary_domains={"secondary": "current collector"}, ) self.c_e_n = pybamm.Variable( "concentration", domain=["negative electrode"], auxiliary_domains={"secondary": "current collector"}, ) self.c_e_p = pybamm.Variable( "concentration", domain=["positive electrode"], auxiliary_domains={"secondary": "current collector"}, ) self.c_s_n_surf = pybamm.Variable( "particle surface conc", domain=["negative electrode"], auxiliary_domains={"secondary": "current collector"}, ) self.c_s_p_surf = pybamm.Variable( "particle surface conc", domain=["positive electrode"], auxiliary_domains={"secondary": "current collector"}, ) self.variables = { "Negative electrode surface potential difference": self.delta_phi_s_n, "Positive electrode surface potential difference": self.delta_phi_s_p, "Negative electrolyte concentration": self.c_e_n, "Positive electrolyte concentration": self.c_e_p, "Negative particle surface concentration": self.c_s_n_surf, "Positive particle surface concentration": self.c_s_p_surf, "Current collector current density": pybamm.Scalar(1), "Negative electrode temperature": 0, "Positive electrode temperature": 0, "Sum of electrolyte reaction source terms": pybamm.Scalar(1), "Sum of interfacial current densities": pybamm.Scalar(1), "Sum of negative electrode interfacial current densities": pybamm.Scalar(1), "Sum of positive electrode interfacial current densities": pybamm.Scalar(1), "Sum of x-averaged negative electrode interfacial current densities": 1, "Sum of x-averaged positive electrode interfacial current densities": 1, "Sum of negative electrode electrolyte reaction source terms": 1, "Sum of positive electrode electrolyte reaction source terms": 1, "Sum of x-averaged negative electrode electrolyte reaction source terms": 1, "Sum of x-averaged positive electrode electrolyte reaction source terms": 1, }
def test_failures(self): # this test implements a python version of the ida Roberts # example provided in sundials # see sundials ida examples pdf model = pybamm.BaseModel() model.use_jacobian = False u = pybamm.Variable("u") model.rhs = {u: -0.1 * u} model.initial_conditions = {u: 1} disc = pybamm.Discretisation() disc.process_model(model) solver = pybamm.IDAKLUSolver(root_method="lm") t_eval = np.linspace(0, 3, 100) with self.assertRaisesRegex(pybamm.SolverError, "KLU requires the Jacobian"): solver.solve(model, t_eval)
def set_soc_variables(self): "Set variables relating to the state of charge." # State of Charge defined as function of dimensionless electrolyte concentration z = pybamm.standard_spatial_vars.z soc = (pybamm.Integral( self.variables["X-averaged electrolyte concentration"], z) * 100) self.variables.update({ "State of Charge": soc, "Depth of Discharge": 100 - soc }) # Fractional charge input if "Fractional Charge Input" not in self.variables: fci = pybamm.Variable("Fractional Charge Input", domain="current collector") self.variables["Fractional Charge Input"] = fci self.rhs[fci] = -self.variables["Total current density"] * 100 self.initial_conditions[fci] = self.param.q_init * 100
def test_function_of_multiple_variables(self): a = pybamm.Variable("a") b = pybamm.Parameter("b") func = pybamm.Function(test_multi_var_function, a, b) self.assertEqual(func.name, "function (test_multi_var_function)") self.assertEqual(func.children[0].name, a.name) self.assertEqual(func.children[1].name, b.name) # test eval and diff a = pybamm.StateVector(slice(0, 1)) b = pybamm.StateVector(slice(1, 2)) y = np.array([5, 2]) func = pybamm.Function(test_multi_var_function, a, b) self.assertEqual(func.evaluate(y=y), 7) self.assertEqual(func.diff(a).evaluate(y=y), 1) self.assertEqual(func.diff(b).evaluate(y=y), 1) self.assertEqual(func.diff(func).evaluate(), 1)
def test_get_solve(self): # Create model model = pybamm.BaseModel() model.convert_to_format = "jax" domain = ["negative electrode", "separator", "positive electrode"] var = pybamm.Variable("var", domain=domain) model.rhs = {var: -pybamm.InputParameter("rate") * var} model.initial_conditions = {var: 1} # No need to set parameters; can use base discretisation (no spatial # operators) # create discretisation mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) disc.process_model(model) # test that another method string gives error with self.assertRaises(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"): solver.get_solve(model, t_eval) solver.solve(model, t_eval, inputs={"rate": 0.1}) solver = solver.get_solve(model, t_eval) y = solver({"rate": 0.1}) np.testing.assert_allclose(y[0], np.exp(-0.1 * t_eval), rtol=1e-6, atol=1e-6) y = solver({"rate": 0.2}) np.testing.assert_allclose(y[0], np.exp(-0.2 * t_eval), rtol=1e-6, atol=1e-6)
def test_boundary_value(self): a = pybamm.Scalar(1) boundary_a = pybamm.boundary_value(a, "right") self.assertEqual(boundary_a.id, a.id) boundary_broad_a = pybamm.boundary_value( pybamm.Broadcast(a, ["negative electrode"]), "left") self.assertEqual(boundary_broad_a.evaluate(), np.array([1])) a = pybamm.Symbol("a", domain=["separator"]) boundary_a = pybamm.boundary_value(a, "right") self.assertIsInstance(boundary_a, pybamm.BoundaryValue) self.assertEqual(boundary_a.side, "right") self.assertEqual(boundary_a.domain, []) self.assertEqual(boundary_a.auxiliary_domains, {}) # test with secondary domain a_sec = pybamm.Symbol( "a", domain=["separator"], auxiliary_domains={"secondary": "current collector"}, ) boundary_a_sec = pybamm.boundary_value(a_sec, "right") self.assertEqual(boundary_a_sec.domain, ["current collector"]) self.assertEqual(boundary_a_sec.auxiliary_domains, {}) # test with secondary domain and tertiary domain a_tert = pybamm.Symbol( "a", domain=["separator"], auxiliary_domains={ "secondary": "current collector", "tertiary": "bla" }, ) boundary_a_tert = pybamm.boundary_value(a_tert, "right") self.assertEqual(boundary_a_tert.domain, ["current collector"]) self.assertEqual(boundary_a_tert.auxiliary_domains, {"secondary": ["bla"]}) # error if boundary value on tabs and domain is not "current collector" var = pybamm.Variable("var", domain=["negative electrode"]) with self.assertRaisesRegex(pybamm.ModelError, "Can only take boundary"): pybamm.boundary_value(var, "negative tab") pybamm.boundary_value(var, "positive tab")
def test_delta_function(self): mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var") delta_fn_left = pybamm.DeltaFunction(var, "left", "negative electrode") delta_fn_right = pybamm.DeltaFunction(var, "right", "negative electrode") disc.set_variable_slices([var]) delta_fn_left_disc = disc.process_symbol(delta_fn_left) delta_fn_right_disc = disc.process_symbol(delta_fn_right) # Basic shape and type tests y = np.ones_like(mesh["negative electrode"][0].nodes[:, np.newaxis]) # Left self.assertEqual(delta_fn_left_disc.domain, delta_fn_left.domain) self.assertEqual(delta_fn_left_disc.auxiliary_domains, delta_fn_left.auxiliary_domains) self.assertIsInstance(delta_fn_left_disc, pybamm.Multiplication) self.assertIsInstance(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) # Right self.assertEqual(delta_fn_right_disc.domain, delta_fn_right.domain) self.assertEqual(delta_fn_right_disc.auxiliary_domains, delta_fn_right.auxiliary_domains) self.assertIsInstance(delta_fn_right_disc, pybamm.Multiplication) self.assertIsInstance(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) # Value tests # Delta function should integrate to the same thing as variable var_disc = disc.process_symbol(var) x = pybamm.standard_spatial_vars.x_n delta_fn_int_disc = disc.process_symbol( pybamm.Integral(delta_fn_left, x)) np.testing.assert_array_equal( var_disc.evaluate(y=y) * mesh["negative electrode"][0].edges[-1], np.sum(delta_fn_int_disc.evaluate(y=y)), )
def test_solve_with_symbolic_input_1D_vector_input(self): var = pybamm.Variable("var", "negative electrode") model = pybamm.BaseModel() param = pybamm.InputParameter("param", "negative electrode") model.rhs = {var: -param * var} model.initial_conditions = {var: 2} model.variables = {"var": var} # create discretisation disc = get_discretisation_for_testing() disc.process_model(model) # Solve - scalar input solver = pybamm.CasadiSolver() solution = solver.solve(model, np.linspace(0, 1)) n = disc.mesh["negative electrode"].npts solver = pybamm.CasadiSolver() t_eval = np.linspace(0, 1) solution = solver.solve(model, t_eval) p = np.linspace(0, 1, n)[:, np.newaxis] np.testing.assert_array_almost_equal( solution["var"].value({"param": 3 * np.ones(n)}), np.repeat(2 * np.exp(-3 * t_eval), 40)[:, np.newaxis], decimal=4, ) np.testing.assert_array_almost_equal( solution["var"].value({"param": 2 * p}), 2 * np.exp(-2 * p * t_eval).T.reshape(-1, 1), decimal=4, ) np.testing.assert_array_almost_equal( solution["var"].sensitivity({"param": 3 * np.ones(n)}), np.kron(-2 * t_eval * np.exp(-3 * t_eval), np.eye(40)).T, decimal=4, ) sens = solution["var"].sensitivity({"param": p}).full() for idx, t in enumerate(t_eval): np.testing.assert_array_almost_equal( sens[40 * idx:40 * (idx + 1), :], -2 * t * np.exp(-p * t) * np.eye(40), decimal=4, )
def test_solver_sensitivities(self): # Create model model = pybamm.BaseModel() model.convert_to_format = "jax" domain = ["negative electrode", "separator", "positive electrode"] var = pybamm.Variable("var", domain=domain) model.rhs = {var: -pybamm.InputParameter("rate") * var} model.initial_conditions = {var: 1.0} # No need to set parameters; can use base discretisation (no spatial operators) # create discretisation mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) disc.process_model(model) for method in ['RK45', 'BDF']: # Solve solver = pybamm.JaxSolver( method=method, rtol=1e-8, atol=1e-8 ) t_eval = np.linspace(0, 1, 80) h = 0.0001 rate = 0.1 # need to solve the model once to get it set up by the base solver solver.solve(model, t_eval, inputs={'rate': rate}) solve = solver.get_solve(model, t_eval) # create a dummy "model" where we calculate the sum of the time series def solve_model(rate): return jax.numpy.sum(solve({'rate': rate})) # check answers with finite difference eval_plus = solve_model(rate + h) eval_neg = solve_model(rate - h) grad_num = (eval_plus - eval_neg) / (2 * h) grad_solve = jax.jit(jax.grad(solve_model)) grad = grad_solve(rate) self.assertAlmostEqual(grad, grad_num, places=1)
def test_processed_variable_2D(self): t = pybamm.t var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) eqn = t * var + x # On nodes disc = tests.get_discretisation_for_testing() disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] var_sol = disc.process_symbol(var) eqn_sol = disc.process_symbol(eqn) t_sol = np.linspace(0, 1) y_sol = np.ones_like(x_sol)[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable(var_sol, t_sol, y_sol, mesh=disc.mesh) np.testing.assert_array_equal(processed_var.entries[1:-1], y_sol) np.testing.assert_array_equal(processed_var(t_sol, x_sol), y_sol) processed_eqn = pybamm.ProcessedVariable(eqn_sol, t_sol, y_sol, mesh=disc.mesh) np.testing.assert_array_equal(processed_eqn(t_sol, x_sol), t_sol * y_sol + x_sol[:, np.newaxis]) # Test extrapolation np.testing.assert_array_equal(processed_var.entries[0], 2 * y_sol[0] - y_sol[1]) np.testing.assert_array_equal(processed_var.entries[1], 2 * y_sol[-1] - y_sol[-2]) # On edges x_s_edge = pybamm.Matrix(disc.mesh["separator"][0].edges, domain="separator") processed_x_s_edge = pybamm.ProcessedVariable(x_s_edge, t_sol, y_sol, disc.mesh) np.testing.assert_array_equal(x_s_edge.entries[:, 0], processed_x_s_edge.entries[1:-1, 0])
def test_model_ode_integrate_failure(self): # Turn off warnings to ignore sqrt error warnings.simplefilter("ignore") model = pybamm.BaseModel() var = pybamm.Variable("var") model.rhs = {var: -pybamm.sqrt(var)} model.initial_conditions = {var: 1} disc = pybamm.Discretisation() disc.process_model(model) t_eval = np.linspace(0, 3, 100) solver = pybamm.ScikitsOdeSolver() # Expect solver to fail when y goes negative with self.assertRaises(pybamm.SolverError): solver.solve(model, t_eval) # Turn warnings back on warnings.simplefilter("default")
def test_process_with_no_check(self): # create model whole_cell = ["negative electrode", "separator", "positive electrode"] c = pybamm.Variable("c", domain=whole_cell) N = pybamm.grad(c) model = pybamm.BaseModel() model.rhs = {c: pybamm.div(N)} model.initial_conditions = {c: pybamm.Scalar(3)} model.boundary_conditions = { c: { "left": (0, "Neumann"), "right": (0, "Neumann") } } model.variables = {"c": c, "N": N} # create discretisation disc = get_discretisation_for_testing() disc.process_model(model, check_model=False)
def test_processed_var_3D_scikit_interpolation(self): var = pybamm.Variable("var", domain=["current collector"]) y = pybamm.SpatialVariable("y", domain=["current collector"]) z = pybamm.SpatialVariable("z", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() disc.set_variable_slices([var]) y_sol = disc.process_symbol(y).entries[:, 0] z_sol = disc.process_symbol(z).entries[:, 0] var_sol = disc.process_symbol(var) t_sol = np.linspace(0, 1) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable(var_sol, t_sol, u_sol, mesh=disc.mesh) # 3 vectors np.testing.assert_array_equal( processed_var(t_sol, y=y_sol, z=z_sol).shape, (15, 15, 50)) np.testing.assert_array_equal( processed_var(t_sol, y=y_sol, z=z_sol), np.reshape( u_sol, [len(y_sol), len(z_sol), len(t_sol)]), ) # 2 vectors, 1 scalar np.testing.assert_array_equal( processed_var(0.5, y=y_sol, z=z_sol).shape, (15, 15)) np.testing.assert_array_equal( processed_var(t_sol, y=0.2, z=z_sol).shape, (15, 50)) np.testing.assert_array_equal( processed_var(t_sol, y=y_sol, z=0.5).shape, (15, 50)) # 1 vectors, 2 scalar np.testing.assert_array_equal( processed_var(0.5, y=0.2, z=z_sol).shape, (15, )) np.testing.assert_array_equal( processed_var(0.5, y=y_sol, z=0.5).shape, (15, )) np.testing.assert_array_equal( processed_var(t_sol, y=0.2, z=0.5).shape, (50, )) # 3 scalars np.testing.assert_array_equal( processed_var(0.2, y=0.2, z=0.2).shape, ())
def test_concatenation_of_scalars(self): whole_cell = ["negative electrode", "separator", "positive electrode"] a = pybamm.PrimaryBroadcast(5, ["negative electrode"]) b = pybamm.PrimaryBroadcast(4, ["separator"]) # create discretisation disc = get_discretisation_for_testing() mesh = disc.mesh variables = [pybamm.Variable("var", domain=whole_cell)] disc.set_variable_slices(variables) eqn = pybamm.Concatenation(a, b) eqn_disc = disc.process_symbol(eqn) expected_vector = np.concatenate([ 5 * np.ones_like(mesh["negative electrode"][0].nodes), 4 * np.ones_like(mesh["separator"][0].nodes), ])[:, np.newaxis] np.testing.assert_allclose(eqn_disc.evaluate(), expected_vector)
def test_timescale_input_fail(self): # Make sure timescale can't depend on inputs model = pybamm.BaseModel() v = pybamm.Variable("v") model.rhs = {v: -1} model.initial_conditions = {v: 1} a = pybamm.InputParameter("a") model.timescale = a solver = pybamm.CasadiSolver() solver.set_up(model, inputs={"a": 10}) sol = solver.step(old_solution=None, model=model, dt=1.0, inputs={"a": 10}) with self.assertRaisesRegex(pybamm.SolverError, "The model timescale"): sol = solver.step(old_solution=sol, model=model, dt=1.0, inputs={"a": 20})
def test_definite_integral(self): mesh = get_2p1d_mesh_for_testing() spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="current collector") y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) integral_eqn = pybamm.Integral(var, [y, z]) disc.set_variable_slices([var]) integral_eqn_disc = disc.process_symbol(integral_eqn) y_test = 6 * np.ones(mesh["current collector"][0].npts) fem_mesh = mesh["current collector"][0] ly = fem_mesh.coordinates[0, -1] lz = fem_mesh.coordinates[1, -1] np.testing.assert_array_almost_equal( integral_eqn_disc.evaluate(None, y_test), 6 * ly * lz)
def test_solve_with_symbolic_input_1D_scalar_input(self): var = pybamm.Variable("var", "negative electrode") model = pybamm.BaseModel() param = pybamm.InputParameter("param") model.algebraic = {var: var + param} model.initial_conditions = {var: 2} model.variables = {"var": var} # create discretisation disc = tests.get_discretisation_for_testing() disc.process_model(model) # Solve - scalar input solver = pybamm.CasadiAlgebraicSolver() solution = solver.solve(model, [0]) np.testing.assert_array_equal(solution["var"].value({"param": 7}), -7) np.testing.assert_array_equal(solution["var"].value({"param": 3}), -3) np.testing.assert_array_equal( solution["var"].sensitivity({"param": 3}), -1)
def test_outer(self): var = pybamm.Variable("var", ["current collector"]) x = pybamm.SpatialVariable("x_s", ["separator"]) # create discretisation disc = get_1p1d_discretisation_for_testing() mesh = disc.mesh # process Outer variable disc.set_variable_slices([var]) outer = pybamm.outer(var, x) outer_disc = disc.process_symbol(outer) self.assertIsInstance(outer_disc, pybamm.Outer) self.assertIsInstance(outer_disc.children[0], pybamm.StateVector) self.assertIsInstance(outer_disc.children[1], pybamm.Vector) self.assertEqual( outer_disc.shape, (mesh["separator"][0].npts * mesh["current collector"][0].npts, 1), )
def test_solve_with_symbolic_input_in_initial_conditions(self): # Simple system: a single algebraic equation var = pybamm.Variable("var") model = pybamm.BaseModel() model.algebraic = {var: var + 2} model.initial_conditions = {var: pybamm.InputParameter("param")} model.variables = {"var": var} # create discretisation disc = pybamm.Discretisation() disc.process_model(model) # Solve solver = pybamm.CasadiAlgebraicSolver() solution = solver.solve(model, [0]) np.testing.assert_array_equal(solution["var"].value({"param": 7}), -2) np.testing.assert_array_equal(solution["var"].value({"param": 3}), -2) np.testing.assert_array_equal( solution["var"].sensitivity({"param": 3}), 0)
def test_broadcast(self): whole_cell = ["negative electrode", "separator", "positive electrode"] a = pybamm.InputParameter("a") var = pybamm.Variable("var") # create discretisation disc = get_discretisation_for_testing() mesh = disc.mesh combined_submesh = mesh.combine_submeshes(*whole_cell) # scalar broad = disc.process_symbol(pybamm.FullBroadcast(a, whole_cell, {})) np.testing.assert_array_equal( broad.evaluate(inputs={"a": 7}), 7 * np.ones_like(combined_submesh[0].nodes[:, np.newaxis]), ) self.assertEqual(broad.domain, whole_cell) broad_disc = disc.process_symbol(broad) self.assertIsInstance(broad_disc, pybamm.Multiplication) self.assertIsInstance(broad_disc.children[0], pybamm.InputParameter) self.assertIsInstance(broad_disc.children[1], pybamm.Vector) # process Broadcast variable disc.y_slices = {var.id: [slice(1)]} broad1 = pybamm.FullBroadcast(var, ["negative electrode"], None) broad1_disc = disc.process_symbol(broad1) self.assertIsInstance(broad1_disc, pybamm.Multiplication) self.assertIsInstance(broad1_disc.children[0], pybamm.StateVector) self.assertIsInstance(broad1_disc.children[1], pybamm.Vector) # broadcast to edges broad_to_edges = pybamm.FullBroadcastToEdges(a, ["negative electrode"], None) broad_to_edges_disc = disc.process_symbol(broad_to_edges) np.testing.assert_array_equal( broad_to_edges_disc.evaluate(inputs={"a": 7}), 7 * np.ones_like(mesh["negative electrode"][0].edges[:, np.newaxis]), )