コード例 #1
0
    def nasty_hack_to_get_phi_s(self, variables):
        "This restates what is already in the electrode submodel which we should not do"

        param = self.param

        x_n = pybamm.standard_spatial_vars.x_n
        x_p = pybamm.standard_spatial_vars.x_p
        tor = variables[self.domain + " electrode tortuosity"]
        i_boundary_cc = variables["Current collector current density"]
        i_e = variables[self.domain + " electrolyte current density"]

        i_s = i_boundary_cc - i_e

        if self.domain == "Negative":
            conductivity = param.sigma_n * tor
            phi_s = -pybamm.IndefiniteIntegral(i_s / conductivity, x_n)

        elif self.domain == "Positive":

            phi_e_s = variables["Separator electrolyte potential"]
            delta_phi_p = variables[
                "Positive electrode surface potential difference"]

            conductivity = param.sigma_p * tor

            phi_s = -pybamm.IndefiniteIntegral(i_s / conductivity, x_p) + (
                pybamm.boundary_value(phi_e_s, "right") +
                pybamm.boundary_value(delta_phi_p, "left"))

        return phi_s
コード例 #2
0
    def set_boundary_conditions(self, variables):
        T = variables["Cell temperature"]
        T_n_left = pybamm.boundary_value(T, "left")
        T_p_right = pybamm.boundary_value(T, "right")

        self.boundary_conditions = {
            T: {
                "left":
                (self.param.h * T_n_left / self.param.lambda_n, "Neumann"),
                "right":
                (-self.param.h * T_p_right / self.param.lambda_p, "Neumann"),
            }
        }
コード例 #3
0
    def set_boundary_conditions(self, variables):
        T_amb = variables["Ambient temperature"]
        T_av = variables["X-averaged cell temperature"]
        T_av_top = pybamm.boundary_value(T_av, "right")
        T_av_bottom = pybamm.boundary_value(T_av, "left")

        # Tab cooling only implemented for both tabs at the top.
        negative_tab_area = self.param.l_tab_n * self.param.l_cn
        positive_tab_area = self.param.l_tab_p * self.param.l_cp
        total_top_area = self.param.l * self.param.l_y
        non_tab_top_area = total_top_area - negative_tab_area - positive_tab_area

        negative_tab_cooling_coefficient = (
            self.param.h_tab_n / self.param.delta * negative_tab_area / total_top_area
        )
        positive_tab_cooling_coefficient = (
            self.param.h_tab_p / self.param.delta * positive_tab_area / total_top_area
        )

        top_edge_cooling_coefficient = (
            self.param.h_edge / self.param.delta * non_tab_top_area / total_top_area
        )

        bottom_edge_cooling_coefficient = (
            self.param.h_edge / self.param.delta * total_top_area / total_top_area
        )

        total_top_cooling_coefficient = (
            negative_tab_cooling_coefficient
            + positive_tab_cooling_coefficient
            + top_edge_cooling_coefficient
        )

        total_bottom_cooling_coefficient = bottom_edge_cooling_coefficient

        # just use left and right for clarity
        # left = bottom of cell (z=0)
        # right = top of cell (z=L_z)
        self.boundary_conditions = {
            T_av: {
                "left": (
                    total_bottom_cooling_coefficient * (T_av_bottom - T_amb),
                    "Neumann",
                ),
                "right": (
                    -total_top_cooling_coefficient * (T_av_top - T_amb),
                    "Neumann",
                ),
            }
        }
コード例 #4
0
    def get_coupled_variables(self, variables):
        param = self.param

        if self.domain in ["Negative", "Positive"]:

            conductivity, sigma_eff = self._get_conductivities(variables)
            i_boundary_cc = variables["Current collector current density"]
            c_e = variables[self.domain + " electrolyte concentration"]
            delta_phi = variables[
                self.domain + " electrode surface potential difference"
            ]
            T = variables[self.domain + " electrode temperature"]

            i_e = conductivity * (
                ((1 + param.Theta * T) * param.chi(c_e, T) / c_e) * pybamm.grad(c_e)
                + pybamm.grad(delta_phi)
                + i_boundary_cc / sigma_eff
            )
            variables.update(self._get_domain_current_variables(i_e))

            phi_s = variables[self.domain + " electrode potential"]
            phi_e = phi_s - delta_phi

            variables.update(self._get_domain_potential_variables(phi_e))

        elif self.domain == "Separator":
            x_s = pybamm.standard_spatial_vars.x_s

            i_boundary_cc = variables["Current collector current density"]
            c_e_s = variables["Separator electrolyte concentration"]
            phi_e_n = variables["Negative electrolyte potential"]
            tor_s = variables["Separator porosity"]
            T = variables["Separator temperature"]

            chi_e_s = param.chi(c_e_s, T)
            kappa_s_eff = param.kappa_e(c_e_s, T) * tor_s

            phi_e_s = pybamm.boundary_value(
                phi_e_n, "right"
            ) + pybamm.IndefiniteIntegral(
                (1 + param.Theta * T) * chi_e_s / c_e_s * pybamm.grad(c_e_s)
                - param.C_e * i_boundary_cc / kappa_s_eff,
                x_s,
            )

            i_e_s = pybamm.PrimaryBroadcast(i_boundary_cc, "separator")

            variables.update(self._get_domain_potential_variables(phi_e_s))
            variables.update(self._get_domain_current_variables(i_e_s))

            # Update boundary conditions (for indefinite integral)
            self.boundary_conditions[c_e_s] = {
                "left": (pybamm.BoundaryGradient(c_e_s, "left"), "Neumann"),
                "right": (pybamm.BoundaryGradient(c_e_s, "right"), "Neumann"),
            }

        if self.domain == "Positive":
            variables.update(self._get_whole_cell_variables(variables))

        return variables
コード例 #5
0
    def _get_standard_potential_variables(self, phi_s):
        """
        A private function to obtain the standard variables which
        can be derived from the potential in the electrode.

        Parameters
        ----------
        phi_s : :class:`pybamm.Symbol`
            The potential in the electrode.

        Returns
        -------
        variables : dict
            The variables which can be derived from the potential in the
            electrode.
        """
        param = self.param
        phi_s_av = pybamm.x_average(phi_s)

        if self.domain == "Negative":
            phi_s_dim = param.potential_scale * phi_s
            phi_s_av_dim = param.potential_scale * phi_s_av
            delta_phi_s = phi_s

        elif self.domain == "Positive":
            phi_s_dim = param.U_p_ref - param.U_n_ref + param.potential_scale * phi_s
            phi_s_av_dim = (param.U_p_ref - param.U_n_ref +
                            param.potential_scale * phi_s_av)

            v = pybamm.boundary_value(phi_s, "right")
            delta_phi_s = phi_s - pybamm.PrimaryBroadcast(
                v, ["positive electrode"])
        delta_phi_s_av = pybamm.x_average(delta_phi_s)
        delta_phi_s_dim = delta_phi_s * param.potential_scale
        delta_phi_s_av_dim = delta_phi_s_av * param.potential_scale

        variables = {
            self.domain + " electrode potential":
            phi_s,
            self.domain + " electrode potential [V]":
            phi_s_dim,
            "X-averaged " + self.domain.lower() + " electrode potential":
            phi_s_av,
            "X-averaged " + self.domain.lower() + " electrode potential [V]":
            phi_s_av_dim,
            self.domain + " electrode ohmic losses":
            delta_phi_s,
            self.domain + " electrode ohmic losses [V]":
            delta_phi_s_dim,
            "X-averaged " + self.domain.lower() + " electrode ohmic losses":
            delta_phi_s_av,
            "X-averaged " + self.domain.lower() + " electrode ohmic losses [V]":
            delta_phi_s_av_dim,
            "Gradient of " + self.domain.lower() + " electrode potential":
            pybamm.grad(phi_s),
        }

        return variables
コード例 #6
0
    def _get_standard_current_collector_potential_variables(
            self, phi_s_cn, phi_s_cp):
        """
        A private function to obtain the standard variables which
        can be derived from the potentials in the current collector.

        Parameters
        ----------
        phi_cc : :class:`pybamm.Symbol`
            The potential in the current collector.

        Returns
        -------
        variables : dict
            The variables which can be derived from the potential in the
            current collector.
        """

        pot_scale = self.param.potential_scale
        U_ref = self.param.U_p_ref - self.param.U_n_ref
        phi_s_cp_dim = U_ref + phi_s_cp * pot_scale

        # Local potential difference
        V_cc = phi_s_cp - phi_s_cn

        # Terminal voltage
        # Note phi_s_cn is always zero at the negative tab
        V = pybamm.boundary_value(phi_s_cp, "positive tab")
        V_dim = pybamm.boundary_value(phi_s_cp_dim, "positive tab")

        # Voltage is local current collector potential difference at the tabs, in 1D
        # this will be equal to the local current collector potential difference

        variables = {
            "Negative current collector potential": phi_s_cn,
            "Negative current collector potential [V]": phi_s_cn * pot_scale,
            "Positive current collector potential": phi_s_cp,
            "Positive current collector potential [V]": phi_s_cp_dim,
            "Local voltage": V_cc,
            "Local voltage [V]": U_ref + V_cc * pot_scale,
            "Terminal voltage": V,
            "Terminal voltage [V]": V_dim,
        }

        return variables
コード例 #7
0
ファイル: surface_form_ohm.py プロジェクト: tinosulzer/PyBaMM
    def get_coupled_variables(self, variables):

        param = self.param
        x_n = pybamm.standard_spatial_vars.x_n
        x_p = pybamm.standard_spatial_vars.x_p
        i_boundary_cc = variables["Current collector current density"]
        i_e = variables[self.domain + " electrolyte current density"]
        eps = variables[self.domain + " electrode porosity"]
        phi_s_cn = variables["Negative current collector potential"]

        i_s = pybamm.PrimaryBroadcast(i_boundary_cc, self.domain_for_broadcast) - i_e

        if self.domain == "Negative":
            conductivity = param.sigma_n * (1 - eps) ** param.b_n
            phi_s = pybamm.PrimaryBroadcast(
                phi_s_cn, "negative electrode"
            ) - pybamm.IndefiniteIntegral(i_s / conductivity, x_n)

        elif self.domain == "Positive":

            phi_e_s = variables["Separator electrolyte potential"]
            delta_phi_p = variables["Positive electrode surface potential difference"]

            conductivity = param.sigma_p * (1 - eps) ** param.b_p
            phi_s = -pybamm.IndefiniteIntegral(
                i_s / conductivity, x_p
            ) + pybamm.PrimaryBroadcast(
                pybamm.boundary_value(phi_e_s, "right")
                + pybamm.boundary_value(delta_phi_p, "left"),
                "positive electrode",
            )

        variables.update(self._get_standard_potential_variables(phi_s))
        variables.update(self._get_standard_current_variables(i_s))

        if (
            "Negative electrode current density" in variables
            and "Positive electrode current density" in variables
        ):
            variables.update(self._get_standard_whole_cell_variables(variables))

        return variables
コード例 #8
0
    def set_boundary_conditions(self, variables):
        T = variables["Cell temperature"]
        T_n_left = pybamm.boundary_value(T, "left")
        T_p_right = pybamm.boundary_value(T, "right")
        T_amb = variables["Ambient temperature"]

        # N.B only y-z surface cooling is implemented for this thermal model.
        # Tab and edge cooling is not accounted for.
        self.boundary_conditions = {
            T: {
                "left": (
                    self.param.h_cn * (T_n_left - T_amb) / self.param.lambda_n,
                    "Neumann",
                ),
                "right": (
                    -self.param.h_cp * (T_p_right - T_amb) /
                    self.param.lambda_p,
                    "Neumann",
                ),
            }
        }
コード例 #9
0
    def _get_diffusion_limited_current_density(self, variables):
        param = self.param
        if self.domain == "Negative":
            tor_s = variables["Separator tortuosity"]
            c_ox_s = variables["Separator oxygen concentration"]
            N_ox_neg_sep_interface = (-pybamm.boundary_value(tor_s, "left") *
                                      param.curlyD_ox *
                                      pybamm.BoundaryGradient(c_ox_s, "left"))
            N_ox_neg_sep_interface.domain = ["current collector"]

            j = -N_ox_neg_sep_interface / param.C_e / param.s_ox_Ox / param.l_n

        return j
