class FluxReconstructionOperator(OperatorBase): linear = True def __init__(self, subdomain, solution_space, grid, block_space, global_rt_space, subdomain_rt_spaces, lambda_xi, kappa): self.grid = grid self.block_space = block_space self.global_rt_space = global_rt_space self.subdomain_rt_spaces = subdomain_rt_spaces self.subdomain = subdomain self.neighborhood = grid.neighborhood_of(subdomain) self.lambda_xi = lambda_xi self.kappa = kappa self.source = solution_space.subspaces[subdomain] vector_type = solution_space.subspaces[0].vector_type self.range = BlockVectorSpace([ DuneXTVectorSpace(vector_type, subdomain_rt_spaces[ii].size(), 'LOCALRT_' + str(ii)) for ii in self.grid.neighborhood_of(subdomain) ], 'RT_{}'.format(subdomain)) def apply(self, U, mu=None): assert U in self.source result = self.range.empty(reserve=len(U)) local_subdomains, num_local_subdomains, num_global_subdomains = _get_subdomains( self.grid) for u_i in range(len(U)): subdomain_uhs_with_global_support = \ make_discrete_function( self.block_space, self.block_space.project_onto_neighborhood( [U._list[u_i].impl if nn == self.subdomain else Vector(self.block_space.local_space(nn).size(), 0.) for nn in range(num_global_subdomains)], [nn for nn in range(num_global_subdomains)] ) ) reconstructed_uh_kk_with_global_support = make_discrete_function( self.global_rt_space) apply_diffusive_flux_reconstruction_in_neighborhood( self.grid, self.subdomain, self.lambda_xi, self.kappa, subdomain_uhs_with_global_support, reconstructed_uh_kk_with_global_support) blocks = [ s.make_array([ self.subdomain_rt_spaces[ii].restrict( reconstructed_uh_kk_with_global_support.vector_copy()) ]) # NOQA for s, ii in zip(self.range.subspaces, self.grid.neighborhood_of(self.subdomain)) ] result.append(self.range.make_array(blocks)) return result
def test_block_identity_lincomb(): space = NumpyVectorSpace(10) space2 = BlockVectorSpace([space, space]) identity = BlockDiagonalOperator([IdentityOperator(space), IdentityOperator(space)]) identity2 = IdentityOperator(space2) ones = space.ones() ones2 = space2.make_array([ones, ones]) idid = identity + identity2 assert almost_equal(ones2 * 2, idid.apply(ones2)) assert almost_equal(ones2 * 2, idid.apply_adjoint(ones2)) assert almost_equal(ones2 * 0.5, idid.apply_inverse(ones2)) assert almost_equal(ones2 * 0.5, idid.apply_inverse_adjoint(ones2))
def test_blk_diag_apply_inverse_adjoint(): np.random.seed(0) A = np.random.randn(2, 2) B = np.random.randn(3, 3) C = spla.block_diag(A, B) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cop = BlockDiagonalOperator((Aop, Bop)) v1 = np.random.randn(2) v2 = np.random.randn(3) v = np.hstack((v1, v2)) v1va = NumpyVectorSpace.from_numpy(v1) v2va = NumpyVectorSpace.from_numpy(v2) vva = BlockVectorSpace.make_array((v1va, v2va)) wva = Cop.apply_inverse_adjoint(vva) w = np.hstack((wva.block(0).to_numpy(), wva.block(1).to_numpy())) assert np.allclose(spla.solve(C.T, v), w)
def test_blk_diag_apply_inverse(): np.random.seed(0) A = np.random.randn(2, 2) B = np.random.randn(3, 3) C = spla.block_diag(A, B) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cop = BlockDiagonalOperator((Aop, Bop)) v1 = np.random.randn(2) v2 = np.random.randn(3) v = np.hstack((v1, v2)) v1va = NumpyVectorSpace.from_data(v1) v2va = NumpyVectorSpace.from_data(v2) vva = BlockVectorSpace.make_array((v1va, v2va)) wva = Cop.apply_inverse(vva) w = np.hstack((wva.block(0).data, wva.block(1).data)) assert np.allclose(spla.solve(C, v), w)
def test_apply_adjoint(): np.random.seed(0) A11 = np.random.randn(2, 3) A12 = np.random.randn(2, 4) A21 = np.zeros((5, 3)) A22 = np.random.randn(5, 4) A = np.vstack((np.hstack((A11, A12)), np.hstack((A21, A22)))) A11op = NumpyMatrixOperator(A11) A12op = NumpyMatrixOperator(A12) A22op = NumpyMatrixOperator(A22) Aop = BlockOperator(np.array([[A11op, A12op], [None, A22op]])) v1 = np.random.randn(2) v2 = np.random.randn(5) v = np.hstack((v1, v2)) v1va = NumpyVectorSpace.from_numpy(v1) v2va = NumpyVectorSpace.from_numpy(v2) vva = BlockVectorSpace.make_array((v1va, v2va)) wva = Aop.apply_adjoint(vva) w = np.hstack((wva.block(0).to_numpy(), wva.block(1).to_numpy())) assert np.allclose(A.T.dot(v), w)
def test_apply_transpose(): np.random.seed(0) A11 = np.random.randn(2, 3) A12 = np.random.randn(2, 4) A21 = np.zeros((5, 3)) A22 = np.random.randn(5, 4) A = np.vstack((np.hstack((A11, A12)), np.hstack((A21, A22)))) A11op = NumpyMatrixOperator(A11) A12op = NumpyMatrixOperator(A12) A22op = NumpyMatrixOperator(A22) Aop = BlockOperator(np.array([[A11op, A12op], [None, A22op]])) v1 = np.random.randn(2) v2 = np.random.randn(5) v = np.hstack((v1, v2)) v1va = NumpyVectorSpace.from_data(v1) v2va = NumpyVectorSpace.from_data(v2) vva = BlockVectorSpace.make_array((v1va, v2va)) wva = Aop.apply_transpose(vva) w = np.hstack((wva.block(0).data, wva.block(1).data)) assert np.allclose(A.T.dot(v), w)
class BlockOperatorBase(OperatorBase): def _operators(self): """Iterator over operators.""" for (i, j) in np.ndindex(self.blocks.shape): yield self.blocks[i, j] def __init__(self, blocks): blocks = np.array(blocks) assert 1 <= blocks.ndim <= 2 if self.blocked_source and self.blocked_range: assert blocks.ndim == 2 elif self.blocked_source: if blocks.ndim == 1: blocks.shape = (1, len(blocks)) else: if blocks.ndim == 1: blocks.shape = (len(blocks), 1) self.blocks = blocks assert all( isinstance(op, OperatorInterface) or op is None for op in self._operators()) # check if every row/column contains at least one operator assert all( any(blocks[i, j] is not None for j in range(blocks.shape[1])) for i in range(blocks.shape[0])) assert all( any(blocks[i, j] is not None for i in range(blocks.shape[0])) for j in range(blocks.shape[1])) # find source/range spaces for every column/row source_spaces = [None for j in range(blocks.shape[1])] range_spaces = [None for i in range(blocks.shape[0])] for (i, j), op in np.ndenumerate(blocks): if op is not None: assert source_spaces[j] is None or op.source == source_spaces[j] source_spaces[j] = op.source assert range_spaces[i] is None or op.range == range_spaces[i] range_spaces[i] = op.range # turn Nones to ZeroOperators for (i, j) in np.ndindex(blocks.shape): if blocks[i, j] is None: self.blocks[i, j] = ZeroOperator(range_spaces[i], source_spaces[j]) self.source = BlockVectorSpace( source_spaces) if self.blocked_source else source_spaces[0] self.range = BlockVectorSpace( range_spaces) if self.blocked_range else range_spaces[0] self.num_source_blocks = len(source_spaces) self.num_range_blocks = len(range_spaces) self.linear = all(op.linear for op in self._operators()) self.build_parameter_type(*self._operators()) @property def H(self): return self.adjoint_type( np.vectorize(lambda op: op.H if op else None)(self.blocks.T)) def apply(self, U, mu=None): assert U in self.source V_blocks = [None for i in range(self.num_range_blocks)] for (i, j), op in np.ndenumerate(self.blocks): Vi = op.apply(U.block(j) if self.blocked_source else U, mu=mu) if V_blocks[i] is None: V_blocks[i] = Vi else: V_blocks[i] += Vi return self.range.make_array( V_blocks) if self.blocked_range else V_blocks[0] def apply_adjoint(self, V, mu=None): assert V in self.range U_blocks = [None for j in range(self.num_source_blocks)] for (i, j), op in np.ndenumerate(self.blocks): Uj = op.apply_adjoint(V.block(i) if self.blocked_range else V, mu=mu) if U_blocks[j] is None: U_blocks[j] = Uj else: U_blocks[j] += Uj return self.source.make_array( U_blocks) if self.blocked_source else U_blocks[0] def assemble(self, mu=None): blocks = np.empty(self.blocks.shape, dtype=object) for (i, j) in np.ndindex(self.blocks.shape): blocks[i, j] = self.blocks[i, j].assemble(mu) if np.all(blocks == self.blocks): return self else: return self.__class__(blocks) def as_range_array(self, mu=None): def process_row(row, space): R = space.empty() for op in row: if op is not None: R.append(op.as_range_array(mu)) return R subspaces = self.range.subspaces if self.blocked_range else [ self.range ] blocks = [ process_row(row, space) for row, space in zip(self.blocks, subspaces) ] return self.range.make_array( blocks) if self.blocked_range else blocks[0] def as_source_array(self, mu=None): def process_col(col, space): R = space.empty() for op in col: if op is not None: R.append(op.as_source_array(mu)) return R subspaces = self.source.subspaces if self.blocked_source else [ self.source ] blocks = [ process_col(col, space) for col, space in zip(self.blocks.T, subspaces) ] return self.source.make_array( blocks) if self.blocked_source else blocks[0]
class BlockOperator(OperatorBase): """A matrix of arbitrary |Operators|. This operator can be :meth:`applied <pymor.operators.interfaces.OperatorInterface.apply>` to a compatible :class:`BlockVectorArrays <pymor.vectorarrays.block.BlockVectorArray>`. Parameters ---------- blocks Two-dimensional array-like where each entry is an |Operator| or `None`. """ def _operators(self): """Iterator over operators.""" for (i, j) in np.ndindex(self._blocks.shape): yield self._blocks[i, j] def __init__(self, blocks): blocks = np.array(blocks) assert isinstance(blocks, np.ndarray) and blocks.ndim == 2 self._blocks = blocks assert all(isinstance(op, OperatorInterface) or op is None for op in self._operators()) # check if every row/column contains at least one operator assert all(any(blocks[i, j] is not None for j in range(blocks.shape[1])) for i in range(blocks.shape[0])) assert all(any(blocks[i, j] is not None for i in range(blocks.shape[0])) for j in range(blocks.shape[1])) # find source/range types for every column/row source_types = [None for j in range(blocks.shape[1])] range_types = [None for i in range(blocks.shape[0])] for (i, j), op in np.ndenumerate(blocks): if op is not None: assert source_types[j] is None or op.source == source_types[j] source_types[j] = op.source assert range_types[i] is None or op.range == range_types[i] range_types[i] = op.range # turn Nones to ZeroOperators for (i, j) in np.ndindex(blocks.shape): if blocks[i, j] is None: self._blocks[i, j] = ZeroOperator(range_types[i], source_types[j]) self.source = BlockVectorSpace(source_types) self.range = BlockVectorSpace(range_types) self.num_source_blocks = len(source_types) self.num_range_blocks = len(range_types) self.linear = all(op.linear for op in self._operators()) self.build_parameter_type(*self._operators()) @property def T(self): return type(self)(np.vectorize(lambda op: op.T if op else None)(self._blocks.T)) @classmethod def hstack(cls, operators): """Horizontal stacking of |Operators|. Parameters ---------- operators An iterable where each item is an |Operator| or `None`. """ blocks = np.array([[op for op in operators]]) return cls(blocks) @classmethod def vstack(cls, operators): """Vertical stacking of |Operators|. Parameters ---------- operators An iterable where each item is an |Operator| or `None`. """ blocks = np.array([[op] for op in operators]) return cls(blocks) def apply(self, U, mu=None): assert U in self.source V_blocks = [None for i in range(self.num_range_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Vi = op.apply(U.block(j), mu=mu) if V_blocks[i] is None: V_blocks[i] = Vi else: V_blocks[i] += Vi return self.range.make_array(V_blocks) def apply_transpose(self, V, mu=None): assert V in self.range U_blocks = [None for j in range(self.num_source_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Uj = op.apply_transpose(V.block(i), mu=mu) if U_blocks[j] is None: U_blocks[j] = Uj else: U_blocks[j] += Uj U = self.source.make_array(U_blocks) return U def assemble(self, mu=None): blocks = np.empty(self._blocks.shape, dtype=object) for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j].assemble(mu) if np.all(blocks == self._blocks): return self else: return self.__class__(blocks) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): assert operators[0] is self blocks = np.empty(self._blocks.shape, dtype=object) if len(operators) > 1: for (i, j) in np.ndindex(self._blocks.shape): operators_ij = [op._blocks[i, j] for op in operators] blocks[i, j] = operators_ij[0].assemble_lincomb(operators_ij, coefficients, solver_options=solver_options, name=name) if blocks[i, j] is None: return None return self.__class__(blocks) else: c = coefficients[0] if c == 1: return self for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j] * c return self.__class__(blocks)
class BlockOperatorBase(OperatorBase): def _operators(self): """Iterator over operators.""" for (i, j) in np.ndindex(self._blocks.shape): yield self._blocks[i, j] def __init__(self, blocks, source_id='STATE', range_id='STATE'): blocks = np.array(blocks) assert 1 <= blocks.ndim <= 2 if self.blocked_source and self.blocked_range: assert blocks.ndim == 2 elif self.blocked_source: if blocks.ndim == 1: blocks.shape = (1, len(blocks)) else: if blocks.ndim == 1: blocks.shape = (len(blocks), 1) self._blocks = blocks assert all(isinstance(op, OperatorInterface) or op is None for op in self._operators()) # check if every row/column contains at least one operator assert all(any(blocks[i, j] is not None for j in range(blocks.shape[1])) for i in range(blocks.shape[0])) assert all(any(blocks[i, j] is not None for i in range(blocks.shape[0])) for j in range(blocks.shape[1])) # find source/range spaces for every column/row source_spaces = [None for j in range(blocks.shape[1])] range_spaces = [None for i in range(blocks.shape[0])] for (i, j), op in np.ndenumerate(blocks): if op is not None: assert source_spaces[j] is None or op.source == source_spaces[j] source_spaces[j] = op.source assert range_spaces[i] is None or op.range == range_spaces[i] range_spaces[i] = op.range # turn Nones to ZeroOperators for (i, j) in np.ndindex(blocks.shape): if blocks[i, j] is None: self._blocks[i, j] = ZeroOperator(range_spaces[i], source_spaces[j]) self.source = BlockVectorSpace(source_spaces, id_=source_id) if self.blocked_source else source_spaces[0] self.range = BlockVectorSpace(range_spaces, id_=range_id) if self.blocked_range else range_spaces[0] self.num_source_blocks = len(source_spaces) self.num_range_blocks = len(range_spaces) self.linear = all(op.linear for op in self._operators()) self.build_parameter_type(*self._operators()) @property def H(self): return self.adjoint_type(np.vectorize(lambda op: op.H if op else None)(self._blocks.T)) def apply(self, U, mu=None): assert U in self.source V_blocks = [None for i in range(self.num_range_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Vi = op.apply(U.block(j) if self.blocked_source else U, mu=mu) if V_blocks[i] is None: V_blocks[i] = Vi else: V_blocks[i] += Vi return self.range.make_array(V_blocks) if self.blocked_range else V_blocks[0] def apply_adjoint(self, V, mu=None): assert V in self.range U_blocks = [None for j in range(self.num_source_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Uj = op.apply_adjoint(V.block(i) if self.blocked_range else V, mu=mu) if U_blocks[j] is None: U_blocks[j] = Uj else: U_blocks[j] += Uj return self.source.make_array(U_blocks) if self.blocked_source else U_blocks[0] def assemble(self, mu=None): blocks = np.empty(self._blocks.shape, dtype=object) for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j].assemble(mu) if np.all(blocks == self._blocks): return self else: return self.__class__(blocks) def _assemble_lincomb_preprocess_operators(self, operators): return [ BlockDiagonalOperator([IdentityOperator(s) for s in op.source.subspaces], source_id=op.source.id, range_id=op.range.id) if isinstance(op, IdentityOperator) else op for op in operators if not isinstance(op, ZeroOperator) ] def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): operators = self._assemble_lincomb_preprocess_operators(operators) if not all(isinstance(op, BlockOperatorBase) for op in operators): return None assert operators[0] is self blocks = np.empty(self._blocks.shape, dtype=object) if len(operators) > 1: for (i, j) in np.ndindex(self._blocks.shape): operators_ij = [op._blocks[i, j] for op in operators] blocks[i, j] = operators_ij[0].assemble_lincomb(operators_ij, coefficients, solver_options=solver_options, name=name) if blocks[i, j] is None: return None return self.__class__(blocks) else: c = coefficients[0] if c == 1: return self for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j] * c return self.__class__(blocks) def as_range_array(self, mu=None): def process_row(row, space): R = space.empty() for op in row: if op is not None: R.append(op.as_range_array(mu)) return R blocks = [process_row(row, space) for row, space in zip(self._blocks, self.range.subspaces)] return self.range.make_array(blocks) if self.blocked_range else blocks[0] def as_source_array(self, mu=None): def process_col(col, space): R = space.empty() for op in col: if op is not None: R.append(op.as_source_array(mu)) return R blocks = [process_col(col, space) for col, space in zip(self._blocks.T, self.source.subspaces)] return self.source.make_array(blocks) if self.blocked_source else blocks[0]
class BlockOperator(OperatorBase): """A matrix of arbitrary |Operators|. This operator can be :meth:`applied <pymor.operators.interfaces.OperatorInterface.apply>` to a compatible :class:`BlockVectorArrays <pymor.vectorarrays.block.BlockVectorArray>`. Parameters ---------- blocks Two-dimensional array-like where each entry is an |Operator| or `None`. """ def _operators(self): """Iterator over operators.""" for (i, j) in np.ndindex(self._blocks.shape): yield self._blocks[i, j] def __init__(self, blocks): blocks = np.array(blocks) assert isinstance(blocks, np.ndarray) and blocks.ndim == 2 self._blocks = blocks assert all( isinstance(op, OperatorInterface) or op is None for op in self._operators()) # check if every row/column contains at least one operator assert all( any(blocks[i, j] is not None for j in range(blocks.shape[1])) for i in range(blocks.shape[0])) assert all( any(blocks[i, j] is not None for i in range(blocks.shape[0])) for j in range(blocks.shape[1])) # find source/range types for every column/row source_types = [None for j in range(blocks.shape[1])] range_types = [None for i in range(blocks.shape[0])] for (i, j), op in np.ndenumerate(blocks): if op is not None: assert source_types[j] is None or op.source == source_types[j] source_types[j] = op.source assert range_types[i] is None or op.range == range_types[i] range_types[i] = op.range # turn Nones to ZeroOperators for (i, j) in np.ndindex(blocks.shape): if blocks[i, j] is None: self._blocks[i, j] = ZeroOperator(source_types[j], range_types[i]) self.source = BlockVectorSpace(source_types) self.range = BlockVectorSpace(range_types) self.num_source_blocks = len(source_types) self.num_range_blocks = len(range_types) self.linear = all(op.linear for op in self._operators()) self.build_parameter_type(*self._operators()) @property def T(self): return type(self)(np.vectorize(lambda op: op.T if op else None)( self._blocks.T)) @classmethod def hstack(cls, operators): """Horizontal stacking of |Operators|. Parameters ---------- operators An iterable where each item is an |Operator| or `None`. """ blocks = np.array([[op for op in operators]]) return cls(blocks) @classmethod def vstack(cls, operators): """Vertical stacking of |Operators|. Parameters ---------- operators An iterable where each item is an |Operator| or `None`. """ blocks = np.array([[op] for op in operators]) return cls(blocks) def apply(self, U, mu=None): assert U in self.source V_blocks = [None for i in range(self.num_range_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Vi = op.apply(U.block(j), mu=mu) if V_blocks[i] is None: V_blocks[i] = Vi else: V_blocks[i] += Vi return self.range.make_array(V_blocks) def apply_transpose(self, V, mu=None): assert V in self.range U_blocks = [None for j in range(self.num_source_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Uj = op.apply_transpose(V.block(i), mu=mu) if U_blocks[j] is None: U_blocks[j] = Uj else: U_blocks[j] += Uj U = self.source.make_array(U_blocks) return U def assemble(self, mu=None): blocks = np.empty(self._blocks.shape, dtype=object) for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j].assemble(mu) if np.all(blocks == self._blocks): return self else: return self.__class__(blocks) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): assert operators[0] is self blocks = np.empty(self._blocks.shape, dtype=object) if len(operators) > 1: for (i, j) in np.ndindex(self._blocks.shape): operators_ij = [op._blocks[i, j] for op in operators] blocks[i, j] = operators_ij[0].assemble_lincomb( operators_ij, coefficients, solver_options=solver_options, name=name) if blocks[i, j] is None: return None return self.__class__(blocks) else: c = coefficients[0] if c == 1: return self for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j] * c return self.__class__(blocks)
class BlockOperatorBase(OperatorBase): def _operators(self): """Iterator over operators.""" for (i, j) in np.ndindex(self._blocks.shape): yield self._blocks[i, j] def __init__(self, blocks): blocks = np.array(blocks) assert 1 <= blocks.ndim <= 2 if self.blocked_source and self.blocked_range: assert blocks.ndim == 2 elif self.blocked_source: if blocks.ndim == 1: blocks.shape = (1, len(blocks)) else: if blocks.ndim == 1: blocks.shape = (len(blocks), 1) self._blocks = blocks assert all(isinstance(op, OperatorInterface) or op is None for op in self._operators()) # check if every row/column contains at least one operator assert all(any(blocks[i, j] is not None for j in range(blocks.shape[1])) for i in range(blocks.shape[0])) assert all(any(blocks[i, j] is not None for i in range(blocks.shape[0])) for j in range(blocks.shape[1])) # find source/range spaces for every column/row source_spaces = [None for j in range(blocks.shape[1])] range_spaces = [None for i in range(blocks.shape[0])] for (i, j), op in np.ndenumerate(blocks): if op is not None: assert source_spaces[j] is None or op.source == source_spaces[j] source_spaces[j] = op.source assert range_spaces[i] is None or op.range == range_spaces[i] range_spaces[i] = op.range # turn Nones to ZeroOperators for (i, j) in np.ndindex(blocks.shape): if blocks[i, j] is None: self._blocks[i, j] = ZeroOperator(range_spaces[i], source_spaces[j]) self.source = BlockVectorSpace(source_spaces) if self.blocked_source else source_spaces[0] self.range = BlockVectorSpace(range_spaces) if self.blocked_range else range_spaces[0] self.num_source_blocks = len(source_spaces) self.num_range_blocks = len(range_spaces) self.linear = all(op.linear for op in self._operators()) self.build_parameter_type(*self._operators()) @property def H(self): return self.adjoint_type(np.vectorize(lambda op: op.H if op else None)(self._blocks.T)) def apply(self, U, mu=None): assert U in self.source V_blocks = [None for i in range(self.num_range_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Vi = op.apply(U.block(j) if self.blocked_source else U, mu=mu) if V_blocks[i] is None: V_blocks[i] = Vi else: V_blocks[i] += Vi return self.range.make_array(V_blocks) if self.blocked_range else V_blocks[0] def apply_adjoint(self, V, mu=None): assert V in self.range U_blocks = [None for j in range(self.num_source_blocks)] for (i, j), op in np.ndenumerate(self._blocks): Uj = op.apply_adjoint(V.block(i) if self.blocked_range else V, mu=mu) if U_blocks[j] is None: U_blocks[j] = Uj else: U_blocks[j] += Uj return self.source.make_array(U_blocks) if self.blocked_source else U_blocks[0] def assemble(self, mu=None): blocks = np.empty(self._blocks.shape, dtype=object) for (i, j) in np.ndindex(self._blocks.shape): blocks[i, j] = self._blocks[i, j].assemble(mu) if np.all(blocks == self._blocks): return self else: return self.__class__(blocks) def as_range_array(self, mu=None): def process_row(row, space): R = space.empty() for op in row: if op is not None: R.append(op.as_range_array(mu)) return R subspaces = self.range.subspaces if self.blocked_range else [self.range] blocks = [process_row(row, space) for row, space in zip(self._blocks, subspaces)] return self.range.make_array(blocks) if self.blocked_range else blocks[0] def as_source_array(self, mu=None): def process_col(col, space): R = space.empty() for op in col: if op is not None: R.append(op.as_source_array(mu)) return R subspaces = self.source.subspaces if self.blocked_source else [self.source] blocks = [process_col(col, space) for col, space in zip(self._blocks.T, subspaces)] return self.source.make_array(blocks) if self.blocked_source else blocks[0]