# coding=utf-8
"""
.. moduleauthor:: Torbjörn Klatt <[email protected]>
"""
from collections import OrderedDict
from pypint.problems.i_problem import IProblem
from pypint.utilities import assert_is_callable, assert_is_instance, class_name
[docs]class HasExactSolutionMixin(object):
    """Provides exact analytical solution function for a problem.
    """
[docs]    def __init__(self, *args, **kwargs):
        """
        Parameters
        ----------
        exact_function : :py:class:`callable`
            *(optional)*
            If given initializes the problem with the exact solution function.
        """
        assert_is_instance(self, IProblem, descriptor="Problem needs to be a IProblem first: NOT %s" % class_name(self),
                           checking_obj=self)
        self._exact_function = None
        if 'exact_function' in kwargs:
            self.exact_function = kwargs['exact_function']
        self._strings['exact'] = None
        if 'strings' in kwargs:
            if 'exact' in kwargs['strings']:
                assert_is_instance(kwargs['strings']['exact'], str,
                                   descriptor="String representation of Exact Function", checking_obj=self)
                self._strings['exact'] = kwargs['strings']['exact']
 
[docs]    def exact(self, time):
        """Evaluates given exact solution function at given time and with given time-dependent data.
        Parameters
        ----------
        time : :py:class:`float`
            Time point :math:`t`
        Returns
        -------
        exact_solution : :py:class:`numpy.ndarray`
        Raises
        ------
        ValueError :
            * if ``time`` is not a :py:class:`float`
            * if ``phi_of_time`` is not a :py:class:`numpy.ndarray`
            * if not exact function is given
        """
        assert_is_instance(time, float, descriptor="Time Point", checking_obj=self)
        assert_is_callable(self._exact_function, descriptor="Exact Function", checking_obj=self)
        return self._exact_function(time)
 
    @property
    def exact_function(self):
        """Accessor for the exact solution function.
        Raises
        ------
        ValueError :
            On setting, if the new exact solution function is not callable.
        """
        return self._exact_function
    @exact_function.setter
[docs]    def exact_function(self, exact_function):
        assert_is_callable(exact_function, descriptor="Exact Function", checking_obj=self)
        self._exact_function = exact_function
 
    def print_lines_for_log(self):
        _lines = OrderedDict()
        _lines.update({'Exact': self._strings['exact']})
        return _lines
    def __str__(self):
        return r", exact: %s" % self._strings['exact']
 
[docs]def problem_has_exact_solution(problem, checking_obj=None):
    """Convenience accessor for exact solution.
    Parameters
    ----------
    problem : :py:class:`.IProblem`
        The problem to check for an exact solution function.
    checking_obj : object
        *(optional)*
        The object calling this function for a meaningful error message.
        For debugging purposes only.
    Returns
    -------
    has_exact_solution : :py:class:`bool`
        :py:class:`True` if exact solution was given, :py:class:`False` otherwise
    Raises
    ------
    ValueError :
        If the given problem is not an instance of :py:class:`.IProblem`.
    """
    assert_is_instance(problem, IProblem, message="It needs to be a problem to have an exact solution.",
                       checking_obj=checking_obj)
    return isinstance(problem, HasExactSolutionMixin)
 
__all__ = ['problem_has_exact_solution', 'HasExactSolutionMixin']