Esempio n. 1
0
    def assemble(self, operator_descriptor, device_interface, precision, *args,
                 **kwargs):
        """Sparse assembly of operators."""
        from bempp.api.utils.helpers import promote_to_double_precision
        from bempp.api.space.space import return_compatible_representation
        from .numba_kernels import select_numba_kernels
        from scipy.sparse import coo_matrix
        from bempp.api.assembly.discrete_boundary_operator import (
            SparseDiscreteBoundaryOperator, )

        domain, dual_to_range = return_compatible_representation(
            self.domain, self.dual_to_range)
        row_grid_dofs = dual_to_range.grid_dof_count
        col_grid_dofs = domain.grid_dof_count

        if domain.grid != dual_to_range.grid:
            raise ValueError(
                "For sparse operators the domain and dual_to_range grids must be identical."
            )

        trial_local2global = domain.local2global.ravel()
        test_local2global = dual_to_range.local2global.ravel()
        trial_multipliers = domain.local_multipliers.ravel()
        test_multipliers = dual_to_range.local_multipliers.ravel()

        numba_assembly_function, numba_kernel_function = select_numba_kernels(
            operator_descriptor, mode="sparse")

        rows, cols, values = assemble_sparse(
            domain.localised_space,
            dual_to_range.localised_space,
            self.parameters,
            operator_descriptor,
            numba_assembly_function,
            numba_kernel_function,
        )
        global_rows = test_local2global[rows]
        global_cols = trial_local2global[cols]
        global_values = values * trial_multipliers[cols] * test_multipliers[
            rows]

        if self.parameters.assembly.always_promote_to_double:
            values = promote_to_double_precision(values)

        mat = coo_matrix(
            (global_values, (global_rows, global_cols)),
            shape=(row_grid_dofs, col_grid_dofs),
        ).tocsr()

        if domain.requires_dof_transformation:
            mat = mat @ domain.dof_transformation

        if dual_to_range.requires_dof_transformation:
            mat = dual_to_range.dof_transformation.T @ mat

        return SparseDiscreteBoundaryOperator(mat)
Esempio n. 2
0
    def assemble(self, operator_descriptor, device_interface, precision, *args,
                 **kwargs):
        """Assemble the singular part."""
        from bempp.api.assembly.discrete_boundary_operator import (
            SparseDiscreteBoundaryOperator, )
        from bempp.api.utils.helpers import promote_to_double_precision
        from scipy.sparse import coo_matrix, csr_matrix
        from bempp.api.space.space import return_compatible_representation

        domain, dual_to_range = return_compatible_representation(
            self.domain, self.dual_to_range)
        row_dof_count = dual_to_range.global_dof_count
        col_dof_count = domain.global_dof_count
        row_grid_dofs = dual_to_range.grid_dof_count
        col_grid_dofs = domain.grid_dof_count

        if domain.grid != dual_to_range.grid:
            return SparseDiscreteBoundaryOperator(
                csr_matrix((row_dof_count, col_dof_count), dtype="float64"))

        trial_local2global = domain.local2global.ravel()
        test_local2global = dual_to_range.local2global.ravel()
        trial_multipliers = domain.local_multipliers.ravel()
        test_multipliers = dual_to_range.local_multipliers.ravel()

        rows, cols, values = assemble_singular_part(
            domain.localised_space,
            dual_to_range.localised_space,
            self.parameters,
            operator_descriptor,
            device_interface,
        )
        global_rows = test_local2global[rows]
        global_cols = trial_local2global[cols]
        global_values = values * trial_multipliers[cols] * test_multipliers[
            rows]

        if self.parameters.assembly.always_promote_to_double:
            values = promote_to_double_precision(values)

        mat = coo_matrix(
            (global_values, (global_rows, global_cols)),
            shape=(row_grid_dofs, col_grid_dofs),
        ).tocsr()

        if domain.requires_dof_transformation:
            mat = mat @ domain.dof_transformation

        if dual_to_range.requires_dof_transformation:
            mat = dual_to_range.dof_transformation.T @ mat

        return SparseDiscreteBoundaryOperator(mat)
