コード例 #1
0
    def test_model_solver_with_non_identity_mass(self):
        model = pybamm.BaseModel()
        var1 = pybamm.Variable("var1", domain="negative electrode")
        var2 = pybamm.Variable("var2", domain="negative electrode")
        model.rhs = {var1: var1}
        model.algebraic = {var2: 2 * var1 - var2}
        model.initial_conditions = {var1: 1, var2: 2}
        disc = get_discretisation_for_testing()
        disc.process_model(model)

        # FV discretisation has identity mass. Manually set the mass matrix to
        # be a diag of 10s here for testing. Note that the algebraic part is all
        # zeros
        mass_matrix = 10 * model.mass_matrix.entries
        model.mass_matrix = pybamm.Matrix(mass_matrix)

        # Note that mass_matrix_inv is just the inverse of the ode block of the
        # mass matrix
        mass_matrix_inv = 0.1 * eye(int(mass_matrix.shape[0] / 2))
        model.mass_matrix_inv = pybamm.Matrix(mass_matrix_inv)

        # Solve
        solver = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 1, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_equal(solution.t, t_eval)
        np.testing.assert_allclose(solution.y.full()[0],
                                   np.exp(0.1 * solution.t))
        np.testing.assert_allclose(solution.y.full()[-1],
                                   2 * np.exp(0.1 * solution.t))
コード例 #2
0
    def test_simplify_inner(self):
        a1 = pybamm.Scalar(0)
        M1 = pybamm.Matrix(np.zeros((10, 10)))
        v1 = pybamm.Vector(np.ones(10))
        a2 = pybamm.Scalar(1)
        M2 = pybamm.Matrix(np.ones((10, 10)))
        a3 = pybamm.Scalar(3)

        np.testing.assert_array_equal(
            pybamm.inner(a1, M2).simplify().evaluate().toarray(), M1.entries
        )
        self.assertEqual(pybamm.inner(a1, a2).simplify().evaluate(), 0)
        np.testing.assert_array_equal(
            pybamm.inner(M2, a1).simplify().evaluate().toarray(), M1.entries
        )
        self.assertEqual(pybamm.inner(a2, a1).simplify().evaluate(), 0)
        np.testing.assert_array_equal(
            pybamm.inner(M1, a3).simplify().evaluate().toarray(), M1.entries
        )
        np.testing.assert_array_equal(
            pybamm.inner(v1, a3).simplify().evaluate(), 3 * v1.entries
        )
        self.assertEqual(pybamm.inner(a2, a3).simplify().evaluate(), 3)
        self.assertEqual(pybamm.inner(a3, a2).simplify().evaluate(), 3)
        self.assertEqual(pybamm.inner(a3, a3).simplify().evaluate(), 9)
コード例 #3
0
    def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type):
        """
        Broadcast symbol to a specified domain.

        Parameters
        ----------
        symbol : :class:`pybamm.Symbol`
            The symbol to be broadcasted
        domain : iterable of strings
            The domain to broadcast to
        auxiliary_domains : dict of strings
            The auxiliary domains for broadcasting
        broadcast_type : str
            The type of broadcast: 'primary to node', 'primary to edges', 'secondary to
            nodes', 'secondary to edges', 'full to nodes' or 'full to edges'

        Returns
        -------
        broadcasted_symbol: class: `pybamm.Symbol`
            The discretised symbol of the correct size for the spatial method
        """

        primary_domain_size = sum(self.mesh[dom].npts_for_broadcast_to_nodes
                                  for dom in domain)
        secondary_domain_size = self._get_auxiliary_domain_repeats(
            {k: v
             for k, v in auxiliary_domains.items() if k == "secondary"})
        auxiliary_domains_size = self._get_auxiliary_domain_repeats(
            auxiliary_domains)
        full_domain_size = primary_domain_size * auxiliary_domains_size
        if broadcast_type.endswith("to edges"):
            # add one point to each domain for broadcasting to edges
            primary_domain_size += 1
            full_domain_size = primary_domain_size * auxiliary_domains_size
            secondary_domain_size += 1

        if broadcast_type.startswith("primary"):
            # Make copies of the child stacked on top of each other
            sub_vector = np.ones((primary_domain_size, 1))
            if symbol.shape_for_testing == ():
                out = symbol * pybamm.Vector(sub_vector)
            else:
                # Repeat for secondary points
                matrix = csr_matrix(
                    kron(eye(symbol.shape_for_testing[0]), sub_vector))
                out = pybamm.Matrix(matrix) @ symbol
            out.domain = domain
        elif broadcast_type.startswith("secondary"):
            # Make copies of the child stacked on top of each other
            identity = eye(symbol.shape[0])
            matrix = vstack([identity for _ in range(secondary_domain_size)])
            out = pybamm.Matrix(matrix) @ symbol
        elif broadcast_type.startswith("full"):
            out = symbol * pybamm.Vector(np.ones(full_domain_size),
                                         domain=domain)

        out.auxiliary_domains = auxiliary_domains.copy()
        return out
