def set_algebraic(self, variables):

        param = self.param
        applied_current = variables["Total current density"]
        cc_area = self._get_effective_current_collector_area()
        z = pybamm.standard_spatial_vars.z

        phi_s_cn = variables["Negative current collector potential"]
        phi_s_cp = variables["Positive current collector potential"]
        i_boundary_cc = variables["Current collector current density"]
        i_boundary_cc_0 = variables[
            "Leading-order current collector current density"]
        c = variables["Lagrange multiplier"]

        # Note that the second argument of 'source' must be the same as the argument
        # in the laplacian (the variable to which the boundary conditions are applied)
        self.algebraic = {
            phi_s_cn: (param.sigma_cn * param.delta**2 * param.l_cn) *
            pybamm.laplacian(phi_s_cn) -
            pybamm.source(i_boundary_cc_0, phi_s_cn),
            i_boundary_cc: (param.sigma_cp * param.delta**2 * param.l_cp) *
            pybamm.laplacian(phi_s_cp) +
            pybamm.source(i_boundary_cc_0, phi_s_cp) +
            c * pybamm.PrimaryBroadcast(cc_area, "current collector"),
            c:
            pybamm.Integral(i_boundary_cc, z) - applied_current / cc_area +
            0 * c,
        }
    def set_rhs(self, variables):
        T_av = variables["X-averaged cell temperature"]
        Q_av = variables["X-averaged total heating"]
        T_amb = variables["Ambient temperature"]

        # Account for surface area to volume ratio of pouch cell in cooling
        # coefficient. Note: the factor 1/delta^2 comes from the choice of
        # non-dimensionalisation
        yz_surface_area = self.param.l_y * self.param.l_z
        cell_volume = self.param.l * self.param.l_y * self.param.l_z
        yz_surface_cooling_coefficient = (
            -(self.param.h_cn + self.param.h_cp) * yz_surface_area /
            cell_volume / (self.param.delta**2))

        edge_cooling_coefficient = self.param.h_edge / self.param.delta

        # Governing equations contain:
        #   - source term for y-z surface cooling
        #   - boundary source term of edge cooling
        # Boundary conditions contain:
        #   - Neumann condition for tab cooling
        self.rhs = {
            T_av:
            (pybamm.laplacian(T_av) + self.param.B * pybamm.source(Q_av, T_av)
             + yz_surface_cooling_coefficient *
             pybamm.source(T_av - T_amb, T_av) - edge_cooling_coefficient *
             pybamm.source(T_av - T_amb, T_av, boundary=True)) /
            (self.param.C_th * self.param.rho)
        }
Esempio n. 3
0
 def set_algebraic(self, variables):
     R_cn_scaled = variables["Scaled negative current collector resistance"]
     R_cp_scaled = variables["Scaled positive current collector resistance"]
     self.algebraic = {
         R_cn_scaled: pybamm.laplacian(R_cn_scaled) - pybamm.source(1, R_cn_scaled),
         R_cp_scaled: pybamm.laplacian(R_cp_scaled) - pybamm.source(1, R_cp_scaled),
     }
Esempio n. 4
0
    def test_source(self):
        u = pybamm.Variable("u", domain="current collector")
        v = pybamm.Variable("v", domain="current collector")

        source = pybamm.source(u, v)
        self.assertIsInstance(source.children[0], pybamm.Mass)
        boundary_source = pybamm.source(u, v, boundary=True)
        self.assertIsInstance(boundary_source.children[0], pybamm.BoundaryMass)
Esempio n. 5
0
    def set_rhs(self, variables):
        T_av = variables["X-averaged cell temperature"]
        Q_av = variables["X-averaged total heating"]

        # Add boundary source term which accounts for surface cooling around
        # the edge of the domain in the weak formulation.
        # TODO: update to allow different cooling conditions at the tabs
        self.rhs = {
            T_av:
            (pybamm.laplacian(T_av) +
             self.param.B * pybamm.source(Q_av, T_av) -
             (2 * self.param.h / (self.param.delta**2) / self.param.l) *
             pybamm.source(T_av, T_av) - (self.param.h / self.param.delta) *
             pybamm.source(T_av, T_av, boundary=True)) / self.param.C_th
        }
