Skip to content

Welcome to ableiter

This is the documentation of the package ableiter: A tool for the numerical differentiation of functions with several variables using finite differences.

Installation

ableiter can be installed by typing the following command from within the main directory:

pip install .

Issues

In case you notice any bug or otherwise undesired behaviour, please contact me or raise an issue in the gitlab repository.

User guide

The package is able to perform partial derivatives up to order four with respect to any of the variables of the function. First-order derivatives can be computed with the help of the class First, second-order derivatives with the class Second, and so on. The usage of these classes is documented below.

In order to use the package ableiter, it is required to define the function to be derived in the following way. The function (called f from hereon) should have one single argument. This argument shall be a numpy array with one dimension. The index of the array runs over the variables of f, i.e. x[0] is the first variable of f, x[1] is the second variable, and so on. The length of the input array has to be equal to the number of variables of f.

For instance, to define the function

\[ f(x_1, x_2) = x_1^3 - x_2^2 + x_1^3 x_2^4 + x_2 \log(x_1) \]

one should type:

def f(x):
    return x[0]**3 - x[1]**2 + x[0]**3 * x[1]**4 + x[1] * np.log(x[0])

 

First

ableiter.first.First

This class can be used to compute the partial first-order derivatives of a multivariate function.

__init__(self, f, n) special

Sets the function f as the function whose partial derivatives are calculated, and n is the dimension of the input array of f.

Source code in ableiter/first.py
def __init__(self, f, n):
    """
    Sets the function f as the function whose
    partial derivatives are calculated, and n
    is the dimension of the input array of f.
    """

    self.f = f
    self.n = n

    self.x_eps = 1e-8

    self.grf = np.zeros(shape=(n, ))
df_dxi(self, y, i)

Return the value of the first derivative with respect to the i-th variable in the point y.

Source code in ableiter/first.py
def df_dxi(self, y, i):
    """
    Return the value of the first derivative with
    respect to the i-th variable in the point y.
    """

    x = np.copy(y)

    if len(x) != self.n:
        raise ValueError

    f = abs(self.f(x))
    if f > 1e0:
        x_eps = self.x_eps * f**0.5
    else:
        x_eps = self.x_eps

    xiold = x[i]

    x[i] = xiold - x_eps
    f1 = self.f(x)

    x[i] = xiold + x_eps
    f2 = self.f(x)

    return 0.5 * (f2 - f1) / x_eps
gradf(self, x)

Return the gradient in the point x as a numpy array with length n.

Source code in ableiter/first.py
def gradf(self, x):
    """
    Return the gradient in the point x
    as a numpy array with length n.
    """

    for i in range(0, self.n):
        self.grf[i] = self.df_dxi(x, i)

    return self.grf

 

Second

ableiter.second.Second

This class can be used to compute partial second-order derivatives of a multivariate function.

__init__(self, f, n) special

Sets the function f as the function whose partial derivatives are calculated, and n is the dimension of the input array of f.

Source code in ableiter/second.py
def __init__(self, f, n):
    """
    Sets the function f as the function whose
    partial derivatives are calculated, and n
    is the dimension of the input array of f.
    """

    self.f = f
    self.n = n

    self.x_eps = 1e-6
d2f_dxidxj(self, y, i, j)

Return the value of the second derivative in the point y, where first the function is derived with respect to the i-th variable, and then the functions is derived with respect to the j-th variable.

Source code in ableiter/second.py
def d2f_dxidxj(self, y, i, j):
    """
    Return the value of the second derivative
    in the point y,
    where first the function is derived with
    respect to the i-th variable, and then
    the functions is derived with respect
    to the j-th variable.
    """

    x = np.copy(y)

    if len(x) != self.n:
        raise ValueError

    f = abs(self.f(x))
    if f > 1e0:
        x_eps = self.x_eps * f**0.5
    else:
        x_eps = self.x_eps

    if i != j:

        xiold = x[i]
        xjold = x[j]

        x[i] = xiold - x_eps
        x[j] = xjold - x_eps
        fmm = self.f(x)

        # x[i] = xiold - x_eps
        x[j] = xjold + x_eps
        fmp = self.f(x)

        x[i] = xiold + x_eps
        # x[j] = xjold + x_eps
        fpp = self.f(x)

        # x[i] = xiold + x_eps
        x[j] = xjold - x_eps
        fpm = self.f(x)

        y = 0.25 * (fpp - fpm - fmp + fmm) / x_eps**2

    else:

        xiold = x[i]

        f0 = self.f(x)

        x[i] = xiold - x_eps
        fm = self.f(x)

        x[i] = xiold + x_eps
        fp = self.f(x)

        y = -(2. * f0 - fm - fp) / x_eps**2

    return y

 

