Source code for numdifftools.step_generators

from __future__ import division
import numpy as np
from numdifftools.extrapolation import EPS
from collections import namedtuple

_STATE = namedtuple('State', ['x', 'method', 'n', 'order'])


def make_exact(h):
    """Make sure h is an exact representable number

    This is important when calculating numerical derivatives and is
    accomplished by adding 1.0 and then subtracting 1.0.
    """
    return (h + 1.0) - 1.0


def valarray(shape, value=np.NaN, typecode=None):
    """Return an array of all value."""
    if typecode is None:
        typecode = bool
    out = np.ones(shape, dtype=typecode) * value

    if not isinstance(out, np.ndarray):
        out = np.asarray(out)
    return out


def nominal_step(x=None):
    """Return nominal step"""
    if x is None:
        return 1.0
    return np.log1p(np.abs(x)).clip(min=1.0)


def base_step(scale):
    """Return base_step = EPS ** (1. / scale)"""
    return EPS ** (1. / scale)


def default_scale(method='forward', n=1, order=2):
    high_order = int(n > 1 or order >= 4)
    order2 = max(order // 2 - 1, 0)
    n4 = n // 4
    n_mod_4 = n % 4
    c = ([n4 * (10 + 1.5 * int(n > 10)),
          3.65 + n4 * (5 + 1.5**n4),
          3.65 + n4 * (5 + 1.7**n4),
          7.30 + n4 * (5 + 2.1**n4)][n_mod_4]) if high_order else 0

    return (dict(multicomplex=1.35, complex=1.35+c).get(method, 2.5) +
            int(n - 1) * dict(multicomplex=0, complex=0.0).get(method, 1.3) +
            order2 * dict(central=3, forward=2, backward=2).get(method, 0))


class BasicMaxStepGenerator(object):

    """
    Generates a sequence of steps of decreasing magnitude

    where
        steps = base_step * step_ratio ** (-i + offset), i=0, 1,.., num_steps-1.


    Parameters
    ----------
    base_step : float, array-like.
       Defines the start step, i.e., maximum step
    step_ratio : real scalar.
        Ratio between sequential steps generated.  Note: Ratio > 1
    num_steps : scalar integer.
        defines number of steps generated.
    offset : real scalar, optional, default 0
        offset to the base step


    Example
    -------
    >>> step_gen = BasicMaxStepGenerator(base_step=2.0, step_ratio=2, num_steps=4)
    >>> [s for s in step_gen()]
    [2.0, 1.0, 0.5, 0.25]

    """

    _sign = -1

    def __init__(self, base_step, step_ratio, num_steps, offset=0):
        self.base_step = base_step
        self.step_ratio = step_ratio
        self.num_steps = num_steps
        self.offset = offset

    def _range(self):
        return range(self.num_steps)

    def __call__(self):
        base_step, step_ratio = self.base_step, self.step_ratio
        sgn, offset = self._sign, self.offset
        for i in self._range():
            step = base_step * step_ratio ** (sgn * i + offset)
            if (np.abs(step) > 0).all():
                yield step


class BasicMinStepGenerator(BasicMaxStepGenerator):

    """
    Generates a sequence of steps of decreasing magnitude

    where
        steps = base_step * step_ratio ** (i + offset), i=num_steps-1,... 1, 0.


    Parameters
    ----------
    base_step : float, array-like.
       Defines the end step, i.e., minimum step
    step_ratio : real scalar.
        Ratio between sequential steps generated.  Note: Ratio > 1
    num_steps : scalar integer.
        defines number of steps generated.
    offset : real scalar, optional, default 0
        offset to the base step


    Example
    -------
    >>> step_gen = BasicMinStepGenerator(base_step=0.25, step_ratio=2, num_steps=4)
    >>> [s for s in step_gen()]
    [2.0, 1.0, 0.5, 0.25]

    """

    _sign = 1

    def _range(self):
        return range(self.num_steps - 1, -1, -1)


[docs]class MinStepGenerator(object): """ Generates a sequence of steps where steps = step_nom * base_step * step_ratio ** (i + offset) for i = num_steps-1,... 1, 0. Parameters ---------- base_step : float, array-like, optional Defines the minimum step, if None, the value is set to EPS**(1/scale) step_ratio : real scalar, optional, default 2 Ratio between sequential steps generated. Note: Ratio > 1 If None then step_ratio is 2 for n=1 otherwise step_ratio is 1.6 num_steps : scalar integer, optional, default min_num_steps + num_extrap defines number of steps generated. It should be larger than min_num_steps = (n + order - 1) / fact where fact is 1, 2 or 4 depending on differentiation method used. step_nom : default maximum(log(1+|x|), 1) Nominal step where x is supplied at runtime through the __call__ method. offset : real scalar, optional, default 0 offset to the base step num_extrap : scalar integer num_extrap check_num_steps : boolean If True make sure num_steps larger than the minimum required steps. use_exact_steps : boolean If true make sure exact steps are generated scale : real scalar, optional scale used in base step. If not None it will override the default computed with the default_scale function. """ _step_generator = BasicMinStepGenerator
[docs] def __init__(self, base_step=None, step_ratio=2.0, num_steps=None, step_nom=None, offset=0, num_extrap=0, use_exact_steps=True, check_num_steps=True, scale=None): self.base_step = base_step self.step_nom = step_nom self.num_steps = num_steps self.step_ratio = step_ratio self.offset = offset self.num_extrap = num_extrap self.check_num_steps = check_num_steps self.use_exact_steps = use_exact_steps self.scale = scale self._state = _STATE(x=np.asarray(1), method='forward', n=1, order=2)
def __repr__(self): class_name = self.__class__.__name__ kwds = ['{0!s}={1!s}'.format(name, str(getattr(self, name))) for name in self.__dict__.keys()] return """{0!s}({1!s})""".format(class_name, ','.join(kwds)) @property def scale(self): if self._scale is None: _x, method, n, order = self._state return default_scale(method, n, order) return self._scale @scale.setter def scale(self, scale): self._scale = scale @property def base_step(self): if self._base_step is None: return base_step(self.scale) return self._base_step @base_step.setter def base_step(self, base_step): self._base_step = base_step @property def min_num_steps(self): _x, method, n, order = self._state num_steps = int(n + order - 1) if method in ['central', 'central2', 'complex', 'multicomplex']: step = 4 if method == 'complex' and (n > 1 or order >= 4) else 2 num_steps = num_steps // step return max(num_steps, 1) @property def num_steps(self): min_num_steps = self.min_num_steps if self._num_steps is not None: num_steps = int(self._num_steps) if self.check_num_steps: num_steps = max(num_steps, min_num_steps) return num_steps return min_num_steps + int(self.num_extrap) @num_steps.setter def num_steps(self, num_steps): self._num_steps = num_steps @property def step_ratio(self,): step_ratio = self._step_ratio if step_ratio is None: step_ratio = {1: 2.0}.get(self._state.n, 1.6) return float(step_ratio) @step_ratio.setter def step_ratio(self, step_ratio): self._step_ratio = step_ratio @property def step_nom(self): x = self._state.x if self._step_nom is None: return nominal_step(x) return valarray(x.shape, value=self._step_nom) @step_nom.setter def step_nom(self, step_nom): self._step_nom = step_nom def step_generator_function(self, x, method='forward', n=1, order=2): self._state = _STATE(np.asarray(x), method, n, order) base_step, step_ratio = self.base_step * self.step_nom, self.step_ratio if self.use_exact_steps: base_step = make_exact(base_step) step_ratio = make_exact(step_ratio) return self._step_generator(base_step=base_step, step_ratio=step_ratio, num_steps=self.num_steps, offset=self.offset) def __call__(self, x, method='forward', n=1, order=2): step_generator = self.step_generator_function(x, method, n, order) return step_generator()
[docs]class MaxStepGenerator(MinStepGenerator): """ Generates a sequence of steps where steps = step_nom * base_step * step_ratio ** (-i + offset) for i = 0, 1, ..., num_steps-1. Parameters ---------- base_step : float, array-like, default 2.0 Defines the maximum step, if None, the value is set to EPS**(1/scale) step_ratio : real scalar, optional, default 2 Ratio between sequential steps generated. Note: Ratio > 1 If None then step_ratio is 2 for n=1 otherwise step_ratio is 1.6 num_steps : scalar integer, optional, default min_num_steps + num_extrap defines number of steps generated. It should be larger than min_num_steps = (n + order - 1) / fact where fact is 1, 2 or 4 depending on differentiation method used. step_nom : default maximum(log(1+|x|), 1) Nominal step where x is supplied at runtime through the __call__ method. offset : real scalar, optional, default 0 offset to the base step num_extrap : scalar integer num_extrap check_num_steps : boolean If True make sure num_steps larger than the minimum required steps. use_exact_steps : boolean If true make sure exact steps are generated scale : real scalar, default 500 scale used in base step. """ _step_generator = BasicMaxStepGenerator
[docs] def __init__(self, base_step=2.0, step_ratio=2.0, num_steps=15, step_nom=None, offset=0, num_extrap=0, use_exact_steps=False, check_num_steps=True, scale=500): super(MaxStepGenerator, self).__init__(base_step=base_step, step_ratio=step_ratio, num_steps=num_steps, step_nom=step_nom, offset=offset, num_extrap=num_extrap, use_exact_steps=use_exact_steps, check_num_steps=check_num_steps, scale=scale)
if __name__ == '__main__': from numdifftools.testing import test_docstrings test_docstrings()