コード例 #4
0
    def test_sparse_multiply(self):
        row = np.array([0, 3, 1, 0])
        col = np.array([0, 3, 1, 2])
        data = np.array([4, 5, 7, 9])
        S1 = coo_matrix((data, (row, col)), shape=(4, 5))
        S2 = coo_matrix((data, (row, col)), shape=(5, 4))
        pybammS1 = pybamm.Matrix(S1)
        pybammS2 = pybamm.Matrix(S2)
        D1 = np.ones((4, 5))
        D2 = np.ones((5, 4))
        pybammD1 = pybamm.Matrix(D1)
        pybammD2 = pybamm.Matrix(D2)

        # Multiplication is elementwise
        np.testing.assert_array_equal(
            (pybammS1 * pybammS1).evaluate().toarray(),
            S1.multiply(S1).toarray())
        np.testing.assert_array_equal(
            (pybammS2 * pybammS2).evaluate().toarray(),
            S2.multiply(S2).toarray())
        np.testing.assert_array_equal(
            (pybammD1 * pybammS1).evaluate().toarray(),
            S1.toarray() * D1)
        np.testing.assert_array_equal(
            (pybammS1 * pybammD1).evaluate().toarray(),
            S1.toarray() * D1)
        np.testing.assert_array_equal(
            (pybammD2 * pybammS2).evaluate().toarray(),
            S2.toarray() * D2)
        np.testing.assert_array_equal(
            (pybammS2 * pybammD2).evaluate().toarray(),
            S2.toarray() * D2)
        with self.assertRaisesRegex(pybamm.ShapeError, "inconsistent shapes"):
            (pybammS1 * pybammS2).test_shape()
        with self.assertRaisesRegex(pybamm.ShapeError, "inconsistent shapes"):
            (pybammS2 * pybammS1).test_shape()
        with self.assertRaisesRegex(pybamm.ShapeError, "inconsistent shapes"):
            (pybammS2 * pybammS1).evaluate_ignoring_errors()

        # Matrix multiplication is normal matrix multiplication
        np.testing.assert_array_equal(
            (pybammS1 @ pybammS2).evaluate().toarray(), (S1 * S2).toarray())
        np.testing.assert_array_equal(
            (pybammS2 @ pybammS1).evaluate().toarray(), (S2 * S1).toarray())
        np.testing.assert_array_equal((pybammS1 @ pybammD2).evaluate(),
                                      S1 * D2)
        np.testing.assert_array_equal((pybammD2 @ pybammS1).evaluate(),
                                      D2 * S1)
        np.testing.assert_array_equal((pybammS2 @ pybammD1).evaluate(),
                                      S2 * D1)
        np.testing.assert_array_equal((pybammD1 @ pybammS2).evaluate(),
                                      D1 * S2)
        with self.assertRaisesRegex(pybamm.ShapeError, "dimension mismatch"):
            (pybammS1 @ pybammS1).test_shape()
        with self.assertRaisesRegex(pybamm.ShapeError, "dimension mismatch"):
            (pybammS2 @ pybammS2).test_shape()
コード例 #5
0
ファイル: spatial_method.py プロジェクト: soroscaliban/PyBaMM
    def broadcast(self, symbol, domain, auxiliary_domains, broadcast_type):
        """
        Broadcast symbol to a specified domain.

        Parameters
        ----------
        symbol : :class:`pybamm.Symbol`
            The symbol to be broadcasted
        domain : iterable of strings
            The domain to broadcast to
        broadcast_type : str
            The type of broadcast, either: 'primary' or 'full'

        Returns
        -------
        broadcasted_symbol: class: `pybamm.Symbol`
            The discretised symbol of the correct size for the spatial method
        """

        primary_domain_size = sum(self.mesh[dom][0].npts_for_broadcast
                                  for dom in domain)

        full_domain_size = sum(subdom.npts_for_broadcast for dom in domain
                               for subdom in self.mesh[dom])

        if broadcast_type == "primary":
            # Make copies of the child stacked on top of each other
            sub_vector = np.ones((primary_domain_size, 1))
            if symbol.shape_for_testing == ():
                out = symbol * pybamm.Vector(sub_vector)
            else:
                # Repeat for secondary points
                matrix = csr_matrix(
                    kron(eye(symbol.shape_for_testing[0]), sub_vector))
                out = pybamm.Matrix(matrix) @ symbol
            out.domain = domain
        elif broadcast_type == "secondary":
            secondary_domain_size = sum(
                self.mesh[dom][0].npts_for_broadcast
                for dom in auxiliary_domains["secondary"])
            kron_size = full_domain_size // primary_domain_size
            # Symbol may be on edges so need to calculate size carefully
            symbol_primary_size = symbol.shape[0] // kron_size
            # Make copies of the child stacked on top of each other
            identity = eye(symbol_primary_size)
            sub_matrix = vstack(
                [identity for _ in range(secondary_domain_size)])
            # Repeat for secondary points
            matrix = csr_matrix(kron(eye(kron_size), sub_matrix))
            out = pybamm.Matrix(matrix) @ symbol
        elif broadcast_type == "full":
            out = symbol * pybamm.Vector(np.ones(full_domain_size),
                                         domain=domain)

        out.auxiliary_domains = auxiliary_domains
        return out
コード例 #6
0
ファイル: test_copy.py プロジェクト: masoodtamaddon/PyBaMM
    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)