コード例 #10
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.Broadcast(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")
コード例 #11
0
    def set_boundary_conditions(self, variables):
        T_av = variables["X-averaged cell temperature"]
        T_av_left = pybamm.boundary_value(T_av, "negative tab")
        T_av_right = pybamm.boundary_value(T_av, "positive tab")

        # Three boundary conditions here to handle the cases of both tabs at
        # the same side (top or bottom), or one either side. For both tabs on the
        # same side, T_av_left and T_av_right are equal, and the boundary condition
        # "no tab" is used on the other side.
        self.boundary_conditions = {
            T_av: {
                "negative tab": (
                    self.param.h * T_av_left / self.param.delta,
                    "Neumann",
                ),
                "positive tab": (
                    -self.param.h * T_av_right / self.param.delta,
                    "Neumann",
                ),
                "no tab": (pybamm.Scalar(0), "Neumann"),
            }
        }
コード例 #12
0
ファイル: base_ohm.py プロジェクト: yushun9897/PyBaMM
    def set_boundary_conditions(self, variables):

        if self.domain == "Negative":
            phi_s_cn = variables["Negative current collector potential"]
            lbc = (phi_s_cn, "Dirichlet")
            rbc = (pybamm.Scalar(0), "Neumann")

        elif self.domain == "Positive":
            lbc = (pybamm.Scalar(0), "Neumann")
            i_boundary_cc = variables["Current collector current density"]
            sigma_eff = self.param.sigma_p * variables[
                "Positive electrode tortuosity"]
            rbc = (
                i_boundary_cc / pybamm.boundary_value(-sigma_eff, "right"),
                "Neumann",
            )

        phi_s = variables[self.domain + " electrode potential"]
        self.boundary_conditions[phi_s] = {"left": lbc, "right": rbc}
コード例 #13
0
ファイル: diffusion_limited.py プロジェクト: dalonsoa/PyBaMM
    def _get_diffusion_limited_current_density(self, variables):
        param = self.param
        if self.domain == "Negative":
            if self.order == "leading":
                j_p = variables["X-averaged positive electrode" +
                                self.reaction_name +
                                " interfacial current density"]
                j = -self.param.l_p * j_p / self.param.l_n
            elif self.order in ["composite", "full"]:
                tor_s = variables["Separator tortuosity"]
                c_ox_s = variables["Separator oxygen concentration"]
                N_ox_neg_sep_interface = (
                    -pybamm.boundary_value(tor_s, "left") * param.curlyD_ox *
                    pybamm.BoundaryGradient(c_ox_s, "left"))
                N_ox_neg_sep_interface.domain = ["current collector"]

                j = -N_ox_neg_sep_interface / param.C_e / -param.s_ox_Ox / param.l_n

        return j
コード例 #14
0
    def _get_standard_whole_cell_variables(self, variables):
        """
        A private function to obtain the whole-cell versions of the
        current variables.

        Parameters
        ----------
        variables : dict
            The variables in the whole model.

        Returns
        -------
        variables : dict
            The variables in the whole model with the whole-cell
            current variables added.
        """
        pot_scale = self.param.potential_scale
        U_ref = self.param.U_p_ref - self.param.U_n_ref

        i_s_n = variables["Negative electrode current density"]
        i_s_s = pybamm.FullBroadcast(0, ["separator"], "current collector")
        i_s_p = variables["Positive electrode current density"]
        phi_s_p = variables["Positive electrode potential"]

        phi_s_cn = variables["Negative current collector potential"]
        phi_s_cp = pybamm.boundary_value(phi_s_p, "right")
        v_boundary_cc = phi_s_cp - phi_s_cn

        i_s = pybamm.Concatenation(i_s_n, i_s_s, i_s_p)

        variables = {
            "Electrode current density":
            i_s,
            "Positive current collector potential":
            phi_s_cp,
            "Local current collector potential difference":
            v_boundary_cc,
            "Local current collector potential difference [V]":
            U_ref + v_boundary_cc * pot_scale,
        }

        return variables
    def _get_sep_coupled_variables(self, variables):
        """
        A private function to get the coupled variables when the domain is 'Separator'.
        """

        param = self.param
        x_s = pybamm.standard_spatial_vars.x_s

        i_boundary_cc = variables["Current collector current density"]
        c_e_s = variables["Separator electrolyte concentration"]
        phi_e_n = variables["Negative electrolyte potential"]
        eps_s = variables["Separator porosity"]
        T = variables["Separator temperature"]

        chi_e_s = param.chi(c_e_s)
        kappa_s_eff = param.kappa_e(c_e_s, T) * (eps_s ** param.b_s)

        phi_e_s = pybamm.PrimaryBroadcast(
            pybamm.boundary_value(phi_e_n, "right"), "separator"
        ) + pybamm.IndefiniteIntegral(
            chi_e_s / c_e_s * pybamm.grad(c_e_s)
            - param.C_e
            * pybamm.PrimaryBroadcast(i_boundary_cc, self.domain_for_broadcast)
            / kappa_s_eff,
            x_s,
        )

        i_e_s = pybamm.PrimaryBroadcast(i_boundary_cc, "separator")

        variables.update(self._get_domain_potential_variables(phi_e_s))
        variables.update(self._get_domain_current_variables(i_e_s))

        # Update boundary conditions (for indefinite integral)
        self.boundary_conditions[c_e_s] = {
            "left": (pybamm.BoundaryGradient(c_e_s, "left"), "Neumann"),
            "right": (pybamm.BoundaryGradient(c_e_s, "right"), "Neumann"),
        }

        return variables
コード例 #16
0
    def _get_standard_whole_cell_variables(self, variables):
        """
        A private function to obtain the whole-cell versions of the
        current variables.

        Parameters
        ----------
        variables : dict
            The variables in the whole model.

        Returns
        -------
        variables : dict
            The variables in the whole model with the whole-cell
            current variables added.
        """

        i_s_n = variables["Negative electrode current density"]
        i_s_s = pybamm.FullBroadcast(0, ["separator"], "current collector")
        i_s_p = variables["Positive electrode current density"]

        i_s = pybamm.concatenation(i_s_n, i_s_s, i_s_p)

        variables.update({"Electrode current density": i_s})

        if self.set_positive_potential:
            # Get phi_s_cn from the current collector submodel and phi_s_p from the
            # electrode submodel
            phi_s_cn = variables["Negative current collector potential"]
            phi_s_p = variables["Positive electrode potential"]
            phi_s_cp = pybamm.boundary_value(phi_s_p, "right")
            variables.update(
                self._get_standard_current_collector_potential_variables(
                    phi_s_cn, phi_s_cp))

        return variables
コード例 #17
0
    def __init__(self, name="Doyle-Fuller-Newman half cell model", options=None):
        super().__init__({}, name)
        pybamm.citations.register("Marquis2019")
        # `param` is a class containing all the relevant parameters and functions for
        # this model. These are purely symbolic at this stage, and will be set by the
        # `ParameterValues` class when the model is processed.
        param = self.param
        options = options or {"working electrode": None}

        if options["working electrode"] not in ["negative", "positive"]:
            raise ValueError(
                "The option 'working electrode' should be either 'positive'"
                " or 'negative'"
            )

        self.options.update(options)
        working_electrode = options["working electrode"]

        if working_electrode == "negative":
            R_w_typ = param.R_n_typ
        else:
            R_w_typ = param.R_p_typ

        # Set default length scales
        self.length_scales = {
            "working electrode": param.L_x,
            "separator": param.L_x,
            "working particle": R_w_typ,
            "current collector y": param.L_z,
            "current collector z": param.L_z,
        }

        ######################
        # Variables
        ######################
        # Variables that depend on time only are created without a domain
        Q = pybamm.Variable("Discharge capacity [A.h]")

        # Define some useful scalings
        pot = param.potential_scale
        i_typ = param.current_scale

        # Variables that vary spatially are created with a domain.
        c_e_s = pybamm.Variable(
            "Separator electrolyte concentration", domain="separator"
        )
        c_e_w = pybamm.Variable(
            "Working electrolyte concentration", domain="working electrode"
        )
        c_e = pybamm.concatenation(c_e_s, c_e_w)
        c_s_w = pybamm.Variable(
            "Working particle concentration",
            domain="working particle",
            auxiliary_domains={"secondary": "working electrode"},
        )
        phi_s_w = pybamm.Variable(
            "Working electrode potential", domain="working electrode"
        )
        phi_e_s = pybamm.Variable("Separator electrolyte potential", domain="separator")
        phi_e_w = pybamm.Variable(
            "Working electrolyte potential", domain="working electrode"
        )
        phi_e = pybamm.concatenation(phi_e_s, phi_e_w)

        # Constant temperature
        T = param.T_init

        ######################
        # Other set-up
        ######################

        # Current density
        i_cell = param.current_with_time

        # Define particle surface concentration
        # Surf takes the surface value of a variable, i.e. its boundary value on the
        # right side. This is also accessible via `boundary_value(x, "right")`, with
        # "left" providing the boundary value of the left side
        c_s_surf_w = pybamm.surf(c_s_w)

        # Define parameters. We need to assemble them differently depending on the
        # working electrode

        if working_electrode == "negative":
            # Porosity and Tortuosity
            # Primary broadcasts are used to broadcast scalar quantities across a domain
            # into a vector of the right shape, for multiplying with other vectors
            eps_s = pybamm.PrimaryBroadcast(
                pybamm.Parameter("Separator porosity"), "separator"
            )
            eps_w = pybamm.PrimaryBroadcast(
                pybamm.Parameter("Negative electrode porosity"), "working electrode"
            )
            b_e_s = param.b_e_s
            b_e_w = param.b_e_n

            # Interfacial reactions
            j0_w = param.j0_n(c_e_w, c_s_surf_w, T) / param.C_r_n
            U_w = param.U_n
            ne_w = param.ne_n

            # Particle diffusion parameters
            D_w = param.D_n
            C_w = param.C_n
            a_R_w = param.a_R_n
            gamma_w = pybamm.Scalar(1)
            c_w_init = param.c_n_init

            # Electrode equation parameters
            eps_s_w = pybamm.Parameter(
                "Negative electrode active material volume fraction"
            )
            b_s_w = param.b_s_n
            sigma_w = param.sigma_n

            # Other parameters (for outputs)
            c_w_max = param.c_n_max
            U_ref = param.U_n_ref
            phi_s_w_ref = pybamm.Scalar(0)
            L_w = param.L_n

        else:
            # Porosity and Tortuosity
            eps_s = pybamm.PrimaryBroadcast(
                pybamm.Parameter("Separator porosity"), "separator"
            )
            eps_w = pybamm.PrimaryBroadcast(
                pybamm.Parameter("Positive electrode porosity"), "working electrode"
            )
            b_e_s = param.b_e_s
            b_e_w = param.b_e_p

            # Interfacial reactions
            j0_w = param.gamma_p * param.j0_p(c_e_w, c_s_surf_w, T) / param.C_r_p
            U_w = param.U_p
            ne_w = param.ne_p

            # Particle diffusion parameters
            D_w = param.D_p
            C_w = param.C_p
            a_R_w = param.a_R_p
            gamma_w = param.gamma_p
            c_w_init = param.c_p_init

            # Electrode equation parameters
            eps_s_w = pybamm.Parameter(
                "Positive electrode active material volume fraction"
            )
            b_s_w = param.b_s_p
            sigma_w = param.sigma_p

            # Other parameters (for outputs)
            c_w_max = param.c_p_max
            U_ref = param.U_p_ref
            phi_s_w_ref = param.U_p_ref - param.U_n_ref
            L_w = param.L_p

        eps = pybamm.concatenation(eps_s, eps_w)
        tor = pybamm.concatenation(eps_s ** b_e_s, eps_w ** b_e_w)

        j_w = (
            2 * j0_w * pybamm.sinh(ne_w / 2 * (phi_s_w - phi_e_w - U_w(c_s_surf_w, T)))
        )
        j_s = pybamm.PrimaryBroadcast(0, "separator")
        j = pybamm.concatenation(j_s, j_w)

        ######################
        # State of Charge
        ######################
        I = param.dimensional_current_with_time
        # The `rhs` dictionary contains differential equations, with the key being the
        # variable in the d/dt
        self.rhs[Q] = I * param.timescale / 3600
        # Initial conditions must be provided for the ODEs
        self.initial_conditions[Q] = pybamm.Scalar(0)

        ######################
        # Particles
        ######################
        # The div and grad operators will be converted to the appropriate matrix
        # multiplication at the discretisation stage
        N_s_w = -D_w(c_s_w, T) * pybamm.grad(c_s_w)
        self.rhs[c_s_w] = -(1 / C_w) * pybamm.div(N_s_w)

        # Boundary conditions must be provided for equations with spatial
        # derivatives
        self.boundary_conditions[c_s_w] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (
                -C_w * j_w / a_R_w / gamma_w / D_w(c_s_surf_w, T),
                "Neumann",
            ),
        }

        # c_w_init can in general be a function of x
        # Note the broadcasting, for domains
        x_w = pybamm.PrimaryBroadcast(half_cell_spatial_vars.x_w, "working particle")
        self.initial_conditions[c_s_w] = c_w_init(x_w)

        # Events specify points at which a solution should terminate
        self.events += [
            pybamm.Event(
                "Minimum working particle surface concentration",
                pybamm.min(c_s_surf_w) - 0.01,
            ),
            pybamm.Event(
                "Maximum working particle surface concentration",
                (1 - 0.01) - pybamm.max(c_s_surf_w),
            ),
        ]

        ######################
        # Current in the solid
        ######################
        sigma_eff_w = sigma_w * eps_s_w ** b_s_w
        i_s_w = -sigma_eff_w * pybamm.grad(phi_s_w)
        self.boundary_conditions[phi_s_w] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (
                i_cell / pybamm.boundary_value(-sigma_eff_w, "right"),
                "Neumann",
            ),
        }
        self.algebraic[phi_s_w] = pybamm.div(i_s_w) + j_w
        # Initial conditions must also be provided for algebraic equations, as an
        # initial guess for a root-finding algorithm which calculates consistent
        # initial conditions
        self.initial_conditions[phi_s_w] = U_w(c_w_init(1), param.T_init)

        ######################
        # Electrolyte concentration
        ######################
        N_e = -tor * param.D_e(c_e, T) * pybamm.grad(c_e)
        self.rhs[c_e] = (1 / eps) * (
            -pybamm.div(N_e) / param.C_e
            + (1 - param.t_plus(c_e, T)) * j / param.gamma_e
        )
        dce_dx = (
            -(1 - param.t_plus(c_e, T))
            * i_cell
            * param.C_e
            / (tor * param.gamma_e * param.D_e(c_e, T))
        )

        self.boundary_conditions[c_e] = {
            "left": (pybamm.boundary_value(dce_dx, "left"), "Neumann"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }

        self.initial_conditions[c_e] = param.c_e_init
        self.events.append(
            pybamm.Event(
                "Zero electrolyte concentration cut-off", pybamm.min(c_e) - 0.002
            )
        )

        ######################
        # Current in the electrolyte
        ######################
        i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * (
            param.chi(c_e, T) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e)
        )
        self.algebraic[phi_e] = pybamm.div(i_e) - j

        ref_potential = param.U_n_ref / pot

        self.boundary_conditions[phi_e] = {
            "left": (ref_potential, "Dirichlet"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }

        self.initial_conditions[phi_e] = ref_potential

        ######################
        # (Some) variables
        ######################
        L_Li = pybamm.Parameter("Lithium counter electrode thickness [m]")
        sigma_Li = pybamm.Parameter("Lithium counter electrode conductivity [S.m-1]")
        j_Li = pybamm.Parameter(
            "Lithium counter electrode exchange-current density [A.m-2]"
        )

        vdrop_cell = pybamm.boundary_value(phi_s_w, "right") - ref_potential
        vdrop_Li = -(
            2 * pybamm.arcsinh(i_cell * i_typ / j_Li)
            + L_Li * i_typ * i_cell / (sigma_Li * pot)
        )
        voltage = vdrop_cell + vdrop_Li

        c_e_total = pybamm.x_average(eps * c_e)
        c_s_surf_w_av = pybamm.x_average(c_s_surf_w)

        c_s_rav = pybamm.r_average(c_s_w)
        c_s_vol_av = pybamm.x_average(eps_s_w * c_s_rav)

        # The `variables` dictionary contains all variables that might be useful for
        # visualising the solution of the model
        self.variables = {
            "Time [s]": param.timescale * pybamm.t,
            "Working particle surface concentration": c_s_surf_w,
            "X-averaged working particle surface concentration": c_s_surf_w_av,
            "Working particle concentration": c_s_w,
            "Working particle surface concentration [mol.m-3]": c_w_max * c_s_surf_w,
            "X-averaged working particle surface concentration "
            "[mol.m-3]": c_w_max * c_s_surf_w_av,
            "Working particle concentration [mol.m-3]": c_w_max * c_s_w,
            "Total lithium in working electrode": c_s_vol_av,
            "Total lithium in working electrode [mol]": c_s_vol_av
            * c_w_max
            * L_w
            * param.A_cc,
            "Electrolyte concentration": c_e,
            "Electrolyte concentration [mol.m-3]": param.c_e_typ * c_e,
            "Total electrolyte concentration": c_e_total,
            "Total electrolyte concentration [mol]": c_e_total
            * param.c_e_typ
            * L_w
            * param.L_s
            * param.A_cc,
            "Current [A]": I,
            "Working electrode potential": phi_s_w,
            "Working electrode potential [V]": phi_s_w_ref + pot * phi_s_w,
            "Working electrode open circuit potential": U_w(c_s_surf_w, T),
            "Working electrode open circuit potential [V]": U_ref
            + pot * U_w(c_s_surf_w, T),
            "Electrolyte potential": phi_e,
            "Electrolyte potential [V]": -param.U_n_ref + pot * phi_e,
            "Voltage drop in the cell": vdrop_cell,
            "Voltage drop in the cell [V]": phi_s_w_ref
            + param.U_n_ref
            + pot * vdrop_cell,
            "Terminal voltage": voltage,
            "Terminal voltage [V]": phi_s_w_ref + param.U_n_ref + pot * voltage,
        }
コード例 #18
0
    def test_symbol_simplify(self):
        a = pybamm.Scalar(0)
        b = pybamm.Scalar(1)
        c = pybamm.Parameter("c")
        d = pybamm.Scalar(-1)
        e = pybamm.Scalar(2)
        g = pybamm.Variable("g")

        # negate
        self.assertIsInstance((-a).simplify(), pybamm.Scalar)
        self.assertEqual((-a).simplify().evaluate(), 0)
        self.assertIsInstance((-b).simplify(), pybamm.Scalar)
        self.assertEqual((-b).simplify().evaluate(), -1)

        # absolute value
        self.assertIsInstance((abs(a)).simplify(), pybamm.Scalar)
        self.assertEqual((abs(a)).simplify().evaluate(), 0)
        self.assertIsInstance((abs(d)).simplify(), pybamm.Scalar)
        self.assertEqual((abs(d)).simplify().evaluate(), 1)

        # function
        def sin(x):
            return math.sin(x)

        f = pybamm.Function(sin, b)
        self.assertIsInstance((f).simplify(), pybamm.Scalar)
        self.assertEqual((f).simplify().evaluate(), math.sin(1))

        def myfunction(x, y):
            return x * y

        f = pybamm.Function(myfunction, a, b)
        self.assertIsInstance((f).simplify(), pybamm.Scalar)
        self.assertEqual((f).simplify().evaluate(), 0)

        # FunctionParameter
        f = pybamm.FunctionParameter("function", b)
        self.assertIsInstance((f).simplify(), pybamm.FunctionParameter)
        self.assertEqual((f).simplify().children[0].id, b.id)

        f = pybamm.FunctionParameter("function", a, b)
        self.assertIsInstance((f).simplify(), pybamm.FunctionParameter)
        self.assertEqual((f).simplify().children[0].id, a.id)
        self.assertEqual((f).simplify().children[1].id, b.id)

        # Gradient
        self.assertIsInstance((pybamm.grad(a)).simplify(), pybamm.Scalar)
        self.assertEqual((pybamm.grad(a)).simplify().evaluate(), 0)
        v = pybamm.Variable("v")
        self.assertIsInstance((pybamm.grad(v)).simplify(), pybamm.Gradient)

        # Divergence
        self.assertIsInstance((pybamm.div(a)).simplify(), pybamm.Scalar)
        self.assertEqual((pybamm.div(a)).simplify().evaluate(), 0)
        self.assertIsInstance((pybamm.div(v)).simplify(), pybamm.Divergence)

        # Integral
        self.assertIsInstance(
            (pybamm.Integral(a, pybamm.t)).simplify(), pybamm.Integral
        )

        # BoundaryValue
        v_neg = pybamm.Variable("v", domain=["negative electrode"])
        self.assertIsInstance(
            (pybamm.boundary_value(v_neg, "right")).simplify(), pybamm.BoundaryValue
        )

        # Delta function
        self.assertIsInstance(
            (pybamm.DeltaFunction(v_neg, "right", "domain")).simplify(),
            pybamm.DeltaFunction,
        )

        # addition
        self.assertIsInstance((a + b).simplify(), pybamm.Scalar)
        self.assertEqual((a + b).simplify().evaluate(), 1)
        self.assertIsInstance((b + b).simplify(), pybamm.Scalar)
        self.assertEqual((b + b).simplify().evaluate(), 2)
        self.assertIsInstance((b + a).simplify(), pybamm.Scalar)
        self.assertEqual((b + a).simplify().evaluate(), 1)

        # subtraction
        self.assertIsInstance((a - b).simplify(), pybamm.Scalar)
        self.assertEqual((a - b).simplify().evaluate(), -1)
        self.assertIsInstance((b - b).simplify(), pybamm.Scalar)
        self.assertEqual((b - b).simplify().evaluate(), 0)
        self.assertIsInstance((b - a).simplify(), pybamm.Scalar)
        self.assertEqual((b - a).simplify().evaluate(), 1)

        # addition and subtraction with matrix zero
        v = pybamm.Vector(np.zeros((10, 1)))
        self.assertIsInstance((b + v).simplify(), pybamm.Array)
        np.testing.assert_array_equal((b + v).simplify().evaluate(), np.ones((10, 1)))
        self.assertIsInstance((v + b).simplify(), pybamm.Array)
        np.testing.assert_array_equal((v + b).simplify().evaluate(), np.ones((10, 1)))
        self.assertIsInstance((b - v).simplify(), pybamm.Array)
        np.testing.assert_array_equal((b - v).simplify().evaluate(), np.ones((10, 1)))
        self.assertIsInstance((v - b).simplify(), pybamm.Array)
        np.testing.assert_array_equal((v - b).simplify().evaluate(), -np.ones((10, 1)))

        # multiplication
        self.assertIsInstance((a * b).simplify(), pybamm.Scalar)
        self.assertEqual((a * b).simplify().evaluate(), 0)
        self.assertIsInstance((b * a).simplify(), pybamm.Scalar)
        self.assertEqual((b * a).simplify().evaluate(), 0)
        self.assertIsInstance((b * b).simplify(), pybamm.Scalar)
        self.assertEqual((b * b).simplify().evaluate(), 1)
        self.assertIsInstance((a * a).simplify(), pybamm.Scalar)
        self.assertEqual((a * a).simplify().evaluate(), 0)

        # test when other node is a parameter
        self.assertIsInstance((a + c).simplify(), pybamm.Parameter)
        self.assertIsInstance((c + a).simplify(), pybamm.Parameter)
        self.assertIsInstance((c + b).simplify(), pybamm.Addition)
        self.assertIsInstance((b + c).simplify(), pybamm.Addition)
        self.assertIsInstance((a * c).simplify(), pybamm.Scalar)
        self.assertEqual((a * c).simplify().evaluate(), 0)
        self.assertIsInstance((c * a).simplify(), pybamm.Scalar)
        self.assertEqual((c * a).simplify().evaluate(), 0)
        self.assertIsInstance((b * c).simplify(), pybamm.Parameter)
        self.assertIsInstance((e * c).simplify(), pybamm.Multiplication)

        expr = (e * (e * c)).simplify()
        self.assertIsInstance(expr, pybamm.Multiplication)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = (e / (e * c)).simplify()
        self.assertIsInstance(expr, pybamm.Division)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 1.0)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = (e * (e / c)).simplify()
        self.assertIsInstance(expr, pybamm.Division)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 4.0)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = (e * (c / e)).simplify()
        self.assertIsInstance(expr, pybamm.Multiplication)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 1.0)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = ((e * c) * (c / e)).simplify()
        self.assertIsInstance(expr, pybamm.Multiplication)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 1.0)
        self.assertIsInstance(expr.children[1], pybamm.Multiplication)
        self.assertIsInstance(expr.children[1].children[0], pybamm.Parameter)
        self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter)

        expr = (e + (e + c)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 4.0)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = (e + (e - c)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 4.0)
        self.assertIsInstance(expr.children[1], pybamm.Negate)
        self.assertIsInstance(expr.children[1].children[0], pybamm.Parameter)

        expr = (e + (g - c)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 2.0)
        self.assertIsInstance(expr.children[1], pybamm.Subtraction)
        self.assertIsInstance(expr.children[1].children[0], pybamm.Variable)
        self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter)

        expr = ((2 + c) + (c + 2)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 4.0)
        self.assertIsInstance(expr.children[1], pybamm.Multiplication)
        self.assertIsInstance(expr.children[1].children[0], pybamm.Scalar)
        self.assertEqual(expr.children[1].children[0].evaluate(), 2)
        self.assertIsInstance(expr.children[1].children[1], pybamm.Parameter)

        expr = ((-1 + c) - (c + 1) + (c - 1)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), -3.0)

        # check these don't simplify
        self.assertIsInstance((c * e).simplify(), pybamm.Multiplication)
        self.assertIsInstance((e / c).simplify(), pybamm.Division)
        self.assertIsInstance((c).simplify(), pybamm.Parameter)
        c1 = pybamm.Parameter("c1")
        self.assertIsInstance((c1 * c).simplify(), pybamm.Multiplication)

        # should simplify division to multiply
        self.assertIsInstance((c / e).simplify(), pybamm.Multiplication)

        self.assertIsInstance((c / b).simplify(), pybamm.Parameter)
        self.assertIsInstance((c * b).simplify(), pybamm.Parameter)

        # negation with parameter
        self.assertIsInstance((-c).simplify(), pybamm.Negate)

        self.assertIsInstance((a + b + a).simplify(), pybamm.Scalar)
        self.assertEqual((a + b + a).simplify().evaluate(), 1)
        self.assertIsInstance((b + a + a).simplify(), pybamm.Scalar)
        self.assertEqual((b + a + a).simplify().evaluate(), 1)
        self.assertIsInstance((a * b * b).simplify(), pybamm.Scalar)
        self.assertEqual((a * b * b).simplify().evaluate(), 0)
        self.assertIsInstance((b * a * b).simplify(), pybamm.Scalar)
        self.assertEqual((b * a * b).simplify().evaluate(), 0)

        # power simplification
        self.assertIsInstance((c ** a).simplify(), pybamm.Scalar)
        self.assertEqual((c ** a).simplify().evaluate(), 1)
        self.assertIsInstance((a ** c).simplify(), pybamm.Scalar)
        self.assertEqual((a ** c).simplify().evaluate(), 0)
        d = pybamm.Scalar(2)
        self.assertIsInstance((c ** d).simplify(), pybamm.Power)

        # division
        self.assertIsInstance((a / b).simplify(), pybamm.Scalar)
        self.assertEqual((a / b).simplify().evaluate(), 0)
        self.assertIsInstance((b / a).simplify(), pybamm.Scalar)
        self.assertEqual((b / a).simplify().evaluate(), np.inf)
        self.assertIsInstance((a / a).simplify(), pybamm.Scalar)
        self.assertTrue(np.isnan((a / a).simplify().evaluate()))
        self.assertIsInstance((b / b).simplify(), pybamm.Scalar)
        self.assertEqual((b / b).simplify().evaluate(), 1)

        # not implemented for Symbol
        sym = pybamm.Symbol("sym")
        with self.assertRaises(NotImplementedError):
            sym.simplify()

        # A + A = 2A (#323)
        a = pybamm.Parameter("A")
        expr = (a + a).simplify()
        self.assertIsInstance(expr, pybamm.Multiplication)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 2)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = (a + a + a + a).simplify()
        self.assertIsInstance(expr, pybamm.Multiplication)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 4)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        expr = (a - a + a - a + a + a).simplify()
        self.assertIsInstance(expr, pybamm.Multiplication)
        self.assertIsInstance(expr.children[0], pybamm.Scalar)
        self.assertEqual(expr.children[0].evaluate(), 2)
        self.assertIsInstance(expr.children[1], pybamm.Parameter)

        # A - A = 0 (#323)
        expr = (a - a).simplify()
        self.assertIsInstance(expr, pybamm.Scalar)
        self.assertEqual(expr.evaluate(), 0)

        # B - (A+A) = B - 2*A (#323)
        expr = (b - (a + a)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.right, pybamm.Negate)
        self.assertIsInstance(expr.right.child, pybamm.Multiplication)
        self.assertEqual(expr.right.child.left.id, pybamm.Scalar(2).id)
        self.assertEqual(expr.right.child.right.id, a.id)

        # B - (1*A + 2*A) = B - 3*A (#323)
        expr = (b - (1 * a + 2 * a)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.right, pybamm.Negate)
        self.assertIsInstance(expr.right.child, pybamm.Multiplication)
        self.assertEqual(expr.right.child.left.id, pybamm.Scalar(3).id)
        self.assertEqual(expr.right.child.right.id, a.id)

        # B - (A + C) = B - (A + C) (not B - (A - C))
        expr = (b - (a + c)).simplify()
        self.assertIsInstance(expr, pybamm.Addition)
        self.assertIsInstance(expr.right, pybamm.Subtraction)
        self.assertEqual(expr.right.left.id, (-a).id)
        self.assertEqual(expr.right.right.id, c.id)
