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]) 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_init_failure(self): geometry = pybamm.battery_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), } with self.assertRaisesRegex(KeyError, "Points not given"): pybamm.Mesh(geometry, submesh_types, {}) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 12} geometry = pybamm.battery_geometry(current_collector_dimension=1) with self.assertRaisesRegex(KeyError, "Points not given"): pybamm.Mesh(geometry, submesh_types, var_pts) # Not processing geometry parameters geometry = pybamm.battery_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), } with self.assertRaisesRegex(pybamm.DiscretisationError, "Parameter values"): pybamm.Mesh(geometry, submesh_types, var_pts) # Geometry has an unrecognized variable type var = pybamm.standard_spatial_vars geometry["negative electrode"] = { var.x_n: { "min": 0, "max": pybamm.Variable("var") } } with self.assertRaisesRegex(NotImplementedError, "for symbol var"): pybamm.Mesh(geometry, submesh_types, var_pts)
def test_init_failure(self): 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), } geometry = pybamm.battery_geometry(include_particles=False, current_collector_dimension=2) with self.assertRaises(KeyError): pybamm.Mesh(geometry, submesh_types, {}) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.y: 10, var.z: 10} # there are parameters in the variables that need to be processed with self.assertRaisesRegex( pybamm.DiscretisationError, "Parameter values have not yet been set for geometry", ): pybamm.Mesh(geometry, submesh_types, var_pts) lims = {var.x_n: {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}} with self.assertRaises(pybamm.GeometryError): pybamm.ScikitUniform2DSubMesh(lims, None) lims = { var.x_n: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, var.x_p: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, } with self.assertRaises(pybamm.DomainError): pybamm.ScikitUniform2DSubMesh(lims, None) lims = { var.y: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, var.z: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, } npts = {var.y.id: 10, var.z.id: 10} var.z.coord_sys = "not cartesian" with self.assertRaises(pybamm.DomainError): pybamm.ScikitUniform2DSubMesh(lims, npts) var.z.coord_sys = "cartesian"
def test_init_failure(self): 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), } geometry = pybamm.Geometryxp1DMacro(cc_dimension=2) with self.assertRaises(KeyError): pybamm.Mesh(geometry, submesh_types, {}) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.y: 10, var.z: 10} # there are parameters in the variables that need to be processed with self.assertRaises(NotImplementedError): pybamm.Mesh(geometry, submesh_types, var_pts) lims = {var.x_n: {"min": pybamm.Scalar(0), "max": pybamm.Scalar(1)}} with self.assertRaises(pybamm.GeometryError): pybamm.ScikitUniform2DSubMesh(lims, None, None) lims = { var.x_n: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, var.x_p: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, } with self.assertRaises(pybamm.DomainError): pybamm.ScikitUniform2DSubMesh(lims, None, None) lims = { var.y: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, var.z: { "min": pybamm.Scalar(0), "max": pybamm.Scalar(1) }, } npts = {var.y.id: 10, var.z.id: 10} var.z.coord_sys = "not cartesian" with self.assertRaises(pybamm.DomainError): pybamm.ScikitUniform2DSubMesh(lims, npts, None) var.z.coord_sys = "cartesian"
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 get_mesh_for_testing( xpts=None, rpts=10, ypts=15, zpts=15, geometry=None, cc_submesh=None, order=2 ): 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.SpectralVolume1DSubMesh, {"order": order} ), "separator": pybamm.MeshGenerator( pybamm.SpectralVolume1DSubMesh, {"order": order} ), "positive electrode": pybamm.MeshGenerator( pybamm.SpectralVolume1DSubMesh, {"order": order} ), "negative particle": pybamm.MeshGenerator( pybamm.SpectralVolume1DSubMesh, {"order": order} ), "positive particle": pybamm.MeshGenerator( pybamm.SpectralVolume1DSubMesh, {"order": order} ), "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_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_parameters_defaults_lead_acid(self): # Tests on how the parameters interact param = pybamm.Parameters(chemistry="lead-acid") mesh = pybamm.Mesh(param, 10) param.set_mesh(mesh) # Dimensionless lengths sum to 1 self.assertAlmostEqual( param.geometric.ln + param.geometric.ls + param.geometric.lp, 1, places=10 ) # Diffusional C-rate should be smaller than C-rate self.assertLess(param.electrolyte.Cd, param.electrical.Crate) # # Dimensionless electrode conductivities should be large self.assertGreater(param.neg_electrode.iota_s, 10) self.assertGreater(param.pos_electrode.iota_s, 10) # # Dimensionless double-layer capacity should be small self.assertLess(param.neg_reactions.gamma_dl, 1e-3) self.assertLess(param.pos_reactions.gamma_dl, 1e-3) # # Volume change positive in negative electrode and negative in positive # # electrode self.assertGreater(param.neg_volume_changes.DeltaVsurf, 0) self.assertLess(param.pos_volume_changes.DeltaVsurf, 0) # # Excluded volume fraction should be less than 1 self.assertLess(param.lead_acid_misc.pi_os, 1e-4)
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_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_changing_grid(self): model = pybamm.lithium_ion.SPM() solver = pybamm.IDAKLUSolver() # load parameter values and geometry geometry = model.default_geometry param = model.default_parameter_values # Process parameters param.process_model(model) param.process_geometry(geometry) # Calculate time for each solver and each number of grid points var = pybamm.standard_spatial_vars t_eval = np.linspace(0, 3600, 100) for npts in [100, 200]: # discretise var_pts = { spatial_var: npts for spatial_var in [var.x_n, var.x_s, var.x_p, var.r_n, var.r_p] } mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) model_disc = disc.process_model(model, inplace=False) # solve solver.solve(model_disc, t_eval)
def test_loqs_spm_base(self): t_eval = np.linspace(0, 0.01, 2) # SPM for model in [pybamm.lithium_ion.SPM(), pybamm.lead_acid.LOQS()]: geometry = model.default_geometry param = model.default_parameter_values param.process_model(model) param.process_geometry(geometry) mesh = pybamm.Mesh( geometry, model.default_submesh_types, model.default_var_pts ) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) solver = model.default_solver solution = solver.solve(model, t_eval) pybamm.QuickPlot(model, mesh, solution) # test quick plot of particle for spm if model.name == "Single Particle Model": output_variables = [ "X-averaged negative particle concentration [mol.m-3]", "X-averaged positive particle concentration [mol.m-3]", ] pybamm.QuickPlot(model, mesh, solution, output_variables)
def __init__(self, model, param=None, mesh=None, solver=None, name="unnamed"): # Defaults if param is None: param = pybamm.Parameters() if mesh is None: mesh = pybamm.Mesh(param) if solver is None: solver = pybamm.Solver() # Assign attributes self.model = model self.param = param self.mesh = mesh self.solver = solver self.name = name # Initialise simulation to prepare for solving # Set mesh dependent parameters self.param.set_mesh(self.mesh) # Create operators from solver self.operators = self.solver.operators(self.mesh) # Assign param, operators and mesh as model attributes self.model.set_simulation(self.param, self.operators, self.mesh)
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 prepare_model(model): geometry = model.default_geometry # load parameter values and process model and geometry chemistry = pybamm.parameter_sets.Marquis2019 param = pybamm.ParameterValues(chemistry=chemistry) param.process_model(model) param.process_geometry(geometry) # set mesh var = pybamm.standard_spatial_vars var_pts = { var.x_n: 20, var.x_s: 20, var.x_p: 20, var.r_n: 30, var.r_p: 30, var.y: 10, var.z: 10, } mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model)
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_process_parameters_and_discretise(self): model = pybamm.lithium_ion.SPM() # Set up geometry and parameters geometry = model.default_geometry parameter_values = model.default_parameter_values parameter_values.process_geometry(geometry) # Set up discretisation mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) disc = pybamm.Discretisation(mesh, model.default_spatial_methods) # Process expression c = pybamm.Parameter("Negative electrode thickness [m]") * pybamm.Variable( "X-averaged negative particle concentration", domain="negative particle", auxiliary_domains={"secondary": "current collector"}, ) processed_c = model.process_parameters_and_discretise(c, parameter_values, disc) self.assertIsInstance(processed_c, pybamm.Multiplication) self.assertIsInstance(processed_c.left, pybamm.Scalar) self.assertIsInstance(processed_c.right, pybamm.StateVector) # Process flux manually and check result against flux computed in particle # submodel c_n = model.variables["X-averaged negative particle concentration"] T = pybamm.PrimaryBroadcast( model.variables["X-averaged negative electrode temperature"], ["negative particle"], ) D = model.param.D_n(c_n, T) N = -D * pybamm.grad(c_n) flux_1 = model.process_parameters_and_discretise(N, parameter_values, disc) flux_2 = model.variables["X-averaged negative particle flux"] param_flux_2 = parameter_values.process_symbol(flux_2) disc_flux_2 = disc.process_symbol(param_flux_2) self.assertEqual(flux_1.id, disc_flux_2.id)
def get_unit_2p1D_mesh_for_testing(ypts=15, zpts=15, include_particles=True): param = pybamm.ParameterValues( values={ "Electrode width [m]": 1, "Electrode height [m]": 1, "Negative tab width [m]": 1, "Negative tab centre y-coordinate [m]": 0.5, "Negative tab centre z-coordinate [m]": 0, "Positive tab width [m]": 1, "Positive tab centre y-coordinate [m]": 0.5, "Positive tab centre z-coordinate [m]": 1, "Negative electrode thickness [m]": 0.3, "Separator thickness [m]": 0.3, "Positive electrode thickness [m]": 0.3, } ) geometry = pybamm.battery_geometry( include_particles=include_particles, current_collector_dimension=2 ) param.process_geometry(geometry) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 3, var.x_s: 3, var.x_p: 3, var.y: ypts, var.z: zpts} 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), } return pybamm.Mesh(geometry, submesh_types, var_pts)
def test_grad_div_1D_FV_basic(self): param = pybamm.Parameters() mesh = pybamm.Mesh(param, target_npts=50) y = np.ones_like(mesh.x.centres) N = np.ones_like(mesh.x.edges) yn = np.ones_like(mesh.xn.centres) Nn = np.ones_like(mesh.xn.edges) yp = np.ones_like(mesh.xp.centres) Np = np.ones_like(mesh.xp.edges) # Get all operators operators = pybamm.Operators("Finite Volumes", mesh) # Check output shape self.assertEqual(operators.x.grad(y).shape[0], y.shape[0] - 1) self.assertEqual(operators.x.div(N).shape[0], N.shape[0] - 1) self.assertEqual(operators.xn.grad(yn).shape[0], yn.shape[0] - 1) self.assertEqual(operators.xn.div(Nn).shape[0], Nn.shape[0] - 1) self.assertEqual(operators.xp.grad(yp).shape[0], yp.shape[0] - 1) self.assertEqual(operators.xp.div(Np).shape[0], Np.shape[0] - 1) # Check grad and div are both zero self.assertEqual(np.linalg.norm(operators.x.grad(y)), 0) self.assertEqual(np.linalg.norm(operators.x.div(N)), 0) self.assertEqual(np.linalg.norm(operators.xn.grad(yn)), 0) self.assertEqual(np.linalg.norm(operators.xn.div(Nn)), 0) self.assertEqual(np.linalg.norm(operators.xp.grad(yp)), 0) self.assertEqual(np.linalg.norm(operators.xp.div(Np)), 0)
def build(self, check_model=True): """ A method to build the model into a system of matrices and vectors suitable for performing numerical computations. If the model has already been built or solved then this function will have no effect. If you want to rebuild, first use "reset()". This method will automatically set the parameters if they have not already been set. Parameters ---------- check_model : bool, optional If True, model checks are performed after discretisation (see :meth:`pybamm.Discretisation.process_model`). Default is True. """ if self.built_model: return None elif self.model.is_discretised: self._model_with_set_params = self.model self._built_model = self.model else: self.set_parameters() self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts) self._disc = pybamm.Discretisation(self._mesh, self._spatial_methods) self._built_model = self._disc.process_model( self._model_with_set_params, inplace=False, check_model=check_model )
def __init__( self, model, parameter_values=None, geometry=None, submesh_types=None, var_pts=None, spatial_methods=None, solver=None, ): self.model = model # Set parameters, geometry, spatial methods etc # The code below is equivalent to: # if parameter_values is None: # self.parameter_values = model.default_parameter_values # else: # self.parameter_values = parameter_values self.parameter_values = parameter_values or model.default_parameter_values geometry = geometry or model.default_geometry submesh_types = submesh_types or model.default_submesh_types var_pts = var_pts or model.default_var_pts spatial_methods = spatial_methods or model.default_spatial_methods self.solver = solver or model.default_solver # Process geometry self.parameter_values.process_geometry(geometry) # Set discretisation mesh = pybamm.Mesh(geometry, submesh_types, var_pts) self.disc = pybamm.Discretisation(mesh, spatial_methods)
def test_plot_1plus1D_spme(self): spm = pybamm.lithium_ion.SPMe( {"current collector": "potential pair", "dimensionality": 1} ) geometry = spm.default_geometry param = spm.default_parameter_values param.process_model(spm) param.process_geometry(geometry) var = pybamm.standard_spatial_vars var_pts = {var.x_n: 5, var.x_s: 5, var.x_p: 5, var.r_n: 5, var.r_p: 5, var.z: 5} mesh = pybamm.Mesh(geometry, spm.default_submesh_types, var_pts) disc_spm = pybamm.Discretisation(mesh, spm.default_spatial_methods) disc_spm.process_model(spm) t_eval = np.linspace(0, 100, 10) solution = spm.default_solver.solve(spm, t_eval) # check 2D (x,z space) variables update properly for different time units # Note: these should be the transpose of the entries in the processed variable c_e = solution["Electrolyte concentration [mol.m-3]"] for unit, scale in zip(["seconds", "minutes", "hours"], [1, 60, 3600]): quick_plot = pybamm.QuickPlot( solution, ["Electrolyte concentration [mol.m-3]"], time_unit=unit ) quick_plot.plot(0) qp_data = quick_plot.plots[("Electrolyte concentration [mol.m-3]",)][0][1] c_e_eval = c_e(t_eval[0], x=c_e.first_dim_pts, z=c_e.second_dim_pts) np.testing.assert_array_almost_equal(qp_data.T, c_e_eval) quick_plot.slider_update(t_eval[-1] / scale) qp_data = quick_plot.plots[("Electrolyte concentration [mol.m-3]",)][0][1] c_e_eval = c_e(t_eval[-1], x=c_e.first_dim_pts, z=c_e.second_dim_pts) np.testing.assert_array_almost_equal(qp_data.T, c_e_eval) pybamm.close_plots()
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_inner(self): model = pybamm.lithium_ion.BaseModel() phi_s = pybamm.standard_variables.phi_s_n i = pybamm.grad(phi_s) model.rhs = {phi_s: pybamm.inner(i, i)} model.boundary_conditions = { phi_s: { "left": (pybamm.Scalar(0), "Neumann"), "right": (pybamm.Scalar(0), "Neumann"), } } model.initial_conditions = {phi_s: pybamm.Scalar(0)} model.variables = {"inner": pybamm.inner(i, i)} # load parameter values and process model and geometry param = model.default_parameter_values geometry = model.default_geometry param.process_model(model) param.process_geometry(geometry) # set mesh mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) # check doesn't evaluate on edges anymore self.assertEqual(model.variables["inner"].evaluates_on_edges("primary"), False)
def init_model(): # load model model = pb.lithium_ion.SPMe() # create geometry geometry = model.default_geometry # load parameter values and process model and geometry param = model.default_parameter_values param.update({ "Current function [A]": current_function, }) param.update({"Current": "[input]"}, check_already_exists=False) param.process_model(model) param.process_geometry(geometry) # set mesh mesh = pb.Mesh(geometry, model.default_submesh_types, model.default_var_pts) # discretise model disc = pb.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) return model
def simulation(models, t_eval, extra_parameter_values=None, disc_only=False): # create geometry geometry = models[-1].default_geometry # load parameter values and process models and geometry param = models[0].default_parameter_values extra_parameter_values = extra_parameter_values or {} param.update(extra_parameter_values) for model in models: param.process_model(model) param.process_geometry(geometry) # set mesh var = pybamm.standard_spatial_vars var_pts = {var.x_n: 25, var.x_s: 41, var.x_p: 34} mesh = pybamm.Mesh(geometry, models[-1].default_submesh_types, var_pts) # discretise models for model in models: disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) if disc_only: return model, mesh # solve model solutions = [None] * len(models) for i, model in enumerate(models): solution = model.default_solver.solve(model, t_eval) solutions[i] = solution return models, mesh, solutions
def test_append_external_variables(self): model = pybamm.lithium_ion.SPM({ "thermal": "lumped", "external submodels": ["thermal", "negative particle"], }) # create geometry geometry = model.default_geometry # load parameter values and process model and geometry param = model.default_parameter_values param.process_model(model) param.process_geometry(geometry) # set mesh mesh = pybamm.Mesh(geometry, model.default_submesh_types, model.default_var_pts) # discretise model disc = pybamm.Discretisation(mesh, model.default_spatial_methods) disc.process_model(model) # solve model solver = model.default_solver var = pybamm.standard_spatial_vars Nr = model.default_var_pts[var.r_n] T_av = 0 c_s_n_av = np.ones((Nr, 1)) * 0.6 external_variables = { "Volume-averaged cell temperature": T_av, "X-averaged negative particle concentration": c_s_n_av, } # Step dt = 0.1 sol_step = None for _ in range(5): sol_step = solver.step(sol_step, model, dt, external_variables=external_variables) np.testing.assert_array_equal( sol_step.all_inputs[0]["Volume-averaged cell temperature"], 0) np.testing.assert_array_equal( sol_step.all_inputs[0] ["X-averaged negative particle concentration"], 0.6) # Solve t_eval = np.linspace(0, 3600) sol = solver.solve(model, t_eval, external_variables=external_variables) np.testing.assert_array_equal( sol.all_inputs[0]["Volume-averaged cell temperature"], 0) np.testing.assert_array_equal( sol.all_inputs[0]["X-averaged negative particle concentration"], 0.6)
def setUp(self): self.model = pybamm.ElectrolyteCurrentModel() self.param = pybamm.Parameters() target_npts = 3 tsteps = 10 tend = 1 self.mesh = pybamm.Mesh(self.param, target_npts, tsteps=tsteps, tend=tend) self.param.set_mesh(self.mesh)
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.battery_geometry(include_particles=False, current_collector_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} y_edges = np.linspace(0, 0.8, 16) z_edges = np.linspace(0, 1, 24) submesh_params = {"y_edges": y_edges, "z_edges": z_edges} submesh_types = { "negative electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "separator": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "positive electrode": pybamm.MeshGenerator(pybamm.Uniform1DSubMesh), "current collector": pybamm.MeshGenerator(pybamm.UserSupplied2DSubMesh, submesh_params), } # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) # check boundary locations self.assertEqual(mesh["negative electrode"].edges[0], 0) self.assertEqual(mesh["positive electrode"].edges[-1], 1) # check internal boundary locations self.assertEqual(mesh["negative electrode"].edges[-1], mesh["separator"].edges[0]) self.assertEqual(mesh["positive electrode"].edges[0], mesh["separator"].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].npts, npts) else: self.assertEqual(len(mesh[domain].edges), len(mesh[domain].nodes) + 1)