コード例 #7
0
    def internal_neumann_condition(
        self, left_symbol_disc, right_symbol_disc, left_mesh, right_mesh
    ):
        """
        A method to find the internal neumann conditions between two symbols
        on adjacent subdomains.

        Parameters
        ----------
        left_symbol_disc : :class:`pybamm.Symbol`
            The discretised symbol on the left subdomain
        right_symbol_disc : :class:`pybamm.Symbol`
            The discretised symbol on the right subdomain
        left_mesh : list
            The mesh on the left subdomain
        right_mesh : list
            The mesh on the right subdomain
        """

        left_npts = left_mesh[0].npts
        right_npts = right_mesh[0].npts

        sec_pts = len(left_mesh)

        if sec_pts != len(right_mesh):
            raise pybamm.DomainError(
                """Number of secondary points in subdomains do not match"""
            )

        left_sub_matrix = np.zeros((1, left_npts))
        left_sub_matrix[0][left_npts - 1] = 1
        left_matrix = pybamm.Matrix(csr_matrix(kron(eye(sec_pts), left_sub_matrix)))

        right_sub_matrix = np.zeros((1, right_npts))
        right_sub_matrix[0][0] = 1
        right_matrix = pybamm.Matrix(csr_matrix(kron(eye(sec_pts), right_sub_matrix)))

        # Remove domains to avoid clash
        left_domain = left_symbol_disc.domain
        right_domain = right_symbol_disc.domain
        left_symbol_disc.domain = []
        right_symbol_disc.domain = []

        # Finite volume derivative
        dy = right_matrix @ right_symbol_disc - left_matrix @ left_symbol_disc
        dx = right_mesh[0].nodes[0] - left_mesh[0].nodes[-1]

        # Change domains back
        left_symbol_disc.domain = left_domain
        right_symbol_disc.domain = right_domain

        return dy / dx
コード例 #8
0
    def boundary_value_or_flux(self, symbol, discretised_child, bcs=None):
        """
        Returns the boundary value or flux using the approriate expression for the
        spatial method. To do this, we create a sparse vector 'bv_vector' that extracts
        either the first (for side="left") or last (for side="right") point from
        'discretised_child'.

        Parameters
        -----------
        symbol: :class:`pybamm.Symbol`
            The boundary value or flux symbol
        discretised_child : :class:`pybamm.StateVector`
            The discretised variable from which to calculate the boundary value
        bcs : dict (optional)
            The boundary conditions. If these are supplied and "use bcs" is True in
            the options, then these will be used to improve the accuracy of the
            extrapolation.

        Returns
        -------
        :class:`pybamm.MatrixMultiplication`
            The variable representing the surface value.
        """

        if bcs is None:
            bcs = {}
        if self._get_auxiliary_domain_repeats(
                discretised_child.auxiliary_domains) > 1:
            raise NotImplementedError(
                "Cannot process 2D symbol in base spatial method")
        if isinstance(symbol, pybamm.BoundaryGradient):
            raise TypeError(
                "Cannot process BoundaryGradient in base spatial method")
        n = sum(self.mesh[dom].npts for dom in discretised_child.domain)
        if symbol.side == "left":
            # coo_matrix takes inputs (data, (row, col)) and puts data[i] at the point
            # (row[i], col[i]) for each index of data. Here we just want a single point
            # with value 1 at (0,0).
            # Convert to a csr_matrix to allow indexing and other functionality
            left_vector = csr_matrix(
                coo_matrix(([1], ([0], [0])), shape=(1, n)))
            bv_vector = pybamm.Matrix(left_vector)
        elif symbol.side == "right":
            # as above, but now we want a single point with value 1 at (0, n-1)
            right_vector = csr_matrix(
                coo_matrix(([1], ([0], [n - 1])), shape=(1, n)))
            bv_vector = pybamm.Matrix(right_vector)

        out = bv_vector @ discretised_child
        # boundary value removes domain
        out.clear_domains()
        return out
コード例 #9
0
    def indefinite_integral_matrix_nodes(self, domain):
        """
        Matrix for finite-volume implementation of the indefinite integral where the
        integrand is evaluated on mesh nodes.
        This is just a straightforward cumulative sum of the integrand

        Parameters
        ----------
        domain : list
            The domain(s) of integration

        Returns
        -------
        :class:`pybamm.Matrix`
            The finite volume integral matrix for the domain
        """

        # Create appropriate submesh by combining submeshes in domain
        submesh_list = self.mesh.combine_submeshes(*domain)
        submesh = submesh_list[0]
        n = submesh.npts
        sec_pts = len(submesh_list)

        du_n = submesh.d_edges
        du_entries = [du_n] * (n)
        offset = -np.arange(1, n + 1, 1)
        sub_matrix = diags(du_entries, offset, shape=(n + 1, n))
        # Convert to csr_matrix so that we can take the index (row-slicing), which is
        # not supported by the default kron format
        # Note that this makes column-slicing inefficient, but this should not be an
        # issue
        matrix = csr_matrix(kron(eye(sec_pts), sub_matrix))

        return pybamm.Matrix(matrix)
