Example #1
0
    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,
        )
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)
Example #5
0
    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)
Example #6
0
    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())
Example #7
0
    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)
Example #8
0
    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")
Example #9
0
    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)
Example #10
0
    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)
Example #11
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),
        )
Example #12
0
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)
Example #13
0
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)
Example #14
0
    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)
Example #15
0
    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
Example #16
0
    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
Example #17
0
    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)