Esempio n. 6
0
    def set_algebraic(self, variables):

        param = self.param

        phi_s_cn = variables["Negative current collector potential"]
        phi_s_cp = variables["Positive current collector potential"]
        i_boundary_cc = variables["Current collector current density"]

        self.algebraic = {
            phi_s_cn: (param.sigma_cn * param.delta ** 2 * param.l_cn)
            * pybamm.laplacian(phi_s_cn)
            - pybamm.source(i_boundary_cc, phi_s_cn),
            i_boundary_cc: (param.sigma_cp * param.delta ** 2 * param.l_cp)
            * pybamm.laplacian(phi_s_cp)
            + pybamm.source(i_boundary_cc, phi_s_cp),
        }
    def test_pure_neumann_poisson(self):
        # grad^2 u = 1, du/dz = 1 at z = 1, du/dn = 0 elsewhere, u has zero average
        u = pybamm.Variable("u", domain="current collector")
        c = pybamm.Variable("c")  # lagrange multiplier
        y = pybamm.SpatialVariable("y", ["current collector"])
        z = pybamm.SpatialVariable("z", ["current collector"])

        model = pybamm.BaseModel()
        # 0*c hack otherwise gives KeyError
        model.algebraic = {
            u:
            pybamm.laplacian(u) - pybamm.source(1, u) +
            c * pybamm.DefiniteIntegralVector(u, vector_type="column"),
            c:
            pybamm.Integral(u, [y, z]) + 0 * c,
        }
        model.initial_conditions = {u: pybamm.Scalar(0), c: pybamm.Scalar(0)}
        # set boundary conditions ("negative tab" = bottom of unit square,
        # "positive tab" = top of unit square, elsewhere normal derivative is zero)
        model.boundary_conditions = {
            u: {
                "negative tab": (0, "Neumann"),
                "positive tab": (1, "Neumann")
            }
        }
        model.variables = {"c": c, "u": u}
        # create discretisation
        mesh = get_unit_2p1D_mesh_for_testing(ypts=32,
                                              zpts=32,
                                              include_particles=False)
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(),
            "current collector": pybamm.ScikitFiniteElement(),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # solve model
        solver = pybamm.AlgebraicSolver()
        solution = solver.solve(model)

        z = mesh["current collector"].coordinates[1, :][:, np.newaxis]
        u_exact = z**2 / 2 - 1 / 6
        np.testing.assert_array_almost_equal(solution.y[:-1],
                                             u_exact,
                                             decimal=1)
    def test_dirichlet_bcs(self):
        # manufactured solution u = a*z^2 + b*z + c
        model = pybamm.BaseModel()
        a = 3
        b = 4
        c = 5
        u = pybamm.Variable("variable", domain="current collector")
        model.algebraic = {u: -pybamm.laplacian(u) + pybamm.source(2 * a, u)}
        # set boundary conditions ("negative tab" = bottom of unit square,
        # "positive tab" = top of unit square, elsewhere normal derivative is zero)
        model.boundary_conditions = {
            u: {
                "negative tab": (pybamm.Scalar(c), "Dirichlet"),
                "positive tab": (pybamm.Scalar(a + b + c), "Dirichlet"),
            }
        }
        # bad initial guess (on purpose)
        model.initial_conditions = {u: pybamm.Scalar(1)}
        model.variables = {"u": u}
        # create discretisation
        mesh = get_unit_2p1D_mesh_for_testing(ypts=8,
                                              zpts=32,
                                              include_particles=False)
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(),
            "current collector": pybamm.ScikitFiniteElement(),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)
        disc.process_model(model)

        # solve model
        solver = pybamm.AlgebraicSolver()
        solution = solver.solve(model)

        # indepedent of y, so just check values for one y
        z = mesh["current collector"].edges["z"][:, np.newaxis]
        u_exact = a * z**2 + b * z + c
        np.testing.assert_array_almost_equal(solution.y[0:len(z)], u_exact)