コード例 #19
0
ファイル: base_battery_model.py プロジェクト: yonas-y/PyBaMM
    def set_voltage_variables(self):

        ocp_n = self.variables["Negative electrode open circuit potential"]
        ocp_p = self.variables["Positive electrode open circuit potential"]
        ocp_n_av = self.variables[
            "X-averaged negative electrode open circuit potential"]
        ocp_p_av = self.variables[
            "X-averaged positive electrode open circuit potential"]

        ocp_n_dim = self.variables[
            "Negative electrode open circuit potential [V]"]
        ocp_p_dim = self.variables[
            "Positive electrode open circuit potential [V]"]
        ocp_n_av_dim = self.variables[
            "X-averaged negative electrode open circuit potential [V]"]
        ocp_p_av_dim = self.variables[
            "X-averaged positive electrode open circuit potential [V]"]

        ocp_n_left = pybamm.boundary_value(ocp_n, "left")
        ocp_n_left_dim = pybamm.boundary_value(ocp_n_dim, "left")
        ocp_p_right = pybamm.boundary_value(ocp_p, "right")
        ocp_p_right_dim = pybamm.boundary_value(ocp_p_dim, "right")

        ocv_av = ocp_p_av - ocp_n_av
        ocv_av_dim = ocp_p_av_dim - ocp_n_av_dim
        ocv = ocp_p_right - ocp_n_left
        ocv_dim = ocp_p_right_dim - ocp_n_left_dim

        # overpotentials
        eta_r_n_av = self.variables[
            "X-averaged negative electrode reaction overpotential"]
        eta_r_n_av_dim = self.variables[
            "X-averaged negative electrode reaction overpotential [V]"]
        eta_r_p_av = self.variables[
            "X-averaged positive electrode reaction overpotential"]
        eta_r_p_av_dim = self.variables[
            "X-averaged positive electrode reaction overpotential [V]"]

        delta_phi_s_n_av = self.variables[
            "X-averaged negative electrode ohmic losses"]
        delta_phi_s_n_av_dim = self.variables[
            "X-averaged negative electrode ohmic losses [V]"]
        delta_phi_s_p_av = self.variables[
            "X-averaged positive electrode ohmic losses"]
        delta_phi_s_p_av_dim = self.variables[
            "X-averaged positive electrode ohmic losses [V]"]

        delta_phi_s_av = delta_phi_s_p_av - delta_phi_s_n_av
        delta_phi_s_av_dim = delta_phi_s_p_av_dim - delta_phi_s_n_av_dim

        eta_r_av = eta_r_p_av - eta_r_n_av
        eta_r_av_dim = eta_r_p_av_dim - eta_r_n_av_dim

        # SEI film overpotential
        eta_sei_n_av = self.variables[
            "X-averaged negative electrode sei film overpotential"]
        eta_sei_p_av = self.variables[
            "X-averaged positive electrode sei film overpotential"]
        eta_sei_n_av_dim = self.variables[
            "X-averaged negative electrode sei film overpotential [V]"]
        eta_sei_p_av_dim = self.variables[
            "X-averaged positive electrode sei film overpotential [V]"]
        eta_sei_av = eta_sei_n_av + eta_sei_p_av
        eta_sei_av_dim = eta_sei_n_av_dim + eta_sei_p_av_dim

        # TODO: add current collector losses to the voltage in 3D

        self.variables.update({
            "X-averaged open circuit voltage":
            ocv_av,
            "Measured open circuit voltage":
            ocv,
            "X-averaged open circuit voltage [V]":
            ocv_av_dim,
            "Measured open circuit voltage [V]":
            ocv_dim,
            "X-averaged reaction overpotential":
            eta_r_av,
            "X-averaged reaction overpotential [V]":
            eta_r_av_dim,
            "X-averaged sei film overpotential":
            eta_sei_av,
            "X-averaged sei film overpotential [V]":
            eta_sei_av_dim,
            "X-averaged solid phase ohmic losses":
            delta_phi_s_av,
            "X-averaged solid phase ohmic losses [V]":
            delta_phi_s_av_dim,
        })

        # Battery-wide variables
        V_dim = self.variables["Terminal voltage [V]"]
        eta_e_av = self.variables.get("X-averaged electrolyte ohmic losses", 0)
        eta_c_av = self.variables.get("X-averaged concentration overpotential",
                                      0)
        eta_e_av_dim = self.variables.get(
            "X-averaged electrolyte ohmic losses [V]", 0)
        eta_c_av_dim = self.variables.get(
            "X-averaged concentration overpotential [V]", 0)
        num_cells = pybamm.Parameter(
            "Number of cells connected in series to make a battery")
        self.variables.update({
            "X-averaged battery open circuit voltage [V]":
            ocv_av_dim * num_cells,
            "Measured battery open circuit voltage [V]":
            ocv_dim * num_cells,
            "X-averaged battery reaction overpotential [V]":
            eta_r_av_dim * num_cells,
            "X-averaged battery solid phase ohmic losses [V]":
            delta_phi_s_av_dim * num_cells,
            "X-averaged battery electrolyte ohmic losses [V]":
            eta_e_av_dim * num_cells,
            "X-averaged battery concentration overpotential [V]":
            eta_c_av_dim * num_cells,
            "Battery voltage [V]":
            V_dim * num_cells,
        })
        # Variables for calculating the equivalent circuit model (ECM) resistance
        # Need to compare OCV to initial value to capture this as an overpotential
        ocv_init = self.param.U_p(
            self.param.c_p_init(1), self.param.T_init) - self.param.U_n(
                self.param.c_n_init(0), self.param.T_init)
        ocv_init_dim = (self.param.U_p_ref - self.param.U_n_ref +
                        self.param.potential_scale * ocv_init)
        eta_ocv = ocv - ocv_init
        eta_ocv_dim = ocv_dim - ocv_init_dim
        # Current collector current density for working out euiqvalent resistance
        # based on Ohm's Law
        i_cc = self.variables["Current collector current density"]
        i_cc_dim = self.variables["Current collector current density [A.m-2]"]
        # Gather all overpotentials
        v_ecm = -(eta_ocv + eta_r_av + eta_c_av + eta_e_av + delta_phi_s_av)
        v_ecm_dim = -(eta_ocv_dim + eta_r_av_dim + eta_c_av_dim +
                      eta_e_av_dim + delta_phi_s_av_dim)
        # Current collector area for turning resistivity into resistance
        A_cc = self.param.A_cc
        self.variables.update({
            "Change in measured open circuit voltage":
            eta_ocv,
            "Change in measured open circuit voltage [V]":
            eta_ocv_dim,
            "Local ECM resistance":
            v_ecm / (i_cc * A_cc),
            "Local ECM resistance [Ohm]":
            v_ecm_dim / (i_cc_dim * A_cc),
        })

        # Cut-off voltage
        voltage = self.variables["Terminal voltage"]
        self.events.append(
            pybamm.Event(
                "Minimum voltage",
                voltage - self.param.voltage_low_cut,
                pybamm.EventType.TERMINATION,
            ))
        self.events.append(
            pybamm.Event(
                "Maximum voltage",
                voltage - self.param.voltage_high_cut,
                pybamm.EventType.TERMINATION,
            ))

        # Power
        I_dim = self.variables["Current [A]"]
        self.variables.update({"Terminal power [W]": I_dim * V_dim})
