def visualize(self, U, codim=2, **kwargs): """Visualize scalar data associated to the grid as a patch plot. Parameters ---------- U |NumPy array| of the data to visualize. If `U.dim == 2 and len(U) > 1`, the data is visualized as a time series of plots. Alternatively, a tuple of |Numpy arrays| can be provided, in which case a subplot is created for each entry of the tuple. The lengths of all arrays have to agree. codim The codimension of the entities the data in `U` is attached to (either 0 or 2). kwargs See :func:`~pymor.gui.qt.visualize_patch` """ from pymor.gui.qt import visualize_patch from pymor.vectorarrays.interfaces import VectorArrayInterface from pymor.vectorarrays.numpy import NumpyVectorSpace, NumpyVectorArray if isinstance(U, (np.ndarray, VectorArrayInterface)): U = (U,) assert all(isinstance(u, (np.ndarray, VectorArrayInterface)) for u in U) U = tuple(NumpyVectorSpace.make_array(u) if isinstance(u, np.ndarray) else u if isinstance(u, NumpyVectorArray) else NumpyVectorSpace.make_array(u.data) for u in U) bounding_box = kwargs.pop('bounding_box', self.domain) visualize_patch(self, U, codim=codim, bounding_box=bounding_box, **kwargs)
def projected(self, range_basis, source_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 len(self.interpolation_dofs) == 0: return ZeroOperator(self.source, self.range, self.name).projected(range_basis, source_basis, product, name) elif not hasattr(self, 'restricted_operator') or source_basis is None: return super().projected(range_basis, source_basis, product, name) else: name = name or self.name + '_projected' if range_basis is not None: if product is None: projected_collateral_basis = NumpyVectorSpace.make_array(self.collateral_basis.dot(range_basis), self.range.id) else: projected_collateral_basis = NumpyVectorSpace.make_array(product.apply2(self.collateral_basis, range_basis), self.range.id) else: projected_collateral_basis = self.collateral_basis return ProjectedEmpiciralInterpolatedOperator(self.restricted_operator, self.interpolation_matrix, NumpyVectorSpace.make_array(source_basis.components(self.source_dofs)), projected_collateral_basis, self.triangular, self.source.id, None, name)
def projected_to_subbasis(self, dim_range=None, dim_source=None, dim_collateral=None, name=None): assert dim_source is None or dim_source <= self.source.dim assert dim_range is None or dim_range <= self.range.dim assert dim_collateral is None or dim_collateral <= self.restricted_operator.range.dim if not isinstance(self.projected_collateral_basis.space, NumpyVectorSpace): raise NotImplementedError name = name or '{}_projected_to_subbasis'.format(self.name) interpolation_matrix = self.interpolation_matrix[:dim_collateral, :dim_collateral] if dim_collateral is not None: restricted_operator, source_dofs = self.restricted_operator.restricted(np.arange(dim_collateral)) else: restricted_operator = self.restricted_operator old_pcb = self.projected_collateral_basis projected_collateral_basis = NumpyVectorSpace.make_array(old_pcb.data[:dim_collateral, :dim_range], old_pcb.space.id) old_sbd = self.source_basis_dofs source_basis_dofs = NumpyVectorSpace.make_array(old_sbd.data[:dim_source]) if dim_collateral is None \ else NumpyVectorSpace.make_array(old_sbd.data[:dim_source, source_dofs]) return ProjectedEmpiciralInterpolatedOperator(restricted_operator, interpolation_matrix, source_basis_dofs, projected_collateral_basis, self.triangular, self.source.id, solver_options=self.solver_options, name=name)
def save(self): if not config.HAVE_PYVTK: msg = QMessageBox(QMessageBox.Critical, 'Error', 'VTK output disabled. Pleas install pyvtk.') msg.exec_() return filename = QFileDialog.getSaveFileName(self, 'Save as vtk file')[0] base_name = filename.split('.vtu')[0].split('.vtk')[0].split('.pvd')[0] if base_name: if len(self.U) == 1: write_vtk(self.grid, NumpyVectorSpace.make_array(self.U[0]), base_name, codim=self.codim) else: for i, u in enumerate(self.U): write_vtk(self.grid, NumpyVectorSpace.make_array(u), '{}-{}'.format(base_name, i), codim=self.codim)
def projected(self, range_basis, source_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 = NumpyVectorSpace.make_array(product.apply2(range_basis, self._value).T, self.range.id) else: projected_value = NumpyVectorSpace.make_array(range_basis.dot(self._value).T, self.range.id) else: projected_value = self._value if source_basis is None: return ConstantOperator(projected_value, self.source, name=self.name + '_projected') else: return ConstantOperator(projected_value, NumpyVectorSpace(len(source_basis), self.source.id), name=self.name + '_projected')
class ComponentProjection(OperatorBase): """|Operator| representing the projection of a |VectorArray| on some of its components. Parameters ---------- components List or 1D |NumPy array| of the indices of the vector :meth:`~pymor.vectorarrays.interfaces.VectorArrayInterface.components` that ar to be extracted by the operator. source Source |VectorSpace| of the operator. name Name of the operator. """ linear = True def __init__(self, components, source, name=None): assert all(0 <= c < source.dim for c in components) self.components = np.array(components, dtype=np.int32) self.range = NumpyVectorSpace(len(components)) self.source = source self.name = name def apply(self, U, mu=None): assert U in self.source return self.range.make_array(U.components(self.components)) 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 with_cb_dim(self, dim): assert dim <= self.restricted_operator.range.dim interpolation_matrix = self.interpolation_matrix[:dim, :dim] restricted_operator, source_dofs = self.restricted_operator.restricted(np.arange(dim)) old_pcb = self.projected_collateral_basis projected_collateral_basis = NumpyVectorSpace.make_array(old_pcb.to_numpy()[:dim, :]) old_sbd = self.source_basis_dofs source_basis_dofs = NumpyVectorSpace.make_array(old_sbd.to_numpy()[:, source_dofs]) return ProjectedEmpiciralInterpolatedOperator(restricted_operator, interpolation_matrix, source_basis_dofs, projected_collateral_basis, self.triangular, solver_options=self.solver_options, name=self.name)
def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) if not self.transposed: restricted_value = NumpyVectorSpace.make_array(self._array.components(dofs)) return VectorArrayOperator(restricted_value, False), np.arange(self.source.dim, dtype=np.int32) else: raise NotImplementedError
def action_ProjectedEmpiciralInterpolatedOperator(self, op): if not isinstance(op.projected_collateral_basis.space, NumpyVectorSpace): raise NotImplementedError restricted_operator = op.restricted_operator old_pcb = op.projected_collateral_basis projected_collateral_basis = NumpyVectorSpace.make_array(old_pcb.to_numpy()[:, :self.dim_range], old_pcb.space.id) old_sbd = op.source_basis_dofs source_basis_dofs = NumpyVectorSpace.make_array(old_sbd.to_numpy()[:self.dim_source]) return ProjectedEmpiciralInterpolatedOperator(restricted_operator, op.interpolation_matrix, source_basis_dofs, projected_collateral_basis, op.triangular, op.source.id, solver_options=op.solver_options, name=op.name)
def test_to_matrix(): np.random.seed(0) A = np.random.randn(2, 2) B = np.random.randn(3, 3) C = np.random.randn(3, 3) X = np.bmat([[np.eye(2) + A, np.zeros((2, 3))], [np.zeros((3, 2)), B.dot(C.T)]]) C = sps.csc_matrix(C) Aop = NumpyMatrixOperator(A) Bop = NumpyMatrixOperator(B) Cop = NumpyMatrixOperator(C) Xop = BlockDiagonalOperator([LincombOperator([IdentityOperator(NumpyVectorSpace(2)), Aop], [1, 1]), Concatenation(Bop, AdjointOperator(Cop))]) assert np.allclose(X, to_matrix(Xop)) assert np.allclose(X, to_matrix(Xop, format='csr').toarray()) np.random.seed(0) V = np.random.randn(10, 2) Vva = NumpyVectorSpace.make_array(V.T) Vop = VectorArrayOperator(Vva) assert np.allclose(V, to_matrix(Vop)) Vop = VectorArrayOperator(Vva, transposed=True) assert np.allclose(V, to_matrix(Vop).T)
def action_EmpiricalInterpolatedOperator(self, op): range_basis, source_basis, product = self.range_basis, self.source_basis, self.product if len(op.interpolation_dofs) == 0: return self.apply(ZeroOperator(op.range, op.source, op.name)) elif not hasattr(op, 'restricted_operator') or source_basis is None: raise RuleNotMatchingError('Has no restricted operator or source_basis is None') else: if range_basis is not None: projected_collateral_basis = NumpyVectorSpace.make_array(op.collateral_basis.inner(range_basis, product), op.range.id) else: projected_collateral_basis = op.collateral_basis return ProjectedEmpiciralInterpolatedOperator(op.restricted_operator, op.interpolation_matrix, NumpyVectorSpace.make_array(source_basis.dofs(op.source_dofs)), projected_collateral_basis, op.triangular, op.source.id, None, op.name)
def reconstruct(self, U): """Reconstruct high-dimensional vector from reduced vector `U`.""" assert isinstance(U.space, NumpyVectorSpace) UU = np.zeros((len(U), self.dim)) UU[:, :self.dim_subbasis] = U.data UU = NumpyVectorSpace.make_array(UU, U.space.id) if self.old_recontructor: return self.old_recontructor.reconstruct(UU) else: return UU
def apply(self, U, mu=None): mu = self.parse_parameter(mu) if len(self.interpolation_dofs) == 0: return self.range.zeros(len(U)) if hasattr(self, 'restricted_operator'): U_dofs = NumpyVectorSpace.make_array(U.dofs(self.source_dofs)) AU = self.restricted_operator.apply(U_dofs, mu=mu) else: AU = NumpyVectorSpace.make_array(self.operator.apply(U, mu=mu).dofs(self.interpolation_dofs)) try: if self.triangular: interpolation_coefficients = solve_triangular(self.interpolation_matrix, AU.to_numpy().T, lower=True, unit_diagonal=True).T else: interpolation_coefficients = solve(self.interpolation_matrix, AU.to_numpy().T).T except ValueError: # this exception occurs when AU contains NaNs ... interpolation_coefficients = np.empty((len(AU), len(self.collateral_basis))) + np.nan return self.collateral_basis.lincomb(interpolation_coefficients)
def test_to_matrix_VectorArrayOperator(): np.random.seed(0) V = np.random.randn(10, 2) Vva = NumpyVectorSpace.make_array(V.T) Vop = VectorArrayOperator(Vva) assert_type_and_allclose(V, Vop, 'dense') Vop = VectorArrayOperator(Vva, adjoint=True) assert_type_and_allclose(V.T, Vop, 'dense')
def action_ConstantOperator(self, op): range_basis, source_basis, product = self.range_basis, self.source_basis, self.product if range_basis is not None: projected_value = NumpyVectorSpace.make_array(range_basis.inner(op._value, product).T, op.range.id) else: projected_value = op._value if source_basis is None: return ConstantOperator(projected_value, op.source, name=op.name) else: return ConstantOperator(projected_value, NumpyVectorSpace(len(source_basis), op.source.id), name=op.name)
def action_ConstantOperator(self, op): range_basis, source_basis, product = self.range_basis, self.source_basis, self.product if range_basis is not None: projected_value = NumpyVectorSpace.make_array( range_basis.inner(op._value, product).T, op.range.id) else: projected_value = op._value if source_basis is None: return ConstantOperator(projected_value, op.source, name=op.name) else: return ConstantOperator(projected_value, NumpyVectorSpace(len(source_basis), op.source.id), name=op.name)
def action_ProjectedEmpiciralInterpolatedOperator(self, op): if not isinstance(op.projected_collateral_basis.space, NumpyVectorSpace): raise NotImplementedError restricted_operator = op.restricted_operator old_pcb = op.projected_collateral_basis projected_collateral_basis = NumpyVectorSpace.make_array( old_pcb.to_numpy()[:, :self.dim_range]) old_sbd = op.source_basis_dofs source_basis_dofs = NumpyVectorSpace.make_array( old_sbd.to_numpy()[:self.dim_source]) return ProjectedEmpiciralInterpolatedOperator( restricted_operator, op.interpolation_matrix, source_basis_dofs, projected_collateral_basis, op.triangular, solver_options=op.solver_options, name=op.name)
def with_cb_dim(self, dim): assert dim <= self.restricted_operator.range.dim interpolation_matrix = self.interpolation_matrix[:dim, :dim] restricted_operator, source_dofs = self.restricted_operator.restricted( np.arange(dim)) old_pcb = self.projected_collateral_basis projected_collateral_basis = NumpyVectorSpace.make_array( old_pcb.to_numpy()[:dim, :]) old_sbd = self.source_basis_dofs source_basis_dofs = NumpyVectorSpace.make_array( old_sbd.to_numpy()[:, source_dofs]) return ProjectedEmpiciralInterpolatedOperator( restricted_operator, interpolation_matrix, source_basis_dofs, projected_collateral_basis, self.triangular, solver_options=self.solver_options, name=self.name)
def apply_inverse(self, V, mu=None, least_squares=False): assert V in self.range assert not self.functional and not self.vector if V.dim == 0: if self.source.dim == 0 and least_squares: return self.source.make_array([np.zeros(0) for _ in range(len(V))]) else: raise InversionError op = NumpyMatrixOperator(self.matrix, solver_options=self.solver_options) return self.source.make_array([op.apply_inverse(NumpyVectorSpace.make_array(v._array), least_squares=least_squares).to_numpy().ravel() for v in V._list])
class PyClawModel(): def __init__(self, claw): self.claw = claw self.num_eqn = claw.solution.state.q.shape[0] self.mx = claw.solution.state.q.shape[1] self.my = claw.solution.state.q.shape[2] self.mz = claw.solution.state.q.shape[3] self.solution_space = NumpyVectorSpace(claw.solution.state.q.size) self.claw.start_frame = self.claw.solution.start_frame def solve_manual(self, mu=None): assert mu is None use_claw_run = True if use_claw_run: status = self.claw.run() else: import copy from clawpack.pyclaw.util import FrameCounter frame = FrameCounter() frame.set_counter(self.claw.start_frame) self.claw.frames.append(copy.deepcopy( self.claw.solution)) # initial solution self.claw.solution.write(frame, self.claw.outdir, self.claw.output_format, self.claw.output_file_prefix, self.claw.write_aux_always, self.claw.output_options) t = 0.0 t_increment = 10.0 for i in range(12): self.claw.solver.evolve_to_time(self.claw.solution, t + t_increment) frame.increment() self.claw.frames.append(copy.deepcopy(self.claw.solution)) self.claw.solution.write(frame, self.claw.outdir, self.claw.output_format, self.claw.output_file_prefix, self.claw.write_aux_always, self.claw.output_options) self.claw.solution._start_frame = len(self.claw.frames) t += t_increment dim = self.claw.frames[0].q.size print(dim) qr = [] for f in range(len(self.claw.frames)): qr.append(np.reshape(self.claw.frames[f].q, dim, order='F')) return self.solution_space.make_array(qr)
def estimate(self, U, mu, d): if len(U) > 1: raise NotImplementedError if not d.rhs.parametric: CR = np.ones(1) else: CR = np.array(d.rhs.evaluate_coefficients(mu)) if not d.operator.parametric: CO = np.ones(1) else: CO = np.array(d.operator.evaluate_coefficients(mu)) C = np.hstack((CR, np.dot(CO[..., np.newaxis], U.data).ravel())) est = self.norm(NumpyVectorSpace.make_array(C)) if self.coercivity_estimator: est /= self.coercivity_estimator(mu) return est
def estimate(self, U, mu, m): if len(U) > 1: raise NotImplementedError if not m.rhs.parametric: CR = np.ones(1) else: CR = np.array(m.rhs.evaluate_coefficients(mu)) if not m.operator.parametric: CO = np.ones(1) else: CO = np.array(m.operator.evaluate_coefficients(mu)) C = np.hstack((CR, np.dot(CO[..., np.newaxis], U.to_numpy()).ravel())) est = self.norm(NumpyVectorSpace.make_array(C)) if self.coercivity_estimator: est /= self.coercivity_estimator(mu) return est
def calculate_csis(gq, lq): spaces = gq["spaces"] for space in spaces: ldict = lq[space] T = ldict["solution_matrix_robin"] u_s = ldict["local_sol2"] product = ldict["omega_star_product"] norm = induced_norm(product) if norm(u_s).real < 1e-14: result = 1 else: x = np.linalg.lstsq(T, u_s.data.T, rcond=None)[0] y = T.dot(x) y_p = NumpyVectorSpace.make_array(y.T) y_p = y_p.lincomb(1 / norm(y_p).real) result = np.sqrt( norm(u_s).real[0]**2 / (norm(u_s).real[0]**2 - product.apply2(u_s, y_p).real[0][0]**2)) ldict["csi"] = result print("calculated csis")
def _localizedly_project_functional(self, op): if isinstance(op, LincombOperator): ops = [ self._localizedly_project_functional(foo) for foo in op.operators ] return LincombOperator(ops, coefficients=op.coefficients) assert op.linear and not op.parametric v = op.as_vector() def project_block(ids, basis): o = self.l.localize_vector_array(v, ids) if basis is None: return o.data else: return basis.dot(o).T mats = [ project_block(ids, basis) for ids, basis in zip(self.range_spaces, self.range_bases) ] return NumpyVectorSpace.make_array(np.concatenate(mats, axis=1))
class NumpyMatrixOperator(NumpyMatrixBasedOperator): """Wraps a 2D |NumPy Array| as an |Operator|. Parameters ---------- matrix The |NumPy array| which is to be wrapped. source_id The id of the operator's `source` |VectorSpace|. range_id The id of the operator's `range` |VectorSpace|. solver_options The |solver_options| for the operator. name Name of the operator. """ def __init__(self, matrix, source_id=None, range_id=None, solver_options=None, name=None): assert matrix.ndim <= 2 if matrix.ndim == 1: matrix = np.reshape(matrix, (1, -1)) try: matrix.setflags(write=False) # make numpy arrays read-only except AttributeError: pass self.source = NumpyVectorSpace(matrix.shape[1], source_id) self.range = NumpyVectorSpace(matrix.shape[0], range_id) self.solver_options = solver_options self.name = name self.matrix = matrix self.source_id = source_id self.range_id = range_id self.sparse = issparse(matrix) @classmethod def from_file(cls, path, key=None, source_id=None, range_id=None, solver_options=None, name=None): from pymor.tools.io import load_matrix matrix = load_matrix(path, key=key) return cls(matrix, solver_options=solver_options, source_id=source_id, range_id=range_id, name=name or key or path) @property def H(self): options = { 'inverse': self.solver_options.get('inverse_adjoint'), 'inverse_adjoint': self.solver_options.get('inverse') } if self.solver_options else None if self.sparse: adjoint_matrix = self.matrix.transpose(copy=False).conj(copy=False) elif np.isrealobj(self.matrix): adjoint_matrix = self.matrix.T else: adjoint_matrix = self.matrix.T.conj() return self.with_(matrix=adjoint_matrix, source_id=self.range_id, range_id=self.source_id, solver_options=options, name=self.name + '_adjoint') def _assemble(self, mu=None): pass def assemble(self, mu=None): return self def as_range_array(self, mu=None): return self.range.make_array(self.matrix.T.copy()) def as_source_array(self, mu=None): return self.source.make_array(self.matrix.copy()).conj() def apply(self, U, mu=None): assert U in self.source return self.range.make_array(self.matrix.dot(U.to_numpy().T).T) def apply_adjoint(self, V, mu=None): assert V in self.range return self.H.apply(V, mu=mu) @defaults('check_finite', 'default_sparse_solver_backend') def apply_inverse(self, V, mu=None, least_squares=False, check_finite=True, default_sparse_solver_backend='scipy'): """Apply the inverse operator. Parameters ---------- V |VectorArray| of vectors to which the inverse operator is applied. mu The |Parameter| for which to evaluate the inverse operator. least_squares If `True`, solve the least squares problem:: u = argmin ||op(u) - v||_2. Since for an invertible operator the least squares solution agrees with the result of the application of the inverse operator, setting this option should, in general, have no effect on the result for those operators. However, note that when no appropriate |solver_options| are set for the operator, most implementations will choose a least squares solver by default which may be undesirable. check_finite Test if solution only contains finite values. default_sparse_solver_backend Default sparse solver backend to use (scipy, pyamg, generic). Returns ------- |VectorArray| of the inverse operator evaluations. Raises ------ InversionError The operator could not be inverted. """ assert V in self.range if V.dim == 0: if self.source.dim == 0 or least_squares: return self.source.make_array( np.zeros((len(V), self.source.dim))) else: raise InversionError options = self.solver_options.get( 'inverse') if self.solver_options else None assert self.sparse or not options if self.sparse: if options: solver = options if isinstance(options, str) else options['type'] backend = solver.split('_')[0] else: backend = default_sparse_solver_backend if backend == 'scipy': from pymor.bindings.scipy import apply_inverse as apply_inverse_impl elif backend == 'pyamg': if not config.HAVE_PYAMG: raise RuntimeError('PyAMG support not enabled.') from pymor.bindings.pyamg import apply_inverse as apply_inverse_impl elif backend == 'generic': logger = getLogger('pymor.bindings.scipy.scipy_apply_inverse') logger.warning( 'You have selected a (potentially slow) generic solver for a NumPy matrix operator!' ) from pymor.algorithms.genericsolvers import apply_inverse as apply_inverse_impl else: raise NotImplementedError return apply_inverse_impl(self, V, options=options, least_squares=least_squares, check_finite=check_finite) else: if least_squares: try: R, _, _, _ = np.linalg.lstsq(self.matrix, V.to_numpy().T) except np.linalg.LinAlgError as e: raise InversionError(f'{str(type(e))}: {str(e)}') R = R.T else: try: R = np.linalg.solve(self.matrix, V.to_numpy().T).T except np.linalg.LinAlgError as e: raise InversionError(f'{str(type(e))}: {str(e)}') if check_finite: if not np.isfinite(np.sum(R)): raise InversionError('Result contains non-finite values') return self.source.make_array(R) def apply_inverse_adjoint(self, U, mu=None, least_squares=False): return self.H.apply_inverse(U, mu=mu, least_squares=least_squares) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): if not all( isinstance(op, (NumpyMatrixOperator, ZeroOperator, IdentityOperator)) for op in operators): return None common_mat_dtype = reduce( np.promote_types, (op.matrix.dtype for op in operators if hasattr(op, 'matrix'))) common_coef_dtype = reduce(np.promote_types, (type(c) for c in coefficients)) common_dtype = np.promote_types(common_mat_dtype, common_coef_dtype) if coefficients[0] == 1: matrix = operators[0].matrix.astype(common_dtype) else: matrix = operators[0].matrix * coefficients[0] if matrix.dtype != common_dtype: matrix = matrix.astype(common_dtype) for op, c in zip(operators[1:], coefficients[1:]): if type(op) is ZeroOperator: continue elif type(op) is IdentityOperator: if c.imag == 0: c = c.real if operators[0].sparse: try: matrix += (scipy.sparse.eye(matrix.shape[0]) * c) except NotImplementedError: matrix = matrix + (scipy.sparse.eye(matrix.shape[0]) * c) else: matrix += (np.eye(matrix.shape[0]) * c) elif c == 1: try: matrix += op.matrix except NotImplementedError: matrix = matrix + op.matrix elif c == -1: try: matrix -= op.matrix except NotImplementedError: matrix = matrix - op.matrix else: try: matrix += (op.matrix * c) except NotImplementedError: matrix = matrix + (op.matrix * c) return NumpyMatrixOperator(matrix, source_id=self.source.id, range_id=self.range.id, solver_options=solver_options) def __getstate__(self): if hasattr( self.matrix, 'factorization'): # remove unplicklable SuperLU factorization del self.matrix.factorization return self.__dict__
class NeuralNetworkModel(Model): """Class for models of stationary problems that use artificial neural networks. This class implements a |Model| that uses a neural network for solving. Parameters ---------- neural_network The neural network that approximates the mapping from parameter space to solution space. Should be an instance of :class:`~pymor.models.neural_network.FullyConnectedNN` with input size that matches the (total) number of parameters and output size equal to the dimension of the reduced space. output_functional |Operator| mapping a given solution to the model output. In many applications, this will be a |Functional|, i.e. an |Operator| mapping to scalars. This is not required, however. products A dict of inner product |Operators| defined on the discrete space the problem is posed on. For each product with key `'x'` a corresponding attribute `x_product`, as well as a norm method `x_norm` is added to the model. error_estimator An error estimator for the problem. This can be any object with an `estimate_error(U, mu, m)` method. If `error_estimator` is not `None`, an `estimate_error(U, mu)` method is added to the model which will call `error_estimator.estimate_error(U, mu, self)`. visualizer A visualizer for the problem. This can be any object with a `visualize(U, m, ...)` method. If `visualizer` is not `None`, a `visualize(U, *args, **kwargs)` method is added to the model which forwards its arguments to the visualizer's `visualize` method. name Name of the model. """ def __init__(self, neural_network, output_functional=None, products=None, error_estimator=None, visualizer=None, name=None): super().__init__(products=products, error_estimator=error_estimator, visualizer=visualizer, name=name) self.__auto_init(locals()) self.solution_space = NumpyVectorSpace(neural_network.output_dimension) self.linear = output_functional is None or output_functional.linear if output_functional is not None: self.output_space = output_functional.range def _solve(self, mu=None, return_output=False): if not self.logging_disabled: self.logger.info(f'Solving {self.name} for {mu} ...') # convert the parameter `mu` into a form that is usable in PyTorch converted_input = torch.from_numpy(mu.to_numpy()).double() # obtain (reduced) coordinates by forward pass of the parameter values through the neural network U = self.neural_network(converted_input).data.numpy() # convert plain numpy array to element of the actual solution space U = self.solution_space.make_array(U) if return_output: if self.output_functional is None: raise ValueError('Model has no output') return U, self.output_functional.apply(U, mu=mu) else: return U
class NumpyMatrixOperator(NumpyMatrixBasedOperator): """Wraps a 2D |NumPy Array| as an |Operator|. Parameters ---------- matrix The |NumPy array| which is to be wrapped. name Name of the operator. """ def __init__(self, matrix, source_id=None, range_id=None, solver_options=None, name=None): assert matrix.ndim <= 2 if matrix.ndim == 1: matrix = np.reshape(matrix, (1, -1)) try: matrix.setflags(write=False) # make numpy arrays read-only except AttributeError: pass self.source = NumpyVectorSpace(matrix.shape[1], source_id) self.range = NumpyVectorSpace(matrix.shape[0], range_id) self.solver_options = solver_options self.name = name self.matrix = matrix self.source_id = source_id self.range_id = range_id self.sparse = issparse(matrix) @classmethod def from_file(cls, path, key=None, source_id=None, range_id=None, solver_options=None, name=None): from pymor.tools.io import load_matrix matrix = load_matrix(path, key=key) return cls(matrix, solver_options=solver_options, source_id=source_id, range_id=range_id, name=name or key or path) @property def H(self): options = {'inverse': self.solver_options.get('inverse_adjoint'), 'inverse_adjoint': self.solver_options.get('inverse')} if self.solver_options else None if self.sparse: adjoint_matrix = self.matrix.transpose(copy=False).conj(copy=False) elif np.isrealobj(self.matrix): adjoint_matrix = self.matrix.T else: adjoint_matrix = self.matrix.T.conj() return self.with_(matrix=adjoint_matrix, source_id=self.range_id, range_id=self.source_id, solver_options=options, name=self.name + '_adjoint') def _assemble(self, mu=None): pass def assemble(self, mu=None): return self def as_range_array(self, mu=None): return self.range.make_array(self.matrix.T.copy()) def as_source_array(self, mu=None): return self.source.make_array(self.matrix.copy()).conj() def apply(self, U, mu=None): assert U in self.source return self.range.make_array(self.matrix.dot(U.to_numpy().T).T) def apply_adjoint(self, V, mu=None): assert V in self.range return self.H.apply(V, mu=mu) @defaults('check_finite', 'default_sparse_solver_backend') def apply_inverse(self, V, mu=None, least_squares=False, check_finite=True, default_sparse_solver_backend='scipy'): """Apply the inverse operator. Parameters ---------- V |VectorArray| of vectors to which the inverse operator is applied. mu The |Parameter| for which to evaluate the inverse operator. least_squares If `True`, solve the least squares problem:: u = argmin ||op(u) - v||_2. Since for an invertible operator the least squares solution agrees with the result of the application of the inverse operator, setting this option should, in general, have no effect on the result for those operators. However, note that when no appropriate |solver_options| are set for the operator, most implementations will choose a least squares solver by default which may be undesirable. check_finite Test if solution only containes finite values. default_solver Default sparse solver backend to use (scipy, pyamg, generic). Returns ------- |VectorArray| of the inverse operator evaluations. Raises ------ InversionError The operator could not be inverted. """ assert V in self.range if V.dim == 0: if self.source.dim == 0 or least_squares: return self.source.make_array(np.zeros((len(V), self.source.dim))) else: raise InversionError options = self.solver_options.get('inverse') if self.solver_options else None assert self.sparse or not options if self.sparse: if options: solver = options if isinstance(options, str) else options['type'] backend = solver.split('_')[0] else: backend = default_sparse_solver_backend if backend == 'scipy': from pymor.bindings.scipy import apply_inverse as apply_inverse_impl elif backend == 'pyamg': if not config.HAVE_PYAMG: raise RuntimeError('PyAMG support not enabled.') from pymor.bindings.pyamg import apply_inverse as apply_inverse_impl elif backend == 'generic': logger = getLogger('pymor.bindings.scipy.scipy_apply_inverse') logger.warning('You have selected a (potentially slow) generic solver for a NumPy matrix operator!') from pymor.algorithms.genericsolvers import apply_inverse as apply_inverse_impl else: raise NotImplementedError return apply_inverse_impl(self, V, options=options, least_squares=least_squares, check_finite=check_finite) else: if least_squares: try: R, _, _, _ = np.linalg.lstsq(self.matrix, V.to_numpy().T) except np.linalg.LinAlgError as e: raise InversionError('{}: {}'.format(str(type(e)), str(e))) R = R.T else: try: R = np.linalg.solve(self.matrix, V.to_numpy().T).T except np.linalg.LinAlgError as e: raise InversionError('{}: {}'.format(str(type(e)), str(e))) if check_finite: if not np.isfinite(np.sum(R)): raise InversionError('Result contains non-finite values') return self.source.make_array(R) def apply_inverse_adjoint(self, U, mu=None, least_squares=False): return self.H.apply_inverse(U, mu=mu, least_squares=least_squares) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): if not all(isinstance(op, (NumpyMatrixOperator, ZeroOperator, IdentityOperator)) for op in operators): return None common_mat_dtype = reduce(np.promote_types, (op.matrix.dtype for op in operators if hasattr(op, 'matrix'))) common_coef_dtype = reduce(np.promote_types, (type(c) for c in coefficients)) common_dtype = np.promote_types(common_mat_dtype, common_coef_dtype) if coefficients[0] == 1: matrix = operators[0].matrix.astype(common_dtype) else: matrix = operators[0].matrix * coefficients[0] if matrix.dtype != common_dtype: matrix = matrix.astype(common_dtype) for op, c in zip(operators[1:], coefficients[1:]): if type(op) is ZeroOperator: continue elif type(op) is IdentityOperator: if c.imag == 0: c = c.real if operators[0].sparse: try: matrix += (scipy.sparse.eye(matrix.shape[0]) * c) except NotImplementedError: matrix = matrix + (scipy.sparse.eye(matrix.shape[0]) * c) else: matrix += (np.eye(matrix.shape[0]) * c) elif c == 1: try: matrix += op.matrix except NotImplementedError: matrix = matrix + op.matrix elif c == -1: try: matrix -= op.matrix except NotImplementedError: matrix = matrix - op.matrix else: try: matrix += (op.matrix * c) except NotImplementedError: matrix = matrix + (op.matrix * c) return NumpyMatrixOperator(matrix, source_id=self.source.id, range_id=self.range.id, solver_options=solver_options) def __getstate__(self): if hasattr(self.matrix, 'factorization'): # remove unplicklable SuperLU factorization del self.matrix.factorization return self.__dict__
def transfer(va): range_solution = localizer.to_space( local_op.apply_inverse( -rhsop.apply(NumpyVectorSpace.make_array(va))), training_space, range_space) return pou[range_space](range_solution).data
def _random_array(dims, length, seed): np.random.seed(seed + mpi.rank) dim = dims[mpi.rank] if len(dims) > 1 else dims[0] array = NumpyVectorSpace.make_array(np.random.random((length, dim))) obj_id = mpi.manage_object(array) return obj_id
class ProjectedOperator(OperatorBase): """Generic |Operator| representing the projection of an |Operator| to a subspace. This operator is implemented as the concatenation of the linear combination with `source_basis`, application of the original `operator` and projection onto `range_basis`. As such, this operator can be used to obtain a reduced basis projection of any given |Operator|. However, no offline/online decomposition is performed, so this operator is mainly useful for testing before implementing offline/online decomposition for a specific application. This operator is instantiated in :func:`pymor.algorithms.projection.project` as a default implementation for parametric or nonlinear operators. Parameters ---------- operator The |Operator| to project. range_basis See :func:`pymor.algorithms.projection.project`. source_basis See :func:`pymor.algorithms.projection.project`. product See :func:`pymor.algorithms.projection.project`. solver_options The |solver_options| for the projected operator. """ linear = False def __init__(self, operator, range_basis, source_basis, product=None, solver_options=None): assert isinstance(operator, OperatorInterface) assert source_basis is None or source_basis in operator.source assert range_basis is None 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(operator) self.source = NumpyVectorSpace(len(source_basis)) if source_basis is not None else operator.source self.range = NumpyVectorSpace(len(range_basis)) if range_basis is not None else operator.range self.solver_options = solver_options self.name = operator.name self.operator = operator self.source_basis = source_basis.copy() if source_basis is not None else None self.range_basis = range_basis.copy() if range_basis is not None else None self.linear = operator.linear self.product = product @property def H(self): if self.product: return super().H else: options = {'inverse': self.solver_options.get('inverse_adjoint'), 'inverse_adjoint': self.solver_options.get('inverse')} if self.solver_options else None return ProjectedOperator(self.operator.H, self.source_basis, self.range_basis, solver_options=options) def apply(self, U, mu=None): mu = self.parse_parameter(mu) if self.source_basis is None: if self.range_basis is None: return self.operator.apply(U, mu=mu) elif self.product is None: return self.range.make_array(self.operator.apply2(self.range_basis, U, mu=mu).T) else: V = self.operator.apply(U, mu=mu) return self.range.make_array(self.product.apply2(V, self.range_basis)) else: UU = self.source_basis.lincomb(U.to_numpy()) if self.range_basis is None: return self.operator.apply(UU, mu=mu) elif self.product is None: return self.range.make_array(self.operator.apply2(self.range_basis, UU, mu=mu).T) else: V = self.operator.apply(UU, mu=mu) return self.range.make_array(self.product.apply2(V, self.range_basis)) def jacobian(self, U, mu=None): if self.linear: return self.assemble(mu) assert len(U) == 1 mu = self.parse_parameter(mu) if self.source_basis is None: J = self.operator.jacobian(U, mu=mu) else: J = self.operator.jacobian(self.source_basis.lincomb(U.to_numpy()), mu=mu) from pymor.algorithms.projection import project pop = project(J, range_basis=self.range_basis, source_basis=self.source_basis, product=self.product, name=self.name + '_jacobian') if self.solver_options: options = self.solver_options.get('jacobian') if options: pop = pop.with_(solver_options=options) return pop def assemble(self, mu=None): op = self.operator.assemble(mu=mu) if op == self.operator: # avoid infinite recursion in apply_inverse default impl return self from pymor.algorithms.projection import project pop = project(op, range_basis=self.range_basis, source_basis=self.source_basis, product=self.product) if self.solver_options: pop = pop.with_(solver_options=self.solver_options) return pop def apply_adjoint(self, V, mu=None): assert V in self.range if self.range_basis is not None: V = self.range_basis.lincomb(V.to_numpy()) U = self.operator.apply_adjoint(V, mu) if self.source_basis is not None: U = self.source.make_array(U.dot(self.source_basis)) return U
class NumpyMatrixOperator(NumpyMatrixBasedOperator): """Wraps a 2D |NumPy Array| as an |Operator|. Parameters ---------- matrix The |NumPy array| which is to be wrapped. name Name of the operator. """ def __init__(self, matrix, source_id=None, range_id=None, solver_options=None, name=None): assert matrix.ndim <= 2 if matrix.ndim == 1: matrix = np.reshape(matrix, (1, -1)) self.source = NumpyVectorSpace(matrix.shape[1], source_id) self.range = NumpyVectorSpace(matrix.shape[0], range_id) self.solver_options = solver_options self.name = name self._matrix = matrix self.source_id = source_id self.range_id = range_id self.sparse = issparse(matrix) @classmethod def from_file(cls, path, key=None, source_id=None, range_id=None, solver_options=None, name=None): from pymor.tools.io import load_matrix matrix = load_matrix(path, key=key) return cls(matrix, solver_options=solver_options, source_id=source_id, range_id=range_id, name=name or key or path) @property def T(self): options = {'inverse': self.solver_options.get('inverse_transpose'), 'inverse_transpose': self.solver_options.get('inverse')} if self.solver_options else None return self.with_(matrix=self._matrix.T, source_id=self.range_id, range_id=self.source_id, solver_options=options, name=self.name + '_transposed') def _assemble(self, mu=None): pass def assemble(self, mu=None): return self def as_range_array(self, mu=None): assert not self.sparse return self.range.make_array(self._matrix.T.copy()) def as_source_array(self, mu=None): assert not self.sparse return self.source.make_array(self._matrix.copy()) def apply(self, U, mu=None): assert U in self.source return self.range.make_array(self._matrix.dot(U.data.T).T) def apply_transpose(self, V, mu=None): assert V in self.range return self.source.make_array(self._matrix.T.dot(V.data.T).T) @defaults('check_finite', 'default_sparse_solver_backend', qualname='pymor.operators.numpy.NumpyMatrixOperator.apply_inverse') def apply_inverse(self, V, mu=None, least_squares=False, check_finite=True, default_sparse_solver_backend='scipy'): """Apply the inverse operator. Parameters ---------- V |VectorArray| of vectors to which the inverse operator is applied. mu The |Parameter| for which to evaluate the inverse operator. least_squares If `True`, solve the least squares problem:: u = argmin ||op(u) - v||_2. Since for an invertible operator the least squares solution agrees with the result of the application of the inverse operator, setting this option should, in general, have no effect on the result for those operators. However, note that when no appropriate |solver_options| are set for the operator, most implementations will choose a least squares solver by default which may be undesirable. check_finite Test if solution only containes finite values. default_solver Default sparse solver backend to use (scipy, pyamg, generic). Returns ------- |VectorArray| of the inverse operator evaluations. Raises ------ InversionError The operator could not be inverted. """ assert V in self.range if V.dim == 0: if self.source.dim == 0 or least_squares: return self.source.make_array(np.zeros((len(V), self.source.dim))) else: raise InversionError options = self.solver_options.get('inverse') if self.solver_options else None assert self.sparse or not options if self.sparse: if options: solver = options if isinstance(options, str) else options['type'] backend = solver.split('_')[0] else: backend = default_sparse_solver_backend if backend == 'scipy': from pymor.bindings.scipy import apply_inverse as apply_inverse_impl elif backend == 'pyamg': if not config.HAVE_PYAMG: raise RuntimeError('PyAMG support not enabled.') from pymor.bindings.pyamg import apply_inverse as apply_inverse_impl elif backend == 'generic': logger = getLogger('pymor.bindings.scipy.scipy_apply_inverse') logger.warn('You have selected a (potentially slow) generic solver for a NumPy matrix operator!') from pymor.algorithms.genericsolvers import apply_inverse as apply_inverse_impl else: raise NotImplementedError return apply_inverse_impl(self, V, options=options, least_squares=least_squares, check_finite=check_finite) else: if least_squares: try: R, _, _, _ = np.linalg.lstsq(self._matrix, V.data.T) except np.linalg.LinAlgError as e: raise InversionError('{}: {}'.format(str(type(e)), str(e))) R = R.T else: try: R = np.linalg.solve(self._matrix, V.data.T).T except np.linalg.LinAlgError as e: raise InversionError('{}: {}'.format(str(type(e)), str(e))) if check_finite: if not np.isfinite(np.sum(R)): raise InversionError('Result contains non-finite values') return self.source.make_array(R) def apply_inverse_transpose(self, U, mu=None, least_squares=False): options = {'inverse': self.solver_options.get('inverse_transpose') if self.solver_options else None} transpose_op = NumpyMatrixOperator(self._matrix.T, source_id=self.range.id, range_id=self.source.id, solver_options=options) return transpose_op.apply_inverse(U, mu=mu, least_squares=least_squares) def projected_to_subbasis(self, dim_range=None, dim_source=None, name=None): """Project the operator to a subbasis. The purpose of this method is to further project an operator that has been obtained through :meth:`~pymor.operators.interfaces.OperatorInterface.projected` to subbases of the original projection bases, i.e. :: op.projected(r_basis, s_basis, prod).projected_to_subbasis(dim_range, dim_source) should be the same as :: op.projected(r_basis[:dim_range], s_basis[:dim_source], prod) For a |NumpyMatrixOperator| this amounts to extracting the upper-left (dim_range, dim_source) corner of the matrix it wraps. Parameters ---------- dim_range Dimension of the range subbasis. dim_source Dimension of the source subbasis. name optional name for the returned |Operator| Returns ------- The projected |Operator|. """ assert dim_source is None or dim_source <= self.source.dim assert dim_range is None or dim_range <= self.range.dim name = name or '{}_projected_to_subbasis'.format(self.name) # copy instead of just slicing the matrix to ensure contiguous memory return NumpyMatrixOperator(self._matrix[:dim_range, :dim_source].copy(), source_id=self.source.id, range_id=self.range.id, solver_options=self.solver_options, name=name) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): if not all(isinstance(op, (NumpyMatrixOperator, ZeroOperator, IdentityOperator)) for op in operators): return None common_mat_dtype = reduce(np.promote_types, (op._matrix.dtype for op in operators if hasattr(op, '_matrix'))) common_coef_dtype = reduce(np.promote_types, (type(c.real if c.imag == 0 else c) for c in coefficients)) common_dtype = np.promote_types(common_mat_dtype, common_coef_dtype) if coefficients[0] == 1: matrix = operators[0]._matrix.astype(common_dtype) else: if coefficients[0].imag == 0: matrix = operators[0]._matrix * coefficients[0].real else: matrix = operators[0]._matrix * coefficients[0] if matrix.dtype != common_dtype: matrix = matrix.astype(common_dtype) for op, c in zip(operators[1:], coefficients[1:]): if type(op) is ZeroOperator: continue elif type(op) is IdentityOperator: if operators[0].sparse: try: matrix += (scipy.sparse.eye(matrix.shape[0]) * c) except NotImplementedError: matrix = matrix + (scipy.sparse.eye(matrix.shape[0]) * c) else: matrix += (np.eye(matrix.shape[0]) * c) elif c == 1: try: matrix += op._matrix except NotImplementedError: matrix = matrix + op._matrix elif c == -1: try: matrix -= op._matrix except NotImplementedError: matrix = matrix - op._matrix elif c.imag == 0: try: matrix += (op._matrix * c.real) except NotImplementedError: matrix = matrix + (op._matrix * c.real) else: try: matrix += (op._matrix * c) except NotImplementedError: matrix = matrix + (op._matrix * c) return NumpyMatrixOperator(matrix, source_id=self.source.id, range_id=self.range.id, solver_options=solver_options) def __getstate__(self): if hasattr(self._matrix, 'factorization'): # remove unplicklable SuperLU factorization del self._matrix.factorization return self.__dict__
def as_vector(self, copy=True): vec = self.matrix.AsVector().FV().NumPy() return NumpyVectorSpace.make_array(vec.copy() if copy else vec)
def solve(va): solution = local_op.apply_inverse( -rhsop.apply(NumpyVectorSpace.make_array(va))) return solution.data
class VectorArrayOperator(OperatorBase): """Wraps a |VectorArray| as an |Operator|. If `transposed` is `False`, the operator maps from `NumpyVectorSpace(len(array))` to `array.space` by forming linear combinations of the vectors in the array with given coefficient arrays. If `transposed == True`, the operator maps from `array.space` to `NumpyVectorSpace(len(array))` by forming the inner products of the argument with the vectors in the given array. Parameters ---------- array The |VectorArray| which is to be treated as an operator. transposed See description above. name The name of the operator. """ linear = True def __init__(self, array, transposed=False, space_id=None, name=None): self._array = array.copy() if transposed: self.source = array.space self.range = NumpyVectorSpace(len(array), space_id) else: self.source = NumpyVectorSpace(len(array), space_id) self.range = array.space self.transposed = transposed self.space_id = space_id self.name = name @property def T(self): return VectorArrayOperator(self._array, not self.transposed, self.space_id, self.name + '_transposed') def apply(self, U, mu=None): assert U in self.source if not self.transposed: return self._array.lincomb(U.data) else: return self.range.make_array(U.dot(self._array)) def apply_transpose(self, V, mu=None): assert V in self.range if not self.transposed: return self.source.make_array(self._array.dot(V).T) else: return self._array.lincomb(V.data) def apply_inverse_transpose(self, U, mu=None, least_squares=False): transpose_op = VectorArrayOperator(self._array, transposed=not self.transposed) return transpose_op.apply_inverse(U, mu=mu, least_squares=least_squares) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): transposed = operators[0].transposed if not all(isinstance(op, VectorArrayOperator) and op.transposed == transposed for op in operators): return None assert not solver_options if coefficients[0] == 1: array = operators[0]._array.copy() else: array = operators[0]._array * coefficients[0] for op, c in zip(operators[1:], coefficients[1:]): array.axpy(c, op._array) return VectorArrayOperator(array, transposed=transposed, name=name) def as_range_array(self, mu=None): if not self.transposed: return self._array.copy() else: super().as_range_array(mu) def as_source_array(self, mu=None): if self.transposed: return self._array.copy() else: super().as_source_array(mu) def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) if not self.transposed: restricted_value = NumpyVectorSpace.make_array(self._array.components(dofs)) return VectorArrayOperator(restricted_value, False), np.arange(self.source.dim, dtype=np.int32) else: raise NotImplementedError
def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) restricted_value = NumpyVectorSpace.make_array(self._value.components(dofs)) return ConstantOperator(restricted_value, NumpyVectorSpace(len(dofs))), dofs
def solve(va): g = localizer.to_space(NumpyVectorSpace.make_array(va), source_space, omega_star_space) solution = bilifo.apply_inverse(g) return solution.data
def localize_problem(p, coarse_grid_resolution, fine_grid_resolution, mus=None, calT=False, calTd=False, calQ=False, dof_codim=2, localization_codim=2, discretizer=None, m=1): assert coarse_grid_resolution > (m + 1) * 2 assert fine_grid_resolution % coarse_grid_resolution == 0 print("localizing problem") global_quantities = {} global_quantities["coarse_grid_resolution"] = coarse_grid_resolution local_quantities = {} # Diskretisierung auf dem feinen Gitter: diameter = 1. / fine_grid_resolution global_quantities["diameter"] = diameter if discretizer is None: discretizer = discretize_stationary_cg d, data = discretizer(p, diameter=diameter) grid = data["grid"] global_quantities["d"] = d global_quantities["data"] = data global_operator = d.operator.assemble(mus) global_quantities["op"] = global_operator global_quantities["op_not_assembled"] = d.operator global_rhs = d.rhs.assemble(mus) global_quantities["rhs"] = global_rhs op_fixed = copy.deepcopy(d.operator) # Skalierung der Dirichlet-Freiheitsgrade: try: dirichlet_dofs = data['boundary_info'].dirichlet_boundaries(dof_codim) for op in op_fixed.operators: op.assemble(mus).matrix[dirichlet_dofs, dirichlet_dofs] *= 1e5 # d.rhs.assemble(mus)._matrix[:, dirichlet_dofs] *= 1e5 except KeyError: pass global_operator_fixed = op_fixed.assemble(mus) global_quantities["op_fixed"] = global_operator_fixed global_quantities["op_fixed_not_assembled"] = op_fixed global_quantities["p"] = p try: dmask = data['boundary_info'].dirichlet_mask(dof_codim) except KeyError: dmask = None # Konstruktion der Teilraeume: subspaces, subspaces_per_codim = build_subspaces( *partition_any_grid(grid, num_intervals=(coarse_grid_resolution, coarse_grid_resolution), dmask=dmask, codim=dof_codim)) global_quantities["subspaces"] = subspaces global_quantities["subspaces_per_codim"] = subspaces_per_codim localizer = NumpyLocalizer(d.solution_space, subspaces['dofs']) global_quantities["localizer"] = localizer def create_m_patch(xspace, m): for i in range(m): space = xspace cspace = tuple( set(i for k in space if subspaces[k]['codim'] == 0 for i in subspaces[k]['cpatch'] if i not in space)) xspace = tuple( set(i for k in cspace if subspaces[k]['codim'] == 1 for i in subspaces[k]['env']) | set(space)) cxspace = tuple( set(i for k in xspace if subspaces[k]['codim'] == 0 for i in subspaces[k]['cpatch'] if i not in xspace)) return xspace, cxspace pou = localized_pou(subspaces, subspaces_per_codim, localizer, coarse_grid_resolution, grid, localization_codim, dof_codim) global_quantities["pou"] = pou spaces = [subspaces[s_id]["env"] for s_id in subspaces_per_codim[2]] global_quantities["spaces"] = spaces full_l2_product = d.products["l2"].assemble() full_h1_semi_product = d.products["h1_semi"].assemble() k_product = LincombOperator((full_h1_semi_product, full_l2_product), (1, mus["k"]**2)).assemble() global_quantities["full_norm"] = induced_norm(k_product) global_quantities["k_product"] = k_product for xpos in range(coarse_grid_resolution - 1): for ypos in range(coarse_grid_resolution - 1): # print "localizing..." s_id = subspaces_per_codim[localization_codim][ ypos + xpos * (coarse_grid_resolution - 1)] space = subspaces[s_id]["env"] ldict = {} local_quantities[space] = ldict ldict["pos"] = (xpos, ypos) # Konstruktion der lokalen Raeume: range_space = subspaces[s_id]["env"] ldict["range_space"] = range_space omega_space = tuple( sorted( set(subspaces[s_id]['env']) | set(subspaces[s_id]['cenv']))) ldict["omega_space"] = omega_space training_space, source_space = create_m_patch(range_space, m) # source_space = subspaces[s_id]["cxenv"] ldict["source_space"] = source_space # training_space = subspaces[s_id]["xenv"] ldict["training_space"] = training_space omega_star_space = tuple( sorted(set(training_space) | set(source_space))) ldict["omega_star_space"] = omega_star_space # lokale Shift-Loesung mit f(Dirichlet) local_op = localizer.localize_operator(global_operator, training_space, training_space) local_rhs = localizer.localize_operator(global_rhs, None, training_space) local_solution = local_op.apply_inverse(local_rhs.as_range_array()) local_solution = localizer.to_space(local_solution, training_space, range_space) local_solution = pou[range_space](local_solution) ldict["local_solution_dirichlet"] = local_solution # Dirichlet Transferoperator: rhsop = localizer.localize_operator(global_operator, training_space, source_space) transop_dirichlet = create_dirichlet_transfer( localizer, local_op, rhsop, source_space, training_space, range_space, pou) ldict["dirichlet_transfer"] = transop_dirichlet # subgrid xmin = max(0, xpos - m) xsize = min(xpos + 2 + m, coarse_grid_resolution) - xmin ymin = max(0, ypos - m) ysize = min(ypos + 2 + m, coarse_grid_resolution) - ymin ldict["posext"] = [(xmin / coarse_grid_resolution, ymin / coarse_grid_resolution), ((xmin + xsize) / coarse_grid_resolution, (ymin + ysize) / coarse_grid_resolution)] mysubgrid = getsubgrid(grid, xmin, ymin, coarse_grid_resolution, xsize=xsize, ysize=ysize) mysubbi = SubGridBoundaryInfo(mysubgrid, grid, data['boundary_info'], 'robin') ld, ldata = discretizer(p, grid=mysubgrid, boundary_info=mysubbi) lop = ld.operator.assemble(mus) # index conversion ndofsext = len(ldata['grid'].parent_indices(dof_codim)) global_dofnrsext = -100000000 * np.ones( shape=(d.solution_space.dim, )) global_dofnrsext[ldata['grid'].parent_indices( dof_codim)] = np.array(range(ndofsext)) lvecext = localizer.localize_vector_array( NumpyVectorSpace.make_array(global_dofnrsext), omega_star_space).data[0] # Robin Transferoperator: bilifo = NumpyMatrixOperator(lop.matrix[:, lvecext][lvecext, :]) transop_robin = create_robin_transfer(localizer, bilifo, source_space, omega_star_space, range_space, pou) ldict["robin_transfer"] = transop_robin # lokale Shift-Loesung mit f(Robin) lrhs = ld.rhs.assemble(mus) llrhs = NumpyMatrixOperator(lrhs.matrix[lvecext.astype(int)]) local_solution = bilifo.apply_inverse(llrhs.as_range_array()) ldict["local_sol2"] = local_solution local_solution = localizer.to_space(local_solution, omega_star_space, range_space) local_solution_pou = pou[range_space](local_solution) ldict["local_solution_robin"] = local_solution_pou if calT: # Transfer-Matrix: ldict["transfer_matrix_robin"] = transop_robin.as_range_array( ).data.T if calTd: # Transfer-Matrix: ldict[ "transfer_matrix_dirichlet"] = transop_dirichlet.as_range_array( ).data.T # Konstruktion der Produkte: range_k = localizer.localize_operator(k_product, range_space, range_space) omstar_k = LincombOperator((NumpyMatrixOperator( ld.products["h1_semi"].assemble().matrix[:, lvecext][lvecext, :]), NumpyMatrixOperator( ld.products["l2"].assemble( ).matrix[:, lvecext][lvecext, :])), (1, mus["k"]**2)).assemble() ldict["omega_star_product"] = omstar_k ldict["range_product"] = range_k if calQ: # Loesungs-Matrix: solution_op_robin = create_robin_solop(localizer, bilifo, source_space, omega_star_space) Q_r = solution_op_robin.as_range_array() ldict["solution_matrix_robin"] = Q_r.data.T source_Q_r_product = NumpyMatrixOperator( omstar_k.apply(Q_r).data.T) ldict["source_product"] = source_Q_r_product lproduct = localizer.localize_operator(full_l2_product, source_space, source_space) lmat = lproduct.matrix.tocoo() lmat.data = np.array([ 4. / 6. * diameter if (row == col) else diameter / 6. for row, col in zip(lmat.row, lmat.col) ]) ldict["source_product"] = NumpyMatrixOperator(lmat.tocsc().astype( np.cfloat)) return global_quantities, local_quantities
def localize_vector_array(self, va, ind): assert isinstance(va, NumpyVectorArray) subspace = self.join_spaces(ind) a = va._array[:, subspace] return NumpyVectorSpace.make_array(a)
class NumpyGenericOperator(OperatorBase): """Wraps an arbitrary Python function between |NumPy arrays| as an |Operator|. Parameters ---------- mapping The function to wrap. If `parameter_type` is `None`, the function is of the form `mapping(U)` and is expected to be vectorized. In particular:: mapping(U).shape == U.shape[:-1] + (dim_range,). If `parameter_type` is not `None`, the function has to have the signature `mapping(U, mu)`. adjoint_mapping The adjoint function to wrap. If `parameter_type` is `None`, the function is of the form `adjoint_mapping(U)` and is expected to be vectorized. In particular:: adjoint_mapping(U).shape == U.shape[:-1] + (dim_source,). If `parameter_type` is not `None`, the function has to have the signature `adjoint_mapping(U, mu)`. dim_source Dimension of the operator's source. dim_range Dimension of the operator's range. linear Set to `True` if the provided `mapping` and `adjoint_mapping` are linear. parameter_type The |ParameterType| of the |Parameters| the mapping accepts. solver_options The |solver_options| for the operator. name Name of the operator. """ def __init__(self, mapping, adjoint_mapping=None, dim_source=1, dim_range=1, linear=False, parameter_type=None, source_id=None, range_id=None, solver_options=None, name=None): self.source = NumpyVectorSpace(dim_source, source_id) self.range = NumpyVectorSpace(dim_range, range_id) self.solver_options = solver_options self.name = name self._mapping = mapping self._adjoint_mapping = adjoint_mapping self.linear = linear if parameter_type is not None: self.build_parameter_type(parameter_type) self.source_id = source_id # needed for with_ self.range_id = range_id def apply(self, U, mu=None): assert U in self.source if self.parametric: mu = self.parse_parameter(mu) return self.range.make_array(self._mapping(U.to_numpy(), mu=mu)) else: return self.range.make_array(self._mapping(U.to_numpy())) def apply_adjoint(self, V, mu=None): if self._adjoint_mapping is None: raise ValueError( 'NumpyGenericOperator: adjoint mapping was not defined.') assert V in self.range V = V.to_numpy() if self.parametric: mu = self.parse_parameter(mu) return self.source.make_array(self._adjoint_mapping(V, mu=mu)) else: return self.source.make_array(self._adjoint_mapping(V))
class NeuralNetworkInstationaryModel(Model): """Class for models of instationary problems that use artificial neural networks. This class implements a |Model| that uses a neural network for solving. Parameters ---------- T The final time T. nt The number of time steps. neural_network The neural network that approximates the mapping from parameter space to solution space. Should be an instance of :class:`~pymor.models.neural_network.FullyConnectedNN` with input size that matches the (total) number of parameters and output size equal to the dimension of the reduced space. parameters |Parameters| of the reduced order model (the same as used in the full-order model). output_functional |Operator| mapping a given solution to the model output. In many applications, this will be a |Functional|, i.e. an |Operator| mapping to scalars. This is not required, however. products A dict of inner product |Operators| defined on the discrete space the problem is posed on. For each product with key `'x'` a corresponding attribute `x_product`, as well as a norm method `x_norm` is added to the model. error_estimator An error estimator for the problem. This can be any object with an `estimate_error(U, mu, m)` method. If `error_estimator` is not `None`, an `estimate_error(U, mu)` method is added to the model which will call `error_estimator.estimate_error(U, mu, self)`. visualizer A visualizer for the problem. This can be any object with a `visualize(U, m, ...)` method. If `visualizer` is not `None`, a `visualize(U, *args, **kwargs)` method is added to the model which forwards its arguments to the visualizer's `visualize` method. name Name of the model. """ def __init__(self, T, nt, neural_network, parameters={}, output_functional=None, products=None, error_estimator=None, visualizer=None, name=None): super().__init__(products=products, error_estimator=error_estimator, visualizer=visualizer, name=name) self.__auto_init(locals()) self.solution_space = NumpyVectorSpace( neural_network.output_dimension) if output_functional is not None: self.dim_output = output_functional.range.dim def _compute_solution(self, mu=None, **kwargs): U = self.solution_space.empty(reserve=self.nt) dt = self.T / (self.nt - 1) t = 0. # iterate over time steps for i in range(self.nt): mu = mu.with_(t=t) # convert the parameter `mu` into a form that is usable in PyTorch converted_input = torch.from_numpy(mu.to_numpy()).double() # obtain (reduced) coordinates by forward pass of the parameter values through the neural network result_neural_network = self.neural_network( converted_input).data.numpy() # convert plain numpy array to element of the actual solution space U.append(self.solution_space.make_array(result_neural_network)) t += dt return U
def localize_operator(self, op, range_ind, source_ind): if isinstance(op, VectorArrayOperator): assert range_ind is None source_ind = tuple(source_ind) source_subspace = self.join_spaces(source_ind) a = NumpyVectorSpace.make_array(op._array.data[:, source_subspace]) result = VectorArrayOperator(a, transposed=op.transposed, name='{}-{}-{}'.format( op.name, source_ind, range_ind)) return result if isinstance(op, NumpyMatrixOperator): # special case for functionals if range_ind is None: source_ind = tuple(source_ind) source_subspace = self.join_spaces(source_ind) m = op.matrix[source_subspace] result = NumpyMatrixOperator(m, name='{}-{}-{}'.format( op.name, source_ind, range_ind)) return result source_ind = tuple(source_ind) range_ind = tuple(range_ind) op_id = getattr(op, 'sid', op.uid) identification_key = (op_id, source_ind, range_ind) if identification_key in self._resultcache: return self._resultcache[identification_key] if op_id not in self._non_zeros: # FIXME also handle dense case M = op.matrix.tocoo() incidences = np.empty(len(M.col), dtype=[('row', np.int32), ('col', np.int32)]) incidences['row'] = self.subspace_map[M.row] incidences['col'] = self.subspace_map[M.col] incidences = np.unique(incidences) self._non_zeros[op_id] = set(incidences.tolist()) non_zeros = self._non_zeros[op_id] if all((ri, si) not in non_zeros for si, ri in product(source_ind, range_ind)): return None source_subspace = self.join_spaces(source_ind) range_subspace = self.join_spaces(range_ind) if issparse(op.matrix): m = op.matrix.tocsc()[:, source_subspace][range_subspace, :] else: m = op.matrix[:, source_subspace][range_subspace, :] result = NumpyMatrixOperator(m, name='{}-{}-{}'.format( op.name, source_ind, range_ind)) self._resultcache[identification_key] = result return result elif isinstance(op, LincombOperator): ops = [ self.localize_operator(o, range_ind, source_ind) for o in op.operators ] return LincombOperator(ops, op.coefficients) else: print("op is ", op) raise NotImplementedError
class ProjectedOperator(OperatorBase): """Generic |Operator| representing the projection of an |Operator| to a subspace. This operator is implemented as the concatenation of the linear combination with `source_basis`, application of the original `operator` and projection onto `range_basis`. As such, this operator can be used to obtain a reduced basis projection of any given |Operator|. However, no offline/online decomposition is performed, so this operator is mainly useful for testing before implementing offline/online decomposition for a specific application. This operator is instantiated in the default implementation of :meth:`~pymor.operators.interfaces.OperatorInterface.projected` in :class:`OperatorBase` for parametric or nonlinear operators. Parameters ---------- operator The |Operator| to project. source_basis See :meth:`~pymor.operators.interfaces.OperatorInterface.projected`. range_basis See :meth:`~pymor.operators.interfaces.OperatorInterface.projected`. product See :meth:`~pymor.operators.interfaces.OperatorInterface.projected`. name Name of the projected operator. """ linear = False def __init__(self, operator, range_basis, source_basis, product=None, solver_options=None, name=None): assert isinstance(operator, OperatorInterface) assert source_basis is None or source_basis in operator.source assert range_basis is None 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(operator) self.source = NumpyVectorSpace(len(source_basis), operator.source.id) if source_basis is not None else operator.source self.range = NumpyVectorSpace(len(range_basis), operator.range.id) if range_basis is not None else operator.range self.solver_options = solver_options self.name = name self.operator = operator self.source_basis = source_basis.copy() if source_basis is not None else None self.range_basis = range_basis.copy() if range_basis is not None else None self.linear = operator.linear self.product = product @property def T(self): if self.product: return super().T else: options = {'inverse': self.solver_options.get('inverse_transpose'), 'inverse_transpose': self.solver_options.get('inverse')} if self.solver_options else None return ProjectedOperator(self.operator.T, self.source_basis, self.range_basis, solver_options=options, name=self.name + '_transposed') def apply(self, U, mu=None): mu = self.parse_parameter(mu) if self.source_basis is None: if self.range_basis is None: return self.operator.apply(U, mu=mu) elif self.product is None: return self.range.make_array(self.operator.apply2(self.range_basis, U, mu=mu).T) else: V = self.operator.apply(U, mu=mu) return self.range.make_array(self.product.apply2(V, self.range_basis)) else: UU = self.source_basis.lincomb(U.data) if self.range_basis is None: return self.operator.apply(UU, mu=mu) elif self.product is None: return self.range.make_array(self.operator.apply2(self.range_basis, UU, mu=mu).T) else: V = self.operator.apply(UU, mu=mu) return self.range.make_array(self.product.apply2(V, self.range_basis)) def projected_to_subbasis(self, dim_range=None, dim_source=None, name=None): """See :meth:`NumpyMatrixOperator.projected_to_subbasis`.""" assert dim_source is None or dim_source <= self.source.dim assert dim_range is None or dim_range <= self.range.dim assert dim_source is None or self.source_basis is not None, 'not implemented' assert dim_range is None or self.range_basis is not None, 'not implemented' name = name or '{}_projected_to_subbasis'.format(self.name) source_basis = self.source_basis if dim_source is None \ else self.source_basis[:dim_source] range_basis = self.range_basis if dim_range is None \ else self.range_basis[:dim_range] return ProjectedOperator(self.operator, range_basis, source_basis, product=None, solver_options=self.solver_options, name=name) def jacobian(self, U, mu=None): if self.linear: return self.assemble(mu) assert len(U) == 1 mu = self.parse_parameter(mu) if self.source_basis is None: J = self.operator.jacobian(U, mu=mu) else: J = self.operator.jacobian(self.source_basis.lincomb(U.data), mu=mu) pop = J.projected(range_basis=self.range_basis, source_basis=self.source_basis, product=self.product, name=self.name + '_jacobian') if self.solver_options: options = self.solver_options.get('jacobian') if options: pop = pop.with_(solver_options=options) return pop def assemble(self, mu=None): op = self.operator.assemble(mu=mu) pop = op.projected(range_basis=self.range_basis, source_basis=self.source_basis, product=self.product, name=self.name + '_assembled') if self.solver_options: pop = pop.with_(solver_options=self.solver_options) return pop
def discrete_multiply(va, function): assert len(function.shape) == 1 return NumpyVectorSpace.make_array(va.data * function)
def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) restricted_value = NumpyVectorSpace.make_array(self._value.dofs(dofs)) return ConstantOperator(restricted_value, NumpyVectorSpace(len(dofs))), dofs
class NumpyGenericOperator(OperatorBase): """Wraps an arbitrary Python function between |NumPy arrays| as a an |Operator|. Parameters ---------- mapping The function to wrap. If `parameter_type` is `None`, the function is of the form `mapping(U)` and is expected to be vectorized. In particular:: mapping(U).shape == U.shape[:-1] + (dim_range,). If `parameter_type` is not `None`, the function has to have the signature `mapping(U, mu)`. adjoint_mapping The adjoint function to wrap. If `parameter_type` is `None`, the function is of the form `adjoint_mapping(U)` and is expected to be vectorized. In particular:: adjoint_mapping(U).shape == U.shape[:-1] + (dim_source,). If `parameter_type` is not `None`, the function has to have the signature `adjoint_mapping(U, mu)`. dim_source Dimension of the operator's source. dim_range Dimension of the operator's range. linear Set to `True` if the provided `mapping` and `adjoint_mapping` are linear. parameter_type The |ParameterType| of the |Parameters| the mapping accepts. name Name of the operator. """ def __init__(self, mapping, adjoint_mapping=None, dim_source=1, dim_range=1, linear=False, parameter_type=None, source_id=None, range_id=None, solver_options=None, name=None): self.source = NumpyVectorSpace(dim_source, source_id) self.range = NumpyVectorSpace(dim_range, range_id) self.solver_options = solver_options self.name = name self._mapping = mapping self._adjoint_mapping = adjoint_mapping self.linear = linear if parameter_type is not None: self.build_parameter_type(parameter_type) self.source_id = source_id # needed for with_ self.range_id = range_id def apply(self, U, mu=None): assert U in self.source if self.parametric: mu = self.parse_parameter(mu) return self.range.make_array(self._mapping(U.to_numpy(), mu=mu)) else: return self.range.make_array(self._mapping(U.to_numpy())) def apply_adjoint(self, V, mu=None): if self._adjoint_mapping is None: raise ValueError('NumpyGenericOperator: adjoint mapping was not defined.') assert V in self.range V = V.to_numpy() if self.parametric: mu = self.parse_parameter(mu) return self.source.make_array(self._adjoint_mapping(V, mu=mu)) else: return self.source.make_array(self._adjoint_mapping(V))
class VectorArrayOperator(OperatorBase): """Wraps a |VectorArray| as an |Operator|. If `adjoint` is `False`, the operator maps from `NumpyVectorSpace(len(array))` to `array.space` by forming linear combinations of the vectors in the array with given coefficient arrays. If `adjoint == True`, the operator maps from `array.space` to `NumpyVectorSpace(len(array))` by forming the inner products of the argument with the vectors in the given array. Parameters ---------- array The |VectorArray| which is to be treated as an operator. adjoint See description above. space_id Id of the `source` (`range`) |VectorSpace| in case `adjoint` is `False` (`True`). name The name of the operator. """ linear = True def __init__(self, array, adjoint=False, space_id=None, name=None): self._array = array.copy() if adjoint: self.source = array.space self.range = NumpyVectorSpace(len(array), space_id) else: self.source = NumpyVectorSpace(len(array), space_id) self.range = array.space self.adjoint = adjoint self.space_id = space_id self.name = name @property def H(self): return VectorArrayOperator(self._array, not self.adjoint, self.space_id, self.name + '_adjoint') def apply(self, U, mu=None): assert U in self.source if not self.adjoint: return self._array.lincomb(U.to_numpy()) else: return self.range.make_array(self._array.dot(U).T) def apply_adjoint(self, V, mu=None): assert V in self.range if not self.adjoint: return self.source.make_array(self._array.dot(V).T) else: return self._array.lincomb(V.to_numpy()) def apply_inverse_adjoint(self, U, mu=None, least_squares=False): adjoint_op = VectorArrayOperator(self._array, adjoint=not self.adjoint) return adjoint_op.apply_inverse(U, mu=mu, least_squares=least_squares) def as_range_array(self, mu=None): if not self.adjoint: return self._array.copy() else: return super().as_range_array(mu) def as_source_array(self, mu=None): if self.adjoint: return self._array.copy() else: return super().as_source_array(mu) def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) if not self.adjoint: restricted_value = NumpyVectorSpace.make_array( self._array.dofs(dofs)) return VectorArrayOperator(restricted_value, False), np.arange(self.source.dim, dtype=np.int32) else: raise NotImplementedError
p = EllipticProblem(domain=RectDomain([[0, 0], [1, 1]]), diffusion_functions=(ConstantFunction(1., dim_domain=2), ), diffusion_functionals=(1., ), rhs=ConstantFunction(1., dim_domain=2)) d, data = discretize_elliptic_cg(p, diameter=0.01) grid = data["grid"] subspaces, subspaces_per_codim = build_subspaces(*partition_any_grid( grid, num_intervals=(coarse_grid_resolution, coarse_grid_resolution))) localizer = NumpyLocalizer(d.solution_space, subspaces['dofs']) images = d.solution_space.empty() fdict = localized_pou(subspaces, subspaces_per_codim, localizer, coarse_grid_resolution, grid) for space in sorted(fdict): lvec = localizer.localize_vector_array( NumpyVectorSpace.make_array(np.ones(d.solution_space.dim)), space) lvec = fdict[space](lvec) gvec = localizer.globalize_vector_array(lvec, space) images.append(gvec) sum = d.solution_space.zeros() for i in range(len(images)): sum += images.copy(ind=i) d.visualize(images) assert np.abs(np.max(sum.data) - 1) < 1e-12 assert np.abs(np.min(sum.data) - 1) < 1e-12
class VectorArrayOperator(OperatorBase): """Wraps a |VectorArray| as an |Operator|. If `transposed` is `False`, the operator maps from `NumpyVectorSpace(len(array))` to `array.space` by forming linear combinations of the vectors in the array with given coefficient arrays. If `transposed == True`, the operator maps from `array.space` to `NumpyVectorSpace(len(array))` by forming the inner products of the argument with the vectors in the given array. Parameters ---------- array The |VectorArray| which is to be treated as an operator. transposed See description above. name The name of the operator. """ linear = True def __init__(self, array, transposed=False, space_id=None, name=None): self._array = array.copy() if transposed: self.source = array.space self.range = NumpyVectorSpace(len(array), space_id) else: self.source = NumpyVectorSpace(len(array), space_id) self.range = array.space self.transposed = transposed self.space_id = space_id self.name = name @property def T(self): return VectorArrayOperator(self._array, not self.transposed, self.space_id, self.name + '_transposed') def apply(self, U, mu=None): assert U in self.source if not self.transposed: return self._array.lincomb(U.data) else: return self.range.make_array(U.dot(self._array)) def apply_transpose(self, V, mu=None): assert V in self.range if not self.transposed: return self.source.make_array(self._array.dot(V).T) else: return self._array.lincomb(V.data) def apply_inverse_transpose(self, U, mu=None, least_squares=False): transpose_op = VectorArrayOperator(self._array, transposed=not self.transposed) return transpose_op.apply_inverse(U, mu=mu, least_squares=least_squares) def assemble_lincomb(self, operators, coefficients, solver_options=None, name=None): transposed = operators[0].transposed if not all( isinstance(op, VectorArrayOperator) and op.transposed == transposed for op in operators): return None assert not solver_options if coefficients[0] == 1: array = operators[0]._array.copy() else: array = operators[0]._array * coefficients[0] for op, c in zip(operators[1:], coefficients[1:]): array.axpy(c, op._array) return VectorArrayOperator(array, transposed=transposed, name=name) def as_range_array(self, mu=None): if not self.transposed: return self._array.copy() else: super().as_range_array(mu) def as_source_array(self, mu=None): if self.transposed: return self._array.copy() else: super().as_source_array(mu) def restricted(self, dofs): assert all(0 <= c < self.range.dim for c in dofs) if not self.transposed: restricted_value = NumpyVectorSpace.make_array( self._array.dofs(dofs)) return VectorArrayOperator(restricted_value, False), np.arange(self.source.dim, dtype=np.int32) else: raise NotImplementedError
class NumpyGenericOperator(Operator): """Wraps an arbitrary Python function between |NumPy arrays| as an |Operator|. Parameters ---------- mapping The function to wrap. If `parameters` is `None`, the function is of the form `mapping(U)` and is expected to be vectorized. In particular:: mapping(U).shape == U.shape[:-1] + (dim_range,). If `parameters` is not `None`, the function has to have the signature `mapping(U, mu)`. adjoint_mapping The adjoint function to wrap. If `parameters` is `None`, the function is of the form `adjoint_mapping(U)` and is expected to be vectorized. In particular:: adjoint_mapping(U).shape == U.shape[:-1] + (dim_source,). If `parameters` is not `None`, the function has to have the signature `adjoint_mapping(U, mu)`. dim_source Dimension of the operator's source. dim_range Dimension of the operator's range. linear Set to `True` if the provided `mapping` and `adjoint_mapping` are linear. parameters The |Parameters| the depends on. solver_options The |solver_options| for the operator. name Name of the operator. """ def __init__(self, mapping, adjoint_mapping=None, dim_source=1, dim_range=1, linear=False, parameters={}, source_id=None, range_id=None, solver_options=None, name=None): self.__auto_init(locals()) self.source = NumpyVectorSpace(dim_source, source_id) self.range = NumpyVectorSpace(dim_range, range_id) self.parameters_own = parameters def apply(self, U, mu=None): assert U in self.source assert self.parameters.assert_compatible(mu) if self.parametric: return self.range.make_array(self.mapping(U.to_numpy(), mu=mu)) else: return self.range.make_array(self.mapping(U.to_numpy())) def apply_adjoint(self, V, mu=None): if self.adjoint_mapping is None: raise ValueError( 'NumpyGenericOperator: adjoint mapping was not defined.') assert V in self.range assert self.parameters.assert_compatible(mu) V = V.to_numpy() if self.parametric: return self.source.make_array(self.adjoint_mapping(V, mu=mu)) else: return self.source.make_array(self.adjoint_mapping(V))