Esempio n. 9
0
 def test_source_error(self):
     # test error with domain not current collector
     v = pybamm.Vector(np.ones(5), domain="current collector")
     w = pybamm.Vector(2 * np.ones(3), domain="test")
     with self.assertRaisesRegex(pybamm.DomainError, "'source'"):
         pybamm.source(v, w)
    def __init__(self):
        super().__init__()
        self.name = "Effective resistance in current collector model"
        self.param = pybamm.standard_parameters_lithium_ion

        # Get useful parameters
        param = self.param
        l_cn = param.l_cn
        l_cp = param.l_cp
        l_y = param.l_y
        sigma_cn_dbl_prime = param.sigma_cn_dbl_prime
        sigma_cp_dbl_prime = param.sigma_cp_dbl_prime
        alpha_prime = param.alpha_prime

        # Set model variables
        var = pybamm.standard_spatial_vars

        psi = pybamm.Variable("Current collector potential weighted sum",
                              ["current collector"])
        W = pybamm.Variable(
            "Perturbation to current collector potential difference",
            ["current collector"],
        )
        c_psi = pybamm.Variable("Lagrange multiplier for variable `psi`")
        c_W = pybamm.Variable("Lagrange multiplier for variable `W`")

        self.variables = {
            "Current collector potential weighted sum": psi,
            "Perturbation to current collector potential difference": W,
            "Lagrange multiplier for variable `psi`": c_psi,
            "Lagrange multiplier for variable `W`": c_W,
        }

        # Algebraic equations (enforce zero mean constraint through Lagrange multiplier)
        # 0*LagrangeMultiplier hack otherwise gives KeyError
        self.algebraic = {
            psi:
            pybamm.laplacian(psi) +
            c_psi * pybamm.DefiniteIntegralVector(psi, vector_type="column"),
            W:
            pybamm.laplacian(W) - pybamm.source(1, W) +
            c_W * pybamm.DefiniteIntegralVector(W, vector_type="column"),
            c_psi:
            pybamm.Integral(psi, [var.y, var.z]) + 0 * c_psi,
            c_W:
            pybamm.Integral(W, [var.y, var.z]) + 0 * c_W,
        }

        # Boundary conditons
        psi_neg_tab_bc = l_cn
        psi_pos_tab_bc = -l_cp
        W_neg_tab_bc = l_y / (alpha_prime * sigma_cn_dbl_prime)
        W_pos_tab_bc = l_y / (alpha_prime * sigma_cp_dbl_prime)

        self.boundary_conditions = {
            psi: {
                "negative tab": (psi_neg_tab_bc, "Neumann"),
                "positive tab": (psi_pos_tab_bc, "Neumann"),
            },
            W: {
                "negative tab": (W_neg_tab_bc, "Neumann"),
                "positive tab": (W_pos_tab_bc, "Neumann"),
            },
        }

        # "Initial conditions" provides initial guess for solver
        # TODO: better guess than zero?
        self.initial_conditions = {
            psi: pybamm.Scalar(0),
            W: pybamm.Scalar(0),
            c_psi: pybamm.Scalar(0),
            c_W: pybamm.Scalar(0),
        }

        # Define effective current collector resistance
        psi_neg_tab = pybamm.BoundaryValue(psi, "negative tab")
        psi_pos_tab = pybamm.BoundaryValue(psi, "positive tab")
        W_neg_tab = pybamm.BoundaryValue(W, "negative tab")
        W_pos_tab = pybamm.BoundaryValue(W, "positive tab")

        R_cc = ((alpha_prime / l_y) * (sigma_cn_dbl_prime * l_cn * W_pos_tab +
                                       sigma_cp_dbl_prime * l_cp * W_neg_tab) -
                (psi_pos_tab - psi_neg_tab)) / (sigma_cn_dbl_prime * l_cn +
                                                sigma_cp_dbl_prime * l_cp)

        R_cc_dim = R_cc * param.potential_scale / param.I_typ

        self.variables.update({
            "Current collector potential weighted sum (negative tab)":
            psi_neg_tab,
            "Current collector potential weighted sum (positive tab)":
            psi_pos_tab,
            "Perturbation to c.c. potential difference (negative tab)":
            W_neg_tab,
            "Perturbation to c.c. potential difference (positive tab)":
            W_pos_tab,
            "Effective current collector resistance":
            R_cc,
            "Effective current collector resistance [Ohm]":
            R_cc_dim,
        })