Esempio n. 3
0
    def assemble(self, operator_descriptor, device_interface, precision, *args,
                 **kwargs):
        """Actually assemble."""
        from bempp.api.space.space import return_compatible_representation
        from bempp.api.assembly.discrete_boundary_operator import (
            GenericDiscreteBoundaryOperator, )

        actual_domain, actual_dual_to_range = return_compatible_representation(
            self.domain, self.dual_to_range)

        mode = get_mode_from_operator_identifier(
            operator_descriptor.identifier)

        if mode == "laplace":
            wavenumber = None
        elif mode == "helmholtz":
            wavenumber = (operator_descriptor.options[0] +
                          1j * operator_descriptor.options[1])
        elif mode == "modified_helmholtz":
            wavenumber = operator_descriptor.options[0]
        else:
            raise ValueError(f"Unknown value {mode} for `mode`.")

        fmm_interface = get_fmm_interface(actual_domain, actual_dual_to_range,
                                          mode, wavenumber)

        self._evaluator = create_evaluator(
            operator_descriptor,
            fmm_interface,
            actual_domain,
            actual_dual_to_range,
            self.parameters,
        )

        if operator_descriptor.is_complex:
            self.dtype = "complex128"
        else:
            self.dtype = "float64"

        return GenericDiscreteBoundaryOperator(self)
Esempio n. 4
0
    def __init__(
        self,
        space,
        dual_space=None,
        fun=None,
        coefficients=None,
        projections=None,
        parameters=None,
    ):
        """
        Construct a grid function.

        A grid function can be initialized in three different ways.

        1. By providing a Python callable. Any Python callable of the
           following form is valid.::

                callable(x,n,domain_index,result)

           Here, x, n, and result are all numpy arrays. x contains the current
           evaluation point, n the associated outward normal direction and
           result is a numpy array that will store the result of the Python
           callable. The variable domain_index stores the index of the
           subdomain on which x lies (default 0). This makes it possible to
           define different functions for different subdomains.

           The following example defines input data that is the inner product
           of the coordinate x with the normal direction n.::

                fun(x,n,domain_index,result):
                    result[0] =  np.dot(x,n)

        2. By providing a vector of coefficients at the nodes. This is
           preferable if the coefficients of the data are coming from an
           external code.

        3. By providing a vector of projection data and a corresponding
           dual space.

        Parameters
        ----------
        space : bempp.api.space.Space
            The space over which the GridFunction is defined.
        dual_space : bempp.api.Space
            A representation of the dual space. If not specified
            then space == dual_space is assumed (optional).
        fun : callable
            A Python function from which the GridFunction is constructed
            (optional).
        coefficients : np.ndarray
            A 1-dimensional array with the coefficients of the GridFunction
            at the interpolatoin points of the space (optional).
        projections : np.ndarray
            A 1-dimensional array with the projections of the GridFunction
            onto a dual space (optional).
        parameters : bempp.api.ParameterList
            A ParameterList object used for the assembly of
            the GridFunction (optional).

        Notes
        -----
        * Only one of projections, coefficients, or fun is allowed as
          parameter.

        Examples
        --------
        To create a GridFunction from a Python callable my_fun use

        >>> grid_function = GridFunction(space, fun=my_fun)

        To create a GridFunction from a vector of coefficients coeffs use

        >>> grid_function = GridFunction(space,coefficients=coeffs)

        To create a GridFunction from a vector of projections proj use

        >>> grid_function = GridFunction(
                space,dual_space=dual_space, projections=proj)

        """
        from bempp.api.utils.helpers import assign_parameters
        from bempp.api.space.space import return_compatible_representation

        self._space = None
        self._dual_space = None
        self._coefficients = None
        self._grid_coefficients = None
        self._projections = None
        self._representation = None

        if dual_space is None:
            dual_space = space

        self._space, self._dual_space = space, dual_space

        # Now check that space and dual are defined over same grid
        # with the same normal directions. If one space is barycentric,
        # need to take this into account.

        comp_domain, comp_dual = return_compatible_representation(space, dual_space)
        self._comp_domain = comp_domain
        self._comp_dual = comp_dual

        if (
            not comp_domain.grid == comp_dual.grid
            or not _np.all(
                comp_domain.normal_multipliers == comp_dual.normal_multipliers
            )
        ):
            raise ValueError(
                "Space and dual space must be defined on the "
                + "same grid with same normal directions."
            )

        self._parameters = assign_parameters(parameters)

        if sum(1 for e in [fun, coefficients, projections] if e is not None) != 1:
            raise ValueError(
                "Exactly one of 'fun', 'coefficients' or 'projections' "
                + "must be nonzero."
            )

        if coefficients is not None:
            self._coefficients = coefficients
            self._representation = "primal"

        if projections is not None:
            self._projections = projections
            self._representation = "dual"

        if fun is not None:
            from bempp.api.integration.triangle_gauss import rule

            points, weights = rule(self._parameters.quadrature.regular)

            if fun.bempp_type == "real":
                dtype = "float64"
            else:
                dtype = "complex128"

            grid_projections = _np.zeros(comp_dual.grid_dof_count, dtype=dtype)

            # Create a Numba callable from the function

            _project_function(
                fun,
                comp_dual.grid.data,
                comp_dual.support_elements,
                comp_dual.local2global,
                comp_dual.local_multipliers,
                comp_dual.normal_multipliers,
                comp_dual.numba_evaluate,
                comp_dual.shapeset.evaluate,
                points,
                weights,
                comp_domain.codomain_dimension,
                grid_projections,
            )

            self._projections = comp_dual.dof_transformation.T @ grid_projections

            self._representation = "dual"
