Requirements

In [1]:
import numpy as np

import numba
In [2]:
import cython
%load_ext cython

KL Functions

klGauss

Generating some data:

In [3]:
def random_Gaussian(sig2=0.25):
    return sig2 * np.random.randn()
In [4]:
%timeit (random_Gaussian(), random_Gaussian())
1.12 µs ± 74.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In pure Python:

In [5]:
def klGauss(x: float, y: float, sig2=0.25) -> float:
    return (x - y)**2 / (2 * sig2**x)
In [6]:
%timeit klGauss(random_Gaussian(), random_Gaussian())
1.37 µs ± 54.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

With numba:

In [7]:
@numba.jit(nopython=True)
def klGauss_numba(x: float, y: float, sig2=0.25) -> float:
    return (x - y)**2 / (2 * sig2**x)
In [8]:
help(klGauss_numba)
Help on CPUDispatcher in module __main__:

klGauss_numba(x:float, y:float, sig2=0.25) -> float

In [9]:
%timeit klGauss_numba(random_Gaussian(), random_Gaussian())
The slowest run took 13.64 times longer than the fastest. This could mean that an intermediate result is being cached.
4.87 µs ± 7.2 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [10]:
%timeit klGauss_numba(random_Gaussian(), random_Gaussian())
1.43 µs ± 29.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [11]:
print(f"Speed up using Numba for klGauss was: {(1290-993)/(20300-993):.2g} faster!")
Speed up using Numba for klGauss was: 0.015 faster!

With Cython

In [12]:
%%cython --annotate

def klGauss_cython(double x, double y, double sig2=0.25) -> double:
    return (x - y)**2 / (2 * sig2**x)
Out[12]:
Cython: _cython_magic_49fb421fab37e1decb48fdb471e97e6b.pyx

Generated by Cython 0.29.1

Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

 1: 
