def test_processed_variable_2D_x_r(self): var = pybamm.Variable( "var", domain=["negative particle"], auxiliary_domains={"secondary": ["negative electrode"]}, ) x = pybamm.SpatialVariable("x", domain=["negative electrode"]) r = pybamm.SpatialVariable("r", domain=["negative particle"]) disc = tests.get_p2d_discretisation_for_testing() disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] r_sol = disc.process_symbol(r).entries[:, 0] # Keep only the first iteration of entries r_sol = r_sol[:len(r_sol) // len(x_sol)] var_sol = disc.process_symbol(var) t_sol = np.linspace(0, 1) y_sol = np.ones(len(x_sol) * len(r_sol))[:, np.newaxis] * np.linspace( 0, 5) processed_var = pybamm.ProcessedVariable(var_sol, pybamm.Solution(t_sol, y_sol)) np.testing.assert_array_equal( processed_var.entries, np.reshape( y_sol, [len(r_sol), len(x_sol), len(t_sol)]), )
def test_call_failure(self): # x domain var = pybamm.Variable("var x", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) 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) t_sol = np.linspace(0, 1) y_sol = x_sol[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable(var_sol, pybamm.Solution(t_sol, y_sol)) with self.assertRaisesRegex(ValueError, "x cannot be None"): processed_var(0) # r domain var = pybamm.Variable("var r", domain=["negative particle"]) r = pybamm.SpatialVariable("r", domain=["negative particle"]) disc = tests.get_discretisation_for_testing() disc.set_variable_slices([var]) r_sol = disc.process_symbol(r).entries[:, 0] var_sol = disc.process_symbol(var) y_sol = r_sol[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable(var_sol, pybamm.Solution(t_sol, y_sol)) with self.assertRaisesRegex(ValueError, "r cannot be None"): processed_var(0) with self.assertRaisesRegex(ValueError, "r cannot be None"): processed_var(0, 1)
def test_processed_var_2Dspace_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.array([0]) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] processed_var = pybamm.ProcessedVariable(var_sol, t_sol, u_sol, mesh=disc.mesh) # 2 vectors np.testing.assert_array_equal( processed_var(t=None, y=y_sol, z=z_sol).shape, (15, 15)) # 1 vector, 1 scalar np.testing.assert_array_equal( processed_var(t=None, y=0.2, z=z_sol).shape, (15, 1)) np.testing.assert_array_equal( processed_var(t=None, y=y_sol, z=0.5).shape, (15, )) # 2 scalars np.testing.assert_array_equal( processed_var(t=None, y=0.2, z=0.2).shape, (1, ))
def test_discretise_spatial_variable(self): # create discretisation mesh = get_mesh_for_testing() spatial_method = pybamm.SpatialMethod() spatial_method.build(mesh) # centre x1 = pybamm.SpatialVariable("x", ["negative electrode"]) x2 = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) 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) np.testing.assert_array_equal( var_disc.evaluate()[:, 0], mesh.combine_submeshes(*var.domain)[0].nodes ) # edges x1_edge = pybamm.SpatialVariableEdge("x", ["negative electrode"]) x2_edge = pybamm.SpatialVariableEdge("x", ["negative electrode", "separator"]) 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) np.testing.assert_array_equal( var_disc.evaluate()[:, 0], mesh.combine_submeshes(*var.domain)[0].edges )
def test_disc_spatial_var(self): mesh = get_unit_2p1D_mesh_for_testing(ypts=4, zpts=5, include_particles=False) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) # discretise y and z y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) y_disc = disc.process_symbol(y) z_disc = disc.process_symbol(z) # create expected meshgrid y_vec = np.linspace(0, 1, 4) z_vec = np.linspace(0, 1, 5) Y, Z = np.meshgrid(y_vec, z_vec) y_actual = np.transpose(Y).flatten()[:, np.newaxis] z_actual = np.transpose(Z).flatten()[:, np.newaxis] # 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)
def test_processed_var_2D_fixed_t_interpolation(self): var = pybamm.Variable( "var", domain=["negative particle"], auxiliary_domains={"secondary": ["negative electrode"]}, ) x = pybamm.SpatialVariable("x", domain=["negative electrode"]) r = pybamm.SpatialVariable( "r", domain=["negative particle"], auxiliary_domains={"secondary": ["negative electrode"]}, ) disc = tests.get_p2d_discretisation_for_testing() disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] r_sol = disc.process_symbol(r).entries[:, 0] # Keep only the first iteration of entries r_sol = r_sol[: len(r_sol) // len(x_sol)] var_sol = disc.process_symbol(var) t_sol = np.array([0]) y_sol = np.ones(len(x_sol) * len(r_sol))[:, np.newaxis] processed_var = pybamm.ProcessedVariable( var_sol, pybamm.Solution(t_sol, y_sol), warn=False ) # 2 vectors np.testing.assert_array_equal(processed_var(x=x_sol, r=r_sol).shape, (10, 40)) # 1 vector, 1 scalar np.testing.assert_array_equal(processed_var(x=0.2, r=r_sol).shape, (10,)) np.testing.assert_array_equal(processed_var(x=x_sol, r=0.5).shape, (40,)) # 2 scalars np.testing.assert_array_equal(processed_var(x=0.2, r=0.2).shape, ())
def test_discretise_spatial_variable(self): # create discretisation mesh = get_mesh_for_testing() spatial_methods = { "macroscale": pybamm.FiniteVolume(), "negative particle": pybamm.FiniteVolume(), "positive particle": pybamm.FiniteVolume(), } disc = pybamm.Discretisation(mesh, spatial_methods) # space x1 = pybamm.SpatialVariable("x", ["negative electrode"]) x1_disc = disc.process_symbol(x1) self.assertIsInstance(x1_disc, pybamm.Vector) np.testing.assert_array_equal( x1_disc.evaluate(), disc.mesh["negative electrode"][0].nodes[:, np.newaxis]) x2 = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) x2_disc = disc.process_symbol(x2) self.assertIsInstance(x2_disc, pybamm.Vector) np.testing.assert_array_equal( x2_disc.evaluate(), disc.mesh.combine_submeshes("negative electrode", "separator")[0].nodes[:, np.newaxis], ) r = 3 * pybamm.SpatialVariable("r", ["negative particle"]) r_disc = disc.process_symbol(r) self.assertIsInstance(r_disc, pybamm.Vector) np.testing.assert_array_equal( r_disc.evaluate(), 3 * disc.mesh["negative particle"][0].nodes[:, np.newaxis], )
def test_yz_average(self): a = pybamm.Scalar(1) z_average_a = pybamm.z_average(a) yz_average_a = pybamm.yz_average(a) self.assertEqual(z_average_a.id, a.id) self.assertEqual(yz_average_a.id, a.id) z_average_broad_a = pybamm.z_average( pybamm.PrimaryBroadcast(a, ["current collector"])) yz_average_broad_a = pybamm.yz_average( pybamm.PrimaryBroadcast(a, ["current collector"])) self.assertEqual(z_average_broad_a.evaluate(), np.array([1])) self.assertEqual(yz_average_broad_a.evaluate(), np.array([1])) a = pybamm.Variable("a", domain=["current collector"]) y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) z_av_a = pybamm.z_average(a) yz_av_a = pybamm.yz_average(a) self.assertIsInstance(yz_av_a, pybamm.Division) self.assertIsInstance(z_av_a, pybamm.Division) self.assertIsInstance(z_av_a.children[0], pybamm.Integral) self.assertIsInstance(yz_av_a.children[0], pybamm.Integral) self.assertEqual(z_av_a.children[0].integration_variable[0].domain, z.domain) self.assertEqual(yz_av_a.children[0].integration_variable[0].domain, y.domain) self.assertEqual(yz_av_a.children[0].integration_variable[1].domain, z.domain) self.assertIsInstance(z_av_a.children[1], pybamm.Integral) self.assertIsInstance(yz_av_a.children[1], pybamm.Integral) self.assertEqual(z_av_a.children[1].integration_variable[0].domain, a.domain) self.assertEqual(z_av_a.children[1].children[0].id, pybamm.ones_like(a).id) self.assertEqual(yz_av_a.children[1].integration_variable[0].domain, y.domain) self.assertEqual(yz_av_a.children[1].integration_variable[0].domain, z.domain) self.assertEqual(yz_av_a.children[1].children[0].id, pybamm.ones_like(a).id) self.assertEqual(z_av_a.domain, []) self.assertEqual(yz_av_a.domain, []) a = pybamm.Symbol("a", domain="bad domain") with self.assertRaises(pybamm.DomainError): pybamm.z_average(a) with self.assertRaises(pybamm.DomainError): pybamm.yz_average(a) # average of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the z-average of a symbol that evaluates on edges" ): pybamm.z_average(symbol_on_edges)
def test_processed_var_2D_secondary_broadcast(self): var = pybamm.Variable("var", domain=["negative particle"]) broad_var = pybamm.SecondaryBroadcast(var, "negative electrode") x = pybamm.SpatialVariable("x", domain=["negative electrode"]) r = pybamm.SpatialVariable("r", domain=["negative particle"]) disc = tests.get_discretisation_for_testing() disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] r_sol = disc.process_symbol(r).entries[:, 0] var_sol = disc.process_symbol(broad_var) t_sol = np.linspace(0, 1) y_sol = np.ones(len(x_sol) * len(r_sol))[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable( var_sol, pybamm.Solution(t_sol, y_sol), warn=False ) # 3 vectors np.testing.assert_array_equal( processed_var(t_sol, x_sol, r_sol).shape, (10, 40, 50) ) np.testing.assert_array_equal( processed_var(t_sol, x_sol, r_sol), np.reshape(y_sol, [len(r_sol), len(x_sol), len(t_sol)]), ) # 2 vectors, 1 scalar np.testing.assert_array_equal(processed_var(0.5, x_sol, r_sol).shape, (10, 40)) np.testing.assert_array_equal(processed_var(t_sol, 0.2, r_sol).shape, (10, 50)) np.testing.assert_array_equal(processed_var(t_sol, x_sol, 0.5).shape, (40, 50)) # 1 vectors, 2 scalar np.testing.assert_array_equal(processed_var(0.5, 0.2, r_sol).shape, (10,)) np.testing.assert_array_equal(processed_var(0.5, x_sol, 0.5).shape, (40,)) np.testing.assert_array_equal(processed_var(t_sol, 0.2, 0.5).shape, (50,)) # 3 scalars np.testing.assert_array_equal(processed_var(0.2, 0.2, 0.2).shape, ()) # positive particle var = pybamm.Variable("var", domain=["positive particle"]) broad_var = pybamm.SecondaryBroadcast(var, "positive electrode") x = pybamm.SpatialVariable("x", domain=["positive electrode"]) r = pybamm.SpatialVariable("r", domain=["positive particle"]) disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] r_sol = disc.process_symbol(r).entries[:, 0] var_sol = disc.process_symbol(broad_var) t_sol = np.linspace(0, 1) y_sol = np.ones(len(x_sol) * len(r_sol))[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable( var_sol, pybamm.Solution(t_sol, y_sol), warn=False ) # 3 vectors np.testing.assert_array_equal( processed_var(t_sol, x_sol, r_sol).shape, (10, 35, 50) )
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) var_casadi = to_casadi(var_sol, y_sol) processed_var = pybamm.ProcessedVariable( [var_sol], [var_casadi], pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}), 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"] x_s_casadi = to_casadi(x_s_edge, y_sol) processed_x_s_edge = pybamm.ProcessedVariable( [x_s_edge], [x_s_casadi], pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}), warn=False, ) np.testing.assert_array_equal( x_s_edge.entries.flatten(), processed_x_s_edge.entries[:, :, 0].T.flatten())
def test_call_failure(self): # x domain var = pybamm.Variable("var x", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) 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) t_sol = np.linspace(0, 1) y_sol = x_sol[:, np.newaxis] * np.linspace(0, 5) var_casadi = to_casadi(var_sol, y_sol) processed_var = pybamm.ProcessedVariable( [var_sol], [var_casadi], pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}), warn=False, ) with self.assertRaisesRegex(ValueError, "x cannot be None"): processed_var(0) # r domain var = pybamm.Variable("var r", domain=["negative particle"]) r = pybamm.SpatialVariable( "r", domain=["negative particle"], auxiliary_domains={"secondary": ["negative electrode"]}, ) disc = tests.get_discretisation_for_testing() disc.set_variable_slices([var]) r_sol = disc.process_symbol(r).entries[:, 0] var_sol = disc.process_symbol(var) y_sol = r_sol[:, np.newaxis] * np.linspace(0, 5) var_casadi = to_casadi(var_sol, y_sol) processed_var = pybamm.ProcessedVariable( [var_sol], [var_casadi], pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}), warn=False, ) with self.assertRaisesRegex(ValueError, "r cannot be None"): processed_var(0) with self.assertRaisesRegex(ValueError, "r cannot be None"): processed_var(0, 1) # t is None but len(solution.t) > 1 with self.assertRaisesRegex(ValueError, "t cannot be None"): processed_var()
def test_adding_1D_external_variable(self): model = pybamm.BaseModel() a = pybamm.Variable("a", domain=["test"]) b = pybamm.Variable("b", domain=["test"]) model.rhs = {a: a * b} model.boundary_conditions = { a: { "left": (0, "Dirichlet"), "right": (0, "Dirichlet") } } model.initial_conditions = {a: 0} model.external_variables = [b] model.variables = { "a": a, "b": b, "c": a * b, "grad b": pybamm.grad(b), "div grad b": pybamm.div(pybamm.grad(b)), } x = pybamm.SpatialVariable("x", domain="test", coord_sys="cartesian") geometry = { "test": { "primary": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } } } submesh_types = {"test": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)} var_pts = {x: 10} mesh = pybamm.Mesh(geometry, submesh_types, var_pts) spatial_methods = {"test": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) disc.process_model(model) self.assertEqual(disc.y_slices[a.id][0], slice(0, 10, None)) self.assertEqual(model.y_slices[a][0], slice(0, 10, None)) b_test = np.ones((10, 1)) np.testing.assert_array_equal( model.variables["b"].evaluate(inputs={"b": b_test}), b_test) # check that b is added to the boundary conditions model.bcs[b.id]["left"] model.bcs[b.id]["right"] # check that grad and div(grad ) produce the correct shapes self.assertEqual(model.variables["b"].shape_for_testing, (10, 1)) self.assertEqual(model.variables["grad b"].shape_for_testing, (11, 1)) self.assertEqual(model.variables["div grad b"].shape_for_testing, (10, 1))
def test_symmetric_mesh_creation_no_parameters_odd(self): r = pybamm.SpatialVariable("r", domain=["negative particle"], coord_sys="spherical polar") geometry = { "negative particle": { r: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } } submesh_params = {"side": "symmetric", "stretch": 1.5} submesh_types = { "negative particle": pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, submesh_params) } var_pts = {r: 21} # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative particle"].edges[0], 0) self.assertEqual(mesh["negative particle"].edges[-1], 1) # check number of edges and nodes self.assertEqual(len(mesh["negative particle"].nodes), var_pts[r]) self.assertEqual( len(mesh["negative particle"].edges), len(mesh["negative particle"].nodes) + 1, )
def test_r_average(self): a = pybamm.Scalar(1) average_a = pybamm.r_average(a) self.assertEqual(average_a.id, a.id) average_broad_a = pybamm.r_average( pybamm.PrimaryBroadcast(a, ["negative particle"])) self.assertEqual(average_broad_a.evaluate(), np.array([1])) for domain in [["negative particle"], ["positive particle"]]: a = pybamm.Symbol("a", domain=domain) r = pybamm.SpatialVariable("r", domain) av_a = pybamm.r_average(a) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, r.domain) # electrode domains go to current collector when averaged self.assertEqual(av_a.domain, []) # r-average of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the r-average of a symbol that evaluates on edges" ): pybamm.r_average(symbol_on_edges)
def test_mesh_creation_no_parameters(self): r = pybamm.SpatialVariable("r", domain=["negative particle"], coord_sys="spherical polar") geometry = { "negative particle": { "primary": { r: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } } } submesh_types = { "negative particle": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh) } var_pts = {r: 20} mesh = pybamm.Mesh(geometry, submesh_types, var_pts) # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative particle"][0].edges[0], 0) self.assertEqual(mesh["negative particle"][0].edges[-1], 1) # check number of edges and nodes self.assertEqual(len(mesh["negative particle"][0].nodes), var_pts[r]) self.assertEqual( len(mesh["negative particle"][0].edges), len(mesh["negative particle"][0].nodes) + 1, )
def test_mesh_creation_no_parameters(self): r = pybamm.SpatialVariable("r", domain=["negative particle"], coord_sys="spherical polar") geometry = { "negative particle": { r: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } } edges = np.array([0, 0.3, 1]) submesh_params = {"edges": edges} submesh_types = { "negative particle": pybamm.MeshGenerator(pybamm.UserSupplied1DSubMesh, submesh_params) } var_pts = {r: len(edges) - 1} # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative particle"].edges[0], 0) self.assertEqual(mesh["negative particle"].edges[-1], 1) # check number of edges and nodes self.assertEqual(len(mesh["negative particle"].nodes), var_pts[r]) self.assertEqual( len(mesh["negative particle"].edges), len(mesh["negative particle"].nodes) + 1, )
def test_processed_variable_1D_unknown_domain(self): x = pybamm.SpatialVariable("x", domain="SEI layer", coord_sys="cartesian") geometry = pybamm.Geometry() geometry.add_domain( "SEI layer", { "primary": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } }, ) submesh_types = {"SEI layer": pybamm.Uniform1DSubMesh} var_pts = {x: 100} mesh = pybamm.Mesh(geometry, submesh_types, var_pts) nt = 100 solution = pybamm.Solution( np.linspace(0, 1, nt), np.zeros((var_pts[x], nt)), np.linspace(0, 1, 1), np.zeros((var_pts[x])), "test", ) c = pybamm.StateVector(slice(0, var_pts[x]), domain=["SEI layer"]) c.mesh = mesh["SEI layer"] pybamm.ProcessedVariable(c, solution)
def r_average(symbol): """convenience function for creating an average in the r-direction Parameters ---------- symbol : :class:`pybamm.Symbol` The function to be averaged Returns ------- :class:`Symbol` the new averaged symbol """ # If symbol doesn't have a particle domain, its r-averaged value is itself if symbol.domain not in [["positive particle"], ["negative particle"]]: new_symbol = symbol.new_copy() new_symbol.parent = None return new_symbol # If symbol is a Broadcast, its average value is its child elif isinstance(symbol, pybamm.Broadcast): return symbol.orphans[0] else: r = pybamm.SpatialVariable("r", symbol.domain) v = pybamm.Broadcast(pybamm.Scalar(1), symbol.domain) return Integral(symbol, r) / Integral(v, r)
def test_processed_variable_1D_unknown_domain(self): x = pybamm.SpatialVariable("x", domain="SEI layer", coord_sys="cartesian") geometry = pybamm.Geometry({ "SEI layer": { x: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } }) submesh_types = {"SEI layer": pybamm.Uniform1DSubMesh} var_pts = {x: 100} mesh = pybamm.Mesh(geometry, submesh_types, var_pts) nt = 100 y_sol = np.zeros((var_pts[x], nt)) solution = pybamm.Solution( np.linspace(0, 1, nt), y_sol, pybamm.BaseModel(), {}, np.linspace(0, 1, 1), np.zeros((var_pts[x])), "test", ) c = pybamm.StateVector(slice(0, var_pts[x]), domain=["SEI layer"]) c.mesh = mesh["SEI layer"] c_casadi = to_casadi(c, y_sol) pybamm.ProcessedVariable([c], [c_casadi], solution, warn=False)
def test_processed_var_1D_fixed_t_interpolation(self): var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) eqn = var + x disc = tests.get_discretisation_for_testing() disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] eqn_sol = disc.process_symbol(eqn) t_sol = np.array([1]) y_sol = x_sol[:, np.newaxis] eqn_casadi = to_casadi(eqn_sol, y_sol) processed_var = pybamm.ProcessedVariable( [eqn_sol], [eqn_casadi], pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}), warn=False, ) # vector np.testing.assert_array_almost_equal(processed_var(x=x_sol), 2 * x_sol[:, np.newaxis]) # scalar np.testing.assert_array_almost_equal(processed_var(x=0.5), 1)
def r_average(symbol): """convenience function for creating an average in the r-direction Parameters ---------- symbol : :class:`pybamm.Symbol` The function to be averaged Returns ------- :class:`Symbol` the new averaged symbol """ # Can't take average if the symbol evaluates on edges if symbol.evaluates_on_edges("primary"): raise ValueError( "Can't take the r-average of a symbol that evaluates on edges") # If symbol doesn't have a particle domain, its r-averaged value is itself if symbol.domain not in [["positive particle"], ["negative particle"]]: new_symbol = symbol.new_copy() new_symbol.parent = None return new_symbol # If symbol is a Broadcast, its average value is its child elif isinstance(symbol, pybamm.Broadcast): return symbol.orphans[0] else: r = pybamm.SpatialVariable("r", symbol.domain) v = pybamm.FullBroadcast(pybamm.Scalar(1), symbol.domain, symbol.auxiliary_domains) return Integral(symbol, r) / Integral(v, r)
def test_processed_variable_1D(self): var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) eqn = var + x # On nodes disc = tests.get_discretisation_for_testing() disc.set_variable_slices([var]) x_sol = disc.process_symbol(x).entries[:, 0] eqn_sol = disc.process_symbol(eqn) # With scalar t_sol t_sol = [0] y_sol = np.ones_like(x_sol)[:, np.newaxis] * 5 sol = pybamm.Solution(t_sol, y_sol) processed_eqn = pybamm.ProcessedSymbolicVariable(eqn_sol, sol) np.testing.assert_array_equal( processed_eqn.value(), y_sol + x_sol[:, np.newaxis] ) # With vector t_sol t_sol = np.linspace(0, 1) y_sol = np.ones_like(x_sol)[:, np.newaxis] * np.linspace(0, 5) sol = pybamm.Solution(t_sol, y_sol) processed_eqn = pybamm.ProcessedSymbolicVariable(eqn_sol, sol) np.testing.assert_array_equal( processed_eqn.value(), (y_sol + x_sol[:, np.newaxis]).T.reshape(-1, 1) )
def errors(pts, function, method_options, bcs=None): domain = "test" x = pybamm.SpatialVariable("x", domain=domain) geometry = { domain: {"primary": {x: {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}}} } submesh_types = {domain: pybamm.MeshGenerator(pybamm.Uniform1DSubMesh)} var_pts = {x: pts} mesh = pybamm.Mesh(geometry, submesh_types, var_pts) spatial_methods = {"test": pybamm.FiniteVolume(method_options)} disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var", domain="test") left_extrap = pybamm.BoundaryValue(var, "left") right_extrap = pybamm.BoundaryValue(var, "right") if bcs: model = pybamm.BaseBatteryModel() bc_dict = {var: bcs} model.boundary_conditions = bc_dict disc.bcs = disc.process_boundary_conditions(model) submesh = mesh["test"] y, l_true, r_true = function(submesh[0].nodes) disc.set_variable_slices([var]) left_extrap_processed = disc.process_symbol(left_extrap) right_extrap_processed = disc.process_symbol(right_extrap) l_error = np.abs(l_true - left_extrap_processed.evaluate(None, y)) r_error = np.abs(r_true - right_extrap_processed.evaluate(None, y)) return l_error, r_error
def test_combine_geometries(self): geometry1Dmacro = pybamm.Geometry1DMacro() geometry1Dmicro = pybamm.Geometry1DMicro() geometry = pybamm.Geometry(geometry1Dmacro, geometry1Dmicro) self.assertEqual( set(geometry.keys()), set( [ "negative electrode", "separator", "positive electrode", "negative particle", "positive particle", "current collector", ] ), ) # update with custom geometry whole_cell = ["negative electrode", "separator", "positive electrode"] x = pybamm.SpatialVariable("x", whole_cell) custom_geometry = { "negative electrode": { "primary": {x: {"min": pybamm.Scalar(1), "max": pybamm.Scalar(2)}} } } geometry = pybamm.Geometry( geometry1Dmacro, geometry1Dmicro, custom_geometry=custom_geometry ) self.assertEqual( geometry["negative electrode"], custom_geometry["negative electrode"] )
def test_mesh_creation_no_parameters(self): r = pybamm.SpatialVariable("r", domain=["negative particle"], coord_sys="spherical polar") geometry = { "negative particle": { r: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) } } } edges = np.array([0, 0.3, 1]) order = 3 submesh_params = {"edges": edges, "order": order} submesh_types = { "negative particle": pybamm.MeshGenerator(pybamm.SpectralVolume1DSubMesh, submesh_params) } var_pts = {r: len(edges) - 1} # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative particle"].edges[0], 0) self.assertEqual(mesh["negative particle"].edges[-1], 1) # check number of edges and nodes self.assertEqual(len(mesh["negative particle"].sv_nodes), var_pts[r]) self.assertEqual(len(mesh["negative particle"].nodes), order * var_pts[r]) self.assertEqual( len(mesh["negative particle"].edges), len(mesh["negative particle"].nodes) + 1, ) # check Chebyshev subdivision locations for (a, b) in zip(mesh["negative particle"].edges.tolist(), [0, 0.075, 0.225, 0.3, 0.475, 0.825, 1]): self.assertAlmostEqual(a, b) # test uniform submesh creation submesh_params = {"order": order} submesh_types = { "negative particle": pybamm.MeshGenerator(pybamm.SpectralVolume1DSubMesh, submesh_params) } var_pts = {r: 2} # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) for (a, b) in zip(mesh["negative particle"].edges.tolist(), [0.0, 0.125, 0.375, 0.5, 0.625, 0.875, 1.0]): self.assertAlmostEqual(a, b)
def test_pure_neumann_poisson(self): # grad^2 u = 1, du/dz = 1 at z = 1, du/dn = 0 elsewhere, u has zero average u = pybamm.Variable("u", domain="current collector") c = pybamm.Variable("c") # lagrange multiplier y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) model = pybamm.BaseModel() # 0*c hack otherwise gives KeyError model.algebraic = { u: pybamm.laplacian(u) - pybamm.source(1, u) + c * pybamm.DefiniteIntegralVector(u, vector_type="column"), c: pybamm.Integral(u, [y, z]) + 0 * c, } model.initial_conditions = {u: pybamm.Scalar(0), c: pybamm.Scalar(0)} # set boundary conditions ("negative tab" = bottom of unit square, # "positive tab" = top of unit square, elsewhere normal derivative is zero) model.boundary_conditions = { u: { "negative tab": (0, "Neumann"), "positive tab": (1, "Neumann") } } model.variables = {"c": c, "u": u} # create discretisation mesh = get_unit_2p1D_mesh_for_testing(ypts=32, zpts=32, include_particles=False) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) disc.process_model(model) # solve model solver = pybamm.AlgebraicSolver() solution = solver.solve(model) z = mesh["current collector"].coordinates[1, :][:, np.newaxis] u_exact = z**2 / 2 - 1 / 6 np.testing.assert_array_almost_equal(solution.y[:-1], u_exact, decimal=1)
def test_processed_variable_ode_pde_solution(self): # without space model = pybamm.BaseBatteryModel() c = pybamm.Variable("conc") model.rhs = {c: -c} model.initial_conditions = {c: 1} model.variables = {"c": c} modeltest = tests.StandardModelTest(model) modeltest.test_all() t_sol, y_sol = modeltest.solution.t, modeltest.solution.y processed_vars = pybamm.post_process_variables(model.variables, t_sol, y_sol) np.testing.assert_array_almost_equal(processed_vars["c"](t_sol), np.exp(-t_sol)) # with space # set up and solve model whole_cell = ["negative electrode", "separator", "positive electrode"] model = pybamm.BaseBatteryModel() c = pybamm.Variable("conc", domain=whole_cell) c_s = pybamm.Variable( "particle conc", domain="negative particle", auxiliary_domains={"secondary": ["negative electrode"]}, ) model.rhs = {c: -c, c_s: 1 - c_s} model.initial_conditions = {c: 1, c_s: 0.5} model.boundary_conditions = { c: { "left": (0, "Neumann"), "right": (0, "Neumann") }, c_s: { "left": (0, "Neumann"), "right": (0, "Neumann") }, } model.variables = { "c": c, "N": pybamm.grad(c), "c_s": c_s, "N_s": pybamm.grad(c_s), } modeltest = tests.StandardModelTest(model) modeltest.test_all() # set up testing t_sol, y_sol = modeltest.solution.t, modeltest.solution.y x = pybamm.SpatialVariable("x", domain=whole_cell) x_sol = modeltest.disc.process_symbol(x).entries[:, 0] processed_vars = pybamm.post_process_variables(model.variables, t_sol, y_sol, modeltest.disc.mesh) # test np.testing.assert_array_almost_equal( processed_vars["c"](t_sol, x_sol), np.ones_like(x_sol)[:, np.newaxis] * np.exp(-t_sol), )
def test_processed_var_1D_interpolation(self): t = pybamm.t var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) eqn = t * var + x 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 = x_sol[:, np.newaxis] * np.linspace(0, 5) processed_var = pybamm.ProcessedVariable( var_sol, pybamm.Solution(t_sol, y_sol), warn=False ) # 2 vectors np.testing.assert_array_almost_equal(processed_var(t_sol, x_sol), y_sol) # 1 vector, 1 scalar np.testing.assert_array_almost_equal( processed_var(0.5, x_sol)[:, 0], 2.5 * x_sol ) np.testing.assert_array_equal( processed_var(t_sol, x_sol[-1]), x_sol[-1] * np.linspace(0, 5) ) # 2 scalars np.testing.assert_array_almost_equal( processed_var(0.5, x_sol[-1]), 2.5 * x_sol[-1] ) processed_eqn = pybamm.ProcessedVariable( eqn_sol, pybamm.Solution(t_sol, y_sol), warn=False ) # 2 vectors np.testing.assert_array_almost_equal( 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, 1)) self.assertEqual(processed_eqn(t_sol[4:9], x_sol[-1]).shape, (5,)) # 2 scalars self.assertEqual(processed_eqn(0.5, x_sol[-1]).shape, (1,)) # test x processed_x = pybamm.ProcessedVariable( disc.process_symbol(x), pybamm.Solution(t_sol, y_sol), warn=False ) np.testing.assert_array_almost_equal(processed_x(x=x_sol), x_sol[:, np.newaxis]) # On microscale r_n = pybamm.Matrix( disc.mesh["negative particle"].nodes, domain="negative particle" ) r_n.mesh = disc.mesh["negative particle"] processed_r_n = pybamm.ProcessedVariable( r_n, pybamm.Solution(t_sol, y_sol), warn=False ) np.testing.assert_array_equal(r_n.entries[:, 0], processed_r_n.entries[:, 0])
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_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, ())