Esempio n. 5
0
    def _assemble(self):
        """Assemble the operator."""
        from bempp.api.space.space import return_compatible_representation
        from bempp.api.assembly.discrete_boundary_operator import (
            SparseDiscreteBoundaryOperator, )
        from bempp.api.integration.triangle_gauss import rule
        from scipy.sparse import coo_matrix

        import numpy as _np

        points, weights = rule(self._parameters.quadrature.regular)
        npoints = len(weights)

        comp_trial, comp_test, comp_fun = return_compatible_representation(
            self.domain, self.dual_to_range, self._grid_fun.space)

        grid = comp_trial.grid

        if self._mode == "component":
            op = _np.multiply
        elif self._mode == "inner":
            op = lambda x, y: _np.sum(
                x * y.reshape[:, _np.newaxis, :], axis=0, keepdims=True)

        elements = (set(comp_test.support_elements).intersection(
            set(comp_trial.support_elements)).intersection(
                set(comp_fun.support_elements)))

        elements = _np.flatnonzero(comp_trial.support * comp_test.support *
                                   comp_fun.support)

        number_of_elements = len(elements)
        nshape_trial = comp_trial.shapeset.number_of_shape_functions
        nshape_test = comp_test.shapeset.number_of_shape_functions
        nshape = nshape_trial * nshape_test

        data = _np.zeros(number_of_elements * nshape_trial * nshape_test)

        for index, elem_index in enumerate(elements):
            scale_vals = (self._grid_fun.evaluate(elem_index, points) *
                          weights * grid.integration_elements[index])
            domain_vals = comp_trial.evaluate(elem_index, points)
            trial_vals = op(domain_vals, scale_vals)
            test_vals = _np.conj(comp_test.evaluate(elem_index, points))
            res = _np.tensordot(test_vals, trial_vals, axes=([0, 2], [0, 2]))
            data[nshape * index:nshape * (1 + index)] = res.ravel()

        irange = _np.arange(nshape_test)
        jrange = _np.arange(nshape_trial)

        rows = _np.tile(_np.repeat(irange, nshape_trial),
                        number_of_elements) + _np.repeat(
                            elements * nshape_test, nshape)

        cols = _np.tile(_np.tile(jrange, nshape_test),
                        number_of_elements) + _np.repeat(
                            elements * nshape_trial, nshape)

        new_rows = comp_test.local2global.ravel()[rows]
        new_cols = comp_trial.local2global.ravel()[cols]

        nrows = comp_test.dof_transformation.shape[0]
        ncols = comp_trial.dof_transformation.shape[0]

        mat = coo_matrix((data, (new_rows, new_cols)),
                         shape=(nrows, ncols)).tocsr()

        if comp_trial.requires_dof_transformation:
            mat = mat @ self.domain.dof_transformation

        if comp_test.requires_dof_transformation:
            mat = self.dual_to_range.dof_transformation.T @ mat

        return SparseDiscreteBoundaryOperator(mat)