def _get_neg_pos_coupled_variables(self, variables): """ A private function to get the coupled variables when the domain is 'Negative' or 'Positive'. """ param = self.param conductivity, sigma_eff = self._get_conductivities(variables) i_boundary_cc = variables["Current collector current density"] c_e = variables[self.domain + " electrolyte concentration"] delta_phi = variables[self.domain + " electrode surface potential difference"] T = variables[self.domain + " electrode temperature"] i_e = conductivity * ( ((1 + param.Theta * T) * param.chi(c_e) / c_e) * pybamm.grad(c_e) + pybamm.grad(delta_phi) + i_boundary_cc / sigma_eff) variables.update(self._get_domain_current_variables(i_e)) # TODO: Expression can be written in a form which does not require phi_s and # so avoid this hack. phi_s = self.nasty_hack_to_get_phi_s(variables) phi_e = phi_s - delta_phi variables.update(self._get_domain_potential_variables(phi_e)) variables.update({"test": pybamm.x_average(phi_s)}) return variables
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 get_fundamental_variables(self): # Electrolyte pressure p_n = pybamm.Variable( "Negative electrode pressure", domain="negative electrode", auxiliary_domains={"secondary": "current collector"}, ) p_p = pybamm.Variable( "Positive electrode pressure", domain="positive electrode", auxiliary_domains={"secondary": "current collector"}, ) variables = self._get_standard_neg_pos_pressure_variables(p_n, p_p) # TODO: add permeability and viscosity, and other terms v_mass_n = -pybamm.grad(p_n) v_mass_p = -pybamm.grad(p_p) v_box_n = v_mass_n v_box_p = v_mass_p variables.update( self._get_standard_neg_pos_velocity_variables(v_box_n, v_box_p)) div_v_box_n = pybamm.div(v_box_n) div_v_box_p = pybamm.div(v_box_p) variables.update( self._get_standard_neg_pos_acceleration_variables( div_v_box_n, div_v_box_p)) return variables
def test_exceptions(self): c_n = pybamm.Variable("c", domain=["negative electrode"]) N_n = pybamm.grad(c_n) c_s = pybamm.Variable("c", domain=["separator"]) N_s = pybamm.grad(c_s) model = pybamm.BaseModel() model.rhs = {c_n: pybamm.div(N_n), c_s: pybamm.div(N_s)} model.initial_conditions = {c_n: pybamm.Scalar(3), c_s: pybamm.Scalar(1)} model.boundary_conditions = { c_n: {"left": (0, "Neumann"), "right": (0, "Neumann")}, c_s: {"left": (0, "Neumann"), "right": (0, "Neumann")}, } disc = get_discretisation_for_testing() # check raises error if different sized key and output var model.variables = {c_n.name: c_s} with self.assertRaisesRegex(pybamm.ModelError, "variable and its eqn"): disc.process_model(model) # check doesn't raise if concatenation model.variables = {c_n.name: pybamm.Concatenation(c_n, c_s)} disc.process_model(model, inplace=False) # check doesn't raise if broadcast model.variables = { c_n.name: pybamm.PrimaryBroadcast( pybamm.InputParameter("a"), ["negative electrode"] ) } disc.process_model(model)
def get_coupled_variables(self, variables): param = self.param if self.domain in ["Negative", "Positive"]: conductivity, sigma_eff = self._get_conductivities(variables) i_boundary_cc = variables["Current collector current density"] c_e = variables[self.domain + " electrolyte concentration"] delta_phi = variables[ self.domain + " electrode surface potential difference" ] T = variables[self.domain + " electrode temperature"] i_e = conductivity * ( ((1 + param.Theta * T) * param.chi(c_e, T) / c_e) * pybamm.grad(c_e) + pybamm.grad(delta_phi) + i_boundary_cc / sigma_eff ) variables.update(self._get_domain_current_variables(i_e)) phi_s = variables[self.domain + " electrode potential"] phi_e = phi_s - delta_phi variables.update(self._get_domain_potential_variables(phi_e)) elif self.domain == "Separator": x_s = pybamm.standard_spatial_vars.x_s i_boundary_cc = variables["Current collector current density"] c_e_s = variables["Separator electrolyte concentration"] phi_e_n = variables["Negative electrolyte potential"] tor_s = variables["Separator porosity"] T = variables["Separator temperature"] chi_e_s = param.chi(c_e_s, T) kappa_s_eff = param.kappa_e(c_e_s, T) * tor_s phi_e_s = pybamm.boundary_value( phi_e_n, "right" ) + pybamm.IndefiniteIntegral( (1 + param.Theta * T) * chi_e_s / c_e_s * pybamm.grad(c_e_s) - param.C_e * i_boundary_cc / kappa_s_eff, x_s, ) i_e_s = pybamm.PrimaryBroadcast(i_boundary_cc, "separator") variables.update(self._get_domain_potential_variables(phi_e_s)) variables.update(self._get_domain_current_variables(i_e_s)) # Update boundary conditions (for indefinite integral) self.boundary_conditions[c_e_s] = { "left": (pybamm.BoundaryGradient(c_e_s, "left"), "Neumann"), "right": (pybamm.BoundaryGradient(c_e_s, "right"), "Neumann"), } if self.domain == "Positive": variables.update(self._get_whole_cell_variables(variables)) return variables
def _current_collector_heating(self, variables): "Compute Ohmic heating in current collectors" # TODO: implement grad in 0D to return a scalar zero # TODO: implement grad_squared in other spatial methods so that the if # statement can be removed # In the limit of infinitely large current collector conductivity (i.e. # 0D current collectors), the Ohmic heating in the current collectors is # zero if self.cc_dimension == 0: Q_s_cn = pybamm.Scalar(0) Q_s_cp = pybamm.Scalar(0) # Otherwise we compute the Ohmic heating for 1 or 2D current collectors elif self.cc_dimension in [1, 2]: phi_s_cn = variables["Negative current collector potential"] phi_s_cp = variables["Positive current collector potential"] if self.cc_dimension == 1: Q_s_cn = self.param.sigma_cn_prime * pybamm.inner( pybamm.grad(phi_s_cn), pybamm.grad(phi_s_cn)) Q_s_cp = self.param.sigma_cp_prime * pybamm.inner( pybamm.grad(phi_s_cp), pybamm.grad(phi_s_cp)) elif self.cc_dimension == 2: # Inner not implemented in 2D -- have to call grad_squared directly Q_s_cn = self.param.sigma_cn_prime * pybamm.grad_squared( phi_s_cn) Q_s_cp = self.param.sigma_cp_prime * pybamm.grad_squared( phi_s_cp) return Q_s_cn, Q_s_cp
def get_coupled_variables(self, variables): c_s = variables[self.domain + " particle concentration"] T_k = pybamm.PrimaryBroadcast( variables[self.domain + " electrode temperature"], [self.domain.lower() + " particle"], ) if self.domain == "Negative": N_s = -self.param.D_n(c_s, T_k) * pybamm.grad(c_s) elif self.domain == "Positive": N_s = -self.param.D_p(c_s, T_k) * pybamm.grad(c_s) variables.update(self._get_standard_flux_variables(N_s, N_s)) if self.domain == "Negative": x = pybamm.standard_spatial_vars.x_n R = pybamm.FunctionParameter( "Negative particle distribution in x", {"Dimensionless through-cell position (x_n)": x}, ) variables.update({"Negative particle distribution in x": R}) elif self.domain == "Positive": x = pybamm.standard_spatial_vars.x_p R = pybamm.FunctionParameter( "Positive particle distribution in x", {"Dimensionless through-cell position (x_p)": x}, ) variables.update({"Positive particle distribution in x": R}) return variables
def test_grad_div_with_bcs_on_tab(self): # 2d macroscale mesh = get_1p1d_mesh_for_testing() spatial_methods = { "macroscale": pybamm.FiniteVolume(), "negative particle": pybamm.FiniteVolume(), "positive particle": pybamm.FiniteVolume(), "current collector": pybamm.FiniteVolume(), } disc = pybamm.Discretisation(mesh, spatial_methods) y_test = np.ones(mesh["current collector"][0].npts) # var var = pybamm.Variable("var", domain="current collector") disc.set_variable_slices([var]) # grad grad_eqn = pybamm.grad(var) # div N = pybamm.grad(var) div_eqn = pybamm.div(N) # bcs (on each tab) boundary_conditions = { var.id: { "negative tab": (pybamm.Scalar(1), "Dirichlet"), "positive tab": (pybamm.Scalar(0), "Neumann"), } } disc.bcs = boundary_conditions grad_eqn_disc = disc.process_symbol(grad_eqn) grad_eqn_disc.evaluate(None, y_test) div_eqn_disc = disc.process_symbol(div_eqn) div_eqn_disc.evaluate(None, y_test) # bcs (one pos, one not tab) boundary_conditions = { var.id: { "no tab": (pybamm.Scalar(1), "Dirichlet"), "positive tab": (pybamm.Scalar(0), "Dirichlet"), } } disc.bcs = boundary_conditions grad_eqn_disc = disc.process_symbol(grad_eqn) grad_eqn_disc.evaluate(None, y_test) div_eqn_disc = disc.process_symbol(div_eqn) div_eqn_disc.evaluate(None, y_test) # bcs (one neg, one not tab) boundary_conditions = { var.id: { "negative tab": (pybamm.Scalar(1), "Neumann"), "no tab": (pybamm.Scalar(0), "Neumann"), } } disc.bcs = boundary_conditions grad_eqn_disc = disc.process_symbol(grad_eqn) grad_eqn_disc.evaluate(None, y_test) div_eqn_disc = disc.process_symbol(div_eqn) div_eqn_disc.evaluate(None, y_test)
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_grad_div_shapes_mixed_domain(self): """ Test grad and div with Dirichlet boundary conditions (applied by grad on var) """ # create discretisation mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.SpectralVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) # grad var = pybamm.Variable("var", domain=["negative electrode", "separator"]) grad_eqn = pybamm.grad(var) boundary_conditions = { var.id: { "left": (pybamm.Scalar(1), "Dirichlet"), "right": (pybamm.Scalar(1), "Dirichlet"), } } disc.bcs = boundary_conditions disc.set_variable_slices([var]) grad_eqn_disc = disc.process_symbol(grad_eqn) combined_submesh = mesh.combine_submeshes("negative electrode", "separator") 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: test on linear y (should have laplacian zero) so change bcs linear_y = combined_submesh.nodes N = pybamm.grad(var) div_eqn = pybamm.div(N) boundary_conditions = { var.id: { "left": (pybamm.Scalar(0), "Dirichlet"), "right": (pybamm.Scalar(combined_submesh.edges[-1]), "Dirichlet"), } } disc.bcs = boundary_conditions grad_eqn_disc = disc.process_symbol(grad_eqn) np.testing.assert_array_almost_equal( grad_eqn_disc.evaluate(None, linear_y), np.ones_like(combined_submesh.edges[:, np.newaxis]), ) div_eqn_disc = disc.process_symbol(div_eqn) np.testing.assert_array_almost_equal( div_eqn_disc.evaluate(None, linear_y), np.zeros_like(combined_submesh.nodes[:, np.newaxis]), )
def _current_collector_heating(self, variables): """Returns the heat source terms in the 1D current collector""" phi_s_cn = variables["Negative current collector potential"] phi_s_cp = variables["Positive current collector potential"] Q_s_cn = self.param.sigma_cn_prime * pybamm.inner( pybamm.grad(phi_s_cn), pybamm.grad(phi_s_cn)) Q_s_cp = self.param.sigma_cp_prime * pybamm.inner( pybamm.grad(phi_s_cp), pybamm.grad(phi_s_cp)) return Q_s_cn, Q_s_cp
def test_symbol_repr(self): """ test that __repr___ returns the string `__class__(id, name, parent expression)` """ a = pybamm.Symbol("a") b = pybamm.Symbol("b") c = pybamm.Symbol("c", domain=["test"]) d = pybamm.Symbol("d", domain=["test"], auxiliary_domains={"sec": "other test"}) hex_regex = r"\-?0x[0-9,a-f]+" self.assertRegex( a.__repr__(), r"Symbol\(" + hex_regex + r", a, children\=\[\], domain\=\[\], auxiliary_domains\=\{\}\)", ) self.assertRegex( b.__repr__(), r"Symbol\(" + hex_regex + r", b, children\=\[\], domain\=\[\], auxiliary_domains\=\{\}\)", ) self.assertRegex( c.__repr__(), r"Symbol\(" + hex_regex + r", c, children\=\[\], domain\=\['test'\], auxiliary_domains\=\{\}\)", ) self.assertRegex( d.__repr__(), r"Symbol\(" + hex_regex + r", d, children\=\[\], domain\=\['test'\]" + r", auxiliary_domains\=\{'sec': \"\['other test'\]\"\}\)", ) self.assertRegex( (a + b).__repr__(), r"Addition\(" + hex_regex + r", \+, children\=\['a', 'b'\], domain=\[\]", ) self.assertRegex( (c * d).__repr__(), r"Multiplication\(" + hex_regex + r", \*, children\=\['c', 'd'\], domain=\['test'\]" + r", auxiliary_domains\=\{'sec': \"\['other test'\]\"\}\)", ) self.assertRegex( pybamm.grad(a).__repr__(), r"Gradient\(" + hex_regex + r", grad, children\=\['a'\], domain=\[\], auxiliary_domains\=\{\}\)", ) self.assertRegex( pybamm.grad(c).__repr__(), r"Gradient\(" + hex_regex + r", grad, children\=\['c'\], domain=\['test'\]" + r", auxiliary_domains\=\{\}\)", )
def test_symbol_new_copy(self): a = pybamm.Parameter("a") b = pybamm.Parameter("b") v_n = pybamm.Variable("v", "negative electrode") x_n = pybamm.standard_spatial_vars.x_n v_s = pybamm.Variable("v", "separator") vec = pybamm.Vector([1, 2, 3, 4, 5]) mat = pybamm.Matrix([[1, 2], [3, 4]]) mesh = get_mesh_for_testing() for symbol in [ a + b, a - b, a * b, a / b, a**b, -a, abs(a), pybamm.Function(np.sin, a), pybamm.FunctionParameter("function", {"a": a}), pybamm.grad(v_n), pybamm.div(pybamm.grad(v_n)), pybamm.upwind(v_n), pybamm.IndefiniteIntegral(v_n, x_n), pybamm.BackwardIndefiniteIntegral(v_n, x_n), pybamm.BoundaryValue(v_n, "right"), pybamm.BoundaryGradient(v_n, "right"), pybamm.PrimaryBroadcast(a, "domain"), pybamm.SecondaryBroadcast(v_n, "current collector"), pybamm.FullBroadcast(a, "domain", {"secondary": "other domain"}), pybamm.concatenation(v_n, v_s), pybamm.NumpyConcatenation(a, b, v_s), pybamm.DomainConcatenation([v_n, v_s], mesh), pybamm.Parameter("param"), pybamm.InputParameter("param"), pybamm.StateVector(slice(0, 56)), pybamm.Matrix(np.ones((50, 40))), pybamm.SpatialVariable("x", ["negative electrode"]), pybamm.t, pybamm.Index(vec, 1), pybamm.NotConstant(a), pybamm.ExternalVariable( "external variable", 20, domain="test", auxiliary_domains={"secondary": "test2"}, ), pybamm.minimum(a, b), pybamm.maximum(a, b), pybamm.SparseStack(mat, mat), ]: self.assertEqual(symbol.id, symbol.new_copy().id)
def test_p2d_spherical_grad_div_shapes_Neumann_bcs(self): """ Test grad and div with Dirichlet boundary conditions (applied by grad on var) in the pseudo 2-dimensional case """ mesh = get_p2d_mesh_for_testing() spatial_methods = {"negative particle": pybamm.SpectralVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) n_mesh = mesh["negative particle"] mesh.add_ghost_meshes() disc.mesh.add_ghost_meshes() # test grad var = pybamm.Variable( "var", domain=["negative particle"], auxiliary_domains={"secondary": "negative electrode"}, ) grad_eqn = pybamm.grad(var) disc.set_variable_slices([var]) grad_eqn_disc = disc.process_symbol(grad_eqn) prim_pts = n_mesh.npts sec_pts = mesh["negative electrode"].npts constant_y = np.kron(np.ones(sec_pts), np.ones(prim_pts)) grad_eval = grad_eqn_disc.evaluate(None, constant_y) grad_eval = np.reshape(grad_eval, [sec_pts, prim_pts + 1]) np.testing.assert_array_equal(grad_eval, np.zeros([sec_pts, prim_pts + 1])) # 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) const = 6 * np.ones(sec_pts * prim_pts) div_eval = div_eqn_disc.evaluate(None, const) div_eval = np.reshape(div_eval, [sec_pts, prim_pts]) np.testing.assert_array_almost_equal(div_eval, np.zeros([sec_pts, prim_pts]))
def get_coupled_variables(self, variables): param = self.param T = variables["Cell temperature"] eps = variables["Porosity"] c_e = variables["Electrolyte concentration"] phi_e = variables["Electrolyte potential"] i_e = (param.kappa_e(c_e, T) * (eps**param.b) * param.gamma_e / param.C_e) * (param.chi(c_e) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e)) variables.update(self._get_standard_current_variables(i_e)) return variables
def test_check_well_posedness_initial_boundary_conditions(self): # Well-posed model - Dirichlet whole_cell = ["negative electrode", "separator", "positive electrode"] model = pybamm.BaseModel() c = pybamm.Variable("c", domain=whole_cell) model.rhs = {c: 5 * pybamm.div(pybamm.grad(c)) - 1} model.initial_conditions = {c: 1} model.boundary_conditions = { c: { "left": (0, "Dirichlet"), "right": (0, "Dirichlet") } } model.check_well_posedness() # Well-posed model - Neumann model.boundary_conditions = { c: { "left": (0, "Neumann"), "right": (0, "Neumann") } } model.check_well_posedness() # Model with bad initial conditions (expect assertion error) d = pybamm.Variable("d", domain=whole_cell) model.initial_conditions = {d: 3} with self.assertRaisesRegex(pybamm.ModelError, "initial condition"): model.check_well_posedness() # Algebraic well-posed model whole_cell = ["negative electrode", "separator", "positive electrode"] model = pybamm.BaseModel() model.algebraic = {c: 5 * pybamm.div(pybamm.grad(c)) - 1} model.boundary_conditions = { c: { "left": (0, "Dirichlet"), "right": (0, "Dirichlet") } } model.check_well_posedness() model.boundary_conditions = { c: { "left": (0, "Neumann"), "right": (0, "Neumann") } } model.check_well_posedness()
def get_coupled_variables(self, variables): eps = separator_and_positive_only(variables["Porosity"]) c_ox = variables[ "Separator and positive electrode oxygen concentration"] # TODO: allow charge and convection? v_box = pybamm.Scalar(0) param = self.param b = pybamm.Concatenation( pybamm.FullBroadcast(param.b_s, ["separator"], "current collector"), pybamm.FullBroadcast(param.b_p, ["positive electrode"], "current collector"), ) N_ox_diffusion = -(eps**b) * param.curlyD_ox * pybamm.grad(c_ox) N_ox = N_ox_diffusion + c_ox * v_box # Flux in the negative electrode is zero N_ox = pybamm.Concatenation( pybamm.FullBroadcast(0, "negative electrode", "current collector"), N_ox) variables.update(self._get_standard_flux_variables(N_ox)) return variables
def get_coupled_variables(self, variables): tor_0 = variables["Leading-order electrolyte tortuosity"] c_e_0_av = variables[ "Leading-order x-averaged electrolyte concentration"] c_e = variables["Electrolyte concentration"] # i_e = variables["Electrolyte current density"] v_box_0 = variables["Leading-order volume-averaged velocity"] T_0 = variables["Leading-order cell temperature"] param = self.param N_e_diffusion = -tor_0 * param.D_e(c_e_0_av, T_0) * pybamm.grad(c_e) # N_e_migration = (param.C_e * param.t_plus) / param.gamma_e * i_e # N_e_convection = c_e * v_box_0 # N_e = N_e_diffusion + N_e_migration + N_e_convection if v_box_0.id == pybamm.Scalar(0).id: N_e = N_e_diffusion else: N_e = N_e_diffusion + v_box_0 * c_e variables.update(self._get_standard_flux_variables(N_e)) return variables
def test_gradient(self): # gradient of scalar symbol should fail a = pybamm.Symbol("a") with self.assertRaisesRegex( pybamm.DomainError, "Cannot take gradient of 'a' since its domain is empty"): pybamm.Gradient(a) # gradient of variable evaluating on edges should fail a = pybamm.PrimaryBroadcastToEdges(pybamm.Scalar(1), "test") with self.assertRaisesRegex(TypeError, "evaluates on edges"): pybamm.Gradient(a) # gradient of broadcast should return broadcasted zero a = pybamm.PrimaryBroadcast(pybamm.Variable("a"), "test domain") grad = pybamm.grad(a) self.assertIsInstance(grad, pybamm.PrimaryBroadcastToEdges) self.assertIsInstance(grad.child, pybamm.PrimaryBroadcast) self.assertIsInstance(grad.child.child, pybamm.Scalar) self.assertEqual(grad.child.child.value, 0) # otherwise gradient should work a = pybamm.Symbol("a", domain="test domain") grad = pybamm.Gradient(a) self.assertEqual(grad.children[0].name, a.name) self.assertEqual(grad.domain, a.domain)
def get_coupled_variables(self, variables): eps_0 = variables["Leading-order porosity"] c_e_0_av = variables[ "Leading-order x-averaged electrolyte concentration"] c_e = variables["Electrolyte concentration"] # i_e = variables["Electrolyte current density"] v_box_0 = variables["Leading-order volume-averaged velocity"] T_0 = variables["Leading-order cell temperature"] param = self.param whole_cell = ["negative electrode", "separator", "positive electrode"] N_e_diffusion = (-(eps_0**param.b) * pybamm.PrimaryBroadcast( param.D_e(c_e_0_av, T_0), whole_cell) * pybamm.grad(c_e)) # N_e_migration = (param.C_e * param.t_plus) / param.gamma_e * i_e # N_e_convection = c_e * v_box_0 # N_e = N_e_diffusion + N_e_migration + N_e_convection if v_box_0.id == pybamm.Scalar(0).id: N_e = N_e_diffusion else: N_e = N_e_diffusion + pybamm.outer(v_box_0, c_e) variables.update(self._get_standard_flux_variables(N_e)) return variables
def get_coupled_variables(self, variables): phi_s = variables[self.domain + " electrode potential"] eps = variables[self.domain + " electrode porosity"] if self.domain == "Negative": sigma = self.param.sigma_n b = self.param.b_n elif self.domain == "Positive": sigma = self.param.sigma_p b = self.param.b_p sigma_eff = sigma * (1 - eps)**b i_s = -sigma_eff * pybamm.grad(phi_s) variables.update( {self.domain + " electrode effective conductivity": sigma_eff}) variables.update(self._get_standard_current_variables(i_s)) if self.domain == "Positive": variables.update( self._get_standard_whole_cell_variables(variables)) return variables
def test_new_copy(self): model = pybamm.BaseModel(name="a model") whole_cell = ["negative electrode", "separator", "positive electrode"] c = pybamm.Variable("c", domain=whole_cell) d = pybamm.Variable("d", domain=whole_cell) model.rhs = {c: 5 * pybamm.div(pybamm.grad(d)) - 1, d: -c} model.initial_conditions = {c: 1, d: 2} model.boundary_conditions = { c: { "left": (0, "Dirichlet"), "right": (0, "Dirichlet") }, d: { "left": (0, "Dirichlet"), "right": (0, "Dirichlet") }, } model.use_jacobian = False model.use_simplify = False model.convert_to_format = "python" new_model = model.new_copy() self.assertEqual(new_model.name, model.name) self.assertEqual(new_model.use_jacobian, model.use_jacobian) self.assertEqual(new_model.use_simplify, model.use_simplify) self.assertEqual(new_model.convert_to_format, model.convert_to_format) self.assertEqual(new_model.timescale, model.timescale)
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 test_gradient(self): 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) # test gradient of 5*y + 6*z var = pybamm.Variable("var", domain="current collector") disc.set_variable_slices([var]) y = mesh["current collector"].coordinates[0, :] z = mesh["current collector"].coordinates[1, :] gradient = pybamm.grad(var) grad_disc = disc.process_symbol(gradient) grad_disc_y, grad_disc_z = grad_disc.children np.testing.assert_array_almost_equal( grad_disc_y.evaluate(None, 5 * y + 6 * z), 5 * np.ones_like(y)[:, np.newaxis], ) np.testing.assert_array_almost_equal( grad_disc_z.evaluate(None, 5 * y + 6 * z), 6 * np.ones_like(z)[:, np.newaxis], ) # check grad_squared positive eqn = pybamm.grad_squared(var) eqn_disc = disc.process_symbol(eqn) ans = eqn_disc.evaluate(None, 3 * y ** 2) np.testing.assert_array_less(0, ans)
def get_coupled_variables(self, variables): param = self.param T = variables["Cell temperature"] tor = variables["Electrolyte tortuosity"] c_e = variables["Electrolyte concentration"] phi_e = variables["Electrolyte potential"] i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * (param.chi(c_e, T) * (1 + param.Theta * T) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e)) variables.update(self._get_standard_current_variables(i_e)) variables.update(self._get_electrolyte_overpotentials(variables)) return variables
def test_grad_div_broadcast(self): # create mesh and discretisation spatial_methods = {"macroscale": pybamm.FiniteVolume()} mesh = get_mesh_for_testing() disc = pybamm.Discretisation(mesh, spatial_methods) a = pybamm.PrimaryBroadcast(1, "negative electrode") grad_a = disc.process_symbol(pybamm.grad(a)) np.testing.assert_array_equal(grad_a.evaluate(), 0) a_edge = pybamm.PrimaryBroadcastToEdges(1, "negative electrode") div_a = disc.process_symbol(pybamm.div(a_edge)) np.testing.assert_array_equal(div_a.evaluate(), 0) div_grad_a = disc.process_symbol(pybamm.div(pybamm.grad(a))) np.testing.assert_array_equal(div_grad_a.evaluate(), 0)
def set_rhs(self, variables): T = variables["Cell temperature"] T_n = variables["Negative electrode temperature"] T_s = variables["Separator temperature"] T_p = variables["Positive electrode temperature"] Q = variables["Total heating"] # Define volumetric heat capacity rho_k = pybamm.concatenation( self.param.rho_n(T_n), self.param.rho_s(T_s), self.param.rho_p(T_p), ) # Devine thermal conductivity lambda_k = pybamm.concatenation( self.param.lambda_n(T_n), self.param.lambda_s(T_s), self.param.lambda_p(T_p), ) # Fourier's law for heat flux q = -lambda_k * pybamm.grad(T) # N.B only y-z surface cooling is implemented for this model self.rhs = { T: (-pybamm.div(q) / self.param.delta**2 + self.param.B * Q) / (self.param.C_th * rho_k) }
def test_process_model_not_inplace(self): # concatenation of variables as the key c = pybamm.Variable("c", domain=["negative electrode"]) 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.check_well_posedness() # create discretisation disc = get_discretisation_for_testing() mesh = disc.mesh submesh = mesh["negative electrode"] discretised_model = disc.process_model(model, inplace=False) y0 = discretised_model.concatenated_initial_conditions.evaluate() np.testing.assert_array_equal( y0, 3 * np.ones_like(submesh.nodes[:, np.newaxis]) ) # grad and div are identity operators here np.testing.assert_array_equal( y0, discretised_model.concatenated_rhs.evaluate(None, y0) ) discretised_model.check_well_posedness()
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 get_coupled_variables(self, variables): c_s = variables[self.domain + " particle concentration"] T = pybamm.PrimaryBroadcast( variables[self.domain + " electrode temperature"], [self.domain.lower() + " particle"], ) if self.domain == "Negative": N_s = -self.param.D_n(c_s, T) * pybamm.grad(c_s) elif self.domain == "Positive": N_s = -self.param.D_p(c_s, T) * pybamm.grad(c_s) variables.update(self._get_standard_flux_variables(N_s, N_s)) return variables