コード例 #20
0
    def set_voltage_variables(self):

        ocp_n = self.variables["Negative electrode open circuit potential"]
        ocp_p = self.variables["Positive electrode open circuit potential"]
        ocp_n_av = self.variables[
            "X-averaged negative electrode open circuit potential"]
        ocp_p_av = self.variables[
            "X-averaged positive electrode open circuit potential"]

        ocp_n_dim = self.variables[
            "Negative electrode open circuit potential [V]"]
        ocp_p_dim = self.variables[
            "Positive electrode open circuit potential [V]"]
        ocp_n_av_dim = self.variables[
            "X-averaged negative electrode open circuit potential [V]"]
        ocp_p_av_dim = self.variables[
            "X-averaged positive electrode open circuit potential [V]"]

        ocp_n_left = pybamm.boundary_value(ocp_n, "left")
        ocp_n_left_dim = pybamm.boundary_value(ocp_n_dim, "left")
        ocp_p_right = pybamm.boundary_value(ocp_p, "right")
        ocp_p_right_dim = pybamm.boundary_value(ocp_p_dim, "right")

        ocv_av = ocp_p_av - ocp_n_av
        ocv_av_dim = ocp_p_av_dim - ocp_n_av_dim
        ocv = ocp_p_right - ocp_n_left
        ocv_dim = ocp_p_right_dim - ocp_n_left_dim

        # overpotentials
        eta_r_n_av = self.variables[
            "X-averaged negative electrode reaction overpotential"]
        eta_r_n_av_dim = self.variables[
            "X-averaged negative electrode reaction overpotential [V]"]
        eta_r_p_av = self.variables[
            "X-averaged positive electrode reaction overpotential"]
        eta_r_p_av_dim = self.variables[
            "X-averaged positive electrode reaction overpotential [V]"]

        delta_phi_s_n_av = self.variables[
            "X-averaged negative electrode ohmic losses"]
        delta_phi_s_n_av_dim = self.variables[
            "X-averaged negative electrode ohmic losses [V]"]
        delta_phi_s_p_av = self.variables[
            "X-averaged positive electrode ohmic losses"]
        delta_phi_s_p_av_dim = self.variables[
            "X-averaged positive electrode ohmic losses [V]"]

        delta_phi_s_av = delta_phi_s_p_av - delta_phi_s_n_av
        delta_phi_s_av_dim = delta_phi_s_p_av_dim - delta_phi_s_n_av_dim

        eta_r_av = eta_r_p_av - eta_r_n_av
        eta_r_av_dim = eta_r_p_av_dim - eta_r_n_av_dim

        # terminal voltage (Note: phi_s_cn is zero at the negative tab)
        phi_s_cp = self.variables["Positive current collector potential"]
        phi_s_cp_dim = self.variables[
            "Positive current collector potential [V]"]
        if self.options["dimensionality"] == 0:
            V = phi_s_cp
            V_dim = phi_s_cp_dim
        elif self.options["dimensionality"] in [1, 2]:
            V = pybamm.BoundaryValue(phi_s_cp, "positive tab")
            V_dim = pybamm.BoundaryValue(phi_s_cp_dim, "positive tab")

        # TODO: add current collector losses to the voltage in 3D

        self.variables.update({
            "X-averaged open circuit voltage": ocv_av,
            "Measured open circuit voltage": ocv,
            "X-averaged open circuit voltage [V]": ocv_av_dim,
            "Measured open circuit voltage [V]": ocv_dim,
            "X-averaged reaction overpotential": eta_r_av,
            "X-averaged reaction overpotential [V]": eta_r_av_dim,
            "X-averaged solid phase ohmic losses": delta_phi_s_av,
            "X-averaged solid phase ohmic losses [V]": delta_phi_s_av_dim,
            "Terminal voltage": V,
            "Terminal voltage [V]": V_dim,
        })

        # Battery-wide variables
        eta_e_av_dim = self.variables.get(
            "X-averaged electrolyte ohmic losses [V]", 0)
        eta_c_av_dim = self.variables.get(
            "X-averaged concentration overpotential [V]", 0)
        num_cells = pybamm.Parameter(
            "Number of cells connected in series to make a battery")

        self.variables.update({
            "X-averaged battery open circuit voltage [V]":
            ocv_av_dim * num_cells,
            "Measured battery open circuit voltage [V]":
            ocv_dim * num_cells,
            "X-averaged battery reaction overpotential [V]":
            eta_r_av_dim * num_cells,
            "X-averaged battery solid phase ohmic losses [V]":
            delta_phi_s_av_dim * num_cells,
            "X-averaged battery electrolyte ohmic losses [V]":
            eta_e_av_dim * num_cells,
            "X-averaged battery concentration overpotential [V]":
            eta_c_av_dim * num_cells,
            "Battery voltage [V]":
            V_dim * num_cells,
        })

        # Cut-off voltage
        voltage = self.variables["Terminal voltage"]
        self.events["Minimum voltage"] = voltage - self.param.voltage_low_cut
        self.events["Maximum voltage"] = voltage - self.param.voltage_high_cut
