colour.algebra.extrapolation — Colour 0.3.0 documentation

# Source code for colour.algebra.extrapolation

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """
    Extrapolation
    =============

    Defines classes for extrapolating variables:

    -   :class:`Extrapolator1d`: 1-D function extrapolation.
    """

    from __future__ import division, unicode_literals

    import numpy as np

    from colour.algebra import is_numeric, to_ndarray

    __author__ = 'Colour Developers'
    __copyright__ = 'Copyright (C) 2013 - 2014 - Colour Developers'
    __license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
    __maintainer__ = 'Colour Developers'
    __email__ = 'colour-science@googlegroups.com'
    __status__ = 'Production'

    __all__ = ['Extrapolator1d']

    class Extrapolator1d(object):
        """
        Extrapolates the 1-D function of given interpolator.

        The Extrapolator1d acts as a wrapper around a given *Colour* or *scipy*
        interpolator class instance with compatible signature. Two extrapolation
        methods are available:

        -   *Linear*: Linearly extrapolates given points using the slope defined by
            the interpolator boundaries (xi[0], xi[1]) if x < xi[0] and
            (xi[-1], xi[-2]) if x > xi[-1].
        -   *Constant*: Extrapolates given points by assigning the interpolator
            boundaries values xi[0] if x < xi[0] and xi[-1] if x > xi[-1].

        Specifying the *left* and *right* arguments takes precedence on the chosen
        extrapolation method and will assign the respective *left* and *right*
        values to the given points.

        Parameters
        ----------
        interpolator : object
            Interpolator object.
        method : unicode, optional
            ('Linear', 'Constant'),
            Extrapolation method.
        left : numeric, optional
            Value to return for x < xi[0].
        right : numeric, optional
            Value to return for x > xi[-1].

        Methods
        -------
        __class__

        Notes
        -----
        The interpolator must define *x* and *y* attributes.

        References
        ----------
        .. [1]  http://stackoverflow.com/a/2745496/931625
                (Last accessed 8 August 2014)

        Examples
        --------
        Extrapolating a single numeric variable:

        >>> from colour.algebra import LinearInterpolator1d
        >>> x = np.array([3, 4, 5])
        >>> y = np.array([1, 2, 3])
        >>> interpolator = LinearInterpolator1d(x, y)
        >>> extrapolator = Extrapolator1d(interpolator)
        >>> extrapolator(1)
        -1.0

        Extrapolating an *array_like* variable:

        >>> extrapolator(np.array([6, 7 , 8]))
        array([ 4.,  5.,  6.])

        Using the *Constant* extrapolation method:

        >>> x = np.array([3, 4, 5])
        >>> y = np.array([1, 2, 3])
        >>> interpolator = LinearInterpolator1d(x, y)
        >>> extrapolator = Extrapolator1d(interpolator, method='Constant')
        >>> extrapolator(np.array([0.1, 0.2, 8, 9]))
        array([ 1.,  1.,  3.,  3.])

        Using defined *left* boundary and *Constant* extrapolation method:

        >>> x = np.array([3, 4, 5])
        >>> y = np.array([1, 2, 3])
        >>> interpolator = LinearInterpolator1d(x, y)
        >>> extrapolator = Extrapolator1d(interpolator, method='Constant', left=0)
        >>> extrapolator(np.array([0.1, 0.2, 8, 9]))
        array([ 0.,  0.,  3.,  3.])
        """

        def __init__(self,
                     interpolator=None,
                     method='Linear',
                     left=None,
                     right=None):

            self.__interpolator = None
            self.interpolator = interpolator
            self.__method = None
            self.method = method
            self.__right = None
            self.right = right
            self.__left = None
            self.left = left

        @property
        def interpolator(self):
            """
            Property for **self.__interpolator** private attribute.

            Returns
            -------
            object
                self.__interpolator
            """

            return self.__interpolator

        @interpolator.setter
        def interpolator(self, value):
            """
            Setter for **self.__interpolator** private attribute.

            Parameters
            ----------
            value : object
                Attribute value.
            """

            if value is not None:
                assert hasattr(value, 'x'), (
                    '"{0}" interpolator has no "x" attribute!'.format(value))
                assert hasattr(value, 'y'), (
                    '"{0}" interpolator has no "y" attribute!'.format(value))

            self.__interpolator = value

        @property
        def method(self):
            """
            Property for **self.__method** private attribute.

            Returns
            -------
            unicode
                self.__method
            """

            return self.__method

        @method.setter
        def method(self, value):
            """
            Setter for **self.__method** private attribute.

            Parameters
            ----------
            value : unicode
                Attribute value.
            """

            if value is not None:
                assert type(value) in (str, unicode), (
                    ('"{0}" attribute: "{1}" type is not '
                     '"str" or "unicode"!').format('method', value))

                value = value.lower()

            self.__method = value

        @property
        def left(self):
            """
            Property for **self.__left** private attribute.

            Returns
            -------
            numeric
                self.__left
            """

            return self.__left

        @left.setter
        def left(self, value):
            """
            Setter for **self.__left** private attribute.

            Parameters
            ----------
            value : numeric
                Attribute value.
            """

            if value is not None:
                assert is_numeric(value), (
                    '"{0}" attribute: "{1}" type is not "numeric"!').format(
                    'left', value)
            self.__left = value

        @property
        def right(self):
            """
            Property for **self.__right** private attribute.

            Returns
            -------
            numeric
                self.__right
            """

            return self.__right

        @right.setter
        def right(self, value):
            """
            Setter for **self.__right** private attribute.

            Parameters
            ----------
            value : numeric
                Attribute value.
            """

            if value is not None:
                assert is_numeric(value), (
                    '"{0}" attribute: "{1}" type is not "numeric"!').format(
                    'right', value)
            self.__right = value

        def __call__(self, x):
            """
            Evaluates the Extrapolator1d at given point(s).

            Parameters
            ----------
            x : numeric or array_like
                Point(s) to evaluate the Extrapolator1d at.

            Returns
            -------
            float or ndarray
                Extrapolated points value(s).
            """

            xe = self.__evaluate(to_ndarray(x))

            if is_numeric(x):
                return float(xe)
            else:
                return xe

        def __evaluate(self, x):
            """
            Performs the extrapolating evaluation at given points.

            Parameters
            ----------
            x : ndarray
                Points to evaluate the Extrapolator1d at.

            Returns
            -------
            ndarray
                Extrapolated points values.
            """

            xi = self.__interpolator.x
            yi = self.__interpolator.y

            y = np.empty_like(x)

            if self.__method == 'linear':
                y[x < xi[0]] = (yi[0] + (x[x < xi[0]] - xi[0]) *
                                (yi[1] - yi[0]) / (xi[1] - xi[0]))
                y[x > xi[-1]] = (yi[-1] + (x[x > xi[-1]] - xi[-1]) *
                                 (yi[-1] - yi[-2]) / (xi[-1] - xi[-2]))
            elif self.__method == 'constant':
                y[x < xi[0]] = yi[0]
                y[x > xi[-1]] = yi[-1]

            if self.__left is not None:
                y[x < xi[0]] = self.__left
            if self.__right is not None:
                y[x > xi[-1]] = self.__right

            in_range = np.logical_and(x >= xi[0], x <= xi[-1])
            y[in_range] = self.__interpolator(x[in_range])

            return y