def test_boundary_integral(self): mesh = get_2p1d_mesh_for_testing(include_particles=False) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="current collector") disc.set_variable_slices([var]) full = pybamm.BoundaryIntegral(var) neg = pybamm.BoundaryIntegral(var, region="negative tab") pos = pybamm.BoundaryIntegral(var, region="positive tab") full_disc = disc.process_symbol(full) neg_disc = disc.process_symbol(neg) pos_disc = disc.process_symbol(pos) # check integrating 1 gives correct *dimensionless* region lengths perimeter = 2 * (1 + 0.8) l_tab_n = 0.1 / 0.5 l_tab_p = 0.1 / 0.5 constant_y = np.ones(mesh["current collector"].npts) # Integral around boundary is exact np.testing.assert_array_almost_equal( full_disc.evaluate(None, constant_y), perimeter ) # Ideally mesh edges should line up with tab edges.... then we would get # better agreement between actual and numerical tab width np.testing.assert_array_almost_equal( neg_disc.evaluate(None, constant_y), l_tab_n, decimal=1 ) np.testing.assert_array_almost_equal( pos_disc.evaluate(None, constant_y), l_tab_p, decimal=1 )
def test_mass_matrix_inverse(self): # get mesh mesh = get_2p1d_mesh_for_testing(ypts=5, zpts=5) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } # create model a = pybamm.Variable("a", domain="negative electrode") b = pybamm.Variable("b", domain="current collector") model = pybamm.BaseModel() model.rhs = {a: pybamm.Laplacian(a), b: 4 * pybamm.Laplacian(b)} model.initial_conditions = {a: pybamm.Scalar(3), b: pybamm.Scalar(10)} model.boundary_conditions = { a: {"left": (0, "Neumann"), "right": (0, "Neumann")}, b: {"negative tab": (0, "Neumann"), "positive tab": (0, "Neumann")}, } model.variables = {"a": a, "b": b} # create discretisation disc = pybamm.Discretisation(mesh, spatial_methods) disc.process_model(model) # test that computing mass matrix block-by-block (as is done during # discretisation) gives the correct result # Note: inverse is more efficient in csc format mass_inv = inv(csc_matrix(model.mass_matrix.entries)) np.testing.assert_equal( model.mass_matrix_inv.entries.toarray(), mass_inv.toarray() )
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): spatial_method.divergence(None, None, None) with self.assertRaises(NotImplementedError): spatial_method.indefinite_integral(None, None)
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_neg_pos(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") disc.set_variable_slices([var]) extrap_neg = pybamm.BoundaryValue(var, "negative tab") extrap_pos = pybamm.BoundaryValue(var, "positive tab") extrap_neg_disc = disc.process_symbol(extrap_neg) extrap_pos_disc = disc.process_symbol(extrap_pos) # check constant returns constant at tab constant_y = np.ones(mesh["current collector"][0].npts)[:, np.newaxis] np.testing.assert_array_almost_equal( extrap_neg_disc.evaluate(None, constant_y), 1) np.testing.assert_array_almost_equal( extrap_pos_disc.evaluate(None, constant_y), 1)
def test_definite_integral_vector(self): mesh = get_2p1d_mesh_for_testing(include_particles=False) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="current collector") disc.set_variable_slices([var]) # 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) # 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)
def test_discretise_equations(self): # get mesh mesh = get_2p1d_mesh_for_testing(include_particles=False) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) # discretise some equations var = pybamm.Variable("var", domain="current collector") y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) disc.set_variable_slices([var]) y_test = np.ones(mesh["current collector"].npts) unit_source = pybamm.PrimaryBroadcast(1, "current collector") disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Neumann"), "positive tab": (pybamm.Scalar(0), "Neumann"), } } for eqn in [ pybamm.laplacian(var), pybamm.source(unit_source, var), pybamm.laplacian(var) - pybamm.source(unit_source, var), pybamm.source(var, var), pybamm.laplacian(var) - pybamm.source(2 * var, var), pybamm.laplacian(var) - pybamm.source(unit_source ** 2 + 1 / var, var), pybamm.Integral(var, [y, z]) - 1, pybamm.source(var, var, boundary=True), pybamm.laplacian(var) - pybamm.source(unit_source, var, boundary=True), pybamm.laplacian(var) - pybamm.source(unit_source ** 2 + 1 / var, var, boundary=True), ]: # Check that equation can be evaluated in each case # Dirichlet disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Dirichlet"), "positive tab": (pybamm.Scalar(1), "Dirichlet"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # Neumann disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Neumann"), "positive tab": (pybamm.Scalar(1), "Neumann"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # One of each disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Neumann"), "positive tab": (pybamm.Scalar(1), "Dirichlet"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # One of each disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Dirichlet"), "positive tab": (pybamm.Scalar(1), "Neumann"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # check ValueError raised for non Dirichlet or Neumann BCs eqn = pybamm.laplacian(var) - pybamm.source(unit_source, var) disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Dirichlet"), "positive tab": (pybamm.Scalar(1), "Other BC"), } } with self.assertRaises(ValueError): eqn_disc = disc.process_symbol(eqn) disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Other BC"), "positive tab": (pybamm.Scalar(1), "Neumann"), } } with self.assertRaises(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): 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): disc.process_symbol(x)