コード例 #21
0
    def __init__(self, name="Doyle-Fuller-Newman half cell model", options=None):
        super().__init__({}, name)
        pybamm.citations.register("marquis2019asymptotic")
        # `param` is a class containing all the relevant parameters and functions for
        # this model. These are purely symbolic at this stage, and will be set by the
        # `ParameterValues` class when the model is processed.
        param = self.param
        options = options or {"working electrode": None}

        if options["working electrode"] not in ["negative", "positive"]:
            raise ValueError(
                "The option 'working electrode' should be either 'positive'"
                " or 'negative'"
            )

        self.options.update(options)
        working_electrode = options["working electrode"]

        ######################
        # Variables
        ######################
        # Variables that depend on time only are created without a domain
        Q = pybamm.Variable("Discharge capacity [A.h]")

        # Define some useful scalings
        pot = param.potential_scale
        i_typ = param.current_scale

        # Variables that vary spatially are created with a domain. Depending on
        # which is the working electrode we need to define a set variables or another
        if working_electrode == "negative":
            # Electrolyte concentration
            c_e_n = pybamm.Variable(
                "Negative electrolyte concentration", domain="negative electrode"
            )
            c_e_s = pybamm.Variable(
                "Separator electrolyte concentration", domain="separator"
            )
            # Concatenations combine several variables into a single variable, to
            # simplify implementing equations that hold over several domains
            c_e = pybamm.Concatenation(c_e_n, c_e_s)

            # Electrolyte potential
            phi_e_n = pybamm.Variable(
                "Negative electrolyte potential", domain="negative electrode"
            )
            phi_e_s = pybamm.Variable(
                "Separator electrolyte potential", domain="separator"
            )
            phi_e = pybamm.Concatenation(phi_e_n, phi_e_s)

            # Particle concentrations are variables on the particle domain, but also
            # vary in the x-direction (electrode domain) and so must be provided with
            # auxiliary domains
            c_s_n = pybamm.Variable(
                "Negative particle concentration",
                domain="negative particle",
                auxiliary_domains={"secondary": "negative electrode"},
            )
            # Set concentration in positive particle to be equal to the initial
            # concentration as it is not the working electrode
            x_p = pybamm.PrimaryBroadcast(
                pybamm.standard_spatial_vars.x_p, "positive particle"
            )
            c_s_p = param.c_n_init(x_p)

            # Electrode potential
            phi_s_n = pybamm.Variable(
                "Negative electrode potential", domain="negative electrode"
            )
            # Set potential in positive electrode to be equal to the initial OCV
            phi_s_p = param.U_p(pybamm.surf(param.c_p_init(x_p)), param.T_init)
        else:
            c_e_p = pybamm.Variable(
                "Positive electrolyte concentration", domain="positive electrode"
            )
            c_e_s = pybamm.Variable(
                "Separator electrolyte concentration", domain="separator"
            )
            # Concatenations combine several variables into a single variable, to
            # simplify implementing equations that hold over several domains
            c_e = pybamm.Concatenation(c_e_s, c_e_p)

            # Electrolyte potential
            phi_e_s = pybamm.Variable(
                "Separator electrolyte potential", domain="separator"
            )
            phi_e_p = pybamm.Variable(
                "Positive electrolyte potential", domain="positive electrode"
            )
            phi_e = pybamm.Concatenation(phi_e_s, phi_e_p)

            # Particle concentrations are variables on the particle domain, but also
            # vary in the x-direction (electrode domain) and so must be provided with
            # auxiliary domains
            c_s_p = pybamm.Variable(
                "Positive particle concentration",
                domain="positive particle",
                auxiliary_domains={"secondary": "positive electrode"},
            )
            # Set concentration in negative particle to be equal to the initial
            # concentration as it is not the working electrode
            x_n = pybamm.PrimaryBroadcast(
                pybamm.standard_spatial_vars.x_n, "negative particle"
            )
            c_s_n = param.c_n_init(x_n)

            # Electrode potential
            phi_s_p = pybamm.Variable(
                "Positive electrode potential", domain="positive electrode"
            )
            # Set potential in negative electrode to be equal to the initial OCV
            phi_s_n = param.U_n(pybamm.surf(param.c_n_init(x_n)), param.T_init)

        # Constant temperature
        T = param.T_init

        ######################
        # Other set-up
        ######################

        # Current density
        i_cell = param.current_with_time

        # Porosity and Tortuosity
        # Primary broadcasts are used to broadcast scalar quantities across a domain
        # into a vector of the right shape, for multiplying with other vectors
        eps_n = pybamm.PrimaryBroadcast(
            pybamm.Parameter("Negative electrode porosity"), "negative electrode"
        )
        eps_s = pybamm.PrimaryBroadcast(
            pybamm.Parameter("Separator porosity"), "separator"
        )
        eps_p = pybamm.PrimaryBroadcast(
            pybamm.Parameter("Positive electrode porosity"), "positive electrode"
        )

        if working_electrode == "negative":
            eps = pybamm.Concatenation(eps_n, eps_s)
            tor = pybamm.Concatenation(eps_n ** param.b_e_n, eps_s ** param.b_e_s)
        else:
            eps = pybamm.Concatenation(eps_s, eps_p)
            tor = pybamm.Concatenation(eps_s ** param.b_e_s, eps_p ** param.b_e_p)

        # Interfacial reactions
        # Surf takes the surface value of a variable, i.e. its boundary value on the
        # right side. This is also accessible via `boundary_value(x, "right")`, with
        # "left" providing the boundary value of the left side
        c_s_surf_n = pybamm.surf(c_s_n)
        c_s_surf_p = pybamm.surf(c_s_p)

        if working_electrode == "negative":
            j0_n = param.j0_n(c_e_n, c_s_surf_n, T) / param.C_r_n
            j_n = (
                2
                * j0_n
                * pybamm.sinh(
                    param.ne_n / 2 * (phi_s_n - phi_e_n - param.U_n(c_s_surf_n, T))
                )
            )
            j_s = pybamm.PrimaryBroadcast(0, "separator")
            j_p = pybamm.PrimaryBroadcast(0, "positive electrode")
            j = pybamm.Concatenation(j_n, j_s)
        else:
            j0_p = param.gamma_p * param.j0_p(c_e_p, c_s_surf_p, T) / param.C_r_p
            j_p = (
                2
                * j0_p
                * pybamm.sinh(
                    param.ne_p / 2 * (phi_s_p - phi_e_p - param.U_p(c_s_surf_p, T))
                )
            )
            j_s = pybamm.PrimaryBroadcast(0, "separator")
            j_n = pybamm.PrimaryBroadcast(0, "negative electrode")
            j = pybamm.Concatenation(j_s, j_p)

        ######################
        # State of Charge
        ######################
        I = param.dimensional_current_with_time
        # The `rhs` dictionary contains differential equations, with the key being the
        # variable in the d/dt
        self.rhs[Q] = I * param.timescale / 3600
        # Initial conditions must be provided for the ODEs
        self.initial_conditions[Q] = pybamm.Scalar(0)

        ######################
        # Particles
        ######################

        if working_electrode == "negative":
            # The div and grad operators will be converted to the appropriate matrix
            # multiplication at the discretisation stage
            N_s_n = -param.D_n(c_s_n, T) * pybamm.grad(c_s_n)
            self.rhs[c_s_n] = -(1 / param.C_n) * pybamm.div(N_s_n)

            # Boundary conditions must be provided for equations with spatial
            # derivatives
            self.boundary_conditions[c_s_n] = {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (
                    -param.C_n * j_n / param.a_R_n / param.D_n(c_s_surf_n, T),
                    "Neumann",
                ),
            }

            # c_n_init can in general be a function of x
            # Note the broadcasting, for domains
            x_n = pybamm.PrimaryBroadcast(
                pybamm.standard_spatial_vars.x_n, "negative particle"
            )
            self.initial_conditions[c_s_n] = param.c_n_init(x_n)

            # Events specify points at which a solution should terminate
            self.events += [
                pybamm.Event(
                    "Minimum negative particle surface concentration",
                    pybamm.min(c_s_surf_n) - 0.01,
                ),
                pybamm.Event(
                    "Maximum negative particle surface concentration",
                    (1 - 0.01) - pybamm.max(c_s_surf_n),
                ),
            ]
        else:
            # The div and grad operators will be converted to the appropriate matrix
            # multiplication at the discretisation stage
            N_s_p = -param.D_p(c_s_p, T) * pybamm.grad(c_s_p)
            self.rhs[c_s_p] = -(1 / param.C_p) * pybamm.div(N_s_p)

            # Boundary conditions must be provided for equations with spatial
            # derivatives
            self.boundary_conditions[c_s_p] = {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (
                    -param.C_p
                    * j_p
                    / param.a_R_p
                    / param.gamma_p
                    / param.D_p(c_s_surf_p, T),
                    "Neumann",
                ),
            }

            # c_p_init can in general be a function of x
            # Note the broadcasting, for domains
            x_p = pybamm.PrimaryBroadcast(
                pybamm.standard_spatial_vars.x_p, "positive particle"
            )
            self.initial_conditions[c_s_p] = param.c_p_init(x_p)

            # Events specify points at which a solution should terminate
            self.events += [
                pybamm.Event(
                    "Minimum positive particle surface concentration",
                    pybamm.min(c_s_surf_p) - 0.01,
                ),
                pybamm.Event(
                    "Maximum positive particle surface concentration",
                    (1 - 0.01) - pybamm.max(c_s_surf_p),
                ),
            ]

        ######################
        # Current in the solid
        ######################
        eps_s_n = pybamm.Parameter("Negative electrode active material volume fraction")
        eps_s_p = pybamm.Parameter("Positive electrode active material volume fraction")

        if working_electrode == "negative":
            sigma_eff_n = param.sigma_n * eps_s_n ** param.b_s_n
            i_s_n = -sigma_eff_n * pybamm.grad(phi_s_n)
            self.boundary_conditions[phi_s_n] = {
                "left": (
                    i_cell / pybamm.boundary_value(-sigma_eff_n, "left"),
                    "Neumann",
                ),
                "right": (pybamm.Scalar(0), "Neumann"),
            }
            # The `algebraic` dictionary contains differential equations, with the key
            # being the main scalar variable of interest in the equation
            self.algebraic[phi_s_n] = pybamm.div(i_s_n) + j_n

            # Initial conditions must also be provided for algebraic equations, as an
            # initial guess for a root-finding algorithm which calculates consistent
            # initial conditions
            self.initial_conditions[phi_s_n] = param.U_n(
                param.c_n_init(0), param.T_init
            )
        else:
            sigma_eff_p = param.sigma_p * eps_s_p ** param.b_s_p
            i_s_p = -sigma_eff_p * pybamm.grad(phi_s_p)
            self.boundary_conditions[phi_s_p] = {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (
                    i_cell / pybamm.boundary_value(-sigma_eff_p, "right"),
                    "Neumann",
                ),
            }
            self.algebraic[phi_s_p] = pybamm.div(i_s_p) + j_p
            # Initial conditions must also be provided for algebraic equations, as an
            # initial guess for a root-finding algorithm which calculates consistent
            # initial conditions
            self.initial_conditions[phi_s_p] = param.U_p(
                param.c_p_init(1), param.T_init
            )

        ######################
        # Electrolyte concentration
        ######################
        N_e = -tor * param.D_e(c_e, T) * pybamm.grad(c_e)
        self.rhs[c_e] = (1 / eps) * (
            -pybamm.div(N_e) / param.C_e + (1 - param.t_plus(c_e)) * j / param.gamma_e
        )
        dce_dx = (
            -(1 - param.t_plus(c_e))
            * i_cell
            * param.C_e
            / (tor * param.gamma_e * param.D_e(c_e, T))
        )

        if working_electrode == "negative":
            self.boundary_conditions[c_e] = {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (pybamm.boundary_value(dce_dx, "right"), "Neumann"),
            }
        else:
            self.boundary_conditions[c_e] = {
                "left": (pybamm.boundary_value(dce_dx, "left"), "Neumann"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }

        self.initial_conditions[c_e] = param.c_e_init
        self.events.append(
            pybamm.Event(
                "Zero electrolyte concentration cut-off", pybamm.min(c_e) - 0.002
            )
        )

        ######################
        # Current in the electrolyte
        ######################
        i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * (
            param.chi(c_e, T) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e)
        )
        self.algebraic[phi_e] = pybamm.div(i_e) - j

        ref_potential = param.U_n_ref / pot

        if working_electrode == "negative":
            self.boundary_conditions[phi_e] = {
                "left": (pybamm.Scalar(0), "Neumann"),
                "right": (ref_potential, "Dirichlet"),
            }
        else:
            self.boundary_conditions[phi_e] = {
                "left": (ref_potential, "Dirichlet"),
                "right": (pybamm.Scalar(0), "Neumann"),
            }

        self.initial_conditions[phi_e] = ref_potential
        ######################
        # (Some) variables
        ######################
        L_Li = pybamm.Parameter("Lithium counter electrode thickness [m]")
        sigma_Li = pybamm.Parameter("Lithium counter electrode conductivity [S.m-1]")
        j_Li = pybamm.Parameter(
            "Lithium counter electrode exchange-current density [A.m-2]"
        )

        if working_electrode == "negative":
            voltage = pybamm.boundary_value(phi_s_n, "left") - ref_potential
            voltage_dim = pot * pybamm.boundary_value(phi_s_n, "left")
            vdrop_Li = 2 * pybamm.arcsinh(
                i_cell * i_typ / j_Li
            ) + L_Li * i_typ * i_cell / (sigma_Li * pot)
            vdrop_Li_dim = (
                2 * pot * pybamm.arcsinh(i_cell * i_typ / j_Li)
                + L_Li * i_typ * i_cell / sigma_Li
            )
        else:
            voltage = pybamm.boundary_value(phi_s_p, "right") - ref_potential
            voltage_dim = param.U_p_ref + pot * voltage
            vdrop_Li = -(
                2 * pybamm.arcsinh(i_cell * i_typ / j_Li)
                + L_Li * i_typ * i_cell / (sigma_Li * pot)
            )
            vdrop_Li_dim = -(
                2 * pot * pybamm.arcsinh(i_cell * i_typ / j_Li)
                + L_Li * i_typ * i_cell / sigma_Li
            )

        c_s_surf_p_av = pybamm.x_average(c_s_surf_p)
        c_s_surf_n_av = pybamm.x_average(c_s_surf_n)

        # The `variables` dictionary contains all variables that might be useful for
        # visualising the solution of the model
        self.variables = {
            "Time [s]": param.timescale * pybamm.t,
            "Negative particle surface concentration": c_s_surf_n,
            "X-averaged negative particle surface concentration": c_s_surf_n_av,
            "Negative particle concentration": c_s_n,
            "Negative particle surface concentration [mol.m-3]": param.c_n_max
            * c_s_surf_n,
            "X-averaged negative particle surface concentration "
            "[mol.m-3]": param.c_n_max * c_s_surf_n_av,
            "Negative particle concentration [mol.m-3]": param.c_n_max * c_s_n,
            "Electrolyte concentration": c_e,
            "Electrolyte concentration [mol.m-3]": param.c_e_typ * c_e,
            "Positive particle surface concentration": c_s_surf_p,
            "X-averaged positive particle surface concentration": c_s_surf_p_av,
            "Positive particle concentration": c_s_p,
            "Positive particle surface concentration [mol.m-3]": param.c_p_max
            * c_s_surf_p,
            "X-averaged positive particle surface concentration "
            "[mol.m-3]": param.c_p_max * c_s_surf_p_av,
            "Positive particle concentration [mol.m-3]": param.c_p_max * c_s_p,
            "Current [A]": I,
            "Negative electrode potential": phi_s_n,
            "Negative electrode potential [V]": pot * phi_s_n,
            "Negative electrode open circuit potential": param.U_n(c_s_surf_n, T),
            "Electrolyte potential": phi_e,
            "Electrolyte potential [V]": -param.U_n_ref + pot * phi_e,
            "Positive electrode potential": phi_s_p,
            "Positive electrode potential [V]": (param.U_p_ref - param.U_n_ref)
            + pot * phi_s_p,
            "Positive electrode open circuit potential": param.U_p(c_s_surf_p, T),
            "Voltage drop": voltage,
            "Voltage drop [V]": voltage_dim,
            "Terminal voltage": voltage + vdrop_Li,
            "Terminal voltage [V]": voltage_dim + vdrop_Li_dim,
        }