Third

ableiter.third.Third

This class can be used to compute partial third-order derivatives of a multivariate function.

__init__(self, f, n) special

Sets the function f as the function whose partial derivatives are calculated, and n is the dimension of the input array of f.

Source code in ableiter/third.py
def __init__(self, f, n):
    """
    Sets the function f as the function whose
    partial derivatives are calculated, and n
    is the dimension of the input array of f.
    """

    self.f = f
    self.n = n

    self.x_eps = 1e-4
d3f_dxidxjdxk(self, y, i, j, k)

Return the value of the third derivative in the point y, where first the function is derived with respect to the i-th variable, then the functions is derived with respect to the j-th variable, and then the function is derived with respect to the k-th variable.

Source code in ableiter/third.py
def d3f_dxidxjdxk(self, y, i, j, k):
    """
    Return the value of the third derivative
    in the point y,
    where first the function is derived with
    respect to the i-th variable, then
    the functions is derived with respect
    to the j-th variable, and then the
    function is derived with respect to
    the k-th variable.
    """

    x = np.copy(y)

    if len(x) != self.n:
        raise ValueError

    f = abs(self.f(x))
    if f > 1e0:
        x_eps = self.x_eps * f**0.5
    else:
        x_eps = self.x_eps

    if (i == j) and (j == k):

        xiold = x[i]

        x[i] = xiold - 2. * x_eps
        fmm = self.f(x)

        x[i] = xiold - x_eps
        fm0 = self.f(x)

        x[i] = xiold + x_eps
        fp0 = self.f(x)

        x[i] = xiold + 2. * x_eps
        fpp = self.f(x)

        y = 0.5 * (2. * fm0 - 2. * fp0 - fmm + fpp) / \
            x_eps**3

    elif (i == j) or (j == k) or (i == k):

        if i == j:

            il = i
            ir = k

        elif j == k:

            il = j
            ir = i

        else:

            il = i
            ir = j

        xilold = x[il]
        xirold = x[ir]

        x[il] = xilold + 2. * x_eps
        x[ir] = xirold + x_eps
        f21 = self.f(x)

        x[il] = xilold
        x[ir] = xirold + x_eps
        f01 = self.f(x)

        x[il] = xilold - 2. * x_eps
        x[ir] = xirold + x_eps
        fm21 = self.f(x)

        x[il] = xilold + 2. * x_eps
        x[ir] = xirold - x_eps
        f2m1 = self.f(x)

        x[il] = xilold
        x[ir] = xirold - x_eps
        f0m1 = self.f(x)

        x[il] = xilold - 2. * x_eps
        x[ir] = xirold - x_eps
        fm2m1 = self.f(x)

        y = 0.125 * (f21 - 2. * f01 + fm21 - f2m1 + \
            2. * f0m1 - fm2m1) / x_eps**3

    else:

        xiold = x[i]
        xjold = x[j]
        xkold = x[k]

        x[i] = xiold + x_eps
        x[j] = xjold + x_eps
        x[k] = xkold + x_eps
        fppp = self.f(x)

        x[i] = xiold - x_eps
        x[j] = xjold + x_eps
        x[k] = xkold + x_eps
        fmpp = self.f(x)

        x[i] = xiold + x_eps
        x[j] = xjold - x_eps
        x[k] = xkold + x_eps
        fpmp = self.f(x)

        x[i] = xiold - x_eps
        x[j] = xjold - x_eps
        x[k] = xkold + x_eps
        fmmp = self.f(x)

        x[i] = xiold + x_eps
        x[j] = xjold + x_eps
        x[k] = xkold - x_eps
        fppm = self.f(x)

        x[i] = xiold - x_eps
        x[j] = xjold + x_eps
        x[k] = xkold - x_eps
        fmpm = self.f(x)

        x[i] = xiold + x_eps
        x[j] = xjold - x_eps
        x[k] = xkold - x_eps
        fpmm = self.f(x)

        x[i] = xiold - x_eps
        x[j] = xjold - x_eps
        x[k] = xkold - x_eps
        fmmm = self.f(x)

        y = 0.125 * (fppp - fmpp - fpmp + fmmp
            - fppm + fmpm + fpmm - fmmm) / x_eps**3

    return y

 

