Пример #1
0
 def test_not_constant(self):
     a = pybamm.NotConstant(pybamm.Scalar(1))
     self.assertEqual(a.name, "not_constant")
     self.assertEqual(a.domain, [])
     self.assertEqual(a.evaluate(), 1)
     self.assertEqual(a.jac(pybamm.StateVector(slice(0, 1))).evaluate(), 0)
     self.assertFalse(a.is_constant())
     self.assertFalse((2 * a).is_constant())
Пример #2
0
    def test_symbol_new_copy(self):
        a = pybamm.Parameter("a")
        b = pybamm.Parameter("b")
        v_n = pybamm.Variable("v", "negative electrode")
        x_n = pybamm.standard_spatial_vars.x_n
        v_s = pybamm.Variable("v", "separator")
        vec = pybamm.Vector([1, 2, 3, 4, 5])
        mat = pybamm.Matrix([[1, 2], [3, 4]])
        mesh = get_mesh_for_testing()

        for symbol in [
                a + b,
                a - b,
                a * b,
                a / b,
                a**b,
                -a,
                abs(a),
                pybamm.Function(np.sin, a),
                pybamm.FunctionParameter("function", {"a": a}),
                pybamm.grad(v_n),
                pybamm.div(pybamm.grad(v_n)),
                pybamm.upwind(v_n),
                pybamm.IndefiniteIntegral(v_n, x_n),
                pybamm.BackwardIndefiniteIntegral(v_n, x_n),
                pybamm.BoundaryValue(v_n, "right"),
                pybamm.BoundaryGradient(v_n, "right"),
                pybamm.PrimaryBroadcast(a, "domain"),
                pybamm.SecondaryBroadcast(v_n, "current collector"),
                pybamm.FullBroadcast(a, "domain",
                                     {"secondary": "other domain"}),
                pybamm.concatenation(v_n, v_s),
                pybamm.NumpyConcatenation(a, b, v_s),
                pybamm.DomainConcatenation([v_n, v_s], mesh),
                pybamm.Parameter("param"),
                pybamm.InputParameter("param"),
                pybamm.StateVector(slice(0, 56)),
                pybamm.Matrix(np.ones((50, 40))),
                pybamm.SpatialVariable("x", ["negative electrode"]),
                pybamm.t,
                pybamm.Index(vec, 1),
                pybamm.NotConstant(a),
                pybamm.ExternalVariable(
                    "external variable",
                    20,
                    domain="test",
                    auxiliary_domains={"secondary": "test2"},
                ),
                pybamm.minimum(a, b),
                pybamm.maximum(a, b),
                pybamm.SparseStack(mat, mat),
        ]:
            self.assertEqual(symbol.id, symbol.new_copy().id)
Пример #3
0
 def _get_interface_variables_for_first_order(self, variables):
     # This is a bit of a hack, but we need to wrap electrolyte concentration with
     # the NotConstant class
     # to differentiate it from the electrolyte concentration inside the
     # surface potential difference when taking j.diff(c_e) later on
     c_e_0 = pybamm.NotConstant(
         variables["Leading-order x-averaged electrolyte concentration"])
     hacked_variables = {
         **variables,
         self.domain + " electrolyte concentration":
         pybamm.PrimaryBroadcast(c_e_0, self.domain_for_broadcast),
     }
     delta_phi = variables["Leading-order x-averaged " +
                           self.domain.lower() +
                           " electrode surface potential difference"]
     j0 = self._get_exchange_current_density(hacked_variables)
     ne = self._get_number_of_electrons_in_reaction()
     ocp = self._get_open_circuit_potential(hacked_variables)[0]
     if j0.domain in ["current collector", ["current collector"]]:
         T = variables["X-averaged cell temperature"]
     else:
         T = variables[self.domain + " electrode temperature"]
     return c_e_0, delta_phi, j0, ne, ocp, T
Пример #4
0
    def test_process_not_constant(self):
        param = pybamm.ParameterValues({"a": 4})

        a = pybamm.NotConstant(pybamm.Parameter("a"))
        self.assertIsInstance(param.process_symbol(a), pybamm.NotConstant)
        self.assertEqual(param.process_symbol(a).evaluate(), 4)