コード例 #22
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
コード例 #23
0
    def set_voltage_variables(self):

        ocp_n = self.variables["Negative electrode open circuit potential"]
        ocp_p = self.variables["Positive electrode open circuit potential"]
        ocp_n_av = self.variables[
            "X-averaged negative electrode open circuit potential"
        ]
        ocp_p_av = self.variables[
            "X-averaged positive electrode open circuit potential"
        ]

        ocp_n_dim = self.variables["Negative electrode open circuit potential [V]"]
        ocp_p_dim = self.variables["Positive electrode open circuit potential [V]"]
        ocp_n_av_dim = self.variables[
            "X-averaged negative electrode open circuit potential [V]"
        ]
        ocp_p_av_dim = self.variables[
            "X-averaged positive electrode open circuit potential [V]"
        ]

        ocp_n_left = pybamm.boundary_value(ocp_n, "left")
        ocp_n_left_dim = pybamm.boundary_value(ocp_n_dim, "left")
        ocp_p_right = pybamm.boundary_value(ocp_p, "right")
        ocp_p_right_dim = pybamm.boundary_value(ocp_p_dim, "right")

        ocv_av = ocp_p_av - ocp_n_av
        ocv_av_dim = ocp_p_av_dim - ocp_n_av_dim
        ocv = ocp_p_right - ocp_n_left
        ocv_dim = ocp_p_right_dim - ocp_n_left_dim

        # overpotentials
        eta_r_n_av = self.variables[
            "X-averaged negative electrode reaction overpotential"
        ]
        eta_r_n_av_dim = self.variables[
            "X-averaged negative electrode reaction overpotential [V]"
        ]
        eta_r_p_av = self.variables[
            "X-averaged positive electrode reaction overpotential"
        ]
        eta_r_p_av_dim = self.variables[
            "X-averaged positive electrode reaction overpotential [V]"
        ]

        delta_phi_s_n_av = self.variables["X-averaged negative electrode ohmic losses"]
        delta_phi_s_n_av_dim = self.variables[
            "X-averaged negative electrode ohmic losses [V]"
        ]
        delta_phi_s_p_av = self.variables["X-averaged positive electrode ohmic losses"]
        delta_phi_s_p_av_dim = self.variables[
            "X-averaged positive electrode ohmic losses [V]"
        ]

        delta_phi_s_av = delta_phi_s_p_av - delta_phi_s_n_av
        delta_phi_s_av_dim = delta_phi_s_p_av_dim - delta_phi_s_n_av_dim

        eta_r_av = eta_r_p_av - eta_r_n_av
        eta_r_av_dim = eta_r_p_av_dim - eta_r_n_av_dim

        # SEI film overpotential
        eta_sei_n_av = self.variables[
            "X-averaged negative electrode sei film overpotential"
        ]
        eta_sei_p_av = self.variables[
            "X-averaged positive electrode sei film overpotential"
        ]
        eta_sei_n_av_dim = self.variables[
            "X-averaged negative electrode sei film overpotential [V]"
        ]
        eta_sei_p_av_dim = self.variables[
            "X-averaged positive electrode sei film overpotential [V]"
        ]
        eta_sei_av = eta_sei_n_av + eta_sei_p_av
        eta_sei_av_dim = eta_sei_n_av_dim + eta_sei_p_av_dim

        # TODO: add current collector losses to the voltage in 3D

        self.variables.update(
            {
                "X-averaged open circuit voltage": ocv_av,
                "Measured open circuit voltage": ocv,
                "X-averaged open circuit voltage [V]": ocv_av_dim,
                "Measured open circuit voltage [V]": ocv_dim,
                "X-averaged reaction overpotential": eta_r_av,
                "X-averaged reaction overpotential [V]": eta_r_av_dim,
                "X-averaged sei film overpotential": eta_sei_av,
                "X-averaged sei film overpotential [V]": eta_sei_av_dim,
                "X-averaged solid phase ohmic losses": delta_phi_s_av,
                "X-averaged solid phase ohmic losses [V]": delta_phi_s_av_dim,
            }
        )

        # Battery-wide variables
        V_dim = self.variables["Terminal voltage [V]"]
        eta_e_av_dim = self.variables.get("X-averaged electrolyte ohmic losses [V]", 0)
        eta_c_av_dim = self.variables.get(
            "X-averaged concentration overpotential [V]", 0
        )
        num_cells = pybamm.Parameter(
            "Number of cells connected in series to make a battery"
        )

        self.variables.update(
            {
                "X-averaged battery open circuit voltage [V]": ocv_av_dim * num_cells,
                "Measured battery open circuit voltage [V]": ocv_dim * num_cells,
                "X-averaged battery reaction overpotential [V]": eta_r_av_dim
                * num_cells,
                "X-averaged battery solid phase ohmic losses [V]": delta_phi_s_av_dim
                * num_cells,
                "X-averaged battery electrolyte ohmic losses [V]": eta_e_av_dim
                * num_cells,
                "X-averaged battery concentration overpotential [V]": eta_c_av_dim
                * num_cells,
                "Battery voltage [V]": V_dim * num_cells,
            }
        )

        # Cut-off voltage
        voltage = self.variables["Terminal voltage"]
        self.events.append(
            pybamm.Event(
                "Minimum voltage",
                voltage - self.param.voltage_low_cut,
                pybamm.EventType.TERMINATION,
            )
        )
        self.events.append(
            pybamm.Event(
                "Maximum voltage",
                voltage - self.param.voltage_high_cut,
                pybamm.EventType.TERMINATION,
            )
        )

        # Power
        I_dim = self.variables["Current [A]"]
        self.variables.update({"Terminal power [W]": I_dim * V_dim})
