Ejemplo n.º 1
0
 def test_external_variable_diff(self):
     a = pybamm.ExternalVariable("a", 10)
     b = pybamm.Variable("b")
     self.assertIsInstance(a.diff(a), pybamm.Scalar)
     self.assertEqual(a.diff(a).evaluate(), 1)
     self.assertIsInstance(a.diff(b), pybamm.Scalar)
     self.assertEqual(a.diff(b).evaluate(), 0)
Ejemplo n.º 2
0
    def test_external_variable_scalar(self):
        a = pybamm.ExternalVariable("a", 1)
        self.assertEqual(a.size, 1)

        self.assertEqual(a.evaluate(u={"a": 3}), 3)

        with self.assertRaisesRegex(KeyError, "External variable"):
            a.evaluate()
        with self.assertRaisesRegex(TypeError, "inputs u"):
            a.evaluate(u="not a dictionary")
Ejemplo n.º 3
0
    def test_external_variable_vector(self):
        a = pybamm.ExternalVariable("a", 10)
        self.assertEqual(a.size, 10)

        a_test = 2 * np.ones((10, 1))
        np.testing.assert_array_equal(a.evaluate(inputs={"a": a_test}), a_test)

        np.testing.assert_array_equal(a.evaluate(inputs={"a": 2}), a_test)

        with self.assertRaisesRegex(ValueError, "External variable"):
            a.evaluate(inputs={"a": np.ones((5, 1))})
Ejemplo n.º 4
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)
Ejemplo n.º 5
0
    def test_convert_external_variable(self):
        casadi_t = casadi.MX.sym("t")
        casadi_y = casadi.MX.sym("y", 10)
        casadi_us = {
            "External 1": casadi.MX.sym("External 1", 3),
            "External 2": casadi.MX.sym("External 2", 10),
        }

        pybamm_y = pybamm.StateVector(slice(0, 10))
        pybamm_u1 = pybamm.ExternalVariable("External 1", 3)
        pybamm_u2 = pybamm.ExternalVariable("External 2", 10)

        # External only
        self.assert_casadi_equal(
            pybamm_u1.to_casadi(casadi_t, casadi_y, casadi_us),
            casadi_us["External 1"])

        # More complex
        expr = pybamm_u2 + pybamm_y
        self.assert_casadi_equal(
            expr.to_casadi(casadi_t, casadi_y, casadi_us),
            casadi_us["External 2"] + casadi_y,
        )
Ejemplo n.º 6
0
    def test_time_derivative_of_variable(self):

        a = (pybamm.Variable("a")).diff(pybamm.t)
        self.assertIsInstance(a, pybamm.VariableDot)
        self.assertEqual(a.name, "a'")

        p = pybamm.Parameter("p")
        a = (1 + p * pybamm.Variable("a")).diff(pybamm.t).simplify()
        self.assertIsInstance(a, pybamm.Multiplication)
        self.assertEqual(a.children[0].name, "p")
        self.assertEqual(a.children[1].name, "a'")

        with self.assertRaises(pybamm.ModelError):
            a = (pybamm.Variable("a")).diff(pybamm.t).diff(pybamm.t)

        with self.assertRaises(pybamm.ModelError):
            a = pybamm.ExternalVariable("a", 1).diff(pybamm.t)
