Source code for pyjams.functions.logistic_function

#!/usr/bin/env python
"""
Several forms of the logistic function and their first and second derivatives

The current functions are the following. Each function has a second form with
`_p` appended to the name, where parameters are given as an iterable, e.g.
`logistic` and `logistic_p` = `logistic(x, *p)`:

.. list-table::
   :widths: 15 50
   :header-rows: 1

   * - Function
     - Description
   * - logistic
     - Logistic function `L/(1+exp(-k(x-x0)))`
   * - dlogistic
     - First derivative of logistic function
   * - d2logistic
     - Second derivative of logistic function
   * - logistic_offset
     - logistic function with offset `L/(1+exp(-k(x-x0))) + a`
   * - dlogistic_offset
     - First derivative of logistic function with offset
   * - d2logistic_offset
     - Second derivative of logistic function with offset
   * - logistic2_offset
     - Double logistic function with offset
       `L1/(1+exp(-k1(x-x01))) - L2/(1+exp(-k2(x-x02))) + a`
   * - dlogistic2_offset
     - First derivative of double logistic function with offset
   * - d2logistic2_offset
     - Second derivative of double logistic function with offset

This module was written by Matthias Cuntz while at Department of
Computational Hydrosystems, Helmholtz Centre for Environmental
Research - UFZ, Leipzig, Germany, and continued while at Institut
National de Recherche pour l'Agriculture, l'Alimentation et
l'Environnement (INRAE), Nancy, France.

:copyright: Copyright 2015-2022 Matthias Cuntz, see AUTHORS.rst for details.
:license: MIT License, see LICENSE for details.

.. moduleauthor:: Matthias Cuntz

Functions:

.. autosummary::
   logistic
   logistic_p
   dlogistic
   dlogistic_p
   d2logistic
   d2logistic_p
   logistic_offset
   logistic_offset_p
   dlogistic_offset
   dlogistic_offset_p
   d2logistic_offset
   d2logistic_offset_p
   logistic2_offset
   logistic2_offset_p
   dlogistic2_offset
   dlogistic2_offset_p
   d2logistic2_offset
   d2logistic2_offset_p

History
    * Written Mar 2015 by Matthias Cuntz (mc (at) macu (dot) de)
    * Added functions logistic_p and logistic_offset_p,
      Dec 2017, Matthias Cuntz
    * Changed to Sphinx docstring and numpydoc, Dec 2019, Matthias Cuntz
    * Distinguish iterable and array_like parameter types,
      Jan 2020, Matthias Cuntz
    * Make systematically function_p versions of all logistic functions and its
      derivatives, Feb 2020, Matthias Cuntz
    * Split logistic and curvature into separate files,
      May 2020, Matthias Cuntz
    * More consistent docstrings, Jan 2022, Matthias Cuntz

"""
import numpy as np
import scipy.special as sp


__all__ = ['logistic', 'logistic_p',
           'dlogistic', 'dlogistic_p',
           'd2logistic', 'd2logistic_p',
           'logistic_offset', 'logistic_offset_p',
           'dlogistic_offset', 'dlogistic_offset_p',
           'd2logistic_offset', 'd2logistic_offset_p',
           'logistic2_offset', 'logistic2_offset_p',
           'dlogistic2_offset', 'dlogistic2_offset_p',
           'd2logistic2_offset', 'd2logistic2_offset_p']