コード例 #10
0
    def _binary_simplify(self, left, right):
        """ See :meth:`pybamm.BinaryOperator.simplify()`. """

        # anything multiplied by a scalar zero returns a scalar zero
        if is_scalar_zero(left):
            if isinstance(right, pybamm.Array):
                return pybamm.Array(np.zeros(right.shape))
            else:
                return pybamm.Scalar(0)
        if is_scalar_zero(right):
            if isinstance(left, pybamm.Array):
                return pybamm.Array(np.zeros(left.shape))
            else:
                return pybamm.Scalar(0)

        # if one of the children is a zero matrix, we have to be careful about shapes
        if is_matrix_zero(left) or is_matrix_zero(right):
            shape = (left * right).shape
            if len(shape) == 1 or shape[1] == 1:
                return pybamm.Vector(np.zeros(shape))
            else:
                return pybamm.Matrix(csr_matrix(shape))

        # anything multiplied by a scalar one returns itself
        if is_one(left):
            return right
        if is_one(right):
            return left

        return pybamm.simplify_multiplication_division(self.__class__, left,
                                                       right)
コード例 #11
0
    def test_known_eval(self):
        # Scalars
        a = pybamm.Scalar(4)
        b = pybamm.StateVector(slice(0, 1))
        expr = (a + b) - (a + b) * (a + b)
        value = expr.evaluate(y=np.array([2]))
        self.assertEqual(expr.evaluate(y=np.array([2]), known_evals={})[0], value)
        self.assertIn((a + b).id, expr.evaluate(y=np.array([2]), known_evals={})[1])
        self.assertEqual(
            expr.evaluate(y=np.array([2]), known_evals={})[1][(a + b).id], 6
        )

        # Matrices
        a = pybamm.Matrix(np.random.rand(5, 5))
        b = pybamm.StateVector(slice(0, 5))
        expr2 = (a @ b) - (a @ b) * (a @ b) + (a @ b)
        y_test = np.linspace(0, 1, 5)
        value = expr2.evaluate(y=y_test)
        np.testing.assert_array_equal(
            expr2.evaluate(y=y_test, known_evals={})[0], value
        )
        self.assertIn((a @ b).id, expr2.evaluate(y=y_test, known_evals={})[1])
        np.testing.assert_array_equal(
            expr2.evaluate(y=y_test, known_evals={})[1][(a @ b).id],
            (a @ b).evaluate(y=y_test),
        )
コード例 #12
0
def simplify_if_constant(symbol, keep_domains=False):
    """
    Utility function to simplify an expression tree if it evalutes to a constant
    scalar, vector or matrix
    """
    if keep_domains is True:
        domain = symbol.domain
        auxiliary_domains = symbol.auxiliary_domains
    else:
        domain = None
        auxiliary_domains = None
    if symbol.is_constant():
        result = symbol.evaluate_ignoring_errors()
        if result is not None:
            if (isinstance(result, numbers.Number)
                    or (isinstance(result, np.ndarray) and result.ndim == 0)
                    or isinstance(result, np.bool_)):
                return pybamm.Scalar(result)
            elif isinstance(result, np.ndarray) or issparse(result):
                if result.ndim == 1 or result.shape[1] == 1:
                    return pybamm.Vector(result,
                                         domain=domain,
                                         auxiliary_domains=auxiliary_domains)
                else:
                    # Turn matrix of zeros into sparse matrix
                    if isinstance(result, np.ndarray) and np.all(result == 0):
                        result = csr_matrix(result)
                    return pybamm.Matrix(result,
                                         domain=domain,
                                         auxiliary_domains=auxiliary_domains)

    return symbol
コード例 #13
0
ファイル: test_symbol.py プロジェクト: boninglyu/PyBaMM
    def test_symbol_evaluates_to_constant_number(self):
        a = pybamm.Scalar(3)
        self.assertTrue(a.evaluates_to_constant_number())

        a = pybamm.Parameter("a")
        self.assertFalse(a.evaluates_to_constant_number())

        a = pybamm.Variable("a")
        self.assertFalse(a.evaluates_to_constant_number())

        a = pybamm.Scalar(3) - 2
        self.assertTrue(a.evaluates_to_constant_number())

        a = pybamm.Vector(np.ones(5))
        self.assertFalse(a.evaluates_to_constant_number())

        a = pybamm.Matrix(np.ones((4, 6)))
        self.assertFalse(a.evaluates_to_constant_number())

        a = pybamm.StateVector(slice(0, 10))
        self.assertFalse(a.evaluates_to_constant_number())

        # Time variable returns true
        a = 3 * pybamm.t + 2
        self.assertFalse(a.evaluates_to_constant_number())