Ejemplo n.º 7
0
    def _process_symbol(self, symbol):
        """ See :meth:`Discretisation.process_symbol()`. """

        if symbol.domain != []:
            spatial_method = self.spatial_methods[symbol.domain[0]]
            # If boundary conditions are provided, need to check for BCs on tabs
            if self.bcs:
                key_id = list(self.bcs.keys())[0]
                if any("tab" in side
                       for side in list(self.bcs[key_id].keys())):
                    self.bcs[key_id] = self.check_tab_conditions(
                        symbol, self.bcs[key_id])

        if isinstance(symbol, pybamm.BinaryOperator):
            # Pre-process children
            left, right = symbol.children
            disc_left = self.process_symbol(left)
            disc_right = self.process_symbol(right)
            if symbol.domain == []:
                return symbol._binary_new_copy(disc_left, disc_right)
            else:
                return spatial_method.process_binary_operators(
                    symbol, left, right, disc_left, disc_right)

        elif isinstance(symbol, pybamm.UnaryOperator):
            child = symbol.child
            disc_child = self.process_symbol(child)
            if child.domain != []:
                child_spatial_method = self.spatial_methods[child.domain[0]]

            if isinstance(symbol, pybamm.Gradient):
                return child_spatial_method.gradient(child, disc_child,
                                                     self.bcs)

            elif isinstance(symbol, pybamm.Divergence):
                return child_spatial_method.divergence(child, disc_child,
                                                       self.bcs)

            elif isinstance(symbol, pybamm.Laplacian):
                return child_spatial_method.laplacian(child, disc_child,
                                                      self.bcs)

            elif isinstance(symbol, pybamm.Gradient_Squared):
                return child_spatial_method.gradient_squared(
                    child, disc_child, self.bcs)

            elif isinstance(symbol, pybamm.Mass):
                return child_spatial_method.mass_matrix(child, self.bcs)

            elif isinstance(symbol, pybamm.BoundaryMass):
                return child_spatial_method.boundary_mass_matrix(
                    child, self.bcs)

            elif isinstance(symbol, pybamm.IndefiniteIntegral):
                return child_spatial_method.indefinite_integral(
                    child, disc_child)

            elif isinstance(symbol, pybamm.Integral):
                out = child_spatial_method.integral(child, disc_child)
                out.copy_domains(symbol)
                return out

            elif isinstance(symbol, pybamm.DefiniteIntegralVector):
                return child_spatial_method.definite_integral_matrix(
                    child.domain, vector_type=symbol.vector_type)

            elif isinstance(symbol, pybamm.BoundaryIntegral):
                return child_spatial_method.boundary_integral(
                    child, disc_child, symbol.region)

            elif isinstance(symbol, pybamm.Broadcast):
                # Broadcast new_child to the domain specified by symbol.domain
                # Different discretisations may broadcast differently
                if symbol.domain == []:
                    symbol = disc_child * pybamm.Vector(np.array([1]))
                else:
                    symbol = spatial_method.broadcast(
                        disc_child,
                        symbol.domain,
                        symbol.auxiliary_domains,
                        symbol.broadcast_type,
                    )
                return symbol

            elif isinstance(symbol, pybamm.DeltaFunction):
                return spatial_method.delta_function(symbol, disc_child)

            elif isinstance(symbol, pybamm.BoundaryOperator):
                # if boundary operator applied on "negative tab" or
                # "positive tab" *and* the mesh is 1D then change side to
                # "left" or "right" as appropriate
                if symbol.side in ["negative tab", "positive tab"]:
                    mesh = self.mesh[symbol.children[0].domain[0]][0]
                    if isinstance(mesh, pybamm.SubMesh1D):
                        symbol.side = mesh.tabs[symbol.side]
                return child_spatial_method.boundary_value_or_flux(
                    symbol, disc_child, self.bcs)

            else:
                return symbol._unary_new_copy(disc_child)

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

        elif isinstance(symbol, pybamm.Variable):
            # Check if variable is a standard variable or an external variable
            if any(symbol.id == var.id
                   for var in self.external_variables.values()):
                # Look up dictionary key based on value
                idx = [x.id for x in self.external_variables.values()
                       ].index(symbol.id)
                name, parent_and_slice = list(
                    self.external_variables.keys())[idx]
                if parent_and_slice is None:
                    # Variable didn't come from a concatenation so we can just create a
                    # normal external variable using the symbol's name
                    return pybamm.ExternalVariable(
                        symbol.name,
                        size=self._get_variable_size(symbol),
                        domain=symbol.domain,
                        auxiliary_domains=symbol.auxiliary_domains,
                    )
                else:
                    # We have to use a special name since the concatenation doesn't have
                    # a very informative name. Needs improving
                    parent, start, end = parent_and_slice
                    ext = pybamm.ExternalVariable(
                        name,
                        size=self._get_variable_size(parent),
                        domain=parent.domain,
                        auxiliary_domains=parent.auxiliary_domains,
                    )
                    out = ext[slice(start, end)]
                    out.domain = symbol.domain
                    return out

            else:
                return pybamm.StateVector(
                    *self.y_slices[symbol.id],
                    domain=symbol.domain,
                    auxiliary_domains=symbol.auxiliary_domains)

        elif isinstance(symbol, pybamm.SpatialVariable):
            return spatial_method.spatial_variable(symbol)

        elif isinstance(symbol, pybamm.Concatenation):
            new_children = [
                self.process_symbol(child) for child in symbol.children
            ]
            new_symbol = spatial_method.concatenation(new_children)

            return new_symbol

        else:
            # Backup option: return new copy of the object
            try:
                return symbol.new_copy()
            except NotImplementedError:
                raise NotImplementedError(
                    "Cannot discretise symbol of type '{}'".format(
                        type(symbol)))
