def __init__(self, operator, rhs, outputs=None, products=None, parameter_space=None, estimator=None, visualizer=None, cache_region=None, name=None): if isinstance(rhs, VectorArrayInterface): assert rhs in operator.range rhs = VectorOperator(rhs, name='rhs') assert rhs.range == operator.range and rhs.source.is_scalar and rhs.linear super().__init__(products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.operator = operator self.rhs = rhs self.outputs = FrozenDict(outputs or {}) self.solution_space = self.operator.source self.linear = operator.linear and all( output.linear for output in self.outputs.values()) self.build_parameter_type(operator, rhs) self.parameter_space = parameter_space
def __init__(self, operators, functionals, vector_operators, products=None, estimator=None, visualizer=None, cache_region='disk', name=None): self.operators = FrozenDict(operators) self.functionals = FrozenDict(functionals) self.vector_operators = FrozenDict(vector_operators) self.linear = all( op is None or op.linear for op in chain(operators.itervalues(), functionals.itervalues())) self.products = products self.estimator = estimator self.visualizer = visualizer self.cache_region = cache_region self.name = name if products: for k, v in products.iteritems(): setattr(self, '{}_product'.format(k), v) setattr(self, '{}_norm'.format(k), induced_norm(v))
def __init__(self, products=None, estimator=None, visualizer=None, name=None, **kwargs): products = FrozenDict(products or {}) if products: for k, v in products.items(): setattr(self, f'{k}_product', v) setattr(self, f'{k}_norm', induced_norm(v)) self.__auto_init(locals())
def __init__(self, T, initial_data, operator, rhs, mass=None, time_stepper=None, num_values=None, outputs=None, products=None, parameter_space=None, estimator=None, visualizer=None, cache_region=None, name=None): if isinstance(rhs, VectorArrayInterface): assert rhs in operator.range rhs = VectorOperator(rhs, name='rhs') if isinstance(initial_data, VectorArrayInterface): assert initial_data in operator.source initial_data = VectorOperator(initial_data, name='initial_data') assert isinstance(time_stepper, TimeStepperInterface) assert initial_data.source.is_scalar assert operator.source == initial_data.range assert rhs is None \ or rhs.linear and rhs.range == operator.range and rhs.source.is_scalar assert mass is None \ or mass.linear and mass.source == mass.range == operator.source super().__init__(products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.T = T self.initial_data = initial_data self.operator = operator self.rhs = rhs self.mass = mass self.solution_space = self.operator.source self.time_stepper = time_stepper self.num_values = num_values self.outputs = FrozenDict(outputs or {}) self.linear = operator.linear and all( output.linear for output in self.outputs.values()) self.build_parameter_type(self.initial_data, self.operator, self.rhs, self.mass, provides={'_t': 0}) self.parameter_space = parameter_space
def __init__(self, T, initial_data, operator, rhs, mass=None, time_stepper=None, num_values=None, outputs=None, products=None, parameter_space=None, estimator=None, visualizer=None, cache_region=None, name=None): if isinstance(rhs, VectorArrayInterface): assert rhs in operator.range rhs = VectorOperator(rhs, name='rhs') if isinstance(initial_data, VectorArrayInterface): assert initial_data in operator.source initial_data = VectorOperator(initial_data, name='initial_data') assert isinstance(time_stepper, TimeStepperInterface) assert initial_data.source.is_scalar assert operator.source == initial_data.range assert rhs is None \ or rhs.linear and rhs.range == operator.range and rhs.source.is_scalar assert mass is None \ or mass.linear and mass.source == mass.range == operator.source super().__init__(products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.T = T self.initial_data = initial_data self.operator = operator self.rhs = rhs self.mass = mass self.solution_space = self.operator.source self.time_stepper = time_stepper self.num_values = num_values self.outputs = FrozenDict(outputs or {}) self.build_parameter_type(self.initial_data, self.operator, self.rhs, self.mass, provides={'_t': 0}) self.parameter_space = parameter_space
def __init__(self, stationary_part, initial_data, T=1., parameter_ranges=None, name=None): name = name or ('instationary_' + stationary_part.name) assert (initial_data is None or initial_data.dim_domain == stationary_part.domain.dim and initial_data.shape_range == ()) assert ( parameter_ranges is None or (isinstance(parameter_ranges, (list, tuple)) and len(parameter_ranges) == 2 and parameter_ranges[0] <= parameter_ranges[1]) or (isinstance(parameter_ranges, dict) and all( isinstance(v, (list, tuple)) and len(v) == 2 and v[0] <= v[1] for v in parameter_ranges.values()))) parameter_ranges = (None if parameter_ranges is None else tuple(parameter_ranges) if isinstance( parameter_ranges, (list, tuple)) else FrozenDict( (k, tuple(v)) for k, v in parameter_ranges.items())) self.__auto_init(locals())
def __init__(self, d): def wrap_op(op, name=None): if not op.parametric() and op.num_components() == 0 and op.has_affine_part(): wrapped_op = self._wrapper[op.affine_part()] else: wrapped_op = self._wrapper[op] return wrapped_op.with_(name=name) if name else wrapped_op self._impl = d operators = {'operator': wrap_op(d.get_operator())} functionals = {'rhs': self._wrapper[d.get_rhs()]} vector_operators = FrozenDict({k: VectorOperator(make_listvectorarray(self._wrapper[d.get_vector(k)])) for k in list(d.available_vectors())}) self.operators = FrozenDict(operators) self.functionals = FrozenDict(functionals) self.vector_operators = FrozenDict(vector_operators) self.operator = operators['operator'] self.rhs = functionals['rhs'] self.products = FrozenDict({k: wrap_op(d.get_product(k), k) for k in list(d.available_products())}) if self.products: for k, v in self.products.iteritems(): setattr(self, '{}_product'.format(k), v) setattr(self, '{}_norm'.format(k), induced_norm(v)) self.linear = all(op.linear for op in operators.itervalues()) self.solution_space = self.operator.source self.build_parameter_type(inherits=operators.values() + functionals.values()) assert self.parameter_type == self._wrapper[d.parameter_type()] self.solver_options = self._impl.solver_options()
def __init__(self, operators=None, products=None, estimator=None, visualizer=None, cache_region=None, name=None, **kwargs): operators = {} if operators is None else dict(operators) # handle special operators for on in self.special_operators: # special operators must be provided as keyword argument to __init__ assert on in kwargs # special operators may not already exist as attributes assert not hasattr(self, on) # special operators may not be contained in operators dict assert on not in operators op = kwargs[on] # operators either have to be None or an Operator assert op is None \ or isinstance(op, OperatorInterface) \ or all(isinstance(o, OperatorInterface) for o in op) # set special operator as an attribute setattr(self, on, op) # add special operator to the operators dict operators[on] = op self.operators = FrozenDict(operators) self.linear = all(op is None or op.linear for op in operators.values()) self.products = FrozenDict(products or {}) self.estimator = estimator self.visualizer = visualizer self.enable_caching(cache_region) self.name = name if products: for k, v in products.items(): setattr(self, '{}_product'.format(k), v) setattr(self, '{}_norm'.format(k), induced_norm(v)) self.build_parameter_type(*operators.values())
def __init__(self, parameters, *ranges): assert isinstance(parameters, Parameters) assert 1 <= len(ranges) <= 2 if len(ranges) == 1: ranges = ranges[0] if isinstance(ranges, (tuple, list)): assert len(ranges) == 2 ranges = {k: ranges for k in parameters} assert isinstance(ranges, dict) assert all(k in ranges and len(ranges[k]) == 2 and all( isinstance(v, Number) for v in ranges[k]) and ranges[k][0] <= ranges[k][1] for k in parameters) self.parameters = parameters self.ranges = FrozenDict((k, tuple(v)) for k, v in ranges.items())
def __init__(self, products=None, estimator=None, visualizer=None, cache_region=None, name=None, **kwargs): self.products = FrozenDict(products or {}) self.estimator = estimator self.visualizer = visualizer self.enable_caching(cache_region) self.name = name if products: for k, v in products.items(): setattr(self, f'{k}_product', v) setattr(self, f'{k}_norm', induced_norm(v))
class DiscretizationBase(DiscretizationInterface): """Base class for |Discretizations| providing some common functionality.""" sid_ignore = DiscretizationInterface.sid_ignore | {'visualizer'} add_with_arguments = DiscretizationInterface.add_with_arguments | {'operators'} special_operators = frozenset() def __init__(self, operators=None, products=None, estimator=None, visualizer=None, cache_region=None, name=None, **kwargs): operators = {} if operators is None else dict(operators) # handle special operators for on in self.special_operators: # special operators must be provided as keyword argument to __init__ assert on in kwargs # special operators may not already exist as attributes assert not hasattr(self, on) # special operators may not be contained in operators dict assert on not in operators op = kwargs[on] # operators either have to be None or an Operator assert op is None \ or isinstance(op, OperatorInterface) \ or all(isinstance(o, OperatorInterface) for o in op) # set special operator as an attribute setattr(self, on, op) # add special operator to the operators dict operators[on] = op self.operators = FrozenDict(operators) self.linear = all(op is None or op.linear for op in operators.values()) self.products = FrozenDict(products or {}) self.estimator = estimator self.visualizer = visualizer self.enable_caching(cache_region) self.name = name if products: for k, v in products.items(): setattr(self, '{}_product'.format(k), v) setattr(self, '{}_norm'.format(k), induced_norm(v)) self.build_parameter_type(*operators.values()) def with_(self, **kwargs): assert set(kwargs.keys()) <= self.with_arguments if 'operators' in kwargs: # extract special operators from provided operators dict operators = kwargs['operators'].copy() for on in self.special_operators: if on in operators: assert on not in kwargs or kwargs[on] == operators[on] kwargs[on] = operators.pop(on) kwargs['operators'] = operators else: # when an operators dict is not specified make sure that we use the old operators dict # but without the special operators kwargs['operators'] = {on: op for on, op in self.operators.items() if on not in self.special_operators} # delete empty 'operators' dicts for cases where __init__ does not take # an 'operator' argument if 'operators' not in self._init_arguments: operators = kwargs.pop('operators') # in that case, there should not be any operators left in 'operators' assert not operators return super(DiscretizationBase, self).with_(**kwargs) def visualize(self, U, **kwargs): """Visualize a solution |VectorArray| U. Parameters ---------- U The |VectorArray| from :attr:`~pymor.discretizations.interfaces.DiscretizationInterface.solution_space` that shall be visualized. kwargs See docstring of `self.visualizer.visualize`. """ if self.visualizer is not None: self.visualizer.visualize(U, self, **kwargs) else: raise NotImplementedError('Discretization has no visualizer.') def estimate(self, U, mu=None): if self.estimator is not None: return self.estimator.estimate(U, mu=mu, d=self) else: raise NotImplementedError('Discretization has no estimator.')
def __init__(self, domain, rhs=None, diffusion=None, advection=None, nonlinear_advection=None, nonlinear_advection_derivative=None, reaction=None, nonlinear_reaction=None, nonlinear_reaction_derivative=None, dirichlet_data=None, neumann_data=None, robin_data=None, functionals=None, parameter_space=None, name=None): assert (rhs is None or rhs.dim_domain == domain.dim and rhs.shape_range == ()) assert (diffusion is None or diffusion.dim_domain == domain.dim and diffusion.shape_range in ((), (domain.dim, domain.dim))) assert (advection is None or advection.dim_domain == domain.dim and advection.shape_range == (domain.dim, )) assert (nonlinear_advection is None or nonlinear_advection.dim_domain == 1 and nonlinear_advection.shape_range == (domain.dim, )) assert (nonlinear_advection_derivative is None or (nonlinear_advection_derivative.dim_domain == 1 and nonlinear_advection_derivative.shape_range == (domain.dim, ))) assert (reaction is None or reaction.dim_domain == domain.dim and reaction.shape_range == ()) assert (nonlinear_reaction is None or nonlinear_reaction.dim_domain == 1 and nonlinear_reaction.shape_range == ()) assert (nonlinear_reaction_derivative is None or nonlinear_reaction_derivative.dim_domain == 1 and nonlinear_reaction_derivative.shape_range == ()) assert (dirichlet_data is None or dirichlet_data.dim_domain == domain.dim and dirichlet_data.shape_range == ()) assert (neumann_data is None or neumann_data.dim_domain == domain.dim and neumann_data.shape_range == ()) assert (robin_data is None or (isinstance(robin_data, tuple) and len(robin_data) == 2 and np.all([ f.dim_domain == domain.dim and f.shape_range == () for f in robin_data ]))) assert (functionals is None or all( isinstance(v, tuple) and len(v) == 2 and v[0] in ('l2', 'l2_boundary') and v[1].dim_domain == domain.dim and v[1].shape_range == () for v in functionals.values())) self.domain = domain self.rhs = rhs self.diffusion = diffusion self.advection = advection self.nonlinear_advection = nonlinear_advection self.nonlinear_advection_derivative = nonlinear_advection_derivative self.reaction = reaction self.nonlinear_reaction = nonlinear_reaction self.nonlinear_reaction_derivative = nonlinear_reaction_derivative self.dirichlet_data = dirichlet_data self.neumann_data = neumann_data self.robin_data = robin_data self.functionals = FrozenDict( functionals) if functionals is not None else None self.parameter_space = parameter_space self.name = name
class DiscretizationBase(DiscretizationInterface): """Base class for |Discretizations| providing some common functionality.""" sid_ignore = DiscretizationInterface.sid_ignore | {'visualizer'} add_with_arguments = DiscretizationInterface.add_with_arguments | {'operators'} special_operators = frozenset() def __init__(self, operators=None, products=None, estimator=None, visualizer=None, cache_region=None, name=None, **kwargs): operators = {} if operators is None else dict(operators) # handle special operators for on in self.special_operators: # special operators must be provided as keyword argument to __init__ assert on in kwargs # special operators may not already exist as attributes assert not hasattr(self, on) # special operators may not be contained in operators dict assert on not in operators op = kwargs[on] # operators either have to be None or an Operator assert op is None \ or isinstance(op, OperatorInterface) \ or all(isinstance(o, OperatorInterface) for o in op) # set special operator as an attribute setattr(self, on, op) # add special operator to the operators dict operators[on] = op self.operators = FrozenDict(operators) self.linear = all(op is None or op.linear for op in operators.values()) self.products = FrozenDict(products or {}) self.estimator = estimator self.visualizer = visualizer self.enable_caching(cache_region) self.name = name if products: for k, v in products.items(): setattr(self, '{}_product'.format(k), v) setattr(self, '{}_norm'.format(k), induced_norm(v)) self.build_parameter_type(*operators.values()) def with_(self, **kwargs): assert set(kwargs.keys()) <= self.with_arguments if 'operators' in kwargs: # extract special operators from provided operators dict operators = kwargs['operators'].copy() for on in self.special_operators: if on in operators: assert on not in kwargs or kwargs[on] == operators[on] kwargs[on] = operators.pop(on) kwargs['operators'] = operators else: # when an operators dict is not specified make sure that we use the old operators dict # but without the special operators kwargs['operators'] = {on: op for on, op in self.operators.items() if on not in self.special_operators} # delete empty 'operators' dicts for cases where __init__ does not take # an 'operator' argument if 'operators' not in self._init_arguments: operators = kwargs.pop('operators') # in that case, there should not be any operators left in 'operators' assert not operators return super(DiscretizationBase, self).with_(**kwargs) def visualize(self, U, **kwargs): """Visualize a solution |VectorArray| U. Parameters ---------- U The |VectorArray| from :attr:`~pymor.discretizations.interfaces.DiscretizationInterface.solution_space` that shall be visualized. kwargs See docstring of `self.visualizer.visualize`. """ if self.visualizer is not None: self.visualizer.visualize(U, self, **kwargs) else: raise NotImplementedError('Discretization has no visualizer.') def estimate(self, U, mu=None): if self.estimator is not None: return self.estimator.estimate(U, mu=mu, discretization=self) else: raise NotImplementedError('Discretization has no estimator.')
class Model(CacheableObject, Parametric): """Interface for model objects. A model object defines a discrete problem via its `class` and the |Operators| it contains. Furthermore, models can be :meth:`solved <Model.solve>` for a given |Parameter| resulting in a solution |VectorArray|. Attributes ---------- solution_space |VectorSpace| of the solution |VectorArrays| returned by :meth:`solve`. output_space |VectorSpace| of the model output |VectorArrays| returned by :meth:`output` (typically `NumpyVectorSpace(k)` where `k` is a small). linear `True` if the model describes a linear problem. products Dict of inner product operators associated with the model. """ solution_space = None output_space = None linear = False products = FrozenDict() def __init__(self, products=None, estimator=None, visualizer=None, name=None, **kwargs): products = FrozenDict(products or {}) if products: for k, v in products.items(): setattr(self, f'{k}_product', v) setattr(self, f'{k}_norm', induced_norm(v)) self.__auto_init(locals()) @abstractmethod def _solve(self, mu=None, return_output=False, **kwargs): """Perform the actual solving.""" pass def solve(self, mu=None, return_output=False, **kwargs): """Solve the discrete problem for the |Parameter| `mu`. The result will be :mod:`cached <pymor.core.cache>` in case caching has been activated for the given model. Parameters ---------- mu |Parameter| for which to solve. return_output If `True`, the model output for the given |Parameter| `mu` is returned as a |VectorArray| from :attr:`output_space`. Returns ------- The solution |VectorArray|. When `return_output` is `True`, the output |VectorArray| is returned as second value. """ mu = self.parse_parameter(mu) return self.cached_method_call(self._solve, mu=mu, return_output=return_output, **kwargs) def output(self, mu=None, **kwargs): """Return the model output for given |Parameter| `mu`. Parameters ---------- mu |Parameter| for which to compute the output. Returns ------- The computed model output as a |VectorArray| from `output_space`. """ return self.solve(mu=mu, return_output=True, **kwargs)[1] def estimate(self, U, mu=None): """Estimate the model error for a given solution. The model error could be the error w.r.t. the analytical solution of the given problem or the model reduction error w.r.t. a corresponding high-dimensional |Model|. Parameters ---------- U The solution obtained by :meth:`~solve`. mu |Parameter| for which `U` has been obtained. Returns ------- The estimated error. """ if getattr(self, 'estimator') is not None: return self.estimator.estimate(U, mu=mu, m=self) else: raise NotImplementedError('Model has no estimator.') def visualize(self, U, **kwargs): """Visualize a solution |VectorArray| U. Parameters ---------- U The |VectorArray| from :attr:`~pymor.models.interface.Model.solution_space` that shall be visualized. kwargs See docstring of `self.visualizer.visualize`. """ if getattr(self, 'visualizer') is not None: return self.visualizer.visualize(U, self, **kwargs) else: raise NotImplementedError('Model has no visualizer.')
class StationaryModel(ModelBase): """Generic class for models of stationary problems. This class describes discrete problems given by the equation:: L(u(μ), μ) = F(μ) with a vector-like right-hand side F and a (possibly non-linear) operator L. Note that even when solving a variational formulation where F is a functional and not a vector, F has to be specified as a vector-like |Operator| (mapping scalars to vectors). This ensures that in the complex case both L and F are anti-linear in the test variable. Parameters ---------- operator The |Operator| L. rhs The vector F. Either a |VectorArray| of length 1 or a vector-like |Operator|. outputs A dict of additional output |Functionals| associated with the model. products A dict of inner product |Operators| defined on the discrete space the problem is posed on. For each product a corresponding norm is added as a method of the model. parameter_space The |ParameterSpace| for which the discrete problem is posed. estimator An error estimator for the problem. This can be any object with an `estimate(U, mu, m)` method. If `estimator` is not `None`, an `estimate(U, mu)` method is added to the model which will call `estimator.estimate(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. cache_region `None` or name of the |CacheRegion| to use. name Name of the model. Attributes ---------- operator The |Operator| L. The same as `operators['operator']`. rhs The right-hand side F. The same as `operators['rhs']`. outputs Dict of all output |Functionals|. products Dict of all product |Operators| associated with the model. """ def __init__(self, operator, rhs, outputs=None, products=None, parameter_space=None, estimator=None, visualizer=None, cache_region=None, name=None): if isinstance(rhs, VectorArrayInterface): assert rhs in operator.range rhs = VectorOperator(rhs, name='rhs') assert rhs.range == operator.range and rhs.source.is_scalar and rhs.linear super().__init__(products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.operator = operator self.rhs = rhs self.outputs = FrozenDict(outputs or {}) self.solution_space = self.operator.source self.linear = operator.linear and all( output.linear for output in self.outputs.values()) self.build_parameter_type(operator, rhs) self.parameter_space = parameter_space def _solve(self, mu=None): mu = self.parse_parameter(mu) # explicitly checking if logging is disabled saves the str(mu) call if not self.logging_disabled: self.logger.info(f'Solving {self.name} for {mu} ...') return self.operator.apply_inverse(self.rhs.as_range_array(mu), mu=mu)
class InstationaryModel(ModelBase): """Generic class for models of instationary problems. This class describes instationary problems given by the equations:: M * ∂_t u(t, μ) + L(u(μ), t, μ) = F(t, μ) u(0, μ) = u_0(μ) for t in [0,T], where L is a (possibly non-linear) time-dependent |Operator|, F is a time-dependent vector-like |Operator|, and u_0 the initial data. The mass |Operator| M is assumed to be linear, time-independent and |Parameter|-independent. Parameters ---------- T The final time T. initial_data The initial data `u_0`. Either a |VectorArray| of length 1 or (for the |Parameter|-dependent case) a vector-like |Operator| (i.e. a linear |Operator| with `source.dim == 1`) which applied to `NumpyVectorArray(np.array([1]))` will yield the initial data for a given |Parameter|. operator The |Operator| L. rhs The right-hand side F. mass The mass |Operator| `M`. If `None`, the identity is assumed. time_stepper The :class:`time-stepper <pymor.algorithms.timestepping.TimeStepperInterface>` to be used by :meth:`~pymor.models.interfaces.ModelInterface.solve`. num_values The number of returned vectors of the solution trajectory. If `None`, each intermediate vector that is calculated is returned. outputs A dict of additional output |Functionals| associated with the model. products A dict of product |Operators| defined on the discrete space the problem is posed on. For each product a corresponding norm is added as a method of the model. parameter_space The |ParameterSpace| for which the discrete problem is posed. estimator An error estimator for the problem. This can be any object with an `estimate(U, mu, m)` method. If `estimator` is not `None`, an `estimate(U, mu)` method is added to the model which will call `estimator.estimate(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. cache_region `None` or name of the |CacheRegion| to use. name Name of the model. Attributes ---------- T The final time T. initial_data The intial data u_0 given by a vector-like |Operator|. The same as `operators['initial_data']`. operator The |Operator| L. The same as `operators['operator']`. rhs The right-hand side F. The same as `operators['rhs']`. mass The mass operator M. The same as `operators['mass']`. time_stepper The provided :class:`time-stepper <pymor.algorithms.timestepping.TimeStepperInterface>`. outputs Dict of all output |Functionals|. products Dict of all product |Operators| associated with the model. """ def __init__(self, T, initial_data, operator, rhs, mass=None, time_stepper=None, num_values=None, outputs=None, products=None, parameter_space=None, estimator=None, visualizer=None, cache_region=None, name=None): if isinstance(rhs, VectorArrayInterface): assert rhs in operator.range rhs = VectorOperator(rhs, name='rhs') if isinstance(initial_data, VectorArrayInterface): assert initial_data in operator.source initial_data = VectorOperator(initial_data, name='initial_data') assert isinstance(time_stepper, TimeStepperInterface) assert initial_data.source.is_scalar assert operator.source == initial_data.range assert rhs is None \ or rhs.linear and rhs.range == operator.range and rhs.source.is_scalar assert mass is None \ or mass.linear and mass.source == mass.range == operator.source super().__init__(products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.T = T self.initial_data = initial_data self.operator = operator self.rhs = rhs self.mass = mass self.solution_space = self.operator.source self.time_stepper = time_stepper self.num_values = num_values self.outputs = FrozenDict(outputs or {}) self.build_parameter_type(self.initial_data, self.operator, self.rhs, self.mass, provides={'_t': 0}) self.parameter_space = parameter_space def with_time_stepper(self, **kwargs): return self.with_(time_stepper=self.time_stepper.with_(**kwargs)) def _solve(self, mu=None): mu = self.parse_parameter(mu).copy() # explicitly checking if logging is disabled saves the expensive str(mu) call if not self.logging_disabled: self.logger.info(f'Solving {self.name} for {mu} ...') mu['_t'] = 0 U0 = self.initial_data.as_range_array(mu) return self.time_stepper.solve(operator=self.operator, rhs=self.rhs, initial_data=U0, mass=self.mass, initial_time=0, end_time=self.T, mu=mu, num_values=self.num_values) def to_lti(self): """Convert model to |LTIModel|. This method interprets the given model as an |LTIModel| in the following way:: - self.operator -> A self.rhs -> B self.outputs -> C None -> D self.mass -> E """ if len(self.outputs) == 0: raise ValueError('No outputs defined.') if len(self.outputs) > 1: raise NotImplementedError('Only one output supported.') A = -self.operator B = self.rhs C = next(iter(self.outputs.values())) E = self.mass if not all(op.linear for op in [A, B, C, E]): raise ValueError('Operators not linear.') from pymor.models.iosys import LTIModel return LTIModel(A, B, C, E=E, visualizer=self.visualizer)
def __init__(self, domain, rhs=None, diffusion=None, advection=None, nonlinear_advection=None, nonlinear_advection_derivative=None, reaction=None, nonlinear_reaction=None, nonlinear_reaction_derivative=None, dirichlet_data=None, neumann_data=None, robin_data=None, outputs=None, parameter_ranges=None, name=None): assert (rhs is None or rhs.dim_domain == domain.dim and rhs.shape_range == ()) assert (diffusion is None or diffusion.dim_domain == domain.dim and diffusion.shape_range in ((), (domain.dim, domain.dim))) assert (advection is None or advection.dim_domain == domain.dim and advection.shape_range == (domain.dim, )) assert (nonlinear_advection is None or nonlinear_advection.dim_domain == 1 and nonlinear_advection.shape_range == (domain.dim, )) assert (nonlinear_advection_derivative is None or (nonlinear_advection_derivative.dim_domain == 1 and nonlinear_advection_derivative.shape_range == (domain.dim, ))) assert (reaction is None or reaction.dim_domain == domain.dim and reaction.shape_range == ()) assert (nonlinear_reaction is None or nonlinear_reaction.dim_domain == 1 and nonlinear_reaction.shape_range == ()) assert (nonlinear_reaction_derivative is None or nonlinear_reaction_derivative.dim_domain == 1 and nonlinear_reaction_derivative.shape_range == ()) assert (dirichlet_data is None or dirichlet_data.dim_domain == domain.dim and dirichlet_data.shape_range == ()) assert (neumann_data is None or neumann_data.dim_domain == domain.dim and neumann_data.shape_range == ()) assert (robin_data is None or (isinstance(robin_data, tuple) and len(robin_data) == 2 and np.all([ f.dim_domain == domain.dim and f.shape_range == () for f in robin_data ]))) assert (outputs is None or all( isinstance(v, tuple) and len(v) == 2 and v[0] in ('l2', 'l2_boundary') and v[1].dim_domain == domain.dim and v[1].shape_range == () for v in outputs)) assert ( parameter_ranges is None or (isinstance(parameter_ranges, (list, tuple)) and len(parameter_ranges) == 2 and parameter_ranges[0] <= parameter_ranges[1]) or (isinstance(parameter_ranges, dict) and all( isinstance(v, (list, tuple)) and len(v) == 2 and v[0] <= v[1] for v in parameter_ranges.values()))) outputs = tuple(outputs) if outputs is not None else None parameter_ranges = (None if parameter_ranges is None else tuple(parameter_ranges) if isinstance( parameter_ranges, (list, tuple)) else FrozenDict( (k, tuple(v)) for k, v in parameter_ranges.items())) self.__auto_init(locals())
class Model(CacheableObject, ParametricObject): """Interface for model objects. A model object defines a discrete problem via its `class` and the |Operators| it contains. Furthermore, models can be :meth:`solved <Model.solve>` for given |parameter values| resulting in a solution |VectorArray|. Attributes ---------- solution_space |VectorSpace| of the solution |VectorArrays| returned by :meth:`solve`. dim_output Dimension of the model output returned by :meth:`output`. 0 if the model has no output. linear `True` if the model describes a linear problem. products Dict of inner product operators associated with the model. """ solution_space = None dim_output = 0 linear = False products = FrozenDict() def __init__(self, products=None, error_estimator=None, visualizer=None, name=None): products = FrozenDict(products or {}) if products: for k, v in products.items(): setattr(self, f'{k}_product', v) setattr(self, f'{k}_norm', induced_norm(v)) self.__auto_init(locals()) def _compute(self, solution=False, output=False, solution_d_mu=False, output_d_mu=False, solution_error_estimate=False, output_error_estimate=False, output_d_mu_return_array=False, mu=None, **kwargs): return {} def _compute_solution(self, mu=None, **kwargs): """Compute the model's solution for |parameter values| `mu`. This method is called by the default implementation of :meth:`compute` in :class:`pymor.models.interface.Model`. Parameters ---------- mu |Parameter values| for which to compute the solution. kwargs Additional keyword arguments to customize how the solution is computed or to select additional data to be returned. Returns ------- |VectorArray| with the computed solution or a dict which at least must contain the key `'solution'`. """ raise NotImplementedError def _compute_output(self, solution, mu=None, **kwargs): """Compute the model's output for |parameter values| `mu`. This method is called by the default implementation of :meth:`compute` in :class:`pymor.models.interface.Model`. The assumption is made that the output is a derived quantity from the model's internal state as returned my :meth:`_compute_solution`. When this is not the case, the computation of the output should be implemented in :meth:`_compute`. .. note:: The default implementation applies the |Operator| given by the :attr:`output_functional` attribute to the given `solution` |VectorArray|. Parameters ---------- solution Internal model state for the given |parameter values|. mu |Parameter values| for which to compute the output. kwargs Additional keyword arguments to customize how the output is computed or to select additional data to be returned. Returns ------- |NumPy array| with the computed output or a dict which at least must contain the key `'output'`. """ if not getattr(self, 'output_functional', None): return np.zeros(len(solution), 0) else: return self.output_functional.apply(solution, mu=mu).to_numpy() def _compute_solution_d_mu_single_direction(self, parameter, index, solution, mu=None, **kwargs): """Compute the partial derivative of the solution w.r.t. a parameter index Parameters ---------- parameter parameter for which to compute the sensitivity index parameter index for which to compute the sensitivity solution Internal model state for the given |Parameter value|. mu |Parameter value| for which to solve Returns ------- The sensitivity of the solution as a |VectorArray|. """ raise NotImplementedError def _compute_solution_d_mu(self, solution, mu=None, **kwargs): """Compute all partial derivative of the solution w.r.t. a parameter index Parameters ---------- solution Internal model state for the given |Parameter value|. mu |Parameter value| for which to solve Returns ------- A dict of all partial sensitivities of the solution. """ sensitivities = {} for (parameter, size) in self.parameters.items(): sens_for_param = self.solution_space.empty() for l in range(size): sens_for_param.append( self._compute_solution_d_mu_single_direction( parameter, l, solution, mu)) sensitivities[parameter] = sens_for_param return sensitivities def _compute_output_d_mu(self, solution, mu=None, return_array=False, **kwargs): """compute the gradient w.r.t. the parameter of the output functional Parameters ---------- solution Internal model state for the given |Parameter value|. mu |Parameter value| for which to compute the gradient return_array if `True`, return the output gradient as a |NumPy array|. Otherwise, return a dict of gradients for each |Parameter|. Returns ------- The gradient as a |NumPy array| or a dict of |NumPy arrays|. """ assert self.output_functional is not None U_d_mus = self._compute_solution_d_mu(solution, mu) gradients = [] if return_array else {} for (parameter, size) in self.parameters.items(): array = np.empty(shape=(size, self.output_functional.range.dim)) for index in range(size): output_partial_dmu = self.output_functional.d_mu( parameter, index).apply(solution, mu=mu).to_numpy()[0] U_d_mu = U_d_mus[parameter][index] array[ index] = output_partial_dmu + self.output_functional.jacobian( solution, mu).apply(U_d_mu, mu).to_numpy()[0] if return_array: gradients.extend(array) else: gradients[parameter] = array if return_array: return np.array(gradients) else: return gradients def _compute_solution_error_estimate(self, solution, mu=None, **kwargs): """Compute an error estimate for the computed internal state. This method is called by the default implementation of :meth:`compute` in :class:`pymor.models.interface.Model`. The assumption is made that the error estimate is a derived quantity from the model's internal state as returned my :meth:`_compute_solution`. When this is not the case, the computation of the error estimate should be implemented in :meth:`_compute`. .. note:: The default implementation calls the `estimate_error` method of the object given by the :attr:`error_estimator` attribute, passing `solution`, `mu`, `self` and `**kwargs`. Parameters ---------- solution Internal model state for the given |parameter values|. mu |Parameter values| for which to compute the error estimate. kwargs Additional keyword arguments to customize how the error estimate is computed or to select additional data to be returned. Returns ------- The computed error estimate or a dict which at least must contain the key `'solution_error_estimate'`. """ if self.error_estimator is None: raise ValueError('Model has no error estimator') return self.error_estimator.estimate_error(solution, mu, self, **kwargs) def _compute_output_error_estimate(self, solution, mu=None, **kwargs): """Compute an error estimate for the computed model output. This method is called by the default implementation of :meth:`compute` in :class:`pymor.models.interface.Model`. The assumption is made that the error estimate is a derived quantity from the model's internal state as returned my :meth:`_compute_solution`. When this is not the case, the computation of the error estimate should be implemented in :meth:`_compute`. .. note:: The default implementation calls the `estimate_output_error` method of the object given by the :attr:`error_estimator` attribute, passing `solution`, `mu`, `self` and `**kwargs`. Parameters ---------- solution Internal model state for the given |parameter values|. mu |Parameter values| for which to compute the error estimate. kwargs Additional keyword arguments to customize how the error estimate is computed or to select additional data to be returned. Returns ------- The computed error estimate or a dict which at least must contain the key `'solution_error_estimate'`. """ if self.error_estimator is None: raise ValueError('Model has no error estimator') return self.error_estimator.estimate_output_error( solution, mu, self, **kwargs) _compute_allowed_kwargs = frozenset() def compute(self, solution=False, output=False, solution_d_mu=False, output_d_mu=False, solution_error_estimate=False, output_error_estimate=False, output_d_mu_return_array=False, *, mu=None, **kwargs): """Compute the solution of the model and associated quantities. This methods computes the output of the model it's internal state and various associated quantities for given |parameter values| `mu`. .. note:: The default implementation defers the actual computations to the methods :meth:`_compute_solution`, :meth:`_compute_output`, :meth:`_compute_solution_error_estimate` and :meth:`_compute_output_error_estimate`. The call to :meth:`_compute_solution` is :mod:`cached <pymor.core.cache>`. In addition, |Model| implementors may implement :meth:`_compute` to simultaneously compute multiple values in an optimized way. The corresponding `_compute_XXX` methods will not be called for values already returned by :meth:`_compute`. Parameters ---------- solution If `True`, return the model's internal state. output If `True`, return the model output. solution_d_mu If not `False`, either `True` to return the derivative of the model's internal state w.r.t. all parameter components or a tuple `(parameter, index)` to return the derivative of a single parameter component. output_d_mu If `True`, return the gradient of the model output w.r.t. the |Parameter|. solution_error_estimate If `True`, return an error estimate for the computed internal state. output_error_estimate If `True`, return an error estimate for the computed output. output_d_mu_return_array if `True`, return the output gradient as a |NumPy array|. Otherwise, return a dict of gradients for each |Parameter|. mu |Parameter values| for which to compute the values. kwargs Further keyword arguments to select further quantities that sould be returned or to customize how the values are computed. Returns ------- A dict with the computed values. """ # make sure no unknown kwargs are passed assert kwargs.keys() <= self._compute_allowed_kwargs # parse parameter values if not isinstance(mu, Mu): mu = self.parameters.parse(mu) assert self.parameters.assert_compatible(mu) # log output # explicitly checking if logging is disabled saves some cpu cycles if not self.logging_disabled: self.logger.info(f'Solving {self.name} for {mu} ...') # first call _compute to give subclasses more control data = self._compute(solution=solution, output=output, solution_d_mu=solution_d_mu, output_d_mu=output_d_mu, solution_error_estimate=solution_error_estimate, output_error_estimate=output_error_estimate, mu=mu, **kwargs) if (solution or output or solution_error_estimate or output_error_estimate or solution_d_mu or output_d_mu) \ and 'solution' not in data: retval = self.cached_method_call(self._compute_solution, mu=mu, **kwargs) if isinstance(retval, dict): assert 'solution' in retval data.update(retval) else: data['solution'] = retval if output and 'output' not in data: # TODO use caching here (requires skipping args in key generation) retval = self._compute_output(data['solution'], mu=mu, **kwargs) if isinstance(retval, dict): assert 'output' in retval data.update(retval) else: data['output'] = retval if solution_d_mu and 'solution_d_mu' not in data: if isinstance(solution_d_mu, tuple): retval = self._compute_solution_d_mu_single_direction( solution_d_mu[0], solution_d_mu[1], data['solution'], mu=mu, **kwargs) else: retval = self._compute_solution_d_mu(data['solution'], mu=mu, **kwargs) # retval is always a dict if isinstance(retval, dict) and 'solution_d_mu' in retval: data.update(retval) else: data['solution_d_mu'] = retval if output_d_mu and 'output_d_mu' not in data: # TODO use caching here (requires skipping args in key generation) retval = self._compute_output_d_mu( data['solution'], mu=mu, return_array=output_d_mu_return_array, **kwargs) # retval is always a dict if isinstance(retval, dict) and 'output_d_mu' in retval: data.update(retval) else: data['output_d_mu'] = retval if solution_error_estimate and 'solution_error_estimate' not in data: # TODO use caching here (requires skipping args in key generation) retval = self._compute_solution_error_estimate(data['solution'], mu=mu, **kwargs) if isinstance(retval, dict): assert 'solution_error_estimate' in retval data.update(retval) else: data['solution_error_estimate'] = retval if output_error_estimate and 'output_error_estimate' not in data: # TODO use caching here (requires skipping args in key generation) retval = self._compute_output_error_estimate(data['solution'], mu=mu, **kwargs) if isinstance(retval, dict): assert 'output_error_estimate' in retval data.update(retval) else: data['output_error_estimate'] = retval return data def solve(self, mu=None, return_error_estimate=False, **kwargs): """Solve the discrete problem for the |parameter values| `mu`. This method returns a |VectorArray| with a internal state representation of the model's solution for given |parameter values|. It is a convenience wrapper around :meth:`compute`. The result may be :mod:`cached <pymor.core.cache>` in case caching has been activated for the given model. Parameters ---------- mu |Parameter values| for which to solve. return_error_estimate If `True`, also return an error estimate for the computed solution. kwargs Additional keyword arguments passed to :meth:`compute` that might affect how the solution is computed. Returns ------- The solution |VectorArray|. When `return_error_estimate` is `True`, the estimate is returned as second value. """ data = self.compute(solution=True, solution_error_estimate=return_error_estimate, mu=mu, **kwargs) if return_error_estimate: return data['solution'], data['solution_error_estimate'] else: return data['solution'] def output(self, mu=None, return_error_estimate=False, **kwargs): """Return the model output for given |parameter values| `mu`. This method is a convenience wrapper around :meth:`compute`. Parameters ---------- mu |Parameter values| for which to compute the output. return_error_estimate If `True`, also return an error estimate for the computed output. kwargs Additional keyword arguments passed to :meth:`compute` that might affect how the solution is computed. Returns ------- The computed model output as a 2D |NumPy array|. The dimension of axis 1 is :attr:`dim_output`. (For stationary problems, axis 0 has dimension 1. For time-dependent problems, the dimension of axis 0 depends on the number of time steps.) When `return_error_estimate` is `True`, the estimate is returned as second value. """ data = self.compute(output=True, output_error_estimate=return_error_estimate, mu=mu, **kwargs) if return_error_estimate: return data['output'], data['output_error_estimate'] else: return data['output'] def solve_d_mu(self, parameter, index, mu=None, **kwargs): """Solve for the partial derivative of the solution w.r.t. a parameter index Parameters ---------- parameter parameter for which to compute the sensitivity index parameter index for which to compute the sensitivity mu |Parameter value| for which to solve Returns ------- The sensitivity of the solution as a |VectorArray|. """ data = self.compute(solution_d_mu=(parameter, index), mu=mu, **kwargs) return data['solution_d_mu'] def output_d_mu(self, mu=None, return_array=False, **kwargs): """compute the gradient w.r.t. the parameter of the output functional Parameters ---------- mu |Parameter value| for which to compute the gradient return_array if `True`, return the output gradient as a |NumPy array|. Otherwise, return a dict of gradients for each |Parameter|. Returns ------- The gradient as a |NumPy array| or a dict of |NumPy arrays|. """ data = self.compute(output_d_mu=True, mu=mu, output_d_mu_return_array=return_array, **kwargs) return data['output_d_mu'] def estimate_error(self, mu=None, **kwargs): """Estimate the error for the computed internal state. For given |parameter values| `mu` this method returns an error estimate for the computed internal model state as returned by :meth:`solve`. It is a convenience wrapper around :meth:`compute`. The model error could be the error w.r.t. the analytical solution of the given problem or the model reduction error w.r.t. a corresponding high-dimensional |Model|. Parameters ---------- mu |Parameter values| for which to estimate the error. kwargs Additional keyword arguments passed to :meth:`compute` that might affect how the error estimate (or the solution) is computed. Returns ------- The estimated error. """ return self.compute(solution_error_estimate=True, mu=mu, **kwargs)['solution_error_estimate'] @Deprecated('estimate_error') def estimate(self, U, mu=None): return self.estimate_error(mu) def estimate_output_error(self, mu=None, **kwargs): """Estimate the error for the computed output. For given |parameter values| `mu` this method returns an error estimate for the computed model output as returned by :meth:`output`. It is a convenience wrapper around :meth:`compute`. The output error could be the error w.r.t. the analytical solution of the given problem or the model reduction error w.r.t. a corresponding high-dimensional |Model|. Parameters ---------- mu |Parameter values| for which to estimate the error. kwargs Additional keyword arguments passed to :meth:`compute` that might affect how the error estimate (or the output) is computed. Returns ------- The estimated error. """ return self.compute(output_error_estimate=True, mu=mu, **kwargs)['output_error_estimate'] def visualize(self, U, **kwargs): """Visualize a |VectorArray| U of the model's :attr:`solution_space`. Parameters ---------- U The |VectorArray| from :attr:`solution_space` that shall be visualized. kwargs Additional keyword arguments to customize the visualization. See the docstring of `self.visualizer.visualize`. """ if getattr(self, 'visualizer') is not None: return self.visualizer.visualize(U, self, **kwargs) else: raise NotImplementedError('Model has no visualizer.')
class WrappedDiscretization(DiscretizationInterface): wrapped_type = cls _wrapper = wrapper def __init__(self, d): def wrap_op(op, name=None): if not op.parametric() and op.num_components() == 0 and op.has_affine_part(): wrapped_op = self._wrapper[op.affine_part()] else: wrapped_op = self._wrapper[op] return wrapped_op.with_(name=name) if name else wrapped_op self._impl = d operators = {'operator': wrap_op(d.get_operator())} functionals = {'rhs': self._wrapper[d.get_rhs()]} vector_operators = FrozenDict({k: VectorOperator(make_listvectorarray(self._wrapper[d.get_vector(k)])) for k in list(d.available_vectors())}) self.operators = FrozenDict(operators) self.functionals = FrozenDict(functionals) self.vector_operators = FrozenDict(vector_operators) self.operator = operators['operator'] self.rhs = functionals['rhs'] self.products = FrozenDict({k: wrap_op(d.get_product(k), k) for k in list(d.available_products())}) if self.products: for k, v in self.products.iteritems(): setattr(self, '{}_product'.format(k), v) setattr(self, '{}_norm'.format(k), induced_norm(v)) self.linear = all(op.linear for op in operators.itervalues()) self.solution_space = self.operator.source self.build_parameter_type(inherits=operators.values() + functionals.values()) assert self.parameter_type == self._wrapper[d.parameter_type()] self.solver_options = self._impl.solver_options() with_arguments = frozenset({'operators', 'functionals', 'vector_operators', 'solver_options', 'cache_region'}) def with_(self, **kwargs): assert 'vector_operators' not in kwargs or not kwargs['vector_operators'] if 'operators' in kwargs and 'functionals' in kwargs: operators = kwargs.pop('operators') functionals = kwargs.pop('functionals') assert set(operators.keys()) == {'operator'} assert set(functionals.keys()) == {'rhs'} operator = operators['operator'] rhs = functionals['rhs'] d = StationaryDiscretization(operator=operator, rhs=rhs, parameter_space=self.parameter_space) return d.with_(**kwargs) elif 'cache_region' in kwargs: d = type(self)(self._impl) d.unlock() d.enable_caching(kwargs.pop('cache_region')) d.lock() return d.with_(**kwargs) else: d = type(self)(self._impl) d.unlock() for attr in ('solver_options', 'parameter_space'): if attr in dir(self): setattr(d, attr, getattr(self, attr)) if 'parameter_space' in kwargs: d.parameter_space = kwargs.pop('parameter_space') assert len(kwargs) == 0 d.lock() return d def solve(self, mu=None): mu = self.parse_parameter(mu) if not self.logging_disabled: self.logger.info('Solving {} for {} ...'.format(self.name, mu)) mu = self._wrapper.dune_parameter(mu) solution = self._impl.solve_and_return_ptr(self.solver_options, mu) assert solution.valid() solution = self._wrapper[solution] return ListVectorArray([solution]) _solve = solve def visualize(self, U, file_name=None, name='solution', delete=True, legend=None, separate_colorbars=None): if isinstance(U, tuple) or isinstance(U, list): Us = [V._list[0] for V in U] else: Us = U._list if not legend: legend = [name for ii in range(len(Us))] assert len(Us) == len(legend) if not self.logging_disabled: self.logger.info('Visualizing {} functions ...'.format(len(Us))) for V, name in izip(Us, legend): if file_name is None: _, file_name = mkstemp(suffix='.vtu') if not file_name.endswith('.vtu'): file_name = file_name + '.vtu' self._impl.visualize(V._impl, file_name[:-4], name) subprocess.call(['paraview', file_name]) if delete: os.remove(file_name)
class InstationaryModel(ModelBase): """Generic class for models of instationary problems. This class describes instationary problems given by the equations:: M * ∂_t u(t, μ) + L(u(μ), t, μ) = F(t, μ) u(0, μ) = u_0(μ) for t in [0,T], where L is a (possibly non-linear) time-dependent |Operator|, F is a time-dependent vector-like |Operator|, and u_0 the initial data. The mass |Operator| M is assumed to be linear, time-independent and |Parameter|-independent. Parameters ---------- T The final time T. initial_data The initial data `u_0`. Either a |VectorArray| of length 1 or (for the |Parameter|-dependent case) a vector-like |Operator| (i.e. a linear |Operator| with `source.dim == 1`) which applied to `NumpyVectorArray(np.array([1]))` will yield the initial data for a given |Parameter|. operator The |Operator| L. rhs The right-hand side F. mass The mass |Operator| `M`. If `None`, the identity is assumed. time_stepper The :class:`time-stepper <pymor.algorithms.timestepping.TimeStepperInterface>` to be used by :meth:`~pymor.models.interfaces.ModelInterface.solve`. num_values The number of returned vectors of the solution trajectory. If `None`, each intermediate vector that is calculated is returned. outputs A dict of additional output |Functionals| associated with the model. products A dict of product |Operators| defined on the discrete space the problem is posed on. For each product a corresponding norm is added as a method of the model. parameter_space The |ParameterSpace| for which the discrete problem is posed. estimator An error estimator for the problem. This can be any object with an `estimate(U, mu, m)` method. If `estimator` is not `None`, an `estimate(U, mu)` method is added to the model which will call `estimator.estimate(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. cache_region `None` or name of the |CacheRegion| to use. name Name of the model. Attributes ---------- T The final time T. initial_data The intial data u_0 given by a vector-like |Operator|. The same as `operators['initial_data']`. operator The |Operator| L. The same as `operators['operator']`. rhs The right-hand side F. The same as `operators['rhs']`. mass The mass operator M. The same as `operators['mass']`. time_stepper The provided :class:`time-stepper <pymor.algorithms.timestepping.TimeStepperInterface>`. outputs Dict of all output |Functionals|. products Dict of all product |Operators| associated with the model. """ def __init__(self, T, initial_data, operator, rhs, mass=None, time_stepper=None, num_values=None, outputs=None, products=None, parameter_space=None, estimator=None, visualizer=None, cache_region=None, name=None): if isinstance(rhs, VectorArrayInterface): assert rhs in operator.range rhs = VectorOperator(rhs, name='rhs') if isinstance(initial_data, VectorArrayInterface): assert initial_data in operator.source initial_data = VectorOperator(initial_data, name='initial_data') assert isinstance(time_stepper, TimeStepperInterface) assert initial_data.source.is_scalar assert operator.source == initial_data.range assert rhs is None \ or rhs.linear and rhs.range == operator.range and rhs.source.is_scalar assert mass is None \ or mass.linear and mass.source == mass.range == operator.source super().__init__(products=products, estimator=estimator, visualizer=visualizer, cache_region=cache_region, name=name) self.T = T self.initial_data = initial_data self.operator = operator self.rhs = rhs self.mass = mass self.solution_space = self.operator.source self.time_stepper = time_stepper self.num_values = num_values self.outputs = FrozenDict(outputs or {}) self.build_parameter_type(self.initial_data, self.operator, self.rhs, self.mass, provides={'_t': 0}) self.parameter_space = parameter_space def with_time_stepper(self, **kwargs): return self.with_(time_stepper=self.time_stepper.with_(**kwargs)) def _solve(self, mu=None): mu = self.parse_parameter(mu).copy() # explicitly checking if logging is disabled saves the expensive str(mu) call if not self.logging_disabled: self.logger.info(f'Solving {self.name} for {mu} ...') mu['_t'] = 0 U0 = self.initial_data.as_range_array(mu) return self.time_stepper.solve(operator=self.operator, rhs=self.rhs, initial_data=U0, mass=self.mass, initial_time=0, end_time=self.T, mu=mu, num_values=self.num_values) def to_lti(self): """Convert model to |LTIModel|. This method interprets the given model as an |LTIModel| in the following way:: - self.operator -> A self.rhs -> B self.outputs -> C None -> D self.mass -> E """ if len(self.outputs) == 0: raise ValueError('No outputs defined.') if len(self.outputs) > 1: raise NotImplementedError('Only one output supported.') A = - self.operator B = self.rhs C = next(iter(self.outputs.values())) E = self.mass if not all(op.linear for op in [A, B, C, E]): raise ValueError('Operators not linear.') from pymor.models.iosys import LTIModel return LTIModel(A, B, C, E=E, visualizer=self.visualizer)