コード例 #14
0
ファイル: test_symbol.py プロジェクト: xanderyin/PyBaMM
    def test_symbol_evaluates_to_number(self):
        a = pybamm.Scalar(3)
        self.assertTrue(a.evaluates_to_number())

        a = pybamm.Parameter("a")
        self.assertFalse(a.evaluates_to_number())

        a = pybamm.Scalar(3) * pybamm.Time()
        self.assertTrue(a.evaluates_to_number())
        # highlight difference between this function and isinstance(a, Scalar)
        self.assertNotIsInstance(a, pybamm.Scalar)

        a = pybamm.Variable("a")
        self.assertFalse(a.evaluates_to_number())

        a = pybamm.Scalar(3) - 2
        self.assertTrue(a.evaluates_to_number())

        a = pybamm.Vector(np.ones(5))
        self.assertFalse(a.evaluates_to_number())

        a = pybamm.Matrix(np.ones((4, 6)))
        self.assertFalse(a.evaluates_to_number())

        a = pybamm.StateVector(slice(0, 10))
        self.assertFalse(a.evaluates_to_number())

        # Time variable returns true
        a = 3 * pybamm.t + 2
        self.assertTrue(a.evaluates_to_number())
コード例 #15
0
    def divergence_matrix(self, domain):
        """
        Divergence matrix for finite volumes in the appropriate domain.
        Equivalent to div(N) = (N[1:] - N[:-1])/dx

        Parameters
        ----------
        domain : list
            The domain(s) in which to compute the divergence matrix

        Returns
        -------
        :class:`pybamm.Matrix`
            The (sparse) finite volume divergence matrix for the domain
        """
        # Create appropriate submesh by combining submeshes in domain
        submesh_list = self.mesh.combine_submeshes(*domain)

        # can just use 1st entry of list to obtain the point etc
        submesh = submesh_list[0]
        e = 1 / submesh.d_edges

        # Create matrix using submesh
        n = submesh.npts + 1
        sub_matrix = diags([-e, e], [0, 1], shape=(n - 1, n))

        # repeat matrix for each node in secondary dimensions
        second_dim_len = len(submesh_list)
        # generate full matrix from the submatrix
        # Convert to csr_matrix so that we can take the index (row-slicing), which is
        # not supported by the default kron format
        # Note that this makes column-slicing inefficient, but this should not be an
        # issue
        matrix = csr_matrix(kron(eye(second_dim_len), sub_matrix))
        return pybamm.Matrix(matrix)
コード例 #16
0
ファイル: discretisation.py プロジェクト: tinosulzer/PyBaMM
    def create_mass_matrix(self, model):
        """Creates mass matrix of the discretised model.
        Note that the model is assumed to be of the form M*y_dot = f(t,y), where
        M is the (possibly singular) mass matrix.

        Parameters
        ----------
        model : :class:`pybamm.BaseModel`
            Discretised model. Must have attributes rhs, initial_conditions and
            boundary_conditions (all dicts of {variable: equation})

        Returns
        -------
        :class:`pybamm.Matrix`
            The mass matrix
        """
        # Create list of mass matrices for each equation to be put into block
        # diagonal mass matrix for the model
        mass_list = []

        # get a list of model rhs variables that are sorted according to
        # where they are in the state vector
        model_variables = model.rhs.keys()
        model_slices = []
        for v in model_variables:
            if isinstance(v, pybamm.Concatenation):
                model_slices.append(
                    slice(
                        self.y_slices[v.children[0].id][0].start,
                        self.y_slices[v.children[-1].id][0].stop,
                    ))
            else:
                model_slices.append(self.y_slices[v.id][0])
        sorted_model_variables = [
            v for _, v in sorted(zip(model_slices, model_variables))
        ]

        # Process mass matrices for the differential equations
        for var in sorted_model_variables:
            if var.domain == []:
                # If variable domain empty then mass matrix is just 1
                mass_list.append(1.0)
            else:
                mass_list.append(
                    self.spatial_methods[var.domain[0]].mass_matrix(
                        var, self.bcs).entries)

        # Create lumped mass matrix (of zeros) of the correct shape for the
        # discretised algebraic equations
        if model.algebraic.keys():
            mass_algebraic_size = model.concatenated_algebraic.shape[0]
            mass_algebraic = csr_matrix(
                (mass_algebraic_size, mass_algebraic_size))
            mass_list.append(mass_algebraic)

        # Create block diagonal (sparse) mass matrix
        mass_matrix = block_diag(mass_list, format="csr")

        return pybamm.Matrix(mass_matrix)
コード例 #17
0
 def test_simplify_kron(self):
     A = pybamm.Matrix(np.eye(2))
     b = pybamm.Vector(np.array([[4], [5]]))
     kron = pybamm.Kron(A, b)
     kron_simp = kron.simplify()
     self.assertIsInstance(kron_simp, pybamm.Matrix)
     np.testing.assert_array_equal(kron_simp.evaluate().toarray(),
                                   np.kron(A.entries, b.entries))
