def set_algebraic(self, variables): param = self.param applied_current = variables["Total current density"] cc_area = self._get_effective_current_collector_area() z = pybamm.standard_spatial_vars.z phi_s_cn = variables["Negative current collector potential"] phi_s_cp = variables["Positive current collector potential"] i_boundary_cc = variables["Current collector current density"] i_boundary_cc_0 = variables[ "Leading-order current collector current density"] c = variables["Lagrange multiplier"] # Note that the second argument of 'source' must be the same as the argument # in the laplacian (the variable to which the boundary conditions are applied) self.algebraic = { phi_s_cn: (param.sigma_cn * param.delta**2 * param.l_cn) * pybamm.laplacian(phi_s_cn) - pybamm.source(i_boundary_cc_0, phi_s_cn), i_boundary_cc: (param.sigma_cp * param.delta**2 * param.l_cp) * pybamm.laplacian(phi_s_cp) + pybamm.source(i_boundary_cc_0, phi_s_cp) + c * pybamm.PrimaryBroadcast(cc_area, "current collector"), c: pybamm.Integral(i_boundary_cc, z) - applied_current / cc_area + 0 * c, }
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 set_soc_variables(self): "Set variables relating to the state of charge." # State of Charge defined as function of dimensionless electrolyte concentration z = pybamm.standard_spatial_vars.z soc = (pybamm.Integral( self.variables["X-averaged electrolyte concentration"], z) * 100) self.variables.update({ "State of Charge": soc, "Depth of Discharge": 100 - soc }) # Fractional charge input if "Fractional Charge Input" not in self.variables: fci = pybamm.Variable("Fractional Charge Input", domain="current collector") self.variables["Fractional Charge Input"] = fci self.rhs[fci] = -self.variables["Total current density"] * 100 self.initial_conditions[fci] = self.param.q_init * 100
def test_delta_function(self): mesh = get_mesh_for_testing() spatial_methods = {"macroscale": pybamm.FiniteVolume()} disc = pybamm.Discretisation(mesh, spatial_methods) var = pybamm.Variable("var") delta_fn_left = pybamm.DeltaFunction(var, "left", "negative electrode") delta_fn_right = pybamm.DeltaFunction(var, "right", "negative electrode") disc.set_variable_slices([var]) delta_fn_left_disc = disc.process_symbol(delta_fn_left) delta_fn_right_disc = disc.process_symbol(delta_fn_right) # Basic shape and type tests y = np.ones_like(mesh["negative electrode"][0].nodes[:, np.newaxis]) # Left self.assertEqual(delta_fn_left_disc.domain, delta_fn_left.domain) self.assertEqual(delta_fn_left_disc.auxiliary_domains, delta_fn_left.auxiliary_domains) self.assertIsInstance(delta_fn_left_disc, pybamm.Multiplication) self.assertIsInstance(delta_fn_left_disc.left, pybamm.Matrix) np.testing.assert_array_equal( delta_fn_left_disc.left.evaluate()[:, 1:], 0) self.assertEqual(delta_fn_left_disc.shape, y.shape) # Right self.assertEqual(delta_fn_right_disc.domain, delta_fn_right.domain) self.assertEqual(delta_fn_right_disc.auxiliary_domains, delta_fn_right.auxiliary_domains) self.assertIsInstance(delta_fn_right_disc, pybamm.Multiplication) self.assertIsInstance(delta_fn_right_disc.left, pybamm.Matrix) np.testing.assert_array_equal( delta_fn_right_disc.left.evaluate()[:, :-1], 0) self.assertEqual(delta_fn_right_disc.shape, y.shape) # Value tests # Delta function should integrate to the same thing as variable var_disc = disc.process_symbol(var) x = pybamm.standard_spatial_vars.x_n delta_fn_int_disc = disc.process_symbol( pybamm.Integral(delta_fn_left, x)) np.testing.assert_array_equal( var_disc.evaluate(y=y) * mesh["negative electrode"][0].edges[-1], np.sum(delta_fn_int_disc.evaluate(y=y)), )
def test_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_symbol_new_copy(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) v_n = pybamm.Variable("v", "negative electrode") x_n = pybamm.standard_spatial_vars.x_n v_s = pybamm.Variable("v", "separator") vec = pybamm.Vector(np.array([1, 2, 3, 4, 5])) 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.Integral(a, pybamm.t), 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), ]: self.assertEqual(symbol.id, symbol.new_copy().id)
def z_average(symbol): """ convenience function for creating an average in the z-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 z-average of a symbol that evaluates on edges") # Symbol must have domain [] or ["current collector"] if symbol.domain not in [[], ["current collector"]]: raise pybamm.DomainError( """z-average only implemented in the 'current collector' domain, but symbol has domains {}""".format( symbol.domain ) ) # If symbol doesn't have a domain, its average value is itself if symbol.domain == []: 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] # Otherwise, use Integral to calculate average value else: # We compute the length as Integral(1, z) as this will be easier to identify # for simplifications later on and it gives the correct behaviour when using # ZeroDimensionalSpatialMethod z = pybamm.standard_spatial_vars.z v = pybamm.ones_like(symbol) l = pybamm.Integral(v, z) return Integral(symbol, z) / l
def test_process_symbol(self): parameter_values = pybamm.ParameterValues({"a": 4, "b": 2, "c": 3}) # process parameter a = pybamm.Parameter("a") processed_a = parameter_values.process_symbol(a) self.assertIsInstance(processed_a, pybamm.Scalar) self.assertEqual(processed_a.value, 4) # process binary operation var = pybamm.Variable("var") add = a + var processed_add = parameter_values.process_symbol(add) self.assertIsInstance(processed_add, pybamm.Addition) self.assertIsInstance(processed_add.children[0], pybamm.Scalar) self.assertIsInstance(processed_add.children[1], pybamm.Variable) self.assertEqual(processed_add.children[0].value, 4) b = pybamm.Parameter("b") add = a + b processed_add = parameter_values.process_symbol(add) self.assertIsInstance(processed_add, pybamm.Scalar) self.assertEqual(processed_add.value, 6) scal = pybamm.Scalar(34) mul = a * scal processed_mul = parameter_values.process_symbol(mul) self.assertIsInstance(processed_mul, pybamm.Scalar) self.assertEqual(processed_mul.value, 136) # process integral aa = pybamm.Parameter("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", domain=["negative electrode"]) integ = pybamm.Integral(aa, x) processed_integ = parameter_values.process_symbol(integ) self.assertIsInstance(processed_integ, pybamm.Integral) self.assertIsInstance(processed_integ.children[0], pybamm.Scalar) self.assertEqual(processed_integ.children[0].value, 4) self.assertEqual(processed_integ.integration_variable[0].id, x.id) # process unary operation v = pybamm.Variable("v", domain="test") grad = pybamm.Gradient(v) processed_grad = parameter_values.process_symbol(grad) self.assertIsInstance(processed_grad, pybamm.Gradient) self.assertIsInstance(processed_grad.children[0], pybamm.Variable) # process delta function aa = pybamm.Parameter("a") delta_aa = pybamm.DeltaFunction(aa, "left", "some domain") processed_delta_aa = parameter_values.process_symbol(delta_aa) self.assertIsInstance(processed_delta_aa, pybamm.DeltaFunction) self.assertEqual(processed_delta_aa.side, "left") processed_a = processed_delta_aa.children[0] self.assertIsInstance(processed_a, pybamm.Scalar) self.assertEqual(processed_a.value, 4) # process boundary operator (test for BoundaryValue) aa = pybamm.Parameter("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", domain=["negative electrode"]) boundary_op = pybamm.BoundaryValue(aa * x, "left") processed_boundary_op = parameter_values.process_symbol(boundary_op) self.assertIsInstance(processed_boundary_op, pybamm.BoundaryOperator) processed_a = processed_boundary_op.children[0].children[0] processed_x = processed_boundary_op.children[0].children[1] self.assertIsInstance(processed_a, pybamm.Scalar) self.assertEqual(processed_a.value, 4) self.assertEqual(processed_x.id, x.id) # process broadcast whole_cell = ["negative electrode", "separator", "positive electrode"] broad = pybamm.PrimaryBroadcast(a, whole_cell) processed_broad = parameter_values.process_symbol(broad) self.assertIsInstance(processed_broad, pybamm.Broadcast) self.assertEqual(processed_broad.domain, whole_cell) self.assertIsInstance(processed_broad.children[0], pybamm.Scalar) self.assertEqual(processed_broad.children[0].evaluate(), 4) # process concatenation conc = pybamm.concatenation( pybamm.Vector(np.ones(10), domain="test"), pybamm.Vector(2 * np.ones(15), domain="test 2"), ) processed_conc = parameter_values.process_symbol(conc) self.assertIsInstance(processed_conc.children[0], pybamm.Vector) self.assertIsInstance(processed_conc.children[1], pybamm.Vector) np.testing.assert_array_equal(processed_conc.children[0].entries, 1) np.testing.assert_array_equal(processed_conc.children[1].entries, 2) # process domain concatenation c_e_n = pybamm.Variable("c_e_n", ["negative electrode"]) c_e_s = pybamm.Variable("c_e_p", ["separator"]) test_mesh = shared.get_mesh_for_testing() dom_con = pybamm.DomainConcatenation([a * c_e_n, b * c_e_s], test_mesh) processed_dom_con = parameter_values.process_symbol(dom_con) a_proc = processed_dom_con.children[0].children[0] b_proc = processed_dom_con.children[1].children[0] self.assertIsInstance(a_proc, pybamm.Scalar) self.assertIsInstance(b_proc, pybamm.Scalar) self.assertEqual(a_proc.value, 4) self.assertEqual(b_proc.value, 2) # process variable c = pybamm.Variable("c") processed_c = parameter_values.process_symbol(c) self.assertIsInstance(processed_c, pybamm.Variable) self.assertEqual(processed_c.name, "c") # process scalar d = pybamm.Scalar(14) processed_d = parameter_values.process_symbol(d) self.assertIsInstance(processed_d, pybamm.Scalar) self.assertEqual(processed_d.value, 14) # process array types e = pybamm.Vector(np.ones(4)) processed_e = parameter_values.process_symbol(e) self.assertIsInstance(processed_e, pybamm.Vector) np.testing.assert_array_equal(processed_e.evaluate(), np.ones((4, 1))) f = pybamm.Matrix(np.ones((5, 6))) processed_f = parameter_values.process_symbol(f) self.assertIsInstance(processed_f, pybamm.Matrix) np.testing.assert_array_equal(processed_f.evaluate(), np.ones((5, 6))) # process statevector g = pybamm.StateVector(slice(0, 10)) processed_g = parameter_values.process_symbol(g) self.assertIsInstance(processed_g, pybamm.StateVector) np.testing.assert_array_equal( processed_g.evaluate(y=np.ones(10)), np.ones((10, 1)) ) # not implemented sym = pybamm.Symbol("sym") with self.assertRaises(NotImplementedError): parameter_values.process_symbol(sym) # not found with self.assertRaises(KeyError): x = pybamm.Parameter("x") parameter_values.process_symbol(x)
def test_integral(self): # time integral a = pybamm.Symbol("a") t = pybamm.t inta = pybamm.Integral(a, t) self.assertEqual(inta.name, "integral dtime") # self.assertTrue(inta.definite) self.assertEqual(inta.children[0].name, a.name) self.assertEqual(inta.integration_variable[0], t) self.assertEqual(inta.domain, []) # space integral a = pybamm.Symbol("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", ["negative electrode"]) inta = pybamm.Integral(a, x) self.assertEqual(inta.name, "integral dx ['negative electrode']") self.assertEqual(inta.children[0].name, a.name) self.assertEqual(inta.integration_variable[0], x) self.assertEqual(inta.domain, []) self.assertEqual(inta.auxiliary_domains, {}) # space integral with secondary domain a_sec = pybamm.Symbol( "a", domain=["negative electrode"], auxiliary_domains={"secondary": "current collector"}, ) x = pybamm.SpatialVariable("x", ["negative electrode"]) inta_sec = pybamm.Integral(a_sec, x) self.assertEqual(inta_sec.domain, ["current collector"]) self.assertEqual(inta_sec.auxiliary_domains, {}) # space integral with secondary domain a_tert = pybamm.Symbol( "a", domain=["negative electrode"], auxiliary_domains={ "secondary": "current collector", "tertiary": "some extra domain", }, ) x = pybamm.SpatialVariable("x", ["negative electrode"]) inta_tert = pybamm.Integral(a_tert, x) self.assertEqual(inta_tert.domain, ["current collector"]) self.assertEqual(inta_tert.auxiliary_domains, {"secondary": ["some extra domain"]}) # space integral over two variables b = pybamm.Symbol("b", domain=["current collector"]) y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) inta = pybamm.Integral(b, [y, z]) self.assertEqual(inta.name, "integral dy dz ['current collector']") self.assertEqual(inta.children[0].name, b.name) self.assertEqual(inta.integration_variable[0], y) self.assertEqual(inta.integration_variable[1], z) self.assertEqual(inta.domain, []) # Indefinite inta = pybamm.IndefiniteIntegral(a, x) self.assertEqual(inta.name, "a integrated w.r.t x on ['negative electrode']") self.assertEqual(inta.children[0].name, a.name) self.assertEqual(inta.integration_variable[0], x) self.assertEqual(inta.domain, ["negative electrode"]) inta_sec = pybamm.IndefiniteIntegral(a_sec, x) self.assertEqual(inta_sec.domain, ["negative electrode"]) self.assertEqual(inta_sec.auxiliary_domains, {"secondary": ["current collector"]}) # expected errors a = pybamm.Symbol("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", ["separator"]) y = pybamm.Variable("y") z = pybamm.SpatialVariable("z", ["negative electrode"]) with self.assertRaises(pybamm.DomainError): pybamm.Integral(a, x) with self.assertRaises(ValueError): pybamm.Integral(a, y)
def test_symbol_simplify(self): a = pybamm.Scalar(0) b = pybamm.Scalar(1) c = pybamm.Parameter("c") d = pybamm.Scalar(-1) e = pybamm.Scalar(2) g = pybamm.Variable("g") # negate self.assertIsInstance((-a).simplify(), pybamm.Scalar) self.assertEqual((-a).simplify().evaluate(), 0) self.assertIsInstance((-b).simplify(), pybamm.Scalar) self.assertEqual((-b).simplify().evaluate(), -1) # absolute value self.assertIsInstance((abs(a)).simplify(), pybamm.Scalar) self.assertEqual((abs(a)).simplify().evaluate(), 0) self.assertIsInstance((abs(d)).simplify(), pybamm.Scalar) self.assertEqual((abs(d)).simplify().evaluate(), 1) # function def sin(x): return math.sin(x) f = pybamm.Function(sin, b) self.assertIsInstance((f).simplify(), pybamm.Scalar) self.assertEqual((f).simplify().evaluate(), math.sin(1)) def myfunction(x, y): return x * y f = pybamm.Function(myfunction, a, b) self.assertIsInstance((f).simplify(), pybamm.Scalar) self.assertEqual((f).simplify().evaluate(), 0) # FunctionParameter f = pybamm.FunctionParameter("function", b) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, b.id) f = pybamm.FunctionParameter("function", a, b) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, a.id) self.assertEqual((f).simplify().children[1].id, b.id) # Gradient self.assertIsInstance((pybamm.grad(a)).simplify(), pybamm.Scalar) self.assertEqual((pybamm.grad(a)).simplify().evaluate(), 0) v = pybamm.Variable("v") self.assertIsInstance((pybamm.grad(v)).simplify(), pybamm.Gradient) # Divergence self.assertIsInstance((pybamm.div(a)).simplify(), pybamm.Scalar) self.assertEqual((pybamm.div(a)).simplify().evaluate(), 0) self.assertIsInstance((pybamm.div(v)).simplify(), pybamm.Divergence) # Integral self.assertIsInstance( (pybamm.Integral(a, pybamm.t)).simplify(), pybamm.Integral ) # BoundaryValue v_neg = pybamm.Variable("v", domain=["negative electrode"]) self.assertIsInstance( (pybamm.boundary_value(v_neg, "right")).simplify(), pybamm.BoundaryValue ) # Delta function self.assertIsInstance( (pybamm.DeltaFunction(v_neg, "right", "domain")).simplify(), pybamm.DeltaFunction, ) # addition self.assertIsInstance((a + b).simplify(), pybamm.Scalar) self.assertEqual((a + b).simplify().evaluate(), 1) self.assertIsInstance((b + b).simplify(), pybamm.Scalar) self.assertEqual((b + b).simplify().evaluate(), 2) self.assertIsInstance((b + a).simplify(), pybamm.Scalar) self.assertEqual((b + a).simplify().evaluate(), 1) # subtraction self.assertIsInstance((a - b).simplify(), pybamm.Scalar) self.assertEqual((a - b).simplify().evaluate(), -1) self.assertIsInstance((b - b).simplify(), pybamm.Scalar) self.assertEqual((b - b).simplify().evaluate(), 0) self.assertIsInstance((b - a).simplify(), pybamm.Scalar) self.assertEqual((b - a).simplify().evaluate(), 1) # addition and subtraction with matrix zero v = pybamm.Vector(np.zeros((10, 1))) self.assertIsInstance((b + v).simplify(), pybamm.Array) np.testing.assert_array_equal((b + v).simplify().evaluate(), np.ones((10, 1))) self.assertIsInstance((v + b).simplify(), pybamm.Array) np.testing.assert_array_equal((v + b).simplify().evaluate(), np.ones((10, 1))) self.assertIsInstance((b - v).simplify(), pybamm.Array) np.testing.assert_array_equal((b - v).simplify().evaluate(), np.ones((10, 1))) self.assertIsInstance((v - b).simplify(), pybamm.Array) np.testing.assert_array_equal((v - b).simplify().evaluate(), -np.ones((10, 1))) # multiplication self.assertIsInstance((a * b).simplify(), pybamm.Scalar) self.assertEqual((a * b).simplify().evaluate(), 0) self.assertIsInstance((b * a).simplify(), pybamm.Scalar) self.assertEqual((b * a).simplify().evaluate(), 0) self.assertIsInstance((b * b).simplify(), pybamm.Scalar) self.assertEqual((b * b).simplify().evaluate(), 1) self.assertIsInstance((a * a).simplify(), pybamm.Scalar) self.assertEqual((a * a).simplify().evaluate(), 0) # test when other node is a parameter self.assertIsInstance((a + c).simplify(), pybamm.Parameter) self.assertIsInstance((c + a).simplify(), pybamm.Parameter) self.assertIsInstance((c + b).simplify(), pybamm.Addition) self.assertIsInstance((b + c).simplify(), pybamm.Addition) self.assertIsInstance((a * c).simplify(), pybamm.Scalar) self.assertEqual((a * c).simplify().evaluate(), 0) self.assertIsInstance((c * a).simplify(), pybamm.Scalar) self.assertEqual((c * a).simplify().evaluate(), 0) self.assertIsInstance((b * c).simplify(), pybamm.Parameter) self.assertIsInstance((e * c).simplify(), pybamm.Multiplication) expr = (e * (e * c)).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e / (e * c)).simplify() self.assertIsInstance(expr, pybamm.Division) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 1.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e * (e / c)).simplify() self.assertIsInstance(expr, pybamm.Division) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e * (c / e)).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 1.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = ((e * c) * (c / e)).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 1.0) self.assertIsInstance(expr.children[1], pybamm.Multiplication) self.assertIsInstance(expr.children[1].children[0], pybamm.Parameter) self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter) expr = (e + (e + c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (e + (e - c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Negate) self.assertIsInstance(expr.children[1].children[0], pybamm.Parameter) expr = (e + (g - c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2.0) self.assertIsInstance(expr.children[1], pybamm.Subtraction) self.assertIsInstance(expr.children[1].children[0], pybamm.Variable) self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter) expr = ((2 + c) + (c + 2)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4.0) self.assertIsInstance(expr.children[1], pybamm.Multiplication) self.assertIsInstance(expr.children[1].children[0], pybamm.Scalar) self.assertEqual(expr.children[1].children[0].evaluate(), 2) self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter) expr = ((-1 + c) - (c + 1) + (c - 1)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), -3.0) # check these don't simplify self.assertIsInstance((c * e).simplify(), pybamm.Multiplication) self.assertIsInstance((e / c).simplify(), pybamm.Division) self.assertIsInstance((c).simplify(), pybamm.Parameter) c1 = pybamm.Parameter("c1") self.assertIsInstance((c1 * c).simplify(), pybamm.Multiplication) # should simplify division to multiply self.assertIsInstance((c / e).simplify(), pybamm.Multiplication) self.assertIsInstance((c / b).simplify(), pybamm.Parameter) self.assertIsInstance((c * b).simplify(), pybamm.Parameter) # negation with parameter self.assertIsInstance((-c).simplify(), pybamm.Negate) self.assertIsInstance((a + b + a).simplify(), pybamm.Scalar) self.assertEqual((a + b + a).simplify().evaluate(), 1) self.assertIsInstance((b + a + a).simplify(), pybamm.Scalar) self.assertEqual((b + a + a).simplify().evaluate(), 1) self.assertIsInstance((a * b * b).simplify(), pybamm.Scalar) self.assertEqual((a * b * b).simplify().evaluate(), 0) self.assertIsInstance((b * a * b).simplify(), pybamm.Scalar) self.assertEqual((b * a * b).simplify().evaluate(), 0) # power simplification self.assertIsInstance((c ** a).simplify(), pybamm.Scalar) self.assertEqual((c ** a).simplify().evaluate(), 1) self.assertIsInstance((a ** c).simplify(), pybamm.Scalar) self.assertEqual((a ** c).simplify().evaluate(), 0) d = pybamm.Scalar(2) self.assertIsInstance((c ** d).simplify(), pybamm.Power) # division self.assertIsInstance((a / b).simplify(), pybamm.Scalar) self.assertEqual((a / b).simplify().evaluate(), 0) self.assertIsInstance((b / a).simplify(), pybamm.Scalar) self.assertEqual((b / a).simplify().evaluate(), np.inf) self.assertIsInstance((a / a).simplify(), pybamm.Scalar) self.assertTrue(np.isnan((a / a).simplify().evaluate())) self.assertIsInstance((b / b).simplify(), pybamm.Scalar) self.assertEqual((b / b).simplify().evaluate(), 1) # not implemented for Symbol sym = pybamm.Symbol("sym") with self.assertRaises(NotImplementedError): sym.simplify() # A + A = 2A (#323) a = pybamm.Parameter("A") expr = (a + a).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (a + a + a + a).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 4) self.assertIsInstance(expr.children[1], pybamm.Parameter) expr = (a - a + a - a + a + a).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2) self.assertIsInstance(expr.children[1], pybamm.Parameter) # A - A = 0 (#323) expr = (a - a).simplify() self.assertIsInstance(expr, pybamm.Scalar) self.assertEqual(expr.evaluate(), 0) # B - (A+A) = B - 2*A (#323) expr = (b - (a + a)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.right, pybamm.Negate) self.assertIsInstance(expr.right.child, pybamm.Multiplication) self.assertEqual(expr.right.child.left.id, pybamm.Scalar(2).id) self.assertEqual(expr.right.child.right.id, a.id) # B - (1*A + 2*A) = B - 3*A (#323) expr = (b - (1 * a + 2 * a)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.right, pybamm.Negate) self.assertIsInstance(expr.right.child, pybamm.Multiplication) self.assertEqual(expr.right.child.left.id, pybamm.Scalar(3).id) self.assertEqual(expr.right.child.right.id, a.id) # B - (A + C) = B - (A + C) (not B - (A - C)) expr = (b - (a + c)).simplify() self.assertIsInstance(expr, pybamm.Addition) self.assertIsInstance(expr.right, pybamm.Subtraction) self.assertEqual(expr.right.left.id, (-a).id) self.assertEqual(expr.right.right.id, c.id)
def __init__(self): super().__init__() self.name = "Effective resistance in current collector model" self.param = pybamm.standard_parameters_lithium_ion # Get useful parameters param = self.param l_cn = param.l_cn l_cp = param.l_cp l_y = param.l_y sigma_cn_dbl_prime = param.sigma_cn_dbl_prime sigma_cp_dbl_prime = param.sigma_cp_dbl_prime alpha_prime = param.alpha_prime # Set model variables var = pybamm.standard_spatial_vars psi = pybamm.Variable("Current collector potential weighted sum", ["current collector"]) W = pybamm.Variable( "Perturbation to current collector potential difference", ["current collector"], ) c_psi = pybamm.Variable("Lagrange multiplier for variable `psi`") c_W = pybamm.Variable("Lagrange multiplier for variable `W`") self.variables = { "Current collector potential weighted sum": psi, "Perturbation to current collector potential difference": W, "Lagrange multiplier for variable `psi`": c_psi, "Lagrange multiplier for variable `W`": c_W, } # Algebraic equations (enforce zero mean constraint through Lagrange multiplier) # 0*LagrangeMultiplier hack otherwise gives KeyError self.algebraic = { psi: pybamm.laplacian(psi) + c_psi * pybamm.DefiniteIntegralVector(psi, vector_type="column"), W: pybamm.laplacian(W) - pybamm.source(1, W) + c_W * pybamm.DefiniteIntegralVector(W, vector_type="column"), c_psi: pybamm.Integral(psi, [var.y, var.z]) + 0 * c_psi, c_W: pybamm.Integral(W, [var.y, var.z]) + 0 * c_W, } # Boundary conditons psi_neg_tab_bc = l_cn psi_pos_tab_bc = -l_cp W_neg_tab_bc = l_y / (alpha_prime * sigma_cn_dbl_prime) W_pos_tab_bc = l_y / (alpha_prime * sigma_cp_dbl_prime) self.boundary_conditions = { psi: { "negative tab": (psi_neg_tab_bc, "Neumann"), "positive tab": (psi_pos_tab_bc, "Neumann"), }, W: { "negative tab": (W_neg_tab_bc, "Neumann"), "positive tab": (W_pos_tab_bc, "Neumann"), }, } # "Initial conditions" provides initial guess for solver # TODO: better guess than zero? self.initial_conditions = { psi: pybamm.Scalar(0), W: pybamm.Scalar(0), c_psi: pybamm.Scalar(0), c_W: pybamm.Scalar(0), } # Define effective current collector resistance psi_neg_tab = pybamm.BoundaryValue(psi, "negative tab") psi_pos_tab = pybamm.BoundaryValue(psi, "positive tab") W_neg_tab = pybamm.BoundaryValue(W, "negative tab") W_pos_tab = pybamm.BoundaryValue(W, "positive tab") R_cc = ((alpha_prime / l_y) * (sigma_cn_dbl_prime * l_cn * W_pos_tab + sigma_cp_dbl_prime * l_cp * W_neg_tab) - (psi_pos_tab - psi_neg_tab)) / (sigma_cn_dbl_prime * l_cn + sigma_cp_dbl_prime * l_cp) R_cc_dim = R_cc * param.potential_scale / param.I_typ self.variables.update({ "Current collector potential weighted sum (negative tab)": psi_neg_tab, "Current collector potential weighted sum (positive tab)": psi_pos_tab, "Perturbation to c.c. potential difference (negative tab)": W_neg_tab, "Perturbation to c.c. potential difference (positive tab)": W_pos_tab, "Effective current collector resistance": R_cc, "Effective current collector resistance [Ohm]": R_cc_dim, })
def x_average(symbol): """convenience function for creating an average in the x-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 x-average of a symbol that evaluates on edges") # If symbol doesn't have a domain, its average value is itself if symbol.domain in [[], ["current collector"]]: 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] # If symbol is a concatenation of Broadcasts, its average value is its child elif (isinstance(symbol, pybamm.Concatenation) and all( isinstance(child, pybamm.Broadcast) for child in symbol.children) and symbol.domain == ["negative electrode", "separator", "positive electrode"]): a, b, c = [orp.orphans[0] for orp in symbol.orphans] if a.id == b.id == c.id: return a else: geo = pybamm.geometric_parameters l_n = geo.l_n l_s = geo.l_s l_p = geo.l_p return (l_n * a + l_s * b + l_p * c) / (l_n + l_s + l_p) # Otherwise, use Integral to calculate average value else: geo = pybamm.geometric_parameters if symbol.domain == ["negative electrode"]: x = pybamm.standard_spatial_vars.x_n l = geo.l_n elif symbol.domain == ["separator"]: x = pybamm.standard_spatial_vars.x_s l = geo.l_s elif symbol.domain == ["positive electrode"]: x = pybamm.standard_spatial_vars.x_p l = geo.l_p elif symbol.domain == [ "negative electrode", "separator", "positive electrode" ]: x = pybamm.standard_spatial_vars.x l = pybamm.Scalar(1) elif symbol.domain == ["negative particle"]: x = pybamm.standard_spatial_vars.x_n l = geo.l_n elif symbol.domain == ["positive particle"]: x = pybamm.standard_spatial_vars.x_p l = geo.l_p else: x = pybamm.SpatialVariable("x", domain=symbol.domain) v = pybamm.ones_like(symbol) l = pybamm.Integral(v, x) return Integral(symbol, x) / l
def test_integral(self): # space integral a = pybamm.Symbol("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", ["negative electrode"]) inta = pybamm.Integral(a, x) self.assertEqual(inta.name, "integral dx ['negative electrode']") self.assertEqual(inta.children[0].name, a.name) self.assertEqual(inta.integration_variable[0], x) self.assertEqual(inta.domain, []) self.assertEqual(inta.auxiliary_domains, {}) # space integral with secondary domain a_sec = pybamm.Symbol( "a", domain=["negative electrode"], auxiliary_domains={"secondary": "current collector"}, ) x = pybamm.SpatialVariable("x", ["negative electrode"]) inta_sec = pybamm.Integral(a_sec, x) self.assertEqual(inta_sec.domain, ["current collector"]) self.assertEqual(inta_sec.auxiliary_domains, {}) # space integral with tertiary domain a_tert = pybamm.Symbol( "a", domain=["negative electrode"], auxiliary_domains={ "secondary": "current collector", "tertiary": "some extra domain", }, ) x = pybamm.SpatialVariable("x", ["negative electrode"]) inta_tert = pybamm.Integral(a_tert, x) self.assertEqual(inta_tert.domain, ["current collector"]) self.assertEqual(inta_tert.auxiliary_domains, {"secondary": ["some extra domain"]}) # space integral *in* secondary domain y = pybamm.SpatialVariable("y", ["current collector"]) # without a tertiary domain inta_sec_y = pybamm.Integral(a_sec, y) self.assertEqual(inta_sec_y.domain, ["negative electrode"]) self.assertEqual(inta_sec_y.auxiliary_domains, {}) # with a tertiary domain inta_tert_y = pybamm.Integral(a_tert, y) self.assertEqual(inta_tert_y.domain, ["negative electrode"]) self.assertEqual(inta_tert_y.auxiliary_domains, {"secondary": ["some extra domain"]}) # space integral *in* tertiary domain z = pybamm.SpatialVariable("z", ["some extra domain"]) inta_tert_z = pybamm.Integral(a_tert, z) self.assertEqual(inta_tert_z.domain, ["negative electrode"]) self.assertEqual(inta_tert_z.auxiliary_domains, {"secondary": ["current collector"]}) # space integral over two variables b = pybamm.Symbol("b", domain=["current collector"]) y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) inta = pybamm.Integral(b, [y, z]) self.assertEqual(inta.name, "integral dy dz ['current collector']") self.assertEqual(inta.children[0].name, b.name) self.assertEqual(inta.integration_variable[0], y) self.assertEqual(inta.integration_variable[1], z) self.assertEqual(inta.domain, []) # Indefinite inta = pybamm.IndefiniteIntegral(a, x) self.assertEqual(inta.name, "a integrated w.r.t x on ['negative electrode']") self.assertEqual(inta.children[0].name, a.name) self.assertEqual(inta.integration_variable[0], x) self.assertEqual(inta.domain, ["negative electrode"]) inta_sec = pybamm.IndefiniteIntegral(a_sec, x) self.assertEqual(inta_sec.domain, ["negative electrode"]) self.assertEqual(inta_sec.auxiliary_domains, {"secondary": ["current collector"]}) # backward indefinite integral inta = pybamm.BackwardIndefiniteIntegral(a, x) self.assertEqual( inta.name, "a integrated backward w.r.t x on ['negative electrode']") # expected errors a = pybamm.Symbol("a", domain=["negative electrode"]) x = pybamm.SpatialVariable("x", ["separator"]) y = pybamm.Variable("y") z = pybamm.SpatialVariable("z", ["negative electrode"]) with self.assertRaises(pybamm.DomainError): pybamm.Integral(a, x) with self.assertRaisesRegex(TypeError, "integration_variable must be"): pybamm.Integral(a, y) with self.assertRaisesRegex( NotImplementedError, "Indefinite integral only implemeted w.r.t. one variable", ): pybamm.IndefiniteIntegral(a, [x, y])
def test_discretise_equations(self): # get mesh mesh = get_2p1d_mesh_for_testing(include_particles=False) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "current collector": pybamm.ScikitFiniteElement(), } disc = pybamm.Discretisation(mesh, spatial_methods) # discretise some equations var = pybamm.Variable("var", domain="current collector") y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) disc.set_variable_slices([var]) y_test = np.ones(mesh["current collector"].npts) unit_source = pybamm.PrimaryBroadcast(1, "current collector") disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Neumann"), "positive tab": (pybamm.Scalar(0), "Neumann"), } } for eqn in [ pybamm.laplacian(var), pybamm.source(unit_source, var), pybamm.laplacian(var) - pybamm.source(unit_source, var), pybamm.source(var, var), pybamm.laplacian(var) - pybamm.source(2 * var, var), pybamm.laplacian(var) - pybamm.source(unit_source ** 2 + 1 / var, var), pybamm.Integral(var, [y, z]) - 1, pybamm.source(var, var, boundary=True), pybamm.laplacian(var) - pybamm.source(unit_source, var, boundary=True), pybamm.laplacian(var) - pybamm.source(unit_source ** 2 + 1 / var, var, boundary=True), ]: # Check that equation can be evaluated in each case # Dirichlet disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Dirichlet"), "positive tab": (pybamm.Scalar(1), "Dirichlet"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # Neumann disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Neumann"), "positive tab": (pybamm.Scalar(1), "Neumann"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # One of each disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Neumann"), "positive tab": (pybamm.Scalar(1), "Dirichlet"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # One of each disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Dirichlet"), "positive tab": (pybamm.Scalar(1), "Neumann"), } } eqn_disc = disc.process_symbol(eqn) eqn_disc.evaluate(None, y_test) # check ValueError raised for non Dirichlet or Neumann BCs eqn = pybamm.laplacian(var) - pybamm.source(unit_source, var) disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Dirichlet"), "positive tab": (pybamm.Scalar(1), "Other BC"), } } with self.assertRaises(ValueError): eqn_disc = disc.process_symbol(eqn) disc.bcs = { var.id: { "negative tab": (pybamm.Scalar(0), "Other BC"), "positive tab": (pybamm.Scalar(1), "Neumann"), } } with self.assertRaises(ValueError): eqn_disc = disc.process_symbol(eqn) # raise ModelError if no BCs provided new_var = pybamm.Variable("new_var", domain="current collector") disc.set_variable_slices([new_var]) eqn = pybamm.laplacian(new_var) with self.assertRaises(pybamm.ModelError): eqn_disc = disc.process_symbol(eqn) # check GeometryError if using scikit-fem not in y or z x = pybamm.SpatialVariable("x", ["current collector"]) with self.assertRaises(pybamm.GeometryError): disc.process_symbol(x)
def x_average(symbol): """ convenience function for creating an average in the x-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 x-average of a symbol that evaluates on edges") # If symbol doesn't have a domain, its average value is itself if symbol.domain in [[], ["current collector"]]: new_symbol = symbol.new_copy() new_symbol.parent = None return new_symbol # If symbol is a primary or full broadcast, reduce by one dimension if isinstance(symbol, (pybamm.PrimaryBroadcast, pybamm.FullBroadcast)): return symbol.reduce_one_dimension() # If symbol is a concatenation of Broadcasts, its average value is its child elif (isinstance(symbol, pybamm.Concatenation) and all( isinstance(child, pybamm.Broadcast) for child in symbol.children) and symbol.domain == ["negative electrode", "separator", "positive electrode"]): a, b, c = [orp.orphans[0] for orp in symbol.orphans] geo = pybamm.geometric_parameters l_n = geo.l_n l_s = geo.l_s l_p = geo.l_p out = (l_n * a + l_s * b + l_p * c) / (l_n + l_s + l_p) # To respect domains we may need to broadcast the child back out child = symbol.children[0] # If symbol being returned doesn't have empty domain, return it if out.domain != []: return out # Otherwise we may need to broadcast it elif child.auxiliary_domains == {}: return out else: domain = child.auxiliary_domains["secondary"] if "tertiary" not in child.auxiliary_domains: return pybamm.PrimaryBroadcast(out, domain) else: auxiliary_domains = { "secondary": child.auxiliary_domains["tertiary"] } return pybamm.FullBroadcast(out, domain, auxiliary_domains) # Otherwise, use Integral to calculate average value else: geo = pybamm.geometric_parameters # Even if domain is "negative electrode", "separator", or # "positive electrode", and we know l, we still compute it as Integral(1, x) # as this will be easier to identify for simplifications later on if symbol.domain == ["negative particle"]: x = pybamm.standard_spatial_vars.x_n l = geo.l_n elif symbol.domain == ["positive particle"]: x = pybamm.standard_spatial_vars.x_p l = geo.l_p else: x = pybamm.SpatialVariable("x", domain=symbol.domain) v = pybamm.ones_like(symbol) l = pybamm.Integral(v, x) return Integral(symbol, x) / l
def test_definite_integral(self): # create discretisation mesh = get_mesh_for_testing(xpts=200, rpts=200) spatial_methods = { "macroscale": pybamm.FiniteVolume(), "negative particle": pybamm.FiniteVolume(), "positive particle": pybamm.FiniteVolume(), "current collector": pybamm.ZeroDimensionalMethod(), } disc = pybamm.Discretisation(mesh, spatial_methods) # lengths ln = mesh["negative electrode"][0].edges[-1] ls = mesh["separator"][0].edges[-1] - ln lp = 1 - (ln + ls) # macroscale variable var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", ["negative electrode", "separator"]) integral_eqn = pybamm.Integral(var, x) disc.set_variable_slices([var]) integral_eqn_disc = disc.process_symbol(integral_eqn) combined_submesh = mesh.combine_submeshes("negative electrode", "separator") constant_y = np.ones_like(combined_submesh[0].nodes[:, np.newaxis]) self.assertEqual(integral_eqn_disc.evaluate(None, constant_y), ln + ls) linear_y = combined_submesh[0].nodes np.testing.assert_array_almost_equal( integral_eqn_disc.evaluate(None, linear_y), (ln + ls)**2 / 2) cos_y = np.cos(combined_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal(integral_eqn_disc.evaluate( None, cos_y), np.sin(ln + ls), decimal=4) # domain not starting at zero var = pybamm.Variable("var", domain=["separator", "positive electrode"]) x = pybamm.SpatialVariable("x", ["separator", "positive electrode"]) integral_eqn = pybamm.Integral(var, x) disc.set_variable_slices([var]) integral_eqn_disc = disc.process_symbol(integral_eqn) combined_submesh = mesh.combine_submeshes("separator", "positive electrode") constant_y = np.ones_like(combined_submesh[0].nodes[:, np.newaxis]) self.assertEqual(integral_eqn_disc.evaluate(None, constant_y), ls + lp) linear_y = combined_submesh[0].nodes self.assertAlmostEqual( integral_eqn_disc.evaluate(None, linear_y)[0][0], (1 - (ln)**2) / 2) cos_y = np.cos(combined_submesh[0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal(integral_eqn_disc.evaluate( None, cos_y), np.sin(1) - np.sin(ln), decimal=4) # microscale variable var = pybamm.Variable("var", domain=["negative particle"]) r = pybamm.SpatialVariable("r", ["negative particle"]) integral_eqn = pybamm.Integral(var, r) disc.set_variable_slices([var]) integral_eqn_disc = disc.process_symbol(integral_eqn) constant_y = np.ones_like( mesh["negative particle"][0].nodes[:, np.newaxis]) np.testing.assert_array_almost_equal( integral_eqn_disc.evaluate(None, constant_y), 2 * np.pi**2) linear_y = mesh["negative particle"][0].nodes np.testing.assert_array_almost_equal(integral_eqn_disc.evaluate( None, linear_y), 4 * np.pi**2 / 3, decimal=3) one_over_y = 1 / mesh["negative particle"][0].nodes np.testing.assert_array_almost_equal( integral_eqn_disc.evaluate(None, one_over_y), 4 * np.pi**2)