Пример #5
0
    def _process_symbol(self, symbol):
        """ See :meth:`ParameterValues.process_symbol()`. """

        if isinstance(symbol, pybamm.Parameter):
            value = self[symbol.name]
            if isinstance(value, numbers.Number):
                # Scalar inherits name (for updating parameters) and domain (for
                # Broadcast)
                return pybamm.Scalar(value,
                                     name=symbol.name,
                                     domain=symbol.domain)
            elif isinstance(value, pybamm.Symbol):
                new_value = self.process_symbol(value)
                new_value.domain = symbol.domain
                return new_value
            else:
                raise TypeError("Cannot process parameter '{}'".format(value))

        elif isinstance(symbol, pybamm.FunctionParameter):
            new_children = []
            for child in symbol.children:
                if symbol.diff_variable is not None and any(
                        x.id == symbol.diff_variable.id
                        for x in child.pre_order()):
                    # Wrap with NotConstant to avoid simplification,
                    # which would stop symbolic diff from working properly
                    new_child = pybamm.NotConstant(child.new_copy())
                    new_children.append(self.process_symbol(new_child))
                else:
                    new_children.append(self.process_symbol(child))
            function_name = self[symbol.name]

            # Create Function or Interpolant or Scalar object
            if isinstance(function_name, tuple):
                # If function_name is a tuple then it should be (name, data) and we need
                # to create an Interpolant
                name, data = function_name
                function = pybamm.Interpolant(data[:, 0],
                                              data[:, 1],
                                              *new_children,
                                              name=name)
                # Define event to catch extrapolation. In these events the sign is
                # important: it should be positive inside of the range and negative
                # outside of it
                self.parameter_events.append(
                    pybamm.Event(
                        "Interpolant {} lower bound".format(name),
                        pybamm.min(new_children[0] - min(data[:, 0])),
                        pybamm.EventType.INTERPOLANT_EXTRAPOLATION,
                    ))
                self.parameter_events.append(
                    pybamm.Event(
                        "Interpolant {} upper bound".format(name),
                        pybamm.min(max(data[:, 0]) - new_children[0]),
                        pybamm.EventType.INTERPOLANT_EXTRAPOLATION,
                    ))
            elif isinstance(function_name, numbers.Number):
                # If the "function" is provided is actually a scalar, return a Scalar
                # object instead of throwing an error.
                # Also use ones_like so that we get the right shapes
                function = pybamm.Scalar(
                    function_name,
                    name=symbol.name) * pybamm.ones_like(*new_children)
            elif (isinstance(function_name, pybamm.Symbol)
                  and function_name.evaluates_to_number()):
                # If the "function" provided is a pybamm scalar-like, use ones_like to
                # get the right shape
                # This also catches input parameters
                function = function_name * pybamm.ones_like(*new_children)
            elif callable(function_name):
                # otherwise evaluate the function to create a new PyBaMM object
                function = function_name(*new_children)
            elif isinstance(function_name, pybamm.Interpolant):
                function = function_name
            else:
                raise TypeError(
                    "Parameter provided for '{}' ".format(symbol.name) +
                    "is of the wrong type (should either be scalar-like or callable)"
                )
            # Differentiate if necessary
            if symbol.diff_variable is None:
                function_out = function
            else:
                # return differentiated function
                new_diff_variable = self.process_symbol(symbol.diff_variable)
                function_out = function.diff(new_diff_variable)
            # Convert possible float output to a pybamm scalar
            if isinstance(function_out, numbers.Number):
                return pybamm.Scalar(function_out)
            # Process again just to be sure
            return self.process_symbol(function_out)

        elif isinstance(symbol, pybamm.BinaryOperator):
            # process children
            new_left = self.process_symbol(symbol.left)
            new_right = self.process_symbol(symbol.right)
            # Special case for averages, which can appear as "integral of a broadcast"
            # divided by "integral of a broadcast"
            # this construction seems very specific but can appear often when averaging
            if (isinstance(symbol, pybamm.Division)
                    # right is integral(Broadcast(1))
                    and (isinstance(new_right, pybamm.Integral)
                         and isinstance(new_right.child, pybamm.Broadcast)
                         and new_right.child.child.id == pybamm.Scalar(1).id)
                    # left is integral
                    and isinstance(new_left, pybamm.Integral)):
                # left is integral(Broadcast)
                if (isinstance(new_left.child, pybamm.Broadcast)
                        and new_left.child.child.domain == []):
                    integrand = new_left.child
                    if integrand.auxiliary_domains == {}:
                        return integrand.orphans[0]
                    else:
                        domain = integrand.auxiliary_domains["secondary"]
                        if "tertiary" not in integrand.auxiliary_domains:
                            return pybamm.PrimaryBroadcast(
                                integrand.orphans[0], domain)
                        else:
                            auxiliary_domains = {
                                "secondary":
                                integrand.auxiliary_domains["tertiary"]
                            }
                            return pybamm.FullBroadcast(
                                integrand.orphans[0], domain,
                                auxiliary_domains)
                # left is "integral of concatenation of broadcasts"
                elif isinstance(new_left.child, pybamm.Concatenation) and all(
                        isinstance(child, pybamm.Broadcast)
                        for child in new_left.child.children):
                    return self.process_symbol(pybamm.x_average(
                        new_left.child))
            # make new symbol, ensure domain remains the same
            new_symbol = symbol._binary_new_copy(new_left, new_right)
            new_symbol.domain = symbol.domain
            return new_symbol

        # Unary operators
        elif isinstance(symbol, pybamm.UnaryOperator):
            new_child = self.process_symbol(symbol.child)
            new_symbol = symbol._unary_new_copy(new_child)
            # ensure domain remains the same
            new_symbol.domain = symbol.domain
            return new_symbol

        # Functions
        elif isinstance(symbol, pybamm.Function):
            new_children = [
                self.process_symbol(child) for child in symbol.children
            ]
            return symbol._function_new_copy(new_children)

        # Concatenations
        elif isinstance(symbol, pybamm.Concatenation):
            new_children = [
                self.process_symbol(child) for child in symbol.children
            ]
            return symbol._concatenation_new_copy(new_children)

        else:
            # Backup option: return new copy of the object
            try:
                return symbol.new_copy()
            except NotImplementedError:
                raise NotImplementedError(
                    "Cannot process parameters for symbol of type '{}'".format(
                        type(symbol)))
    def __init__(self):
        super().__init__()
        self.name = "Effective resistance in current collector model (2D)"
        self.param = pybamm.LithiumIonParameters()

        # Set default length scales
        self.length_scales = {
            "current collector y": self.param.L_z,
            "current collector z": self.param.L_z,
        }

        # 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),
            f_p:
            pybamm.laplacian(f_p) - pybamm.source(1, f_p) +
            c * pybamm.DefiniteIntegralVector(f_p, vector_type="column"),
            c:
            pybamm.yz_average(f_p) + pybamm.NotConstant(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 = param.L_y
        L_z = param.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")