Ejemplo n.º 8
0
    def _process_symbol(self, symbol):
        """ See :meth:`Discretisation.process_symbol()`. """

        if symbol.domain != []:
            spatial_method = self.spatial_methods[symbol.domain[0]]
            # If boundary conditions are provided, need to check for BCs on tabs
            if self.bcs:
                key_id = list(self.bcs.keys())[0]
                if any("tab" in side
                       for side in list(self.bcs[key_id].keys())):
                    self.bcs[key_id] = self.check_tab_conditions(
                        symbol, self.bcs[key_id])

        if isinstance(symbol, pybamm.BinaryOperator):
            # Pre-process children
            left, right = symbol.children
            disc_left = self.process_symbol(left)
            disc_right = self.process_symbol(right)
            if symbol.domain == []:
                return symbol._binary_new_copy(disc_left, disc_right)
            else:
                return spatial_method.process_binary_operators(
                    symbol, left, right, disc_left, disc_right)
        elif isinstance(symbol, pybamm.UnaryOperator):
            child = symbol.child
            disc_child = self.process_symbol(child)
            if child.domain != []:
                child_spatial_method = self.spatial_methods[child.domain[0]]

            if isinstance(symbol, pybamm.Gradient):
                return child_spatial_method.gradient(child, disc_child,
                                                     self.bcs)

            elif isinstance(symbol, pybamm.Divergence):
                return child_spatial_method.divergence(child, disc_child,
                                                       self.bcs)

            elif isinstance(symbol, pybamm.Laplacian):
                return child_spatial_method.laplacian(child, disc_child,
                                                      self.bcs)

            elif isinstance(symbol, pybamm.Gradient_Squared):
                return child_spatial_method.gradient_squared(
                    child, disc_child, self.bcs)

            elif isinstance(symbol, pybamm.Mass):
                return child_spatial_method.mass_matrix(child, self.bcs)

            elif isinstance(symbol, pybamm.BoundaryMass):
                return child_spatial_method.boundary_mass_matrix(
                    child, self.bcs)

            elif isinstance(symbol, pybamm.IndefiniteIntegral):
                return child_spatial_method.indefinite_integral(
                    child, disc_child, "forward")
            elif isinstance(symbol, pybamm.BackwardIndefiniteIntegral):
                return child_spatial_method.indefinite_integral(
                    child, disc_child, "backward")

            elif isinstance(symbol, pybamm.Integral):
                integral_spatial_method = self.spatial_methods[
                    symbol.integration_variable[0].domain[0]]
                out = integral_spatial_method.integral(
                    child, disc_child, symbol._integration_dimension)
                out.copy_domains(symbol)
                return out

            elif isinstance(symbol, pybamm.DefiniteIntegralVector):
                return child_spatial_method.definite_integral_matrix(
                    child, vector_type=symbol.vector_type)

            elif isinstance(symbol, pybamm.BoundaryIntegral):
                return child_spatial_method.boundary_integral(
                    child, disc_child, symbol.region)

            elif isinstance(symbol, pybamm.Broadcast):
                # Broadcast new_child to the domain specified by symbol.domain
                # Different discretisations may broadcast differently
                if symbol.domain == []:
                    symbol = disc_child * pybamm.Vector([1])
                else:
                    symbol = spatial_method.broadcast(
                        disc_child,
                        symbol.domain,
                        symbol.auxiliary_domains,
                        symbol.broadcast_type,
                    )
                return symbol

            elif isinstance(symbol, pybamm.DeltaFunction):
                return spatial_method.delta_function(symbol, disc_child)

            elif isinstance(symbol, pybamm.BoundaryOperator):
                # if boundary operator applied on "negative tab" or
                # "positive tab" *and* the mesh is 1D then change side to
                # "left" or "right" as appropriate
                if symbol.side in ["negative tab", "positive tab"]:
                    mesh = self.mesh[symbol.children[0].domain[0]]
                    if isinstance(mesh, pybamm.SubMesh1D):
                        symbol.side = mesh.tabs[symbol.side]
                return child_spatial_method.boundary_value_or_flux(
                    symbol, disc_child, self.bcs)
            elif isinstance(symbol, pybamm.UpwindDownwind):
                direction = symbol.name  # upwind or downwind
                return spatial_method.upwind_or_downwind(
                    child, disc_child, self.bcs, direction)
            else:
                return symbol._unary_new_copy(disc_child)

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

        elif isinstance(symbol, pybamm.VariableDot):
            return pybamm.StateVectorDot(
                *self.y_slices[symbol.get_variable().id],
                domain=symbol.domain,
                auxiliary_domains=symbol.auxiliary_domains,
            )

        elif isinstance(symbol, pybamm.Variable):
            # Check if variable is a standard variable or an external variable
            if any(symbol.id == var.id
                   for var in self.external_variables.values()):
                # Look up dictionary key based on value
                idx = [x.id for x in self.external_variables.values()
                       ].index(symbol.id)
                name, parent_and_slice = list(
                    self.external_variables.keys())[idx]
                if parent_and_slice is None:
                    # Variable didn't come from a concatenation so we can just create a
                    # normal external variable using the symbol's name
                    return pybamm.ExternalVariable(
                        symbol.name,
                        size=self._get_variable_size(symbol),
                        domain=symbol.domain,
                        auxiliary_domains=symbol.auxiliary_domains,
                    )
                else:
                    # We have to use a special name since the concatenation doesn't have
                    # a very informative name. Needs improving
                    parent, start, end = parent_and_slice
                    ext = pybamm.ExternalVariable(
                        name,
                        size=self._get_variable_size(parent),
                        domain=parent.domain,
                        auxiliary_domains=parent.auxiliary_domains,
                    )
                    out = pybamm.Index(ext, slice(start, end))
                    out.domain = symbol.domain
                    return out

            else:
                # add a try except block for a more informative error if a variable
                # can't be found. This should usually be caught earlier by
                # model.check_well_posedness, but won't be if debug_mode is False
                try:
                    y_slices = self.y_slices[symbol.id]
                except KeyError:
                    raise pybamm.ModelError("""
                        No key set for variable '{}'. Make sure it is included in either
                        model.rhs, model.algebraic, or model.external_variables in an
                        unmodified form (e.g. not Broadcasted)
                        """.format(symbol.name))
                return pybamm.StateVector(
                    *y_slices,
                    domain=symbol.domain,
                    auxiliary_domains=symbol.auxiliary_domains,
                )

        elif isinstance(symbol, pybamm.SpatialVariable):
            return spatial_method.spatial_variable(symbol)

        elif isinstance(symbol, pybamm.Concatenation):
            new_children = [
                self.process_symbol(child) for child in symbol.children
            ]
            new_symbol = spatial_method.concatenation(new_children)

            return new_symbol

        elif isinstance(symbol, pybamm.InputParameter):
            # Return a new copy of the input parameter, but set the expected size
            # according to the domain of the input parameter
            expected_size = self._get_variable_size(symbol)
            new_input_parameter = symbol.new_copy()
            new_input_parameter.set_expected_size(expected_size)
            return new_input_parameter

        else:
            # Backup option: return new copy of the object
            try:
                return symbol.new_copy()
            except NotImplementedError:
                raise NotImplementedError(
                    "Cannot discretise symbol of type '{}'".format(
                        type(symbol)))