Esempio n. 1
0
    def test_secondary_broadcast_2D(self):
        # secondary broadcast in 2D --> Matrix multiplication
        disc = get_discretisation_for_testing()
        mesh = disc.mesh
        var = pybamm.Variable("var", domain=["negative particle"])
        broad = pybamm.SecondaryBroadcast(var, "negative electrode")

        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["negative particle"].npts * mesh["negative electrode"].npts, 1),
        )
        broad = pybamm.SecondaryBroadcast(var, "negative electrode")

        # test broadcast to edges
        broad_to_edges = pybamm.SecondaryBroadcastToEdges(var, "negative electrode")
        disc.set_variable_slices([var])
        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["negative particle"].npts * (mesh["negative electrode"].npts + 1),
                1,
            ),
        )
Esempio n. 2
0
    def test_secondary_broadcast(self):
        a = pybamm.Symbol(
            "a",
            domain=["negative particle"],
            auxiliary_domains={"secondary": "current collector"},
        )
        broad_a = pybamm.SecondaryBroadcast(a, ["negative electrode"])
        self.assertEqual(broad_a.domain, ["negative particle"])
        self.assertEqual(
            broad_a.auxiliary_domains,
            {
                "secondary": ["negative electrode"],
                "tertiary": ["current collector"]
            },
        )

        a = pybamm.Symbol("a", domain="negative particle")
        with self.assertRaisesRegex(pybamm.DomainError,
                                    "Secondary broadcast from particle"):
            pybamm.SecondaryBroadcast(a, "current collector")
        a = pybamm.Symbol("a", domain="negative electrode")
        with self.assertRaisesRegex(pybamm.DomainError,
                                    "Secondary broadcast from electrode"):
            pybamm.SecondaryBroadcast(a, "negative particle")

        a = pybamm.Symbol("a", domain="current collector")
        with self.assertRaisesRegex(pybamm.DomainError,
                                    "Cannot do secondary broadcast"):
            pybamm.SecondaryBroadcast(a, "electrode")
    def test_processed_var_2D_secondary_broadcast(self):
        var = pybamm.Variable("var", domain=["negative particle"])
        broad_var = pybamm.SecondaryBroadcast(var, "negative electrode")
        x = pybamm.SpatialVariable("x", domain=["negative electrode"])
        r = pybamm.SpatialVariable("r", domain=["negative particle"])

        disc = tests.get_discretisation_for_testing()
        disc.set_variable_slices([var])
        x_sol = disc.process_symbol(x).entries[:, 0]
        r_sol = disc.process_symbol(r).entries[:, 0]
        var_sol = disc.process_symbol(broad_var)
        t_sol = np.linspace(0, 1)
        y_sol = np.ones(len(x_sol) * len(r_sol))[:, np.newaxis] * np.linspace(0, 5)

        processed_var = pybamm.ProcessedVariable(
            var_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        # 3 vectors
        np.testing.assert_array_equal(
            processed_var(t_sol, x_sol, r_sol).shape, (10, 40, 50)
        )
        np.testing.assert_array_equal(
            processed_var(t_sol, x_sol, r_sol),
            np.reshape(y_sol, [len(r_sol), len(x_sol), len(t_sol)]),
        )
        # 2 vectors, 1 scalar
        np.testing.assert_array_equal(processed_var(0.5, x_sol, r_sol).shape, (10, 40))
        np.testing.assert_array_equal(processed_var(t_sol, 0.2, r_sol).shape, (10, 50))
        np.testing.assert_array_equal(processed_var(t_sol, x_sol, 0.5).shape, (40, 50))
        # 1 vectors, 2 scalar
        np.testing.assert_array_equal(processed_var(0.5, 0.2, r_sol).shape, (10,))
        np.testing.assert_array_equal(processed_var(0.5, x_sol, 0.5).shape, (40,))
        np.testing.assert_array_equal(processed_var(t_sol, 0.2, 0.5).shape, (50,))
        # 3 scalars
        np.testing.assert_array_equal(processed_var(0.2, 0.2, 0.2).shape, ())

        # positive particle
        var = pybamm.Variable("var", domain=["positive particle"])
        broad_var = pybamm.SecondaryBroadcast(var, "positive electrode")
        x = pybamm.SpatialVariable("x", domain=["positive electrode"])
        r = pybamm.SpatialVariable("r", domain=["positive particle"])

        disc.set_variable_slices([var])
        x_sol = disc.process_symbol(x).entries[:, 0]
        r_sol = disc.process_symbol(r).entries[:, 0]
        var_sol = disc.process_symbol(broad_var)
        t_sol = np.linspace(0, 1)
        y_sol = np.ones(len(x_sol) * len(r_sol))[:, np.newaxis] * np.linspace(0, 5)

        processed_var = pybamm.ProcessedVariable(
            var_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        # 3 vectors
        np.testing.assert_array_equal(
            processed_var(t_sol, x_sol, r_sol).shape, (10, 35, 50)
        )
    def get_fundamental_variables(self):
        if self.domain == "Negative":
            c_s_xav = pybamm.standard_variables.c_s_n_xav
            c_s = pybamm.SecondaryBroadcast(c_s_xav, ["negative electrode"])

        elif self.domain == "Positive":
            c_s_xav = pybamm.standard_variables.c_s_p_xav
            c_s = pybamm.SecondaryBroadcast(c_s_xav, ["positive electrode"])

        variables = self._get_standard_concentration_variables(c_s, c_s_xav)

        return variables
Esempio n. 5
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 a symbol that is broadcast to x
        # takes the average of the child then broadcasts it
        a = pybamm.Scalar(1, domain="positive particle")
        broad_a = pybamm.SecondaryBroadcast(a, "positive electrode")
        average_broad_a = pybamm.r_average(broad_a)
        self.assertIsInstance(average_broad_a, pybamm.PrimaryBroadcast)
        self.assertEqual(average_broad_a.domain, ["positive electrode"])
        self.assertEqual(average_broad_a.children[0].id, pybamm.r_average(a).id)

        # 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 get_fundamental_variables(self):
        # The particle concentration is uniform throughout the particle, so we
        # can just use the surface value. This avoids dealing with both
        # x *and* r averaged quantities, which may be confusing.

        if self.domain == "Negative":
            c_s_surf_xav = pybamm.standard_variables.c_s_n_surf_xav
            c_s_xav = pybamm.PrimaryBroadcast(c_s_surf_xav,
                                              ["negative particle"])
            c_s = pybamm.SecondaryBroadcast(c_s_xav, ["negative electrode"])

            N_s = pybamm.FullBroadcastToEdges(
                0,
                ["negative particle"],
                auxiliary_domains={
                    "secondary": "negative electrode",
                    "tertiary": "current collector",
                },
            )
            N_s_xav = pybamm.FullBroadcast(0, "negative electrode",
                                           "current collector")

        elif self.domain == "Positive":
            c_s_surf_xav = pybamm.standard_variables.c_s_p_surf_xav
            c_s_xav = pybamm.PrimaryBroadcast(c_s_surf_xav,
                                              ["positive particle"])
            c_s = pybamm.SecondaryBroadcast(c_s_xav, ["positive electrode"])

            N_s = pybamm.FullBroadcastToEdges(
                0,
                ["positive particle"],
                auxiliary_domains={
                    "secondary": "positive electrode",
                    "tertiary": "current collector",
                },
            )
            N_s_xav = pybamm.FullBroadcast(0, "positive electrode",
                                           "current collector")

        variables = self._get_standard_concentration_variables(c_s, c_s_xav)
        variables.update(self._get_standard_flux_variables(N_s, N_s_xav))

        return variables
Esempio n. 7
0
    def test_symbol_new_copy(self):
        a = pybamm.Parameter("a")
        b = pybamm.Parameter("b")
        v_n = pybamm.Variable("v", "negative electrode")
        x_n = pybamm.standard_spatial_vars.x_n
        v_s = pybamm.Variable("v", "separator")
        vec = pybamm.Vector([1, 2, 3, 4, 5])
        mat = pybamm.Matrix([[1, 2], [3, 4]])
        mesh = get_mesh_for_testing()

        for symbol in [
                a + b,
                a - b,
                a * b,
                a / b,
                a**b,
                -a,
                abs(a),
                pybamm.Function(np.sin, a),
                pybamm.FunctionParameter("function", {"a": a}),
                pybamm.grad(v_n),
                pybamm.div(pybamm.grad(v_n)),
                pybamm.upwind(v_n),
                pybamm.IndefiniteIntegral(v_n, x_n),
                pybamm.BackwardIndefiniteIntegral(v_n, x_n),
                pybamm.BoundaryValue(v_n, "right"),
                pybamm.BoundaryGradient(v_n, "right"),
                pybamm.PrimaryBroadcast(a, "domain"),
                pybamm.SecondaryBroadcast(v_n, "current collector"),
                pybamm.FullBroadcast(a, "domain",
                                     {"secondary": "other domain"}),
                pybamm.concatenation(v_n, v_s),
                pybamm.NumpyConcatenation(a, b, v_s),
                pybamm.DomainConcatenation([v_n, v_s], mesh),
                pybamm.Parameter("param"),
                pybamm.InputParameter("param"),
                pybamm.StateVector(slice(0, 56)),
                pybamm.Matrix(np.ones((50, 40))),
                pybamm.SpatialVariable("x", ["negative electrode"]),
                pybamm.t,
                pybamm.Index(vec, 1),
                pybamm.NotConstant(a),
                pybamm.ExternalVariable(
                    "external variable",
                    20,
                    domain="test",
                    auxiliary_domains={"secondary": "test2"},
                ),
                pybamm.minimum(a, b),
                pybamm.maximum(a, b),
                pybamm.SparseStack(mat, mat),
        ]:
            self.assertEqual(symbol.id, symbol.new_copy().id)
Esempio n. 8
0
    def test_secondary_broadcast(self):
        a = pybamm.Symbol(
            "a",
            domain=["negative particle"],
            auxiliary_domains={"secondary": "current collector"},
        )
        broad_a = pybamm.SecondaryBroadcast(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.broadcasts_to_nodes)

        with self.assertRaises(NotImplementedError):
            broad_a.reduce_one_dimension()

        a = pybamm.Symbol("a")
        with self.assertRaisesRegex(TypeError, "empty domain"):
            pybamm.SecondaryBroadcast(a, "current collector")
        a = pybamm.Symbol("a", domain="negative particle")
        with self.assertRaisesRegex(pybamm.DomainError,
                                    "Secondary broadcast from particle"):
            pybamm.SecondaryBroadcast(a, "current collector")
        a = pybamm.Symbol("a", domain="negative electrode")
        with self.assertRaisesRegex(pybamm.DomainError,
                                    "Secondary broadcast from electrode"):
            pybamm.SecondaryBroadcast(a, "negative particle")

        a = pybamm.Symbol("a", domain="current collector")
        with self.assertRaisesRegex(pybamm.DomainError,
                                    "Cannot do secondary broadcast"):
            pybamm.SecondaryBroadcast(a, "electrode")
Esempio n. 9
0
    def test_symbol_new_copy(self):
        a = pybamm.Scalar(0)
        b = pybamm.Scalar(1)
        v_n = pybamm.Variable("v", "negative electrode")
        x_n = pybamm.standard_spatial_vars.x_n
        v_s = pybamm.Variable("v", "separator")
        vec = pybamm.Vector(np.array([1, 2, 3, 4, 5]))
        mesh = get_mesh_for_testing()

        for symbol in [
                a + b,
                a - b,
                a * b,
                a / b,
                a**b,
                -a,
                abs(a),
                pybamm.Function(np.sin, a),
                pybamm.FunctionParameter("function", {"a": a}),
                pybamm.grad(v_n),
                pybamm.div(pybamm.grad(v_n)),
                pybamm.Integral(a, pybamm.t),
                pybamm.IndefiniteIntegral(v_n, x_n),
                pybamm.BackwardIndefiniteIntegral(v_n, x_n),
                pybamm.BoundaryValue(v_n, "right"),
                pybamm.BoundaryGradient(v_n, "right"),
                pybamm.PrimaryBroadcast(a, "domain"),
                pybamm.SecondaryBroadcast(v_n, "current collector"),
                pybamm.FullBroadcast(a, "domain",
                                     {"secondary": "other domain"}),
                pybamm.Concatenation(v_n, v_s),
                pybamm.NumpyConcatenation(a, b, v_s),
                pybamm.DomainConcatenation([v_n, v_s], mesh),
                pybamm.Parameter("param"),
                pybamm.InputParameter("param"),
                pybamm.StateVector(slice(0, 56)),
                pybamm.Matrix(np.ones((50, 40))),
                pybamm.SpatialVariable("x", ["negative electrode"]),
                pybamm.t,
                pybamm.Index(vec, 1),
        ]:
            self.assertEqual(symbol.id, symbol.new_copy().id)
Esempio n. 10
0
    def get_coupled_variables(self, variables):
        c_s_xav = variables["X-averaged " + self.domain.lower() +
                            " particle concentration"]
        T_xav = pybamm.PrimaryBroadcast(
            variables["X-averaged " + self.domain.lower() +
                      " electrode temperature"],
            [self.domain.lower() + " particle"],
        )

        if self.domain == "Negative":
            N_s_xav = -self.param.D_n(c_s_xav, T_xav) * pybamm.grad(c_s_xav)

        elif self.domain == "Positive":
            N_s_xav = -self.param.D_p(c_s_xav, T_xav) * pybamm.grad(c_s_xav)

        N_s = pybamm.SecondaryBroadcast(N_s_xav,
                                        [self._domain.lower() + " electrode"])

        variables.update(self._get_standard_flux_variables(N_s, N_s_xav))

        return variables
Esempio n. 11
0
    def get_coupled_variables(self, variables):
        c_s_rxav = variables["Average " + self.domain.lower() +
                             " particle concentration"]
        i_boundary_cc = variables["Current collector current density"]
        T_xav = pybamm.PrimaryBroadcast(
            variables["X-averaged " + self.domain.lower() +
                      " electrode temperature"],
            [self.domain.lower() + " particle"],
        )

        # Set surface concentration based on polynomial order
        if self.name == "uniform profile":
            # The concentration is uniform so the surface value is equal to
            # the average
            c_s_surf_xav = c_s_rxav
        elif self.name == "quadratic profile":
            # The surface concentration is computed from the average concentration
            # and boundary flux
            # Note 1: here we use the total average interfacial current for the single
            # particle. We explicitly write this as the current density divided by the
            # electrode thickness instead of getting the average current from the
            # interface submodel since the interface submodel requires the surface
            # concentration to be defined first to compute the exchange current density.
            # Explicitly writing out the average interfacial current here avoids
            # KeyErrors due to variables not being set in the "right" order.
            # Note 2: the concentration, c, inside the diffusion coefficient, D, here
            # should really be the surface value, but this requires solving a nonlinear
            # equation for c_surf (if the diffusion coefficient is nonlinear), adding
            # an extra algebraic equation to solve. For now, using the average c is an
            # ok approximation and means the SPM(e) still gives a system of ODEs rather
            # than DAEs.
            if self.domain == "Negative":
                j_xav = i_boundary_cc / self.param.l_n
                c_s_surf_xav = c_s_rxav - self.param.C_n * (
                    j_xav / 5 / self.param.a_R_n /
                    self.param.D_n(c_s_rxav, pybamm.surf(T_xav)))

            if self.domain == "Positive":
                j_xav = -i_boundary_cc / self.param.l_p
                c_s_surf_xav = c_s_rxav - self.param.C_p * (
                    j_xav / 5 / self.param.a_R_p / self.param.gamma_p /
                    self.param.D_p(c_s_rxav, pybamm.surf(T_xav)))
        elif self.name == "quartic profile":
            # The surface concentration is computed from the average concentration,
            # the average concentration gradient and the boundary flux (see notes
            # for the case order=2)
            q_s_rxav = variables["Average " + self.domain.lower() +
                                 " particle concentration gradient"]
            if self.domain == "Negative":
                j_xav = i_boundary_cc / self.param.l_n
                c_s_surf_xav = (c_s_rxav + 8 * q_s_rxav / 35 - self.param.C_n *
                                (j_xav / 35 / self.param.a_R_n /
                                 self.param.D_n(c_s_rxav, pybamm.surf(T_xav))))

            if self.domain == "Positive":
                j_xav = -i_boundary_cc / self.param.l_p
                c_s_surf_xav = (
                    c_s_rxav + 8 * q_s_rxav / 35 - self.param.C_p *
                    (j_xav / 35 / self.param.a_R_p / self.param.gamma_p /
                     self.param.D_p(c_s_rxav, pybamm.surf(T_xav))))

        # Set concentration depending on polynomial order
        if self.name == "uniform profile":
            # The concentration is uniform
            c_s_xav = pybamm.PrimaryBroadcast(
                c_s_rxav, [self.domain.lower() + " particle"])
        elif self.name == "quadratic profile":
            # The concentration is given by c = A + B*r**2
            A = pybamm.PrimaryBroadcast(
                (1 / 2) * (5 * c_s_rxav - 3 * c_s_surf_xav),
                [self.domain.lower() + " particle"],
            )
            B = pybamm.PrimaryBroadcast((5 / 2) * (c_s_surf_xav - c_s_rxav),
                                        [self.domain.lower() + " particle"])
            if self.domain == "Negative":
                # Since c_s_xav doesn't depend on x, we need to define a spatial
                # variable r which only has "negative particle" and "current
                # collector" as domains
                r = pybamm.SpatialVariable(
                    "r_n",
                    domain=["negative particle"],
                    auxiliary_domains={"secondary": "current collector"},
                    coord_sys="spherical polar",
                )
                c_s_xav = A + B * r**2
            if self.domain == "Positive":
                # Since c_s_xav doesn't depend on x, we need to define a spatial
                # variable r which only has "positive particle" and "current
                # collector" as domains
                r = pybamm.SpatialVariable(
                    "r_p",
                    domain=["positive particle"],
                    auxiliary_domains={"secondary": "current collector"},
                    coord_sys="spherical polar",
                )
                c_s_xav = A + B * r**2

        elif self.name == "quartic profile":
            # The concentration is given by c = A + B*r**2 + C*r**4
            A = pybamm.PrimaryBroadcast(
                39 * c_s_surf_xav / 4 - 3 * q_s_rxav - 35 * c_s_rxav / 4,
                [self.domain.lower() + " particle"],
            )
            B = pybamm.PrimaryBroadcast(
                -35 * c_s_surf_xav + 10 * q_s_rxav + 35 * c_s_rxav,
                [self.domain.lower() + " particle"],
            )
            C = pybamm.PrimaryBroadcast(
                105 * c_s_surf_xav / 4 - 7 * q_s_rxav - 105 * c_s_rxav / 4,
                [self.domain.lower() + " particle"],
            )
            if self.domain == "Negative":
                # Since c_s_xav doesn't depend on x, we need to define a spatial
                # variable r which only has "negative particle" and "current
                # collector" as domains
                r = pybamm.SpatialVariable(
                    "r_n",
                    domain=["negative particle"],
                    auxiliary_domains={"secondary": "current collector"},
                    coord_sys="spherical polar",
                )
                c_s_xav = A + B * r**2 + C * r**4
            if self.domain == "Positive":
                # Since c_s_xav doesn't depend on x, we need to define a spatial
                # variable r which only has "positive particle" and "current
                # collector" as domains
                r = pybamm.SpatialVariable(
                    "r_p",
                    domain=["positive particle"],
                    auxiliary_domains={"secondary": "current collector"},
                    coord_sys="spherical polar",
                )
                c_s_xav = A + B * r**2 + C * r**4

        c_s = pybamm.SecondaryBroadcast(c_s_xav,
                                        [self.domain.lower() + " electrode"])
        c_s_surf = pybamm.PrimaryBroadcast(
            c_s_surf_xav, [self.domain.lower() + " electrode"])

        # Set flux based on polynomial order
        if self.name == "uniform profile":
            # The flux is zero since there is no concentration gradient
            N_s_xav = pybamm.FullBroadcastToEdges(
                0,
                self.domain.lower() + " particle", "current collector")
        elif self.name == "quadratic profile":
            # The flux may be computed directly from the polynomial for c
            if self.domain == "Negative":
                N_s_xav = (-self.param.D_n(c_s_xav, T_xav) * 5 *
                           (c_s_surf_xav - c_s_rxav) * r)
            if self.domain == "Positive":
                N_s_xav = (-self.param.D_p(c_s_xav, T_xav) * 5 *
                           (c_s_surf_xav - c_s_rxav) * r)
        elif self.name == "quartic profile":
            q_s_rxav = variables["Average " + self.domain.lower() +
                                 " particle concentration gradient"]
            # The flux may be computed directly from the polynomial for c
            if self.domain == "Negative":
                N_s_xav = -self.param.D_n(c_s_xav, T_xav) * (
                    (-70 * c_s_surf_xav + 20 * q_s_rxav + 70 * c_s_rxav) * r +
                    (105 * c_s_surf_xav - 28 * q_s_rxav - 105 * c_s_rxav) *
                    r**3)
            elif self.domain == "Positive":
                N_s_xav = -self.param.D_p(c_s_xav, T_xav) * (
                    (-70 * c_s_surf_xav + 20 * q_s_rxav + 70 * c_s_rxav) * r +
                    (105 * c_s_surf_xav - 28 * q_s_rxav - 105 * c_s_rxav) *
                    r**3)

        N_s = pybamm.SecondaryBroadcast(N_s_xav,
                                        [self._domain.lower() + " electrode"])

        variables = self._get_standard_concentration_variables(
            c_s, c_s_av=c_s_rxav, c_s_surf=c_s_surf)
        variables.update(self._get_standard_flux_variables(N_s, N_s_xav))

        return variables