コード例 #24
0
    def __init__(self, name="Doyle-Fuller-Newman model"):
        super().__init__({}, name)
        # `param` is a class containing all the relevant parameters and functions for
        # this model. These are purely symbolic at this stage, and will be set by the
        # `ParameterValues` class when the model is processed.
        param = self.param

        ######################
        # Variables
        ######################
        # Variables that depend on time only are created without a domain
        Q = pybamm.Variable("Discharge capacity [A.h]")
        # Variables that vary spatially are created with a domain
        c_e_n = pybamm.Variable(
            "Negative electrolyte concentration",
            domain="negative electrode",
        )
        c_e_s = pybamm.Variable(
            "Separator electrolyte concentration",
            domain="separator",
        )
        c_e_p = pybamm.Variable(
            "Positive electrolyte concentration",
            domain="positive electrode",
        )
        # Concatenations combine several variables into a single variable, to simplify
        # implementing equations that hold over several domains
        c_e = pybamm.Concatenation(c_e_n, c_e_s, c_e_p)

        # Electrolyte potential
        phi_e_n = pybamm.Variable(
            "Negative electrolyte potential",
            domain="negative electrode",
        )
        phi_e_s = pybamm.Variable(
            "Separator electrolyte potential",
            domain="separator",
        )
        phi_e_p = pybamm.Variable(
            "Positive electrolyte potential",
            domain="positive electrode",
        )
        phi_e = pybamm.Concatenation(phi_e_n, phi_e_s, phi_e_p)

        # Electrode potential
        phi_s_n = pybamm.Variable(
            "Negative electrode potential",
            domain="negative electrode",
        )
        phi_s_p = pybamm.Variable(
            "Positive electrode potential",
            domain="positive electrode",
        )
        # Particle concentrations are variables on the particle domain, but also vary in
        # the x-direction (electrode domain) and so must be provided with auxiliary
        # domains
        c_s_n = pybamm.Variable(
            "Negative particle concentration",
            domain="negative particle",
            auxiliary_domains={"secondary": "negative electrode"},
        )
        c_s_p = pybamm.Variable(
            "Positive particle concentration",
            domain="positive particle",
            auxiliary_domains={"secondary": "positive electrode"},
        )

        # Constant temperature
        T = param.T_init

        ######################
        # Other set-up
        ######################

        # Current density
        i_cell = param.current_with_time

        # Porosity
        # Primary broadcasts are used to broadcast scalar quantities across a domain
        # into a vector of the right shape, for multiplying with other vectors
        eps_n = pybamm.PrimaryBroadcast(
            pybamm.Parameter("Negative electrode porosity"),
            "negative electrode")
        eps_s = pybamm.PrimaryBroadcast(pybamm.Parameter("Separator porosity"),
                                        "separator")
        eps_p = pybamm.PrimaryBroadcast(
            pybamm.Parameter("Positive electrode porosity"),
            "positive electrode")
        eps = pybamm.Concatenation(eps_n, eps_s, eps_p)

        # Tortuosity
        tor = pybamm.Concatenation(eps_n**param.b_e_n, eps_s**param.b_e_s,
                                   eps_p**param.b_e_p)

        # Interfacial reactions
        # Surf takes the surface value of a variable, i.e. its boundary value on the
        # right side. This is also accessible via `boundary_value(x, "right")`, with
        # "left" providing the boundary value of the left side
        c_s_surf_n = pybamm.surf(c_s_n)
        j0_n = (param.m_n(T) / param.C_r_n * c_e_n**(1 / 2) *
                c_s_surf_n**(1 / 2) * (1 - c_s_surf_n)**(1 / 2))
        j_n = (2 * j0_n *
               pybamm.sinh(param.ne_n / 2 *
                           (phi_s_n - phi_e_n - param.U_n(c_s_surf_n, T))))
        c_s_surf_p = pybamm.surf(c_s_p)
        j0_p = (param.gamma_p * param.m_p(T) / param.C_r_p * c_e_p**(1 / 2) *
                c_s_surf_p**(1 / 2) * (1 - c_s_surf_p)**(1 / 2))
        j_s = pybamm.PrimaryBroadcast(0, "separator")
        j_p = (2 * j0_p *
               pybamm.sinh(param.ne_p / 2 *
                           (phi_s_p - phi_e_p - param.U_p(c_s_surf_p, T))))
        j = pybamm.Concatenation(j_n, j_s, j_p)

        ######################
        # State of Charge
        ######################
        I = param.dimensional_current_with_time
        # The `rhs` dictionary contains differential equations, with the key being the
        # variable in the d/dt
        self.rhs[Q] = I * param.timescale / 3600
        # Initial conditions must be provided for the ODEs
        self.initial_conditions[Q] = pybamm.Scalar(0)

        ######################
        # Particles
        ######################

        # The div and grad operators will be converted to the appropriate matrix
        # multiplication at the discretisation stage
        N_s_n = -param.D_n(c_s_n, T) * pybamm.grad(c_s_n)
        N_s_p = -param.D_p(c_s_p, T) * pybamm.grad(c_s_p)
        self.rhs[c_s_n] = -(1 / param.C_n) * pybamm.div(N_s_n)
        self.rhs[c_s_p] = -(1 / param.C_p) * pybamm.div(N_s_p)
        # Boundary conditions must be provided for equations with spatial derivatives
        self.boundary_conditions[c_s_n] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (
                -param.C_n * j_n / param.a_n / param.D_n(c_s_surf_n, T),
                "Neumann",
            ),
        }
        self.boundary_conditions[c_s_p] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (
                -param.C_p * j_p / param.a_p / param.gamma_p /
                param.D_p(c_s_surf_p, T),
                "Neumann",
            ),
        }
        # c_n_init and c_p_init can in general be functions of x
        # Note the broadcasting, for domains
        x_n = pybamm.PrimaryBroadcast(pybamm.standard_spatial_vars.x_n,
                                      "negative particle")
        self.initial_conditions[c_s_n] = param.c_n_init(x_n)
        x_p = pybamm.PrimaryBroadcast(pybamm.standard_spatial_vars.x_p,
                                      "positive particle")
        self.initial_conditions[c_s_p] = param.c_p_init(x_p)
        # Events specify points at which a solution should terminate
        self.events += [
            pybamm.Event(
                "Minimum negative particle surface concentration",
                pybamm.min(c_s_surf_n) - 0.01,
            ),
            pybamm.Event(
                "Maximum negative particle surface concentration",
                (1 - 0.01) - pybamm.max(c_s_surf_n),
            ),
            pybamm.Event(
                "Minimum positive particle surface concentration",
                pybamm.min(c_s_surf_p) - 0.01,
            ),
            pybamm.Event(
                "Maximum positive particle surface concentration",
                (1 - 0.01) - pybamm.max(c_s_surf_p),
            ),
        ]
        ######################
        # Current in the solid
        ######################
        i_s_n = -param.sigma_n * (1 -
                                  eps_n)**param.b_s_n * pybamm.grad(phi_s_n)
        sigma_eff_p = param.sigma_p * (1 - eps_p)**param.b_s_p
        i_s_p = -sigma_eff_p * pybamm.grad(phi_s_p)
        # The `algebraic` dictionary contains differential equations, with the key being
        # the main scalar variable of interest in the equation
        self.algebraic[phi_s_n] = pybamm.div(i_s_n) + j_n
        self.algebraic[phi_s_p] = pybamm.div(i_s_p) + j_p
        self.boundary_conditions[phi_s_n] = {
            "left": (pybamm.Scalar(0), "Dirichlet"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.boundary_conditions[phi_s_p] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right":
            (i_cell / pybamm.boundary_value(-sigma_eff_p, "right"), "Neumann"),
        }
        # Initial conditions must also be provided for algebraic equations, as an
        # initial guess for a root-finding algorithm which calculates consistent initial
        # conditions
        # We evaluate c_n_init at x=0 and c_p_init at x=1 (this is just an initial
        # guess so actual value is not too important)
        self.initial_conditions[phi_s_n] = pybamm.Scalar(0)
        self.initial_conditions[phi_s_p] = param.U_p(
            param.c_p_init(1), param.T_init) - param.U_n(
                param.c_n_init(0), param.T_init)

        ######################
        # Current in the electrolyte
        ######################
        i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * (
            param.chi(c_e) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e))
        self.algebraic[phi_e] = pybamm.div(i_e) - j
        self.boundary_conditions[phi_e] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.initial_conditions[phi_e] = -param.U_n(param.c_n_init(0),
                                                    param.T_init)

        ######################
        # Electrolyte concentration
        ######################
        N_e = -tor * param.D_e(c_e, T) * pybamm.grad(c_e)
        self.rhs[c_e] = (1 /
                         eps) * (-pybamm.div(N_e) / param.C_e +
                                 (1 - param.t_plus(c_e)) * j / param.gamma_e)
        self.boundary_conditions[c_e] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.initial_conditions[c_e] = param.c_e_init
        self.events.append(
            pybamm.Event("Zero electrolyte concentration cut-off",
                         pybamm.min(c_e) - 0.002))

        ######################
        # (Some) variables
        ######################
        voltage = pybamm.boundary_value(phi_s_p, "right")
        # The `variables` dictionary contains all variables that might be useful for
        # visualising the solution of the model
        self.variables = {
            "Negative particle surface concentration": c_s_surf_n,
            "Electrolyte concentration": c_e,
            "Positive particle surface concentration": c_s_surf_p,
            "Current [A]": I,
            "Negative electrode potential": phi_s_n,
            "Electrolyte potential": phi_e,
            "Positive electrode potential": phi_s_p,
            "Terminal voltage": voltage,
        }
        self.events += [
            pybamm.Event("Minimum voltage", voltage - param.voltage_low_cut),
            pybamm.Event("Maximum voltage", voltage - param.voltage_high_cut),
        ]
