summaryrefslogtreecommitdiff
path: root/pyspike/cython
diff options
context:
space:
mode:
Diffstat (limited to 'pyspike/cython')
-rw-r--r--pyspike/cython/__init__.py0
-rw-r--r--pyspike/cython/cython_add.pyx232
-rw-r--r--pyspike/cython/cython_directionality.pyx262
-rw-r--r--pyspike/cython/cython_distances.pyx631
-rw-r--r--pyspike/cython/cython_profiles.pyx485
-rw-r--r--pyspike/cython/cython_simulated_annealing.pyx82
-rw-r--r--pyspike/cython/directionality_python_backend.py144
-rw-r--r--pyspike/cython/python_backend.py638
8 files changed, 2474 insertions, 0 deletions
diff --git a/pyspike/cython/__init__.py b/pyspike/cython/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/pyspike/cython/__init__.py
diff --git a/pyspike/cython/cython_add.pyx b/pyspike/cython/cython_add.pyx
new file mode 100644
index 0000000..25f1181
--- /dev/null
+++ b/pyspike/cython/cython_add.pyx
@@ -0,0 +1,232 @@
+#cython: boundscheck=False
+#cython: wraparound=False
+#cython: cdivision=True
+
+"""
+cython_add.pyx
+
+cython implementation of the add function for piece-wise const and
+piece-wise linear functions
+
+Note: using cython memoryviews (e.g. double[:]) instead of ndarray objects
+improves the performance of spike_distance by a factor of 10!
+
+Copyright 2014, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+"""
+To test whether things can be optimized: remove all yellow stuff
+in the html output::
+
+ cython -a cython_add.pyx
+
+which gives::
+
+ cython_add.html
+
+"""
+
+import numpy as np
+cimport numpy as np
+
+from libc.math cimport fabs
+
+DTYPE = np.float
+ctypedef np.float_t DTYPE_t
+
+
+############################################################
+# add_piece_wise_const_cython
+############################################################
+def add_piece_wise_const_cython(double[:] x1, double[:] y1,
+ double[:] x2, double[:] y2):
+
+ cdef int N1 = len(x1)
+ cdef int N2 = len(x2)
+ cdef double[:] x_new = np.empty(N1+N2)
+ cdef double[:] y_new = np.empty(N1+N2-1)
+ cdef int index1 = 0
+ cdef int index2 = 0
+ cdef int index = 0
+ cdef int i
+ with nogil: # release the interpreter lock to allow multi-threading
+ x_new[0] = x1[0]
+ y_new[0] = y1[0] + y2[0]
+ while (index1+1 < N1-1) and (index2+1 < N2-1):
+ index += 1
+ # print(index1+1, x1[index1+1], y1[index1+1], x_new[index])
+ if x1[index1+1] < x2[index2+1]:
+ index1 += 1
+ x_new[index] = x1[index1]
+ elif x1[index1+1] > x2[index2+1]:
+ index2 += 1
+ x_new[index] = x2[index2]
+ else: # x1[index1+1] == x2[index2+1]:
+ index1 += 1
+ index2 += 1
+ x_new[index] = x1[index1]
+ y_new[index] = y1[index1] + y2[index2]
+ # one array reached the end -> copy the contents of the other to the end
+ if index1+1 < N1-1:
+ x_new[index+1:index+1+N1-index1-1] = x1[index1+1:]
+ for i in xrange(N1-index1-2):
+ y_new[index+1+i] = y1[index1+1+i] + y2[N2-2]
+ index += N1-index1-2
+ elif index2+1 < N2-1:
+ x_new[index+1:index+1+N2-index2-1] = x2[index2+1:]
+ for i in xrange(N2-index2-2):
+ y_new[index+1+i] = y2[index2+1+i] + y1[N1-2]
+ index += N2-index2-2
+ else: # both arrays reached the end simultaneously
+ # only the last x-value missing
+ x_new[index+1] = x1[N1-1]
+ # end nogil
+ # return np.asarray(x_new[:index+2]), np.asarray(y_new[:index+1])
+ return np.asarray(x_new[:index+2]), np.asarray(y_new[:index+1])
+
+
+############################################################
+# add_piece_wise_lin_cython
+############################################################
+def add_piece_wise_lin_cython(double[:] x1, double[:] y11, double[:] y12,
+ double[:] x2, double[:] y21, double[:] y22):
+ cdef int N1 = len(x1)
+ cdef int N2 = len(x2)
+ cdef double[:] x_new = np.empty(N1+N2)
+ cdef double[:] y1_new = np.empty(N1+N2-1)
+ cdef double[:] y2_new = np.empty_like(y1_new)
+ cdef int index1 = 0 # index for self
+ cdef int index2 = 0 # index for f
+ cdef int index = 0 # index for new
+ cdef int i
+ cdef double y
+ with nogil: # release the interpreter lock to allow multi-threading
+ x_new[0] = x1[0]
+ y1_new[0] = y11[0] + y21[0]
+ while (index1+1 < N1-1) and (index2+1 < N2-1):
+ # print(index1+1, x1[index1+1], self.y[index1+1], x_new[index])
+ if x1[index1+1] < x2[index2+1]:
+ # first compute the end value of the previous interval
+ # linear interpolation of the interval
+ y = y21[index2] + (y22[index2]-y21[index2]) * \
+ (x1[index1+1]-x2[index2]) / (x2[index2+1]-x2[index2])
+ y2_new[index] = y12[index1] + y
+ index1 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ # and the starting value for the next interval
+ y1_new[index] = y11[index1] + y
+ elif x1[index1+1] > x2[index2+1]:
+ # first compute the end value of the previous interval
+ # linear interpolation of the interval
+ y = y11[index1] + (y12[index1]-y11[index1]) * \
+ (x2[index2+1]-x1[index1]) / \
+ (x1[index1+1]-x1[index1])
+ y2_new[index] = y22[index2] + y
+ index2 += 1
+ index += 1
+ x_new[index] = x2[index2]
+ # and the starting value for the next interval
+ y1_new[index] = y21[index2] + y
+ else: # x1[index1+1] == x2[index2+1]:
+ y2_new[index] = y12[index1] + y22[index2]
+ index1 += 1
+ index2 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ y1_new[index] = y11[index1] + y21[index2]
+ # one array reached the end -> copy the contents of the other to the end
+ if index1+1 < N1-1:
+ x_new[index+1:index+1+N1-index1-1] = x1[index1+1:]
+ for i in xrange(N1-index1-2):
+ # compute the linear interpolations value
+ y = y21[index2] + (y22[index2]-y21[index2]) * \
+ (x1[index1+1+i]-x2[index2]) / (x2[index2+1]-x2[index2])
+ y1_new[index+1+i] = y11[index1+1+i] + y
+ y2_new[index+i] = y12[index1+i] + y
+ index += N1-index1-2
+ elif index2+1 < N2-1:
+ x_new[index+1:index+1+N2-index2-1] = x2[index2+1:]
+ # compute the linear interpolations values
+ for i in xrange(N2-index2-2):
+ y = y11[index1] + (y12[index1]-y11[index1]) * \
+ (x2[index2+1+i]-x1[index1]) / \
+ (x1[index1+1]-x1[index1])
+ y1_new[index+1+i] = y21[index2+1+i] + y
+ y2_new[index+i] = y22[index2+i] + y
+ index += N2-index2-2
+ else: # both arrays reached the end simultaneously
+ # only the last x-value missing
+ x_new[index+1] = x1[N1-1]
+ # finally, the end value for the last interval
+ y2_new[index] = y12[N1-2]+y22[N2-2]
+ # only use the data that was actually filled
+ # end nogil
+ return (np.asarray(x_new[:index+2]),
+ np.asarray(y1_new[:index+1]),
+ np.asarray(y2_new[:index+1]))
+
+
+############################################################
+# add_discrete_function_cython
+############################################################
+def add_discrete_function_cython(double[:] x1, double[:] y1, double[:] mp1,
+ double[:] x2, double[:] y2, double[:] mp2):
+
+ cdef double[:] x_new = np.empty(len(x1) + len(x2))
+ cdef double[:] y_new = np.empty_like(x_new)
+ cdef double[:] mp_new = np.empty_like(x_new)
+ cdef int index1 = 0
+ cdef int index2 = 0
+ cdef int index = 0
+ cdef int N1 = len(y1)-1
+ cdef int N2 = len(y2)-1
+ x_new[0] = x1[0]
+ while (index1+1 < N1) and (index2+1 < N2):
+ if x1[index1+1] < x2[index2+1]:
+ index1 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ y_new[index] = y1[index1]
+ mp_new[index] = mp1[index1]
+ elif x1[index1+1] > x2[index2+1]:
+ index2 += 1
+ index += 1
+ x_new[index] = x2[index2]
+ y_new[index] = y2[index2]
+ mp_new[index] = mp2[index2]
+ else: # x1[index1+1] == x2[index2+1]
+ index1 += 1
+ index2 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ y_new[index] = y1[index1] + y2[index2]
+ mp_new[index] = mp1[index1] + mp2[index2]
+ # one array reached the end -> copy the contents of the other to the end
+ if index1+1 < N1:
+ x_new[index+1:index+1+N1-index1] = x1[index1+1:]
+ y_new[index+1:index+1+N1-index1] = y1[index1+1:]
+ mp_new[index+1:index+1+N1-index1] = mp1[index1+1:]
+ index += N1-index1
+ elif index2+1 < N2:
+ x_new[index+1:index+1+N2-index2] = x2[index2+1:]
+ y_new[index+1:index+1+N2-index2] = y2[index2+1:]
+ mp_new[index+1:index+1+N2-index2] = mp2[index2+1:]
+ index += N2-index2
+ else: # both arrays reached the end simultaneously
+ x_new[index+1] = x1[index1+1]
+ y_new[index+1] = y1[index1+1] + y2[index2+1]
+ mp_new[index+1] = mp1[index1+1] + mp2[index2+1]
+ index += 1
+
+ y_new[0] = y_new[1]
+ mp_new[0] = mp_new[1]
+
+ # the last value is again the end of the interval
+ # only use the data that was actually filled
+ return (np.asarray(x_new[:index+1]),
+ np.asarray(y_new[:index+1]),
+ np.asarray(mp_new[:index+1]))
diff --git a/pyspike/cython/cython_directionality.pyx b/pyspike/cython/cython_directionality.pyx
new file mode 100644
index 0000000..ac37690
--- /dev/null
+++ b/pyspike/cython/cython_directionality.pyx
@@ -0,0 +1,262 @@
+#cython: boundscheck=False
+#cython: wraparound=False
+#cython: cdivision=True
+
+"""
+cython_directionality.pyx
+
+cython implementation of the spike delay asymmetry measures
+
+Copyright 2015, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+"""
+To test whether things can be optimized: remove all yellow stuff
+in the html output::
+
+ cython -a cython_directionality.pyx
+
+which gives::
+
+ cython_directionality.html
+
+"""
+
+import numpy as np
+cimport numpy as np
+
+from libc.math cimport fabs
+from libc.math cimport fmax
+from libc.math cimport fmin
+
+# from pyspike.cython.cython_distances cimport get_tau
+
+DTYPE = np.float
+ctypedef np.float_t DTYPE_t
+
+
+############################################################
+# get_tau
+############################################################
+cdef inline double get_tau(double[:] spikes1, double[:] spikes2,
+ int i, int j, double interval, double max_tau):
+ cdef double m = interval # use interval length as initial tau
+ cdef int N1 = spikes1.shape[0]-1 # len(spikes1)-1
+ cdef int N2 = spikes2.shape[0]-1 # len(spikes2)-1
+ if i < N1 and i > -1:
+ m = fmin(m, spikes1[i+1]-spikes1[i])
+ if j < N2 and j > -1:
+ m = fmin(m, spikes2[j+1]-spikes2[j])
+ if i > 0:
+ m = fmin(m, spikes1[i]-spikes1[i-1])
+ if j > 0:
+ m = fmin(m, spikes2[j]-spikes2[j-1])
+ m *= 0.5
+ if max_tau > 0.0:
+ m = fmin(m, max_tau)
+ return m
+
+
+############################################################
+# spike_train_order_profile_cython
+############################################################
+def spike_train_order_profile_cython(double[:] spikes1, double[:] spikes2,
+ double t_start, double t_end,
+ double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int i = -1
+ cdef int j = -1
+ cdef int n = 0
+ cdef double[:] st = np.zeros(N1 + N2 + 2) # spike times
+ cdef double[:] a = np.zeros(N1 + N2 + 2) # asymmetry values
+ cdef double[:] mp = np.ones(N1 + N2 + 2) # multiplicity
+ cdef double interval = t_end - t_start
+ cdef double tau
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ st[n] = spikes1[i]
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike from spike train 1 after spike train 2
+ # both get marked with -1
+ a[n] = -1
+ a[n-1] = -1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ st[n] = spikes2[j]
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike from spike train 1 before spike train 2
+ # both get marked with 1
+ a[n] = 1
+ a[n-1] = 1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ n += 1
+ # add only one event with zero asymmetry value and multiplicity 2
+ st[n] = spikes1[i]
+ a[n] = 0
+ mp[n] = 2
+
+ st = st[:n+2]
+ a = a[:n+2]
+ mp = mp[:n+2]
+
+ st[0] = t_start
+ st[len(st)-1] = t_end
+ if N1 + N2 > 0:
+ a[0] = a[1]
+ a[len(a)-1] = a[len(a)-2]
+ mp[0] = mp[1]
+ mp[len(mp)-1] = mp[len(mp)-2]
+ else:
+ a[0] = 1
+ a[1] = 1
+
+ return st, a, mp
+
+
+############################################################
+# spike_train_order_cython
+############################################################
+def spike_train_order_cython(double[:] spikes1, double[:] spikes2,
+ double t_start, double t_end, double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int i = -1
+ cdef int j = -1
+ cdef int d = 0
+ cdef int mp = 0
+ cdef double interval = t_end - t_start
+ cdef double tau
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ mp += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike in spike train 2 appeared before spike in spike train 1
+ # mark with -1
+ d -= 2
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ mp += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike in spike train 1 appeared before spike in spike train 2
+ # mark with +1
+ d += 2
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ # add only one event with multiplicity 2, but no asymmetry counting
+ mp += 2
+
+ if d == 0 and mp == 0:
+ # empty spike trains -> spike sync = 1 by definition
+ d = 1
+ mp = 1
+
+ return d, mp
+
+
+############################################################
+# spike_directionality_profiles_cython
+############################################################
+def spike_directionality_profiles_cython(double[:] spikes1,
+ double[:] spikes2,
+ double t_start, double t_end,
+ double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int i = -1
+ cdef int j = -1
+ cdef double[:] d1 = np.zeros(N1) # directionality values
+ cdef double[:] d2 = np.zeros(N2) # directionality values
+ cdef double interval = t_end - t_start
+ cdef double tau
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike from spike train 1 after spike train 2
+ # leading spike gets +1, following spike -1
+ d1[i] = -1
+ d2[j] = +1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike from spike train 1 before spike train 2
+ # leading spike gets +1, following spike -1
+ d1[i] = +1
+ d2[j] = -1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ # equal spike times: zero asymmetry value
+ d1[i] = 0
+ d2[j] = 0
+
+ return d1, d2
+
+
+############################################################
+# spike_directionality_cython
+############################################################
+def spike_directionality_cython(double[:] spikes1,
+ double[:] spikes2,
+ double t_start, double t_end,
+ double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int i = -1
+ cdef int j = -1
+ cdef int d = 0 # directionality value
+ cdef double interval = t_end - t_start
+ cdef double tau
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike from spike train 1 after spike train 2
+ # leading spike gets +1, following spike -1
+ d -= 1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike from spike train 1 before spike train 2
+ # leading spike gets +1, following spike -1
+ d += 1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+
+ return d
diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx
new file mode 100644
index 0000000..d4070ae
--- /dev/null
+++ b/pyspike/cython/cython_distances.pyx
@@ -0,0 +1,631 @@
+#cython: boundscheck=False
+#cython: wraparound=False
+#cython: cdivision=True
+
+"""
+cython_distances.pyx
+
+cython implementation of the isi-, spike- and spike-sync distances
+
+Note: using cython memoryviews (e.g. double[:]) instead of ndarray objects
+improves the performance of spike_distance by a factor of 10!
+
+Copyright 2015, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+"""
+To test whether things can be optimized: remove all yellow stuff
+in the html output::
+
+ cython -a cython_distances.pyx
+
+which gives::
+
+ cython_distances.html
+
+"""
+
+import numpy as np
+cimport numpy as np
+
+from libc.math cimport fabs
+from libc.math cimport fmax
+from libc.math cimport fmin
+
+DTYPE = np.float
+ctypedef np.float_t DTYPE_t
+
+
+############################################################
+# isi_distance_cython
+############################################################
+def isi_distance_cython(double[:] s1, double[:] s2,
+ double t_start, double t_end):
+
+ cdef double isi_value
+ cdef int index1, index2, index
+ cdef int N1, N2
+ cdef double nu1, nu2
+ cdef double last_t, curr_t, curr_isi
+ isi_value = 0.0
+ N1 = len(s1)
+ N2 = len(s2)
+
+ # first interspike interval - check if a spike exists at the start time
+ # and also account for spike trains with single spikes
+ if s1[0] > t_start:
+ # edge correction for the first interspike interval:
+ # take the maximum of the distance from the beginning to the first
+ # spike and the interval between the first two spikes.
+ # if there is only one spike, take the its distance to the beginning
+ nu1 = fmax(s1[0]-t_start, s1[1]-s1[0]) if N1 > 1 else s1[0]-t_start
+ index1 = -1
+ else:
+ # if the first spike is exactly at the start, take the distance
+ # to the next spike. If this is the only spike, take the distance to
+ # the end.
+ nu1 = s1[1]-s1[0] if N1 > 1 else t_end-s1[0]
+ index1 = 0
+
+ if s2[0] > t_start:
+ # edge correction as above
+ nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) if N2 > 1 else s2[0]-t_start
+ index2 = -1
+ else:
+ nu2 = s2[1]-s2[0] if N2 > 1 else t_end-s2[0]
+ index2 = 0
+
+ last_t = t_start
+ curr_isi = fabs(nu1-nu2)/fmax(nu1, nu2)
+ index = 1
+
+ with nogil: # release the interpreter to allow multithreading
+ while index1+index2 < N1+N2-2:
+ # check which spike is next, only if there are spikes left in 1
+ # next spike in 1 is earlier, or there are no spikes left in 2
+ if (index1 < N1-1) and ((index2 == N2-1) or
+ (s1[index1+1] < s2[index2+1])):
+ index1 += 1
+ curr_t = s1[index1]
+ if index1 < N1-1:
+ nu1 = s1[index1+1]-s1[index1]
+ else:
+ # edge correction for the last ISI:
+ # take the max of the distance of the last
+ # spike to the end and the previous ISI. If there was only
+ # one spike, always take the distance to the end.
+ nu1 = fmax(t_end-s1[index1], nu1) if N1 > 1 \
+ else t_end-s1[index1]
+ elif (index2 < N2-1) and ((index1 == N1-1) or
+ (s1[index1+1] > s2[index2+1])):
+ index2 += 1
+ curr_t = s2[index2]
+ if index2 < N2-1:
+ nu2 = s2[index2+1]-s2[index2]
+ else:
+ # edge correction for the end as above
+ nu2 = fmax(t_end-s2[index2], nu2) if N2 > 1 \
+ else t_end-s2[index2]
+ else: # s1[index1+1] == s2[index2+1]
+ index1 += 1
+ index2 += 1
+ curr_t = s1[index1]
+ if index1 < N1-1:
+ nu1 = s1[index1+1]-s1[index1]
+ else:
+ # edge correction for the end as above
+ nu1 = fmax(t_end-s1[index1], nu1) if N1 > 1 \
+ else t_end-s1[index1]
+ if index2 < N2-1:
+ nu2 = s2[index2+1]-s2[index2]
+ else:
+ # edge correction for the end as above
+ nu2 = fmax(t_end-s2[index2], nu2) if N2 > 1 \
+ else t_end-s2[index2]
+ # compute the corresponding isi-distance
+ isi_value += curr_isi * (curr_t - last_t)
+ curr_isi = fabs(nu1 - nu2) / fmax(nu1, nu2)
+ last_t = curr_t
+ index += 1
+
+ isi_value += curr_isi * (t_end - last_t)
+ # end nogil
+
+ return isi_value / (t_end-t_start)
+
+
+############################################################
+# get_min_dist_cython
+############################################################
+cdef inline double get_min_dist_cython(double spike_time,
+ double[:] spike_train,
+ # use memory view to ensure inlining
+ # np.ndarray[DTYPE_t,ndim=1] spike_train,
+ int N,
+ int start_index,
+ double t_start, double t_end) nogil:
+ """ Returns the minimal distance |spike_time - spike_train[i]|
+ with i>=start_index.
+ """
+ cdef double d, d_temp
+ # start with the distance to the start time
+ d = fabs(spike_time - t_start)
+ if start_index < 0:
+ start_index = 0
+ while start_index < N:
+ d_temp = fabs(spike_time - spike_train[start_index])
+ if d_temp > d:
+ return d
+ else:
+ d = d_temp
+ start_index += 1
+
+ # finally, check the distance to end time
+ d_temp = fabs(t_end - spike_time)
+ if d_temp > d:
+ return d
+ else:
+ return d_temp
+
+
+############################################################
+# isi_avrg_cython
+############################################################
+cdef inline double isi_avrg_cython(double isi1, double isi2) nogil:
+ return 0.5*(isi1+isi2)*(isi1+isi2)
+ # alternative definition to obtain <S> ~ 0.5 for Poisson spikes
+ # return 0.5*(isi1*isi1+isi2*isi2)
+ # another alternative definition without second normalization
+ # return 0.5*(isi1+isi2)
+
+
+############################################################
+# spike_distance_cython
+############################################################
+def spike_distance_cython(double[:] t1, double[:] t2,
+ double t_start, double t_end):
+
+ cdef int N1, N2, index1, index2, index
+ cdef double t_p1, t_f1, t_p2, t_f2, dt_p1, dt_p2, dt_f1, dt_f2
+ cdef double isi1, isi2, s1, s2
+ cdef double y_start, y_end, t_last, t_current, spike_value
+ cdef double[:] t_aux1 = np.empty(2)
+ cdef double[:] t_aux2 = np.empty(2)
+
+ spike_value = 0.0
+
+ N1 = len(t1)
+ N2 = len(t2)
+
+ # we can assume at least one spikes per spike train
+ assert N1 > 0
+ assert N2 > 0
+
+
+ with nogil: # release the interpreter to allow multithreading
+ t_last = t_start
+ # auxiliary spikes for edge correction - consistent with first/last ISI
+ t_aux1[0] = fmin(t_start, 2*t1[0]-t1[1]) if N1 > 1 else t_start
+ t_aux1[1] = fmax(t_end, 2*t1[N1-1]-t1[N1-2]) if N1 > 1 else t_end
+ t_aux2[0] = fmin(t_start, 2*t2[0]-t2[1]) if N2 > 1 else t_start
+ t_aux2[1] = fmax(t_end, 2*t2[N2-1]+-t2[N2-2]) if N2 > 1 else t_end
+ # print "aux spikes %.15f, %.15f ; %.15f, %.15f" % (t_aux1[0], t_aux1[1], t_aux2[0], t_aux2[1])
+ t_p1 = t_start if (t1[0] == t_start) else t_aux1[0]
+ t_p2 = t_start if (t2[0] == t_start) else t_aux2[0]
+ if t1[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f1 = t1[0]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1])
+ isi1 = fmax(t_f1-t_start, t1[1]-t1[0]) if N1 > 1 else t_f1-t_start
+ dt_p1 = dt_f1
+ # s1 = dt_p1*(t_f1-t_start)/isi1
+ s1 = dt_p1
+ index1 = -1
+ else: # t1[0] == t_start
+ t_f1 = t1[1] if N1 > 1 else t_end
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1])
+ dt_p1 = get_min_dist_cython(t_p1, t2, N2, 0, t_aux2[0], t_aux2[1])
+ isi1 = t_f1-t1[0]
+ s1 = dt_p1
+ index1 = 0
+ if t2[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f2 = t2[0]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1])
+ dt_p2 = dt_f2
+ isi2 = fmax(t_f2-t_start, t2[1]-t2[0]) if N2 > 1 else t_f2-t_start
+ # s2 = dt_p2*(t_f2-t_start)/isi2
+ s2 = dt_p2
+ index2 = -1
+ else: # t2[0] == t_start
+ t_f2 = t2[1] if N2 > 1 else t_end
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1])
+ # dt_p2 = t_start-t_p1 # 0.0
+ dt_p2 = get_min_dist_cython(t_p2, t1, N1, 0, t_aux1[0], t_aux1[1])
+ isi2 = t_f2-t2[0]
+ s2 = dt_p2
+ index2 = 0
+
+ y_start = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ # y_start = (s1 + s2) / isi_avrg_cython(isi1, isi2)
+ index = 1
+
+ while index1+index2 < N1+N2-2:
+ # print(index, index1, index2)
+ if (index1 < N1-1) and (t_f1 < t_f2 or index2 == N2-1):
+ index1 += 1
+ # first calculate the previous interval end value
+ s1 = dt_f1*(t_f1-t_p1) / isi1
+ # the previous time now was the following time before:
+ dt_p1 = dt_f1
+ t_p1 = t_f1 # t_p1 contains the current time point
+ # get the next time
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ else:
+ t_f1 = t_aux1[1]
+ t_curr = t_p1
+ s2 = (dt_p2*(t_f2-t_p1) + dt_f2*(t_p1-t_p2)) / isi2
+ y_end = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ # y_end = (s1 + s2) / isi_avrg_cython(isi1, isi2)
+
+ spike_value += 0.5*(y_start + y_end) * (t_curr - t_last)
+
+ # now the next interval start value
+ if index1 < N1-1:
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2,
+ t_aux2[0], t_aux2[1])
+ isi1 = t_f1-t_p1
+ s1 = dt_p1
+ else:
+ dt_f1 = dt_p1
+ isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) if N1 > 1 \
+ else t_end-t1[N1-1]
+ # s1 needs adjustment due to change of isi1
+ # s1 = dt_p1*(t_end-t1[N1-1])/isi1
+ # Eero's correction: no adjustment
+ s1 = dt_p1
+ # s2 is the same as above, thus we can compute y2 immediately
+ y_start = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ # y_start = (s1 + s2) / isi_avrg_cython(isi1, isi2)
+ elif (index2 < N2-1) and (t_f1 > t_f2 or index1 == N1-1):
+ index2 += 1
+ # first calculate the previous interval end value
+ s2 = dt_f2*(t_f2-t_p2) / isi2
+ # the previous time now was the following time before:
+ dt_p2 = dt_f2
+ t_p2 = t_f2 # t_p2 contains the current time point
+ # get the next time
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ else:
+ t_f2 = t_aux2[1]
+ t_curr = t_p2
+ s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1
+ y_end = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ # y_end = (s1 + s2) / isi_avrg_cython(isi1, isi2)
+
+ spike_value += 0.5*(y_start + y_end) * (t_curr - t_last)
+
+ # now the next interval start value
+ if index2 < N2-1:
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1,
+ t_aux1[0], t_aux1[1])
+ isi2 = t_f2-t_p2
+ s2 = dt_p2
+ else:
+ dt_f2 = dt_p2
+ isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \
+ else t_end-t2[N2-1]
+ # s2 needs adjustment due to change of isi2
+ # s2 = dt_p2*(t_end-t2[N2-1])/isi2
+ # Eero's correction: no adjustment
+ s2 = dt_p2
+ # s1 is the same as above, thus we can compute y2 immediately
+ y_start = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ # y_start = (s1 + s2) / isi_avrg_cython(isi1, isi2)
+
+ else: # t_f1 == t_f2 - generate only one event
+ index1 += 1
+ index2 += 1
+ t_p1 = t_f1
+ t_p2 = t_f2
+ dt_p1 = 0.0
+ dt_p2 = 0.0
+ t_curr = t_f1
+ y_end = 0.0
+ spike_value += 0.5*(y_start + y_end) * (t_curr - t_last)
+ y_start = 0.0
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2,
+ t_aux2[0], t_aux2[1])
+ isi1 = t_f1 - t_p1
+ else:
+ t_f1 = t_aux1[1]
+ dt_f1 = dt_p1
+ isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) if N1 > 1 \
+ else t_end-t1[N1-1]
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1,
+ t_aux1[0], t_aux1[1])
+ isi2 = t_f2 - t_p2
+ else:
+ t_f2 = t_aux2[1]
+ dt_f2 = dt_p2
+ isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \
+ else t_end-t2[N2-1]
+ index += 1
+ t_last = t_curr
+ # isi1 = max(t_end-t1[N1-1], t1[N1-1]-t1[N1-2])
+ # isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2])
+ s1 = dt_f1 # *(t_end-t1[N1-1])/isi1
+ s2 = dt_f2 # *(t_end-t2[N2-1])/isi2
+ y_end = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ # y_end = (s1 + s2) / isi_avrg_cython(isi1, isi2)
+
+ spike_value += 0.5*(y_start + y_end) * (t_end - t_last)
+ # end nogil
+
+ # use only the data added above
+ # could be less than original length due to equal spike times
+ return spike_value / (t_end-t_start)
+
+
+############################################################
+# isi_avrg_rf_cython
+############################################################
+cdef inline double isi_avrg_rf_cython(double isi1, double isi2) nogil:
+ # rate free version
+ return (isi1+isi2)
+
+
+############################################################
+# spike_distance_rf_cython
+############################################################
+def spike_distance_rf_cython(double[:] t1, double[:] t2,
+ double t_start, double t_end):
+
+ cdef int N1, N2, index1, index2, index
+ cdef double t_p1, t_f1, t_p2, t_f2, dt_p1, dt_p2, dt_f1, dt_f2
+ cdef double isi1, isi2, s1, s2
+ cdef double y_start, y_end, t_last, t_current, spike_value
+
+ spike_value = 0.0
+
+ N1 = len(t1)
+ N2 = len(t2)
+
+ with nogil: # release the interpreter to allow multithreading
+ t_last = t_start
+ t_p1 = t_start
+ t_p2 = t_start
+ if t1[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f1 = t1[0]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_start, t_end)
+ isi1 = fmax(t_f1-t_start, t1[1]-t1[0])
+ dt_p1 = dt_f1
+ s1 = dt_p1*(t_f1-t_start)/isi1
+ index1 = -1
+ else:
+ t_f1 = t1[1]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_start, t_end)
+ dt_p1 = 0.0
+ isi1 = t1[1]-t1[0]
+ s1 = dt_p1
+ index1 = 0
+ if t2[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f2 = t2[0]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_start, t_end)
+ dt_p2 = dt_f2
+ isi2 = fmax(t_f2-t_start, t2[1]-t2[0])
+ s2 = dt_p2*(t_f2-t_start)/isi2
+ index2 = -1
+ else:
+ t_f2 = t2[1]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_start, t_end)
+ dt_p2 = 0.0
+ isi2 = t2[1]-t2[0]
+ s2 = dt_p2
+ index2 = 0
+
+ # y_start = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ y_start = (s1 + s2) / isi_avrg_rf_cython(isi1, isi2)
+ index = 1
+
+ while index1+index2 < N1+N2-2:
+ # print(index, index1, index2)
+ if (index1 < N1-1) and (t_f1 < t_f2 or index2 == N2-1):
+ index1 += 1
+ # first calculate the previous interval end value
+ s1 = dt_f1*(t_f1-t_p1) / isi1
+ # the previous time now was the following time before:
+ dt_p1 = dt_f1
+ t_p1 = t_f1 # t_p1 contains the current time point
+ # get the next time
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ else:
+ t_f1 = t_end
+ t_curr = t_p1
+ s2 = (dt_p2*(t_f2-t_p1) + dt_f2*(t_p1-t_p2)) / isi2
+ # y_end = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ y_end = (s1 + s2) / isi_avrg_rf_cython(isi1, isi2)
+
+ spike_value += 0.5*(y_start + y_end) * (t_curr - t_last)
+
+ # now the next interval start value
+ if index1 < N1-1:
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2,
+ t_start, t_end)
+ isi1 = t_f1-t_p1
+ s1 = dt_p1
+ else:
+ dt_f1 = dt_p1
+ isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2])
+ # s1 needs adjustment due to change of isi1
+ s1 = dt_p1*(t_end-t1[N1-1])/isi1
+ # s2 is the same as above, thus we can compute y2 immediately
+ # y_start = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ y_start = (s1 + s2) / isi_avrg_rf_cython(isi1, isi2)
+ elif (index2 < N2-1) and (t_f1 > t_f2 or index1 == N1-1):
+ index2 += 1
+ # first calculate the previous interval end value
+ s2 = dt_f2*(t_f2-t_p2) / isi2
+ # the previous time now was the following time before:
+ dt_p2 = dt_f2
+ t_p2 = t_f2 # t_p2 contains the current time point
+ # get the next time
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ else:
+ t_f2 = t_end
+ t_curr = t_p2
+ s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1
+ # y_end = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ y_end = (s1 + s2) / isi_avrg_rf_cython(isi1, isi2)
+
+ spike_value += 0.5*(y_start + y_end) * (t_curr - t_last)
+
+ # now the next interval start value
+ if index2 < N2-1:
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1,
+ t_start, t_end)
+ isi2 = t_f2-t_p2
+ s2 = dt_p2
+ else:
+ dt_f2 = dt_p2
+ isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2])
+ # s2 needs adjustment due to change of isi2
+ s2 = dt_p2*(t_end-t2[N2-1])/isi2
+ # s1 is the same as above, thus we can compute y2 immediately
+ # y_start = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ y_start = (s1 + s2) / isi_avrg_rf_cython(isi1, isi2)
+
+ else: # t_f1 == t_f2 - generate only one event
+ index1 += 1
+ index2 += 1
+ t_p1 = t_f1
+ t_p2 = t_f2
+ dt_p1 = 0.0
+ dt_p2 = 0.0
+ t_curr = t_f1
+ y_end = 0.0
+ spike_value += 0.5*(y_start + y_end) * (t_curr - t_last)
+ y_start = 0.0
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2,
+ t_start, t_end)
+ isi1 = t_f1 - t_p1
+ else:
+ t_f1 = t_end
+ dt_f1 = dt_p1
+ isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2])
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1,
+ t_start, t_end)
+ isi2 = t_f2 - t_p2
+ else:
+ t_f2 = t_end
+ dt_f2 = dt_p2
+ isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2])
+ index += 1
+ t_last = t_curr
+ # isi1 = max(t_end-t1[N1-1], t1[N1-1]-t1[N1-2])
+ # isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2])
+ s1 = dt_f1*(t_end-t1[N1-1])/isi1
+ s2 = dt_f2*(t_end-t2[N2-1])/isi2
+ # y_end = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # alternative definition without second normalization
+ y_end = (s1 + s2) / isi_avrg_rf_cython(isi1, isi2)
+
+ spike_value += 0.5*(y_start + y_end) * (t_end - t_last)
+ # end nogil
+
+ # use only the data added above
+ # could be less than original length due to equal spike times
+ return spike_value / (t_end-t_start)
+
+
+
+############################################################
+# get_tau
+############################################################
+cdef inline double get_tau(double[:] spikes1, double[:] spikes2,
+ int i, int j, double interval, double max_tau):
+ cdef double m = interval # use interval length as initial tau
+ cdef int N1 = spikes1.shape[0]-1 # len(spikes1)-1
+ cdef int N2 = spikes2.shape[0]-1 # len(spikes2)-1
+ if i < N1 and i > -1:
+ m = fmin(m, spikes1[i+1]-spikes1[i])
+ if j < N2 and j > -1:
+ m = fmin(m, spikes2[j+1]-spikes2[j])
+ if i > 0:
+ m = fmin(m, spikes1[i]-spikes1[i-1])
+ if j > 0:
+ m = fmin(m, spikes2[j]-spikes2[j-1])
+ m *= 0.5
+ if max_tau > 0.0:
+ m = fmin(m, max_tau)
+ return m
+
+
+############################################################
+# coincidence_value_cython
+############################################################
+def coincidence_value_cython(double[:] spikes1, double[:] spikes2,
+ double t_start, double t_end, double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int i = -1
+ cdef int j = -1
+ cdef double coinc = 0.0
+ cdef double mp = 0.0
+ cdef double interval = t_end - t_start
+ cdef double tau
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ mp += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ coinc += 2
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ mp += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ coinc += 2
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ # add only one event, but with coincidence 2 and multiplicity 2
+ mp += 2
+ coinc += 2
+
+ return coinc, mp
diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx
new file mode 100644
index 0000000..aa24db4
--- /dev/null
+++ b/pyspike/cython/cython_profiles.pyx
@@ -0,0 +1,485 @@
+#cython: boundscheck=False
+#cython: wraparound=False
+#cython: cdivision=True
+
+"""
+cython_profiles.pyx
+
+cython implementation of the isi-, spike- and spike-sync profiles
+
+Note: using cython memoryviews (e.g. double[:]) instead of ndarray objects
+improves the performance of spike_distance by a factor of 10!
+
+Copyright 2014-2015, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+"""
+To test whether things can be optimized: remove all yellow stuff
+in the html output::
+
+ cython -a cython_profiles.pyx
+
+which gives::
+
+ cython_profiles.html
+
+"""
+
+import numpy as np
+cimport numpy as np
+
+from libc.math cimport fabs
+from libc.math cimport fmax
+from libc.math cimport fmin
+
+DTYPE = np.float
+ctypedef np.float_t DTYPE_t
+
+
+############################################################
+# isi_profile_cython
+############################################################
+def isi_profile_cython(double[:] s1, double[:] s2,
+ double t_start, double t_end):
+
+ cdef double[:] spike_events
+ cdef double[:] isi_values
+ cdef int index1, index2, index
+ cdef int N1, N2
+ cdef double nu1, nu2
+ N1 = len(s1)
+ N2 = len(s2)
+
+ spike_events = np.empty(N1+N2+2)
+ # the values have one entry less as they are defined at the intervals
+ isi_values = np.empty(N1+N2+1)
+
+ # first x-value of the profile
+ spike_events[0] = t_start
+
+ # first interspike interval - check if a spike exists at the start time
+ if s1[0] > t_start:
+ # edge correction
+ nu1 = fmax(s1[0]-t_start, s1[1]-s1[0]) if N1 > 1 else s1[0]-t_start
+ index1 = -1
+ else:
+ nu1 = s1[1]-s1[0] if N1 > 1 else t_end-s1[0]
+ index1 = 0
+
+ if s2[0] > t_start:
+ # edge correction
+ nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) if N2 > 1 else s2[0]-t_start
+ index2 = -1
+ else:
+ nu2 = s2[1]-s2[0] if N2 > 1 else t_end-s2[0]
+ index2 = 0
+
+ isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2)
+ index = 1
+
+ with nogil: # release the interpreter to allow multithreading
+ while index1+index2 < N1+N2-2:
+ # check which spike is next, only if there are spikes left in 1
+ # next spike in 1 is earlier, or there are no spikes left in 2
+ if (index1 < N1-1) and ((index2 == N2-1) or
+ (s1[index1+1] < s2[index2+1])):
+ index1 += 1
+ spike_events[index] = s1[index1]
+ if index1 < N1-1:
+ nu1 = s1[index1+1]-s1[index1]
+ else:
+ # edge correction
+ nu1 = fmax(t_end-s1[index1], nu1) if N1 > 1 \
+ else t_end-s1[index1]
+ elif (index2 < N2-1) and ((index1 == N1-1) or
+ (s1[index1+1] > s2[index2+1])):
+ index2 += 1
+ spike_events[index] = s2[index2]
+ if index2 < N2-1:
+ nu2 = s2[index2+1]-s2[index2]
+ else:
+ # edge correction
+ nu2 = fmax(t_end-s2[index2], nu2) if N2 > 1 \
+ else t_end-s2[index2]
+ else: # s1[index1+1] == s2[index2+1]
+ index1 += 1
+ index2 += 1
+ spike_events[index] = s1[index1]
+ if index1 < N1-1:
+ nu1 = s1[index1+1]-s1[index1]
+ else:
+ # edge correction
+ nu1 = fmax(t_end-s1[index1], nu1) if N1 > 1 \
+ else t_end-s1[index1]
+ if index2 < N2-1:
+ nu2 = s2[index2+1]-s2[index2]
+ else:
+ # edge correction
+ nu2 = fmax(t_end-s2[index2], nu2) if N2 > 1 \
+ else t_end-s2[index2]
+ # compute the corresponding isi-distance
+ isi_values[index] = fabs(nu1 - nu2) / fmax(nu1, nu2)
+ index += 1
+ # the last event is the interval end
+ if spike_events[index-1] == t_end:
+ index -= 1
+ else:
+ spike_events[index] = t_end
+ # end nogil
+
+ return spike_events[:index+1], isi_values[:index]
+
+
+############################################################
+# get_min_dist_cython
+############################################################
+cdef inline double get_min_dist_cython(double spike_time,
+ double[:] spike_train,
+ # use memory view to ensure inlining
+ # np.ndarray[DTYPE_t,ndim=1] spike_train,
+ int N,
+ int start_index,
+ double t_start, double t_end) nogil:
+ """ Returns the minimal distance |spike_time - spike_train[i]|
+ with i>=start_index.
+ """
+ cdef double d, d_temp
+ # start with the distance to the start time
+ d = fabs(spike_time - t_start)
+ if start_index < 0:
+ start_index = 0
+ while start_index < N:
+ d_temp = fabs(spike_time - spike_train[start_index])
+ if d_temp > d:
+ return d
+ else:
+ d = d_temp
+ start_index += 1
+
+ # finally, check the distance to end time
+ d_temp = fabs(t_end - spike_time)
+ if d_temp > d:
+ return d
+ else:
+ return d_temp
+
+
+############################################################
+# isi_avrg_cython
+############################################################
+cdef inline double isi_avrg_cython(double isi1, double isi2) nogil:
+ return 0.5*(isi1+isi2)*(isi1+isi2)
+ # alternative definition to obtain <S> ~ 0.5 for Poisson spikes
+ # return 0.5*(isi1*isi1+isi2*isi2)
+
+
+############################################################
+# spike_profile_cython
+############################################################
+def spike_profile_cython(double[:] t1, double[:] t2,
+ double t_start, double t_end):
+
+ cdef double[:] spike_events
+ cdef double[:] y_starts
+ cdef double[:] y_ends
+ cdef double[:] t_aux1 = np.empty(2)
+ cdef double[:] t_aux2 = np.empty(2)
+
+ cdef int N1, N2, index1, index2, index
+ cdef double t_p1, t_f1, t_p2, t_f2, dt_p1, dt_p2, dt_f1, dt_f2
+ cdef double isi1, isi2, s1, s2
+
+ N1 = len(t1)
+ N2 = len(t2)
+
+ # we can assume at least one spikes per spike train
+ assert N1 > 0
+ assert N2 > 0
+
+ spike_events = np.empty(N1+N2+2)
+
+ y_starts = np.empty(len(spike_events)-1)
+ y_ends = np.empty(len(spike_events)-1)
+
+ with nogil: # release the interpreter to allow multithreading
+ spike_events[0] = t_start
+ # t_p1 = t_start
+ # t_p2 = t_start
+ # auxiliary spikes for edge correction - consistent with first/last ISI
+ t_aux1[0] = fmin(t_start, 2*t1[0]-t1[1]) if N1 > 1 else t_start
+ t_aux1[1] = fmax(t_end, 2*t1[N1-1]-t1[N1-2]) if N1 > 1 else t_end
+ t_aux2[0] = fmin(t_start, 2*t2[0]-t2[1]) if N2 > 1 else t_start
+ t_aux2[1] = fmax(t_end, 2*t2[N2-1]-t2[N2-2]) if N2 > 1 else t_end
+ t_p1 = t_start if (t1[0] == t_start) else t_aux1[0]
+ t_p2 = t_start if (t2[0] == t_start) else t_aux2[0]
+ if t1[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f1 = t1[0]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1])
+ isi1 = fmax(t_f1-t_start, t1[1]-t1[0]) if N1 > 1 else t_f1-t_start
+ dt_p1 = dt_f1
+ # s1 = dt_p1*(t_f1-t_start)/isi1
+ s1 = dt_p1
+ index1 = -1
+ else:
+ t_f1 = t1[1] if N1 > 1 else t_end
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1])
+ dt_p1 = get_min_dist_cython(t_p1, t2, N2, 0, t_aux2[0], t_aux2[1])
+ isi1 = t_f1-t1[0]
+ s1 = dt_p1
+ index1 = 0
+ if t2[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f2 = t2[0]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1])
+ dt_p2 = dt_f2
+ isi2 = fmax(t_f2-t_start, t2[1]-t2[0]) if N2 > 1 else t_f2-t_start
+ # s2 = dt_p2*(t_f2-t_start)/isi2
+ s2 = dt_p2
+ index2 = -1
+ else:
+ t_f2 = t2[1] if N2 > 1 else t_end
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1])
+ dt_p2 = get_min_dist_cython(t_p2, t1, N1, 0, t_aux1[0], t_aux1[1])
+ isi2 = t_f2-t2[0]
+ s2 = dt_p2
+ index2 = 0
+
+ y_starts[0] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ index = 1
+
+ while index1+index2 < N1+N2-2:
+ # print(index, index1, index2)
+ if (index1 < N1-1) and (t_f1 < t_f2 or index2 == N2-1):
+ index1 += 1
+ # first calculate the previous interval end value
+ s1 = dt_f1*(t_f1-t_p1) / isi1
+ # the previous time now was the following time before:
+ dt_p1 = dt_f1
+ t_p1 = t_f1 # t_p1 contains the current time point
+ # get the next time
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ else:
+ t_f1 = t_aux1[1]
+ spike_events[index] = t_p1
+ s2 = (dt_p2*(t_f2-t_p1) + dt_f2*(t_p1-t_p2)) / isi2
+ y_ends[index-1] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1,
+ isi2)
+ # now the next interval start value
+ if index1 < N1-1:
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2,
+ t_aux2[0], t_aux2[1])
+ isi1 = t_f1-t_p1
+ s1 = dt_p1
+ else:
+ dt_f1 = dt_p1
+ isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) if N1 > 1 \
+ else t_end-t1[N1-1]
+ # s1 needs adjustment due to change of isi1
+ # s1 = dt_p1*(t_end-t1[N1-1])/isi1
+ # Eero's correction: no adjustment
+ s1 = dt_p1
+ # s2 is the same as above, thus we can compute y2 immediately
+ y_starts[index] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1,
+ isi2)
+ elif (index2 < N2-1) and (t_f1 > t_f2 or index1 == N1-1):
+ index2 += 1
+ # first calculate the previous interval end value
+ s2 = dt_f2*(t_f2-t_p2) / isi2
+ # the previous time now was the following time before:
+ dt_p2 = dt_f2
+ t_p2 = t_f2 # t_p2 contains the current time point
+ # get the next time
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ else:
+ t_f2 = t_aux2[1]
+ spike_events[index] = t_p2
+ s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1
+ y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1,
+ isi2)
+ # now the next interval start value
+ if index2 < N2-1:
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1,
+ t_aux1[0], t_aux1[1])
+ isi2 = t_f2-t_p2
+ s2 = dt_p2
+ else:
+ dt_f2 = dt_p2
+ isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \
+ else t_end-t2[N2-1]
+ # s2 needs adjustment due to change of isi2
+ # s2 = dt_p2*(t_end-t2[N2-1])/isi2
+ # Eero's correction: no adjustment
+ s2 = dt_p2
+ # s2 is the same as above, thus we can compute y2 immediately
+ y_starts[index] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2)
+ else: # t_f1 == t_f2 - generate only one event
+ index1 += 1
+ index2 += 1
+ t_p1 = t_f1
+ t_p2 = t_f2
+ dt_p1 = 0.0
+ dt_p2 = 0.0
+ spike_events[index] = t_f1
+ y_ends[index-1] = 0.0
+ y_starts[index] = 0.0
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2,
+ t_aux2[0], t_aux2[1])
+ isi1 = t_f1 - t_p1
+ else:
+ t_f1 = t_aux1[1]
+ dt_f1 = dt_p1
+ isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) if N1 > 1 \
+ else t_end-t1[N1-1]
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1,
+ t_aux1[0], t_aux1[1])
+ isi2 = t_f2 - t_p2
+ else:
+ t_f2 = t_aux2[1]
+ dt_f2 = dt_p2
+ isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \
+ else t_end-t2[N2-1]
+ index += 1
+ # the last event is the interval end
+ if spike_events[index-1] == t_end:
+ index -= 1
+ else:
+ spike_events[index] = t_end
+ s1 = dt_f1
+ s2 = dt_f2
+ y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2)
+ # end nogil
+
+ # use only the data added above
+ # could be less than original length due to equal spike times
+ return spike_events[:index+1], y_starts[:index], y_ends[:index]
+
+
+
+############################################################
+# get_tau
+############################################################
+cdef inline double get_tau(double[:] spikes1, double[:] spikes2,
+ int i, int j, double interval, double max_tau):
+ cdef double m = interval # use interval as initial tau
+ cdef int N1 = spikes1.shape[0]-1 # len(spikes1)-1
+ cdef int N2 = spikes2.shape[0]-1 # len(spikes2)-1
+ if i < N1 and i > -1:
+ m = fmin(m, spikes1[i+1]-spikes1[i])
+ if j < N2 and j > -1:
+ m = fmin(m, spikes2[j+1]-spikes2[j])
+ if i > 0:
+ m = fmin(m, spikes1[i]-spikes1[i-1])
+ if j > 0:
+ m = fmin(m, spikes2[j]-spikes2[j-1])
+ m *= 0.5
+ if max_tau > 0.0:
+ m = fmin(m, max_tau)
+ return m
+
+
+############################################################
+# coincidence_profile_cython
+############################################################
+def coincidence_profile_cython(double[:] spikes1, double[:] spikes2,
+ double t_start, double t_end, double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int i = -1
+ cdef int j = -1
+ cdef int n = 0
+ cdef double[:] st = np.zeros(N1 + N2 + 2) # spike times
+ cdef double[:] c = np.zeros(N1 + N2 + 2) # coincidences
+ cdef double[:] mp = np.ones(N1 + N2 + 2) # multiplicity
+ cdef double interval = t_end - t_start
+ cdef double tau
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ st[n] = spikes1[i]
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ c[n] = 1
+ c[n-1] = 1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ st[n] = spikes2[j]
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ c[n] = 1
+ c[n-1] = 1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ n += 1
+ # add only one event, but with coincidence 2 and multiplicity 2
+ st[n] = spikes1[i]
+ c[n] = 2
+ mp[n] = 2
+
+ st = st[:n+2]
+ c = c[:n+2]
+ mp = mp[:n+2]
+
+ st[0] = t_start
+ st[len(st)-1] = t_end
+ if N1 + N2 > 0:
+ c[0] = c[1]
+ c[len(c)-1] = c[len(c)-2]
+ mp[0] = mp[1]
+ mp[len(mp)-1] = mp[len(mp)-2]
+ else:
+ c[0] = 1
+ c[1] = 1
+
+ return st, c, mp
+
+
+############################################################
+# coincidence_single_profile_cython
+############################################################
+def coincidence_single_profile_cython(double[:] spikes1, double[:] spikes2,
+ double t_start, double t_end, double max_tau):
+
+ cdef int N1 = len(spikes1)
+ cdef int N2 = len(spikes2)
+ cdef int j = -1
+ cdef double[:] c = np.zeros(N1) # coincidences
+ cdef double interval = t_end - t_start
+ cdef double tau
+ for i in xrange(N1):
+ while j < N2-1 and spikes2[j+1] < spikes1[i]:
+ # move forward until spikes2[j] is the last spike before spikes1[i]
+ # note that if spikes2[j] is after spikes1[i] we dont do anything
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if j > -1 and fabs(spikes1[i]-spikes2[j]) < tau:
+ # current spike in st1 is coincident
+ c[i] = 1
+ if j < N2-1 and (j < 0 or spikes2[j] < spikes1[i]):
+ # in case spikes2[j] is before spikes1[i] it has to be the one
+ # right before (see above), hence we move one forward and also
+ # check the next spike
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, interval, max_tau)
+ if fabs(spikes2[j]-spikes1[i]) < tau:
+ # current spike in st1 is coincident
+ c[i] = 1
+ return c
diff --git a/pyspike/cython/cython_simulated_annealing.pyx b/pyspike/cython/cython_simulated_annealing.pyx
new file mode 100644
index 0000000..be9423c
--- /dev/null
+++ b/pyspike/cython/cython_simulated_annealing.pyx
@@ -0,0 +1,82 @@
+#cython: boundscheck=False
+#cython: wraparound=False
+#cython: cdivision=True
+
+"""
+cython_simulated_annealing.pyx
+
+cython implementation of a simulated annealing algorithm to find the optimal
+spike train order
+
+Note: using cython memoryviews (e.g. double[:]) instead of ndarray objects
+improves the performance of spike_distance by a factor of 10!
+
+Copyright 2015, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+"""
+To test whether things can be optimized: remove all yellow stuff
+in the html output::
+
+ cython -a cython_simulated_annealing.pyx
+
+which gives:
+
+ cython_simulated_annealing.html
+
+"""
+
+import numpy as np
+cimport numpy as np
+
+from libc.math cimport exp
+from libc.math cimport fmod
+from libc.stdlib cimport rand
+from libc.stdlib cimport RAND_MAX
+
+DTYPE = np.float
+ctypedef np.float_t DTYPE_t
+
+
+def sim_ann_cython(double[:, :] D, double T_start, double T_end, double alpha):
+
+ cdef long N = len(D)
+ cdef double A = np.sum(np.triu(D, 0))
+ cdef long[:] p = np.arange(N)
+ cdef double T = T_start
+ cdef long iterations
+ cdef long succ_iter
+ cdef long total_iter = 0
+ cdef double delta_A
+ cdef long ind1
+ cdef long ind2
+
+ while T > T_end:
+ iterations = 0
+ succ_iter = 0
+ # equilibrate for 100*N steps or 10*N successful steps
+ while iterations < 100*N and succ_iter < 10*N:
+ # exchange two rows and cols
+ # ind1 = np.random.randint(N-1)
+ ind1 = rand() % (N-1)
+ if ind1 < N-1:
+ ind2 = ind1+1
+ else: # this can never happen!
+ ind2 = 0
+ delta_A = -2*D[p[ind1], p[ind2]]
+ if delta_A > 0.0 or exp(delta_A/T) > ((1.0*rand()) / RAND_MAX):
+ # swap indices
+ p[ind1], p[ind2] = p[ind2], p[ind1]
+ A += delta_A
+ succ_iter += 1
+ iterations += 1
+ total_iter += iterations
+ T *= alpha # cool down
+ if succ_iter == 0:
+ # no successful step -> we believe we have converged
+ break
+
+ return p, A, total_iter
diff --git a/pyspike/cython/directionality_python_backend.py b/pyspike/cython/directionality_python_backend.py
new file mode 100644
index 0000000..c1d820b
--- /dev/null
+++ b/pyspike/cython/directionality_python_backend.py
@@ -0,0 +1,144 @@
+""" directionality_python_backend.py
+
+Collection of python functions that can be used instead of the cython
+implementation.
+
+Copyright 2015, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+import numpy as np
+
+
+############################################################
+# spike_train_order_python
+############################################################
+def spike_directionality_profile_python(spikes1, spikes2, t_start, t_end,
+ max_tau):
+
+ def get_tau(spikes1, spikes2, i, j, max_tau):
+ m = t_end - t_start # use interval as initial tau
+ if i < len(spikes1)-1 and i > -1:
+ m = min(m, spikes1[i+1]-spikes1[i])
+ if j < len(spikes2)-1 and j > -1:
+ m = min(m, spikes2[j+1]-spikes2[j])
+ if i > 0:
+ m = min(m, spikes1[i]-spikes1[i-1])
+ if j > 0:
+ m = min(m, spikes2[j]-spikes2[j-1])
+ m *= 0.5
+ if max_tau > 0.0:
+ m = min(m, max_tau)
+ return m
+
+ N1 = len(spikes1)
+ N2 = len(spikes2)
+ i = -1
+ j = -1
+ d1 = np.zeros(N1) # directionality values
+ d2 = np.zeros(N2) # directionality values
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau)
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike in first spike train occurs after second
+ d1[i] = -1
+ d2[j] = +1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau)
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # spike in second spike train occurs after first
+ d1[i] = +1
+ d2[j] = -1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ d1[i] = 0
+ d2[j] = 0
+
+ return d1, d2
+
+
+############################################################
+# spike_train_order_python
+############################################################
+def spike_train_order_profile_python(spikes1, spikes2, t_start, t_end,
+ max_tau):
+
+ def get_tau(spikes1, spikes2, i, j, max_tau):
+ m = t_end - t_start # use interval as initial tau
+ if i < len(spikes1)-1 and i > -1:
+ m = min(m, spikes1[i+1]-spikes1[i])
+ if j < len(spikes2)-1 and j > -1:
+ m = min(m, spikes2[j+1]-spikes2[j])
+ if i > 0:
+ m = min(m, spikes1[i]-spikes1[i-1])
+ if j > 0:
+ m = min(m, spikes2[j]-spikes2[j-1])
+ m *= 0.5
+ if max_tau > 0.0:
+ m = min(m, max_tau)
+ return m
+
+ N1 = len(spikes1)
+ N2 = len(spikes2)
+ i = -1
+ j = -1
+ n = 0
+ st = np.zeros(N1 + N2 + 2) # spike times
+ a = np.zeros(N1 + N2 + 2) # coincidences
+ mp = np.ones(N1 + N2 + 2) # multiplicity
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau)
+ st[n] = spikes1[i]
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ a[n] = -1
+ a[n-1] = -1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau)
+ st[n] = spikes2[j]
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ a[n] = 1
+ a[n-1] = 1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ n += 1
+ # add only one event with zero asymmetry value and multiplicity 2
+ st[n] = spikes1[i]
+ a[n] = 0
+ mp[n] = 2
+
+ st = st[:n+2]
+ a = a[:n+2]
+ mp = mp[:n+2]
+
+ st[0] = t_start
+ st[len(st)-1] = t_end
+ if N1 + N2 > 0:
+ a[0] = a[1]
+ a[len(a)-1] = a[len(a)-2]
+ mp[0] = mp[1]
+ mp[len(mp)-1] = mp[len(mp)-2]
+ else:
+ a[0] = 1
+ a[1] = 1
+
+ return st, a, mp
diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py
new file mode 100644
index 0000000..e75f181
--- /dev/null
+++ b/pyspike/cython/python_backend.py
@@ -0,0 +1,638 @@
+""" python_backend.py
+
+Collection of python functions that can be used instead of the cython
+implementation.
+
+Copyright 2014-2015, Mario Mulansky <mario.mulansky@gmx.net>
+
+Distributed under the BSD License
+
+"""
+
+import numpy as np
+
+
+############################################################
+# isi_distance_python
+############################################################
+def isi_distance_python(s1, s2, t_start, t_end):
+ """ Plain Python implementation of the isi distance.
+ """
+ N1 = len(s1)
+ N2 = len(s2)
+
+ # compute the isi-distance
+ spike_events = np.empty(N1+N2+2)
+ spike_events[0] = t_start
+ # the values have one entry less - the number of intervals between events
+ isi_values = np.empty(len(spike_events) - 1)
+ if s1[0] > t_start:
+ # edge correction
+ nu1 = max(s1[0] - t_start, s1[1] - s1[0]) if N1 > 1 else s1[0]-t_start
+ index1 = -1
+ else:
+ nu1 = s1[1] - s1[0] if N1 > 1 else t_end-s1[0]
+ index1 = 0
+ if s2[0] > t_start:
+ # edge correction
+ nu2 = max(s2[0] - t_start, s2[1] - s2[0]) if N2 > 1 else s2[0]-t_start
+ index2 = -1
+ else:
+ nu2 = s2[1] - s2[0] if N2 > 1 else t_end-s2[0]
+ index2 = 0
+
+ isi_values[0] = abs(nu1 - nu2) / max(nu1, nu2)
+ index = 1
+ while index1+index2 < N1+N2-2:
+ # check which spike is next - from s1 or s2
+ if (index1 < N1-1) and (index2 == N2-1 or s1[index1+1] < s2[index2+1]):
+ index1 += 1
+ spike_events[index] = s1[index1]
+ if index1 < N1-1:
+ nu1 = s1[index1+1]-s1[index1]
+ else:
+ # edge correction
+ nu1 = max(t_end-s1[N1-1], s1[N1-1]-s1[N1-2]) if N1 > 1 \
+ else t_end-s1[N1-1]
+
+ elif (index2 < N2-1) and (index1 == N1-1 or
+ s1[index1+1] > s2[index2+1]):
+ index2 += 1
+ spike_events[index] = s2[index2]
+ if index2 < N2-1:
+ nu2 = s2[index2+1]-s2[index2]
+ else:
+ # edge correction
+ nu2 = max(t_end-s2[N2-1], s2[N2-1]-s2[N2-2]) if N2 > 1 \
+ else t_end-s2[N2-1]
+
+ else: # s1[index1 + 1] == s2[index2 + 1]
+ index1 += 1
+ index2 += 1
+ spike_events[index] = s1[index1]
+ if index1 < N1-1:
+ nu1 = s1[index1+1]-s1[index1]
+ else:
+ # edge correction
+ nu1 = max(t_end-s1[N1-1], s1[N1-1]-s1[N1-2]) if N1 > 1 \
+ else t_end-s1[N1-1]
+ if index2 < N2-1:
+ nu2 = s2[index2+1]-s2[index2]
+ else:
+ # edge correction
+ nu2 = max(t_end-s2[N2-1], s2[N2-1]-s2[N2-2]) if N2 > 1 \
+ else t_end-s2[N2-1]
+ # compute the corresponding isi-distance
+ isi_values[index] = abs(nu1 - nu2) / \
+ max(nu1, nu2)
+ index += 1
+ # the last event is the interval end
+ if spike_events[index-1] == t_end:
+ index -= 1
+ else:
+ spike_events[index] = t_end
+ # use only the data added above
+ # could be less than original length due to equal spike times
+ return spike_events[:index + 1], isi_values[:index]
+
+
+############################################################
+# get_min_dist
+############################################################
+def get_min_dist(spike_time, spike_train, start_index, t_start, t_end):
+ """ Returns the minimal distance |spike_time - spike_train[i]|
+ with i>=start_index.
+ """
+ d = abs(spike_time - t_start)
+ if start_index < 0:
+ start_index = 0
+ while start_index < len(spike_train):
+ d_temp = abs(spike_time - spike_train[start_index])
+ if d_temp > d:
+ return d
+ else:
+ d = d_temp
+ start_index += 1
+ # finally, check the distance to end time
+ d_temp = abs(t_end - spike_time)
+ if d_temp > d:
+ return d
+ else:
+ return d_temp
+
+
+############################################################
+# spike_distance_python
+############################################################
+def spike_distance_python(spikes1, spikes2, t_start, t_end):
+ """ Computes the instantaneous spike-distance S_spike (t) of the two given
+ spike trains. The spike trains are expected to have auxiliary spikes at the
+ beginning and end of the interval. Use the function add_auxiliary_spikes to
+ add those spikes to the spike train.
+ Args:
+ - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes.
+ - t_start, t_end: edges of the spike train
+ Returns:
+ - PieceWiseLinFunc describing the spike-distance.
+ """
+
+ # shorter variables
+ t1 = spikes1
+ t2 = spikes2
+
+ N1 = len(t1)
+ N2 = len(t2)
+
+ spike_events = np.empty(N1+N2+2)
+
+ y_starts = np.empty(len(spike_events)-1)
+ y_ends = np.empty(len(spike_events)-1)
+
+ t_aux1 = np.zeros(2)
+ t_aux2 = np.zeros(2)
+ t_aux1[0] = min(t_start, t1[0]-(t1[1]-t1[0])) if N1 > 1 else t_start
+ t_aux1[1] = max(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) if N1 > 1 else t_end
+ t_aux2[0] = min(t_start, t2[0]-(t2[1]-t2[0])) if N2 > 1 else t_start
+ t_aux2[1] = max(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) if N2 > 1 else t_end
+ t_p1 = t_start if (t1[0] == t_start) else t_aux1[0]
+ t_p2 = t_start if (t2[0] == t_start) else t_aux2[0]
+
+ # print "t_aux1", t_aux1, ", t_aux2:", t_aux2
+
+ spike_events[0] = t_start
+ if t1[0] > t_start:
+ t_f1 = t1[0]
+ dt_f1 = get_min_dist(t_f1, t2, 0, t_aux2[0], t_aux2[1])
+ dt_p1 = dt_f1
+ isi1 = max(t_f1-t_start, t1[1]-t1[0]) if N1 > 1 else t_f1-t_start
+ # s1 = dt_p1*(t_f1-t_start)/isi1
+ s1 = dt_p1
+ index1 = -1
+ else:
+ # dt_p1 = t_start-t_p2
+ t_f1 = t1[1] if N1 > 1 else t_end
+ dt_p1 = get_min_dist(t_p1, t2, 0, t_aux2[0], t_aux2[1])
+ dt_f1 = get_min_dist(t_f1, t2, 0, t_aux2[0], t_aux2[1])
+ isi1 = t_f1-t1[0]
+ s1 = dt_p1
+ index1 = 0
+ if t2[0] > t_start:
+ # dt_p1 = t2[0]-t_start
+ t_f2 = t2[0]
+ dt_f2 = get_min_dist(t_f2, t1, 0, t_aux1[0], t_aux1[1])
+ dt_p2 = dt_f2
+ isi2 = max(t_f2-t_start, t2[1]-t2[0]) if N2 > 1 else t_f2-t_start
+ # s2 = dt_p2*(t_f2-t_start)/isi2
+ s2 = dt_p2
+ index2 = -1
+ else:
+ t_f2 = t2[1] if N2 > 1 else t_end
+ dt_p2 = get_min_dist(t_p2, t1, 0, t_aux1[0], t_aux1[1])
+ dt_f2 = get_min_dist(t_f2, t1, 0, t_aux1[0], t_aux1[1])
+ isi2 = t_f2-t2[0]
+ s2 = dt_p2
+ index2 = 0
+
+ y_starts[0] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2)
+ index = 1
+
+ while index1+index2 < N1+N2-2:
+ # print(index, index1, index2)
+ if (index1 < N1-1) and (t_f1 < t_f2 or index2 == N2-1):
+ index1 += 1
+ # first calculate the previous interval end value
+ s1 = dt_f1*(t_f1-t_p1) / isi1
+ # the previous time now was the following time before:
+ dt_p1 = dt_f1
+ t_p1 = t_f1 # t_p1 contains the current time point
+ # get the next time
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ else:
+ t_f1 = t_aux1[1]
+ spike_events[index] = t_p1
+ s2 = (dt_p2*(t_f2-t_p1) + dt_f2*(t_p1-t_p2)) / isi2
+ y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2)
+ # now the next interval start value
+ if index1 < N1-1:
+ dt_f1 = get_min_dist(t_f1, t2, index2, t_aux2[0], t_aux2[1])
+ isi1 = t_f1-t_p1
+ s1 = dt_p1
+ else:
+ dt_f1 = dt_p1
+ isi1 = max(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) if N1 > 1 \
+ else t_end-t1[N1-1]
+ # s1 needs adjustment due to change of isi1
+ # s1 = dt_p1*(t_end-t1[N1-1])/isi1
+ # Eero's correction: no adjustment
+ s1 = dt_p1
+ # s2 is the same as above, thus we can compute y2 immediately
+ y_starts[index] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2)
+ elif (index2 < N2-1) and (t_f1 > t_f2 or index1 == N1-1):
+ index2 += 1
+ # first calculate the previous interval end value
+ s2 = dt_f2*(t_f2-t_p2) / isi2
+ # the previous time now was the following time before:
+ dt_p2 = dt_f2
+ t_p2 = t_f2 # t_p1 contains the current time point
+ # get the next time
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ else:
+ t_f2 = t_aux2[1]
+ spike_events[index] = t_p2
+ s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1
+ y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2)
+ # now the next interval start value
+ if index2 < N2-1:
+ dt_f2 = get_min_dist(t_f2, t1, index1, t_aux1[0], t_aux1[1])
+ isi2 = t_f2-t_p2
+ s2 = dt_p2
+ else:
+ dt_f2 = dt_p2
+ isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \
+ else t_end-t2[N2-1]
+ # s2 needs adjustment due to change of isi2
+ # s2 = dt_p2*(t_end-t2[N2-1])/isi2
+ # Eero's adjustment: no correction
+ s2 = dt_p2
+ # s2 is the same as above, thus we can compute y2 immediately
+ y_starts[index] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2)
+ else: # t_f1 == t_f2 - generate only one event
+ index1 += 1
+ index2 += 1
+ t_p1 = t_f1
+ t_p2 = t_f2
+ dt_p1 = 0.0
+ dt_p2 = 0.0
+ spike_events[index] = t_f1
+ y_ends[index-1] = 0.0
+ y_starts[index] = 0.0
+ if index1 < N1-1:
+ t_f1 = t1[index1+1]
+ dt_f1 = get_min_dist(t_f1, t2, index2, t_aux2[0], t_aux2[1])
+ isi1 = t_f1 - t_p1
+ else:
+ t_f1 = t_aux1[1]
+ dt_f1 = dt_p1
+ isi1 = max(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) if N1 > 1 \
+ else t_end-t1[N1-1]
+ if index2 < N2-1:
+ t_f2 = t2[index2+1]
+ dt_f2 = get_min_dist(t_f2, t1, index1, t_aux1[0], t_aux1[1])
+ isi2 = t_f2 - t_p2
+ else:
+ t_f2 = t_aux2[1]
+ dt_f2 = dt_p2
+ isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \
+ else t_end-t2[N2-1]
+ index += 1
+
+ # the last event is the interval end
+ if spike_events[index-1] == t_end:
+ index -= 1
+ else:
+ spike_events[index] = t_end
+ s1 = dt_f1 # *(t_end-t1[N1-1])/isi1
+ s2 = dt_f2 # *(t_end-t2[N2-1])/isi2
+ y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2)
+
+ # use only the data added above
+ # could be less than original length due to equal spike times
+ return spike_events[:index+1], y_starts[:index], y_ends[:index]
+
+
+############################################################
+# cumulative_sync_python
+############################################################
+def cumulative_sync_python(spikes1, spikes2):
+
+ def get_tau(spikes1, spikes2, i, j):
+ return 0.5*min([spikes1[i]-spikes1[i-1], spikes1[i+1]-spikes1[i],
+ spikes2[j]-spikes2[j-1], spikes2[j+1]-spikes2[j]])
+ N1 = len(spikes1)
+ N2 = len(spikes2)
+ i = 0
+ j = 0
+ n = 0
+ st = np.zeros(N1 + N2 - 2)
+ c = np.zeros(N1 + N2 - 3)
+ c[0] = 0
+ st[0] = 0
+ while n < N1 + N2:
+ if spikes1[i+1] < spikes2[j+1]:
+ i += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j)
+ st[n] = spikes1[i]
+ if spikes1[i]-spikes2[j] > tau:
+ c[n] = c[n-1]
+ else:
+ c[n] = c[n-1]+1
+ elif spikes1[i+1] > spikes2[j+1]:
+ j += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j)
+ st[n] = spikes2[j]
+ if spikes2[j]-spikes1[i] > tau:
+ c[n] = c[n-1]
+ else:
+ c[n] = c[n-1]+1
+ else: # spikes1[i+1] = spikes2[j+1]
+ j += 1
+ i += 1
+ if i == N1-1 or j == N2-1:
+ break
+ n += 1
+ st[n] = spikes1[i]
+ c[n] = c[n-1]
+ n += 1
+ st[n] = spikes1[i]
+ c[n] = c[n-1]+1
+ c[0] = 0
+ st[0] = spikes1[0]
+ st[-1] = spikes1[-1]
+
+ return st, c
+
+
+def get_tau(spikes1, spikes2, i, j, max_tau, init_tau):
+ m = init_tau
+ if i < len(spikes1)-1 and i > -1:
+ m = min(m, spikes1[i+1]-spikes1[i])
+ if j < len(spikes2)-1 and j > -1:
+ m = min(m, spikes2[j+1]-spikes2[j])
+ if i > 0:
+ m = min(m, spikes1[i]-spikes1[i-1])
+ if j > 0:
+ m = min(m, spikes2[j]-spikes2[j-1])
+ m *= 0.5
+ if max_tau > 0.0:
+ m = min(m, max_tau)
+ return m
+
+
+############################################################
+# coincidence_python
+############################################################
+def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau):
+
+ N1 = len(spikes1)
+ N2 = len(spikes2)
+ i = -1
+ j = -1
+ n = 0
+ st = np.zeros(N1 + N2 + 2) # spike times
+ c = np.zeros(N1 + N2 + 2) # coincidences
+ mp = np.ones(N1 + N2 + 2) # multiplicity
+ while i + j < N1 + N2 - 2:
+ if (i < N1-1) and (j == N2-1 or spikes1[i+1] < spikes2[j+1]):
+ i += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau, t_end-t_start)
+ st[n] = spikes1[i]
+ if j > -1 and spikes1[i]-spikes2[j] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ c[n] = 1
+ c[n-1] = 1
+ elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]):
+ j += 1
+ n += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau, t_end-t_start)
+ st[n] = spikes2[j]
+ if i > -1 and spikes2[j]-spikes1[i] < tau:
+ # coincidence between the current spike and the previous spike
+ # both get marked with 1
+ c[n] = 1
+ c[n-1] = 1
+ else: # spikes1[i+1] = spikes2[j+1]
+ # advance in both spike trains
+ j += 1
+ i += 1
+ n += 1
+ # add only one event, but with coincidence 2 and multiplicity 2
+ st[n] = spikes1[i]
+ c[n] = 2
+ mp[n] = 2
+
+ st = st[:n+2]
+ c = c[:n+2]
+ mp = mp[:n+2]
+
+ st[0] = t_start
+ st[len(st)-1] = t_end
+ if N1 + N2 > 0:
+ c[0] = c[1]
+ c[len(c)-1] = c[len(c)-2]
+ mp[0] = mp[1]
+ mp[len(mp)-1] = mp[len(mp)-2]
+ else:
+ c[0] = 1
+ c[1] = 1
+
+ return st, c, mp
+
+
+############################################################
+# coincidence_single_profile_cython
+############################################################
+def coincidence_single_python(spikes1, spikes2, t_start, t_end, max_tau):
+
+ N1 = len(spikes1)
+ N2 = len(spikes2)
+ j = -1
+ c = np.zeros(N1) # coincidences
+ for i in range(N1):
+ while j < N2-1 and spikes2[j+1] < spikes1[i]:
+ # move forward until spikes2[j] is the last spike before spikes1[i]
+ # note that if spikes2[j] is after spikes1[i] we dont do anything
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau, t_end-t_start)
+ if j > -1 and abs(spikes1[i]-spikes2[j]) < tau:
+ # current spike in st1 is coincident
+ c[i] = 1
+ if j < N2-1 and (j < 0 or spikes2[j] < spikes1[i]):
+ # in case spikes2[j] is before spikes1[i] it has to be the first or
+ # the one right before (see above), hence we move one forward and
+ # also check the next spike
+ j += 1
+ tau = get_tau(spikes1, spikes2, i, j, max_tau, t_end-t_start)
+ if abs(spikes2[j]-spikes1[i]) < tau:
+ # current spike in st1 is coincident
+ c[i] = 1
+ return c
+
+
+############################################################
+# add_piece_wise_const_python
+############################################################
+def add_piece_wise_const_python(x1, y1, x2, y2):
+ x_new = np.empty(len(x1) + len(x2))
+ y_new = np.empty(len(x_new)-1)
+ x_new[0] = x1[0]
+ y_new[0] = y1[0] + y2[0]
+ index1 = 0
+ index2 = 0
+ index = 0
+ while (index1+1 < len(y1)) and (index2+1 < len(y2)):
+ index += 1
+ # print(index1+1, x1[index1+1], y1[index1+1], x_new[index])
+ if x1[index1+1] < x2[index2+1]:
+ index1 += 1
+ x_new[index] = x1[index1]
+ elif x1[index1+1] > x2[index2+1]:
+ index2 += 1
+ x_new[index] = x2[index2]
+ else: # x1[index1+1] == x2[index2+1]:
+ index1 += 1
+ index2 += 1
+ x_new[index] = x1[index1]
+ y_new[index] = y1[index1] + y2[index2]
+ # one array reached the end -> copy the contents of the other to the end
+ if index1+1 < len(y1):
+ x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:]
+ y_new[index+1:index+1+len(y1)-index1-1] = y1[index1+1:] + y2[-1]
+ index += len(x1)-index1-2
+ elif index2+1 < len(y2):
+ x_new[index+1:index+1+len(x2)-index2-1] = x2[index2+1:]
+ y_new[index+1:index+1+len(y2)-index2-1] = y2[index2+1:] + y1[-1]
+ index += len(x2)-index2-2
+ else: # both arrays reached the end simultaneously
+ # only the last x-value missing
+ x_new[index+1] = x1[-1]
+ # the last value is again the end of the interval
+ # x_new[index+1] = x1[-1]
+ # only use the data that was actually filled
+
+ return x_new[:index+2], y_new[:index+1]
+
+
+############################################################
+# add_piece_lin_const_python
+############################################################
+def add_piece_wise_lin_python(x1, y11, y12, x2, y21, y22):
+ x_new = np.empty(len(x1) + len(x2))
+ y1_new = np.empty(len(x_new)-1)
+ y2_new = np.empty_like(y1_new)
+ x_new[0] = x1[0]
+ y1_new[0] = y11[0] + y21[0]
+ index1 = 0 # index for self
+ index2 = 0 # index for f
+ index = 0 # index for new
+ while (index1+1 < len(y11)) and (index2+1 < len(y21)):
+ # print(index1+1, x1[index1+1], self.y[index1+1], x_new[index])
+ if x1[index1+1] < x2[index2+1]:
+ # first compute the end value of the previous interval
+ # linear interpolation of the interval
+ y = y21[index2] + (y22[index2]-y21[index2]) * \
+ (x1[index1+1]-x2[index2]) / (x2[index2+1]-x2[index2])
+ y2_new[index] = y12[index1] + y
+ index1 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ # and the starting value for the next interval
+ y1_new[index] = y11[index1] + y
+ elif x1[index1+1] > x2[index2+1]:
+ # first compute the end value of the previous interval
+ # linear interpolation of the interval
+ y = y11[index1] + (y12[index1]-y11[index1]) * \
+ (x2[index2+1]-x1[index1]) / \
+ (x1[index1+1]-x1[index1])
+ y2_new[index] = y22[index2] + y
+ index2 += 1
+ index += 1
+ x_new[index] = x2[index2]
+ # and the starting value for the next interval
+ y1_new[index] = y21[index2] + y
+ else: # x1[index1+1] == x2[index2+1]:
+ y2_new[index] = y12[index1] + y22[index2]
+ index1 += 1
+ index2 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ y1_new[index] = y11[index1] + y21[index2]
+ # one array reached the end -> copy the contents of the other to the end
+ if index1+1 < len(y11):
+ # compute the linear interpolations values
+ y = y21[index2] + (y22[index2]-y21[index2]) * \
+ (x1[index1+1:-1]-x2[index2]) / (x2[index2+1]-x2[index2])
+ x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:]
+ y1_new[index+1:index+1+len(y11)-index1-1] = y11[index1+1:]+y
+ y2_new[index:index+len(y12)-index1-1] = y12[index1:-1] + y
+ index += len(x1)-index1-2
+ elif index2+1 < len(y21):
+ # compute the linear interpolations values
+ y = y11[index1] + (y12[index1]-y11[index1]) * \
+ (x2[index2+1:-1]-x1[index1]) / \
+ (x1[index1+1]-x1[index1])
+ x_new[index+1:index+1+len(x2)-index2-1] = x2[index2+1:]
+ y1_new[index+1:index+1+len(y21)-index2-1] = y21[index2+1:] + y
+ y2_new[index:index+len(y22)-index2-1] = y22[index2:-1] + y
+ index += len(x2)-index2-2
+ else: # both arrays reached the end simultaneously
+ # only the last x-value missing
+ x_new[index+1] = x1[-1]
+ # finally, the end value for the last interval
+ y2_new[index] = y12[-1]+y22[-1]
+ # only use the data that was actually filled
+ return x_new[:index+2], y1_new[:index+1], y2_new[:index+1]
+
+
+############################################################
+# add_discrete_function_python
+############################################################
+def add_discrete_function_python(x1, y1, mp1, x2, y2, mp2):
+
+ x_new = np.empty(len(x1) + len(x2))
+ y_new = np.empty_like(x_new)
+ mp_new = np.empty_like(x_new)
+ x_new[0] = x1[0]
+ index1 = 0
+ index2 = 0
+ index = 0
+ N1 = len(x1)-1
+ N2 = len(x2)-1
+ while (index1+1 < N1) and (index2+1 < N2):
+ if x1[index1+1] < x2[index2+1]:
+ index1 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ y_new[index] = y1[index1]
+ mp_new[index] = mp1[index1]
+ elif x1[index1+1] > x2[index2+1]:
+ index2 += 1
+ index += 1
+ x_new[index] = x2[index2]
+ y_new[index] = y2[index2]
+ mp_new[index] = mp2[index2]
+ else: # x1[index1+1] == x2[index2+1]
+ index1 += 1
+ index2 += 1
+ index += 1
+ x_new[index] = x1[index1]
+ y_new[index] = y1[index1] + y2[index2]
+ mp_new[index] = mp1[index1] + mp2[index2]
+ # one array reached the end -> copy the contents of the other to the end
+ if index1+1 < N1:
+ x_new[index+1:index+1+N1-index1] = x1[index1+1:]
+ y_new[index+1:index+1+N1-index1] = y1[index1+1:]
+ mp_new[index+1:index+1+N1-index1] = mp1[index1+1:]
+ index += N1-index1
+ elif index2+1 < N2:
+ x_new[index+1:index+1+N2-index2] = x2[index2+1:]
+ y_new[index+1:index+1+N2-index2] = y2[index2+1:]
+ mp_new[index+1:index+1+N2-index2] = mp2[index2+1:]
+ index += N2-index2
+ else: # both arrays reached the end simultaneously
+ x_new[index+1] = x1[-1]
+ y_new[index+1] = y1[-1] + y2[-1]
+ mp_new[index+1] = mp1[-1] + mp2[-1]
+ index += 1
+
+ y_new[0] = y_new[1]
+ mp_new[0] = mp_new[1]
+
+ # the last value is again the end of the interval
+ # only use the data that was actually filled
+ return x_new[:index+1], y_new[:index+1], mp_new[:index+1]