def reduce(self): if self.residual_range is not False: with self.logger.block('Estimating residual range ...'): try: self.residual_range, self.residual_range_dims = \ estimate_image_hierarchical([self.operator], [self.rhs], self.RB, (self.residual_range, self.residual_range_dims), orthonormalize=True, product=self.product, riesz_representatives=self.riesz_representatives) except ImageCollectionError as e: self.logger.warning('Cannot compute range of {}. Evaluation will be slow.'.format(e.op)) self.residual_range = False if self.residual_range is False: operator = project(self.operator, None, self.RB) return NonProjectedResidualOperator(operator, self.rhs, self.riesz_representatives, self.product) with self.logger.block('Projecting residual operator ...'): if self.riesz_representatives: operator = project(self.operator, self.residual_range, self.RB, product=None) # the product cancels out. rhs = project(self.rhs, self.residual_range, None, product=None) else: operator = project(self.operator, self.residual_range, self.RB, product=self.product) rhs = project(self.rhs, self.residual_range, None, product=self.product) return ResidualOperator(operator, rhs)
def reduce(self): if self.residual_range is not False: with self.logger.block('Estimating residual range ...'): try: self.residual_range, self.residual_range_dims = \ estimate_image_hierarchical([self.operator], [self.rhs], self.RB, (self.residual_range, self.residual_range_dims), orthonormalize=True, product=self.product, riesz_representatives=self.riesz_representatives) except ImageCollectionError as e: self.logger.warning(f'Cannot compute range of {e.op}. Evaluation will be slow.') self.residual_range = False if self.residual_range is False: operator = project(self.operator, None, self.RB) return NonProjectedResidualOperator(operator, self.rhs, self.riesz_representatives, self.product) with self.logger.block('Projecting residual operator ...'): if self.riesz_representatives: operator = project(self.operator, self.residual_range, self.RB, product=None) # the product cancels out. rhs = project(self.rhs, self.residual_range, None, product=None) else: operator = project(self.operator, self.residual_range, self.RB, product=self.product) rhs = project(self.rhs, self.residual_range, None, product=self.product) return ResidualOperator(operator, rhs)
def reduce(self): if self.residual_range is not False: with self.logger.block('Estimating residual range ...'): try: self.residual_range, self.residual_range_dims = \ estimate_image_hierarchical([self.operator, self.mass], [self.rhs], self.RB, (self.residual_range, self.residual_range_dims), orthonormalize=True, product=self.product, riesz_representatives=True) except ImageCollectionError as e: self.logger.warning(f'Cannot compute range of {e.op}. Evaluation will be slow.') self.residual_range = False if self.residual_range is False: operator = project(self.operator, None, self.RB) mass = project(self.mass, None, self.RB) return NonProjectedImplicitEulerResidualOperator(operator, mass, self.rhs, self.dt, self.product) with self.logger.block('Projecting residual operator ...'): operator = project(self.operator, self.residual_range, self.RB, product=None) # the product always cancels out. mass = project(self.mass, self.residual_range, self.RB, product=None) rhs = project(self.rhs, self.residual_range, None, product=None) return ImplicitEulerResidualOperator(operator, mass, rhs, self.dt)
def reduce(self): if self.residual_range is not False: with self.logger.block('Estimating residual range ...'): try: self.residual_range, self.residual_range_dims = \ estimate_image_hierarchical([self.operator, self.mass], [self.functional.T], self.RB, (self.residual_range, self.residual_range_dims), orthonormalize=True, product=self.product, riesz_representatives=True) except ImageCollectionError as e: self.logger.warning('Cannot compute range of {}. Evaluation will be slow.'.format(e.op)) self.residual_range = False if self.residual_range is False: operator = project(self.operator, None, self.RB) mass = project(self.mass, None, self.RB) return NonProjectedImplicitEulerResidualOperator(operator, mass, self.functional, self.dt, self.product) with self.logger.block('Projecting residual operator ...'): operator = project(self.operator, self.residual_range, self.RB, product=None) # the product always cancels out. mass = project(self.mass, self.residual_range, self.RB, product=None) functional = project(self.functional, None, self.residual_range, product=None) return ImplicitEulerResidualOperator(operator, mass, functional, self.dt)
def reduce(self): # Note that it is possible that rhs.source == rhs.range, nameley if both # are one-dimensional NumpyVectorSpaces which agree with the range of # operator. Usually, this should not happen, since at least one of these # spaces should have an id which is different from the id of operator.range. # However, even if it happens but rhs is actually a vector, we are on # the safe side, since first computing the Riesz representatives does not # change anything in one-dimensional spaces, and it does not matter whether # we project from the left or from the right. rhs_is_functional = (self.rhs.source == self.operator.range) if self.residual_range is not False: with self.logger.block('Estimating residual range ...'): try: self.residual_range, self.residual_range_dims = \ estimate_image_hierarchical([self.operator], [self.rhs.T if rhs_is_functional else self.rhs], self.RB, (self.residual_range, self.residual_range_dims), orthonormalize=True, product=self.product, riesz_representatives=rhs_is_functional) except ImageCollectionError as e: self.logger.warning('Cannot compute range of {}. Evaluation will be slow.'.format(e.op)) self.residual_range = False if self.residual_range is False: operator = project(self.operator, None, self.RB) return NonProjectedResidualOperator(operator, self.rhs, rhs_is_functional, self.product) with self.logger.block('Projecting residual operator ...'): if rhs_is_functional: operator = project(self.operator, self.residual_range, self.RB, product=None) # the product cancels out. rhs = project(self.rhs, None, self.residual_range, product=None) else: operator = project(self.operator, self.residual_range, self.RB, product=self.product) rhs = project(self.rhs, self.residual_range, None, product=self.product) return ResidualOperator(operator, rhs, rhs_is_functional)
def reduce_residual(operator, functional=None, RB=None, product=None, extends=None): """Generic reduced basis residual reductor. Given an operator and a functional, the concatenation of residual operator with the Riesz isomorphism is given by:: riesz_residual.apply(U, mu) == product.apply_inverse(operator.apply(U, mu) - functional.as_vector(mu)) This reductor determines a low-dimensional subspace of image of a reduced basis space under `riesz_residual`, computes an orthonormal basis `residual_range` of this range spaces and then returns the Petrov-Galerkin projection :: projected_riesz_residual === riesz_residual.projected(range_basis=residual_range, source_basis=RB) of the `riesz_residual` operator. Given an reduced basis coefficient vector `u`, the dual norm of the residual can then be computed as :: projected_riesz_residual.apply(u, mu).l2_norm() Moreover, a `reconstructor` is provided such that :: reconstructor.reconstruct(projected_riesz_residual.apply(u, mu)) == riesz_residual.apply(RB.lincomb(u), mu) Parameters ---------- operator See definition of `riesz_residual`. functional See definition of `riesz_residual`. If `None`, zero right-hand side is assumed. RB |VectorArray| containing a basis of the reduced space onto which to project. product Scalar product |Operator| w.r.t. which to compute the Riesz representatives. extends Set by :meth:`~pymor.algorithms.greedy.greedy` to the result of the last reduction in case the basis extension was `hierarchic`. Used to prevent re-computation of `residual_range` basis vectors already obtained from previous reductions. Returns ------- projected_riesz_residual See above. reconstructor See above. reduction_data Additional data produced by the reduction process. (Compare the `extends` parameter.) """ assert functional is None \ or functional.range == NumpyVectorSpace(1) and functional.source == operator.source and functional.linear assert RB is None or RB in operator.source assert product is None or product.source == product.range == operator.range assert extends is None or len(extends) == 3 logger = getLogger('pymor.reductors.residual.reduce_residual') if RB is None: RB = operator.source.empty() if extends and isinstance(extends[0], NonProjectedResidualOperator): extends = None if extends: residual_range = extends[1].RB residual_range_dims = list(extends[2]['residual_range_dims']) else: residual_range = operator.range.empty() residual_range_dims = [] with logger.block('Estimating residual range ...'): try: residual_range, residual_range_dims = \ estimate_image_hierarchical([operator], [functional], RB, (residual_range, residual_range_dims), orthonormalize=True, product=product, riesz_representatives=True) except ImageCollectionError as e: logger.warn('Cannot compute range of {}. Evaluation will be slow.'.format(e.op)) operator = operator.projected(None, RB) return (NonProjectedResidualOperator(operator, functional, product), NonProjectedReconstructor(product), {}) with logger.block('Projecting residual operator ...'): operator = operator.projected(residual_range, RB, product=None) # the product always cancels out. functional = functional.projected(None, residual_range, product=None) return (ResidualOperator(operator, functional), GenericRBReconstructor(residual_range), {'residual_range_dims': residual_range_dims})
def reduce_residual(operator, rhs=None, RB=None, product=None, extends=None): """Generic reduced basis residual reductor. Given an operator and a right-hand side, the residual is given by:: residual.apply(U, mu) == operator.apply(U, mu) - rhs.as_vector(mu) When the rhs is a functional we are interested in the Riesz representative of the residual:: residual.apply(U, mu) == product.apply_inverse(operator.apply(U, mu) - rhs.as_vector(mu)) Given a basis `RB` of a subspace of the source space of `operator`, this method uses :func:`~pymor.algorithms.image.estimate_image_hierarchical` to determine a low-dimensional subspace containing the image of the subspace under `residual` (resp. `riesz_residual`), computes an orthonormal basis `residual_range` for this range space and then returns the Petrov-Galerkin projection :: projected_residual === residual.projected(range_basis=residual_range, source_basis=RB) of the residual operator. Given a reduced basis coefficient vector `u`, w.r.t. `RB`, the (dual) norm of the residual can then be computed as :: projected_residual.apply(u, mu).l2_norm() Moreover, a `reconstructor` is provided such that :: reconstructor.reconstruct(projected_residual.apply(u, mu)) == residual.apply(RB.lincomb(u), mu) Parameters ---------- operator See definition of `residual`. rhs See definition of `residual`. If `None`, zero right-hand side is assumed. RB |VectorArray| containing a basis of the reduced space onto which to project. product Inner product |Operator| w.r.t. which to orthonormalize and w.r.t. which to compute the Riesz representatives in case `rhs` is a functional. extends Set by :meth:`~pymor.algorithms.greedy.greedy` to the result of the last reduction in case the basis extension was `hierarchic` (used to prevent re-computation of `residual_range` basis vectors already obtained from previous reductions). Returns ------- projected_residual See above. reconstructor See above. reduction_data Additional data produced by the reduction process (compare the `extends` parameter). """ assert rhs is None \ or (rhs.range.is_scalar and rhs.source == operator.range and rhs.linear) \ or (rhs.source.is_scalar and rhs.range == operator.range and rhs.linear) assert RB is None or RB in operator.source assert product is None or product.source == product.range == operator.range assert extends is None or len(extends) == 3 # Note that it is possible that rhs.source == rhs.range, nameley if both # are one-dimensional NumpyVectorSpaces which agree with the range of # operator. Usually, this should not happen, since at least one of these # spaces should have an id which is different from the id of operator.range. # However, even if it happens but rhs is actually a vector, we are on # the safe side, since first computing the Riesz representatives does not # change anything in one-dimensional spaces, and it does not matter whether # we project from the left or from the right. rhs_is_functional = (rhs.source == operator.range) logger = getLogger('pymor.reductors.residual.reduce_residual') if RB is None: RB = operator.source.empty() if extends and isinstance(extends[0], NonProjectedResidualOperator): extends = None if extends: residual_range = extends[1].RB residual_range_dims = list(extends[2]['residual_range_dims']) else: residual_range = operator.range.empty() residual_range_dims = [] with logger.block('Estimating residual range ...'): try: residual_range, residual_range_dims = \ estimate_image_hierarchical([operator], [rhs.T if rhs_is_functional else rhs], RB, (residual_range, residual_range_dims), orthonormalize=True, product=product, riesz_representatives=rhs_is_functional) except ImageCollectionError as e: logger.warn('Cannot compute range of {}. Evaluation will be slow.'.format(e.op)) operator = operator.projected(None, RB) return (NonProjectedResidualOperator(operator, rhs, rhs_is_functional, product), NonProjectedReconstructor(product), {}) with logger.block('Projecting residual operator ...'): if rhs_is_functional: operator = operator.projected(residual_range, RB, product=None) # the product cancels out. rhs = rhs.projected(None, residual_range, product=None) else: operator = operator.projected(residual_range, RB, product=product) rhs = rhs.projected(residual_range, None, product=product) return (ResidualOperator(operator, rhs, rhs_is_functional), GenericRBReconstructor(residual_range), {'residual_range_dims': residual_range_dims})