Fourth

ableiter.fourth.Fourth

This class can be used to compute partial fourth-order derivatives of a multivariate function.

__init__(self, f, n) special

Sets the function f as the function whose partial derivatives are calculated, and n is the dimension of the input array of f.

Source code in ableiter/fourth.py
def __init__(self, f, n):
    """
    Sets the function f as the function whose
    partial derivatives are calculated, and n
    is the dimension of the input array of f.
    """

    self.f = f
    self.n = n

    self.x_eps = 1e-3
d4f_dxidxjdxkdxl(self, y, i, j, k, l)

Return the value of the fourth derivative in the point y, where first the function is derived with respect to the i-th variable, then the functions is derived with respect to the j-th variable, then the function is derived with respect to the k-th variable, and then the function is derived with respect to the l-th variable.

Source code in ableiter/fourth.py
def d4f_dxidxjdxkdxl(self, y, i, j, k, l):
    """
    Return the value of the fourth derivative
    in the point y,
    where first the function is derived with
    respect to the i-th variable, then
    the functions is derived with respect
    to the j-th variable, then the
    function is derived with respect to
    the k-th variable, and then the function
    is derived with respect to the l-th variable.
    """

    x = np.copy(y)

    if len(x) != self.n:
        raise ValueError

    f = abs(self.f(x))
    if f > 1e0:
        x_eps = self.x_eps * f**0.5
    else:
        x_eps = self.x_eps

    if len(set([i, j, k, l])) == 1:

        complexity = 1

    elif (i == j) and (k == l) and (i != k):

        il = i
        ir = k
        complexity = 2

    elif (i == k) and (j == l) and (i != j):

        il = i
        ir = j
        complexity = 2

    elif (i == l) and (j == k) and (i != j):

        il = i
        ir = j
        complexity = 2

    elif (i == j) and (i == k) and (i != l):

        il = i
        ir = l
        complexity = 3

    elif (i == j) and (i == l) and (i != k):

        il = i
        ir = k
        complexity = 3

    elif (i == k) and (i == l) and (i != j):

        il = i
        ir = j
        complexity = 3

    elif (i != j) and (j == k) and (j == l):

        il = j
        ir = i
        complexity = 3

    elif len(set([i, j, k, l])) == 4:

        complexity = 4

    elif (i == j) and (len(set([i, j, k, l])) == 3):

        il = i
        im = k
        ir = l
        complexity = 5

    elif (i == k) and (len(set([i, j, k, l])) == 3):

        il = i
        im = j
        ir = l
        complexity = 5

    elif (i == l) and (len(set([i, j, k, l])) == 3):

        il = i
        im = j
        ir = k
        complexity = 5

    elif (j == k) and (len(set([i, j, k, l])) == 3):

        il = j
        im = i
        ir = l
        complexity = 5

    elif (j == l) and (len(set([i, j, k, l])) == 3):

        il = j
        im = i
        ir = k
        complexity = 5

    elif (k == l) and (len(set([i, j, k, l])) == 3):

        il = k
        im = i
        ir = j
        complexity = 5

    else:

        raise NotImplementedError

    if complexity == 2:

        xilold = x[il]
        xirold = x[ir]

        x[il] = xilold + x_eps
        x[ir] = xirold + x_eps
        f11 = self.f(x)

        x[il] = xilold
        x[ir] = xirold + x_eps
        f01 = self.f(x)

        x[il] = xilold - x_eps
        x[ir] = xirold + x_eps
        fm11 = self.f(x)

        x[il] = xilold + x_eps
        x[ir] = xirold
        f10 = self.f(x)

        x[il] = xilold
        x[ir] = xirold
        f00 = self.f(x)

        x[il] = xilold - x_eps
        x[ir] = xirold
        fm10 = self.f(x)

        x[il] = xilold + x_eps
        x[ir] = xirold - x_eps
        f1m1 = self.f(x)

        x[il] = xilold
        x[ir] = xirold - x_eps
        f0m1 = self.f(x)

        x[il] = xilold - x_eps
        x[ir] = xirold - x_eps
        fm1m1 = self.f(x)

        y = (f11 - 2. * f01 + fm11
            - 2. * f10 + 4. * f00 - 2. * fm10
                + f1m1 - 2. * f0m1 + fm1m1) / \
                    x_eps**4

    elif complexity == 1:

        xiold = x[i]

        f0 = self.f(x)

        x[i] = xiold + 2. * x_eps
        f2 = self.f(x)

        x[i] = xiold + x_eps
        f1 = self.f(x)

        x[i] = xiold - x_eps
        fm1 = self.f(x)

        x[i] = xiold - 2. * x_eps
        fm2 = self.f(x)

        y = (f2 - 4. * f1 + 6. * f0
            - 4. * fm1 + fm2) / x_eps**4

    elif complexity == 3:

        xilold = x[il]
        xirold = x[ir]

        x[il] = xilold + 3. * x_eps
        x[ir] = xirold + x_eps
        f13 = self.f(x)

        # x[il] = xilold + 3. * x_eps
        x[ir] = xirold - x_eps
        fm13 = self.f(x)

        x[il] = xilold + x_eps
        x[ir] = xirold + x_eps
        f11 = self.f(x)

        # x[il] = xilold + x_eps
        x[ir] = xirold - x_eps
        fm11 = self.f(x)

        x[il] = xilold - x_eps
        x[ir] = xirold + x_eps
        f1m1 = self.f(x)

        # x[il] = xilold - x_eps
        x[ir] = xirold - x_eps
        fm1m1 = self.f(x)

        x[il] = xilold - 3. * x_eps
        x[ir] = xirold + x_eps
        f1m3 = self.f(x)

        # x[il] = xilold - 3. * x_eps
        x[ir] = xirold - x_eps
        fm1m3 = self.f(x)

        y = 0.0625 * (f13 - fm13 - 3. * (f11 -
            fm11) + 3. * (f1m1 - fm1m1) - (f1m3 -
                fm1m3)) / x_eps**4

    elif complexity == 4:

        xiold = x[i]
        xjold = x[j]
        xkold = x[k]
        xlold = x[l]

        x[i] = xiold + x_eps
        x[j] = xjold + x_eps
        x[k] = xkold + x_eps
        x[l] = xlold + x_eps
        f1111 = self.f(x)

        # x[i] = xiold + x_eps
        # x[j] = xjold + x_eps
        # x[k] = xkold + x_eps
        x[l] = xlold - x_eps
        fm1111 = self.f(x)

        # x[i] = xiold + x_eps
        # x[j] = xjold + x_eps
        x[k] = xkold - x_eps
        x[l] = xlold + x_eps
        f1m111 = self.f(x)

        # x[i] = xiold + x_eps
        # x[j] = xjold + x_eps
        # x[k] = xkold - x_eps
        x[l] = xlold - x_eps
        fm1m111 = self.f(x)

        # x[i] = xiold + x_eps
        x[j] = xjold - x_eps
        x[k] = xkold + x_eps
        x[l] = xlold + x_eps
        f11m11 = self.f(x)

        # x[i] = xiold + x_eps
        # x[j] = xjold - x_eps
        # x[k] = xkold + x_eps
        x[l] = xlold - x_eps
        fm11m11 = self.f(x)

        # x[i] = xiold + x_eps
        # x[j] = xjold - x_eps
        x[k] = xkold - x_eps
        x[l] = xlold + x_eps
        f1m1m11 = self.f(x)

        # x[i] = xiold + x_eps
        # x[j] = xjold - x_eps
        # x[k] = xkold - x_eps
        x[l] = xlold - x_eps
        fm1m1m11 = self.f(x)

        x[i] = xiold - x_eps
        x[j] = xjold + x_eps
        x[k] = xkold + x_eps
        x[l] = xlold + x_eps
        f111m1 = self.f(x)

        # x[i] = xiold - x_eps
        # x[j] = xjold + x_eps
        # x[k] = xkold + x_eps
        x[l] = xlold - x_eps
        fm111m1 = self.f(x)

        # x[i] = xiold - x_eps
        # x[j] = xjold + x_eps
        x[k] = xkold - x_eps
        x[l] = xlold + x_eps
        f1m11m1 = self.f(x)

        # x[i] = xiold - x_eps
        # x[j] = xjold + x_eps
        # x[k] = xkold - x_eps
        x[l] = xlold - x_eps
        fm1m11m1 = self.f(x)

        # x[i] = xiold - x_eps
        x[j] = xjold - x_eps
        x[k] = xkold + x_eps
        x[l] = xlold + x_eps
        f11m1m1 = self.f(x)

        # x[i] = xiold - x_eps
        # x[j] = xjold - x_eps
        # x[k] = xkold + x_eps
        x[l] = xlold - x_eps
        fm11m1m1 = self.f(x)

        # x[i] = xiold - x_eps
        # x[j] = xjold - x_eps
        x[k] = xkold - x_eps
        x[l] = xlold + x_eps
        f1m1m1m1 = self.f(x)

        # x[i] = xiold - x_eps
        # x[j] = xjold - x_eps
        # x[k] = xkold - x_eps
        x[l] = xlold - x_eps
        fm1m1m1m1 = self.f(x)

        y = 0.0625 * ((f1111 - fm1111) - (f1m111 -
            fm1m111) - (f11m11 - fm11m11) + (f1m1m11 -
                fm1m1m11) - (f111m1 - fm111m1) + (f1m11m1 -
                    fm1m11m1) + (f11m1m1 - fm11m1m1) - (f1m1m1m1 -
                        fm1m1m1m1)) / x_eps**4

    elif complexity == 5:

        xilold = x[il]
        ximold = x[im]
        xirold = x[ir]

        x[il] = xilold + 2. * x_eps
        x[im] = ximold + x_eps
        x[ir] = xirold + x_eps
        f112 = self.f(x)

        # x[il] = xilold + 2 * x_eps
        # x[im] = ximold + x_eps
        x[ir] = xirold - x_eps
        fm112 = self.f(x)

        # x[il] = xilold + 2 * x_eps
        x[im] = ximold - x_eps
        x[ir] = xirold + x_eps
        f1m12 = self.f(x)

        # x[il] = xilold + 2 * x_eps
        # x[im] = ximold - x_eps
        x[ir] = xirold - x_eps
        fm1m12 = self.f(x)

        x[il] = xilold
        x[im] = ximold + x_eps
        x[ir] = xirold + x_eps
        f110 = self.f(x)

        # x[il] = xilold
        # x[im] = ximold + x_eps
        x[ir] = xirold - x_eps
        fm110 = self.f(x)

        # x[il] = xilold
        x[im] = ximold - x_eps
        x[ir] = xirold + x_eps
        f1m10 = self.f(x)

        # x[il] = xilold
        # x[im] = ximold - x_eps
        x[ir] = xirold - x_eps
        fm1m10 = self.f(x)

        x[il] = xilold - 2. * x_eps
        x[im] = ximold + x_eps
        x[ir] = xirold + x_eps
        f11m2 = self.f(x)

        # x[il] = xilold - 2. * x_eps
        # x[im] = ximold + x_eps
        x[ir] = xirold - x_eps
        fm11m2 = self.f(x)

        # x[il] = xilold - 2. * x_eps
        x[im] = ximold - x_eps
        x[ir] = xirold + x_eps
        f1m1m2 = self.f(x)

        # x[il] = xilold - 2. * x_eps
        # x[im] = ximold - x_eps
        x[ir] = xirold - x_eps
        fm1m1m2 = self.f(x)

        y = 0.0625 * (f112 - fm112 - (f1m12 - fm1m12)
            - 2. * (f110 - fm110) + 2. * (f1m10 - fm1m10)
                + (f11m2 - fm11m2) - (f1m1m2 - fm1m1m2)) / x_eps**4

    return y