示例#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,
        )
示例#2
0
    def get_coupled_variables(self, variables):
        c_s = variables[self.domain + " particle concentration"]
        c_s_rav = variables["R-averaged " + self.domain.lower() +
                            " particle concentration"]
        c_s_surf = variables[self.domain + " particle surface concentration"]
        T = pybamm.PrimaryBroadcast(
            variables[self.domain + " electrode temperature"],
            [self.domain.lower() + " particle"],
        )

        # Set flux depending on polynomial order
        if self.name == "uniform profile":
            # The flux is zero since there is no concentration gradient
            N_s = pybamm.FullBroadcastToEdges(
                0,
                [self.domain.lower() + " particle"],
                auxiliary_domains={
                    "secondary": self.domain.lower() + " electrode",
                    "tertiary": "current collector",
                },
            )
            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":
                r = pybamm.standard_spatial_vars.r_n
                N_s = -self.param.D_n(c_s, T) * 5 * (c_s_surf - c_s_rav) * r
            elif self.domain == "Positive":
                r = pybamm.standard_spatial_vars.r_p
                N_s = -self.param.D_p(c_s, T) * 5 * (c_s_surf - c_s_rav) * r
            N_s_xav = pybamm.x_average(N_s)
        elif self.name == "quartic profile":
            q_s_rav = variables["R-averaged " + self.domain.lower() +
                                " particle concentration gradient"]
            # The flux may be computed directly from the polynomial for c
            if self.domain == "Negative":
                r = pybamm.standard_spatial_vars.r_n
                N_s = -self.param.D_n(c_s, T) * (
                    (-70 * c_s_surf + 20 * q_s_rav + 70 * c_s_rav) * r +
                    (105 * c_s_surf - 28 * q_s_rav - 105 * c_s_rav) * r**3)
            elif self.domain == "Positive":
                r = pybamm.standard_spatial_vars.r_p
                N_s = -self.param.D_p(c_s, T) * (
                    (-70 * c_s_surf + 20 * q_s_rav + 70 * c_s_rav) * r +
                    (105 * c_s_surf - 28 * q_s_rav - 105 * c_s_rav) * r**3)
            N_s_xav = pybamm.x_average(N_s)

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

        return variables
示例#3
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())
示例#4
0
 def _flux_law(self, T):
     """Zero heat flux since temperature is constant"""
     q = pybamm.FullBroadcastToEdges(
         pybamm.Scalar(0),
         ["negative electrode", "separator", "positive electrode"],
         "current collector",
     )
     return q
示例#5
0
 def _flux_law(self, T):
     """Fast heat diffusion (temperature has no spatial dependence)"""
     q = pybamm.FullBroadcastToEdges(
         pybamm.Scalar(0),
         ["negative electrode", "separator", "positive electrode"],
         "current collector",
     )
     return q
    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
    def get_coupled_variables(self, variables):

        N_e = pybamm.FullBroadcastToEdges(
            0,
            ["negative electrode", "separator", "positive electrode"],
            "current collector",
        )

        variables.update(self._get_standard_flux_variables(N_e))

        return variables
    def get_fundamental_variables(self):
        c_e_n = pybamm.FullBroadcast(1, "negative electrode", "current collector")
        c_e_s = pybamm.FullBroadcast(1, "separator", "current collector")
        c_e_p = pybamm.FullBroadcast(1, "positive electrode", "current collector")

        variables = self._get_standard_concentration_variables(c_e_n, c_e_s, c_e_p)

        N_e = pybamm.FullBroadcastToEdges(
            0,
            ["negative electrode", "separator", "positive electrode"],
            "current collector",
        )

        variables.update(self._get_standard_flux_variables(N_e))

        return variables
示例#9
0
    def test_broadcast(self):
        whole_cell = ["negative electrode", "separator", "positive electrode"]

        a = pybamm.InputParameter("a")
        var = pybamm.Variable("var")

        # create discretisation
        disc = get_discretisation_for_testing()
        mesh = disc.mesh

        combined_submesh = mesh.combine_submeshes(*whole_cell)

        # scalar
        broad = disc.process_symbol(pybamm.FullBroadcast(a, whole_cell, {}))
        np.testing.assert_array_equal(
            broad.evaluate(inputs={"a": 7}),
            7 * np.ones_like(combined_submesh[0].nodes[:, np.newaxis]),
        )
        self.assertEqual(broad.domain, whole_cell)

        broad_disc = disc.process_symbol(broad)
        self.assertIsInstance(broad_disc, pybamm.Multiplication)
        self.assertIsInstance(broad_disc.children[0], pybamm.InputParameter)
        self.assertIsInstance(broad_disc.children[1], pybamm.Vector)

        # process Broadcast variable
        disc.y_slices = {var.id: [slice(1)]}
        broad1 = pybamm.FullBroadcast(var, ["negative electrode"], None)
        broad1_disc = disc.process_symbol(broad1)
        self.assertIsInstance(broad1_disc, pybamm.Multiplication)
        self.assertIsInstance(broad1_disc.children[0], pybamm.StateVector)
        self.assertIsInstance(broad1_disc.children[1], pybamm.Vector)

        # broadcast to edges
        broad_to_edges = pybamm.FullBroadcastToEdges(a, ["negative electrode"],
                                                     None)
        broad_to_edges_disc = disc.process_symbol(broad_to_edges)
        np.testing.assert_array_equal(
            broad_to_edges_disc.evaluate(inputs={"a": 7}),
            7 *
            np.ones_like(mesh["negative electrode"][0].edges[:, np.newaxis]),
        )
    def get_coupled_variables(self, variables):

        N_e = pybamm.FullBroadcastToEdges(
            0,
            ["negative electrode", "separator", "positive electrode"],
            "current collector",
        )

        variables.update(self._get_standard_flux_variables(N_e))

        c_e_av = pybamm.standard_variables.c_e_av
        c_e = pybamm.concatenation(
            pybamm.PrimaryBroadcast(c_e_av, ["negative electrode"]),
            pybamm.PrimaryBroadcast(c_e_av, ["separator"]),
            pybamm.PrimaryBroadcast(c_e_av, ["positive electrode"]),
        )
        eps = variables["Porosity"]

        variables.update(self._get_total_concentration_electrolyte(c_e, eps))

        return variables
示例#11
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)
    elif isinstance(symbol, pybamm.FullBroadcast):
        return pybamm.FullBroadcastToEdges(0, symbol.domain,
                                           symbol.auxiliary_domains)
    else:
        return Gradient(symbol)
示例#12
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