Esempio n. 11
0
    def __init__(self):
        super().__init__()
        self.name = "Effective resistance in current collector model (2D)"
        self.param = pybamm.standard_parameters_lithium_ion

        # Get necessary parameters
        param = self.param
        l_cn = param.l_cn
        l_cp = param.l_cp
        l_tab_p = param.l_tab_p
        A_tab_p = l_cp * l_tab_p
        sigma_cn_dbl_prime = param.sigma_cn_dbl_prime
        sigma_cp_dbl_prime = param.sigma_cp_dbl_prime
        delta = param.delta

        # Set model variables -- we solve a auxilliary problem in each current collector
        # then relate this to the potentials and resistances later
        f_n = pybamm.Variable("Unit solution in negative current collector",
                              domain="current collector")
        f_p = pybamm.Variable("Unit solution in positive current collector",
                              domain="current collector")

        # Governing equations -- we impose that the average of f_p is zero
        # by introducing a Lagrange multiplier
        c = pybamm.Variable("Lagrange multiplier")

        self.algebraic = {
            f_n:
            pybamm.laplacian(f_n) + pybamm.source(1, f_n),
            c:
            pybamm.laplacian(f_p) - pybamm.source(1, f_p) +
            c * pybamm.DefiniteIntegralVector(f_p, vector_type="column"),
            f_p:
            pybamm.yz_average(f_p) + 0 * c,
        }

        # Boundary conditons
        pos_tab_bc = l_cp / A_tab_p
        self.boundary_conditions = {
            f_n: {
                "negative tab": (0, "Dirichlet"),
                "positive tab": (0, "Neumann")
            },
            f_p: {
                "negative tab": (0, "Neumann"),
                "positive tab": (pos_tab_bc, "Neumann"),
            },
        }

        # "Initial conditions" provides initial guess for solver
        self.initial_conditions = {
            f_n: pybamm.Scalar(0),
            f_p: pybamm.Scalar(0),
            c: pybamm.Scalar(0),
        }

        # Define effective current collector resistance
        R_cc_n = delta * pybamm.yz_average(f_n) / (l_cn * sigma_cn_dbl_prime)
        R_cc_p = (delta * pybamm.BoundaryIntegral(f_p, "positive tab") /
                  (l_cp * sigma_cp_dbl_prime))
        R_cc = R_cc_n + R_cc_p
        R_scale = param.potential_scale / param.I_typ

        self.variables = {
            "Unit solution in negative current collector":
            f_n,
            "Unit solution in positive current collector":
            f_p,
            "Effective current collector resistance":
            R_cc,
            "Effective current collector resistance [Ohm]":
            R_cc * R_scale,
            "Effective negative current collector resistance":
            R_cc_n,
            "Effective negative current collector resistance [Ohm]":
            R_cc_n * R_scale,
            "Effective positive current collector resistance":
            R_cc_p,
            "Effective positive current collector resistance [Ohm]":
            R_cc_p * R_scale,
        }

        # Add spatial variables
        var = pybamm.standard_spatial_vars
        L_y = pybamm.geometric_parameters.L_y
        L_z = pybamm.geometric_parameters.L_z
        self.variables.update({
            "y": var.y,
            "y [m]": var.y * L_y,
            "z": var.z,
            "z [m]": var.z * L_z
        })

        pybamm.citations.register("timms2020")