コード例 #18
0
    def test_processed_var_1D_interpolation(self):
        t = pybamm.t
        var = pybamm.Variable("var", domain=["negative electrode", "separator"])
        x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"])
        eqn = t * var + x

        disc = tests.get_discretisation_for_testing()
        disc.set_variable_slices([var])
        x_sol = disc.process_symbol(x).entries[:, 0]
        var_sol = disc.process_symbol(var)
        eqn_sol = disc.process_symbol(eqn)
        t_sol = np.linspace(0, 1)
        y_sol = x_sol[:, np.newaxis] * np.linspace(0, 5)

        processed_var = pybamm.ProcessedVariable(
            var_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        # 2 vectors
        np.testing.assert_array_almost_equal(processed_var(t_sol, x_sol), y_sol)
        # 1 vector, 1 scalar
        np.testing.assert_array_almost_equal(
            processed_var(0.5, x_sol)[:, 0], 2.5 * x_sol
        )
        np.testing.assert_array_equal(
            processed_var(t_sol, x_sol[-1]), x_sol[-1] * np.linspace(0, 5)
        )
        # 2 scalars
        np.testing.assert_array_almost_equal(
            processed_var(0.5, x_sol[-1]), 2.5 * x_sol[-1]
        )
        processed_eqn = pybamm.ProcessedVariable(
            eqn_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        # 2 vectors
        np.testing.assert_array_almost_equal(
            processed_eqn(t_sol, x_sol), t_sol * y_sol + x_sol[:, np.newaxis]
        )
        # 1 vector, 1 scalar
        self.assertEqual(processed_eqn(0.5, x_sol[10:30]).shape, (20, 1))
        self.assertEqual(processed_eqn(t_sol[4:9], x_sol[-1]).shape, (5,))
        # 2 scalars
        self.assertEqual(processed_eqn(0.5, x_sol[-1]).shape, (1,))

        # test x
        processed_x = pybamm.ProcessedVariable(
            disc.process_symbol(x), pybamm.Solution(t_sol, y_sol), warn=False
        )
        np.testing.assert_array_almost_equal(processed_x(x=x_sol), x_sol[:, np.newaxis])

        # On microscale
        r_n = pybamm.Matrix(
            disc.mesh["negative particle"].nodes, domain="negative particle"
        )
        r_n.mesh = disc.mesh["negative particle"]
        processed_r_n = pybamm.ProcessedVariable(
            r_n, pybamm.Solution(t_sol, y_sol), warn=False
        )
        np.testing.assert_array_equal(r_n.entries[:, 0], processed_r_n.entries[:, 0])
コード例 #19
0
    def test_processed_variable_2D_x_z(self):
        var = pybamm.Variable(
            "var",
            domain=["negative electrode", "separator"],
            auxiliary_domains={"secondary": "current collector"},
        )
        x = pybamm.SpatialVariable(
            "x",
            domain=["negative electrode", "separator"],
            auxiliary_domains={"secondary": "current collector"},
        )
        z = pybamm.SpatialVariable("z", domain=["current collector"])

        disc = tests.get_1p1d_discretisation_for_testing()
        disc.set_variable_slices([var])
        z_sol = disc.process_symbol(z).entries[:, 0]
        x_sol = disc.process_symbol(x).entries[:, 0]
        # Keep only the first iteration of entries
        x_sol = x_sol[:len(x_sol) // len(z_sol)]
        var_sol = disc.process_symbol(var)
        t_sol = np.linspace(0, 1)
        y_sol = np.ones(len(x_sol) * len(z_sol))[:, np.newaxis] * np.linspace(
            0, 5)

        var_casadi = to_casadi(var_sol, y_sol)
        processed_var = pybamm.ProcessedVariable(
            [var_sol],
            [var_casadi],
            pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}),
            warn=False,
        )
        np.testing.assert_array_equal(
            processed_var.entries,
            np.reshape(
                y_sol,
                [len(x_sol), len(z_sol), len(t_sol)]),
        )

        # On edges
        x_s_edge = pybamm.Matrix(
            np.tile(disc.mesh["separator"].edges, len(z_sol)),
            domain="separator",
            auxiliary_domains={"secondary": "current collector"},
        )
        x_s_edge.mesh = disc.mesh["separator"]
        x_s_edge.secondary_mesh = disc.mesh["current collector"]
        x_s_casadi = to_casadi(x_s_edge, y_sol)
        processed_x_s_edge = pybamm.ProcessedVariable(
            [x_s_edge],
            [x_s_casadi],
            pybamm.Solution(t_sol, y_sol, pybamm.BaseModel(), {}),
            warn=False,
        )
        np.testing.assert_array_equal(
            x_s_edge.entries.flatten(),
            processed_x_s_edge.entries[:, :, 0].T.flatten())
コード例 #20
0
    def gradient(self, symbol, discretised_symbol, boundary_conditions):
        """Matrix-vector multiplication to implement the gradient operator. The
        gradient w of the function u is approximated by the finite element method
        using the same function space as u, i.e. we solve w = grad(u), which
        corresponds to the weak form w*v*dx = grad(u)*v*dx, where v is a suitable
        test function.

        Parameters
        ----------
        symbol: :class:`pybamm.Symbol`
            The symbol that we will take the laplacian of.
        discretised_symbol: :class:`pybamm.Symbol`
            The discretised symbol of the correct size
        boundary_conditions : dict
            The boundary conditions of the model
            ({symbol.id: {"negative tab": neg. tab bc, "positive tab": pos. tab bc}})

        Returns
        -------
        :class: `pybamm.Concatenation`
            A concatenation that contains the result of acting the discretised
            gradient on the child discretised_symbol. The first column corresponds
            to the y-component of the gradient and the second column corresponds
            to the z component of the gradient.
        """
        domain = symbol.domain[0]
        mesh = self.mesh[domain][0]

        # get gradient matrix
        grad_y_matrix, grad_z_matrix = self.gradient_matrix(
            symbol, boundary_conditions)

        # assemble mass matrix (there is no need to zero out entries here, since
        # boundary conditions are already accounted for in the governing pde
        # for the symbol we are taking the gradient of. we just want to get the
        # correct weights)
        @skfem.bilinear_form
        def mass_form(u, du, v, dv, w):
            return u * v

        mass = skfem.asm(mass_form, mesh.basis)
        # we need the inverse
        mass_inv = pybamm.Matrix(inv(csc_matrix(mass)))

        # compute gradient
        grad_y = mass_inv @ (grad_y_matrix @ discretised_symbol)
        grad_z = mass_inv @ (grad_z_matrix @ discretised_symbol)

        # create concatenation
        grad = pybamm.Concatenation(grad_y,
                                    grad_z,
                                    check_domain=False,
                                    concat_fun=np.hstack)
        grad.domain = domain

        return grad
コード例 #21
0
 def _jac(self, variable):
     """ See :meth:`pybamm.Symbol._jac()`. """
     # right cannot be a StateVector, so no need for product rule
     left, right = self.orphans
     if left.evaluates_to_number():
         # Return zeros of correct size
         return pybamm.Matrix(
             csr_matrix((self.size, variable.evaluation_array.count(True))))
     else:
         return pybamm.Kron(left.jac(variable), right)
コード例 #22
0
    def test_known_eval(self):
        # Scalars
        a = pybamm.Scalar(4)
        b = pybamm.Scalar(2)
        expr = (a + b) - (a + b) * (a + b)
        value = expr.evaluate()
        self.assertEqual(expr.evaluate(known_evals={})[0], value)
        self.assertIn((a + b).id, expr.evaluate(known_evals={})[1])
        self.assertEqual(expr.evaluate(known_evals={})[1][(a + b).id], 6)

        # Matrices
        a = pybamm.Matrix(np.random.rand(5, 5))
        b = pybamm.Matrix(np.random.rand(5, 5))
        expr2 = (a @ b) - (a @ b) * (a @ b) + (a @ b)
        value = expr2.evaluate()
        np.testing.assert_array_equal(expr2.evaluate(known_evals={})[0], value)
        self.assertIn((a @ b).id, expr2.evaluate(known_evals={})[1])
        np.testing.assert_array_equal(
            expr2.evaluate(known_evals={})[1][(a @ b).id], (a @ b).evaluate())
コード例 #23
0
    def test_sign(self):
        b = pybamm.Scalar(-4)
        signb = pybamm.sign(b)
        self.assertEqual(signb.evaluate(), -1)

        A = diags(np.linspace(-1, 1, 5))
        b = pybamm.Matrix(A)
        signb = pybamm.sign(b)
        np.testing.assert_array_equal(np.diag(signb.evaluate().toarray()),
                                      [-1, -1, 0, 1, 1])
コード例 #24
0
    def test_matrix_divide_simplify(self):
        m = pybamm.Matrix(np.random.rand(30, 20))
        zero = pybamm.Scalar(0)

        expr1 = (zero / m).simplify()
        self.assertIsInstance(expr1, pybamm.Matrix)
        self.assertEqual(expr1.shape, m.shape)
        np.testing.assert_array_equal(expr1.evaluate().toarray(), np.zeros((30, 20)))

        expr2 = (m / zero).simplify()
        self.assertIsInstance(expr2, pybamm.Matrix)
        self.assertEqual(expr2.shape, m.shape)
        np.testing.assert_array_equal(expr2.evaluate(), np.inf)

        m = pybamm.Matrix(np.zeros((10, 10)))
        a = pybamm.Scalar(7)
        expr3 = (m / a).simplify()
        self.assertIsInstance(expr3, pybamm.Matrix)
        self.assertEqual(expr3.shape, m.shape)
        np.testing.assert_array_equal(expr3.evaluate().toarray(), np.zeros((10, 10)))
コード例 #25
0
 def test_find_symbols_jax(self):
     # test sparse conversion
     constant_symbols = OrderedDict()
     variable_symbols = OrderedDict()
     A = pybamm.Matrix(scipy.sparse.csr_matrix(np.array([[0, 2], [0, 4]])))
     pybamm.find_symbols(A, constant_symbols, variable_symbols, output_jax=True)
     self.assertEqual(len(variable_symbols), 0)
     self.assertEqual(list(constant_symbols.keys())[0], A.id)
     np.testing.assert_allclose(
         list(constant_symbols.values())[0].toarray(), A.entries.toarray()
     )
コード例 #26
0
        class Model:
            mass_matrix = pybamm.Matrix(np.array([[1.0, 0.0], [0.0, 0.0]]))
            y0 = np.array([0.0, 1.0])
            terminate_events_eval = []
            timescale_eval = 1

            def residuals_eval(self, t, y, ydot):
                return np.array([0.5 * np.ones_like(y[0]) - ydot[0], 2 * y[0] - y[1]])

            def jacobian_eval(self, t, y):
                return np.array([[0.0, 0.0], [2.0, -1.0]])
コード例 #27
0
    def test_processed_variable_1D(self):
        t = pybamm.t
        var = pybamm.Variable("var", domain=["negative electrode", "separator"])
        x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"])
        eqn = t * var + x

        # On nodes
        disc = tests.get_discretisation_for_testing()
        disc.set_variable_slices([var])
        x_sol = disc.process_symbol(x).entries[:, 0]
        var_sol = disc.process_symbol(var)
        eqn_sol = disc.process_symbol(eqn)
        t_sol = np.linspace(0, 1)
        y_sol = np.ones_like(x_sol)[:, np.newaxis] * np.linspace(0, 5)

        processed_var = pybamm.ProcessedVariable(
            var_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        np.testing.assert_array_equal(processed_var.entries, y_sol)
        np.testing.assert_array_equal(processed_var(t_sol, x_sol), y_sol)
        processed_eqn = pybamm.ProcessedVariable(
            eqn_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        np.testing.assert_array_equal(
            processed_eqn(t_sol, x_sol), t_sol * y_sol + x_sol[:, np.newaxis]
        )

        # Test extrapolation
        np.testing.assert_array_equal(processed_var.entries[0], 2 * y_sol[0] - y_sol[1])
        np.testing.assert_array_equal(
            processed_var.entries[1], 2 * y_sol[-1] - y_sol[-2]
        )

        # On edges
        x_s_edge = pybamm.Matrix(disc.mesh["separator"].edges, domain="separator")
        x_s_edge.mesh = disc.mesh["separator"]
        processed_x_s_edge = pybamm.ProcessedVariable(
            x_s_edge, pybamm.Solution(t_sol, y_sol), warn=False
        )
        np.testing.assert_array_equal(
            x_s_edge.entries[:, 0], processed_x_s_edge.entries[:, 0]
        )

        # space only
        eqn = var + x
        eqn_sol = disc.process_symbol(eqn)
        t_sol = np.array([0])
        y_sol = np.ones_like(x_sol)[:, np.newaxis]
        processed_eqn2 = pybamm.ProcessedVariable(
            eqn_sol, pybamm.Solution(t_sol, y_sol), warn=False
        )
        np.testing.assert_array_equal(
            processed_eqn2.entries, y_sol + x_sol[:, np.newaxis]
        )
コード例 #28
0
    def _unary_jac(self, child_jac):
        """ See :meth:`pybamm.UnaryOperator._unary_jac()`. """

        # if child.jac returns a matrix of zeros, this subsequently gives a bug
        # when trying to simplify the node Index(child_jac). Instead, search the
        # tree for StateVectors and return a matrix of zeros of the correct size
        # if none are found.
        if all([not (isinstance(n, pybamm.StateVector)) for n in self.pre_order()]):
            jac = csr_matrix((1, child_jac.shape[1]))
            return pybamm.Matrix(jac)
        else:
            return Index(child_jac, self.index)
コード例 #29
0
    def test_sparse_divide(self):
        row = np.array([0, 3, 1, 0])
        col = np.array([0, 3, 1, 2])
        data = np.array([4, 5, 7, 9])
        S1 = coo_matrix((data, (row, col)), shape=(4, 5))
        pybammS1 = pybamm.Matrix(S1)
        v1 = np.ones((4, 1))
        pybammv1 = pybamm.Vector(v1)

        np.testing.assert_array_equal(
            (pybammS1 / pybammv1).evaluate().toarray(), S1.toarray() / v1
        )
コード例 #30
0
    def assemble_mass_form(self,
                           symbol,
                           boundary_conditions,
                           region="interior"):
        """
        Assembles the form of the finite element mass matrix over the domain
        interior or boundary.

        Parameters
        ----------
        symbol: :class:`pybamm.Variable`
            The variable corresponding to the equation for which we are
            calculating the mass matrix.
        boundary_conditions : dict
            The boundary conditions of the model
            ({symbol.id: {"negative tab": neg. tab bc, "positive tab": pos. tab bc}})
        region: str, optional
            The domain over which to assemble the mass matrix form. Can be "interior"
            (default) or "boundary".

        Returns
        -------
        :class:`pybamm.Matrix`
            The (sparse) mass matrix for the spatial method.
        """
        # get primary domain mesh
        domain = symbol.domain[0]
        mesh = self.mesh[domain][0]

        # create form for mass
        @skfem.bilinear_form
        def mass_form(u, du, v, dv, w):
            return u * v

        # assemble mass matrix
        if region == "interior":
            mass = skfem.asm(mass_form, mesh.basis)
        if region == "boundary":
            mass = skfem.asm(mass_form, mesh.facet_basis)

        # get boundary conditions and type
        if symbol.id in boundary_conditions:
            _, neg_bc_type = boundary_conditions[symbol.id]["negative tab"]
            _, pos_bc_type = boundary_conditions[symbol.id]["positive tab"]

            if neg_bc_type == "Dirichlet":
                # set source terms to zero on boundary by zeroing out mass matrix
                self.bc_apply(mass, mesh.negative_tab_dofs, zero=True)
            if pos_bc_type == "Dirichlet":
                # set source terms to zero on boundary by zeroing out mass matrix
                self.bc_apply(mass, mesh.positive_tab_dofs, zero=True)

        return pybamm.Matrix(mass)