def interpolate_operators(fom, operator_names, parameter_sample, error_norm=None, product=None, atol=None, rtol=None, max_interpolation_dofs=None, pod_options={}, alg='ei_greedy', pool=dummy_pool): """Empirical operator interpolation using the EI-Greedy/DEIM algorithm. This is a convenience method to facilitate the use of :func:`ei_greedy` or :func:`deim`. Given a |Model|, names of |Operators|, and a sample of |Parameters|, first the operators are evaluated on the solution snapshots of the model for the provided parameters. These evaluations are then used as input for :func:`ei_greedy`/:func:`deim`. Finally the resulting interpolation data is used to create |EmpiricalInterpolatedOperators| and a new model with the interpolated operators is returned. Note that this implementation creates *one* common collateral basis for all specified operators, which might not be what you want. Parameters ---------- fom The |Model| whose |Operators| will be interpolated. operator_names List of keys in the `operators` dict of the model. The corresponding |Operators| will be interpolated. parameter_sample A list of |Parameters| for which solution snapshots are calculated. error_norm See :func:`ei_greedy`. Has no effect if `alg == 'deim'`. product Inner product for POD computation in :func:`deim`. Has no effect if `alg == 'ei_greedy'`. atol See :func:`ei_greedy`. rtol See :func:`ei_greedy`. max_interpolation_dofs See :func:`ei_greedy`. pod_options Further options for :func:`~pymor.algorithms.pod.pod` algorithm. Has no effect if `alg == 'ei_greedy'`. alg Either `ei_greedy` or `deim`. pool If not `None`, the |WorkerPool| to use for parallelization. Returns ------- eim |Model| with |Operators| given by `operator_names` replaced by |EmpiricalInterpolatedOperators|. data Dict containing the following fields: :dofs: |NumPy array| of the DOFs at which the |Operators| have to be evaluated. :basis: |VectorArray| containing the generated collateral basis. In addition, `data` contains the fields of the `data` `dict` returned by :func:`ei_greedy`/:func:`deim`. """ assert alg in ('ei_greedy', 'deim') logger = getLogger('pymor.algorithms.ei.interpolate_operators') with RemoteObjectManager() as rom: operators = [ getattr(fom, operator_name) for operator_name in operator_names ] with logger.block( 'Computing operator evaluations on solution snapshots ...'): if pool: logger.info( f'Using pool of {len(pool)} workers for parallel evaluation' ) evaluations = rom.manage(pool.push(fom.solution_space.empty())) pool.map(_interpolate_operators_build_evaluations, parameter_sample, fom=fom, operators=operators, evaluations=evaluations) else: evaluations = operators[0].range.empty() for mu in parameter_sample: U = fom.solve(mu) for op in operators: evaluations.append(op.apply(U, mu=mu)) if alg == 'ei_greedy': with logger.block('Performing EI-Greedy:'): dofs, basis, data = ei_greedy( evaluations, error_norm, atol=atol, rtol=rtol, max_interpolation_dofs=max_interpolation_dofs, copy=False, pool=pool) elif alg == 'deim': if alg == 'deim' and pool is not dummy_pool: logger.warn( 'DEIM algorithm not parallel. Collecting operator evaluations.' ) evaluations = pool.apply(_identity, x=evaluations) evs = evaluations[0] for e in evaluations[1:]: evs.append(e, remove_from_other=True) evaluations = evs with logger.block('Executing DEIM algorithm:'): dofs, basis, data = deim(evaluations, modes=max_interpolation_dofs, atol=atol, rtol=rtol, pod_options=pod_options, product=product) else: assert False ei_operators = { name: EmpiricalInterpolatedOperator(operator, dofs, basis, triangular=(alg == 'ei_greedy')) for name, operator in zip(operator_names, operators) } eim = fom.with_(name=f'{fom.name}_ei', **ei_operators) data.update({'dofs': dofs, 'basis': basis}) return eim, data
def main( dim: int = Argument(..., help='Spatial dimension of the problem.'), n: int = Argument( ..., help='Number of mesh intervals per spatial dimension.'), order: int = Argument(..., help='Finite element order.'), ): """Reduces a FEniCS-based nonlinear diffusion problem using POD/DEIM.""" from pymor.tools import mpi if mpi.parallel: from pymor.models.mpi import mpi_wrap_model local_models = mpi.call(mpi.function_call_manage, discretize, dim, n, order) fom = mpi_wrap_model(local_models, use_with=True, pickle_local_spaces=False) else: fom = discretize(dim, n, order) parameter_space = fom.parameters.space((0, 1000.)) # ### ROM generation (POD/DEIM) from pymor.algorithms.ei import ei_greedy from pymor.algorithms.newton import newton from pymor.algorithms.pod import pod from pymor.operators.ei import EmpiricalInterpolatedOperator from pymor.reductors.basic import StationaryRBReductor U = fom.solution_space.empty() residuals = fom.solution_space.empty() for mu in parameter_space.sample_uniformly(10): UU, data = newton(fom.operator, fom.rhs.as_vector(), mu=mu, rtol=1e-6, return_residuals=True) U.append(UU) residuals.append(data['residuals']) dofs, cb, _ = ei_greedy(residuals, rtol=1e-7) ei_op = EmpiricalInterpolatedOperator(fom.operator, collateral_basis=cb, interpolation_dofs=dofs, triangular=True) rb, svals = pod(U, rtol=1e-7) fom_ei = fom.with_(operator=ei_op) reductor = StationaryRBReductor(fom_ei, rb) rom = reductor.reduce() # the reductor currently removes all solver_options so we need to add them again rom = rom.with_(operator=rom.operator.with_( solver_options=fom.operator.solver_options)) # ### ROM validation import time import numpy as np # ensure that FFC is not called during runtime measurements rom.solve(1) errs = [] speedups = [] for mu in parameter_space.sample_randomly(10): tic = time.perf_counter() U = fom.solve(mu) t_fom = time.perf_counter() - tic tic = time.perf_counter() u_red = rom.solve(mu) t_rom = time.perf_counter() - tic U_red = reductor.reconstruct(u_red) errs.append(((U - U_red).norm() / U.norm())[0]) speedups.append(t_fom / t_rom) print(f'Maximum relative ROM error: {max(errs)}') print(f'Median of ROM speedup: {np.median(speedups)}')
def interpolate_operators(d, operator_names, parameter_sample, error_norm=None, atol=None, rtol=None, max_interpolation_dofs=None, pool=dummy_pool): """Empirical operator interpolation using the EI-Greedy algorithm. This is a convenience method to facilitate the use of :func:`ei_greedy`. Given a |Discretization|, names of |Operators|, and a sample of |Parameters|, first the operators are evaluated on the solution snapshots of the discretization for the provided parameters. These evaluations are then used as input for :func:`ei_greedy`. Finally the resulting interpolation data is used to create |EmpiricalInterpolatedOperators| and a new discretization with the interpolated operators is returned. Note that this implementation creates *one* common collateral basis for all specified operators, which might not be what you want. Parameters ---------- d The |Discretization| whose |Operators| will be interpolated. operator_names List of keys in the `operators` dict of the discretization. The corresponding |Operators| will be interpolated. parameter_sample A list of |Parameters| for which solution snapshots are calculated. error_norm See :func:`ei_greedy`. atol See :func:`ei_greedy`. rtol See :func:`ei_greedy`. max_interpolation_dofs See :func:`ei_greedy`. pool If not `None`, the |WorkerPool| to use for parallelization. Returns ------- ei_d |Discretization| with |Operators| given by `operator_names` replaced by |EmpiricalInterpolatedOperators|. data Dict containing the following fields: :dofs: |NumPy array| of the DOFs at which the |Operators| have to be evaluated. :basis: |VectorArray| containing the generated collateral basis. :errors: Sequence of maximum approximation errors during greedy search. :triangularity_errors: Sequence of maximum absolute values of interoplation matrix coefficients in the upper triangle (should be near zero). """ logger = getLogger('pymor.algorithms.ei.interpolate_operators') with RemoteObjectManager() as rom: operators = [d.operators[operator_name] for operator_name in operator_names] with logger.block('Computing operator evaluations on solution snapshots ...'): if pool: logger.info('Using pool of {} workers for parallel evaluation'.format(len(pool))) evaluations = rom.manage(pool.push(d.solution_space.empty())) pool.map(_interpolate_operators_build_evaluations, parameter_sample, d=d, operators=operators, evaluations=evaluations) else: evaluations = operators[0].range.empty() for mu in parameter_sample: U = d.solve(mu) for op in operators: evaluations.append(op.apply(U, mu=mu)) with logger.block('Performing EI-Greedy:'): dofs, basis, data = ei_greedy(evaluations, error_norm, atol=atol, rtol=rtol, max_interpolation_dofs=max_interpolation_dofs, copy=False, pool=pool) ei_operators = {name: EmpiricalInterpolatedOperator(operator, dofs, basis, triangular=True) for name, operator in zip(operator_names, operators)} operators_dict = d.operators.copy() operators_dict.update(ei_operators) ei_d = d.with_(operators=operators_dict, name='{}_ei'.format(d.name)) data.update({'dofs': dofs, 'basis': basis}) return ei_d, data
def fenics_nonlinear_demo(args): DIM = int(args['DIM']) N = int(args['N']) ORDER = int(args['ORDER']) from pymor.tools import mpi if mpi.parallel: from pymor.models.mpi import mpi_wrap_model local_models = mpi.call(mpi.function_call_manage, discretize, DIM, N, ORDER) fom = mpi_wrap_model(local_models, use_with=True, pickle_local_spaces=False) else: fom = discretize(DIM, N, ORDER) # ### ROM generation (POD/DEIM) from pymor.algorithms.ei import ei_greedy from pymor.algorithms.newton import newton from pymor.algorithms.pod import pod from pymor.operators.ei import EmpiricalInterpolatedOperator from pymor.reductors.basic import StationaryRBReductor U = fom.solution_space.empty() residuals = fom.solution_space.empty() for mu in fom.parameter_space.sample_uniformly(10): UU, data = newton(fom.operator, fom.rhs.as_vector(), mu=mu, rtol=1e-6, return_residuals=True) U.append(UU) residuals.append(data['residuals']) dofs, cb, _ = ei_greedy(residuals, rtol=1e-7) ei_op = EmpiricalInterpolatedOperator(fom.operator, collateral_basis=cb, interpolation_dofs=dofs, triangular=True) rb, svals = pod(U, rtol=1e-7) fom_ei = fom.with_(operator=ei_op) reductor = StationaryRBReductor(fom_ei, rb) rom = reductor.reduce() # the reductor currently removes all solver_options so we need to add them again rom = rom.with_(operator=rom.operator.with_( solver_options=fom.operator.solver_options)) # ### ROM validation import time import numpy as np # ensure that FFC is not called during runtime measurements rom.solve(1) errs = [] speedups = [] for mu in fom.parameter_space.sample_randomly(10): tic = time.time() U = fom.solve(mu) t_fom = time.time() - tic tic = time.time() u_red = rom.solve(mu) t_rom = time.time() - tic U_red = reductor.reconstruct(u_red) errs.append(((U - U_red).l2_norm() / U.l2_norm())[0]) speedups.append(t_fom / t_rom) print(f'Maximum relative ROM error: {max(errs)}') print(f'Median of ROM speedup: {np.median(speedups)}')