def test_broadcast_to_edges(self): a = pybamm.Symbol("a") # primary broad_a = pybamm.PrimaryBroadcastToEdges(a, ["negative electrode"]) self.assertEqual(broad_a.name, "broadcast to edges") self.assertEqual(broad_a.children[0].name, a.name) self.assertEqual(broad_a.domain, ["negative electrode"]) self.assertTrue(broad_a.evaluates_on_edges("primary")) self.assertFalse(broad_a.broadcasts_to_nodes) self.assertEqual(broad_a.reduce_one_dimension(), a) # secondary a = pybamm.Symbol( "a", domain=["negative particle"], auxiliary_domains={"secondary": "current collector"}, ) broad_a = pybamm.SecondaryBroadcastToEdges(a, ["negative electrode"]) self.assertEqual(broad_a.domain, ["negative particle"]) self.assertEqual( broad_a.auxiliary_domains, { "secondary": ["negative electrode"], "tertiary": ["current collector"] }, ) self.assertTrue(broad_a.evaluates_on_edges("primary")) self.assertFalse(broad_a.broadcasts_to_nodes) # full a = pybamm.Symbol("a") broad_a = pybamm.FullBroadcastToEdges(a, ["negative electrode"], "current collector") self.assertEqual(broad_a.domain, ["negative electrode"]) self.assertEqual(broad_a.auxiliary_domains["secondary"], ["current collector"]) self.assertTrue(broad_a.evaluates_on_edges("primary")) self.assertFalse(broad_a.broadcasts_to_nodes) self.assertEqual( broad_a.reduce_one_dimension().id, pybamm.PrimaryBroadcastToEdges(a, "current collector").id, ) broad_a = pybamm.FullBroadcastToEdges(a, ["negative electrode"], {}) self.assertEqual(broad_a.reduce_one_dimension(), a) broad_a = pybamm.FullBroadcastToEdges( a, "negative particle", { "secondary": "negative electrode", "tertiary": "current collector" }, ) self.assertEqual( broad_a.reduce_one_dimension().id, pybamm.FullBroadcastToEdges(a, "negative electrode", "current collector").id, )
def test_r_average(self): a = pybamm.Scalar(1) average_a = pybamm.r_average(a) self.assertEqual(average_a.id, a.id) average_broad_a = pybamm.r_average( pybamm.PrimaryBroadcast(a, ["negative particle"])) self.assertEqual(average_broad_a.evaluate(), np.array([1])) for domain in [["negative particle"], ["positive particle"]]: a = pybamm.Symbol("a", domain=domain) r = pybamm.SpatialVariable("r", domain) av_a = pybamm.r_average(a) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, r.domain) # electrode domains go to current collector when averaged self.assertEqual(av_a.domain, []) # r-average of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the r-average of a symbol that evaluates on edges" ): pybamm.r_average(symbol_on_edges)
def test_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 test_div(self): # divergence of scalar symbol should fail a = pybamm.Symbol("a") with self.assertRaisesRegex( pybamm.DomainError, "Cannot take divergence of 'a' since its domain is empty", ): pybamm.Divergence(a) # divergence of variable evaluating on edges should fail a = pybamm.PrimaryBroadcast(pybamm.Scalar(1), "test") with self.assertRaisesRegex(TypeError, "evaluates on nodes"): pybamm.Divergence(a) # divergence of broadcast should return broadcasted zero a = pybamm.PrimaryBroadcastToEdges(pybamm.Variable("a"), "test domain") div = pybamm.div(a) self.assertIsInstance(div, pybamm.PrimaryBroadcast) self.assertIsInstance(div.child, pybamm.PrimaryBroadcast) self.assertIsInstance(div.child.child, pybamm.Scalar) self.assertEqual(div.child.child.value, 0) # otherwise divergence should work a = pybamm.Symbol("a", domain="test domain") div = pybamm.Divergence(pybamm.Gradient(a)) self.assertEqual(div.domain, a.domain)
def test_upwind_downwind(self): # upwind of scalar symbol should fail a = pybamm.Symbol("a") with self.assertRaisesRegex( pybamm.DomainError, "Cannot upwind 'a' since its domain is empty"): pybamm.Upwind(a) # upwind of variable evaluating on edges should fail a = pybamm.PrimaryBroadcastToEdges(pybamm.Scalar(1), "test") with self.assertRaisesRegex(TypeError, "evaluate on nodes"): pybamm.Upwind(a) # otherwise upwind should work a = pybamm.Symbol("a", domain="test domain") upwind = pybamm.upwind(a) self.assertIsInstance(upwind, pybamm.Upwind) self.assertEqual(upwind.children[0].name, a.name) self.assertEqual(upwind.domain, a.domain) # also test downwind a = pybamm.Symbol("a", domain="test domain") downwind = pybamm.downwind(a) self.assertIsInstance(downwind, pybamm.Downwind) self.assertEqual(downwind.children[0].name, a.name) self.assertEqual(downwind.domain, a.domain)
def test_broadcast_to_edges(self): a = pybamm.Symbol("a") broad_a = pybamm.PrimaryBroadcastToEdges(a, ["negative electrode"]) self.assertEqual(broad_a.name, "broadcast to edges") self.assertEqual(broad_a.children[0].name, a.name) self.assertEqual(broad_a.domain, ["negative electrode"]) self.assertTrue(broad_a.evaluates_on_edges()) a = pybamm.Symbol( "a", domain=["negative particle"], auxiliary_domains={"secondary": "current collector"}, ) broad_a = pybamm.SecondaryBroadcastToEdges(a, ["negative electrode"]) self.assertEqual(broad_a.domain, ["negative particle"]) self.assertEqual( broad_a.auxiliary_domains, { "secondary": ["negative electrode"], "tertiary": ["current collector"] }, ) self.assertTrue(broad_a.evaluates_on_edges()) a = pybamm.Symbol("a") broad_a = pybamm.FullBroadcastToEdges(a, ["negative electrode"], "current collector") self.assertEqual(broad_a.domain, ["negative electrode"]) self.assertEqual(broad_a.auxiliary_domains["secondary"], ["current collector"]) self.assertTrue(broad_a.evaluates_on_edges())
def test_yz_average(self): a = pybamm.Scalar(1) z_average_a = pybamm.z_average(a) yz_average_a = pybamm.yz_average(a) self.assertEqual(z_average_a.id, a.id) self.assertEqual(yz_average_a.id, a.id) z_average_broad_a = pybamm.z_average( pybamm.PrimaryBroadcast(a, ["current collector"])) yz_average_broad_a = pybamm.yz_average( pybamm.PrimaryBroadcast(a, ["current collector"])) self.assertEqual(z_average_broad_a.evaluate(), np.array([1])) self.assertEqual(yz_average_broad_a.evaluate(), np.array([1])) a = pybamm.Variable("a", domain=["current collector"]) y = pybamm.SpatialVariable("y", ["current collector"]) z = pybamm.SpatialVariable("z", ["current collector"]) z_av_a = pybamm.z_average(a) yz_av_a = pybamm.yz_average(a) self.assertIsInstance(yz_av_a, pybamm.Division) self.assertIsInstance(z_av_a, pybamm.Division) self.assertIsInstance(z_av_a.children[0], pybamm.Integral) self.assertIsInstance(yz_av_a.children[0], pybamm.Integral) self.assertEqual(z_av_a.children[0].integration_variable[0].domain, z.domain) self.assertEqual(yz_av_a.children[0].integration_variable[0].domain, y.domain) self.assertEqual(yz_av_a.children[0].integration_variable[1].domain, z.domain) self.assertIsInstance(z_av_a.children[1], pybamm.Integral) self.assertIsInstance(yz_av_a.children[1], pybamm.Integral) self.assertEqual(z_av_a.children[1].integration_variable[0].domain, a.domain) self.assertEqual(z_av_a.children[1].children[0].id, pybamm.ones_like(a).id) self.assertEqual(yz_av_a.children[1].integration_variable[0].domain, y.domain) self.assertEqual(yz_av_a.children[1].integration_variable[0].domain, z.domain) self.assertEqual(yz_av_a.children[1].children[0].id, pybamm.ones_like(a).id) self.assertEqual(z_av_a.domain, []) self.assertEqual(yz_av_a.domain, []) a = pybamm.Symbol("a", domain="bad domain") with self.assertRaises(pybamm.DomainError): pybamm.z_average(a) with self.assertRaises(pybamm.DomainError): pybamm.yz_average(a) # average of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the z-average of a symbol that evaluates on edges" ): pybamm.z_average(symbol_on_edges)
def test_boundary_value(self): a = pybamm.Scalar(1) boundary_a = pybamm.boundary_value(a, "right") self.assertEqual(boundary_a.id, a.id) boundary_broad_a = pybamm.boundary_value( pybamm.PrimaryBroadcast(a, ["negative electrode"]), "left") self.assertEqual(boundary_broad_a.evaluate(), np.array([1])) a = pybamm.Symbol("a", domain=["separator"]) boundary_a = pybamm.boundary_value(a, "right") self.assertIsInstance(boundary_a, pybamm.BoundaryValue) self.assertEqual(boundary_a.side, "right") self.assertEqual(boundary_a.domain, []) self.assertEqual(boundary_a.auxiliary_domains, {}) # test with secondary domain a_sec = pybamm.Symbol( "a", domain=["separator"], auxiliary_domains={"secondary": "current collector"}, ) boundary_a_sec = pybamm.boundary_value(a_sec, "right") self.assertEqual(boundary_a_sec.domain, ["current collector"]) self.assertEqual(boundary_a_sec.auxiliary_domains, {}) # test with secondary domain and tertiary domain a_tert = pybamm.Symbol( "a", domain=["separator"], auxiliary_domains={ "secondary": "current collector", "tertiary": "bla" }, ) boundary_a_tert = pybamm.boundary_value(a_tert, "right") self.assertEqual(boundary_a_tert.domain, ["current collector"]) self.assertEqual(boundary_a_tert.auxiliary_domains, {"secondary": ["bla"]}) # error if boundary value on tabs and domain is not "current collector" var = pybamm.Variable("var", domain=["negative electrode"]) with self.assertRaisesRegex(pybamm.ModelError, "Can only take boundary"): pybamm.boundary_value(var, "negative tab") pybamm.boundary_value(var, "positive tab") # boundary value of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the boundary value of a symbol that evaluates on edges", ): pybamm.boundary_value(symbol_on_edges, "right")
def test_x_average(self): a = pybamm.Scalar(1) average_a = pybamm.x_average(a) self.assertEqual(average_a.id, a.id) average_broad_a = pybamm.x_average( pybamm.PrimaryBroadcast(a, ["negative electrode"])) self.assertEqual(average_broad_a.evaluate(), np.array([1])) conc_broad = pybamm.Concatenation( pybamm.PrimaryBroadcast(1, ["negative electrode"]), pybamm.PrimaryBroadcast(2, ["separator"]), pybamm.PrimaryBroadcast(3, ["positive electrode"]), ) average_conc_broad = pybamm.x_average(conc_broad) self.assertIsInstance(average_conc_broad, pybamm.Division) for domain in [ ["negative electrode"], ["separator"], ["positive electrode"], ["negative electrode", "separator", "positive electrode"], ]: a = pybamm.Symbol("a", domain=domain) x = pybamm.SpatialVariable("x", domain) av_a = pybamm.x_average(a) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, x.domain) self.assertEqual(av_a.domain, []) a = pybamm.Symbol("a", domain="new domain") av_a = pybamm.x_average(a) self.assertEqual(av_a.domain, []) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, a.domain) self.assertIsInstance(av_a.children[1], pybamm.Integral) self.assertEqual(av_a.children[1].integration_variable[0].domain, a.domain) self.assertEqual(av_a.children[1].children[0].id, pybamm.ones_like(a).id) # x-average of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the x-average of a symbol that evaluates on edges" ): pybamm.x_average(symbol_on_edges)
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 test_broadcast_2D(self): # broadcast in 2D --> MatrixMultiplication var = pybamm.Variable("var", ["current collector"]) disc = get_1p1d_discretisation_for_testing() mesh = disc.mesh broad = pybamm.PrimaryBroadcast(var, "separator") disc.set_variable_slices([var]) broad_disc = disc.process_symbol(broad) self.assertIsInstance(broad_disc, pybamm.MatrixMultiplication) self.assertIsInstance(broad_disc.children[0], pybamm.Matrix) self.assertIsInstance(broad_disc.children[1], pybamm.StateVector) self.assertEqual( broad_disc.shape, (mesh["separator"][0].npts * mesh["current collector"][0].npts, 1), ) y_test = np.linspace(0, 1, mesh["current collector"][0].npts) np.testing.assert_array_equal( broad_disc.evaluate(y=y_test), np.outer(y_test, np.ones(mesh["separator"][0].npts)).reshape(-1, 1), ) # test broadcast to edges broad_to_edges = pybamm.PrimaryBroadcastToEdges(var, "separator") broad_to_edges_disc = disc.process_symbol(broad_to_edges) self.assertIsInstance(broad_to_edges_disc, pybamm.MatrixMultiplication) self.assertIsInstance(broad_to_edges_disc.children[0], pybamm.Matrix) self.assertIsInstance(broad_to_edges_disc.children[1], pybamm.StateVector) self.assertEqual( broad_to_edges_disc.shape, ((mesh["separator"][0].npts + 1) * mesh["current collector"][0].npts, 1), ) y_test = np.linspace(0, 1, mesh["current collector"][0].npts) np.testing.assert_array_equal( broad_to_edges_disc.evaluate(y=y_test), np.outer(y_test, np.ones(mesh["separator"][0].npts + 1)).reshape(-1, 1), )
def grad(symbol): """convenience function for creating a :class:`Gradient` Parameters ---------- symbol : :class:`Symbol` the gradient will be performed on this sub-symbol Returns ------- :class:`Gradient` the gradient of ``symbol`` """ # Gradient of a broadcast is zero if isinstance(symbol, pybamm.PrimaryBroadcast): new_child = pybamm.PrimaryBroadcast(0, symbol.child.domain) return pybamm.PrimaryBroadcastToEdges(new_child, symbol.domain) else: return Gradient(symbol)
def grad(expression): """convenience function for creating a :class:`Gradient` Parameters ---------- expression : :class:`Symbol` the gradient will be performed on this sub-expression Returns ------- :class:`Gradient` the gradient of ``expression`` """ # Gradient of a broadcast is zero if isinstance(expression, pybamm.PrimaryBroadcast): new_child = pybamm.PrimaryBroadcast(0, expression.child.domain) return pybamm.PrimaryBroadcastToEdges(new_child, expression.domain) else: return Gradient(expression)
def test_symbol_simplify(self): a = pybamm.Scalar(0, domain="domain") b = pybamm.Scalar(1) c = pybamm.Parameter("c") d = pybamm.Scalar(-1) e = pybamm.Scalar(2) g = pybamm.Variable("g") gdot = pybamm.VariableDot("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": b}) self.assertIsInstance((f).simplify(), pybamm.FunctionParameter) self.assertEqual((f).simplify().children[0].id, b.id) f = pybamm.FunctionParameter("function", {"a": a, "b": 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", domain="domain") grad_v = pybamm.grad(v) self.assertIsInstance(grad_v.simplify(), pybamm.Gradient) # Divergence div_b = pybamm.div(pybamm.PrimaryBroadcastToEdges(b, "domain")) self.assertIsInstance(div_b.simplify(), pybamm.PrimaryBroadcast) self.assertEqual(div_b.simplify().child.child.evaluate(), 0) self.assertIsInstance((pybamm.div(pybamm.grad(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 * b).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2.0) self.assertIsInstance(expr.children[1], pybamm.Variable) expr = (e * gdot * b).simplify() self.assertIsInstance(expr, pybamm.Multiplication) self.assertIsInstance(expr.children[0], pybamm.Scalar) self.assertEqual(expr.children[0].evaluate(), 2.0) self.assertIsInstance(expr.children[1], pybamm.VariableDot) 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 get_coupled_variables(self, variables): c_e_av = variables["X-averaged electrolyte concentration"] i_boundary_cc_0 = variables[ "Leading-order current collector current density"] c_e_n = variables["Negative electrolyte concentration"] c_e_s = variables["Separator electrolyte concentration"] c_e_p = variables["Positive electrolyte concentration"] c_e_n0 = pybamm.boundary_value(c_e_n, "left") delta_phi_n_av = variables[ "X-averaged negative electrode surface potential difference"] phi_s_n_av = variables["X-averaged negative electrode potential"] tor_n = variables["Negative electrolyte tortuosity"] tor_s = variables["Separator tortuosity"] tor_p = variables["Positive electrolyte tortuosity"] T_av = variables["X-averaged cell temperature"] T_av_n = pybamm.PrimaryBroadcast(T_av, "negative electrode") T_av_s = pybamm.PrimaryBroadcast(T_av, "separator") T_av_p = pybamm.PrimaryBroadcast(T_av, "positive electrode") param = self.param l_n = param.l_n l_p = param.l_p x_n = pybamm.standard_spatial_vars.x_n x_s = pybamm.standard_spatial_vars.x_s x_p = pybamm.standard_spatial_vars.x_p x_n_edge = pybamm.standard_spatial_vars.x_n_edge x_p_edge = pybamm.standard_spatial_vars.x_p_edge chi_av = param.chi(c_e_av, T_av) chi_av_n = pybamm.PrimaryBroadcast(chi_av, "negative electrode") chi_av_s = pybamm.PrimaryBroadcast(chi_av, "separator") chi_av_p = pybamm.PrimaryBroadcast(chi_av, "positive electrode") # electrolyte current i_e_n = i_boundary_cc_0 * x_n / l_n i_e_s = pybamm.PrimaryBroadcast(i_boundary_cc_0, "separator") i_e_p = i_boundary_cc_0 * (1 - x_p) / l_p i_e = pybamm.Concatenation(i_e_n, i_e_s, i_e_p) i_e_n_edge = i_boundary_cc_0 * x_n_edge / l_n i_e_s_edge = pybamm.PrimaryBroadcastToEdges(i_boundary_cc_0, "separator") i_e_p_edge = i_boundary_cc_0 * (1 - x_p_edge) / l_p # electrolyte potential indef_integral_n = (pybamm.IndefiniteIntegral( i_e_n_edge / (param.kappa_e(c_e_n, T_av_n) * tor_n), x_n) * param.C_e / param.gamma_e) indef_integral_s = (pybamm.IndefiniteIntegral( i_e_s_edge / (param.kappa_e(c_e_s, T_av_s) * tor_s), x_s) * param.C_e / param.gamma_e) indef_integral_p = (pybamm.IndefiniteIntegral( i_e_p_edge / (param.kappa_e(c_e_p, T_av_p) * tor_p), x_p) * param.C_e / param.gamma_e) integral_n = indef_integral_n integral_s = indef_integral_s + pybamm.boundary_value( integral_n, "right") integral_p = indef_integral_p + pybamm.boundary_value( integral_s, "right") phi_e_const = ( -delta_phi_n_av + phi_s_n_av - (chi_av * (1 + param.Theta * T_av) * pybamm.x_average( self._higher_order_macinnes_function(c_e_n / c_e_n0))) + pybamm.x_average(integral_n)) phi_e_n = (phi_e_const + (chi_av_n * (1 + param.Theta * T_av_n) * self._higher_order_macinnes_function(c_e_n / c_e_n0)) - integral_n) phi_e_s = (phi_e_const + (chi_av_s * (1 + param.Theta * T_av_s) * self._higher_order_macinnes_function(c_e_s / c_e_n0)) - integral_s) phi_e_p = (phi_e_const + (chi_av_p * (1 + param.Theta * T_av_p) * self._higher_order_macinnes_function(c_e_p / c_e_n0)) - integral_p) # concentration overpotential eta_c_av = (chi_av * (1 + param.Theta * T_av) * (pybamm.x_average( self._higher_order_macinnes_function( c_e_p / c_e_av)) - pybamm.x_average( self._higher_order_macinnes_function(c_e_n / c_e_av)))) # average electrolyte ohmic losses delta_phi_e_av = -(pybamm.x_average(integral_p) - pybamm.x_average(integral_n)) variables.update( self._get_standard_potential_variables(phi_e_n, phi_e_s, phi_e_p)) variables.update(self._get_standard_current_variables(i_e)) variables.update( self._get_split_overpotential(eta_c_av, delta_phi_e_av)) return variables
def test_binary_simplifications(self): a = pybamm.Scalar(0, domain="domain") b = pybamm.Scalar(1) c = pybamm.Parameter("c") v = pybamm.Vector(np.zeros((10, 1))) v1 = pybamm.Vector(np.ones((10, 1))) f = pybamm.StateVector(slice(0, 10)) var = pybamm.Variable("var", domain="domain") broad0 = pybamm.PrimaryBroadcast(0, "domain") broad1 = pybamm.PrimaryBroadcast(1, "domain") broad2 = pybamm.PrimaryBroadcast(2, "domain") broad2_edge = pybamm.PrimaryBroadcastToEdges(2, "domain") # power self.assertEqual((c ** a).id, pybamm.Scalar(1).id) self.assertEqual((0 ** c).id, pybamm.Scalar(0).id) self.assertEqual((c ** b).id, c.id) # power with broadcasts self.assertEqual((c ** broad2).id, pybamm.PrimaryBroadcast(c ** 2, "domain").id) self.assertEqual((broad2 ** c).id, pybamm.PrimaryBroadcast(2 ** c, "domain").id) self.assertEqual( (broad2 ** pybamm.PrimaryBroadcast(c, "domain")).id, pybamm.PrimaryBroadcast(2 ** c, "domain").id, ) # power with broadcasts to edge self.assertIsInstance(var ** broad2_edge, pybamm.Power) self.assertEqual((var ** broad2_edge).left.id, var.id) self.assertEqual((var ** broad2_edge).right.id, broad2_edge.id) # addition self.assertIsInstance((a + b), pybamm.Scalar) self.assertEqual((a + b).evaluate(), 1) self.assertIsInstance((b + b), pybamm.Scalar) self.assertEqual((b + b).evaluate(), 2) self.assertIsInstance((b + a), pybamm.Scalar) self.assertEqual((b + a).evaluate(), 1) self.assertIsInstance((0 + b), pybamm.Scalar) self.assertEqual((0 + b).evaluate(), 1) self.assertIsInstance((a + c), pybamm.Parameter) self.assertIsInstance((c + a), pybamm.Parameter) self.assertIsInstance((c + b), pybamm.Addition) self.assertIsInstance((b + c), pybamm.Addition) # rearranging additions self.assertEqual(((c + 1) + 2).id, (c + 3).id) self.assertEqual(((1 + c) + 2).id, (3 + c).id) self.assertEqual((2 + (c + 1)).id, (3 + c).id) self.assertEqual((2 + (1 + c)).id, (3 + c).id) # addition with broadcast zero self.assertIsInstance((b + broad0), pybamm.PrimaryBroadcast) np.testing.assert_array_equal((b + broad0).child.evaluate(), 1) np.testing.assert_array_equal((b + broad0).domain, "domain") self.assertIsInstance((broad0 + b), pybamm.PrimaryBroadcast) np.testing.assert_array_equal((broad0 + b).child.evaluate(), 1) np.testing.assert_array_equal((broad0 + b).domain, "domain") # addition with broadcasts self.assertEqual((c + broad2).id, pybamm.PrimaryBroadcast(c + 2, "domain").id) self.assertEqual((broad2 + c).id, pybamm.PrimaryBroadcast(2 + c, "domain").id) # subtraction self.assertIsInstance((a - b), pybamm.Scalar) self.assertEqual((a - b).evaluate(), -1) self.assertIsInstance((b - b), pybamm.Scalar) self.assertEqual((b - b).evaluate(), 0) self.assertIsInstance((b - a), pybamm.Scalar) self.assertEqual((b - a).evaluate(), 1) # subtraction with broadcasts self.assertEqual((c - broad2).id, pybamm.PrimaryBroadcast(c - 2, "domain").id) self.assertEqual((broad2 - c).id, pybamm.PrimaryBroadcast(2 - c, "domain").id) # subtraction from itself self.assertEqual((c - c).id, pybamm.Scalar(0).id) self.assertEqual((broad2 - broad2).id, broad0.id) # addition and subtraction with matrix zero self.assertIsInstance((b + v), pybamm.Array) np.testing.assert_array_equal((b + v).evaluate(), np.ones((10, 1))) self.assertIsInstance((v + b), pybamm.Array) np.testing.assert_array_equal((v + b).evaluate(), np.ones((10, 1))) self.assertIsInstance((b - v), pybamm.Array) np.testing.assert_array_equal((b - v).evaluate(), np.ones((10, 1))) self.assertIsInstance((v - b), pybamm.Array) np.testing.assert_array_equal((v - b).evaluate(), -np.ones((10, 1))) # multiplication self.assertIsInstance((a * b), pybamm.Scalar) self.assertEqual((a * b).evaluate(), 0) self.assertIsInstance((b * a), pybamm.Scalar) self.assertEqual((b * a).evaluate(), 0) self.assertIsInstance((b * b), pybamm.Scalar) self.assertEqual((b * b).evaluate(), 1) self.assertIsInstance((a * a), pybamm.Scalar) self.assertEqual((a * a).evaluate(), 0) self.assertIsInstance((a * c), pybamm.Scalar) self.assertEqual((a * c).evaluate(), 0) self.assertIsInstance((c * a), pybamm.Scalar) self.assertEqual((c * a).evaluate(), 0) self.assertIsInstance((b * c), pybamm.Parameter) self.assertIsInstance((2 * c), pybamm.Multiplication) # multiplication with -1 self.assertEqual((c * -1).id, (-c).id) self.assertEqual((-1 * c).id, (-c).id) # multiplication with a negation self.assertEqual((-c * 4).id, (c * -4).id) self.assertEqual((4 * -c).id, (-4 * c).id) # multiplication with broadcasts self.assertEqual((c * broad2).id, pybamm.PrimaryBroadcast(c * 2, "domain").id) self.assertEqual((broad2 * c).id, pybamm.PrimaryBroadcast(2 * c, "domain").id) # multiplication with matrix zero self.assertIsInstance((b * v), pybamm.Array) np.testing.assert_array_equal((b * v).evaluate(), np.zeros((10, 1))) self.assertIsInstance((v * b), pybamm.Array) np.testing.assert_array_equal((v * b).evaluate(), np.zeros((10, 1))) # multiplication with matrix one self.assertEqual((f * v1), f) self.assertEqual((v1 * f), f) # multiplication with matrix minus one self.assertEqual((f * (-v1)).id, (-f).id) self.assertEqual(((-v1) * f).id, (-f).id) # multiplication with broadcast self.assertEqual((var * broad2).id, (var * 2).id) self.assertEqual((broad2 * var).id, (2 * var).id) # multiplication with broadcast one self.assertEqual((var * broad1).id, var.id) self.assertEqual((broad1 * var).id, var.id) # multiplication with broadcast minus one self.assertEqual((var * -broad1).id, (-var).id) self.assertEqual((-broad1 * var).id, (-var).id) # division by itself self.assertEqual((c / c).id, pybamm.Scalar(1).id) self.assertEqual((broad2 / broad2).id, broad1.id) # division with a negation self.assertEqual((-c / 4).id, (c / -4).id) self.assertEqual((4 / -c).id, (-4 / c).id) # division with broadcasts self.assertEqual((c / broad2).id, pybamm.PrimaryBroadcast(c / 2, "domain").id) self.assertEqual((broad2 / c).id, pybamm.PrimaryBroadcast(2 / c, "domain").id) # division with matrix one self.assertEqual((f / v1), f) self.assertEqual((f / -v1).id, (-f).id) # division by zero with self.assertRaises(ZeroDivisionError): b / a
def test_x_average(self): a = pybamm.Scalar(4) average_a = pybamm.x_average(a) self.assertEqual(average_a.id, a.id) # average of a broadcast is the child average_broad_a = pybamm.x_average( pybamm.PrimaryBroadcast(a, ["negative electrode"])) self.assertEqual(average_broad_a.id, pybamm.Scalar(4).id) # average of a number times a broadcast is the number times the child average_two_broad_a = pybamm.x_average( 2 * pybamm.PrimaryBroadcast(a, ["negative electrode"])) self.assertEqual(average_two_broad_a.id, pybamm.Scalar(8).id) average_t_broad_a = pybamm.x_average( pybamm.t * pybamm.PrimaryBroadcast(a, ["negative electrode"])) self.assertEqual(average_t_broad_a.id, (pybamm.t * pybamm.Scalar(4)).id) # x-average of concatenation of broadcasts conc_broad = pybamm.concatenation( pybamm.PrimaryBroadcast(1, ["negative electrode"]), pybamm.PrimaryBroadcast(2, ["separator"]), pybamm.PrimaryBroadcast(3, ["positive electrode"]), ) average_conc_broad = pybamm.x_average(conc_broad) self.assertIsInstance(average_conc_broad, pybamm.Division) self.assertEqual(average_conc_broad.domain, []) # with auxiliary domains conc_broad = pybamm.concatenation( pybamm.FullBroadcast( 1, ["negative electrode"], auxiliary_domains={"secondary": "current collector"}, ), pybamm.FullBroadcast( 2, ["separator"], auxiliary_domains={"secondary": "current collector"}), pybamm.FullBroadcast( 3, ["positive electrode"], auxiliary_domains={"secondary": "current collector"}, ), ) average_conc_broad = pybamm.x_average(conc_broad) self.assertIsInstance(average_conc_broad, pybamm.PrimaryBroadcast) self.assertEqual(average_conc_broad.domain, ["current collector"]) conc_broad = pybamm.concatenation( pybamm.FullBroadcast( 1, ["negative electrode"], auxiliary_domains={ "secondary": "current collector", "tertiary": "test", }, ), pybamm.FullBroadcast( 2, ["separator"], auxiliary_domains={ "secondary": "current collector", "tertiary": "test", }, ), pybamm.FullBroadcast( 3, ["positive electrode"], auxiliary_domains={ "secondary": "current collector", "tertiary": "test", }, ), ) average_conc_broad = pybamm.x_average(conc_broad) self.assertIsInstance(average_conc_broad, pybamm.FullBroadcast) self.assertEqual(average_conc_broad.domain, ["current collector"]) self.assertEqual(average_conc_broad.auxiliary_domains, {"secondary": ["test"]}) # x-average of broadcast for domain in [ ["negative electrode"], ["separator"], ["positive electrode"], ]: a = pybamm.Variable("a", domain=domain) x = pybamm.SpatialVariable("x", domain) av_a = pybamm.x_average(a) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, x.domain) self.assertEqual(av_a.domain, []) # whole electrode domain is different as the division by 1 gets simplified out domain = ["negative electrode", "separator", "positive electrode"] a = pybamm.Variable("a", domain=domain) x = pybamm.SpatialVariable("x", domain) av_a = pybamm.x_average(a) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, x.domain) self.assertEqual(av_a.domain, []) a = pybamm.Variable("a", domain="new domain") av_a = pybamm.x_average(a) self.assertEqual(av_a.domain, []) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[0].integration_variable[0].domain, a.domain) self.assertIsInstance(av_a.children[1], pybamm.Integral) self.assertEqual(av_a.children[1].integration_variable[0].domain, a.domain) self.assertEqual(av_a.children[1].children[0].id, pybamm.ones_like(a).id) # x-average of symbol that evaluates on edges raises error symbol_on_edges = pybamm.PrimaryBroadcastToEdges(1, "domain") with self.assertRaisesRegex( ValueError, "Can't take the x-average of a symbol that evaluates on edges" ): pybamm.x_average(symbol_on_edges) # Particle domains geo = pybamm.geometric_parameters l_n = geo.l_n l_p = geo.l_p a = pybamm.Symbol( "a", domain="negative particle", auxiliary_domains={"secondary": "negative electrode"}, ) av_a = pybamm.x_average(a) self.assertEqual(a.domain, ["negative particle"]) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[1].id, l_n.id) a = pybamm.Symbol( "a", domain="positive particle", auxiliary_domains={"secondary": "positive electrode"}, ) av_a = pybamm.x_average(a) self.assertEqual(a.domain, ["positive particle"]) self.assertIsInstance(av_a, pybamm.Division) self.assertIsInstance(av_a.children[0], pybamm.Integral) self.assertEqual(av_a.children[1].id, l_p.id)