Esempio n. 12
0
    def test_discretise_equations(self):
        # get mesh
        mesh = get_2p1d_mesh_for_testing(include_particles=False)
        spatial_methods = {
            "macroscale": pybamm.FiniteVolume(),
            "current collector": pybamm.ScikitFiniteElement(),
        }
        disc = pybamm.Discretisation(mesh, spatial_methods)
        # discretise some equations
        var = pybamm.Variable("var", domain="current collector")
        y = pybamm.SpatialVariable("y", ["current collector"])
        z = pybamm.SpatialVariable("z", ["current collector"])
        disc.set_variable_slices([var])
        y_test = np.ones(mesh["current collector"].npts)
        unit_source = pybamm.PrimaryBroadcast(1, "current collector")
        disc.bcs = {
            var.id: {
                "negative tab": (pybamm.Scalar(0), "Neumann"),
                "positive tab": (pybamm.Scalar(0), "Neumann"),
            }
        }

        for eqn in [
            pybamm.laplacian(var),
            pybamm.source(unit_source, var),
            pybamm.laplacian(var) - pybamm.source(unit_source, var),
            pybamm.source(var, var),
            pybamm.laplacian(var) - pybamm.source(2 * var, var),
            pybamm.laplacian(var) - pybamm.source(unit_source ** 2 + 1 / var, var),
            pybamm.Integral(var, [y, z]) - 1,
            pybamm.source(var, var, boundary=True),
            pybamm.laplacian(var) - pybamm.source(unit_source, var, boundary=True),
            pybamm.laplacian(var)
            - pybamm.source(unit_source ** 2 + 1 / var, var, boundary=True),
        ]:
            # Check that equation can be evaluated in each case
            # Dirichlet
            disc.bcs = {
                var.id: {
                    "negative tab": (pybamm.Scalar(0), "Dirichlet"),
                    "positive tab": (pybamm.Scalar(1), "Dirichlet"),
                }
            }
            eqn_disc = disc.process_symbol(eqn)
            eqn_disc.evaluate(None, y_test)
            # Neumann
            disc.bcs = {
                var.id: {
                    "negative tab": (pybamm.Scalar(0), "Neumann"),
                    "positive tab": (pybamm.Scalar(1), "Neumann"),
                }
            }
            eqn_disc = disc.process_symbol(eqn)
            eqn_disc.evaluate(None, y_test)
            # One of each
            disc.bcs = {
                var.id: {
                    "negative tab": (pybamm.Scalar(0), "Neumann"),
                    "positive tab": (pybamm.Scalar(1), "Dirichlet"),
                }
            }
            eqn_disc = disc.process_symbol(eqn)
            eqn_disc.evaluate(None, y_test)
            # One of each
            disc.bcs = {
                var.id: {
                    "negative tab": (pybamm.Scalar(0), "Dirichlet"),
                    "positive tab": (pybamm.Scalar(1), "Neumann"),
                }
            }
            eqn_disc = disc.process_symbol(eqn)
            eqn_disc.evaluate(None, y_test)

        # check  ValueError raised for non Dirichlet or Neumann BCs
        eqn = pybamm.laplacian(var) - pybamm.source(unit_source, var)
        disc.bcs = {
            var.id: {
                "negative tab": (pybamm.Scalar(0), "Dirichlet"),
                "positive tab": (pybamm.Scalar(1), "Other BC"),
            }
        }
        with self.assertRaises(ValueError):
            eqn_disc = disc.process_symbol(eqn)
        disc.bcs = {
            var.id: {
                "negative tab": (pybamm.Scalar(0), "Other BC"),
                "positive tab": (pybamm.Scalar(1), "Neumann"),
            }
        }
        with self.assertRaises(ValueError):
            eqn_disc = disc.process_symbol(eqn)

        # raise ModelError if no BCs provided
        new_var = pybamm.Variable("new_var", domain="current collector")
        disc.set_variable_slices([new_var])
        eqn = pybamm.laplacian(new_var)
        with self.assertRaises(pybamm.ModelError):
            eqn_disc = disc.process_symbol(eqn)

        # check GeometryError if using scikit-fem not in y or z
        x = pybamm.SpatialVariable("x", ["current collector"])
        with self.assertRaises(pybamm.GeometryError):
            disc.process_symbol(x)
    def set_rhs(self, variables):
        # Note: we have to use `pybamm.source(rhs, var)` in the rhs dict so that
        # the scalar source term gets multplied by the correct mass matrix when
        # using this model with 2D current collectors with the finite element
        # method (see #1399)

        c_s_rxav = variables[
            "Average " + self.domain.lower() + " particle concentration"
        ]
        j_xav = variables[
            "X-averaged "
            + self.domain.lower()
            + " electrode interfacial current density"
        ]

        if self.domain == "Negative":
            self.rhs = {
                c_s_rxav: pybamm.source(-3 * j_xav / self.param.a_R_n, c_s_rxav)
            }

        elif self.domain == "Positive":
            self.rhs = {
                c_s_rxav: pybamm.source(
                    -3 * j_xav / self.param.a_R_p / self.param.gamma_p, c_s_rxav
                )
            }

        if self.name == "quartic profile":
            # We solve an extra ODE for the average particle concentration gradient
            q_s_rxav = variables[
                "Average " + self.domain.lower() + " particle concentration gradient"
            ]
            c_s_surf_xav = variables[
                "X-averaged " + self.domain.lower() + " particle surface concentration"
            ]
            T_xav = variables[
                "X-averaged " + self.domain.lower() + " electrode temperature"
            ]
            if self.domain == "Negative":
                self.rhs.update(
                    {
                        q_s_rxav: pybamm.source(
                            -30
                            * self.param.D_n(c_s_surf_xav, T_xav)
                            * q_s_rxav
                            / self.param.C_n
                            - 45 * j_xav / self.param.a_R_n / 2,
                            q_s_rxav,
                        )
                    }
                )
            elif self.domain == "Positive":
                self.rhs.update(
                    {
                        q_s_rxav: pybamm.source(
                            -30
                            * self.param.D_p(c_s_surf_xav, T_xav)
                            * q_s_rxav
                            / self.param.C_p
                            - 45 * j_xav / self.param.a_R_p / self.param.gamma_p / 2,
                            q_s_rxav,
                        )
                    }
                )