Example #1
0
    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
Example #2
0
    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))
Example #3
0
    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())
Example #4
0
    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
Example #5
0
File: basic.py Project: pymor/pymor
    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
Example #6
0
    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())
Example #7
0
 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()
Example #8
0
    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())
Example #9
0
 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())
Example #10
0
    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))
Example #11
0
    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())
Example #12
0
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.')
Example #13
0
    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
Example #14
0
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.')
Example #15
0
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.')
Example #16
0
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)
Example #17
0
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)
Example #18
0
    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())
Example #19
0
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.')
Example #20
0
    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)
Example #21
0
File: basic.py Project: pymor/pymor
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)