# -----------------------------------------------------------
# a/(1+exp(-b(x-c))) - logistic function
[docs]def logistic(x, L, k, x0): """ Logistic function .. math:: L/(1+exp(-k(x-x0))) Parameters ---------- x : array_like Independent variable to evalute logistic function L : float Maximum of logistic function k : float Steepness of logistic function x0 : float Inflection point of logistic function Returns ------- float or ndarray Logistic function at *x* with maximum *L*, steepness *k* and inflection point *x0* """ return L * sp.expit(k * (x - x0))
[docs]def logistic_p(x, p): """ Wrapper function for :func:`logistic`: `logistic(x, *p)` """ return logistic(x, *p)
# ----------------------------------------------------------- # 1st derivative of logistic functions
[docs]def dlogistic(x, L, k, x0): """ First derivative of :func:`logistic` function .. math:: L/(1+exp(-k(x-x0))) which is .. math:: k.L/(2(cosh(k(x-x0))+1)) Parameters ---------- x : array_like Independent variable to evalute derivative of logistic function L : float Maximum of logistic function k : float Steepness of logistic function x0 : float Inflection point of logistic function Returns ------- float or ndarray First derivative of logistic function at *x* with maximum *L*, steepness *k* and inflection point *x0* """ return k * L / (2. * (np.cosh(k * (x - x0)) + 1.))
[docs]def dlogistic_p(x, p): """ Wrapper function for :func:`dlogistic`: `dlogistic(x, *p)` """ return dlogistic(x, *p)
# ----------------------------------------------------------- # 2nd derivative of logistic functions
[docs]def d2logistic(x, L, k, x0): """ Second derivative of :func:`logistic` function .. math:: L/(1+exp(-k(x-x0))) which is .. math:: -k^2.L.sinh(k(x-x0))/(2(cosh(k(x-x0))+1)^2) Parameters ---------- x : array_like Independent variable to evalute derivative of logistic function L : float Maximum of logistic function k : float Steepness of logistic function x0 : float Inflection point of logistic function Returns ------- float or ndarray Second derivative of logistic function at *x* with maximum *L*, steepness *k* and inflection point *x0* """ return ( -k**2 * L * np.sinh(k * (x - x0)) / (2. * (np.cosh(k * (x - x0)) + 1.)**2) )
[docs]def d2logistic_p(x, p): """ Wrapper function for :func:`d2logistic`: `d2logistic(x, *p)` """ return d2logistic(x, *p)
# ----------------------------------------------------------- # L/(1+exp(-k(x-x0))) + a - logistic function with offset
[docs]def logistic_offset(x, L, k, x0, a): """ Logistic function with offset .. math:: L/(1+exp(-k(x-x0))) + a Parameters ---------- x : array_like Independent variable to evalute logistic function L : float Maximum of logistic function k : float Steepness of logistic function x0 : float Inflection point of logistic function a : float Offset of logistic function Returns ------- float or ndarray Logistic function at *x* with maximum *L*, steepness *k*, inflection point *x0* and offset *a* """ return L * sp.expit(k * (x - x0)) + a
[docs]def logistic_offset_p(x, p): """ Wrapper function for :func:`logistic_offset`: `logistic_offset(x, *p)` """ return logistic_offset(x, *p)
# ----------------------------------------------------------- # 1st derivative of logistic functions with offset
[docs]def dlogistic_offset(x, L, k, x0, a): """ First derivative of :func:`logistic_offset` function .. math:: L/(1+exp(-k(x-x0))) + a which is .. math:: k.L/(2(cosh(k(x-x0))+1)) Parameters ---------- x : array_like Independent variable to evalute derivative of logistic function L : float Maximum of logistic function k : float Steepness of logistic function x0 : float Inflection point of logistic function a : float Offset of logistic function Returns ------- float or ndarray First derivative of logistic function with offset at *x* with maximum *L*, steepness *k*, inflection point *x0*, and offset *a* """ return k * L / (2. * (np.cosh(k * (x - x0)) + 1.))
[docs]def dlogistic_offset_p(x, p): """ Wrapper function for :func:`dlogistic_offset`: `dlogistic_offset(x, *p)` """ return dlogistic_offset(x, *p)
# ----------------------------------------------------------- # 2nd derivative of logistic functions with offset
[docs]def d2logistic_offset(x, L, k, x0, a): """ Second derivative of :func:`logistic_offset` function .. math:: L/(1+exp(-k(x-x0))) + a which is .. math:: -k^2.L.sinh(k(x-x0))/(2(cosh(k(x-x0))+1)^2) Parameters ---------- x : array_like Independent variable to evalute derivative of logistic function L : float Maximum of logistic function k : float Steepness of logistic function x0 : float Inflection point of logistic function a : float Offset of logistic function Returns ------- float or ndarray Second derivative of logistic function at *x* with maximum *L*, steepness *k*, inflection point *x0*, and offset *a* """ return ( -k**2 * L * np.sinh(k * (x - x0)) / (2. * (np.cosh(k * (x - x0)) + 1.)**2) )
[docs]def d2logistic_offset_p(x, p): """ Wrapper function for :func:`d2logistic_offset`: `d2logistic_offset(x, *p)` """ return d2logistic_offset(x, *p)
# ----------------------------------------------------------- # L/(1+exp(-k(x-x0))) + a - logistic function with offset
[docs]def logistic2_offset(x, L1, k1, x01, L2, k2, x02, a): """ Double logistic function with offset .. math:: L1/(1+exp(-k1(x-x01))) - L2/(1+exp(-k2(x-x02))) + a Parameters ---------- x : array_like Independent variable to evalute logistic function L1 : float Maximum of first logistic function k1 : float Steepness of first logistic function x01 : float Inflection point of first logistic function L2 : float Maximum of second logistic function k2 : float Steepness of second logistic function x02 : float Inflection point of second logistic function a : float Offset of double logistic function Returns ------- float or ndarray Double Logistic function at *x* """ return L1 * sp.expit(k1 * (x - x01)) - L2 * sp.expit(k2 * (x - x02)) + a
[docs]def logistic2_offset_p(x, p): """ Wrapper function for :func:`logistic2_offset`: `logistic2_offset(x, *p)` """ return logistic2_offset(x, *p)
# ----------------------------------------------------------- # 1st derivative of logistic functions with offset
[docs]def dlogistic2_offset(x, L1, k1, x01, L2, k2, x02, a): """ First derivative of :func:`logistic2_offset` function .. math:: L1/(1+exp(-k1(x-x01))) - L2/(1+exp(-k2(x-x02))) + a which is .. math:: k1.L1/(2(cosh(k1(x-x01))+1)) - k2.L2/(2(cosh(k2(x-x02))+1)) Parameters ---------- x : array_like Independent variable to evalute logistic function L1 : float Maximum of first logistic function k1 : float Steepness of first logistic function x01 : float Inflection point of first logistic function L2 : float Maximum of second logistic function k2 : float Steepness of second logistic function x02 : float Inflection point of second logistic function a : float Offset of double logistic function Returns ------- float or ndarray First derivative of double logistic function with offset at *x* """ return ( k1 * L1 / (2. * (np.cosh(k1 * (x - x01)) + 1.)) - k2 * L2 / (2. * (np.cosh(k2 * (x - x02)) + 1.)) )
[docs]def dlogistic2_offset_p(x, p): """ Wrapper function for :func:`dlogistic2_offset`: `dlogistic2_offset(x, *p)` """ return dlogistic2_offset(x, *p)
# ----------------------------------------------------------- # 2nd derivative of logistic functions with offset
[docs]def d2logistic2_offset(x, L1, k1, x01, L2, k2, x02, a): """ Second derivative of :func:`logistic_offset` function .. math:: L1/(1+exp(-k1(x-x01))) - L2/(1+exp(-k2(x-x02))) + a which is .. math:: - k1^2.L1.sinh(k1(x-x01)) / (2(cosh(k1(x-x01))+1)^2) + k2^2.L2.sinh(k2(x-x02)) / (2(cosh(k2(x-x02))+1)^2) Parameters ---------- x : array_like Independent variable to evalute logistic function L1 : float Maximum of first logistic function k1 : float Steepness of first logistic function x01 : float Inflection point of first logistic function L2 : float Maximum of second logistic function k2 : float Steepness of second logistic function x02 : float Inflection point of second logistic function a : float Offset of double logistic function Returns ------- float or ndarray Second derivative of double logistic function with offset at *x* """ return ( -k1**2 * L1 * np.sinh(k1 * (x - x01)) / (2. * (np.cosh(k1 * (x - x01)) + 1.)**2) + k2**2 * L2 * np.sinh(k2 * (x - x02)) / (2. * (np.cosh(k2 * (x - x02)) + 1.)**2) )
[docs]def d2logistic2_offset_p(x, p): """ Wrapper function for :func:`d2logistic2_offset`: `d2logistic2_offset(x, *p)` """ return d2logistic2_offset(x, *p)
# ----------------------------------------------------------- if __name__ == '__main__': import doctest doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) # logistic(1., 1., 0., 2.) # # 0.5 # logistic(1., 1., 2., 1.) # # 0.5 # logistic(2., 1., 1., 1.) # # 1./(1.+np.exp(-1.)) # # 0.7310585786300049 # logistic_p(1., [1., 0., 2.]) # logistic_p(1., [1., 2., 1.]) # logistic_p(2., [1., 1., 1.]) # logistic_offset(1., 1., 0., 2., 1.) # logistic_offset(1., 1., 2., 1., 1.) # # 1.5 # logistic_offset(2., 1., 1., 1., 1.) # # 1./(1.+np.exp(-1.)) + 1. # # 1.7310585786300049 # logistic_offset_p(1., [1., 0., 2., 1.]) # logistic_offset_p(1., [1., 2., 1., 1.]) # logistic_offset_p(2., [1., 1., 1., 1.]) # logistic2_offset(1., 1., 2., 1., 2., 2., 1., 1.) # # 0.5 # logistic2_offset_p(1., [1., 2., 1., 2., 2., 1., 1.]) # dlogistic(1., 1., 2., 1.) # # 0.5 # dlogistic_offset(1., 1., 2., 1., 1.) # # 0.5 # dlogistic2_offset(1., 1., 2., 1., 2., 2., 1., 1.) # # -0.5 # print(np.around(d2logistic(1., 1., 2., 2.),4)) # # 0.3199 # print(np.around(d2logistic_offset(1., 1., 2., 2., 1.),4)) # # 0.3199 # print(np.around(d2logistic2_offset(1., 1., 2., 2., 2., 2., 2., 1.),4)) # # -0.3199