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)
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)
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)
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"
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)