def __init__(self, operator, source_basis, range_basis, product=None, copy=True, name=None): assert isinstance(operator, OperatorInterface) assert source_basis is None and issubclass(operator.source.type, NumpyVectorArray) \ or source_basis in operator.source assert range_basis is None and issubclass(operator.range.type, NumpyVectorArray) \ or range_basis in operator.range assert product is None \ or (isinstance(product, OperatorInterface) and range_basis is not None and operator.range == product.source and product.range == product.source) self.build_parameter_type(inherits=(operator, )) self.source = NumpyVectorSpace( len(source_basis) if source_basis is not None else operator.source. dim) self.range = NumpyVectorSpace( len(range_basis) if range_basis is not None else operator.range.dim ) self.name = name self.operator = operator self.source_basis = source_basis.copy( ) if source_basis is not None and copy else source_basis self.range_basis = range_basis.copy( ) if range_basis is not None and copy else range_basis self.linear = operator.linear self.product = product
def __init__(self, matrix, name=None): assert matrix.ndim <= 2 if matrix.ndim == 1: matrix = np.reshape(matrix, (1, -1)) self.source = NumpyVectorSpace(matrix.shape[1]) self.range = NumpyVectorSpace(matrix.shape[0]) self.name = name self._matrix = matrix self.sparse = issparse(matrix) self.calculate_sid = hasattr(matrix, 'sid')
def __init__(self, array, transposed=False, copy=True, name=None): self._array = array.copy() if copy else array if transposed: self.source = array.space self.range = NumpyVectorSpace(len(array)) else: self.source = NumpyVectorSpace(len(array)) self.range = array.space self.transposed = transposed self.name = name
def __init__(self, mapping, dim_source=1, dim_range=1, linear=False, parameter_type=None, name=None): self.source = NumpyVectorSpace(dim_source) self.range = NumpyVectorSpace(dim_range) self.name = name self._mapping = mapping self.linear = linear if parameter_type is not None: self.build_parameter_type(parameter_type, local_global=True)
class VectorOperator(VectorArrayOperator): """Wrap a vector as a vector-like |Operator|. Given a vector `v` of dimension `d`, this class represents the operator :: op: R^1 ----> R^d x |---> x⋅v In particular :: VectorOperator(vector).as_vector() == vector Parameters ---------- vector |VectorArray| of length 1 containing the vector `v`. copy If `True`, store a copy of `vector` instead of `vector` itself. name Name of the operator. """ linear = True source = NumpyVectorSpace(1) def __init__(self, vector, copy=True, name=None): assert isinstance(vector, VectorArrayInterface) assert len(vector) == 1 super(VectorOperator, self).__init__(vector, transposed=False, copy=copy, name=name)
def projected(self, source_basis, range_basis, product=None, name=None): assert source_basis is None or source_basis in self.source assert range_basis is None or range_basis in self.range assert product is None or product.source == product.range == self.range if source_basis is not None and range_basis is not None: return NumpyMatrixOperator(np.zeros( (len(range_basis), len(source_basis))), name=self.name + '_projected') else: new_source = NumpyVectorSpace( len(source_basis)) if source_basis is not None else self.source new_range = NumpyVectorSpace( len(range_basis)) if range_basis is not None else self.source return ZeroOperator(new_source, new_range, name=self.name + '_projected')
def __init__(self, grid, function): assert function.dim_domain == grid.dim_outer assert function.shape_range == tuple() self.grid = grid self.function = function self.range = NumpyVectorSpace(grid.size(grid.dim)) self.build_parameter_type(inherits=(function,))
def projected(self, source_basis, range_basis, product=None, name=None): assert source_basis is None or source_basis in self.source assert range_basis is None or range_basis in self.range assert product is None or product.source == product.range == self.range if range_basis is not None: if product: projected_value = NumpyVectorArray(product.apply2( range_basis, self._value, pairwise=False).T, copy=False) else: projected_value = NumpyVectorArray(range_basis.dot( self._value, pairwise=False).T, copy=False) else: projected_value = self._value if source_basis is None: return ConstantOperator(projected_value, self.source, copy=False, name=self.name + '_projected') else: return ConstantOperator(projected_value, NumpyVectorSpace(len(source_basis)), copy=False, name=self.name + '_projected')
def __init__(self, grid, boundary_info, velocity_field, lxf_lambda=1.0, name=None): self.grid = grid self.boundary_info = boundary_info self.velocity_field = velocity_field self.lxf_lambda = lxf_lambda self.name = name self.build_parameter_type(inherits=(velocity_field,)) self.source = self.range = NumpyVectorSpace(grid.size(0))
def __init__(self, grid, boundary_info, dirichlet_clear_rows=True, dirichlet_clear_columns=False, dirichlet_clear_diag=False, name=None): assert grid.reference_element in {square} self.source = self.range = NumpyVectorSpace(grid.size(grid.dim)) self.grid = grid self.boundary_info = boundary_info self.dirichlet_clear_rows = dirichlet_clear_rows self.dirichlet_clear_columns = dirichlet_clear_columns self.dirichlet_clear_diag = dirichlet_clear_diag self.name = name
class GenericOperator(OperatorBase): source = range = NumpyVectorSpace(10) op = NumpyMatrixOperator(np.eye(10) * np.arange(1, 11)) linear = True def apply(self, U, ind=None, mu=None): return self.op.apply(U, ind=ind, mu=mu) def apply_adjoint(self, U, ind=None, mu=None): return self.op.apply_adjoint(U, ind=ind, mu=mu)
def __init__(self, grid, function, boundary_info=None, dirichlet_data=None, neumann_data=None, order=2, name=None): assert grid.reference_element(0) in {line, triangle} assert function.shape_range == tuple() self.source = NumpyVectorSpace(grid.size(grid.dim)) self.grid = grid self.boundary_info = boundary_info self.function = function self.dirichlet_data = dirichlet_data self.neumann_data = neumann_data self.order = order self.name = name self.build_parameter_type(inherits=(function, dirichlet_data, neumann_data))
def __init__(self, restricted_operator, interpolation_matrix, source_basis_dofs, projected_collateral_basis, triangular, name=None): self.source = NumpyVectorSpace(len(source_basis_dofs)) self.range = projected_collateral_basis.space self.linear = restricted_operator.linear self.build_parameter_type(inherits=(restricted_operator,)) self.restricted_operator = restricted_operator self.interpolation_matrix = interpolation_matrix self.source_basis_dofs = source_basis_dofs self.projected_collateral_basis = projected_collateral_basis self.triangular = triangular self.name = name or '{}_projected'.format(restricted_operator.name)
class VectorFunctional(VectorArrayOperator): """Wrap a vector as a linear |Functional|. Given a vector `v` of dimension `d`, this class represents the functional :: f: R^d ----> R^1 u |---> (u, v) where `( , )` denotes the scalar product given by `product`. In particular, if `product` is `None` :: VectorFunctional(vector).as_vector() == vector. If `product` is not none, we obtain :: VectorFunctional(vector).as_vector() == product.apply(vector). Parameters ---------- vector |VectorArray| of length 1 containing the vector `v`. product |Operator| representing the scalar product to use. copy If `True`, store a copy of `vector` instead of `vector` itself. name Name of the operator. """ linear = True range = NumpyVectorSpace(1) def __init__(self, vector, product=None, copy=True, name=None): assert isinstance(vector, VectorArrayInterface) assert len(vector) == 1 assert product is None or isinstance( product, OperatorInterface) and vector in product.source if product is None: super(VectorFunctional, self).__init__(vector, transposed=True, copy=copy, name=name) else: super(VectorFunctional, self).__init__(product.apply(vector), transposed=True, copy=False, name=name)
def __init__(self, grid, function=None, boundary_info=None, dirichlet_data=None, diffusion_function=None, diffusion_constant=None, neumann_data=None, order=1, name=None): assert function is None or function.shape_range == tuple() self.source = NumpyVectorSpace(grid.size(0)) self.grid = grid self.boundary_info = boundary_info self.function = function self.dirichlet_data = dirichlet_data self.diffusion_function = diffusion_function self.diffusion_constant = diffusion_constant self.neumann_data = neumann_data self.order = order self.name = name self.build_parameter_type(inherits=(function, dirichlet_data, diffusion_function, neumann_data))
def __init__(self, grid, boundary_info, diffusion_function=None, diffusion_constant=None, name=None): super(DiffusionOperator, self).__init__() assert isinstance(grid, AffineGridWithOrthogonalCentersInterface) assert diffusion_function is None \ or (isinstance(diffusion_function, FunctionInterface) and diffusion_function.dim_domain == grid.dim_outer and diffusion_function.shape_range == tuple()) self.grid = grid self.boundary_info = boundary_info self.diffusion_function = diffusion_function self.diffusion_constant = diffusion_constant self.name = name self.source = self.range = NumpyVectorSpace(grid.size(0)) if diffusion_function is not None: self.build_parameter_type(inherits=(diffusion_function,))
def __init__(self, grid, boundary_info, numerical_flux, dirichlet_data=None, name=None): assert dirichlet_data is None or isinstance(dirichlet_data, FunctionInterface) self.grid = grid self.boundary_info = boundary_info self.numerical_flux = numerical_flux self.dirichlet_data = dirichlet_data self.name = name if (isinstance(dirichlet_data, FunctionInterface) and boundary_info.has_dirichlet and not dirichlet_data.parametric): self._dirichlet_values = self.dirichlet_data(grid.centers(1)[boundary_info.dirichlet_boundaries(1)]) self._dirichlet_values = self._dirichlet_values.ravel() self._dirichlet_values_flux_shaped = self._dirichlet_values.reshape((-1, 1)) self.build_parameter_type(inherits=(numerical_flux, dirichlet_data)) self.source = self.range = NumpyVectorSpace(grid.size(0)) self.with_arguments = self.with_arguments.union('numerical_flux_{}'.format(arg) for arg in numerical_flux.with_arguments)
def __init__(self, grid, boundary_info, diffusion_function=None, diffusion_constant=None, dirichlet_clear_columns=False, dirichlet_clear_diag=False, name=None): assert grid.reference_element(0) in {square}, 'A square grid is expected!' assert diffusion_function is None \ or (isinstance(diffusion_function, FunctionInterface) and diffusion_function.dim_domain == grid.dim_outer and diffusion_function.shape_range == tuple() or diffusion_function.shape_range == (grid.dim_outer,) * 2) self.source = self.range = NumpyVectorSpace(grid.size(grid.dim)) self.grid = grid self.boundary_info = boundary_info self.diffusion_constant = diffusion_constant self.diffusion_function = diffusion_function self.dirichlet_clear_columns = dirichlet_clear_columns self.dirichlet_clear_diag = dirichlet_clear_diag self.name = name if diffusion_function is not None: self.build_parameter_type(inherits=(diffusion_function,))
class MonomOperator(OperatorBase): source = range = NumpyVectorSpace(1) type_source = type_range = NumpyVectorArray def __init__(self, order, monom=None): self.monom = monom if monom else Polynomial(np.identity(order + 1)[order]) assert isinstance(self.monom, Polynomial) self.order = order self.derivative = self.monom.deriv() self.linear = order == 1 def apply(self, U, ind=None, mu=None): return NumpyVectorArray(self.monom(U.data)) def jacobian(self, U, mu=None): return MonomOperator(self.order - 1, self.derivative) def apply_inverse(self, U, ind=None, mu=None, options=None): return NumpyVectorArray(1. / U.data)
class InterpolationOperator(NumpyMatrixBasedOperator): """Lagrange interpolation operator for continuous finite element spaces. Parameters ---------- grid The |Grid| on which to interpolate. function The |Function| to interpolate. """ source = NumpyVectorSpace(1) linear = True def __init__(self, grid, function): assert function.dim_domain == grid.dim_outer assert function.shape_range == tuple() self.grid = grid self.function = function self.range = NumpyVectorSpace(grid.size(grid.dim)) self.build_parameter_type(inherits=(function,)) def _assemble(self, mu=None): return self.function.evaluate(self.grid.centers(self.grid.dim), mu=mu).reshape((-1, 1))
def reduce_residual(operator, functional=None, RB=None, product=None, extends=None): """Generic reduced basis residual reductor. Given an operator and a functional, the concatenation of residual operator with the Riesz isomorphism is given by:: riesz_residual.apply(U, mu) == product.apply_inverse(operator.apply(U, mu) - functional.as_vector(mu)) This reductor determines a low-dimensional subspace of image of a reduced basis space under `riesz_residual`, computes an orthonormal basis `residual_range` of this range spaces and then returns the Petrov-Galerkin projection :: projected_riesz_residual === riesz_residual.projected(source_basis=RB, range_basis=residual_range) of the `riesz_residual` operator. Given an reduced basis coefficient vector `u`, the dual norm of the residual can then be computed as :: projected_riesz_residual.apply(u, mu).l2_norm() Moreover, a `reconstructor` is provided such that :: reconstructor.reconstruct(projected_riesz_residual.apply(u, mu)) == riesz_residual.apply(RB.lincomb(u), mu) Parameters ---------- operator See definition of `riesz_residual`. functional See definition of `riesz_residual`. If `None`, zero right-hand side is assumed. RB |VectorArray| containing a basis of the reduced space onto which to project. product Scalar product |Operator| w.r.t. which to compute the Riesz representatives. extends Set by :meth:`~pymor.algorithms.greedy.greedy` to the result of the last reduction in case the basis extension was `hierarchic`. Used to prevent re-computation of `residual_range` basis vectors already obtained from previous reductions. Returns ------- projected_riesz_residual See above. reconstructor See above. reduction_data Additional data produced by the reduction process. (Compare the `extends` parameter.) """ assert functional is None \ or functional.range == NumpyVectorSpace(1) and functional.source == operator.source and functional.linear assert RB is None or RB in operator.source assert product is None or product.source == product.range == operator.range assert extends is None or len(extends) == 3 logger = getLogger('pymor.reductors.reduce_residual') if RB is None: RB = operator.source.empty() if extends and isinstance(extends[0], NonProjectedResiudalOperator): extends = None if extends: residual_range = extends[1].RB.copy() residual_range_dims = list(extends[2]['residual_range_dims']) ind_range = range(extends[0].source.dim, len(RB)) else: residual_range = operator.range.empty() residual_range_dims = [] ind_range = range(-1, len(RB)) class CollectionError(Exception): def __init__(self, op): super(CollectionError, self).__init__() self.op = op def collect_operator_ranges(op, ind, residual_range): if isinstance(op, LincombOperator): for o in op.operators: collect_operator_ranges(o, ind, residual_range) elif isinstance(op, EmpiricalInterpolatedOperator): if hasattr(op, 'collateral_basis') and ind == -1: residual_range.append(op.collateral_basis) elif op.linear and not op.parametric: if ind >= 0: residual_range.append(op.apply(RB, ind=ind)) else: raise CollectionError(op) def collect_functional_ranges(op, residual_range): if isinstance(op, LincombOperator): for o in op.operators: collect_functional_ranges(o, residual_range) elif op.linear and not op.parametric: residual_range.append(op.as_vector()) else: raise CollectionError(op) for i in ind_range: logger.info('Computing residual range for basis vector {}...'.format(i)) new_residual_range = operator.range.empty() try: if i == -1: collect_functional_ranges(functional, new_residual_range) collect_operator_ranges(operator, i, new_residual_range) except CollectionError as e: logger.warn('Cannot compute range of {}. Evaluation will be slow.'.format(e.op)) operator = operator.projected(RB, None) return (NonProjectedResiudalOperator(operator, functional, product), NonProjectedReconstructor(product), {}) if product: logger.info('Computing Riesz representatives for basis vector {}...'.format(i)) new_residual_range = product.apply_inverse(new_residual_range) gram_schmidt_offset = len(residual_range) residual_range.append(new_residual_range) logger.info('Orthonormalizing ...') gram_schmidt(residual_range, offset=gram_schmidt_offset, product=product, copy=False) residual_range_dims.append(len(residual_range)) logger.info('Projecting ...') operator = operator.projected(RB, residual_range, product=None) # the product always cancels out. functional = functional.projected(residual_range, None, product=None) return (ResidualOperator(operator, functional), GenericRBReconstructor(residual_range), {'residual_range_dims': residual_range_dims})
def __init__(self, T, initial_data, operator, rhs=None, mass=None, time_stepper=None, num_values=None, products=None, operators=None, functionals=None, vector_operators=None, parameter_space=None, estimator=None, visualizer=None, cache_region='disk', name=None): functionals = functionals or {} operators = operators or {} vector_operators = vector_operators or {} if isinstance(initial_data, VectorArrayInterface): initial_data = VectorOperator(initial_data, name='initial_data') assert isinstance(initial_data, OperatorInterface) assert initial_data.source == NumpyVectorSpace(1) assert 'initial_data' not in vector_operators or initial_data == vector_operators[ 'initial_data'] assert isinstance(operator, OperatorInterface) assert operator.source == operator.range == initial_data.range assert 'operator' not in operators or operator == operators['operator'] assert rhs is None or isinstance(rhs, OperatorInterface) and rhs.linear assert rhs is None or rhs.source == operator.source and rhs.range.dim == 1 assert 'rhs' not in functionals or rhs == functionals['rhs'] assert mass is None or isinstance(mass, OperatorInterface) and mass.linear assert mass is None or mass.source == mass.range == operator.source assert 'mass' not in operators or mass == operators['mass'] assert isinstance(time_stepper, TimeStepperInterface) assert all(f.source == operator.source for f in functionals.values()) operators_with_operator_mass = {'operator': operator, 'mass': mass} operators_with_operator_mass.update(operators) functionals_with_rhs = {'rhs': rhs} if rhs else {} functionals_with_rhs.update(functionals) vector_operators_with_initial_data = {'initial_data': initial_data} vector_operators_with_initial_data.update(vector_operators) super(InstationaryDiscretization, self).__init__( operators=operators_with_operator_mass, functionals=functionals_with_rhs, vector_operators=vector_operators_with_initial_data, products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.T = T self.solution_space = operator.source self.initial_data = initial_data self.operator = operator self.rhs = rhs self.mass = mass self.time_stepper = time_stepper self.num_values = num_values self.build_parameter_type(inherits=(initial_data, operator, rhs, mass), provides={'_t': 0}) self.parameter_space = parameter_space if hasattr(time_stepper, 'nt'): self.with_arguments = self.with_arguments.union( {'time_stepper_nt'})
def __init__(self, grid, name=None): self.source = self.range = NumpyVectorSpace(grid.size(0)) self.grid = grid self.name = name
class L2ProductFunctional(NumpyMatrixBasedOperator): """Finite volume |Functional| representing the scalar product with an L2-|Function|. Additionally boundary conditions can be enforced by providing `dirichlet_data` and `neumann_data` functions. Parameters ---------- grid |Grid| for which to assemble the functional. function The |Function| with which to take the scalar product or `None`. boundary_info |BoundaryInfo| determining the Dirichlet and Neumann boundaries or `None`. If `None`, no boundary treatment is performed. dirichlet_data |Function| providing the Dirichlet boundary values. If `None`, constant-zero boundary is assumed. diffusion_function See :class:`DiffusionOperator`. Has to be specified in case `dirichlet_data` is given. diffusion_constant See :class:`DiffusionOperator`. Has to be specified in case `dirichlet_data` is given. neumann_data |Function| providing the Neumann boundary values. If `None`, constant-zero is assumed. order Order of the Gauss quadrature to use for numerical integration. name The name of the functional. """ range = NumpyVectorSpace(1) sparse = False def __init__(self, grid, function=None, boundary_info=None, dirichlet_data=None, diffusion_function=None, diffusion_constant=None, neumann_data=None, order=1, name=None): assert function is None or function.shape_range == tuple() self.source = NumpyVectorSpace(grid.size(0)) self.grid = grid self.boundary_info = boundary_info self.function = function self.dirichlet_data = dirichlet_data self.diffusion_function = diffusion_function self.diffusion_constant = diffusion_constant self.neumann_data = neumann_data self.order = order self.name = name self.build_parameter_type(inherits=(function, dirichlet_data, diffusion_function, neumann_data)) def _assemble(self, mu=None): g = self.grid bi = self.boundary_info if self.function is not None: # evaluate function at all quadrature points -> shape = (g.size(0), number of quadrature points, 1) F = self.function(g.quadrature_points(0, order=self.order), mu=mu) _, w = g.reference_element.quadrature(order=self.order) # integrate the products of the function with the shape functions on each element # -> shape = (g.size(0), number of shape functions) F_INTS = np.einsum('ei,e,i->e', F, g.integration_elements(0), w).ravel() else: F_INTS = np.zeros(g.size(0)) if bi is not None and (bi.has_dirichlet and self.dirichlet_data is not None or bi.has_neumann and self.neumann_data): centers = g.centers(1) superentities = g.superentities(1, 0) superentity_indices = g.superentity_indices(1, 0) SE_I0 = superentities[:, 0] VOLS = g.volumes(1) FLUXES = np.zeros(g.size(1)) if bi.has_dirichlet and self.dirichlet_data is not None: dirichlet_mask = bi.dirichlet_mask(1) SE_I0_D = SE_I0[dirichlet_mask] boundary_normals = g.unit_outer_normals()[SE_I0_D, superentity_indices[:, 0][dirichlet_mask]] BOUNDARY_DISTS = np.sum((centers[dirichlet_mask, :] - g.orthogonal_centers()[SE_I0_D, :]) * boundary_normals, axis=-1) DIRICHLET_FLUXES = VOLS[dirichlet_mask] * self.dirichlet_data(centers[dirichlet_mask]) / BOUNDARY_DISTS if self.diffusion_function is not None: DIRICHLET_FLUXES *= self.diffusion_function(centers[dirichlet_mask], mu=mu) if self.diffusion_constant is not None: DIRICHLET_FLUXES *= self.diffusion_constant FLUXES[dirichlet_mask] = DIRICHLET_FLUXES if bi.has_neumann and self.neumann_data is not None: neumann_mask = bi.neumann_mask(1) FLUXES[neumann_mask] -= VOLS[neumann_mask] * self.neumann_data(centers[neumann_mask]) F_INTS += np.bincount(SE_I0, weights=FLUXES, minlength=len(F_INTS)) F_INTS /= g.volumes(0) return F_INTS.reshape((1, -1))
def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) source_dofs = self.components[dofs] return IdentityOperator(NumpyVectorSpace( len(source_dofs))), source_dofs
def __init__(self, components, source, name=None): assert all(0 <= c < source.dim for c in components) self.components = np.array(components) self.range = NumpyVectorSpace(len(components)) self.source = source self.name = name
class L2ProductFunctionalP1(NumpyMatrixBasedOperator): """|Functional| representing the scalar product with an L2-|Function| for linear finite elements. Boundary treatment can be performed by providing `boundary_info` and `dirichlet_data`, in which case the DOFs corresponding to Dirichlet boundaries are set to the values provided by `dirichlet_data`. Neumann boundaries are handled by providing a `neumann_data` function. The current implementation works in one and two dimensions, but can be trivially extended to arbitrary dimensions. Parameters ---------- grid |Grid| for which to assemble the functional. function The |Function| with which to take the scalar product. boundary_info |BoundaryInfo| determining the Dirichlet and Neumann boundaries or `None`. If `None`, no boundary treatment is performed. dirichlet_data |Function| providing the Dirichlet boundary values. If `None`, constant-zero boundary is assumed. neumann_data |Function| providing the Neumann boundary values. If `None`, constant-zero is assumed. order Order of the Gauss quadrature to use for numerical integration. name The name of the functional. """ sparse = False range = NumpyVectorSpace(1) def __init__(self, grid, function, boundary_info=None, dirichlet_data=None, neumann_data=None, order=2, name=None): assert grid.reference_element(0) in {line, triangle} assert function.shape_range == tuple() self.source = NumpyVectorSpace(grid.size(grid.dim)) self.grid = grid self.boundary_info = boundary_info self.function = function self.dirichlet_data = dirichlet_data self.neumann_data = neumann_data self.order = order self.name = name self.build_parameter_type(inherits=(function, dirichlet_data, neumann_data)) def _assemble(self, mu=None): g = self.grid bi = self.boundary_info # evaluate function at all quadrature points -> shape = (g.size(0), number of quadrature points) F = self.function(g.quadrature_points(0, order=self.order), mu=mu) # evaluate the shape functions at the quadrature points on the reference # element -> shape = (number of shape functions, number of quadrature points) q, w = g.reference_element.quadrature(order=self.order) if g.dim == 1: SF = np.array((1 - q[..., 0], q[..., 0])) elif g.dim == 2: SF = np.array(((1 - np.sum(q, axis=-1)), q[..., 0], q[..., 1])) else: raise NotImplementedError # integrate the products of the function with the shape functions on each element # -> shape = (g.size(0), number of shape functions) SF_INTS = np.einsum('ei,pi,e,i->ep', F, SF, g.integration_elements(0), w).ravel() # map local DOFs to global DOFs # FIXME This implementation is horrible, find a better way! SF_I = g.subentities(0, g.dim).ravel() I = np.array(coo_matrix((SF_INTS, (np.zeros_like(SF_I), SF_I)), shape=(1, g.size(g.dim))).todense()).ravel() # boundary treatment if bi is not None and bi.has_neumann and self.neumann_data is not None: NI = bi.neumann_boundaries(1) if g.dim == 1: I[NI] -= self.neumann_data(g.centers(1)[NI]) else: F = -self.neumann_data(g.quadrature_points(1, order=self.order)[NI], mu=mu) q, w = line.quadrature(order=self.order) SF = np.squeeze(np.array([1 - q, q])) SF_INTS = np.einsum('ei,pi,e,i->ep', F, SF, g.integration_elements(1)[NI], w).ravel() SF_I = g.subentities(1, 2)[NI].ravel() I += np.array(coo_matrix((SF_INTS, (np.zeros_like(SF_I), SF_I)), shape=(1, g.size(g.dim))) .todense()).ravel() if bi is not None and bi.has_dirichlet: DI = bi.dirichlet_boundaries(g.dim) if self.dirichlet_data is not None: I[DI] = self.dirichlet_data(g.centers(g.dim)[DI], mu=mu) else: I[DI] = 0 return I.reshape((1, -1))