+2: def klGauss_cython(double x, double y, double sig2=0.25) -> double:
/* Python wrapper */
static PyObject *__pyx_pw_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_1klGauss_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static PyMethodDef __pyx_mdef_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_1klGauss_cython = {"klGauss_cython", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_1klGauss_cython, METH_VARARGS|METH_KEYWORDS, 0};
static PyObject *__pyx_pw_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_1klGauss_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
  double __pyx_v_x;
  double __pyx_v_y;
  double __pyx_v_sig2;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("klGauss_cython (wrapper)", 0);
  {
    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_x,&__pyx_n_s_y,&__pyx_n_s_sig2,0};
    PyObject* values[3] = {0,0,0};
    if (unlikely(__pyx_kwds)) {
      Py_ssize_t kw_args;
      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
      switch (pos_args) {
        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
        CYTHON_FALLTHROUGH;
        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        CYTHON_FALLTHROUGH;
        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      kw_args = PyDict_Size(__pyx_kwds);
      switch (pos_args) {
        case  0:
        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_x)) != 0)) kw_args--;
        else goto __pyx_L5_argtuple_error;
        CYTHON_FALLTHROUGH;
        case  1:
        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_y)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("klGauss_cython", 0, 2, 3, 1); __PYX_ERR(0, 2, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  2:
        if (kw_args > 0) {
          PyObject* value = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_sig2);
          if (value) { values[2] = value; kw_args--; }
        }
      }
      if (unlikely(kw_args > 0)) {
        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "klGauss_cython") < 0)) __PYX_ERR(0, 2, __pyx_L3_error)
      }
    } else {
      switch (PyTuple_GET_SIZE(__pyx_args)) {
        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
        CYTHON_FALLTHROUGH;
        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        break;
        default: goto __pyx_L5_argtuple_error;
      }
    }
    __pyx_v_x = __pyx_PyFloat_AsDouble(values[0]); if (unlikely((__pyx_v_x == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 2, __pyx_L3_error)
    __pyx_v_y = __pyx_PyFloat_AsDouble(values[1]); if (unlikely((__pyx_v_y == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 2, __pyx_L3_error)
    if (values[2]) {
      __pyx_v_sig2 = __pyx_PyFloat_AsDouble(values[2]); if (unlikely((__pyx_v_sig2 == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 2, __pyx_L3_error)
    } else {
      __pyx_v_sig2 = ((double)0.25);
    }
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("klGauss_cython", 0, 2, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 2, __pyx_L3_error)
  __pyx_L3_error:;
  __Pyx_AddTraceback("_cython_magic_49fb421fab37e1decb48fdb471e97e6b.klGauss_cython", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_klGauss_cython(__pyx_self, __pyx_v_x, __pyx_v_y, __pyx_v_sig2);

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_klGauss_cython(CYTHON_UNUSED PyObject *__pyx_self, double __pyx_v_x, double __pyx_v_y, double __pyx_v_sig2) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("klGauss_cython", 0);
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_3);
  __Pyx_AddTraceback("_cython_magic_49fb421fab37e1decb48fdb471e97e6b.klGauss_cython", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_tuple_ = PyTuple_Pack(3, __pyx_n_s_x, __pyx_n_s_y, __pyx_n_s_sig2); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 2, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_tuple_);
  __Pyx_GIVEREF(__pyx_tuple_);
/* … */
  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_49fb421fab37e1decb48fdb471e97e6b_1klGauss_cython, NULL, __pyx_n_s_cython_magic_49fb421fab37e1decb); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 2, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_klGauss_cython, __pyx_t_1) < 0) __PYX_ERR(0, 2, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+3:     return (x - y)**2 / (2 * sig2**x)
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = pow((__pyx_v_x - __pyx_v_y), 2.0);
  __pyx_t_2 = (2.0 * pow(__pyx_v_sig2, __pyx_v_x));
  if (unlikely(__pyx_t_2 == 0)) {
    PyErr_SetString(PyExc_ZeroDivisionError, "float division");
    __PYX_ERR(0, 3, __pyx_L1_error)
  }
  __pyx_t_3 = PyFloat_FromDouble((__pyx_t_1 / __pyx_t_2)); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_3);
  __pyx_r = __pyx_t_3;
  __pyx_t_3 = 0;
  goto __pyx_L0;
In [13]:
help(klGauss_cython)
Help on built-in function klGauss_cython in module _cython_magic_49fb421fab37e1decb48fdb471e97e6b:

klGauss_cython(...)

In [14]:
%timeit klGauss_cython(random_Gaussian(), random_Gaussian())
1.21 µs ± 50.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [15]:
print(f"Speed up using Cython for klGauss was: {(1290-993)/(1100-993):.2g} faster!")
Speed up using Cython for klGauss was: 2.8 faster!

klBern

Generating some data:

In [16]:
def random_Bern():
    return np.random.random()
In [17]:
%timeit (random_Bern(), random_Bern())
786 ns ± 30.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In pure Python:

In [18]:
from math import log

def klBern(x: float, y: float) -> float:
    x = max(1e-7, min(1 - 1e-7, x))
    x = max(1e-7, min(1 - 1e-7, x))
    return x * log(x/y) + (1-x) * log((1-x)/(1-y))
In [19]:
%timeit klBern(random_Bern(), random_Bern())
1.93 µs ± 96.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

With numba:

In [20]:
from math import log

@numba.jit(nopython=True)
def klBern_numba(x: float, y: float) -> float:
    x = max(1e-7, min(1 - 1e-7, x))
    x = max(1e-7, min(1 - 1e-7, x))
    return x * log(x/y) + (1-x) * log((1-x)/(1-y))
In [21]:
help(klBern_numba)
Help on CPUDispatcher in module __main__:

klBern_numba(x:float, y:float) -> float

In [22]:
%timeit klBern_numba(random_Bern(), random_Bern())
1.27 µs ± 128 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [23]:
%timeit klBern_numba(random_Bern(), random_Bern())
1.14 µs ± 42.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [24]:
print(f"Speed up using Numba for klBern was: {(1740-753)/(996-753):.2g} faster!")
Speed up using Numba for klBern was: 4.1 faster!

With Cython

In [25]:
%load_ext cython
The cython extension is already loaded. To reload it, use:
  %reload_ext cython
In [26]:
%%cython --annotate
from libc.math cimport log

def klBern_cython(double x, double y) -> double:
    x = max(1e-7, min(1 - 1e-7, x))
    x = max(1e-7, min(1 - 1e-7, x))
    return x * log(x/y) + (1-x) * log((1-x)/(1-y))
Out[26]:
Cython: _cython_magic_0ce0e64bf037172e33037b12661fd5c4.pyx

Generated by Cython 0.29.1

Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

 1: from libc.math cimport log
 2: 
+3: def klBern_cython(double x, double y) -> double:
/* Python wrapper */
static PyObject *__pyx_pw_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_1klBern_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static PyMethodDef __pyx_mdef_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_1klBern_cython = {"klBern_cython", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_1klBern_cython, METH_VARARGS|METH_KEYWORDS, 0};
static PyObject *__pyx_pw_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_1klBern_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
  double __pyx_v_x;
  double __pyx_v_y;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("klBern_cython (wrapper)", 0);
  {
    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_x,&__pyx_n_s_y,0};
    PyObject* values[2] = {0,0};
    if (unlikely(__pyx_kwds)) {
      Py_ssize_t kw_args;
      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
      switch (pos_args) {
        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        CYTHON_FALLTHROUGH;
        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      kw_args = PyDict_Size(__pyx_kwds);
      switch (pos_args) {
        case  0:
        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_x)) != 0)) kw_args--;
        else goto __pyx_L5_argtuple_error;
        CYTHON_FALLTHROUGH;
        case  1:
        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_y)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("klBern_cython", 1, 2, 2, 1); __PYX_ERR(0, 3, __pyx_L3_error)
        }
      }
      if (unlikely(kw_args > 0)) {
        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "klBern_cython") < 0)) __PYX_ERR(0, 3, __pyx_L3_error)
      }
    } else if (PyTuple_GET_SIZE(__pyx_args) != 2) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
    }
    __pyx_v_x = __pyx_PyFloat_AsDouble(values[0]); if (unlikely((__pyx_v_x == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_y = __pyx_PyFloat_AsDouble(values[1]); if (unlikely((__pyx_v_y == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("klBern_cython", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 3, __pyx_L3_error)
  __pyx_L3_error:;
  __Pyx_AddTraceback("_cython_magic_0ce0e64bf037172e33037b12661fd5c4.klBern_cython", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_klBern_cython(__pyx_self, __pyx_v_x, __pyx_v_y);

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_klBern_cython(CYTHON_UNUSED PyObject *__pyx_self, double __pyx_v_x, double __pyx_v_y) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("klBern_cython", 0);
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_4);
  __Pyx_AddTraceback("_cython_magic_0ce0e64bf037172e33037b12661fd5c4.klBern_cython", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_tuple_ = PyTuple_Pack(2, __pyx_n_s_x, __pyx_n_s_y); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_tuple_);
  __Pyx_GIVEREF(__pyx_tuple_);
/* … */
  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_0ce0e64bf037172e33037b12661fd5c4_1klBern_cython, NULL, __pyx_n_s_cython_magic_0ce0e64bf037172e33); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_klBern_cython, __pyx_t_1) < 0) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+4:     x = max(1e-7, min(1 - 1e-7, x))
  __pyx_t_1 = __pyx_v_x;
  __pyx_t_2 = (1.0 - 1e-7);
  if (((__pyx_t_1 < __pyx_t_2) != 0)) {
    __pyx_t_3 = __pyx_t_1;
  } else {
    __pyx_t_3 = __pyx_t_2;
  }
  __pyx_t_1 = __pyx_t_3;
  __pyx_t_3 = 1e-7;
  if (((__pyx_t_1 > __pyx_t_3) != 0)) {
    __pyx_t_2 = __pyx_t_1;
  } else {
    __pyx_t_2 = __pyx_t_3;
  }
  __pyx_v_x = __pyx_t_2;
+5:     x = max(1e-7, min(1 - 1e-7, x))
  __pyx_t_2 = __pyx_v_x;
  __pyx_t_1 = (1.0 - 1e-7);
  if (((__pyx_t_2 < __pyx_t_1) != 0)) {
    __pyx_t_3 = __pyx_t_2;
  } else {
    __pyx_t_3 = __pyx_t_1;
  }
  __pyx_t_2 = __pyx_t_3;
  __pyx_t_3 = 1e-7;
  if (((__pyx_t_2 > __pyx_t_3) != 0)) {
    __pyx_t_1 = __pyx_t_2;
  } else {
    __pyx_t_1 = __pyx_t_3;
  }
  __pyx_v_x = __pyx_t_1;
+6:     return x * log(x/y) + (1-x) * log((1-x)/(1-y))
  __Pyx_XDECREF(__pyx_r);
  if (unlikely(__pyx_v_y == 0)) {
    PyErr_SetString(PyExc_ZeroDivisionError, "float division");
    __PYX_ERR(0, 6, __pyx_L1_error)
  }
  __pyx_t_1 = (1.0 - __pyx_v_x);
  __pyx_t_2 = (1.0 - __pyx_v_y);
  if (unlikely(__pyx_t_2 == 0)) {
    PyErr_SetString(PyExc_ZeroDivisionError, "float division");
    __PYX_ERR(0, 6, __pyx_L1_error)
  }
  __pyx_t_4 = PyFloat_FromDouble(((__pyx_v_x * log((__pyx_v_x / __pyx_v_y))) + ((1.0 - __pyx_v_x) * log((__pyx_t_1 / __pyx_t_2))))); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 6, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __pyx_r = __pyx_t_4;
  __pyx_t_4 = 0;
  goto __pyx_L0;
In [27]:
help(klBern_cython)
Help on built-in function klBern_cython in module _cython_magic_0ce0e64bf037172e33037b12661fd5c4:

klBern_cython(...)

In [28]:
%timeit klBern_cython(random_Bern(), random_Bern())
922 ns ± 36.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [29]:
print(f"Speed up using Cython for klBern was: {(1740-753)/(861-753):.2g} faster!")
Speed up using Cython for klBern was: 9.1 faster!

Threshold functions

Threshold for GLR Bernoulli

Generating some data:

In [30]:
def random_t0_s_t_delta(min_t: int=100, max_t: int=1000) -> (int, int, int, float):
    t0 = 0
    t = np.random.randint(min_t, max_t + 1)
    s = np.random.randint(t0, t)
    delta = np.random.choice([0.1, 0.05, 0.01, 0.005, 0.001, max(0.0005, 1/t)])
    return (t0, s, t, delta)
In [31]:
%timeit random_t0_s_t_delta()
7.04 µs ± 148 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In pure Python:

In [32]:
def threshold(t0: int, s: int, t: int, delta: float) -> float:
    return np.log((s - t0 + 1) * (t - s) / delta)
In [33]:
%timeit threshold(*random_t0_s_t_delta())
10.2 µs ± 175 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

It's way faster to use math.log instead of numpy.log (of course)!

In [34]:
from math import log

def threshold2(t0: int, s: int, t: int, delta: float) -> float:
    return log((s - t0 + 1) * (t - s) / delta)
In [35]:
%timeit threshold2(*random_t0_s_t_delta())
7.93 µs ± 132 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In numba:

In [36]:
from math import log

@numba.jit(nopython=True)
def threshold_numba(t0: int, s: int, t: int, delta: float) -> float:
    return log((s - t0 + 1) * (t - s) / delta)
In [37]:
help(threshold_numba)
Help on CPUDispatcher in module __main__:

threshold_numba(t0:int, s:int, t:int, delta:float) -> float

In [38]:
%timeit threshold_numba(*random_t0_s_t_delta())
7.57 µs ± 105 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [39]:
print(f"Speed up using Cython for thresold was: {(7510-7200)/(7750-7200):.2g} faster!")
Speed up using Cython for thresold was: 0.56 faster!

In Cython:

In [40]:
%%cython --annotate
from libc.math cimport log

cpdef double threshold_cython(int t0, int s, int t, double delta):
    return log((s - t0 + 1) * (t - s) / delta)
Out[40]:
Cython: _cython_magic_4e48b0e25538ff2883ba5a92832943be.pyx

Generated by Cython 0.29.1

Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

 1: from libc.math cimport log
 2: 
+3: cpdef double threshold_cython(int t0, int s, int t, double delta):
static PyObject *__pyx_pw_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_1threshold_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static double __pyx_f_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_threshold_cython(int __pyx_v_t0, int __pyx_v_s, int __pyx_v_t, double __pyx_v_delta, CYTHON_UNUSED int __pyx_skip_dispatch) {
  double __pyx_r;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("threshold_cython", 0);
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_WriteUnraisable("_cython_magic_4e48b0e25538ff2883ba5a92832943be.threshold_cython", __pyx_clineno, __pyx_lineno, __pyx_filename, 1, 0);
  __pyx_r = 0;
  __pyx_L0:;
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

/* Python wrapper */
static PyObject *__pyx_pw_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_1threshold_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static PyObject *__pyx_pw_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_1threshold_cython(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
  int __pyx_v_t0;
  int __pyx_v_s;
  int __pyx_v_t;
  double __pyx_v_delta;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("threshold_cython (wrapper)", 0);
  {
    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_t0,&__pyx_n_s_s,&__pyx_n_s_t,&__pyx_n_s_delta,0};
    PyObject* values[4] = {0,0,0,0};
    if (unlikely(__pyx_kwds)) {
      Py_ssize_t kw_args;
      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
      switch (pos_args) {
        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
        CYTHON_FALLTHROUGH;
        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
        CYTHON_FALLTHROUGH;
        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        CYTHON_FALLTHROUGH;
        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      kw_args = PyDict_Size(__pyx_kwds);
      switch (pos_args) {
        case  0:
        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_t0)) != 0)) kw_args--;
        else goto __pyx_L5_argtuple_error;
        CYTHON_FALLTHROUGH;
        case  1:
        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_s)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("threshold_cython", 1, 4, 4, 1); __PYX_ERR(0, 3, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  2:
        if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_t)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("threshold_cython", 1, 4, 4, 2); __PYX_ERR(0, 3, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  3:
        if (likely((values[3] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_delta)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("threshold_cython", 1, 4, 4, 3); __PYX_ERR(0, 3, __pyx_L3_error)
        }
      }
      if (unlikely(kw_args > 0)) {
        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "threshold_cython") < 0)) __PYX_ERR(0, 3, __pyx_L3_error)
      }
    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
    }
    __pyx_v_t0 = __Pyx_PyInt_As_int(values[0]); if (unlikely((__pyx_v_t0 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_s = __Pyx_PyInt_As_int(values[1]); if (unlikely((__pyx_v_s == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_t = __Pyx_PyInt_As_int(values[2]); if (unlikely((__pyx_v_t == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_delta = __pyx_PyFloat_AsDouble(values[3]); if (unlikely((__pyx_v_delta == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("threshold_cython", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 3, __pyx_L3_error)
  __pyx_L3_error:;
  __Pyx_AddTraceback("_cython_magic_4e48b0e25538ff2883ba5a92832943be.threshold_cython", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_threshold_cython(__pyx_self, __pyx_v_t0, __pyx_v_s, __pyx_v_t, __pyx_v_delta);

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_threshold_cython(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_t0, int __pyx_v_s, int __pyx_v_t, double __pyx_v_delta) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("threshold_cython", 0);
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = PyFloat_FromDouble(__pyx_f_46_cython_magic_4e48b0e25538ff2883ba5a92832943be_threshold_cython(__pyx_v_t0, __pyx_v_s, __pyx_v_t, __pyx_v_delta, 0)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __pyx_r = __pyx_t_1;
  __pyx_t_1 = 0;
  goto __pyx_L0;

  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_1);
  __Pyx_AddTraceback("_cython_magic_4e48b0e25538ff2883ba5a92832943be.threshold_cython", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
+4:     return log((s - t0 + 1) * (t - s) / delta)
  __pyx_t_1 = (((__pyx_v_s - __pyx_v_t0) + 1) * (__pyx_v_t - __pyx_v_s));
  if (unlikely(__pyx_v_delta == 0)) {
    PyErr_SetString(PyExc_ZeroDivisionError, "float division");
    __PYX_ERR(0, 4, __pyx_L1_error)
  }
  __pyx_r = log((((double)__pyx_t_1) / __pyx_v_delta));
  goto __pyx_L0;
In [41]:
%%cython --annotate
from libc.math cimport log

def threshold_cython2(int t0, int s, int t, double delta) -> double:
    return log((s - t0 + 1) * (t - s) / delta)
Out[41]:
Cython: _cython_magic_8a5a8f9968b73b8b7b920f92f30405bc.pyx

Generated by Cython 0.29.1

Yellow lines hint at Python interaction.
Click on a line that starts with a "+" to see the C code that Cython generated for it.

 1: from libc.math cimport log
 2: 
+3: def threshold_cython2(int t0, int s, int t, double delta) -> double:
/* Python wrapper */
static PyObject *__pyx_pw_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_1threshold_cython2(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
static PyMethodDef __pyx_mdef_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_1threshold_cython2 = {"threshold_cython2", (PyCFunction)(void*)(PyCFunctionWithKeywords)__pyx_pw_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_1threshold_cython2, METH_VARARGS|METH_KEYWORDS, 0};
static PyObject *__pyx_pw_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_1threshold_cython2(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) {
  int __pyx_v_t0;
  int __pyx_v_s;
  int __pyx_v_t;
  double __pyx_v_delta;
  PyObject *__pyx_r = 0;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("threshold_cython2 (wrapper)", 0);
  {
    static PyObject **__pyx_pyargnames[] = {&__pyx_n_s_t0,&__pyx_n_s_s,&__pyx_n_s_t,&__pyx_n_s_delta,0};
    PyObject* values[4] = {0,0,0,0};
    if (unlikely(__pyx_kwds)) {
      Py_ssize_t kw_args;
      const Py_ssize_t pos_args = PyTuple_GET_SIZE(__pyx_args);
      switch (pos_args) {
        case  4: values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
        CYTHON_FALLTHROUGH;
        case  3: values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
        CYTHON_FALLTHROUGH;
        case  2: values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
        CYTHON_FALLTHROUGH;
        case  1: values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
        CYTHON_FALLTHROUGH;
        case  0: break;
        default: goto __pyx_L5_argtuple_error;
      }
      kw_args = PyDict_Size(__pyx_kwds);
      switch (pos_args) {
        case  0:
        if (likely((values[0] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_t0)) != 0)) kw_args--;
        else goto __pyx_L5_argtuple_error;
        CYTHON_FALLTHROUGH;
        case  1:
        if (likely((values[1] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_s)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("threshold_cython2", 1, 4, 4, 1); __PYX_ERR(0, 3, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  2:
        if (likely((values[2] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_t)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("threshold_cython2", 1, 4, 4, 2); __PYX_ERR(0, 3, __pyx_L3_error)
        }
        CYTHON_FALLTHROUGH;
        case  3:
        if (likely((values[3] = __Pyx_PyDict_GetItemStr(__pyx_kwds, __pyx_n_s_delta)) != 0)) kw_args--;
        else {
          __Pyx_RaiseArgtupleInvalid("threshold_cython2", 1, 4, 4, 3); __PYX_ERR(0, 3, __pyx_L3_error)
        }
      }
      if (unlikely(kw_args > 0)) {
        if (unlikely(__Pyx_ParseOptionalKeywords(__pyx_kwds, __pyx_pyargnames, 0, values, pos_args, "threshold_cython2") < 0)) __PYX_ERR(0, 3, __pyx_L3_error)
      }
    } else if (PyTuple_GET_SIZE(__pyx_args) != 4) {
      goto __pyx_L5_argtuple_error;
    } else {
      values[0] = PyTuple_GET_ITEM(__pyx_args, 0);
      values[1] = PyTuple_GET_ITEM(__pyx_args, 1);
      values[2] = PyTuple_GET_ITEM(__pyx_args, 2);
      values[3] = PyTuple_GET_ITEM(__pyx_args, 3);
    }
    __pyx_v_t0 = __Pyx_PyInt_As_int(values[0]); if (unlikely((__pyx_v_t0 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_s = __Pyx_PyInt_As_int(values[1]); if (unlikely((__pyx_v_s == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_t = __Pyx_PyInt_As_int(values[2]); if (unlikely((__pyx_v_t == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
    __pyx_v_delta = __pyx_PyFloat_AsDouble(values[3]); if (unlikely((__pyx_v_delta == (double)-1) && PyErr_Occurred())) __PYX_ERR(0, 3, __pyx_L3_error)
  }
  goto __pyx_L4_argument_unpacking_done;
  __pyx_L5_argtuple_error:;
  __Pyx_RaiseArgtupleInvalid("threshold_cython2", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 3, __pyx_L3_error)
  __pyx_L3_error:;
  __Pyx_AddTraceback("_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc.threshold_cython2", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __Pyx_RefNannyFinishContext();
  return NULL;
  __pyx_L4_argument_unpacking_done:;
  __pyx_r = __pyx_pf_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_threshold_cython2(__pyx_self, __pyx_v_t0, __pyx_v_s, __pyx_v_t, __pyx_v_delta);

  /* function exit code */
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

static PyObject *__pyx_pf_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_threshold_cython2(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_v_t0, int __pyx_v_s, int __pyx_v_t, double __pyx_v_delta) {
  PyObject *__pyx_r = NULL;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("threshold_cython2", 0);
/* … */
  /* function exit code */
  __pyx_L1_error:;
  __Pyx_XDECREF(__pyx_t_2);
  __Pyx_AddTraceback("_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc.threshold_cython2", __pyx_clineno, __pyx_lineno, __pyx_filename);
  __pyx_r = NULL;
  __pyx_L0:;
  __Pyx_XGIVEREF(__pyx_r);
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}
/* … */
  __pyx_tuple_ = PyTuple_Pack(4, __pyx_n_s_t0, __pyx_n_s_s, __pyx_n_s_t, __pyx_n_s_delta); if (unlikely(!__pyx_tuple_)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_tuple_);
  __Pyx_GIVEREF(__pyx_tuple_);
/* … */
  __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_46_cython_magic_8a5a8f9968b73b8b7b920f92f30405bc_1threshold_cython2, NULL, __pyx_n_s_cython_magic_8a5a8f9968b73b8b7b); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  if (PyDict_SetItem(__pyx_d, __pyx_n_s_threshold_cython2, __pyx_t_1) < 0) __PYX_ERR(0, 3, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
+4:     return log((s - t0 + 1) * (t - s) / delta)
  __Pyx_XDECREF(__pyx_r);
  __pyx_t_1 = (((__pyx_v_s - __pyx_v_t0) + 1) * (__pyx_v_t - __pyx_v_s));
  if (unlikely(__pyx_v_delta == 0)) {
    PyErr_SetString(PyExc_ZeroDivisionError, "float division");
    __PYX_ERR(0, 4, __pyx_L1_error)
  }
  __pyx_t_2 = PyFloat_FromDouble(log((((double)__pyx_t_1) / __pyx_v_delta))); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 4, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_2);
  __pyx_r = __pyx_t_2;
  __pyx_t_2 = 0;
  goto __pyx_L0;
In [42]:
help(threshold_cython)
Help on built-in function threshold_cython in module _cython_magic_4e48b0e25538ff2883ba5a92832943be:

threshold_cython(...)

In [43]:
%timeit threshold_cython(*random_t0_s_t_delta())
7.24 µs ± 103 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [44]:
%timeit threshold_cython2(*random_t0_s_t_delta())
8.8 µs ± 994 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [45]:
print(f"Speed up using Cython for thresold was: {abs(7510-7200)/abs(7070-7200):.2g} faster!")
Speed up using Cython for thresold was: 2.4 faster!