コード例 #25
0
    def __init__(self, name="Basic full model"):
        super().__init__({}, name)
        # `param` is a class containing all the relevant parameters and functions for
        # this model. These are purely symbolic at this stage, and will be set by the
        # `ParameterValues` class when the model is processed.
        param = self.param

        ######################
        # Variables
        ######################
        # Variables that depend on time only are created without a domain
        Q = pybamm.Variable("Discharge capacity [A.h]")
        # Variables that vary spatially are created with a domain
        c_e_n = pybamm.Variable(
            "Negative electrolyte concentration",
            domain="negative electrode",
        )
        c_e_s = pybamm.Variable(
            "Separator electrolyte concentration",
            domain="separator",
        )
        c_e_p = pybamm.Variable(
            "Positive electrolyte concentration",
            domain="positive electrode",
        )
        # Concatenations combine several variables into a single variable, to simplify
        # implementing equations that hold over several domains
        c_e = pybamm.Concatenation(c_e_n, c_e_s, c_e_p)

        # Electrolyte potential
        phi_e_n = pybamm.Variable(
            "Negative electrolyte potential",
            domain="negative electrode",
        )
        phi_e_s = pybamm.Variable(
            "Separator electrolyte potential",
            domain="separator",
        )
        phi_e_p = pybamm.Variable(
            "Positive electrolyte potential",
            domain="positive electrode",
        )
        phi_e = pybamm.Concatenation(phi_e_n, phi_e_s, phi_e_p)

        # Electrode potential
        phi_s_n = pybamm.Variable(
            "Negative electrode potential",
            domain="negative electrode",
        )
        phi_s_p = pybamm.Variable(
            "Positive electrode potential",
            domain="positive electrode",
        )

        # Porosity
        eps_n = pybamm.Variable(
            "Negative electrode porosity",
            domain="negative electrode",
        )
        eps_s = pybamm.Variable("Separator porosity", domain="separator")
        eps_p = pybamm.Variable(
            "Positive electrode porosity",
            domain="positive electrode",
        )
        eps = pybamm.Concatenation(eps_n, eps_s, eps_p)

        # Pressure (for convection)
        pressure_n = pybamm.Variable(
            "Negative electrolyte pressure",
            domain="negative electrode",
        )
        pressure_p = pybamm.Variable(
            "Positive electrolyte pressure",
            domain="positive electrode",
        )

        # Constant temperature
        T = param.T_init

        ######################
        # Other set-up
        ######################

        # Current density
        i_cell = param.current_with_time

        # Tortuosity
        tor = pybamm.Concatenation(eps_n**param.b_e_n, eps_s**param.b_e_s,
                                   eps_p**param.b_e_p)

        # Interfacial reactions
        j0_n = param.j0_n(c_e_n, T)
        j_n = (2 * j0_n *
               pybamm.sinh(param.ne_n / 2 *
                           (phi_s_n - phi_e_n - param.U_n(c_e_n, T))))
        j0_p = param.j0_p(c_e_p, T)
        j_s = pybamm.PrimaryBroadcast(0, "separator")
        j_p = (2 * j0_p *
               pybamm.sinh(param.ne_p / 2 *
                           (phi_s_p - phi_e_p - param.U_p(c_e_p, T))))
        j = pybamm.Concatenation(j_n, j_s, j_p)

        ######################
        # State of Charge
        ######################
        I = param.dimensional_current_with_time
        # The `rhs` dictionary contains differential equations, with the key being the
        # variable in the d/dt
        self.rhs[Q] = I * param.timescale / 3600
        # Initial conditions must be provided for the ODEs
        self.initial_conditions[Q] = pybamm.Scalar(0)

        ######################
        # Convection
        ######################
        v_n = -pybamm.grad(pressure_n)
        v_p = -pybamm.grad(pressure_p)
        l_s = pybamm.geometric_parameters.l_s
        l_n = pybamm.geometric_parameters.l_n
        x_s = pybamm.SpatialVariable("x_s", domain="separator")

        # Difference in negative and positive electrode velocities determines the
        # velocity in the separator
        v_n_right = param.beta_n * i_cell
        v_p_left = param.beta_p * i_cell
        d_v_s__dx = (v_p_left - v_n_right) / l_s

        # Simple formula for velocity in the separator
        div_V_s = -d_v_s__dx
        v_s = d_v_s__dx * (x_s - l_n) + v_n_right

        # v is the velocity in the x-direction
        # div_V is the divergence of the velocity in the yz-directions
        v = pybamm.Concatenation(v_n, v_s, v_p)
        div_V = pybamm.Concatenation(
            pybamm.PrimaryBroadcast(0, "negative electrode"),
            pybamm.PrimaryBroadcast(div_V_s, "separator"),
            pybamm.PrimaryBroadcast(0, "positive electrode"),
        )
        # Simple formula for velocity in the separator
        self.algebraic[pressure_n] = pybamm.div(v_n) - param.beta_n * j_n
        self.algebraic[pressure_p] = pybamm.div(v_p) - param.beta_p * j_p
        self.boundary_conditions[pressure_n] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (pybamm.Scalar(0), "Dirichlet"),
        }
        self.boundary_conditions[pressure_p] = {
            "left": (pybamm.Scalar(0), "Dirichlet"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.initial_conditions[pressure_n] = pybamm.Scalar(0)
        self.initial_conditions[pressure_p] = pybamm.Scalar(0)

        ######################
        # Current in the electrolyte
        ######################
        i_e = (param.kappa_e(c_e, T) * tor * param.gamma_e / param.C_e) * (
            param.chi(c_e) * pybamm.grad(c_e) / c_e - pybamm.grad(phi_e))
        self.algebraic[phi_e] = pybamm.div(i_e) - j
        self.boundary_conditions[phi_e] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.initial_conditions[phi_e] = -param.U_n(param.c_e_init,
                                                    param.T_init)

        ######################
        # Current in the solid
        ######################
        i_s_n = -param.sigma_n * (1 -
                                  eps_n)**param.b_s_n * pybamm.grad(phi_s_n)
        sigma_eff_p = param.sigma_p * (1 - eps_p)**param.b_s_p
        i_s_p = -sigma_eff_p * pybamm.grad(phi_s_p)
        # The `algebraic` dictionary contains differential equations, with the key being
        # the main scalar variable of interest in the equation
        self.algebraic[phi_s_n] = pybamm.div(i_s_n) + j_n
        self.algebraic[phi_s_p] = pybamm.div(i_s_p) + j_p
        self.boundary_conditions[phi_s_n] = {
            "left": (pybamm.Scalar(0), "Dirichlet"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.boundary_conditions[phi_s_p] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right":
            (i_cell / pybamm.boundary_value(-sigma_eff_p, "right"), "Neumann"),
        }
        # Initial conditions must also be provided for algebraic equations, as an
        # initial guess for a root-finding algorithm which calculates consistent initial
        # conditions
        self.initial_conditions[phi_s_n] = pybamm.Scalar(0)
        self.initial_conditions[phi_s_p] = param.U_p(
            param.c_e_init, param.T_init) - param.U_n(param.c_e_init,
                                                      param.T_init)

        ######################
        # Porosity
        ######################
        beta_surf = pybamm.Concatenation(
            pybamm.PrimaryBroadcast(param.beta_surf_n, "negative electrode"),
            pybamm.PrimaryBroadcast(0, "separator"),
            pybamm.PrimaryBroadcast(param.beta_surf_p, "positive electrode"),
        )
        deps_dt = -beta_surf * j
        self.rhs[eps] = deps_dt
        self.initial_conditions[eps] = param.epsilon_init
        self.events.extend([
            pybamm.Event("Zero negative electrode porosity cut-off",
                         pybamm.min(eps_n)),
            pybamm.Event("Max negative electrode porosity cut-off",
                         pybamm.max(eps_n) - 1),
            pybamm.Event("Zero positive electrode porosity cut-off",
                         pybamm.min(eps_p)),
            pybamm.Event("Max positive electrode porosity cut-off",
                         pybamm.max(eps_p) - 1),
        ])

        ######################
        # Electrolyte concentration
        ######################
        N_e = (-tor * param.D_e(c_e, T) * pybamm.grad(c_e) +
               param.C_e * param.t_plus(c_e) * i_e / param.gamma_e +
               param.C_e * c_e * v)
        s = pybamm.Concatenation(
            pybamm.PrimaryBroadcast(param.s_plus_n_S, "negative electrode"),
            pybamm.PrimaryBroadcast(0, "separator"),
            pybamm.PrimaryBroadcast(param.s_plus_p_S, "positive electrode"),
        )
        self.rhs[c_e] = (1 / eps) * (-pybamm.div(N_e) / param.C_e +
                                     s * j / param.gamma_e - c_e * deps_dt -
                                     c_e * div_V)
        self.boundary_conditions[c_e] = {
            "left": (pybamm.Scalar(0), "Neumann"),
            "right": (pybamm.Scalar(0), "Neumann"),
        }
        self.initial_conditions[c_e] = param.c_e_init
        self.events.append(
            pybamm.Event("Zero electrolyte concentration cut-off",
                         pybamm.min(c_e) - 0.002))

        ######################
        # (Some) variables
        ######################
        voltage = pybamm.boundary_value(phi_s_p, "right")
        # The `variables` dictionary contains all variables that might be useful for
        # visualising the solution of the model
        pot = param.potential_scale

        self.variables = {
            "Electrolyte concentration":
            c_e,
            "Current [A]":
            I,
            "Negative electrode potential [V]":
            pot * phi_s_n,
            "Electrolyte potential [V]":
            -param.U_n_ref + pot * phi_e,
            "Positive electrode potential [V]":
            param.U_p_ref - param.U_n_ref + pot * phi_s_p,
            "Terminal voltage [V]":
            param.U_p_ref - param.U_n_ref + pot * voltage,
            "x [m]":
            pybamm.standard_spatial_vars.x * param.L_x,
            "x":
            pybamm.standard_spatial_vars.x,
            "Porosity":
            eps,
            "Volume-averaged velocity":
            v,
            "X-averaged separator transverse volume-averaged velocity":
            div_V_s,
        }
        self.events.extend([
            pybamm.Event("Minimum voltage", voltage - param.voltage_low_cut),
            pybamm.Event("Maximum voltage", voltage - param.voltage_high_cut),
        ])
コード例 #26
0
    def set_voltage_variables(self):

        ocp_n = self.variables["Negative electrode open circuit potential"]
        ocp_p = self.variables["Positive electrode open circuit potential"]
        ocp_n_av = self.variables[
            "X-averaged negative electrode open circuit potential"]
        ocp_p_av = self.variables[
            "X-averaged positive electrode open circuit potential"]

        ocp_n_dim = self.variables[
            "Negative electrode open circuit potential [V]"]
        ocp_p_dim = self.variables[
            "Positive electrode open circuit potential [V]"]
        ocp_n_av_dim = self.variables[
            "X-averaged negative electrode open circuit potential [V]"]
        ocp_p_av_dim = self.variables[
            "X-averaged positive electrode open circuit potential [V]"]

        ocp_n_left = pybamm.boundary_value(ocp_n, "left")
        ocp_n_left_dim = pybamm.boundary_value(ocp_n_dim, "left")
        ocp_p_right = pybamm.boundary_value(ocp_p, "right")
        ocp_p_right_dim = pybamm.boundary_value(ocp_p_dim, "right")

        ocv_av = ocp_p_av - ocp_n_av
        ocv_av_dim = ocp_p_av_dim - ocp_n_av_dim
        ocv = ocp_p_right - ocp_n_left
        ocv_dim = ocp_p_right_dim - ocp_n_left_dim

        # overpotentials
        eta_r_n_av = self.variables[
            "X-averaged negative electrode reaction overpotential"]
        eta_r_n_av_dim = self.variables[
            "X-averaged negative electrode reaction overpotential [V]"]
        eta_r_p_av = self.variables[
            "X-averaged positive electrode reaction overpotential"]
        eta_r_p_av_dim = self.variables[
            "X-averaged positive electrode reaction overpotential [V]"]

        delta_phi_s_n_av = self.variables[
            "X-averaged negative electrode ohmic losses"]
        delta_phi_s_n_av_dim = self.variables[
            "X-averaged negative electrode ohmic losses [V]"]
        delta_phi_s_p_av = self.variables[
            "X-averaged positive electrode ohmic losses"]
        delta_phi_s_p_av_dim = self.variables[
            "X-averaged positive electrode ohmic losses [V]"]

        delta_phi_s_av = delta_phi_s_p_av - delta_phi_s_n_av
        delta_phi_s_av_dim = delta_phi_s_p_av_dim - delta_phi_s_n_av_dim

        eta_r_av = eta_r_p_av - eta_r_n_av
        eta_r_av_dim = eta_r_p_av_dim - eta_r_n_av_dim

        # SEI film overpotential
        eta_sei_n_av = self.variables[
            "X-averaged negative electrode SEI film overpotential"]
        eta_sei_p_av = self.variables[
            "X-averaged positive electrode SEI film overpotential"]
        eta_sei_n_av_dim = self.variables[
            "X-averaged negative electrode SEI film overpotential [V]"]
        eta_sei_p_av_dim = self.variables[
            "X-averaged positive electrode SEI film overpotential [V]"]
        eta_sei_av = eta_sei_n_av + eta_sei_p_av
        eta_sei_av_dim = eta_sei_n_av_dim + eta_sei_p_av_dim

        # TODO: add current collector losses to the voltage in 3D

        self.variables.update({
            "X-averaged open circuit voltage":
            ocv_av,
            "Measured open circuit voltage":
            ocv,
            "X-averaged open circuit voltage [V]":
            ocv_av_dim,
            "Measured open circuit voltage [V]":
            ocv_dim,
            "X-averaged reaction overpotential":
            eta_r_av,
            "X-averaged reaction overpotential [V]":
            eta_r_av_dim,
            "X-averaged SEI film overpotential":
            eta_sei_av,
            "X-averaged SEI film overpotential [V]":
            eta_sei_av_dim,
            "X-averaged solid phase ohmic losses":
            delta_phi_s_av,
            "X-averaged solid phase ohmic losses [V]":
            delta_phi_s_av_dim,
        })

        # Battery-wide variables
        V = self.variables["Terminal voltage"]
        V_dim = self.variables["Terminal voltage [V]"]
        eta_e_av_dim = self.variables[
            "X-averaged electrolyte ohmic losses [V]"]
        eta_c_av_dim = self.variables[
            "X-averaged concentration overpotential [V]"]
        num_cells = pybamm.Parameter(
            "Number of cells connected in series to make a battery")
        self.variables.update({
            "X-averaged battery open circuit voltage [V]":
            ocv_av_dim * num_cells,
            "Measured battery open circuit voltage [V]":
            ocv_dim * num_cells,
            "X-averaged battery reaction overpotential [V]":
            eta_r_av_dim * num_cells,
            "X-averaged battery solid phase ohmic losses [V]":
            delta_phi_s_av_dim * num_cells,
            "X-averaged battery electrolyte ohmic losses [V]":
            eta_e_av_dim * num_cells,
            "X-averaged battery concentration overpotential [V]":
            eta_c_av_dim * num_cells,
            "Battery voltage [V]":
            V_dim * num_cells,
        })
        # Variables for calculating the equivalent circuit model (ECM) resistance
        # Need to compare OCV to initial value to capture this as an overpotential
        ocv_init = self.param.U_p(
            self.param.c_p_init(1), self.param.T_init) - self.param.U_n(
                self.param.c_n_init(0), self.param.T_init)
        ocv_init_dim = (self.param.U_p_ref - self.param.U_n_ref +
                        self.param.potential_scale * ocv_init)
        eta_ocv = ocv - ocv_init
        eta_ocv_dim = ocv_dim - ocv_init_dim
        # Current collector current density for working out euiqvalent resistance
        # based on Ohm's Law
        i_cc = self.variables["Current collector current density"]
        i_cc_dim = self.variables["Current collector current density [A.m-2]"]
        # ECM overvoltage is OCV minus terminal voltage
        v_ecm = ocv - V
        v_ecm_dim = ocv_dim - V_dim
        # Current collector area for turning resistivity into resistance
        A_cc = self.param.A_cc

        # Hack to avoid division by zero if i_cc is exactly zero
        # If i_cc is zero, i_cc_not_zero becomes 1. But multiplying by sign(i_cc) makes
        # the local resistance 'zero' (really, it's not defined when i_cc is zero)
        i_cc_not_zero = ((i_cc > 0) +
                         (i_cc < 0)) * i_cc + (i_cc >= 0) * (i_cc <= 0)
        i_cc_dim_not_zero = (
            (i_cc_dim > 0) +
            (i_cc_dim < 0)) * i_cc_dim + (i_cc_dim >= 0) * (i_cc_dim <= 0)

        self.variables.update({
            "Change in measured open circuit voltage":
            eta_ocv,
            "Change in measured open circuit voltage [V]":
            eta_ocv_dim,
            "Local ECM resistance":
            pybamm.sign(i_cc) * v_ecm / (i_cc_not_zero * A_cc),
            "Local ECM resistance [Ohm]":
            pybamm.sign(i_cc) * v_ecm_dim / (i_cc_dim_not_zero * A_cc),
        })

        # Cut-off voltage
        self.events.append(
            pybamm.Event(
                "Minimum voltage",
                V - self.param.voltage_low_cut,
                pybamm.EventType.TERMINATION,
            ))
        self.events.append(
            pybamm.Event(
                "Maximum voltage",
                V - self.param.voltage_high_cut,
                pybamm.EventType.TERMINATION,
            ))

        # Cut-off open-circuit voltage (for event switch with casadi 'fast with events'
        # mode)
        # A tolerance of 1 is sufficiently small since the dimensionless voltage is
        # scaled with the thermal voltage (0.025V) and hence has a range of around 60
        tol = 1
        self.events.append(
            pybamm.Event(
                "Minimum voltage switch",
                V - (self.param.voltage_low_cut - tol),
                pybamm.EventType.SWITCH,
            ))
        self.events.append(
            pybamm.Event(
                "Maximum voltage switch",
                V - (self.param.voltage_high_cut + tol),
                pybamm.EventType.SWITCH,
            ))

        # Power
        I_dim = self.variables["Current [A]"]
        self.variables.update({"Terminal power [W]": I_dim * V_dim})