From e08c05f9bf121e272aadce4fc79e9499e662111e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 9 Sep 2014 16:55:13 +0200 Subject: Initial commit with readme --- Readme | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Readme diff --git a/Readme b/Readme new file mode 100644 index 0000000..39bd308 --- /dev/null +++ b/Readme @@ -0,0 +1,3 @@ +Python Codes for Measuring Spike Synchrony + +Mario Mulansky (2014) -- cgit v1.2.3 From f9529c78538882879a07cb67e342eade8d2153ab Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 15 Sep 2014 17:01:13 +0200 Subject: isi distance and basic example --- examples/SPIKY_testdata.txt | 40 ++++++++++++++++++++++++ examples/test_data.py | 28 +++++++++++++++++ pyspike/__init__.py | 5 +++ pyspike/distances.py | 75 +++++++++++++++++++++++++++++++++++++++++++++ pyspike/function.py | 60 ++++++++++++++++++++++++++++++++++++ pyspike/spikes.py | 18 +++++++++++ 6 files changed, 226 insertions(+) create mode 100755 examples/SPIKY_testdata.txt create mode 100644 examples/test_data.py create mode 100644 pyspike/__init__.py create mode 100644 pyspike/distances.py create mode 100644 pyspike/function.py create mode 100644 pyspike/spikes.py diff --git a/examples/SPIKY_testdata.txt b/examples/SPIKY_testdata.txt new file mode 100755 index 0000000..8fa3fcf --- /dev/null +++ b/examples/SPIKY_testdata.txt @@ -0,0 +1,40 @@ +64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 +65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 +69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 +59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 +59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 +66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 +66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 +63.764 318.45 697.48 936.97 1059.3 1325 1687.9 1944.7 2132.5 2377.1 2713.1 2976.6 3196.8 3442.6 3741.6 3998.3 +63.906 314.79 693.26 937.12 1065.9 1315.8 1584.3 1821.5 2126.3 2396.8 2709.1 2967 3197.4 3444 3732.8 3849.5 +69.493 316.62 689.81 943.62 1071.9 1296.3 1654.8 1931.9 2127.5 2390.6 2708.9 2950.4 3194.8 3445.2 3670.1 3903.3 +61.789 317.53 555.82 813.15 1198.7 1448.7 1686.7 1943.5 2060.7 2311.4 2658.2 2900.2 3167.4 3418.2 3617.3 3771 +64.098 309.86 567.27 813.91 1182 1464.3 1576.8 1822.5 2063.1 2311.7 2655.8 2911.7 3168.3 3418.2 3586.4 3999.7 +68.59 315.5 559.52 806.23 1182.5 1441.1 1567.2 1804.8 2074.9 2315.8 2655.1 2913.2 3165.9 3419.5 3648.1 3884.4 +66.507 314.42 556.42 814.83 1182.5 1440.3 1701.3 1911.1 2069.7 2319.3 2662.3 2903.2 3167.4 3418.5 3545 3893.9 +72.744 318.45 554.4 819.64 1186.9 1449.7 1676 1957.4 2051.4 2302.8 2657.8 2916.2 3169.4 3416.7 3570.4 3884.8 +64.779 324.42 560.56 828.99 1174.8 1439.9 1563.7 1790.6 2067.7 2287.6 2657.4 2905.2 3139.2 3389.1 3507.8 3807.5 +64.852 316.63 568.89 815.61 1198.3 1454.1 1710.6 1933.9 2091.5 2309.6 2660.9 2907.5 3137.2 3389.3 3617.2 +63.089 314.52 553.8 827.06 1183.9 1457.6 1558.9 1808.3 2064.5 2337.1 2653.6 2897 3143.7 3385.7 3668.7 3803.8 +62.23 315.16 564.35 812.15 1199.6 1448.9 1562.7 1839.1 2069.7 2308.9 2649.6 2919.7 3141 3389.9 3723.6 3882.2 +69.662 311.93 564.91 805.25 1209.7 1451.4 1691.9 1932.1 2044.2 2329.4 2657.1 2908.5 3142.8 3390.5 3597.3 3991.1 +183.42 431.34 562.41 809.57 1086.3 1308.9 1555.9 1831.3 2057 2326.9 2591.3 2831.4 3113.9 3367.9 3555.3 3956 +188.49 442.39 572.4 810.76 1065 1326.7 1564.3 1803.4 2060.4 2322.4 2607.2 2824.1 3110.2 3363.9 3644.1 3819.6 +177 437.76 569.82 819.66 1064.1 1309.2 1685.7 1957.5 2066.9 2313.8 2593.2 2847 3116.8 3364.5 3727.3 3881.6 +193.9 441.93 586.9 804.98 1062.5 1312.4 1542.4 1793.1 2073.9 2314.7 2587.8 2845.9 3112.4 3359.8 +193.01 440.26 555.64 814.08 1056.3 1315 1689.9 1961.4 2049.1 2305 2593.9 2847.5 3110.6 3361.1 3711.6 3914.7 +194.71 437.57 566.18 806.73 1069.2 1314.6 1682.7 1942.2 2061.8 2304.6 2607.6 2841.7 3082.9 3330.3 3679.7 3848.2 +184.88 441.22 570.92 794.35 1063.7 1309.9 1678.7 1930 2058 2321.3 2606.7 2845 3084.8 3337.3 3640 3952.1 +189.66 443.59 560.67 816.89 1070.4 1303.4 1550.1 1815.5 2057.6 2323.7 2587.1 2843.5 3086.6 3333.6 3618.2 3815.4 +190.41 440.77 568.96 808.56 1073.8 1322.1 1686.5 1952.8 2068.7 2335.7 2595.7 2845.4 3086 3333.5 3635.6 3939.3 +181.16 440.67 577.54 823.52 1052.5 1322.3 1578.4 1822.2 2079.4 2309.1 2596.9 2851.9 3083.5 3335.1 3531.2 3770.6 +181.09 434.97 687.15 943.33 1192.9 1444 1699.4 1942 2194.6 2445.9 2549.4 2785.1 3056.5 3308.2 3620.5 3932.7 +186.7 446.53 688.18 942.86 1186.1 1441.9 1688.1 1922.2 2196.6 2455.3 2534.8 2776.5 3060.3 3309.4 3514.1 3808.6 +196.76 446 681.26 948.27 1195.8 1433.1 1699 1933 2201.2 2461.4 2547.4 2777.8 3055.7 3307.1 3590.6 3952.8 +200.68 427.11 695.67 946.42 1178.6 1440.1 1538.4 1809 2199.8 2432.5 2531.6 2793.2 3056.6 3308.6 3510.6 3928.1 +190.83 429.57 698.73 931.16 1190.6 1428.9 1698.3 1935 2176.8 2424.7 2530.5 2766.9 3062 3309.7 3689.8 +181.47 441.93 682.32 943.01 1190.1 1459.1 1570.6 1819.6 2189.8 2437.9 2543.3 2782.8 3025.9 3280.2 3581 3855.9 +191.38 435.69 702.76 935.62 1188.3 1438.3 1564.2 1823.9 2191.3 2444.9 2531.9 2782.4 3030.7 3275.7 3677.7 3829.2 +191.97 433.85 686.29 932.65 1183.1 1432.7 1563.9 1826.5 2214.1 2436.8 2529.8 2778.3 3028.3 3281.8 3582 3863.4 +189.51 453.21 691.3 940.86 1180.1 1430.1 1567.1 1835 2199 2448.2 2526.7 2773.8 3030.5 3280.1 3576.2 3893.6 +190.88 435.48 692.66 940.51 1189.5 1448.9 1575.1 1824.2 2190.8 2425.9 2530.6 2783.3 3033.3 3279.5 3733 3838.9 diff --git a/examples/test_data.py b/examples/test_data.py new file mode 100644 index 0000000..bddcb15 --- /dev/null +++ b/examples/test_data.py @@ -0,0 +1,28 @@ +# compute the isi distance of some test data +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt + +import pyspike as spk + +# first load the data +spike_trains = [] +spike_file = open("SPIKY_testdata.txt", 'r') +for line in spike_file: + spike_trains.append(spk.spike_train_from_string(line)) + +# plot the spike time +for (i,spikes) in enumerate(spike_trains): + plt.plot(spikes, i*np.ones_like(spikes), 'o') + +f = spk.isi_distance(spike_trains[0], spike_trains[1], 4000) +x, y = f.get_plottable_data() + +plt.figure() +plt.plot(x, y, '-k') + +print("Average: %.8f" % f.avrg()) +print("Absolute average: %.8f" % f.abs_avrg()) + +plt.show() diff --git a/pyspike/__init__.py b/pyspike/__init__.py new file mode 100644 index 0000000..6651eb5 --- /dev/null +++ b/pyspike/__init__.py @@ -0,0 +1,5 @@ +__all__ = ["function", "distances", "spikes"] + +from function import PieceWiseConstFunc +from distances import isi_distance +from spikes import spike_train_from_string diff --git a/pyspike/distances.py b/pyspike/distances.py new file mode 100644 index 0000000..d9790dc --- /dev/null +++ b/pyspike/distances.py @@ -0,0 +1,75 @@ +""" distances.py + +Module containing several function to compute spike distances + +Copyright 2014, Mario Mulansky +""" + +import numpy as np + +from pyspike import PieceWiseConstFunc + +def spike_train_from_string(s, sep=' '): + """ Converts a string of times into a SpikeTrain object. + Params: + - s: the string with (ordered) spike times + - sep: The separator between the time numbers. + Returns: + - array of spike times + """ + return np.fromstring(s, sep=sep) + +def isi_distance(spikes1, spikes2, T_end, T_start=0.0): + """ Computes the instantaneous isi-distance S_isi (t) of the two given spike + trains. + Args: + - spikes1, spikes2: ordered arrays of spike times. + - T_end: end time of the observation interval. + - T_start: begin of the observation interval (default=0.0). + Returns: + - PieceWiseConstFunc describing the isi-distance. + """ + # add spikes at the beginning and end of the interval + s1 = np.empty(len(spikes1)+2) + s1[0] = T_start + s1[-1] = T_end + s1[1:-1] = spikes1 + s2 = np.empty(len(spikes2)+2) + s2[0] = T_start + s2[-1] = T_end + s2[1:-1] = spikes2 + + # compute the interspike interval + nu1 = s1[1:]-s1[:-1] + nu2 = s2[1:]-s2[:-1] + + # compute the isi-distance + spike_events = np.empty(len(nu1)+len(nu2)) + spike_events[0] = T_start + spike_events[-1] = T_end + # the values have one entry less - the number of intervals between events + isi_values = np.empty(len(spike_events)-1) + # add the distance of the first events + # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ + # else 1.0 - nu2[0]/nu1[0] + isi_values[0] = (nu1[0]-nu2[0])/max(nu1[0],nu2[0]) + index1 = 0 + index2 = 0 + index = 1 + while True: + # check which spike is next - from s1 or s2 + if s1[index1+1] <= s2[index2+1]: + index1 += 1 + if index1 >= len(nu1): + break + spike_events[index] = s1[index1] + else: + index2 += 1 + if index2 >= len(nu2): + break + spike_events[index] = s2[index2] + # compute the corresponding isi-distance + isi_values[index] = (nu1[index1]-nu2[index2]) / \ + max(nu1[index1], nu2[index2]) + index += 1 + return PieceWiseConstFunc(spike_events, isi_values) diff --git a/pyspike/function.py b/pyspike/function.py new file mode 100644 index 0000000..c1de9cb --- /dev/null +++ b/pyspike/function.py @@ -0,0 +1,60 @@ +""" function.py + +Module containing classes representing piece-wise constant and piece-wise linear +functions. + +Copyright 2014, Mario Mulansky + +""" +from __future__ import print_function + +import numpy as np + +class PieceWiseConstFunc: + """ A class representing a piece-wise constant function. """ + + def __init__(self, x, y): + """ Constructs the piece-wise const function. + Params: + - x: array of length N+1 defining the edges of the intervals of the pwc + function. + - y: array of length N defining the function values at the intervals. + """ + self.x = x + self.y = y + + def get_plottable_data(self): + """ Returns two arrays containing x- and y-coordinates for immeditate + plotting of the piece-wise function. + """ + + x_plot = np.empty(2*len(self.x)-2) + x_plot[0] = self.x[0] + x_plot[1::2] = self.x[1:] + x_plot[2::2] = self.x[1:-1] + y_plot = np.empty(2*len(self.y)) + y_plot[::2] = self.y + y_plot[1::2] = self.y + + return x_plot, y_plot + + def avrg(self): + """ Computes the average of the piece-wise const function: + a = 1/T int f(x) dx where T is the length of the interval. + Returns: + - the average a. + """ + print(self.x) + print(self.y) + return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ + (self.x[-1]-self.x[0]) + + def abs_avrg(self): + """ Computes the average of the abs value of the piece-wise const + function: + a = 1/T int |f(x)| dx where T is the length of the interval. + Returns: + - the average a. + """ + return np.sum((self.x[1:]-self.x[:-1]) * np.abs(self.y)) / \ + (self.x[-1]-self.x[0]) diff --git a/pyspike/spikes.py b/pyspike/spikes.py new file mode 100644 index 0000000..42b6501 --- /dev/null +++ b/pyspike/spikes.py @@ -0,0 +1,18 @@ +""" spikes.py + +Module containing several function to load and transform spike trains + +Copyright 2014, Mario Mulansky +""" + +import numpy as np + +def spike_train_from_string(s, sep=' '): + """ Converts a string of times into a SpikeTrain object. + Params: + - s: the string with (ordered) spike times + - sep: The separator between the time numbers. + Returns: + - array of spike times + """ + return np.fromstring(s, sep=sep) -- cgit v1.2.3 From 5ea0fc218bb3bb30b1c40dd20e2e35a8bd11151c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 15 Sep 2014 17:31:55 +0200 Subject: +merge_spike_trains --- examples/test_merge.py | 23 +++++++++++++++++++++++ pyspike/__init__.py | 2 +- pyspike/distances.py | 10 ---------- pyspike/spikes.py | 26 +++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 examples/test_merge.py diff --git a/examples/test_merge.py b/examples/test_merge.py new file mode 100644 index 0000000..1186062 --- /dev/null +++ b/examples/test_merge.py @@ -0,0 +1,23 @@ +# compute the isi distance of some test data +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt + +import pyspike as spk + +# first load the data +spike_trains = [] +spike_file = open("SPIKY_testdata.txt", 'r') +for line in spike_file: + spike_trains.append(spk.spike_train_from_string(line)) + +spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) + +print(spikes) + +plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') +plt.plot(spike_trains[1], np.ones_like(spike_trains[1]), 'x') +plt.plot(spikes, 2*np.ones_like(spikes), 'o') + +plt.show() diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 6651eb5..6895bd8 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -2,4 +2,4 @@ __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc from distances import isi_distance -from spikes import spike_train_from_string +from spikes import spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index d9790dc..7044a52 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -9,16 +9,6 @@ import numpy as np from pyspike import PieceWiseConstFunc -def spike_train_from_string(s, sep=' '): - """ Converts a string of times into a SpikeTrain object. - Params: - - s: the string with (ordered) spike times - - sep: The separator between the time numbers. - Returns: - - array of spike times - """ - return np.fromstring(s, sep=sep) - def isi_distance(spikes1, spikes2, T_end, T_start=0.0): """ Computes the instantaneous isi-distance S_isi (t) of the two given spike trains. diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 42b6501..66ef554 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -8,7 +8,7 @@ Copyright 2014, Mario Mulansky import numpy as np def spike_train_from_string(s, sep=' '): - """ Converts a string of times into a SpikeTrain object. + """ Converts a string of times into an array of spike times. Params: - s: the string with (ordered) spike times - sep: The separator between the time numbers. @@ -16,3 +16,27 @@ def spike_train_from_string(s, sep=' '): - array of spike times """ return np.fromstring(s, sep=sep) + + +def merge_spike_trains( spike_trains ): + """ Merges a number of spike trains into a single spike train. + Params: + - spike_trains: list of arrays of spike times + Returns: + - array with the merged spike times + """ + # get the lengths of the spike trains + lens = np.array([len(st) for st in spike_trains]) + merged_spikes = np.empty(np.sum(lens)) + index = 0 + indices = np.zeros_like(lens) + vals = [spike_trains[i][indices[i]] for i in xrange(len(indices))] + while len(indices) > 0: + i = np.argmin(vals) + merged_spikes[index] = vals[i] + index += 1 + indices[i] += 1 + if indices[i] >= lens[i]: + indices = np.delete(indices, i) + vals = [spike_trains[i][indices[i]] for i in xrange(len(indices))] + return merged_spikes -- cgit v1.2.3 From eb076dcd9d76ed3b848c78fb067c1ad6a1d6da23 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 16 Sep 2014 15:07:10 +0200 Subject: added merge spikes test --- pyspike/distances.py | 2 +- pyspike/spikes.py | 28 +++++++++++++++------------ test/SPIKY_testdata.txt | 40 ++++++++++++++++++++++++++++++++++++++ test/test_merge_spikes.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 13 deletions(-) create mode 100755 test/SPIKY_testdata.txt create mode 100644 test/test_merge_spikes.py diff --git a/pyspike/distances.py b/pyspike/distances.py index 7044a52..f4989c8 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -1,6 +1,6 @@ """ distances.py -Module containing several function to compute spike distances +Module containing several functions to compute spike distances Copyright 2014, Mario Mulansky """ diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 66ef554..6b2eea3 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -18,7 +18,7 @@ def spike_train_from_string(s, sep=' '): return np.fromstring(s, sep=sep) -def merge_spike_trains( spike_trains ): +def merge_spike_trains(spike_trains): """ Merges a number of spike trains into a single spike train. Params: - spike_trains: list of arrays of spike times @@ -28,15 +28,19 @@ def merge_spike_trains( spike_trains ): # get the lengths of the spike trains lens = np.array([len(st) for st in spike_trains]) merged_spikes = np.empty(np.sum(lens)) - index = 0 - indices = np.zeros_like(lens) - vals = [spike_trains[i][indices[i]] for i in xrange(len(indices))] - while len(indices) > 0: - i = np.argmin(vals) - merged_spikes[index] = vals[i] - index += 1 - indices[i] += 1 - if indices[i] >= lens[i]: - indices = np.delete(indices, i) - vals = [spike_trains[i][indices[i]] for i in xrange(len(indices))] + index = 0 # the index for merged_spikes + indices = np.zeros_like(lens) # indices of the spike trains + index_list = np.arange(len(indices)) # indices of indices of spike trains + # that have not yet reached the end + # list of the possible events in the spike trains + vals = [spike_trains[i][indices[i]] for i in index_list] + while len(index_list) > 0: + i = np.argmin(vals) # the next spike is the minimum + merged_spikes[index] = vals[i] # put it to the merged spike train + i = index_list[i] + index += 1 # next index of merged spike train + indices[i] += 1 # next index for the chosen spike train + if indices[i] >= lens[i]: # remove spike train index if ended + index_list = index_list[index_list != i] + vals = [spike_trains[i][indices[i]] for i in index_list] return merged_spikes diff --git a/test/SPIKY_testdata.txt b/test/SPIKY_testdata.txt new file mode 100755 index 0000000..8fa3fcf --- /dev/null +++ b/test/SPIKY_testdata.txt @@ -0,0 +1,40 @@ +64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 +65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 +69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 +59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 +59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 +66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 +66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 +63.764 318.45 697.48 936.97 1059.3 1325 1687.9 1944.7 2132.5 2377.1 2713.1 2976.6 3196.8 3442.6 3741.6 3998.3 +63.906 314.79 693.26 937.12 1065.9 1315.8 1584.3 1821.5 2126.3 2396.8 2709.1 2967 3197.4 3444 3732.8 3849.5 +69.493 316.62 689.81 943.62 1071.9 1296.3 1654.8 1931.9 2127.5 2390.6 2708.9 2950.4 3194.8 3445.2 3670.1 3903.3 +61.789 317.53 555.82 813.15 1198.7 1448.7 1686.7 1943.5 2060.7 2311.4 2658.2 2900.2 3167.4 3418.2 3617.3 3771 +64.098 309.86 567.27 813.91 1182 1464.3 1576.8 1822.5 2063.1 2311.7 2655.8 2911.7 3168.3 3418.2 3586.4 3999.7 +68.59 315.5 559.52 806.23 1182.5 1441.1 1567.2 1804.8 2074.9 2315.8 2655.1 2913.2 3165.9 3419.5 3648.1 3884.4 +66.507 314.42 556.42 814.83 1182.5 1440.3 1701.3 1911.1 2069.7 2319.3 2662.3 2903.2 3167.4 3418.5 3545 3893.9 +72.744 318.45 554.4 819.64 1186.9 1449.7 1676 1957.4 2051.4 2302.8 2657.8 2916.2 3169.4 3416.7 3570.4 3884.8 +64.779 324.42 560.56 828.99 1174.8 1439.9 1563.7 1790.6 2067.7 2287.6 2657.4 2905.2 3139.2 3389.1 3507.8 3807.5 +64.852 316.63 568.89 815.61 1198.3 1454.1 1710.6 1933.9 2091.5 2309.6 2660.9 2907.5 3137.2 3389.3 3617.2 +63.089 314.52 553.8 827.06 1183.9 1457.6 1558.9 1808.3 2064.5 2337.1 2653.6 2897 3143.7 3385.7 3668.7 3803.8 +62.23 315.16 564.35 812.15 1199.6 1448.9 1562.7 1839.1 2069.7 2308.9 2649.6 2919.7 3141 3389.9 3723.6 3882.2 +69.662 311.93 564.91 805.25 1209.7 1451.4 1691.9 1932.1 2044.2 2329.4 2657.1 2908.5 3142.8 3390.5 3597.3 3991.1 +183.42 431.34 562.41 809.57 1086.3 1308.9 1555.9 1831.3 2057 2326.9 2591.3 2831.4 3113.9 3367.9 3555.3 3956 +188.49 442.39 572.4 810.76 1065 1326.7 1564.3 1803.4 2060.4 2322.4 2607.2 2824.1 3110.2 3363.9 3644.1 3819.6 +177 437.76 569.82 819.66 1064.1 1309.2 1685.7 1957.5 2066.9 2313.8 2593.2 2847 3116.8 3364.5 3727.3 3881.6 +193.9 441.93 586.9 804.98 1062.5 1312.4 1542.4 1793.1 2073.9 2314.7 2587.8 2845.9 3112.4 3359.8 +193.01 440.26 555.64 814.08 1056.3 1315 1689.9 1961.4 2049.1 2305 2593.9 2847.5 3110.6 3361.1 3711.6 3914.7 +194.71 437.57 566.18 806.73 1069.2 1314.6 1682.7 1942.2 2061.8 2304.6 2607.6 2841.7 3082.9 3330.3 3679.7 3848.2 +184.88 441.22 570.92 794.35 1063.7 1309.9 1678.7 1930 2058 2321.3 2606.7 2845 3084.8 3337.3 3640 3952.1 +189.66 443.59 560.67 816.89 1070.4 1303.4 1550.1 1815.5 2057.6 2323.7 2587.1 2843.5 3086.6 3333.6 3618.2 3815.4 +190.41 440.77 568.96 808.56 1073.8 1322.1 1686.5 1952.8 2068.7 2335.7 2595.7 2845.4 3086 3333.5 3635.6 3939.3 +181.16 440.67 577.54 823.52 1052.5 1322.3 1578.4 1822.2 2079.4 2309.1 2596.9 2851.9 3083.5 3335.1 3531.2 3770.6 +181.09 434.97 687.15 943.33 1192.9 1444 1699.4 1942 2194.6 2445.9 2549.4 2785.1 3056.5 3308.2 3620.5 3932.7 +186.7 446.53 688.18 942.86 1186.1 1441.9 1688.1 1922.2 2196.6 2455.3 2534.8 2776.5 3060.3 3309.4 3514.1 3808.6 +196.76 446 681.26 948.27 1195.8 1433.1 1699 1933 2201.2 2461.4 2547.4 2777.8 3055.7 3307.1 3590.6 3952.8 +200.68 427.11 695.67 946.42 1178.6 1440.1 1538.4 1809 2199.8 2432.5 2531.6 2793.2 3056.6 3308.6 3510.6 3928.1 +190.83 429.57 698.73 931.16 1190.6 1428.9 1698.3 1935 2176.8 2424.7 2530.5 2766.9 3062 3309.7 3689.8 +181.47 441.93 682.32 943.01 1190.1 1459.1 1570.6 1819.6 2189.8 2437.9 2543.3 2782.8 3025.9 3280.2 3581 3855.9 +191.38 435.69 702.76 935.62 1188.3 1438.3 1564.2 1823.9 2191.3 2444.9 2531.9 2782.4 3030.7 3275.7 3677.7 3829.2 +191.97 433.85 686.29 932.65 1183.1 1432.7 1563.9 1826.5 2214.1 2436.8 2529.8 2778.3 3028.3 3281.8 3582 3863.4 +189.51 453.21 691.3 940.86 1180.1 1430.1 1567.1 1835 2199 2448.2 2526.7 2773.8 3030.5 3280.1 3576.2 3893.6 +190.88 435.48 692.66 940.51 1189.5 1448.9 1575.1 1824.2 2190.8 2425.9 2530.6 2783.3 3033.3 3279.5 3733 3838.9 diff --git a/test/test_merge_spikes.py b/test/test_merge_spikes.py new file mode 100644 index 0000000..3162700 --- /dev/null +++ b/test/test_merge_spikes.py @@ -0,0 +1,49 @@ +""" test_merge_spikes.py + +Tests merging spikes + +Copyright 2014, Mario Mulansky +""" +from __future__ import print_function +import numpy as np + +import pyspike as spk + +def check_merged_spikes( merged_spikes, spike_trains ): + # create a flat array with all spike events + all_spikes = np.array([]) + for spike_train in spike_trains: + all_spikes = np.append(all_spikes, spike_train) + indices = np.zeros_like(all_spikes, dtype='bool') + # check if we find all the spike events in the original spike trains + for x in merged_spikes: + i = np.where(all_spikes == x)[0][0] # the first axis and the first entry + # change to something impossible so we dont find this event again + all_spikes[i] = -1.0 + indices[i] = True + assert( indices.all() ) + +def test_merge_spike_trains(): + + # first load the data + spike_trains = [] + spike_file = open("SPIKY_testdata.txt", 'r') + for line in spike_file: + spike_trains.append(spk.spike_train_from_string(line)) + + spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) + # test if result is sorted + assert((spikes == np.sort(spikes)).all()) + # check merging + check_merged_spikes( spikes, [spike_trains[0], spike_trains[1]] ) + + spikes = spk.merge_spike_trains(spike_trains) + # test if result is sorted + assert((spikes == np.sort(spikes)).all()) + # check merging + check_merged_spikes( spikes, spike_trains ) + + +if __name__ == "main": + test_merge_spike_trains() + -- cgit v1.2.3 From b20d416c4765b2280526c633ca62f43677b1d26a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 18 Sep 2014 17:09:58 +0200 Subject: add spike-distance, PWL function (probably buggy) --- examples/test_data.py | 12 +++++-- pyspike/__init__.py | 4 +-- pyspike/distances.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++++-- pyspike/function.py | 33 +++++++++++++++-- 4 files changed, 139 insertions(+), 9 deletions(-) diff --git a/examples/test_data.py b/examples/test_data.py index bddcb15..94e7d51 100644 --- a/examples/test_data.py +++ b/examples/test_data.py @@ -16,13 +16,21 @@ for line in spike_file: for (i,spikes) in enumerate(spike_trains): plt.plot(spikes, i*np.ones_like(spikes), 'o') -f = spk.isi_distance(spike_trains[0], spike_trains[1], 4000) +f = spk.isi_distance(spike_trains[0], spike_trains[10], 4000) x, y = f.get_plottable_data() plt.figure() -plt.plot(x, y, '-k') +plt.plot(x, np.abs(y), '--k') print("Average: %.8f" % f.avrg()) print("Absolute average: %.8f" % f.abs_avrg()) + +f = spk.spike_distance(spike_trains[0], spike_trains[10], 4000) +x, y = f.get_plottable_data() +print(x) +print(y) +#plt.figure() +plt.plot(x, y, '-b') + plt.show() diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 6895bd8..a5f146a 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,5 +1,5 @@ __all__ = ["function", "distances", "spikes"] -from function import PieceWiseConstFunc -from distances import isi_distance +from function import PieceWiseConstFunc, PieceWiseLinFunc +from distances import isi_distance, spike_distance from spikes import spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index f4989c8..2ea80e7 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -7,11 +7,11 @@ Copyright 2014, Mario Mulansky import numpy as np -from pyspike import PieceWiseConstFunc +from pyspike import PieceWiseConstFunc, PieceWiseLinFunc def isi_distance(spikes1, spikes2, T_end, T_start=0.0): - """ Computes the instantaneous isi-distance S_isi (t) of the two given spike - trains. + """ Computes the instantaneous isi-distance S_isi (t) of the two given + spike trains. Args: - spikes1, spikes2: ordered arrays of spike times. - T_end: end time of the observation interval. @@ -50,6 +50,7 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): # check which spike is next - from s1 or s2 if s1[index1+1] <= s2[index2+1]: index1 += 1 + # break condition relies on existence of spikes at T_end if index1 >= len(nu1): break spike_events[index] = s1[index1] @@ -63,3 +64,95 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): max(nu1[index1], nu2[index2]) index += 1 return PieceWiseConstFunc(spike_events, isi_values) + + +def get_min_dist(spike_time, spike_train, start_index=0): + """ Returns the minimal distance |spike_time - spike_train[i]| + with i>=start_index + """ + d = abs(spike_time - spike_train[start_index]) + start_index += 1 + while start_index < len(spike_train): + d_temp = abs(spike_time - spike_train[start_index]) + if d_temp > d: + break + else: + d = d_temp + start_index += 1 + return d + + +def spike_distance(spikes1, spikes2, T_end, T_start=0.0): + """ Computes the instantaneous spike-distance S_spike (t) of the two given + spike trains. + Args: + - spikes1, spikes2: ordered arrays of spike times. + - T_end: end time of the observation interval. + - T_start: begin of the observation interval (default=0.0). + Returns: + - PieceWiseLinFunc describing the spike-distance. + """ + # add spikes at the beginning and end of the interval + t1 = np.empty(len(spikes1)+2) + t1[0] = T_start + t1[-1] = T_end + t1[1:-1] = spikes1 + t2 = np.empty(len(spikes2)+2) + t2[0] = T_start + t2[-1] = T_end + t2[1:-1] = spikes2 + + spike_events = np.empty(len(t1)+len(t2)-2) + spike_events[0] = T_start + spike_events[-1] = T_end + y_starts = np.empty(len(spike_events)-1) + y_starts[0] = 0.0 + y_ends = np.empty(len(spike_events)-1) + + index1 = 0 + index2 = 0 + index = 1 + dt_p1 = 0.0 + dt_f1 = get_min_dist(t1[1], t2, 0) + dt_p2 = 0.0 + dt_f2 = get_min_dist(t2[1], t1, 0) + isi1 = t1[1]-t1[0] + isi2 = t2[1]-t2[0] + while True: + print(index, index1, index2) + if t1[index1+1] < t2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1+1 >= len(t1): + break + spike_events[index] = t1[index1] + # first calculate the previous interval end value + dt_p1 = dt_f1 # the previous time now was the following time before + s1 = dt_p1 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f1 = get_min_dist(t1[index1+1], t2, index2) + s1 = dt_f1 + isi1 = t1[index1+1]-t1[index1] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + else: + index2 += 1 + if index2+1 >= len(t2): + break + spike_events[index] = t2[index2] + # first calculate the previous interval end value + dt_p2 = dt_f2 # the previous time now was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 + s2 = dt_p2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f2 = get_min_dist(t2[index2+1], t1, index1) + s2 = dt_f2 + isi2 = t2[index2+1]-t2[index2] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + index += 1 + + return PieceWiseLinFunc(spike_events, y_starts, y_ends) diff --git a/pyspike/function.py b/pyspike/function.py index c1de9cb..adf4dbb 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -44,8 +44,6 @@ class PieceWiseConstFunc: Returns: - the average a. """ - print(self.x) - print(self.y) return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ (self.x[-1]-self.x[0]) @@ -58,3 +56,34 @@ class PieceWiseConstFunc: """ return np.sum((self.x[1:]-self.x[:-1]) * np.abs(self.y)) / \ (self.x[-1]-self.x[0]) + + +class PieceWiseLinFunc: + """ A class representing a piece-wise linear function. """ + + def __init__(self, x, y1, y2): + """ Constructs the piece-wise linear function. + Params: + - x: array of length N+1 defining the edges of the intervals of the pwc + function. + - y1: array of length N defining the function values at the left of the + intervals. + - y2: array of length N defining the function values at the right of the + intervals. + """ + self.x = x + self.y1 = y1 + self.y2 = y2 + + def get_plottable_data(self): + """ Returns two arrays containing x- and y-coordinates for immeditate + plotting of the piece-wise function. + """ + x_plot = np.empty(2*len(self.x)-2) + x_plot[0] = self.x[0] + x_plot[1::2] = self.x[1:] + x_plot[2::2] = self.x[1:-1] + y_plot = np.empty_like(x_plot) + y_plot[0::2] = self.y1 + y_plot[1::2] = self.y2 + return x_plot, y_plot -- cgit v1.2.3 From ab22a4d07ec803f1d52a505442989c19f343aa35 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 21 Sep 2014 14:09:55 +0200 Subject: added spike-distance test + bugfix --- examples/test_data.py | 4 ++-- pyspike/distances.py | 22 +++++++++++------ test/test_distance.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 test/test_distance.py diff --git a/examples/test_data.py b/examples/test_data.py index 94e7d51..afafe7a 100644 --- a/examples/test_data.py +++ b/examples/test_data.py @@ -16,7 +16,7 @@ for line in spike_file: for (i,spikes) in enumerate(spike_trains): plt.plot(spikes, i*np.ones_like(spikes), 'o') -f = spk.isi_distance(spike_trains[0], spike_trains[10], 4000) +f = spk.isi_distance(spike_trains[0], spike_trains[1], 4000) x, y = f.get_plottable_data() plt.figure() @@ -26,7 +26,7 @@ print("Average: %.8f" % f.avrg()) print("Absolute average: %.8f" % f.abs_avrg()) -f = spk.spike_distance(spike_trains[0], spike_trains[10], 4000) +f = spk.spike_distance(spike_trains[0], spike_trains[1], 4000) x, y = f.get_plottable_data() print(x) print(y) diff --git a/pyspike/distances.py b/pyspike/distances.py index 2ea80e7..766c75a 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -36,7 +36,6 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): # compute the isi-distance spike_events = np.empty(len(nu1)+len(nu2)) spike_events[0] = T_start - spike_events[-1] = T_end # the values have one entry less - the number of intervals between events isi_values = np.empty(len(spike_events)-1) # add the distance of the first events @@ -48,22 +47,32 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): index = 1 while True: # check which spike is next - from s1 or s2 - if s1[index1+1] <= s2[index2+1]: + if s1[index1+1] < s2[index2+1]: index1 += 1 # break condition relies on existence of spikes at T_end if index1 >= len(nu1): break spike_events[index] = s1[index1] - else: + elif s1[index1+1] > s2[index2+1]: index2 += 1 if index2 >= len(nu2): break spike_events[index] = s2[index2] + else: # s1[index1+1] == s2[index2+1] + index1 += 1 + index2 += 1 + if (index1 >= len(nu1)) or (index2 >= len(nu2)): + break + spike_events[index] = s1[index1] # compute the corresponding isi-distance isi_values[index] = (nu1[index1]-nu2[index2]) / \ max(nu1[index1], nu2[index2]) index += 1 - return PieceWiseConstFunc(spike_events, isi_values) + # the last event is the interval end + spike_events[index] = T_end + # use only the data added above + # could be less than original length due to equal spike times + return PieceWiseConstFunc(spike_events[:index+1], isi_values[:index]) def get_min_dist(spike_time, spike_train, start_index=0): @@ -119,7 +128,7 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): isi1 = t1[1]-t1[0] isi2 = t2[1]-t2[0] while True: - print(index, index1, index2) + # print(index, index1, index2) if t1[index1+1] < t2[index2+1]: index1 += 1 # break condition relies on existence of spikes at T_end @@ -133,7 +142,6 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) # now the next interval start value dt_f1 = get_min_dist(t1[index1+1], t2, index2) - s1 = dt_f1 isi1 = t1[index1+1]-t1[index1] # s2 is the same as above, thus we can compute y2 immediately y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) @@ -149,7 +157,7 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) # now the next interval start value dt_f2 = get_min_dist(t2[index2+1], t1, index1) - s2 = dt_f2 + #s2 = dt_f2 isi2 = t2[index2+1]-t2[index2] # s2 is the same as above, thus we can compute y2 immediately y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) diff --git a/test/test_distance.py b/test/test_distance.py new file mode 100644 index 0000000..93053e7 --- /dev/null +++ b/test/test_distance.py @@ -0,0 +1,66 @@ +""" test_distance.py + +Tests the isi- and spike-distance computation + +Copyright 2014, Mario Mulansky +""" + +from __future__ import print_function +import numpy as np +from numpy.testing import assert_equal, assert_array_almost_equal + +import pyspike as spk + +def test_isi(): + # generate two spike trains: + t1 = np.array([0.2, 0.4, 0.6, 0.7]) + t2 = np.array([0.3, 0.45, 0.8, 0.9, 0.95]) + + # pen&paper calculation of the isi distance + expected_times = [0.0,0.2,0.3,0.4,0.45,0.6,0.7,0.8,0.9,0.95,1.0] + expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, + -0.25/0.35, -0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] + + f = spk.isi_distance(t1, t2, 1.0) + + assert_equal(f.x, expected_times) + assert_array_almost_equal(f.y, expected_isi, decimal=14) + + # check with some equal spike times + t1 = np.array([0.2,0.4,0.6]) + t2 = np.array([0.1,0.4,0.5,0.6]) + + expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] + expected_isi = [0.1/0.2, -0.1/0.3, -0.1/0.3, 0.1/0.2, 0.1/0.2, -0.0/0.5] + + f = spk.isi_distance(t1, t2, 1.0) + + assert_equal(f.x, expected_times) + assert_array_almost_equal(f.y, expected_isi, decimal=14) + + +def test_spike(): + # generate two spike trains: + t1 = np.array([0.2, 0.4, 0.6, 0.7]) + t2 = np.array([0.3, 0.45, 0.8, 0.9, 0.95]) + + # pen&paper calculation of the spike distance + expected_times = [0.0,0.2,0.3,0.4,0.45,0.6,0.7,0.8,0.9,0.95,1.0] + s1 = np.array([0.0, 0.1, (0.1*0.1+0.05*0.1)/0.2, 0.05, (0.05*0.15 * 2)/0.2, + 0.15, 0.1, 0.1*0.2/0.3, 0.1**2/0.3, 0.1*0.05/0.3, 0.0]) + s2 = np.array([0.0, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, + (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, 0.1,0.1,0.05,0.0]) + isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.1, 0.3, 0.3, 0.3, 0.3]) + isi2 = np.array([0.3, 0.3, 0.15, 0.15, 0.35, 0.35, 0.35, 0.1, 0.05, 0.05]) + expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) + expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) + + f = spk.spike_distance(t1, t2, 1.0) + + assert_equal(f.x, expected_times) + assert_array_almost_equal(f.y1, expected_y1, decimal=14) + assert_array_almost_equal(f.y2, expected_y2, decimal=14) + +if __name__ == "main": + test_isi() + test_spike() -- cgit v1.2.3 From 066b3994ff296abc36a8224002bc1d312b7d5cc9 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 24 Sep 2014 13:15:18 +0200 Subject: spike dist now supports double spikes (incl tests) --- pyspike/distances.py | 30 ++++++++++++++++++++++++++---- test/test_distance.py | 19 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index 766c75a..a9a2cc8 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -128,7 +128,7 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): isi1 = t1[1]-t1[0] isi2 = t2[1]-t2[0] while True: - # print(index, index1, index2) + print(index, index1, index2) if t1[index1+1] < t2[index2+1]: index1 += 1 # break condition relies on existence of spikes at T_end @@ -145,7 +145,7 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): isi1 = t1[index1+1]-t1[index1] # s2 is the same as above, thus we can compute y2 immediately y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - else: + elif t1[index1+1] > t2[index2+1]: index2 += 1 if index2+1 >= len(t2): break @@ -161,6 +161,28 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): isi2 = t2[index2+1]-t2[index2] # s2 is the same as above, thus we can compute y2 immediately y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + else: # t1[index1+1] == t2[index2+1] - generate only one event + index1 += 1 + index2 += 1 + if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): + break + assert( dt_f2 == 0.0 ) + assert( dt_f1 == 0.0 ) + spike_events[index] = t1[index1] + y_ends[index-1] = 0.0 + y_starts[index] = 0.0 + dt_p1 = 0.0 + dt_p2 = 0.0 + dt_f1 = get_min_dist(t1[index1+1], t2, index2) + dt_f2 = get_min_dist(t2[index2+1], t1, index1) + isi1 = t1[index1+1]-t1[index1] + isi2 = t2[index2+1]-t2[index2] index += 1 - - return PieceWiseLinFunc(spike_events, y_starts, y_ends) + # the last event is the interval end + spike_events[index] = T_end + # the ending value of the last interval is 0 + y_ends[index-1] = 0.0 + # use only the data added above + # could be less than original length due to equal spike times + return PieceWiseLinFunc(spike_events[:index+1], + y_starts[:index], y_ends[:index]) diff --git a/test/test_distance.py b/test/test_distance.py index 93053e7..17ca14a 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -61,6 +61,25 @@ def test_spike(): assert_array_almost_equal(f.y1, expected_y1, decimal=14) assert_array_almost_equal(f.y2, expected_y2, decimal=14) + # check with some equal spike times + t1 = np.array([0.2,0.4,0.6]) + t2 = np.array([0.1,0.4,0.5,0.6]) + + expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] + s1 = np.array([0.0, 0.1*0.1/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) + s2 = np.array([0.0, 0.1, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) + isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.4]) + isi2 = np.array([0.1, 0.3, 0.3, 0.1, 0.1, 0.4]) + expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) + expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) + + f = spk.spike_distance(t1, t2, 1.0) + + assert_equal(f.x, expected_times) + assert_array_almost_equal(f.y1, expected_y1, decimal=14) + assert_array_almost_equal(f.y2, expected_y2, decimal=14) + + if __name__ == "main": test_isi() test_spike() -- cgit v1.2.3 From 375e210d2a54bcff345495d9bb6dc90534d94bfb Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 26 Sep 2014 16:17:18 +0200 Subject: + add_auxiliary_spikes function incl test --- pyspike/__init__.py | 2 +- pyspike/distances.py | 108 ++++++++++++++++++++++++++++++-------------------- test/test_distance.py | 43 ++++++++++++++------ 3 files changed, 98 insertions(+), 55 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index a5f146a..1784037 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,5 +1,5 @@ __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc -from distances import isi_distance, spike_distance +from distances import add_auxiliary_spikes, isi_distance, spike_distance from spikes import spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index a9a2cc8..10b1d3c 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -9,25 +9,45 @@ import numpy as np from pyspike import PieceWiseConstFunc, PieceWiseLinFunc -def isi_distance(spikes1, spikes2, T_end, T_start=0.0): +def add_auxiliary_spikes( spike_train, T_end , T_start=0.0): + """ Adds spikes at the beginning (T_start) and end (T_end) of the + observation interval. + Args: + - spike_train: ordered array of spike times + - T_end: end time of the observation interval + - T_start: start time of the observation interval (default 0.0) + Returns: + - spike train with additional spikes at T_start and T_end. + """ + assert spike_train[0] >= T_start, \ + "Spike train has events before the given start time" + assert spike_train[-1] <= T_end, \ + "Spike train has events after the given end time" + if spike_train[0] != T_start: + spike_train = np.insert(spike_train, 0, T_start) + if spike_train[-1] != T_end: + spike_train = np.append(spike_train, T_end) + return spike_train + +def isi_distance(spikes1, spikes2): """ Computes the instantaneous isi-distance S_isi (t) of the two given - spike trains. + 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. - - T_end: end time of the observation interval. - - T_start: begin of the observation interval (default=0.0). + - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes. Returns: - PieceWiseConstFunc describing the isi-distance. """ - # add spikes at the beginning and end of the interval - s1 = np.empty(len(spikes1)+2) - s1[0] = T_start - s1[-1] = T_end - s1[1:-1] = spikes1 - s2 = np.empty(len(spikes2)+2) - s2[0] = T_start - s2[-1] = T_end - s2[1:-1] = spikes2 + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0]==spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1]==spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + + # shorter names + s1 = spikes1 + s2 = spikes2 # compute the interspike interval nu1 = s1[1:]-s1[:-1] @@ -35,7 +55,7 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): # compute the isi-distance spike_events = np.empty(len(nu1)+len(nu2)) - spike_events[0] = T_start + spike_events[0] = s1[0] # the values have one entry less - the number of intervals between events isi_values = np.empty(len(spike_events)-1) # add the distance of the first events @@ -69,7 +89,7 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): max(nu1[index1], nu2[index2]) index += 1 # the last event is the interval end - spike_events[index] = T_end + spike_events[index] = s1[-1] # use only the data added above # could be less than original length due to equal spike times return PieceWiseConstFunc(spike_events[:index+1], isi_values[:index]) @@ -77,7 +97,7 @@ def isi_distance(spikes1, spikes2, T_end, T_start=0.0): def get_min_dist(spike_time, spike_train, start_index=0): """ Returns the minimal distance |spike_time - spike_train[i]| - with i>=start_index + with i>=start_index. """ d = abs(spike_time - spike_train[start_index]) start_index += 1 @@ -91,31 +111,28 @@ def get_min_dist(spike_time, spike_train, start_index=0): return d -def spike_distance(spikes1, spikes2, T_end, T_start=0.0): +def spike_distance(spikes1, spikes2): """ Computes the instantaneous spike-distance S_spike (t) of the two given - spike trains. + 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. - - T_end: end time of the observation interval. - - T_start: begin of the observation interval (default=0.0). + - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes. Returns: - PieceWiseLinFunc describing the spike-distance. """ - # add spikes at the beginning and end of the interval - t1 = np.empty(len(spikes1)+2) - t1[0] = T_start - t1[-1] = T_end - t1[1:-1] = spikes1 - t2 = np.empty(len(spikes2)+2) - t2[0] = T_start - t2[-1] = T_end - t2[1:-1] = spikes2 + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0]==spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1]==spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + # shorter variables + t1 = spikes1 + t2 = spikes2 spike_events = np.empty(len(t1)+len(t2)-2) - spike_events[0] = T_start - spike_events[-1] = T_end + spike_events[0] = t1[0] y_starts = np.empty(len(spike_events)-1) - y_starts[0] = 0.0 y_ends = np.empty(len(spike_events)-1) index1 = 0 @@ -125,10 +142,13 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): dt_f1 = get_min_dist(t1[1], t2, 0) dt_p2 = 0.0 dt_f2 = get_min_dist(t2[1], t1, 0) - isi1 = t1[1]-t1[0] - isi2 = t2[1]-t2[0] + isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) + isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) + s1 = dt_f1*(t1[1]-t1[0])/isi1 + s2 = dt_f2*(t2[1]-t2[0])/isi2 + y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) while True: - print(index, index1, index2) + # print(index, index1, index2) if t1[index1+1] < t2[index2+1]: index1 += 1 # break condition relies on existence of spikes at T_end @@ -166,8 +186,8 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): index2 += 1 if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): break - assert( dt_f2 == 0.0 ) - assert( dt_f1 == 0.0 ) + assert dt_f2 == 0.0 + assert dt_f1 == 0.0 spike_events[index] = t1[index1] y_ends[index-1] = 0.0 y_starts[index] = 0.0 @@ -179,9 +199,13 @@ def spike_distance(spikes1, spikes2, T_end, T_start=0.0): isi2 = t2[index2+1]-t2[index2] index += 1 # the last event is the interval end - spike_events[index] = T_end - # the ending value of the last interval is 0 - y_ends[index-1] = 0.0 + spike_events[index] = t1[-1] + # the ending value of the last interval + isi1 = max(t1[-1]-t1[-2], t1[-2]-t1[-3]) + isi2 = max(t2[-1]-t2[-2], t2[-2]-t2[-3]) + s1 = dt_p1*(t1[-1]-t1[-2])/isi1 + s2 = dt_p2*(t2[-1]-t2[-2])/isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) # use only the data added above # could be less than original length due to equal spike times return PieceWiseLinFunc(spike_events[:index+1], diff --git a/test/test_distance.py b/test/test_distance.py index 17ca14a..35bdf85 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -11,6 +11,13 @@ from numpy.testing import assert_equal, assert_array_almost_equal import pyspike as spk +def test_auxiliary_spikes(): + t = np.array([0.2, 0.4, 0.6, 0.7]) + t_aux = spk.add_auxiliary_spikes(t, T_end=1.0, T_start=0.1) + assert_equal(t_aux, [0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) + t_aux = spk.add_auxiliary_spikes(t_aux, 1.0) + assert_equal(t_aux, [0.0, 0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) + def test_isi(): # generate two spike trains: t1 = np.array([0.2, 0.4, 0.6, 0.7]) @@ -21,7 +28,11 @@ def test_isi(): expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, -0.25/0.35, -0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] - f = spk.isi_distance(t1, t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) + f = spk.isi_distance(t1, t2) + + print("ISI: ", f.y) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y, expected_isi, decimal=14) @@ -33,7 +44,9 @@ def test_isi(): expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] expected_isi = [0.1/0.2, -0.1/0.3, -0.1/0.3, 0.1/0.2, 0.1/0.2, -0.0/0.5] - f = spk.isi_distance(t1, t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) + f = spk.isi_distance(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y, expected_isi, decimal=14) @@ -46,16 +59,19 @@ def test_spike(): # pen&paper calculation of the spike distance expected_times = [0.0,0.2,0.3,0.4,0.45,0.6,0.7,0.8,0.9,0.95,1.0] - s1 = np.array([0.0, 0.1, (0.1*0.1+0.05*0.1)/0.2, 0.05, (0.05*0.15 * 2)/0.2, - 0.15, 0.1, 0.1*0.2/0.3, 0.1**2/0.3, 0.1*0.05/0.3, 0.0]) - s2 = np.array([0.0, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, - (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, 0.1,0.1,0.05,0.0]) + s1 = np.array([0.1, 0.1, (0.1*0.1+0.05*0.1)/0.2, 0.05, (0.05*0.15 * 2)/0.2, + 0.15, 0.1, 0.1*0.2/0.3, 0.1**2/0.3, 0.1*0.05/0.3, 0.1]) + s2 = np.array([0.1, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, + (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, + 0.1, 0.1, 0.05, 0.05]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.1, 0.3, 0.3, 0.3, 0.3]) isi2 = np.array([0.3, 0.3, 0.15, 0.15, 0.35, 0.35, 0.35, 0.1, 0.05, 0.05]) expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - f = spk.spike_distance(t1, t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) + f = spk.spike_distance(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y1, expected_y1, decimal=14) @@ -66,20 +82,23 @@ def test_spike(): t2 = np.array([0.1,0.4,0.5,0.6]) expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] - s1 = np.array([0.0, 0.1*0.1/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) - s2 = np.array([0.0, 0.1, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) + s1 = np.array([0.1, 0.1*0.1/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) + s2 = np.array([0.1*0.1/0.3, 0.1, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.4]) - isi2 = np.array([0.1, 0.3, 0.3, 0.1, 0.1, 0.4]) + isi2 = np.array([0.3, 0.3, 0.3, 0.1, 0.1, 0.4]) expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - f = spk.spike_distance(t1, t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) + f = spk.spike_distance(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y1, expected_y1, decimal=14) assert_array_almost_equal(f.y2, expected_y2, decimal=14) -if __name__ == "main": +if __name__ == "__main__": + test_auxiliary_spikes() test_isi() test_spike() -- cgit v1.2.3 From 4886fc67254121002862efd568f04e4fc9440b93 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 26 Sep 2014 16:17:51 +0200 Subject: + add function for PieceWiseConstFunc, no tests yet --- pyspike/function.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pyspike/function.py b/pyspike/function.py index adf4dbb..7177a3d 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -57,6 +57,39 @@ class PieceWiseConstFunc: return np.sum((self.x[1:]-self.x[:-1]) * np.abs(self.y)) / \ (self.x[-1]-self.x[0]) + def add(self, f): + """ Adds another PieceWiseConst function to this function. + Note: only functions defined on the same interval can be summed. + Params: + - f: PieceWiseConst function to be added. + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + x_new = np.empty(len(self.x) + len(f.x)) + y_new = np.empty_like(x_new) + x_new[0] = self.x[0] + y_new[0] = self.y[0] + f.y[0] + index1 = 1 + index2 = 1 + index = 1 + while (index1+1 < len(self.x)) and (index2+1 < len(f.x)): + if self.x[index1+1] < f.x[index2+1]: + x_new[index] = self.x[index1] + index1 += 1 + elif self.x[index1+1] > f.x[index2+1]: + x_new[index] = f.x[index2+1] + index2 += 1 + else: # self.x[index1+1] == f.x[index2+1]: + x_new[index] = self.x[index1] + index1 += 1 + index2 += 1 + index += 1 + y_new[index] = self.y[index1] + f.y[index2] + # both indices should have reached the maximum simultaneously + assert (index1+1 == len(self.x)) and (index2+1 == len(f.x)) + # only use the data that was actually filled + self.x = x_new[:index+1] + self.y = y_new[:index+1] class PieceWiseLinFunc: """ A class representing a piece-wise linear function. """ @@ -87,3 +120,4 @@ class PieceWiseLinFunc: y_plot[0::2] = self.y1 y_plot[1::2] = self.y2 return x_plot, y_plot + -- cgit v1.2.3 From 03440863ae92c14abae25f54bad9349119b0ec64 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sat, 27 Sep 2014 14:34:46 +0200 Subject: added test for functions, bug fixes in add --- pyspike/function.py | 43 +++++++++++++++++++++++++++-------------- test/test_function.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 test/test_function.py diff --git a/pyspike/function.py b/pyspike/function.py index 7177a3d..ef6b0f1 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -20,8 +20,8 @@ class PieceWiseConstFunc: function. - y: array of length N defining the function values at the intervals. """ - self.x = x - self.y = y + self.x = np.array(x) + self.y = np.array(y) def get_plottable_data(self): """ Returns two arrays containing x- and y-coordinates for immeditate @@ -66,29 +66,44 @@ class PieceWiseConstFunc: assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" x_new = np.empty(len(self.x) + len(f.x)) - y_new = np.empty_like(x_new) + y_new = np.empty(len(x_new)-1) x_new[0] = self.x[0] y_new[0] = self.y[0] + f.y[0] - index1 = 1 - index2 = 1 - index = 1 - while (index1+1 < len(self.x)) and (index2+1 < len(f.x)): + index1 = 0 + index2 = 0 + index = 0 + while (index1+1 < len(self.y)) and (index2+1 < len(f.y)): + index += 1 + # print(index1+1, self.x[index1+1], self.y[index1+1], x_new[index]) if self.x[index1+1] < f.x[index2+1]: - x_new[index] = self.x[index1] index1 += 1 + x_new[index] = self.x[index1] elif self.x[index1+1] > f.x[index2+1]: - x_new[index] = f.x[index2+1] index2 += 1 + x_new[index] = f.x[index2] else: # self.x[index1+1] == f.x[index2+1]: - x_new[index] = self.x[index1] index1 += 1 index2 += 1 - index += 1 + x_new[index] = self.x[index1] y_new[index] = self.y[index1] + f.y[index2] - # both indices should have reached the maximum simultaneously - assert (index1+1 == len(self.x)) and (index2+1 == len(f.x)) + # one array reached the end -> copy the contents of the other to the end + if index1+1 < len(self.y): + x_new[index+1:index+1+len(self.x)-index1-1] = self.x[index1+1:] + y_new[index+1:index+1+len(self.y)-index1-1] = self.y[index1+1:] + \ + f.y[-1] + index += len(self.x)-index1-2 + elif index2+1 < len(f.y): + x_new[index+1:index+1+len(f.x)-index2-1] = f.x[index2+1:] + y_new[index+1:index+1+len(f.y)-index2-1] = f.y[index2+1:] + \ + self.y[-1] + index += len(f.x)-index2-2 + else: # both arrays reached the end simultaneously + # only the last x-value missing + x_new[index+1] = self.x[-1] + # the last value is again the end of the interval + # x_new[index+1] = self.x[-1] # only use the data that was actually filled - self.x = x_new[:index+1] + self.x = x_new[:index+2] self.y = y_new[:index+1] class PieceWiseLinFunc: diff --git a/test/test_function.py b/test/test_function.py new file mode 100644 index 0000000..386b999 --- /dev/null +++ b/test/test_function.py @@ -0,0 +1,53 @@ +""" test_function.py + +Tests the PieceWiseConst and PieceWiseLinear functions + +Copyright 2014, Mario Mulansky +""" + +from __future__ import print_function +import numpy as np +from copy import copy +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_almost_equal + +import pyspike as spk + +def test_pwc(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f = spk.PieceWiseConstFunc(x, y) + xp, yp = f.get_plottable_data() + + xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] + yp_expected = [1.0, 1.0, -0.5, -0.5, 1.5, 1.5, 0.75, 0.75] + assert_array_almost_equal(xp, xp_expected) + assert_array_almost_equal(yp, yp_expected) + + assert_almost_equal(f.avrg(), (1.0-0.5+0.5*1.5+1.5*0.75)/4.0, decimal=16) + assert_almost_equal(f.abs_avrg(), (1.0+0.5+0.5*1.5+1.5*0.75)/4.0, + decimal=16) + + f1 = copy(f) + x = [0.0, 0.75, 2.0, 2.5, 2.7, 4.0] + y = [0.5, 1.0, -0.25, 0.0, 1.5] + f2 = spk.PieceWiseConstFunc(x, y) + f1.add(f2) + x_expected = [0.0, 0.75, 1.0, 2.0, 2.5, 2.7, 4.0] + y_expected = [1.5, 2.0, 0.5, 1.25, 0.75, 2.25] + assert_array_almost_equal(f1.x, x_expected, decimal=16) + assert_array_almost_equal(f1.y, y_expected, decimal=16) + + f2.add(f) + assert_array_almost_equal(f2.x, x_expected, decimal=16) + assert_array_almost_equal(f2.y, y_expected, decimal=16) + + f1.add(f2) + # same x, but y doubled + assert_array_almost_equal(f1.x, f2.x, decimal=16) + assert_array_almost_equal(f1.y, 2*f2.y, decimal=16) + + +if __name__ == "__main__": + test_pwc() -- cgit v1.2.3 From 604c7cfe89ebaa37bc51fbfabb656b8aa7829554 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sat, 27 Sep 2014 18:37:42 +0200 Subject: + addition for piece wise linear function with tests --- pyspike/function.py | 106 ++++++++++++++++++++++++++++++++++++++++++++++++-- test/test_function.py | 49 ++++++++++++++++++++++- 2 files changed, 150 insertions(+), 5 deletions(-) diff --git a/pyspike/function.py b/pyspike/function.py index ef6b0f1..b705293 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -10,6 +10,9 @@ from __future__ import print_function import numpy as np +############################################################## +# PieceWiseConstFunc +############################################################## class PieceWiseConstFunc: """ A class representing a piece-wise constant function. """ @@ -106,6 +109,10 @@ class PieceWiseConstFunc: self.x = x_new[:index+2] self.y = y_new[:index+1] + +############################################################## +# PieceWiseLinFunc +############################################################## class PieceWiseLinFunc: """ A class representing a piece-wise linear function. """ @@ -119,9 +126,9 @@ class PieceWiseLinFunc: - y2: array of length N defining the function values at the right of the intervals. """ - self.x = x - self.y1 = y1 - self.y2 = y2 + self.x = np.array(x) + self.y1 = np.array(y1) + self.y2 = np.array(y2) def get_plottable_data(self): """ Returns two arrays containing x- and y-coordinates for immeditate @@ -136,3 +143,96 @@ class PieceWiseLinFunc: y_plot[1::2] = self.y2 return x_plot, y_plot + def avrg(self): + """ Computes the average of the piece-wise linear function: + a = 1/T int f(x) dx where T is the length of the interval. + Returns: + - the average a. + """ + return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ + (self.x[-1]-self.x[0]) + + def abs_avrg(self): + """ Computes the absolute average of the piece-wise linear function: + a = 1/T int |f(x)| dx where T is the length of the interval. + Returns: + - the average a. + """ + return np.sum((self.x[1:]-self.x[:-1]) * 0.5 * + (np.abs(self.y1)+np.abs(self.y2)))/(self.x[-1]-self.x[0]) + + def add(self, f): + """ Adds another PieceWiseLin function to this function. + Note: only functions defined on the same interval can be summed. + Params: + - f: PieceWiseLin function to be added. + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + x_new = np.empty(len(self.x) + len(f.x)) + y1_new = np.empty(len(x_new)-1) + y2_new = np.empty_like(y1_new) + x_new[0] = self.x[0] + y1_new[0] = self.y1[0] + f.y1[0] + index1 = 0 # index for self + index2 = 0 # index for f + index = 0 # index for new + while (index1+1 < len(self.y1)) and (index2+1 < len(f.y1)): + # print(index1+1, self.x[index1+1], self.y[index1+1], x_new[index]) + if self.x[index1+1] < f.x[index2+1]: + # first compute the end value of the previous interval + # linear interpolation of the interval + y = f.y1[index2] + (f.y2[index2]-f.y1[index2]) * \ + (self.x[index1+1]-f.x[index2]) / (f.x[index2+1]-f.x[index2]) + y2_new[index] = self.y2[index1] + y + index1 += 1 + index += 1 + x_new[index] = self.x[index1] + # and the starting value for the next interval + y1_new[index] = self.y1[index1] + y + elif self.x[index1+1] > f.x[index2+1]: + # first compute the end value of the previous interval + # linear interpolation of the interval + y = self.y1[index1] + (self.y2[index1]-self.y1[index1]) * \ + (f.x[index2+1]-self.x[index1]) / \ + (self.x[index1+1]-self.x[index1]) + y2_new[index] = f.y2[index2] + y + index2 += 1 + index += 1 + x_new[index] = f.x[index2] + # and the starting value for the next interval + y1_new[index] = f.y1[index2] + y + else: # self.x[index1+1] == f.x[index2+1]: + y2_new[index] = self.y2[index1] + f.y2[index2] + index1 += 1 + index2 += 1 + index += 1 + x_new[index] = self.x[index1] + y1_new[index] = self.y1[index1] + f.y1[index2] + # one array reached the end -> copy the contents of the other to the end + if index1+1 < len(self.y1): + # compute the linear interpolations values + y = f.y1[index2] + (f.y2[index2]-f.y1[index2]) * \ + (self.x[index1+1:-1]-f.x[index2]) / (f.x[index2+1]-f.x[index2]) + x_new[index+1:index+1+len(self.x)-index1-1] = self.x[index1+1:] + y1_new[index+1:index+1+len(self.y1)-index1-1] = self.y1[index1+1:]+y + y2_new[index:index+len(self.y2)-index1-1] = self.y2[index1:-1] + y + index += len(self.x)-index1-2 + elif index2+1 < len(f.y1): + # compute the linear interpolations values + y = self.y1[index1] + (self.y2[index1]-self.y1[index1]) * \ + (f.x[index2+1:-1]-self.x[index1]) / \ + (self.x[index1+1]-self.x[index1]) + x_new[index+1:index+1+len(f.x)-index2-1] = f.x[index2+1:] + y1_new[index+1:index+1+len(f.y1)-index2-1] = f.y1[index2+1:] + y + y2_new[index:index+len(f.y2)-index2-1] = f.y2[index2:-1] + y + index += len(f.x)-index2-2 + else: # both arrays reached the end simultaneously + # only the last x-value missing + x_new[index+1] = self.x[-1] + # finally, the end value for the last interval + y2_new[index] = self.y2[-1]+f.y2[-1] + # only use the data that was actually filled + self.x = x_new[:index+2] + self.y1 = y1_new[:index+1] + self.y2 = y2_new[:index+1] diff --git a/test/test_function.py b/test/test_function.py index 386b999..014ecac 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -22,8 +22,8 @@ def test_pwc(): xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] yp_expected = [1.0, 1.0, -0.5, -0.5, 1.5, 1.5, 0.75, 0.75] - assert_array_almost_equal(xp, xp_expected) - assert_array_almost_equal(yp, yp_expected) + assert_array_almost_equal(xp, xp_expected, decimal=16) + assert_array_almost_equal(yp, yp_expected, decimal=16) assert_almost_equal(f.avrg(), (1.0-0.5+0.5*1.5+1.5*0.75)/4.0, decimal=16) assert_almost_equal(f.abs_avrg(), (1.0+0.5+0.5*1.5+1.5*0.75)/4.0, @@ -49,5 +49,50 @@ def test_pwc(): assert_array_almost_equal(f1.y, 2*f2.y, decimal=16) +def test_pwl(): + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y1 = [1.0, -0.5, 1.5, 0.75] + y2 = [1.5, -0.4, 1.5, 0.25] + f = spk.PieceWiseLinFunc(x, y1, y2) + xp, yp = f.get_plottable_data() + + xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] + yp_expected = [1.0, 1.5, -0.5, -0.4, 1.5, 1.5, 0.75, 0.25] + assert_array_almost_equal(xp, xp_expected, decimal=16) + assert_array_almost_equal(yp, yp_expected, decimal=16) + + avrg_expected = (1.25 - 0.45 + 0.75 + 1.5*0.5) / 4.0 + assert_almost_equal(f.avrg(), avrg_expected, decimal=16) + + abs_avrg_expected = (1.25 + 0.45 + 0.75 + 1.5*0.5) / 4.0 + assert_almost_equal(f.abs_avrg(), abs_avrg_expected, decimal=16) + + f1 = copy(f) + x = [0.0, 0.75, 2.0, 2.5, 2.7, 4.0] + y1 = [0.5, 1.0, -0.25, 0.0, 1.5] + y2 = [0.8, 0.2, -1.0, 0.0, 2.0] + f2 = spk.PieceWiseLinFunc(x, y1, y2) + f1.add(f2) + x_expected = [0.0, 0.75, 1.0, 2.0, 2.5, 2.7, 4.0] + y1_expected = [1.5, 1.0+1.0+0.5*0.75, -0.5+1.0-0.8*0.25/1.25, 1.5-0.25, + 0.75, 1.5+0.75-0.5*0.2/1.5] + y2_expected = [0.8+1.0+0.5*0.75, 1.5+1.0-0.8*0.25/1.25, -0.4+0.2, 1.5-1.0, + 0.75-0.5*0.2/1.5, 2.25] + assert_array_almost_equal(f1.x, x_expected, decimal=16) + assert_array_almost_equal(f1.y1, y1_expected, decimal=16) + assert_array_almost_equal(f1.y2, y2_expected, decimal=16) + + f2.add(f) + assert_array_almost_equal(f2.x, x_expected, decimal=16) + assert_array_almost_equal(f2.y1, y1_expected, decimal=16) + assert_array_almost_equal(f2.y2, y2_expected, decimal=16) + + f1.add(f2) + # same x, but y doubled + assert_array_almost_equal(f1.x, f2.x, decimal=16) + assert_array_almost_equal(f1.y1, 2*f2.y1, decimal=16) + assert_array_almost_equal(f1.y2, 2*f2.y2, decimal=16) + + if __name__ == "__main__": test_pwc() -- cgit v1.2.3 From fa6b1e375eb8a4b93582d2935352447849e66203 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 29 Sep 2014 12:27:08 +0200 Subject: cosmetics --- examples/test_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/test_data.py b/examples/test_data.py index afafe7a..ff7b510 100644 --- a/examples/test_data.py +++ b/examples/test_data.py @@ -1,4 +1,5 @@ # compute the isi distance of some test data + from __future__ import print_function import numpy as np -- cgit v1.2.3 From e4f1c09672068e4778f7b5f3e27b47ff8986863c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 29 Sep 2014 12:55:56 +0200 Subject: +mul_scalar, tests restructured and cosmetics --- pyspike/distances.py | 14 ++++++++++++++ pyspike/function.py | 15 +++++++++++++++ pyspike/spikes.py | 6 ++++++ test/test_function.py | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/pyspike/distances.py b/pyspike/distances.py index 10b1d3c..f4be625 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -9,6 +9,10 @@ import numpy as np from pyspike import PieceWiseConstFunc, PieceWiseLinFunc + +############################################################ +# add_auxiliary_spikes +############################################################ def add_auxiliary_spikes( spike_train, T_end , T_start=0.0): """ Adds spikes at the beginning (T_start) and end (T_end) of the observation interval. @@ -29,6 +33,10 @@ def add_auxiliary_spikes( spike_train, T_end , T_start=0.0): spike_train = np.append(spike_train, T_end) return spike_train + +############################################################ +# isi_distance +############################################################ def isi_distance(spikes1, spikes2): """ Computes the instantaneous isi-distance S_isi (t) of the two given spike trains. The spike trains are expected to have auxiliary spikes at the @@ -95,6 +103,9 @@ def isi_distance(spikes1, spikes2): return PieceWiseConstFunc(spike_events[:index+1], isi_values[:index]) +############################################################ +# get_min_dist +############################################################ def get_min_dist(spike_time, spike_train, start_index=0): """ Returns the minimal distance |spike_time - spike_train[i]| with i>=start_index. @@ -111,6 +122,9 @@ def get_min_dist(spike_time, spike_train, start_index=0): return d +############################################################ +# spike_distance +############################################################ def spike_distance(spikes1, spikes2): """ 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 diff --git a/pyspike/function.py b/pyspike/function.py index b705293..3a5a01c 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -109,6 +109,13 @@ class PieceWiseConstFunc: self.x = x_new[:index+2] self.y = y_new[:index+1] + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + Params: + - fac: Value to multiply + """ + self.y *= fac + ############################################################## # PieceWiseLinFunc @@ -236,3 +243,11 @@ class PieceWiseLinFunc: self.x = x_new[:index+2] self.y1 = y1_new[:index+1] self.y2 = y2_new[:index+1] + + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + Params: + - fac: Value to multiply + """ + self.y1 *= fac + self.y2 *= fac diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 6b2eea3..70b48ff 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -7,6 +7,9 @@ Copyright 2014, Mario Mulansky import numpy as np +############################################################ +# spike_train_from_string +############################################################ def spike_train_from_string(s, sep=' '): """ Converts a string of times into an array of spike times. Params: @@ -18,6 +21,9 @@ def spike_train_from_string(s, sep=' '): return np.fromstring(s, sep=sep) +############################################################ +# merge_spike_trains +############################################################ def merge_spike_trains(spike_trains): """ Merges a number of spike trains into a single spike train. Params: diff --git a/test/test_function.py b/test/test_function.py index 014ecac..7420011 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -29,6 +29,13 @@ def test_pwc(): assert_almost_equal(f.abs_avrg(), (1.0+0.5+0.5*1.5+1.5*0.75)/4.0, decimal=16) + +def test_pwc_add(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f = spk.PieceWiseConstFunc(x, y) + f1 = copy(f) x = [0.0, 0.75, 2.0, 2.5, 2.7, 4.0] y = [0.5, 1.0, -0.25, 0.0, 1.5] @@ -48,6 +55,17 @@ def test_pwc(): assert_array_almost_equal(f1.x, f2.x, decimal=16) assert_array_almost_equal(f1.y, 2*f2.y, decimal=16) +def test_pwc_mul(): + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f = spk.PieceWiseConstFunc(x, y) + + f.mul_scalar(1.5) + assert_array_almost_equal(f.x, x, decimal=16) + assert_array_almost_equal(f.y, 1.5*np.array(y), decimal=16) + f.mul_scalar(1.0/5.0) + assert_array_almost_equal(f.y, 1.5/5.0*np.array(y), decimal=16) + def test_pwl(): x = [0.0, 1.0, 2.0, 2.5, 4.0] @@ -67,6 +85,13 @@ def test_pwl(): abs_avrg_expected = (1.25 + 0.45 + 0.75 + 1.5*0.5) / 4.0 assert_almost_equal(f.abs_avrg(), abs_avrg_expected, decimal=16) + +def test_pwl_add(): + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y1 = [1.0, -0.5, 1.5, 0.75] + y2 = [1.5, -0.4, 1.5, 0.25] + f = spk.PieceWiseLinFunc(x, y1, y2) + f1 = copy(f) x = [0.0, 0.75, 2.0, 2.5, 2.7, 4.0] y1 = [0.5, 1.0, -0.25, 0.0, 1.5] @@ -94,5 +119,19 @@ def test_pwl(): assert_array_almost_equal(f1.y2, 2*f2.y2, decimal=16) +def test_pwc_mul(): + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y1 = [1.0, -0.5, 1.5, 0.75] + y2 = [1.5, -0.4, 1.5, 0.25] + f = spk.PieceWiseLinFunc(x, y1, y2) + + f.mul_scalar(1.5) + assert_array_almost_equal(f.x, x, decimal=16) + assert_array_almost_equal(f.y1, 1.5*np.array(y1), decimal=16) + assert_array_almost_equal(f.y2, 1.5*np.array(y2), decimal=16) + f.mul_scalar(1.0/5.0) + assert_array_almost_equal(f.y1, 1.5/5.0*np.array(y1), decimal=16) + assert_array_almost_equal(f.y2, 1.5/5.0*np.array(y2), decimal=16) + if __name__ == "__main__": test_pwc() -- cgit v1.2.3 From b726773a29f85d465ff71867fab4fa5b8e5bcfe1 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 29 Sep 2014 16:08:45 +0200 Subject: + multivariate distances --- pyspike/__init__.py | 3 +- pyspike/distances.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ pyspike/function.py | 40 +++++++++++++++++++++++---- test/test_distance.py | 47 ++++++++++++++++++++++++++++++- 4 files changed, 158 insertions(+), 8 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 1784037..2143bdc 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,5 +1,6 @@ __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc -from distances import add_auxiliary_spikes, isi_distance, spike_distance +from distances import add_auxiliary_spikes, isi_distance, spike_distance, \ + isi_distance_multi, spike_distance_multi from spikes import spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index f4be625..52c6640 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -224,3 +224,79 @@ def spike_distance(spikes1, spikes2): # could be less than original length due to equal spike times return PieceWiseLinFunc(spike_events[:index+1], y_starts[:index], y_ends[:index]) + + + + +############################################################ +# multi_distance +############################################################ +def multi_distance(spike_trains, pair_distance_func, indices=None): + """ Internal implementation detail, use isi_distance_multi or + spike_distance_multi. + + Computes the multi-variate distance for a set of spike-trains using the + pair_dist_func to compute pair-wise distances. That is it computes the + average distance of all pairs of spike-trains: + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + where the sum goes over all pairs . + Args: + - spike_trains: list of spike trains + - pair_distance_func: function computing the distance of two spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - The averaged multi-variate distance of all pairs + """ + if indices==None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(i,j) for i in indices for j in indices[i+1:]] + # start with first pair + (i,j) = pairs[0] + average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + for (i,j) in pairs[1:]: + current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + average_dist.add(current_dist) # add to the average + average_dist.mul_scalar(1.0/len(pairs)) # normalize + return average_dist + + +############################################################ +# isi_distance_multi +############################################################ +def isi_distance_multi(spike_trains, indices=None): + """ computes the multi-variate isi-distance for a set of spike-trains. That + is the average isi-distance of all pairs of spike-trains: + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + where the sum goes over all pairs + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - A PieceWiseConstFunc representing the averaged isi distance S + """ + return multi_distance(spike_trains, isi_distance, indices) + + +############################################################ +# spike_distance_multi +############################################################ +def spike_distance_multi(spike_trains, indices=None): + """ computes the multi-variate spike-distance for a set of spike-trains. + That is the average spike-distance of all pairs of spike-trains: + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + where the sum goes over all pairs + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - A PieceWiseLinFunc representing the averaged spike distance S + """ + return multi_distance(spike_trains, spike_distance, indices) diff --git a/pyspike/function.py b/pyspike/function.py index 3a5a01c..26ca4b2 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -10,6 +10,7 @@ from __future__ import print_function import numpy as np + ############################################################## # PieceWiseConstFunc ############################################################## @@ -18,7 +19,7 @@ class PieceWiseConstFunc: def __init__(self, x, y): """ Constructs the piece-wise const function. - Params: + Args: - x: array of length N+1 defining the edges of the intervals of the pwc function. - y: array of length N defining the function values at the intervals. @@ -26,6 +27,19 @@ class PieceWiseConstFunc: self.x = np.array(x) self.y = np.array(y) + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + Args: + - other: another PieceWiseConstFunc object + Returns: + True if the two functions are equal up to `decimal` decimals, + False otherwise + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y, other.y, atol=eps, rtol=0.0) + def get_plottable_data(self): """ Returns two arrays containing x- and y-coordinates for immeditate plotting of the piece-wise function. @@ -63,7 +77,7 @@ class PieceWiseConstFunc: def add(self, f): """ Adds another PieceWiseConst function to this function. Note: only functions defined on the same interval can be summed. - Params: + Args: - f: PieceWiseConst function to be added. """ assert self.x[0] == f.x[0], "The functions have different intervals" @@ -111,7 +125,7 @@ class PieceWiseConstFunc: def mul_scalar(self, fac): """ Multiplies the function with a scalar value - Params: + Args: - fac: Value to multiply """ self.y *= fac @@ -125,7 +139,7 @@ class PieceWiseLinFunc: def __init__(self, x, y1, y2): """ Constructs the piece-wise linear function. - Params: + Args: - x: array of length N+1 defining the edges of the intervals of the pwc function. - y1: array of length N defining the function values at the left of the @@ -137,6 +151,20 @@ class PieceWiseLinFunc: self.y1 = np.array(y1) self.y2 = np.array(y2) + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + Args: + - other: another PieceWiseLinFunc object + Returns: + True if the two functions are equal up to `decimal` decimals, + False otherwise + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y1, other.y1, atol=eps, rtol=0.0) and \ + np.allclose(self.y2, other.y2, atol=eps, rtol=0.0) + def get_plottable_data(self): """ Returns two arrays containing x- and y-coordinates for immeditate plotting of the piece-wise function. @@ -171,7 +199,7 @@ class PieceWiseLinFunc: def add(self, f): """ Adds another PieceWiseLin function to this function. Note: only functions defined on the same interval can be summed. - Params: + Args: - f: PieceWiseLin function to be added. """ assert self.x[0] == f.x[0], "The functions have different intervals" @@ -246,7 +274,7 @@ class PieceWiseLinFunc: def mul_scalar(self, fac): """ Multiplies the function with a scalar value - Params: + Args: - fac: Value to multiply """ self.y1 *= fac diff --git a/test/test_distance.py b/test/test_distance.py index 35bdf85..c43f0b3 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -7,10 +7,12 @@ Copyright 2014, Mario Mulansky from __future__ import print_function import numpy as np +from copy import copy from numpy.testing import assert_equal, assert_array_almost_equal import pyspike as spk + def test_auxiliary_spikes(): t = np.array([0.2, 0.4, 0.6, 0.7]) t_aux = spk.add_auxiliary_spikes(t, T_end=1.0, T_start=0.1) @@ -18,6 +20,7 @@ def test_auxiliary_spikes(): t_aux = spk.add_auxiliary_spikes(t_aux, 1.0) assert_equal(t_aux, [0.0, 0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) + def test_isi(): # generate two spike trains: t1 = np.array([0.2, 0.4, 0.6, 0.7]) @@ -32,7 +35,7 @@ def test_isi(): t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_distance(t1, t2) - print("ISI: ", f.y) + # print("ISI: ", f.y) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y, expected_isi, decimal=14) @@ -98,6 +101,48 @@ def test_spike(): assert_array_almost_equal(f.y2, expected_y2, decimal=14) +def check_multi_distance(dist_func, dist_func_multi): + # generate spike trains: + t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) + t2 = spk.add_auxiliary_spikes(np.array([0.3, 0.45, 0.8, 0.9, 0.95]), 1.0) + t3 = spk.add_auxiliary_spikes(np.array([0.2,0.4,0.6]), 1.0) + t4 = spk.add_auxiliary_spikes(np.array([0.1,0.4,0.5,0.6]), 1.0) + spike_trains = [t1, t2, t3, t4] + + f12 = dist_func(t1, t2) + f13 = dist_func(t1, t3) + f14 = dist_func(t1, t4) + f23 = dist_func(t2, t3) + f24 = dist_func(t2, t4) + f34 = dist_func(t3, t4) + + f_multi = dist_func_multi(spike_trains, [0,1]) + assert f_multi.almost_equal(f12, decimal=14) + + f = copy(f12) + f.add(f13) + f.add(f23) + f.mul_scalar(1.0/3) + f_multi = dist_func_multi(spike_trains, [0,1,2]) + assert f_multi.almost_equal(f, decimal=14) + + f.mul_scalar(3) # revert above normalization + f.add(f14) + f.add(f24) + f.add(f34) + f.mul_scalar(1.0/6) + f_multi = dist_func_multi(spike_trains) + assert f_multi.almost_equal(f, decimal=14) + + +def test_multi_isi(): + check_multi_distance(spk.isi_distance, spk.isi_distance_multi) + + +def test_multi_spike(): + check_multi_distance(spk.spike_distance, spk.spike_distance_multi) + + if __name__ == "__main__": test_auxiliary_spikes() test_isi() -- cgit v1.2.3 From aeec6cfafed8df110e60743073cff6d778f65af0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 29 Sep 2014 18:45:10 +0200 Subject: +cython version for isi and performance tests --- examples/perf_isi.py | 46 +++++++++++++++++++++++++++ examples/perf_spike.py | 39 +++++++++++++++++++++++ pyspike/cython_distance.pyx | 75 +++++++++++++++++++++++++++++++++++++++++++++ pyspike/distances.py | 17 ++++++++-- 4 files changed, 174 insertions(+), 3 deletions(-) create mode 100644 examples/perf_isi.py create mode 100644 examples/perf_spike.py create mode 100644 pyspike/cython_distance.pyx diff --git a/examples/perf_isi.py b/examples/perf_isi.py new file mode 100644 index 0000000..8b44946 --- /dev/null +++ b/examples/perf_isi.py @@ -0,0 +1,46 @@ +# performance measure of the isi calculation + +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt +import time +from functools import partial + +import pyspike as spk +#import pyspike.distances # for the python functions + +def measure_perf(func, loops=10): + times = np.empty(loops) + for i in xrange(loops): + start = time.clock() + func() + times[i] = time.clock() - start + return np.min(times) + +print("# approximate number of spikes\tcython time [ms]\tpython time [ms]") + +# fix seed to get reproducible results +np.random.seed(1) + +# max times +Ns = np.arange(10000, 50001, 10000) +for N in Ns: + + # first generate some data + times = 2.0*np.random.random(1.1*N) + t1 = np.cumsum(times) + # only up to T + t1 = spk.add_auxiliary_spikes(t1[t1= N1: + break + spike_events[index] = s1[index1] + nu1 = s1[index1+1]-s1[index1] + elif s1[index1+1] > s2[index2+1]: + index2 += 1 + if index2 >= N2: + break + spike_events[index] = s2[index2] + nu2 = s2[index2+1]-s2[index2] + else: # s1[index1+1] == s2[index2+1] + index1 += 1 + index2 += 1 + if (index1 >= N1) or (index2 >= N2): + break + spike_events[index] = s1[index1] + nu1 = s1[index1+1]-s1[index1] + nu2 = s2[index2+1]-s2[index2] + # compute the corresponding isi-distance + isi_values[index] = (nu1 - nu2) / max(nu1, nu2) + index += 1 + # the last event is the interval end + spike_events[index] = s1[N1] + + return spike_events[:index+1], isi_values[:index] diff --git a/pyspike/distances.py b/pyspike/distances.py index 52c6640..76dcd83 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -53,10 +53,21 @@ def isi_distance(spikes1, spikes2): assert spikes1[-1]==spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" - # shorter names - s1 = spikes1 - s2 = spikes2 + # compile and load cython implementation + import pyximport + pyximport.install(setup_args={'include_dirs': [np.get_include()]}) + from cython_distance import isi_distance_cython + times, values = isi_distance_cython(spikes1, spikes2) + return PieceWiseConstFunc(times, values) + + +############################################################ +# isi_distance_python +############################################################ +def isi_distance_python(s1, s2): + """ Plain Python implementation of the isi distance. + """ # compute the interspike interval nu1 = s1[1:]-s1[:-1] nu2 = s2[1:]-s2[:-1] -- cgit v1.2.3 From 68f2f8e6297be829d29ef428784ac0002348877b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 30 Sep 2014 12:50:56 +0200 Subject: cython version of spike dist, using memory views --- examples/perf_spike.py | 9 ++- pyspike/cython_distance.pyx | 148 ++++++++++++++++++++++++++++++++-- pyspike/distances.py | 190 ++++++++++++++++++++++++++------------------ 3 files changed, 260 insertions(+), 87 deletions(-) diff --git a/examples/perf_spike.py b/examples/perf_spike.py index 37154ae..5b1c1cc 100644 --- a/examples/perf_spike.py +++ b/examples/perf_spike.py @@ -17,7 +17,7 @@ def measure_perf(func, loops=10): times[i] = time.clock() - start return np.min(times) -print("# approximate number of spikes\texecution time [ms]") +print("# approximate number of spikes\tcython time [ms]\tpython time [ms]") # max times Ns = np.arange(10000, 50001, 10000) @@ -34,6 +34,9 @@ for N in Ns: # only up to T t2 = spk.add_auxiliary_spikes(t2[t2 +""" + +""" To test whether things can be optimized: remove all yellow stuff in the html output:: @@ -14,20 +23,25 @@ which gives:: cython_distance.html - """ import numpy as np cimport numpy as np +from libc.math cimport fabs + DTYPE = np.float ctypedef np.float_t DTYPE_t -def isi_distance_cython(np.ndarray[DTYPE_t, ndim=1] s1, np.ndarray[DTYPE_t, ndim=1] s2): - cdef np.ndarray[DTYPE_t, ndim=1] spike_events - # the values have one entry less - the number of intervals between events - cdef np.ndarray[DTYPE_t, ndim=1] isi_values +############################################################ +# isi_distance_cython +############################################################ +def isi_distance_cython(double[:] s1, + double[:] s2): + + cdef double[:] spike_events + cdef double[:] isi_values cdef int index1, index2, index cdef int N1, N2 cdef double nu1, nu2 @@ -38,6 +52,7 @@ def isi_distance_cython(np.ndarray[DTYPE_t, ndim=1] s1, np.ndarray[DTYPE_t, ndim nu2 = s2[1]-s2[0] spike_events = np.empty(N1+N2) spike_events[0] = s1[0] + # the values have one entry less - the number of intervals between events isi_values = np.empty(N1+N2-1) isi_values[0] = (nu1-nu2)/max(nu1,nu2) index1 = 0 @@ -73,3 +88,124 @@ def isi_distance_cython(np.ndarray[DTYPE_t, ndim=1] s1, np.ndarray[DTYPE_t, ndim spike_events[index] = s1[N1] 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=0): + """ Returns the minimal distance |spike_time - spike_train[i]| + with i>=start_index. + """ + cdef double d, d_temp + d = fabs(spike_time - spike_train[start_index]) + start_index += 1 + while start_index < N: + d_temp = fabs(spike_time - spike_train[start_index]) + if d_temp > d: + break + else: + d = d_temp + start_index += 1 + return d + + +############################################################ +# spike_distance_cython +############################################################ +def spike_distance_cython(double[:] t1, + double[:] t2): + + cdef double[:] spike_events + cdef double[:] y_starts + cdef double[:] y_ends + + cdef int N1, N2, index1, index2, index + cdef double dt_p1, dt_p2, dt_f1, dt_f2, isi1, isi2, s1, s2 + + N1 = len(t1) + N2 = len(t2) + + spike_events = np.empty(N1+N2-2) + spike_events[0] = t1[0] + y_starts = np.empty(len(spike_events)-1) + y_ends = np.empty(len(spike_events)-1) + + index1 = 0 + index2 = 0 + index = 1 + dt_p1 = 0.0 + dt_f1 = get_min_dist_cython(t1[1], t2, N2, 0) + dt_p2 = 0.0 + dt_f2 = get_min_dist_cython(t2[1], t1, N1, 0) + isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) + isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) + s1 = dt_f1*(t1[1]-t1[0])/isi1 + s2 = dt_f2*(t2[1]-t2[0])/isi2 + y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + while True: + # print(index, index1, index2) + if t1[index1+1] < t2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1+1 >= N1: + break + spike_events[index] = t1[index1] + # first calculate the previous interval end value + dt_p1 = dt_f1 # the previous time now was the following time before + s1 = dt_p1 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) + isi1 = t1[index1+1]-t1[index1] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + elif t1[index1+1] > t2[index2+1]: + index2 += 1 + if index2+1 >= N2: + break + spike_events[index] = t2[index2] + # first calculate the previous interval end value + dt_p2 = dt_f2 # the previous time now was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 + s2 = dt_p2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) + #s2 = dt_f2 + isi2 = t2[index2+1]-t2[index2] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + else: # t1[index1+1] == t2[index2+1] - generate only one event + index1 += 1 + index2 += 1 + if (index1+1 >= N1) or (index2+1 >= N2): + break + spike_events[index] = t1[index1] + y_ends[index-1] = 0.0 + y_starts[index] = 0.0 + dt_p1 = 0.0 + dt_p2 = 0.0 + dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) + dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) + isi1 = t1[index1+1]-t1[index1] + isi2 = t2[index2+1]-t2[index2] + index += 1 + # the last event is the interval end + spike_events[index] = t1[N1-1] + # the ending value of the last interval + isi1 = max(t1[N1-1]-t1[N1-2], t1[N1-2]-t1[N1-3]) + isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) + s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 + s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/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] diff --git a/pyspike/distances.py b/pyspike/distances.py index 76dcd83..7a85471 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -62,6 +62,116 @@ def isi_distance(spikes1, spikes2): return PieceWiseConstFunc(times, values) +############################################################ +# spike_distance +############################################################ +def spike_distance(spikes1, spikes2): + """ 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. + Returns: + - PieceWiseLinFunc describing the spike-distance. + """ + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0]==spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1]==spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + + # compile and load cython implementation + import pyximport + pyximport.install(setup_args={'include_dirs': [np.get_include()]}) + from cython_distance import spike_distance_cython + + times, y_starts, y_ends = spike_distance_cython(spikes1, spikes2) + + return PieceWiseLinFunc(times, y_starts, y_ends) + + +############################################################ +# multi_distance +############################################################ +def multi_distance(spike_trains, pair_distance_func, indices=None): + """ Internal implementation detail, use isi_distance_multi or + spike_distance_multi. + + Computes the multi-variate distance for a set of spike-trains using the + pair_dist_func to compute pair-wise distances. That is it computes the + average distance of all pairs of spike-trains: + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + where the sum goes over all pairs . + Args: + - spike_trains: list of spike trains + - pair_distance_func: function computing the distance of two spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - The averaged multi-variate distance of all pairs + """ + if indices==None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(i,j) for i in indices for j in indices[i+1:]] + # start with first pair + (i,j) = pairs[0] + average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + for (i,j) in pairs[1:]: + current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + average_dist.add(current_dist) # add to the average + average_dist.mul_scalar(1.0/len(pairs)) # normalize + return average_dist + + +############################################################ +# isi_distance_multi +############################################################ +def isi_distance_multi(spike_trains, indices=None): + """ computes the multi-variate isi-distance for a set of spike-trains. That + is the average isi-distance of all pairs of spike-trains: + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + where the sum goes over all pairs + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - A PieceWiseConstFunc representing the averaged isi distance S + """ + return multi_distance(spike_trains, isi_distance, indices) + + +############################################################ +# spike_distance_multi +############################################################ +def spike_distance_multi(spike_trains, indices=None): + """ computes the multi-variate spike-distance for a set of spike-trains. + That is the average spike-distance of all pairs of spike-trains: + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + where the sum goes over all pairs + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - A PieceWiseLinFunc representing the averaged spike distance S + """ + return multi_distance(spike_trains, spike_distance, indices) + + +############################################################ +############################################################ +# VANILLA PYTHON IMPLEMENTATIONS OF ISI AND SPIKE DISTANCE +############################################################ +############################################################ + + ############################################################ # isi_distance_python ############################################################ @@ -134,9 +244,9 @@ def get_min_dist(spike_time, spike_train, start_index=0): ############################################################ -# spike_distance +# spike_distance_python ############################################################ -def spike_distance(spikes1, spikes2): +def spike_distance_python(spikes1, spikes2): """ 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 @@ -235,79 +345,3 @@ def spike_distance(spikes1, spikes2): # could be less than original length due to equal spike times return PieceWiseLinFunc(spike_events[:index+1], y_starts[:index], y_ends[:index]) - - - - -############################################################ -# multi_distance -############################################################ -def multi_distance(spike_trains, pair_distance_func, indices=None): - """ Internal implementation detail, use isi_distance_multi or - spike_distance_multi. - - Computes the multi-variate distance for a set of spike-trains using the - pair_dist_func to compute pair-wise distances. That is it computes the - average distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, - where the sum goes over all pairs . - Args: - - spike_trains: list of spike trains - - pair_distance_func: function computing the distance of two spike trains - - indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - Returns: - - The averaged multi-variate distance of all pairs - """ - if indices==None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # generate a list of possible index pairs - pairs = [(i,j) for i in indices for j in indices[i+1:]] - # start with first pair - (i,j) = pairs[0] - average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - for (i,j) in pairs[1:]: - current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - average_dist.add(current_dist) # add to the average - average_dist.mul_scalar(1.0/len(pairs)) # normalize - return average_dist - - -############################################################ -# isi_distance_multi -############################################################ -def isi_distance_multi(spike_trains, indices=None): - """ computes the multi-variate isi-distance for a set of spike-trains. That - is the average isi-distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, - where the sum goes over all pairs - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - Returns: - - A PieceWiseConstFunc representing the averaged isi distance S - """ - return multi_distance(spike_trains, isi_distance, indices) - - -############################################################ -# spike_distance_multi -############################################################ -def spike_distance_multi(spike_trains, indices=None): - """ computes the multi-variate spike-distance for a set of spike-trains. - That is the average spike-distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, - where the sum goes over all pairs - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - Returns: - - A PieceWiseLinFunc representing the averaged spike distance S - """ - return multi_distance(spike_trains, spike_distance, indices) -- cgit v1.2.3 From 9335f9a4f78b561b8c875704a2e52ba27a45ad9b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 1 Oct 2014 10:32:31 +0200 Subject: removed performance tests --- examples/perf_isi.py | 46 ---------------------------------------------- examples/perf_spike.py | 42 ------------------------------------------ 2 files changed, 88 deletions(-) delete mode 100644 examples/perf_isi.py delete mode 100644 examples/perf_spike.py diff --git a/examples/perf_isi.py b/examples/perf_isi.py deleted file mode 100644 index 8b44946..0000000 --- a/examples/perf_isi.py +++ /dev/null @@ -1,46 +0,0 @@ -# performance measure of the isi calculation - -from __future__ import print_function - -import numpy as np -import matplotlib.pyplot as plt -import time -from functools import partial - -import pyspike as spk -#import pyspike.distances # for the python functions - -def measure_perf(func, loops=10): - times = np.empty(loops) - for i in xrange(loops): - start = time.clock() - func() - times[i] = time.clock() - start - return np.min(times) - -print("# approximate number of spikes\tcython time [ms]\tpython time [ms]") - -# fix seed to get reproducible results -np.random.seed(1) - -# max times -Ns = np.arange(10000, 50001, 10000) -for N in Ns: - - # first generate some data - times = 2.0*np.random.random(1.1*N) - t1 = np.cumsum(times) - # only up to T - t1 = spk.add_auxiliary_spikes(t1[t1 Date: Wed, 1 Oct 2014 17:01:07 +0200 Subject: switched from pyximport to distutils --- Readme | 4 ++ pyspike/cython_distance.pyx | 18 ++++--- pyspike/distances.py | 13 ++--- pyspike/function.py | 127 +++++++------------------------------------- setup.py | 16 ++++++ 5 files changed, 55 insertions(+), 123 deletions(-) create mode 100644 setup.py diff --git a/Readme b/Readme index 39bd308..786ee87 100644 --- a/Readme +++ b/Readme @@ -1,3 +1,7 @@ Python Codes for Measuring Spike Synchrony +before using the module you should compile the cython backend via: + +python setup.py build_ext --inplace + Mario Mulansky (2014) diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 1a6d24a..6edcc01 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -3,7 +3,7 @@ #cython: cdivision=True """ -cython_distances.py +cython_distances.pyx cython implementation of the isi- and spike-distance @@ -159,13 +159,14 @@ def spike_distance_cython(double[:] t1, # first calculate the previous interval end value dt_p1 = dt_f1 # the previous time now was the following time before s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) # now the next interval start value dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) isi1 = t1[index1+1]-t1[index1] # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) elif t1[index1+1] > t2[index2+1]: index2 += 1 if index2+1 >= N2: @@ -173,15 +174,16 @@ def spike_distance_cython(double[:] t1, spike_events[index] = t2[index2] # first calculate the previous interval end value dt_p2 = dt_f2 # the previous time now was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + + dt_f1*(t2[index2]-t1[index1])) / isi1 s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) # now the next interval start value dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) #s2 = dt_f2 isi2 = t2[index2+1]-t2[index2] # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) else: # t1[index1+1] == t2[index2+1] - generate only one event index1 += 1 index2 += 1 @@ -204,7 +206,7 @@ def spike_distance_cython(double[:] t1, isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) # use only the data added above # could be less than original length due to equal spike times diff --git a/pyspike/distances.py b/pyspike/distances.py index 7a85471..79278b4 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -22,6 +22,7 @@ def add_auxiliary_spikes( spike_train, T_end , T_start=0.0): - T_start: start time of the observation interval (default 0.0) Returns: - spike train with additional spikes at T_start and T_end. + """ assert spike_train[0] >= T_start, \ "Spike train has events before the given start time" @@ -53,9 +54,7 @@ def isi_distance(spikes1, spikes2): assert spikes1[-1]==spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" - # compile and load cython implementation - import pyximport - pyximport.install(setup_args={'include_dirs': [np.get_include()]}) + # cython implementation from cython_distance import isi_distance_cython times, values = isi_distance_cython(spikes1, spikes2) @@ -81,9 +80,7 @@ def spike_distance(spikes1, spikes2): assert spikes1[-1]==spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" - # compile and load cython implementation - import pyximport - pyximport.install(setup_args={'include_dirs': [np.get_include()]}) + # cython implementation from cython_distance import spike_distance_cython times, y_starts, y_ends = spike_distance_cython(spikes1, spikes2) @@ -95,8 +92,8 @@ def spike_distance(spikes1, spikes2): # multi_distance ############################################################ def multi_distance(spike_trains, pair_distance_func, indices=None): - """ Internal implementation detail, use isi_distance_multi or - spike_distance_multi. + """ Internal implementation detail, don't call this function directly, + use isi_distance_multi or spike_distance_multi instead. Computes the multi-variate distance for a set of spike-trains using the pair_dist_func to compute pair-wise distances. That is it computes the diff --git a/pyspike/function.py b/pyspike/function.py index 26ca4b2..5444c36 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -82,46 +82,15 @@ class PieceWiseConstFunc: """ assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" - x_new = np.empty(len(self.x) + len(f.x)) - y_new = np.empty(len(x_new)-1) - x_new[0] = self.x[0] - y_new[0] = self.y[0] + f.y[0] - index1 = 0 - index2 = 0 - index = 0 - while (index1+1 < len(self.y)) and (index2+1 < len(f.y)): - index += 1 - # print(index1+1, self.x[index1+1], self.y[index1+1], x_new[index]) - if self.x[index1+1] < f.x[index2+1]: - index1 += 1 - x_new[index] = self.x[index1] - elif self.x[index1+1] > f.x[index2+1]: - index2 += 1 - x_new[index] = f.x[index2] - else: # self.x[index1+1] == f.x[index2+1]: - index1 += 1 - index2 += 1 - x_new[index] = self.x[index1] - y_new[index] = self.y[index1] + f.y[index2] - # one array reached the end -> copy the contents of the other to the end - if index1+1 < len(self.y): - x_new[index+1:index+1+len(self.x)-index1-1] = self.x[index1+1:] - y_new[index+1:index+1+len(self.y)-index1-1] = self.y[index1+1:] + \ - f.y[-1] - index += len(self.x)-index1-2 - elif index2+1 < len(f.y): - x_new[index+1:index+1+len(f.x)-index2-1] = f.x[index2+1:] - y_new[index+1:index+1+len(f.y)-index2-1] = f.y[index2+1:] + \ - self.y[-1] - index += len(f.x)-index2-2 - else: # both arrays reached the end simultaneously - # only the last x-value missing - x_new[index+1] = self.x[-1] - # the last value is again the end of the interval - # x_new[index+1] = self.x[-1] - # only use the data that was actually filled - self.x = x_new[:index+2] - self.y = y_new[:index+1] + + # python implementation + # from python_backend import add_piece_wise_const_python + # self.x, self.y = add_piece_wise_const_python(self.x, self.y, f.x, f.y) + + # cython version + from cython_add import add_piece_wise_const_cython + self.x, self.y = add_piece_wise_const_cython(self.x, self.y, f.x, f.y) + def mul_scalar(self, fac): """ Multiplies the function with a scalar value @@ -204,73 +173,17 @@ class PieceWiseLinFunc: """ assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" - x_new = np.empty(len(self.x) + len(f.x)) - y1_new = np.empty(len(x_new)-1) - y2_new = np.empty_like(y1_new) - x_new[0] = self.x[0] - y1_new[0] = self.y1[0] + f.y1[0] - index1 = 0 # index for self - index2 = 0 # index for f - index = 0 # index for new - while (index1+1 < len(self.y1)) and (index2+1 < len(f.y1)): - # print(index1+1, self.x[index1+1], self.y[index1+1], x_new[index]) - if self.x[index1+1] < f.x[index2+1]: - # first compute the end value of the previous interval - # linear interpolation of the interval - y = f.y1[index2] + (f.y2[index2]-f.y1[index2]) * \ - (self.x[index1+1]-f.x[index2]) / (f.x[index2+1]-f.x[index2]) - y2_new[index] = self.y2[index1] + y - index1 += 1 - index += 1 - x_new[index] = self.x[index1] - # and the starting value for the next interval - y1_new[index] = self.y1[index1] + y - elif self.x[index1+1] > f.x[index2+1]: - # first compute the end value of the previous interval - # linear interpolation of the interval - y = self.y1[index1] + (self.y2[index1]-self.y1[index1]) * \ - (f.x[index2+1]-self.x[index1]) / \ - (self.x[index1+1]-self.x[index1]) - y2_new[index] = f.y2[index2] + y - index2 += 1 - index += 1 - x_new[index] = f.x[index2] - # and the starting value for the next interval - y1_new[index] = f.y1[index2] + y - else: # self.x[index1+1] == f.x[index2+1]: - y2_new[index] = self.y2[index1] + f.y2[index2] - index1 += 1 - index2 += 1 - index += 1 - x_new[index] = self.x[index1] - y1_new[index] = self.y1[index1] + f.y1[index2] - # one array reached the end -> copy the contents of the other to the end - if index1+1 < len(self.y1): - # compute the linear interpolations values - y = f.y1[index2] + (f.y2[index2]-f.y1[index2]) * \ - (self.x[index1+1:-1]-f.x[index2]) / (f.x[index2+1]-f.x[index2]) - x_new[index+1:index+1+len(self.x)-index1-1] = self.x[index1+1:] - y1_new[index+1:index+1+len(self.y1)-index1-1] = self.y1[index1+1:]+y - y2_new[index:index+len(self.y2)-index1-1] = self.y2[index1:-1] + y - index += len(self.x)-index1-2 - elif index2+1 < len(f.y1): - # compute the linear interpolations values - y = self.y1[index1] + (self.y2[index1]-self.y1[index1]) * \ - (f.x[index2+1:-1]-self.x[index1]) / \ - (self.x[index1+1]-self.x[index1]) - x_new[index+1:index+1+len(f.x)-index2-1] = f.x[index2+1:] - y1_new[index+1:index+1+len(f.y1)-index2-1] = f.y1[index2+1:] + y - y2_new[index:index+len(f.y2)-index2-1] = f.y2[index2:-1] + y - index += len(f.x)-index2-2 - else: # both arrays reached the end simultaneously - # only the last x-value missing - x_new[index+1] = self.x[-1] - # finally, the end value for the last interval - y2_new[index] = self.y2[-1]+f.y2[-1] - # only use the data that was actually filled - self.x = x_new[:index+2] - self.y1 = y1_new[:index+1] - self.y2 = y2_new[:index+1] + + # python implementation + # from python_backend import add_piece_wise_lin_python + # self.x, self.y1, self.y2 = add_piece_wise_lin_python( + # self.x, self.y1, self.y2, f.x, f.y1, f.y2) + + # cython version + from cython_add import add_piece_wise_lin_cython + self.x, self.y1, self.y2 = add_piece_wise_lin_cython( + self.x, self.y1, self.y2, f.x, f.y1, f.y2) + def mul_scalar(self, fac): """ Multiplies the function with a scalar value diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7c8e4e1 --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +""" setup.py + +Handles the compilation of pyx source files + +Copyright 2014, Mario Mulansky + +""" +import os +from distutils.core import setup +from Cython.Build import cythonize + +import numpy.distutils.intelccompiler + +setup( + ext_modules = cythonize("pyspike/*.pyx") +) -- cgit v1.2.3 From b2f1047fb874374527719f34c09bfd0ace2a51dc Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 1 Oct 2014 17:15:57 +0200 Subject: moved python function to python backend --- pyspike/python_backend.py | 304 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 pyspike/python_backend.py diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py new file mode 100644 index 0000000..9134149 --- /dev/null +++ b/pyspike/python_backend.py @@ -0,0 +1,304 @@ +""" python_backend.py + +Collection of python functions that can be used instead of the cython +implementation. + +Copyright 2014, Mario Mulanksy + +""" + +import numpy as np + +from pyspike import PieceWiseConstFunc, PieceWiseLinFunc + + +############################################################ +# isi_distance_python +############################################################ +def isi_distance_python(s1, s2): + """ Plain Python implementation of the isi distance. + """ + # compute the interspike interval + nu1 = s1[1:]-s1[:-1] + nu2 = s2[1:]-s2[:-1] + + # compute the isi-distance + spike_events = np.empty(len(nu1)+len(nu2)) + spike_events[0] = s1[0] + # the values have one entry less - the number of intervals between events + isi_values = np.empty(len(spike_events)-1) + # add the distance of the first events + # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ + # else 1.0 - nu2[0]/nu1[0] + isi_values[0] = (nu1[0]-nu2[0])/max(nu1[0],nu2[0]) + index1 = 0 + index2 = 0 + index = 1 + while True: + # check which spike is next - from s1 or s2 + if s1[index1+1] < s2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1 >= len(nu1): + break + spike_events[index] = s1[index1] + elif s1[index1+1] > s2[index2+1]: + index2 += 1 + if index2 >= len(nu2): + break + spike_events[index] = s2[index2] + else: # s1[index1+1] == s2[index2+1] + index1 += 1 + index2 += 1 + if (index1 >= len(nu1)) or (index2 >= len(nu2)): + break + spike_events[index] = s1[index1] + # compute the corresponding isi-distance + isi_values[index] = (nu1[index1]-nu2[index2]) / \ + max(nu1[index1], nu2[index2]) + index += 1 + # the last event is the interval end + spike_events[index] = s1[-1] + # use only the data added above + # could be less than original length due to equal spike times + return PieceWiseConstFunc(spike_events[:index+1], isi_values[:index]) + + +############################################################ +# get_min_dist +############################################################ +def get_min_dist(spike_time, spike_train, start_index=0): + """ Returns the minimal distance |spike_time - spike_train[i]| + with i>=start_index. + """ + d = abs(spike_time - spike_train[start_index]) + start_index += 1 + while start_index < len(spike_train): + d_temp = abs(spike_time - spike_train[start_index]) + if d_temp > d: + break + else: + d = d_temp + start_index += 1 + return d + + +############################################################ +# spike_distance_python +############################################################ +def spike_distance_python(spikes1, spikes2): + """ 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. + Returns: + - PieceWiseLinFunc describing the spike-distance. + """ + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0]==spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1]==spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + # shorter variables + t1 = spikes1 + t2 = spikes2 + + spike_events = np.empty(len(t1)+len(t2)-2) + spike_events[0] = t1[0] + y_starts = np.empty(len(spike_events)-1) + y_ends = np.empty(len(spike_events)-1) + + index1 = 0 + index2 = 0 + index = 1 + dt_p1 = 0.0 + dt_f1 = get_min_dist(t1[1], t2, 0) + dt_p2 = 0.0 + dt_f2 = get_min_dist(t2[1], t1, 0) + isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) + isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) + s1 = dt_f1*(t1[1]-t1[0])/isi1 + s2 = dt_f2*(t2[1]-t2[0])/isi2 + y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + while True: + # print(index, index1, index2) + if t1[index1+1] < t2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1+1 >= len(t1): + break + spike_events[index] = t1[index1] + # first calculate the previous interval end value + dt_p1 = dt_f1 # the previous time now was the following time before + s1 = dt_p1 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f1 = get_min_dist(t1[index1+1], t2, index2) + isi1 = t1[index1+1]-t1[index1] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + elif t1[index1+1] > t2[index2+1]: + index2 += 1 + if index2+1 >= len(t2): + break + spike_events[index] = t2[index2] + # first calculate the previous interval end value + dt_p2 = dt_f2 # the previous time now was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 + s2 = dt_p2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f2 = get_min_dist(t2[index2+1], t1, index1) + #s2 = dt_f2 + isi2 = t2[index2+1]-t2[index2] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + else: # t1[index1+1] == t2[index2+1] - generate only one event + index1 += 1 + index2 += 1 + if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): + break + assert dt_f2 == 0.0 + assert dt_f1 == 0.0 + spike_events[index] = t1[index1] + y_ends[index-1] = 0.0 + y_starts[index] = 0.0 + dt_p1 = 0.0 + dt_p2 = 0.0 + dt_f1 = get_min_dist(t1[index1+1], t2, index2) + dt_f2 = get_min_dist(t2[index2+1], t1, index1) + isi1 = t1[index1+1]-t1[index1] + isi2 = t2[index2+1]-t2[index2] + index += 1 + # the last event is the interval end + spike_events[index] = t1[-1] + # the ending value of the last interval + isi1 = max(t1[-1]-t1[-2], t1[-2]-t1[-3]) + isi2 = max(t2[-1]-t2[-2], t2[-2]-t2[-3]) + s1 = dt_p1*(t1[-1]-t1[-2])/isi1 + s2 = dt_p2*(t2[-1]-t2[-2])/isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # use only the data added above + # could be less than original length due to equal spike times + return PieceWiseLinFunc(spike_events[:index+1], + y_starts[:index], y_ends[:index]) + + +############################################################ +# 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] -- cgit v1.2.3 From 99730806c22f79089d4cdaf2a1ce713712ad557b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 1 Oct 2014 18:21:36 +0200 Subject: added multithreaded version of multi_distance (slow) --- pyspike/cython_distance.pyx | 215 ++++++++++++++++++++-------------------- pyspike/distances.py | 233 ++++++++++---------------------------------- 2 files changed, 162 insertions(+), 286 deletions(-) diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 6edcc01..23ffc37 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -54,38 +54,41 @@ def isi_distance_cython(double[:] s1, spike_events[0] = s1[0] # the values have one entry less - the number of intervals between events isi_values = np.empty(N1+N2-1) - isi_values[0] = (nu1-nu2)/max(nu1,nu2) - index1 = 0 - index2 = 0 - index = 1 - while True: - # check which spike is next - from s1 or s2 - if s1[index1+1] < s2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1 >= N1: - break - spike_events[index] = s1[index1] - nu1 = s1[index1+1]-s1[index1] - elif s1[index1+1] > s2[index2+1]: - index2 += 1 - if index2 >= N2: - break - spike_events[index] = s2[index2] - nu2 = s2[index2+1]-s2[index2] - else: # s1[index1+1] == s2[index2+1] - index1 += 1 - index2 += 1 - if (index1 >= N1) or (index2 >= N2): - break - spike_events[index] = s1[index1] - nu1 = s1[index1+1]-s1[index1] - nu2 = s2[index2+1]-s2[index2] - # compute the corresponding isi-distance - isi_values[index] = (nu1 - nu2) / max(nu1, nu2) - index += 1 - # the last event is the interval end - spike_events[index] = s1[N1] + + with nogil: # release the interpreter to allow multithreading + isi_values[0] = (nu1-nu2)/max(nu1,nu2) + index1 = 0 + index2 = 0 + index = 1 + while True: + # check which spike is next - from s1 or s2 + if s1[index1+1] < s2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1 >= N1: + break + spike_events[index] = s1[index1] + nu1 = s1[index1+1]-s1[index1] + elif s1[index1+1] > s2[index2+1]: + index2 += 1 + if index2 >= N2: + break + spike_events[index] = s2[index2] + nu2 = s2[index2+1]-s2[index2] + else: # s1[index1+1] == s2[index2+1] + index1 += 1 + index2 += 1 + if (index1 >= N1) or (index2 >= N2): + break + spike_events[index] = s1[index1] + nu1 = s1[index1+1]-s1[index1] + nu2 = s2[index2+1]-s2[index2] + # compute the corresponding isi-distance + isi_values[index] = (nu1 - nu2) / max(nu1, nu2) + index += 1 + # the last event is the interval end + spike_events[index] = s1[N1] + # end nogil return spike_events[:index+1], isi_values[:index] @@ -98,7 +101,7 @@ cdef inline double get_min_dist_cython(double spike_time, # use memory view to ensure inlining # np.ndarray[DTYPE_t,ndim=1] spike_train, int N, - int start_index=0): + int start_index=0) nogil: """ Returns the minimal distance |spike_time - spike_train[i]| with i>=start_index. """ @@ -136,78 +139,80 @@ def spike_distance_cython(double[:] t1, y_starts = np.empty(len(spike_events)-1) y_ends = np.empty(len(spike_events)-1) - index1 = 0 - index2 = 0 - index = 1 - dt_p1 = 0.0 - dt_f1 = get_min_dist_cython(t1[1], t2, N2, 0) - dt_p2 = 0.0 - dt_f2 = get_min_dist_cython(t2[1], t1, N1, 0) - isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) - isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) - s1 = dt_f1*(t1[1]-t1[0])/isi1 - s2 = dt_f2*(t2[1]-t2[0])/isi2 - y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - while True: - # print(index, index1, index2) - if t1[index1+1] < t2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1+1 >= N1: - break - spike_events[index] = t1[index1] - # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time now was the following time before - s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + - dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) - # now the next interval start value - dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) - isi1 = t1[index1+1]-t1[index1] - # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) - elif t1[index1+1] > t2[index2+1]: - index2 += 1 - if index2+1 >= N2: - break - spike_events[index] = t2[index2] - # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time now was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + - dt_f1*(t2[index2]-t1[index1])) / isi1 - s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) - # now the next interval start value - dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) - #s2 = dt_f2 - isi2 = t2[index2+1]-t2[index2] - # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) - else: # t1[index1+1] == t2[index2+1] - generate only one event - index1 += 1 - index2 += 1 - if (index1+1 >= N1) or (index2+1 >= N2): - break - spike_events[index] = t1[index1] - y_ends[index-1] = 0.0 - y_starts[index] = 0.0 - dt_p1 = 0.0 - dt_p2 = 0.0 - dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) - dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) - isi1 = t1[index1+1]-t1[index1] - isi2 = t2[index2+1]-t2[index2] - index += 1 - # the last event is the interval end - spike_events[index] = t1[N1-1] - # the ending value of the last interval - isi1 = max(t1[N1-1]-t1[N1-2], t1[N1-2]-t1[N1-3]) - isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) - s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 - s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) + with nogil: # release the interpreter to allow multithreading + index1 = 0 + index2 = 0 + index = 1 + dt_p1 = 0.0 + dt_f1 = get_min_dist_cython(t1[1], t2, N2, 0) + dt_p2 = 0.0 + dt_f2 = get_min_dist_cython(t2[1], t1, N1, 0) + isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) + isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) + s1 = dt_f1*(t1[1]-t1[0])/isi1 + s2 = dt_f2*(t2[1]-t2[0])/isi2 + y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + while True: + # print(index, index1, index2) + if t1[index1+1] < t2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1+1 >= N1: + break + spike_events[index] = t1[index1] + # first calculate the previous interval end value + dt_p1 = dt_f1 # the previous time now was the following time before + s1 = dt_p1 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) + # now the next interval start value + dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) + isi1 = t1[index1+1]-t1[index1] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) + elif t1[index1+1] > t2[index2+1]: + index2 += 1 + if index2+1 >= N2: + break + spike_events[index] = t2[index2] + # first calculate the previous interval end value + dt_p2 = dt_f2 # the previous time now was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + + dt_f1*(t2[index2]-t1[index1])) / isi1 + s2 = dt_p2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) + # now the next interval start value + dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) + #s2 = dt_f2 + isi2 = t2[index2+1]-t2[index2] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) + else: # t1[index1+1] == t2[index2+1] - generate only one event + index1 += 1 + index2 += 1 + if (index1+1 >= N1) or (index2+1 >= N2): + break + spike_events[index] = t1[index1] + y_ends[index-1] = 0.0 + y_starts[index] = 0.0 + dt_p1 = 0.0 + dt_p2 = 0.0 + dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) + dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) + isi1 = t1[index1+1]-t1[index1] + isi2 = t2[index2+1]-t2[index2] + index += 1 + # the last event is the interval end + spike_events[index] = t1[N1-1] + # the ending value of the last interval + isi1 = max(t1[N1-1]-t1[N1-2], t1[N1-2]-t1[N1-3]) + isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) + s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 + s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(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] diff --git a/pyspike/distances.py b/pyspike/distances.py index 79278b4..35650f7 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -6,6 +6,7 @@ Copyright 2014, Mario Mulansky """ import numpy as np +import threading from pyspike import PieceWiseConstFunc, PieceWiseLinFunc @@ -126,6 +127,57 @@ def multi_distance(spike_trains, pair_distance_func, indices=None): return average_dist +############################################################ +# multi_distance_par +############################################################ +def multi_distance_par(spike_trains, pair_distance_func, indices=None): + """ parallel implementation of the multi-distance. Not currently used as + it does not improve the performance. + """ + + num_threads = 2 + + lock = threading.Lock() + def run(spike_trains, index_pairs, average_dist): + (i,j) = index_pairs[0] + # print(i,j) + this_avrg = pair_distance_func(spike_trains[i], spike_trains[j]) + for (i,j) in index_pairs[1:]: + # print(i,j) + current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + this_avrg.add(current_dist) + with lock: + average_dist.add(this_avrg) + + if indices==None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(i,j) for i in indices for j in indices[i+1:]] + num_pairs = len(pairs) + + # start with first pair + (i,j) = pairs[0] + average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + # remove the one we already computed + pairs = pairs[1:] + # distribute the rest into num_threads pieces + clustered_pairs = [ pairs[i::num_threads] for i in xrange(num_threads) ] + + threads = [] + for pairs in clustered_pairs: + t = threading.Thread(target=run, args=(spike_trains, pairs, average_dist)) + threads.append(t) + t.start() + for t in threads: + t.join() + average_dist.mul_scalar(1.0/num_pairs) # normalize + return average_dist + + ############################################################ # isi_distance_multi ############################################################ @@ -161,184 +213,3 @@ def spike_distance_multi(spike_trains, indices=None): """ return multi_distance(spike_trains, spike_distance, indices) - -############################################################ -############################################################ -# VANILLA PYTHON IMPLEMENTATIONS OF ISI AND SPIKE DISTANCE -############################################################ -############################################################ - - -############################################################ -# isi_distance_python -############################################################ -def isi_distance_python(s1, s2): - """ Plain Python implementation of the isi distance. - """ - # compute the interspike interval - nu1 = s1[1:]-s1[:-1] - nu2 = s2[1:]-s2[:-1] - - # compute the isi-distance - spike_events = np.empty(len(nu1)+len(nu2)) - spike_events[0] = s1[0] - # the values have one entry less - the number of intervals between events - isi_values = np.empty(len(spike_events)-1) - # add the distance of the first events - # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ - # else 1.0 - nu2[0]/nu1[0] - isi_values[0] = (nu1[0]-nu2[0])/max(nu1[0],nu2[0]) - index1 = 0 - index2 = 0 - index = 1 - while True: - # check which spike is next - from s1 or s2 - if s1[index1+1] < s2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1 >= len(nu1): - break - spike_events[index] = s1[index1] - elif s1[index1+1] > s2[index2+1]: - index2 += 1 - if index2 >= len(nu2): - break - spike_events[index] = s2[index2] - else: # s1[index1+1] == s2[index2+1] - index1 += 1 - index2 += 1 - if (index1 >= len(nu1)) or (index2 >= len(nu2)): - break - spike_events[index] = s1[index1] - # compute the corresponding isi-distance - isi_values[index] = (nu1[index1]-nu2[index2]) / \ - max(nu1[index1], nu2[index2]) - index += 1 - # the last event is the interval end - spike_events[index] = s1[-1] - # use only the data added above - # could be less than original length due to equal spike times - return PieceWiseConstFunc(spike_events[:index+1], isi_values[:index]) - - -############################################################ -# get_min_dist -############################################################ -def get_min_dist(spike_time, spike_train, start_index=0): - """ Returns the minimal distance |spike_time - spike_train[i]| - with i>=start_index. - """ - d = abs(spike_time - spike_train[start_index]) - start_index += 1 - while start_index < len(spike_train): - d_temp = abs(spike_time - spike_train[start_index]) - if d_temp > d: - break - else: - d = d_temp - start_index += 1 - return d - - -############################################################ -# spike_distance_python -############################################################ -def spike_distance_python(spikes1, spikes2): - """ 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. - Returns: - - PieceWiseLinFunc describing the spike-distance. - """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0]==spikes2[0], \ - "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1]==spikes2[-1], \ - "Given spike trains seems not to have auxiliary spikes!" - # shorter variables - t1 = spikes1 - t2 = spikes2 - - spike_events = np.empty(len(t1)+len(t2)-2) - spike_events[0] = t1[0] - y_starts = np.empty(len(spike_events)-1) - y_ends = np.empty(len(spike_events)-1) - - index1 = 0 - index2 = 0 - index = 1 - dt_p1 = 0.0 - dt_f1 = get_min_dist(t1[1], t2, 0) - dt_p2 = 0.0 - dt_f2 = get_min_dist(t2[1], t1, 0) - isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) - isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) - s1 = dt_f1*(t1[1]-t1[0])/isi1 - s2 = dt_f2*(t2[1]-t2[0])/isi2 - y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - while True: - # print(index, index1, index2) - if t1[index1+1] < t2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1+1 >= len(t1): - break - spike_events[index] = t1[index1] - # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time now was the following time before - s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - # now the next interval start value - dt_f1 = get_min_dist(t1[index1+1], t2, index2) - isi1 = t1[index1+1]-t1[index1] - # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - elif t1[index1+1] > t2[index2+1]: - index2 += 1 - if index2+1 >= len(t2): - break - spike_events[index] = t2[index2] - # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time now was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 - s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - # now the next interval start value - dt_f2 = get_min_dist(t2[index2+1], t1, index1) - #s2 = dt_f2 - isi2 = t2[index2+1]-t2[index2] - # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - else: # t1[index1+1] == t2[index2+1] - generate only one event - index1 += 1 - index2 += 1 - if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): - break - assert dt_f2 == 0.0 - assert dt_f1 == 0.0 - spike_events[index] = t1[index1] - y_ends[index-1] = 0.0 - y_starts[index] = 0.0 - dt_p1 = 0.0 - dt_p2 = 0.0 - dt_f1 = get_min_dist(t1[index1+1], t2, index2) - dt_f2 = get_min_dist(t2[index2+1], t1, index1) - isi1 = t1[index1+1]-t1[index1] - isi2 = t2[index2+1]-t2[index2] - index += 1 - # the last event is the interval end - spike_events[index] = t1[-1] - # the ending value of the last interval - isi1 = max(t1[-1]-t1[-2], t1[-2]-t1[-3]) - isi2 = max(t2[-1]-t2[-2], t2[-2]-t2[-3]) - s1 = dt_p1*(t1[-1]-t1[-2])/isi1 - s2 = dt_p2*(t2[-1]-t2[-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - # use only the data added above - # could be less than original length due to equal spike times - return PieceWiseLinFunc(spike_events[:index+1], - y_starts[:index], y_ends[:index]) -- cgit v1.2.3 From 7bbbf06c23e8eb727f45dc47d6613fb7d03f4c8f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 2 Oct 2014 14:30:02 +0200 Subject: +isi distance matrix with examples --- examples/isi_matrix.py | 21 +++++++++++++++++++++ pyspike/__init__.py | 2 +- pyspike/cython_distance.pyx | 5 +++-- pyspike/distances.py | 28 +++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 examples/isi_matrix.py diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py new file mode 100644 index 0000000..a149cd6 --- /dev/null +++ b/examples/isi_matrix.py @@ -0,0 +1,21 @@ +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt + +import pyspike as spk + +# first load the data +spike_trains = [] +spike_file = open("SPIKY_testdata.txt", 'r') +for line in spike_file: + spike_trains.append(spk.add_auxiliary_spikes( + spk.spike_train_from_string(line), 4000)) + +print(len(spike_trains)) + +m = spk.isi_distance_matrix(spike_trains) + +plt.imshow(m, interpolation='none') +plt.show() + diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 2143bdc..21005e9 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -2,5 +2,5 @@ __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc from distances import add_auxiliary_spikes, isi_distance, spike_distance, \ - isi_distance_multi, spike_distance_multi + isi_distance_multi, spike_distance_multi, isi_distance_matrix from spikes import spike_train_from_string, merge_spike_trains diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 23ffc37..2be8525 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -29,6 +29,7 @@ import numpy as np cimport numpy as np from libc.math cimport fabs +from libc.math cimport fmax DTYPE = np.float ctypedef np.float_t DTYPE_t @@ -56,7 +57,7 @@ def isi_distance_cython(double[:] s1, isi_values = np.empty(N1+N2-1) with nogil: # release the interpreter to allow multithreading - isi_values[0] = (nu1-nu2)/max(nu1,nu2) + isi_values[0] = (nu1-nu2)/fmax(nu1,nu2) index1 = 0 index2 = 0 index = 1 @@ -84,7 +85,7 @@ def isi_distance_cython(double[:] s1, nu1 = s1[index1+1]-s1[index1] nu2 = s2[index2+1]-s2[index2] # compute the corresponding isi-distance - isi_values[index] = (nu1 - nu2) / max(nu1, nu2) + isi_values[index] = (nu1 - nu2) / fmax(nu1, nu2) index += 1 # the last event is the interval end spike_events[index] = s1[N1] diff --git a/pyspike/distances.py b/pyspike/distances.py index 35650f7..f78c0d4 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -206,10 +206,36 @@ def spike_distance_multi(spike_trains, indices=None): where the sum goes over all pairs Args: - spike_trains: list of spike trains - - indices: list of indices defining which spike trains to use, + - indices: list of indices defining which spike-trains to use, if None all given spike trains are used (default=None) Returns: - A PieceWiseLinFunc representing the averaged spike distance S """ return multi_distance(spike_trains, spike_distance, indices) + +def isi_distance_matrix(spike_trains, indices=None): + """ Computes the average isi-distance of all pairs of spike-trains. + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike-trains to use + if None all given spike-trains are used (default=None) + Return: + - a 2D array of size len(indices)*len(indices) containing the average + pair-wise isi-distance + """ + if indices==None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(i,j) for i in indices for j in indices[i+1:]] + + distance_matrix = np.zeros((len(indices), len(indices))) + for i,j in pairs: + d = isi_distance(spike_trains[i], spike_trains[j]).abs_avrg() + distance_matrix[i,j] = d + distance_matrix[j,i] = d + return distance_matrix -- cgit v1.2.3 From cefbc57be0a8badc21453ea460608b3caf344673 Mon Sep 17 00:00:00 2001 From: mariomulansky Date: Fri, 10 Oct 2014 12:59:26 +0000 Subject: Readme edited online with Bitbucket --- Readme | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Readme b/Readme index 786ee87..c82233b 100644 --- a/Readme +++ b/Readme @@ -1,7 +1,6 @@ -Python Codes for Measuring Spike Synchrony - -before using the module you should compile the cython backend via: - -python setup.py build_ext --inplace - -Mario Mulansky (2014) +PySpike +======= + +PySpike is a Python library for numerical analysis of spike train similarity. Its core functionality are the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). Additionally, it allows to compute multi-variate spike train distances, averaging and general spike train processing. + +All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). -- cgit v1.2.3 From 62f792fa52801234d4f9c33800a44b0308e9b8ab Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 15:18:48 +0200 Subject: Basic readme docs --- Readme | 6 ------ Readme.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) delete mode 100644 Readme create mode 100644 Readme.md diff --git a/Readme b/Readme deleted file mode 100644 index c82233b..0000000 --- a/Readme +++ /dev/null @@ -1,6 +0,0 @@ -PySpike -======= - -PySpike is a Python library for numerical analysis of spike train similarity. Its core functionality are the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). Additionally, it allows to compute multi-variate spike train distances, averaging and general spike train processing. - -All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..368eef4 --- /dev/null +++ b/Readme.md @@ -0,0 +1,37 @@ +# PySpike + +PySpike is a Python library for numerical analysis of spike train similarity. +Its core functionality are the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). +Additionally, it allows to compute multi-variate spike train distances, averaging and general spike train processing. + +All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). + +## Requirements and Installation + +To use PySpike you need Python installed with the following additional packages: + +- numpy +- scipy +- matplotlib +- cython + +In particular, make sure that [cython](http://www.cython.org) is configured properly and able to locate a C compiler. + +To install PySpike, simply download the source, i.e. via git, and run the setup.py script: + git clone ... + cd PySpike + python setup.py build_ext --inplace + +## Loading spike trains + + +## Computing bi-variate distances + + +## Computing multi-variate distances + + +## Plotting + + +## Averaging \ No newline at end of file -- cgit v1.2.3 From a769a03d089ac0c61e2155239a28665c9316e14a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:04:04 +0200 Subject: added load_txt function, some restructuring --- Readme.md | 2 +- examples/SPIKY_testdata.txt | 3 ++ examples/test_data.py | 11 +++--- pyspike/__init__.py | 5 +-- pyspike/distances.py | 25 -------------- pyspike/spikes.py | 72 ++++++++++++++++++++++++++++++++++++-- test/SPIKY_testdata.txt | 3 ++ test/test_distance.py | 24 +++++-------- test/test_merge_spikes.py | 49 -------------------------- test/test_spikes.py | 84 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 176 insertions(+), 102 deletions(-) delete mode 100644 test/test_merge_spikes.py create mode 100644 test/test_spikes.py diff --git a/Readme.md b/Readme.md index 368eef4..8b84ebd 100644 --- a/Readme.md +++ b/Readme.md @@ -1,7 +1,7 @@ # PySpike PySpike is a Python library for numerical analysis of spike train similarity. -Its core functionality are the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). +Its core functionality is the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). Additionally, it allows to compute multi-variate spike train distances, averaging and general spike train processing. All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). diff --git a/examples/SPIKY_testdata.txt b/examples/SPIKY_testdata.txt index 8fa3fcf..c8bea67 100755 --- a/examples/SPIKY_testdata.txt +++ b/examples/SPIKY_testdata.txt @@ -1,7 +1,10 @@ 64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 +# test comment 69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 +# empty line + 59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 diff --git a/examples/test_data.py b/examples/test_data.py index ff7b510..dcd0f20 100644 --- a/examples/test_data.py +++ b/examples/test_data.py @@ -7,17 +7,14 @@ import matplotlib.pyplot as plt import pyspike as spk -# first load the data -spike_trains = [] -spike_file = open("SPIKY_testdata.txt", 'r') -for line in spike_file: - spike_trains.append(spk.spike_train_from_string(line)) +spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=(0,4000)) # plot the spike time for (i,spikes) in enumerate(spike_trains): plt.plot(spikes, i*np.ones_like(spikes), 'o') -f = spk.isi_distance(spike_trains[0], spike_trains[1], 4000) +f = spk.isi_distance(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() plt.figure() @@ -27,7 +24,7 @@ print("Average: %.8f" % f.avrg()) print("Absolute average: %.8f" % f.abs_avrg()) -f = spk.spike_distance(spike_trains[0], spike_trains[1], 4000) +f = spk.spike_distance(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() print(x) print(y) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 21005e9..2703f65 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,6 +1,7 @@ __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc -from distances import add_auxiliary_spikes, isi_distance, spike_distance, \ +from distances import isi_distance, spike_distance, \ isi_distance_multi, spike_distance_multi, isi_distance_matrix -from spikes import spike_train_from_string, merge_spike_trains +from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ + spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index f78c0d4..da603ad 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -11,31 +11,6 @@ import threading from pyspike import PieceWiseConstFunc, PieceWiseLinFunc -############################################################ -# add_auxiliary_spikes -############################################################ -def add_auxiliary_spikes( spike_train, T_end , T_start=0.0): - """ Adds spikes at the beginning (T_start) and end (T_end) of the - observation interval. - Args: - - spike_train: ordered array of spike times - - T_end: end time of the observation interval - - T_start: start time of the observation interval (default 0.0) - Returns: - - spike train with additional spikes at T_start and T_end. - - """ - assert spike_train[0] >= T_start, \ - "Spike train has events before the given start time" - assert spike_train[-1] <= T_end, \ - "Spike train has events after the given end time" - if spike_train[0] != T_start: - spike_train = np.insert(spike_train, 0, T_start) - if spike_train[-1] != T_end: - spike_train = np.append(spike_train, T_end) - return spike_train - - ############################################################ # isi_distance ############################################################ diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 70b48ff..502c460 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -7,12 +7,46 @@ Copyright 2014, Mario Mulansky import numpy as np + +############################################################ +# add_auxiliary_spikes +############################################################ +def add_auxiliary_spikes(spike_train, time_interval): + """ Adds spikes at the beginning and end of the given time interval. + Args: + - spike_train: ordered array of spike times + - time_interval: A pair (T_start, T_end) of values representing the start + and end time of the spike train measurement or a single value representing + the end time, the T_start is then assuemd as 0. Auxiliary spikes will be + added to the spike train at the beginning and end of this interval. + Returns: + - spike train with additional spikes at T_start and T_end. + + """ + try: + T_start = time_interval[0] + T_end = time_interval[1] + except: + T_start = 0 + T_end = time_interval + + assert spike_train[0] >= T_start, \ + "Spike train has events before the given start time" + assert spike_train[-1] <= T_end, \ + "Spike train has events after the given end time" + if spike_train[0] != T_start: + spike_train = np.insert(spike_train, 0, T_start) + if spike_train[-1] != T_end: + spike_train = np.append(spike_train, T_end) + return spike_train + + ############################################################ # spike_train_from_string ############################################################ def spike_train_from_string(s, sep=' '): """ Converts a string of times into an array of spike times. - Params: + Args: - s: the string with (ordered) spike times - sep: The separator between the time numbers. Returns: @@ -21,12 +55,46 @@ def spike_train_from_string(s, sep=' '): return np.fromstring(s, sep=sep) +############################################################ +# load_spike_trains_txt +############################################################ +def load_spike_trains_from_txt(file_name, time_interval=None, + separator=' ', comment='#'): + """ Loads a number of spike trains from a text file. Each line of the text + file should contain one spike train as a sequence of spike times separated + by `separator`. Empty lines as well as lines starting with `comment` are + neglected. The `time_interval` represents the start and the end of the spike + trains and it is used to add auxiliary spikes at the beginning and end of + each spike train. However, if `time_interval == None`, no auxiliary spikes + are added, but note that the Spike and ISI distance both require auxiliary + spikes. + Args: + - file_name: The name of the text file. + - time_interval: A pair (T_start, T_end) of values representing the start + and end time of the spike train measurement or a single value representing + the end time, the T_start is then assuemd as 0. Auxiliary spikes will be + added to the spike train at the beginning and end of this interval. + - separator: The character used to seprate the values in the text file. + - comment: Lines starting with this character are ignored. + """ + spike_trains = [] + spike_file = open(file_name, 'r') + for line in spike_file: + if len(line) > 1 and not line.startswith(comment): + # use only the lines with actual data and not commented + spike_train = spike_train_from_string(line) + if not time_interval == None: # add auxiliary spikes if times given + spike_train = add_auxiliary_spikes(spike_train, time_interval) + spike_trains.append(spike_train) + return spike_trains + + ############################################################ # merge_spike_trains ############################################################ def merge_spike_trains(spike_trains): """ Merges a number of spike trains into a single spike train. - Params: + Args: - spike_trains: list of arrays of spike times Returns: - array with the merged spike times diff --git a/test/SPIKY_testdata.txt b/test/SPIKY_testdata.txt index 8fa3fcf..c8bea67 100755 --- a/test/SPIKY_testdata.txt +++ b/test/SPIKY_testdata.txt @@ -1,7 +1,10 @@ 64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 +# test comment 69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 +# empty line + 59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 diff --git a/test/test_distance.py b/test/test_distance.py index c43f0b3..92b99ae 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -13,14 +13,6 @@ from numpy.testing import assert_equal, assert_array_almost_equal import pyspike as spk -def test_auxiliary_spikes(): - t = np.array([0.2, 0.4, 0.6, 0.7]) - t_aux = spk.add_auxiliary_spikes(t, T_end=1.0, T_start=0.1) - assert_equal(t_aux, [0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) - t_aux = spk.add_auxiliary_spikes(t_aux, 1.0) - assert_equal(t_aux, [0.0, 0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) - - def test_isi(): # generate two spike trains: t1 = np.array([0.2, 0.4, 0.6, 0.7]) @@ -31,8 +23,8 @@ def test_isi(): expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, -0.25/0.35, -0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) + t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) f = spk.isi_distance(t1, t2) # print("ISI: ", f.y) @@ -47,8 +39,8 @@ def test_isi(): expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] expected_isi = [0.1/0.2, -0.1/0.3, -0.1/0.3, 0.1/0.2, 0.1/0.2, -0.0/0.5] - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) + t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) f = spk.isi_distance(t1, t2) assert_equal(f.x, expected_times) @@ -72,8 +64,8 @@ def test_spike(): expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) + t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) f = spk.spike_distance(t1, t2) assert_equal(f.x, expected_times) @@ -92,8 +84,8 @@ def test_spike(): expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) + t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) + t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) f = spk.spike_distance(t1, t2) assert_equal(f.x, expected_times) diff --git a/test/test_merge_spikes.py b/test/test_merge_spikes.py deleted file mode 100644 index 3162700..0000000 --- a/test/test_merge_spikes.py +++ /dev/null @@ -1,49 +0,0 @@ -""" test_merge_spikes.py - -Tests merging spikes - -Copyright 2014, Mario Mulansky -""" -from __future__ import print_function -import numpy as np - -import pyspike as spk - -def check_merged_spikes( merged_spikes, spike_trains ): - # create a flat array with all spike events - all_spikes = np.array([]) - for spike_train in spike_trains: - all_spikes = np.append(all_spikes, spike_train) - indices = np.zeros_like(all_spikes, dtype='bool') - # check if we find all the spike events in the original spike trains - for x in merged_spikes: - i = np.where(all_spikes == x)[0][0] # the first axis and the first entry - # change to something impossible so we dont find this event again - all_spikes[i] = -1.0 - indices[i] = True - assert( indices.all() ) - -def test_merge_spike_trains(): - - # first load the data - spike_trains = [] - spike_file = open("SPIKY_testdata.txt", 'r') - for line in spike_file: - spike_trains.append(spk.spike_train_from_string(line)) - - spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) - # test if result is sorted - assert((spikes == np.sort(spikes)).all()) - # check merging - check_merged_spikes( spikes, [spike_trains[0], spike_trains[1]] ) - - spikes = spk.merge_spike_trains(spike_trains) - # test if result is sorted - assert((spikes == np.sort(spikes)).all()) - # check merging - check_merged_spikes( spikes, spike_trains ) - - -if __name__ == "main": - test_merge_spike_trains() - diff --git a/test/test_spikes.py b/test/test_spikes.py new file mode 100644 index 0000000..dca580f --- /dev/null +++ b/test/test_spikes.py @@ -0,0 +1,84 @@ +""" test_load.py + +Test loading of spike trains from text files + +Copyright 2014, Mario Mulansky +""" + +from __future__ import print_function +import numpy as np +from numpy.testing import assert_equal + +import pyspike as spk + + +def test_auxiliary_spikes(): + t = np.array([0.2, 0.4, 0.6, 0.7]) + t_aux = spk.add_auxiliary_spikes(t, time_interval=(0.1, 1.0)) + assert_equal(t_aux, [0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) + t_aux = spk.add_auxiliary_spikes(t_aux, time_interval=(0.0, 1.0)) + assert_equal(t_aux, [0.0, 0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) + + +def test_load_from_txt(): + spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=(0,4000)) + assert len(spike_trains) == 40 + + # check the first spike train + spike_times = [0, 64.886, 305.81, 696, 937.77, 1059.7, 1322.2, 1576.1, + 1808.1, 2121.5, 2381.1, 2728.6, 2966.9, 3223.7, 3473.7, + 3644.3, 3936.3, 4000] + assert_equal(spike_times, spike_trains[0]) + + # check auxiliary spikes + for spike_train in spike_trains: + assert spike_train[0] == 0.0 + assert spike_train[-1] == 4000 + + # load without adding auxiliary spikes + spike_trains2 = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=None) + assert len(spike_trains2) == 40 + # check auxiliary spikes + for i in xrange(len(spike_trains)): + assert len(spike_trains[i]) == len(spike_trains2[i])+2 # two spikes less + + +def check_merged_spikes( merged_spikes, spike_trains ): + # create a flat array with all spike events + all_spikes = np.array([]) + for spike_train in spike_trains: + all_spikes = np.append(all_spikes, spike_train) + indices = np.zeros_like(all_spikes, dtype='bool') + # check if we find all the spike events in the original spike trains + for x in merged_spikes: + i = np.where(all_spikes == x)[0][0] # the first axis and the first entry + # change to something impossible so we dont find this event again + all_spikes[i] = -1.0 + indices[i] = True + assert( indices.all() ) + + +def test_merge_spike_trains(): + # first load the data + spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=(0,4000)) + + spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) + # test if result is sorted + assert((spikes == np.sort(spikes)).all()) + # check merging + check_merged_spikes( spikes, [spike_trains[0], spike_trains[1]] ) + + spikes = spk.merge_spike_trains(spike_trains) + # test if result is sorted + assert((spikes == np.sort(spikes)).all()) + # check merging + check_merged_spikes( spikes, spike_trains ) + +if __name__ == "main": + test_auxiliary_spikes() + test_load_from_txt() + test_merge_spike_trains() + -- cgit v1.2.3 From 7a8f12ab65ceea0211f6f24c26042768ef005302 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:09:57 +0200 Subject: fixed the examples --- examples/isi_matrix.py | 8 ++------ examples/test_merge.py | 7 ++----- test/test_distance.py | 16 ++++++++-------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py index a149cd6..0d6e185 100644 --- a/examples/isi_matrix.py +++ b/examples/isi_matrix.py @@ -5,12 +5,8 @@ import matplotlib.pyplot as plt import pyspike as spk -# first load the data -spike_trains = [] -spike_file = open("SPIKY_testdata.txt", 'r') -for line in spike_file: - spike_trains.append(spk.add_auxiliary_spikes( - spk.spike_train_from_string(line), 4000)) +# first load the data, interval ending time = 4000, start=0 (default) +spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", 4000) print(len(spike_trains)) diff --git a/examples/test_merge.py b/examples/test_merge.py index 1186062..0c34608 100644 --- a/examples/test_merge.py +++ b/examples/test_merge.py @@ -6,11 +6,8 @@ import matplotlib.pyplot as plt import pyspike as spk -# first load the data -spike_trains = [] -spike_file = open("SPIKY_testdata.txt", 'r') -for line in spike_file: - spike_trains.append(spk.spike_train_from_string(line)) +# first load the data, ending time = 4000 +spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", 4000) spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) diff --git a/test/test_distance.py b/test/test_distance.py index 92b99ae..84d0af9 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -23,8 +23,8 @@ def test_isi(): expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, -0.25/0.35, -0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] - t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) - t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_distance(t1, t2) # print("ISI: ", f.y) @@ -39,8 +39,8 @@ def test_isi(): expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] expected_isi = [0.1/0.2, -0.1/0.3, -0.1/0.3, 0.1/0.2, 0.1/0.2, -0.0/0.5] - t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) - t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_distance(t1, t2) assert_equal(f.x, expected_times) @@ -64,8 +64,8 @@ def test_spike(): expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) - t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_distance(t1, t2) assert_equal(f.x, expected_times) @@ -84,8 +84,8 @@ def test_spike(): expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - t1 = spk.add_auxiliary_spikes(t1, (0.0,1.0)) - t2 = spk.add_auxiliary_spikes(t2, (0.0,1.0)) + t1 = spk.add_auxiliary_spikes(t1, 1.0) + t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_distance(t1, t2) assert_equal(f.x, expected_times) -- cgit v1.2.3 From c1c5403b8274bd19aa1e71933cfaefe1ba622e59 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:23:28 +0200 Subject: added License note in headers --- examples/isi_matrix.py | 11 +++++++++++ examples/merge.py | 28 ++++++++++++++++++++++++++++ examples/plot.py | 42 ++++++++++++++++++++++++++++++++++++++++++ examples/test_data.py | 34 ---------------------------------- examples/test_merge.py | 20 -------------------- pyspike/__init__.py | 6 ++++++ pyspike/cython_distance.pyx | 3 +++ pyspike/distances.py | 2 ++ pyspike/function.py | 2 ++ pyspike/python_backend.py | 4 +++- pyspike/spikes.py | 2 ++ test/test_distance.py | 3 +++ test/test_function.py | 2 ++ 13 files changed, 104 insertions(+), 55 deletions(-) create mode 100644 examples/merge.py create mode 100644 examples/plot.py delete mode 100644 examples/test_data.py delete mode 100644 examples/test_merge.py diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py index 0d6e185..3297d3d 100644 --- a/examples/isi_matrix.py +++ b/examples/isi_matrix.py @@ -1,3 +1,14 @@ +""" isi_matrix.py + +Simple example showing how to compute the isi distance matrix of a set of spike +trains. + +Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) +""" + + from __future__ import print_function import numpy as np diff --git a/examples/merge.py b/examples/merge.py new file mode 100644 index 0000000..55c7f0a --- /dev/null +++ b/examples/merge.py @@ -0,0 +1,28 @@ +""" merge.py + +Simple example showing the merging of two spike trains. + +Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) +""" + +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt + +import pyspike as spk + +# first load the data, ending time = 4000 +spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", 4000) + +spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) + +print(spikes) + +plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') +plt.plot(spike_trains[1], np.ones_like(spike_trains[1]), 'x') +plt.plot(spikes, 2*np.ones_like(spikes), 'o') + +plt.show() diff --git a/examples/plot.py b/examples/plot.py new file mode 100644 index 0000000..d7e2173 --- /dev/null +++ b/examples/plot.py @@ -0,0 +1,42 @@ +""" plot.py + +Simple example showing how to load and plot spike trains and their distances. + +Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) +""" + + +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt + +import pyspike as spk + +spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=(0,4000)) + +# plot the spike time +for (i,spikes) in enumerate(spike_trains): + plt.plot(spikes, i*np.ones_like(spikes), 'o') + +f = spk.isi_distance(spike_trains[0], spike_trains[1]) +x, y = f.get_plottable_data() + +plt.figure() +plt.plot(x, np.abs(y), '--k') + +print("Average: %.8f" % f.avrg()) +print("Absolute average: %.8f" % f.abs_avrg()) + + +f = spk.spike_distance(spike_trains[0], spike_trains[1]) +x, y = f.get_plottable_data() +print(x) +print(y) +#plt.figure() +plt.plot(x, y, '-b') + +plt.show() diff --git a/examples/test_data.py b/examples/test_data.py deleted file mode 100644 index dcd0f20..0000000 --- a/examples/test_data.py +++ /dev/null @@ -1,34 +0,0 @@ -# compute the isi distance of some test data - -from __future__ import print_function - -import numpy as np -import matplotlib.pyplot as plt - -import pyspike as spk - -spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", - time_interval=(0,4000)) - -# plot the spike time -for (i,spikes) in enumerate(spike_trains): - plt.plot(spikes, i*np.ones_like(spikes), 'o') - -f = spk.isi_distance(spike_trains[0], spike_trains[1]) -x, y = f.get_plottable_data() - -plt.figure() -plt.plot(x, np.abs(y), '--k') - -print("Average: %.8f" % f.avrg()) -print("Absolute average: %.8f" % f.abs_avrg()) - - -f = spk.spike_distance(spike_trains[0], spike_trains[1]) -x, y = f.get_plottable_data() -print(x) -print(y) -#plt.figure() -plt.plot(x, y, '-b') - -plt.show() diff --git a/examples/test_merge.py b/examples/test_merge.py deleted file mode 100644 index 0c34608..0000000 --- a/examples/test_merge.py +++ /dev/null @@ -1,20 +0,0 @@ -# compute the isi distance of some test data -from __future__ import print_function - -import numpy as np -import matplotlib.pyplot as plt - -import pyspike as spk - -# first load the data, ending time = 4000 -spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", 4000) - -spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) - -print(spikes) - -plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') -plt.plot(spike_trains[1], np.ones_like(spike_trains[1]), 'x') -plt.plot(spikes, 2*np.ones_like(spikes), 'o') - -plt.show() diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 2703f65..3867e6e 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,3 +1,9 @@ +""" +Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) +""" + __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 2be8525..4ab4381 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -11,6 +11,9 @@ 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 + +Distributed under the MIT License (MIT) + """ """ diff --git a/pyspike/distances.py b/pyspike/distances.py index da603ad..db04c4e 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -3,6 +3,8 @@ Module containing several functions to compute spike distances Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) """ import numpy as np diff --git a/pyspike/function.py b/pyspike/function.py index 5444c36..243ef67 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -5,6 +5,8 @@ functions. Copyright 2014, Mario Mulansky +Distributed under the MIT License (MIT) + """ from __future__ import print_function diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index 9134149..e5b74e9 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -3,7 +3,9 @@ Collection of python functions that can be used instead of the cython implementation. -Copyright 2014, Mario Mulanksy +Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) """ diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 502c460..9375e30 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -3,6 +3,8 @@ Module containing several function to load and transform spike trains Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) """ import numpy as np diff --git a/test/test_distance.py b/test/test_distance.py index 84d0af9..dafe693 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -3,6 +3,9 @@ Tests the isi- and spike-distance computation Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) + """ from __future__ import print_function diff --git a/test/test_function.py b/test/test_function.py index 7420011..c0fb3fd 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -3,6 +3,8 @@ Tests the PieceWiseConst and PieceWiseLinear functions Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) """ from __future__ import print_function -- cgit v1.2.3 From a9e1c10415dff6ada94ea438dd445682abcf6b3b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:24:51 +0200 Subject: added license file --- License | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 License diff --git a/License b/License new file mode 100644 index 0000000..95d0405 --- /dev/null +++ b/License @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Mario Mulansky, + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file -- cgit v1.2.3 From 756214a515c8927df45533def7a913ea54a27a35 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:26:49 +0200 Subject: added missing license note --- test/test_spikes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_spikes.py b/test/test_spikes.py index dca580f..456ed62 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -3,6 +3,8 @@ Test loading of spike trains from text files Copyright 2014, Mario Mulansky + +Distributed under the MIT License (MIT) """ from __future__ import print_function -- cgit v1.2.3 From dea3679bf53326301d626f1e8e24d2f635685307 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:27:30 +0200 Subject: cosmetic fix --- Readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Readme.md b/Readme.md index 8b84ebd..aa1078e 100644 --- a/Readme.md +++ b/Readme.md @@ -18,6 +18,7 @@ To use PySpike you need Python installed with the following additional packages: In particular, make sure that [cython](http://www.cython.org) is configured properly and able to locate a C compiler. To install PySpike, simply download the source, i.e. via git, and run the setup.py script: + git clone ... cd PySpike python setup.py build_ext --inplace -- cgit v1.2.3 From c70a030f23b89d086772177300040081c8ac1c54 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:32:54 +0200 Subject: added note on running tests --- Readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Readme.md b/Readme.md index aa1078e..cdb2597 100644 --- a/Readme.md +++ b/Readme.md @@ -14,6 +14,7 @@ To use PySpike you need Python installed with the following additional packages: - scipy - matplotlib - cython +- nosetests (for running the tests) In particular, make sure that [cython](http://www.cython.org) is configured properly and able to locate a C compiler. @@ -23,6 +24,11 @@ To install PySpike, simply download the source, i.e. via git, and run the setup. cd PySpike python setup.py build_ext --inplace +The you can run the tests using the `nosetests` test framework: + + cd test + nosetests + ## Loading spike trains -- cgit v1.2.3 From 32b367dbca5e5d3b8847c3a0604c88e4c0020bed Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 10 Oct 2014 17:58:58 +0200 Subject: Update Readme.md --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index cdb2597..bf80553 100644 --- a/Readme.md +++ b/Readme.md @@ -24,7 +24,7 @@ To install PySpike, simply download the source, i.e. via git, and run the setup. cd PySpike python setup.py build_ext --inplace -The you can run the tests using the `nosetests` test framework: +Then you can run the tests using the `nosetests` test framework: cd test nosetests @@ -41,4 +41,4 @@ The you can run the tests using the `nosetests` test framework: ## Plotting -## Averaging \ No newline at end of file +## Averaging -- cgit v1.2.3 From f3fefa32f9c02c217448529454011d56669b42ae Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 12 Oct 2014 18:27:38 +0200 Subject: changed name of example spike data file --- examples/PySpike_testdata.txt | 43 +++++++++++++++++++++++++++++++++++++++++++ examples/SPIKY_testdata.txt | 43 ------------------------------------------- examples/isi_matrix.py | 2 +- examples/merge.py | 2 +- examples/plot.py | 2 +- 5 files changed, 46 insertions(+), 46 deletions(-) create mode 100755 examples/PySpike_testdata.txt delete mode 100755 examples/SPIKY_testdata.txt diff --git a/examples/PySpike_testdata.txt b/examples/PySpike_testdata.txt new file mode 100755 index 0000000..c8bea67 --- /dev/null +++ b/examples/PySpike_testdata.txt @@ -0,0 +1,43 @@ +64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 +65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 +# test comment +69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 +59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 +# empty line + +59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 +66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 +66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 +63.764 318.45 697.48 936.97 1059.3 1325 1687.9 1944.7 2132.5 2377.1 2713.1 2976.6 3196.8 3442.6 3741.6 3998.3 +63.906 314.79 693.26 937.12 1065.9 1315.8 1584.3 1821.5 2126.3 2396.8 2709.1 2967 3197.4 3444 3732.8 3849.5 +69.493 316.62 689.81 943.62 1071.9 1296.3 1654.8 1931.9 2127.5 2390.6 2708.9 2950.4 3194.8 3445.2 3670.1 3903.3 +61.789 317.53 555.82 813.15 1198.7 1448.7 1686.7 1943.5 2060.7 2311.4 2658.2 2900.2 3167.4 3418.2 3617.3 3771 +64.098 309.86 567.27 813.91 1182 1464.3 1576.8 1822.5 2063.1 2311.7 2655.8 2911.7 3168.3 3418.2 3586.4 3999.7 +68.59 315.5 559.52 806.23 1182.5 1441.1 1567.2 1804.8 2074.9 2315.8 2655.1 2913.2 3165.9 3419.5 3648.1 3884.4 +66.507 314.42 556.42 814.83 1182.5 1440.3 1701.3 1911.1 2069.7 2319.3 2662.3 2903.2 3167.4 3418.5 3545 3893.9 +72.744 318.45 554.4 819.64 1186.9 1449.7 1676 1957.4 2051.4 2302.8 2657.8 2916.2 3169.4 3416.7 3570.4 3884.8 +64.779 324.42 560.56 828.99 1174.8 1439.9 1563.7 1790.6 2067.7 2287.6 2657.4 2905.2 3139.2 3389.1 3507.8 3807.5 +64.852 316.63 568.89 815.61 1198.3 1454.1 1710.6 1933.9 2091.5 2309.6 2660.9 2907.5 3137.2 3389.3 3617.2 +63.089 314.52 553.8 827.06 1183.9 1457.6 1558.9 1808.3 2064.5 2337.1 2653.6 2897 3143.7 3385.7 3668.7 3803.8 +62.23 315.16 564.35 812.15 1199.6 1448.9 1562.7 1839.1 2069.7 2308.9 2649.6 2919.7 3141 3389.9 3723.6 3882.2 +69.662 311.93 564.91 805.25 1209.7 1451.4 1691.9 1932.1 2044.2 2329.4 2657.1 2908.5 3142.8 3390.5 3597.3 3991.1 +183.42 431.34 562.41 809.57 1086.3 1308.9 1555.9 1831.3 2057 2326.9 2591.3 2831.4 3113.9 3367.9 3555.3 3956 +188.49 442.39 572.4 810.76 1065 1326.7 1564.3 1803.4 2060.4 2322.4 2607.2 2824.1 3110.2 3363.9 3644.1 3819.6 +177 437.76 569.82 819.66 1064.1 1309.2 1685.7 1957.5 2066.9 2313.8 2593.2 2847 3116.8 3364.5 3727.3 3881.6 +193.9 441.93 586.9 804.98 1062.5 1312.4 1542.4 1793.1 2073.9 2314.7 2587.8 2845.9 3112.4 3359.8 +193.01 440.26 555.64 814.08 1056.3 1315 1689.9 1961.4 2049.1 2305 2593.9 2847.5 3110.6 3361.1 3711.6 3914.7 +194.71 437.57 566.18 806.73 1069.2 1314.6 1682.7 1942.2 2061.8 2304.6 2607.6 2841.7 3082.9 3330.3 3679.7 3848.2 +184.88 441.22 570.92 794.35 1063.7 1309.9 1678.7 1930 2058 2321.3 2606.7 2845 3084.8 3337.3 3640 3952.1 +189.66 443.59 560.67 816.89 1070.4 1303.4 1550.1 1815.5 2057.6 2323.7 2587.1 2843.5 3086.6 3333.6 3618.2 3815.4 +190.41 440.77 568.96 808.56 1073.8 1322.1 1686.5 1952.8 2068.7 2335.7 2595.7 2845.4 3086 3333.5 3635.6 3939.3 +181.16 440.67 577.54 823.52 1052.5 1322.3 1578.4 1822.2 2079.4 2309.1 2596.9 2851.9 3083.5 3335.1 3531.2 3770.6 +181.09 434.97 687.15 943.33 1192.9 1444 1699.4 1942 2194.6 2445.9 2549.4 2785.1 3056.5 3308.2 3620.5 3932.7 +186.7 446.53 688.18 942.86 1186.1 1441.9 1688.1 1922.2 2196.6 2455.3 2534.8 2776.5 3060.3 3309.4 3514.1 3808.6 +196.76 446 681.26 948.27 1195.8 1433.1 1699 1933 2201.2 2461.4 2547.4 2777.8 3055.7 3307.1 3590.6 3952.8 +200.68 427.11 695.67 946.42 1178.6 1440.1 1538.4 1809 2199.8 2432.5 2531.6 2793.2 3056.6 3308.6 3510.6 3928.1 +190.83 429.57 698.73 931.16 1190.6 1428.9 1698.3 1935 2176.8 2424.7 2530.5 2766.9 3062 3309.7 3689.8 +181.47 441.93 682.32 943.01 1190.1 1459.1 1570.6 1819.6 2189.8 2437.9 2543.3 2782.8 3025.9 3280.2 3581 3855.9 +191.38 435.69 702.76 935.62 1188.3 1438.3 1564.2 1823.9 2191.3 2444.9 2531.9 2782.4 3030.7 3275.7 3677.7 3829.2 +191.97 433.85 686.29 932.65 1183.1 1432.7 1563.9 1826.5 2214.1 2436.8 2529.8 2778.3 3028.3 3281.8 3582 3863.4 +189.51 453.21 691.3 940.86 1180.1 1430.1 1567.1 1835 2199 2448.2 2526.7 2773.8 3030.5 3280.1 3576.2 3893.6 +190.88 435.48 692.66 940.51 1189.5 1448.9 1575.1 1824.2 2190.8 2425.9 2530.6 2783.3 3033.3 3279.5 3733 3838.9 diff --git a/examples/SPIKY_testdata.txt b/examples/SPIKY_testdata.txt deleted file mode 100755 index c8bea67..0000000 --- a/examples/SPIKY_testdata.txt +++ /dev/null @@ -1,43 +0,0 @@ -64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 -65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 -# test comment -69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 -59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 -# empty line - -59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 -66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 -66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 -63.764 318.45 697.48 936.97 1059.3 1325 1687.9 1944.7 2132.5 2377.1 2713.1 2976.6 3196.8 3442.6 3741.6 3998.3 -63.906 314.79 693.26 937.12 1065.9 1315.8 1584.3 1821.5 2126.3 2396.8 2709.1 2967 3197.4 3444 3732.8 3849.5 -69.493 316.62 689.81 943.62 1071.9 1296.3 1654.8 1931.9 2127.5 2390.6 2708.9 2950.4 3194.8 3445.2 3670.1 3903.3 -61.789 317.53 555.82 813.15 1198.7 1448.7 1686.7 1943.5 2060.7 2311.4 2658.2 2900.2 3167.4 3418.2 3617.3 3771 -64.098 309.86 567.27 813.91 1182 1464.3 1576.8 1822.5 2063.1 2311.7 2655.8 2911.7 3168.3 3418.2 3586.4 3999.7 -68.59 315.5 559.52 806.23 1182.5 1441.1 1567.2 1804.8 2074.9 2315.8 2655.1 2913.2 3165.9 3419.5 3648.1 3884.4 -66.507 314.42 556.42 814.83 1182.5 1440.3 1701.3 1911.1 2069.7 2319.3 2662.3 2903.2 3167.4 3418.5 3545 3893.9 -72.744 318.45 554.4 819.64 1186.9 1449.7 1676 1957.4 2051.4 2302.8 2657.8 2916.2 3169.4 3416.7 3570.4 3884.8 -64.779 324.42 560.56 828.99 1174.8 1439.9 1563.7 1790.6 2067.7 2287.6 2657.4 2905.2 3139.2 3389.1 3507.8 3807.5 -64.852 316.63 568.89 815.61 1198.3 1454.1 1710.6 1933.9 2091.5 2309.6 2660.9 2907.5 3137.2 3389.3 3617.2 -63.089 314.52 553.8 827.06 1183.9 1457.6 1558.9 1808.3 2064.5 2337.1 2653.6 2897 3143.7 3385.7 3668.7 3803.8 -62.23 315.16 564.35 812.15 1199.6 1448.9 1562.7 1839.1 2069.7 2308.9 2649.6 2919.7 3141 3389.9 3723.6 3882.2 -69.662 311.93 564.91 805.25 1209.7 1451.4 1691.9 1932.1 2044.2 2329.4 2657.1 2908.5 3142.8 3390.5 3597.3 3991.1 -183.42 431.34 562.41 809.57 1086.3 1308.9 1555.9 1831.3 2057 2326.9 2591.3 2831.4 3113.9 3367.9 3555.3 3956 -188.49 442.39 572.4 810.76 1065 1326.7 1564.3 1803.4 2060.4 2322.4 2607.2 2824.1 3110.2 3363.9 3644.1 3819.6 -177 437.76 569.82 819.66 1064.1 1309.2 1685.7 1957.5 2066.9 2313.8 2593.2 2847 3116.8 3364.5 3727.3 3881.6 -193.9 441.93 586.9 804.98 1062.5 1312.4 1542.4 1793.1 2073.9 2314.7 2587.8 2845.9 3112.4 3359.8 -193.01 440.26 555.64 814.08 1056.3 1315 1689.9 1961.4 2049.1 2305 2593.9 2847.5 3110.6 3361.1 3711.6 3914.7 -194.71 437.57 566.18 806.73 1069.2 1314.6 1682.7 1942.2 2061.8 2304.6 2607.6 2841.7 3082.9 3330.3 3679.7 3848.2 -184.88 441.22 570.92 794.35 1063.7 1309.9 1678.7 1930 2058 2321.3 2606.7 2845 3084.8 3337.3 3640 3952.1 -189.66 443.59 560.67 816.89 1070.4 1303.4 1550.1 1815.5 2057.6 2323.7 2587.1 2843.5 3086.6 3333.6 3618.2 3815.4 -190.41 440.77 568.96 808.56 1073.8 1322.1 1686.5 1952.8 2068.7 2335.7 2595.7 2845.4 3086 3333.5 3635.6 3939.3 -181.16 440.67 577.54 823.52 1052.5 1322.3 1578.4 1822.2 2079.4 2309.1 2596.9 2851.9 3083.5 3335.1 3531.2 3770.6 -181.09 434.97 687.15 943.33 1192.9 1444 1699.4 1942 2194.6 2445.9 2549.4 2785.1 3056.5 3308.2 3620.5 3932.7 -186.7 446.53 688.18 942.86 1186.1 1441.9 1688.1 1922.2 2196.6 2455.3 2534.8 2776.5 3060.3 3309.4 3514.1 3808.6 -196.76 446 681.26 948.27 1195.8 1433.1 1699 1933 2201.2 2461.4 2547.4 2777.8 3055.7 3307.1 3590.6 3952.8 -200.68 427.11 695.67 946.42 1178.6 1440.1 1538.4 1809 2199.8 2432.5 2531.6 2793.2 3056.6 3308.6 3510.6 3928.1 -190.83 429.57 698.73 931.16 1190.6 1428.9 1698.3 1935 2176.8 2424.7 2530.5 2766.9 3062 3309.7 3689.8 -181.47 441.93 682.32 943.01 1190.1 1459.1 1570.6 1819.6 2189.8 2437.9 2543.3 2782.8 3025.9 3280.2 3581 3855.9 -191.38 435.69 702.76 935.62 1188.3 1438.3 1564.2 1823.9 2191.3 2444.9 2531.9 2782.4 3030.7 3275.7 3677.7 3829.2 -191.97 433.85 686.29 932.65 1183.1 1432.7 1563.9 1826.5 2214.1 2436.8 2529.8 2778.3 3028.3 3281.8 3582 3863.4 -189.51 453.21 691.3 940.86 1180.1 1430.1 1567.1 1835 2199 2448.2 2526.7 2773.8 3030.5 3280.1 3576.2 3893.6 -190.88 435.48 692.66 940.51 1189.5 1448.9 1575.1 1824.2 2190.8 2425.9 2530.6 2783.3 3033.3 3279.5 3733 3838.9 diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py index 3297d3d..2a4d075 100644 --- a/examples/isi_matrix.py +++ b/examples/isi_matrix.py @@ -17,7 +17,7 @@ import matplotlib.pyplot as plt import pyspike as spk # first load the data, interval ending time = 4000, start=0 (default) -spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", 4000) +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) print(len(spike_trains)) diff --git a/examples/merge.py b/examples/merge.py index 55c7f0a..726d32b 100644 --- a/examples/merge.py +++ b/examples/merge.py @@ -15,7 +15,7 @@ import matplotlib.pyplot as plt import pyspike as spk # first load the data, ending time = 4000 -spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", 4000) +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) diff --git a/examples/plot.py b/examples/plot.py index d7e2173..4ff75c4 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -15,7 +15,7 @@ import matplotlib.pyplot as plt import pyspike as spk -spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", time_interval=(0,4000)) # plot the spike time -- cgit v1.2.3 From 94408401e31830e7db7902afdeef714cb981bce2 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 12 Oct 2014 18:29:43 +0200 Subject: + load section --- Readme.md | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index bf80553..7f07b65 100644 --- a/Readme.md +++ b/Readme.md @@ -18,9 +18,9 @@ To use PySpike you need Python installed with the following additional packages: In particular, make sure that [cython](http://www.cython.org) is configured properly and able to locate a C compiler. -To install PySpike, simply download the source, i.e. via git, and run the setup.py script: +To install PySpike, simply download the source, i.e. via git clone, and run the setup.py script: - git clone ... + git clone https://github.com/mariomulansky/PySpike.git cd PySpike python setup.py build_ext --inplace @@ -29,11 +29,47 @@ Then you can run the tests using the `nosetests` test framework: cd test nosetests -## Loading spike trains +Finally, you should make the installation folder known to Python to be able to import pyspike in your own projects. +Therefore, add your `/path/to/PySpike` to the `$PYTHONPATH` environment variable. + +## Spike trains + +In PySpike, spike trains are represented by one-dimensional numpy arrays containing the sequence of spike times as double values. +The following code creates such a spike train with some arbitrary spike times: + + import numpy as np + + spike_train = np.array([0.1, 0.3, 0.45, 0.6, 0.9]) + +Typically, spike train data is loaded into PySpike from data files. +The most straight-forward data files are text files where each line represents one spike train given as an sequence of spike times. +An exemplary file with several spike trains is [PySpike_testdata.txt](https://github.com/mariomulansky/PySpike/blob/master/examples/PySpike_testdata.txt). +To quickly obtain spike trains from such files, PySpike provides the function `load_spike_trains_from_txt`. + + import numpy as np + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=(0,4000)) + +This function expects the name of the datafile as first parameter, and additionally the time intervall of the spike train measurement can be provided as a pair of start- and end-time values. +If the time interval is provided (`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. +Furthermore, the spike trains are ordered via `np.sort`. +As result, `load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. ## Computing bi-variate distances +---------------------- +**Important note:** + +>Spike trains are expected to be *ordered sequences*! +>For performance reasons, the PySpike distance function do not check if the spike trains provided are indeed ordered. +>Make sure that all your spike trains are ordered. +>If in doubt, use `spike_train = np.sort(spike_train)` to obtain a correctly ordered spike train. + +---------------------- + ## Computing multi-variate distances -- cgit v1.2.3 From adc1e91c89aeecf3ee1743d3595b282061a22573 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 12 Oct 2014 18:49:37 +0200 Subject: added sort to load function, renamed test data file --- examples/PySpike_testdata.txt | 6 +++--- pyspike/spikes.py | 15 ++++++++++----- test/PySpike_testdata.txt | 43 +++++++++++++++++++++++++++++++++++++++++++ test/SPIKY_testdata.txt | 43 ------------------------------------------- test/test_spikes.py | 8 ++++---- 5 files changed, 60 insertions(+), 55 deletions(-) create mode 100755 test/PySpike_testdata.txt delete mode 100755 test/SPIKY_testdata.txt diff --git a/examples/PySpike_testdata.txt b/examples/PySpike_testdata.txt index c8bea67..41b2362 100755 --- a/examples/PySpike_testdata.txt +++ b/examples/PySpike_testdata.txt @@ -1,10 +1,10 @@ +# PySpike exemplary spike trains +# lines starting with # are ignored, just like empty lines + 64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 -# test comment 69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 -# empty line - 59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 9375e30..6ea94de 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -46,22 +46,26 @@ def add_auxiliary_spikes(spike_train, time_interval): ############################################################ # spike_train_from_string ############################################################ -def spike_train_from_string(s, sep=' '): +def spike_train_from_string(s, sep=' ', sort=True): """ Converts a string of times into an array of spike times. Args: - s: the string with (ordered) spike times - - sep: The separator between the time numbers. + - sep: The separator between the time numbers, default=' '. + - sort: If True, the spike times are order via `np.sort`, default=True. Returns: - array of spike times """ - return np.fromstring(s, sep=sep) + if sort: + return np.sort(np.fromstring(s, sep=sep)) + else: + return np.fromstring(s, sep=sep) ############################################################ # load_spike_trains_txt ############################################################ def load_spike_trains_from_txt(file_name, time_interval=None, - separator=' ', comment='#'): + separator=' ', comment='#', sort=True): """ Loads a number of spike trains from a text file. Each line of the text file should contain one spike train as a sequence of spike times separated by `separator`. Empty lines as well as lines starting with `comment` are @@ -78,13 +82,14 @@ def load_spike_trains_from_txt(file_name, time_interval=None, added to the spike train at the beginning and end of this interval. - separator: The character used to seprate the values in the text file. - comment: Lines starting with this character are ignored. + - sort: If true, the spike times are order via `np.sort`, default=True. """ spike_trains = [] spike_file = open(file_name, 'r') for line in spike_file: if len(line) > 1 and not line.startswith(comment): # use only the lines with actual data and not commented - spike_train = spike_train_from_string(line) + spike_train = spike_train_from_string(line, separator, sort) if not time_interval == None: # add auxiliary spikes if times given spike_train = add_auxiliary_spikes(spike_train, time_interval) spike_trains.append(spike_train) diff --git a/test/PySpike_testdata.txt b/test/PySpike_testdata.txt new file mode 100755 index 0000000..c8bea67 --- /dev/null +++ b/test/PySpike_testdata.txt @@ -0,0 +1,43 @@ +64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 +65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 +# test comment +69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 +59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 +# empty line + +59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 +66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 +66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 +63.764 318.45 697.48 936.97 1059.3 1325 1687.9 1944.7 2132.5 2377.1 2713.1 2976.6 3196.8 3442.6 3741.6 3998.3 +63.906 314.79 693.26 937.12 1065.9 1315.8 1584.3 1821.5 2126.3 2396.8 2709.1 2967 3197.4 3444 3732.8 3849.5 +69.493 316.62 689.81 943.62 1071.9 1296.3 1654.8 1931.9 2127.5 2390.6 2708.9 2950.4 3194.8 3445.2 3670.1 3903.3 +61.789 317.53 555.82 813.15 1198.7 1448.7 1686.7 1943.5 2060.7 2311.4 2658.2 2900.2 3167.4 3418.2 3617.3 3771 +64.098 309.86 567.27 813.91 1182 1464.3 1576.8 1822.5 2063.1 2311.7 2655.8 2911.7 3168.3 3418.2 3586.4 3999.7 +68.59 315.5 559.52 806.23 1182.5 1441.1 1567.2 1804.8 2074.9 2315.8 2655.1 2913.2 3165.9 3419.5 3648.1 3884.4 +66.507 314.42 556.42 814.83 1182.5 1440.3 1701.3 1911.1 2069.7 2319.3 2662.3 2903.2 3167.4 3418.5 3545 3893.9 +72.744 318.45 554.4 819.64 1186.9 1449.7 1676 1957.4 2051.4 2302.8 2657.8 2916.2 3169.4 3416.7 3570.4 3884.8 +64.779 324.42 560.56 828.99 1174.8 1439.9 1563.7 1790.6 2067.7 2287.6 2657.4 2905.2 3139.2 3389.1 3507.8 3807.5 +64.852 316.63 568.89 815.61 1198.3 1454.1 1710.6 1933.9 2091.5 2309.6 2660.9 2907.5 3137.2 3389.3 3617.2 +63.089 314.52 553.8 827.06 1183.9 1457.6 1558.9 1808.3 2064.5 2337.1 2653.6 2897 3143.7 3385.7 3668.7 3803.8 +62.23 315.16 564.35 812.15 1199.6 1448.9 1562.7 1839.1 2069.7 2308.9 2649.6 2919.7 3141 3389.9 3723.6 3882.2 +69.662 311.93 564.91 805.25 1209.7 1451.4 1691.9 1932.1 2044.2 2329.4 2657.1 2908.5 3142.8 3390.5 3597.3 3991.1 +183.42 431.34 562.41 809.57 1086.3 1308.9 1555.9 1831.3 2057 2326.9 2591.3 2831.4 3113.9 3367.9 3555.3 3956 +188.49 442.39 572.4 810.76 1065 1326.7 1564.3 1803.4 2060.4 2322.4 2607.2 2824.1 3110.2 3363.9 3644.1 3819.6 +177 437.76 569.82 819.66 1064.1 1309.2 1685.7 1957.5 2066.9 2313.8 2593.2 2847 3116.8 3364.5 3727.3 3881.6 +193.9 441.93 586.9 804.98 1062.5 1312.4 1542.4 1793.1 2073.9 2314.7 2587.8 2845.9 3112.4 3359.8 +193.01 440.26 555.64 814.08 1056.3 1315 1689.9 1961.4 2049.1 2305 2593.9 2847.5 3110.6 3361.1 3711.6 3914.7 +194.71 437.57 566.18 806.73 1069.2 1314.6 1682.7 1942.2 2061.8 2304.6 2607.6 2841.7 3082.9 3330.3 3679.7 3848.2 +184.88 441.22 570.92 794.35 1063.7 1309.9 1678.7 1930 2058 2321.3 2606.7 2845 3084.8 3337.3 3640 3952.1 +189.66 443.59 560.67 816.89 1070.4 1303.4 1550.1 1815.5 2057.6 2323.7 2587.1 2843.5 3086.6 3333.6 3618.2 3815.4 +190.41 440.77 568.96 808.56 1073.8 1322.1 1686.5 1952.8 2068.7 2335.7 2595.7 2845.4 3086 3333.5 3635.6 3939.3 +181.16 440.67 577.54 823.52 1052.5 1322.3 1578.4 1822.2 2079.4 2309.1 2596.9 2851.9 3083.5 3335.1 3531.2 3770.6 +181.09 434.97 687.15 943.33 1192.9 1444 1699.4 1942 2194.6 2445.9 2549.4 2785.1 3056.5 3308.2 3620.5 3932.7 +186.7 446.53 688.18 942.86 1186.1 1441.9 1688.1 1922.2 2196.6 2455.3 2534.8 2776.5 3060.3 3309.4 3514.1 3808.6 +196.76 446 681.26 948.27 1195.8 1433.1 1699 1933 2201.2 2461.4 2547.4 2777.8 3055.7 3307.1 3590.6 3952.8 +200.68 427.11 695.67 946.42 1178.6 1440.1 1538.4 1809 2199.8 2432.5 2531.6 2793.2 3056.6 3308.6 3510.6 3928.1 +190.83 429.57 698.73 931.16 1190.6 1428.9 1698.3 1935 2176.8 2424.7 2530.5 2766.9 3062 3309.7 3689.8 +181.47 441.93 682.32 943.01 1190.1 1459.1 1570.6 1819.6 2189.8 2437.9 2543.3 2782.8 3025.9 3280.2 3581 3855.9 +191.38 435.69 702.76 935.62 1188.3 1438.3 1564.2 1823.9 2191.3 2444.9 2531.9 2782.4 3030.7 3275.7 3677.7 3829.2 +191.97 433.85 686.29 932.65 1183.1 1432.7 1563.9 1826.5 2214.1 2436.8 2529.8 2778.3 3028.3 3281.8 3582 3863.4 +189.51 453.21 691.3 940.86 1180.1 1430.1 1567.1 1835 2199 2448.2 2526.7 2773.8 3030.5 3280.1 3576.2 3893.6 +190.88 435.48 692.66 940.51 1189.5 1448.9 1575.1 1824.2 2190.8 2425.9 2530.6 2783.3 3033.3 3279.5 3733 3838.9 diff --git a/test/SPIKY_testdata.txt b/test/SPIKY_testdata.txt deleted file mode 100755 index c8bea67..0000000 --- a/test/SPIKY_testdata.txt +++ /dev/null @@ -1,43 +0,0 @@ -64.886 305.81 696 937.77 1059.7 1322.2 1576.1 1808.1 2121.5 2381.1 2728.6 2966.9 3223.7 3473.7 3644.3 3936.3 -65.553 307.49 696.63 948.66 1070.4 1312.2 1712.7 1934.3 2117.6 2356.9 2727.3 2980.6 3226.9 3475.7 3726.4 3944 -# test comment -69.064 319.1 688.32 947.85 1071.8 1300.8 1697.2 1930.6 2139.4 2354.2 2723.7 2963.6 3221.3 3470.1 -59.955 313.83 692.23 955.95 1070.4 1319.6 1681.9 1963.5 2151.4 2373.8 2729.4 2971.2 3220.2 3475.5 3632.3 3788.9 -# empty line - -59.977 306.84 686.09 935.08 1059.9 1325.9 1543.4 1821.9 2150.2 2390.4 2724.5 2969.6 3222.5 3471.5 3576 3913.9 -66.415 313.41 688.83 931.43 1051.8 1304.6 1555.6 1820.2 2150.5 2383.1 2723.4 2947.7 3196.6 3443.5 3575 3804.9 -66.449 311.02 689.26 947.12 1058.9 1286.6 1708.2 1957.3 2124.8 2375.7 2709.4 2977.6 3191.1 3449.6 3590.4 3831.2 -63.764 318.45 697.48 936.97 1059.3 1325 1687.9 1944.7 2132.5 2377.1 2713.1 2976.6 3196.8 3442.6 3741.6 3998.3 -63.906 314.79 693.26 937.12 1065.9 1315.8 1584.3 1821.5 2126.3 2396.8 2709.1 2967 3197.4 3444 3732.8 3849.5 -69.493 316.62 689.81 943.62 1071.9 1296.3 1654.8 1931.9 2127.5 2390.6 2708.9 2950.4 3194.8 3445.2 3670.1 3903.3 -61.789 317.53 555.82 813.15 1198.7 1448.7 1686.7 1943.5 2060.7 2311.4 2658.2 2900.2 3167.4 3418.2 3617.3 3771 -64.098 309.86 567.27 813.91 1182 1464.3 1576.8 1822.5 2063.1 2311.7 2655.8 2911.7 3168.3 3418.2 3586.4 3999.7 -68.59 315.5 559.52 806.23 1182.5 1441.1 1567.2 1804.8 2074.9 2315.8 2655.1 2913.2 3165.9 3419.5 3648.1 3884.4 -66.507 314.42 556.42 814.83 1182.5 1440.3 1701.3 1911.1 2069.7 2319.3 2662.3 2903.2 3167.4 3418.5 3545 3893.9 -72.744 318.45 554.4 819.64 1186.9 1449.7 1676 1957.4 2051.4 2302.8 2657.8 2916.2 3169.4 3416.7 3570.4 3884.8 -64.779 324.42 560.56 828.99 1174.8 1439.9 1563.7 1790.6 2067.7 2287.6 2657.4 2905.2 3139.2 3389.1 3507.8 3807.5 -64.852 316.63 568.89 815.61 1198.3 1454.1 1710.6 1933.9 2091.5 2309.6 2660.9 2907.5 3137.2 3389.3 3617.2 -63.089 314.52 553.8 827.06 1183.9 1457.6 1558.9 1808.3 2064.5 2337.1 2653.6 2897 3143.7 3385.7 3668.7 3803.8 -62.23 315.16 564.35 812.15 1199.6 1448.9 1562.7 1839.1 2069.7 2308.9 2649.6 2919.7 3141 3389.9 3723.6 3882.2 -69.662 311.93 564.91 805.25 1209.7 1451.4 1691.9 1932.1 2044.2 2329.4 2657.1 2908.5 3142.8 3390.5 3597.3 3991.1 -183.42 431.34 562.41 809.57 1086.3 1308.9 1555.9 1831.3 2057 2326.9 2591.3 2831.4 3113.9 3367.9 3555.3 3956 -188.49 442.39 572.4 810.76 1065 1326.7 1564.3 1803.4 2060.4 2322.4 2607.2 2824.1 3110.2 3363.9 3644.1 3819.6 -177 437.76 569.82 819.66 1064.1 1309.2 1685.7 1957.5 2066.9 2313.8 2593.2 2847 3116.8 3364.5 3727.3 3881.6 -193.9 441.93 586.9 804.98 1062.5 1312.4 1542.4 1793.1 2073.9 2314.7 2587.8 2845.9 3112.4 3359.8 -193.01 440.26 555.64 814.08 1056.3 1315 1689.9 1961.4 2049.1 2305 2593.9 2847.5 3110.6 3361.1 3711.6 3914.7 -194.71 437.57 566.18 806.73 1069.2 1314.6 1682.7 1942.2 2061.8 2304.6 2607.6 2841.7 3082.9 3330.3 3679.7 3848.2 -184.88 441.22 570.92 794.35 1063.7 1309.9 1678.7 1930 2058 2321.3 2606.7 2845 3084.8 3337.3 3640 3952.1 -189.66 443.59 560.67 816.89 1070.4 1303.4 1550.1 1815.5 2057.6 2323.7 2587.1 2843.5 3086.6 3333.6 3618.2 3815.4 -190.41 440.77 568.96 808.56 1073.8 1322.1 1686.5 1952.8 2068.7 2335.7 2595.7 2845.4 3086 3333.5 3635.6 3939.3 -181.16 440.67 577.54 823.52 1052.5 1322.3 1578.4 1822.2 2079.4 2309.1 2596.9 2851.9 3083.5 3335.1 3531.2 3770.6 -181.09 434.97 687.15 943.33 1192.9 1444 1699.4 1942 2194.6 2445.9 2549.4 2785.1 3056.5 3308.2 3620.5 3932.7 -186.7 446.53 688.18 942.86 1186.1 1441.9 1688.1 1922.2 2196.6 2455.3 2534.8 2776.5 3060.3 3309.4 3514.1 3808.6 -196.76 446 681.26 948.27 1195.8 1433.1 1699 1933 2201.2 2461.4 2547.4 2777.8 3055.7 3307.1 3590.6 3952.8 -200.68 427.11 695.67 946.42 1178.6 1440.1 1538.4 1809 2199.8 2432.5 2531.6 2793.2 3056.6 3308.6 3510.6 3928.1 -190.83 429.57 698.73 931.16 1190.6 1428.9 1698.3 1935 2176.8 2424.7 2530.5 2766.9 3062 3309.7 3689.8 -181.47 441.93 682.32 943.01 1190.1 1459.1 1570.6 1819.6 2189.8 2437.9 2543.3 2782.8 3025.9 3280.2 3581 3855.9 -191.38 435.69 702.76 935.62 1188.3 1438.3 1564.2 1823.9 2191.3 2444.9 2531.9 2782.4 3030.7 3275.7 3677.7 3829.2 -191.97 433.85 686.29 932.65 1183.1 1432.7 1563.9 1826.5 2214.1 2436.8 2529.8 2778.3 3028.3 3281.8 3582 3863.4 -189.51 453.21 691.3 940.86 1180.1 1430.1 1567.1 1835 2199 2448.2 2526.7 2773.8 3030.5 3280.1 3576.2 3893.6 -190.88 435.48 692.66 940.51 1189.5 1448.9 1575.1 1824.2 2190.8 2425.9 2530.6 2783.3 3033.3 3279.5 3733 3838.9 diff --git a/test/test_spikes.py b/test/test_spikes.py index 456ed62..e008207 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -23,7 +23,7 @@ def test_auxiliary_spikes(): def test_load_from_txt(): - spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", time_interval=(0,4000)) assert len(spike_trains) == 40 @@ -39,7 +39,7 @@ def test_load_from_txt(): assert spike_train[-1] == 4000 # load without adding auxiliary spikes - spike_trains2 = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + spike_trains2 = spk.load_spike_trains_from_txt("PySpike_testdata.txt", time_interval=None) assert len(spike_trains2) == 40 # check auxiliary spikes @@ -59,12 +59,12 @@ def check_merged_spikes( merged_spikes, spike_trains ): # change to something impossible so we dont find this event again all_spikes[i] = -1.0 indices[i] = True - assert( indices.all() ) + assert indices.all() def test_merge_spike_trains(): # first load the data - spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", time_interval=(0,4000)) spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) -- cgit v1.2.3 From 1ed2083d6ca865067d20e259b38659bd8c913728 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 12 Oct 2014 18:50:20 +0200 Subject: typos, a few words on cython performance --- Readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 7f07b65..acb7d2a 100644 --- a/Readme.md +++ b/Readme.md @@ -2,7 +2,8 @@ PySpike is a Python library for numerical analysis of spike train similarity. Its core functionality is the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). -Additionally, it allows to compute multi-variate spike train distances, averaging and general spike train processing. +Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, averaging and general spike train processing. +All computation intensive parts are implemented in C via [cython](http://www.cython.org) to reach a competitive performance (factor 100-200 over plain Python). All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). @@ -18,7 +19,7 @@ To use PySpike you need Python installed with the following additional packages: In particular, make sure that [cython](http://www.cython.org) is configured properly and able to locate a C compiler. -To install PySpike, simply download the source, i.e. via git clone, and run the setup.py script: +To install PySpike, simply download the source, e.g. from Github, and run the `setup.py` script: git clone https://github.com/mariomulansky/PySpike.git cd PySpike @@ -29,7 +30,7 @@ Then you can run the tests using the `nosetests` test framework: cd test nosetests -Finally, you should make the installation folder known to Python to be able to import pyspike in your own projects. +Finally, you should make PySpike's installation folder known to Python to be able to import pyspike in your own projects. Therefore, add your `/path/to/PySpike` to the `$PYTHONPATH` environment variable. ## Spike trains @@ -64,7 +65,7 @@ As result, `load_spike_trains_from_txt` returns a *list of arrays* containing th **Important note:** >Spike trains are expected to be *ordered sequences*! ->For performance reasons, the PySpike distance function do not check if the spike trains provided are indeed ordered. +>For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed ordered. >Make sure that all your spike trains are ordered. >If in doubt, use `spike_train = np.sort(spike_train)` to obtain a correctly ordered spike train. -- cgit v1.2.3 From ef15a482604d8ce9bef094d470d8a905c6da49a0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 12 Oct 2014 18:55:07 +0200 Subject: typos --- Readme.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index acb7d2a..2101d59 100644 --- a/Readme.md +++ b/Readme.md @@ -1,8 +1,8 @@ # PySpike -PySpike is a Python library for numerical analysis of spike train similarity. +PySpike is a Python library for the numerical analysis of spike train similarity. Its core functionality is the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). -Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, averaging and general spike train processing. +Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via [cython](http://www.cython.org) to reach a competitive performance (factor 100-200 over plain Python). All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). @@ -42,6 +42,8 @@ The following code creates such a spike train with some arbitrary spike times: spike_train = np.array([0.1, 0.3, 0.45, 0.6, 0.9]) +### Loading from text files + Typically, spike train data is loaded into PySpike from data files. The most straight-forward data files are text files where each line represents one spike train given as an sequence of spike times. An exemplary file with several spike trains is [PySpike_testdata.txt](https://github.com/mariomulansky/PySpike/blob/master/examples/PySpike_testdata.txt). @@ -53,9 +55,9 @@ To quickly obtain spike trains from such files, PySpike provides the function `l spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", time_interval=(0,4000)) -This function expects the name of the datafile as first parameter, and additionally the time intervall of the spike train measurement can be provided as a pair of start- and end-time values. +This function expects the name of the data file as first parameter, and additionally the time intervall of the spike train measurement can be provided as a pair of start- and end-time values. If the time interval is provided (`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. -Furthermore, the spike trains are ordered via `np.sort`. +Furthermore, the spike trains are ordered via `np.sort` (disable this feature by providing `sort=False` as a parameter to the load function). As result, `load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. -- cgit v1.2.3 From 4274c328a4927b392036d1c3b759b0787b05f300 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 13 Oct 2014 10:47:18 +0200 Subject: code formatting following PEP8 --- examples/isi_matrix.py | 2 -- examples/plot.py | 6 ++-- pyspike/distances.py | 77 ++++++++++++++++++++++++----------------------- pyspike/function.py | 35 +++++++++++---------- pyspike/python_backend.py | 72 ++++++++++++++++++++++---------------------- pyspike/spikes.py | 48 ++++++++++++++--------------- test/test_distance.py | 37 ++++++++++++----------- test/test_function.py | 28 ++++++++++------- test/test_spikes.py | 27 ++++++++--------- 9 files changed, 168 insertions(+), 164 deletions(-) diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py index 2a4d075..db740dd 100644 --- a/examples/isi_matrix.py +++ b/examples/isi_matrix.py @@ -11,7 +11,6 @@ Distributed under the MIT License (MIT) from __future__ import print_function -import numpy as np import matplotlib.pyplot as plt import pyspike as spk @@ -25,4 +24,3 @@ m = spk.isi_distance_matrix(spike_trains) plt.imshow(m, interpolation='none') plt.show() - diff --git a/examples/plot.py b/examples/plot.py index 4ff75c4..5c3ad4a 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -15,11 +15,11 @@ import matplotlib.pyplot as plt import pyspike as spk -spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0,4000)) +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) # plot the spike time -for (i,spikes) in enumerate(spike_trains): +for (i, spikes) in enumerate(spike_trains): plt.plot(spikes, i*np.ones_like(spikes), 'o') f = spk.isi_distance(spike_trains[0], spike_trains[1]) diff --git a/pyspike/distances.py b/pyspike/distances.py index db04c4e..b2eec92 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -17,7 +17,7 @@ from pyspike import PieceWiseConstFunc, PieceWiseLinFunc # isi_distance ############################################################ def isi_distance(spikes1, spikes2): - """ Computes the instantaneous isi-distance S_isi (t) of the two given + """ Computes the instantaneous isi-distance S_isi (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. @@ -27,9 +27,9 @@ def isi_distance(spikes1, spikes2): - PieceWiseConstFunc describing the isi-distance. """ # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0]==spikes2[0], \ + assert spikes1[0] == spikes2[0], \ "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1]==spikes2[-1], \ + assert spikes1[-1] == spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" # cython implementation @@ -53,9 +53,9 @@ def spike_distance(spikes1, spikes2): - PieceWiseLinFunc describing the spike-distance. """ # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0]==spikes2[0], \ + assert spikes1[0] == spikes2[0], \ "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1]==spikes2[-1], \ + assert spikes1[-1] == spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" # cython implementation @@ -74,33 +74,33 @@ def multi_distance(spike_trains, pair_distance_func, indices=None): use isi_distance_multi or spike_distance_multi instead. Computes the multi-variate distance for a set of spike-trains using the - pair_dist_func to compute pair-wise distances. That is it computes the + pair_dist_func to compute pair-wise distances. That is it computes the average distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, where the sum goes over all pairs . Args: - spike_trains: list of spike trains - pair_distance_func: function computing the distance of two spike trains - - indices: list of indices defining which spike trains to use, + - indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) Returns: - The averaged multi-variate distance of all pairs """ - if indices==None: + if indices is None: indices = np.arange(len(spike_trains)) indices = np.array(indices) # check validity of indices assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." + "Invalid index list." # generate a list of possible index pairs - pairs = [(i,j) for i in indices for j in indices[i+1:]] + pairs = [(i, j) for i in indices for j in indices[i+1:]] # start with first pair - (i,j) = pairs[0] + (i, j) = pairs[0] average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - for (i,j) in pairs[1:]: + for (i, j) in pairs[1:]: current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - average_dist.add(current_dist) # add to the average - average_dist.mul_scalar(1.0/len(pairs)) # normalize + average_dist.add(current_dist) # add to the average + average_dist.mul_scalar(1.0/len(pairs)) # normalize return average_dist @@ -113,45 +113,46 @@ def multi_distance_par(spike_trains, pair_distance_func, indices=None): """ num_threads = 2 - lock = threading.Lock() + def run(spike_trains, index_pairs, average_dist): - (i,j) = index_pairs[0] + (i, j) = index_pairs[0] # print(i,j) this_avrg = pair_distance_func(spike_trains[i], spike_trains[j]) - for (i,j) in index_pairs[1:]: + for (i, j) in index_pairs[1:]: # print(i,j) current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) this_avrg.add(current_dist) with lock: - average_dist.add(this_avrg) + average_dist.add(this_avrg) - if indices==None: + if indices is None: indices = np.arange(len(spike_trains)) indices = np.array(indices) # check validity of indices assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." + "Invalid index list." # generate a list of possible index pairs - pairs = [(i,j) for i in indices for j in indices[i+1:]] + pairs = [(i, j) for i in indices for j in indices[i+1:]] num_pairs = len(pairs) # start with first pair - (i,j) = pairs[0] + (i, j) = pairs[0] average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) # remove the one we already computed pairs = pairs[1:] # distribute the rest into num_threads pieces - clustered_pairs = [ pairs[i::num_threads] for i in xrange(num_threads) ] + clustered_pairs = [pairs[n::num_threads] for n in xrange(num_threads)] threads = [] for pairs in clustered_pairs: - t = threading.Thread(target=run, args=(spike_trains, pairs, average_dist)) + t = threading.Thread(target=run, args=(spike_trains, pairs, + average_dist)) threads.append(t) t.start() for t in threads: t.join() - average_dist.mul_scalar(1.0/num_pairs) # normalize + average_dist.mul_scalar(1.0/num_pairs) # normalize return average_dist @@ -161,11 +162,11 @@ def multi_distance_par(spike_trains, pair_distance_func, indices=None): def isi_distance_multi(spike_trains, indices=None): """ computes the multi-variate isi-distance for a set of spike-trains. That is the average isi-distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + S(t) = 2/((N(N-1)) sum_{} S_{i,j}, where the sum goes over all pairs Args: - spike_trains: list of spike trains - - indices: list of indices defining which spike trains to use, + - indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) Returns: - A PieceWiseConstFunc representing the averaged isi distance S @@ -177,13 +178,13 @@ def isi_distance_multi(spike_trains, indices=None): # spike_distance_multi ############################################################ def spike_distance_multi(spike_trains, indices=None): - """ computes the multi-variate spike-distance for a set of spike-trains. + """ computes the multi-variate spike-distance for a set of spike-trains. That is the average spike-distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + S(t) = 2/((N(N-1)) sum_{} S_{i, j}, where the sum goes over all pairs Args: - spike_trains: list of spike trains - - indices: list of indices defining which spike-trains to use, + - indices: list of indices defining which spike-trains to use, if None all given spike trains are used (default=None) Returns: - A PieceWiseLinFunc representing the averaged spike distance S @@ -198,21 +199,21 @@ def isi_distance_matrix(spike_trains, indices=None): - indices: list of indices defining which spike-trains to use if None all given spike-trains are used (default=None) Return: - - a 2D array of size len(indices)*len(indices) containing the average + - a 2D array of size len(indices)*len(indices) containing the average pair-wise isi-distance """ - if indices==None: + if indices is None: indices = np.arange(len(spike_trains)) indices = np.array(indices) # check validity of indices assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." + "Invalid index list." # generate a list of possible index pairs - pairs = [(i,j) for i in indices for j in indices[i+1:]] + pairs = [(i, j) for i in indices for j in indices[i+1:]] distance_matrix = np.zeros((len(indices), len(indices))) - for i,j in pairs: + for i, j in pairs: d = isi_distance(spike_trains[i], spike_trains[j]).abs_avrg() - distance_matrix[i,j] = d - distance_matrix[j,i] = d + distance_matrix[i, j] = d + distance_matrix[j, i] = d return distance_matrix diff --git a/pyspike/function.py b/pyspike/function.py index 243ef67..8107538 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -1,7 +1,7 @@ """ function.py -Module containing classes representing piece-wise constant and piece-wise linear -functions. +Module containing classes representing piece-wise constant and piece-wise +linear functions. Copyright 2014, Mario Mulansky @@ -35,7 +35,7 @@ class PieceWiseConstFunc: Args: - other: another PieceWiseConstFunc object Returns: - True if the two functions are equal up to `decimal` decimals, + True if the two functions are equal up to `decimal` decimals, False otherwise """ eps = 10.0**(-decimal) @@ -61,23 +61,23 @@ class PieceWiseConstFunc: """ Computes the average of the piece-wise const function: a = 1/T int f(x) dx where T is the length of the interval. Returns: - - the average a. + - the average a. """ return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ (self.x[-1]-self.x[0]) def abs_avrg(self): - """ Computes the average of the abs value of the piece-wise const + """ Computes the average of the abs value of the piece-wise const function: a = 1/T int |f(x)| dx where T is the length of the interval. Returns: - - the average a. + - the average a. """ return np.sum((self.x[1:]-self.x[:-1]) * np.abs(self.y)) / \ (self.x[-1]-self.x[0]) def add(self, f): - """ Adds another PieceWiseConst function to this function. + """ Adds another PieceWiseConst function to this function. Note: only functions defined on the same interval can be summed. Args: - f: PieceWiseConst function to be added. @@ -87,13 +87,13 @@ class PieceWiseConstFunc: # python implementation # from python_backend import add_piece_wise_const_python - # self.x, self.y = add_piece_wise_const_python(self.x, self.y, f.x, f.y) + # self.x, self.y = add_piece_wise_const_python(self.x, self.y, + # f.x, f.y) # cython version from cython_add import add_piece_wise_const_cython self.x, self.y = add_piece_wise_const_cython(self.x, self.y, f.x, f.y) - def mul_scalar(self, fac): """ Multiplies the function with a scalar value Args: @@ -113,10 +113,10 @@ class PieceWiseLinFunc: Args: - x: array of length N+1 defining the edges of the intervals of the pwc function. - - y1: array of length N defining the function values at the left of the - intervals. - - y2: array of length N defining the function values at the right of the + - y1: array of length N defining the function values at the left of the intervals. + - y2: array of length N defining the function values at the right of + the intervals. """ self.x = np.array(x) self.y1 = np.array(y1) @@ -128,7 +128,7 @@ class PieceWiseLinFunc: Args: - other: another PieceWiseLinFunc object Returns: - True if the two functions are equal up to `decimal` decimals, + True if the two functions are equal up to `decimal` decimals, False otherwise """ eps = 10.0**(-decimal) @@ -153,7 +153,7 @@ class PieceWiseLinFunc: """ Computes the average of the piece-wise linear function: a = 1/T int f(x) dx where T is the length of the interval. Returns: - - the average a. + - the average a. """ return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ (self.x[-1]-self.x[0]) @@ -162,13 +162,13 @@ class PieceWiseLinFunc: """ Computes the absolute average of the piece-wise linear function: a = 1/T int |f(x)| dx where T is the length of the interval. Returns: - - the average a. + - the average a. """ return np.sum((self.x[1:]-self.x[:-1]) * 0.5 * (np.abs(self.y1)+np.abs(self.y2)))/(self.x[-1]-self.x[0]) def add(self, f): - """ Adds another PieceWiseLin function to this function. + """ Adds another PieceWiseLin function to this function. Note: only functions defined on the same interval can be summed. Args: - f: PieceWiseLin function to be added. @@ -178,7 +178,7 @@ class PieceWiseLinFunc: # python implementation # from python_backend import add_piece_wise_lin_python - # self.x, self.y1, self.y2 = add_piece_wise_lin_python( + # self.x, self.y1, self.y2 = add_piece_wise_lin_python( # self.x, self.y1, self.y2, f.x, f.y1, f.y2) # cython version @@ -186,7 +186,6 @@ class PieceWiseLinFunc: self.x, self.y1, self.y2 = add_piece_wise_lin_cython( self.x, self.y1, self.y2, f.x, f.y1, f.y2) - def mul_scalar(self, fac): """ Multiplies the function with a scalar value Args: diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index e5b74e9..cf1a92f 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -1,6 +1,6 @@ """ python_backend.py -Collection of python functions that can be used instead of the cython +Collection of python functions that can be used instead of the cython implementation. Copyright 2014, Mario Mulansky @@ -21,18 +21,18 @@ def isi_distance_python(s1, s2): """ Plain Python implementation of the isi distance. """ # compute the interspike interval - nu1 = s1[1:]-s1[:-1] - nu2 = s2[1:]-s2[:-1] - + nu1 = s1[1:] - s1[:-1] + nu2 = s2[1:] - s2[:-1] + # compute the isi-distance - spike_events = np.empty(len(nu1)+len(nu2)) + spike_events = np.empty(len(nu1) + len(nu2)) spike_events[0] = s1[0] # the values have one entry less - the number of intervals between events - isi_values = np.empty(len(spike_events)-1) + isi_values = np.empty(len(spike_events) - 1) # add the distance of the first events # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ # else 1.0 - nu2[0]/nu1[0] - isi_values[0] = (nu1[0]-nu2[0])/max(nu1[0],nu2[0]) + isi_values[0] = (nu1[0] - nu2[0]) / max(nu1[0], nu2[0]) index1 = 0 index2 = 0 index = 1 @@ -49,28 +49,28 @@ def isi_distance_python(s1, s2): if index2 >= len(nu2): break spike_events[index] = s2[index2] - else: # s1[index1+1] == s2[index2+1] + else: # s1[index1 + 1] == s2[index2 + 1] index1 += 1 index2 += 1 if (index1 >= len(nu1)) or (index2 >= len(nu2)): break spike_events[index] = s1[index1] # compute the corresponding isi-distance - isi_values[index] = (nu1[index1]-nu2[index2]) / \ - max(nu1[index1], nu2[index2]) + isi_values[index] = (nu1[index1] - nu2[index2]) / \ + max(nu1[index1], nu2[index2]) index += 1 # the last event is the interval end spike_events[index] = s1[-1] - # use only the data added above + # use only the data added above # could be less than original length due to equal spike times - return PieceWiseConstFunc(spike_events[:index+1], isi_values[:index]) + return PieceWiseConstFunc(spike_events[:index + 1], isi_values[:index]) ############################################################ # get_min_dist ############################################################ def get_min_dist(spike_time, spike_train, start_index=0): - """ Returns the minimal distance |spike_time - spike_train[i]| + """ Returns the minimal distance |spike_time - spike_train[i]| with i>=start_index. """ d = abs(spike_time - spike_train[start_index]) @@ -99,18 +99,18 @@ def spike_distance_python(spikes1, spikes2): - PieceWiseLinFunc describing the spike-distance. """ # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0]==spikes2[0], \ + assert spikes1[0] == spikes2[0], \ "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1]==spikes2[-1], \ + assert spikes1[-1] == spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" # shorter variables t1 = spikes1 t2 = spikes2 - spike_events = np.empty(len(t1)+len(t2)-2) + spike_events = np.empty(len(t1) + len(t2) - 2) spike_events[0] = t1[0] - y_starts = np.empty(len(spike_events)-1) - y_ends = np.empty(len(spike_events)-1) + y_starts = np.empty(len(spike_events) - 1) + y_ends = np.empty(len(spike_events) - 1) index1 = 0 index2 = 0 @@ -133,9 +133,10 @@ def spike_distance_python(spikes1, spikes2): break spike_events[index] = t1[index1] # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time now was the following time before + dt_p1 = dt_f1 # the previous time was the following time before s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + + dt_f2*(t1[index1]-t2[index2])) / isi2 y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) # now the next interval start value dt_f1 = get_min_dist(t1[index1+1], t2, index2) @@ -148,8 +149,9 @@ def spike_distance_python(spikes1, spikes2): break spike_events[index] = t2[index2] # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time now was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 + dt_p2 = dt_f2 # the previous time was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + + dt_f1*(t2[index2]-t1[index1])) / isi1 s2 = dt_p2 y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) # now the next interval start value @@ -158,7 +160,7 @@ def spike_distance_python(spikes1, spikes2): isi2 = t2[index2+1]-t2[index2] # s2 is the same as above, thus we can compute y2 immediately y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - else: # t1[index1+1] == t2[index2+1] - generate only one event + else: # t1[index1+1] == t2[index2+1] - generate only one event index1 += 1 index2 += 1 if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): @@ -183,9 +185,9 @@ def spike_distance_python(spikes1, spikes2): s1 = dt_p1*(t1[-1]-t1[-2])/isi1 s2 = dt_p2*(t2[-1]-t2[-2])/isi2 y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - # use only the data added above + # use only the data added above # could be less than original length due to equal spike times - return PieceWiseLinFunc(spike_events[:index+1], + return PieceWiseLinFunc(spike_events[:index+1], y_starts[:index], y_ends[:index]) @@ -209,7 +211,7 @@ def add_piece_wise_const_python(x1, y1, x2, y2): elif x1[index1+1] > x2[index2+1]: index2 += 1 x_new[index] = x2[index2] - else: # x1[index1+1] == x2[index2+1]: + else: # x1[index1+1] == x2[index2+1]: index1 += 1 index2 += 1 x_new[index] = x1[index1] @@ -217,15 +219,13 @@ def add_piece_wise_const_python(x1, y1, x2, y2): # 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] + 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] + 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 + 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 @@ -244,9 +244,9 @@ def add_piece_wise_lin_python(x1, y11, y12, x2, y21, y22): 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 + 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]: @@ -272,7 +272,7 @@ def add_piece_wise_lin_python(x1, y11, y12, x2, y21, y22): 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]: + else: # x1[index1+1] == x2[index2+1]: y2_new[index] = y12[index1] + y22[index2] index1 += 1 index2 += 1 @@ -297,7 +297,7 @@ def add_piece_wise_lin_python(x1, y11, y12, x2, y21, y22): 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 + 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 diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 6ea94de..c496ab8 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -31,11 +31,11 @@ def add_auxiliary_spikes(spike_train, time_interval): except: T_start = 0 T_end = time_interval - + assert spike_train[0] >= T_start, \ - "Spike train has events before the given start time" + "Spike train has events before the given start time" assert spike_train[-1] <= T_end, \ - "Spike train has events after the given end time" + "Spike train has events after the given end time" if spike_train[0] != T_start: spike_train = np.insert(spike_train, 0, T_start) if spike_train[-1] != T_end: @@ -64,16 +64,16 @@ def spike_train_from_string(s, sep=' ', sort=True): ############################################################ # load_spike_trains_txt ############################################################ -def load_spike_trains_from_txt(file_name, time_interval=None, +def load_spike_trains_from_txt(file_name, time_interval=None, separator=' ', comment='#', sort=True): - """ Loads a number of spike trains from a text file. Each line of the text - file should contain one spike train as a sequence of spike times separated - by `separator`. Empty lines as well as lines starting with `comment` are - neglected. The `time_interval` represents the start and the end of the spike - trains and it is used to add auxiliary spikes at the beginning and end of - each spike train. However, if `time_interval == None`, no auxiliary spikes - are added, but note that the Spike and ISI distance both require auxiliary - spikes. + """ Loads a number of spike trains from a text file. Each line of the text + file should contain one spike train as a sequence of spike times separated + by `separator`. Empty lines as well as lines starting with `comment` are + neglected. The `time_interval` represents the start and the end of the + spike trains and it is used to add auxiliary spikes at the beginning and + end of each spike train. However, if `time_interval == None`, no auxiliary + spikes are added, but note that the Spike and ISI distance both require + auxiliary spikes. Args: - file_name: The name of the text file. - time_interval: A pair (T_start, T_end) of values representing the start @@ -87,10 +87,10 @@ def load_spike_trains_from_txt(file_name, time_interval=None, spike_trains = [] spike_file = open(file_name, 'r') for line in spike_file: - if len(line) > 1 and not line.startswith(comment): + if len(line) > 1 and not line.startswith(comment): # use only the lines with actual data and not commented spike_train = spike_train_from_string(line, separator, sort) - if not time_interval == None: # add auxiliary spikes if times given + if time_interval is not None: # add auxil. spikes if times given spike_train = add_auxiliary_spikes(spike_train, time_interval) spike_trains.append(spike_train) return spike_trains @@ -109,19 +109,19 @@ def merge_spike_trains(spike_trains): # get the lengths of the spike trains lens = np.array([len(st) for st in spike_trains]) merged_spikes = np.empty(np.sum(lens)) - index = 0 # the index for merged_spikes - indices = np.zeros_like(lens) # indices of the spike trains - index_list = np.arange(len(indices)) # indices of indices of spike trains - # that have not yet reached the end + index = 0 # the index for merged_spikes + indices = np.zeros_like(lens) # indices of the spike trains + index_list = np.arange(len(indices)) # indices of indices of spike trains + # that have not yet reached the end # list of the possible events in the spike trains vals = [spike_trains[i][indices[i]] for i in index_list] while len(index_list) > 0: - i = np.argmin(vals) # the next spike is the minimum - merged_spikes[index] = vals[i] # put it to the merged spike train + i = np.argmin(vals) # the next spike is the minimum + merged_spikes[index] = vals[i] # put it to the merged spike train i = index_list[i] - index += 1 # next index of merged spike train - indices[i] += 1 # next index for the chosen spike train - if indices[i] >= lens[i]: # remove spike train index if ended + index += 1 # next index of merged spike train + indices[i] += 1 # next index for the chosen spike train + if indices[i] >= lens[i]: # remove spike train index if ended index_list = index_list[index_list != i] - vals = [spike_trains[i][indices[i]] for i in index_list] + vals = [spike_trains[n][indices[n]] for n in index_list] return merged_spikes diff --git a/test/test_distance.py b/test/test_distance.py index dafe693..3371cbd 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -22,8 +22,8 @@ def test_isi(): t2 = np.array([0.3, 0.45, 0.8, 0.9, 0.95]) # pen&paper calculation of the isi distance - expected_times = [0.0,0.2,0.3,0.4,0.45,0.6,0.7,0.8,0.9,0.95,1.0] - expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, + expected_times = [0.0, 0.2, 0.3, 0.4, 0.45, 0.6, 0.7, 0.8, 0.9, 0.95, 1.0] + expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, -0.25/0.35, -0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] t1 = spk.add_auxiliary_spikes(t1, 1.0) @@ -36,10 +36,10 @@ def test_isi(): assert_array_almost_equal(f.y, expected_isi, decimal=14) # check with some equal spike times - t1 = np.array([0.2,0.4,0.6]) - t2 = np.array([0.1,0.4,0.5,0.6]) + t1 = np.array([0.2, 0.4, 0.6]) + t2 = np.array([0.1, 0.4, 0.5, 0.6]) - expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] + expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] expected_isi = [0.1/0.2, -0.1/0.3, -0.1/0.3, 0.1/0.2, 0.1/0.2, -0.0/0.5] t1 = spk.add_auxiliary_spikes(t1, 1.0) @@ -56,11 +56,11 @@ def test_spike(): t2 = np.array([0.3, 0.45, 0.8, 0.9, 0.95]) # pen&paper calculation of the spike distance - expected_times = [0.0,0.2,0.3,0.4,0.45,0.6,0.7,0.8,0.9,0.95,1.0] + expected_times = [0.0, 0.2, 0.3, 0.4, 0.45, 0.6, 0.7, 0.8, 0.9, 0.95, 1.0] s1 = np.array([0.1, 0.1, (0.1*0.1+0.05*0.1)/0.2, 0.05, (0.05*0.15 * 2)/0.2, 0.15, 0.1, 0.1*0.2/0.3, 0.1**2/0.3, 0.1*0.05/0.3, 0.1]) - s2 = np.array([0.1, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, - (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, + s2 = np.array([0.1, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, + (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, 0.1, 0.1, 0.05, 0.05]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.1, 0.3, 0.3, 0.3, 0.3]) isi2 = np.array([0.3, 0.3, 0.15, 0.15, 0.35, 0.35, 0.35, 0.1, 0.05, 0.05]) @@ -76,17 +76,17 @@ def test_spike(): assert_array_almost_equal(f.y2, expected_y2, decimal=14) # check with some equal spike times - t1 = np.array([0.2,0.4,0.6]) - t2 = np.array([0.1,0.4,0.5,0.6]) + t1 = np.array([0.2, 0.4, 0.6]) + t2 = np.array([0.1, 0.4, 0.5, 0.6]) - expected_times = [0.0,0.1,0.2,0.4,0.5,0.6,1.0] + expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] s1 = np.array([0.1, 0.1*0.1/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) s2 = np.array([0.1*0.1/0.3, 0.1, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.4]) isi2 = np.array([0.3, 0.3, 0.3, 0.1, 0.1, 0.4]) expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) - + t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_distance(t1, t2) @@ -100,8 +100,8 @@ def check_multi_distance(dist_func, dist_func_multi): # generate spike trains: t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) t2 = spk.add_auxiliary_spikes(np.array([0.3, 0.45, 0.8, 0.9, 0.95]), 1.0) - t3 = spk.add_auxiliary_spikes(np.array([0.2,0.4,0.6]), 1.0) - t4 = spk.add_auxiliary_spikes(np.array([0.1,0.4,0.5,0.6]), 1.0) + t3 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6]), 1.0) + t4 = spk.add_auxiliary_spikes(np.array([0.1, 0.4, 0.5, 0.6]), 1.0) spike_trains = [t1, t2, t3, t4] f12 = dist_func(t1, t2) @@ -111,17 +111,17 @@ def check_multi_distance(dist_func, dist_func_multi): f24 = dist_func(t2, t4) f34 = dist_func(t3, t4) - f_multi = dist_func_multi(spike_trains, [0,1]) + f_multi = dist_func_multi(spike_trains, [0, 1]) assert f_multi.almost_equal(f12, decimal=14) f = copy(f12) f.add(f13) f.add(f23) f.mul_scalar(1.0/3) - f_multi = dist_func_multi(spike_trains, [0,1,2]) + f_multi = dist_func_multi(spike_trains, [0, 1, 2]) assert f_multi.almost_equal(f, decimal=14) - f.mul_scalar(3) # revert above normalization + f.mul_scalar(3) # revert above normalization f.add(f14) f.add(f24) f.add(f34) @@ -139,6 +139,7 @@ def test_multi_spike(): if __name__ == "__main__": - test_auxiliary_spikes() test_isi() test_spike() + test_multi_isi() + test_multi_spike() diff --git a/test/test_function.py b/test/test_function.py index c0fb3fd..ed7d6bc 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -10,18 +10,18 @@ Distributed under the MIT License (MIT) from __future__ import print_function import numpy as np from copy import copy -from numpy.testing import assert_equal, assert_almost_equal, \ - assert_array_almost_equal +from numpy.testing import assert_almost_equal, assert_array_almost_equal import pyspike as spk + def test_pwc(): # some random data x = [0.0, 1.0, 2.0, 2.5, 4.0] y = [1.0, -0.5, 1.5, 0.75] f = spk.PieceWiseConstFunc(x, y) xp, yp = f.get_plottable_data() - + xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] yp_expected = [1.0, 1.0, -0.5, -0.5, 1.5, 1.5, 0.75, 0.75] assert_array_almost_equal(xp, xp_expected, decimal=16) @@ -51,17 +51,18 @@ def test_pwc_add(): f2.add(f) assert_array_almost_equal(f2.x, x_expected, decimal=16) assert_array_almost_equal(f2.y, y_expected, decimal=16) - + f1.add(f2) # same x, but y doubled assert_array_almost_equal(f1.x, f2.x, decimal=16) assert_array_almost_equal(f1.y, 2*f2.y, decimal=16) + def test_pwc_mul(): x = [0.0, 1.0, 2.0, 2.5, 4.0] y = [1.0, -0.5, 1.5, 0.75] f = spk.PieceWiseConstFunc(x, y) - + f.mul_scalar(1.5) assert_array_almost_equal(f.x, x, decimal=16) assert_array_almost_equal(f.y, 1.5*np.array(y), decimal=16) @@ -75,15 +76,15 @@ def test_pwl(): y2 = [1.5, -0.4, 1.5, 0.25] f = spk.PieceWiseLinFunc(x, y1, y2) xp, yp = f.get_plottable_data() - + xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] yp_expected = [1.0, 1.5, -0.5, -0.4, 1.5, 1.5, 0.75, 0.25] assert_array_almost_equal(xp, xp_expected, decimal=16) assert_array_almost_equal(yp, yp_expected, decimal=16) - + avrg_expected = (1.25 - 0.45 + 0.75 + 1.5*0.5) / 4.0 assert_almost_equal(f.avrg(), avrg_expected, decimal=16) - + abs_avrg_expected = (1.25 + 0.45 + 0.75 + 1.5*0.5) / 4.0 assert_almost_equal(f.abs_avrg(), abs_avrg_expected, decimal=16) @@ -113,7 +114,7 @@ def test_pwl_add(): assert_array_almost_equal(f2.x, x_expected, decimal=16) assert_array_almost_equal(f2.y1, y1_expected, decimal=16) assert_array_almost_equal(f2.y2, y2_expected, decimal=16) - + f1.add(f2) # same x, but y doubled assert_array_almost_equal(f1.x, f2.x, decimal=16) @@ -121,12 +122,12 @@ def test_pwl_add(): assert_array_almost_equal(f1.y2, 2*f2.y2, decimal=16) -def test_pwc_mul(): +def test_pwl_mul(): x = [0.0, 1.0, 2.0, 2.5, 4.0] y1 = [1.0, -0.5, 1.5, 0.75] y2 = [1.5, -0.4, 1.5, 0.25] f = spk.PieceWiseLinFunc(x, y1, y2) - + f.mul_scalar(1.5) assert_array_almost_equal(f.x, x, decimal=16) assert_array_almost_equal(f.y1, 1.5*np.array(y1), decimal=16) @@ -137,3 +138,8 @@ def test_pwc_mul(): if __name__ == "__main__": test_pwc() + test_pwc_add() + test_pwc_mul() + test_pwl() + test_pwl_add() + test_pwl_mul() diff --git a/test/test_spikes.py b/test/test_spikes.py index e008207..349e0bf 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -23,13 +23,13 @@ def test_auxiliary_spikes(): def test_load_from_txt(): - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0,4000)) + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) assert len(spike_trains) == 40 # check the first spike train - spike_times = [0, 64.886, 305.81, 696, 937.77, 1059.7, 1322.2, 1576.1, - 1808.1, 2121.5, 2381.1, 2728.6, 2966.9, 3223.7, 3473.7, + spike_times = [0, 64.886, 305.81, 696, 937.77, 1059.7, 1322.2, 1576.1, + 1808.1, 2121.5, 2381.1, 2728.6, 2966.9, 3223.7, 3473.7, 3644.3, 3936.3, 4000] assert_equal(spike_times, spike_trains[0]) @@ -39,15 +39,15 @@ def test_load_from_txt(): assert spike_train[-1] == 4000 # load without adding auxiliary spikes - spike_trains2 = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=None) + spike_trains2 = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=None) assert len(spike_trains2) == 40 # check auxiliary spikes for i in xrange(len(spike_trains)): - assert len(spike_trains[i]) == len(spike_trains2[i])+2 # two spikes less + assert len(spike_trains[i]) == len(spike_trains2[i])+2 # 2 spikes less -def check_merged_spikes( merged_spikes, spike_trains ): +def check_merged_spikes(merged_spikes, spike_trains): # create a flat array with all spike events all_spikes = np.array([]) for spike_train in spike_trains: @@ -55,7 +55,7 @@ def check_merged_spikes( merged_spikes, spike_trains ): indices = np.zeros_like(all_spikes, dtype='bool') # check if we find all the spike events in the original spike trains for x in merged_spikes: - i = np.where(all_spikes == x)[0][0] # the first axis and the first entry + i = np.where(all_spikes == x)[0][0] # first axis and first entry # change to something impossible so we dont find this event again all_spikes[i] = -1.0 indices[i] = True @@ -64,23 +64,22 @@ def check_merged_spikes( merged_spikes, spike_trains ): def test_merge_spike_trains(): # first load the data - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0,4000)) + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) # test if result is sorted assert((spikes == np.sort(spikes)).all()) # check merging - check_merged_spikes( spikes, [spike_trains[0], spike_trains[1]] ) + check_merged_spikes(spikes, [spike_trains[0], spike_trains[1]]) spikes = spk.merge_spike_trains(spike_trains) # test if result is sorted assert((spikes == np.sort(spikes)).all()) # check merging - check_merged_spikes( spikes, spike_trains ) + check_merged_spikes(spikes, spike_trains) if __name__ == "main": test_auxiliary_spikes() test_load_from_txt() test_merge_spike_trains() - -- cgit v1.2.3 From 5ce807943fab2ba233cff661e34e4d6a83397b99 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 13 Oct 2014 11:03:42 +0200 Subject: changed to BSD license --- License | 25 ++++++++----------------- examples/isi_matrix.py | 2 +- examples/merge.py | 2 +- examples/plot.py | 2 +- pyspike/__init__.py | 2 +- pyspike/cython_distance.pyx | 2 +- pyspike/distances.py | 2 +- pyspike/function.py | 2 +- pyspike/python_backend.py | 2 +- pyspike/spikes.py | 2 +- test/test_distance.py | 2 +- test/test_function.py | 2 +- test/test_spikes.py | 2 +- 13 files changed, 20 insertions(+), 29 deletions(-) diff --git a/License b/License index 95d0405..472deac 100644 --- a/License +++ b/License @@ -1,21 +1,12 @@ -The MIT License (MIT) +BSD License -Copyright (c) 2014 Mario Mulansky, +Copyright (c) 2014, Mario Mulansky +All rights reserved. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py index db740dd..7bf1cf9 100644 --- a/examples/isi_matrix.py +++ b/examples/isi_matrix.py @@ -5,7 +5,7 @@ trains. Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ diff --git a/examples/merge.py b/examples/merge.py index 726d32b..2550cdb 100644 --- a/examples/merge.py +++ b/examples/merge.py @@ -4,7 +4,7 @@ Simple example showing the merging of two spike trains. Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ from __future__ import print_function diff --git a/examples/plot.py b/examples/plot.py index 5c3ad4a..da53670 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -4,7 +4,7 @@ Simple example showing how to load and plot spike trains and their distances. Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 3867e6e..c58a6b1 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,7 +1,7 @@ """ Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ __all__ = ["function", "distances", "spikes"] diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 4ab4381..ccf8060 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -12,7 +12,7 @@ improves the performance of spike_distance by a factor of 10! Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ diff --git a/pyspike/distances.py b/pyspike/distances.py index b2eec92..3b9fe1f 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -4,7 +4,7 @@ Module containing several functions to compute spike distances Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ import numpy as np diff --git a/pyspike/function.py b/pyspike/function.py index 8107538..7722cc3 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -5,7 +5,7 @@ linear functions. Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ from __future__ import print_function diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index cf1a92f..a1f5ea2 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -5,7 +5,7 @@ implementation. Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ diff --git a/pyspike/spikes.py b/pyspike/spikes.py index c496ab8..d390222 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -4,7 +4,7 @@ Module containing several function to load and transform spike trains Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ import numpy as np diff --git a/test/test_distance.py b/test/test_distance.py index 3371cbd..b500b2c 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -4,7 +4,7 @@ Tests the isi- and spike-distance computation Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ diff --git a/test/test_function.py b/test/test_function.py index ed7d6bc..a579796 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -4,7 +4,7 @@ Tests the PieceWiseConst and PieceWiseLinear functions Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ from __future__ import print_function diff --git a/test/test_spikes.py b/test/test_spikes.py index 349e0bf..bf914c0 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -4,7 +4,7 @@ Test loading of spike trains from text files Copyright 2014, Mario Mulansky -Distributed under the MIT License (MIT) +Distributed under the BSD License """ from __future__ import print_function -- cgit v1.2.3 From 2751cce9533bf8be7092fde4031ed585897e638d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 13 Oct 2014 12:55:46 +0200 Subject: changed licence reference --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 2101d59..4810f77 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,7 @@ Its core functionality is the implementation of the bivariate [ISI and SPIKE dis Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via [cython](http://www.cython.org) to reach a competitive performance (factor 100-200 over plain Python). -All source codes are published under the liberal [MIT License](http://opensource.org/licenses/MIT). +All source codes are published under the [BSD License](http://opensource.org/licenses/BSD-2-Clause). ## Requirements and Installation -- cgit v1.2.3 From e827249fecb88ad5835201a9fb2a975bf7476a41 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 13 Oct 2014 15:22:22 +0200 Subject: +contributors --- Contributors | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Contributors diff --git a/Contributors b/Contributors new file mode 100644 index 0000000..51e6751 --- /dev/null +++ b/Contributors @@ -0,0 +1,10 @@ +Python/C Programming: +- Mario Mulansky + +Scientific Methods: +- Thomas Kreuz +- Nebojsa D. Bozanic + +The work on PySpike was supported by the European Comission through the Marie +Curie Initial Training Network 'Neural Engineering Transformative Technologies +(NETT)' under the project number 289146. \ No newline at end of file -- cgit v1.2.3 From cb3c4dc6b023c309f21b832ccc5616a22083586d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 13 Oct 2014 15:44:09 +0200 Subject: separate scholarpedia links, scientific references --- Readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 4810f77..a1b6e9e 100644 --- a/Readme.md +++ b/Readme.md @@ -1,12 +1,16 @@ # PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the bivariate [ISI and SPIKE distance](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony). +Its core functionality is the implementation of the bivariate [ISI](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance) [1] and [SPIKE](http://www.scholarpedia.org/article/SPIKE-distance) [2] distance. Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via [cython](http://www.cython.org) to reach a competitive performance (factor 100-200 over plain Python). All source codes are published under the [BSD License](http://opensource.org/licenses/BSD-2-Clause). +>[1] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) + +>[2] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) + ## Requirements and Installation To use PySpike you need Python installed with the following additional packages: -- cgit v1.2.3 From cf91333e4aac74d96cca32042f4363e79e7ab051 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 14 Oct 2014 17:14:44 +0200 Subject: more docs --- Readme.md | 27 +++++++++++++++++++++++++++ pyspike/spikes.py | 3 ++- setup.py | 5 +---- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Readme.md b/Readme.md index a1b6e9e..55dbd33 100644 --- a/Readme.md +++ b/Readme.md @@ -64,6 +64,12 @@ If the time interval is provided (`time_interval is not None`), auxiliary spikes Furthermore, the spike trains are ordered via `np.sort` (disable this feature by providing `sort=False` as a parameter to the load function). As result, `load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. +If you load spike trains yourself, i.e. from data files with different structure, you can use the helper function `add_auxiliary_spikes` to add the auxiliary spikes at the beginning and end of the observation interval. +Both the ISI and the SPIKE distance computation require the presence of auxiliary spikes, so make sure you have those in your spike trains: + + spike_train = spk.add_auxiliary_spikes(spike_train, (T_start, T_end)) + # you provide only a single value, it is interpreted as T_end, while T_start=0 + spike_train = spk.add_auxiliary_spikes(spike_train, T_end) ## Computing bi-variate distances @@ -74,9 +80,30 @@ As result, `load_spike_trains_from_txt` returns a *list of arrays* containing th >For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed ordered. >Make sure that all your spike trains are ordered. >If in doubt, use `spike_train = np.sort(spike_train)` to obtain a correctly ordered spike train. +> +>Furthermore, the spike trains should have auxiliary spikes at the beginning and end of the observation interval. +>You can ensure this by providing the `time_interval` in the `load_spike_trains_from_txt` function, or calling `add_auxiliary_spikes` for your spike trains. +>The spike trains must have *the same* observation interval! ---------------------- +### ISI-distance + +The following code loads some exemplary spike trains, computes the dissimilarity profile ISI-distance of the first two spike trains, and plots it with matplotlib: + + import matplotlib.pyplot as plt + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + isi_profile = spk.isi_distance(spike_trains[0], spike_trains[1]) + x, y = isi_profile.get_plottable_data() + plt.plot(x, np.abs(y), '--k') + print("ISI distance: %.8f" % isi_profil.abs_avrg()) + plt.show() + +The ISI-profile is a piece-wise constant function, there the function `isi_distance` returns an instance of the `PieceWiseConstFunc` class. +As above, this class allows you to obtain arrays that can be used to plot the function with `plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. ## Computing multi-variate distances diff --git a/pyspike/spikes.py b/pyspike/spikes.py index d390222..68c8bc1 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -20,7 +20,8 @@ def add_auxiliary_spikes(spike_train, time_interval): - time_interval: A pair (T_start, T_end) of values representing the start and end time of the spike train measurement or a single value representing the end time, the T_start is then assuemd as 0. Auxiliary spikes will be - added to the spike train at the beginning and end of this interval. + added to the spike train at the beginning and end of this interval, if they + are not yet present. Returns: - spike train with additional spikes at T_start and T_end. diff --git a/setup.py b/setup.py index 7c8e4e1..16a87ea 100644 --- a/setup.py +++ b/setup.py @@ -5,12 +5,9 @@ Handles the compilation of pyx source files Copyright 2014, Mario Mulansky """ -import os from distutils.core import setup from Cython.Build import cythonize -import numpy.distutils.intelccompiler - setup( - ext_modules = cythonize("pyspike/*.pyx") + ext_modules=cythonize("pyspike/*.pyx") ) -- cgit v1.2.3 From 823e44d055b71ba406ea380e8f1087443b38ebb4 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 14 Oct 2014 17:15:44 +0200 Subject: missing cython add implementation --- pyspike/cython_add.pyx | 174 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 pyspike/cython_add.pyx diff --git a/pyspike/cython_add.pyx b/pyspike/cython_add.pyx new file mode 100644 index 0000000..bfbe208 --- /dev/null +++ b/pyspike/cython_add.pyx @@ -0,0 +1,174 @@ +#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 + +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] + # the last value is again the end of the interval + # x_new[index+1] = x1[-1] + # only use the data that was actually filled + x1 = x_new[:index+2] + y1 = y_new[:index+1] + # end nogil + return np.array(x_new[:index+2]), np.array(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.array(x_new[:index+2]), + np.array(y1_new[:index+1]), + np.array(y2_new[:index+1])) -- cgit v1.2.3 From 4ff7112e69e75a31c476304ef35960432b8ee17b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 14 Oct 2014 17:39:49 +0200 Subject: more isi docs --- Readme.md | 12 ++++++++++-- setup.py | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 55dbd33..6348d09 100644 --- a/Readme.md +++ b/Readme.md @@ -98,13 +98,21 @@ The following code loads some exemplary spike trains, computes the dissimilarity time_interval=(0, 4000)) isi_profile = spk.isi_distance(spike_trains[0], spike_trains[1]) x, y = isi_profile.get_plottable_data() - plt.plot(x, np.abs(y), '--k') - print("ISI distance: %.8f" % isi_profil.abs_avrg()) + plt.plot(x, y, '--k') + print("ISI distance: %.8f" % isi_profil.avrg()) plt.show() The ISI-profile is a piece-wise constant function, there the function `isi_distance` returns an instance of the `PieceWiseConstFunc` class. As above, this class allows you to obtain arrays that can be used to plot the function with `plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. +Furthermore, `PieceWiseConstFunc` provides an `add` function that can be used to add piece-wise constant function, and a `mul_scalar` function that rescales the function by a scalar. +This can be used to obtain an average profile: + + isi_profile1.add(isi_profile2) + isi_profile1.mul_scalar(0.5) + x, y = isi_profile1.get_plottable_data() + plt.plot(x, y, label="Average ISI profile") + ## Computing multi-variate distances diff --git a/setup.py b/setup.py index 16a87ea..31db6f8 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ Handles the compilation of pyx source files Copyright 2014, Mario Mulansky +Distributed under the BSD License + """ from distutils.core import setup from Cython.Build import cythonize -- cgit v1.2.3 From 1bd3fa7ef0bf395f44aea493858bbdd563fcb0c4 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 14 Oct 2014 17:40:49 +0200 Subject: typo --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 6348d09..5ab9570 100644 --- a/Readme.md +++ b/Readme.md @@ -89,7 +89,7 @@ Both the ISI and the SPIKE distance computation require the presence of auxiliar ### ISI-distance -The following code loads some exemplary spike trains, computes the dissimilarity profile ISI-distance of the first two spike trains, and plots it with matplotlib: +The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two spike trains, and plots it with matplotlib: import matplotlib.pyplot as plt import pyspike as spk -- cgit v1.2.3 From 65801901e6d3325c8d1c82ab92334ca19ebd92d7 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 14 Oct 2014 17:49:23 +0200 Subject: changed isi distance profile to abs values --- examples/plot.py | 2 -- pyspike/cython_distance.pyx | 4 ++-- pyspike/distances.py | 2 +- pyspike/function.py | 16 +++------------- test/test_distance.py | 6 +++--- test/test_function.py | 2 -- 6 files changed, 9 insertions(+), 23 deletions(-) diff --git a/examples/plot.py b/examples/plot.py index da53670..4ac8f5d 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -29,8 +29,6 @@ plt.figure() plt.plot(x, np.abs(y), '--k') print("Average: %.8f" % f.avrg()) -print("Absolute average: %.8f" % f.abs_avrg()) - f = spk.spike_distance(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index ccf8060..178fcba 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -60,7 +60,7 @@ def isi_distance_cython(double[:] s1, isi_values = np.empty(N1+N2-1) with nogil: # release the interpreter to allow multithreading - isi_values[0] = (nu1-nu2)/fmax(nu1,nu2) + isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2) index1 = 0 index2 = 0 index = 1 @@ -88,7 +88,7 @@ def isi_distance_cython(double[:] s1, nu1 = s1[index1+1]-s1[index1] nu2 = s2[index2+1]-s2[index2] # compute the corresponding isi-distance - isi_values[index] = (nu1 - nu2) / fmax(nu1, nu2) + isi_values[index] = fabs(nu1 - nu2) / fmax(nu1, nu2) index += 1 # the last event is the interval end spike_events[index] = s1[N1] diff --git a/pyspike/distances.py b/pyspike/distances.py index 3b9fe1f..08d0ed8 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -213,7 +213,7 @@ def isi_distance_matrix(spike_trains, indices=None): distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: - d = isi_distance(spike_trains[i], spike_trains[j]).abs_avrg() + d = isi_distance(spike_trains[i], spike_trains[j]).avrg() distance_matrix[i, j] = d distance_matrix[j, i] = d return distance_matrix diff --git a/pyspike/function.py b/pyspike/function.py index 7722cc3..bd3e2d5 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -18,7 +18,7 @@ import numpy as np ############################################################## class PieceWiseConstFunc: """ A class representing a piece-wise constant function. """ - + def __init__(self, x, y): """ Constructs the piece-wise const function. Args: @@ -66,16 +66,6 @@ class PieceWiseConstFunc: return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ (self.x[-1]-self.x[0]) - def abs_avrg(self): - """ Computes the average of the abs value of the piece-wise const - function: - a = 1/T int |f(x)| dx where T is the length of the interval. - Returns: - - the average a. - """ - return np.sum((self.x[1:]-self.x[:-1]) * np.abs(self.y)) / \ - (self.x[-1]-self.x[0]) - def add(self, f): """ Adds another PieceWiseConst function to this function. Note: only functions defined on the same interval can be summed. @@ -84,7 +74,7 @@ class PieceWiseConstFunc: """ assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" - + # python implementation # from python_backend import add_piece_wise_const_python # self.x, self.y = add_piece_wise_const_python(self.x, self.y, @@ -107,7 +97,7 @@ class PieceWiseConstFunc: ############################################################## class PieceWiseLinFunc: """ A class representing a piece-wise linear function. """ - + def __init__(self, x, y1, y2): """ Constructs the piece-wise linear function. Args: diff --git a/test/test_distance.py b/test/test_distance.py index b500b2c..6e50467 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -23,8 +23,8 @@ def test_isi(): # pen&paper calculation of the isi distance expected_times = [0.0, 0.2, 0.3, 0.4, 0.45, 0.6, 0.7, 0.8, 0.9, 0.95, 1.0] - expected_isi = [-0.1/0.3, -0.1/0.3, 0.05/0.2, 0.05/0.2, -0.15/0.35, - -0.25/0.35, -0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] + expected_isi = [0.1/0.3, 0.1/0.3, 0.05/0.2, 0.05/0.2, 0.15/0.35, + 0.25/0.35, 0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) @@ -40,7 +40,7 @@ def test_isi(): t2 = np.array([0.1, 0.4, 0.5, 0.6]) expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] - expected_isi = [0.1/0.2, -0.1/0.3, -0.1/0.3, 0.1/0.2, 0.1/0.2, -0.0/0.5] + expected_isi = [0.1/0.2, 0.1/0.3, 0.1/0.3, 0.1/0.2, 0.1/0.2, 0.0/0.5] t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) diff --git a/test/test_function.py b/test/test_function.py index a579796..c5caa5a 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -28,8 +28,6 @@ def test_pwc(): assert_array_almost_equal(yp, yp_expected, decimal=16) assert_almost_equal(f.avrg(), (1.0-0.5+0.5*1.5+1.5*0.75)/4.0, decimal=16) - assert_almost_equal(f.abs_avrg(), (1.0+0.5+0.5*1.5+1.5*0.75)/4.0, - decimal=16) def test_pwc_add(): -- cgit v1.2.3 From baba170fb8d300974de1611e016a98dcc8769563 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 15 Oct 2014 11:34:05 +0200 Subject: improved docs --- pyspike/distances.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index 08d0ed8..55515f6 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -17,10 +17,11 @@ from pyspike import PieceWiseConstFunc, PieceWiseLinFunc # isi_distance ############################################################ def isi_distance(spikes1, spikes2): - """ Computes the instantaneous isi-distance S_isi (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. + """ Computes the isi-distance profile S_isi(t) of the two given spike + trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi + values are defined in the interval [0,1). 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. Returns: @@ -43,10 +44,11 @@ def isi_distance(spikes1, spikes2): # spike_distance ############################################################ def spike_distance(spikes1, spikes2): - """ 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. + """ Computes the spike-distance profile S_spike(t) of the two given spike + trains. Returns the profile as a PieceWiseLinFunc object. The S_spike + values are defined in the interval [0,1). 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. Returns: -- cgit v1.2.3 From 2a99e3bf2c575efc9abbc1cf262810d223f2cad0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 15 Oct 2014 12:32:09 +0200 Subject: +average_profile function --- pyspike/__init__.py | 2 +- pyspike/function.py | 35 +++++++++++++++++++++++++++++++++++ test/test_function.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index c58a6b1..1bfa7fc 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -6,7 +6,7 @@ Distributed under the BSD License __all__ = ["function", "distances", "spikes"] -from function import PieceWiseConstFunc, PieceWiseLinFunc +from function import PieceWiseConstFunc, PieceWiseLinFunc, average_profile from distances import isi_distance, spike_distance, \ isi_distance_multi, spike_distance_multi, isi_distance_matrix from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ diff --git a/pyspike/function.py b/pyspike/function.py index bd3e2d5..46fdea2 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -26,9 +26,17 @@ class PieceWiseConstFunc: function. - y: array of length N defining the function values at the intervals. """ + # convert parameters to arrays, also ensures copying self.x = np.array(x) self.y = np.array(y) + def copy(self): + """ Returns a copy of itself + Returns: + - PieceWiseConstFunc copy + """ + return PieceWiseConstFunc(self.x, self.y) + def almost_equal(self, other, decimal=14): """ Checks if the function is equal to another function up to `decimal` precision. @@ -108,10 +116,18 @@ class PieceWiseLinFunc: - y2: array of length N defining the function values at the right of the intervals. """ + # convert to array, which also ensures copying self.x = np.array(x) self.y1 = np.array(y1) self.y2 = np.array(y2) + def copy(self): + """ Returns a copy of itself + Returns: + - PieceWiseLinFunc copy + """ + return PieceWiseLinFunc(self.x, self.y1, self.y2) + def almost_equal(self, other, decimal=14): """ Checks if the function is equal to another function up to `decimal` precision. @@ -183,3 +199,22 @@ class PieceWiseLinFunc: """ self.y1 *= fac self.y2 *= fac + + +def average_profile(profiles): + """ Computes the average profile from the given ISI- or SPIKE-profiles. + Args: + - profiles: list of PieceWiseConstFunc or PieceWiseLinFunc representing + ISI- or SPIKE-profiles to be averaged + Returns: + - avrg_profile: PieceWiseConstFunc or PieceWiseLinFunc containing the + average profile. + """ + assert len(profiles) > 1 + + avrg_profile = profiles[0].copy() + for i in xrange(1, len(profiles)): + avrg_profile.add(profiles[i]) + avrg_profile.mul_scalar(1.0/len(profiles)) # normalize + + return avrg_profile diff --git a/test/test_function.py b/test/test_function.py index c5caa5a..ed70180 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -68,6 +68,23 @@ def test_pwc_mul(): assert_array_almost_equal(f.y, 1.5/5.0*np.array(y), decimal=16) +def test_pwc_avrg(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f1 = spk.PieceWiseConstFunc(x, y) + + x = [0.0, 0.75, 2.0, 2.5, 2.7, 4.0] + y = [0.5, 1.0, -0.25, 0.0, 1.5] + f2 = spk.PieceWiseConstFunc(x, y) + + f_avrg = spk.average_profile([f1, f2]) + x_expected = [0.0, 0.75, 1.0, 2.0, 2.5, 2.7, 4.0] + y_expected = [0.75, 1.0, 0.25, 0.625, 0.375, 1.125] + assert_array_almost_equal(f_avrg.x, x_expected, decimal=16) + assert_array_almost_equal(f_avrg.y, y_expected, decimal=16) + + def test_pwl(): x = [0.0, 1.0, 2.0, 2.5, 4.0] y1 = [1.0, -0.5, 1.5, 0.75] @@ -134,6 +151,31 @@ def test_pwl_mul(): assert_array_almost_equal(f.y1, 1.5/5.0*np.array(y1), decimal=16) assert_array_almost_equal(f.y2, 1.5/5.0*np.array(y2), decimal=16) + +def test_pwl_avrg(): + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y1 = [1.0, -0.5, 1.5, 0.75] + y2 = [1.5, -0.4, 1.5, 0.25] + f1 = spk.PieceWiseLinFunc(x, y1, y2) + + x = [0.0, 0.75, 2.0, 2.5, 2.7, 4.0] + y1 = [0.5, 1.0, -0.25, 0.0, 1.5] + y2 = [0.8, 0.2, -1.0, 0.0, 2.0] + f2 = spk.PieceWiseLinFunc(x, y1, y2) + + x_expected = [0.0, 0.75, 1.0, 2.0, 2.5, 2.7, 4.0] + y1_expected = np.array([1.5, 1.0+1.0+0.5*0.75, -0.5+1.0-0.8*0.25/1.25, + 1.5-0.25, 0.75, 1.5+0.75-0.5*0.2/1.5]) / 2 + y2_expected = np.array([0.8+1.0+0.5*0.75, 1.5+1.0-0.8*0.25/1.25, -0.4+0.2, + 1.5-1.0, 0.75-0.5*0.2/1.5, 2.25]) / 2 + + f_avrg = spk.average_profile([f1, f2]) + + assert_array_almost_equal(f_avrg.x, x_expected, decimal=16) + assert_array_almost_equal(f_avrg.y1, y1_expected, decimal=16) + assert_array_almost_equal(f_avrg.y2, y2_expected, decimal=16) + + if __name__ == "__main__": test_pwc() test_pwc_add() -- cgit v1.2.3 From 39433f4cf4994edacf1814aff5d5f54b666d8f47 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 15 Oct 2014 12:43:27 +0200 Subject: tiny docs improvements --- Readme.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index 5ab9570..cc3fdd0 100644 --- a/Readme.md +++ b/Readme.md @@ -105,13 +105,18 @@ The following code loads some exemplary spike trains, computes the dissimilarity The ISI-profile is a piece-wise constant function, there the function `isi_distance` returns an instance of the `PieceWiseConstFunc` class. As above, this class allows you to obtain arrays that can be used to plot the function with `plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. -Furthermore, `PieceWiseConstFunc` provides an `add` function that can be used to add piece-wise constant function, and a `mul_scalar` function that rescales the function by a scalar. -This can be used to obtain an average profile: +Furthermore, PySpike provides the `average_profile` function that can be used to compute the average profile of a list of given `PieceWiseConstFunc` instances. - isi_profile1.add(isi_profile2) - isi_profile1.mul_scalar(0.5) - x, y = isi_profile1.get_plottable_data() - plt.plot(x, y, label="Average ISI profile") + avrg_profile = spk.average_profile([spike_train1, spike_train2]) + x, y = avrg_profile.get_plottable_data() + plt.plot(x, y, label="Average profile") + +Note the difference between the `average_profile` function, which returns a `PieceWiseConstFunc` (or `PieceWiseLinFunc`, see below), and the `avrg` member function above, that computes the integral over the time profile. +So to obtain overall average ISI-distance of a list of ISI profiles you can first compute the average profile using `average_profile` and the use + + avrg_isi = avrg_profile.avrg() + +to obtain the final, scalar average ISI distance of the whole set (see also "Computing multi-variate distance" below). ## Computing multi-variate distances -- cgit v1.2.3 From 944e666163cc8db9860cfb76270e97d9437fcaed Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:16:59 +0200 Subject: added travis build script --- .travis.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..cec04c2 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +python: + - "2.6" + - "2.7" + +before_install: + - sudo apt-get install -qq cython + +script: + - python setup.py build_ext --inplace + - cd test + - nosetests + -- cgit v1.2.3 From 87b53407f2948d36dd2e332112a3626d252aa693 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:20:35 +0200 Subject: travis fix --- .travis.yml | 2 +- pyspike/distances.py | 4 ++-- test/test_distance.py | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index cec04c2..d2d1437 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ python: - "2.6" - "2.7" -before_install: +install: - sudo apt-get install -qq cython script: diff --git a/pyspike/distances.py b/pyspike/distances.py index 55515f6..4ba2bd3 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -19,7 +19,7 @@ from pyspike import PieceWiseConstFunc, PieceWiseLinFunc def isi_distance(spikes1, spikes2): """ Computes the isi-distance profile S_isi(t) of the two given spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi - values are defined in the interval [0,1). The spike trains are expected + values are defined positive S_isi(t)>=0. 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: @@ -46,7 +46,7 @@ def isi_distance(spikes1, spikes2): def spike_distance(spikes1, spikes2): """ Computes the spike-distance profile S_spike(t) of the two given spike trains. Returns the profile as a PieceWiseLinFunc object. The S_spike - values are defined in the interval [0,1). The spike trains are expected to + values are defined positive S_spike(t)>=0. 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: diff --git a/test/test_distance.py b/test/test_distance.py index 6e50467..0695701 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -138,6 +138,22 @@ def test_multi_spike(): check_multi_distance(spk.spike_distance, spk.spike_distance_multi) +def test_regression_spiky(): + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + (0.0, 4000.0)) + isi_profile = spk.isi_distance_multi(spike_trains) + isi_dist = isi_profile.avrg() + print(isi_dist) + # get the full precision from SPIKY + # assert_equal(isi_dist, 0.1832) + + spike_profile = spk.spike_distance_multi(spike_trains) + spike_dist = spike_profile.avrg() + print(spike_dist) + # get the full precision from SPIKY + # assert_equal(spike_dist, 0.2445) + + if __name__ == "__main__": test_isi() test_spike() -- cgit v1.2.3 From be22b4080e25ae1f7b0228eb942d313d7cf70890 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:24:57 +0200 Subject: travis fix --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d2d1437..edb59c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ python: - "2.7" install: - - sudo apt-get install -qq cython + - sudo apt-get install cython script: - python setup.py build_ext --inplace -- cgit v1.2.3 From c688ab18756bfe08be0e7e219dd6f2d8f3557ccc Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:30:13 +0200 Subject: travis fix --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index edb59c2..b687aaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ python: - "2.7" install: - - sudo apt-get install cython + - pip install cython script: - python setup.py build_ext --inplace -- cgit v1.2.3 From 6b1ed71863548c8059205993301ed36cdc42f594 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:42:10 +0200 Subject: travis fix --- .travis.yml | 3 ++- setup.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b687aaa..5981801 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,10 @@ python: - "2.7" install: - - pip install cython + - pip install -q cython script: + - export $PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR - python setup.py build_ext --inplace - cd test - nosetests diff --git a/setup.py b/setup.py index 31db6f8..a9f40bd 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,9 @@ Distributed under the BSD License """ from distutils.core import setup from Cython.Build import cythonize +import numpy setup( - ext_modules=cythonize("pyspike/*.pyx") + ext_modules=cythonize("pyspike/*.pyx"), + include_dirs=[numpy.get_include()] ) -- cgit v1.2.3 From 668eeac3812d7ffa6057bac479769890b7dd9aca Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:47:31 +0200 Subject: travis fix --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5981801..d039318 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ install: - pip install -q cython script: - - export $PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR + - export PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR - python setup.py build_ext --inplace - cd test - nosetests -- cgit v1.2.3 From e1a8f3f8ac97ba0ab00d81f4e6e85012d53cdcfd Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 11:53:09 +0200 Subject: added build image --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index cc3fdd0..c12b0b6 100644 --- a/Readme.md +++ b/Readme.md @@ -1,5 +1,7 @@ # PySpike +[![Build Status](https://travis-ci.org/mariomulansky/PySpike.svg?branch=master)](https://travis-ci.org/mariomulansky/PySpike) + PySpike is a Python library for the numerical analysis of spike train similarity. Its core functionality is the implementation of the bivariate [ISI](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance) [1] and [SPIKE](http://www.scholarpedia.org/article/SPIKE-distance) [2] distance. Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. -- cgit v1.2.3 From a32c32e968368589dc9ca7baf08636a2ce25d4cc Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 12:07:56 +0200 Subject: no pythonpath necessary for travis --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d039318..31df3bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,8 @@ install: - pip install -q cython script: - - export PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR +# - export PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR - python setup.py build_ext --inplace - - cd test +# - cd test - nosetests -- cgit v1.2.3 From 4249dd363e992fe1178c9d76db3f74c5005afb0a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 12:41:43 +0200 Subject: changed function names distance -> profiles, added distance functions --- pyspike/__init__.py | 6 ++- pyspike/distances.py | 105 +++++++++++++++++++++++++++++++++++++++++--------- test/test_distance.py | 18 ++++----- test/test_spikes.py | 7 ++-- 4 files changed, 103 insertions(+), 33 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 1bfa7fc..5146507 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -7,7 +7,9 @@ Distributed under the BSD License __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc, average_profile -from distances import isi_distance, spike_distance, \ - isi_distance_multi, spike_distance_multi, isi_distance_matrix +from distances import isi_profile, isi_distance, \ + spike_profile, spike_distance, \ + isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ + spike_profile_multi, spike_distance_multi from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index 4ba2bd3..e50772f 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -14,9 +14,9 @@ from pyspike import PieceWiseConstFunc, PieceWiseLinFunc ############################################################ -# isi_distance +# isi_profile ############################################################ -def isi_distance(spikes1, spikes2): +def isi_profile(spikes1, spikes2): """ Computes the isi-distance profile S_isi(t) of the two given spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi values are defined positive S_isi(t)>=0. The spike trains are expected @@ -41,9 +41,24 @@ def isi_distance(spikes1, spikes2): ############################################################ -# spike_distance +# isi_distance ############################################################ -def spike_distance(spikes1, spikes2): +def isi_distance(spikes1, spikes2): + """ Computes the isi-distance I of the given spike trains. The + isi-distance is the integral over the isi distance profile S_isi(t): + I = \int_^T S_isi(t) dt. + Args: + - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes. + Returns: + - double value: The isi-distance I. + """ + return isi_profile(spikes1, spikes2).avrg() + + +############################################################ +# spike_profile +############################################################ +def spike_profile(spikes1, spikes2): """ Computes the spike-distance profile S_spike(t) of the two given spike trains. Returns the profile as a PieceWiseLinFunc object. The S_spike values are defined positive S_spike(t)>=0. The spike trains are expected to @@ -69,11 +84,26 @@ def spike_distance(spikes1, spikes2): ############################################################ -# multi_distance +# spike_distance +############################################################ +def spike_distance(spikes1, spikes2): + """ Computes the spike-distance S of the given spike trains. The + spike-distance is the integral over the isi distance profile S_spike(t): + S = \int_^T S_spike(t) dt. + Args: + - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes. + Returns: + - double value: The spike-distance S. + """ + return spike_profile(spikes1, spikes2).avrg() + + +############################################################ +# multi_profile ############################################################ -def multi_distance(spike_trains, pair_distance_func, indices=None): +def multi_profile(spike_trains, pair_distance_func, indices=None): """ Internal implementation detail, don't call this function directly, - use isi_distance_multi or spike_distance_multi instead. + use isi_profile_multi or spike_profile_multi instead. Computes the multi-variate distance for a set of spike-trains using the pair_dist_func to compute pair-wise distances. That is it computes the @@ -158,42 +188,81 @@ def multi_distance_par(spike_trains, pair_distance_func, indices=None): return average_dist +############################################################ +# isi_profile_multi +############################################################ +def isi_profile_multi(spike_trains, indices=None): + """ computes the multi-variate isi distance profile for a set of spike + trains. That is the average isi-distance of all pairs of spike-trains: + S_isi(t) = 2/((N(N-1)) sum_{} S_{isi}^{i,j}, + where the sum goes over all pairs + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - A PieceWiseConstFunc representing the averaged isi distance S_isi(t) + """ + return multi_profile(spike_trains, isi_profile, indices) + + ############################################################ # isi_distance_multi ############################################################ def isi_distance_multi(spike_trains, indices=None): - """ computes the multi-variate isi-distance for a set of spike-trains. That - is the average isi-distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + """ computes the multi-variate isi-distance for a set of spike-trains. + That is the time average of the multi-variate spike profile: + S_isi = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, where the sum goes over all pairs Args: - spike_trains: list of spike trains - indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) Returns: - - A PieceWiseConstFunc representing the averaged isi distance S + - A double value representing the averaged isi distance S_isi """ - return multi_distance(spike_trains, isi_distance, indices) + return isi_profile_multi(spike_trains, indices).avrg() + + +############################################################ +# spike_profile_multi +############################################################ +def spike_profile_multi(spike_trains, indices=None): + """ Computes the multi-variate spike distance profile for a set of spike + trains. That is the average spike-distance of all pairs of spike-trains: + S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}, + where the sum goes over all pairs + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike-trains to use, + if None all given spike trains are used (default=None) + Returns: + - A PieceWiseLinFunc representing the averaged spike distance S(t) + """ + return multi_profile(spike_trains, spike_profile, indices) ############################################################ # spike_distance_multi ############################################################ def spike_distance_multi(spike_trains, indices=None): - """ computes the multi-variate spike-distance for a set of spike-trains. - That is the average spike-distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i, j}, + """ Computes the multi-variate spike distance for a set of spike trains. + That is the time average of the multi-variate spike profile: + S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt where the sum goes over all pairs Args: - spike_trains: list of spike trains - indices: list of indices defining which spike-trains to use, if None all given spike trains are used (default=None) Returns: - - A PieceWiseLinFunc representing the averaged spike distance S + - A double value representing the averaged spike distance S """ - return multi_distance(spike_trains, spike_distance, indices) + return spike_profile_multi(spike_trains, indices).avrg() +############################################################ +# isi_distance_matrix +############################################################ def isi_distance_matrix(spike_trains, indices=None): """ Computes the average isi-distance of all pairs of spike-trains. Args: @@ -212,7 +281,7 @@ def isi_distance_matrix(spike_trains, indices=None): "Invalid index list." # generate a list of possible index pairs pairs = [(i, j) for i in indices for j in indices[i+1:]] - + distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: d = isi_distance(spike_trains[i], spike_trains[j]).avrg() diff --git a/test/test_distance.py b/test/test_distance.py index 0695701..81ffe09 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -28,7 +28,7 @@ def test_isi(): t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) - f = spk.isi_distance(t1, t2) + f = spk.isi_profile(t1, t2) # print("ISI: ", f.y) @@ -44,7 +44,7 @@ def test_isi(): t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) - f = spk.isi_distance(t1, t2) + f = spk.isi_profile(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y, expected_isi, decimal=14) @@ -69,7 +69,7 @@ def test_spike(): t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) - f = spk.spike_distance(t1, t2) + f = spk.spike_profile(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y1, expected_y1, decimal=14) @@ -89,7 +89,7 @@ def test_spike(): t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) - f = spk.spike_distance(t1, t2) + f = spk.spike_profile(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y1, expected_y1, decimal=14) @@ -131,23 +131,23 @@ def check_multi_distance(dist_func, dist_func_multi): def test_multi_isi(): - check_multi_distance(spk.isi_distance, spk.isi_distance_multi) + check_multi_distance(spk.isi_profile, spk.isi_profile_multi) def test_multi_spike(): - check_multi_distance(spk.spike_distance, spk.spike_distance_multi) + check_multi_distance(spk.spike_profile, spk.spike_profile_multi) def test_regression_spiky(): - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", (0.0, 4000.0)) - isi_profile = spk.isi_distance_multi(spike_trains) + isi_profile = spk.isi_profile_multi(spike_trains) isi_dist = isi_profile.avrg() print(isi_dist) # get the full precision from SPIKY # assert_equal(isi_dist, 0.1832) - spike_profile = spk.spike_distance_multi(spike_trains) + spike_profile = spk.spike_profile_multi(spike_trains) spike_dist = spike_profile.avrg() print(spike_dist) # get the full precision from SPIKY diff --git a/test/test_spikes.py b/test/test_spikes.py index bf914c0..d650d5d 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -23,7 +23,7 @@ def test_auxiliary_spikes(): def test_load_from_txt(): - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", time_interval=(0, 4000)) assert len(spike_trains) == 40 @@ -39,7 +39,7 @@ def test_load_from_txt(): assert spike_train[-1] == 4000 # load without adding auxiliary spikes - spike_trains2 = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + spike_trains2 = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", time_interval=None) assert len(spike_trains2) == 40 # check auxiliary spikes @@ -64,9 +64,8 @@ def check_merged_spikes(merged_spikes, spike_trains): def test_merge_spike_trains(): # first load the data - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", time_interval=(0, 4000)) - spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) # test if result is sorted assert((spikes == np.sort(spikes)).all()) -- cgit v1.2.3 From 165b1e7c707324115ab2d21a78716ea2e243fc60 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 13:00:00 +0200 Subject: added distance tests --- test/test_distance.py | 46 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/test/test_distance.py b/test/test_distance.py index 81ffe09..2a6bf4e 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -11,7 +11,8 @@ Distributed under the BSD License from __future__ import print_function import numpy as np from copy import copy -from numpy.testing import assert_equal, assert_array_almost_equal +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_almost_equal import pyspike as spk @@ -25,7 +26,12 @@ def test_isi(): expected_times = [0.0, 0.2, 0.3, 0.4, 0.45, 0.6, 0.7, 0.8, 0.9, 0.95, 1.0] expected_isi = [0.1/0.3, 0.1/0.3, 0.05/0.2, 0.05/0.2, 0.15/0.35, 0.25/0.35, 0.05/0.35, 0.2/0.3, 0.25/0.3, 0.25/0.3] - + expected_times = np.array(expected_times) + expected_isi = np.array(expected_isi) + + expected_isi_val = sum((expected_times[1:] - expected_times[:-1]) * + expected_isi)/(expected_times[-1]-expected_times[0]) + t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_profile(t1, t2) @@ -33,7 +39,9 @@ def test_isi(): # print("ISI: ", f.y) assert_equal(f.x, expected_times) - assert_array_almost_equal(f.y, expected_isi, decimal=14) + assert_array_almost_equal(f.y, expected_isi, decimal=15) + assert_equal(f.avrg(), expected_isi_val) + assert_equal(spk.isi_distance(t1, t2), expected_isi_val) # check with some equal spike times t1 = np.array([0.2, 0.4, 0.6]) @@ -41,13 +49,20 @@ def test_isi(): expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] expected_isi = [0.1/0.2, 0.1/0.3, 0.1/0.3, 0.1/0.2, 0.1/0.2, 0.0/0.5] + expected_times = np.array(expected_times) + expected_isi = np.array(expected_isi) + + expected_isi_val = sum((expected_times[1:] - expected_times[:-1]) * + expected_isi)/(expected_times[-1]-expected_times[0]) t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_profile(t1, t2) assert_equal(f.x, expected_times) - assert_array_almost_equal(f.y, expected_isi, decimal=14) + assert_array_almost_equal(f.y, expected_isi, decimal=15) + assert_equal(f.avrg(), expected_isi_val) + assert_equal(spk.isi_distance(t1, t2), expected_isi_val) def test_spike(): @@ -67,13 +82,22 @@ def test_spike(): expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) + expected_times = np.array(expected_times) + expected_y1 = np.array(expected_y1) + expected_y2 = np.array(expected_y2) + expected_spike_val = sum((expected_times[1:] - expected_times[:-1]) * + (expected_y1+expected_y2)/2) + expected_spike_val /= (expected_times[-1]-expected_times[0]) + t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_profile(t1, t2) assert_equal(f.x, expected_times) - assert_array_almost_equal(f.y1, expected_y1, decimal=14) - assert_array_almost_equal(f.y2, expected_y2, decimal=14) + assert_array_almost_equal(f.y1, expected_y1, decimal=15) + assert_array_almost_equal(f.y2, expected_y2, decimal=15) + assert_equal(f.avrg(), expected_spike_val) + assert_equal(spk.spike_distance(t1, t2), expected_spike_val) # check with some equal spike times t1 = np.array([0.2, 0.4, 0.6]) @@ -87,6 +111,13 @@ def test_spike(): expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) + expected_times = np.array(expected_times) + expected_y1 = np.array(expected_y1) + expected_y2 = np.array(expected_y2) + expected_spike_val = sum((expected_times[1:] - expected_times[:-1]) * + (expected_y1+expected_y2)/2) + expected_spike_val /= (expected_times[-1]-expected_times[0]) + t1 = spk.add_auxiliary_spikes(t1, 1.0) t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_profile(t1, t2) @@ -94,6 +125,9 @@ def test_spike(): assert_equal(f.x, expected_times) assert_array_almost_equal(f.y1, expected_y1, decimal=14) assert_array_almost_equal(f.y2, expected_y2, decimal=14) + assert_almost_equal(f.avrg(), expected_spike_val, decimal=16) + assert_almost_equal(spk.spike_distance(t1, t2), expected_spike_val, + decimal=16) def check_multi_distance(dist_func, dist_func_multi): -- cgit v1.2.3 From d869d4d822c651ea3d094eaf17ba7732bf91136f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 14:16:32 +0200 Subject: new function names in examples and readme --- .travis.yml | 2 -- Readme.md | 12 +++++++----- examples/plot.py | 4 ++-- pyspike/distances.py | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 31df3bc..43f3ef7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,6 @@ install: - pip install -q cython script: -# - export PYTHONPATH=$PYTHONPATH:$TRAVIS_BUILD_DIR - python setup.py build_ext --inplace -# - cd test - nosetests diff --git a/Readme.md b/Readme.md index c12b0b6..be42c4a 100644 --- a/Readme.md +++ b/Readme.md @@ -33,7 +33,6 @@ To install PySpike, simply download the source, e.g. from Github, and run the `s Then you can run the tests using the `nosetests` test framework: - cd test nosetests Finally, you should make PySpike's installation folder known to Python to be able to import pyspike in your own projects. @@ -70,7 +69,7 @@ If you load spike trains yourself, i.e. from data files with different structure Both the ISI and the SPIKE distance computation require the presence of auxiliary spikes, so make sure you have those in your spike trains: spike_train = spk.add_auxiliary_spikes(spike_train, (T_start, T_end)) - # you provide only a single value, it is interpreted as T_end, while T_start=0 + # if you provide only a single value, it is interpreted as T_end, while T_start=0 spike_train = spk.add_auxiliary_spikes(spike_train, T_end) ## Computing bi-variate distances @@ -98,14 +97,17 @@ The following code loads some exemplary spike trains, computes the dissimilarity spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", time_interval=(0, 4000)) - isi_profile = spk.isi_distance(spike_trains[0], spike_trains[1]) + isi_profile = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = isi_profile.get_plottable_data() plt.plot(x, y, '--k') print("ISI distance: %.8f" % isi_profil.avrg()) plt.show() -The ISI-profile is a piece-wise constant function, there the function `isi_distance` returns an instance of the `PieceWiseConstFunc` class. -As above, this class allows you to obtain arrays that can be used to plot the function with `plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. +The ISI-profile is a piece-wise constant function, there the function `isi_profile` returns an instance of the `PieceWiseConstFunc` class. +As shown above, this class allows you to obtain arrays that can be used to plot the function with `plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. +If you are only interested in the scalar ISI-distance and not the profile, you can simly use: + + isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1]) Furthermore, PySpike provides the `average_profile` function that can be used to compute the average profile of a list of given `PieceWiseConstFunc` instances. diff --git a/examples/plot.py b/examples/plot.py index 4ac8f5d..6da7f49 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -22,7 +22,7 @@ spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", for (i, spikes) in enumerate(spike_trains): plt.plot(spikes, i*np.ones_like(spikes), 'o') -f = spk.isi_distance(spike_trains[0], spike_trains[1]) +f = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() plt.figure() @@ -30,7 +30,7 @@ plt.plot(x, np.abs(y), '--k') print("Average: %.8f" % f.avrg()) -f = spk.spike_distance(spike_trains[0], spike_trains[1]) +f = spk.spike_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() print(x) print(y) diff --git a/pyspike/distances.py b/pyspike/distances.py index e50772f..9056863 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -284,7 +284,7 @@ def isi_distance_matrix(spike_trains, indices=None): distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: - d = isi_distance(spike_trains[i], spike_trains[j]).avrg() + d = isi_distance(spike_trains[i], spike_trains[j]) distance_matrix[i, j] = d distance_matrix[j, i] = d return distance_matrix -- cgit v1.2.3 From 5970a9cfdbecc1af232b7ffe485bdc057591a2b8 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 14:50:26 +0200 Subject: added spike_matrix, refactoring dist matrix functs --- pyspike/__init__.py | 2 +- pyspike/distances.py | 52 ++++++++++++++++++++++++++++++++++-------- test/test_distance.py | 62 +++++++++++++++++++++++++++++++++++++++++---------- test/test_spikes.py | 1 + 4 files changed, 95 insertions(+), 22 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 5146507..d2d5b57 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -10,6 +10,6 @@ from function import PieceWiseConstFunc, PieceWiseLinFunc, average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ - spike_profile_multi, spike_distance_multi + spike_profile_multi, spike_distance_multi, spike_distance_matrix from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ spike_train_from_string, merge_spike_trains diff --git a/pyspike/distances.py b/pyspike/distances.py index 9056863..7d7044b 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -99,9 +99,9 @@ def spike_distance(spikes1, spikes2): ############################################################ -# multi_profile +# generic_profile_multi ############################################################ -def multi_profile(spike_trains, pair_distance_func, indices=None): +def generic_profile_multi(spike_trains, pair_distance_func, indices=None): """ Internal implementation detail, don't call this function directly, use isi_profile_multi or spike_profile_multi instead. @@ -203,7 +203,7 @@ def isi_profile_multi(spike_trains, indices=None): Returns: - A PieceWiseConstFunc representing the averaged isi distance S_isi(t) """ - return multi_profile(spike_trains, isi_profile, indices) + return generic_profile_multi(spike_trains, isi_profile, indices) ############################################################ @@ -239,7 +239,7 @@ def spike_profile_multi(spike_trains, indices=None): Returns: - A PieceWiseLinFunc representing the averaged spike distance S(t) """ - return multi_profile(spike_trains, spike_profile, indices) + return generic_profile_multi(spike_trains, spike_profile, indices) ############################################################ @@ -261,17 +261,19 @@ def spike_distance_multi(spike_trains, indices=None): ############################################################ -# isi_distance_matrix +# generic_distance_matrix ############################################################ -def isi_distance_matrix(spike_trains, indices=None): - """ Computes the average isi-distance of all pairs of spike-trains. +def generic_distance_matrix(spike_trains, dist_function, indices=None): + """ Internal implementation detail. Don't use this function directly. + Instead use isi_distance_matrix or spike_distance_matrix. + Computes the time averaged distance of all pairs of spike-trains. Args: - spike_trains: list of spike trains - indices: list of indices defining which spike-trains to use if None all given spike-trains are used (default=None) Return: - a 2D array of size len(indices)*len(indices) containing the average - pair-wise isi-distance + pair-wise distance """ if indices is None: indices = np.arange(len(spike_trains)) @@ -284,7 +286,39 @@ def isi_distance_matrix(spike_trains, indices=None): distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: - d = isi_distance(spike_trains[i], spike_trains[j]) + d = dist_function(spike_trains[i], spike_trains[j]) distance_matrix[i, j] = d distance_matrix[j, i] = d return distance_matrix + + +############################################################ +# isi_distance_matrix +############################################################ +def isi_distance_matrix(spike_trains, indices=None): + """ Computes the time averaged isi-distance of all pairs of spike-trains. + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike-trains to use + if None all given spike-trains are used (default=None) + Return: + - a 2D array of size len(indices)*len(indices) containing the average + pair-wise isi-distance + """ + return generic_distance_matrix(spike_trains, isi_distance, indices) + + +############################################################ +# spike_distance_matrix +############################################################ +def spike_distance_matrix(spike_trains, indices=None): + """ Computes the time averaged spike-distance of all pairs of spike-trains. + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike-trains to use + if None all given spike-trains are used (default=None) + Return: + - a 2D array of size len(indices)*len(indices) containing the average + pair-wise spike-distance + """ + return generic_distance_matrix(spike_trains, spike_distance, indices) diff --git a/test/test_distance.py b/test/test_distance.py index 2a6bf4e..7be0d9b 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -130,7 +130,7 @@ def test_spike(): decimal=16) -def check_multi_distance(dist_func, dist_func_multi): +def check_multi_profile(profile_func, profile_func_multi): # generate spike trains: t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) t2 = spk.add_auxiliary_spikes(np.array([0.3, 0.45, 0.8, 0.9, 0.95]), 1.0) @@ -138,21 +138,21 @@ def check_multi_distance(dist_func, dist_func_multi): t4 = spk.add_auxiliary_spikes(np.array([0.1, 0.4, 0.5, 0.6]), 1.0) spike_trains = [t1, t2, t3, t4] - f12 = dist_func(t1, t2) - f13 = dist_func(t1, t3) - f14 = dist_func(t1, t4) - f23 = dist_func(t2, t3) - f24 = dist_func(t2, t4) - f34 = dist_func(t3, t4) + f12 = profile_func(t1, t2) + f13 = profile_func(t1, t3) + f14 = profile_func(t1, t4) + f23 = profile_func(t2, t3) + f24 = profile_func(t2, t4) + f34 = profile_func(t3, t4) - f_multi = dist_func_multi(spike_trains, [0, 1]) + f_multi = profile_func_multi(spike_trains, [0, 1]) assert f_multi.almost_equal(f12, decimal=14) f = copy(f12) f.add(f13) f.add(f23) f.mul_scalar(1.0/3) - f_multi = dist_func_multi(spike_trains, [0, 1, 2]) + f_multi = profile_func_multi(spike_trains, [0, 1, 2]) assert f_multi.almost_equal(f, decimal=14) f.mul_scalar(3) # revert above normalization @@ -160,16 +160,54 @@ def check_multi_distance(dist_func, dist_func_multi): f.add(f24) f.add(f34) f.mul_scalar(1.0/6) - f_multi = dist_func_multi(spike_trains) + f_multi = profile_func_multi(spike_trains) assert f_multi.almost_equal(f, decimal=14) def test_multi_isi(): - check_multi_distance(spk.isi_profile, spk.isi_profile_multi) + check_multi_profile(spk.isi_profile, spk.isi_profile_multi) def test_multi_spike(): - check_multi_distance(spk.spike_profile, spk.spike_profile_multi) + check_multi_profile(spk.spike_profile, spk.spike_profile_multi) + + +def check_dist_matrix(dist_func, dist_matrix_func): + # generate spike trains: + t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) + t2 = spk.add_auxiliary_spikes(np.array([0.3, 0.45, 0.8, 0.9, 0.95]), 1.0) + t3 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6]), 1.0) + t4 = spk.add_auxiliary_spikes(np.array([0.1, 0.4, 0.5, 0.6]), 1.0) + spike_trains = [t1, t2, t3, t4] + + f12 = dist_func(t1, t2) + f13 = dist_func(t1, t3) + f14 = dist_func(t1, t4) + f23 = dist_func(t2, t3) + f24 = dist_func(t2, t4) + f34 = dist_func(t3, t4) + + f_matrix = dist_matrix_func(spike_trains) + # check zero diagonal + for i in xrange(4): + assert_equal(0.0, f_matrix[i, i]) + for i in xrange(4): + for j in xrange(i+1, 4): + assert_equal(f_matrix[i, j], f_matrix[j, i]) + assert_equal(f12, f_matrix[1, 0]) + assert_equal(f13, f_matrix[2, 0]) + assert_equal(f14, f_matrix[3, 0]) + assert_equal(f23, f_matrix[2, 1]) + assert_equal(f24, f_matrix[3, 1]) + assert_equal(f34, f_matrix[3, 2]) + + +def test_isi_matrix(): + check_dist_matrix(spk.isi_distance, spk.isi_distance_matrix) + + +def test_spike_matrix(): + check_dist_matrix(spk.spike_distance, spk.spike_distance_matrix) def test_regression_spiky(): diff --git a/test/test_spikes.py b/test/test_spikes.py index d650d5d..b12099e 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -66,6 +66,7 @@ def test_merge_spike_trains(): # first load the data spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", time_interval=(0, 4000)) + spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) # test if result is sorted assert((spikes == np.sort(spikes)).all()) -- cgit v1.2.3 From bbb424f75e3063f823a880bced3de1b2063c408a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 15:15:13 +0200 Subject: added fall-back to python when cython is missing --- pyspike/distances.py | 30 ++++++++++++++++++++++-------- pyspike/function.py | 33 ++++++++++++++++++++++++--------- pyspike/python_backend.py | 11 ++++------- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index 7d7044b..3e97b77 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -33,10 +33,17 @@ def isi_profile(spikes1, spikes2): assert spikes1[-1] == spikes2[-1], \ "Given spike trains seems not to have auxiliary spikes!" - # cython implementation - from cython_distance import isi_distance_cython - - times, values = isi_distance_cython(spikes1, spikes2) + # load cython implementation + try: + from cython_distance import isi_distance_cython as isi_distance_impl + except ImportError: + print("Warning: isi_distance_cython not found. Make sure that PySpike \ +is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from python_backend import isi_distance_python as isi_distance_impl + + times, values = isi_distance_impl(spikes1, spikes2) return PieceWiseConstFunc(times, values) @@ -76,10 +83,17 @@ def spike_profile(spikes1, spikes2): "Given spike trains seems not to have auxiliary spikes!" # cython implementation - from cython_distance import spike_distance_cython - - times, y_starts, y_ends = spike_distance_cython(spikes1, spikes2) - + try: + from cython_distance import spike_distance_cython \ + as spike_distance_impl + except ImportError: + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from python_backend import spike_distance_python as spike_distance_impl + + times, y_starts, y_ends = spike_distance_impl(spikes1, spikes2) return PieceWiseLinFunc(times, y_starts, y_ends) diff --git a/pyspike/function.py b/pyspike/function.py index 46fdea2..b161034 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -83,14 +83,19 @@ class PieceWiseConstFunc: assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" - # python implementation - # from python_backend import add_piece_wise_const_python - # self.x, self.y = add_piece_wise_const_python(self.x, self.y, - # f.x, f.y) - # cython version - from cython_add import add_piece_wise_const_cython - self.x, self.y = add_piece_wise_const_cython(self.x, self.y, f.x, f.y) + try: + from cython_add import add_piece_wise_const_cython as \ + add_piece_wise_const_impl + except ImportError: + print("Warning: add_piece_wise_const_cython not found. Make sure \ +that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ +\n Falling back to slow python backend.") + # use python backend + from python_backend import add_piece_wise_const_python as \ + add_piece_wise_const_impl + + self.x, self.y = add_piece_wise_const_impl(self.x, self.y, f.x, f.y) def mul_scalar(self, fac): """ Multiplies the function with a scalar value @@ -188,8 +193,18 @@ class PieceWiseLinFunc: # self.x, self.y1, self.y2, f.x, f.y1, f.y2) # cython version - from cython_add import add_piece_wise_lin_cython - self.x, self.y1, self.y2 = add_piece_wise_lin_cython( + try: + from cython_add import add_piece_wise_lin_cython as \ + add_piece_wise_lin_impl + except ImportError: + print("Warning: add_piece_wise_lin_cython not found. Make sure \ +that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ +\n Falling back to slow python backend.") + # use python backend + from python_backend import add_piece_wise_lin_python as \ + add_piece_wise_lin_impl + + self.x, self.y1, self.y2 = add_piece_wise_lin_impl( self.x, self.y1, self.y2, f.x, f.y1, f.y2) def mul_scalar(self, fac): diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index a1f5ea2..874c689 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -11,8 +11,6 @@ Distributed under the BSD License import numpy as np -from pyspike import PieceWiseConstFunc, PieceWiseLinFunc - ############################################################ # isi_distance_python @@ -32,7 +30,7 @@ def isi_distance_python(s1, s2): # add the distance of the first events # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ # else 1.0 - nu2[0]/nu1[0] - isi_values[0] = (nu1[0] - nu2[0]) / max(nu1[0], nu2[0]) + isi_values[0] = abs(nu1[0] - nu2[0]) / max(nu1[0], nu2[0]) index1 = 0 index2 = 0 index = 1 @@ -56,14 +54,14 @@ def isi_distance_python(s1, s2): break spike_events[index] = s1[index1] # compute the corresponding isi-distance - isi_values[index] = (nu1[index1] - nu2[index2]) / \ + isi_values[index] = abs(nu1[index1] - nu2[index2]) / \ max(nu1[index1], nu2[index2]) index += 1 # the last event is the interval end spike_events[index] = s1[-1] # use only the data added above # could be less than original length due to equal spike times - return PieceWiseConstFunc(spike_events[:index + 1], isi_values[:index]) + return spike_events[:index + 1], isi_values[:index] ############################################################ @@ -187,8 +185,7 @@ def spike_distance_python(spikes1, spikes2): y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) # use only the data added above # could be less than original length due to equal spike times - return PieceWiseLinFunc(spike_events[:index+1], - y_starts[:index], y_ends[:index]) + return spike_events[:index+1], y_starts[:index], y_ends[:index] ############################################################ -- cgit v1.2.3 From d516af64dbef41ed0668fd5e0ae70de3cf787e13 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 15:21:23 +0200 Subject: add non-cython tests --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 43f3ef7..2bbb9ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,9 +2,11 @@ language: python python: - "2.6" - "2.7" - +env: + - CYTHON_INSTALL="pip install -q cython" + - CYTHON_INSTALL="" install: - - pip install -q cython + - $CYTHON_INSTALL script: - python setup.py build_ext --inplace -- cgit v1.2.3 From 7ff5a4afe2d7a40dce34ae187a23b7d0feba33ba Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 15:26:58 +0200 Subject: catch ImportError in setup.py --- .travis.yml | 1 - setup.py | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2bbb9ca..1035775 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,4 +11,3 @@ install: script: - python setup.py build_ext --inplace - nosetests - diff --git a/setup.py b/setup.py index a9f40bd..fc8d0d2 100644 --- a/setup.py +++ b/setup.py @@ -2,16 +2,23 @@ Handles the compilation of pyx source files +run as: +python setup.py build_ext --inplace + Copyright 2014, Mario Mulansky Distributed under the BSD License """ from distutils.core import setup -from Cython.Build import cythonize import numpy -setup( - ext_modules=cythonize("pyspike/*.pyx"), - include_dirs=[numpy.get_include()] -) +try: + from Cython.Build import cythonize + setup( + ext_modules=cythonize("pyspike/*.pyx"), + include_dirs=[numpy.get_include()] + ) +except ImportError: + print("Error: Cython is not installed! You will only be able to use the \ +much slower Python backend in PySpike.") -- cgit v1.2.3 From 3f810c231e661e9141c9c586ebd6d9d182488c92 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 17:51:23 +0200 Subject: added sphinx doc generation --- doc/Makefile | 177 +++++++++++++++++++++++++++++++++ doc/conf.py | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++ doc/index.rst | 22 +++++ doc/pyspike.rst | 38 ++++++++ pyspike/distances.py | 147 +++++++++++++++------------- pyspike/function.py | 126 +++++++++++++----------- pyspike/spikes.py | 58 +++++------ 7 files changed, 689 insertions(+), 150 deletions(-) create mode 100644 doc/Makefile create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 doc/pyspike.rst diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..5acfcec --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PySpike.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PySpike.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/PySpike" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PySpike" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..48ebc7e --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,271 @@ +# -*- coding: utf-8 -*- +# +# PySpike documentation build configuration file, created by +# sphinx-quickstart on Thu Oct 16 15:56:58 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('../pyspike')) + +def skip(app, what, name, obj, skip, options): + if name == "__init__": + return False + return skip + +def setup(app): + app.connect("autodoc-skip-member", skip) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'PySpike' +copyright = u'2014, Mario Mulansky' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PySpikedoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'PySpike.tex', u'PySpike Documentation', + u'Mario Mulansky', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'pyspike', u'PySpike Documentation', + [u'Mario Mulansky'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'PySpike', u'PySpike Documentation', + u'Mario Mulansky', 'PySpike', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..b94c162 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,22 @@ +.. PySpike documentation master file, created by + sphinx-quickstart on Thu Oct 16 15:56:58 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to PySpike's documentation! +=================================== + +Reference: + +.. toctree:: + :maxdepth: 2 + + pyspike + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/pyspike.rst b/doc/pyspike.rst new file mode 100644 index 0000000..39adea0 --- /dev/null +++ b/doc/pyspike.rst @@ -0,0 +1,38 @@ +pyspike package +=============== + +Submodules +---------- + +pyspike.distances module +------------------------ + +.. automodule:: pyspike.distances + :members: + :undoc-members: + :show-inheritance: + +pyspike.function module +----------------------- + +.. automodule:: pyspike.function + :members: + :undoc-members: + :show-inheritance: + +pyspike.spikes module +--------------------- + +.. automodule:: pyspike.spikes + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pyspike + :members: + :undoc-members: + :show-inheritance: diff --git a/pyspike/distances.py b/pyspike/distances.py index 3e97b77..b0af24c 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -17,15 +17,17 @@ from pyspike import PieceWiseConstFunc, PieceWiseLinFunc # isi_profile ############################################################ def isi_profile(spikes1, spikes2): - """ Computes the isi-distance profile S_isi(t) of the two given spike - trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi + """ Computes the isi-distance profile :math:`S_{isi}(t)` of the two given + spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi values are defined positive S_isi(t)>=0. 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. - Returns: - - PieceWiseConstFunc describing the isi-distance. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The isi-distance profile :math:`S_{isi}(t)` + :rtype: :class:`pyspike.function.PieceWiseConstFunc` + """ # check for auxiliary spikes - first and last spikes should be identical assert spikes1[0] == spikes2[0], \ @@ -52,12 +54,15 @@ Falling back to slow python backend.") ############################################################ def isi_distance(spikes1, spikes2): """ Computes the isi-distance I of the given spike trains. The - isi-distance is the integral over the isi distance profile S_isi(t): - I = \int_^T S_isi(t) dt. - Args: - - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes. - Returns: - - double value: The isi-distance I. + isi-distance is the integral over the isi distance profile + :math:`S_{isi}(t)`: + + .. math:: I = \int_0^T S_{isi}(t) dt. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The isi-distance I. + :rtype: double """ return isi_profile(spikes1, spikes2).avrg() @@ -71,10 +76,12 @@ def spike_profile(spikes1, spikes2): values are defined positive S_spike(t)>=0. 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. - Returns: - - PieceWiseLinFunc describing the spike-distance. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The spike-distance profile :math:`S_{spike}(t). + :rtype: :class:`pyspike.function.PieceWiseLinFunc` + """ # check for auxiliary spikes - first and last spikes should be identical assert spikes1[0] == spikes2[0], \ @@ -104,18 +111,20 @@ def spike_distance(spikes1, spikes2): """ Computes the spike-distance S of the given spike trains. The spike-distance is the integral over the isi distance profile S_spike(t): S = \int_^T S_spike(t) dt. - Args: - - spikes1, spikes2: ordered arrays of spike times with auxiliary spikes. - Returns: - - double value: The spike-distance S. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The spike-distance. + :rtype: double + """ return spike_profile(spikes1, spikes2).avrg() ############################################################ -# generic_profile_multi +# _generic_profile_multi ############################################################ -def generic_profile_multi(spike_trains, pair_distance_func, indices=None): +def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): """ Internal implementation detail, don't call this function directly, use isi_profile_multi or spike_profile_multi instead. @@ -153,7 +162,7 @@ def generic_profile_multi(spike_trains, pair_distance_func, indices=None): ############################################################ # multi_distance_par ############################################################ -def multi_distance_par(spike_trains, pair_distance_func, indices=None): +def _multi_distance_par(spike_trains, pair_distance_func, indices=None): """ parallel implementation of the multi-distance. Not currently used as it does not improve the performance. """ @@ -210,14 +219,15 @@ def isi_profile_multi(spike_trains, indices=None): trains. That is the average isi-distance of all pairs of spike-trains: S_isi(t) = 2/((N(N-1)) sum_{} S_{isi}^{i,j}, where the sum goes over all pairs - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - Returns: - - A PieceWiseConstFunc representing the averaged isi distance S_isi(t) + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type state: list or None + :returns: The averaged isi profile :math:`(t)` + :rtype: :class:`pyspike.function.PieceWiseConstFunc` """ - return generic_profile_multi(spike_trains, isi_profile, indices) + return _generic_profile_multi(spike_trains, isi_profile, indices) ############################################################ @@ -226,14 +236,14 @@ def isi_profile_multi(spike_trains, indices=None): def isi_distance_multi(spike_trains, indices=None): """ computes the multi-variate isi-distance for a set of spike-trains. That is the time average of the multi-variate spike profile: - S_isi = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, + I = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, where the sum goes over all pairs - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - Returns: - - A double value representing the averaged isi distance S_isi + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :returns: The time-averaged isi distance :math:`I` + :rtype: double """ return isi_profile_multi(spike_trains, indices).avrg() @@ -246,14 +256,14 @@ def spike_profile_multi(spike_trains, indices=None): trains. That is the average spike-distance of all pairs of spike-trains: S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}, where the sum goes over all pairs - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike-trains to use, - if None all given spike trains are used (default=None) - Returns: - - A PieceWiseLinFunc representing the averaged spike distance S(t) + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: The averaged spike profile :math:`(t)` + :rtype: :class:`pyspike.function.PieceWiseLinFunc` """ - return generic_profile_multi(spike_trains, spike_profile, indices) + return _generic_profile_multi(spike_trains, spike_profile, indices) ############################################################ @@ -264,12 +274,13 @@ def spike_distance_multi(spike_trains, indices=None): That is the time average of the multi-variate spike profile: S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt where the sum goes over all pairs - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike-trains to use, - if None all given spike trains are used (default=None) - Returns: - - A double value representing the averaged spike distance S + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: The averaged spike distance S. + :rtype: double """ return spike_profile_multi(spike_trains, indices).avrg() @@ -277,7 +288,7 @@ def spike_distance_multi(spike_trains, indices=None): ############################################################ # generic_distance_matrix ############################################################ -def generic_distance_matrix(spike_trains, dist_function, indices=None): +def _generic_distance_matrix(spike_trains, dist_function, indices=None): """ Internal implementation detail. Don't use this function directly. Instead use isi_distance_matrix or spike_distance_matrix. Computes the time averaged distance of all pairs of spike-trains. @@ -311,15 +322,16 @@ def generic_distance_matrix(spike_trains, dist_function, indices=None): ############################################################ def isi_distance_matrix(spike_trains, indices=None): """ Computes the time averaged isi-distance of all pairs of spike-trains. - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike-trains to use - if None all given spike-trains are used (default=None) - Return: - - a 2D array of size len(indices)*len(indices) containing the average - pair-wise isi-distance + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: 2D array with the pair wise time average isi distances + :math:`I_{ij}` + :rtype: np.array """ - return generic_distance_matrix(spike_trains, isi_distance, indices) + return _generic_distance_matrix(spike_trains, isi_distance, indices) ############################################################ @@ -327,12 +339,13 @@ def isi_distance_matrix(spike_trains, indices=None): ############################################################ def spike_distance_matrix(spike_trains, indices=None): """ Computes the time averaged spike-distance of all pairs of spike-trains. - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike-trains to use - if None all given spike-trains are used (default=None) - Return: - - a 2D array of size len(indices)*len(indices) containing the average - pair-wise spike-distance + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: 2D array with the pair wise time average spike distances + :math:`S_{ij}` + :rtype: np.array """ - return generic_distance_matrix(spike_trains, spike_distance, indices) + return _generic_distance_matrix(spike_trains, spike_distance, indices) diff --git a/pyspike/function.py b/pyspike/function.py index b161034..ed47f27 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -16,15 +16,16 @@ import numpy as np ############################################################## # PieceWiseConstFunc ############################################################## -class PieceWiseConstFunc: +class PieceWiseConstFunc(object): """ A class representing a piece-wise constant function. """ def __init__(self, x, y): """ Constructs the piece-wise const function. - Args: - - x: array of length N+1 defining the edges of the intervals of the pwc - function. - - y: array of length N defining the function values at the intervals. + + :param x: array of length N+1 defining the edges of the intervals of + the pwc function. + :param y: array of length N defining the function values at the + intervals. """ # convert parameters to arrays, also ensures copying self.x = np.array(x) @@ -32,19 +33,19 @@ class PieceWiseConstFunc: def copy(self): """ Returns a copy of itself - Returns: - - PieceWiseConstFunc copy + + :rtype: :class:`PieceWiseConstFunc` """ return PieceWiseConstFunc(self.x, self.y) def almost_equal(self, other, decimal=14): """ Checks if the function is equal to another function up to `decimal` precision. - Args: - - other: another PieceWiseConstFunc object - Returns: - True if the two functions are equal up to `decimal` decimals, - False otherwise + + :param: other: another :class:`PieceWiseConstFunc` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool """ eps = 10.0**(-decimal) return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ @@ -53,6 +54,14 @@ class PieceWiseConstFunc: def get_plottable_data(self): """ Returns two arrays containing x- and y-coordinates for immeditate plotting of the piece-wise function. + + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Piece-wise const function") """ x_plot = np.empty(2*len(self.x)-2) @@ -67,9 +76,10 @@ class PieceWiseConstFunc: def avrg(self): """ Computes the average of the piece-wise const function: - a = 1/T int f(x) dx where T is the length of the interval. - Returns: - - the average a. + :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + + :returns: the average a. + :rtype: double """ return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ (self.x[-1]-self.x[0]) @@ -77,8 +87,9 @@ class PieceWiseConstFunc: def add(self, f): """ Adds another PieceWiseConst function to this function. Note: only functions defined on the same interval can be summed. - Args: - - f: PieceWiseConst function to be added. + + :param f: :class:`PieceWiseConstFunc` function to be added. + :rtype: None """ assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" @@ -99,8 +110,10 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ def mul_scalar(self, fac): """ Multiplies the function with a scalar value - Args: - - fac: Value to multiply + + :param fac: Value to multiply + :type fac: double + :rtype: None """ self.y *= fac @@ -113,13 +126,13 @@ class PieceWiseLinFunc: def __init__(self, x, y1, y2): """ Constructs the piece-wise linear function. - Args: - - x: array of length N+1 defining the edges of the intervals of the pwc - function. - - y1: array of length N defining the function values at the left of the - intervals. - - y2: array of length N defining the function values at the right of - the intervals. + + :param x: array of length N+1 defining the edges of the intervals of + the pwc function. + :param y1: array of length N defining the function values at the left + of the intervals. + :param y2: array of length N defining the function values at the right + of the intervals. """ # convert to array, which also ensures copying self.x = np.array(x) @@ -128,19 +141,19 @@ class PieceWiseLinFunc: def copy(self): """ Returns a copy of itself - Returns: - - PieceWiseLinFunc copy + + :rtype: :class`PieceWiseLinFunc` """ return PieceWiseLinFunc(self.x, self.y1, self.y2) def almost_equal(self, other, decimal=14): """ Checks if the function is equal to another function up to `decimal` precision. - Args: - - other: another PieceWiseLinFunc object - Returns: - True if the two functions are equal up to `decimal` decimals, - False otherwise + + :param: other: another :class:`PieceWiseLinFunc` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool """ eps = 10.0**(-decimal) return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ @@ -150,6 +163,14 @@ class PieceWiseLinFunc: def get_plottable_data(self): """ Returns two arrays containing x- and y-coordinates for immeditate plotting of the piece-wise function. + + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Piece-wise const function") """ x_plot = np.empty(2*len(self.x)-2) x_plot[0] = self.x[0] @@ -162,27 +183,20 @@ class PieceWiseLinFunc: def avrg(self): """ Computes the average of the piece-wise linear function: - a = 1/T int f(x) dx where T is the length of the interval. - Returns: - - the average a. + :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + + :returns: the average a. + :rtype: double """ return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ (self.x[-1]-self.x[0]) - def abs_avrg(self): - """ Computes the absolute average of the piece-wise linear function: - a = 1/T int |f(x)| dx where T is the length of the interval. - Returns: - - the average a. - """ - return np.sum((self.x[1:]-self.x[:-1]) * 0.5 * - (np.abs(self.y1)+np.abs(self.y2)))/(self.x[-1]-self.x[0]) - def add(self, f): """ Adds another PieceWiseLin function to this function. Note: only functions defined on the same interval can be summed. - Args: - - f: PieceWiseLin function to be added. + + :param f: :class:`PieceWiseLinFunc` function to be added. + :rtype: None """ assert self.x[0] == f.x[0], "The functions have different intervals" assert self.x[-1] == f.x[-1], "The functions have different intervals" @@ -209,8 +223,10 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ def mul_scalar(self, fac): """ Multiplies the function with a scalar value - Args: - - fac: Value to multiply + + :param fac: Value to multiply + :type fac: double + :rtype: None """ self.y1 *= fac self.y2 *= fac @@ -218,12 +234,12 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ def average_profile(profiles): """ Computes the average profile from the given ISI- or SPIKE-profiles. - Args: - - profiles: list of PieceWiseConstFunc or PieceWiseLinFunc representing - ISI- or SPIKE-profiles to be averaged - Returns: - - avrg_profile: PieceWiseConstFunc or PieceWiseLinFunc containing the - average profile. + + :param profiles: list of :class:`PieceWiseConstFunc` or + :class:`PieceWiseLinFunc` representing ISI- or + SPIKE-profiles to be averaged. + :returns: the averages profile :math:`` or :math:``. + :rtype: :class:`PieceWiseConstFunc` or :class:`PieceWiseLinFunc` """ assert len(profiles) > 1 diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 68c8bc1..6b6e2e7 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -15,15 +15,16 @@ import numpy as np ############################################################ def add_auxiliary_spikes(spike_train, time_interval): """ Adds spikes at the beginning and end of the given time interval. - Args: - - spike_train: ordered array of spike times - - time_interval: A pair (T_start, T_end) of values representing the start - and end time of the spike train measurement or a single value representing - the end time, the T_start is then assuemd as 0. Auxiliary spikes will be - added to the spike train at the beginning and end of this interval, if they - are not yet present. - Returns: - - spike train with additional spikes at T_start and T_end. + + :param spike_train: ordered array of spike times + :param time_interval: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement or + a single value representing the end time, the T_start + is then assuemd as 0. Auxiliary spikes will be added + to the spike train at the beginning and end of this + interval, if they are not yet present. + :type time_interval: pair of doubles or double + :returns: spike train with additional spikes at T_start and T_end. """ try: @@ -49,12 +50,11 @@ def add_auxiliary_spikes(spike_train, time_interval): ############################################################ def spike_train_from_string(s, sep=' ', sort=True): """ Converts a string of times into an array of spike times. - Args: - - s: the string with (ordered) spike times - - sep: The separator between the time numbers, default=' '. - - sort: If True, the spike times are order via `np.sort`, default=True. - Returns: - - array of spike times + + :param s: the string with (ordered) spike times + :param sep: The separator between the time numbers, default=' '. + :param sort: If True, the spike times are order via `np.sort`, default=True + :returns: array of spike times """ if sort: return np.sort(np.fromstring(s, sep=sep)) @@ -75,15 +75,18 @@ def load_spike_trains_from_txt(file_name, time_interval=None, end of each spike train. However, if `time_interval == None`, no auxiliary spikes are added, but note that the Spike and ISI distance both require auxiliary spikes. - Args: - - file_name: The name of the text file. - - time_interval: A pair (T_start, T_end) of values representing the start - and end time of the spike train measurement or a single value representing - the end time, the T_start is then assuemd as 0. Auxiliary spikes will be - added to the spike train at the beginning and end of this interval. - - separator: The character used to seprate the values in the text file. - - comment: Lines starting with this character are ignored. - - sort: If true, the spike times are order via `np.sort`, default=True. + + :param file_name: The name of the text file. + :param time_interval: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement + or a single value representing the end time, the + T_start is then assuemd as 0. Auxiliary spikes will + be added to the spike train at the beginning and end + of this interval. + :param separator: The character used to seprate the values in the text file + :param comment: Lines starting with this character are ignored. + :param sort: If true, the spike times are order via `np.sort`, default=True + :returns: list of spike trains """ spike_trains = [] spike_file = open(file_name, 'r') @@ -102,10 +105,9 @@ def load_spike_trains_from_txt(file_name, time_interval=None, ############################################################ def merge_spike_trains(spike_trains): """ Merges a number of spike trains into a single spike train. - Args: - - spike_trains: list of arrays of spike times - Returns: - - array with the merged spike times + + :param spike_trains: list of arrays of spike times + :returns: spike train with the merged spike times """ # get the lengths of the spike trains lens = np.array([len(st) for st in spike_trains]) -- cgit v1.2.3 From 0005a0f170f69573c8101c4d400131b0dc9d8ee5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 16 Oct 2014 18:07:49 +0200 Subject: deleted abs_avrg test --- test/test_function.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/test_function.py b/test/test_function.py index ed70180..c856d76 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -100,9 +100,6 @@ def test_pwl(): avrg_expected = (1.25 - 0.45 + 0.75 + 1.5*0.5) / 4.0 assert_almost_equal(f.avrg(), avrg_expected, decimal=16) - abs_avrg_expected = (1.25 + 0.45 + 0.75 + 1.5*0.5) / 4.0 - assert_almost_equal(f.abs_avrg(), abs_avrg_expected, decimal=16) - def test_pwl_add(): x = [0.0, 1.0, 2.0, 2.5, 4.0] -- cgit v1.2.3 From a8c271257c9f684997d1a86e66f004c942ed4447 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 20 Oct 2014 11:57:43 +0200 Subject: changed readme from markdown to rst --- Readme.md | 131 --------------------------------------------- Readme.rst | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ doc/index.rst | 6 +-- 3 files changed, 169 insertions(+), 134 deletions(-) delete mode 100644 Readme.md create mode 100644 Readme.rst diff --git a/Readme.md b/Readme.md deleted file mode 100644 index be42c4a..0000000 --- a/Readme.md +++ /dev/null @@ -1,131 +0,0 @@ -# PySpike - -[![Build Status](https://travis-ci.org/mariomulansky/PySpike.svg?branch=master)](https://travis-ci.org/mariomulansky/PySpike) - -PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the bivariate [ISI](http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance) [1] and [SPIKE](http://www.scholarpedia.org/article/SPIKE-distance) [2] distance. -Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. -All computation intensive parts are implemented in C via [cython](http://www.cython.org) to reach a competitive performance (factor 100-200 over plain Python). - -All source codes are published under the [BSD License](http://opensource.org/licenses/BSD-2-Clause). - ->[1] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) - ->[2] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) - -## Requirements and Installation - -To use PySpike you need Python installed with the following additional packages: - -- numpy -- scipy -- matplotlib -- cython -- nosetests (for running the tests) - -In particular, make sure that [cython](http://www.cython.org) is configured properly and able to locate a C compiler. - -To install PySpike, simply download the source, e.g. from Github, and run the `setup.py` script: - - git clone https://github.com/mariomulansky/PySpike.git - cd PySpike - python setup.py build_ext --inplace - -Then you can run the tests using the `nosetests` test framework: - - nosetests - -Finally, you should make PySpike's installation folder known to Python to be able to import pyspike in your own projects. -Therefore, add your `/path/to/PySpike` to the `$PYTHONPATH` environment variable. - -## Spike trains - -In PySpike, spike trains are represented by one-dimensional numpy arrays containing the sequence of spike times as double values. -The following code creates such a spike train with some arbitrary spike times: - - import numpy as np - - spike_train = np.array([0.1, 0.3, 0.45, 0.6, 0.9]) - -### Loading from text files - -Typically, spike train data is loaded into PySpike from data files. -The most straight-forward data files are text files where each line represents one spike train given as an sequence of spike times. -An exemplary file with several spike trains is [PySpike_testdata.txt](https://github.com/mariomulansky/PySpike/blob/master/examples/PySpike_testdata.txt). -To quickly obtain spike trains from such files, PySpike provides the function `load_spike_trains_from_txt`. - - import numpy as np - import pyspike as spk - - spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", - time_interval=(0,4000)) - -This function expects the name of the data file as first parameter, and additionally the time intervall of the spike train measurement can be provided as a pair of start- and end-time values. -If the time interval is provided (`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. -Furthermore, the spike trains are ordered via `np.sort` (disable this feature by providing `sort=False` as a parameter to the load function). -As result, `load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. - -If you load spike trains yourself, i.e. from data files with different structure, you can use the helper function `add_auxiliary_spikes` to add the auxiliary spikes at the beginning and end of the observation interval. -Both the ISI and the SPIKE distance computation require the presence of auxiliary spikes, so make sure you have those in your spike trains: - - spike_train = spk.add_auxiliary_spikes(spike_train, (T_start, T_end)) - # if you provide only a single value, it is interpreted as T_end, while T_start=0 - spike_train = spk.add_auxiliary_spikes(spike_train, T_end) - -## Computing bi-variate distances - ----------------------- -**Important note:** - ->Spike trains are expected to be *ordered sequences*! ->For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed ordered. ->Make sure that all your spike trains are ordered. ->If in doubt, use `spike_train = np.sort(spike_train)` to obtain a correctly ordered spike train. -> ->Furthermore, the spike trains should have auxiliary spikes at the beginning and end of the observation interval. ->You can ensure this by providing the `time_interval` in the `load_spike_trains_from_txt` function, or calling `add_auxiliary_spikes` for your spike trains. ->The spike trains must have *the same* observation interval! - ----------------------- - -### ISI-distance - -The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two spike trains, and plots it with matplotlib: - - import matplotlib.pyplot as plt - import pyspike as spk - - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) - isi_profile = spk.isi_profile(spike_trains[0], spike_trains[1]) - x, y = isi_profile.get_plottable_data() - plt.plot(x, y, '--k') - print("ISI distance: %.8f" % isi_profil.avrg()) - plt.show() - -The ISI-profile is a piece-wise constant function, there the function `isi_profile` returns an instance of the `PieceWiseConstFunc` class. -As shown above, this class allows you to obtain arrays that can be used to plot the function with `plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. -If you are only interested in the scalar ISI-distance and not the profile, you can simly use: - - isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1]) - -Furthermore, PySpike provides the `average_profile` function that can be used to compute the average profile of a list of given `PieceWiseConstFunc` instances. - - avrg_profile = spk.average_profile([spike_train1, spike_train2]) - x, y = avrg_profile.get_plottable_data() - plt.plot(x, y, label="Average profile") - -Note the difference between the `average_profile` function, which returns a `PieceWiseConstFunc` (or `PieceWiseLinFunc`, see below), and the `avrg` member function above, that computes the integral over the time profile. -So to obtain overall average ISI-distance of a list of ISI profiles you can first compute the average profile using `average_profile` and the use - - avrg_isi = avrg_profile.avrg() - -to obtain the final, scalar average ISI distance of the whole set (see also "Computing multi-variate distance" below). - -## Computing multi-variate distances - - -## Plotting - - -## Averaging diff --git a/Readme.rst b/Readme.rst new file mode 100644 index 0000000..9f394e4 --- /dev/null +++ b/Readme.rst @@ -0,0 +1,166 @@ +PySpike +======= + +.. image:: https://travis-ci.org/mariomulansky/PySpike.svg?branch=master + :target: https://travis-ci.org/mariomulansky/PySpike + +PySpike is a Python library for the numerical analysis of spike train similarity. +Its core functionality is the implementation of the bivariate ISI_ [#]_ and SPIKE_ [#]_. +Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. +All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). + +All source codes are published under the BSD_License_. + +.. [#] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) + +.. [#] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) + +Requirements and Installation +----------------------------- + +To use PySpike you need Python installed with the following additional packages: + +- numpy +- scipy +- matplotlib +- cython +- nosetests (for running the tests) + +In particular, make sure that cython_ is configured properly and able to locate a C compiler. + +To install PySpike, simply download the source, e.g. from Github, and run the :code:`setup.py` script: + +.. code:: bash + + git clone https://github.com/mariomulansky/PySpike.git + cd PySpike + python setup.py build_ext --inplace + +Then you can run the tests using the `nosetests` test framework: + +.. code:: bash + + nosetests + +Finally, you should make PySpike's installation folder known to Python to be able to import pyspike in your own projects. +Therefore, add your :code:`/path/to/PySpike` to the :code:`$PYTHONPATH` environment variable. + +Spike trains +------------ + +In PySpike, spike trains are represented by one-dimensional numpy arrays containing the sequence of spike times as double values. +The following code creates such a spike train with some arbitrary spike times: + +.. code:: python + + import numpy as np + + spike_train = np.array([0.1, 0.3, 0.45, 0.6, 0.9]) + +Loading from text files +....................... + +Typically, spike train data is loaded into PySpike from data files. +The most straight-forward data files are text files where each line represents one spike train given as an sequence of spike times. +An exemplary file with several spike trains is `PySpike_testdata.txt `_. +To quickly obtain spike trains from such files, PySpike provides the function :code:`load_spike_trains_from_txt`. + +.. code:: python + + import numpy as np + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + time_interval=(0, 4000)) + +This function expects the name of the data file as first parameter, and additionally the time intervall of the spike train measurement can be provided as a pair of start- and end-time values. +If the time interval is provided (:code:`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. +Furthermore, the spike trains are ordered via :code:`np.sort` (disable this feature by providing :code:`sort=False` as a parameter to the load function). +As result, :code:`load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. + +If you load spike trains yourself, i.e. from data files with different structure, you can use the helper function :code:`add_auxiliary_spikes` to add the auxiliary spikes at the beginning and end of the observation interval. +Both the ISI and the SPIKE distance computation require the presence of auxiliary spikes, so make sure you have those in your spike trains: + +..code:: python + + spike_train = spk.add_auxiliary_spikes(spike_train, (T_start, T_end)) + # if you provide only a single value, it is interpreted as T_end, while T_start=0 + spike_train = spk.add_auxiliary_spikes(spike_train, T_end) + +Computing bi-variate distances +------------------------------ + +------------------------------ + +**Important note:** + + Spike trains are expected to be *ordered sequences*! + For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed ordered. + Make sure that all your spike trains are ordered. + If in doubt, use :code:`spike_train = np.sort(spike_train)` to obtain a correctly ordered spike train. + + Furthermore, the spike trains should have auxiliary spikes at the beginning and end of the observation interval. + You can ensure this by providing the :code:`time_interval` in the :code:`load_spike_trains_from_txt` function, or calling :code:`add_auxiliary_spikes` for your spike trains. + The spike trains must have *the same* observation interval! + +---------------------- + +ISI-distance +............ + +The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two spike trains, and plots it with matplotlib: + +.. code:: python + + import matplotlib.pyplot as plt + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + isi_profile = spk.isi_profile(spike_trains[0], spike_trains[1]) + x, y = isi_profile.get_plottable_data() + plt.plot(x, y, '--k') + print("ISI distance: %.8f" % isi_profil.avrg()) + plt.show() + +The ISI-profile is a piece-wise constant function, there the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. +As shown above, this class allows you to obtain arrays that can be used to plot the function with :code":`plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. +If you are only interested in the scalar ISI-distance and not the profile, you can simly use: + +.. code:: python + + isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1]) + +Furthermore, PySpike provides the :code:`average_profile` function that can be used to compute the average profile of a list of given :code:`PieceWiseConstFunc` instances. + +.. code:: python + + avrg_profile = spk.average_profile([spike_train1, spike_train2]) + x, y = avrg_profile.get_plottable_data() + plt.plot(x, y, label="Average profile") + +Note the difference between the :code:`average_profile` function, which returns a :code:`PieceWiseConstFunc` (or :code:`PieceWiseLinFunc`, see below), and the :code:`avrg` member function above, that computes the integral over the time profile. +So to obtain overall average ISI-distance of a list of ISI profiles you can first compute the average profile using :code:`average_profile` and the use + +.. code:: python + + avrg_isi = avrg_profile.avrg() + +to obtain the final, scalar average ISI distance of the whole set (see also "Computing multi-variate distance" below). + +Computing multi-variate distances +--------------------------------- + + +Plotting +-------- + + +Averaging +--------- + + +.. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance +.. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance +.. _cython: http://www.cython.org +.. _BSD_License: http://opensource.org/licenses/BSD-2-Clause diff --git a/doc/index.rst b/doc/index.rst index b94c162..bc05b60 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -3,10 +3,10 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to PySpike's documentation! -=================================== +.. include:: ../Readme.rst -Reference: +Reference +=================================== .. toctree:: :maxdepth: 2 -- cgit v1.2.3 From 0b238639ed487f47c846905294060c8f679780da Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 20 Oct 2014 12:03:35 +0200 Subject: fix rst error --- Readme.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.rst b/Readme.rst index 9f394e4..81ef338 100644 --- a/Readme.rst +++ b/Readme.rst @@ -90,10 +90,10 @@ Both the ISI and the SPIKE distance computation require the presence of auxiliar Computing bi-variate distances ------------------------------ ------------------------------- - **Important note:** +------------------------------ + Spike trains are expected to be *ordered sequences*! For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed ordered. Make sure that all your spike trains are ordered. -- cgit v1.2.3 From e85e6a72662d30b677dd4c9ded6d2b1520ba63ec Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 20 Oct 2014 18:13:23 +0200 Subject: +multivariate example, docs for spike profile --- Readme.rst | 60 ++++++++++++++++++++++++++++++++++++++++-------- examples/multivariate.py | 50 ++++++++++++++++++++++++++++++++++++++++ examples/plot.py | 14 ++++++----- 3 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 examples/multivariate.py diff --git a/Readme.rst b/Readme.rst index 81ef338..b0128c0 100644 --- a/Readme.rst +++ b/Readme.rst @@ -26,7 +26,7 @@ To use PySpike you need Python installed with the following additional packages: - cython - nosetests (for running the tests) -In particular, make sure that cython_ is configured properly and able to locate a C compiler. +In particular, make sure that cython_ is configured properly and able to locate a C compiler, otherwise you will only be able to use the much slower plain Python implementations. To install PySpike, simply download the source, e.g. from Github, and run the :code:`setup.py` script: @@ -124,7 +124,7 @@ The following code loads some exemplary spike trains, computes the dissimilarity plt.show() The ISI-profile is a piece-wise constant function, there the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. -As shown above, this class allows you to obtain arrays that can be used to plot the function with :code":`plt.plt`, but also to compute the absolute average, which amounts to the final scalar ISI-distance. +As shown above, this class allows you to obtain arrays that can be used to plot the function with :code":`plt.plt`, but also to compute the average, which amounts to the final scalar ISI-distance. If you are only interested in the scalar ISI-distance and not the profile, you can simly use: .. code:: python @@ -135,11 +135,15 @@ Furthermore, PySpike provides the :code:`average_profile` function that can be u .. code:: python - avrg_profile = spk.average_profile([spike_train1, spike_train2]) + isi_profile1 = spk.isi_profile(spike_trains[0], spike_trains[1]) + isi_profile2 = spk.isi_profile(spike_trains[0], spike_trains[2]) + isi_profile3 = spk.isi_profile(spike_trains[1], spike_trains[2]) + + avrg_profile = spk.average_profile([isi_profile1, isi_profile2, isi_profile3]) x, y = avrg_profile.get_plottable_data() - plt.plot(x, y, label="Average profile") + plt.plot(x, y, label="Average ISI profile") -Note the difference between the :code:`average_profile` function, which returns a :code:`PieceWiseConstFunc` (or :code:`PieceWiseLinFunc`, see below), and the :code:`avrg` member function above, that computes the integral over the time profile. +Note the difference between the :code:`average_profile` function, which returns a :code:`PieceWiseConstFunc` (or :code:`PieceWiseLinFunc`, see below), and the :code:`avrg` member function above, that computes the integral over the time profile resulting in a single value. So to obtain overall average ISI-distance of a list of ISI profiles you can first compute the average profile using :code:`average_profile` and the use .. code:: python @@ -148,12 +152,50 @@ So to obtain overall average ISI-distance of a list of ISI profiles you can firs to obtain the final, scalar average ISI distance of the whole set (see also "Computing multi-variate distance" below). -Computing multi-variate distances ---------------------------------- +SPIKE-distance +.............. + +To computation for the spike distance you use the function :code:`spike_profile` instead of :code:`isi_profile` above. +But the general approach is very similar: + +.. code:: python + + import matplotlib.pyplot as plt + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + spike_profile = spk.spike_profile(spike_trains[0], spike_trains[1]) + x, y = spike_profile.get_plottable_data() + plt.plot(x, y, '--k') + print("SPIKE distance: %.8f" % spike_profil.avrg()) + plt.show() -Plotting --------- +This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. +In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and thusly represented by a :code:`PieceWiseLinFunc` object. +Just like the :code:`PieceWiseconstFunc` for the ISI-profile, the :code:`PieceWiseLinFunc` provides a :code:`get_plottable_data` member function that returns array that can be used directly to plot the function. +Furthermore, the :code:`avrg` member function returns the average of the profile defined as the overall SPIKE distance. + +Again, you can use + +.. code:: python + + spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1]) + +to compute the SPIKE distance directly, if you are not interested in the profile at all. +Furthmore, you can use the :code:`average_profile` function to compute an average profile of a list of SPIKE-profiles: + +.. code:: python + + avrg_profile = spk.average_profile([spike_profile1, spike_profile2, + spike_profile3]) + x, y = avrg_profile.get_plottable_data() + plt.plot(x, y, label="Average SPIKE profile") + + +Computing multi-variate distances +--------------------------------- Averaging diff --git a/examples/multivariate.py b/examples/multivariate.py new file mode 100644 index 0000000..260b217 --- /dev/null +++ b/examples/multivariate.py @@ -0,0 +1,50 @@ +""" Example for the multivariate spike distance + +Copyright 2014, Mario Mulansky + +""" +from __future__ import print_function +import time +import pyspike as spk + + +def time_diff_in_ms(start, end): + """ Returns the time difference end-start in ms. + """ + return (end-start)*1000 + + +t_start = time.clock() + +# load the data +time_loading = time.clock() +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) +t_loading = time.clock() + +print("Number of spike trains: %d" % len(spike_trains)) +num_of_spikes = sum([len(spike_trains[i]) for i in xrange(len(spike_trains))]) +print("Number of spikes: %d" % num_of_spikes) + +# calculate the multivariate spike distance +f = spk.spike_profile_multi(spike_trains) + +t_spike = time.clock() + +# print the average +avrg = f.avrg() +print("Spike distance from average: %.8f" % avrg) + +t_avrg = time.clock() + +# compute average distance directly, should give the same result as above +spike_dist = spk.spike_distance_multi(spike_trains) +print("Spike distance directly: %.8f" % spike_dist) + +t_dist = time.clock() + +print("Loading: %9.1f ms" % time_diff_in_ms(t_start, t_loading)) +print("Computing profile: %9.1f ms" % time_diff_in_ms(t_loading, t_spike)) +print("Averaging: %9.1f ms" % time_diff_in_ms(t_spike, t_avrg)) +print("Computing distance: %9.1f ms" % time_diff_in_ms(t_avrg, t_dist)) +print("Total: %9.1f ms" % time_diff_in_ms(t_start, t_dist)) diff --git a/examples/plot.py b/examples/plot.py index 6da7f49..59334c9 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -26,15 +26,17 @@ f = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() plt.figure() -plt.plot(x, np.abs(y), '--k') +plt.plot(x, np.abs(y), '--k', label="ISI-profile") -print("Average: %.8f" % f.avrg()) +print("ISI-distance: %.8f" % f.avrg()) f = spk.spike_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() -print(x) -print(y) -#plt.figure() -plt.plot(x, y, '-b') + +plt.plot(x, y, '-b', label="SPIKE-profile") + +print("SPIKE-distance: %.8f" % f.avrg()) + +plt.legend(loc="upper left") plt.show() -- cgit v1.2.3 From 89287c100c7a49d8e5ee5ca848a77550a4ea36b5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 20 Oct 2014 18:17:44 +0200 Subject: fixed typo --- Readme.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.rst b/Readme.rst index b0128c0..72be27e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -81,7 +81,7 @@ As result, :code:`load_spike_trains_from_txt` returns a *list of arrays* contain If you load spike trains yourself, i.e. from data files with different structure, you can use the helper function :code:`add_auxiliary_spikes` to add the auxiliary spikes at the beginning and end of the observation interval. Both the ISI and the SPIKE distance computation require the presence of auxiliary spikes, so make sure you have those in your spike trains: -..code:: python +.. code:: python spike_train = spk.add_auxiliary_spikes(spike_train, (T_start, T_end)) # if you provide only a single value, it is interpreted as T_end, while T_start=0 -- cgit v1.2.3 From 1b0421a207f5a1eb43b12bb18d5e783e753b739e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 21 Oct 2014 19:05:39 +0200 Subject: doc: distance matrix, improved example --- Readme.rst | 37 ++++++++++++++++++++++++++++++++++++- examples/distance_matrix.py | 33 +++++++++++++++++++++++++++++++++ examples/isi_matrix.py | 26 -------------------------- 3 files changed, 69 insertions(+), 27 deletions(-) create mode 100644 examples/distance_matrix.py delete mode 100644 examples/isi_matrix.py diff --git a/Readme.rst b/Readme.rst index 72be27e..4482d01 100644 --- a/Readme.rst +++ b/Readme.rst @@ -194,9 +194,44 @@ Furthmore, you can use the :code:`average_profile` function to compute an averag plt.plot(x, y, label="Average SPIKE profile") -Computing multi-variate distances +Computing multi-variate profiles and distances --------------------------------- +To compute the multi-variate ISI- or SPIKE-profile of a set of spike trains, you can compute all bi-variate profiles separately and then use the :code:`average_profile` function above. +However, PySpike provides convenience functions for that purpose. +The following example computes the multivariate ISI- and SPIKE-profile for a list of spike trains: + +.. code:: python + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + avrg_isi_profile = spk.isi_profile_multi(spike_trains) + avrg_spike_profile = spk.spike_profile_multi(spike_trains) + +Both functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multi-variate profile. +As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi` and :code:`spike_distance_multi`, that return the scalar multi-variate ISI- and SPIKE-distance. + +Another option to address large sets of spike trains are distance matrices. +Each entry in the distance matrix represents a bi-variate distance of the spike trains. +Hence, the distance matrix is symmetric and has zero values at the diagonal. +The following example computes and plots the ISI- and SPIKE-distance matrix. + +.. code:: python + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) + + plt.figure() + isi_distance = spk.isi_distance_matrix(spike_trains) + plt.imshow(isi_distance, interpolation='none') + plt.title("ISI-distance") + + plt.figure() + spike_distance = spk.spike_distance_matrix(spike_trains) + plt.imshow(spike_distance, interpolation='none') + plt.title("SPIKE-distance") + + plt.show() + Averaging --------- diff --git a/examples/distance_matrix.py b/examples/distance_matrix.py new file mode 100644 index 0000000..38bd9c8 --- /dev/null +++ b/examples/distance_matrix.py @@ -0,0 +1,33 @@ +""" distance_matrix.py + +Simple example showing how to compute the isi distance matrix of a set of spike +trains. + +Copyright 2014, Mario Mulansky + +Distributed under the BSD License +""" + + +from __future__ import print_function + +import matplotlib.pyplot as plt + +import pyspike as spk + +# first load the data, interval ending time = 4000, start=0 (default) +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) + +print(len(spike_trains)) + +plt.figure() +isi_distance = spk.isi_distance_matrix(spike_trains) +plt.imshow(isi_distance, interpolation='none') +plt.title("ISI-distance") + +plt.figure() +spike_distance = spk.spike_distance_matrix(spike_trains) +plt.imshow(spike_distance, interpolation='none') +plt.title("SPIKE-distance") + +plt.show() diff --git a/examples/isi_matrix.py b/examples/isi_matrix.py deleted file mode 100644 index 7bf1cf9..0000000 --- a/examples/isi_matrix.py +++ /dev/null @@ -1,26 +0,0 @@ -""" isi_matrix.py - -Simple example showing how to compute the isi distance matrix of a set of spike -trains. - -Copyright 2014, Mario Mulansky - -Distributed under the BSD License -""" - - -from __future__ import print_function - -import matplotlib.pyplot as plt - -import pyspike as spk - -# first load the data, interval ending time = 4000, start=0 (default) -spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) - -print(len(spike_trains)) - -m = spk.isi_distance_matrix(spike_trains) - -plt.imshow(m, interpolation='none') -plt.show() -- cgit v1.2.3 From 7dbb5f87321fc89b6c84552cfd821f96fe364bbe Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 22 Oct 2014 18:30:17 +0200 Subject: pwc avrg now takes interval --- pyspike/function.py | 28 ++++++++++++++++++++++------ test/test_function.py | 5 +++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/pyspike/function.py b/pyspike/function.py index ed47f27..14ad7bd 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -74,15 +74,29 @@ class PieceWiseConstFunc(object): return x_plot, y_plot - def avrg(self): + def avrg(self, interval=None): """ Computes the average of the piece-wise const function: :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. :returns: the average a. :rtype: double """ - return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ - (self.x[-1]-self.x[0]) + if interval is None: + # no interval given, average over the whole spike train + return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ + (self.x[-1]-self.x[0]) + else: + start_ind = np.searchsorted(self.x, interval[0], side='right') + end_ind = np.searchsorted(self.x, interval[1], side='left')-1 + print(start_ind, end_ind) + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + a = np.sum((self.x[start_ind+1:end_ind] - + self.x[start_ind:end_ind-1]) * + self.y[start_ind:end_ind]) + a += (self.x[start_ind]-interval[0]) * self.y[start_ind] + a += (interval[1]-self.x[end_ind]) * self.y[end_ind] + return a / (interval[1]-interval[0]) def add(self, f): """ Adds another PieceWiseConst function to this function. @@ -181,15 +195,17 @@ class PieceWiseLinFunc: y_plot[1::2] = self.y2 return x_plot, y_plot - def avrg(self): + def avrg(self, interval=None): """ Computes the average of the piece-wise linear function: :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. :returns: the average a. :rtype: double """ - return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ - (self.x[-1]-self.x[0]) + if interval is None: + # no interval given, average over the whole spike train + return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ + (self.x[-1]-self.x[0]) def add(self, f): """ Adds another PieceWiseLin function to this function. diff --git a/test/test_function.py b/test/test_function.py index c856d76..cabcf44 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -29,6 +29,11 @@ def test_pwc(): assert_almost_equal(f.avrg(), (1.0-0.5+0.5*1.5+1.5*0.75)/4.0, decimal=16) + # interval averaging + a = f.avrg([0.5, 3.5]) + print(a) + assert_almost_equal(a, (0.5-0.5+0.5*1.5+1.0*0.75)/3.0, decimal=16) + def test_pwc_add(): # some random data -- cgit v1.2.3 From 11f05d9dac3711d89db37a043db3d9437958c6f3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 22 Oct 2014 22:17:27 +0200 Subject: avrg functions now take intervals --- pyspike/function.py | 51 ++++++++++++++++++++++++++++++++++++++++++++------- test/test_function.py | 19 ++++++++++++++++++- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/pyspike/function.py b/pyspike/function.py index 14ad7bd..e340096 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -83,20 +83,24 @@ class PieceWiseConstFunc(object): """ if interval is None: # no interval given, average over the whole spike train - return np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ + a = np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ (self.x[-1]-self.x[0]) else: + # find the indices corresponding to the interval start_ind = np.searchsorted(self.x, interval[0], side='right') end_ind = np.searchsorted(self.x, interval[1], side='left')-1 - print(start_ind, end_ind) assert start_ind > 0 and end_ind < len(self.x), \ "Invalid averaging interval" - a = np.sum((self.x[start_ind+1:end_ind] - - self.x[start_ind:end_ind-1]) * + # first the contribution from between the indices + a = np.sum((self.x[start_ind+1:end_ind+1] - + self.x[start_ind:end_ind]) * self.y[start_ind:end_ind]) - a += (self.x[start_ind]-interval[0]) * self.y[start_ind] + # correction from start to first index + a += (self.x[start_ind]-interval[0]) * self.y[start_ind-1] + # correction from last index to end a += (interval[1]-self.x[end_ind]) * self.y[end_ind] - return a / (interval[1]-interval[0]) + a /= (interval[1]-interval[0]) + return a def add(self, f): """ Adds another PieceWiseConst function to this function. @@ -202,10 +206,43 @@ class PieceWiseLinFunc: :returns: the average a. :rtype: double """ + + def intermediate_value(x0, x1, y0, y1, x): + """ computes the intermediate value of a linear function """ + return y0 + (y1-y0)*(x-x0)/(x1-x0) + if interval is None: # no interval given, average over the whole spike train - return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ + a = np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ (self.x[-1]-self.x[0]) + else: + # find the indices corresponding to the interval + start_ind = np.searchsorted(self.x, interval[0], side='right') + end_ind = np.searchsorted(self.x, interval[1], side='left')-1 + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + # first the contribution from between the indices + a = np.sum((self.x[start_ind+1:end_ind+1] - + self.x[start_ind:end_ind]) * + 0.5*(self.y1[start_ind:end_ind] + + self.y2[start_ind:end_ind])) + # correction from start to first index + a += (self.x[start_ind]-interval[0]) * 0.5 * \ + (self.y2[start_ind-1] + + intermediate_value(self.x[start_ind-1], self.x[start_ind], + self.y1[start_ind-1], + self.y2[start_ind-1], + interval[0] + )) + # correction from last index to end + a += (interval[1]-self.x[end_ind]) * 0.5 * \ + (self.y1[end_ind] + + intermediate_value(self.x[end_ind], self.x[end_ind+1], + self.y1[end_ind], self.y2[end_ind], + interval[1] + )) + a /= (interval[1]-interval[0]) + return a def add(self, f): """ Adds another PieceWiseLin function to this function. diff --git a/test/test_function.py b/test/test_function.py index cabcf44..ed0b2ed 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -31,8 +31,13 @@ def test_pwc(): # interval averaging a = f.avrg([0.5, 3.5]) - print(a) assert_almost_equal(a, (0.5-0.5+0.5*1.5+1.0*0.75)/3.0, decimal=16) + a = f.avrg([1.5, 3.5]) + assert_almost_equal(a, (-0.5*0.5+0.5*1.5+1.0*0.75)/2.0, decimal=16) + a = f.avrg([1.0, 3.5]) + assert_almost_equal(a, (-0.5*1.0+0.5*1.5+1.0*0.75)/2.5, decimal=16) + a = f.avrg([1.0, 4.0]) + assert_almost_equal(a, (-0.5*1.0+0.5*1.5+1.5*0.75)/3.0, decimal=16) def test_pwc_add(): @@ -105,6 +110,18 @@ def test_pwl(): avrg_expected = (1.25 - 0.45 + 0.75 + 1.5*0.5) / 4.0 assert_almost_equal(f.avrg(), avrg_expected, decimal=16) + # interval averaging + a = f.avrg([0.5, 2.5]) + assert_almost_equal(a, (1.375*0.5 - 0.45 + 0.75)/2.0, decimal=16) + a = f.avrg([1.5, 3.5]) + assert_almost_equal(a, (-0.425*0.5 + 0.75 + (0.75+0.75-0.5/1.5)/2) / 2.0, + decimal=16) + a = f.avrg([1.0, 3.5]) + assert_almost_equal(a, (-0.45 + 0.75 + (0.75+0.75-0.5/1.5)/2) / 2.5, + decimal=16) + a = f.avrg([1.0, 4.0]) + assert_almost_equal(a, (-0.45 + 0.75 + 1.5*0.5) / 3.0, decimal=16) + def test_pwl_add(): x = [0.0, 1.0, 2.0, 2.5, 4.0] -- cgit v1.2.3 From 316f3650a469a5e487790df142f59dcf1efac609 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 22 Oct 2014 22:34:41 +0200 Subject: docs for interval averaging, fixed doc typos --- pyspike/distances.py | 10 ++++++---- pyspike/function.py | 14 +++++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index b0af24c..ae564d2 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -79,7 +79,7 @@ def spike_profile(spikes1, spikes2): :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. - :returns: The spike-distance profile :math:`S_{spike}(t). + :returns: The spike-distance profile :math:`S_{spike}(t)`. :rtype: :class:`pyspike.function.PieceWiseLinFunc` """ @@ -110,7 +110,7 @@ Falling back to slow python backend.") def spike_distance(spikes1, spikes2): """ Computes the spike-distance S of the given spike trains. The spike-distance is the integral over the isi distance profile S_spike(t): - S = \int_^T S_spike(t) dt. + :math:`S = \int_^T S_spike(t) dt`. :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. @@ -131,7 +131,7 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): Computes the multi-variate distance for a set of spike-trains using the pair_dist_func to compute pair-wise distances. That is it computes the average distance of all pairs of spike-trains: - S(t) = 2/((N(N-1)) sum_{} S_{i,j}, + :math:`S(t) = 2/((N(N-1)) sum_{} S_{i,j}`, where the sum goes over all pairs . Args: - spike_trains: list of spike trains @@ -254,14 +254,16 @@ def isi_distance_multi(spike_trains, indices=None): def spike_profile_multi(spike_trains, indices=None): """ Computes the multi-variate spike distance profile for a set of spike trains. That is the average spike-distance of all pairs of spike-trains: - S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}, + :math:`S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}`, where the sum goes over all pairs + :param spike_trains: list of spike trains :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None :returns: The averaged spike profile :math:`(t)` :rtype: :class:`pyspike.function.PieceWiseLinFunc` + """ return _generic_profile_multi(spike_trains, spike_profile, indices) diff --git a/pyspike/function.py b/pyspike/function.py index e340096..d53042f 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -42,7 +42,7 @@ class PieceWiseConstFunc(object): """ Checks if the function is equal to another function up to `decimal` precision. - :param: other: another :class:`PieceWiseConstFunc` + :param other: another :class:`PieceWiseConstFunc` :returns: True if the two functions are equal up to `decimal` decimals, False otherwise :rtype: bool @@ -78,8 +78,12 @@ class PieceWiseConstFunc(object): """ Computes the average of the piece-wise const function: :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: the average a. :rtype: double + """ if interval is None: # no interval given, average over the whole spike train @@ -160,7 +164,7 @@ class PieceWiseLinFunc: def copy(self): """ Returns a copy of itself - :rtype: :class`PieceWiseLinFunc` + :rtype: :class:`PieceWiseLinFunc` """ return PieceWiseLinFunc(self.x, self.y1, self.y2) @@ -168,7 +172,7 @@ class PieceWiseLinFunc: """ Checks if the function is equal to another function up to `decimal` precision. - :param: other: another :class:`PieceWiseLinFunc` + :param other: another :class:`PieceWiseLinFunc` :returns: True if the two functions are equal up to `decimal` decimals, False otherwise :rtype: bool @@ -203,8 +207,12 @@ class PieceWiseLinFunc: """ Computes the average of the piece-wise linear function: :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: the average a. :rtype: double + """ def intermediate_value(x0, x1, y0, y1, x): -- cgit v1.2.3 From 239cde5c6712f5b8ab6435e24a2aa901c3f350ce Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 22 Oct 2014 22:45:28 +0200 Subject: added interval averaging to distance functions --- pyspike/distances.py | 50 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index ae564d2..5135b9b 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -52,7 +52,7 @@ Falling back to slow python backend.") ############################################################ # isi_distance ############################################################ -def isi_distance(spikes1, spikes2): +def isi_distance(spikes1, spikes2, interval=None): """ Computes the isi-distance I of the given spike trains. The isi-distance is the integral over the isi distance profile :math:`S_{isi}(t)`: @@ -61,10 +61,14 @@ def isi_distance(spikes1, spikes2): :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The isi-distance I. :rtype: double """ - return isi_profile(spikes1, spikes2).avrg() + return isi_profile(spikes1, spikes2).avrg(interval) ############################################################ @@ -107,18 +111,21 @@ Falling back to slow python backend.") ############################################################ # spike_distance ############################################################ -def spike_distance(spikes1, spikes2): +def spike_distance(spikes1, spikes2, interval=None): """ Computes the spike-distance S of the given spike trains. The spike-distance is the integral over the isi distance profile S_spike(t): :math:`S = \int_^T S_spike(t) dt`. :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: The spike-distance. :rtype: double """ - return spike_profile(spikes1, spikes2).avrg() + return spike_profile(spikes1, spikes2).avrg(interval) ############################################################ @@ -233,7 +240,7 @@ def isi_profile_multi(spike_trains, indices=None): ############################################################ # isi_distance_multi ############################################################ -def isi_distance_multi(spike_trains, indices=None): +def isi_distance_multi(spike_trains, indices=None, interval=None): """ computes the multi-variate isi-distance for a set of spike-trains. That is the time average of the multi-variate spike profile: I = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, @@ -242,10 +249,13 @@ def isi_distance_multi(spike_trains, indices=None): :param spike_trains: list of spike trains :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: The time-averaged isi distance :math:`I` :rtype: double """ - return isi_profile_multi(spike_trains, indices).avrg() + return isi_profile_multi(spike_trains, indices).avrg(interval) ############################################################ @@ -271,7 +281,7 @@ def spike_profile_multi(spike_trains, indices=None): ############################################################ # spike_distance_multi ############################################################ -def spike_distance_multi(spike_trains, indices=None): +def spike_distance_multi(spike_trains, indices=None, interval=None): """ Computes the multi-variate spike distance for a set of spike trains. That is the time average of the multi-variate spike profile: S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt @@ -281,16 +291,20 @@ def spike_distance_multi(spike_trains, indices=None): :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: The averaged spike distance S. :rtype: double """ - return spike_profile_multi(spike_trains, indices).avrg() + return spike_profile_multi(spike_trains, indices).avrg(interval) ############################################################ # generic_distance_matrix ############################################################ -def _generic_distance_matrix(spike_trains, dist_function, indices=None): +def _generic_distance_matrix(spike_trains, dist_function, + indices=None, interval=None): """ Internal implementation detail. Don't use this function directly. Instead use isi_distance_matrix or spike_distance_matrix. Computes the time averaged distance of all pairs of spike-trains. @@ -313,7 +327,7 @@ def _generic_distance_matrix(spike_trains, dist_function, indices=None): distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: - d = dist_function(spike_trains[i], spike_trains[j]) + d = dist_function(spike_trains[i], spike_trains[j], interval) distance_matrix[i, j] = d distance_matrix[j, i] = d return distance_matrix @@ -322,32 +336,40 @@ def _generic_distance_matrix(spike_trains, dist_function, indices=None): ############################################################ # isi_distance_matrix ############################################################ -def isi_distance_matrix(spike_trains, indices=None): +def isi_distance_matrix(spike_trains, indices=None, interval=None): """ Computes the time averaged isi-distance of all pairs of spike-trains. :param spike_trains: list of spike trains :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: 2D array with the pair wise time average isi distances :math:`I_{ij}` :rtype: np.array """ - return _generic_distance_matrix(spike_trains, isi_distance, indices) + return _generic_distance_matrix(spike_trains, isi_distance, + indices, interval) ############################################################ # spike_distance_matrix ############################################################ -def spike_distance_matrix(spike_trains, indices=None): +def spike_distance_matrix(spike_trains, indices=None, interval=None): """ Computes the time averaged spike-distance of all pairs of spike-trains. :param spike_trains: list of spike trains :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. :returns: 2D array with the pair wise time average spike distances :math:`S_{ij}` :rtype: np.array """ - return _generic_distance_matrix(spike_trains, spike_distance, indices) + return _generic_distance_matrix(spike_trains, spike_distance, + indices, interval) -- cgit v1.2.3 From 4a295e6045abc7564a2e72d1a2173bf2b04c5950 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Oct 2014 12:46:55 +0200 Subject: docs: added interval averaging explanations --- Readme.rst | 38 ++++++++++++++++++++++++++++---------- examples/distance_matrix.py | 4 ++-- examples/plot.py | 3 ++- pyspike/distances.py | 14 +++++++------- 4 files changed, 39 insertions(+), 20 deletions(-) diff --git a/Readme.rst b/Readme.rst index 4482d01..c6ded74 100644 --- a/Readme.rst +++ b/Readme.rst @@ -87,8 +87,8 @@ Both the ISI and the SPIKE distance computation require the presence of auxiliar # if you provide only a single value, it is interpreted as T_end, while T_start=0 spike_train = spk.add_auxiliary_spikes(spike_train, T_end) -Computing bi-variate distances ------------------------------- +Computing bi-variate distances profiles +--------------------------------------- **Important note:** @@ -124,12 +124,25 @@ The following code loads some exemplary spike trains, computes the dissimilarity plt.show() The ISI-profile is a piece-wise constant function, there the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. -As shown above, this class allows you to obtain arrays that can be used to plot the function with :code":`plt.plt`, but also to compute the average, which amounts to the final scalar ISI-distance. +As shown above, this class allows you to obtain arrays that can be used to plot the function with :code:`plt.plt`, but also to compute the time average, which amounts to the final scalar ISI-distance. +By default, the time average is computed for the whole :code:`PieceWiseConstFunc` function. +However, it is also possible to obtain the average of some interval by providing a pair of floats defining the start and end of the interval. +In the above example, the following code computes the ISI-distances obtained from averaging the ISI-profile over four different intervals: + +.. code:: python + + isi1 = isi_profil.avrg(interval=(0,1000)) + isi2 = isi_profil.avrg(interval=(1000,2000)) + isi3 = isi_profil.avrg(interval=(2000,3000)) + isi4 = isi_profil.avrg(interval=(3000,4000)) + If you are only interested in the scalar ISI-distance and not the profile, you can simly use: .. code:: python - isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1]) + isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1], interval) + +where :code:`interval` is optional, as above, and if omitted the ISI-distance is computed for the complete spike trains. Furthermore, PySpike provides the :code:`average_profile` function that can be used to compute the average profile of a list of given :code:`PieceWiseConstFunc` instances. @@ -176,14 +189,16 @@ This short example computes and plots the SPIKE-profile of the first two spike t In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and thusly represented by a :code:`PieceWiseLinFunc` object. Just like the :code:`PieceWiseconstFunc` for the ISI-profile, the :code:`PieceWiseLinFunc` provides a :code:`get_plottable_data` member function that returns array that can be used directly to plot the function. Furthermore, the :code:`avrg` member function returns the average of the profile defined as the overall SPIKE distance. +As above, you can provide an interval as a pair of floats to :code:`avrg` to specify the averaging interval if required. Again, you can use .. code:: python - spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1]) + spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval) to compute the SPIKE distance directly, if you are not interested in the profile at all. +:code:`interval` is optional and defines the averaging interval, if neglected the whole spike train is used. Furthmore, you can use the :code:`average_profile` function to compute an average profile of a list of SPIKE-profiles: .. code:: python @@ -195,7 +210,7 @@ Furthmore, you can use the :code:`average_profile` function to compute an averag Computing multi-variate profiles and distances ---------------------------------- +---------------------------------------------- To compute the multi-variate ISI- or SPIKE-profile of a set of spike trains, you can compute all bi-variate profiles separately and then use the :code:`average_profile` function above. However, PySpike provides convenience functions for that purpose. @@ -210,11 +225,12 @@ The following example computes the multivariate ISI- and SPIKE-profile for a lis Both functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multi-variate profile. As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi` and :code:`spike_distance_multi`, that return the scalar multi-variate ISI- and SPIKE-distance. +Both distance functions also accept an :code:`interval` parameter that can be used to specify the averaging interval as a pair of floats, if neglected the complete interval is used. Another option to address large sets of spike trains are distance matrices. Each entry in the distance matrix represents a bi-variate distance of the spike trains. Hence, the distance matrix is symmetric and has zero values at the diagonal. -The following example computes and plots the ISI- and SPIKE-distance matrix. +The following example computes and plots the ISI- and SPIKE-distance matrix, where for the latter one only the time interval T=0..1000 is used for the averaging. .. code:: python @@ -226,15 +242,17 @@ The following example computes and plots the ISI- and SPIKE-distance matrix. plt.title("ISI-distance") plt.figure() - spike_distance = spk.spike_distance_matrix(spike_trains) + spike_distance = spk.spike_distance_matrix(spike_trains, interval=(0,1000)) plt.imshow(spike_distance, interpolation='none') plt.title("SPIKE-distance") plt.show() -Averaging ---------- +Time Averages +------------- + + .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance diff --git a/examples/distance_matrix.py b/examples/distance_matrix.py index 38bd9c8..142db2c 100644 --- a/examples/distance_matrix.py +++ b/examples/distance_matrix.py @@ -26,8 +26,8 @@ plt.imshow(isi_distance, interpolation='none') plt.title("ISI-distance") plt.figure() -spike_distance = spk.spike_distance_matrix(spike_trains) +spike_distance = spk.spike_distance_matrix(spike_trains, interval=(0, 1000)) plt.imshow(spike_distance, interpolation='none') -plt.title("SPIKE-distance") +plt.title("SPIKE-distance, T=0-1000") plt.show() diff --git a/examples/plot.py b/examples/plot.py index 59334c9..d32c464 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -1,6 +1,7 @@ """ plot.py -Simple example showing how to load and plot spike trains and their distances. +Simple example showing how to load and plot spike trains and their distance +profiles. Copyright 2014, Mario Mulansky diff --git a/pyspike/distances.py b/pyspike/distances.py index 5135b9b..34f7d78 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -57,14 +57,13 @@ def isi_distance(spikes1, spikes2, interval=None): isi-distance is the integral over the isi distance profile :math:`S_{isi}(t)`: - .. math:: I = \int_0^T S_{isi}(t) dt. + .. math:: I = \int_{T_0}^{T_1} S_{isi}(t) dt. :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. + :param interval: averaging interval given as a pair of floats (T0, T1), + if None the average over the whole function is computed. :type interval: Pair of floats or None. - :returns: The isi-distance I. :rtype: double """ @@ -114,12 +113,13 @@ Falling back to slow python backend.") def spike_distance(spikes1, spikes2, interval=None): """ Computes the spike-distance S of the given spike trains. The spike-distance is the integral over the isi distance profile S_spike(t): - :math:`S = \int_^T S_spike(t) dt`. + + .. math:: S = \int_{T_0}^{T_1} S_{spike}(t) dt. :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. + :param interval: averaging interval given as a pair of floats (T0, T1), + if None the average over the whole function is computed. :type interval: Pair of floats or None. :returns: The spike-distance. :rtype: double -- cgit v1.2.3 From ac8e9ca85a889ee2d9fe984d19976917ffdfea46 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Oct 2014 12:48:19 +0200 Subject: +averaging example, removed empty averaging sec from docs --- Readme.rst | 5 ----- examples/averages.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 examples/averages.py diff --git a/Readme.rst b/Readme.rst index c6ded74..8228d7f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -249,11 +249,6 @@ The following example computes and plots the ISI- and SPIKE-distance matrix, whe plt.show() -Time Averages -------------- - - - .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance .. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance diff --git a/examples/averages.py b/examples/averages.py new file mode 100644 index 0000000..a06871a --- /dev/null +++ b/examples/averages.py @@ -0,0 +1,42 @@ +""" averages.py + +Simple example showing how to compute averages of distance profiles + +Copyright 2014, Mario Mulansky + +Distributed under the BSD License +""" + +from __future__ import print_function + +import pyspike as spk + +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + +f = spk.isi_profile(spike_trains[0], spike_trains[1]) + +print("ISI-distance: %.8f" % f.avrg()) + +isi1 = f.avrg(interval=(0, 1000)) +isi2 = f.avrg(interval=(1000, 2000)) +isi3 = f.avrg(interval=(2000, 3000)) +isi4 = f.avrg(interval=(3000, 4000)) + +print("ISI-distance (0-1000): %.8f" % isi1) +print("ISI-distance (1000-2000): %.8f" % isi2) +print("ISI-distance (2000-3000): %.8f" % isi3) +print("ISI-distance (3000-4000): %.8f" % isi4) +print() + +print("SPIKE-distance: %.8f" % f.avrg()) + +spike1 = f.avrg(interval=(0, 1000)) +spike2 = f.avrg(interval=(1000, 2000)) +spike3 = f.avrg(interval=(2000, 3000)) +spike4 = f.avrg(interval=(3000, 4000)) + +print("SPIKE-distance (0-1000): %.8f" % spike1) +print("SPIKE-distance (1000-2000): %.8f" % spike2) +print("SPIKE-distance (2000-3000): %.8f" % spike3) +print("SPIKE-distance (3000-4000): %.8f" % spike4) -- cgit v1.2.3 From 3a36f81d52137435910a4b7c656a478ae5b38ece Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Oct 2014 15:33:06 +0200 Subject: support for multiple averaging intervals --- examples/averages.py | 26 ++++----- pyspike/function.py | 144 ++++++++++++++++++++++++++++++++++++-------------- test/test_function.py | 10 +++- 3 files changed, 126 insertions(+), 54 deletions(-) diff --git a/examples/averages.py b/examples/averages.py index a06871a..6413ab2 100644 --- a/examples/averages.py +++ b/examples/averages.py @@ -20,23 +20,25 @@ print("ISI-distance: %.8f" % f.avrg()) isi1 = f.avrg(interval=(0, 1000)) isi2 = f.avrg(interval=(1000, 2000)) -isi3 = f.avrg(interval=(2000, 3000)) -isi4 = f.avrg(interval=(3000, 4000)) +isi3 = f.avrg(interval=[(0, 1000), (2000, 3000)]) +isi4 = f.avrg(interval=[(1000, 2000), (3000, 4000)]) -print("ISI-distance (0-1000): %.8f" % isi1) -print("ISI-distance (1000-2000): %.8f" % isi2) -print("ISI-distance (2000-3000): %.8f" % isi3) -print("ISI-distance (3000-4000): %.8f" % isi4) +print("ISI-distance (0-1000): %.8f" % isi1) +print("ISI-distance (1000-2000): %.8f" % isi2) +print("ISI-distance (0-1000) and (2000-3000): %.8f" % isi3) +print("ISI-distance (1000-2000) and (3000-4000): %.8f" % isi4) print() +f = spk.spike_profile(spike_trains[0], spike_trains[1]) + print("SPIKE-distance: %.8f" % f.avrg()) spike1 = f.avrg(interval=(0, 1000)) spike2 = f.avrg(interval=(1000, 2000)) -spike3 = f.avrg(interval=(2000, 3000)) -spike4 = f.avrg(interval=(3000, 4000)) +spike3 = f.avrg(interval=[(0, 1000), (2000, 3000)]) +spike4 = f.avrg(interval=[(1000, 2000), (3000, 4000)]) -print("SPIKE-distance (0-1000): %.8f" % spike1) -print("SPIKE-distance (1000-2000): %.8f" % spike2) -print("SPIKE-distance (2000-3000): %.8f" % spike3) -print("SPIKE-distance (3000-4000): %.8f" % spike4) +print("SPIKE-distance (0-1000): %.8f" % spike1) +print("SPIKE-distance (1000-2000): %.8f" % spike2) +print("SPIKE-distance (0-1000) and (2000-3000): %.8f" % spike3) +print("SPIKE-distance (1000-2000) and (3000-4000): %.8f" % spike4) diff --git a/pyspike/function.py b/pyspike/function.py index d53042f..0c0a391 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -11,6 +11,7 @@ Distributed under the BSD License from __future__ import print_function import numpy as np +import collections ############################################################## @@ -74,21 +75,18 @@ class PieceWiseConstFunc(object): return x_plot, y_plot - def avrg(self, interval=None): - """ Computes the average of the piece-wise const function: - :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + def integral(self, interval=None): + """ Returns the integral over the given interval. - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. + :param interval: integration interval given as a pair of floats, if + None the integral over the whole function is computed. :type interval: Pair of floats or None. - :returns: the average a. - :rtype: double - + :returns: the integral + :rtype: float """ if interval is None: - # no interval given, average over the whole spike train - a = np.sum((self.x[1:]-self.x[:-1]) * self.y) / \ - (self.x[-1]-self.x[0]) + # no interval given, integrate over the whole spike train + a = np.sum((self.x[1:]-self.x[:-1]) * self.y) else: # find the indices corresponding to the interval start_ind = np.searchsorted(self.x, interval[0], side='right') @@ -103,7 +101,39 @@ class PieceWiseConstFunc(object): a += (self.x[start_ind]-interval[0]) * self.y[start_ind-1] # correction from last index to end a += (interval[1]-self.x[end_ind]) * self.y[end_ind] - a /= (interval[1]-interval[0]) + return a + + def avrg(self, interval=None): + """ Computes the average of the piece-wise const function: + :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + """ + if interval is None: + # no interval given, average over the whole spike train + return self.integral() / (self.x[-1]-self.x[0]) + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): + # just one interval + a = self.integral(interval) / (interval[1]-interval[0]) + else: + # several intervals + a = 0.0 + int_length = 0.0 + for ival in interval: + a += self.integral(ival) + int_length += ival[1] - ival[0] + a /= int_length return a def add(self, f): @@ -203,16 +233,14 @@ class PieceWiseLinFunc: y_plot[1::2] = self.y2 return x_plot, y_plot - def avrg(self, interval=None): - """ Computes the average of the piece-wise linear function: - :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + def integral(self, interval=None): + """ Returns the integral over the given interval. - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. + :param interval: integration interval given as a pair of floats, if + None the integral over the whole function is computed. :type interval: Pair of floats or None. - :returns: the average a. - :rtype: double - + :returns: the integral + :rtype: float """ def intermediate_value(x0, x1, y0, y1, x): @@ -220,9 +248,8 @@ class PieceWiseLinFunc: return y0 + (y1-y0)*(x-x0)/(x1-x0) if interval is None: - # no interval given, average over the whole spike train - a = np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) / \ - (self.x[-1]-self.x[0]) + # no interval given, integrate over the whole spike train + integral = np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) else: # find the indices corresponding to the interval start_ind = np.searchsorted(self.x, interval[0], side='right') @@ -230,26 +257,61 @@ class PieceWiseLinFunc: assert start_ind > 0 and end_ind < len(self.x), \ "Invalid averaging interval" # first the contribution from between the indices - a = np.sum((self.x[start_ind+1:end_ind+1] - - self.x[start_ind:end_ind]) * - 0.5*(self.y1[start_ind:end_ind] + - self.y2[start_ind:end_ind])) + integral = np.sum((self.x[start_ind+1:end_ind+1] - + self.x[start_ind:end_ind]) * + 0.5*(self.y1[start_ind:end_ind] + + self.y2[start_ind:end_ind])) # correction from start to first index - a += (self.x[start_ind]-interval[0]) * 0.5 * \ - (self.y2[start_ind-1] + - intermediate_value(self.x[start_ind-1], self.x[start_ind], - self.y1[start_ind-1], - self.y2[start_ind-1], - interval[0] - )) + integral += (self.x[start_ind]-interval[0]) * 0.5 * \ + (self.y2[start_ind-1] + + intermediate_value(self.x[start_ind-1], + self.x[start_ind], + self.y1[start_ind-1], + self.y2[start_ind-1], + interval[0] + )) # correction from last index to end - a += (interval[1]-self.x[end_ind]) * 0.5 * \ - (self.y1[end_ind] + - intermediate_value(self.x[end_ind], self.x[end_ind+1], - self.y1[end_ind], self.y2[end_ind], - interval[1] - )) - a /= (interval[1]-interval[0]) + integral += (interval[1]-self.x[end_ind]) * 0.5 * \ + (self.y1[end_ind] + + intermediate_value(self.x[end_ind], self.x[end_ind+1], + self.y1[end_ind], self.y2[end_ind], + interval[1] + )) + return integral + + def avrg(self, interval=None): + """ Computes the average of the piece-wise linear function: + :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + + """ + + if interval is None: + # no interval given, average over the whole spike train + return self.integral() / (self.x[-1]-self.x[0]) + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): + # just one interval + a = self.integral(interval) / (interval[1]-interval[0]) + else: + # several intervals + a = 0.0 + int_length = 0.0 + for ival in interval: + a += self.integral(ival) + int_length += ival[1] - ival[0] + a /= int_length return a def add(self, f): diff --git a/test/test_function.py b/test/test_function.py index ed0b2ed..ba87db8 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -39,6 +39,10 @@ def test_pwc(): a = f.avrg([1.0, 4.0]) assert_almost_equal(a, (-0.5*1.0+0.5*1.5+1.5*0.75)/3.0, decimal=16) + # averaging over multiple intervals + a = f.avrg([(0.5, 1.5), (1.5, 3.5)]) + assert_almost_equal(a, (0.5-0.5+0.5*1.5+1.0*0.75)/3.0, decimal=16) + def test_pwc_add(): # some random data @@ -116,12 +120,16 @@ def test_pwl(): a = f.avrg([1.5, 3.5]) assert_almost_equal(a, (-0.425*0.5 + 0.75 + (0.75+0.75-0.5/1.5)/2) / 2.0, decimal=16) - a = f.avrg([1.0, 3.5]) + a = f.avrg((1.0, 3.5)) assert_almost_equal(a, (-0.45 + 0.75 + (0.75+0.75-0.5/1.5)/2) / 2.5, decimal=16) a = f.avrg([1.0, 4.0]) assert_almost_equal(a, (-0.45 + 0.75 + 1.5*0.5) / 3.0, decimal=16) + # averaging over multiple intervals + a = f.avrg([(0.5, 1.5), (1.5, 2.5)]) + assert_almost_equal(a, (1.375*0.5 - 0.45 + 0.75)/2.0, decimal=16) + def test_pwl_add(): x = [0.0, 1.0, 2.0, 2.5, 4.0] -- cgit v1.2.3 From 21e5e634ddb2c6939b550e41ec4003db9ef5d37a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Oct 2014 15:54:37 +0200 Subject: note in docs about multiple averaging intervals --- Readme.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Readme.rst b/Readme.rst index 8228d7f..fb0053a 100644 --- a/Readme.rst +++ b/Readme.rst @@ -131,10 +131,12 @@ In the above example, the following code computes the ISI-distances obtained fro .. code:: python - isi1 = isi_profil.avrg(interval=(0,1000)) - isi2 = isi_profil.avrg(interval=(1000,2000)) - isi3 = isi_profil.avrg(interval=(2000,3000)) - isi4 = isi_profil.avrg(interval=(3000,4000)) + isi1 = isi_profil.avrg(interval=(0, 1000)) + isi2 = isi_profil.avrg(interval=(1000, 2000)) + isi3 = isi_profil.avrg(interval=[(0, 1000), (2000, 3000)]) + isi4 = isi_profil.avrg(interval=[(1000, 2000), (3000, 4000)]) + +Note, how also multiple intervals can be supplied by giving a list of tuples. If you are only interested in the scalar ISI-distance and not the profile, you can simly use: @@ -189,7 +191,7 @@ This short example computes and plots the SPIKE-profile of the first two spike t In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and thusly represented by a :code:`PieceWiseLinFunc` object. Just like the :code:`PieceWiseconstFunc` for the ISI-profile, the :code:`PieceWiseLinFunc` provides a :code:`get_plottable_data` member function that returns array that can be used directly to plot the function. Furthermore, the :code:`avrg` member function returns the average of the profile defined as the overall SPIKE distance. -As above, you can provide an interval as a pair of floats to :code:`avrg` to specify the averaging interval if required. +As above, you can provide an interval as a pair of floats as well as a sequence of such pairs to :code:`avrg` to specify the averaging interval if required. Again, you can use @@ -198,7 +200,7 @@ Again, you can use spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval) to compute the SPIKE distance directly, if you are not interested in the profile at all. -:code:`interval` is optional and defines the averaging interval, if neglected the whole spike train is used. +:code:`interval` is optional and if neglected the whole spike train is used. Furthmore, you can use the :code:`average_profile` function to compute an average profile of a list of SPIKE-profiles: .. code:: python -- cgit v1.2.3 From 8ba04b660dc3213ab6e5f1e839558d9fc692660d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Oct 2014 10:27:26 +0200 Subject: Thomas' feedback --- Readme.rst | 53 +++++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/Readme.rst b/Readme.rst index fb0053a..9eb6b29 100644 --- a/Readme.rst +++ b/Readme.rst @@ -5,8 +5,8 @@ PySpike :target: https://travis-ci.org/mariomulansky/PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the bivariate ISI_ [#]_ and SPIKE_ [#]_. -Additionally, it provides functions to compute multi-variate SPIKE and ISI distances, as well as averaging and general spike train processing. +Its core functionality is the implementation of the bivariate ISI_ [#]_ and SPIKE_ [#]_ distance. +Additionally, it provides functions to compute multivariate SPIKE and ISI distances, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). All source codes are published under the BSD_License_. @@ -26,7 +26,7 @@ To use PySpike you need Python installed with the following additional packages: - cython - nosetests (for running the tests) -In particular, make sure that cython_ is configured properly and able to locate a C compiler, otherwise you will only be able to use the much slower plain Python implementations. +In particular, make sure that cython_ is configured properly and able to locate a C compiler, otherwise PySpike will use the much slower Python implementations. To install PySpike, simply download the source, e.g. from Github, and run the :code:`setup.py` script: @@ -73,7 +73,8 @@ To quickly obtain spike trains from such files, PySpike provides the function :c spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", time_interval=(0, 4000)) -This function expects the name of the data file as first parameter, and additionally the time intervall of the spike train measurement can be provided as a pair of start- and end-time values. +This function expects the name of the data file as first parameter. +Additionally, the time interval of the spike train measurement can be provided as a pair of start- and end-time values. If the time interval is provided (:code:`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. Furthermore, the spike trains are ordered via :code:`np.sort` (disable this feature by providing :code:`sort=False` as a parameter to the load function). As result, :code:`load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. @@ -87,17 +88,17 @@ Both the ISI and the SPIKE distance computation require the presence of auxiliar # if you provide only a single value, it is interpreted as T_end, while T_start=0 spike_train = spk.add_auxiliary_spikes(spike_train, T_end) -Computing bi-variate distances profiles +Computing bivariate distances profiles --------------------------------------- **Important note:** ------------------------------ - Spike trains are expected to be *ordered sequences*! - For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed ordered. - Make sure that all your spike trains are ordered. - If in doubt, use :code:`spike_train = np.sort(spike_train)` to obtain a correctly ordered spike train. + Spike trains are expected to be *sorted*! + For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed sorted. + Make sure that all your spike trains are sorted. + If in doubt, use :code:`spike_train = np.sort(spike_train)` to obtain a correctly sorted spike train. Furthermore, the spike trains should have auxiliary spikes at the beginning and end of the observation interval. You can ensure this by providing the :code:`time_interval` in the :code:`load_spike_trains_from_txt` function, or calling :code:`add_auxiliary_spikes` for your spike trains. @@ -123,10 +124,10 @@ The following code loads some exemplary spike trains, computes the dissimilarity print("ISI distance: %.8f" % isi_profil.avrg()) plt.show() -The ISI-profile is a piece-wise constant function, there the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. +The ISI-profile is a piece-wise constant function, and hence the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. As shown above, this class allows you to obtain arrays that can be used to plot the function with :code:`plt.plt`, but also to compute the time average, which amounts to the final scalar ISI-distance. By default, the time average is computed for the whole :code:`PieceWiseConstFunc` function. -However, it is also possible to obtain the average of some interval by providing a pair of floats defining the start and end of the interval. +However, it is also possible to obtain the average of a specific interval by providing a pair of floats defining the start and end of the interval. In the above example, the following code computes the ISI-distances obtained from averaging the ISI-profile over four different intervals: .. code:: python @@ -138,7 +139,7 @@ In the above example, the following code computes the ISI-distances obtained fro Note, how also multiple intervals can be supplied by giving a list of tuples. -If you are only interested in the scalar ISI-distance and not the profile, you can simly use: +If you are only interested in the scalar ISI-distance and not the profile, you can simply use: .. code:: python @@ -165,13 +166,13 @@ So to obtain overall average ISI-distance of a list of ISI profiles you can firs avrg_isi = avrg_profile.avrg() -to obtain the final, scalar average ISI distance of the whole set (see also "Computing multi-variate distance" below). +to obtain the final, scalar average ISI distance of the whole set (see also "Computing multivariate distance" below). SPIKE-distance .............. -To computation for the spike distance you use the function :code:`spike_profile` instead of :code:`isi_profile` above. +To compute for the spike distance you use the function :code:`spike_profile` instead of :code:`isi_profile` above. But the general approach is very similar: .. code:: python @@ -188,8 +189,8 @@ But the general approach is very similar: plt.show() This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. -In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and thusly represented by a :code:`PieceWiseLinFunc` object. -Just like the :code:`PieceWiseconstFunc` for the ISI-profile, the :code:`PieceWiseLinFunc` provides a :code:`get_plottable_data` member function that returns array that can be used directly to plot the function. +In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and is therefore represented by a :code:`PieceWiseLinFunc` object. +Just like the :code:`PieceWiseConstFunc` for the ISI-profile, the :code:`PieceWiseLinFunc` provides a :code:`get_plottable_data` member function that returns arrays that can be used directly to plot the function. Furthermore, the :code:`avrg` member function returns the average of the profile defined as the overall SPIKE distance. As above, you can provide an interval as a pair of floats as well as a sequence of such pairs to :code:`avrg` to specify the averaging interval if required. @@ -200,7 +201,7 @@ Again, you can use spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval) to compute the SPIKE distance directly, if you are not interested in the profile at all. -:code:`interval` is optional and if neglected the whole spike train is used. +The parameter :code:`interval` is optional and if neglected the whole spike train is used. Furthmore, you can use the :code:`average_profile` function to compute an average profile of a list of SPIKE-profiles: .. code:: python @@ -211,10 +212,10 @@ Furthmore, you can use the :code:`average_profile` function to compute an averag plt.plot(x, y, label="Average SPIKE profile") -Computing multi-variate profiles and distances +Computing multivariate profiles and distances ---------------------------------------------- -To compute the multi-variate ISI- or SPIKE-profile of a set of spike trains, you can compute all bi-variate profiles separately and then use the :code:`average_profile` function above. +To compute the multivariate ISI- or SPIKE-profile of a set of spike trains, you can compute all bivariate profiles separately and then use the :code:`average_profile` function above. However, PySpike provides convenience functions for that purpose. The following example computes the multivariate ISI- and SPIKE-profile for a list of spike trains: @@ -225,14 +226,14 @@ The following example computes the multivariate ISI- and SPIKE-profile for a lis avrg_isi_profile = spk.isi_profile_multi(spike_trains) avrg_spike_profile = spk.spike_profile_multi(spike_trains) -Both functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multi-variate profile. -As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi` and :code:`spike_distance_multi`, that return the scalar multi-variate ISI- and SPIKE-distance. -Both distance functions also accept an :code:`interval` parameter that can be used to specify the averaging interval as a pair of floats, if neglected the complete interval is used. +Both functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. +As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi` and :code:`spike_distance_multi`, that return the scalar multivariate ISI- and SPIKE-distance. +Both distance functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. -Another option to address large sets of spike trains are distance matrices. -Each entry in the distance matrix represents a bi-variate distance of the spike trains. -Hence, the distance matrix is symmetric and has zero values at the diagonal. -The following example computes and plots the ISI- and SPIKE-distance matrix, where for the latter one only the time interval T=0..1000 is used for the averaging. +Another option to characterize large sets of spike trains are distance matrices. +Each entry in the distance matrix represents a bivariate distance of two spike trains. +The distance matrix is symmetric and has zero values at the diagonal. +The following example computes and plots the ISI- and SPIKE-distance matrix, where for the latter one the averaging is performed only the time interval T=0..1000. .. code:: python -- cgit v1.2.3 From 874d980976169baf9ec01f8ff4ee1c218a447077 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Oct 2014 10:28:04 +0200 Subject: +update website script --- doc/copy_html | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100755 doc/copy_html diff --git a/doc/copy_html b/doc/copy_html new file mode 100755 index 0000000..1be8f05 --- /dev/null +++ b/doc/copy_html @@ -0,0 +1,12 @@ +#!/bin/bash + +git checkout gh-pages +rc=$? +if [[ $rc != 0 ]] ; then + exit $rc +fi +cp -r _build/html/*.html _build/html/*.js _build/html/_static ../ +echo "html files copied" +git commit -am"website update" +git push +git checkout master -- cgit v1.2.3 From 0261baf17427d854bdd7368e0a4e5ef8c0997d25 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Oct 2014 10:28:51 +0200 Subject: renamed update website script --- doc/copy_html | 12 ------------ doc/update_website | 12 ++++++++++++ 2 files changed, 12 insertions(+), 12 deletions(-) delete mode 100755 doc/copy_html create mode 100755 doc/update_website diff --git a/doc/copy_html b/doc/copy_html deleted file mode 100755 index 1be8f05..0000000 --- a/doc/copy_html +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -git checkout gh-pages -rc=$? -if [[ $rc != 0 ]] ; then - exit $rc -fi -cp -r _build/html/*.html _build/html/*.js _build/html/_static ../ -echo "html files copied" -git commit -am"website update" -git push -git checkout master diff --git a/doc/update_website b/doc/update_website new file mode 100755 index 0000000..1be8f05 --- /dev/null +++ b/doc/update_website @@ -0,0 +1,12 @@ +#!/bin/bash + +git checkout gh-pages +rc=$? +if [[ $rc != 0 ]] ; then + exit $rc +fi +cp -r _build/html/*.html _build/html/*.js _build/html/_static ../ +echo "html files copied" +git commit -am"website update" +git push +git checkout master -- cgit v1.2.3 From 7a88fb2ffa6b19059e281fb51d8ac4a1abe3c0d5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Oct 2014 10:36:59 +0200 Subject: update website script now copies also _modules --- doc/update_website | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update_website b/doc/update_website index 1be8f05..dc73f49 100755 --- a/doc/update_website +++ b/doc/update_website @@ -5,7 +5,7 @@ rc=$? if [[ $rc != 0 ]] ; then exit $rc fi -cp -r _build/html/*.html _build/html/*.js _build/html/_static ../ +cp -r _build/html/*.html _build/html/*.js _build/html/_static _build/html/_modules ../ echo "html files copied" git commit -am"website update" git push -- cgit v1.2.3 From bc9cdee56fefa62b50415ac7e1f74b8d7af4992d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Oct 2014 16:01:34 +0200 Subject: added contributions note --- Readme.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Readme.rst b/Readme.rst index 9eb6b29..265ecae 100644 --- a/Readme.rst +++ b/Readme.rst @@ -252,6 +252,20 @@ The following example computes and plots the ISI- and SPIKE-distance matrix, whe plt.show() +=============================================================================== + +`The work on PySpike was supported by the European Comission through the Marie +Curie Initial Training Network 'Neural Engineering Transformative Technologies +(NETT)' under the project number 289146.` + + +**Python/C Programming:** + - Mario Mulansky + +**Scientific Methods:** + - Thomas Kreuz + - Nebojsa D. Bozanic + .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance .. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance -- cgit v1.2.3 From 8b5b287a79f3b42ddfefdf30ecf435f77306cfb8 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Oct 2014 16:36:13 +0200 Subject: links to SPIKY and paper pdfs --- Readme.rst | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Readme.rst b/Readme.rst index 265ecae..b9f29e3 100644 --- a/Readme.rst +++ b/Readme.rst @@ -5,15 +5,17 @@ PySpike :target: https://travis-ci.org/mariomulansky/PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the bivariate ISI_ [#]_ and SPIKE_ [#]_ distance. +Its core functionality is the implementation of the bivariate ISI_ and SPIKE_ distance [#]_ [#]_. Additionally, it provides functions to compute multivariate SPIKE and ISI distances, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). +PySpike provides the same fundamental functionality as the SPIKY_ framework for Matlab, which additionally contains spike-train generators, more spike train distance measures and many visualization routines. + All source codes are published under the BSD_License_. -.. [#] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) +.. [#] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) `[pdf] `_ -.. [#] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) +.. [#] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) `[pdf] `_ Requirements and Installation ----------------------------- @@ -254,9 +256,9 @@ The following example computes and plots the ISI- and SPIKE-distance matrix, whe =============================================================================== -`The work on PySpike was supported by the European Comission through the Marie -Curie Initial Training Network 'Neural Engineering Transformative Technologies -(NETT)' under the project number 289146.` +*The work on PySpike was supported by the European Comission through the Marie +Curie Initial Training Network* `Neural Engineering Transformative Technologies +(NETT) `_ *under the project number 289146.* **Python/C Programming:** @@ -270,4 +272,5 @@ Curie Initial Training Network 'Neural Engineering Transformative Technologies .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance .. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance .. _cython: http://www.cython.org +.. _SPIKY: http://wwwold.fi.isc.cnr.it/users/thomas.kreuz/Source-Code/SPIKY.html .. _BSD_License: http://opensource.org/licenses/BSD-2-Clause -- cgit v1.2.3 From 110d9c0e596c7a87fdc1c890e48732acd98375d7 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 4 Nov 2014 09:38:42 +0100 Subject: change "sort" parameter to "is_sorted" --- Readme.rst | 4 ++-- pyspike/spikes.py | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Readme.rst b/Readme.rst index b9f29e3..662cc1f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -78,7 +78,7 @@ To quickly obtain spike trains from such files, PySpike provides the function :c This function expects the name of the data file as first parameter. Additionally, the time interval of the spike train measurement can be provided as a pair of start- and end-time values. If the time interval is provided (:code:`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. -Furthermore, the spike trains are ordered via :code:`np.sort` (disable this feature by providing :code:`sort=False` as a parameter to the load function). +Furthermore, the spike trains are sorted via :code:`np.sort` (disable this feature by providing :code:`is_sorted=True` as a parameter to the load function). As result, :code:`load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. If you load spike trains yourself, i.e. from data files with different structure, you can use the helper function :code:`add_auxiliary_spikes` to add the auxiliary spikes at the beginning and end of the observation interval. @@ -99,7 +99,7 @@ Computing bivariate distances profiles Spike trains are expected to be *sorted*! For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed sorted. - Make sure that all your spike trains are sorted. + Make sure that all your spike trains are sorted, which is ensured if you use the `load_spike_trains_from_txt` function with the parameter `is_sorted=False`. If in doubt, use :code:`spike_train = np.sort(spike_train)` to obtain a correctly sorted spike train. Furthermore, the spike trains should have auxiliary spikes at the beginning and end of the observation interval. diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 6b6e2e7..f7172c9 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -48,15 +48,16 @@ def add_auxiliary_spikes(spike_train, time_interval): ############################################################ # spike_train_from_string ############################################################ -def spike_train_from_string(s, sep=' ', sort=True): +def spike_train_from_string(s, sep=' ', is_sorted=False): """ Converts a string of times into an array of spike times. :param s: the string with (ordered) spike times :param sep: The separator between the time numbers, default=' '. - :param sort: If True, the spike times are order via `np.sort`, default=True + :param is_sorted: if True, the spike times are not sorted after loading, + if False, spike times are sorted with `np.sort` :returns: array of spike times """ - if sort: + if not(is_sorted): return np.sort(np.fromstring(s, sep=sep)) else: return np.fromstring(s, sep=sep) -- cgit v1.2.3 From ab67a6fa5e5503cfcb31b57bcb6640ba82cce35b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 4 Nov 2014 09:55:54 +0100 Subject: renamed text files --- Contributors | 10 ---------- Contributors.txt | 10 ++++++++++ License | 12 ------------ License.txt | 12 ++++++++++++ 4 files changed, 22 insertions(+), 22 deletions(-) delete mode 100644 Contributors create mode 100644 Contributors.txt delete mode 100644 License create mode 100644 License.txt diff --git a/Contributors b/Contributors deleted file mode 100644 index 51e6751..0000000 --- a/Contributors +++ /dev/null @@ -1,10 +0,0 @@ -Python/C Programming: -- Mario Mulansky - -Scientific Methods: -- Thomas Kreuz -- Nebojsa D. Bozanic - -The work on PySpike was supported by the European Comission through the Marie -Curie Initial Training Network 'Neural Engineering Transformative Technologies -(NETT)' under the project number 289146. \ No newline at end of file diff --git a/Contributors.txt b/Contributors.txt new file mode 100644 index 0000000..51e6751 --- /dev/null +++ b/Contributors.txt @@ -0,0 +1,10 @@ +Python/C Programming: +- Mario Mulansky + +Scientific Methods: +- Thomas Kreuz +- Nebojsa D. Bozanic + +The work on PySpike was supported by the European Comission through the Marie +Curie Initial Training Network 'Neural Engineering Transformative Technologies +(NETT)' under the project number 289146. \ No newline at end of file diff --git a/License b/License deleted file mode 100644 index 472deac..0000000 --- a/License +++ /dev/null @@ -1,12 +0,0 @@ -BSD License - -Copyright (c) 2014, Mario Mulansky -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..472deac --- /dev/null +++ b/License.txt @@ -0,0 +1,12 @@ +BSD License + +Copyright (c) 2014, Mario Mulansky +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file -- cgit v1.2.3 From fcfe1f9637973c4c5f7f2f57aba4ce77c0fb2002 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 4 Nov 2014 12:02:08 +0100 Subject: packaging info to distribute on PyPI --- Readme.rst | 19 +++++++++++++--- setup.cfg | 2 ++ setup.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 setup.cfg diff --git a/Readme.rst b/Readme.rst index 662cc1f..4053995 100644 --- a/Readme.rst +++ b/Readme.rst @@ -20,12 +20,25 @@ All source codes are published under the BSD_License_. Requirements and Installation ----------------------------- -To use PySpike you need Python installed with the following additional packages: +PySpike is available at Python Package Index and this is the easiest way to obtain the PySpike package. +If you have `pip` installed, just run + +.. code:: bash + + sudo pip install pyspike + +to install pyspike. +PySpike requires `numpy` as minimal requirement, as well as a C compiler to generate the binaries. + +Install from Github sources +........................... + +You can also obtain the latest PySpike developer version from the github repository. +For that, make sure you have the following Python libraries installed: - numpy -- scipy -- matplotlib - cython +- matplotlib (for the examples) - nosetests (for running the tests) In particular, make sure that cython_ is configured properly and able to locate a C compiler, otherwise PySpike will use the much slower Python implementations. diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2aee194 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +description-file = Readme.rst diff --git a/setup.py b/setup.py index fc8d0d2..3bae9d5 100644 --- a/setup.py +++ b/setup.py @@ -1,24 +1,75 @@ """ setup.py -Handles the compilation of pyx source files - -run as: +to compile cython files: python setup.py build_ext --inplace + Copyright 2014, Mario Mulansky Distributed under the BSD License """ -from distutils.core import setup +from setuptools import setup, find_packages +from distutils.extension import Extension import numpy try: - from Cython.Build import cythonize - setup( - ext_modules=cythonize("pyspike/*.pyx"), - include_dirs=[numpy.get_include()] - ) + from Cython.Distutils import build_ext except ImportError: - print("Error: Cython is not installed! You will only be able to use the \ -much slower Python backend in PySpike.") + use_cython = False +else: + use_cython = True + +cmdclass = {} +ext_modules = [] + +if use_cython: + ext_modules += [ + Extension("pyspike.cython_add", ["pyspike/cython_add.pyx"]), + Extension("pyspike.cython_distance", ["pyspike/cython_distance.pyx"]), + ] + cmdclass.update({'build_ext': build_ext}) +else: + ext_modules += [ + Extension("pyspike.cython_add", ["pyspike/cython_add.c"]), + Extension("pyspike.cython_distance", ["pyspike/cython_distance.c"]), + ] + +setup( + name='pyspike', + packages=find_packages(exclude=['doc']), + version='0.1.2', + cmdclass=cmdclass, + ext_modules=ext_modules, + description='A Python library for the numerical analysis of spike\ +train similarity', + author='Mario Mulansky', + author_email='mario.mulanskygmx.net', + license='BSD', + url='https://github.com/mariomulansky/PySpike', + # download_url='https://github.com/mariomulansky/PySpike/tarball/0.1', + install_requires=['numpy'], + keywords=['data analysis', 'spike', 'neuroscience'], # arbitrary keywords + classifiers=[ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # Indicate who your project is intended for + 'Intended Audience :: Science/Research', + 'Topic :: Scientific/Engineering', + 'Topic :: Scientific/Engineering :: Information Analysis', + + 'License :: OSI Approved :: BSD License', + + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + ], + package_data={ + 'pyspike': ['cython_add.c', 'cython_distance.c'], + 'test': ['Spike_testdata.txt'] + } +) -- cgit v1.2.3 From 9a2471612551bb07726a9afc97fac5f3b704cc41 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 4 Nov 2014 12:20:07 +0100 Subject: fixed setup for travis test runs --- setup.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 3bae9d5..eaef7e1 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ Distributed under the BSD License """ from setuptools import setup, find_packages from distutils.extension import Extension +import os.path import numpy try: @@ -20,20 +21,27 @@ except ImportError: else: use_cython = True +if os.path.isfile("pyspike/cython_add.c") and \ + os.path.isfile("pyspike/cython_distance.c"): + use_c = True +else: + use_c = False + cmdclass = {} ext_modules = [] -if use_cython: +if use_cython: # Cython is available, compile .pyx -> .c ext_modules += [ Extension("pyspike.cython_add", ["pyspike/cython_add.pyx"]), Extension("pyspike.cython_distance", ["pyspike/cython_distance.pyx"]), ] cmdclass.update({'build_ext': build_ext}) -else: +elif use_c: # c files are there, compile to binaries ext_modules += [ Extension("pyspike.cython_add", ["pyspike/cython_add.c"]), Extension("pyspike.cython_distance", ["pyspike/cython_distance.c"]), ] +# neither cython nor c files available -> automatic fall-back to python backend setup( name='pyspike', @@ -41,6 +49,7 @@ setup( version='0.1.2', cmdclass=cmdclass, ext_modules=ext_modules, + include_dirs=[numpy.get_include()], description='A Python library for the numerical analysis of spike\ train similarity', author='Mario Mulansky', -- cgit v1.2.3 From 888f245fede0ddc9b5d742cb3cbf7a6727125ef0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 4 Nov 2014 14:12:12 +0100 Subject: v0.1.3 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index eaef7e1..9cab5de 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.1.2', + version='0.1.3', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy.get_include()], -- cgit v1.2.3 From 1b2aa84e7d642c7a5f4b99ca83b5ca25d6905960 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 21 Nov 2014 17:40:10 +0100 Subject: added spike generation function --- pyspike/__init__.py | 2 +- pyspike/cython_distance.pyx | 21 +++++++++++++++------ pyspike/spikes.py | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index d2d5b57..d700e7a 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -12,4 +12,4 @@ from distances import isi_profile, isi_distance, \ isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ spike_profile_multi, spike_distance_multi, spike_distance_matrix from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ - spike_train_from_string, merge_spike_trains + spike_train_from_string, merge_spike_trains, generate_poisson_spikes diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 178fcba..779ff94 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -122,6 +122,15 @@ cdef inline double get_min_dist_cython(double spike_time, return d +############################################################ +# 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 ~ 0.5 for Poisson spikes + # return 0.5*(isi1*isi1+isi2*isi2) + + ############################################################ # spike_distance_cython ############################################################ @@ -155,7 +164,7 @@ def spike_distance_cython(double[:] t1, isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) s1 = dt_f1*(t1[1]-t1[0])/isi1 s2 = dt_f2*(t2[1]-t2[0])/isi2 - y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + y_starts[0] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) while True: # print(index, index1, index2) if t1[index1+1] < t2[index2+1]: @@ -169,12 +178,12 @@ def spike_distance_cython(double[:] t1, s1 = dt_p1 s2 = (dt_p2*(t2[index2+1]-t1[index1]) + dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) + y_ends[index-1] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) # now the next interval start value dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) isi1 = t1[index1+1]-t1[index1] # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) + y_starts[index] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) elif t1[index1+1] > t2[index2+1]: index2 += 1 if index2+1 >= N2: @@ -185,13 +194,13 @@ def spike_distance_cython(double[:] t1, s1 = (dt_p1*(t1[index1+1]-t2[index2]) + dt_f1*(t2[index2]-t1[index1])) / isi1 s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) + y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) # now the next interval start value dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) #s2 = dt_f2 isi2 = t2[index2+1]-t2[index2] # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1)/(0.5*(isi1+isi2)*(isi1+isi2)) + y_starts[index] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) else: # t1[index1+1] == t2[index2+1] - generate only one event index1 += 1 index2 += 1 @@ -214,7 +223,7 @@ def spike_distance_cython(double[:] t1, isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)*(isi1+isi2)) + y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) # end nogil # use only the data added above diff --git a/pyspike/spikes.py b/pyspike/spikes.py index f7172c9..aa25c48 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -129,3 +129,43 @@ def merge_spike_trains(spike_trains): index_list = index_list[index_list != i] vals = [spike_trains[n][indices[n]] for n in index_list] return merged_spikes + + +############################################################ +# generate_poisson_spikes +############################################################ +def generate_poisson_spikes(rate, time_interval, add_aux_spikes=True): + """ Generates a Poisson spike train with the given rate in the given time + interval + + :param rate: The rate of the spike trains + :param time_interval: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement or + a single value representing the end time, the T_start + is then assuemd as 0. Auxiliary spikes will be added + to the spike train at the beginning and end of this + interval, if they are not yet present. + :type time_interval: pair of doubles or double + :returns: Poisson spike train + """ + try: + T_start = time_interval[0] + T_end = time_interval[1] + except: + T_start = 0 + T_end = time_interval + # roughly how many spikes are required to fill the interval + N = max(1, int(1.2 * rate * (T_end-T_start))) + N_append = max(1, int(0.1 * rate * (T_end-T_start))) + intervals = np.random.exponential(1.0/rate, N) + # make sure we have enough spikes + while T_start + sum(intervals) < T_end: + print T_start + sum(intervals) + intervals = np.append(intervals, + np.random.exponential(1.0/rate, N_append)) + spikes = T_start + np.cumsum(intervals) + spikes = spikes[spikes < T_end] + if add_aux_spikes: + return add_auxiliary_spikes(spikes, time_interval) + else: + return spikes -- cgit v1.2.3 From 7c8391298843e3ead55896e075fe28d5fe5bf795 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 12 Dec 2014 16:44:25 +0100 Subject: +spike synchronization, python impl only --- pyspike/__init__.py | 4 +++- pyspike/distances.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++ pyspike/function.py | 9 ++++++++ pyspike/python_backend.py | 54 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index d700e7a..dca5722 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -9,7 +9,9 @@ __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc, average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ + spike_sync_profile, \ isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ - spike_profile_multi, spike_distance_multi, spike_distance_matrix + spike_profile_multi, spike_distance_multi, spike_distance_matrix, \ + spike_sync_profile_multi from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ spike_train_from_string, merge_spike_trains, generate_poisson_spikes diff --git a/pyspike/distances.py b/pyspike/distances.py index 34f7d78..fbedce5 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -9,6 +9,7 @@ Distributed under the BSD License import numpy as np import threading +from functools import partial from pyspike import PieceWiseConstFunc, PieceWiseLinFunc @@ -128,6 +129,42 @@ def spike_distance(spikes1, spikes2, interval=None): return spike_profile(spikes1, spikes2).avrg(interval) +############################################################ +# spike_sync_profile +############################################################ +def spike_sync_profile(spikes1, spikes2, k=3): + + assert k > 0 + + # cython implementation + try: + from cython_distance import cumulative_sync_cython \ + as cumulative_sync_impl + except ImportError: +# print("Warning: spike_distance_cython not found. Make sure that \ +# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +# Falling back to slow python backend.") + # use python backend + from python_backend import cumulative_sync_python \ + as cumulative_sync_impl + + st, c = cumulative_sync_impl(spikes1, spikes2) + + # print c + # print 2*(c[-1]-c[0])/(len(spikes1)+len(spikes2)-2) + + dc = np.zeros(len(c)) + dc[k:-k] = (c[2*k:] - c[:-2*k]) / k + for n in xrange(1, k): + dc[n] = (c[2*n] - c[0]) / k + dc[-n-1] = (c[-1]-c[-2*n-1]) / k + dc[0] = dc[1] + dc[-1] = dc[-2] + # dc[-1] = (c[-1]-c[-2])/k + # print dc + return PieceWiseConstFunc(st, dc) + + ############################################################ # _generic_profile_multi ############################################################ @@ -278,6 +315,28 @@ def spike_profile_multi(spike_trains, indices=None): return _generic_profile_multi(spike_trains, spike_profile, indices) +############################################################ +# spike_profile_multi +############################################################ +def spike_sync_profile_multi(spike_trains, indices=None, k=3): + """ Computes the multi-variate spike synchronization profile for a set of + spike trains. That is the average spike-distance of all pairs of spike + trains: + :math:`S_ss(t) = 2/((N(N-1)) sum_{} S_{ss}^{i, j}`, + where the sum goes over all pairs + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: The averaged spike profile :math:`(t)` + :rtype: :class:`pyspike.function.PieceWiseConstFunc` + + """ + prof_func = partial(spike_sync_profile, k=k) + return _generic_profile_multi(spike_trains, prof_func, indices) + + ############################################################ # spike_distance_multi ############################################################ diff --git a/pyspike/function.py b/pyspike/function.py index 0c0a391..662606c 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -136,6 +136,15 @@ class PieceWiseConstFunc(object): a /= int_length return a + def avrg_function_value(self): + """ Computes the average function value of the piece-wise const + function: :math:`a = 1/N sum_i f_i` where N is the number of intervals. + + :returns: the average a. + :rtype: float + """ + return sum(self.y)/(len(self.y)) + def add(self, f): """ Adds another PieceWiseConst function to this function. Note: only functions defined on the same interval can be summed. diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index 874c689..b85262d 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -188,6 +188,60 @@ def spike_distance_python(spikes1, spikes2): 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 + + ############################################################ # add_piece_wise_const_python ############################################################ -- cgit v1.2.3 From 4fd98cc5f26c516f742c9b0a2dc787d309d65d32 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 15 Dec 2014 16:52:44 +0100 Subject: improved implementation of spike_sync --- pyspike/distances.py | 34 +++++++++++++++-------------- pyspike/python_backend.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index fbedce5..c28fd7a 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -138,30 +138,32 @@ def spike_sync_profile(spikes1, spikes2, k=3): # cython implementation try: - from cython_distance import cumulative_sync_cython \ - as cumulative_sync_impl + from cython_distance import coincidence_cython \ + as coincidence_impl except ImportError: # print("Warning: spike_distance_cython not found. Make sure that \ # PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ # Falling back to slow python backend.") # use python backend - from python_backend import cumulative_sync_python \ - as cumulative_sync_impl + from python_backend import coincidence_python \ + as coincidence_impl - st, c = cumulative_sync_impl(spikes1, spikes2) - - # print c - # print 2*(c[-1]-c[0])/(len(spikes1)+len(spikes2)-2) + st, c = coincidence_impl(spikes1, spikes2) dc = np.zeros(len(c)) - dc[k:-k] = (c[2*k:] - c[:-2*k]) / k - for n in xrange(1, k): - dc[n] = (c[2*n] - c[0]) / k - dc[-n-1] = (c[-1]-c[-2*n-1]) / k - dc[0] = dc[1] - dc[-1] = dc[-2] - # dc[-1] = (c[-1]-c[-2])/k - # print dc + for i in xrange(2*k): + dc[k:-k] += c[i:-2*k+i] + + for n in xrange(0, k): + for i in xrange(n+k): + dc[n] += c[i] + dc[-n-1] += c[-i-1] + for i in xrange(k-n-1): + dc[n] += c[i] + dc[-n-1] += c[-i-1] + + dc *= 1.0/k + return PieceWiseConstFunc(st, dc) diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index b85262d..7f8ea8c 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -242,6 +242,60 @@ def cumulative_sync_python(spikes1, spikes2): return st, c +############################################################ +# coincidence_python +############################################################ +def coincidence_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] = 0 + else: + c[n] = 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] = 0 + else: + c[n] = 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] = 0 + n += 1 + st[n] = spikes1[i] + c[n] = 1 + c[0] = c[2] + st[0] = spikes1[0] + st[-1] = spikes1[-1] + + return st, c + + ############################################################ # add_piece_wise_const_python ############################################################ -- cgit v1.2.3 From 4724eb755354458e8b0730ceecb52a1a9c1c7e52 Mon Sep 17 00:00:00 2001 From: Richard Tomsett Date: Wed, 17 Dec 2014 17:05:52 +0900 Subject: Fixed bug in distances.py when calculating pairs from arbitrary indices lists --- pyspike/distances.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyspike/distances.py b/pyspike/distances.py index c28fd7a..04ea77b 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -194,7 +194,7 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(i, j) for i in indices for j in indices[i+1:]] + pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] # start with first pair (i, j) = pairs[0] average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) @@ -234,7 +234,7 @@ def _multi_distance_par(spike_trains, pair_distance_func, indices=None): assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(i, j) for i in indices for j in indices[i+1:]] + pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] num_pairs = len(pairs) # start with first pair @@ -384,7 +384,7 @@ def _generic_distance_matrix(spike_trains, dist_function, assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(i, j) for i in indices for j in indices[i+1:]] + pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: -- cgit v1.2.3 From 8c98f0043fa785b8352b3c685615da24b30e6149 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 26 Dec 2014 15:31:15 -0600 Subject: spike sync --- pyspike/__init__.py | 5 +- pyspike/distances.py | 57 ++++++++++++------ pyspike/function.py | 149 +++++++++++++++++++++++++++++++++++++++++++--- pyspike/python_backend.py | 82 ++++++++++++++++++++++++- test/test_distance.py | 54 +++++++++++++++++ 5 files changed, 316 insertions(+), 31 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index dca5722..fa90d99 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -6,10 +6,11 @@ Distributed under the BSD License __all__ = ["function", "distances", "spikes"] -from function import PieceWiseConstFunc, PieceWiseLinFunc, average_profile +from function import PieceWiseConstFunc, PieceWiseLinFunc, IntervalSequence,\ + average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ - spike_sync_profile, \ + spike_sync_profile, spike_sync_distance, \ isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ spike_profile_multi, spike_distance_multi, spike_distance_matrix, \ spike_sync_profile_multi diff --git a/pyspike/distances.py b/pyspike/distances.py index c28fd7a..38c5cc2 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -11,7 +11,7 @@ import numpy as np import threading from functools import partial -from pyspike import PieceWiseConstFunc, PieceWiseLinFunc +from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, IntervalSequence ############################################################ @@ -148,23 +148,34 @@ def spike_sync_profile(spikes1, spikes2, k=3): from python_backend import coincidence_python \ as coincidence_impl - st, c = coincidence_impl(spikes1, spikes2) + st, J = coincidence_impl(spikes1, spikes2) - dc = np.zeros(len(c)) - for i in xrange(2*k): - dc[k:-k] += c[i:-2*k+i] + N = len(J) - for n in xrange(0, k): - for i in xrange(n+k): - dc[n] += c[i] - dc[-n-1] += c[-i-1] - for i in xrange(k-n-1): - dc[n] += c[i] - dc[-n-1] += c[-i-1] + # compute the cumulative sum, include some extra values for boundary + # conditions + c = np.zeros(N + 2*k) + c[k:-k] = np.cumsum(J) + # set the boundary values + # on the left: c_0 = -c_1, c_{-1} = -c_2, ..., c{-k+1} = c_k + # on the right: c_{N+1} = c_N, c_{N+2} = 2*c_N - c_{N-1}, + # c_{N+2} = 2*c_N - c_{N-2}, ..., c_{N+k} = 2*c_N - c_{N-k+1} + for n in xrange(k): + c[k-n-1] = -c[k+n] + c[-k+n] = 2*c[-k-1] - c[-k-1-n] + # with the right boundary values, the differences become trivial + J_w = c[2*k:] - c[:-2*k] + # normalize to half the interval width + J_w *= 1.0/k - dc *= 1.0/k + return IntervalSequence(st, J_w) - return PieceWiseConstFunc(st, dc) + +############################################################ +# spike_sync_distance +############################################################ +def spike_sync_distance(spikes1, spikes2, k=3): + return spike_sync_profile(spikes1, spikes2, k).avrg() ############################################################ @@ -201,8 +212,7 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): for (i, j) in pairs[1:]: current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) average_dist.add(current_dist) # add to the average - average_dist.mul_scalar(1.0/len(pairs)) # normalize - return average_dist + return average_dist, len(pairs) ############################################################ @@ -273,7 +283,10 @@ def isi_profile_multi(spike_trains, indices=None): :returns: The averaged isi profile :math:`(t)` :rtype: :class:`pyspike.function.PieceWiseConstFunc` """ - return _generic_profile_multi(spike_trains, isi_profile, indices) + average_dist, M = _generic_profile_multi(spike_trains, isi_profile, + indices) + average_dist.mul_scalar(1.0/M) # normalize + return average_dist ############################################################ @@ -314,7 +327,10 @@ def spike_profile_multi(spike_trains, indices=None): :rtype: :class:`pyspike.function.PieceWiseLinFunc` """ - return _generic_profile_multi(spike_trains, spike_profile, indices) + average_dist, M = _generic_profile_multi(spike_trains, spike_profile, + indices) + average_dist.mul_scalar(1.0/M) # normalize + return average_dist ############################################################ @@ -336,7 +352,10 @@ def spike_sync_profile_multi(spike_trains, indices=None, k=3): """ prof_func = partial(spike_sync_profile, k=k) - return _generic_profile_multi(spike_trains, prof_func, indices) + average_dist, M = _generic_profile_multi(spike_trains, prof_func, + indices) + # average_dist.mul_scalar(1.0/M) # no normalization here! + return average_dist ############################################################ diff --git a/pyspike/function.py b/pyspike/function.py index 662606c..f5a1133 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -136,15 +136,6 @@ class PieceWiseConstFunc(object): a /= int_length return a - def avrg_function_value(self): - """ Computes the average function value of the piece-wise const - function: :math:`a = 1/N sum_i f_i` where N is the number of intervals. - - :returns: the average a. - :rtype: float - """ - return sum(self.y)/(len(self.y)) - def add(self, f): """ Adds another PieceWiseConst function to this function. Note: only functions defined on the same interval can be summed. @@ -179,6 +170,146 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ self.y *= fac +############################################################## +# IntervalSequence +############################################################## +class IntervalSequence(object): + """ A class representing a sequence of values defined in some interval. + This is very similar to a `PieceWiseConstFunc`, but with different + averaging and addition. + """ + + def __init__(self, x, y): + """ Constructs the interval sequence. + + :param x: array of length N+1 defining the edges of the intervals of + the intervals. + :param y: array of length N defining the values at the intervals. + """ + # convert parameters to arrays, also ensures copying + self.x = np.array(x) + self.y = np.array(y) + self.extra_zero_intervals = 0 + + def copy(self): + """ Returns a copy of itself + + :rtype: :class:`IntervalSequence` + """ + return IntervalSequence(self.x, self.y) + + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + + :param other: another :class:`IntervalSequence` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y, other.y, atol=eps, rtol=0.0) + + def get_plottable_data(self): + """ Returns two arrays containing x- and y-coordinates for immeditate + plotting of the interval sequence. + + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Piece-wise const function") + """ + + x_plot = np.empty(2*len(self.x)-2) + x_plot[0] = self.x[0] + x_plot[1::2] = self.x[1:] + x_plot[2::2] = self.x[1:-1] + y_plot = np.empty(2*len(self.y)) + y_plot[::2] = self.y + normalization = 1.0 * (len(self.y)-1) / (len(self.y) + + self.extra_zero_intervals-1) + y_plot[1::2] = self.y + + return x_plot, y_plot * normalization + + def integral(self, interval=None): + """ Returns the integral over the given interval. For the interval + sequence this amounts to the sum over all values divided by the count + of intervals. + + :param interval: integration interval given as a pair of floats, if + None the integral over the whole function is computed. + :type interval: Pair of floats or None. + :returns: the integral + :rtype: float + """ + if interval is None: + # no interval given, integrate over the whole spike train + # don't count the first value, which is zero by definition + a = np.sum(self.y) + else: + raise NotImplementedError() + return a + + def avrg(self, interval=None): + """ Computes the average of the interval sequence: + :math:`a = 1/N sum f_n ` where N is the number of intervals. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + """ + if interval is None: + # no interval given, average over the whole spike train + # don't count the first interval for normalization + return self.integral() / (len(self.y)-1+self.extra_zero_intervals) + else: + raise NotImplementedError() + + def add(self, f): + """ Adds another `IntervalSequence` function to this function. + Note: only functions defined on the same interval can be summed. + + :param f: :class:`IntervalSequence` function to be added. + :rtype: None + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + + # cython version + try: + from cython_add import add_interval_sequence_cython as \ + add_interval_sequence_impl + except ImportError: +# print("Warning: add_piece_wise_const_cython not found. Make sure \ +# that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ +# \n Falling back to slow python backend.") + # use python backend + from python_backend import add_interval_sequence_python as \ + add_interval_sequence_impl + + self.x, self.y, extra_intervals = \ + add_interval_sequence_impl(self.x, self.y, f.x, f.y) + self.extra_zero_intervals += extra_intervals + + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + + :param fac: Value to multiply + :type fac: double + :rtype: None + """ + self.y *= fac + + ############################################################## # PieceWiseLinFunc ############################################################## diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index 7f8ea8c..154d250 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -289,7 +289,7 @@ def coincidence_python(spikes1, spikes2): n += 1 st[n] = spikes1[i] c[n] = 1 - c[0] = c[2] + #c[0] = c[2] st[0] = spikes1[0] st[-1] = spikes1[-1] @@ -340,6 +340,86 @@ def add_piece_wise_const_python(x1, y1, x2, y2): return x_new[:index+2], y_new[:index+1] +############################################################ +# add_interval_sequence_python +############################################################ +def add_interval_sequence_python(x1, y1, x2, y2): + yscale1 = np.empty_like(y1) + index2 = 1 + # s1 = (len(y1)+len(y2)-2.0) / (len(y1)-1.0) + # s2 = (len(y1)+len(y2)-2.0) / (len(y2)-1.0) + s1 = 1.0 + s2 = 1.0 + for i in xrange(len(y1)): + c = 1 + while index2 < len(x2)-1 and x2[index2] < x1[i+1]: + index2 += 1 + c += 1 + if index2 < len(x2)-1 and x2[index2] == x1[i+1]: + index2 += 1 + # c += 1 + yscale1[i] = s1/c + + yscale2 = np.empty_like(y2) + index1 = 1 + for i in xrange(len(y2)): + c = 1 + while index1 < len(x1)-1 and x1[index1] < x2[i+1]: + index1 += 1 + c += 1 + if index1 < len(x1)-1 and x1[index1] == x2[i+1]: + index1 += 1 + # c += 1 + yscale2[i] = s2/c + + x_new = np.empty(len(x1) + len(x2)) + y_new = np.empty(len(x_new)-1) + x_new[0] = x1[0] + index1 = 0 + index2 = 0 + index = 0 + additional_intervals = 0 + while (index1+1 < len(y1)) and (index2+1 < len(y2)): + y_new[index] = y1[index1]*yscale1[index1] + y2[index2]*yscale2[index2] + 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] + # y_new[index] = y1[index1]*yscale1[index1] + \ + # y2[index2]*yscale2[index2] + index1 += 1 + # x_new[index] = x1[index1] + index2 += 1 + # index += 1 + x_new[index] = x1[index1] + additional_intervals += 1 + y_new[index] = y1[index1]*yscale1[index1] + y2[index2]*yscale2[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:]*yscale1[index1+1:] + y2[-1]*yscale2[-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:]*yscale2[index2+1:] + y1[-1]*yscale1[-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], additional_intervals + + ############################################################ # add_piece_lin_const_python ############################################################ diff --git a/test/test_distance.py b/test/test_distance.py index 7be0d9b..d98069d 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -130,6 +130,60 @@ def test_spike(): decimal=16) +def test_spike_sync(): + spikes1 = np.array([1.0, 2.0, 3.0]) + spikes2 = np.array([2.1]) + spikes1 = spk.add_auxiliary_spikes(spikes1, 4.0) + spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + for k in xrange(1, 3): + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), + 0.5, decimal=16) + + spikes2 = np.array([3.1]) + spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + for k in xrange(1, 3): + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), + 0.5, decimal=16) + + spikes2 = np.array([1.1]) + spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + for k in xrange(1, 3): + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), + 0.5, decimal=16) + + spikes2 = np.array([0.9]) + spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + for k in xrange(1, 3): + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), + 0.5, decimal=16) + + spikes1 = np.array([100, 300, 400, 405, 410, 500, 700, 800, + 805, 810, 815, 900]) + spikes2 = np.array([100, 200, 205, 210, 295, 350, 400, 510, + 600, 605, 700, 910]) + spikes3 = np.array([100, 180, 198, 295, 412, 420, 510, 640, + 695, 795, 820, 920]) + spikes1 = spk.add_auxiliary_spikes(spikes1, 1000) + spikes2 = spk.add_auxiliary_spikes(spikes2, 1000) + spikes3 = spk.add_auxiliary_spikes(spikes3, 1000) + for k in xrange(1, 10): + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), + 0.5, decimal=15) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes3, k=k), + 0.5, decimal=15) + assert_almost_equal(spk.spike_sync_distance(spikes2, spikes3, k=k), + 0.5, decimal=15) + + f1 = spk.spike_sync_profile(spikes1, spikes2, k=1) + f2 = spk.spike_sync_profile(spikes1, spikes3, k=1) + f3 = spk.spike_sync_profile(spikes2, spikes3, k=1) + f = spk.spike_sync_profile_multi([spikes1, spikes2, spikes3], k=1) + # hands on definition of the average multivariate spike synchronization + expected = (f1.integral() + f2.integral() + f3.integral()) / \ + (len(f1.y)+len(f2.y)+len(f3.y)-3) + assert_almost_equal(f.avrg(), expected, decimal=15) + + def check_multi_profile(profile_func, profile_func_multi): # generate spike trains: t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) -- cgit v1.2.3 From f3f6eb8eec87c2a13183bfe512ef2747dfb33ed6 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 26 Dec 2014 15:31:37 -0600 Subject: spike sync example --- examples/spike_sync.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 examples/spike_sync.py diff --git a/examples/spike_sync.py b/examples/spike_sync.py new file mode 100644 index 0000000..464dbb0 --- /dev/null +++ b/examples/spike_sync.py @@ -0,0 +1,32 @@ +from __future__ import print_function + +import numpy as np +import matplotlib.pyplot as plt + +import pyspike as spk + +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + +print(spike_trains[0]) +print(spike_trains[1]) + +# plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') +# plt.plot(spike_trains[1], np.zeros_like(spike_trains[1]), 'o') + +plt.figure() + +f = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) +x, y = f.get_plottable_data() +plt.plot(x, y, '--k', label="SPIKE-SYNC profile") +print(x) +print(y) + +f = spk.spike_profile(spike_trains[0], spike_trains[1]) +x, y = f.get_plottable_data() + +plt.plot(x, y, '-b', label="SPIKE-profile") + +plt.legend(loc="upper left") + +plt.show() -- cgit v1.2.3 From fed0ceec753fc1a7e5a1e20632de5a9800fe4fb1 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 19 Jan 2015 16:39:17 +0100 Subject: final version for spike sync --- examples/spike_sync.py | 30 +++++++---- pyspike/__init__.py | 4 +- pyspike/distances.py | 36 +++---------- pyspike/function.py | 58 +++++++++----------- pyspike/python_backend.py | 135 ++++++++++++++++++++++------------------------ pyspike/spikes.py | 4 +- test/SPIKE_Sync_Test.txt | 100 ++++++++++++++++++++++++++++++++++ test/test_distance.py | 79 ++++++++++++++------------- 8 files changed, 262 insertions(+), 184 deletions(-) create mode 100644 test/SPIKE_Sync_Test.txt diff --git a/examples/spike_sync.py b/examples/spike_sync.py index 464dbb0..535f19f 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -5,28 +5,36 @@ import matplotlib.pyplot as plt import pyspike as spk -spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", +spike_trains = spk.load_spike_trains_from_txt("../test/SPIKE_Sync_Test.txt", time_interval=(0, 4000)) -print(spike_trains[0]) -print(spike_trains[1]) - -# plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') -# plt.plot(spike_trains[1], np.zeros_like(spike_trains[1]), 'o') - plt.figure() f = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) +# f = spk.spike_sync_profile(spikes1, spikes2) x, y = f.get_plottable_data() -plt.plot(x, y, '--k', label="SPIKE-SYNC profile") -print(x) -print(y) +plt.plot(x, y, '--ok', label="SPIKE-SYNC profile") +print(f.x) +print(f.y) +print(f.mp) + +print("Average:", f.avrg()) + f = spk.spike_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() plt.plot(x, y, '-b', label="SPIKE-profile") -plt.legend(loc="upper left") +plt.axis([0, 4000, -0.1, 1.1]) +plt.legend(loc="center right") + +plt.figure() + +f = spk.spike_sync_profile_multi(spike_trains) +x, y = f.get_plottable_data() +plt.plot(x, y, '-k', label="SPIKE-SYNC profile") + +print("Average:", f.avrg()) plt.show() diff --git a/pyspike/__init__.py b/pyspike/__init__.py index fa90d99..74d52c5 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -6,8 +6,8 @@ Distributed under the BSD License __all__ = ["function", "distances", "spikes"] -from function import PieceWiseConstFunc, PieceWiseLinFunc, IntervalSequence,\ - average_profile +from function import PieceWiseConstFunc, PieceWiseLinFunc, \ + MultipleValueSequence, average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ spike_sync_profile, spike_sync_distance, \ diff --git a/pyspike/distances.py b/pyspike/distances.py index 38c5cc2..5ee8261 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -11,7 +11,7 @@ import numpy as np import threading from functools import partial -from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, IntervalSequence +from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, MultipleValueSequence ############################################################ @@ -132,9 +132,7 @@ def spike_distance(spikes1, spikes2, interval=None): ############################################################ # spike_sync_profile ############################################################ -def spike_sync_profile(spikes1, spikes2, k=3): - - assert k > 0 +def spike_sync_profile(spikes1, spikes2): # cython implementation try: @@ -148,34 +146,16 @@ def spike_sync_profile(spikes1, spikes2, k=3): from python_backend import coincidence_python \ as coincidence_impl - st, J = coincidence_impl(spikes1, spikes2) - - N = len(J) - - # compute the cumulative sum, include some extra values for boundary - # conditions - c = np.zeros(N + 2*k) - c[k:-k] = np.cumsum(J) - # set the boundary values - # on the left: c_0 = -c_1, c_{-1} = -c_2, ..., c{-k+1} = c_k - # on the right: c_{N+1} = c_N, c_{N+2} = 2*c_N - c_{N-1}, - # c_{N+2} = 2*c_N - c_{N-2}, ..., c_{N+k} = 2*c_N - c_{N-k+1} - for n in xrange(k): - c[k-n-1] = -c[k+n] - c[-k+n] = 2*c[-k-1] - c[-k-1-n] - # with the right boundary values, the differences become trivial - J_w = c[2*k:] - c[:-2*k] - # normalize to half the interval width - J_w *= 1.0/k + times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) - return IntervalSequence(st, J_w) + return MultipleValueSequence(times, coincidences, multiplicity) ############################################################ # spike_sync_distance ############################################################ -def spike_sync_distance(spikes1, spikes2, k=3): - return spike_sync_profile(spikes1, spikes2, k).avrg() +def spike_sync_distance(spikes1, spikes2): + return spike_sync_profile(spikes1, spikes2).avrg() ############################################################ @@ -336,7 +316,7 @@ def spike_profile_multi(spike_trains, indices=None): ############################################################ # spike_profile_multi ############################################################ -def spike_sync_profile_multi(spike_trains, indices=None, k=3): +def spike_sync_profile_multi(spike_trains, indices=None): """ Computes the multi-variate spike synchronization profile for a set of spike trains. That is the average spike-distance of all pairs of spike trains: @@ -351,7 +331,7 @@ def spike_sync_profile_multi(spike_trains, indices=None, k=3): :rtype: :class:`pyspike.function.PieceWiseConstFunc` """ - prof_func = partial(spike_sync_profile, k=k) + prof_func = partial(spike_sync_profile) average_dist, M = _generic_profile_multi(spike_trains, prof_func, indices) # average_dist.mul_scalar(1.0/M) # no normalization here! diff --git a/pyspike/function.py b/pyspike/function.py index f5a1133..f10c136 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -171,32 +171,32 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ ############################################################## -# IntervalSequence +# MultipleValueSequence ############################################################## -class IntervalSequence(object): +class MultipleValueSequence(object): """ A class representing a sequence of values defined in some interval. - This is very similar to a `PieceWiseConstFunc`, but with different - averaging and addition. """ - def __init__(self, x, y): - """ Constructs the interval sequence. + def __init__(self, x, y, multiplicity): + """ Constructs the value sequence. - :param x: array of length N+1 defining the edges of the intervals of - the intervals. - :param y: array of length N defining the values at the intervals. + :param x: array of length N defining the points at which the values are + defined. + :param y: array of length N degining the values at the points x. + :param multiplicity: array of length N defining the multiplicity of the + values. """ # convert parameters to arrays, also ensures copying self.x = np.array(x) self.y = np.array(y) - self.extra_zero_intervals = 0 + self.mp = np.array(multiplicity) def copy(self): """ Returns a copy of itself :rtype: :class:`IntervalSequence` """ - return IntervalSequence(self.x, self.y) + return MultipleValueSequence(self.x, self.y, self.mp) def almost_equal(self, other, decimal=14): """ Checks if the function is equal to another function up to `decimal` @@ -209,9 +209,10 @@ class IntervalSequence(object): """ eps = 10.0**(-decimal) return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ - np.allclose(self.y, other.y, atol=eps, rtol=0.0) + np.allclose(self.y, other.y, atol=eps, rtol=0.0) and \ + np.allclose(self.mp, other.mp, atol=eps, rtol=0.0) - def get_plottable_data(self): + def get_plottable_data(self, k=0): """ Returns two arrays containing x- and y-coordinates for immeditate plotting of the interval sequence. @@ -224,17 +225,10 @@ class IntervalSequence(object): plt.plot(x, y, '-o', label="Piece-wise const function") """ - x_plot = np.empty(2*len(self.x)-2) - x_plot[0] = self.x[0] - x_plot[1::2] = self.x[1:] - x_plot[2::2] = self.x[1:-1] - y_plot = np.empty(2*len(self.y)) - y_plot[::2] = self.y - normalization = 1.0 * (len(self.y)-1) / (len(self.y) + - self.extra_zero_intervals-1) - y_plot[1::2] = self.y + if k > 0: + raise NotImplementedError() - return x_plot, y_plot * normalization + return 1.0*self.x, 1.0*self.y/self.mp def integral(self, interval=None): """ Returns the integral over the given interval. For the interval @@ -250,7 +244,7 @@ class IntervalSequence(object): if interval is None: # no interval given, integrate over the whole spike train # don't count the first value, which is zero by definition - a = np.sum(self.y) + a = 1.0*np.sum(self.y[1:-1]) else: raise NotImplementedError() return a @@ -270,15 +264,15 @@ class IntervalSequence(object): if interval is None: # no interval given, average over the whole spike train # don't count the first interval for normalization - return self.integral() / (len(self.y)-1+self.extra_zero_intervals) + return self.integral() / np.sum(self.mp[1:-1]) else: raise NotImplementedError() def add(self, f): - """ Adds another `IntervalSequence` function to this function. + """ Adds another `MultipleValueSequence` function to this function. Note: only functions defined on the same interval can be summed. - :param f: :class:`IntervalSequence` function to be added. + :param f: :class:`MultipleValueSequence` function to be added. :rtype: None """ assert self.x[0] == f.x[0], "The functions have different intervals" @@ -293,12 +287,12 @@ class IntervalSequence(object): # that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ # \n Falling back to slow python backend.") # use python backend - from python_backend import add_interval_sequence_python as \ - add_interval_sequence_impl + from python_backend import add_multiple_value_sequence_python as \ + add_multiple_value_sequence_impl - self.x, self.y, extra_intervals = \ - add_interval_sequence_impl(self.x, self.y, f.x, f.y) - self.extra_zero_intervals += extra_intervals + self.x, self.y, self.mp = \ + add_multiple_value_sequence_impl(self.x, self.y, self.mp, + f.x, f.y, f.mp) def mul_scalar(self, fac): """ Multiplies the function with a scalar value diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index 154d250..bbbd572 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -248,52 +248,69 @@ def cumulative_sync_python(spikes1, spikes2): def coincidence_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]]) + m = 1E100 # some huge number + if i < len(spikes1)-2: + m = min(m, spikes1[i+1]-spikes1[i]) + if j < len(spikes2)-2: + m = min(m, spikes2[j+1]-spikes2[j]) + if i > 1: + m = min(m, spikes1[i]-spikes1[i-1]) + if j > 1: + m = min(m, spikes2[j]-spikes2[j-1]) + return 0.5*m 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: + st = np.zeros(N1 + N2 - 2) # spike times + c = np.zeros(N1 + N2 - 2) # coincidences + mp = np.ones(N1 + N2 - 2) # multiplicity + while n < N1 + N2 - 2: 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] = 0 - else: + if j > 0 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 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] = 0 - else: + if i > 0 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 if i == N1-1 or j == N2-1: break n += 1 + # add only one event, but with coincidence 2 and multiplicity 2 st[n] = spikes1[i] - c[n] = 0 - n += 1 - st[n] = spikes1[i] - c[n] = 1 - #c[0] = c[2] + c[n] = 2 + mp[n] = 2 + + st = st[:n+2] + c = c[:n+2] + mp = mp[:n+2] + st[0] = spikes1[0] st[-1] = spikes1[-1] + c[0] = c[1] + c[-1] = c[-2] + mp[0] = mp[1] + mp[-1] = mp[-2] - return st, c + return st, c, mp ############################################################ @@ -341,83 +358,59 @@ def add_piece_wise_const_python(x1, y1, x2, y2): ############################################################ -# add_interval_sequence_python +# add_multiple_value_sequence_python ############################################################ -def add_interval_sequence_python(x1, y1, x2, y2): - yscale1 = np.empty_like(y1) - index2 = 1 - # s1 = (len(y1)+len(y2)-2.0) / (len(y1)-1.0) - # s2 = (len(y1)+len(y2)-2.0) / (len(y2)-1.0) - s1 = 1.0 - s2 = 1.0 - for i in xrange(len(y1)): - c = 1 - while index2 < len(x2)-1 and x2[index2] < x1[i+1]: - index2 += 1 - c += 1 - if index2 < len(x2)-1 and x2[index2] == x1[i+1]: - index2 += 1 - # c += 1 - yscale1[i] = s1/c - - yscale2 = np.empty_like(y2) - index1 = 1 - for i in xrange(len(y2)): - c = 1 - while index1 < len(x1)-1 and x1[index1] < x2[i+1]: - index1 += 1 - c += 1 - if index1 < len(x1)-1 and x1[index1] == x2[i+1]: - index1 += 1 - # c += 1 - yscale2[i] = s2/c +def add_multiple_value_sequence_python(x1, y1, mp1, x2, y2, mp2): x_new = np.empty(len(x1) + len(x2)) - y_new = np.empty(len(x_new)-1) + 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 - additional_intervals = 0 while (index1+1 < len(y1)) and (index2+1 < len(y2)): - y_new[index] = y1[index1]*yscale1[index1] + y2[index2]*yscale2[index2] - index += 1 - # print(index1+1, x1[index1+1], y1[index1+1], x_new[index]) 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] - # y_new[index] = y1[index1]*yscale1[index1] + \ - # y2[index2]*yscale2[index2] index1 += 1 - # x_new[index] = x1[index1] index2 += 1 - # index += 1 + index += 1 x_new[index] = x1[index1] - additional_intervals += 1 - y_new[index] = y1[index1]*yscale1[index1] + y2[index2]*yscale2[index2] + 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 < 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:]*yscale1[index1+1:] + y2[-1]*yscale2[-1] - index += len(x1)-index1-2 + y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] + mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] + index += len(x1)-index1-1 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:]*yscale2[index2+1:] + y1[-1]*yscale1[-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] + y_new[index+1:index+1+len(x2)-index2-1] = y2[index2+1:] + mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] + index += len(x2)-index2-1 + # 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] + + y_new[0] = y_new[1] + mp_new[0] = mp_new[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], additional_intervals + return x_new[:index+1], y_new[:index+1], mp_new[:index+1] ############################################################ diff --git a/pyspike/spikes.py b/pyspike/spikes.py index aa25c48..6a3353e 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -67,7 +67,7 @@ def spike_train_from_string(s, sep=' ', is_sorted=False): # load_spike_trains_txt ############################################################ def load_spike_trains_from_txt(file_name, time_interval=None, - separator=' ', comment='#', sort=True): + separator=' ', comment='#', is_sorted=False): """ Loads a number of spike trains from a text file. Each line of the text file should contain one spike train as a sequence of spike times separated by `separator`. Empty lines as well as lines starting with `comment` are @@ -94,7 +94,7 @@ def load_spike_trains_from_txt(file_name, time_interval=None, for line in spike_file: if len(line) > 1 and not line.startswith(comment): # use only the lines with actual data and not commented - spike_train = spike_train_from_string(line, separator, sort) + spike_train = spike_train_from_string(line, separator, is_sorted) if time_interval is not None: # add auxil. spikes if times given spike_train = add_auxiliary_spikes(spike_train, time_interval) spike_trains.append(spike_train) diff --git a/test/SPIKE_Sync_Test.txt b/test/SPIKE_Sync_Test.txt new file mode 100644 index 0000000..b97f777 --- /dev/null +++ b/test/SPIKE_Sync_Test.txt @@ -0,0 +1,100 @@ +61.000000 171.000000 268.000000 278.000000 326.000000 373.000000 400.000000 577.000000 793.000000 796.000000 798.000000 936.000000 994.000000 1026.000000 1083.000000 1097.000000 1187.000000 1228.000000 1400.000000 1522.000000 1554.000000 1579.000000 1661.000000 1895.000000 2040.000000 2082.000000 2264.000000 2502.000000 2689.000000 2922.000000 3093.000000 3276.000000 3495.000000 3693.000000 3900.000000 + +195.000000 400.000000 518.000000 522.000000 565.000000 569.000000 572.000000 630.000000 802.000000 938.000000 1095.000000 1198.000000 1222.000000 1316.000000 1319.000000 1328.000000 1382.000000 1505.000000 1631.000000 1662.000000 1676.000000 1708.000000 1803.000000 1947.000000 1999.000000 2129.000000 2332.000000 2466.000000 2726.000000 2896.000000 3102.000000 3316.000000 3505.000000 3707.000000 3900.000000 + +45.000000 111.000000 282.000000 319.000000 366.000000 400.000000 570.000000 633.000000 673.000000 750.000000 796.000000 1014.000000 1096.000000 1167.000000 1180.000000 1237.000000 1341.000000 1524.000000 1571.000000 1574.000000 1590.000000 1610.000000 1832.000000 1869.000000 1949.000000 1968.000000 2353.000000 2497.000000 2713.000000 2868.000000 3095.000000 3302.000000 3525.000000 3704.000000 3900.000000 + +60.000000 135.000000 204.000000 260.000000 297.000000 361.000000 364.000000 400.000000 438.000000 631.000000 787.000000 794.000000 908.000000 927.000000 1205.000000 1251.000000 1315.000000 1463.000000 1546.000000 1548.000000 1569.000000 1604.000000 1705.000000 1733.000000 1994.000000 2146.000000 2306.000000 2554.000000 2691.000000 2905.000000 3090.000000 3296.000000 3508.000000 3702.000000 3900.000000 + +159.000000 186.000000 308.000000 331.000000 400.000000 624.000000 758.000000 805.000000 811.000000 876.000000 1018.000000 1122.000000 1193.000000 1308.000000 1354.000000 1524.000000 1550.000000 1672.000000 1728.000000 1738.000000 1899.000000 1919.000000 1980.000000 1991.000000 2050.000000 2124.000000 2308.000000 2450.000000 2703.000000 2876.000000 3096.000000 3288.000000 3494.000000 3709.000000 3900.000000 + +23.000000 134.000000 278.000000 400.000000 443.000000 555.000000 589.000000 597.000000 750.000000 754.000000 807.000000 1207.000000 1302.000000 1386.000000 1390.000000 1391.000000 1411.000000 1467.000000 1564.000000 1621.000000 1672.000000 1707.000000 1752.000000 1889.000000 1983.000000 2026.000000 2277.000000 2527.000000 2685.000000 2903.000000 3124.000000 3326.000000 3499.000000 3695.000000 3900.000000 + +65.000000 120.000000 142.000000 200.000000 331.000000 400.000000 683.000000 701.000000 707.000000 788.000000 795.000000 880.000000 948.000000 972.000000 1197.000000 1282.000000 1354.000000 1451.000000 1543.000000 1549.000000 1555.000000 1624.000000 1723.000000 1766.000000 2023.000000 2071.000000 2285.000000 2478.000000 2766.000000 2883.000000 3100.000000 3292.000000 3502.000000 3700.000000 3900.000000 + +83.000000 122.000000 214.000000 354.000000 400.000000 505.000000 614.000000 621.000000 697.000000 788.000000 846.000000 871.000000 878.000000 1174.000000 1204.000000 1215.000000 1317.000000 1353.000000 1365.000000 1453.000000 1463.000000 1540.000000 1832.000000 2016.000000 2023.000000 2290.000000 2449.000000 2708.000000 2881.000000 3090.000000 3329.000000 3489.000000 3705.000000 3900.000000 + +56.000000 62.000000 143.000000 226.000000 259.000000 400.000000 439.000000 441.000000 569.000000 572.000000 639.000000 697.000000 808.000000 1162.000000 1178.000000 1250.000000 1360.000000 1427.000000 1598.000000 1667.000000 1671.000000 1780.000000 1865.000000 1902.000000 1972.000000 2092.000000 2318.000000 2548.000000 2741.000000 2888.000000 3096.000000 3304.000000 3518.000000 3705.000000 3900.000000 + +110.000000 116.000000 215.000000 400.000000 462.000000 542.000000 602.000000 614.000000 619.000000 795.000000 1166.000000 1196.000000 1240.000000 1252.000000 1268.000000 1295.000000 1405.000000 1561.000000 1597.000000 1725.000000 1750.000000 1759.000000 1877.000000 1948.000000 2053.000000 2119.000000 2339.000000 2527.000000 2672.000000 2874.000000 3137.000000 3312.000000 3488.000000 3698.000000 3900.000000 + +141.000000 159.000000 332.000000 333.000000 400.000000 756.000000 801.000000 843.000000 1161.000000 1208.000000 1225.000000 1246.000000 1418.000000 1448.000000 1501.000000 1559.000000 1578.000000 1684.000000 1751.000000 1797.000000 1815.000000 1818.000000 1948.000000 1975.000000 1989.000000 2110.000000 2360.000000 2453.000000 2704.000000 2906.000000 3106.000000 3286.000000 3491.000000 3697.000000 3900.000000 + +61.000000 145.000000 151.000000 340.000000 400.000000 642.000000 741.000000 801.000000 901.000000 912.000000 939.000000 1072.000000 1180.000000 1216.000000 1271.000000 1336.000000 1344.000000 1584.000000 1608.000000 1617.000000 1648.000000 1695.000000 1789.000000 1835.000000 2053.000000 2089.000000 2223.000000 2531.000000 2688.000000 2901.000000 3114.000000 3268.000000 3496.000000 3703.000000 3900.000000 + +313.000000 378.000000 400.000000 548.000000 657.000000 691.000000 715.000000 728.000000 802.000000 813.000000 1092.000000 1203.000000 1237.000000 1388.000000 1562.000000 1566.000000 1573.000000 1659.000000 1781.000000 1788.000000 1821.000000 1825.000000 1835.000000 1985.000000 1993.000000 2152.000000 2308.000000 2492.000000 2681.000000 2890.000000 3101.000000 3305.000000 3492.000000 3694.000000 3900.000000 + +244.000000 311.000000 392.000000 400.000000 431.000000 441.000000 562.000000 577.000000 632.000000 791.000000 818.000000 875.000000 1020.000000 1059.000000 1134.000000 1164.000000 1201.000000 1238.000000 1273.000000 1387.000000 1562.000000 1609.000000 1831.000000 1949.000000 1961.000000 2088.000000 2329.000000 2509.000000 2691.000000 2902.000000 3096.000000 3279.000000 3506.000000 3704.000000 3900.000000 + +11.000000 111.000000 159.000000 277.000000 334.000000 400.000000 480.000000 646.000000 804.000000 1122.000000 1129.000000 1178.000000 1198.000000 1233.000000 1359.000000 1374.000000 1411.000000 1476.000000 1477.000000 1571.000000 1582.000000 1622.000000 1706.000000 1867.000000 1988.000000 2094.000000 2233.000000 2512.000000 2671.000000 2931.000000 3111.000000 3292.000000 3488.000000 3691.000000 3900.000000 + +57.000000 114.000000 328.000000 400.000000 442.000000 582.000000 662.000000 752.000000 766.000000 795.000000 1035.000000 1115.000000 1204.000000 1242.000000 1261.000000 1277.000000 1295.000000 1300.000000 1333.000000 1398.000000 1571.000000 1594.000000 1743.000000 1765.000000 2076.000000 2094.000000 2319.000000 2518.000000 2683.000000 2933.000000 3109.000000 3317.000000 3492.000000 3696.000000 3900.000000 + +92.000000 102.000000 111.000000 190.000000 400.000000 446.000000 478.000000 630.000000 631.000000 805.000000 823.000000 918.000000 985.000000 1199.000000 1209.000000 1217.000000 1355.000000 1466.000000 1503.000000 1563.000000 1582.000000 1636.000000 1819.000000 1944.000000 1977.000000 2014.000000 2359.000000 2428.000000 2728.000000 2868.000000 3101.000000 3296.000000 3509.000000 3708.000000 3900.000000 + +34.000000 66.000000 70.000000 113.000000 135.000000 238.000000 284.000000 400.000000 528.000000 766.000000 805.000000 921.000000 994.000000 1045.000000 1137.000000 1180.000000 1193.000000 1481.000000 1625.000000 1660.000000 1699.000000 1764.000000 1809.000000 1861.000000 1967.000000 2095.000000 2267.000000 2518.000000 2719.000000 2885.000000 3081.000000 3252.000000 3484.000000 3705.000000 3900.000000 + +65.000000 90.000000 123.000000 199.000000 330.000000 400.000000 805.000000 1005.000000 1035.000000 1044.000000 1064.000000 1138.000000 1155.000000 1205.000000 1217.000000 1248.000000 1318.000000 1345.000000 1403.000000 1567.000000 1609.000000 1781.000000 1875.000000 1929.000000 2024.000000 2140.000000 2258.000000 2477.000000 2747.000000 2890.000000 3120.000000 3325.000000 3510.000000 3708.000000 3900.000000 + +70.000000 221.000000 280.000000 400.000000 489.000000 786.000000 1016.000000 1027.000000 1029.000000 1145.000000 1186.000000 1195.000000 1256.000000 1304.000000 1314.000000 1476.000000 1618.000000 1657.000000 1730.000000 1748.000000 1802.000000 1812.000000 1832.000000 1947.000000 1999.000000 2027.000000 2288.000000 2532.000000 2679.000000 2919.000000 3077.000000 3316.000000 3516.000000 3705.000000 3900.000000 + +153.000000 400.000000 474.000000 532.000000 568.000000 693.000000 738.000000 798.000000 806.000000 949.000000 1077.000000 1083.000000 1098.000000 1169.000000 1172.000000 1192.000000 1517.000000 1530.000000 1538.000000 1560.000000 1582.000000 1699.000000 1981.000000 1982.000000 2171.000000 2312.000000 2475.000000 2680.000000 2887.000000 3119.000000 3300.000000 3502.000000 3701.000000 3900.000000 + +92.000000 152.000000 164.000000 400.000000 520.000000 619.000000 621.000000 647.000000 648.000000 808.000000 853.000000 865.000000 920.000000 949.000000 1148.000000 1225.000000 1231.000000 1348.000000 1375.000000 1635.000000 1646.000000 1686.000000 1711.000000 2004.000000 2079.000000 2347.000000 2501.000000 2709.000000 2930.000000 3061.000000 3319.000000 3494.000000 3690.000000 3900.000000 + +74.000000 103.000000 247.000000 265.000000 400.000000 495.000000 501.000000 534.000000 552.000000 557.000000 601.000000 604.000000 792.000000 1003.000000 1138.000000 1195.000000 1252.000000 1325.000000 1336.000000 1425.000000 1646.000000 1657.000000 1795.000000 1990.000000 1992.000000 2062.000000 2300.000000 2509.000000 2690.000000 2913.000000 3066.000000 3276.000000 3460.000000 3700.000000 3900.000000 + +45.000000 90.000000 156.000000 400.000000 468.000000 523.000000 577.000000 583.000000 708.000000 797.000000 815.000000 1052.000000 1063.000000 1189.000000 1215.000000 1218.000000 1266.000000 1288.000000 1299.000000 1512.000000 1519.000000 1584.000000 1769.000000 1791.000000 1964.000000 2082.000000 2348.000000 2530.000000 2703.000000 2893.000000 3031.000000 3290.000000 3504.000000 3702.000000 3900.000000 + +140.000000 269.000000 400.000000 475.000000 492.000000 520.000000 569.000000 645.000000 727.000000 794.000000 819.000000 834.000000 957.000000 1122.000000 1210.000000 1374.000000 1471.000000 1485.000000 1515.000000 1574.000000 1668.000000 1732.000000 1743.000000 1917.000000 2041.000000 2104.000000 2294.000000 2453.000000 2662.000000 2894.000000 3128.000000 3301.000000 3489.000000 3705.000000 3900.000000 + +28.000000 96.000000 112.000000 400.000000 426.000000 477.000000 584.000000 763.000000 804.000000 815.000000 1089.000000 1175.000000 1218.000000 1366.000000 1394.000000 1506.000000 1553.000000 1564.000000 1592.000000 1712.000000 1755.000000 1788.000000 1814.000000 1816.000000 1997.000000 2072.000000 2345.000000 2487.000000 2741.000000 2881.000000 3074.000000 3310.000000 3521.000000 3707.000000 3900.000000 + +215.000000 286.000000 400.000000 461.000000 488.000000 489.000000 768.000000 796.000000 885.000000 919.000000 1188.000000 1253.000000 1432.000000 1476.000000 1521.000000 1524.000000 1566.000000 1590.000000 1684.000000 1714.000000 1733.000000 1776.000000 1816.000000 1943.000000 2016.000000 2031.000000 2308.000000 2488.000000 2642.000000 2832.000000 3120.000000 3293.000000 3507.000000 3702.000000 3900.000000 + +77.000000 229.000000 302.000000 369.000000 400.000000 401.000000 404.000000 418.000000 804.000000 1026.000000 1110.000000 1179.000000 1187.000000 1227.000000 1456.000000 1458.000000 1476.000000 1629.000000 1630.000000 1640.000000 1697.000000 1734.000000 1785.000000 1919.000000 1956.000000 2057.000000 2324.000000 2416.000000 2656.000000 2889.000000 3126.000000 3323.000000 3491.000000 3696.000000 3900.000000 + +244.000000 302.000000 400.000000 455.000000 533.000000 562.000000 673.000000 748.000000 791.000000 1120.000000 1136.000000 1191.000000 1235.000000 1238.000000 1296.000000 1336.000000 1447.000000 1466.000000 1551.000000 1594.000000 1691.000000 1744.000000 1897.000000 1959.000000 2060.000000 2109.000000 2230.000000 2564.000000 2717.000000 2900.000000 3089.000000 3320.000000 3491.000000 3712.000000 3900.000000 + +3.000000 196.000000 199.000000 320.000000 339.000000 358.000000 400.000000 495.000000 690.000000 737.000000 760.000000 791.000000 849.000000 1027.000000 1194.000000 1220.000000 1242.000000 1313.000000 1354.000000 1435.000000 1523.000000 1621.000000 1775.000000 1788.000000 1999.000000 2074.000000 2245.000000 2478.000000 2750.000000 2893.000000 3113.000000 3302.000000 3485.000000 3690.000000 3900.000000 + +206.000000 234.000000 261.000000 277.000000 341.000000 374.000000 400.000000 465.000000 613.000000 672.000000 745.000000 793.000000 799.000000 917.000000 954.000000 1144.000000 1180.000000 1283.000000 1484.000000 1574.000000 1575.000000 1795.000000 1965.000000 1984.000000 2086.000000 2093.000000 2312.000000 2501.000000 2738.000000 2879.000000 3084.000000 3270.000000 3483.000000 3701.000000 3900.000000 + +154.000000 314.000000 400.000000 611.000000 615.000000 795.000000 823.000000 869.000000 908.000000 938.000000 960.000000 1024.000000 1049.000000 1068.000000 1185.000000 1420.000000 1441.000000 1496.000000 1610.000000 1709.000000 1712.000000 1740.000000 1885.000000 1917.000000 1992.000000 2079.000000 2224.000000 2508.000000 2713.000000 2861.000000 3096.000000 3300.000000 3509.000000 3696.000000 3900.000000 + +26.000000 51.000000 83.000000 121.000000 343.000000 400.000000 625.000000 695.000000 697.000000 783.000000 803.000000 933.000000 1014.000000 1135.000000 1158.000000 1210.000000 1548.000000 1589.000000 1662.000000 1663.000000 1674.000000 1677.000000 1733.000000 1801.000000 1978.000000 2027.000000 2276.000000 2477.000000 2687.000000 2946.000000 3108.000000 3293.000000 3503.000000 3702.000000 3900.000000 + +21.000000 39.000000 125.000000 198.000000 254.000000 400.000000 456.000000 510.000000 806.000000 881.000000 920.000000 1000.000000 1046.000000 1067.000000 1129.000000 1143.000000 1188.000000 1438.000000 1552.000000 1603.000000 1754.000000 1761.000000 1943.000000 1960.000000 1980.000000 2068.000000 2246.000000 2544.000000 2731.000000 2923.000000 3060.000000 3271.000000 3517.000000 3700.000000 3900.000000 + +166.000000 237.000000 295.000000 300.000000 319.000000 369.000000 400.000000 407.000000 413.000000 428.000000 439.000000 804.000000 831.000000 899.000000 971.000000 1164.000000 1199.000000 1259.000000 1331.000000 1497.000000 1564.000000 1832.000000 1881.000000 1915.000000 1970.000000 2189.000000 2271.000000 2482.000000 2742.000000 2863.000000 3116.000000 3293.000000 3492.000000 3705.000000 3900.000000 + +298.000000 323.000000 400.000000 423.000000 526.000000 662.000000 799.000000 821.000000 830.000000 933.000000 989.000000 1190.000000 1200.000000 1227.000000 1251.000000 1306.000000 1543.000000 1574.000000 1589.000000 1690.000000 1697.000000 1849.000000 1938.000000 1951.000000 2027.000000 2059.000000 2315.000000 2456.000000 2703.000000 2944.000000 3103.000000 3307.000000 3497.000000 3693.000000 3900.000000 + +60.000000 172.000000 400.000000 413.000000 420.000000 600.000000 660.000000 690.000000 752.000000 789.000000 951.000000 1056.000000 1176.000000 1201.000000 1290.000000 1440.000000 1450.000000 1456.000000 1638.000000 1653.000000 1703.000000 1710.000000 1730.000000 1856.000000 2006.000000 2082.000000 2296.000000 2383.000000 2693.000000 2887.000000 3091.000000 3299.000000 3485.000000 3691.000000 3900.000000 + +20.000000 127.000000 326.000000 369.000000 400.000000 521.000000 588.000000 595.000000 700.000000 798.000000 799.000000 858.000000 913.000000 1101.000000 1193.000000 1379.000000 1432.000000 1440.000000 1482.000000 1486.000000 1575.000000 1577.000000 1792.000000 1820.000000 1957.000000 2097.000000 2309.000000 2493.000000 2639.000000 2854.000000 3109.000000 3294.000000 3488.000000 3713.000000 3900.000000 + +65.000000 119.000000 362.000000 400.000000 779.000000 803.000000 804.000000 897.000000 938.000000 984.000000 1147.000000 1207.000000 1266.000000 1319.000000 1373.000000 1579.000000 1596.000000 1626.000000 1644.000000 1650.000000 1725.000000 1776.000000 1851.000000 1965.000000 2023.000000 2116.000000 2331.000000 2552.000000 2727.000000 2855.000000 3081.000000 3268.000000 3521.000000 3698.000000 3900.000000 + +4.000000 10.000000 50.000000 124.000000 151.000000 169.000000 314.000000 317.000000 400.000000 474.000000 549.000000 630.000000 704.000000 798.000000 1030.000000 1144.000000 1155.000000 1188.000000 1345.000000 1390.000000 1428.000000 1603.000000 1867.000000 1902.000000 1922.000000 1995.000000 2290.000000 2431.000000 2679.000000 2886.000000 3092.000000 3305.000000 3501.000000 3704.000000 3900.000000 + +31.000000 37.000000 44.000000 211.000000 400.000000 445.000000 454.000000 602.000000 641.000000 760.000000 802.000000 850.000000 945.000000 1079.000000 1104.000000 1149.000000 1201.000000 1305.000000 1537.000000 1568.000000 1613.000000 1702.000000 1805.000000 1958.000000 1969.000000 2112.000000 2300.000000 2532.000000 2680.000000 2952.000000 3124.000000 3303.000000 3500.000000 3695.000000 3900.000000 + +43.000000 259.000000 276.000000 342.000000 362.000000 375.000000 380.000000 400.000000 674.000000 800.000000 804.000000 809.000000 882.000000 947.000000 952.000000 1219.000000 1351.000000 1504.000000 1568.000000 1593.000000 1720.000000 1752.000000 1871.000000 1961.000000 2022.000000 2046.000000 2254.000000 2486.000000 2651.000000 2868.000000 3103.000000 3278.000000 3482.000000 3708.000000 3900.000000 + +1.000000 219.000000 227.000000 235.000000 241.000000 400.000000 606.000000 618.000000 645.000000 738.000000 797.000000 943.000000 1217.000000 1343.000000 1424.000000 1448.000000 1578.000000 1661.000000 1706.000000 1765.000000 1903.000000 1915.000000 1975.000000 1987.000000 2084.000000 2324.000000 2490.000000 2671.000000 2865.000000 3063.000000 3331.000000 3505.000000 3702.000000 3900.000000 + +103.000000 109.000000 356.000000 357.000000 400.000000 501.000000 714.000000 788.000000 793.000000 810.000000 859.000000 974.000000 1109.000000 1172.000000 1238.000000 1252.000000 1291.000000 1319.000000 1479.000000 1559.000000 1598.000000 1678.000000 1753.000000 1768.000000 1940.000000 2100.000000 2331.000000 2600.000000 2758.000000 2889.000000 3073.000000 3292.000000 3487.000000 3707.000000 3900.000000 + +234.000000 362.000000 388.000000 399.000000 400.000000 407.000000 452.000000 483.000000 692.000000 721.000000 797.000000 809.000000 863.000000 1216.000000 1227.000000 1338.000000 1445.000000 1473.000000 1536.000000 1596.000000 1608.000000 1619.000000 1914.000000 1990.000000 2052.000000 2117.000000 2316.000000 2488.000000 2682.000000 2918.000000 3104.000000 3299.000000 3506.000000 3696.000000 3900.000000 + +31.000000 91.000000 400.000000 422.000000 545.000000 587.000000 751.000000 794.000000 828.000000 962.000000 963.000000 1032.000000 1073.000000 1166.000000 1174.000000 1188.000000 1320.000000 1423.000000 1462.000000 1589.000000 1625.000000 1677.000000 1706.000000 1939.000000 2023.000000 2103.000000 2292.000000 2507.000000 2745.000000 2921.000000 3088.000000 3297.000000 3506.000000 3698.000000 3900.000000 + +35.000000 92.000000 237.000000 296.000000 400.000000 515.000000 601.000000 613.000000 798.000000 852.000000 1201.000000 1248.000000 1257.000000 1286.000000 1429.000000 1616.000000 1633.000000 1656.000000 1778.000000 1819.000000 1838.000000 1864.000000 1903.000000 1918.000000 1991.000000 2106.000000 2315.000000 2455.000000 2690.000000 2891.000000 3084.000000 3280.000000 3488.000000 3698.000000 3900.000000 + +20.000000 25.000000 172.000000 223.000000 274.000000 295.000000 368.000000 372.000000 400.000000 493.000000 717.000000 775.000000 795.000000 1015.000000 1200.000000 1319.000000 1444.000000 1559.000000 1592.000000 1694.000000 1743.000000 1757.000000 1841.000000 1859.000000 2043.000000 2075.000000 2336.000000 2461.000000 2764.000000 2905.000000 3099.000000 3293.000000 3494.000000 3697.000000 3900.000000 + +76.000000 120.000000 130.000000 209.000000 396.000000 400.000000 559.000000 572.000000 671.000000 726.000000 803.000000 907.000000 1011.000000 1128.000000 1208.000000 1232.000000 1321.000000 1337.000000 1531.000000 1600.000000 1702.000000 1777.000000 1824.000000 1862.000000 1988.000000 1999.000000 2352.000000 2537.000000 2750.000000 2957.000000 3102.000000 3291.000000 3503.000000 3701.000000 3900.000000 + +135.000000 250.000000 302.000000 310.000000 393.000000 400.000000 417.000000 684.000000 730.000000 804.000000 981.000000 982.000000 1081.000000 1197.000000 1240.000000 1313.000000 1409.000000 1431.000000 1473.000000 1498.000000 1561.000000 1615.000000 1782.000000 1925.000000 1979.000000 2149.000000 2293.000000 2490.000000 2676.000000 2932.000000 3117.000000 3315.000000 3502.000000 3690.000000 3900.000000 + diff --git a/test/test_distance.py b/test/test_distance.py index d98069d..4f8f6e8 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -135,53 +135,23 @@ def test_spike_sync(): spikes2 = np.array([2.1]) spikes1 = spk.add_auxiliary_spikes(spikes1, 4.0) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - for k in xrange(1, 3): - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), - 0.5, decimal=16) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + 0.5, decimal=16) spikes2 = np.array([3.1]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - for k in xrange(1, 3): - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), - 0.5, decimal=16) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + 0.5, decimal=16) spikes2 = np.array([1.1]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - for k in xrange(1, 3): - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), - 0.5, decimal=16) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + 0.5, decimal=16) spikes2 = np.array([0.9]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - for k in xrange(1, 3): - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), - 0.5, decimal=16) - - spikes1 = np.array([100, 300, 400, 405, 410, 500, 700, 800, - 805, 810, 815, 900]) - spikes2 = np.array([100, 200, 205, 210, 295, 350, 400, 510, - 600, 605, 700, 910]) - spikes3 = np.array([100, 180, 198, 295, 412, 420, 510, 640, - 695, 795, 820, 920]) - spikes1 = spk.add_auxiliary_spikes(spikes1, 1000) - spikes2 = spk.add_auxiliary_spikes(spikes2, 1000) - spikes3 = spk.add_auxiliary_spikes(spikes3, 1000) - for k in xrange(1, 10): - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2, k=k), - 0.5, decimal=15) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes3, k=k), - 0.5, decimal=15) - assert_almost_equal(spk.spike_sync_distance(spikes2, spikes3, k=k), - 0.5, decimal=15) - - f1 = spk.spike_sync_profile(spikes1, spikes2, k=1) - f2 = spk.spike_sync_profile(spikes1, spikes3, k=1) - f3 = spk.spike_sync_profile(spikes2, spikes3, k=1) - f = spk.spike_sync_profile_multi([spikes1, spikes2, spikes3], k=1) - # hands on definition of the average multivariate spike synchronization - expected = (f1.integral() + f2.integral() + f3.integral()) / \ - (len(f1.y)+len(f2.y)+len(f3.y)-3) - assert_almost_equal(f.avrg(), expected, decimal=15) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + 0.5, decimal=16) def check_multi_profile(profile_func, profile_func_multi): @@ -226,6 +196,39 @@ def test_multi_spike(): check_multi_profile(spk.spike_profile, spk.spike_profile_multi) +def test_multi_spike_sync(): + # some basic multivariate check + spikes1 = np.array([100, 300, 400, 405, 410, 500, 700, 800, + 805, 810, 815, 900]) + spikes2 = np.array([100, 200, 205, 210, 295, 350, 400, 510, + 600, 605, 700, 910]) + spikes3 = np.array([100, 180, 198, 295, 412, 420, 510, 640, + 695, 795, 820, 920]) + spikes1 = spk.add_auxiliary_spikes(spikes1, 1000) + spikes2 = spk.add_auxiliary_spikes(spikes2, 1000) + spikes3 = spk.add_auxiliary_spikes(spikes3, 1000) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + 0.5, decimal=15) + assert_almost_equal(spk.spike_sync_distance(spikes1, spikes3), + 0.5, decimal=15) + assert_almost_equal(spk.spike_sync_distance(spikes2, spikes3), + 0.5, decimal=15) + + f = spk.spike_sync_profile_multi([spikes1, spikes2, spikes3]) + # hands on definition of the average multivariate spike synchronization + # expected = (f1.integral() + f2.integral() + f3.integral()) / \ + # (np.sum(f1.mp[1:-1])+np.sum(f2.mp[1:-1])+np.sum(f3.mp[1:-1])) + expected = 0.5 + assert_almost_equal(f.avrg(), expected, decimal=15) + + # multivariate regression test + spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", + time_interval=(0, 4000)) + f = spk.spike_sync_profile_multi(spike_trains) + assert_equal(np.sum(f.y[1:-1]), 39932) + assert_equal(np.sum(f.mp[1:-1]), 85554) + + def check_dist_matrix(dist_func, dist_matrix_func): # generate spike trains: t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) -- cgit v1.2.3 From e0a3b5468364342d4468e07029e4daf2cacfd6b9 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 19 Jan 2015 17:25:46 +0100 Subject: cython implementation of spike-sync --- pyspike/cython_distance.pyx | 81 +++++++++++++++++++++++++++++++++++++++++++++ pyspike/distances.py | 6 ++-- test/test_distance.py | 6 ++-- 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx index 779ff94..489aab9 100644 --- a/pyspike/cython_distance.pyx +++ b/pyspike/cython_distance.pyx @@ -33,6 +33,7 @@ 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 @@ -229,3 +230,83 @@ def spike_distance_cython(double[:] t1, # 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] + + + +############################################################ +# coincidence_python +############################################################ +cdef inline double get_tau(double[:] spikes1, double[:] spikes2, int i, int j): + cdef double m = 1E100 # some huge number + cdef int N1 = len(spikes1)-2 + cdef int N2 = len(spikes2)-2 + if i < N1: + m = fmin(m, spikes1[i+1]-spikes1[i]) + if j < N2: + m = fmin(m, spikes2[j+1]-spikes2[j]) + if i > 1: + m = fmin(m, spikes1[i]-spikes1[i-1]) + if j > 1: + m = fmin(m, spikes2[j]-spikes2[j-1]) + return 0.5*m + + +############################################################ +# coincidence_cython +############################################################ +def coincidence_cython(double[:] spikes1, double[:] spikes2): + + cdef int N1 = len(spikes1) + cdef int N2 = len(spikes2) + cdef int i = 0 + cdef int j = 0 + 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 tau + while n < N1 + N2 - 2: + if spikes1[i+1] < spikes2[j+1]: + i += 1 + n += 1 + tau = get_tau(spikes1, spikes2, i, j) + st[n] = spikes1[i] + if j > 0 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 spikes1[i+1] > spikes2[j+1]: + j += 1 + n += 1 + tau = get_tau(spikes1, spikes2, i, j) + st[n] = spikes2[j] + if i > 0 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 + if i == N1-1 or j == N2-1: + break + 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] = spikes1[0] + st[len(st)-1] = spikes1[len(spikes1)-1] + c[0] = c[1] + c[len(c)-1] = c[len(c)-2] + mp[0] = mp[1] + mp[len(mp)-1] = mp[len(mp)-2] + + return st, c, mp diff --git a/pyspike/distances.py b/pyspike/distances.py index 5ee8261..8bde724 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -139,9 +139,9 @@ def spike_sync_profile(spikes1, spikes2): from cython_distance import coincidence_cython \ as coincidence_impl except ImportError: -# print("Warning: spike_distance_cython not found. Make sure that \ -# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -# Falling back to slow python backend.") + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") # use python backend from python_backend import coincidence_python \ as coincidence_impl diff --git a/test/test_distance.py b/test/test_distance.py index 4f8f6e8..6bdb049 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -199,11 +199,11 @@ def test_multi_spike(): def test_multi_spike_sync(): # some basic multivariate check spikes1 = np.array([100, 300, 400, 405, 410, 500, 700, 800, - 805, 810, 815, 900]) + 805, 810, 815, 900], dtype=float) spikes2 = np.array([100, 200, 205, 210, 295, 350, 400, 510, - 600, 605, 700, 910]) + 600, 605, 700, 910], dtype=float) spikes3 = np.array([100, 180, 198, 295, 412, 420, 510, 640, - 695, 795, 820, 920]) + 695, 795, 820, 920], dtype=float) spikes1 = spk.add_auxiliary_spikes(spikes1, 1000) spikes2 = spk.add_auxiliary_spikes(spikes2, 1000) spikes3 = spk.add_auxiliary_spikes(spikes3, 1000) -- cgit v1.2.3 From 66968eedd276eb5d661b25d92775203546a3d646 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 19 Jan 2015 17:58:04 +0100 Subject: renamed IntervalSequence -> DiscreteFunction, cython implementation of add --- pyspike/__init__.py | 2 +- pyspike/cython_add.pyx | 59 ++++++++++ pyspike/distances.py | 6 +- pyspike/function.py | 269 +++++++++++++++++++++++----------------------- pyspike/python_backend.py | 113 +++++++++---------- 5 files changed, 255 insertions(+), 194 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 74d52c5..1b18569 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -7,7 +7,7 @@ Distributed under the BSD License __all__ = ["function", "distances", "spikes"] from function import PieceWiseConstFunc, PieceWiseLinFunc, \ - MultipleValueSequence, average_profile + DiscreteFunction, average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ spike_sync_profile, spike_sync_distance, \ diff --git a/pyspike/cython_add.pyx b/pyspike/cython_add.pyx index bfbe208..817799e 100644 --- a/pyspike/cython_add.pyx +++ b/pyspike/cython_add.pyx @@ -172,3 +172,62 @@ def add_piece_wise_lin_cython(double[:] x1, double[:] y11, double[:] y12, return (np.array(x_new[:index+2]), np.array(y1_new[:index+1]), np.array(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) + cdef int N2 = len(y2) + 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 < len(y1): + x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] + y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] + mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] + index += len(x1)-index1-1 + 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(x2)-index2-1] = y2[index2+1:] + mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] + index += len(x2)-index2-1 + # 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] + + 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] diff --git a/pyspike/distances.py b/pyspike/distances.py index 8bde724..0f0efa9 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -11,7 +11,7 @@ import numpy as np import threading from functools import partial -from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, MultipleValueSequence +from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, DiscreteFunction ############################################################ @@ -148,7 +148,7 @@ Falling back to slow python backend.") times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) - return MultipleValueSequence(times, coincidences, multiplicity) + return DiscreteFunction(times, coincidences, multiplicity) ############################################################ @@ -328,7 +328,7 @@ def spike_sync_profile_multi(spike_trains, indices=None): if None all given spike trains are used (default=None) :type indices: list or None :returns: The averaged spike profile :math:`(t)` - :rtype: :class:`pyspike.function.PieceWiseConstFunc` + :rtype: :class:`pyspike.function.DiscreteFunction` """ prof_func = partial(spike_sync_profile) diff --git a/pyspike/function.py b/pyspike/function.py index f10c136..6fb7537 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -170,140 +170,6 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ self.y *= fac -############################################################## -# MultipleValueSequence -############################################################## -class MultipleValueSequence(object): - """ A class representing a sequence of values defined in some interval. - """ - - def __init__(self, x, y, multiplicity): - """ Constructs the value sequence. - - :param x: array of length N defining the points at which the values are - defined. - :param y: array of length N degining the values at the points x. - :param multiplicity: array of length N defining the multiplicity of the - values. - """ - # convert parameters to arrays, also ensures copying - self.x = np.array(x) - self.y = np.array(y) - self.mp = np.array(multiplicity) - - def copy(self): - """ Returns a copy of itself - - :rtype: :class:`IntervalSequence` - """ - return MultipleValueSequence(self.x, self.y, self.mp) - - def almost_equal(self, other, decimal=14): - """ Checks if the function is equal to another function up to `decimal` - precision. - - :param other: another :class:`IntervalSequence` - :returns: True if the two functions are equal up to `decimal` decimals, - False otherwise - :rtype: bool - """ - eps = 10.0**(-decimal) - return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ - np.allclose(self.y, other.y, atol=eps, rtol=0.0) and \ - np.allclose(self.mp, other.mp, atol=eps, rtol=0.0) - - def get_plottable_data(self, k=0): - """ Returns two arrays containing x- and y-coordinates for immeditate - plotting of the interval sequence. - - :returns: (x_plot, y_plot) containing plottable data - :rtype: pair of np.array - - Example:: - - x, y = f.get_plottable_data() - plt.plot(x, y, '-o', label="Piece-wise const function") - """ - - if k > 0: - raise NotImplementedError() - - return 1.0*self.x, 1.0*self.y/self.mp - - def integral(self, interval=None): - """ Returns the integral over the given interval. For the interval - sequence this amounts to the sum over all values divided by the count - of intervals. - - :param interval: integration interval given as a pair of floats, if - None the integral over the whole function is computed. - :type interval: Pair of floats or None. - :returns: the integral - :rtype: float - """ - if interval is None: - # no interval given, integrate over the whole spike train - # don't count the first value, which is zero by definition - a = 1.0*np.sum(self.y[1:-1]) - else: - raise NotImplementedError() - return a - - def avrg(self, interval=None): - """ Computes the average of the interval sequence: - :math:`a = 1/N sum f_n ` where N is the number of intervals. - - :param interval: averaging interval given as a pair of floats, a - sequence of pairs for averaging multiple intervals, or - None, if None the average over the whole function is - computed. - :type interval: Pair, sequence of pairs, or None. - :returns: the average a. - :rtype: float - """ - if interval is None: - # no interval given, average over the whole spike train - # don't count the first interval for normalization - return self.integral() / np.sum(self.mp[1:-1]) - else: - raise NotImplementedError() - - def add(self, f): - """ Adds another `MultipleValueSequence` function to this function. - Note: only functions defined on the same interval can be summed. - - :param f: :class:`MultipleValueSequence` function to be added. - :rtype: None - """ - assert self.x[0] == f.x[0], "The functions have different intervals" - assert self.x[-1] == f.x[-1], "The functions have different intervals" - - # cython version - try: - from cython_add import add_interval_sequence_cython as \ - add_interval_sequence_impl - except ImportError: -# print("Warning: add_piece_wise_const_cython not found. Make sure \ -# that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ -# \n Falling back to slow python backend.") - # use python backend - from python_backend import add_multiple_value_sequence_python as \ - add_multiple_value_sequence_impl - - self.x, self.y, self.mp = \ - add_multiple_value_sequence_impl(self.x, self.y, self.mp, - f.x, f.y, f.mp) - - def mul_scalar(self, fac): - """ Multiplies the function with a scalar value - - :param fac: Value to multiply - :type fac: double - :rtype: None - """ - self.y *= fac - - ############################################################## # PieceWiseLinFunc ############################################################## @@ -489,6 +355,141 @@ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ self.y2 *= fac +############################################################## +# DiscreteFunction +############################################################## +class DiscreteFunction(object): + """ A class representing values defined on a discrete set of points. + """ + + def __init__(self, x, y, multiplicity): + """ Constructs the discrete function. + + :param x: array of length N defining the points at which the values are + defined. + :param y: array of length N degining the values at the points x. + :param multiplicity: array of length N defining the multiplicity of the + values. + """ + # convert parameters to arrays, also ensures copying + self.x = np.array(x) + self.y = np.array(y) + self.mp = np.array(multiplicity) + + def copy(self): + """ Returns a copy of itself + + :rtype: :class:`DiscreteFunction` + """ + return DiscreteFunction(self.x, self.y, self.mp) + + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + + :param other: another :class:`DiscreteFunction` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y, other.y, atol=eps, rtol=0.0) and \ + np.allclose(self.mp, other.mp, atol=eps, rtol=0.0) + + def get_plottable_data(self, k=0): + """ Returns two arrays containing x- and y-coordinates for immeditate + plotting of the interval sequence. + + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Discrete function") + """ + + if k > 0: + raise NotImplementedError() + + return 1.0*self.x, 1.0*self.y/self.mp + + def integral(self, interval=None): + """ Returns the integral over the given interval. For the interval + sequence this amounts to the sum over all values divided by the count + of intervals. + + :param interval: integration interval given as a pair of floats, if + None the integral over the whole function is computed. + :type interval: Pair of floats or None. + :returns: the integral + :rtype: float + """ + if interval is None: + # no interval given, integrate over the whole spike train + # don't count the first value, which is zero by definition + a = 1.0*np.sum(self.y[1:-1]) + else: + raise NotImplementedError() + return a + + def avrg(self, interval=None): + """ Computes the average of the interval sequence: + :math:`a = 1/N sum f_n ` where N is the number of intervals. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + """ + if interval is None: + # no interval given, average over the whole spike train + # don't count the first interval for normalization + return self.integral() / np.sum(self.mp[1:-1]) + else: + raise NotImplementedError() + + def add(self, f): + """ Adds another `DiscreteFunction` function to this function. + Note: only functions defined on the same interval can be summed. + + :param f: :class:`DiscreteFunction` function to be added. + :rtype: None + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + + # cython version + try: + from cython_add import add_discrete_function_cython as \ + add_discrete_function_impl + except ImportError: + print("Warning: add_discrete_function_cython not found. Make \ +sure that PySpike is installed by running\n\ +'python setup.py build_ext --inplace'! \ +\n Falling back to slow python backend.") + # use python backend + from python_backend import add_discrete_function_python as \ + add_discrete_function_impl + + self.x, self.y, self.mp = \ + add_discrete_function_impl(self.x, self.y, self.mp, + f.x, f.y, f.mp) + + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + + :param fac: Value to multiply + :type fac: double + :rtype: None + """ + self.y *= fac + + def average_profile(profiles): """ Computes the average profile from the given ISI- or SPIKE-profiles. diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py index bbbd572..481daf9 100644 --- a/pyspike/python_backend.py +++ b/pyspike/python_backend.py @@ -357,62 +357,6 @@ def add_piece_wise_const_python(x1, y1, x2, y2): return x_new[:index+2], y_new[:index+1] -############################################################ -# add_multiple_value_sequence_python -############################################################ -def add_multiple_value_sequence_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 - while (index1+1 < len(y1)) and (index2+1 < len(y2)): - 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 < len(y1): - x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] - y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] - mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] - index += len(x1)-index1-1 - 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(x2)-index2-1] = y2[index2+1:] - mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] - index += len(x2)-index2-1 - # 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] - - 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] - - ############################################################ # add_piece_lin_const_python ############################################################ @@ -482,3 +426,60 @@ def add_piece_wise_lin_python(x1, y11, y12, x2, y21, y22): 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 + while (index1+1 < len(y1)) and (index2+1 < len(y2)): + 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 < len(y1): + x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] + y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] + mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] + index += len(x1)-index1-1 + 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(x2)-index2-1] = y2[index2+1:] + mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] + index += len(x2)-index2-1 + # 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] + + 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] + -- cgit v1.2.3 From 6c0f966649c8dedd4115d6809e569732ee5709c9 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 19 Jan 2015 22:32:42 +0100 Subject: interval averages for discrete functions --- pyspike/function.py | 34 ++++++++++++++++++++++++++-------- test/test_function.py | 27 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/pyspike/function.py b/pyspike/function.py index 6fb7537..ebf4189 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -416,9 +416,9 @@ class DiscreteFunction(object): return 1.0*self.x, 1.0*self.y/self.mp def integral(self, interval=None): - """ Returns the integral over the given interval. For the interval - sequence this amounts to the sum over all values divided by the count - of intervals. + """ Returns the integral over the given interval. For the discrete + function, this amounts to the sum over all values divided by the total + multiplicity. :param interval: integration interval given as a pair of floats, if None the integral over the whole function is computed. @@ -429,9 +429,16 @@ class DiscreteFunction(object): if interval is None: # no interval given, integrate over the whole spike train # don't count the first value, which is zero by definition - a = 1.0*np.sum(self.y[1:-1]) + a = 1.0 * np.sum(self.y[1:-1]) / np.sum(self.mp[1:-1]) else: - raise NotImplementedError() + # find the indices corresponding to the interval + start_ind = np.searchsorted(self.x, interval[0], side='right') + end_ind = np.searchsorted(self.x, interval[1], side='left') + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + # first the contribution from between the indices + a = np.sum(self.y[start_ind:end_ind]) / \ + np.sum(self.mp[start_ind:end_ind]) return a def avrg(self, interval=None): @@ -448,10 +455,21 @@ class DiscreteFunction(object): """ if interval is None: # no interval given, average over the whole spike train - # don't count the first interval for normalization - return self.integral() / np.sum(self.mp[1:-1]) + return self.integral() + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): + # just one interval + a = self.integral(interval) else: - raise NotImplementedError() + # several intervals + a = 0.0 + for ival in interval: + a += self.integral(ival) + return a def add(self, f): """ Adds another `DiscreteFunction` function to this function. diff --git a/test/test_function.py b/test/test_function.py index ba87db8..da3d851 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -203,6 +203,33 @@ def test_pwl_avrg(): assert_array_almost_equal(f_avrg.y2, y2_expected, decimal=16) +def test_df(): + # testing discrete function + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [0.0, 1.0, 1.0, 0.0, 1.0] + mp = [1.0, 2.0, 1.0, 2.0, 1.0] + f = spk.DiscreteFunction(x, y, mp) + xp, yp = f.get_plottable_data() + + xp_expected = [0.0, 1.0, 2.0, 2.5, 4.0] + yp_expected = [0.0, 0.5, 1.0, 0.0, 1.0] + assert_array_almost_equal(xp, xp_expected, decimal=16) + assert_array_almost_equal(yp, yp_expected, decimal=16) + + avrg_expected = 2.0 / 5.0 + assert_almost_equal(f.avrg(), avrg_expected, decimal=16) + + # interval averaging + a = f.avrg([0.5, 2.4]) + assert_almost_equal(a, 2.0/3.0, decimal=16) + a = f.avrg([1.5, 3.5]) + assert_almost_equal(a, 1.0/3.0, decimal=16) + a = f.avrg((0.9, 3.5)) + assert_almost_equal(a, 2.0/5.0, decimal=16) + a = f.avrg([1.1, 4.0]) + assert_almost_equal(a, 1.0/3.0, decimal=16) + + if __name__ == "__main__": test_pwc() test_pwc_add() -- cgit v1.2.3 From f2d742c06fd013a013c811593257b67502ea9486 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 19 Jan 2015 23:07:09 +0100 Subject: fixed bug for multiple intervals --- pyspike/function.py | 60 ++++++++++++++++++++++++++------------------------- test/test_function.py | 7 ++++-- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/pyspike/function.py b/pyspike/function.py index ebf4189..62b0e2c 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -420,26 +420,44 @@ class DiscreteFunction(object): function, this amounts to the sum over all values divided by the total multiplicity. - :param interval: integration interval given as a pair of floats, if + :param interval: integration interval given as a pair of floats, or a + sequence of pairs in case of multiple intervals, if None the integral over the whole function is computed. - :type interval: Pair of floats or None. + :type interval: Pair, sequence of pairs, or None. :returns: the integral :rtype: float """ + + def get_indices(ival): + start_ind = np.searchsorted(self.x, ival[0], side='right') + end_ind = np.searchsorted(self.x, ival[1], side='left') + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + return start_ind, end_ind + if interval is None: # no interval given, integrate over the whole spike train # don't count the first value, which is zero by definition - a = 1.0 * np.sum(self.y[1:-1]) / np.sum(self.mp[1:-1]) - else: + return 1.0 * np.sum(self.y[1:-1]) / np.sum(self.mp[1:-1]) + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): # find the indices corresponding to the interval - start_ind = np.searchsorted(self.x, interval[0], side='right') - end_ind = np.searchsorted(self.x, interval[1], side='left') - assert start_ind > 0 and end_ind < len(self.x), \ - "Invalid averaging interval" - # first the contribution from between the indices - a = np.sum(self.y[start_ind:end_ind]) / \ - np.sum(self.mp[start_ind:end_ind]) - return a + start_ind, end_ind = get_indices(interval) + return (np.sum(self.y[start_ind:end_ind]) / + np.sum(self.mp[start_ind:end_ind])) + else: + value = 0.0 + multiplicity = 0.0 + for ival in interval: + # find the indices corresponding to the interval + start_ind, end_ind = get_indices(ival) + value += np.sum(self.y[start_ind:end_ind]) + multiplicity += np.sum(self.mp[start_ind:end_ind]) + return value/multiplicity def avrg(self, interval=None): """ Computes the average of the interval sequence: @@ -453,23 +471,7 @@ class DiscreteFunction(object): :returns: the average a. :rtype: float """ - if interval is None: - # no interval given, average over the whole spike train - return self.integral() - - # check if interval is as sequence - assert isinstance(interval, collections.Sequence), \ - "Invalid value for `interval`. None, Sequence or Tuple expected." - # check if interval is a sequence of intervals - if not isinstance(interval[0], collections.Sequence): - # just one interval - a = self.integral(interval) - else: - # several intervals - a = 0.0 - for ival in interval: - a += self.integral(ival) - return a + return self.integral(interval) def add(self, f): """ Adds another `DiscreteFunction` function to this function. diff --git a/test/test_function.py b/test/test_function.py index da3d851..933fd2e 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -216,8 +216,7 @@ def test_df(): assert_array_almost_equal(xp, xp_expected, decimal=16) assert_array_almost_equal(yp, yp_expected, decimal=16) - avrg_expected = 2.0 / 5.0 - assert_almost_equal(f.avrg(), avrg_expected, decimal=16) + assert_almost_equal(f.avrg(), 2.0/5.0, decimal=16) # interval averaging a = f.avrg([0.5, 2.4]) @@ -229,6 +228,10 @@ def test_df(): a = f.avrg([1.1, 4.0]) assert_almost_equal(a, 1.0/3.0, decimal=16) + # averaging over multiple intervals + a = f.avrg([(0.5, 1.5), (1.5, 2.6)]) + assert_almost_equal(a, 2.0/5.0, decimal=16) + if __name__ == "__main__": test_pwc() -- cgit v1.2.3 From cce93ecbb1c961ab075a4924a42543483ffffb77 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 20 Jan 2015 10:47:27 +0100 Subject: added spike sync matrix --- pyspike/__init__.py | 4 ++-- pyspike/distances.py | 62 ++++++++++++++++++++++++++++++++++++++++++--------- pyspike/function.py | 1 + test/test_distance.py | 20 +++++++++++------ 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 1b18569..55687e6 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -10,9 +10,9 @@ from function import PieceWiseConstFunc, PieceWiseLinFunc, \ DiscreteFunction, average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ - spike_sync_profile, spike_sync_distance, \ + spike_sync_profile, spike_sync, \ isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ spike_profile_multi, spike_distance_multi, spike_distance_matrix, \ - spike_sync_profile_multi + spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ spike_train_from_string, merge_spike_trains, generate_poisson_spikes diff --git a/pyspike/distances.py b/pyspike/distances.py index 0f0efa9..8a14a8d 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -152,10 +152,10 @@ Falling back to slow python backend.") ############################################################ -# spike_sync_distance +# spike_sync ############################################################ -def spike_sync_distance(spikes1, spikes2): - return spike_sync_profile(spikes1, spikes2).avrg() +def spike_sync(spikes1, spikes2, interval=None): + return spike_sync_profile(spikes1, spikes2).avrg(interval) ############################################################ @@ -313,6 +313,28 @@ def spike_profile_multi(spike_trains, indices=None): return average_dist +############################################################ +# spike_distance_multi +############################################################ +def spike_distance_multi(spike_trains, indices=None, interval=None): + """ Computes the multi-variate spike distance for a set of spike trains. + That is the time average of the multi-variate spike profile: + S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt + where the sum goes over all pairs + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The averaged spike distance S. + :rtype: double + """ + return spike_profile_multi(spike_trains, indices).avrg(interval) + + ############################################################ # spike_profile_multi ############################################################ @@ -341,11 +363,9 @@ def spike_sync_profile_multi(spike_trains, indices=None): ############################################################ # spike_distance_multi ############################################################ -def spike_distance_multi(spike_trains, indices=None, interval=None): - """ Computes the multi-variate spike distance for a set of spike trains. - That is the time average of the multi-variate spike profile: - S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt - where the sum goes over all pairs +def spike_sync_multi(spike_trains, indices=None, interval=None): + """ Computes the multi-variate spike synchronization value for a set of + spike trains. :param spike_trains: list of spike trains :param indices: list of indices defining which spike trains to use, @@ -354,10 +374,10 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): :param interval: averaging interval given as a pair of floats, if None the average over the whole function is computed. :type interval: Pair of floats or None. - :returns: The averaged spike distance S. + :returns: The averaged spike synchronization value SYNC. :rtype: double """ - return spike_profile_multi(spike_trains, indices).avrg(interval) + return spike_sync_profile_multi(spike_trains, indices).avrg(interval) ############################################################ @@ -433,3 +453,25 @@ def spike_distance_matrix(spike_trains, indices=None, interval=None): """ return _generic_distance_matrix(spike_trains, spike_distance, indices, interval) + + +############################################################ +# spike_sync_matrix +############################################################ +def spike_sync_matrix(spike_trains, indices=None, interval=None): + """ Computes the time averaged spike-synchronization value of all pairs of + spike-trains. + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: 2D array with the pair wise time spike synchronization values + :math:`SYNC_{ij}` + :rtype: np.array + """ + return _generic_distance_matrix(spike_trains, spike_sync, + indices, interval) diff --git a/pyspike/function.py b/pyspike/function.py index 62b0e2c..e0dadf6 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -429,6 +429,7 @@ class DiscreteFunction(object): """ def get_indices(ival): + """ Retuns the indeces surrounding the given interval""" start_ind = np.searchsorted(self.x, ival[0], side='right') end_ind = np.searchsorted(self.x, ival[1], side='left') assert start_ind > 0 and end_ind < len(self.x), \ diff --git a/test/test_distance.py b/test/test_distance.py index 6bdb049..2650313 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -135,22 +135,22 @@ def test_spike_sync(): spikes2 = np.array([2.1]) spikes1 = spk.add_auxiliary_spikes(spikes1, 4.0) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) spikes2 = np.array([3.1]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) spikes2 = np.array([1.1]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) spikes2 = np.array([0.9]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) @@ -207,11 +207,11 @@ def test_multi_spike_sync(): spikes1 = spk.add_auxiliary_spikes(spikes1, 1000) spikes2 = spk.add_auxiliary_spikes(spikes2, 1000) spikes3 = spk.add_auxiliary_spikes(spikes3, 1000) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes2), + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=15) - assert_almost_equal(spk.spike_sync_distance(spikes1, spikes3), + assert_almost_equal(spk.spike_sync(spikes1, spikes3), 0.5, decimal=15) - assert_almost_equal(spk.spike_sync_distance(spikes2, spikes3), + assert_almost_equal(spk.spike_sync(spikes2, spikes3), 0.5, decimal=15) f = spk.spike_sync_profile_multi([spikes1, spikes2, spikes3]) @@ -220,6 +220,8 @@ def test_multi_spike_sync(): # (np.sum(f1.mp[1:-1])+np.sum(f2.mp[1:-1])+np.sum(f3.mp[1:-1])) expected = 0.5 assert_almost_equal(f.avrg(), expected, decimal=15) + assert_almost_equal(spk.spike_sync_multi([spikes1, spikes2, spikes3]), + expected, decimal=15) # multivariate regression test spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", @@ -267,6 +269,10 @@ def test_spike_matrix(): check_dist_matrix(spk.spike_distance, spk.spike_distance_matrix) +def test_spike_sync_matrix(): + check_dist_matrix(spk.spike_sync, spk.spike_sync_matrix) + + def test_regression_spiky(): spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", (0.0, 4000.0)) -- cgit v1.2.3 From f4266628dc89b8747923bbddcf1b7f6b13b701d8 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 20 Jan 2015 14:30:34 +0100 Subject: added spike sync desc to docs --- Readme.rst | 94 +++++++++++++++++++++++++++------------------ examples/distance_matrix.py | 5 +++ pyspike/distances.py | 41 ++++++++++++++++---- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/Readme.rst b/Readme.rst index 4053995..bcf6fa9 100644 --- a/Readme.rst +++ b/Readme.rst @@ -5,8 +5,8 @@ PySpike :target: https://travis-ci.org/mariomulansky/PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the bivariate ISI_ and SPIKE_ distance [#]_ [#]_. -Additionally, it provides functions to compute multivariate SPIKE and ISI distances, as well as averaging and general spike train processing. +Its core functionality is the implementation of the bivariate ISI_ and SPIKE_ distance [#]_ [#]_ as well as SPIKE-Synchronization_ [#]_. +Additionally, it provides functions to compute multivariate profiles, distance matrices, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). PySpike provides the same fundamental functionality as the SPIKY_ framework for Matlab, which additionally contains spike-train generators, more spike train distance measures and many visualization routines. @@ -17,6 +17,8 @@ All source codes are published under the BSD_License_. .. [#] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) `[pdf] `_ +.. [#] Kreuz T, Mulansky M and Bozanic N, *SPIKY: A graphical user interface for monitoring spike train synchrony*, tbp (2015) + Requirements and Installation ----------------------------- @@ -162,32 +164,11 @@ If you are only interested in the scalar ISI-distance and not the profile, you c where :code:`interval` is optional, as above, and if omitted the ISI-distance is computed for the complete spike trains. -Furthermore, PySpike provides the :code:`average_profile` function that can be used to compute the average profile of a list of given :code:`PieceWiseConstFunc` instances. - -.. code:: python - - isi_profile1 = spk.isi_profile(spike_trains[0], spike_trains[1]) - isi_profile2 = spk.isi_profile(spike_trains[0], spike_trains[2]) - isi_profile3 = spk.isi_profile(spike_trains[1], spike_trains[2]) - - avrg_profile = spk.average_profile([isi_profile1, isi_profile2, isi_profile3]) - x, y = avrg_profile.get_plottable_data() - plt.plot(x, y, label="Average ISI profile") - -Note the difference between the :code:`average_profile` function, which returns a :code:`PieceWiseConstFunc` (or :code:`PieceWiseLinFunc`, see below), and the :code:`avrg` member function above, that computes the integral over the time profile resulting in a single value. -So to obtain overall average ISI-distance of a list of ISI profiles you can first compute the average profile using :code:`average_profile` and the use - -.. code:: python - - avrg_isi = avrg_profile.avrg() - -to obtain the final, scalar average ISI distance of the whole set (see also "Computing multivariate distance" below). - SPIKE-distance .............. -To compute for the spike distance you use the function :code:`spike_profile` instead of :code:`isi_profile` above. +To compute for the spike distance profile you use the function :code:`spike_profile` instead of :code:`isi_profile` above. But the general approach is very similar: .. code:: python @@ -217,22 +198,52 @@ Again, you can use to compute the SPIKE distance directly, if you are not interested in the profile at all. The parameter :code:`interval` is optional and if neglected the whole spike train is used. -Furthmore, you can use the :code:`average_profile` function to compute an average profile of a list of SPIKE-profiles: + + +SPIKE synchronization +.............. + +**Important note:** + +------------------------------ + + SPIKE-Synchronization measures *similarity*. + That means, a value of zero indicates absence of synchrony, while a value of one denotes the presence of synchrony. + This is exactly opposite to the other two measures: ISI- and SPIKE-distance. + +---------------------- + + +SPIKE synchronization is another approach to measure spike synchrony. +In contrast to the SPIKE- and ISI-distance, it measures similarity instead of dissimilarity, i.e. higher values represent larger synchrony. +Another difference is that the SPIKE synchronization profile is only defined exactly at the spike times, not for the whole interval of the spike trains. +Therefore, it is represented by a :code:`DiscreteFunction`. + +To compute for the spike synchronization profile, PySpike provides the function :code:`spike_sync_profile`. +The general handling of the profile, however, is similar to the other profiles above: .. code:: python + + import matplotlib.pyplot as plt + import pyspike as spk - avrg_profile = spk.average_profile([spike_profile1, spike_profile2, - spike_profile3]) - x, y = avrg_profile.get_plottable_data() - plt.plot(x, y, label="Average SPIKE profile") + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + time_interval=(0, 4000)) + spike_profile = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) + x, y = spike_profile.get_plottable_data() + +For the direct computation of the overall spike synchronization value within some interval, the :code:`spike_sync` function can be used: + +.. code:: python + + spike_sync = spk.spike_sync(spike_trains[0], spike_trains[1], interval) Computing multivariate profiles and distances ---------------------------------------------- -To compute the multivariate ISI- or SPIKE-profile of a set of spike trains, you can compute all bivariate profiles separately and then use the :code:`average_profile` function above. -However, PySpike provides convenience functions for that purpose. -The following example computes the multivariate ISI- and SPIKE-profile for a list of spike trains: +To compute the multivariate ISI-profile, SPIKE-profile or SPIKE-Synchronization profile f a set of spike trains, PySpike provides multi-variate version of the profile function. +The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains: .. code:: python @@ -240,15 +251,16 @@ The following example computes the multivariate ISI- and SPIKE-profile for a lis time_interval=(0, 4000)) avrg_isi_profile = spk.isi_profile_multi(spike_trains) avrg_spike_profile = spk.spike_profile_multi(spike_trains) + avrg_spike_sync_profile = spk.spike_sync_profile_multi(spike_trains) -Both functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. -As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi` and :code:`spike_distance_multi`, that return the scalar multivariate ISI- and SPIKE-distance. -Both distance functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. +All functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. +As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi`, :code:`spike_distance_multi` and :code:`spike_sync_multi`, that return the scalar overall multivariate ISI- and SPIKE-distance as well as the SPIKE-Synchronization value. +Those functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. Another option to characterize large sets of spike trains are distance matrices. -Each entry in the distance matrix represents a bivariate distance of two spike trains. -The distance matrix is symmetric and has zero values at the diagonal. -The following example computes and plots the ISI- and SPIKE-distance matrix, where for the latter one the averaging is performed only the time interval T=0..1000. +Each entry in the distance matrix represents a bivariate distance (similarity for SPIKE-Synchronization) of two spike trains. +The distance matrix is symmetric and has zero values (ones) at the diagonal. +The following example computes and plots the ISI- and SPIKE-distance matrix as well as the SPIKE-Synchronization-matrix, with different intervals. .. code:: python @@ -264,6 +276,11 @@ The following example computes and plots the ISI- and SPIKE-distance matrix, whe plt.imshow(spike_distance, interpolation='none') plt.title("SPIKE-distance") + plt.figure() + spike_sync = spk.spike_sync_matrix(spike_trains, interval=(2000,4000)) + plt.imshow(spike_sync, interpolation='none') + plt.title("SPIKE-Sync") + plt.show() @@ -284,6 +301,7 @@ Curie Initial Training Network* `Neural Engineering Transformative Technologies .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance .. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance +.. _SPIKE-Synchronization: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#SPIKE_synchronization .. _cython: http://www.cython.org .. _SPIKY: http://wwwold.fi.isc.cnr.it/users/thomas.kreuz/Source-Code/SPIKY.html .. _BSD_License: http://opensource.org/licenses/BSD-2-Clause diff --git a/examples/distance_matrix.py b/examples/distance_matrix.py index 142db2c..0e4d825 100644 --- a/examples/distance_matrix.py +++ b/examples/distance_matrix.py @@ -30,4 +30,9 @@ spike_distance = spk.spike_distance_matrix(spike_trains, interval=(0, 1000)) plt.imshow(spike_distance, interpolation='none') plt.title("SPIKE-distance, T=0-1000") +plt.figure() +spike_sync = spk.spike_sync_matrix(spike_trains, interval=(2000, 4000)) +plt.imshow(spike_sync, interpolation='none') +plt.title("SPIKE-Sync, T=2000-4000") + plt.show() diff --git a/pyspike/distances.py b/pyspike/distances.py index 8a14a8d..2cac4bc 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -133,6 +133,19 @@ def spike_distance(spikes1, spikes2, interval=None): # spike_sync_profile ############################################################ def spike_sync_profile(spikes1, spikes2): + """ Computes the spike-synchronization profile S_sync(t) of the two given + spike trains. Returns the profile as a DiscreteFunction object. The S_sync + values are either 1 or 0, indicating the presence or absence of a + coincidence. 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. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The spike-distance profile :math:`S_{sync}(t)`. + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ # cython implementation try: @@ -155,6 +168,20 @@ Falling back to slow python backend.") # spike_sync ############################################################ def spike_sync(spikes1, spikes2, interval=None): + """ Computes the spike synchronization value SYNC of the given spike + trains. The spike synchronization value is the computed as the total number + of coincidences divided by the total number of spikes: + + .. math:: SYNC = \sum_n C_n / N. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :param interval: averaging interval given as a pair of floats (T0, T1), + if None the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The spike synchronization value. + :rtype: double + """ return spike_sync_profile(spikes1, spikes2).avrg(interval) @@ -340,16 +367,16 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): ############################################################ def spike_sync_profile_multi(spike_trains, indices=None): """ Computes the multi-variate spike synchronization profile for a set of - spike trains. That is the average spike-distance of all pairs of spike - trains: - :math:`S_ss(t) = 2/((N(N-1)) sum_{} S_{ss}^{i, j}`, - where the sum goes over all pairs + spike trains. For each spike in the set of spike trains, the multi-variate + profile is defined as the number of coincidences divided by the number of + spike trains pairs involving the spike train of containing this spike, + which is the number of spike trains minus one (N-1). :param spike_trains: list of spike trains :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None - :returns: The averaged spike profile :math:`(t)` + :returns: The multi-variate spike sync profile :math:`(t)` :rtype: :class:`pyspike.function.DiscreteFunction` """ @@ -374,7 +401,7 @@ def spike_sync_multi(spike_trains, indices=None, interval=None): :param interval: averaging interval given as a pair of floats, if None the average over the whole function is computed. :type interval: Pair of floats or None. - :returns: The averaged spike synchronization value SYNC. + :returns: The multi-variate spike synchronization value SYNC. :rtype: double """ return spike_sync_profile_multi(spike_trains, indices).avrg(interval) @@ -459,7 +486,7 @@ def spike_distance_matrix(spike_trains, indices=None, interval=None): # spike_sync_matrix ############################################################ def spike_sync_matrix(spike_trains, indices=None, interval=None): - """ Computes the time averaged spike-synchronization value of all pairs of + """ Computes the overall spike-synchronization value of all pairs of spike-trains. :param spike_trains: list of spike trains -- cgit v1.2.3 From 7bec7f0fe1c40146e8b45757d4c156a4f9b49001 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 20 Jan 2015 14:39:22 +0100 Subject: add spike sync to some examples --- examples/averages.py | 15 +++++++++++++++ examples/spike_sync.py | 3 +-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/averages.py b/examples/averages.py index 6413ab2..c3e81e2 100644 --- a/examples/averages.py +++ b/examples/averages.py @@ -42,3 +42,18 @@ print("SPIKE-distance (0-1000): %.8f" % spike1) print("SPIKE-distance (1000-2000): %.8f" % spike2) print("SPIKE-distance (0-1000) and (2000-3000): %.8f" % spike3) print("SPIKE-distance (1000-2000) and (3000-4000): %.8f" % spike4) +print() + +f = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) + +print("SPIKE-Synchronization: %.8f" % f.avrg()) + +spike_sync1 = f.avrg(interval=(0, 1000)) +spike_sync2 = f.avrg(interval=(1000, 2000)) +spike_sync3 = f.avrg(interval=[(0, 1000), (2000, 3000)]) +spike_sync4 = f.avrg(interval=[(1000, 2000), (3000, 4000)]) + +print("SPIKE-Sync (0-1000): %.8f" % spike_sync1) +print("SPIKE-Sync (1000-2000): %.8f" % spike_sync2) +print("SPIKE-Sync (0-1000) and (2000-3000): %.8f" % spike_sync3) +print("SPIKE-Sync (1000-2000) and (3000-4000): %.8f" % spike_sync4) diff --git a/examples/spike_sync.py b/examples/spike_sync.py index 535f19f..a72dbbf 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -11,7 +11,6 @@ spike_trains = spk.load_spike_trains_from_txt("../test/SPIKE_Sync_Test.txt", plt.figure() f = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) -# f = spk.spike_sync_profile(spikes1, spikes2) x, y = f.get_plottable_data() plt.plot(x, y, '--ok', label="SPIKE-SYNC profile") print(f.x) @@ -33,7 +32,7 @@ plt.figure() f = spk.spike_sync_profile_multi(spike_trains) x, y = f.get_plottable_data() -plt.plot(x, y, '-k', label="SPIKE-SYNC profile") +plt.plot(x, y, '-k', label="SPIKE-Sync profile") print("Average:", f.avrg()) -- cgit v1.2.3 From 0074a6ae1ad8de73b9521aafa5ba5e6064779e3e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 22 Jan 2015 16:08:12 +0100 Subject: test mutlivariate with subset of spike trains --- test/test_distance.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_distance.py b/test/test_distance.py index 2650313..3b4329c 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -172,6 +172,10 @@ def check_multi_profile(profile_func, profile_func_multi): f_multi = profile_func_multi(spike_trains, [0, 1]) assert f_multi.almost_equal(f12, decimal=14) + f_multi1 = profile_func_multi(spike_trains, [1, 2, 3]) + f_multi2 = profile_func_multi(spike_trains[1:]) + assert f_multi1.almost_equal(f_multi2, decimal=14) + f = copy(f12) f.add(f13) f.add(f23) -- cgit v1.2.3 From e30a16a76a78aad51c59972b6c5eae3dd74f0459 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 22 Jan 2015 16:17:53 +0100 Subject: better test for subsets of spike train lists added another test case to check if the computation of the multi-variate distance is correct if only a subset of the given spike trains is to be considered. --- test/test_distance.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/test_distance.py b/test/test_distance.py index 3b4329c..41f625e 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -293,6 +293,25 @@ def test_regression_spiky(): # assert_equal(spike_dist, 0.2445) +def test_multi_variate_subsets(): + spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", + (0.0, 4000.0)) + sub_set = [1, 3, 5, 7] + spike_trains_sub_set = [spike_trains[i] for i in sub_set] + + v1 = spk.isi_distance_multi(spike_trains_sub_set) + v2 = spk.isi_distance_multi(spike_trains, sub_set) + assert_equal(v1, v2) + + v1 = spk.spike_distance_multi(spike_trains_sub_set) + v2 = spk.spike_distance_multi(spike_trains, sub_set) + assert_equal(v1, v2) + + v1 = spk.spike_sync_multi(spike_trains_sub_set) + v2 = spk.spike_sync_multi(spike_trains, sub_set) + assert_equal(v1, v2) + + if __name__ == "__main__": test_isi() test_spike() -- cgit v1.2.3 From deb1fb51359085ff5d171e5ffa8cd4c142a4e174 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 28 Jan 2015 16:53:48 +0100 Subject: added smoothing functionality to spike sync --- examples/spike_sync.py | 6 ++++- pyspike/function.py | 69 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/examples/spike_sync.py b/examples/spike_sync.py index a72dbbf..7f9e762 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -32,7 +32,11 @@ plt.figure() f = spk.spike_sync_profile_multi(spike_trains) x, y = f.get_plottable_data() -plt.plot(x, y, '-k', label="SPIKE-Sync profile") +plt.plot(x, y, '-b', alpha=0.7, label="SPIKE-Sync profile") + +x1, y1 = f.get_plottable_data(averaging_window_size=50) +plt.plot(x1, y1, '-k', lw=2.5, label="averaged SPIKE-Sync profile") + print("Average:", f.avrg()) diff --git a/pyspike/function.py b/pyspike/function.py index e0dadf6..047c88a 100644 --- a/pyspike/function.py +++ b/pyspike/function.py @@ -397,10 +397,13 @@ class DiscreteFunction(object): np.allclose(self.y, other.y, atol=eps, rtol=0.0) and \ np.allclose(self.mp, other.mp, atol=eps, rtol=0.0) - def get_plottable_data(self, k=0): - """ Returns two arrays containing x- and y-coordinates for immeditate - plotting of the interval sequence. + def get_plottable_data(self, averaging_window_size=0): + """ Returns two arrays containing x- and y-coordinates for plotting + the interval sequence. The optional parameter `averaging_window_size` + determines the size of an averaging window to smoothen the profile. If + this value is 0, no averaging is performed. + :param averaging_window_size: size of the averaging window, default=0. :returns: (x_plot, y_plot) containing plottable data :rtype: pair of np.array @@ -410,10 +413,62 @@ class DiscreteFunction(object): plt.plot(x, y, '-o', label="Discrete function") """ - if k > 0: - raise NotImplementedError() - - return 1.0*self.x, 1.0*self.y/self.mp + if averaging_window_size > 0: + # for the averaged profile we have to take the multiplicity into + # account. values with higher multiplicity should be consider as if + # they appeared several times. Hence we can not know how many + # entries we have to consider to the left and right. Rather, we + # will iterate until some wanted multiplicity is reached. + + # the first value in self.mp contains the number of averaged + # profiles without any possible extra multiplicities + # (by implementation) + expected_mp = (averaging_window_size+1) * int(self.mp[0]) + y_plot = np.zeros_like(self.y) + # compute the values in a loop, could be done in cython if required + for i in xrange(len(y_plot)): + + if self.mp[i] >= expected_mp: + # the current value contains already all the wanted + # multiplicity + y_plot[i] = self.y[i]/self.mp[i] + continue + + # first look to the right + y = self.y[i] + mp_r = self.mp[i] + j = i+1 + while j < len(y_plot): + if mp_r+self.mp[j] < expected_mp: + # if we still dont reach the required multiplicity + # we take the whole value + y += self.y[j] + mp_r += self.mp[j] + else: + # otherwise, just some fraction + y += self.y[j] * (expected_mp - mp_r)/self.mp[j] + mp_r += (expected_mp - mp_r) + break + j += 1 + + # same story to the left + mp_l = self.mp[i] + j = i-1 + while j >= 0: + if mp_l+self.mp[j] < expected_mp: + y += self.y[j] + mp_l += self.mp[j] + else: + y += self.y[j] * (expected_mp - mp_l)/self.mp[j] + mp_l += (expected_mp - mp_l) + break + j -= 1 + y_plot[i] = y/(mp_l+mp_r-self.mp[i]) + return 1.0*self.x, y_plot + + else: # k = 0 + + return 1.0*self.x, 1.0*self.y/self.mp def integral(self, interval=None): """ Returns the integral over the given interval. For the discrete -- cgit v1.2.3 From 877480b65c94019d25e44cec0e67876b96b4f418 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 28 Jan 2015 16:54:27 +0100 Subject: convert return to arrays --- pyspike/cython_add.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyspike/cython_add.pyx b/pyspike/cython_add.pyx index 817799e..ac64005 100644 --- a/pyspike/cython_add.pyx +++ b/pyspike/cython_add.pyx @@ -230,4 +230,6 @@ def add_discrete_function_cython(double[:] x1, double[:] y1, double[:] mp1, # 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] + return (np.array(x_new[:index+1]), + np.array(y_new[:index+1]), + np.array(mp_new[:index+1])) -- cgit v1.2.3 From ae90eb6bdd6afd716cb90a4aeeec8e6e77635d10 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 28 Jan 2015 17:42:19 +0100 Subject: each function class in separate source file --- pyspike/__init__.py | 9 +- pyspike/distances.py | 13 +- pyspike/function.py | 585 -------------------------------------------------- test/test_function.py | 21 +- 4 files changed, 27 insertions(+), 601 deletions(-) delete mode 100644 pyspike/function.py diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 55687e6..f480964 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -4,10 +4,13 @@ Copyright 2014, Mario Mulansky Distributed under the BSD License """ -__all__ = ["function", "distances", "spikes"] +__all__ = ["distances", "spikes", "PieceWiseConstFunc", "PieceWiseLinFunc", + "DiscreteFunc"] + +from PieceWiseConstFunc import PieceWiseConstFunc +from PieceWiseLinFunc import PieceWiseLinFunc +from DiscreteFunc import DiscreteFunc -from function import PieceWiseConstFunc, PieceWiseLinFunc, \ - DiscreteFunction, average_profile from distances import isi_profile, isi_distance, \ spike_profile, spike_distance, \ spike_sync_profile, spike_sync, \ diff --git a/pyspike/distances.py b/pyspike/distances.py index 5476b6f..9077871 100644 --- a/pyspike/distances.py +++ b/pyspike/distances.py @@ -11,7 +11,7 @@ import numpy as np import threading from functools import partial -from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, DiscreteFunction +from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, DiscreteFunc ############################################################ @@ -161,7 +161,7 @@ Falling back to slow python backend.") times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) - return DiscreteFunction(times, coincidences, multiplicity) + return DiscreteFunc(times, coincidences, multiplicity) ############################################################ @@ -212,7 +212,8 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] # start with first pair (i, j) = pairs[0] average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) @@ -251,7 +252,8 @@ def _multi_distance_par(spike_trains, pair_distance_func, indices=None): assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] num_pairs = len(pairs) # start with first pair @@ -430,7 +432,8 @@ def _generic_distance_matrix(spike_trains, dist_function, assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: diff --git a/pyspike/function.py b/pyspike/function.py deleted file mode 100644 index 047c88a..0000000 --- a/pyspike/function.py +++ /dev/null @@ -1,585 +0,0 @@ -""" function.py - -Module containing classes representing piece-wise constant and piece-wise -linear functions. - -Copyright 2014, Mario Mulansky - -Distributed under the BSD License - -""" -from __future__ import print_function - -import numpy as np -import collections - - -############################################################## -# PieceWiseConstFunc -############################################################## -class PieceWiseConstFunc(object): - """ A class representing a piece-wise constant function. """ - - def __init__(self, x, y): - """ Constructs the piece-wise const function. - - :param x: array of length N+1 defining the edges of the intervals of - the pwc function. - :param y: array of length N defining the function values at the - intervals. - """ - # convert parameters to arrays, also ensures copying - self.x = np.array(x) - self.y = np.array(y) - - def copy(self): - """ Returns a copy of itself - - :rtype: :class:`PieceWiseConstFunc` - """ - return PieceWiseConstFunc(self.x, self.y) - - def almost_equal(self, other, decimal=14): - """ Checks if the function is equal to another function up to `decimal` - precision. - - :param other: another :class:`PieceWiseConstFunc` - :returns: True if the two functions are equal up to `decimal` decimals, - False otherwise - :rtype: bool - """ - eps = 10.0**(-decimal) - return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ - np.allclose(self.y, other.y, atol=eps, rtol=0.0) - - def get_plottable_data(self): - """ Returns two arrays containing x- and y-coordinates for immeditate - plotting of the piece-wise function. - - :returns: (x_plot, y_plot) containing plottable data - :rtype: pair of np.array - - Example:: - - x, y = f.get_plottable_data() - plt.plot(x, y, '-o', label="Piece-wise const function") - """ - - x_plot = np.empty(2*len(self.x)-2) - x_plot[0] = self.x[0] - x_plot[1::2] = self.x[1:] - x_plot[2::2] = self.x[1:-1] - y_plot = np.empty(2*len(self.y)) - y_plot[::2] = self.y - y_plot[1::2] = self.y - - return x_plot, y_plot - - def integral(self, interval=None): - """ Returns the integral over the given interval. - - :param interval: integration interval given as a pair of floats, if - None the integral over the whole function is computed. - :type interval: Pair of floats or None. - :returns: the integral - :rtype: float - """ - if interval is None: - # no interval given, integrate over the whole spike train - a = np.sum((self.x[1:]-self.x[:-1]) * self.y) - else: - # find the indices corresponding to the interval - start_ind = np.searchsorted(self.x, interval[0], side='right') - end_ind = np.searchsorted(self.x, interval[1], side='left')-1 - assert start_ind > 0 and end_ind < len(self.x), \ - "Invalid averaging interval" - # first the contribution from between the indices - a = np.sum((self.x[start_ind+1:end_ind+1] - - self.x[start_ind:end_ind]) * - self.y[start_ind:end_ind]) - # correction from start to first index - a += (self.x[start_ind]-interval[0]) * self.y[start_ind-1] - # correction from last index to end - a += (interval[1]-self.x[end_ind]) * self.y[end_ind] - return a - - def avrg(self, interval=None): - """ Computes the average of the piece-wise const function: - :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. - - :param interval: averaging interval given as a pair of floats, a - sequence of pairs for averaging multiple intervals, or - None, if None the average over the whole function is - computed. - :type interval: Pair, sequence of pairs, or None. - :returns: the average a. - :rtype: float - """ - if interval is None: - # no interval given, average over the whole spike train - return self.integral() / (self.x[-1]-self.x[0]) - - # check if interval is as sequence - assert isinstance(interval, collections.Sequence), \ - "Invalid value for `interval`. None, Sequence or Tuple expected." - # check if interval is a sequence of intervals - if not isinstance(interval[0], collections.Sequence): - # just one interval - a = self.integral(interval) / (interval[1]-interval[0]) - else: - # several intervals - a = 0.0 - int_length = 0.0 - for ival in interval: - a += self.integral(ival) - int_length += ival[1] - ival[0] - a /= int_length - return a - - def add(self, f): - """ Adds another PieceWiseConst function to this function. - Note: only functions defined on the same interval can be summed. - - :param f: :class:`PieceWiseConstFunc` function to be added. - :rtype: None - """ - assert self.x[0] == f.x[0], "The functions have different intervals" - assert self.x[-1] == f.x[-1], "The functions have different intervals" - - # cython version - try: - from cython_add import add_piece_wise_const_cython as \ - add_piece_wise_const_impl - except ImportError: - print("Warning: add_piece_wise_const_cython not found. Make sure \ -that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ -\n Falling back to slow python backend.") - # use python backend - from python_backend import add_piece_wise_const_python as \ - add_piece_wise_const_impl - - self.x, self.y = add_piece_wise_const_impl(self.x, self.y, f.x, f.y) - - def mul_scalar(self, fac): - """ Multiplies the function with a scalar value - - :param fac: Value to multiply - :type fac: double - :rtype: None - """ - self.y *= fac - - -############################################################## -# PieceWiseLinFunc -############################################################## -class PieceWiseLinFunc: - """ A class representing a piece-wise linear function. """ - - def __init__(self, x, y1, y2): - """ Constructs the piece-wise linear function. - - :param x: array of length N+1 defining the edges of the intervals of - the pwc function. - :param y1: array of length N defining the function values at the left - of the intervals. - :param y2: array of length N defining the function values at the right - of the intervals. - """ - # convert to array, which also ensures copying - self.x = np.array(x) - self.y1 = np.array(y1) - self.y2 = np.array(y2) - - def copy(self): - """ Returns a copy of itself - - :rtype: :class:`PieceWiseLinFunc` - """ - return PieceWiseLinFunc(self.x, self.y1, self.y2) - - def almost_equal(self, other, decimal=14): - """ Checks if the function is equal to another function up to `decimal` - precision. - - :param other: another :class:`PieceWiseLinFunc` - :returns: True if the two functions are equal up to `decimal` decimals, - False otherwise - :rtype: bool - """ - eps = 10.0**(-decimal) - return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ - np.allclose(self.y1, other.y1, atol=eps, rtol=0.0) and \ - np.allclose(self.y2, other.y2, atol=eps, rtol=0.0) - - def get_plottable_data(self): - """ Returns two arrays containing x- and y-coordinates for immeditate - plotting of the piece-wise function. - - :returns: (x_plot, y_plot) containing plottable data - :rtype: pair of np.array - - Example:: - - x, y = f.get_plottable_data() - plt.plot(x, y, '-o', label="Piece-wise const function") - """ - x_plot = np.empty(2*len(self.x)-2) - x_plot[0] = self.x[0] - x_plot[1::2] = self.x[1:] - x_plot[2::2] = self.x[1:-1] - y_plot = np.empty_like(x_plot) - y_plot[0::2] = self.y1 - y_plot[1::2] = self.y2 - return x_plot, y_plot - - def integral(self, interval=None): - """ Returns the integral over the given interval. - - :param interval: integration interval given as a pair of floats, if - None the integral over the whole function is computed. - :type interval: Pair of floats or None. - :returns: the integral - :rtype: float - """ - - def intermediate_value(x0, x1, y0, y1, x): - """ computes the intermediate value of a linear function """ - return y0 + (y1-y0)*(x-x0)/(x1-x0) - - if interval is None: - # no interval given, integrate over the whole spike train - integral = np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) - else: - # find the indices corresponding to the interval - start_ind = np.searchsorted(self.x, interval[0], side='right') - end_ind = np.searchsorted(self.x, interval[1], side='left')-1 - assert start_ind > 0 and end_ind < len(self.x), \ - "Invalid averaging interval" - # first the contribution from between the indices - integral = np.sum((self.x[start_ind+1:end_ind+1] - - self.x[start_ind:end_ind]) * - 0.5*(self.y1[start_ind:end_ind] + - self.y2[start_ind:end_ind])) - # correction from start to first index - integral += (self.x[start_ind]-interval[0]) * 0.5 * \ - (self.y2[start_ind-1] + - intermediate_value(self.x[start_ind-1], - self.x[start_ind], - self.y1[start_ind-1], - self.y2[start_ind-1], - interval[0] - )) - # correction from last index to end - integral += (interval[1]-self.x[end_ind]) * 0.5 * \ - (self.y1[end_ind] + - intermediate_value(self.x[end_ind], self.x[end_ind+1], - self.y1[end_ind], self.y2[end_ind], - interval[1] - )) - return integral - - def avrg(self, interval=None): - """ Computes the average of the piece-wise linear function: - :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. - - :param interval: averaging interval given as a pair of floats, a - sequence of pairs for averaging multiple intervals, or - None, if None the average over the whole function is - computed. - :type interval: Pair, sequence of pairs, or None. - :returns: the average a. - :rtype: float - - """ - - if interval is None: - # no interval given, average over the whole spike train - return self.integral() / (self.x[-1]-self.x[0]) - - # check if interval is as sequence - assert isinstance(interval, collections.Sequence), \ - "Invalid value for `interval`. None, Sequence or Tuple expected." - # check if interval is a sequence of intervals - if not isinstance(interval[0], collections.Sequence): - # just one interval - a = self.integral(interval) / (interval[1]-interval[0]) - else: - # several intervals - a = 0.0 - int_length = 0.0 - for ival in interval: - a += self.integral(ival) - int_length += ival[1] - ival[0] - a /= int_length - return a - - def add(self, f): - """ Adds another PieceWiseLin function to this function. - Note: only functions defined on the same interval can be summed. - - :param f: :class:`PieceWiseLinFunc` function to be added. - :rtype: None - """ - assert self.x[0] == f.x[0], "The functions have different intervals" - assert self.x[-1] == f.x[-1], "The functions have different intervals" - - # python implementation - # from python_backend import add_piece_wise_lin_python - # self.x, self.y1, self.y2 = add_piece_wise_lin_python( - # self.x, self.y1, self.y2, f.x, f.y1, f.y2) - - # cython version - try: - from cython_add import add_piece_wise_lin_cython as \ - add_piece_wise_lin_impl - except ImportError: - print("Warning: add_piece_wise_lin_cython not found. Make sure \ -that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ -\n Falling back to slow python backend.") - # use python backend - from python_backend import add_piece_wise_lin_python as \ - add_piece_wise_lin_impl - - self.x, self.y1, self.y2 = add_piece_wise_lin_impl( - self.x, self.y1, self.y2, f.x, f.y1, f.y2) - - def mul_scalar(self, fac): - """ Multiplies the function with a scalar value - - :param fac: Value to multiply - :type fac: double - :rtype: None - """ - self.y1 *= fac - self.y2 *= fac - - -############################################################## -# DiscreteFunction -############################################################## -class DiscreteFunction(object): - """ A class representing values defined on a discrete set of points. - """ - - def __init__(self, x, y, multiplicity): - """ Constructs the discrete function. - - :param x: array of length N defining the points at which the values are - defined. - :param y: array of length N degining the values at the points x. - :param multiplicity: array of length N defining the multiplicity of the - values. - """ - # convert parameters to arrays, also ensures copying - self.x = np.array(x) - self.y = np.array(y) - self.mp = np.array(multiplicity) - - def copy(self): - """ Returns a copy of itself - - :rtype: :class:`DiscreteFunction` - """ - return DiscreteFunction(self.x, self.y, self.mp) - - def almost_equal(self, other, decimal=14): - """ Checks if the function is equal to another function up to `decimal` - precision. - - :param other: another :class:`DiscreteFunction` - :returns: True if the two functions are equal up to `decimal` decimals, - False otherwise - :rtype: bool - """ - eps = 10.0**(-decimal) - return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ - np.allclose(self.y, other.y, atol=eps, rtol=0.0) and \ - np.allclose(self.mp, other.mp, atol=eps, rtol=0.0) - - def get_plottable_data(self, averaging_window_size=0): - """ Returns two arrays containing x- and y-coordinates for plotting - the interval sequence. The optional parameter `averaging_window_size` - determines the size of an averaging window to smoothen the profile. If - this value is 0, no averaging is performed. - - :param averaging_window_size: size of the averaging window, default=0. - :returns: (x_plot, y_plot) containing plottable data - :rtype: pair of np.array - - Example:: - - x, y = f.get_plottable_data() - plt.plot(x, y, '-o', label="Discrete function") - """ - - if averaging_window_size > 0: - # for the averaged profile we have to take the multiplicity into - # account. values with higher multiplicity should be consider as if - # they appeared several times. Hence we can not know how many - # entries we have to consider to the left and right. Rather, we - # will iterate until some wanted multiplicity is reached. - - # the first value in self.mp contains the number of averaged - # profiles without any possible extra multiplicities - # (by implementation) - expected_mp = (averaging_window_size+1) * int(self.mp[0]) - y_plot = np.zeros_like(self.y) - # compute the values in a loop, could be done in cython if required - for i in xrange(len(y_plot)): - - if self.mp[i] >= expected_mp: - # the current value contains already all the wanted - # multiplicity - y_plot[i] = self.y[i]/self.mp[i] - continue - - # first look to the right - y = self.y[i] - mp_r = self.mp[i] - j = i+1 - while j < len(y_plot): - if mp_r+self.mp[j] < expected_mp: - # if we still dont reach the required multiplicity - # we take the whole value - y += self.y[j] - mp_r += self.mp[j] - else: - # otherwise, just some fraction - y += self.y[j] * (expected_mp - mp_r)/self.mp[j] - mp_r += (expected_mp - mp_r) - break - j += 1 - - # same story to the left - mp_l = self.mp[i] - j = i-1 - while j >= 0: - if mp_l+self.mp[j] < expected_mp: - y += self.y[j] - mp_l += self.mp[j] - else: - y += self.y[j] * (expected_mp - mp_l)/self.mp[j] - mp_l += (expected_mp - mp_l) - break - j -= 1 - y_plot[i] = y/(mp_l+mp_r-self.mp[i]) - return 1.0*self.x, y_plot - - else: # k = 0 - - return 1.0*self.x, 1.0*self.y/self.mp - - def integral(self, interval=None): - """ Returns the integral over the given interval. For the discrete - function, this amounts to the sum over all values divided by the total - multiplicity. - - :param interval: integration interval given as a pair of floats, or a - sequence of pairs in case of multiple intervals, if - None the integral over the whole function is computed. - :type interval: Pair, sequence of pairs, or None. - :returns: the integral - :rtype: float - """ - - def get_indices(ival): - """ Retuns the indeces surrounding the given interval""" - start_ind = np.searchsorted(self.x, ival[0], side='right') - end_ind = np.searchsorted(self.x, ival[1], side='left') - assert start_ind > 0 and end_ind < len(self.x), \ - "Invalid averaging interval" - return start_ind, end_ind - - if interval is None: - # no interval given, integrate over the whole spike train - # don't count the first value, which is zero by definition - return 1.0 * np.sum(self.y[1:-1]) / np.sum(self.mp[1:-1]) - - # check if interval is as sequence - assert isinstance(interval, collections.Sequence), \ - "Invalid value for `interval`. None, Sequence or Tuple expected." - # check if interval is a sequence of intervals - if not isinstance(interval[0], collections.Sequence): - # find the indices corresponding to the interval - start_ind, end_ind = get_indices(interval) - return (np.sum(self.y[start_ind:end_ind]) / - np.sum(self.mp[start_ind:end_ind])) - else: - value = 0.0 - multiplicity = 0.0 - for ival in interval: - # find the indices corresponding to the interval - start_ind, end_ind = get_indices(ival) - value += np.sum(self.y[start_ind:end_ind]) - multiplicity += np.sum(self.mp[start_ind:end_ind]) - return value/multiplicity - - def avrg(self, interval=None): - """ Computes the average of the interval sequence: - :math:`a = 1/N sum f_n ` where N is the number of intervals. - - :param interval: averaging interval given as a pair of floats, a - sequence of pairs for averaging multiple intervals, or - None, if None the average over the whole function is - computed. - :type interval: Pair, sequence of pairs, or None. - :returns: the average a. - :rtype: float - """ - return self.integral(interval) - - def add(self, f): - """ Adds another `DiscreteFunction` function to this function. - Note: only functions defined on the same interval can be summed. - - :param f: :class:`DiscreteFunction` function to be added. - :rtype: None - """ - assert self.x[0] == f.x[0], "The functions have different intervals" - assert self.x[-1] == f.x[-1], "The functions have different intervals" - - # cython version - try: - from cython_add import add_discrete_function_cython as \ - add_discrete_function_impl - except ImportError: - print("Warning: add_discrete_function_cython not found. Make \ -sure that PySpike is installed by running\n\ -'python setup.py build_ext --inplace'! \ -\n Falling back to slow python backend.") - # use python backend - from python_backend import add_discrete_function_python as \ - add_discrete_function_impl - - self.x, self.y, self.mp = \ - add_discrete_function_impl(self.x, self.y, self.mp, - f.x, f.y, f.mp) - - def mul_scalar(self, fac): - """ Multiplies the function with a scalar value - - :param fac: Value to multiply - :type fac: double - :rtype: None - """ - self.y *= fac - - -def average_profile(profiles): - """ Computes the average profile from the given ISI- or SPIKE-profiles. - - :param profiles: list of :class:`PieceWiseConstFunc` or - :class:`PieceWiseLinFunc` representing ISI- or - SPIKE-profiles to be averaged. - :returns: the averages profile :math:`` or :math:``. - :rtype: :class:`PieceWiseConstFunc` or :class:`PieceWiseLinFunc` - """ - assert len(profiles) > 1 - - avrg_profile = profiles[0].copy() - for i in xrange(1, len(profiles)): - avrg_profile.add(profiles[i]) - avrg_profile.mul_scalar(1.0/len(profiles)) # normalize - - return avrg_profile diff --git a/test/test_function.py b/test/test_function.py index 933fd2e..d81b03a 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -92,11 +92,12 @@ def test_pwc_avrg(): y = [0.5, 1.0, -0.25, 0.0, 1.5] f2 = spk.PieceWiseConstFunc(x, y) - f_avrg = spk.average_profile([f1, f2]) + f1.add(f2) + f1.mul_scalar(0.5) x_expected = [0.0, 0.75, 1.0, 2.0, 2.5, 2.7, 4.0] y_expected = [0.75, 1.0, 0.25, 0.625, 0.375, 1.125] - assert_array_almost_equal(f_avrg.x, x_expected, decimal=16) - assert_array_almost_equal(f_avrg.y, y_expected, decimal=16) + assert_array_almost_equal(f1.x, x_expected, decimal=16) + assert_array_almost_equal(f1.y, y_expected, decimal=16) def test_pwl(): @@ -196,11 +197,12 @@ def test_pwl_avrg(): y2_expected = np.array([0.8+1.0+0.5*0.75, 1.5+1.0-0.8*0.25/1.25, -0.4+0.2, 1.5-1.0, 0.75-0.5*0.2/1.5, 2.25]) / 2 - f_avrg = spk.average_profile([f1, f2]) + f1.add(f2) + f1.mul_scalar(0.5) - assert_array_almost_equal(f_avrg.x, x_expected, decimal=16) - assert_array_almost_equal(f_avrg.y1, y1_expected, decimal=16) - assert_array_almost_equal(f_avrg.y2, y2_expected, decimal=16) + assert_array_almost_equal(f1.x, x_expected, decimal=16) + assert_array_almost_equal(f1.y1, y1_expected, decimal=16) + assert_array_almost_equal(f1.y2, y2_expected, decimal=16) def test_df(): @@ -208,7 +210,7 @@ def test_df(): x = [0.0, 1.0, 2.0, 2.5, 4.0] y = [0.0, 1.0, 1.0, 0.0, 1.0] mp = [1.0, 2.0, 1.0, 2.0, 1.0] - f = spk.DiscreteFunction(x, y, mp) + f = spk.DiscreteFunc(x, y, mp) xp, yp = f.get_plottable_data() xp_expected = [0.0, 1.0, 2.0, 2.5, 4.0] @@ -237,6 +239,9 @@ if __name__ == "__main__": test_pwc() test_pwc_add() test_pwc_mul() + test_pwc_avrg() test_pwl() test_pwl_add() test_pwl_mul() + test_pwl_avrg() + test_df() -- cgit v1.2.3 From 01fa64bd8a8931544a1734de104e2fe72018694f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 28 Jan 2015 19:22:02 +0100 Subject: added missing py files --- pyspike/DiscreteFunc.py | 242 ++++++++++++++++++++++++++++++++++++++++++ pyspike/PieceWiseConstFunc.py | 168 +++++++++++++++++++++++++++++ pyspike/PieceWiseLinFunc.py | 197 ++++++++++++++++++++++++++++++++++ 3 files changed, 607 insertions(+) create mode 100644 pyspike/DiscreteFunc.py create mode 100644 pyspike/PieceWiseConstFunc.py create mode 100644 pyspike/PieceWiseLinFunc.py diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py new file mode 100644 index 0000000..3e24284 --- /dev/null +++ b/pyspike/DiscreteFunc.py @@ -0,0 +1,242 @@ +""" +Class representing discrete functions. + +Copyright 2014-2015, Mario Mulansky + +Distributed under the BSD License + +""" +from __future__ import print_function + +import numpy as np +import collections + + +############################################################## +# DiscreteFunc +############################################################## +class DiscreteFunc(object): + """ A class representing values defined on a discrete set of points. + """ + + def __init__(self, x, y, multiplicity): + """ Constructs the discrete function. + + :param x: array of length N defining the points at which the values are + defined. + :param y: array of length N degining the values at the points x. + :param multiplicity: array of length N defining the multiplicity of the + values. + """ + # convert parameters to arrays, also ensures copying + self.x = np.array(x) + self.y = np.array(y) + self.mp = np.array(multiplicity) + + def copy(self): + """ Returns a copy of itself + + :rtype: :class:`DiscreteFunc` + """ + return DiscreteFunc(self.x, self.y, self.mp) + + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + + :param other: another :class:`DiscreteFunc` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y, other.y, atol=eps, rtol=0.0) and \ + np.allclose(self.mp, other.mp, atol=eps, rtol=0.0) + + def get_plottable_data(self, averaging_window_size=0): + """ Returns two arrays containing x- and y-coordinates for plotting + the interval sequence. The optional parameter `averaging_window_size` + determines the size of an averaging window to smoothen the profile. If + this value is 0, no averaging is performed. + + :param averaging_window_size: size of the averaging window, default=0. + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Discrete function") + """ + + if averaging_window_size > 0: + # for the averaged profile we have to take the multiplicity into + # account. values with higher multiplicity should be consider as if + # they appeared several times. Hence we can not know how many + # entries we have to consider to the left and right. Rather, we + # will iterate until some wanted multiplicity is reached. + + # the first value in self.mp contains the number of averaged + # profiles without any possible extra multiplicities + # (by implementation) + expected_mp = (averaging_window_size+1) * int(self.mp[0]) + y_plot = np.zeros_like(self.y) + # compute the values in a loop, could be done in cython if required + for i in xrange(len(y_plot)): + + if self.mp[i] >= expected_mp: + # the current value contains already all the wanted + # multiplicity + y_plot[i] = self.y[i]/self.mp[i] + continue + + # first look to the right + y = self.y[i] + mp_r = self.mp[i] + j = i+1 + while j < len(y_plot): + if mp_r+self.mp[j] < expected_mp: + # if we still dont reach the required multiplicity + # we take the whole value + y += self.y[j] + mp_r += self.mp[j] + else: + # otherwise, just some fraction + y += self.y[j] * (expected_mp - mp_r)/self.mp[j] + mp_r += (expected_mp - mp_r) + break + j += 1 + + # same story to the left + mp_l = self.mp[i] + j = i-1 + while j >= 0: + if mp_l+self.mp[j] < expected_mp: + y += self.y[j] + mp_l += self.mp[j] + else: + y += self.y[j] * (expected_mp - mp_l)/self.mp[j] + mp_l += (expected_mp - mp_l) + break + j -= 1 + y_plot[i] = y/(mp_l+mp_r-self.mp[i]) + return 1.0*self.x, y_plot + + else: # k = 0 + + return 1.0*self.x, 1.0*self.y/self.mp + + def integral(self, interval=None): + """ Returns the integral over the given interval. For the discrete + function, this amounts to the sum over all values divided by the total + multiplicity. + + :param interval: integration interval given as a pair of floats, or a + sequence of pairs in case of multiple intervals, if + None the integral over the whole function is computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the integral + :rtype: float + """ + + def get_indices(ival): + """ Retuns the indeces surrounding the given interval""" + start_ind = np.searchsorted(self.x, ival[0], side='right') + end_ind = np.searchsorted(self.x, ival[1], side='left') + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + return start_ind, end_ind + + if interval is None: + # no interval given, integrate over the whole spike train + # don't count the first value, which is zero by definition + return 1.0 * np.sum(self.y[1:-1]) / np.sum(self.mp[1:-1]) + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): + # find the indices corresponding to the interval + start_ind, end_ind = get_indices(interval) + return (np.sum(self.y[start_ind:end_ind]) / + np.sum(self.mp[start_ind:end_ind])) + else: + value = 0.0 + multiplicity = 0.0 + for ival in interval: + # find the indices corresponding to the interval + start_ind, end_ind = get_indices(ival) + value += np.sum(self.y[start_ind:end_ind]) + multiplicity += np.sum(self.mp[start_ind:end_ind]) + return value/multiplicity + + def avrg(self, interval=None): + """ Computes the average of the interval sequence: + :math:`a = 1/N sum f_n ` where N is the number of intervals. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + """ + return self.integral(interval) + + def add(self, f): + """ Adds another `DiscreteFunc` function to this function. + Note: only functions defined on the same interval can be summed. + + :param f: :class:`DiscreteFunc` function to be added. + :rtype: None + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + + # cython version + try: + from cython_add import add_discrete_function_cython as \ + add_discrete_function_impl + except ImportError: + print("Warning: add_discrete_function_cython not found. Make \ +sure that PySpike is installed by running\n\ +'python setup.py build_ext --inplace'! \ +\n Falling back to slow python backend.") + # use python backend + from python_backend import add_discrete_function_python as \ + add_discrete_function_impl + + self.x, self.y, self.mp = \ + add_discrete_function_impl(self.x, self.y, self.mp, + f.x, f.y, f.mp) + + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + + :param fac: Value to multiply + :type fac: double + :rtype: None + """ + self.y *= fac + + +def average_profile(profiles): + """ Computes the average profile from the given ISI- or SPIKE-profiles. + + :param profiles: list of :class:`PieceWiseConstFunc` or + :class:`PieceWiseLinFunc` representing ISI- or + SPIKE-profiles to be averaged. + :returns: the averages profile :math:`` or :math:``. + :rtype: :class:`PieceWiseConstFunc` or :class:`PieceWiseLinFunc` + """ + assert len(profiles) > 1 + + avrg_profile = profiles[0].copy() + for i in xrange(1, len(profiles)): + avrg_profile.add(profiles[i]) + avrg_profile.mul_scalar(1.0/len(profiles)) # normalize + + return avrg_profile diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py new file mode 100644 index 0000000..e639dfc --- /dev/null +++ b/pyspike/PieceWiseConstFunc.py @@ -0,0 +1,168 @@ +""" +Class representing piece-wise constant functions. + +Copyright 2014-2015, Mario Mulansky + +Distributed under the BSD License + +""" +from __future__ import print_function + +import numpy as np +import collections + + +############################################################## +# PieceWiseConstFunc +############################################################## +class PieceWiseConstFunc(object): + """ A class representing a piece-wise constant function. """ + + def __init__(self, x, y): + """ Constructs the piece-wise const function. + + :param x: array of length N+1 defining the edges of the intervals of + the pwc function. + :param y: array of length N defining the function values at the + intervals. + """ + # convert parameters to arrays, also ensures copying + self.x = np.array(x) + self.y = np.array(y) + + def copy(self): + """ Returns a copy of itself + + :rtype: :class:`PieceWiseConstFunc` + """ + return PieceWiseConstFunc(self.x, self.y) + + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + + :param other: another :class:`PieceWiseConstFunc` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y, other.y, atol=eps, rtol=0.0) + + def get_plottable_data(self): + """ Returns two arrays containing x- and y-coordinates for immeditate + plotting of the piece-wise function. + + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Piece-wise const function") + """ + + x_plot = np.empty(2*len(self.x)-2) + x_plot[0] = self.x[0] + x_plot[1::2] = self.x[1:] + x_plot[2::2] = self.x[1:-1] + y_plot = np.empty(2*len(self.y)) + y_plot[::2] = self.y + y_plot[1::2] = self.y + + return x_plot, y_plot + + def integral(self, interval=None): + """ Returns the integral over the given interval. + + :param interval: integration interval given as a pair of floats, if + None the integral over the whole function is computed. + :type interval: Pair of floats or None. + :returns: the integral + :rtype: float + """ + if interval is None: + # no interval given, integrate over the whole spike train + a = np.sum((self.x[1:]-self.x[:-1]) * self.y) + else: + # find the indices corresponding to the interval + start_ind = np.searchsorted(self.x, interval[0], side='right') + end_ind = np.searchsorted(self.x, interval[1], side='left')-1 + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + # first the contribution from between the indices + a = np.sum((self.x[start_ind+1:end_ind+1] - + self.x[start_ind:end_ind]) * + self.y[start_ind:end_ind]) + # correction from start to first index + a += (self.x[start_ind]-interval[0]) * self.y[start_ind-1] + # correction from last index to end + a += (interval[1]-self.x[end_ind]) * self.y[end_ind] + return a + + def avrg(self, interval=None): + """ Computes the average of the piece-wise const function: + :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + """ + if interval is None: + # no interval given, average over the whole spike train + return self.integral() / (self.x[-1]-self.x[0]) + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): + # just one interval + a = self.integral(interval) / (interval[1]-interval[0]) + else: + # several intervals + a = 0.0 + int_length = 0.0 + for ival in interval: + a += self.integral(ival) + int_length += ival[1] - ival[0] + a /= int_length + return a + + def add(self, f): + """ Adds another PieceWiseConst function to this function. + Note: only functions defined on the same interval can be summed. + + :param f: :class:`PieceWiseConstFunc` function to be added. + :rtype: None + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + + # cython version + try: + from cython_add import add_piece_wise_const_cython as \ + add_piece_wise_const_impl + except ImportError: + print("Warning: add_piece_wise_const_cython not found. Make sure \ +that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ +\n Falling back to slow python backend.") + # use python backend + from python_backend import add_piece_wise_const_python as \ + add_piece_wise_const_impl + + self.x, self.y = add_piece_wise_const_impl(self.x, self.y, f.x, f.y) + + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + + :param fac: Value to multiply + :type fac: double + :rtype: None + """ + self.y *= fac diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py new file mode 100644 index 0000000..58a20e5 --- /dev/null +++ b/pyspike/PieceWiseLinFunc.py @@ -0,0 +1,197 @@ +""" +Class representing piece-wise linear functions. + +Copyright 2014-2015, Mario Mulansky + +Distributed under the BSD License + +""" +from __future__ import print_function + +import numpy as np +import collections + + +############################################################## +# PieceWiseLinFunc +############################################################## +class PieceWiseLinFunc: + """ A class representing a piece-wise linear function. """ + + def __init__(self, x, y1, y2): + """ Constructs the piece-wise linear function. + + :param x: array of length N+1 defining the edges of the intervals of + the pwc function. + :param y1: array of length N defining the function values at the left + of the intervals. + :param y2: array of length N defining the function values at the right + of the intervals. + """ + # convert to array, which also ensures copying + self.x = np.array(x) + self.y1 = np.array(y1) + self.y2 = np.array(y2) + + def copy(self): + """ Returns a copy of itself + + :rtype: :class:`PieceWiseLinFunc` + """ + return PieceWiseLinFunc(self.x, self.y1, self.y2) + + def almost_equal(self, other, decimal=14): + """ Checks if the function is equal to another function up to `decimal` + precision. + + :param other: another :class:`PieceWiseLinFunc` + :returns: True if the two functions are equal up to `decimal` decimals, + False otherwise + :rtype: bool + """ + eps = 10.0**(-decimal) + return np.allclose(self.x, other.x, atol=eps, rtol=0.0) and \ + np.allclose(self.y1, other.y1, atol=eps, rtol=0.0) and \ + np.allclose(self.y2, other.y2, atol=eps, rtol=0.0) + + def get_plottable_data(self): + """ Returns two arrays containing x- and y-coordinates for immeditate + plotting of the piece-wise function. + + :returns: (x_plot, y_plot) containing plottable data + :rtype: pair of np.array + + Example:: + + x, y = f.get_plottable_data() + plt.plot(x, y, '-o', label="Piece-wise const function") + """ + x_plot = np.empty(2*len(self.x)-2) + x_plot[0] = self.x[0] + x_plot[1::2] = self.x[1:] + x_plot[2::2] = self.x[1:-1] + y_plot = np.empty_like(x_plot) + y_plot[0::2] = self.y1 + y_plot[1::2] = self.y2 + return x_plot, y_plot + + def integral(self, interval=None): + """ Returns the integral over the given interval. + + :param interval: integration interval given as a pair of floats, if + None the integral over the whole function is computed. + :type interval: Pair of floats or None. + :returns: the integral + :rtype: float + """ + + def intermediate_value(x0, x1, y0, y1, x): + """ computes the intermediate value of a linear function """ + return y0 + (y1-y0)*(x-x0)/(x1-x0) + + if interval is None: + # no interval given, integrate over the whole spike train + integral = np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) + else: + # find the indices corresponding to the interval + start_ind = np.searchsorted(self.x, interval[0], side='right') + end_ind = np.searchsorted(self.x, interval[1], side='left')-1 + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + # first the contribution from between the indices + integral = np.sum((self.x[start_ind+1:end_ind+1] - + self.x[start_ind:end_ind]) * + 0.5*(self.y1[start_ind:end_ind] + + self.y2[start_ind:end_ind])) + # correction from start to first index + integral += (self.x[start_ind]-interval[0]) * 0.5 * \ + (self.y2[start_ind-1] + + intermediate_value(self.x[start_ind-1], + self.x[start_ind], + self.y1[start_ind-1], + self.y2[start_ind-1], + interval[0] + )) + # correction from last index to end + integral += (interval[1]-self.x[end_ind]) * 0.5 * \ + (self.y1[end_ind] + + intermediate_value(self.x[end_ind], self.x[end_ind+1], + self.y1[end_ind], self.y2[end_ind], + interval[1] + )) + return integral + + def avrg(self, interval=None): + """ Computes the average of the piece-wise linear function: + :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + + :param interval: averaging interval given as a pair of floats, a + sequence of pairs for averaging multiple intervals, or + None, if None the average over the whole function is + computed. + :type interval: Pair, sequence of pairs, or None. + :returns: the average a. + :rtype: float + + """ + + if interval is None: + # no interval given, average over the whole spike train + return self.integral() / (self.x[-1]-self.x[0]) + + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): + # just one interval + a = self.integral(interval) / (interval[1]-interval[0]) + else: + # several intervals + a = 0.0 + int_length = 0.0 + for ival in interval: + a += self.integral(ival) + int_length += ival[1] - ival[0] + a /= int_length + return a + + def add(self, f): + """ Adds another PieceWiseLin function to this function. + Note: only functions defined on the same interval can be summed. + + :param f: :class:`PieceWiseLinFunc` function to be added. + :rtype: None + """ + assert self.x[0] == f.x[0], "The functions have different intervals" + assert self.x[-1] == f.x[-1], "The functions have different intervals" + + # python implementation + # from python_backend import add_piece_wise_lin_python + # self.x, self.y1, self.y2 = add_piece_wise_lin_python( + # self.x, self.y1, self.y2, f.x, f.y1, f.y2) + + # cython version + try: + from cython_add import add_piece_wise_lin_cython as \ + add_piece_wise_lin_impl + except ImportError: + print("Warning: add_piece_wise_lin_cython not found. Make sure \ +that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ +\n Falling back to slow python backend.") + # use python backend + from python_backend import add_piece_wise_lin_python as \ + add_piece_wise_lin_impl + + self.x, self.y1, self.y2 = add_piece_wise_lin_impl( + self.x, self.y1, self.y2, f.x, f.y1, f.y2) + + def mul_scalar(self, fac): + """ Multiplies the function with a scalar value + + :param fac: Value to multiply + :type fac: double + :rtype: None + """ + self.y1 *= fac + self.y2 *= fac -- cgit v1.2.3 From be9eeb2e48115134b93ec0cd9035d97117bd019e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 3 Feb 2015 10:45:32 +0100 Subject: split distance.py into 3 separate modules --- pyspike/__init__.py | 14 +- pyspike/distances.py | 507 ---------------------------------------------- pyspike/generic.py | 83 ++++++++ pyspike/isi_distance.py | 132 ++++++++++++ pyspike/spike_distance.py | 135 ++++++++++++ pyspike/spike_sync.py | 136 +++++++++++++ 6 files changed, 494 insertions(+), 513 deletions(-) delete mode 100644 pyspike/distances.py create mode 100644 pyspike/generic.py create mode 100644 pyspike/isi_distance.py create mode 100644 pyspike/spike_distance.py create mode 100644 pyspike/spike_sync.py diff --git a/pyspike/__init__.py b/pyspike/__init__.py index f480964..1c2efbc 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -4,18 +4,20 @@ Copyright 2014, Mario Mulansky Distributed under the BSD License """ -__all__ = ["distances", "spikes", "PieceWiseConstFunc", "PieceWiseLinFunc", +__all__ = ["isi_distance", "spike_distance", "spike_sync", + "spikes", "PieceWiseConstFunc", "PieceWiseLinFunc", "DiscreteFunc"] from PieceWiseConstFunc import PieceWiseConstFunc from PieceWiseLinFunc import PieceWiseLinFunc from DiscreteFunc import DiscreteFunc -from distances import isi_profile, isi_distance, \ - spike_profile, spike_distance, \ - spike_sync_profile, spike_sync, \ - isi_profile_multi, isi_distance_multi, isi_distance_matrix, \ - spike_profile_multi, spike_distance_multi, spike_distance_matrix, \ +from isi_distance import isi_profile, isi_distance, isi_profile_multi,\ + isi_distance_multi, isi_distance_matrix +from spike_distance import spike_profile, spike_distance, spike_profile_multi,\ + spike_distance_multi, spike_distance_matrix +from spike_sync import spike_sync_profile, spike_sync,\ spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix + from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ spike_train_from_string, merge_spike_trains, generate_poisson_spikes diff --git a/pyspike/distances.py b/pyspike/distances.py deleted file mode 100644 index 9077871..0000000 --- a/pyspike/distances.py +++ /dev/null @@ -1,507 +0,0 @@ -""" distances.py - -Module containing several functions to compute spike distances - -Copyright 2014, Mario Mulansky - -Distributed under the BSD License -""" - -import numpy as np -import threading -from functools import partial - -from pyspike import PieceWiseConstFunc, PieceWiseLinFunc, DiscreteFunc - - -############################################################ -# isi_profile -############################################################ -def isi_profile(spikes1, spikes2): - """ Computes the isi-distance profile :math:`S_{isi}(t)` of the two given - spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi - values are defined positive S_isi(t)>=0. 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. - - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. - :returns: The isi-distance profile :math:`S_{isi}(t)` - :rtype: :class:`pyspike.function.PieceWiseConstFunc` - - """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0] == spikes2[0], \ - "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1] == spikes2[-1], \ - "Given spike trains seems not to have auxiliary spikes!" - - # load cython implementation - try: - from cython_distance import isi_distance_cython as isi_distance_impl - except ImportError: - print("Warning: isi_distance_cython not found. Make sure that PySpike \ -is installed by running\n 'python setup.py build_ext --inplace'!\n \ -Falling back to slow python backend.") - # use python backend - from python_backend import isi_distance_python as isi_distance_impl - - times, values = isi_distance_impl(spikes1, spikes2) - return PieceWiseConstFunc(times, values) - - -############################################################ -# isi_distance -############################################################ -def isi_distance(spikes1, spikes2, interval=None): - """ Computes the isi-distance I of the given spike trains. The - isi-distance is the integral over the isi distance profile - :math:`S_{isi}(t)`: - - .. math:: I = \int_{T_0}^{T_1} S_{isi}(t) dt. - - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. - :param interval: averaging interval given as a pair of floats (T0, T1), - if None the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: The isi-distance I. - :rtype: double - """ - return isi_profile(spikes1, spikes2).avrg(interval) - - -############################################################ -# spike_profile -############################################################ -def spike_profile(spikes1, spikes2): - """ Computes the spike-distance profile S_spike(t) of the two given spike - trains. Returns the profile as a PieceWiseLinFunc object. The S_spike - values are defined positive S_spike(t)>=0. 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. - - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. - :returns: The spike-distance profile :math:`S_{spike}(t)`. - :rtype: :class:`pyspike.function.PieceWiseLinFunc` - - """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0] == spikes2[0], \ - "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1] == spikes2[-1], \ - "Given spike trains seems not to have auxiliary spikes!" - - # cython implementation - try: - from cython_distance import spike_distance_cython \ - as spike_distance_impl - except ImportError: - print("Warning: spike_distance_cython not found. Make sure that \ -PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -Falling back to slow python backend.") - # use python backend - from python_backend import spike_distance_python as spike_distance_impl - - times, y_starts, y_ends = spike_distance_impl(spikes1, spikes2) - return PieceWiseLinFunc(times, y_starts, y_ends) - - -############################################################ -# spike_distance -############################################################ -def spike_distance(spikes1, spikes2, interval=None): - """ Computes the spike-distance S of the given spike trains. The - spike-distance is the integral over the isi distance profile S_spike(t): - - .. math:: S = \int_{T_0}^{T_1} S_{spike}(t) dt. - - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. - :param interval: averaging interval given as a pair of floats (T0, T1), - if None the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: The spike-distance. - :rtype: double - - """ - return spike_profile(spikes1, spikes2).avrg(interval) - - -############################################################ -# spike_sync_profile -############################################################ -def spike_sync_profile(spikes1, spikes2): - """ Computes the spike-synchronization profile S_sync(t) of the two given - spike trains. Returns the profile as a DiscreteFunction object. The S_sync - values are either 1 or 0, indicating the presence or absence of a - coincidence. 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. - - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. - :returns: The spike-distance profile :math:`S_{sync}(t)`. - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - - # cython implementation - try: - from cython_distance import coincidence_cython \ - as coincidence_impl - except ImportError: - print("Warning: spike_distance_cython not found. Make sure that \ -PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -Falling back to slow python backend.") - # use python backend - from python_backend import coincidence_python \ - as coincidence_impl - - times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) - - return DiscreteFunc(times, coincidences, multiplicity) - - -############################################################ -# spike_sync -############################################################ -def spike_sync(spikes1, spikes2, interval=None): - """ Computes the spike synchronization value SYNC of the given spike - trains. The spike synchronization value is the computed as the total number - of coincidences divided by the total number of spikes: - - .. math:: SYNC = \sum_n C_n / N. - - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. - :param interval: averaging interval given as a pair of floats (T0, T1), - if None the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: The spike synchronization value. - :rtype: double - """ - return spike_sync_profile(spikes1, spikes2).avrg(interval) - - -############################################################ -# _generic_profile_multi -############################################################ -def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): - """ Internal implementation detail, don't call this function directly, - use isi_profile_multi or spike_profile_multi instead. - - Computes the multi-variate distance for a set of spike-trains using the - pair_dist_func to compute pair-wise distances. That is it computes the - average distance of all pairs of spike-trains: - :math:`S(t) = 2/((N(N-1)) sum_{} S_{i,j}`, - where the sum goes over all pairs . - Args: - - spike_trains: list of spike trains - - pair_distance_func: function computing the distance of two spike trains - - indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - Returns: - - The averaged multi-variate distance of all pairs - """ - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - # start with first pair - (i, j) = pairs[0] - average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - for (i, j) in pairs[1:]: - current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - average_dist.add(current_dist) # add to the average - return average_dist, len(pairs) - - -############################################################ -# multi_distance_par -############################################################ -def _multi_distance_par(spike_trains, pair_distance_func, indices=None): - """ parallel implementation of the multi-distance. Not currently used as - it does not improve the performance. - """ - - num_threads = 2 - lock = threading.Lock() - - def run(spike_trains, index_pairs, average_dist): - (i, j) = index_pairs[0] - # print(i,j) - this_avrg = pair_distance_func(spike_trains[i], spike_trains[j]) - for (i, j) in index_pairs[1:]: - # print(i,j) - current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - this_avrg.add(current_dist) - with lock: - average_dist.add(this_avrg) - - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - num_pairs = len(pairs) - - # start with first pair - (i, j) = pairs[0] - average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - # remove the one we already computed - pairs = pairs[1:] - # distribute the rest into num_threads pieces - clustered_pairs = [pairs[n::num_threads] for n in xrange(num_threads)] - - threads = [] - for pairs in clustered_pairs: - t = threading.Thread(target=run, args=(spike_trains, pairs, - average_dist)) - threads.append(t) - t.start() - for t in threads: - t.join() - average_dist.mul_scalar(1.0/num_pairs) # normalize - return average_dist - - -############################################################ -# isi_profile_multi -############################################################ -def isi_profile_multi(spike_trains, indices=None): - """ computes the multi-variate isi distance profile for a set of spike - trains. That is the average isi-distance of all pairs of spike-trains: - S_isi(t) = 2/((N(N-1)) sum_{} S_{isi}^{i,j}, - where the sum goes over all pairs - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type state: list or None - :returns: The averaged isi profile :math:`(t)` - :rtype: :class:`pyspike.function.PieceWiseConstFunc` - """ - average_dist, M = _generic_profile_multi(spike_trains, isi_profile, - indices) - average_dist.mul_scalar(1.0/M) # normalize - return average_dist - - -############################################################ -# isi_distance_multi -############################################################ -def isi_distance_multi(spike_trains, indices=None, interval=None): - """ computes the multi-variate isi-distance for a set of spike-trains. - That is the time average of the multi-variate spike profile: - I = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, - where the sum goes over all pairs - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: The time-averaged isi distance :math:`I` - :rtype: double - """ - return isi_profile_multi(spike_trains, indices).avrg(interval) - - -############################################################ -# spike_profile_multi -############################################################ -def spike_profile_multi(spike_trains, indices=None): - """ Computes the multi-variate spike distance profile for a set of spike - trains. That is the average spike-distance of all pairs of spike-trains: - :math:`S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}`, - where the sum goes over all pairs - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :returns: The averaged spike profile :math:`(t)` - :rtype: :class:`pyspike.function.PieceWiseLinFunc` - - """ - average_dist, M = _generic_profile_multi(spike_trains, spike_profile, - indices) - average_dist.mul_scalar(1.0/M) # normalize - return average_dist - - -############################################################ -# spike_distance_multi -############################################################ -def spike_distance_multi(spike_trains, indices=None, interval=None): - """ Computes the multi-variate spike distance for a set of spike trains. - That is the time average of the multi-variate spike profile: - S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt - where the sum goes over all pairs - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: The averaged spike distance S. - :rtype: double - """ - return spike_profile_multi(spike_trains, indices).avrg(interval) - - -############################################################ -# spike_profile_multi -############################################################ -def spike_sync_profile_multi(spike_trains, indices=None): - """ Computes the multi-variate spike synchronization profile for a set of - spike trains. For each spike in the set of spike trains, the multi-variate - profile is defined as the number of coincidences divided by the number of - spike trains pairs involving the spike train of containing this spike, - which is the number of spike trains minus one (N-1). - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :returns: The multi-variate spike sync profile :math:`(t)` - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - prof_func = partial(spike_sync_profile) - average_dist, M = _generic_profile_multi(spike_trains, prof_func, - indices) - # average_dist.mul_scalar(1.0/M) # no normalization here! - return average_dist - - -############################################################ -# spike_distance_multi -############################################################ -def spike_sync_multi(spike_trains, indices=None, interval=None): - """ Computes the multi-variate spike synchronization value for a set of - spike trains. - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: The multi-variate spike synchronization value SYNC. - :rtype: double - """ - return spike_sync_profile_multi(spike_trains, indices).avrg(interval) - - -############################################################ -# generic_distance_matrix -############################################################ -def _generic_distance_matrix(spike_trains, dist_function, - indices=None, interval=None): - """ Internal implementation detail. Don't use this function directly. - Instead use isi_distance_matrix or spike_distance_matrix. - Computes the time averaged distance of all pairs of spike-trains. - Args: - - spike_trains: list of spike trains - - indices: list of indices defining which spike-trains to use - if None all given spike-trains are used (default=None) - Return: - - a 2D array of size len(indices)*len(indices) containing the average - pair-wise distance - """ - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - - distance_matrix = np.zeros((len(indices), len(indices))) - for i, j in pairs: - d = dist_function(spike_trains[i], spike_trains[j], interval) - distance_matrix[i, j] = d - distance_matrix[j, i] = d - return distance_matrix - - -############################################################ -# isi_distance_matrix -############################################################ -def isi_distance_matrix(spike_trains, indices=None, interval=None): - """ Computes the time averaged isi-distance of all pairs of spike-trains. - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: 2D array with the pair wise time average isi distances - :math:`I_{ij}` - :rtype: np.array - """ - return _generic_distance_matrix(spike_trains, isi_distance, - indices, interval) - - -############################################################ -# spike_distance_matrix -############################################################ -def spike_distance_matrix(spike_trains, indices=None, interval=None): - """ Computes the time averaged spike-distance of all pairs of spike-trains. - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: 2D array with the pair wise time average spike distances - :math:`S_{ij}` - :rtype: np.array - """ - return _generic_distance_matrix(spike_trains, spike_distance, - indices, interval) - - -############################################################ -# spike_sync_matrix -############################################################ -def spike_sync_matrix(spike_trains, indices=None, interval=None): - """ Computes the overall spike-synchronization value of all pairs of - spike-trains. - - :param spike_trains: list of spike trains - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param interval: averaging interval given as a pair of floats, if None - the average over the whole function is computed. - :type interval: Pair of floats or None. - :returns: 2D array with the pair wise time spike synchronization values - :math:`SYNC_{ij}` - :rtype: np.array - """ - return _generic_distance_matrix(spike_trains, spike_sync, - indices, interval) diff --git a/pyspike/generic.py b/pyspike/generic.py new file mode 100644 index 0000000..4f278d2 --- /dev/null +++ b/pyspike/generic.py @@ -0,0 +1,83 @@ +""" + +Generic functions to compute multi-variate profiles and distance matrices. + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" + + +import numpy as np + + +############################################################ +# _generic_profile_multi +############################################################ +def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): + """ Internal implementation detail, don't call this function directly, + use isi_profile_multi or spike_profile_multi instead. + + Computes the multi-variate distance for a set of spike-trains using the + pair_dist_func to compute pair-wise distances. That is it computes the + average distance of all pairs of spike-trains: + :math:`S(t) = 2/((N(N-1)) sum_{} S_{i,j}`, + where the sum goes over all pairs . + Args: + - spike_trains: list of spike trains + - pair_distance_func: function computing the distance of two spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - The averaged multi-variate distance of all pairs + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + # start with first pair + (i, j) = pairs[0] + average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + for (i, j) in pairs[1:]: + current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) + average_dist.add(current_dist) # add to the average + return average_dist, len(pairs) + + +############################################################ +# generic_distance_matrix +############################################################ +def _generic_distance_matrix(spike_trains, dist_function, + indices=None, interval=None): + """ Internal implementation detail. Don't use this function directly. + Instead use isi_distance_matrix or spike_distance_matrix. + Computes the time averaged distance of all pairs of spike-trains. + Args: + - spike_trains: list of spike trains + - indices: list of indices defining which spike-trains to use + if None all given spike-trains are used (default=None) + Return: + - a 2D array of size len(indices)*len(indices) containing the average + pair-wise distance + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + distance_matrix = np.zeros((len(indices), len(indices))) + for i, j in pairs: + d = dist_function(spike_trains[i], spike_trains[j], interval) + distance_matrix[i, j] = d + distance_matrix[j, i] = d + return distance_matrix diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py new file mode 100644 index 0000000..745d280 --- /dev/null +++ b/pyspike/isi_distance.py @@ -0,0 +1,132 @@ +""" + +Module containing several functions to compute the ISI profiles and distances + +Copyright 2014-2015, Mario Mulansky + +Distributed under the BSD License +""" + +from pyspike import PieceWiseConstFunc +from pyspike.generic import _generic_profile_multi, _generic_distance_matrix + + +############################################################ +# isi_profile +############################################################ +def isi_profile(spikes1, spikes2): + """ Computes the isi-distance profile :math:`S_{isi}(t)` of the two given + spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi + values are defined positive S_isi(t)>=0. 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. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The isi-distance profile :math:`S_{isi}(t)` + :rtype: :class:`pyspike.function.PieceWiseConstFunc` + + """ + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0] == spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1] == spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + + # load cython implementation + try: + from cython_distance import isi_distance_cython as isi_distance_impl + except ImportError: + print("Warning: isi_distance_cython not found. Make sure that PySpike \ +is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from python_backend import isi_distance_python as isi_distance_impl + + times, values = isi_distance_impl(spikes1, spikes2) + return PieceWiseConstFunc(times, values) + + +############################################################ +# isi_distance +############################################################ +def isi_distance(spikes1, spikes2, interval=None): + """ Computes the isi-distance I of the given spike trains. The + isi-distance is the integral over the isi distance profile + :math:`S_{isi}(t)`: + + .. math:: I = \int_{T_0}^{T_1} S_{isi}(t) dt. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :param interval: averaging interval given as a pair of floats (T0, T1), + if None the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The isi-distance I. + :rtype: double + """ + return isi_profile(spikes1, spikes2).avrg(interval) + + +############################################################ +# isi_profile_multi +############################################################ +def isi_profile_multi(spike_trains, indices=None): + """ computes the multi-variate isi distance profile for a set of spike + trains. That is the average isi-distance of all pairs of spike-trains: + S_isi(t) = 2/((N(N-1)) sum_{} S_{isi}^{i,j}, + where the sum goes over all pairs + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type state: list or None + :returns: The averaged isi profile :math:`(t)` + :rtype: :class:`pyspike.function.PieceWiseConstFunc` + """ + average_dist, M = _generic_profile_multi(spike_trains, isi_profile, + indices) + average_dist.mul_scalar(1.0/M) # normalize + return average_dist + + +############################################################ +# isi_distance_multi +############################################################ +def isi_distance_multi(spike_trains, indices=None, interval=None): + """ computes the multi-variate isi-distance for a set of spike-trains. + That is the time average of the multi-variate spike profile: + I = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, + where the sum goes over all pairs + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The time-averaged isi distance :math:`I` + :rtype: double + """ + return isi_profile_multi(spike_trains, indices).avrg(interval) + + +############################################################ +# isi_distance_matrix +############################################################ +def isi_distance_matrix(spike_trains, indices=None, interval=None): + """ Computes the time averaged isi-distance of all pairs of spike-trains. + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: 2D array with the pair wise time average isi distances + :math:`I_{ij}` + :rtype: np.array + """ + return _generic_distance_matrix(spike_trains, isi_distance, + indices, interval) diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py new file mode 100644 index 0000000..2c989a4 --- /dev/null +++ b/pyspike/spike_distance.py @@ -0,0 +1,135 @@ +""" + +Module containing several functions to compute SPIKE profiles and distances + +Copyright 2014-2015, Mario Mulansky + +Distributed under the BSD License +""" + +from pyspike import PieceWiseLinFunc +from pyspike.generic import _generic_profile_multi, _generic_distance_matrix + + +############################################################ +# spike_profile +############################################################ +def spike_profile(spikes1, spikes2): + """ Computes the spike-distance profile S_spike(t) of the two given spike + trains. Returns the profile as a PieceWiseLinFunc object. The S_spike + values are defined positive S_spike(t)>=0. 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. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The spike-distance profile :math:`S_{spike}(t)`. + :rtype: :class:`pyspike.function.PieceWiseLinFunc` + + """ + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0] == spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1] == spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + + # cython implementation + try: + from cython_distance import spike_distance_cython \ + as spike_distance_impl + except ImportError: + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from python_backend import spike_distance_python as spike_distance_impl + + times, y_starts, y_ends = spike_distance_impl(spikes1, spikes2) + return PieceWiseLinFunc(times, y_starts, y_ends) + + +############################################################ +# spike_distance +############################################################ +def spike_distance(spikes1, spikes2, interval=None): + """ Computes the spike-distance S of the given spike trains. The + spike-distance is the integral over the isi distance profile S_spike(t): + + .. math:: S = \int_{T_0}^{T_1} S_{spike}(t) dt. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :param interval: averaging interval given as a pair of floats (T0, T1), + if None the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The spike-distance. + :rtype: double + + """ + return spike_profile(spikes1, spikes2).avrg(interval) + + +############################################################ +# spike_profile_multi +############################################################ +def spike_profile_multi(spike_trains, indices=None): + """ Computes the multi-variate spike distance profile for a set of spike + trains. That is the average spike-distance of all pairs of spike-trains: + :math:`S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}`, + where the sum goes over all pairs + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: The averaged spike profile :math:`(t)` + :rtype: :class:`pyspike.function.PieceWiseLinFunc` + + """ + average_dist, M = _generic_profile_multi(spike_trains, spike_profile, + indices) + average_dist.mul_scalar(1.0/M) # normalize + return average_dist + + +############################################################ +# spike_distance_multi +############################################################ +def spike_distance_multi(spike_trains, indices=None, interval=None): + """ Computes the multi-variate spike distance for a set of spike trains. + That is the time average of the multi-variate spike profile: + S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt + where the sum goes over all pairs + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The averaged spike distance S. + :rtype: double + """ + return spike_profile_multi(spike_trains, indices).avrg(interval) + + +############################################################ +# spike_distance_matrix +############################################################ +def spike_distance_matrix(spike_trains, indices=None, interval=None): + """ Computes the time averaged spike-distance of all pairs of spike-trains. + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: 2D array with the pair wise time average spike distances + :math:`S_{ij}` + :rtype: np.array + """ + return _generic_distance_matrix(spike_trains, spike_distance, + indices, interval) diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py new file mode 100644 index 0000000..f7cbe1d --- /dev/null +++ b/pyspike/spike_sync.py @@ -0,0 +1,136 @@ +""" + +Module containing several functions to compute SPIKE-Synchronization profiles +and distances + +Copyright 2014-2015, Mario Mulansky + +Distributed under the BSD License +""" + +from functools import partial +from pyspike import DiscreteFunc +from pyspike.generic import _generic_profile_multi, _generic_distance_matrix + + +############################################################ +# spike_sync_profile +############################################################ +def spike_sync_profile(spikes1, spikes2): + """ Computes the spike-synchronization profile S_sync(t) of the two given + spike trains. Returns the profile as a DiscreteFunction object. The S_sync + values are either 1 or 0, indicating the presence or absence of a + coincidence. 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. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :returns: The spike-distance profile :math:`S_{sync}(t)`. + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + + # cython implementation + try: + from cython_distance import coincidence_cython \ + as coincidence_impl + except ImportError: + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from python_backend import coincidence_python \ + as coincidence_impl + + times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) + + return DiscreteFunc(times, coincidences, multiplicity) + + +############################################################ +# spike_sync +############################################################ +def spike_sync(spikes1, spikes2, interval=None): + """ Computes the spike synchronization value SYNC of the given spike + trains. The spike synchronization value is the computed as the total number + of coincidences divided by the total number of spikes: + + .. math:: SYNC = \sum_n C_n / N. + + :param spikes1: ordered array of spike times with auxiliary spikes. + :param spikes2: ordered array of spike times with auxiliary spikes. + :param interval: averaging interval given as a pair of floats (T0, T1), + if None the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The spike synchronization value. + :rtype: double + """ + return spike_sync_profile(spikes1, spikes2).avrg(interval) + + +############################################################ +# spike_sync_profile_multi +############################################################ +def spike_sync_profile_multi(spike_trains, indices=None): + """ Computes the multi-variate spike synchronization profile for a set of + spike trains. For each spike in the set of spike trains, the multi-variate + profile is defined as the number of coincidences divided by the number of + spike trains pairs involving the spike train of containing this spike, + which is the number of spike trains minus one (N-1). + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: The multi-variate spike sync profile :math:`(t)` + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + prof_func = partial(spike_sync_profile) + average_dist, M = _generic_profile_multi(spike_trains, prof_func, + indices) + # average_dist.mul_scalar(1.0/M) # no normalization here! + return average_dist + + +############################################################ +# spike_distance_multi +############################################################ +def spike_sync_multi(spike_trains, indices=None, interval=None): + """ Computes the multi-variate spike synchronization value for a set of + spike trains. + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: The multi-variate spike synchronization value SYNC. + :rtype: double + """ + return spike_sync_profile_multi(spike_trains, indices).avrg(interval) + + +############################################################ +# spike_sync_matrix +############################################################ +def spike_sync_matrix(spike_trains, indices=None, interval=None): + """ Computes the overall spike-synchronization value of all pairs of + spike-trains. + + :param spike_trains: list of spike trains + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :returns: 2D array with the pair wise time spike synchronization values + :math:`SYNC_{ij}` + :rtype: np.array + """ + return _generic_distance_matrix(spike_trains, spike_sync, + indices, interval) -- cgit v1.2.3 From 7f20d9a8076326c1800373a7f95f4871873f14b0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 3 Feb 2015 10:46:51 +0100 Subject: copyright, docs --- pyspike/__init__.py | 2 +- pyspike/spike_sync.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 1c2efbc..945dd4e 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,5 +1,5 @@ """ -Copyright 2014, Mario Mulansky +Copyright 2014-2015, Mario Mulansky Distributed under the BSD License """ diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index f7cbe1d..bded8da 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -95,7 +95,7 @@ def spike_sync_profile_multi(spike_trains, indices=None): ############################################################ -# spike_distance_multi +# spike_sync_multi ############################################################ def spike_sync_multi(spike_trains, indices=None, interval=None): """ Computes the multi-variate spike synchronization value for a set of -- cgit v1.2.3 From 6eb6bc486027d3d5304a94cfb417a2257f2b6fd9 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 3 Feb 2015 12:19:53 +0100 Subject: moved cython functions to subdirectory --- pyspike/DiscreteFunc.py | 4 +- pyspike/PieceWiseConstFunc.py | 4 +- pyspike/PieceWiseLinFunc.py | 4 +- pyspike/cython/cython_add.pyx | 235 ++++++++++++++++++ pyspike/cython/cython_distance.pyx | 312 ++++++++++++++++++++++++ pyspike/cython/python_backend.py | 485 +++++++++++++++++++++++++++++++++++++ pyspike/cython_add.pyx | 235 ------------------ pyspike/cython_distance.pyx | 312 ------------------------ pyspike/isi_distance.py | 6 +- pyspike/python_backend.py | 485 ------------------------------------- pyspike/spike_distance.py | 5 +- pyspike/spike_sync.py | 4 +- setup.py | 14 +- 13 files changed, 1054 insertions(+), 1051 deletions(-) create mode 100644 pyspike/cython/cython_add.pyx create mode 100644 pyspike/cython/cython_distance.pyx create mode 100644 pyspike/cython/python_backend.py delete mode 100644 pyspike/cython_add.pyx delete mode 100644 pyspike/cython_distance.pyx delete mode 100644 pyspike/python_backend.py diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 3e24284..2283e03 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -198,7 +198,7 @@ class DiscreteFunc(object): # cython version try: - from cython_add import add_discrete_function_cython as \ + from cython.cython_add import add_discrete_function_cython as \ add_discrete_function_impl except ImportError: print("Warning: add_discrete_function_cython not found. Make \ @@ -206,7 +206,7 @@ sure that PySpike is installed by running\n\ 'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") # use python backend - from python_backend import add_discrete_function_python as \ + from cython.python_backend import add_discrete_function_python as \ add_discrete_function_impl self.x, self.y, self.mp = \ diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index e639dfc..dc57ab1 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -146,14 +146,14 @@ class PieceWiseConstFunc(object): # cython version try: - from cython_add import add_piece_wise_const_cython as \ + from cython.cython_add import add_piece_wise_const_cython as \ add_piece_wise_const_impl except ImportError: print("Warning: add_piece_wise_const_cython not found. Make sure \ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") # use python backend - from python_backend import add_piece_wise_const_python as \ + from cython.python_backend import add_piece_wise_const_python as \ add_piece_wise_const_impl self.x, self.y = add_piece_wise_const_impl(self.x, self.y, f.x, f.y) diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index 58a20e5..bc0aa2a 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -173,14 +173,14 @@ class PieceWiseLinFunc: # cython version try: - from cython_add import add_piece_wise_lin_cython as \ + from cython.cython_add import add_piece_wise_lin_cython as \ add_piece_wise_lin_impl except ImportError: print("Warning: add_piece_wise_lin_cython not found. Make sure \ that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") # use python backend - from python_backend import add_piece_wise_lin_python as \ + from cython.python_backend import add_piece_wise_lin_python as \ add_piece_wise_lin_impl self.x, self.y1, self.y2 = add_piece_wise_lin_impl( diff --git a/pyspike/cython/cython_add.pyx b/pyspike/cython/cython_add.pyx new file mode 100644 index 0000000..ac64005 --- /dev/null +++ b/pyspike/cython/cython_add.pyx @@ -0,0 +1,235 @@ +#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 + +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] + # the last value is again the end of the interval + # x_new[index+1] = x1[-1] + # only use the data that was actually filled + x1 = x_new[:index+2] + y1 = y_new[:index+1] + # end nogil + return np.array(x_new[:index+2]), np.array(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.array(x_new[:index+2]), + np.array(y1_new[:index+1]), + np.array(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) + cdef int N2 = len(y2) + 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 < len(y1): + x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] + y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] + mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] + index += len(x1)-index1-1 + 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(x2)-index2-1] = y2[index2+1:] + mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] + index += len(x2)-index2-1 + # 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] + + 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.array(x_new[:index+1]), + np.array(y_new[:index+1]), + np.array(mp_new[:index+1])) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx new file mode 100644 index 0000000..489aab9 --- /dev/null +++ b/pyspike/cython/cython_distance.pyx @@ -0,0 +1,312 @@ +#cython: boundscheck=False +#cython: wraparound=False +#cython: cdivision=True + +""" +cython_distances.pyx + +cython implementation of the isi- and spike-distance + +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 + +Distributed under the BSD License + +""" + +""" +To test whether things can be optimized: remove all yellow stuff +in the html output:: + + cython -a cython_distance.pyx + +which gives:: + + cython_distance.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): + + cdef double[:] spike_events + cdef double[:] isi_values + cdef int index1, index2, index + cdef int N1, N2 + cdef double nu1, nu2 + N1 = len(s1)-1 + N2 = len(s2)-1 + + nu1 = s1[1]-s1[0] + nu2 = s2[1]-s2[0] + spike_events = np.empty(N1+N2) + spike_events[0] = s1[0] + # the values have one entry less - the number of intervals between events + isi_values = np.empty(N1+N2-1) + + with nogil: # release the interpreter to allow multithreading + isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2) + index1 = 0 + index2 = 0 + index = 1 + while True: + # check which spike is next - from s1 or s2 + if s1[index1+1] < s2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1 >= N1: + break + spike_events[index] = s1[index1] + nu1 = s1[index1+1]-s1[index1] + elif s1[index1+1] > s2[index2+1]: + index2 += 1 + if index2 >= N2: + break + spike_events[index] = s2[index2] + nu2 = s2[index2+1]-s2[index2] + else: # s1[index1+1] == s2[index2+1] + index1 += 1 + index2 += 1 + if (index1 >= N1) or (index2 >= N2): + break + spike_events[index] = s1[index1] + nu1 = s1[index1+1]-s1[index1] + nu2 = s2[index2+1]-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 + spike_events[index] = s1[N1] + # 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=0) nogil: + """ Returns the minimal distance |spike_time - spike_train[i]| + with i>=start_index. + """ + cdef double d, d_temp + d = fabs(spike_time - spike_train[start_index]) + start_index += 1 + while start_index < N: + d_temp = fabs(spike_time - spike_train[start_index]) + if d_temp > d: + break + else: + d = d_temp + start_index += 1 + return d + + +############################################################ +# 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 ~ 0.5 for Poisson spikes + # return 0.5*(isi1*isi1+isi2*isi2) + + +############################################################ +# spike_distance_cython +############################################################ +def spike_distance_cython(double[:] t1, + double[:] t2): + + cdef double[:] spike_events + cdef double[:] y_starts + cdef double[:] y_ends + + cdef int N1, N2, index1, index2, index + cdef double dt_p1, dt_p2, dt_f1, dt_f2, isi1, isi2, s1, s2 + + N1 = len(t1) + N2 = len(t2) + + spike_events = np.empty(N1+N2-2) + spike_events[0] = t1[0] + y_starts = np.empty(len(spike_events)-1) + y_ends = np.empty(len(spike_events)-1) + + with nogil: # release the interpreter to allow multithreading + index1 = 0 + index2 = 0 + index = 1 + dt_p1 = 0.0 + dt_f1 = get_min_dist_cython(t1[1], t2, N2, 0) + dt_p2 = 0.0 + dt_f2 = get_min_dist_cython(t2[1], t1, N1, 0) + isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) + isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) + s1 = dt_f1*(t1[1]-t1[0])/isi1 + s2 = dt_f2*(t2[1]-t2[0])/isi2 + y_starts[0] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) + while True: + # print(index, index1, index2) + if t1[index1+1] < t2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1+1 >= N1: + break + spike_events[index] = t1[index1] + # first calculate the previous interval end value + dt_p1 = dt_f1 # the previous time now was the following time before + s1 = dt_p1 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) + # now the next interval start value + dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) + isi1 = t1[index1+1]-t1[index1] + # 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 t1[index1+1] > t2[index2+1]: + index2 += 1 + if index2+1 >= N2: + break + spike_events[index] = t2[index2] + # first calculate the previous interval end value + dt_p2 = dt_f2 # the previous time now was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + + dt_f1*(t2[index2]-t1[index1])) / isi1 + s2 = dt_p2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) + # now the next interval start value + dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) + #s2 = dt_f2 + isi2 = t2[index2+1]-t2[index2] + # 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: # t1[index1+1] == t2[index2+1] - generate only one event + index1 += 1 + index2 += 1 + if (index1+1 >= N1) or (index2+1 >= N2): + break + spike_events[index] = t1[index1] + y_ends[index-1] = 0.0 + y_starts[index] = 0.0 + dt_p1 = 0.0 + dt_p2 = 0.0 + dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) + dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) + isi1 = t1[index1+1]-t1[index1] + isi2 = t2[index2+1]-t2[index2] + index += 1 + # the last event is the interval end + spike_events[index] = t1[N1-1] + # the ending value of the last interval + isi1 = max(t1[N1-1]-t1[N1-2], t1[N1-2]-t1[N1-3]) + isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) + s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 + s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 + 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] + + + +############################################################ +# coincidence_python +############################################################ +cdef inline double get_tau(double[:] spikes1, double[:] spikes2, int i, int j): + cdef double m = 1E100 # some huge number + cdef int N1 = len(spikes1)-2 + cdef int N2 = len(spikes2)-2 + if i < N1: + m = fmin(m, spikes1[i+1]-spikes1[i]) + if j < N2: + m = fmin(m, spikes2[j+1]-spikes2[j]) + if i > 1: + m = fmin(m, spikes1[i]-spikes1[i-1]) + if j > 1: + m = fmin(m, spikes2[j]-spikes2[j-1]) + return 0.5*m + + +############################################################ +# coincidence_cython +############################################################ +def coincidence_cython(double[:] spikes1, double[:] spikes2): + + cdef int N1 = len(spikes1) + cdef int N2 = len(spikes2) + cdef int i = 0 + cdef int j = 0 + 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 tau + while n < N1 + N2 - 2: + if spikes1[i+1] < spikes2[j+1]: + i += 1 + n += 1 + tau = get_tau(spikes1, spikes2, i, j) + st[n] = spikes1[i] + if j > 0 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 spikes1[i+1] > spikes2[j+1]: + j += 1 + n += 1 + tau = get_tau(spikes1, spikes2, i, j) + st[n] = spikes2[j] + if i > 0 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 + if i == N1-1 or j == N2-1: + break + 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] = spikes1[0] + st[len(st)-1] = spikes1[len(spikes1)-1] + c[0] = c[1] + c[len(c)-1] = c[len(c)-2] + mp[0] = mp[1] + mp[len(mp)-1] = mp[len(mp)-2] + + return st, c, mp diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py new file mode 100644 index 0000000..481daf9 --- /dev/null +++ b/pyspike/cython/python_backend.py @@ -0,0 +1,485 @@ +""" python_backend.py + +Collection of python functions that can be used instead of the cython +implementation. + +Copyright 2014, Mario Mulansky + +Distributed under the BSD License + +""" + +import numpy as np + + +############################################################ +# isi_distance_python +############################################################ +def isi_distance_python(s1, s2): + """ Plain Python implementation of the isi distance. + """ + # compute the interspike interval + nu1 = s1[1:] - s1[:-1] + nu2 = s2[1:] - s2[:-1] + + # compute the isi-distance + spike_events = np.empty(len(nu1) + len(nu2)) + spike_events[0] = s1[0] + # the values have one entry less - the number of intervals between events + isi_values = np.empty(len(spike_events) - 1) + # add the distance of the first events + # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ + # else 1.0 - nu2[0]/nu1[0] + isi_values[0] = abs(nu1[0] - nu2[0]) / max(nu1[0], nu2[0]) + index1 = 0 + index2 = 0 + index = 1 + while True: + # check which spike is next - from s1 or s2 + if s1[index1+1] < s2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1 >= len(nu1): + break + spike_events[index] = s1[index1] + elif s1[index1+1] > s2[index2+1]: + index2 += 1 + if index2 >= len(nu2): + break + spike_events[index] = s2[index2] + else: # s1[index1 + 1] == s2[index2 + 1] + index1 += 1 + index2 += 1 + if (index1 >= len(nu1)) or (index2 >= len(nu2)): + break + spike_events[index] = s1[index1] + # compute the corresponding isi-distance + isi_values[index] = abs(nu1[index1] - nu2[index2]) / \ + max(nu1[index1], nu2[index2]) + index += 1 + # the last event is the interval end + spike_events[index] = s1[-1] + # 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=0): + """ Returns the minimal distance |spike_time - spike_train[i]| + with i>=start_index. + """ + d = abs(spike_time - spike_train[start_index]) + start_index += 1 + while start_index < len(spike_train): + d_temp = abs(spike_time - spike_train[start_index]) + if d_temp > d: + break + else: + d = d_temp + start_index += 1 + return d + + +############################################################ +# spike_distance_python +############################################################ +def spike_distance_python(spikes1, spikes2): + """ 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. + Returns: + - PieceWiseLinFunc describing the spike-distance. + """ + # check for auxiliary spikes - first and last spikes should be identical + assert spikes1[0] == spikes2[0], \ + "Given spike trains seems not to have auxiliary spikes!" + assert spikes1[-1] == spikes2[-1], \ + "Given spike trains seems not to have auxiliary spikes!" + # shorter variables + t1 = spikes1 + t2 = spikes2 + + spike_events = np.empty(len(t1) + len(t2) - 2) + spike_events[0] = t1[0] + y_starts = np.empty(len(spike_events) - 1) + y_ends = np.empty(len(spike_events) - 1) + + index1 = 0 + index2 = 0 + index = 1 + dt_p1 = 0.0 + dt_f1 = get_min_dist(t1[1], t2, 0) + dt_p2 = 0.0 + dt_f2 = get_min_dist(t2[1], t1, 0) + isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) + isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) + s1 = dt_f1*(t1[1]-t1[0])/isi1 + s2 = dt_f2*(t2[1]-t2[0])/isi2 + y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + while True: + # print(index, index1, index2) + if t1[index1+1] < t2[index2+1]: + index1 += 1 + # break condition relies on existence of spikes at T_end + if index1+1 >= len(t1): + break + spike_events[index] = t1[index1] + # first calculate the previous interval end value + dt_p1 = dt_f1 # the previous time was the following time before + s1 = dt_p1 + s2 = (dt_p2*(t2[index2+1]-t1[index1]) + + dt_f2*(t1[index1]-t2[index2])) / isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f1 = get_min_dist(t1[index1+1], t2, index2) + isi1 = t1[index1+1]-t1[index1] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + elif t1[index1+1] > t2[index2+1]: + index2 += 1 + if index2+1 >= len(t2): + break + spike_events[index] = t2[index2] + # first calculate the previous interval end value + dt_p2 = dt_f2 # the previous time was the following time before + s1 = (dt_p1*(t1[index1+1]-t2[index2]) + + dt_f1*(t2[index2]-t1[index1])) / isi1 + s2 = dt_p2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + # now the next interval start value + dt_f2 = get_min_dist(t2[index2+1], t1, index1) + #s2 = dt_f2 + isi2 = t2[index2+1]-t2[index2] + # s2 is the same as above, thus we can compute y2 immediately + y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + else: # t1[index1+1] == t2[index2+1] - generate only one event + index1 += 1 + index2 += 1 + if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): + break + assert dt_f2 == 0.0 + assert dt_f1 == 0.0 + spike_events[index] = t1[index1] + y_ends[index-1] = 0.0 + y_starts[index] = 0.0 + dt_p1 = 0.0 + dt_p2 = 0.0 + dt_f1 = get_min_dist(t1[index1+1], t2, index2) + dt_f2 = get_min_dist(t2[index2+1], t1, index1) + isi1 = t1[index1+1]-t1[index1] + isi2 = t2[index2+1]-t2[index2] + index += 1 + # the last event is the interval end + spike_events[index] = t1[-1] + # the ending value of the last interval + isi1 = max(t1[-1]-t1[-2], t1[-2]-t1[-3]) + isi2 = max(t2[-1]-t2[-2], t2[-2]-t2[-3]) + s1 = dt_p1*(t1[-1]-t1[-2])/isi1 + s2 = dt_p2*(t2[-1]-t2[-2])/isi2 + y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/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 + + +############################################################ +# coincidence_python +############################################################ +def coincidence_python(spikes1, spikes2): + + def get_tau(spikes1, spikes2, i, j): + m = 1E100 # some huge number + if i < len(spikes1)-2: + m = min(m, spikes1[i+1]-spikes1[i]) + if j < len(spikes2)-2: + m = min(m, spikes2[j+1]-spikes2[j]) + if i > 1: + m = min(m, spikes1[i]-spikes1[i-1]) + if j > 1: + m = min(m, spikes2[j]-spikes2[j-1]) + return 0.5*m + N1 = len(spikes1) + N2 = len(spikes2) + i = 0 + j = 0 + 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 n < N1 + N2 - 2: + if spikes1[i+1] < spikes2[j+1]: + i += 1 + n += 1 + tau = get_tau(spikes1, spikes2, i, j) + st[n] = spikes1[i] + if j > 0 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 spikes1[i+1] > spikes2[j+1]: + j += 1 + n += 1 + tau = get_tau(spikes1, spikes2, i, j) + st[n] = spikes2[j] + if i > 0 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 + if i == N1-1 or j == N2-1: + break + 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] = spikes1[0] + st[-1] = spikes1[-1] + c[0] = c[1] + c[-1] = c[-2] + mp[0] = mp[1] + mp[-1] = mp[-2] + + return st, c, mp + + +############################################################ +# 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 + while (index1+1 < len(y1)) and (index2+1 < len(y2)): + 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 < len(y1): + x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] + y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] + mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] + index += len(x1)-index1-1 + 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(x2)-index2-1] = y2[index2+1:] + mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] + index += len(x2)-index2-1 + # 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] + + 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] + diff --git a/pyspike/cython_add.pyx b/pyspike/cython_add.pyx deleted file mode 100644 index ac64005..0000000 --- a/pyspike/cython_add.pyx +++ /dev/null @@ -1,235 +0,0 @@ -#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 - -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] - # the last value is again the end of the interval - # x_new[index+1] = x1[-1] - # only use the data that was actually filled - x1 = x_new[:index+2] - y1 = y_new[:index+1] - # end nogil - return np.array(x_new[:index+2]), np.array(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.array(x_new[:index+2]), - np.array(y1_new[:index+1]), - np.array(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) - cdef int N2 = len(y2) - 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 < len(y1): - x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] - y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] - mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] - index += len(x1)-index1-1 - 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(x2)-index2-1] = y2[index2+1:] - mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] - index += len(x2)-index2-1 - # 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] - - 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.array(x_new[:index+1]), - np.array(y_new[:index+1]), - np.array(mp_new[:index+1])) diff --git a/pyspike/cython_distance.pyx b/pyspike/cython_distance.pyx deleted file mode 100644 index 489aab9..0000000 --- a/pyspike/cython_distance.pyx +++ /dev/null @@ -1,312 +0,0 @@ -#cython: boundscheck=False -#cython: wraparound=False -#cython: cdivision=True - -""" -cython_distances.pyx - -cython implementation of the isi- and spike-distance - -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 - -Distributed under the BSD License - -""" - -""" -To test whether things can be optimized: remove all yellow stuff -in the html output:: - - cython -a cython_distance.pyx - -which gives:: - - cython_distance.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): - - cdef double[:] spike_events - cdef double[:] isi_values - cdef int index1, index2, index - cdef int N1, N2 - cdef double nu1, nu2 - N1 = len(s1)-1 - N2 = len(s2)-1 - - nu1 = s1[1]-s1[0] - nu2 = s2[1]-s2[0] - spike_events = np.empty(N1+N2) - spike_events[0] = s1[0] - # the values have one entry less - the number of intervals between events - isi_values = np.empty(N1+N2-1) - - with nogil: # release the interpreter to allow multithreading - isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2) - index1 = 0 - index2 = 0 - index = 1 - while True: - # check which spike is next - from s1 or s2 - if s1[index1+1] < s2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1 >= N1: - break - spike_events[index] = s1[index1] - nu1 = s1[index1+1]-s1[index1] - elif s1[index1+1] > s2[index2+1]: - index2 += 1 - if index2 >= N2: - break - spike_events[index] = s2[index2] - nu2 = s2[index2+1]-s2[index2] - else: # s1[index1+1] == s2[index2+1] - index1 += 1 - index2 += 1 - if (index1 >= N1) or (index2 >= N2): - break - spike_events[index] = s1[index1] - nu1 = s1[index1+1]-s1[index1] - nu2 = s2[index2+1]-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 - spike_events[index] = s1[N1] - # 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=0) nogil: - """ Returns the minimal distance |spike_time - spike_train[i]| - with i>=start_index. - """ - cdef double d, d_temp - d = fabs(spike_time - spike_train[start_index]) - start_index += 1 - while start_index < N: - d_temp = fabs(spike_time - spike_train[start_index]) - if d_temp > d: - break - else: - d = d_temp - start_index += 1 - return d - - -############################################################ -# 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 ~ 0.5 for Poisson spikes - # return 0.5*(isi1*isi1+isi2*isi2) - - -############################################################ -# spike_distance_cython -############################################################ -def spike_distance_cython(double[:] t1, - double[:] t2): - - cdef double[:] spike_events - cdef double[:] y_starts - cdef double[:] y_ends - - cdef int N1, N2, index1, index2, index - cdef double dt_p1, dt_p2, dt_f1, dt_f2, isi1, isi2, s1, s2 - - N1 = len(t1) - N2 = len(t2) - - spike_events = np.empty(N1+N2-2) - spike_events[0] = t1[0] - y_starts = np.empty(len(spike_events)-1) - y_ends = np.empty(len(spike_events)-1) - - with nogil: # release the interpreter to allow multithreading - index1 = 0 - index2 = 0 - index = 1 - dt_p1 = 0.0 - dt_f1 = get_min_dist_cython(t1[1], t2, N2, 0) - dt_p2 = 0.0 - dt_f2 = get_min_dist_cython(t2[1], t1, N1, 0) - isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) - isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) - s1 = dt_f1*(t1[1]-t1[0])/isi1 - s2 = dt_f2*(t2[1]-t2[0])/isi2 - y_starts[0] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) - while True: - # print(index, index1, index2) - if t1[index1+1] < t2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1+1 >= N1: - break - spike_events[index] = t1[index1] - # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time now was the following time before - s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + - dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) - # now the next interval start value - dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) - isi1 = t1[index1+1]-t1[index1] - # 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 t1[index1+1] > t2[index2+1]: - index2 += 1 - if index2+1 >= N2: - break - spike_events[index] = t2[index2] - # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time now was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + - dt_f1*(t2[index2]-t1[index1])) / isi1 - s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) - # now the next interval start value - dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) - #s2 = dt_f2 - isi2 = t2[index2+1]-t2[index2] - # 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: # t1[index1+1] == t2[index2+1] - generate only one event - index1 += 1 - index2 += 1 - if (index1+1 >= N1) or (index2+1 >= N2): - break - spike_events[index] = t1[index1] - y_ends[index-1] = 0.0 - y_starts[index] = 0.0 - dt_p1 = 0.0 - dt_p2 = 0.0 - dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) - dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) - isi1 = t1[index1+1]-t1[index1] - isi2 = t2[index2+1]-t2[index2] - index += 1 - # the last event is the interval end - spike_events[index] = t1[N1-1] - # the ending value of the last interval - isi1 = max(t1[N1-1]-t1[N1-2], t1[N1-2]-t1[N1-3]) - isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) - s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 - s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 - 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] - - - -############################################################ -# coincidence_python -############################################################ -cdef inline double get_tau(double[:] spikes1, double[:] spikes2, int i, int j): - cdef double m = 1E100 # some huge number - cdef int N1 = len(spikes1)-2 - cdef int N2 = len(spikes2)-2 - if i < N1: - m = fmin(m, spikes1[i+1]-spikes1[i]) - if j < N2: - m = fmin(m, spikes2[j+1]-spikes2[j]) - if i > 1: - m = fmin(m, spikes1[i]-spikes1[i-1]) - if j > 1: - m = fmin(m, spikes2[j]-spikes2[j-1]) - return 0.5*m - - -############################################################ -# coincidence_cython -############################################################ -def coincidence_cython(double[:] spikes1, double[:] spikes2): - - cdef int N1 = len(spikes1) - cdef int N2 = len(spikes2) - cdef int i = 0 - cdef int j = 0 - 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 tau - while n < N1 + N2 - 2: - if spikes1[i+1] < spikes2[j+1]: - i += 1 - n += 1 - tau = get_tau(spikes1, spikes2, i, j) - st[n] = spikes1[i] - if j > 0 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 spikes1[i+1] > spikes2[j+1]: - j += 1 - n += 1 - tau = get_tau(spikes1, spikes2, i, j) - st[n] = spikes2[j] - if i > 0 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 - if i == N1-1 or j == N2-1: - break - 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] = spikes1[0] - st[len(st)-1] = spikes1[len(spikes1)-1] - c[0] = c[1] - c[len(c)-1] = c[len(c)-2] - mp[0] = mp[1] - mp[len(mp)-1] = mp[len(mp)-2] - - return st, c, mp diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 745d280..c2ef8e8 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -35,13 +35,15 @@ def isi_profile(spikes1, spikes2): # load cython implementation try: - from cython_distance import isi_distance_cython as isi_distance_impl + from cython.cython_distance import isi_distance_cython \ + as isi_distance_impl except ImportError: print("Warning: isi_distance_cython not found. Make sure that PySpike \ is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from python_backend import isi_distance_python as isi_distance_impl + from cython.python_backend import isi_distance_python \ + as isi_distance_impl times, values = isi_distance_impl(spikes1, spikes2) return PieceWiseConstFunc(times, values) diff --git a/pyspike/python_backend.py b/pyspike/python_backend.py deleted file mode 100644 index 481daf9..0000000 --- a/pyspike/python_backend.py +++ /dev/null @@ -1,485 +0,0 @@ -""" python_backend.py - -Collection of python functions that can be used instead of the cython -implementation. - -Copyright 2014, Mario Mulansky - -Distributed under the BSD License - -""" - -import numpy as np - - -############################################################ -# isi_distance_python -############################################################ -def isi_distance_python(s1, s2): - """ Plain Python implementation of the isi distance. - """ - # compute the interspike interval - nu1 = s1[1:] - s1[:-1] - nu2 = s2[1:] - s2[:-1] - - # compute the isi-distance - spike_events = np.empty(len(nu1) + len(nu2)) - spike_events[0] = s1[0] - # the values have one entry less - the number of intervals between events - isi_values = np.empty(len(spike_events) - 1) - # add the distance of the first events - # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ - # else 1.0 - nu2[0]/nu1[0] - isi_values[0] = abs(nu1[0] - nu2[0]) / max(nu1[0], nu2[0]) - index1 = 0 - index2 = 0 - index = 1 - while True: - # check which spike is next - from s1 or s2 - if s1[index1+1] < s2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1 >= len(nu1): - break - spike_events[index] = s1[index1] - elif s1[index1+1] > s2[index2+1]: - index2 += 1 - if index2 >= len(nu2): - break - spike_events[index] = s2[index2] - else: # s1[index1 + 1] == s2[index2 + 1] - index1 += 1 - index2 += 1 - if (index1 >= len(nu1)) or (index2 >= len(nu2)): - break - spike_events[index] = s1[index1] - # compute the corresponding isi-distance - isi_values[index] = abs(nu1[index1] - nu2[index2]) / \ - max(nu1[index1], nu2[index2]) - index += 1 - # the last event is the interval end - spike_events[index] = s1[-1] - # 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=0): - """ Returns the minimal distance |spike_time - spike_train[i]| - with i>=start_index. - """ - d = abs(spike_time - spike_train[start_index]) - start_index += 1 - while start_index < len(spike_train): - d_temp = abs(spike_time - spike_train[start_index]) - if d_temp > d: - break - else: - d = d_temp - start_index += 1 - return d - - -############################################################ -# spike_distance_python -############################################################ -def spike_distance_python(spikes1, spikes2): - """ 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. - Returns: - - PieceWiseLinFunc describing the spike-distance. - """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0] == spikes2[0], \ - "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1] == spikes2[-1], \ - "Given spike trains seems not to have auxiliary spikes!" - # shorter variables - t1 = spikes1 - t2 = spikes2 - - spike_events = np.empty(len(t1) + len(t2) - 2) - spike_events[0] = t1[0] - y_starts = np.empty(len(spike_events) - 1) - y_ends = np.empty(len(spike_events) - 1) - - index1 = 0 - index2 = 0 - index = 1 - dt_p1 = 0.0 - dt_f1 = get_min_dist(t1[1], t2, 0) - dt_p2 = 0.0 - dt_f2 = get_min_dist(t2[1], t1, 0) - isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) - isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) - s1 = dt_f1*(t1[1]-t1[0])/isi1 - s2 = dt_f2*(t2[1]-t2[0])/isi2 - y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - while True: - # print(index, index1, index2) - if t1[index1+1] < t2[index2+1]: - index1 += 1 - # break condition relies on existence of spikes at T_end - if index1+1 >= len(t1): - break - spike_events[index] = t1[index1] - # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time was the following time before - s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + - dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - # now the next interval start value - dt_f1 = get_min_dist(t1[index1+1], t2, index2) - isi1 = t1[index1+1]-t1[index1] - # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - elif t1[index1+1] > t2[index2+1]: - index2 += 1 - if index2+1 >= len(t2): - break - spike_events[index] = t2[index2] - # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + - dt_f1*(t2[index2]-t1[index1])) / isi1 - s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - # now the next interval start value - dt_f2 = get_min_dist(t2[index2+1], t1, index1) - #s2 = dt_f2 - isi2 = t2[index2+1]-t2[index2] - # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - else: # t1[index1+1] == t2[index2+1] - generate only one event - index1 += 1 - index2 += 1 - if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): - break - assert dt_f2 == 0.0 - assert dt_f1 == 0.0 - spike_events[index] = t1[index1] - y_ends[index-1] = 0.0 - y_starts[index] = 0.0 - dt_p1 = 0.0 - dt_p2 = 0.0 - dt_f1 = get_min_dist(t1[index1+1], t2, index2) - dt_f2 = get_min_dist(t2[index2+1], t1, index1) - isi1 = t1[index1+1]-t1[index1] - isi2 = t2[index2+1]-t2[index2] - index += 1 - # the last event is the interval end - spike_events[index] = t1[-1] - # the ending value of the last interval - isi1 = max(t1[-1]-t1[-2], t1[-2]-t1[-3]) - isi2 = max(t2[-1]-t2[-2], t2[-2]-t2[-3]) - s1 = dt_p1*(t1[-1]-t1[-2])/isi1 - s2 = dt_p2*(t2[-1]-t2[-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/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 - - -############################################################ -# coincidence_python -############################################################ -def coincidence_python(spikes1, spikes2): - - def get_tau(spikes1, spikes2, i, j): - m = 1E100 # some huge number - if i < len(spikes1)-2: - m = min(m, spikes1[i+1]-spikes1[i]) - if j < len(spikes2)-2: - m = min(m, spikes2[j+1]-spikes2[j]) - if i > 1: - m = min(m, spikes1[i]-spikes1[i-1]) - if j > 1: - m = min(m, spikes2[j]-spikes2[j-1]) - return 0.5*m - N1 = len(spikes1) - N2 = len(spikes2) - i = 0 - j = 0 - 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 n < N1 + N2 - 2: - if spikes1[i+1] < spikes2[j+1]: - i += 1 - n += 1 - tau = get_tau(spikes1, spikes2, i, j) - st[n] = spikes1[i] - if j > 0 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 spikes1[i+1] > spikes2[j+1]: - j += 1 - n += 1 - tau = get_tau(spikes1, spikes2, i, j) - st[n] = spikes2[j] - if i > 0 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 - if i == N1-1 or j == N2-1: - break - 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] = spikes1[0] - st[-1] = spikes1[-1] - c[0] = c[1] - c[-1] = c[-2] - mp[0] = mp[1] - mp[-1] = mp[-2] - - return st, c, mp - - -############################################################ -# 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 - while (index1+1 < len(y1)) and (index2+1 < len(y2)): - 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 < len(y1): - x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] - y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] - mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] - index += len(x1)-index1-1 - 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(x2)-index2-1] = y2[index2+1:] - mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] - index += len(x2)-index2-1 - # 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] - - 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] - diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index 2c989a4..f721c86 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -35,14 +35,15 @@ def spike_profile(spikes1, spikes2): # cython implementation try: - from cython_distance import spike_distance_cython \ + from cython.cython_distance import spike_distance_cython \ as spike_distance_impl except ImportError: print("Warning: spike_distance_cython not found. Make sure that \ PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from python_backend import spike_distance_python as spike_distance_impl + from cython.python_backend import spike_distance_python \ + as spike_distance_impl times, y_starts, y_ends = spike_distance_impl(spikes1, spikes2) return PieceWiseLinFunc(times, y_starts, y_ends) diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index bded8da..342bf69 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -33,14 +33,14 @@ def spike_sync_profile(spikes1, spikes2): # cython implementation try: - from cython_distance import coincidence_cython \ + from cython.cython_distance import coincidence_cython \ as coincidence_impl except ImportError: print("Warning: spike_distance_cython not found. Make sure that \ PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from python_backend import coincidence_python \ + from cython.python_backend import coincidence_python \ as coincidence_impl times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) diff --git a/setup.py b/setup.py index 9cab5de..289d521 100644 --- a/setup.py +++ b/setup.py @@ -21,8 +21,8 @@ except ImportError: else: use_cython = True -if os.path.isfile("pyspike/cython_add.c") and \ - os.path.isfile("pyspike/cython_distance.c"): +if os.path.isfile("pyspike/cython/cython_add.c") and \ + os.path.isfile("pyspike/cython/cython_distance.c"): use_c = True else: use_c = False @@ -32,14 +32,14 @@ ext_modules = [] if use_cython: # Cython is available, compile .pyx -> .c ext_modules += [ - Extension("pyspike.cython_add", ["pyspike/cython_add.pyx"]), - Extension("pyspike.cython_distance", ["pyspike/cython_distance.pyx"]), + Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.pyx"]), + Extension("pyspike.cython.cython_distance", ["pyspike/cython/cython_distance.pyx"]), ] cmdclass.update({'build_ext': build_ext}) elif use_c: # c files are there, compile to binaries ext_modules += [ - Extension("pyspike.cython_add", ["pyspike/cython_add.c"]), - Extension("pyspike.cython_distance", ["pyspike/cython_distance.c"]), + Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.c"]), + Extension("pyspike.cython.cython_distance", ["pyspike/cython/cython_distance.c"]), ] # neither cython nor c files available -> automatic fall-back to python backend @@ -78,7 +78,7 @@ train similarity', 'Programming Language :: Python :: 2.7', ], package_data={ - 'pyspike': ['cython_add.c', 'cython_distance.c'], + 'pyspike': ['cython/cython_add.c', 'cython/cython_distance.c'], 'test': ['Spike_testdata.txt'] } ) -- cgit v1.2.3 From bfee34894f21212bcef5fe271e701b1cf8c86fde Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 3 Feb 2015 12:26:02 +0100 Subject: +init py --- pyspike/cython/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 pyspike/cython/__init__.py diff --git a/pyspike/cython/__init__.py b/pyspike/cython/__init__.py new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From ea61fc2ed03e42b3ea159b7ef7886d005c90e29f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 3 Feb 2015 15:13:22 +0100 Subject: update docs to the structural changes --- Readme.rst | 4 ++-- doc/conf.py | 4 ++-- doc/pyspike.rst | 47 +++++++++++++++++++++++++++++++++++------------ pyspike/DiscreteFunc.py | 6 +++--- pyspike/spikes.py | 2 +- 5 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Readme.rst b/Readme.rst index bcf6fa9..debd32e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -11,7 +11,7 @@ All computation intensive parts are implemented in C via cython_ to reach a comp PySpike provides the same fundamental functionality as the SPIKY_ framework for Matlab, which additionally contains spike-train generators, more spike train distance measures and many visualization routines. -All source codes are published under the BSD_License_. +All source codes are available on `Github `_ and are published under the BSD_License_. .. [#] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) `[pdf] `_ @@ -201,7 +201,7 @@ The parameter :code:`interval` is optional and if neglected the whole spike trai SPIKE synchronization -.............. +..................... **Important note:** diff --git a/doc/conf.py b/doc/conf.py index 48ebc7e..5427bb1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -57,7 +57,7 @@ master_doc = 'index' # General information about the project. project = u'PySpike' -copyright = u'2014, Mario Mulansky' +copyright = u'2014-2015, Mario Mulansky' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -254,7 +254,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ ('index', 'PySpike', u'PySpike Documentation', - u'Mario Mulansky', 'PySpike', 'One line description of project.', + u'Mario Mulansky', 'PySpike', 'Measure of spike train synchrony.', 'Miscellaneous'), ] diff --git a/doc/pyspike.rst b/doc/pyspike.rst index 39adea0..6aa36e7 100644 --- a/doc/pyspike.rst +++ b/doc/pyspike.rst @@ -4,35 +4,58 @@ pyspike package Submodules ---------- -pyspike.distances module ------------------------- +pyspike.isi_distance module +---------------------------------------- -.. automodule:: pyspike.distances +.. automodule:: pyspike.isi_distance :members: :undoc-members: :show-inheritance: -pyspike.function module ------------------------ +pyspike.spike_distance module +---------------------------------------- -.. automodule:: pyspike.function +.. automodule:: pyspike.spike_distance :members: :undoc-members: :show-inheritance: -pyspike.spikes module ---------------------- +pyspike.spike_sync module +---------------------------------------- -.. automodule:: pyspike.spikes +.. automodule:: pyspike.spike_sync + :members: + :undoc-members: + :show-inheritance: + +pyspike.PieceWiseConstFunc module +---------------------------------------- + +.. automodule:: pyspike.PieceWiseConstFunc + :members: + :undoc-members: + :show-inheritance: + +pyspike.PieceWiseLinFunc module +---------------------------------------- + +.. automodule:: pyspike.PieceWiseLinFunc :members: :undoc-members: :show-inheritance: +pyspike.DiscreteFunc module +---------------------------------------- -Module contents ---------------- +.. automodule:: pyspike.DiscreteFunc + :members: + :undoc-members: + :show-inheritance: + +pyspike.spikes module +---------------------------------------- -.. automodule:: pyspike +.. automodule:: pyspike.spikes :members: :undoc-members: :show-inheritance: diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 2283e03..bd13e1f 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -23,10 +23,10 @@ class DiscreteFunc(object): """ Constructs the discrete function. :param x: array of length N defining the points at which the values are - defined. + defined. :param y: array of length N degining the values at the points x. :param multiplicity: array of length N defining the multiplicity of the - values. + values. """ # convert parameters to arrays, also ensures copying self.x = np.array(x) @@ -174,7 +174,7 @@ class DiscreteFunc(object): def avrg(self, interval=None): """ Computes the average of the interval sequence: - :math:`a = 1/N sum f_n ` where N is the number of intervals. + :math:`a = 1/N sum f_n` where N is the number of intervals. :param interval: averaging interval given as a pair of floats, a sequence of pairs for averaging multiple intervals, or diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 6a3353e..c7a1e40 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -54,7 +54,7 @@ def spike_train_from_string(s, sep=' ', is_sorted=False): :param s: the string with (ordered) spike times :param sep: The separator between the time numbers, default=' '. :param is_sorted: if True, the spike times are not sorted after loading, - if False, spike times are sorted with `np.sort` + if False, spike times are sorted with `np.sort` :returns: array of spike times """ if not(is_sorted): -- cgit v1.2.3 From 7989b2d337a0e5d2e0223d7fdec73833ff47c7bb Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 3 Feb 2015 16:40:55 +0100 Subject: first version of psth profile --- examples/spike_sync.py | 8 ++++++++ pyspike/__init__.py | 3 ++- pyspike/cython/python_backend.py | 1 - pyspike/psth.py | 27 +++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 pyspike/psth.py diff --git a/examples/spike_sync.py b/examples/spike_sync.py index 7f9e762..9c5f75c 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -30,6 +30,8 @@ plt.legend(loc="center right") plt.figure() +plt.subplot(211) + f = spk.spike_sync_profile_multi(spike_trains) x, y = f.get_plottable_data() plt.plot(x, y, '-b', alpha=0.7, label="SPIKE-Sync profile") @@ -37,6 +39,12 @@ plt.plot(x, y, '-b', alpha=0.7, label="SPIKE-Sync profile") x1, y1 = f.get_plottable_data(averaging_window_size=50) plt.plot(x1, y1, '-k', lw=2.5, label="averaged SPIKE-Sync profile") +plt.subplot(212) + +f_psth = spk.psth(spike_trains, bin_size=5.0) +x, y = f_psth.get_plottable_data() +plt.plot(x, y, '-k', alpha=1.0, label="PSTH") + print("Average:", f.avrg()) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 945dd4e..4d3f9f6 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -4,7 +4,7 @@ Copyright 2014-2015, Mario Mulansky Distributed under the BSD License """ -__all__ = ["isi_distance", "spike_distance", "spike_sync", +__all__ = ["isi_distance", "spike_distance", "spike_sync", "psth", "spikes", "PieceWiseConstFunc", "PieceWiseLinFunc", "DiscreteFunc"] @@ -18,6 +18,7 @@ from spike_distance import spike_profile, spike_distance, spike_profile_multi,\ spike_distance_multi, spike_distance_matrix from spike_sync import spike_sync_profile, spike_sync,\ spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix +from psth import psth from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ spike_train_from_string, merge_spike_trains, generate_poisson_spikes diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 481daf9..4efefc5 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -482,4 +482,3 @@ def add_discrete_function_python(x1, y1, mp1, x2, y2, mp2): # 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] - diff --git a/pyspike/psth.py b/pyspike/psth.py new file mode 100644 index 0000000..8516460 --- /dev/null +++ b/pyspike/psth.py @@ -0,0 +1,27 @@ +""" + +Module containing functions to compute the PSTH profile + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" + +import numpy as np +from pyspike import PieceWiseConstFunc + + +# Computes the Peristimulus time histogram of a set of spike trains +def psth(spike_trains, bin_size): + + bins = int((spike_trains[0][-1] - spike_trains[0][0]) / bin_size) + + N = len(spike_trains) + combined_spike_train = spike_trains[0][1:-1] + for i in xrange(1, len(spike_trains)): + combined_spike_train = np.append(combined_spike_train, + spike_trains[i][1:-1]) + + vals, edges = np.histogram(combined_spike_train, bins, density=False) + bin_size = edges[1]-edges[0] + return PieceWiseConstFunc(edges, vals/(N*bin_size)) -- cgit v1.2.3 From 83b3ebc8e7b4372e8ac94b5630f080dbc0c86a7a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 22 Mar 2015 11:53:01 +0100 Subject: typos --- Readme.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Readme.rst b/Readme.rst index debd32e..e736262 100644 --- a/Readme.rst +++ b/Readme.rst @@ -138,7 +138,7 @@ The following code loads some exemplary spike trains, computes the dissimilarity isi_profile = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = isi_profile.get_plottable_data() plt.plot(x, y, '--k') - print("ISI distance: %.8f" % isi_profil.avrg()) + print("ISI distance: %.8f" % isi_profile.avrg()) plt.show() The ISI-profile is a piece-wise constant function, and hence the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. @@ -149,10 +149,10 @@ In the above example, the following code computes the ISI-distances obtained fro .. code:: python - isi1 = isi_profil.avrg(interval=(0, 1000)) - isi2 = isi_profil.avrg(interval=(1000, 2000)) - isi3 = isi_profil.avrg(interval=[(0, 1000), (2000, 3000)]) - isi4 = isi_profil.avrg(interval=[(1000, 2000), (3000, 4000)]) + isi1 = isi_profile.avrg(interval=(0, 1000)) + isi2 = isi_profile.avrg(interval=(1000, 2000)) + isi3 = isi_profile.avrg(interval=[(0, 1000), (2000, 3000)]) + isi4 = isi_profile.avrg(interval=[(1000, 2000), (3000, 4000)]) Note, how also multiple intervals can be supplied by giving a list of tuples. @@ -181,7 +181,7 @@ But the general approach is very similar: spike_profile = spk.spike_profile(spike_trains[0], spike_trains[1]) x, y = spike_profile.get_plottable_data() plt.plot(x, y, '--k') - print("SPIKE distance: %.8f" % spike_profil.avrg()) + print("SPIKE distance: %.8f" % spike_profile.avrg()) plt.show() This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. -- cgit v1.2.3 From 06a72795731c69340685e4bc2a8379626343b56e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 22 Mar 2015 11:53:50 +0100 Subject: no print statement --- pyspike/spikes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyspike/spikes.py b/pyspike/spikes.py index c7a1e40..9d7d6f4 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -160,7 +160,7 @@ def generate_poisson_spikes(rate, time_interval, add_aux_spikes=True): intervals = np.random.exponential(1.0/rate, N) # make sure we have enough spikes while T_start + sum(intervals) < T_end: - print T_start + sum(intervals) + # print T_start + sum(intervals) intervals = np.append(intervals, np.random.exponential(1.0/rate, N_append)) spikes = T_start + np.cumsum(intervals) -- cgit v1.2.3 From 4366f30d7a27a9aafdf0efc2192f4780706d439b Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 2 Apr 2015 20:11:37 +0200 Subject: added max_tau to spike_sync functions --- pyspike/cython/cython_distance.pyx | 14 +++++++++----- pyspike/spike_sync.py | 36 ++++++++++++++++++++++++++---------- test/test_distance.py | 4 ++++ 3 files changed, 39 insertions(+), 15 deletions(-) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index 489aab9..2834ca5 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -236,7 +236,8 @@ def spike_distance_cython(double[:] t1, ############################################################ # coincidence_python ############################################################ -cdef inline double get_tau(double[:] spikes1, double[:] spikes2, int i, int j): +cdef inline double get_tau(double[:] spikes1, double[:] spikes2, + int i, int j, max_tau): cdef double m = 1E100 # some huge number cdef int N1 = len(spikes1)-2 cdef int N2 = len(spikes2)-2 @@ -248,13 +249,16 @@ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, int i, int j): m = fmin(m, spikes1[i]-spikes1[i-1]) if j > 1: m = fmin(m, spikes2[j]-spikes2[j-1]) - return 0.5*m + m *= 0.5 + if max_tau > 0.0: + m = fmin(m, max_tau) + return m ############################################################ # coincidence_cython ############################################################ -def coincidence_cython(double[:] spikes1, double[:] spikes2): +def coincidence_cython(double[:] spikes1, double[:] spikes2, double max_tau): cdef int N1 = len(spikes1) cdef int N2 = len(spikes2) @@ -269,7 +273,7 @@ def coincidence_cython(double[:] spikes1, double[:] spikes2): if spikes1[i+1] < spikes2[j+1]: i += 1 n += 1 - tau = get_tau(spikes1, spikes2, i, j) + tau = get_tau(spikes1, spikes2, i, j, max_tau) st[n] = spikes1[i] if j > 0 and spikes1[i]-spikes2[j] < tau: # coincidence between the current spike and the previous spike @@ -279,7 +283,7 @@ def coincidence_cython(double[:] spikes1, double[:] spikes2): elif spikes1[i+1] > spikes2[j+1]: j += 1 n += 1 - tau = get_tau(spikes1, spikes2, i, j) + tau = get_tau(spikes1, spikes2, i, j, max_tau) st[n] = spikes2[j] if i > 0 and spikes2[j]-spikes1[i] < tau: # coincidence between the current spike and the previous spike diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 342bf69..e12ebb8 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -16,7 +16,7 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix ############################################################ # spike_sync_profile ############################################################ -def spike_sync_profile(spikes1, spikes2): +def spike_sync_profile(spikes1, spikes2, max_tau=None): """ Computes the spike-synchronization profile S_sync(t) of the two given spike trains. Returns the profile as a DiscreteFunction object. The S_sync values are either 1 or 0, indicating the presence or absence of a @@ -26,6 +26,8 @@ def spike_sync_profile(spikes1, spikes2): :param spikes1: ordered array of spike times with auxiliary spikes. :param spikes2: ordered array of spike times with auxiliary spikes. + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. :returns: The spike-distance profile :math:`S_{sync}(t)`. :rtype: :class:`pyspike.function.DiscreteFunction` @@ -43,7 +45,11 @@ Falling back to slow python backend.") from cython.python_backend import coincidence_python \ as coincidence_impl - times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2) + if max_tau is None: + max_tau = 0.0 + + times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2, + max_tau) return DiscreteFunc(times, coincidences, multiplicity) @@ -51,7 +57,7 @@ Falling back to slow python backend.") ############################################################ # spike_sync ############################################################ -def spike_sync(spikes1, spikes2, interval=None): +def spike_sync(spikes1, spikes2, interval=None, max_tau=None): """ Computes the spike synchronization value SYNC of the given spike trains. The spike synchronization value is the computed as the total number of coincidences divided by the total number of spikes: @@ -63,16 +69,18 @@ def spike_sync(spikes1, spikes2, interval=None): :param interval: averaging interval given as a pair of floats (T0, T1), if None the average over the whole function is computed. :type interval: Pair of floats or None. + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. :returns: The spike synchronization value. :rtype: double """ - return spike_sync_profile(spikes1, spikes2).avrg(interval) + return spike_sync_profile(spikes1, spikes2, max_tau).avrg(interval) ############################################################ # spike_sync_profile_multi ############################################################ -def spike_sync_profile_multi(spike_trains, indices=None): +def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): """ Computes the multi-variate spike synchronization profile for a set of spike trains. For each spike in the set of spike trains, the multi-variate profile is defined as the number of coincidences divided by the number of @@ -83,11 +91,13 @@ def spike_sync_profile_multi(spike_trains, indices=None): :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. :returns: The multi-variate spike sync profile :math:`(t)` :rtype: :class:`pyspike.function.DiscreteFunction` """ - prof_func = partial(spike_sync_profile) + prof_func = partial(spike_sync_profile, max_tau=max_tau) average_dist, M = _generic_profile_multi(spike_trains, prof_func, indices) # average_dist.mul_scalar(1.0/M) # no normalization here! @@ -97,7 +107,7 @@ def spike_sync_profile_multi(spike_trains, indices=None): ############################################################ # spike_sync_multi ############################################################ -def spike_sync_multi(spike_trains, indices=None, interval=None): +def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): """ Computes the multi-variate spike synchronization value for a set of spike trains. @@ -108,16 +118,19 @@ def spike_sync_multi(spike_trains, indices=None, interval=None): :param interval: averaging interval given as a pair of floats, if None the average over the whole function is computed. :type interval: Pair of floats or None. + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. :returns: The multi-variate spike synchronization value SYNC. :rtype: double """ - return spike_sync_profile_multi(spike_trains, indices).avrg(interval) + return spike_sync_profile_multi(spike_trains, indices, + max_tau).avrg(interval) ############################################################ # spike_sync_matrix ############################################################ -def spike_sync_matrix(spike_trains, indices=None, interval=None): +def spike_sync_matrix(spike_trains, indices=None, interval=None, max_tau=None): """ Computes the overall spike-synchronization value of all pairs of spike-trains. @@ -128,9 +141,12 @@ def spike_sync_matrix(spike_trains, indices=None, interval=None): :param interval: averaging interval given as a pair of floats, if None the average over the whole function is computed. :type interval: Pair of floats or None. + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. :returns: 2D array with the pair wise time spike synchronization values :math:`SYNC_{ij}` :rtype: np.array """ - return _generic_distance_matrix(spike_trains, spike_sync, + dist_func = partial(spike_sync, max_tau=max_tau) + return _generic_distance_matrix(spike_trains, dist_func, indices, interval) diff --git a/test/test_distance.py b/test/test_distance.py index 41f625e..ba19f5e 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -138,6 +138,10 @@ def test_spike_sync(): assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) + # test with some small max_tau, spike_sync should be 0 + assert_almost_equal(spk.spike_sync(spikes1, spikes2, max_tau=0.05), + 0.0, decimal=16) + spikes2 = np.array([3.1]) spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) assert_almost_equal(spk.spike_sync(spikes1, spikes2), -- cgit v1.2.3 From 22f480fc5c53308779667beb24604d8fc019fd9e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 2 Apr 2015 20:24:11 +0200 Subject: add max_tau to spike sync in python backend --- pyspike/cython/python_backend.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 4efefc5..749507a 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -245,9 +245,9 @@ def cumulative_sync_python(spikes1, spikes2): ############################################################ # coincidence_python ############################################################ -def coincidence_python(spikes1, spikes2): +def coincidence_python(spikes1, spikes2, max_tau): - def get_tau(spikes1, spikes2, i, j): + def get_tau(spikes1, spikes2, i, j, max_tau): m = 1E100 # some huge number if i < len(spikes1)-2: m = min(m, spikes1[i+1]-spikes1[i]) @@ -257,7 +257,10 @@ def coincidence_python(spikes1, spikes2): m = min(m, spikes1[i]-spikes1[i-1]) if j > 1: m = min(m, spikes2[j]-spikes2[j-1]) - return 0.5*m + m *= 0.5 + if max_tau > 0.0: + m = min(m, max_tau) + return m N1 = len(spikes1) N2 = len(spikes2) i = 0 @@ -270,7 +273,7 @@ def coincidence_python(spikes1, spikes2): if spikes1[i+1] < spikes2[j+1]: i += 1 n += 1 - tau = get_tau(spikes1, spikes2, i, j) + tau = get_tau(spikes1, spikes2, i, j, max_tau) st[n] = spikes1[i] if j > 0 and spikes1[i]-spikes2[j] < tau: # coincidence between the current spike and the previous spike @@ -280,7 +283,7 @@ def coincidence_python(spikes1, spikes2): elif spikes1[i+1] > spikes2[j+1]: j += 1 n += 1 - tau = get_tau(spikes1, spikes2, i, j) + tau = get_tau(spikes1, spikes2, i, j, max_tau) st[n] = spikes2[j] if i > 0 and spikes2[j]-spikes1[i] < tau: # coincidence between the current spike and the previous spike -- cgit v1.2.3 From 1b9f4ec0aee1281464cfcab02bb4c7c302dbbb00 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 21 Apr 2015 18:21:11 +0200 Subject: updated contributors --- Contributors.txt | 6 +++++- Readme.rst | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Contributors.txt b/Contributors.txt index 51e6751..0d4afee 100644 --- a/Contributors.txt +++ b/Contributors.txt @@ -4,7 +4,11 @@ Python/C Programming: Scientific Methods: - Thomas Kreuz - Nebojsa D. Bozanic +- Mario Mulansky +- Conor Houghton +- Daniel Chicharro + The work on PySpike was supported by the European Comission through the Marie Curie Initial Training Network 'Neural Engineering Transformative Technologies -(NETT)' under the project number 289146. \ No newline at end of file +(NETT)' under the project number 289146. diff --git a/Readme.rst b/Readme.rst index e736262..03441fc 100644 --- a/Readme.rst +++ b/Readme.rst @@ -297,7 +297,9 @@ Curie Initial Training Network* `Neural Engineering Transformative Technologies **Scientific Methods:** - Thomas Kreuz - Nebojsa D. Bozanic - + - Mario Mulansky + - Conor Houghton + - Daniel Chicharro .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance .. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance -- cgit v1.2.3 From 27aa30d1fdb830a04b608c702cf7b616115eeb50 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 22 Apr 2015 18:18:30 +0200 Subject: added SpikeTrain class, changed isi_distance spike trains are now represented as SpikeTrain objects consisting of the spike times and the interval edges. The implementation of the ISI-distance has been modified accordingly. The SPIKE-distance and SPIKE-Synchronization are still to be done. --- pyspike/SpikeTrain.py | 34 +++++++++++++++ pyspike/__init__.py | 3 +- pyspike/cython/cython_distance.pyx | 85 ++++++++++++++++++++++++-------------- pyspike/cython/python_backend.py | 72 +++++++++++++++++++++----------- pyspike/isi_distance.py | 17 ++++---- test/test_distance.py | 19 ++++----- 6 files changed, 156 insertions(+), 74 deletions(-) create mode 100644 pyspike/SpikeTrain.py diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py new file mode 100644 index 0000000..4760014 --- /dev/null +++ b/pyspike/SpikeTrain.py @@ -0,0 +1,34 @@ +""" Module containing the class representing spike trains for PySpike. + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" + +import numpy as np +import collections + + +class SpikeTrain: + """ Class representing spike trains for the PySpike Module.""" + + def __init__(self, spike_times, interval): + """ Constructs the SpikeTrain + :param spike_times: ordered array of spike times. + :param interval: interval defining the edges of the spike train. + Given as a pair of floats (T0, T1) or a single float T1, where T0=0 is + assumed. + """ + + # TODO: sanity checks + self.spikes = np.array(spike_times) + + # check if interval is as sequence + if not isinstance(interval, collections.Sequence): + # treat value as end time and assume t_start = 0 + self.t_start = 0.0 + self.t_end = interval + else: + # extract times from sequence + self.t_start = interval[0] + self.t_end = interval[1] diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 4d3f9f6..76e58a1 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -5,12 +5,13 @@ Distributed under the BSD License """ __all__ = ["isi_distance", "spike_distance", "spike_sync", "psth", - "spikes", "PieceWiseConstFunc", "PieceWiseLinFunc", + "spikes", "SpikeTrain", "PieceWiseConstFunc", "PieceWiseLinFunc", "DiscreteFunc"] from PieceWiseConstFunc import PieceWiseConstFunc from PieceWiseLinFunc import PieceWiseLinFunc from DiscreteFunc import DiscreteFunc +from SpikeTrain import SpikeTrain from isi_distance import isi_profile, isi_distance, isi_profile_multi,\ isi_distance_multi, isi_distance_matrix diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index 2834ca5..1d652ee 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -42,57 +42,82 @@ ctypedef np.float_t DTYPE_t ############################################################ # isi_distance_cython ############################################################ -def isi_distance_cython(double[:] s1, - double[:] s2): +def isi_distance_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)-1 - N2 = len(s2)-1 + 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: + nu1 = s1[0] - t_start + index1 = -1 + else: + nu1 = s1[1]-s1[0] + index1 = 0 + + if s2[0] > t_start: + nu2 = s2[0] - t_start + index2 = -1 + else: + nu2 = s2[1]-s2[0] + index2 = 0 - nu1 = s1[1]-s1[0] - nu2 = s2[1]-s2[0] - spike_events = np.empty(N1+N2) - spike_events[0] = s1[0] - # the values have one entry less - the number of intervals between events - isi_values = np.empty(N1+N2-1) + isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2) + index = 1 with nogil: # release the interpreter to allow multithreading - isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2) - index1 = 0 - index2 = 0 - index = 1 - while True: - # check which spike is next - from s1 or s2 - if s1[index1+1] < s2[index2+1]: + 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 - # break condition relies on existence of spikes at T_end - if index1 >= N1: - break spike_events[index] = s1[index1] - nu1 = s1[index1+1]-s1[index1] - elif s1[index1+1] > s2[index2+1]: + if index1 < N1-1: + nu1 = s1[index1+1]-s1[index1] + else: + nu1 = t_end-s1[index1] + elif (index2 < N2-1) and ((index1 == N1-1) or + (s1[index1+1] > s2[index2+1])): index2 += 1 - if index2 >= N2: - break spike_events[index] = s2[index2] - nu2 = s2[index2+1]-s2[index2] + if index2 < N2-1: + nu2 = s2[index2+1]-s2[index2] + else: + nu2 = t_end-s2[index2] else: # s1[index1+1] == s2[index2+1] index1 += 1 index2 += 1 - if (index1 >= N1) or (index2 >= N2): - break spike_events[index] = s1[index1] - nu1 = s1[index1+1]-s1[index1] - nu2 = s2[index2+1]-s2[index2] + if index1 < N1-1: + nu1 = s1[index1+1]-s1[index1] + else: + nu1 = t_end-s1[index1] + if index2 < N2-1: + nu2 = s2[index2+1]-s2[index2] + else: + nu2 = 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 - spike_events[index] = s1[N1] + 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] diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 749507a..4c37236 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -15,50 +15,72 @@ import numpy as np ############################################################ # isi_distance_python ############################################################ -def isi_distance_python(s1, s2): +def isi_distance_python(s1, s2, t_start, t_end): """ Plain Python implementation of the isi distance. """ - # compute the interspike interval - nu1 = s1[1:] - s1[:-1] - nu2 = s2[1:] - s2[:-1] + N1 = len(s1) + N2 = len(s2) # compute the isi-distance - spike_events = np.empty(len(nu1) + len(nu2)) - spike_events[0] = s1[0] + 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) - # add the distance of the first events - # isi_values[0] = nu1[0]/nu2[0] - 1.0 if nu1[0] <= nu2[0] \ - # else 1.0 - nu2[0]/nu1[0] - isi_values[0] = abs(nu1[0] - nu2[0]) / max(nu1[0], nu2[0]) - index1 = 0 - index2 = 0 + if s1[0] > t_start: + nu1 = s1[0] - t_start + index1 = -1 + else: + nu1 = s1[1] - s1[0] + index1 = 0 + if s2[0] > t_start: + nu2 = s2[0] - t_start + index2 = -1 + else: + nu2 = s2[1] - s2[0] + index2 = 0 + + isi_values[0] = abs(nu1 - nu2) / max(nu1, nu2) index = 1 - while True: + while index1+index2 < N1+N2-2: # check which spike is next - from s1 or s2 - if s1[index1+1] < s2[index2+1]: + if (index1 < N1-1) and (index2 == N2-1 or s1[index1+1] < s2[index2+1]): index1 += 1 - # break condition relies on existence of spikes at T_end - if index1 >= len(nu1): - break spike_events[index] = s1[index1] - elif s1[index1+1] > s2[index2+1]: + if index1 < N1-1: + nu1 = s1[index1+1]-s1[index1] + else: + nu1 = t_end-s1[index1] + + elif (index2 < N2-1) and (index1 == N1-1 or + s1[index1+1] > s2[index2+1]): index2 += 1 - if index2 >= len(nu2): - break spike_events[index] = s2[index2] + if index2 < N2-1: + nu2 = s2[index2+1]-s2[index2] + else: + nu2 = t_end-s2[index2] + else: # s1[index1 + 1] == s2[index2 + 1] index1 += 1 index2 += 1 - if (index1 >= len(nu1)) or (index2 >= len(nu2)): - break spike_events[index] = s1[index1] + if index1 < N1-1: + nu1 = s1[index1+1]-s1[index1] + else: + nu1 = t_end-s1[index1] + if index2 < N2-1: + nu2 = s2[index2+1]-s2[index2] + else: + nu2 = t_end-s2[index2] # compute the corresponding isi-distance - isi_values[index] = abs(nu1[index1] - nu2[index2]) / \ - max(nu1[index1], nu2[index2]) + isi_values[index] = abs(nu1 - nu2) / \ + max(nu1, nu2) index += 1 # the last event is the interval end - spike_events[index] = s1[-1] + 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] diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index c2ef8e8..a34e135 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -14,23 +14,25 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix ############################################################ # isi_profile ############################################################ -def isi_profile(spikes1, spikes2): +def isi_profile(spike_train1, spike_train2): """ Computes the isi-distance profile :math:`S_{isi}(t)` of the two given spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi values are defined positive S_isi(t)>=0. 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. - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: `SpikeTrain` :returns: The isi-distance profile :math:`S_{isi}(t)` :rtype: :class:`pyspike.function.PieceWiseConstFunc` """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0] == spikes2[0], \ + # check whether the spike trains are defined for the same interval + assert spike_train1.t_start == spike_train2.t_start, \ "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1] == spikes2[-1], \ + assert spike_train1.t_end == spike_train2.t_end, \ "Given spike trains seems not to have auxiliary spikes!" # load cython implementation @@ -45,7 +47,8 @@ Falling back to slow python backend.") from cython.python_backend import isi_distance_python \ as isi_distance_impl - times, values = isi_distance_impl(spikes1, spikes2) + times, values = isi_distance_impl(spike_train1.spikes, spike_train2.spikes, + spike_train1.t_start, spike_train1.t_end) return PieceWiseConstFunc(times, values) diff --git a/test/test_distance.py b/test/test_distance.py index ba19f5e..b54e908 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -15,12 +15,13 @@ from numpy.testing import assert_equal, assert_almost_equal, \ assert_array_almost_equal import pyspike as spk +from pyspike import SpikeTrain def test_isi(): # generate two spike trains: - t1 = np.array([0.2, 0.4, 0.6, 0.7]) - t2 = np.array([0.3, 0.45, 0.8, 0.9, 0.95]) + t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) + t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) # pen&paper calculation of the isi distance expected_times = [0.0, 0.2, 0.3, 0.4, 0.45, 0.6, 0.7, 0.8, 0.9, 0.95, 1.0] @@ -32,8 +33,6 @@ def test_isi(): expected_isi_val = sum((expected_times[1:] - expected_times[:-1]) * expected_isi)/(expected_times[-1]-expected_times[0]) - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_profile(t1, t2) # print("ISI: ", f.y) @@ -44,8 +43,8 @@ def test_isi(): assert_equal(spk.isi_distance(t1, t2), expected_isi_val) # check with some equal spike times - t1 = np.array([0.2, 0.4, 0.6]) - t2 = np.array([0.1, 0.4, 0.5, 0.6]) + t1 = SpikeTrain([0.2, 0.4, 0.6], [0.0, 1.0]) + t2 = SpikeTrain([0.1, 0.4, 0.5, 0.6], [0.0, 1.0]) expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] expected_isi = [0.1/0.2, 0.1/0.3, 0.1/0.3, 0.1/0.2, 0.1/0.2, 0.0/0.5] @@ -55,8 +54,6 @@ def test_isi(): expected_isi_val = sum((expected_times[1:] - expected_times[:-1]) * expected_isi)/(expected_times[-1]-expected_times[0]) - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.isi_profile(t1, t2) assert_equal(f.x, expected_times) @@ -318,6 +315,6 @@ def test_multi_variate_subsets(): if __name__ == "__main__": test_isi() - test_spike() - test_multi_isi() - test_multi_spike() + # test_spike() + # test_multi_isi() + # test_multi_spike() -- cgit v1.2.3 From ff829214390f6fb1ef1d5cc3bb746ec8ada1fcb3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Apr 2015 12:22:46 +0200 Subject: fixed some comments --- pyspike/isi_distance.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index a34e135..3b39fbe 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -17,14 +17,12 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix def isi_profile(spike_train1, spike_train2): """ Computes the isi-distance profile :math:`S_{isi}(t)` of the two given spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi - values are defined positive S_isi(t)>=0. 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. + values are defined positive S_isi(t)>=0. :param spike_train1: First spike train. :type spike_train1: :class:`pyspike.SpikeTrain` :param spike_train2: Second spike train. - :type spike_train2: `SpikeTrain` + :type spike_train2: :class:`pyspike.SpikeTrain` :returns: The isi-distance profile :math:`S_{isi}(t)` :rtype: :class:`pyspike.function.PieceWiseConstFunc` @@ -62,8 +60,10 @@ def isi_distance(spikes1, spikes2, interval=None): .. math:: I = \int_{T_0}^{T_1} S_{isi}(t) dt. - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` :param interval: averaging interval given as a pair of floats (T0, T1), if None the average over the whole function is computed. :type interval: Pair of floats or None. @@ -82,7 +82,7 @@ def isi_profile_multi(spike_trains, indices=None): S_isi(t) = 2/((N(N-1)) sum_{} S_{isi}^{i,j}, where the sum goes over all pairs - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type state: list or None -- cgit v1.2.3 From 7b86626102a599d285cead801b3553293295b84c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Apr 2015 12:25:05 +0200 Subject: fixed parameter names --- pyspike/isi_distance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 3b39fbe..621efdb 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -53,7 +53,7 @@ Falling back to slow python backend.") ############################################################ # isi_distance ############################################################ -def isi_distance(spikes1, spikes2, interval=None): +def isi_distance(spike_train1, spike_train2, interval=None): """ Computes the isi-distance I of the given spike trains. The isi-distance is the integral over the isi distance profile :math:`S_{isi}(t)`: @@ -70,7 +70,7 @@ def isi_distance(spikes1, spikes2, interval=None): :returns: The isi-distance I. :rtype: double """ - return isi_profile(spikes1, spikes2).avrg(interval) + return isi_profile(spike_train1, spike_train2).avrg(interval) ############################################################ -- cgit v1.2.3 From 326575c7271abee39330be847fe5c2d4439d756f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 23 Apr 2015 12:26:28 +0200 Subject: improved comment --- pyspike/isi_distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 621efdb..cb8ef54 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -122,7 +122,7 @@ def isi_distance_multi(spike_trains, indices=None, interval=None): def isi_distance_matrix(spike_trains, indices=None, interval=None): """ Computes the time averaged isi-distance of all pairs of spike-trains. - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None -- cgit v1.2.3 From ed85a9b72edcb7bba6ae1105e213b3b0a2f78d3a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 00:49:16 +0200 Subject: changed spike distance to use new SpikeTrain class --- pyspike/cython/cython_distance.pyx | 199 +++++++++++++++++++++++++----------- pyspike/cython/python_backend.py | 203 ++++++++++++++++++++++++------------- pyspike/spike_distance.py | 37 ++++--- test/test_distance.py | 34 ++++--- 4 files changed, 313 insertions(+), 160 deletions(-) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index 1d652ee..7999e0a 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -131,21 +131,30 @@ cdef inline double get_min_dist_cython(double spike_time, # use memory view to ensure inlining # np.ndarray[DTYPE_t,ndim=1] spike_train, int N, - int start_index=0) nogil: + 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 - d = fabs(spike_time - spike_train[start_index]) - start_index += 1 + # 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: - break + return d else: d = d_temp start_index += 1 - return d + + # finally, check the distance to end time + d_temp = fabs(t_end - spike_time) + if d_temp > d: + return d + else: + return d_temp ############################################################ @@ -160,96 +169,162 @@ cdef inline double isi_avrg_cython(double isi1, double isi2) nogil: ############################################################ # spike_distance_cython ############################################################ -def spike_distance_cython(double[:] t1, - double[:] t2): +def spike_distance_cython(double[:] t1, double[:] t2, + double t_start, double t_end): cdef double[:] spike_events cdef double[:] y_starts cdef double[:] y_ends cdef int N1, N2, index1, index2, index - cdef double dt_p1, dt_p2, dt_f1, dt_f2, isi1, isi2, s1, s2 + 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) - spike_events = np.empty(N1+N2-2) - spike_events[0] = t1[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 - index1 = 0 - index2 = 0 - index = 1 - dt_p1 = 0.0 - dt_f1 = get_min_dist_cython(t1[1], t2, N2, 0) - dt_p2 = 0.0 - dt_f2 = get_min_dist_cython(t2[1], t1, N1, 0) - isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) - isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) - s1 = dt_f1*(t1[1]-t1[0])/isi1 - s2 = dt_f2*(t2[1]-t2[0])/isi2 + spike_events[0] = t_start + t_p1 = t_start + t_p2 = t_start + if t1[0] > t_start: + # dt_p1 = t2[0]-t_start + dt_p1 = 0.0 + 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]) + s1 = dt_f1*(t_f1-t_start)/isi1 + index1 = -1 + else: + dt_p1 = 0.0 + t_f1 = t1[1] + dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_start, t_end) + isi1 = t1[1]-t1[0] + s1 = dt_p1 + index1 = 0 + if t2[0] > t_start: + # dt_p1 = t2[0]-t_start + dt_p2 = 0.0 + t_f2 = t2[0] + dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_start, t_end) + isi2 = fmax(t_f2-t_start, t2[1]-t2[0]) + s2 = dt_f2*(t_f2-t_start)/isi2 + index2 = -1 + else: + dt_p2 = 0.0 + t_f2 = t2[1] + dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_start, t_end) + isi2 = t2[1]-t2[0] + s2 = dt_p2 + index2 = 0 + y_starts[0] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) - while True: + index = 1 + + while index1+index2 < N1+N2-2: # print(index, index1, index2) - if t1[index1+1] < t2[index2+1]: + if (index1 < N1-1) and (t_f1 < t_f2 or index2 == N2-1): index1 += 1 - # break condition relies on existence of spikes at T_end - if index1+1 >= N1: - break - spike_events[index] = t1[index1] # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time now was the following time before + # 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 + spike_events[index] = t_p1 s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + - dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) + 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 - dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) - isi1 = t1[index1+1]-t1[index1] + if index1 < N1-1: + dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2, + t_start, t_end) + isi1 = t_f1-t_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_starts[index] = (s1*isi2 + s2*isi1)/isi_avrg_cython(isi1, isi2) - elif t1[index1+1] > t2[index2+1]: + 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 - if index2+1 >= N2: - break - spike_events[index] = t2[index2] # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time now was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + - dt_f1*(t2[index2]-t1[index1])) / isi1 + # 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_end + spike_events[index] = t_p2 + s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1 s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) + y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, + isi2) # now the next interval start value - dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) - #s2 = dt_f2 - isi2 = t2[index2+1]-t2[index2] + if index2 < N2-1: + dt_f2 = get_min_dist_cython(t_f2, t1, N1, index1, + t_start, t_end) + isi2 = t_f2-t_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 # 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: # t1[index1+1] == t2[index2+1] - generate only one event + else: # t_f1 == t_f2 - generate only one event index1 += 1 index2 += 1 - if (index1+1 >= N1) or (index2+1 >= N2): - break - spike_events[index] = t1[index1] - y_ends[index-1] = 0.0 - y_starts[index] = 0.0 + t_p1 = t_f1 + t_p2 = t_f2 dt_p1 = 0.0 dt_p2 = 0.0 - dt_f1 = get_min_dist_cython(t1[index1+1], t2, N2, index2) - dt_f2 = get_min_dist_cython(t2[index2+1], t1, N1, index1) - isi1 = t1[index1+1]-t1[index1] - isi2 = t2[index2+1]-t2[index2] + 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_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 # the last event is the interval end - spike_events[index] = t1[N1-1] - # the ending value of the last interval - isi1 = max(t1[N1-1]-t1[N1-2], t1[N1-2]-t1[N1-3]) - isi2 = max(t2[N2-1]-t2[N2-2], t2[N2-2]-t2[N2-3]) - s1 = dt_p1*(t1[N1-1]-t1[N1-2])/isi1 - s2 = dt_p2*(t2[N2-1]-t2[N2-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) + if spike_events[index-1] == t_end: + index -= 1 + else: + spike_events[index] = t_end + # the ending value of the last interval + 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_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) # end nogil # use only the data added above diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 4c37236..bcf9c30 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -89,122 +89,185 @@ def isi_distance_python(s1, s2, t_start, t_end): ############################################################ # get_min_dist ############################################################ -def get_min_dist(spike_time, spike_train, start_index=0): +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 - spike_train[start_index]) - start_index += 1 + 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: - break + return d else: d = d_temp start_index += 1 - return d + # 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): +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. """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0] == spikes2[0], \ - "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1] == spikes2[-1], \ - "Given spike trains seems not to have auxiliary spikes!" + # shorter variables t1 = spikes1 t2 = spikes2 - spike_events = np.empty(len(t1) + len(t2) - 2) - spike_events[0] = t1[0] - y_starts = np.empty(len(spike_events) - 1) - y_ends = np.empty(len(spike_events) - 1) + N1 = len(t1) + N2 = len(t2) - index1 = 0 - index2 = 0 + spike_events = np.empty(N1+N2+2) + + y_starts = np.empty(len(spike_events)-1) + y_ends = np.empty(len(spike_events)-1) + + spike_events[0] = t_start + t_p1 = t_start + t_p2 = t_start + if t1[0] > t_start: + # dt_p1 = t2[0]-t_start + dt_p1 = 0.0 + t_f1 = t1[0] + dt_f1 = get_min_dist(t_f1, t2, 0, t_start, t_end) + isi1 = max(t_f1-t_start, t1[1]-t1[0]) + s1 = dt_f1*(t_f1-t_start)/isi1 + index1 = -1 + else: + dt_p1 = 0.0 + t_f1 = t1[1] + dt_f1 = get_min_dist(t_f1, t2, 0, t_start, t_end) + isi1 = t1[1]-t1[0] + s1 = dt_p1 + index1 = 0 + if t2[0] > t_start: + # dt_p1 = t2[0]-t_start + dt_p2 = 0.0 + t_f2 = t2[0] + dt_f2 = get_min_dist(t_f2, t1, 0, t_start, t_end) + isi2 = max(t_f2-t_start, t2[1]-t2[0]) + s2 = dt_f2*(t_f2-t_start)/isi2 + index2 = -1 + else: + dt_p2 = 0.0 + t_f2 = t2[1] + dt_f2 = get_min_dist(t_f2, t1, 0, t_start, t_end) + isi2 = t2[1]-t2[0] + s2 = dt_p2 + index2 = 0 + + y_starts[0] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) index = 1 - dt_p1 = 0.0 - dt_f1 = get_min_dist(t1[1], t2, 0) - dt_p2 = 0.0 - dt_f2 = get_min_dist(t2[1], t1, 0) - isi1 = max(t1[1]-t1[0], t1[2]-t1[1]) - isi2 = max(t2[1]-t2[0], t2[2]-t2[1]) - s1 = dt_f1*(t1[1]-t1[0])/isi1 - s2 = dt_f2*(t2[1]-t2[0])/isi2 - y_starts[0] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - while True: + + while index1+index2 < N1+N2-2: # print(index, index1, index2) - if t1[index1+1] < t2[index2+1]: + if (index1 < N1-1) and (t_f1 < t_f2 or index2 == N2-1): index1 += 1 - # break condition relies on existence of spikes at T_end - if index1+1 >= len(t1): - break - spike_events[index] = t1[index1] # first calculate the previous interval end value - dt_p1 = dt_f1 # the previous time was the following time before + # 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 + spike_events[index] = t_p1 s1 = dt_p1 - s2 = (dt_p2*(t2[index2+1]-t1[index1]) + - dt_f2*(t1[index1]-t2[index2])) / isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + 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 - dt_f1 = get_min_dist(t1[index1+1], t2, index2) - isi1 = t1[index1+1]-t1[index1] + if index1 < N1-1: + dt_f1 = get_min_dist(t_f1, t2, index2, t_start, t_end) + isi1 = t_f1-t_p1 + else: + dt_f1 = dt_p1 + isi1 = max(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_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - elif t1[index1+1] > t2[index2+1]: + 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 - if index2+1 >= len(t2): - break - spike_events[index] = t2[index2] # first calculate the previous interval end value - dt_p2 = dt_f2 # the previous time was the following time before - s1 = (dt_p1*(t1[index1+1]-t2[index2]) + - dt_f1*(t2[index2]-t1[index1])) / isi1 + # 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_end + spike_events[index] = t_p2 + s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1 s2 = dt_p2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) # now the next interval start value - dt_f2 = get_min_dist(t2[index2+1], t1, index1) - #s2 = dt_f2 - isi2 = t2[index2+1]-t2[index2] + if index2 < N2-1: + dt_f2 = get_min_dist(t_f2, t1, index1, t_start, t_end) + isi2 = t_f2-t_p2 + else: + dt_f2 = dt_p2 + isi2 = max(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 # s2 is the same as above, thus we can compute y2 immediately - y_starts[index] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) - else: # t1[index1+1] == t2[index2+1] - generate only one event + 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 - if (index1+1 >= len(t1)) or (index2+1 >= len(t2)): - break - assert dt_f2 == 0.0 - assert dt_f1 == 0.0 - spike_events[index] = t1[index1] - y_ends[index-1] = 0.0 - y_starts[index] = 0.0 + t_p1 = t_f1 + t_p2 = t_f2 dt_p1 = 0.0 dt_p2 = 0.0 - dt_f1 = get_min_dist(t1[index1+1], t2, index2) - dt_f2 = get_min_dist(t2[index2+1], t1, index1) - isi1 = t1[index1+1]-t1[index1] - isi2 = t2[index2+1]-t2[index2] + 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_start, t_end) + isi1 = t_f1 - t_p1 + else: + t_f1 = t_end + dt_f1 = dt_p1 + isi1 = max(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(t_f2, t1, index1, t_start, t_end) + isi2 = t_f2 - t_p2 + else: + t_f2 = t_end + dt_f2 = dt_p2 + isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) index += 1 # the last event is the interval end - spike_events[index] = t1[-1] - # the ending value of the last interval - isi1 = max(t1[-1]-t1[-2], t1[-2]-t1[-3]) - isi2 = max(t2[-1]-t2[-2], t2[-2]-t2[-3]) - s1 = dt_p1*(t1[-1]-t1[-2])/isi1 - s2 = dt_p2*(t2[-1]-t2[-2])/isi2 - y_ends[index-1] = (s1*isi2 + s2*isi1) / ((isi1+isi2)**2/2) + if spike_events[index-1] == t_end: + index -= 1 + else: + spike_events[index] = t_end + # the ending value of the last interval + 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_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] diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index f721c86..8d03d70 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -14,23 +14,23 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix ############################################################ # spike_profile ############################################################ -def spike_profile(spikes1, spikes2): +def spike_profile(spike_train1, spike_train2): """ Computes the spike-distance profile S_spike(t) of the two given spike trains. Returns the profile as a PieceWiseLinFunc object. The S_spike - values are defined positive S_spike(t)>=0. 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. + values are defined positive S_spike(t)>=0. - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` :returns: The spike-distance profile :math:`S_{spike}(t)`. :rtype: :class:`pyspike.function.PieceWiseLinFunc` """ - # check for auxiliary spikes - first and last spikes should be identical - assert spikes1[0] == spikes2[0], \ + # check whether the spike trains are defined for the same interval + assert spike_train1.t_start == spike_train2.t_start, \ "Given spike trains seems not to have auxiliary spikes!" - assert spikes1[-1] == spikes2[-1], \ + assert spike_train1.t_end == spike_train2.t_end, \ "Given spike trains seems not to have auxiliary spikes!" # cython implementation @@ -45,21 +45,26 @@ Falling back to slow python backend.") from cython.python_backend import spike_distance_python \ as spike_distance_impl - times, y_starts, y_ends = spike_distance_impl(spikes1, spikes2) + times, y_starts, y_ends = spike_distance_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end) return PieceWiseLinFunc(times, y_starts, y_ends) ############################################################ # spike_distance ############################################################ -def spike_distance(spikes1, spikes2, interval=None): +def spike_distance(spike_train1, spike_train2, interval=None): """ Computes the spike-distance S of the given spike trains. The spike-distance is the integral over the isi distance profile S_spike(t): .. math:: S = \int_{T_0}^{T_1} S_{spike}(t) dt. - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` :param interval: averaging interval given as a pair of floats (T0, T1), if None the average over the whole function is computed. :type interval: Pair of floats or None. @@ -67,7 +72,7 @@ def spike_distance(spikes1, spikes2, interval=None): :rtype: double """ - return spike_profile(spikes1, spikes2).avrg(interval) + return spike_profile(spike_train1, spike_train2).avrg(interval) ############################################################ @@ -102,7 +107,7 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt where the sum goes over all pairs - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None @@ -121,7 +126,7 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): def spike_distance_matrix(spike_trains, indices=None, interval=None): """ Computes the time averaged spike-distance of all pairs of spike-trains. - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None diff --git a/test/test_distance.py b/test/test_distance.py index b54e908..4af0e63 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -64,13 +64,26 @@ def test_isi(): def test_spike(): # generate two spike trains: - t1 = np.array([0.2, 0.4, 0.6, 0.7]) - t2 = np.array([0.3, 0.45, 0.8, 0.9, 0.95]) + t1 = SpikeTrain([0.0, 2.0, 5.0, 8.0], 10.0) + t2 = SpikeTrain([0.0, 1.0, 5.0, 9.0], 10.0) + + expected_times = np.array([0.0, 1.0, 2.0, 5.0, 8.0, 9.0, 10.0]) + + f = spk.spike_profile(t1, t2) + + assert_equal(f.x, expected_times) + + assert_almost_equal(f.avrg(), 0.1662415, decimal=6) + assert_almost_equal(f.y2[-1], 0.1394558, decimal=6) + + t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) + t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) # pen&paper calculation of the spike distance expected_times = [0.0, 0.2, 0.3, 0.4, 0.45, 0.6, 0.7, 0.8, 0.9, 0.95, 1.0] s1 = np.array([0.1, 0.1, (0.1*0.1+0.05*0.1)/0.2, 0.05, (0.05*0.15 * 2)/0.2, - 0.15, 0.1, 0.1*0.2/0.3, 0.1**2/0.3, 0.1*0.05/0.3, 0.1]) + 0.15, 0.1, (0.1*0.1+0.1*0.2)/0.3, (0.1*0.2+0.1*0.1)/0.3, + (0.1*0.05+0.1*0.25)/0.3, 0.1]) s2 = np.array([0.1, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, 0.1, 0.1, 0.05, 0.05]) @@ -86,19 +99,18 @@ def test_spike(): (expected_y1+expected_y2)/2) expected_spike_val /= (expected_times[-1]-expected_times[0]) - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_profile(t1, t2) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y1, expected_y1, decimal=15) assert_array_almost_equal(f.y2, expected_y2, decimal=15) - assert_equal(f.avrg(), expected_spike_val) - assert_equal(spk.spike_distance(t1, t2), expected_spike_val) + assert_almost_equal(f.avrg(), expected_spike_val, decimal=15) + assert_almost_equal(spk.spike_distance(t1, t2), expected_spike_val, + decimal=15) # check with some equal spike times - t1 = np.array([0.2, 0.4, 0.6]) - t2 = np.array([0.1, 0.4, 0.5, 0.6]) + t1 = SpikeTrain([0.2, 0.4, 0.6], [0.0, 1.0]) + t2 = SpikeTrain([0.1, 0.4, 0.5, 0.6], [0.0, 1.0]) expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] s1 = np.array([0.1, 0.1*0.1/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) @@ -115,8 +127,6 @@ def test_spike(): (expected_y1+expected_y2)/2) expected_spike_val /= (expected_times[-1]-expected_times[0]) - t1 = spk.add_auxiliary_spikes(t1, 1.0) - t2 = spk.add_auxiliary_spikes(t2, 1.0) f = spk.spike_profile(t1, t2) assert_equal(f.x, expected_times) @@ -315,6 +325,6 @@ def test_multi_variate_subsets(): if __name__ == "__main__": test_isi() - # test_spike() + test_spike() # test_multi_isi() # test_multi_spike() -- cgit v1.2.3 From 7da6da8533f9f76a99b959c9de37138377119ffc Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 12:08:05 +0200 Subject: changed spike sync implementation to SpikeTrain --- pyspike/SpikeTrain.py | 8 ++--- pyspike/cython/cython_distance.pyx | 37 +++++++++++------------ pyspike/spike_sync.py | 37 +++++++++++++++-------- test/test_distance.py | 62 +++++++++++++++++++++++--------------- 4 files changed, 83 insertions(+), 61 deletions(-) diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index 4760014..041f897 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -21,14 +21,14 @@ class SpikeTrain: """ # TODO: sanity checks - self.spikes = np.array(spike_times) + self.spikes = np.array(spike_times, dtype=float) # check if interval is as sequence if not isinstance(interval, collections.Sequence): # treat value as end time and assume t_start = 0 self.t_start = 0.0 - self.t_end = interval + self.t_end = float(interval) else: # extract times from sequence - self.t_start = interval[0] - self.t_end = interval[1] + self.t_start = float(interval[0]) + self.t_end = float(interval[1]) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index 7999e0a..6d998b9 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -339,11 +339,11 @@ def spike_distance_cython(double[:] t1, double[:] t2, cdef inline double get_tau(double[:] spikes1, double[:] spikes2, int i, int j, max_tau): cdef double m = 1E100 # some huge number - cdef int N1 = len(spikes1)-2 - cdef int N2 = len(spikes2)-2 - if i < N1: + cdef int N1 = len(spikes1)-1 + cdef int N2 = len(spikes2)-1 + if i < N1 and i > -1: m = fmin(m, spikes1[i+1]-spikes1[i]) - if j < N2: + if j < N2 and j > -1: m = fmin(m, spikes2[j+1]-spikes2[j]) if i > 1: m = fmin(m, spikes1[i]-spikes1[i-1]) @@ -358,34 +358,35 @@ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, ############################################################ # coincidence_cython ############################################################ -def coincidence_cython(double[:] spikes1, double[:] spikes2, double max_tau): +def coincidence_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 = 0 - cdef int j = 0 + 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[:] 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 tau - while n < N1 + N2 - 2: - if spikes1[i+1] < spikes2[j+1]: + while i + j < N1 + N2 - 2: + if (i < N1-1) and (spikes1[i+1] < spikes2[j+1] or j == N2-1): i += 1 n += 1 tau = get_tau(spikes1, spikes2, i, j, max_tau) st[n] = spikes1[i] - if j > 0 and spikes1[i]-spikes2[j] < tau: + 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 spikes1[i+1] > spikes2[j+1]: + elif (j < N2-1) and (spikes1[i+1] > spikes2[j+1] or i == N1-1): j += 1 n += 1 tau = get_tau(spikes1, spikes2, i, j, max_tau) st[n] = spikes2[j] - if i > 0 and spikes2[j]-spikes1[i] < tau: + 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 @@ -394,8 +395,6 @@ def coincidence_cython(double[:] spikes1, double[:] spikes2, double max_tau): # advance in both spike trains j += 1 i += 1 - if i == N1-1 or j == N2-1: - break n += 1 # add only one event, but with coincidence 2 and multiplicity 2 st[n] = spikes1[i] @@ -406,8 +405,8 @@ def coincidence_cython(double[:] spikes1, double[:] spikes2, double max_tau): c = c[:n+2] mp = mp[:n+2] - st[0] = spikes1[0] - st[len(st)-1] = spikes1[len(spikes1)-1] + st[0] = t_start + st[len(st)-1] = t_end c[0] = c[1] c[len(c)-1] = c[len(c)-2] mp[0] = mp[1] diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index e12ebb8..bca6f73 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -16,22 +16,27 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix ############################################################ # spike_sync_profile ############################################################ -def spike_sync_profile(spikes1, spikes2, max_tau=None): +def spike_sync_profile(spike_train1, spike_train2, max_tau=None): """ Computes the spike-synchronization profile S_sync(t) of the two given spike trains. Returns the profile as a DiscreteFunction object. The S_sync values are either 1 or 0, indicating the presence or absence of a - coincidence. 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. + coincidence. - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` :param max_tau: Maximum coincidence window size. If 0 or `None`, the coincidence window has no upper bound. :returns: The spike-distance profile :math:`S_{sync}(t)`. :rtype: :class:`pyspike.function.DiscreteFunction` """ + # check whether the spike trains are defined for the same interval + assert spike_train1.t_start == spike_train2.t_start, \ + "Given spike trains seems not to have auxiliary spikes!" + assert spike_train1.t_end == spike_train2.t_end, \ + "Given spike trains seems not to have auxiliary spikes!" # cython implementation try: @@ -48,7 +53,10 @@ Falling back to slow python backend.") if max_tau is None: max_tau = 0.0 - times, coincidences, multiplicity = coincidence_impl(spikes1, spikes2, + times, coincidences, multiplicity = coincidence_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, max_tau) return DiscreteFunc(times, coincidences, multiplicity) @@ -57,15 +65,17 @@ Falling back to slow python backend.") ############################################################ # spike_sync ############################################################ -def spike_sync(spikes1, spikes2, interval=None, max_tau=None): +def spike_sync(spike_train1, spike_train2, interval=None, max_tau=None): """ Computes the spike synchronization value SYNC of the given spike trains. The spike synchronization value is the computed as the total number of coincidences divided by the total number of spikes: .. math:: SYNC = \sum_n C_n / N. - :param spikes1: ordered array of spike times with auxiliary spikes. - :param spikes2: ordered array of spike times with auxiliary spikes. + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` :param interval: averaging interval given as a pair of floats (T0, T1), if None the average over the whole function is computed. :type interval: Pair of floats or None. @@ -74,7 +84,8 @@ def spike_sync(spikes1, spikes2, interval=None, max_tau=None): :returns: The spike synchronization value. :rtype: double """ - return spike_sync_profile(spikes1, spikes2, max_tau).avrg(interval) + return spike_sync_profile(spike_train1, spike_train2, + max_tau).avrg(interval) ############################################################ @@ -87,7 +98,7 @@ def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): spike trains pairs involving the spike train of containing this spike, which is the number of spike trains minus one (N-1). - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None @@ -134,7 +145,7 @@ def spike_sync_matrix(spike_trains, indices=None, interval=None, max_tau=None): """ Computes the overall spike-synchronization value of all pairs of spike-trains. - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None diff --git a/test/test_distance.py b/test/test_distance.py index 4af0e63..dbb72f1 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -138,10 +138,17 @@ def test_spike(): def test_spike_sync(): - spikes1 = np.array([1.0, 2.0, 3.0]) - spikes2 = np.array([2.1]) - spikes1 = spk.add_auxiliary_spikes(spikes1, 4.0) - spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + spikes1 = SpikeTrain([1.0, 2.0, 3.0], 4.0) + spikes2 = SpikeTrain([2.1], 4.0) + + expected_x = np.array([0.0, 1.0, 2.0, 2.1, 3.0, 4.0]) + expected_y = np.array([0.0, 0.0, 1.0, 1.0, 0.0, 0.0]) + + f = spk.spike_sync_profile(spikes1, spikes2) + + assert_array_almost_equal(f.x, expected_x, decimal=16) + assert_array_almost_equal(f.y, expected_y, decimal=16) + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) @@ -149,28 +156,34 @@ def test_spike_sync(): assert_almost_equal(spk.spike_sync(spikes1, spikes2, max_tau=0.05), 0.0, decimal=16) - spikes2 = np.array([3.1]) - spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + spikes2 = SpikeTrain([3.1], 4.0) assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) - spikes2 = np.array([1.1]) - spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + spikes2 = SpikeTrain([1.1], 4.0) + + expected_x = np.array([0.0, 1.0, 1.1, 2.0, 3.0, 4.0]) + expected_y = np.array([1.0, 1.0, 1.0, 0.0, 0.0, 0.0]) + + f = spk.spike_sync_profile(spikes1, spikes2) + + assert_array_almost_equal(f.x, expected_x, decimal=16) + assert_array_almost_equal(f.y, expected_y, decimal=16) + assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) - spikes2 = np.array([0.9]) - spikes2 = spk.add_auxiliary_spikes(spikes2, 4.0) + spikes2 = SpikeTrain([0.9], 4.0) assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) def check_multi_profile(profile_func, profile_func_multi): # generate spike trains: - t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) - t2 = spk.add_auxiliary_spikes(np.array([0.3, 0.45, 0.8, 0.9, 0.95]), 1.0) - t3 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6]), 1.0) - t4 = spk.add_auxiliary_spikes(np.array([0.1, 0.4, 0.5, 0.6]), 1.0) + t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) + t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) + t3 = SpikeTrain([0.2, 0.4, 0.6], 1.0) + t4 = SpikeTrain([0.1, 0.4, 0.5, 0.6], 1.0) spike_trains = [t1, t2, t3, t4] f12 = profile_func(t1, t2) @@ -213,15 +226,12 @@ def test_multi_spike(): def test_multi_spike_sync(): # some basic multivariate check - spikes1 = np.array([100, 300, 400, 405, 410, 500, 700, 800, - 805, 810, 815, 900], dtype=float) - spikes2 = np.array([100, 200, 205, 210, 295, 350, 400, 510, - 600, 605, 700, 910], dtype=float) - spikes3 = np.array([100, 180, 198, 295, 412, 420, 510, 640, - 695, 795, 820, 920], dtype=float) - spikes1 = spk.add_auxiliary_spikes(spikes1, 1000) - spikes2 = spk.add_auxiliary_spikes(spikes2, 1000) - spikes3 = spk.add_auxiliary_spikes(spikes3, 1000) + spikes1 = SpikeTrain([100, 300, 400, 405, 410, 500, 700, 800, + 805, 810, 815, 900], 1000) + spikes2 = SpikeTrain([100, 200, 205, 210, 295, 350, 400, 510, + 600, 605, 700, 910], 1000) + spikes3 = SpikeTrain([100, 180, 198, 295, 412, 420, 510, 640, + 695, 795, 820, 920], 1000) assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=15) assert_almost_equal(spk.spike_sync(spikes1, spikes3), @@ -326,5 +336,7 @@ def test_multi_variate_subsets(): if __name__ == "__main__": test_isi() test_spike() - # test_multi_isi() - # test_multi_spike() + test_spike_sync() + test_multi_isi() + test_multi_spike() + test_multi_spike_sync() -- cgit v1.2.3 From 3bf9e12e6b5667fb1ea72c969848dacaff3cb470 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 14:58:39 +0200 Subject: further adjustments in spike sync --- pyspike/SpikeTrain.py | 2 +- pyspike/__init__.py | 4 +- pyspike/cython/cython_distance.pyx | 6 +- pyspike/spike_sync.py | 6 +- pyspike/spikes.py | 113 ++++++++++++------------------------- test/test_distance.py | 5 +- 6 files changed, 49 insertions(+), 87 deletions(-) diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index 041f897..89520c9 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -9,7 +9,7 @@ import numpy as np import collections -class SpikeTrain: +class SpikeTrain(object): """ Class representing spike trains for the PySpike Module.""" def __init__(self, spike_times, interval): diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 76e58a1..a5f9f0a 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -21,5 +21,5 @@ from spike_sync import spike_sync_profile, spike_sync,\ spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix from psth import psth -from spikes import add_auxiliary_spikes, load_spike_trains_from_txt, \ - spike_train_from_string, merge_spike_trains, generate_poisson_spikes +from spikes import load_spike_trains_from_txt, spike_train_from_string, \ + merge_spike_trains, generate_poisson_spikes diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index 6d998b9..2841da8 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -337,10 +337,10 @@ def spike_distance_cython(double[:] t1, double[:] t2, # coincidence_python ############################################################ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, - int i, int j, max_tau): + int i, int j, double max_tau): cdef double m = 1E100 # some huge number - cdef int N1 = len(spikes1)-1 - cdef int N2 = len(spikes2)-1 + 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: diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index bca6f73..8ddd32c 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -109,10 +109,10 @@ def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): """ prof_func = partial(spike_sync_profile, max_tau=max_tau) - average_dist, M = _generic_profile_multi(spike_trains, prof_func, + average_prof, M = _generic_profile_multi(spike_trains, prof_func, indices) # average_dist.mul_scalar(1.0/M) # no normalization here! - return average_dist + return average_prof ############################################################ @@ -122,7 +122,7 @@ def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): """ Computes the multi-variate spike synchronization value for a set of spike trains. - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 9d7d6f4..128873d 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -8,82 +8,46 @@ Distributed under the BSD License """ import numpy as np - - -############################################################ -# add_auxiliary_spikes -############################################################ -def add_auxiliary_spikes(spike_train, time_interval): - """ Adds spikes at the beginning and end of the given time interval. - - :param spike_train: ordered array of spike times - :param time_interval: A pair (T_start, T_end) of values representing the - start and end time of the spike train measurement or - a single value representing the end time, the T_start - is then assuemd as 0. Auxiliary spikes will be added - to the spike train at the beginning and end of this - interval, if they are not yet present. - :type time_interval: pair of doubles or double - :returns: spike train with additional spikes at T_start and T_end. - - """ - try: - T_start = time_interval[0] - T_end = time_interval[1] - except: - T_start = 0 - T_end = time_interval - - assert spike_train[0] >= T_start, \ - "Spike train has events before the given start time" - assert spike_train[-1] <= T_end, \ - "Spike train has events after the given end time" - if spike_train[0] != T_start: - spike_train = np.insert(spike_train, 0, T_start) - if spike_train[-1] != T_end: - spike_train = np.append(spike_train, T_end) - return spike_train +from pyspike import SpikeTrain ############################################################ # spike_train_from_string ############################################################ -def spike_train_from_string(s, sep=' ', is_sorted=False): - """ Converts a string of times into an array of spike times. +def spike_train_from_string(s, interval, sep=' ', is_sorted=False): + """ Converts a string of times into a :class:`pyspike.SpikeTrain`. - :param s: the string with (ordered) spike times + :param s: the string with (ordered) spike times. + :param interval: interval defining the edges of the spike train. + Given as a pair of floats (T0, T1) or a single float T1, where T0=0 is + assumed. :param sep: The separator between the time numbers, default=' '. :param is_sorted: if True, the spike times are not sorted after loading, if False, spike times are sorted with `np.sort` - :returns: array of spike times + :returns: :class:`pyspike.SpikeTrain` """ if not(is_sorted): - return np.sort(np.fromstring(s, sep=sep)) + return SpikeTrain(np.sort(np.fromstring(s, sep=sep)), interval) else: - return np.fromstring(s, sep=sep) + return SpikeTrain(np.fromstring(s, sep=sep), interval) ############################################################ # load_spike_trains_txt ############################################################ -def load_spike_trains_from_txt(file_name, time_interval=None, +def load_spike_trains_from_txt(file_name, interval=None, separator=' ', comment='#', is_sorted=False): """ Loads a number of spike trains from a text file. Each line of the text file should contain one spike train as a sequence of spike times separated by `separator`. Empty lines as well as lines starting with `comment` are - neglected. The `time_interval` represents the start and the end of the - spike trains and it is used to add auxiliary spikes at the beginning and - end of each spike train. However, if `time_interval == None`, no auxiliary - spikes are added, but note that the Spike and ISI distance both require - auxiliary spikes. + neglected. The `interval` represents the start and the end of the + spike trains. :param file_name: The name of the text file. - :param time_interval: A pair (T_start, T_end) of values representing the - start and end time of the spike train measurement - or a single value representing the end time, the - T_start is then assuemd as 0. Auxiliary spikes will - be added to the spike train at the beginning and end - of this interval. + :param interval: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement + or a single value representing the end time, the + T_start is then assuemd as 0. :param separator: The character used to seprate the values in the text file :param comment: Lines starting with this character are ignored. :param sort: If true, the spike times are order via `np.sort`, default=True @@ -94,9 +58,8 @@ def load_spike_trains_from_txt(file_name, time_interval=None, for line in spike_file: if len(line) > 1 and not line.startswith(comment): # use only the lines with actual data and not commented - spike_train = spike_train_from_string(line, separator, is_sorted) - if time_interval is not None: # add auxil. spikes if times given - spike_train = add_auxiliary_spikes(spike_train, time_interval) + spike_train = spike_train_from_string(line, interval, + separator, is_sorted) spike_trains.append(spike_train) return spike_trains @@ -111,14 +74,14 @@ def merge_spike_trains(spike_trains): :returns: spike train with the merged spike times """ # get the lengths of the spike trains - lens = np.array([len(st) for st in spike_trains]) + lens = np.array([len(st.spikes) for st in spike_trains]) merged_spikes = np.empty(np.sum(lens)) index = 0 # the index for merged_spikes indices = np.zeros_like(lens) # indices of the spike trains index_list = np.arange(len(indices)) # indices of indices of spike trains # that have not yet reached the end # list of the possible events in the spike trains - vals = [spike_trains[i][indices[i]] for i in index_list] + vals = [spike_trains[i].spikes[indices[i]] for i in index_list] while len(index_list) > 0: i = np.argmin(vals) # the next spike is the minimum merged_spikes[index] = vals[i] # put it to the merged spike train @@ -127,33 +90,34 @@ def merge_spike_trains(spike_trains): indices[i] += 1 # next index for the chosen spike train if indices[i] >= lens[i]: # remove spike train index if ended index_list = index_list[index_list != i] - vals = [spike_trains[n][indices[n]] for n in index_list] - return merged_spikes + vals = [spike_trains[n].spikes[indices[n]] for n in index_list] + return SpikeTrain(merged_spikes, [spike_trains[0].t_start, + spike_trains[0].t_end]) ############################################################ # generate_poisson_spikes ############################################################ -def generate_poisson_spikes(rate, time_interval, add_aux_spikes=True): +def generate_poisson_spikes(rate, interval): """ Generates a Poisson spike train with the given rate in the given time interval :param rate: The rate of the spike trains - :param time_interval: A pair (T_start, T_end) of values representing the - start and end time of the spike train measurement or - a single value representing the end time, the T_start - is then assuemd as 0. Auxiliary spikes will be added - to the spike train at the beginning and end of this - interval, if they are not yet present. - :type time_interval: pair of doubles or double - :returns: Poisson spike train + :param interval: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement or + a single value representing the end time, the T_start + is then assuemd as 0. Auxiliary spikes will be added + to the spike train at the beginning and end of this + interval, if they are not yet present. + :type interval: pair of doubles or double + :returns: Poisson spike train as a :class:`pyspike.SpikeTrain` """ try: - T_start = time_interval[0] - T_end = time_interval[1] + T_start = interval[0] + T_end = interval[1] except: T_start = 0 - T_end = time_interval + T_end = interval # roughly how many spikes are required to fill the interval N = max(1, int(1.2 * rate * (T_end-T_start))) N_append = max(1, int(0.1 * rate * (T_end-T_start))) @@ -165,7 +129,4 @@ def generate_poisson_spikes(rate, time_interval, add_aux_spikes=True): np.random.exponential(1.0/rate, N_append)) spikes = T_start + np.cumsum(intervals) spikes = spikes[spikes < T_end] - if add_aux_spikes: - return add_auxiliary_spikes(spikes, time_interval) - else: - return spikes + return SpikeTrain(spikes, interval) diff --git a/test/test_distance.py b/test/test_distance.py index dbb72f1..0fff840 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -250,7 +250,8 @@ def test_multi_spike_sync(): # multivariate regression test spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - time_interval=(0, 4000)) + interval=(0, 4000)) + print(spike_trains[0].spikes) f = spk.spike_sync_profile_multi(spike_trains) assert_equal(np.sum(f.y[1:-1]), 39932) assert_equal(np.sum(f.mp[1:-1]), 85554) @@ -339,4 +340,4 @@ if __name__ == "__main__": test_spike_sync() test_multi_isi() test_multi_spike() - test_multi_spike_sync() + # test_multi_spike_sync() -- cgit v1.2.3 From 36d80c9ec1d28488f9b5c97cd202c196efff694e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 15:58:35 +0200 Subject: distance tests now pass with new spike trains --- pyspike/cython/cython_distance.pyx | 8 ++++---- pyspike/cython/python_backend.py | 41 +++++++++++++++++++------------------- test/test_distance.py | 40 ++++++++++++++++++++++++++++++------- 3 files changed, 57 insertions(+), 32 deletions(-) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index 2841da8..dc2557f 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -345,9 +345,9 @@ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, m = fmin(m, spikes1[i+1]-spikes1[i]) if j < N2 and j > -1: m = fmin(m, spikes2[j+1]-spikes2[j]) - if i > 1: + if i > 0: m = fmin(m, spikes1[i]-spikes1[i-1]) - if j > 1: + if j > 0: m = fmin(m, spikes2[j]-spikes2[j-1]) m *= 0.5 if max_tau > 0.0: @@ -371,7 +371,7 @@ def coincidence_cython(double[:] spikes1, double[:] spikes2, cdef double[:] mp = np.ones(N1 + N2 + 2) # multiplicity cdef double tau while i + j < N1 + N2 - 2: - if (i < N1-1) and (spikes1[i+1] < spikes2[j+1] or j == N2-1): + 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) @@ -381,7 +381,7 @@ def coincidence_cython(double[:] spikes1, double[:] spikes2, # both get marked with 1 c[n] = 1 c[n-1] = 1 - elif (j < N2-1) and (spikes1[i+1] > spikes2[j+1] or i == N1-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) diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index bcf9c30..c65bfb0 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -330,47 +330,48 @@ def cumulative_sync_python(spikes1, spikes2): ############################################################ # coincidence_python ############################################################ -def coincidence_python(spikes1, spikes2, max_tau): +def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau): def get_tau(spikes1, spikes2, i, j, max_tau): m = 1E100 # some huge number - if i < len(spikes1)-2: + if i < len(spikes1)-1 and i > -1: m = min(m, spikes1[i+1]-spikes1[i]) - if j < len(spikes2)-2: + if j < len(spikes2)-1 and j > -1: m = min(m, spikes2[j+1]-spikes2[j]) - if i > 1: + if i > 0: m = min(m, spikes1[i]-spikes1[i-1]) - if j > 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 = 0 - j = 0 + 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 n < N1 + N2 - 2: - if spikes1[i+1] < spikes2[j+1]: + 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) st[n] = spikes1[i] - if j > 0 and spikes1[i]-spikes2[j] < tau: + 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 spikes1[i+1] > spikes2[j+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 > 0 and spikes2[j]-spikes1[i] < tau: + 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 @@ -379,8 +380,6 @@ def coincidence_python(spikes1, spikes2, max_tau): # advance in both spike trains j += 1 i += 1 - if i == N1-1 or j == N2-1: - break n += 1 # add only one event, but with coincidence 2 and multiplicity 2 st[n] = spikes1[i] @@ -391,12 +390,12 @@ def coincidence_python(spikes1, spikes2, max_tau): c = c[:n+2] mp = mp[:n+2] - st[0] = spikes1[0] - st[-1] = spikes1[-1] + st[0] = t_start + st[len(st)-1] = t_end c[0] = c[1] - c[-1] = c[-2] + c[len(c)-1] = c[len(c)-2] mp[0] = mp[1] - mp[-1] = mp[-2] + mp[len(mp)-1] = mp[len(mp)-2] return st, c, mp diff --git a/test/test_distance.py b/test/test_distance.py index 0fff840..88cf40e 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -177,6 +177,18 @@ def test_spike_sync(): assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.5, decimal=16) + spikes2 = SpikeTrain([3.0], 4.0) + assert_almost_equal(spk.spike_sync(spikes1, spikes2), + 0.5, decimal=16) + + spikes2 = SpikeTrain([1.0], 4.0) + assert_almost_equal(spk.spike_sync(spikes1, spikes2), + 0.5, decimal=16) + + spikes2 = SpikeTrain([1.5, 3.0], 4.0) + assert_almost_equal(spk.spike_sync(spikes1, spikes2), + 0.4, decimal=16) + def check_multi_profile(profile_func, profile_func_multi): # generate spike trains: @@ -250,19 +262,28 @@ def test_multi_spike_sync(): # multivariate regression test spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - interval=(0, 4000)) - print(spike_trains[0].spikes) + interval=[0, 4000]) + # extract all spike times + spike_times = np.array([]) + for st in spike_trains: + spike_times = np.append(spike_times, st.spikes) + spike_times = np.unique(np.sort(spike_times)) + f = spk.spike_sync_profile_multi(spike_trains) + + assert_equal(spike_times, f.x[1:-1]) + assert_equal(len(f.x), len(f.y)) + assert_equal(np.sum(f.y[1:-1]), 39932) assert_equal(np.sum(f.mp[1:-1]), 85554) def check_dist_matrix(dist_func, dist_matrix_func): # generate spike trains: - t1 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6, 0.7]), 1.0) - t2 = spk.add_auxiliary_spikes(np.array([0.3, 0.45, 0.8, 0.9, 0.95]), 1.0) - t3 = spk.add_auxiliary_spikes(np.array([0.2, 0.4, 0.6]), 1.0) - t4 = spk.add_auxiliary_spikes(np.array([0.1, 0.4, 0.5, 0.6]), 1.0) + t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) + t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) + t3 = SpikeTrain([0.2, 0.4, 0.6], 1.0) + t4 = SpikeTrain([0.1, 0.4, 0.5, 0.6], 1.0) spike_trains = [t1, t2, t3, t4] f12 = dist_func(t1, t2) @@ -340,4 +361,9 @@ if __name__ == "__main__": test_spike_sync() test_multi_isi() test_multi_spike() - # test_multi_spike_sync() + test_multi_spike_sync() + test_isi_matrix() + test_spike_matrix() + test_spike_sync_matrix() + test_regression_spiky() + test_multi_variate_subsets() -- cgit v1.2.3 From c3e58aeb00ef2a386a4c6a620e4e13652c55aed5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 16:10:38 +0200 Subject: removed auxiliary_spike test, all tests pass --- test/test_spikes.py | 46 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/test/test_spikes.py b/test/test_spikes.py index b12099e..6e11c07 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -14,38 +14,22 @@ from numpy.testing import assert_equal import pyspike as spk -def test_auxiliary_spikes(): - t = np.array([0.2, 0.4, 0.6, 0.7]) - t_aux = spk.add_auxiliary_spikes(t, time_interval=(0.1, 1.0)) - assert_equal(t_aux, [0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) - t_aux = spk.add_auxiliary_spikes(t_aux, time_interval=(0.0, 1.0)) - assert_equal(t_aux, [0.0, 0.1, 0.2, 0.4, 0.6, 0.7, 1.0]) - - def test_load_from_txt(): spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - time_interval=(0, 4000)) + interval=(0, 4000)) assert len(spike_trains) == 40 # check the first spike train - spike_times = [0, 64.886, 305.81, 696, 937.77, 1059.7, 1322.2, 1576.1, + spike_times = [64.886, 305.81, 696, 937.77, 1059.7, 1322.2, 1576.1, 1808.1, 2121.5, 2381.1, 2728.6, 2966.9, 3223.7, 3473.7, - 3644.3, 3936.3, 4000] - assert_equal(spike_times, spike_trains[0]) + 3644.3, 3936.3] + assert_equal(spike_times, spike_trains[0].spikes) # check auxiliary spikes for spike_train in spike_trains: - assert spike_train[0] == 0.0 - assert spike_train[-1] == 4000 + assert spike_train.t_start == 0.0 + assert spike_train.t_end == 4000 - # load without adding auxiliary spikes - spike_trains2 = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - time_interval=None) - assert len(spike_trains2) == 40 - # check auxiliary spikes - for i in xrange(len(spike_trains)): - assert len(spike_trains[i]) == len(spike_trains2[i])+2 # 2 spikes less - def check_merged_spikes(merged_spikes, spike_trains): # create a flat array with all spike events @@ -65,21 +49,23 @@ def check_merged_spikes(merged_spikes, spike_trains): def test_merge_spike_trains(): # first load the data spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - time_interval=(0, 4000)) + interval=(0, 4000)) - spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) + merged_spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) # test if result is sorted - assert((spikes == np.sort(spikes)).all()) + assert((merged_spikes.spikes == np.sort(merged_spikes.spikes)).all()) # check merging - check_merged_spikes(spikes, [spike_trains[0], spike_trains[1]]) + check_merged_spikes(merged_spikes.spikes, [spike_trains[0].spikes, + spike_trains[1].spikes]) - spikes = spk.merge_spike_trains(spike_trains) + merged_spikes = spk.merge_spike_trains(spike_trains) # test if result is sorted - assert((spikes == np.sort(spikes)).all()) + assert((merged_spikes.spikes == np.sort(merged_spikes.spikes)).all()) # check merging - check_merged_spikes(spikes, spike_trains) + check_merged_spikes(merged_spikes.spikes, + [st.spikes for st in spike_trains]) + if __name__ == "main": - test_auxiliary_spikes() test_load_from_txt() test_merge_spike_trains() -- cgit v1.2.3 From 9c205e3b54c5fe0a8917dfb94aad1d1e0f40aca0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 16:43:12 +0200 Subject: renamed interval -> edges in SpikeTrain --- pyspike/SpikeTrain.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index 89520c9..d586fe0 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -6,29 +6,24 @@ Distributed under the BSD License """ import numpy as np -import collections class SpikeTrain(object): """ Class representing spike trains for the PySpike Module.""" - def __init__(self, spike_times, interval): + def __init__(self, spike_times, edges): """ Constructs the SpikeTrain :param spike_times: ordered array of spike times. - :param interval: interval defining the edges of the spike train. - Given as a pair of floats (T0, T1) or a single float T1, where T0=0 is - assumed. + :param edges: The edges of the spike train. Given as a pair of floats + (T0, T1) or a single float T1, where then T0=0 is assumed. """ # TODO: sanity checks self.spikes = np.array(spike_times, dtype=float) - # check if interval is as sequence - if not isinstance(interval, collections.Sequence): - # treat value as end time and assume t_start = 0 + try: + self.t_start = float(edges[0]) + self.t_end = float(edges[1]) + except: self.t_start = 0.0 - self.t_end = float(interval) - else: - # extract times from sequence - self.t_start = float(interval[0]) - self.t_end = float(interval[1]) + self.t_end = float(edges) -- cgit v1.2.3 From f7ad8e6b23f706a2371e2bc25b533b59f8dea137 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 16:48:24 +0200 Subject: renamed interval -> edges in load functions --- pyspike/spikes.py | 20 ++++++++++---------- test/test_distance.py | 2 +- test/test_spikes.py | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 128873d..9401b6e 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -14,11 +14,11 @@ from pyspike import SpikeTrain ############################################################ # spike_train_from_string ############################################################ -def spike_train_from_string(s, interval, sep=' ', is_sorted=False): +def spike_train_from_string(s, edges, sep=' ', is_sorted=False): """ Converts a string of times into a :class:`pyspike.SpikeTrain`. :param s: the string with (ordered) spike times. - :param interval: interval defining the edges of the spike train. + :param edges: interval defining the edges of the spike train. Given as a pair of floats (T0, T1) or a single float T1, where T0=0 is assumed. :param sep: The separator between the time numbers, default=' '. @@ -27,15 +27,15 @@ def spike_train_from_string(s, interval, sep=' ', is_sorted=False): :returns: :class:`pyspike.SpikeTrain` """ if not(is_sorted): - return SpikeTrain(np.sort(np.fromstring(s, sep=sep)), interval) + return SpikeTrain(np.sort(np.fromstring(s, sep=sep)), edges) else: - return SpikeTrain(np.fromstring(s, sep=sep), interval) + return SpikeTrain(np.fromstring(s, sep=sep), edges) ############################################################ # load_spike_trains_txt ############################################################ -def load_spike_trains_from_txt(file_name, interval=None, +def load_spike_trains_from_txt(file_name, edges, separator=' ', comment='#', is_sorted=False): """ Loads a number of spike trains from a text file. Each line of the text file should contain one spike train as a sequence of spike times separated @@ -44,10 +44,10 @@ def load_spike_trains_from_txt(file_name, interval=None, spike trains. :param file_name: The name of the text file. - :param interval: A pair (T_start, T_end) of values representing the - start and end time of the spike train measurement - or a single value representing the end time, the - T_start is then assuemd as 0. + :param edges: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement + or a single value representing the end time, the + T_start is then assuemd as 0. :param separator: The character used to seprate the values in the text file :param comment: Lines starting with this character are ignored. :param sort: If true, the spike times are order via `np.sort`, default=True @@ -58,7 +58,7 @@ def load_spike_trains_from_txt(file_name, interval=None, for line in spike_file: if len(line) > 1 and not line.startswith(comment): # use only the lines with actual data and not commented - spike_train = spike_train_from_string(line, interval, + spike_train = spike_train_from_string(line, edges, separator, is_sorted) spike_trains.append(spike_train) return spike_trains diff --git a/test/test_distance.py b/test/test_distance.py index 88cf40e..0059001 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -262,7 +262,7 @@ def test_multi_spike_sync(): # multivariate regression test spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - interval=[0, 4000]) + edges=[0, 4000]) # extract all spike times spike_times = np.array([]) for st in spike_trains: diff --git a/test/test_spikes.py b/test/test_spikes.py index 6e11c07..d4eb131 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -16,7 +16,7 @@ import pyspike as spk def test_load_from_txt(): spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - interval=(0, 4000)) + edges=(0, 4000)) assert len(spike_trains) == 40 # check the first spike train @@ -49,7 +49,7 @@ def check_merged_spikes(merged_spikes, spike_trains): def test_merge_spike_trains(): # first load the data spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - interval=(0, 4000)) + edges=(0, 4000)) merged_spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) # test if result is sorted -- cgit v1.2.3 From 795e16ffe7afb469ef07a548c1f6a31d924196b3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 24 Apr 2015 23:29:05 +0200 Subject: bugfixes for spike distance --- pyspike/cython/cython_distance.pyx | 24 +++++++++--------- pyspike/cython/python_backend.py | 15 ++++++------ test/test_distance.py | 50 ++++++++++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index dc2557f..a41d8e8 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -194,31 +194,31 @@ def spike_distance_cython(double[:] t1, double[:] t2, t_p2 = t_start if t1[0] > t_start: # dt_p1 = t2[0]-t_start - dt_p1 = 0.0 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]) - s1 = dt_f1*(t_f1-t_start)/isi1 + dt_p1 = dt_f1 + s1 = dt_p1*(t_f1-t_start)/isi1 index1 = -1 else: - dt_p1 = 0.0 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 - dt_p2 = 0.0 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_f2*(t_f2-t_start)/isi2 + s2 = dt_p2*(t_f2-t_start)/isi2 index2 = -1 else: - dt_p2 = 0.0 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 @@ -231,16 +231,16 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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 + dt_p1 = dt_f1 t_p1 = t_f1 # t_p1 contains the current time point - # get the next time + # get the next time if index1 < N1-1: t_f1 = t1[index1+1] else: t_f1 = t_end spike_events[index] = t_p1 - s1 = dt_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) @@ -249,6 +249,7 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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]) @@ -260,9 +261,10 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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 + t_p2 = t_f2 # t_p2 contains the current time point # get the next time if index2 < N2-1: t_f2 = t2[index2+1] @@ -270,7 +272,6 @@ def spike_distance_cython(double[:] t1, double[:] t2, t_f2 = t_end spike_events[index] = t_p2 s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1 - s2 = dt_p2 y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) # now the next interval start value @@ -278,6 +279,7 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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]) diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index c65bfb0..317b568 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -142,12 +142,11 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): t_p1 = t_start t_p2 = t_start if t1[0] > t_start: - # dt_p1 = t2[0]-t_start - dt_p1 = 0.0 t_f1 = t1[0] dt_f1 = get_min_dist(t_f1, t2, 0, t_start, t_end) + dt_p1 = dt_f1 isi1 = max(t_f1-t_start, t1[1]-t1[0]) - s1 = dt_f1*(t_f1-t_start)/isi1 + s1 = dt_p1*(t_f1-t_start)/isi1 index1 = -1 else: dt_p1 = 0.0 @@ -158,11 +157,11 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): index1 = 0 if t2[0] > t_start: # dt_p1 = t2[0]-t_start - dt_p2 = 0.0 t_f2 = t2[0] dt_f2 = get_min_dist(t_f2, t1, 0, t_start, t_end) + dt_p2 = dt_f2 isi2 = max(t_f2-t_start, t2[1]-t2[0]) - s2 = dt_f2*(t_f2-t_start)/isi2 + s2 = dt_p2*(t_f2-t_start)/isi2 index2 = -1 else: dt_p2 = 0.0 @@ -180,6 +179,7 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): 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 @@ -189,13 +189,13 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): else: t_f1 = t_end spike_events[index] = t_p1 - s1 = dt_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_start, t_end) 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]) @@ -206,6 +206,7 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): 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 @@ -216,12 +217,12 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): t_f2 = t_end spike_events[index] = t_p2 s1 = (dt_p1*(t_f1-t_p2) + dt_f1*(t_p2-t_p1)) / isi1 - s2 = dt_p2 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_start, t_end) 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]) diff --git a/test/test_distance.py b/test/test_distance.py index 0059001..20b52e8 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -73,7 +73,7 @@ def test_spike(): assert_equal(f.x, expected_times) - assert_almost_equal(f.avrg(), 0.1662415, decimal=6) + assert_almost_equal(f.avrg(), 1.6624149659863946e-01, decimal=15) assert_almost_equal(f.y2[-1], 0.1394558, decimal=6) t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) @@ -84,7 +84,7 @@ def test_spike(): s1 = np.array([0.1, 0.1, (0.1*0.1+0.05*0.1)/0.2, 0.05, (0.05*0.15 * 2)/0.2, 0.15, 0.1, (0.1*0.1+0.1*0.2)/0.3, (0.1*0.2+0.1*0.1)/0.3, (0.1*0.05+0.1*0.25)/0.3, 0.1]) - s2 = np.array([0.1, 0.1*0.2/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, + s2 = np.array([0.1, (0.1*0.2+0.1*0.1)/0.3, 0.1, (0.1*0.05 * 2)/.15, 0.05, (0.05*0.2+0.1*0.15)/0.35, (0.05*0.1+0.1*0.25)/0.35, 0.1, 0.1, 0.05, 0.05]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.1, 0.3, 0.3, 0.3, 0.3]) @@ -113,12 +113,18 @@ def test_spike(): t2 = SpikeTrain([0.1, 0.4, 0.5, 0.6], [0.0, 1.0]) expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] - s1 = np.array([0.1, 0.1*0.1/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) - s2 = np.array([0.1*0.1/0.3, 0.1, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) + # due to the edge correction in the beginning, s1 and s2 are different + # for left and right values + s1_r = np.array([0.1, (0.1*0.1+0.1*0.1)/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) + s1_l = np.array([0.1, (0.1*0.1+0.1*0.1)/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) + s2_r = np.array([0.1*0.1/0.3, 0.1*0.3/0.3, 0.1*0.2/0.3, + 0.0, 0.1, 0.0, 0.0]) + s2_l = np.array([0.1*0.1/0.3, 0.1*0.1/0.3, 0.1*0.2/0.3, 0.0, + 0.1, 0.0, 0.0]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.4]) isi2 = np.array([0.3, 0.3, 0.3, 0.1, 0.1, 0.4]) - expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) - expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) + expected_y1 = (s1_r[:-1]*isi2+s2_r[:-1]*isi1) / (0.5*(isi1+isi2)**2) + expected_y2 = (s1_l[1:]*isi2+s2_l[1:]*isi1) / (0.5*(isi1+isi2)**2) expected_times = np.array(expected_times) expected_y1 = np.array(expected_y1) @@ -321,19 +327,37 @@ def test_spike_sync_matrix(): def test_regression_spiky(): + # standard example + st1 = SpikeTrain(np.arange(100, 1201, 100), 1300) + st2 = SpikeTrain(np.arange(100, 1201, 110), 1300) + + isi_dist = spk.isi_distance(st1, st2) + assert_almost_equal(isi_dist, 7.6923076923076941e-02, decimal=15) + + spike_dist = spk.spike_distance(st1, st2) + assert_equal(spike_dist, 2.1105878248735391e-01) + + spike_sync = spk.spike_sync(st1, st2) + assert_equal(spike_sync, 8.6956521739130432e-01) + + # multivariate check + spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", (0.0, 4000.0)) - isi_profile = spk.isi_profile_multi(spike_trains) - isi_dist = isi_profile.avrg() - print(isi_dist) + isi_dist = spk.isi_distance_multi(spike_trains) # get the full precision from SPIKY - # assert_equal(isi_dist, 0.1832) + assert_almost_equal(isi_dist, 1.8318789829845508e-01, decimal=15) spike_profile = spk.spike_profile_multi(spike_trains) - spike_dist = spike_profile.avrg() - print(spike_dist) + assert_equal(len(spike_profile.y1)+len(spike_profile.y2), 1252) + + spike_dist = spk.spike_distance_multi(spike_trains) + # get the full precision from SPIKY + assert_almost_equal(spike_dist, 2.4432433330596512e-01, decimal=15) + + spike_sync = spk.spike_sync_multi(spike_trains) # get the full precision from SPIKY - # assert_equal(spike_dist, 0.2445) + assert_equal(spike_sync, 0.7183531505298066) def test_multi_variate_subsets(): -- cgit v1.2.3 From cc8ae1974454307de4c69d9bb2a860538f0adfef Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 27 Apr 2015 17:27:24 +0200 Subject: updated docs --- Readme.rst | 80 +++++++++++++++++++++---------------------- doc/pyspike.rst | 54 +++++++++++++++-------------- pyspike/DiscreteFunc.py | 12 +++---- pyspike/PieceWiseConstFunc.py | 12 +++---- pyspike/PieceWiseLinFunc.py | 12 +++---- pyspike/SpikeTrain.py | 41 ++++++++++++++++------ pyspike/isi_distance.py | 59 ++++++++++++++++--------------- pyspike/spike_distance.py | 58 +++++++++++++++---------------- pyspike/spike_sync.py | 30 ++++++++-------- pyspike/spikes.py | 32 +++++++---------- 10 files changed, 196 insertions(+), 194 deletions(-) diff --git a/Readme.rst b/Readme.rst index 03441fc..e80c0f7 100644 --- a/Readme.rst +++ b/Readme.rst @@ -19,6 +19,14 @@ All source codes are available on `Github `_. -To quickly obtain spike trains from such files, PySpike provides the function :code:`load_spike_trains_from_txt`. +To quickly obtain spike trains from such files, PySpike provides the function :func:`.load_spike_trains_from_txt`. .. code:: python @@ -88,22 +98,13 @@ To quickly obtain spike trains from such files, PySpike provides the function :c import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) This function expects the name of the data file as first parameter. -Additionally, the time interval of the spike train measurement can be provided as a pair of start- and end-time values. -If the time interval is provided (:code:`time_interval is not None`), auxiliary spikes at the start- and end-time of the interval are added to the spike trains. +Furthermore, the time interval of the spike train measurement (edges of the spike trains) should be provided as a pair of start- and end-time values. Furthermore, the spike trains are sorted via :code:`np.sort` (disable this feature by providing :code:`is_sorted=True` as a parameter to the load function). -As result, :code:`load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. - -If you load spike trains yourself, i.e. from data files with different structure, you can use the helper function :code:`add_auxiliary_spikes` to add the auxiliary spikes at the beginning and end of the observation interval. -Both the ISI and the SPIKE distance computation require the presence of auxiliary spikes, so make sure you have those in your spike trains: +As result, :func:`.load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. -.. code:: python - - spike_train = spk.add_auxiliary_spikes(spike_train, (T_start, T_end)) - # if you provide only a single value, it is interpreted as T_end, while T_start=0 - spike_train = spk.add_auxiliary_spikes(spike_train, T_end) Computing bivariate distances profiles --------------------------------------- @@ -114,19 +115,18 @@ Computing bivariate distances profiles Spike trains are expected to be *sorted*! For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed sorted. - Make sure that all your spike trains are sorted, which is ensured if you use the `load_spike_trains_from_txt` function with the parameter `is_sorted=False`. - If in doubt, use :code:`spike_train = np.sort(spike_train)` to obtain a correctly sorted spike train. - - Furthermore, the spike trains should have auxiliary spikes at the beginning and end of the observation interval. - You can ensure this by providing the :code:`time_interval` in the :code:`load_spike_trains_from_txt` function, or calling :code:`add_auxiliary_spikes` for your spike trains. - The spike trains must have *the same* observation interval! + Make sure that all your spike trains are sorted, which is ensured if you use the :func:`.load_spike_trains_from_txt` function with the parameter `is_sorted=False` (default). + If in doubt, use :meth:`.SpikeTrain.sort()` to ensure a correctly sorted spike train. ----------------------- + If you need to copy a spike train, use the :meth:`.SpikeTrain.copy()` method. + Simple assignment `t2 = t1` does not create a copy of the spike train data, but a reference as `numpy.array` is used for storing the data. + +------------------------------ ISI-distance ............ -The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two spike trains, and plots it with matplotlib: +The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two :class:`.SpikeTrain` s, and plots it with matplotlib: .. code:: python @@ -134,18 +134,18 @@ The following code loads some exemplary spike trains, computes the dissimilarity import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) isi_profile = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = isi_profile.get_plottable_data() plt.plot(x, y, '--k') print("ISI distance: %.8f" % isi_profile.avrg()) plt.show() -The ISI-profile is a piece-wise constant function, and hence the function :code:`isi_profile` returns an instance of the :code:`PieceWiseConstFunc` class. +The ISI-profile is a piece-wise constant function, and hence the function :func:`.isi_profile` returns an instance of the :class:`.PieceWiseConstFunc` class. As shown above, this class allows you to obtain arrays that can be used to plot the function with :code:`plt.plt`, but also to compute the time average, which amounts to the final scalar ISI-distance. -By default, the time average is computed for the whole :code:`PieceWiseConstFunc` function. +By default, the time average is computed for the whole :class:`.PieceWiseConstFunc` function. However, it is also possible to obtain the average of a specific interval by providing a pair of floats defining the start and end of the interval. -In the above example, the following code computes the ISI-distances obtained from averaging the ISI-profile over four different intervals: +For the above example, the following code computes the ISI-distances obtained from averaging the ISI-profile over four different intervals: .. code:: python @@ -168,7 +168,7 @@ where :code:`interval` is optional, as above, and if omitted the ISI-distance is SPIKE-distance .............. -To compute for the spike distance profile you use the function :code:`spike_profile` instead of :code:`isi_profile` above. +To compute for the spike distance profile you use the function :func:`.spike_profile` instead of :code:`isi_profile` above. But the general approach is very similar: .. code:: python @@ -177,7 +177,7 @@ But the general approach is very similar: import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) spike_profile = spk.spike_profile(spike_trains[0], spike_trains[1]) x, y = spike_profile.get_plottable_data() plt.plot(x, y, '--k') @@ -185,9 +185,9 @@ But the general approach is very similar: plt.show() This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. -In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and is therefore represented by a :code:`PieceWiseLinFunc` object. -Just like the :code:`PieceWiseConstFunc` for the ISI-profile, the :code:`PieceWiseLinFunc` provides a :code:`get_plottable_data` member function that returns arrays that can be used directly to plot the function. -Furthermore, the :code:`avrg` member function returns the average of the profile defined as the overall SPIKE distance. +In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and is therefore represented by a :class:`.PieceWiseLinFunc` object. +Just like the :class:`.PieceWiseConstFunc` for the ISI-profile, the :class:`.PieceWiseLinFunc` provides a :meth:`.PieceWiseLinFunc.get_plottable_data` member function that returns arrays that can be used directly to plot the function. +Furthermore, the :meth:`.PieceWiseLinFunc.avrg` member function returns the average of the profile defined as the overall SPIKE distance. As above, you can provide an interval as a pair of floats as well as a sequence of such pairs to :code:`avrg` to specify the averaging interval if required. Again, you can use @@ -217,9 +217,9 @@ SPIKE synchronization SPIKE synchronization is another approach to measure spike synchrony. In contrast to the SPIKE- and ISI-distance, it measures similarity instead of dissimilarity, i.e. higher values represent larger synchrony. Another difference is that the SPIKE synchronization profile is only defined exactly at the spike times, not for the whole interval of the spike trains. -Therefore, it is represented by a :code:`DiscreteFunction`. +Therefore, it is represented by a :class:`.DiscreteFunction`. -To compute for the spike synchronization profile, PySpike provides the function :code:`spike_sync_profile`. +To compute for the spike synchronization profile, PySpike provides the function :func:`.spike_sync_profile`. The general handling of the profile, however, is similar to the other profiles above: .. code:: python @@ -228,11 +228,11 @@ The general handling of the profile, however, is similar to the other profiles a import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) spike_profile = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) x, y = spike_profile.get_plottable_data() -For the direct computation of the overall spike synchronization value within some interval, the :code:`spike_sync` function can be used: +For the direct computation of the overall spike synchronization value within some interval, the :func:`.spike_sync` function can be used: .. code:: python @@ -243,23 +243,23 @@ Computing multivariate profiles and distances ---------------------------------------------- To compute the multivariate ISI-profile, SPIKE-profile or SPIKE-Synchronization profile f a set of spike trains, PySpike provides multi-variate version of the profile function. -The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains: +The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains using the :func:`.isi_profile_multi`, :func:`.spike_profile_multi`, :func:`.spike_sync_profile_multi` functions: .. code:: python spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) avrg_isi_profile = spk.isi_profile_multi(spike_trains) avrg_spike_profile = spk.spike_profile_multi(spike_trains) avrg_spike_sync_profile = spk.spike_sync_profile_multi(spike_trains) All functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. -As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :code:`isi_distance_multi`, :code:`spike_distance_multi` and :code:`spike_sync_multi`, that return the scalar overall multivariate ISI- and SPIKE-distance as well as the SPIKE-Synchronization value. +As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :func:`.isi_distance_multi`, :func:`.spike_distance_multi` and :func:`.spike_sync_multi`, that return the scalar overall multivariate ISI- and SPIKE-distance as well as the SPIKE-Synchronization value. Those functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. Another option to characterize large sets of spike trains are distance matrices. Each entry in the distance matrix represents a bivariate distance (similarity for SPIKE-Synchronization) of two spike trains. -The distance matrix is symmetric and has zero values (ones) at the diagonal. +The distance matrix is symmetric and has zero values (ones) at the diagonal and is computed with the functions :func:`.isi_distance_matrix`, :func:`.spike_distance_matrix` and :func:`.spike_sync_matrix`. The following example computes and plots the ISI- and SPIKE-distance matrix as well as the SPIKE-Synchronization-matrix, with different intervals. .. code:: python diff --git a/doc/pyspike.rst b/doc/pyspike.rst index 6aa36e7..a6dc1a0 100644 --- a/doc/pyspike.rst +++ b/doc/pyspike.rst @@ -1,60 +1,64 @@ pyspike package =============== -Submodules ----------- -pyspike.isi_distance module +Classes ---------------------------------------- -.. automodule:: pyspike.isi_distance +SpikeTrain +........................................ +.. automodule:: pyspike.SpikeTrain :members: :undoc-members: :show-inheritance: -pyspike.spike_distance module ----------------------------------------- - -.. automodule:: pyspike.spike_distance +PieceWiseConstFunc +........................................ +.. automodule:: pyspike.PieceWiseConstFunc :members: :undoc-members: :show-inheritance: -pyspike.spike_sync module ----------------------------------------- - -.. automodule:: pyspike.spike_sync +PieceWiseLinFunc +........................................ +.. automodule:: pyspike.PieceWiseLinFunc :members: :undoc-members: :show-inheritance: -pyspike.PieceWiseConstFunc module ----------------------------------------- - -.. automodule:: pyspike.PieceWiseConstFunc +DiscreteFunc +........................................ +.. automodule:: pyspike.DiscreteFunc :members: :undoc-members: :show-inheritance: -pyspike.PieceWiseLinFunc module ----------------------------------------- +Functions +---------- -.. automodule:: pyspike.PieceWiseLinFunc +ISI-distance +........................................ +.. automodule:: pyspike.isi_distance :members: :undoc-members: :show-inheritance: -pyspike.DiscreteFunc module ----------------------------------------- - -.. automodule:: pyspike.DiscreteFunc +SPIKE-distance +........................................ +.. automodule:: pyspike.spike_distance :members: :undoc-members: :show-inheritance: -pyspike.spikes module ----------------------------------------- +SPIKE-synchronization +........................................ +.. automodule:: pyspike.spike_sync + :members: + :undoc-members: + :show-inheritance: +Helper functions +........................................ .. automodule:: pyspike.spikes :members: :undoc-members: diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index bd13e1f..33b7a81 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -1,11 +1,7 @@ -""" -Class representing discrete functions. +# Class representing discrete functions. +# Copyright 2014-2015, Mario Mulansky +# Distributed under the BSD License -Copyright 2014-2015, Mario Mulansky - -Distributed under the BSD License - -""" from __future__ import print_function import numpy as np @@ -174,7 +170,7 @@ class DiscreteFunc(object): def avrg(self, interval=None): """ Computes the average of the interval sequence: - :math:`a = 1/N sum f_n` where N is the number of intervals. + :math:`a = 1/N \\sum f_n` where N is the number of intervals. :param interval: averaging interval given as a pair of floats, a sequence of pairs for averaging multiple intervals, or diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index dc57ab1..41998ef 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -1,11 +1,7 @@ -""" -Class representing piece-wise constant functions. +# Class representing piece-wise constant functions. +# Copyright 2014-2015, Mario Mulansky +# Distributed under the BSD License -Copyright 2014-2015, Mario Mulansky - -Distributed under the BSD License - -""" from __future__ import print_function import numpy as np @@ -103,7 +99,7 @@ class PieceWiseConstFunc(object): def avrg(self, interval=None): """ Computes the average of the piece-wise const function: - :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + :math:`a = 1/T \int_0^T f(x) dx` where T is the length of the interval. :param interval: averaging interval given as a pair of floats, a sequence of pairs for averaging multiple intervals, or diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index bc0aa2a..f2442be 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -1,11 +1,7 @@ -""" -Class representing piece-wise linear functions. +# Class representing piece-wise linear functions. +# Copyright 2014-2015, Mario Mulansky +# Distributed under the BSD License -Copyright 2014-2015, Mario Mulansky - -Distributed under the BSD License - -""" from __future__ import print_function import numpy as np @@ -123,7 +119,7 @@ class PieceWiseLinFunc: def avrg(self, interval=None): """ Computes the average of the piece-wise linear function: - :math:`a = 1/T int_0^T f(x) dx` where T is the length of the interval. + :math:`a = 1/T \int_0^T f(x) dx` where T is the interval length. :param interval: averaging interval given as a pair of floats, a sequence of pairs for averaging multiple intervals, or diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index d586fe0..a02b7ab 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -1,9 +1,6 @@ -""" Module containing the class representing spike trains for PySpike. - -Copyright 2015, Mario Mulansky - -Distributed under the BSD License -""" +# Module containing the class representing spike trains for PySpike. +# Copyright 2015, Mario Mulansky +# Distributed under the BSD License import numpy as np @@ -11,15 +8,22 @@ import numpy as np class SpikeTrain(object): """ Class representing spike trains for the PySpike Module.""" - def __init__(self, spike_times, edges): - """ Constructs the SpikeTrain + def __init__(self, spike_times, edges, is_sorted=True): + """ Constructs the SpikeTrain. + :param spike_times: ordered array of spike times. :param edges: The edges of the spike train. Given as a pair of floats - (T0, T1) or a single float T1, where then T0=0 is assumed. + (T0, T1) or a single float T1, where then T0=0 is + assumed. + :param is_sorted: If `False`, the spike times will sorted by `np.sort`. + """ # TODO: sanity checks - self.spikes = np.array(spike_times, dtype=float) + if is_sorted: + self.spikes = np.array(spike_times, dtype=float) + else: + self.spikes = np.sort(np.array(spike_times, dtype=float)) try: self.t_start = float(edges[0]) @@ -27,3 +31,20 @@ class SpikeTrain(object): except: self.t_start = 0.0 self.t_end = float(edges) + + def sort(self): + """ Sorts the spike times of this spike train using `np.sort` + """ + self.spikes = np.sort(self.spikes) + + def copy(self): + """ Returns a copy of this spike train. + Use this function if you want to create a real (deep) copy of this + spike train. Simple assignment `t2 = t1` does not create a copy of the + spike train data, but a reference as `numpy.array` is used for storing + the data. + + :return: :class:`.SpikeTrain` copy of this spike train. + + """ + return SpikeTrain(self.spikes.copy(), [self.t_start, self.t_end]) diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index cb8ef54..aeab0df 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -1,11 +1,6 @@ -""" - -Module containing several functions to compute the ISI profiles and distances - -Copyright 2014-2015, Mario Mulansky - -Distributed under the BSD License -""" +# Module containing several functions to compute the ISI profiles and distances +# Copyright 2014-2015, Mario Mulansky +# Distributed under the BSD License from pyspike import PieceWiseConstFunc from pyspike.generic import _generic_profile_multi, _generic_distance_matrix @@ -15,16 +10,16 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix # isi_profile ############################################################ def isi_profile(spike_train1, spike_train2): - """ Computes the isi-distance profile :math:`S_{isi}(t)` of the two given - spike trains. Retruns the profile as a PieceWiseConstFunc object. The S_isi - values are defined positive S_isi(t)>=0. + """ Computes the isi-distance profile :math:`I(t)` of the two given + spike trains. Retruns the profile as a PieceWiseConstFunc object. The + ISI-values are defined positive :math:`I(t)>=0`. :param spike_train1: First spike train. - :type spike_train1: :class:`pyspike.SpikeTrain` + :type spike_train1: :class:`.SpikeTrain` :param spike_train2: Second spike train. - :type spike_train2: :class:`pyspike.SpikeTrain` - :returns: The isi-distance profile :math:`S_{isi}(t)` - :rtype: :class:`pyspike.function.PieceWiseConstFunc` + :type spike_train2: :class:`.SpikeTrain` + :returns: The isi-distance profile :math:`I(t)` + :rtype: :class:`.PieceWiseConstFunc` """ # check whether the spike trains are defined for the same interval @@ -54,20 +49,20 @@ Falling back to slow python backend.") # isi_distance ############################################################ def isi_distance(spike_train1, spike_train2, interval=None): - """ Computes the isi-distance I of the given spike trains. The + """ Computes the ISI-distance :math:`D_I` of the given spike trains. The isi-distance is the integral over the isi distance profile - :math:`S_{isi}(t)`: + :math:`I(t)`: - .. math:: I = \int_{T_0}^{T_1} S_{isi}(t) dt. + .. math:: D_I = \\int_{T_0}^{T_1} I(t) dt. :param spike_train1: First spike train. - :type spike_train1: :class:`pyspike.SpikeTrain` + :type spike_train1: :class:`.SpikeTrain` :param spike_train2: Second spike train. - :type spike_train2: :class:`pyspike.SpikeTrain` + :type spike_train2: :class:`.SpikeTrain` :param interval: averaging interval given as a pair of floats (T0, T1), if None the average over the whole function is computed. :type interval: Pair of floats or None. - :returns: The isi-distance I. + :returns: The isi-distance :math:`D_I`. :rtype: double """ return isi_profile(spike_train1, spike_train2).avrg(interval) @@ -79,15 +74,17 @@ def isi_distance(spike_train1, spike_train2, interval=None): def isi_profile_multi(spike_trains, indices=None): """ computes the multi-variate isi distance profile for a set of spike trains. That is the average isi-distance of all pairs of spike-trains: - S_isi(t) = 2/((N(N-1)) sum_{} S_{isi}^{i,j}, + + .. math:: = \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, + where the sum goes over all pairs - :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type state: list or None - :returns: The averaged isi profile :math:`(t)` - :rtype: :class:`pyspike.function.PieceWiseConstFunc` + :returns: The averaged isi profile :math:`` + :rtype: :class:`.PieceWiseConstFunc` """ average_dist, M = _generic_profile_multi(spike_trains, isi_profile, indices) @@ -101,16 +98,18 @@ def isi_profile_multi(spike_trains, indices=None): def isi_distance_multi(spike_trains, indices=None, interval=None): """ computes the multi-variate isi-distance for a set of spike-trains. That is the time average of the multi-variate spike profile: - I = \int_0^T 2/((N(N-1)) sum_{} S_{isi}^{i,j}, + + .. math:: D_I = \\int_0^T \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, + where the sum goes over all pairs - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :param interval: averaging interval given as a pair of floats, if None the average over the whole function is computed. :type interval: Pair of floats or None. - :returns: The time-averaged isi distance :math:`I` + :returns: The time-averaged multivariate ISI distance :math:`D_I` :rtype: double """ return isi_profile_multi(spike_trains, indices).avrg(interval) @@ -122,7 +121,7 @@ def isi_distance_multi(spike_trains, indices=None, interval=None): def isi_distance_matrix(spike_trains, indices=None, interval=None): """ Computes the time averaged isi-distance of all pairs of spike-trains. - :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None @@ -130,7 +129,7 @@ def isi_distance_matrix(spike_trains, indices=None, interval=None): the average over the whole function is computed. :type interval: Pair of floats or None. :returns: 2D array with the pair wise time average isi distances - :math:`I_{ij}` + :math:`D_{I}^{ij}` :rtype: np.array """ return _generic_distance_matrix(spike_trains, isi_distance, diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index 8d03d70..cc620d4 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -1,11 +1,6 @@ -""" - -Module containing several functions to compute SPIKE profiles and distances - -Copyright 2014-2015, Mario Mulansky - -Distributed under the BSD License -""" +# Module containing several functions to compute SPIKE profiles and distances +# Copyright 2014-2015, Mario Mulansky +# Distributed under the BSD License from pyspike import PieceWiseLinFunc from pyspike.generic import _generic_profile_multi, _generic_distance_matrix @@ -15,16 +10,16 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix # spike_profile ############################################################ def spike_profile(spike_train1, spike_train2): - """ Computes the spike-distance profile S_spike(t) of the two given spike - trains. Returns the profile as a PieceWiseLinFunc object. The S_spike - values are defined positive S_spike(t)>=0. + """ Computes the spike-distance profile :math:`S(t)` of the two given spike + trains. Returns the profile as a PieceWiseLinFunc object. The SPIKE-values + are defined positive :math:`S(t)>=0`. :param spike_train1: First spike train. - :type spike_train1: :class:`pyspike.SpikeTrain` + :type spike_train1: :class:`.SpikeTrain` :param spike_train2: Second spike train. - :type spike_train2: :class:`pyspike.SpikeTrain` - :returns: The spike-distance profile :math:`S_{spike}(t)`. - :rtype: :class:`pyspike.function.PieceWiseLinFunc` + :type spike_train2: :class:`.SpikeTrain` + :returns: The spike-distance profile :math:`S(t)`. + :rtype: :class:`.PieceWiseLinFunc` """ # check whether the spike trains are defined for the same interval @@ -56,15 +51,15 @@ Falling back to slow python backend.") # spike_distance ############################################################ def spike_distance(spike_train1, spike_train2, interval=None): - """ Computes the spike-distance S of the given spike trains. The - spike-distance is the integral over the isi distance profile S_spike(t): + """ Computes the spike-distance :math:`D_S` of the given spike trains. The + spike-distance is the integral over the isi distance profile :math:`S(t)`: - .. math:: S = \int_{T_0}^{T_1} S_{spike}(t) dt. + .. math:: D_S = \int_{T_0}^{T_1} S(t) dt. :param spike_train1: First spike train. - :type spike_train1: :class:`pyspike.SpikeTrain` + :type spike_train1: :class:`.SpikeTrain` :param spike_train2: Second spike train. - :type spike_train2: :class:`pyspike.SpikeTrain` + :type spike_train2: :class:`.SpikeTrain` :param interval: averaging interval given as a pair of floats (T0, T1), if None the average over the whole function is computed. :type interval: Pair of floats or None. @@ -81,15 +76,17 @@ def spike_distance(spike_train1, spike_train2, interval=None): def spike_profile_multi(spike_trains, indices=None): """ Computes the multi-variate spike distance profile for a set of spike trains. That is the average spike-distance of all pairs of spike-trains: - :math:`S_spike(t) = 2/((N(N-1)) sum_{} S_{spike}^{i, j}`, + + .. math:: = \\frac{2}{N(N-1)} \\sum_{} S^{i, j}`, + where the sum goes over all pairs - :param spike_trains: list of spike trains + :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None - :returns: The averaged spike profile :math:`(t)` - :rtype: :class:`pyspike.function.PieceWiseLinFunc` + :returns: The averaged spike profile :math:`(t)` + :rtype: :class:`.PieceWiseLinFunc` """ average_dist, M = _generic_profile_multi(spike_trains, spike_profile, @@ -104,17 +101,20 @@ def spike_profile_multi(spike_trains, indices=None): def spike_distance_multi(spike_trains, indices=None, interval=None): """ Computes the multi-variate spike distance for a set of spike trains. That is the time average of the multi-variate spike profile: - S_{spike} = \int_0^T 2/((N(N-1)) sum_{} S_{spike}^{i, j} dt + + .. math:: D_S = \\int_0^T \\frac{2}{N(N-1)} \\sum_{} + S^{i, j} dt + where the sum goes over all pairs - :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None :param interval: averaging interval given as a pair of floats, if None the average over the whole function is computed. :type interval: Pair of floats or None. - :returns: The averaged spike distance S. + :returns: The averaged multi-variate spike distance :math:`D_S`. :rtype: double """ return spike_profile_multi(spike_trains, indices).avrg(interval) @@ -126,7 +126,7 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): def spike_distance_matrix(spike_trains, indices=None, interval=None): """ Computes the time averaged spike-distance of all pairs of spike-trains. - :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, if None all given spike trains are used (default=None) :type indices: list or None @@ -134,7 +134,7 @@ def spike_distance_matrix(spike_trains, indices=None, interval=None): the average over the whole function is computed. :type interval: Pair of floats or None. :returns: 2D array with the pair wise time average spike distances - :math:`S_{ij}` + :math:`D_S^{ij}` :rtype: np.array """ return _generic_distance_matrix(spike_trains, spike_distance, diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 8ddd32c..9d2e363 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -1,12 +1,7 @@ -""" - -Module containing several functions to compute SPIKE-Synchronization profiles -and distances - -Copyright 2014-2015, Mario Mulansky - -Distributed under the BSD License -""" +# Module containing several functions to compute SPIKE-Synchronization profiles +# and distances +# Copyright 2014-2015, Mario Mulansky +# Distributed under the BSD License from functools import partial from pyspike import DiscreteFunc @@ -27,7 +22,7 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): :param spike_train2: Second spike train. :type spike_train2: :class:`pyspike.SpikeTrain` :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. + coincidence window has no upper bound. :returns: The spike-distance profile :math:`S_{sync}(t)`. :rtype: :class:`pyspike.function.DiscreteFunction` @@ -77,12 +72,13 @@ def spike_sync(spike_train1, spike_train2, interval=None, max_tau=None): :param spike_train2: Second spike train. :type spike_train2: :class:`pyspike.SpikeTrain` :param interval: averaging interval given as a pair of floats (T0, T1), - if None the average over the whole function is computed. + if `None` the average over the whole function is computed. :type interval: Pair of floats or None. :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. + coincidence window has no upper bound. :returns: The spike synchronization value. - :rtype: double + :rtype: `double` + """ return spike_sync_profile(spike_train1, spike_train2, max_tau).avrg(interval) @@ -103,7 +99,7 @@ def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): if None all given spike trains are used (default=None) :type indices: list or None :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. + coincidence window has no upper bound. :returns: The multi-variate spike sync profile :math:`(t)` :rtype: :class:`pyspike.function.DiscreteFunction` @@ -130,9 +126,10 @@ def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): the average over the whole function is computed. :type interval: Pair of floats or None. :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. + coincidence window has no upper bound. :returns: The multi-variate spike synchronization value SYNC. :rtype: double + """ return spike_sync_profile_multi(spike_trains, indices, max_tau).avrg(interval) @@ -153,10 +150,11 @@ def spike_sync_matrix(spike_trains, indices=None, interval=None, max_tau=None): the average over the whole function is computed. :type interval: Pair of floats or None. :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. + coincidence window has no upper bound. :returns: 2D array with the pair wise time spike synchronization values :math:`SYNC_{ij}` :rtype: np.array + """ dist_func = partial(spike_sync, max_tau=max_tau) return _generic_distance_matrix(spike_trains, dist_func, diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 9401b6e..35d8533 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -1,11 +1,6 @@ -""" spikes.py - -Module containing several function to load and transform spike trains - -Copyright 2014, Mario Mulansky - -Distributed under the BSD License -""" +# Module containing several function to load and transform spike trains +# Copyright 2014, Mario Mulansky +# Distributed under the BSD License import numpy as np from pyspike import SpikeTrain @@ -15,21 +10,18 @@ from pyspike import SpikeTrain # spike_train_from_string ############################################################ def spike_train_from_string(s, edges, sep=' ', is_sorted=False): - """ Converts a string of times into a :class:`pyspike.SpikeTrain`. + """ Converts a string of times into a :class:`.SpikeTrain`. :param s: the string with (ordered) spike times. :param edges: interval defining the edges of the spike train. - Given as a pair of floats (T0, T1) or a single float T1, where T0=0 is - assumed. + Given as a pair of floats (T0, T1) or a single float T1, + where T0=0 is assumed. :param sep: The separator between the time numbers, default=' '. :param is_sorted: if True, the spike times are not sorted after loading, if False, spike times are sorted with `np.sort` - :returns: :class:`pyspike.SpikeTrain` + :returns: :class:`.SpikeTrain` """ - if not(is_sorted): - return SpikeTrain(np.sort(np.fromstring(s, sep=sep)), edges) - else: - return SpikeTrain(np.fromstring(s, sep=sep), edges) + return SpikeTrain(np.fromstring(s, sep=sep), edges, is_sorted) ############################################################ @@ -40,7 +32,7 @@ def load_spike_trains_from_txt(file_name, edges, """ Loads a number of spike trains from a text file. Each line of the text file should contain one spike train as a sequence of spike times separated by `separator`. Empty lines as well as lines starting with `comment` are - neglected. The `interval` represents the start and the end of the + neglected. The `edges` represents the start and the end of the spike trains. :param file_name: The name of the text file. @@ -51,7 +43,7 @@ def load_spike_trains_from_txt(file_name, edges, :param separator: The character used to seprate the values in the text file :param comment: Lines starting with this character are ignored. :param sort: If true, the spike times are order via `np.sort`, default=True - :returns: list of spike trains + :returns: list of :class:`.SpikeTrain` """ spike_trains = [] spike_file = open(file_name, 'r') @@ -70,7 +62,7 @@ def load_spike_trains_from_txt(file_name, edges, def merge_spike_trains(spike_trains): """ Merges a number of spike trains into a single spike train. - :param spike_trains: list of arrays of spike times + :param spike_trains: list of :class:`.SpikeTrain` :returns: spike train with the merged spike times """ # get the lengths of the spike trains @@ -110,7 +102,7 @@ def generate_poisson_spikes(rate, interval): to the spike train at the beginning and end of this interval, if they are not yet present. :type interval: pair of doubles or double - :returns: Poisson spike train as a :class:`pyspike.SpikeTrain` + :returns: Poisson spike train as a :class:`.SpikeTrain` """ try: T_start = interval[0] -- cgit v1.2.3 From ecc7898a0b6cd5bc353fd246f3ad549934c82229 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 27 Apr 2015 17:35:36 +0200 Subject: adjustments of examples --- examples/PySpike_testdata.txt | 0 examples/merge.py | 11 ++++++----- examples/multivariate.py | 5 +++-- examples/plot.py | 6 +++--- examples/spike_sync.py | 3 +-- 5 files changed, 13 insertions(+), 12 deletions(-) mode change 100755 => 100644 examples/PySpike_testdata.txt diff --git a/examples/PySpike_testdata.txt b/examples/PySpike_testdata.txt old mode 100755 new mode 100644 diff --git a/examples/merge.py b/examples/merge.py index 2550cdb..2ea96ea 100644 --- a/examples/merge.py +++ b/examples/merge.py @@ -17,12 +17,13 @@ import pyspike as spk # first load the data, ending time = 4000 spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) -spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) +merged_spike_train = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) -print(spikes) +print(merged_spike_train.spikes) -plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') -plt.plot(spike_trains[1], np.ones_like(spike_trains[1]), 'x') -plt.plot(spikes, 2*np.ones_like(spikes), 'o') +plt.plot(spike_trains[0].spikes, np.ones_like(spike_trains[0].spikes), 'o') +plt.plot(spike_trains[1].spikes, np.ones_like(spike_trains[1].spikes), 'x') +plt.plot(merged_spike_train.spikes, + 2*np.ones_like(merged_spike_train.spikes), 'o') plt.show() diff --git a/examples/multivariate.py b/examples/multivariate.py index 260b217..53dbf0f 100644 --- a/examples/multivariate.py +++ b/examples/multivariate.py @@ -19,11 +19,12 @@ t_start = time.clock() # load the data time_loading = time.clock() spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) t_loading = time.clock() print("Number of spike trains: %d" % len(spike_trains)) -num_of_spikes = sum([len(spike_trains[i]) for i in xrange(len(spike_trains))]) +num_of_spikes = sum([len(spike_trains[i].spikes) + for i in xrange(len(spike_trains))]) print("Number of spikes: %d" % num_of_spikes) # calculate the multivariate spike distance diff --git a/examples/plot.py b/examples/plot.py index d32c464..9670286 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -17,11 +17,11 @@ import matplotlib.pyplot as plt import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) # plot the spike time -for (i, spikes) in enumerate(spike_trains): - plt.plot(spikes, i*np.ones_like(spikes), 'o') +for (i, spike_train) in enumerate(spike_trains): + plt.plot(spike_train.spikes, i*np.ones_like(spike_train.spikes), 'o') f = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() diff --git a/examples/spike_sync.py b/examples/spike_sync.py index 9c5f75c..9e81536 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -1,12 +1,11 @@ from __future__ import print_function -import numpy as np import matplotlib.pyplot as plt import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("../test/SPIKE_Sync_Test.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) plt.figure() -- cgit v1.2.3 From 2336f1fcf28efeb23e8450f407a008b8032edd5e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 27 Apr 2015 18:16:12 +0200 Subject: adjusted PSTH, added to doc index --- doc/pyspike.rst | 8 ++++++++ examples/spike_sync.py | 2 +- pyspike/psth.py | 39 +++++++++++++++++++++++---------------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/doc/pyspike.rst b/doc/pyspike.rst index a6dc1a0..74ab439 100644 --- a/doc/pyspike.rst +++ b/doc/pyspike.rst @@ -57,6 +57,14 @@ SPIKE-synchronization :undoc-members: :show-inheritance: +PSTH +........................................ +.. automodule:: pyspike.psth + :members: + :undoc-members: + :show-inheritance: + + Helper functions ........................................ .. automodule:: pyspike.spikes diff --git a/examples/spike_sync.py b/examples/spike_sync.py index 9e81536..37dbff4 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -40,7 +40,7 @@ plt.plot(x1, y1, '-k', lw=2.5, label="averaged SPIKE-Sync profile") plt.subplot(212) -f_psth = spk.psth(spike_trains, bin_size=5.0) +f_psth = spk.psth(spike_trains, bin_size=50.0) x, y = f_psth.get_plottable_data() plt.plot(x, y, '-k', alpha=1.0, label="PSTH") diff --git a/pyspike/psth.py b/pyspike/psth.py index 8516460..4027215 100644 --- a/pyspike/psth.py +++ b/pyspike/psth.py @@ -1,27 +1,34 @@ -""" - -Module containing functions to compute the PSTH profile - -Copyright 2015, Mario Mulansky - -Distributed under the BSD License -""" +# Module containing functions to compute the PSTH profile +# Copyright 2015, Mario Mulansky +# Distributed under the BSD License import numpy as np from pyspike import PieceWiseConstFunc -# Computes the Peristimulus time histogram of a set of spike trains +# Computes the peri-stimulus time histogram of a set of spike trains def psth(spike_trains, bin_size): - - bins = int((spike_trains[0][-1] - spike_trains[0][0]) / bin_size) - - N = len(spike_trains) - combined_spike_train = spike_trains[0][1:-1] + """ Computes the peri-stimulus time histogram of a set of + :class:`.SpikeTrain`. The PSTH is simply the histogram of merged spike + events. The :code:`bin_size` defines the width of the histogram bins. + + :param spike_trains: list of :class:`.SpikeTrain` + :param bin_size: width of the histogram bins. + :return: The PSTH as a :class:`.PieceWiseConstFunc` + """ + + bin_count = int((spike_trains[0].t_end - spike_trains[0].t_start) / + bin_size) + bins = np.linspace(spike_trains[0].t_start, spike_trains[0].t_end, + bin_count+1) + + # N = len(spike_trains) + combined_spike_train = spike_trains[0].spikes for i in xrange(1, len(spike_trains)): combined_spike_train = np.append(combined_spike_train, - spike_trains[i][1:-1]) + spike_trains[i].spikes) vals, edges = np.histogram(combined_spike_train, bins, density=False) + bin_size = edges[1]-edges[0] - return PieceWiseConstFunc(edges, vals/(N*bin_size)) + return PieceWiseConstFunc(edges, vals) # /(N*bin_size)) -- cgit v1.2.3 From 2e7351393927ba9e9e0c3b7b59d05e8aeeb41d1f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 28 Apr 2015 16:11:11 +0200 Subject: edge correction for the ISI-distance --- pyspike/cython/cython_distance.pyx | 18 ++++++++++++------ pyspike/cython/python_backend.py | 18 ++++++++++++------ test/test_distance.py | 8 +++++--- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx index a41d8e8..6ee0181 100644 --- a/pyspike/cython/cython_distance.pyx +++ b/pyspike/cython/cython_distance.pyx @@ -62,14 +62,16 @@ def isi_distance_cython(double[:] s1, double[:] s2, # first interspike interval - check if a spike exists at the start time if s1[0] > t_start: - nu1 = s1[0] - t_start + # edge correction + nu1 = fmax(s1[0]-t_start, s1[1]-s1[0]) index1 = -1 else: nu1 = s1[1]-s1[0] index1 = 0 if s2[0] > t_start: - nu2 = s2[0] - t_start + # edge correction + nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) index2 = -1 else: nu2 = s2[1]-s2[0] @@ -89,7 +91,8 @@ def isi_distance_cython(double[:] s1, double[:] s2, if index1 < N1-1: nu1 = s1[index1+1]-s1[index1] else: - nu1 = t_end-s1[index1] + # edge correction + nu1 = fmax(t_end-s1[index1], nu1) elif (index2 < N2-1) and ((index1 == N1-1) or (s1[index1+1] > s2[index2+1])): index2 += 1 @@ -97,7 +100,8 @@ def isi_distance_cython(double[:] s1, double[:] s2, if index2 < N2-1: nu2 = s2[index2+1]-s2[index2] else: - nu2 = t_end-s2[index2] + # edge correction + nu2 = fmax(t_end-s2[index2], nu2) else: # s1[index1+1] == s2[index2+1] index1 += 1 index2 += 1 @@ -105,11 +109,13 @@ def isi_distance_cython(double[:] s1, double[:] s2, if index1 < N1-1: nu1 = s1[index1+1]-s1[index1] else: - nu1 = t_end-s1[index1] + # edge correction + nu1 = fmax(t_end-s1[index1], nu1) if index2 < N2-1: nu2 = s2[index2+1]-s2[index2] else: - nu2 = t_end-s2[index2] + # edge correction + nu2 = fmax(t_end-s2[index2], nu2) # compute the corresponding isi-distance isi_values[index] = fabs(nu1 - nu2) / fmax(nu1, nu2) index += 1 diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 317b568..1fd8c42 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -27,13 +27,15 @@ def isi_distance_python(s1, s2, t_start, t_end): # 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: - nu1 = s1[0] - t_start + # edge correction + nu1 = max(s1[0] - t_start, s1[1] - s1[0]) index1 = -1 else: nu1 = s1[1] - s1[0] index1 = 0 if s2[0] > t_start: - nu2 = s2[0] - t_start + # edge correction + nu2 = max(s2[0] - t_start, s2[1] - s2[0]) index2 = -1 else: nu2 = s2[1] - s2[0] @@ -49,7 +51,8 @@ def isi_distance_python(s1, s2, t_start, t_end): if index1 < N1-1: nu1 = s1[index1+1]-s1[index1] else: - nu1 = t_end-s1[index1] + # edge correction + nu1 = max(t_end-s1[N1-1], s1[N1-1]-s1[N1-2]) elif (index2 < N2-1) and (index1 == N1-1 or s1[index1+1] > s2[index2+1]): @@ -58,7 +61,8 @@ def isi_distance_python(s1, s2, t_start, t_end): if index2 < N2-1: nu2 = s2[index2+1]-s2[index2] else: - nu2 = t_end-s2[index2] + # edge correction + nu2 = max(t_end-s2[N2-1], s2[N2-1]-s2[N2-2]) else: # s1[index1 + 1] == s2[index2 + 1] index1 += 1 @@ -67,11 +71,13 @@ def isi_distance_python(s1, s2, t_start, t_end): if index1 < N1-1: nu1 = s1[index1+1]-s1[index1] else: - nu1 = t_end-s1[index1] + # edge correction + nu1 = max(t_end-s1[N1-1], s1[N1-1]-s1[N1-2]) if index2 < N2-1: nu2 = s2[index2+1]-s2[index2] else: - nu2 = t_end-s2[index2] + # edge correction + nu2 = max(t_end-s2[N2-1], s2[N2-1]-s2[N2-2]) # compute the corresponding isi-distance isi_values[index] = abs(nu1 - nu2) / \ max(nu1, nu2) diff --git a/test/test_distance.py b/test/test_distance.py index 20b52e8..19da35f 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -47,7 +47,7 @@ def test_isi(): t2 = SpikeTrain([0.1, 0.4, 0.5, 0.6], [0.0, 1.0]) expected_times = [0.0, 0.1, 0.2, 0.4, 0.5, 0.6, 1.0] - expected_isi = [0.1/0.2, 0.1/0.3, 0.1/0.3, 0.1/0.2, 0.1/0.2, 0.0/0.5] + expected_isi = [0.1/0.3, 0.1/0.3, 0.1/0.3, 0.1/0.2, 0.1/0.2, 0.0/0.5] expected_times = np.array(expected_times) expected_isi = np.array(expected_isi) @@ -332,7 +332,9 @@ def test_regression_spiky(): st2 = SpikeTrain(np.arange(100, 1201, 110), 1300) isi_dist = spk.isi_distance(st1, st2) - assert_almost_equal(isi_dist, 7.6923076923076941e-02, decimal=15) + assert_almost_equal(isi_dist, 9.0909090909090939e-02, decimal=15) + isi_profile = spk.isi_profile(st1, st2) + assert_equal(isi_profile.y, 0.1/1.1 * np.ones_like(isi_profile.y)) spike_dist = spk.spike_distance(st1, st2) assert_equal(spike_dist, 2.1105878248735391e-01) @@ -346,7 +348,7 @@ def test_regression_spiky(): (0.0, 4000.0)) isi_dist = spk.isi_distance_multi(spike_trains) # get the full precision from SPIKY - assert_almost_equal(isi_dist, 1.8318789829845508e-01, decimal=15) + assert_almost_equal(isi_dist, 0.17051816816999129656, decimal=15) spike_profile = spk.spike_profile_multi(spike_trains) assert_equal(len(spike_profile.y1)+len(spike_profile.y2), 1252) -- cgit v1.2.3 From 26d4dd11f80b1c0b4ab19e7cb74c77b61eaad90c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 30 Apr 2015 12:26:26 +0200 Subject: + Changelog --- Changelog | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 Changelog diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..2ac065b --- /dev/null +++ b/Changelog @@ -0,0 +1,9 @@ +PySpike v0.2: + + * introduction of SpikeTrain class. Breaking interface change of + distance functions + * added max_tau parameter to SPIKE-Synchronization + * added psth function + * restructured modules, separate files for different distance measures + * Bugfix for selective multivariate distance + * added spike generation function -- cgit v1.2.3 From ec9850a12818d9c91d73372dccf83776449be5cc Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 30 Apr 2015 14:26:17 +0200 Subject: updated version in setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 289d521..e35123f 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ to compile cython files: python setup.py build_ext --inplace -Copyright 2014, Mario Mulansky +Copyright 2014-2015, Mario Mulansky Distributed under the BSD License @@ -46,7 +46,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.1.3', + version='0.2.0', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy.get_include()], -- cgit v1.2.3 From 7757dc01e2d6020e0f6d6e44afdb734e61ef3c9c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 30 Apr 2015 17:23:06 +0200 Subject: addresses #5 - added __version__ property --- pyspike/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index a5f9f0a..3e836bd 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -23,3 +23,22 @@ from psth import psth from spikes import load_spike_trains_from_txt, spike_train_from_string, \ merge_spike_trains, generate_poisson_spikes + + +# define the __version__ following +# http://stackoverflow.com/questions/17583443 +from pkg_resources import get_distribution, DistributionNotFound +import os.path + +try: + _dist = get_distribution('pyspike') + # Normalize case for Windows systems + dist_loc = os.path.normcase(_dist.location) + here = os.path.normcase(__file__) + if not here.startswith(os.path.join(dist_loc, 'pyspike')): + # not installed, but there is another version that *is* + raise DistributionNotFound +except DistributionNotFound: + __version__ = 'Please install this project with setup.py' +else: + __version__ = _dist.version -- cgit v1.2.3 From d5995b82d0de1c5b448823b4acd4efe7d5d47eb4 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 30 Apr 2015 18:23:50 +0200 Subject: new version in docs --- doc/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 5427bb1..9b994c2 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,9 +64,9 @@ copyright = u'2014-2015, Mario Mulansky' # built documents. # # The short X.Y version. -version = '0.1' +version = '0.2' # The full version, including alpha/beta/rc tags. -release = '0.1' +release = '0.2.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit v1.2.3 From a718911ba2aac9302465c0522cc18b4470b99f77 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 4 May 2015 10:46:17 +0200 Subject: added pypi badge --- Readme.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.rst b/Readme.rst index e80c0f7..f50b12b 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1,6 +1,8 @@ PySpike ======= +.. image:: https://badge.fury.io/py/pyspike.png + :target: http://badge.fury.io/py/pyspike .. image:: https://travis-ci.org/mariomulansky/PySpike.svg?branch=master :target: https://travis-ci.org/mariomulansky/PySpike -- cgit v1.2.3 From 7e3648264eb3f96257909e53939a70f2fa79f1be Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 4 May 2015 11:37:49 +0200 Subject: restructured docs moved tutorial from Readme to separate section. Readme now contains only two basic examples --- Changelog | 4 ++ Readme.rst | 192 ++-------------------------------------------------------- doc/index.rst | 8 ++- 3 files changed, 15 insertions(+), 189 deletions(-) diff --git a/Changelog b/Changelog index 2ac065b..209b138 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,7 @@ +PySpike v0.2.1: + * addition of __version__ attribute + * restructured docs, Readme now only contains basic examples + PySpike v0.2: * introduction of SpikeTrain class. Breaking interface change of diff --git a/Readme.rst b/Readme.rst index e80c0f7..1094332 100644 --- a/Readme.rst +++ b/Readme.rst @@ -70,63 +70,11 @@ Then you can run the tests using the `nosetests` test framework: Finally, you should make PySpike's installation folder known to Python to be able to import pyspike in your own projects. Therefore, add your :code:`/path/to/PySpike` to the :code:`$PYTHONPATH` environment variable. -Spike trains ------------- -In PySpike, spike trains are represented by :class:`.SpikeTrain` objects. -A :class:`.SpikeTrain` object consists of the spike times given as `numpy` arrays as well as the edges of the spike train as :code:`[t_start, t_end]`. -The following code creates such a spike train with some arbitrary spike times: - -.. code:: python - - import numpy as np - from pyspike import SpikeTrain - - spike_train = SpikeTrain(np.array([0.1, 0.3, 0.45, 0.6, 0.9], [0.0, 1.0])) - -Loading from text files -....................... - -Typically, spike train data is loaded into PySpike from data files. -The most straight-forward data files are text files where each line represents one spike train given as an sequence of spike times. -An exemplary file with several spike trains is `PySpike_testdata.txt `_. -To quickly obtain spike trains from such files, PySpike provides the function :func:`.load_spike_trains_from_txt`. - -.. code:: python - - import numpy as np - import pyspike as spk - - spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", - edges=(0, 4000)) - -This function expects the name of the data file as first parameter. -Furthermore, the time interval of the spike train measurement (edges of the spike trains) should be provided as a pair of start- and end-time values. -Furthermore, the spike trains are sorted via :code:`np.sort` (disable this feature by providing :code:`is_sorted=True` as a parameter to the load function). -As result, :func:`.load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. - - -Computing bivariate distances profiles ---------------------------------------- - -**Important note:** - ------------------------------- - - Spike trains are expected to be *sorted*! - For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed sorted. - Make sure that all your spike trains are sorted, which is ensured if you use the :func:`.load_spike_trains_from_txt` function with the parameter `is_sorted=False` (default). - If in doubt, use :meth:`.SpikeTrain.sort()` to ensure a correctly sorted spike train. - - If you need to copy a spike train, use the :meth:`.SpikeTrain.copy()` method. - Simple assignment `t2 = t1` does not create a copy of the spike train data, but a reference as `numpy.array` is used for storing the data. - ------------------------------- - -ISI-distance -............ +Examples +----------------------------- -The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two :class:`.SpikeTrain` s, and plots it with matplotlib: +The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two :code:`SpikeTrain` s, and plots it with matplotlib: .. code:: python @@ -141,109 +89,8 @@ The following code loads some exemplary spike trains, computes the dissimilarity print("ISI distance: %.8f" % isi_profile.avrg()) plt.show() -The ISI-profile is a piece-wise constant function, and hence the function :func:`.isi_profile` returns an instance of the :class:`.PieceWiseConstFunc` class. -As shown above, this class allows you to obtain arrays that can be used to plot the function with :code:`plt.plt`, but also to compute the time average, which amounts to the final scalar ISI-distance. -By default, the time average is computed for the whole :class:`.PieceWiseConstFunc` function. -However, it is also possible to obtain the average of a specific interval by providing a pair of floats defining the start and end of the interval. -For the above example, the following code computes the ISI-distances obtained from averaging the ISI-profile over four different intervals: - -.. code:: python - - isi1 = isi_profile.avrg(interval=(0, 1000)) - isi2 = isi_profile.avrg(interval=(1000, 2000)) - isi3 = isi_profile.avrg(interval=[(0, 1000), (2000, 3000)]) - isi4 = isi_profile.avrg(interval=[(1000, 2000), (3000, 4000)]) - -Note, how also multiple intervals can be supplied by giving a list of tuples. - -If you are only interested in the scalar ISI-distance and not the profile, you can simply use: - -.. code:: python - - isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1], interval) - -where :code:`interval` is optional, as above, and if omitted the ISI-distance is computed for the complete spike trains. - - -SPIKE-distance -.............. - -To compute for the spike distance profile you use the function :func:`.spike_profile` instead of :code:`isi_profile` above. -But the general approach is very similar: - -.. code:: python - - import matplotlib.pyplot as plt - import pyspike as spk - - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - edges=(0, 4000)) - spike_profile = spk.spike_profile(spike_trains[0], spike_trains[1]) - x, y = spike_profile.get_plottable_data() - plt.plot(x, y, '--k') - print("SPIKE distance: %.8f" % spike_profile.avrg()) - plt.show() - -This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. -In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and is therefore represented by a :class:`.PieceWiseLinFunc` object. -Just like the :class:`.PieceWiseConstFunc` for the ISI-profile, the :class:`.PieceWiseLinFunc` provides a :meth:`.PieceWiseLinFunc.get_plottable_data` member function that returns arrays that can be used directly to plot the function. -Furthermore, the :meth:`.PieceWiseLinFunc.avrg` member function returns the average of the profile defined as the overall SPIKE distance. -As above, you can provide an interval as a pair of floats as well as a sequence of such pairs to :code:`avrg` to specify the averaging interval if required. -Again, you can use - -.. code:: python - - spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval) - -to compute the SPIKE distance directly, if you are not interested in the profile at all. -The parameter :code:`interval` is optional and if neglected the whole spike train is used. - - -SPIKE synchronization -..................... - -**Important note:** - ------------------------------- - - SPIKE-Synchronization measures *similarity*. - That means, a value of zero indicates absence of synchrony, while a value of one denotes the presence of synchrony. - This is exactly opposite to the other two measures: ISI- and SPIKE-distance. - ----------------------- - - -SPIKE synchronization is another approach to measure spike synchrony. -In contrast to the SPIKE- and ISI-distance, it measures similarity instead of dissimilarity, i.e. higher values represent larger synchrony. -Another difference is that the SPIKE synchronization profile is only defined exactly at the spike times, not for the whole interval of the spike trains. -Therefore, it is represented by a :class:`.DiscreteFunction`. - -To compute for the spike synchronization profile, PySpike provides the function :func:`.spike_sync_profile`. -The general handling of the profile, however, is similar to the other profiles above: - -.. code:: python - - import matplotlib.pyplot as plt - import pyspike as spk - - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - edges=(0, 4000)) - spike_profile = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) - x, y = spike_profile.get_plottable_data() - -For the direct computation of the overall spike synchronization value within some interval, the :func:`.spike_sync` function can be used: - -.. code:: python - - spike_sync = spk.spike_sync(spike_trains[0], spike_trains[1], interval) - - -Computing multivariate profiles and distances ----------------------------------------------- - -To compute the multivariate ISI-profile, SPIKE-profile or SPIKE-Synchronization profile f a set of spike trains, PySpike provides multi-variate version of the profile function. -The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains using the :func:`.isi_profile_multi`, :func:`.spike_profile_multi`, :func:`.spike_sync_profile_multi` functions: +The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains using the :code:`isi_profile_multi`, :code:`spike_profile_multi`, :code:`spike_sync_profile_multi` functions: .. code:: python @@ -253,36 +100,7 @@ The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-prof avrg_spike_profile = spk.spike_profile_multi(spike_trains) avrg_spike_sync_profile = spk.spike_sync_profile_multi(spike_trains) -All functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. -As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :func:`.isi_distance_multi`, :func:`.spike_distance_multi` and :func:`.spike_sync_multi`, that return the scalar overall multivariate ISI- and SPIKE-distance as well as the SPIKE-Synchronization value. -Those functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. - -Another option to characterize large sets of spike trains are distance matrices. -Each entry in the distance matrix represents a bivariate distance (similarity for SPIKE-Synchronization) of two spike trains. -The distance matrix is symmetric and has zero values (ones) at the diagonal and is computed with the functions :func:`.isi_distance_matrix`, :func:`.spike_distance_matrix` and :func:`.spike_sync_matrix`. -The following example computes and plots the ISI- and SPIKE-distance matrix as well as the SPIKE-Synchronization-matrix, with different intervals. - -.. code:: python - - spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) - - plt.figure() - isi_distance = spk.isi_distance_matrix(spike_trains) - plt.imshow(isi_distance, interpolation='none') - plt.title("ISI-distance") - - plt.figure() - spike_distance = spk.spike_distance_matrix(spike_trains, interval=(0,1000)) - plt.imshow(spike_distance, interpolation='none') - plt.title("SPIKE-distance") - - plt.figure() - spike_sync = spk.spike_sync_matrix(spike_trains, interval=(2000,4000)) - plt.imshow(spike_sync, interpolation='none') - plt.title("SPIKE-Sync") - - plt.show() - +More examples with detailed descriptions can be found in the `tutorial section `_. =============================================================================== diff --git a/doc/index.rst b/doc/index.rst index bc05b60..f34690b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -5,6 +5,11 @@ .. include:: ../Readme.rst +Tutorial +=================================== + +.. include:: tutorial.rst + Reference =================================== @@ -14,9 +19,8 @@ Reference pyspike Indices and tables -================== +=================================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` - -- cgit v1.2.3 From a7df02c0dc064dbb0586a394bb74200c7d6d67df Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 4 May 2015 11:40:25 +0200 Subject: removed class reference from Readme --- Readme.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.rst b/Readme.rst index 1094332..b8858f5 100644 --- a/Readme.rst +++ b/Readme.rst @@ -22,7 +22,7 @@ All source codes are available on `Github Date: Thu, 7 May 2015 23:08:12 +0200 Subject: performance improvements use recursive approach to compute average profile for average multivariate distances, dont compute average multivariate profile, but average distances directly. --- examples/performance.py | 42 +++++++++++++++++++++++ pyspike/cython/cython_add.pyx | 20 +++++------ pyspike/generic.py | 77 +++++++++++++++++++++++++++++++++++++++---- pyspike/isi_distance.py | 6 ++-- pyspike/spike_distance.py | 6 ++-- test/test_distance.py | 14 ++++++-- 6 files changed, 139 insertions(+), 26 deletions(-) create mode 100644 examples/performance.py diff --git a/examples/performance.py b/examples/performance.py new file mode 100644 index 0000000..469b5ab --- /dev/null +++ b/examples/performance.py @@ -0,0 +1,42 @@ +""" Compute distances of large sets of spike trains for performance tests +""" + +from __future__ import print_function + +import pyspike as spk +from datetime import datetime +import cProfile + +M = 100 # number of spike trains +r = 1.0 # rate of Poisson spike times +T = 1E3 # length of spike trains + +print("%d spike trains with %d spikes" % (M, int(r*T))) + +spike_trains = [] + +t_start = datetime.now() +for i in xrange(M): + spike_trains.append(spk.generate_poisson_spikes(r, T)) +t_end = datetime.now() +runtime = (t_end-t_start).total_seconds() + +print("Spike generation runtime: %.3fs" % runtime) + +print("================ ISI COMPUTATIONS ================") +print(" MULTIVARIATE DISTANCE") +cProfile.run('spk.isi_distance_multi(spike_trains)') +print(" MULTIVARIATE PROFILE") +cProfile.run('spk.isi_profile_multi(spike_trains)') + +print("================ SPIKE COMPUTATIONS ================") +print(" MULTIVARIATE DISTANCE") +cProfile.run('spk.spike_distance_multi(spike_trains)') +print(" MULTIVARIATE PROFILE") +cProfile.run('spk.spike_profile_multi(spike_trains)') + +print("================ SPIKE-SYNC COMPUTATIONS ================") +print(" MULTIVARIATE DISTANCE") +cProfile.run('spk.spike_sync_multi(spike_trains)') +print(" MULTIVARIATE PROFILE") +cProfile.run('spk.spike_sync_profile_multi(spike_trains)') diff --git a/pyspike/cython/cython_add.pyx b/pyspike/cython/cython_add.pyx index ac64005..8da1e53 100644 --- a/pyspike/cython/cython_add.pyx +++ b/pyspike/cython/cython_add.pyx @@ -83,13 +83,9 @@ def add_piece_wise_const_cython(double[:] x1, double[:] y1, else: # both arrays reached the end simultaneously # only the last x-value missing x_new[index+1] = x1[N1-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 - x1 = x_new[:index+2] - y1 = y_new[:index+1] # end nogil - return np.array(x_new[:index+2]), np.array(y_new[:index+1]) + # 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]) ############################################################ @@ -169,9 +165,9 @@ def add_piece_wise_lin_cython(double[:] x1, double[:] y11, double[:] y12, y2_new[index] = y12[N1-2]+y22[N2-2] # only use the data that was actually filled # end nogil - return (np.array(x_new[:index+2]), - np.array(y1_new[:index+1]), - np.array(y2_new[:index+1])) + return (np.asarray(x_new[:index+2]), + np.asarray(y1_new[:index+1]), + np.asarray(y2_new[:index+1])) ############################################################ @@ -230,6 +226,6 @@ def add_discrete_function_cython(double[:] x1, double[:] y1, double[:] mp1, # the last value is again the end of the interval # only use the data that was actually filled - return (np.array(x_new[:index+1]), - np.array(y_new[:index+1]), - np.array(mp_new[:index+1])) + return (np.asarray(x_new[:index+1]), + np.asarray(y_new[:index+1]), + np.asarray(mp_new[:index+1])) diff --git a/pyspike/generic.py b/pyspike/generic.py index 4f278d2..41affcb 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -31,6 +31,69 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): Returns: - The averaged multi-variate distance of all pairs """ + + def divide_and_conquer(pairs1, pairs2): + """ recursive calls by splitting the two lists in half. + """ + L1 = len(pairs1) + if L1 > 1: + dist_prof1 = divide_and_conquer(pairs1[:L1/2], pairs1[L1/2:]) + else: + dist_prof1 = pair_distance_func(spike_trains[pairs1[0][0]], + spike_trains[pairs1[0][1]]) + L2 = len(pairs2) + if L2 > 1: + dist_prof2 = divide_and_conquer(pairs2[:L2/2], pairs2[L2/2:]) + else: + dist_prof2 = pair_distance_func(spike_trains[pairs2[0][0]], + spike_trains[pairs2[0][1]]) + dist_prof1.add(dist_prof2) + return dist_prof1 + + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + L = len(pairs) + if L > 1: + # recursive iteration through the list of pairs to get average profile + avrg_dist = divide_and_conquer(pairs[:len(pairs)/2], + pairs[len(pairs)/2:]) + else: + avrg_dist = pair_distance_func(spike_trains[pairs[0][0]], + spike_trains[pairs[0][1]]) + + return avrg_dist, L + + +############################################################ +# _generic_distance_multi +############################################################ +def _generic_distance_multi(spike_trains, pair_distance_func, + indices=None, interval=None): + """ Internal implementation detail, don't call this function directly, + use isi_distance_multi or spike_distance_multi instead. + + Computes the multi-variate distance for a set of spike-trains using the + pair_dist_func to compute pair-wise distances. That is it computes the + average distance of all pairs of spike-trains: + :math:`S(t) = 2/((N(N-1)) sum_{} D_{i,j}`, + where the sum goes over all pairs . + Args: + - spike_trains: list of spike trains + - pair_distance_func: function computing the distance of two spike trains + - indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + Returns: + - The averaged multi-variate distance of all pairs + """ + if indices is None: indices = np.arange(len(spike_trains)) indices = np.array(indices) @@ -40,13 +103,13 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): # generate a list of possible index pairs pairs = [(indices[i], j) for i in range(len(indices)) for j in indices[i+1:]] - # start with first pair - (i, j) = pairs[0] - average_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - for (i, j) in pairs[1:]: - current_dist = pair_distance_func(spike_trains[i], spike_trains[j]) - average_dist.add(current_dist) # add to the average - return average_dist, len(pairs) + + avrg_dist = 0.0 + for (i, j) in pairs: + avrg_dist += pair_distance_func(spike_trains[i], spike_trains[j], + interval) + + return avrg_dist/len(pairs) ############################################################ diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index aeab0df..21df561 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -3,7 +3,8 @@ # Distributed under the BSD License from pyspike import PieceWiseConstFunc -from pyspike.generic import _generic_profile_multi, _generic_distance_matrix +from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ + _generic_distance_matrix ############################################################ @@ -112,7 +113,8 @@ def isi_distance_multi(spike_trains, indices=None, interval=None): :returns: The time-averaged multivariate ISI distance :math:`D_I` :rtype: double """ - return isi_profile_multi(spike_trains, indices).avrg(interval) + return _generic_distance_multi(spike_trains, isi_distance, indices, + interval) ############################################################ diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index cc620d4..75b3b0e 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -3,7 +3,8 @@ # Distributed under the BSD License from pyspike import PieceWiseLinFunc -from pyspike.generic import _generic_profile_multi, _generic_distance_matrix +from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ + _generic_distance_matrix ############################################################ @@ -117,7 +118,8 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): :returns: The averaged multi-variate spike distance :math:`D_S`. :rtype: double """ - return spike_profile_multi(spike_trains, indices).avrg(interval) + return _generic_distance_multi(spike_trains, spike_distance, indices, + interval) ############################################################ diff --git a/test/test_distance.py b/test/test_distance.py index 19da35f..e45ac16 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -196,7 +196,7 @@ def test_spike_sync(): 0.4, decimal=16) -def check_multi_profile(profile_func, profile_func_multi): +def check_multi_profile(profile_func, profile_func_multi, dist_func_multi): # generate spike trains: t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) @@ -213,10 +213,14 @@ def check_multi_profile(profile_func, profile_func_multi): f_multi = profile_func_multi(spike_trains, [0, 1]) assert f_multi.almost_equal(f12, decimal=14) + d = dist_func_multi(spike_trains, [0, 1]) + assert_equal(f_multi.avrg(), d) f_multi1 = profile_func_multi(spike_trains, [1, 2, 3]) f_multi2 = profile_func_multi(spike_trains[1:]) assert f_multi1.almost_equal(f_multi2, decimal=14) + d = dist_func_multi(spike_trains, [1, 2, 3]) + assert_almost_equal(f_multi1.avrg(), d, decimal=14) f = copy(f12) f.add(f13) @@ -224,6 +228,8 @@ def check_multi_profile(profile_func, profile_func_multi): f.mul_scalar(1.0/3) f_multi = profile_func_multi(spike_trains, [0, 1, 2]) assert f_multi.almost_equal(f, decimal=14) + d = dist_func_multi(spike_trains, [0, 1, 2]) + assert_almost_equal(f_multi.avrg(), d, decimal=14) f.mul_scalar(3) # revert above normalization f.add(f14) @@ -235,11 +241,13 @@ def check_multi_profile(profile_func, profile_func_multi): def test_multi_isi(): - check_multi_profile(spk.isi_profile, spk.isi_profile_multi) + check_multi_profile(spk.isi_profile, spk.isi_profile_multi, + spk.isi_distance_multi) def test_multi_spike(): - check_multi_profile(spk.spike_profile, spk.spike_profile_multi) + check_multi_profile(spk.spike_profile, spk.spike_profile_multi, + spk.spike_distance_multi) def test_multi_spike_sync(): -- cgit v1.2.3 From 76a4bbcc733bdd24bb61072a341c43a14b7f83d1 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 8 May 2015 11:57:00 +0200 Subject: performance improvement for multivar spike sync dont compute the average profile in the function spike_sync_multi, but rather compute the overall average distance directly --- examples/performance.py | 29 +++++++++++++++++++++++------ pyspike/DiscreteFunc.py | 17 +++++++++-------- pyspike/spike_sync.py | 22 ++++++++++++++++++++-- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/examples/performance.py b/examples/performance.py index 469b5ab..94dae25 100644 --- a/examples/performance.py +++ b/examples/performance.py @@ -6,6 +6,7 @@ from __future__ import print_function import pyspike as spk from datetime import datetime import cProfile +import pstats M = 100 # number of spike trains r = 1.0 # rate of Poisson spike times @@ -22,21 +23,37 @@ t_end = datetime.now() runtime = (t_end-t_start).total_seconds() print("Spike generation runtime: %.3fs" % runtime) +print() print("================ ISI COMPUTATIONS ================") print(" MULTIVARIATE DISTANCE") -cProfile.run('spk.isi_distance_multi(spike_trains)') +cProfile.run('spk.isi_distance_multi(spike_trains)', 'performance.stat') +p = pstats.Stats('performance.stat') +p.strip_dirs().sort_stats('tottime').print_stats(5) + print(" MULTIVARIATE PROFILE") -cProfile.run('spk.isi_profile_multi(spike_trains)') +cProfile.run('spk.isi_profile_multi(spike_trains)', 'performance.stat') +p = pstats.Stats('performance.stat') +p.strip_dirs().sort_stats('tottime').print_stats(5) print("================ SPIKE COMPUTATIONS ================") print(" MULTIVARIATE DISTANCE") -cProfile.run('spk.spike_distance_multi(spike_trains)') +cProfile.run('spk.spike_distance_multi(spike_trains)', 'performance.stat') +p = pstats.Stats('performance.stat') +p.strip_dirs().sort_stats('tottime').print_stats(5) + print(" MULTIVARIATE PROFILE") -cProfile.run('spk.spike_profile_multi(spike_trains)') +cProfile.run('spk.spike_profile_multi(spike_trains)', 'performance.stat') +p = pstats.Stats('performance.stat') +p.strip_dirs().sort_stats('tottime').print_stats(5) print("================ SPIKE-SYNC COMPUTATIONS ================") print(" MULTIVARIATE DISTANCE") -cProfile.run('spk.spike_sync_multi(spike_trains)') +cProfile.run('spk.spike_sync_multi(spike_trains)', 'performance.stat') +p = pstats.Stats('performance.stat') +p.strip_dirs().sort_stats('tottime').print_stats(5) + print(" MULTIVARIATE PROFILE") -cProfile.run('spk.spike_sync_profile_multi(spike_trains)') +cProfile.run('spk.spike_sync_profile_multi(spike_trains)', 'performance.stat') +p = pstats.Stats('performance.stat') +p.strip_dirs().sort_stats('tottime').print_stats(5) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 33b7a81..dfe2cab 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -125,15 +125,15 @@ class DiscreteFunc(object): def integral(self, interval=None): """ Returns the integral over the given interval. For the discrete - function, this amounts to the sum over all values divided by the total - multiplicity. + function, this amounts to two values: the sum over all values and the + sum over all multiplicities. :param interval: integration interval given as a pair of floats, or a sequence of pairs in case of multiple intervals, if None the integral over the whole function is computed. :type interval: Pair, sequence of pairs, or None. - :returns: the integral - :rtype: float + :returns: the summed values and the summed multiplicity + :rtype: pair of float """ def get_indices(ival): @@ -147,7 +147,7 @@ class DiscreteFunc(object): if interval is None: # no interval given, integrate over the whole spike train # don't count the first value, which is zero by definition - return 1.0 * np.sum(self.y[1:-1]) / np.sum(self.mp[1:-1]) + return (1.0 * np.sum(self.y[1:-1]), np.sum(self.mp[1:-1])) # check if interval is as sequence assert isinstance(interval, collections.Sequence), \ @@ -156,7 +156,7 @@ class DiscreteFunc(object): if not isinstance(interval[0], collections.Sequence): # find the indices corresponding to the interval start_ind, end_ind = get_indices(interval) - return (np.sum(self.y[start_ind:end_ind]) / + return (np.sum(self.y[start_ind:end_ind]), np.sum(self.mp[start_ind:end_ind])) else: value = 0.0 @@ -166,7 +166,7 @@ class DiscreteFunc(object): start_ind, end_ind = get_indices(ival) value += np.sum(self.y[start_ind:end_ind]) multiplicity += np.sum(self.mp[start_ind:end_ind]) - return value/multiplicity + return (value, multiplicity) def avrg(self, interval=None): """ Computes the average of the interval sequence: @@ -180,7 +180,8 @@ class DiscreteFunc(object): :returns: the average a. :rtype: float """ - return self.integral(interval) + val, mp = self.integral(interval) + return val/mp def add(self, f): """ Adds another `DiscreteFunc` function to this function. diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 9d2e363..0c78228 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -3,6 +3,7 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License +import numpy as np from functools import partial from pyspike import DiscreteFunc from pyspike.generic import _generic_profile_multi, _generic_distance_matrix @@ -131,8 +132,25 @@ def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): :rtype: double """ - return spike_sync_profile_multi(spike_trains, indices, - max_tau).avrg(interval) + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + coincidence = 0.0 + mp = 0.0 + for (i, j) in pairs: + profile = spike_sync_profile(spike_trains[i], spike_trains[j]) + summed_vals = profile.integral(interval) + coincidence += summed_vals[0] + mp += summed_vals[1] + + return coincidence/mp ############################################################ -- cgit v1.2.3 From f44d78a5f0b4ab25bb443accdcb9fc1bd8ff57da Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 8 May 2015 12:08:48 +0200 Subject: improved error message --- pyspike/isi_distance.py | 4 ++-- pyspike/spike_distance.py | 4 ++-- pyspike/spike_sync.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 21df561..8b1e9bf 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -25,9 +25,9 @@ def isi_profile(spike_train1, spike_train2): """ # check whether the spike trains are defined for the same interval assert spike_train1.t_start == spike_train2.t_start, \ - "Given spike trains seems not to have auxiliary spikes!" + "Given spike trains are not defined on the same interval!" assert spike_train1.t_end == spike_train2.t_end, \ - "Given spike trains seems not to have auxiliary spikes!" + "Given spike trains are not defined on the same interval!" # load cython implementation try: diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index 75b3b0e..499ab77 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -25,9 +25,9 @@ def spike_profile(spike_train1, spike_train2): """ # check whether the spike trains are defined for the same interval assert spike_train1.t_start == spike_train2.t_start, \ - "Given spike trains seems not to have auxiliary spikes!" + "Given spike trains are not defined on the same interval!" assert spike_train1.t_end == spike_train2.t_end, \ - "Given spike trains seems not to have auxiliary spikes!" + "Given spike trains are not defined on the same interval!" # cython implementation try: diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 0c78228..7d429e4 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -30,9 +30,9 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): """ # check whether the spike trains are defined for the same interval assert spike_train1.t_start == spike_train2.t_start, \ - "Given spike trains seems not to have auxiliary spikes!" + "Given spike trains are not defined on the same interval!" assert spike_train1.t_end == spike_train2.t_end, \ - "Given spike trains seems not to have auxiliary spikes!" + "Given spike trains are not defined on the same interval!" # cython implementation try: -- cgit v1.2.3 From 619ffd7105203938a26075c79a77d63960da9922 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 8 May 2015 12:29:47 +0200 Subject: renamed cython_distance module -> cython_profiles --- pyspike/cython/cython_distance.pyx | 423 ------------------------------------- pyspike/cython/cython_profiles.pyx | 423 +++++++++++++++++++++++++++++++++++++ pyspike/isi_distance.py | 10 +- pyspike/spike_distance.py | 16 +- pyspike/spike_sync.py | 15 +- setup.py | 8 +- 6 files changed, 447 insertions(+), 448 deletions(-) delete mode 100644 pyspike/cython/cython_distance.pyx create mode 100644 pyspike/cython/cython_profiles.pyx diff --git a/pyspike/cython/cython_distance.pyx b/pyspike/cython/cython_distance.pyx deleted file mode 100644 index 6ee0181..0000000 --- a/pyspike/cython/cython_distance.pyx +++ /dev/null @@ -1,423 +0,0 @@ -#cython: boundscheck=False -#cython: wraparound=False -#cython: cdivision=True - -""" -cython_distances.pyx - -cython implementation of the isi- and spike-distance - -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 - -Distributed under the BSD License - -""" - -""" -To test whether things can be optimized: remove all yellow stuff -in the html output:: - - cython -a cython_distance.pyx - -which gives:: - - cython_distance.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[:] 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]) - index1 = -1 - else: - nu1 = s1[1]-s1[0] - index1 = 0 - - if s2[0] > t_start: - # edge correction - nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) - index2 = -1 - else: - nu2 = s2[1]-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) - 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) - 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 index2 < N2-1: - nu2 = s2[index2+1]-s2[index2] - else: - # edge correction - nu2 = fmax(t_end-s2[index2], nu2) - # 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 ~ 0.5 for Poisson spikes - # return 0.5*(isi1*isi1+isi2*isi2) - - -############################################################ -# spike_distance_cython -############################################################ -def spike_distance_cython(double[:] t1, double[:] t2, - double t_start, double t_end): - - cdef double[:] spike_events - cdef double[:] y_starts - cdef double[:] y_ends - - 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) - - 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 - 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_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_end - 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_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_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_end - 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_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 - # 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_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 - # the last event is the interval end - if spike_events[index-1] == t_end: - index -= 1 - else: - spike_events[index] = t_end - # the ending value of the last interval - 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_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] - - - -############################################################ -# coincidence_python -############################################################ -cdef inline double get_tau(double[:] spikes1, double[:] spikes2, - int i, int j, double max_tau): - cdef double m = 1E100 # some huge number - 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_cython -############################################################ -def coincidence_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 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, 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, 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 - c[0] = c[1] - c[len(c)-1] = c[len(c)-2] - mp[0] = mp[1] - mp[len(mp)-1] = mp[len(mp)-2] - - return st, c, mp diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx new file mode 100644 index 0000000..59a8d30 --- /dev/null +++ b/pyspike/cython/cython_profiles.pyx @@ -0,0 +1,423 @@ +#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, Mario Mulansky + +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]) + index1 = -1 + else: + nu1 = s1[1]-s1[0] + index1 = 0 + + if s2[0] > t_start: + # edge correction + nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) + index2 = -1 + else: + nu2 = s2[1]-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) + 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) + 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 index2 < N2-1: + nu2 = s2[index2+1]-s2[index2] + else: + # edge correction + nu2 = fmax(t_end-s2[index2], nu2) + # 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 ~ 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 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) + + 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 + 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_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_end + 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_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_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_end + 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_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 + # 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_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 + # the last event is the interval end + if spike_events[index-1] == t_end: + index -= 1 + else: + spike_events[index] = t_end + # the ending value of the last interval + 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_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 max_tau): + cdef double m = 1E100 # some huge number + 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 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, 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, 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 + c[0] = c[1] + c[len(c)-1] = c[len(c)-2] + mp[0] = mp[1] + mp[len(mp)-1] = mp[len(mp)-2] + + return st, c, mp diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 8b1e9bf..164378d 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -31,18 +31,18 @@ def isi_profile(spike_train1, spike_train2): # load cython implementation try: - from cython.cython_distance import isi_distance_cython \ - as isi_distance_impl + from cython.cython_profiles import isi_profile_cython \ + as isi_profile_impl except ImportError: print("Warning: isi_distance_cython not found. Make sure that PySpike \ is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend from cython.python_backend import isi_distance_python \ - as isi_distance_impl + as isi_profile_impl - times, values = isi_distance_impl(spike_train1.spikes, spike_train2.spikes, - spike_train1.t_start, spike_train1.t_end) + times, values = isi_profile_impl(spike_train1.spikes, spike_train2.spikes, + spike_train1.t_start, spike_train1.t_end) return PieceWiseConstFunc(times, values) diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index 499ab77..3567585 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -31,20 +31,20 @@ def spike_profile(spike_train1, spike_train2): # cython implementation try: - from cython.cython_distance import spike_distance_cython \ - as spike_distance_impl + from cython.cython_profiles import spike_profile_cython \ + as spike_profile_impl except ImportError: - print("Warning: spike_distance_cython not found. Make sure that \ + print("Warning: spike_profile_cython not found. Make sure that \ PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend from cython.python_backend import spike_distance_python \ - as spike_distance_impl + as spike_profile_impl - times, y_starts, y_ends = spike_distance_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end) + times, y_starts, y_ends = spike_profile_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end) return PieceWiseLinFunc(times, y_starts, y_ends) diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 7d429e4..107734d 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -36,24 +36,23 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): # cython implementation try: - from cython.cython_distance import coincidence_cython \ - as coincidence_impl + from cython.cython_profiles import coincidence_profile_cython \ + as coincidence_profile_impl except ImportError: print("Warning: spike_distance_cython not found. Make sure that \ PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend from cython.python_backend import coincidence_python \ - as coincidence_impl + as coincidence_profile_impl if max_tau is None: max_tau = 0.0 - times, coincidences, multiplicity = coincidence_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end, - max_tau) + times, coincidences, multiplicity \ + = coincidence_profile_impl(spike_train1.spikes, spike_train2.spikes, + spike_train1.t_start, spike_train1.t_end, + max_tau) return DiscreteFunc(times, coincidences, multiplicity) diff --git a/setup.py b/setup.py index 289d521..d687240 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ else: use_cython = True if os.path.isfile("pyspike/cython/cython_add.c") and \ - os.path.isfile("pyspike/cython/cython_distance.c"): + os.path.isfile("pyspike/cython/cython_profiles.c"): use_c = True else: use_c = False @@ -33,13 +33,13 @@ ext_modules = [] if use_cython: # Cython is available, compile .pyx -> .c ext_modules += [ Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.pyx"]), - Extension("pyspike.cython.cython_distance", ["pyspike/cython/cython_distance.pyx"]), + Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.pyx"]), ] cmdclass.update({'build_ext': build_ext}) elif use_c: # c files are there, compile to binaries ext_modules += [ Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.c"]), - Extension("pyspike.cython.cython_distance", ["pyspike/cython/cython_distance.c"]), + Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.c"]), ] # neither cython nor c files available -> automatic fall-back to python backend @@ -78,7 +78,7 @@ train similarity', 'Programming Language :: Python :: 2.7', ], package_data={ - 'pyspike': ['cython/cython_add.c', 'cython/cython_distance.c'], + 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c'], 'test': ['Spike_testdata.txt'] } ) -- cgit v1.2.3 From f688dc2e8616f914040746de845646abb158125d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 8 May 2015 18:06:59 +0200 Subject: introduce backend for distance function isi- and spike distances over complete intervals are now computed without obtaining the profile first. This gives more than x2 performance improvements. --- examples/performance.py | 8 +- pyspike/cython/cython_distances.pyx | 328 ++++++++++++++++++++++++++++++++++++ pyspike/cython/cython_profiles.pyx | 2 +- pyspike/isi_distance.py | 16 +- pyspike/spike_distance.py | 17 +- setup.py | 5 +- 6 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 pyspike/cython/cython_distances.pyx diff --git a/examples/performance.py b/examples/performance.py index 94dae25..1c31e8f 100644 --- a/examples/performance.py +++ b/examples/performance.py @@ -1,4 +1,10 @@ -""" Compute distances of large sets of spike trains for performance tests +""" +Compute distances of large sets of spike trains for performance tests + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License + """ from __future__ import print_function diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx new file mode 100644 index 0000000..65c2872 --- /dev/null +++ b/pyspike/cython/cython_distances.pyx @@ -0,0 +1,328 @@ +#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 + +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 + if s1[0] > t_start: + # edge correction + nu1 = fmax(s1[0]-t_start, s1[1]-s1[0]) + index1 = -1 + else: + nu1 = s1[1]-s1[0] + index1 = 0 + + if s2[0] > t_start: + # edge correction + nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) + index2 = -1 + else: + nu2 = s2[1]-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 + nu1 = fmax(t_end-s1[index1], nu1) + 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 + nu2 = fmax(t_end-s2[index2], nu2) + 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 + nu1 = fmax(t_end-s1[index1], nu1) + if index2 < N2-1: + nu2 = s2[index2+1]-s2[index2] + else: + # edge correction + nu2 = fmax(t_end-s2[index2], nu2) + # 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 ~ 0.5 for Poisson spikes + # return 0.5*(isi1*isi1+isi2*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 + + 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) + 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) + + 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) + 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) + + 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) + 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) + 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) diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx index 59a8d30..3690091 100644 --- a/pyspike/cython/cython_profiles.pyx +++ b/pyspike/cython/cython_profiles.pyx @@ -10,7 +10,7 @@ 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, Mario Mulansky +Copyright 2014-2015, Mario Mulansky Distributed under the BSD License diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 164378d..2a1ed3a 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -66,7 +66,21 @@ def isi_distance(spike_train1, spike_train2, interval=None): :returns: The isi-distance :math:`D_I`. :rtype: double """ - return isi_profile(spike_train1, spike_train2).avrg(interval) + + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from cython.cython_distances import isi_distance_cython \ + as isi_distance_impl + return isi_distance_impl(spike_train1.spikes, spike_train2.spikes, + spike_train1.t_start, spike_train1.t_end) + except ImportError: + # Cython backend not available: fall back to profile averaging + return isi_profile(spike_train1, spike_train2).avrg(interval) + else: + # some specific interval is provided: use profile + return isi_profile(spike_train1, spike_train2).avrg(interval) ############################################################ diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index 3567585..d727fa2 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -68,7 +68,22 @@ def spike_distance(spike_train1, spike_train2, interval=None): :rtype: double """ - return spike_profile(spike_train1, spike_train2).avrg(interval) + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from cython.cython_distances import spike_distance_cython \ + as spike_distance_impl + return spike_distance_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end) + except ImportError: + # Cython backend not available: fall back to average profile + return spike_profile(spike_train1, spike_train2).avrg(interval) + else: + # some specific interval is provided: compute the whole profile + return spike_profile(spike_train1, spike_train2).avrg(interval) ############################################################ diff --git a/setup.py b/setup.py index d687240..7902066 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,8 @@ else: use_cython = True if os.path.isfile("pyspike/cython/cython_add.c") and \ - os.path.isfile("pyspike/cython/cython_profiles.c"): + os.path.isfile("pyspike/cython/cython_profiles.c") and \ + os.path.isfile("pyspike/cython/cython_distances.c"): use_c = True else: use_c = False @@ -34,12 +35,14 @@ if use_cython: # Cython is available, compile .pyx -> .c ext_modules += [ Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.pyx"]), Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.pyx"]), + Extension("pyspike.cython.cython_distances", ["pyspike/cython/cython_distances.pyx"]), ] cmdclass.update({'build_ext': build_ext}) elif use_c: # c files are there, compile to binaries ext_modules += [ Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.c"]), Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.c"]), + Extension("pyspike.cython.cython_distances", ["pyspike/cython/cython_distances.c"]), ] # neither cython nor c files available -> automatic fall-back to python backend -- cgit v1.2.3 From ad7961c9f73d77b87ee23349169618d128418a51 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 12:06:19 +0200 Subject: performance improvement for spike sync Additional cython implementation for overall spike sync values. It is not necessary to compute the profile anymore if only the spike sync value is required. 3x performance gain. --- pyspike/cython/cython_distances.pyx | 64 +++++++++++++++++++++++++++++++++++++ pyspike/spike_sync.py | 46 ++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index 65c2872..bf90638 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -326,3 +326,67 @@ def spike_distance_cython(double[:] t1, double[:] t2, # 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 max_tau): + cdef double m = 1E100 # some huge number + 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 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, 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, 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/spike_sync.py b/pyspike/spike_sync.py index 107734d..40d98d2 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -57,6 +57,40 @@ Falling back to slow python backend.") return DiscreteFunc(times, coincidences, multiplicity) +############################################################ +# _spike_sync_values +############################################################ +def _spike_sync_values(spike_train1, spike_train2, interval, max_tau): + """" Internal function. Computes the summed coincidences and multiplicity + for spike synchronization of the two given spike trains. + + Do not call this function directly, use `spike_sync` or `spike_sync_multi` + instead. + """ + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from cython.cython_distances import coincidence_value_cython \ + as coincidence_value_impl + if max_tau is None: + max_tau = 0.0 + c, mp = coincidence_value_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + return c, mp + except ImportError: + # Cython backend not available: fall back to profile averaging + return spike_sync_profile(spike_train1, spike_train2, + max_tau).integral(interval) + else: + # some specific interval is provided: use profile + return spike_sync_profile(spike_train1, spike_train2, + max_tau).integral(interval) + + ############################################################ # spike_sync ############################################################ @@ -80,8 +114,8 @@ def spike_sync(spike_train1, spike_train2, interval=None, max_tau=None): :rtype: `double` """ - return spike_sync_profile(spike_train1, spike_train2, - max_tau).avrg(interval) + c, mp = _spike_sync_values(spike_train1, spike_train2, interval, max_tau) + return 1.0*c/mp ############################################################ @@ -144,10 +178,10 @@ def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): coincidence = 0.0 mp = 0.0 for (i, j) in pairs: - profile = spike_sync_profile(spike_trains[i], spike_trains[j]) - summed_vals = profile.integral(interval) - coincidence += summed_vals[0] - mp += summed_vals[1] + c, m = _spike_sync_values(spike_trains[i], spike_trains[j], + interval, max_tau) + coincidence += c + mp += m return coincidence/mp -- cgit v1.2.3 From f982bb4870de078b77f0c9fa3230128f8dfe640c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 12:10:30 +0200 Subject: +tutorial, reference, contributors order --- Contributors.txt | 6 +- Readme.rst | 8 +-- doc/tutorial.rst | 213 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 220 insertions(+), 7 deletions(-) create mode 100644 doc/tutorial.rst diff --git a/Contributors.txt b/Contributors.txt index 0d4afee..83563c7 100644 --- a/Contributors.txt +++ b/Contributors.txt @@ -3,10 +3,10 @@ Python/C Programming: Scientific Methods: - Thomas Kreuz -- Nebojsa D. Bozanic -- Mario Mulansky -- Conor Houghton - Daniel Chicharro +- Conor Houghton +- Nebojsa Bozanic +- Mario Mulansky The work on PySpike was supported by the European Comission through the Marie diff --git a/Readme.rst b/Readme.rst index b8858f5..daacbea 100644 --- a/Readme.rst +++ b/Readme.rst @@ -17,7 +17,7 @@ All source codes are available on `Github `_ -.. [#] Kreuz T, Mulansky M and Bozanic N, *SPIKY: A graphical user interface for monitoring spike train synchrony*, tbp (2015) +.. [#] Kreuz T, Mulansky M and Bozanic N, *SPIKY: A graphical user interface for monitoring spike train synchrony*, J Neurophysiol, in press (2015) Important Changelog ----------------------------- @@ -114,10 +114,10 @@ Curie Initial Training Network* `Neural Engineering Transformative Technologies **Scientific Methods:** - Thomas Kreuz - - Nebojsa D. Bozanic - - Mario Mulansky - - Conor Houghton - Daniel Chicharro + - Conor Houghton + - Nebojsa Bozanic + - Mario Mulansky .. _ISI: http://www.scholarpedia.org/article/Measures_of_spike_train_synchrony#ISI-distance .. _SPIKE: http://www.scholarpedia.org/article/SPIKE-distance diff --git a/doc/tutorial.rst b/doc/tutorial.rst new file mode 100644 index 0000000..f7fc20b --- /dev/null +++ b/doc/tutorial.rst @@ -0,0 +1,213 @@ +Spike trains +------------ + +In PySpike, spike trains are represented by :class:`.SpikeTrain` objects. +A :class:`.SpikeTrain` object consists of the spike times given as `numpy` arrays as well as the edges of the spike train as :code:`[t_start, t_end]`. +The following code creates such a spike train with some arbitrary spike times: + +.. code:: python + + import numpy as np + from pyspike import SpikeTrain + + spike_train = SpikeTrain(np.array([0.1, 0.3, 0.45, 0.6, 0.9], [0.0, 1.0])) + +Loading from text files +....................... + +Typically, spike train data is loaded into PySpike from data files. +The most straight-forward data files are text files where each line represents one spike train given as an sequence of spike times. +An exemplary file with several spike trains is `PySpike_testdata.txt `_. +To quickly obtain spike trains from such files, PySpike provides the function :func:`.load_spike_trains_from_txt`. + +.. code:: python + + import numpy as np + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("SPIKY_testdata.txt", + edges=(0, 4000)) + +This function expects the name of the data file as first parameter. +Furthermore, the time interval of the spike train measurement (edges of the spike trains) should be provided as a pair of start- and end-time values. +Furthermore, the spike trains are sorted via :code:`np.sort` (disable this feature by providing :code:`is_sorted=True` as a parameter to the load function). +As result, :func:`.load_spike_trains_from_txt` returns a *list of arrays* containing the spike trains in the text file. + + +Computing bivariate distances profiles +--------------------------------------- + +**Important note:** + +------------------------------ + + Spike trains are expected to be *sorted*! + For performance reasons, the PySpike distance functions do not check if the spike trains provided are indeed sorted. + Make sure that all your spike trains are sorted, which is ensured if you use the :func:`.load_spike_trains_from_txt` function with the parameter `is_sorted=False` (default). + If in doubt, use :meth:`.SpikeTrain.sort()` to ensure a correctly sorted spike train. + + If you need to copy a spike train, use the :meth:`.SpikeTrain.copy()` method. + Simple assignment `t2 = t1` does not create a copy of the spike train data, but a reference as `numpy.array` is used for storing the data. + +------------------------------ + +ISI-distance +............ + +The following code loads some exemplary spike trains, computes the dissimilarity profile of the ISI-distance of the first two :class:`.SpikeTrain` s, and plots it with matplotlib: + +.. code:: python + + import matplotlib.pyplot as plt + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + edges=(0, 4000)) + isi_profile = spk.isi_profile(spike_trains[0], spike_trains[1]) + x, y = isi_profile.get_plottable_data() + plt.plot(x, y, '--k') + print("ISI distance: %.8f" % isi_profile.avrg()) + plt.show() + +The ISI-profile is a piece-wise constant function, and hence the function :func:`.isi_profile` returns an instance of the :class:`.PieceWiseConstFunc` class. +As shown above, this class allows you to obtain arrays that can be used to plot the function with :code:`plt.plt`, but also to compute the time average, which amounts to the final scalar ISI-distance. +By default, the time average is computed for the whole :class:`.PieceWiseConstFunc` function. +However, it is also possible to obtain the average of a specific interval by providing a pair of floats defining the start and end of the interval. +For the above example, the following code computes the ISI-distances obtained from averaging the ISI-profile over four different intervals: + +.. code:: python + + isi1 = isi_profile.avrg(interval=(0, 1000)) + isi2 = isi_profile.avrg(interval=(1000, 2000)) + isi3 = isi_profile.avrg(interval=[(0, 1000), (2000, 3000)]) + isi4 = isi_profile.avrg(interval=[(1000, 2000), (3000, 4000)]) + +Note, how also multiple intervals can be supplied by giving a list of tuples. + +If you are only interested in the scalar ISI-distance and not the profile, you can simply use: + +.. code:: python + + isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1], interval) + +where :code:`interval` is optional, as above, and if omitted the ISI-distance is computed for the complete spike trains. + + +SPIKE-distance +.............. + +To compute for the spike distance profile you use the function :func:`.spike_profile` instead of :code:`isi_profile` above. +But the general approach is very similar: + +.. code:: python + + import matplotlib.pyplot as plt + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + edges=(0, 4000)) + spike_profile = spk.spike_profile(spike_trains[0], spike_trains[1]) + x, y = spike_profile.get_plottable_data() + plt.plot(x, y, '--k') + print("SPIKE distance: %.8f" % spike_profile.avrg()) + plt.show() + +This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. +In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and is therefore represented by a :class:`.PieceWiseLinFunc` object. +Just like the :class:`.PieceWiseConstFunc` for the ISI-profile, the :class:`.PieceWiseLinFunc` provides a :meth:`.PieceWiseLinFunc.get_plottable_data` member function that returns arrays that can be used directly to plot the function. +Furthermore, the :meth:`.PieceWiseLinFunc.avrg` member function returns the average of the profile defined as the overall SPIKE distance. +As above, you can provide an interval as a pair of floats as well as a sequence of such pairs to :code:`avrg` to specify the averaging interval if required. + +Again, you can use + +.. code:: python + + spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval) + +to compute the SPIKE distance directly, if you are not interested in the profile at all. +The parameter :code:`interval` is optional and if neglected the whole spike train is used. + + +SPIKE synchronization +..................... + +**Important note:** + +------------------------------ + + SPIKE-Synchronization measures *similarity*. + That means, a value of zero indicates absence of synchrony, while a value of one denotes the presence of synchrony. + This is exactly opposite to the other two measures: ISI- and SPIKE-distance. + +---------------------- + + +SPIKE synchronization is another approach to measure spike synchrony. +In contrast to the SPIKE- and ISI-distance, it measures similarity instead of dissimilarity, i.e. higher values represent larger synchrony. +Another difference is that the SPIKE synchronization profile is only defined exactly at the spike times, not for the whole interval of the spike trains. +Therefore, it is represented by a :class:`.DiscreteFunction`. + +To compute for the spike synchronization profile, PySpike provides the function :func:`.spike_sync_profile`. +The general handling of the profile, however, is similar to the other profiles above: + +.. code:: python + + import matplotlib.pyplot as plt + import pyspike as spk + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + edges=(0, 4000)) + spike_profile = spk.spike_sync_profile(spike_trains[0], spike_trains[1]) + x, y = spike_profile.get_plottable_data() + +For the direct computation of the overall spike synchronization value within some interval, the :func:`.spike_sync` function can be used: + +.. code:: python + + spike_sync = spk.spike_sync(spike_trains[0], spike_trains[1], interval) + + +Computing multivariate profiles and distances +---------------------------------------------- + +To compute the multivariate ISI-profile, SPIKE-profile or SPIKE-Synchronization profile f a set of spike trains, PySpike provides multi-variate version of the profile function. +The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains using the :func:`.isi_profile_multi`, :func:`.spike_profile_multi`, :func:`.spike_sync_profile_multi` functions: + +.. code:: python + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + edges=(0, 4000)) + avrg_isi_profile = spk.isi_profile_multi(spike_trains) + avrg_spike_profile = spk.spike_profile_multi(spike_trains) + avrg_spike_sync_profile = spk.spike_sync_profile_multi(spike_trains) + +All functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. +As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :func:`.isi_distance_multi`, :func:`.spike_distance_multi` and :func:`.spike_sync_multi`, that return the scalar overall multivariate ISI- and SPIKE-distance as well as the SPIKE-Synchronization value. +Those functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. + +Another option to characterize large sets of spike trains are distance matrices. +Each entry in the distance matrix represents a bivariate distance (similarity for SPIKE-Synchronization) of two spike trains. +The distance matrix is symmetric and has zero values (ones) at the diagonal and is computed with the functions :func:`.isi_distance_matrix`, :func:`.spike_distance_matrix` and :func:`.spike_sync_matrix`. +The following example computes and plots the ISI- and SPIKE-distance matrix as well as the SPIKE-Synchronization-matrix, with different intervals. + +.. code:: python + + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", 4000) + + plt.figure() + isi_distance = spk.isi_distance_matrix(spike_trains) + plt.imshow(isi_distance, interpolation='none') + plt.title("ISI-distance") + + plt.figure() + spike_distance = spk.spike_distance_matrix(spike_trains, interval=(0,1000)) + plt.imshow(spike_distance, interpolation='none') + plt.title("SPIKE-distance") + + plt.figure() + spike_sync = spk.spike_sync_matrix(spike_trains, interval=(2000,4000)) + plt.imshow(spike_sync, interpolation='none') + plt.title("SPIKE-Sync") + + plt.show() + -- cgit v1.2.3 From 97a1a5ace20b641fef78ed7cb2338ece04e07cac Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 12:25:27 +0200 Subject: updated changelog --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index 209b138..beb2ce5 100644 --- a/Changelog +++ b/Changelog @@ -1,6 +1,9 @@ PySpike v0.2.1: * addition of __version__ attribute * restructured docs, Readme now only contains basic examples + * performance improvements by introducing specific distance functions + * performance improvements for multivariate profiles by using recursive + approach PySpike v0.2: -- cgit v1.2.3 From 017893e782d1747e1f031131077a40a48f882e86 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 14:32:48 +0200 Subject: treatment of empty spike trains in isi functions --- pyspike/SpikeTrain.py | 10 ++++++++ pyspike/isi_distance.py | 7 ++++-- test/test_empty.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 test/test_empty.py diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index a02b7ab..9127b60 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -48,3 +48,13 @@ class SpikeTrain(object): """ return SpikeTrain(self.spikes.copy(), [self.t_start, self.t_end]) + + def get_spikes_non_empty(self): + """Returns the spikes of this spike train with auxiliary spikes in case + of empty spike trains. + """ + if len(self.spikes) < 2: + return np.unique(np.insert([self.t_start, self.t_end], 1, + self.spikes)) + else: + return self.spikes diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 2a1ed3a..5ea555d 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -41,7 +41,8 @@ Falling back to slow python backend.") from cython.python_backend import isi_distance_python \ as isi_profile_impl - times, values = isi_profile_impl(spike_train1.spikes, spike_train2.spikes, + times, values = isi_profile_impl(spike_train1.get_spikes_non_empty(), + spike_train2.get_spikes_non_empty(), spike_train1.t_start, spike_train1.t_end) return PieceWiseConstFunc(times, values) @@ -73,7 +74,9 @@ def isi_distance(spike_train1, spike_train2, interval=None): try: from cython.cython_distances import isi_distance_cython \ as isi_distance_impl - return isi_distance_impl(spike_train1.spikes, spike_train2.spikes, + + return isi_distance_impl(spike_train1.get_spikes_non_empty(), + spike_train2.get_spikes_non_empty(), spike_train1.t_start, spike_train1.t_end) except ImportError: # Cython backend not available: fall back to profile averaging diff --git a/test/test_empty.py b/test/test_empty.py new file mode 100644 index 0000000..22982c7 --- /dev/null +++ b/test/test_empty.py @@ -0,0 +1,62 @@ +""" test_empty.py + +Tests the distance measure for empty spike trains + +Copyright 2014, Mario Mulansky + +Distributed under the BSD License + +""" + +from __future__ import print_function +import numpy as np +from copy import copy +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_equal, assert_array_almost_equal + +import pyspike as spk +from pyspike import SpikeTrain + + +def test_get_non_empty(): + st = SpikeTrain([], edges=(0.0, 1.0)) + spikes = st.get_spikes_non_empty() + assert_array_equal(spikes, [0.0, 1.0]) + + st = SpikeTrain([0.5, ], edges=(0.0, 1.0)) + spikes = st.get_spikes_non_empty() + assert_array_equal(spikes, [0.0, 0.5, 1.0]) + + +def test_isi_empty(): + st1 = SpikeTrain([], edges=(0.0, 1.0)) + st2 = SpikeTrain([], edges=(0.0, 1.0)) + d = spk.isi_distance(st1, st2) + assert_equal(d, 0.0) + prof = spk.isi_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_equal(prof.x, [0.0, 1.0]) + assert_array_equal(prof.y, [0.0, ]) + + st1 = SpikeTrain([], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) + d = spk.isi_distance(st1, st2) + assert_equal(d, 0.6*0.4+0.4*0.6) + prof = spk.isi_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_equal(prof.x, [0.0, 0.4, 1.0]) + assert_array_equal(prof.y, [0.6, 0.4]) + + st1 = SpikeTrain([0.6, ], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) + d = spk.isi_distance(st1, st2) + assert_almost_equal(d, 0.2/0.6*0.4 + 0.0 + 0.2/0.6*0.4, decimal=15) + prof = spk.isi_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_almost_equal(prof.x, [0.0, 0.4, 0.6, 1.0], decimal=15) + assert_array_almost_equal(prof.y, [0.2/0.6, 0.0, 0.2/0.6], decimal=15) + + +if __name__ == "__main__": + test_get_non_empty() + test_isi_empty() -- cgit v1.2.3 From b6521869cad89eae119391557bfa57e818dc9894 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 15:54:19 +0200 Subject: treatment of empty spike trains in spike distance --- pyspike/spike_distance.py | 13 +++++++------ test/test_empty.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index d727fa2..ac2d260 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -41,10 +41,11 @@ Falling back to slow python backend.") from cython.python_backend import spike_distance_python \ as spike_profile_impl - times, y_starts, y_ends = spike_profile_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end) + times, y_starts, y_ends = spike_profile_impl( + spike_train1.get_spikes_non_empty(), + spike_train2.get_spikes_non_empty(), + spike_train1.t_start, spike_train1.t_end) + return PieceWiseLinFunc(times, y_starts, y_ends) @@ -74,8 +75,8 @@ def spike_distance(spike_train1, spike_train2, interval=None): try: from cython.cython_distances import spike_distance_cython \ as spike_distance_impl - return spike_distance_impl(spike_train1.spikes, - spike_train2.spikes, + return spike_distance_impl(spike_train1.get_spikes_non_empty(), + spike_train2.get_spikes_non_empty(), spike_train1.t_start, spike_train1.t_end) except ImportError: diff --git a/test/test_empty.py b/test/test_empty.py index 22982c7..e08bd1a 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -10,7 +10,6 @@ Distributed under the BSD License from __future__ import print_function import numpy as np -from copy import copy from numpy.testing import assert_equal, assert_almost_equal, \ assert_array_equal, assert_array_almost_equal @@ -57,6 +56,53 @@ def test_isi_empty(): assert_array_almost_equal(prof.y, [0.2/0.6, 0.0, 0.2/0.6], decimal=15) +def test_spike_empty(): + st1 = SpikeTrain([], edges=(0.0, 1.0)) + st2 = SpikeTrain([], edges=(0.0, 1.0)) + d = spk.spike_distance(st1, st2) + assert_equal(d, 0.0) + prof = spk.spike_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_equal(prof.x, [0.0, 1.0]) + assert_array_equal(prof.y1, [0.0, ]) + assert_array_equal(prof.y2, [0.0, ]) + + st1 = SpikeTrain([], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) + d = spk.spike_distance(st1, st2) + assert_almost_equal(d, 0.4*0.4*1.0/(0.4+1.0)**2 + 0.6*0.4*1.0/(0.6+1.0)**2, + decimal=15) + prof = spk.spike_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_equal(prof.x, [0.0, 0.4, 1.0]) + assert_array_almost_equal(prof.y1, [0.0, 2*0.4*1.0/(0.6+1.0)**2], + decimal=15) + assert_array_almost_equal(prof.y2, [2*0.4*1.0/(0.4+1.0)**2, 0.0], + decimal=15) + + st1 = SpikeTrain([0.6, ], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) + d = spk.spike_distance(st1, st2) + s1 = np.array([0.0, 0.4*0.2/0.6, 0.2, 0.0]) + s2 = np.array([0.0, 0.2, 0.2*0.4/0.6, 0.0]) + isi1 = np.array([0.6, 0.6, 0.4]) + isi2 = np.array([0.4, 0.6, 0.6]) + expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) + expected_y2 = (s1[1:]*isi2+s2[1:]*isi1) / (0.5*(isi1+isi2)**2) + expected_times = np.array([0.0, 0.4, 0.6, 1.0]) + expected_spike_val = sum((expected_times[1:] - expected_times[:-1]) * + (expected_y1+expected_y2)/2) + expected_spike_val /= (expected_times[-1]-expected_times[0]) + + assert_almost_equal(d, expected_spike_val, decimal=15) + prof = spk.spike_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_almost_equal(prof.x, [0.0, 0.4, 0.6, 1.0], decimal=15) + assert_array_almost_equal(prof.y1, expected_y1, decimal=15) + assert_array_almost_equal(prof.y2, expected_y2, decimal=15) + + if __name__ == "__main__": test_get_non_empty() test_isi_empty() + test_spike_empty() -- cgit v1.2.3 From 3b10b416940ae674df6d9dff8cdddc31085d8cf5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 17:03:29 +0200 Subject: updated copyright year --- test/test_empty.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_empty.py b/test/test_empty.py index e08bd1a..42c3716 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -2,7 +2,7 @@ Tests the distance measure for empty spike trains -Copyright 2014, Mario Mulansky +Copyright 2015, Mario Mulansky Distributed under the BSD License -- cgit v1.2.3 From bec2529367f1bdd5dac6d6fbaec560a30feec3c7 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 17:41:08 +0200 Subject: treatment of empty spike trains in spike sync --- pyspike/DiscreteFunc.py | 4 ++++ pyspike/cython/cython_distances.pyx | 13 +++++++++---- pyspike/cython/cython_profiles.pyx | 9 +++++---- pyspike/cython/python_backend.py | 2 +- test/test_empty.py | 39 +++++++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index dfe2cab..6ade87e 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -136,6 +136,10 @@ class DiscreteFunc(object): :rtype: pair of float """ + if len(self.y) <= 2: + # no actual values in the profile, return spike sync of 0 + return 0.0, 1.0 + def get_indices(ival): """ Retuns the indeces surrounding the given interval""" start_ind = np.searchsorted(self.x, ival[0], side='right') diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index bf90638..16780f2 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -333,8 +333,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, # get_tau ############################################################ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, - int i, int j, double max_tau): - cdef double m = 1E100 # some huge number + 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: @@ -363,12 +363,13 @@ def coincidence_value_cython(double[:] spikes1, double[:] spikes2, 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, max_tau) + 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 @@ -376,7 +377,7 @@ def coincidence_value_cython(double[:] spikes1, double[:] spikes2, 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, max_tau) + 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 @@ -389,4 +390,8 @@ def coincidence_value_cython(double[:] spikes1, double[:] spikes2, mp += 2 coinc += 2 + if coinc == 0 and mp == 0: + # empty spike trains -> set mp to one to avoid 0/0 + mp = 1 + return coinc, mp diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx index 3690091..d937a02 100644 --- a/pyspike/cython/cython_profiles.pyx +++ b/pyspike/cython/cython_profiles.pyx @@ -345,8 +345,8 @@ def spike_profile_cython(double[:] t1, double[:] t2, # get_tau ############################################################ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, - int i, int j, double max_tau): - cdef double m = 1E100 # some huge number + 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: @@ -377,12 +377,13 @@ def coincidence_profile_cython(double[:] spikes1, double[:] spikes2, 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, max_tau) + 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 @@ -392,7 +393,7 @@ def coincidence_profile_cython(double[:] spikes1, double[:] spikes2, 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) + 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 diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 1fd8c42..830dc69 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -340,7 +340,7 @@ def cumulative_sync_python(spikes1, spikes2): def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau): def get_tau(spikes1, spikes2, i, j, max_tau): - m = 1E100 # some huge number + 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: diff --git a/test/test_empty.py b/test/test_empty.py index 42c3716..b31d8a4 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -102,7 +102,46 @@ def test_spike_empty(): assert_array_almost_equal(prof.y2, expected_y2, decimal=15) +def test_spike_sync_empty(): + st1 = SpikeTrain([], edges=(0.0, 1.0)) + st2 = SpikeTrain([], edges=(0.0, 1.0)) + d = spk.spike_sync(st1, st2) + assert_equal(d, 0.0) + prof = spk.spike_sync_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_equal(prof.x, [0.0, 1.0]) + assert_array_equal(prof.y, [0.0, 0.0]) + + st1 = SpikeTrain([], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) + d = spk.spike_sync(st1, st2) + assert_equal(d, 0.0) + prof = spk.spike_sync_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_equal(prof.x, [0.0, 0.4, 1.0]) + assert_array_equal(prof.y, [0.0, 0.0, 0.0]) + + st1 = SpikeTrain([0.6, ], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) + d = spk.spike_sync(st1, st2) + assert_almost_equal(d, 1.0, decimal=15) + prof = spk.spike_sync_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_almost_equal(prof.x, [0.0, 0.4, 0.6, 1.0], decimal=15) + assert_array_almost_equal(prof.y, [1.0, 1.0, 1.0, 1.0], decimal=15) + + st1 = SpikeTrain([0.2, ], edges=(0.0, 1.0)) + st2 = SpikeTrain([0.8, ], edges=(0.0, 1.0)) + d = spk.spike_sync(st1, st2) + assert_almost_equal(d, 0.0, decimal=15) + prof = spk.spike_sync_profile(st1, st2) + assert_equal(d, prof.avrg()) + assert_array_almost_equal(prof.x, [0.0, 0.2, 0.8, 1.0], decimal=15) + assert_array_almost_equal(prof.y, [0.0, 0.0, 0.0, 0.0], decimal=15) + + if __name__ == "__main__": test_get_non_empty() test_isi_empty() test_spike_empty() + test_spike_sync_empty() -- cgit v1.2.3 From a35402c208bd0ad31e5e60b6ddc55a3470e7bdde Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 11 May 2015 17:54:02 +0200 Subject: bugfix: spike_sync=1 for empty spike trains --- pyspike/DiscreteFunc.py | 4 ++-- pyspike/cython/cython_distances.pyx | 3 ++- pyspike/cython/cython_profiles.pyx | 12 ++++++++---- pyspike/cython/python_backend.py | 12 ++++++++---- test/test_empty.py | 4 ++-- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 6ade87e..17153ee 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -137,8 +137,8 @@ class DiscreteFunc(object): """ if len(self.y) <= 2: - # no actual values in the profile, return spike sync of 0 - return 0.0, 1.0 + # no actual values in the profile, return spike sync of 1 + return 1.0, 1.0 def get_indices(ival): """ Retuns the indeces surrounding the given interval""" diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index 16780f2..c4f2349 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -391,7 +391,8 @@ def coincidence_value_cython(double[:] spikes1, double[:] spikes2, coinc += 2 if coinc == 0 and mp == 0: - # empty spike trains -> set mp to one to avoid 0/0 + # empty spike trains -> spike sync = 1 by definition + coinc = 1 mp = 1 return coinc, mp diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx index d937a02..f9893eb 100644 --- a/pyspike/cython/cython_profiles.pyx +++ b/pyspike/cython/cython_profiles.pyx @@ -416,9 +416,13 @@ def coincidence_profile_cython(double[:] spikes1, double[:] spikes2, st[0] = t_start st[len(st)-1] = t_end - c[0] = c[1] - c[len(c)-1] = c[len(c)-2] - mp[0] = mp[1] - mp[len(mp)-1] = mp[len(mp)-2] + 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 diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 830dc69..69a420f 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -399,10 +399,14 @@ def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau): st[0] = t_start st[len(st)-1] = t_end - c[0] = c[1] - c[len(c)-1] = c[len(c)-2] - mp[0] = mp[1] - mp[len(mp)-1] = mp[len(mp)-2] + 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 diff --git a/test/test_empty.py b/test/test_empty.py index b31d8a4..48be25d 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -106,11 +106,11 @@ def test_spike_sync_empty(): st1 = SpikeTrain([], edges=(0.0, 1.0)) st2 = SpikeTrain([], edges=(0.0, 1.0)) d = spk.spike_sync(st1, st2) - assert_equal(d, 0.0) + assert_equal(d, 1.0) prof = spk.spike_sync_profile(st1, st2) assert_equal(d, prof.avrg()) assert_array_equal(prof.x, [0.0, 1.0]) - assert_array_equal(prof.y, [0.0, 0.0]) + assert_array_equal(prof.y, [1.0, 1.0]) st1 = SpikeTrain([], edges=(0.0, 1.0)) st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) -- cgit v1.2.3 From 6f418a5a837939b132967bcdb3ff0ede6d899bd2 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 12 May 2015 18:45:03 +0200 Subject: +functions to obtain values of the pwc/pwl profile Added __call__ operators to PieceWiseConst and PieceWiseLin class for obtaining function values at certain points in time. --- pyspike/PieceWiseConstFunc.py | 22 ++++++++++++++++++++++ pyspike/PieceWiseLinFunc.py | 30 ++++++++++++++++++++++++++++++ test/test_function.py | 32 +++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index 41998ef..cf64e58 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -26,6 +26,28 @@ class PieceWiseConstFunc(object): self.x = np.array(x) self.y = np.array(y) + def __call__(self, t): + """ Returns the function value for the given time t. If t is a list of + times, the corresponding list of values is returned. + + :param: time t, or list of times + :returns: function value(s) at that time(s). + """ + assert np.all(t >= self.x[0]) and np.all(t <= self.x[-1]), \ + "Invalid time: " + str(t) + + ind = np.searchsorted(self.x, t, side='right') + # correct the cases t == x[0], t == x[-1] + try: + ind[ind == 0] = 1 + ind[ind == len(self.x)] = len(self.x)-1 + except TypeError: + if ind == 0: + ind = 1 + if ind == len(self.x): + ind = len(self.x)-1 + return self.y[ind-1] + def copy(self): """ Returns a copy of itself diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index f2442be..b9787eb 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -29,6 +29,36 @@ class PieceWiseLinFunc: self.y1 = np.array(y1) self.y2 = np.array(y2) + def __call__(self, t): + """ Returns the function value for the given time t. If t is a list of + times, the corresponding list of values is returned. + + :param: time t, or list of times + :returns: function value(s) at that time(s). + """ + def intermediate_value(x0, x1, y0, y1, x): + """ computes the intermediate value of a linear function """ + return y0 + (y1-y0)*(x-x0)/(x1-x0) + + assert np.all(t >= self.x[0]) and np.all(t <= self.x[-1]), \ + "Invalid time: " + str(t) + + ind = np.searchsorted(self.x, t, side='right') + # correct the cases t == x[0], t == x[-1] + try: + ind[ind == 0] = 1 + ind[ind == len(self.x)] = len(self.x)-1 + except TypeError: + if ind == 0: + ind = 1 + if ind == len(self.x): + ind = len(self.x)-1 + return intermediate_value(self.x[ind-1], + self.x[ind], + self.y1[ind-1], + self.y2[ind-1], + t) + def copy(self): """ Returns a copy of itself diff --git a/test/test_function.py b/test/test_function.py index d81b03a..c56a295 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -10,7 +10,8 @@ Distributed under the BSD License from __future__ import print_function import numpy as np from copy import copy -from numpy.testing import assert_almost_equal, assert_array_almost_equal +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_equal, assert_array_almost_equal import pyspike as spk @@ -20,6 +21,17 @@ def test_pwc(): x = [0.0, 1.0, 2.0, 2.5, 4.0] y = [1.0, -0.5, 1.5, 0.75] f = spk.PieceWiseConstFunc(x, y) + + # function values + assert_equal(f(0.0), 1.0) + assert_equal(f(0.5), 1.0) + assert_equal(f(2.25), 1.5) + assert_equal(f(3.5), 0.75) + assert_equal(f(4.0), 0.75) + + assert_array_equal(f([0.0, 0.5, 2.25, 3.5, 4.0]), + [1.0, 1.0, 1.5, 0.75, 0.75]) + xp, yp = f.get_plottable_data() xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] @@ -38,11 +50,17 @@ def test_pwc(): assert_almost_equal(a, (-0.5*1.0+0.5*1.5+1.0*0.75)/2.5, decimal=16) a = f.avrg([1.0, 4.0]) assert_almost_equal(a, (-0.5*1.0+0.5*1.5+1.5*0.75)/3.0, decimal=16) + a = f.avrg([0.0, 2.2]) + assert_almost_equal(a, (1.0*1.0-0.5*1.0+0.2*1.5)/2.2, decimal=15) # averaging over multiple intervals a = f.avrg([(0.5, 1.5), (1.5, 3.5)]) assert_almost_equal(a, (0.5-0.5+0.5*1.5+1.0*0.75)/3.0, decimal=16) + # averaging over multiple intervals + a = f.avrg([(0.5, 1.5), (2.2, 3.5)]) + assert_almost_equal(a, (0.5*1.0-0.5*0.5+0.3*1.5+1.0*0.75)/2.3, decimal=15) + def test_pwc_add(): # some random data @@ -105,6 +123,18 @@ def test_pwl(): y1 = [1.0, -0.5, 1.5, 0.75] y2 = [1.5, -0.4, 1.5, 0.25] f = spk.PieceWiseLinFunc(x, y1, y2) + + # function values + assert_equal(f(0.0), 1.0) + assert_equal(f(0.5), 1.25) + assert_equal(f(2.25), 1.5) + assert_equal(f(2.5), 0.75) + assert_equal(f(3.5), 0.75-0.5*1.0/1.5) + assert_equal(f(4.0), 0.25) + + assert_array_equal(f([0.0, 0.5, 2.25, 2.5, 3.5, 4.0]), + [1.0, 1.25, 1.5, 0.75, 0.75-0.5*1.0/1.5, 0.25]) + xp, yp = f.get_plottable_data() xp_expected = [0.0, 1.0, 1.0, 2.0, 2.0, 2.5, 2.5, 4.0] -- cgit v1.2.3 From 40e6372c0813cb8f34abd0ffee353bde7a96f158 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 13 May 2015 11:26:38 +0200 Subject: new profiles example --- examples/profiles.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 examples/profiles.py diff --git a/examples/profiles.py b/examples/profiles.py new file mode 100644 index 0000000..05494bd --- /dev/null +++ b/examples/profiles.py @@ -0,0 +1,66 @@ +""" profiles.py + +Simple example showing some functionality of distance profiles. + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" + + +from __future__ import print_function + +import pyspike as spk + +spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", + edges=(0, 4000)) + +##### ISI PROFILES ####### + +# compute the ISI profile of the first two spike trains +f = spk.isi_profile(spike_trains[0], spike_trains[1]) + +# ISI values at certain points +t = 1200 +print("ISI value at t =", t, ":", f(t)) +t = [900, 1100, 2000, 3100] +print("ISI value at t =", t, ":", f(t)) +print("Average ISI distance:", f.avrg()) +print() + +# compute the multivariate ISI profile +f = spk.isi_profile_multi(spike_trains) + +t = 1200 +print("Multivariate ISI value at t =", t, ":", f(t)) +t = [900, 1100, 2000, 3100] +print("Multivariate ISI value at t =", t, ":", f(t)) +print("Average multivariate ISI distance:", f.avrg()) +print() +print() + +# for plotting, use the get_plottable_data() member function, see plot.py + + +##### SPIKE PROFILES ####### + +# compute the SPIKE profile of the first two spike trains +f = spk.spike_profile(spike_trains[0], spike_trains[1]) + +# SPIKE distance values at certain points +t = 1200 +print("SPIKE value at t =", t, ":", f(t)) +t = [900, 1100, 2000, 3100] +print("SPIKE value at t =", t, ":", f(t)) +print("Average SPIKE distance:", f.avrg()) +print() + +# compute the multivariate SPIKE profile +f = spk.spike_profile_multi(spike_trains) + +# SPIKE values at certain points +t = 1200 +print("Multivariate SPIKE value at t =", t, ":", f(t)) +t = [900, 1100, 2000, 3100] +print("Multivariate SPIKE value at t =", t, ":", f(t)) +print("Average multivariate SPIKE distance:", f.avrg()) -- cgit v1.2.3 From f3e21dcc82d48f0980b0107f1e5cf320a8b213f3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 13 May 2015 11:28:07 +0200 Subject: updated Changelog --- Changelog | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Changelog b/Changelog index beb2ce5..798403e 100644 --- a/Changelog +++ b/Changelog @@ -4,6 +4,9 @@ PySpike v0.2.1: * performance improvements by introducing specific distance functions * performance improvements for multivariate profiles by using recursive approach + * new example: performance.py + * consistent treatment of empty spike trains + * new example: profiles.py PySpike v0.2: -- cgit v1.2.3 From 8841138b74242ed9eb77c972c76e9a617778a79a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 13 May 2015 18:19:02 +0200 Subject: pwc function now returns intermediate value at exact spike times --- pyspike/PieceWiseConstFunc.py | 35 +++++++++++++++++++++++++++-------- test/test_function.py | 6 ++++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index cf64e58..dea1a56 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -37,16 +37,35 @@ class PieceWiseConstFunc(object): "Invalid time: " + str(t) ind = np.searchsorted(self.x, t, side='right') - # correct the cases t == x[0], t == x[-1] - try: + if isinstance(t, collections.Sequence): + # t is a sequence of values + # correct the cases t == x[0], t == x[-1] ind[ind == 0] = 1 ind[ind == len(self.x)] = len(self.x)-1 - except TypeError: - if ind == 0: - ind = 1 - if ind == len(self.x): - ind = len(self.x)-1 - return self.y[ind-1] + value = self.y[ind-1] + # correct the values at exact spike times: there the value should + # be the at half of the step + # obtain the 'left' side indices for t + ind_l = np.searchsorted(self.x, t, side='left') + # if left and right side indices differ, the time t has to appear + # in self.x + ind_at_spike = ind[np.logical_and(np.logical_and(ind != ind_l, + ind > 1), + ind < len(self.x))] + value[ind_at_spike] = 0.5 * (self.y[ind_at_spike-1] + + self.y[ind_at_spike-2]) + return value + else: + # specific check for interval edges + if t == self.x[0]: + return self.y[0] + if t == self.x[-1]: + return self.y[-1] + # check if we are on any other exact spike time + if sum(self.x == t) > 0: + # use the middle of the left and right ISI value + return 0.5 * (self.y[ind-1] + self.y[ind-2]) + return self.y[ind-1] def copy(self): """ Returns a copy of itself diff --git a/test/test_function.py b/test/test_function.py index c56a295..8ad4b17 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -25,12 +25,14 @@ def test_pwc(): # function values assert_equal(f(0.0), 1.0) assert_equal(f(0.5), 1.0) + assert_equal(f(1.0), 0.25) assert_equal(f(2.25), 1.5) + assert_equal(f(2.5), 2.25/2) assert_equal(f(3.5), 0.75) assert_equal(f(4.0), 0.75) - assert_array_equal(f([0.0, 0.5, 2.25, 3.5, 4.0]), - [1.0, 1.0, 1.5, 0.75, 0.75]) + assert_array_equal(f([0.0, 0.5, 1.0, 2.25, 2.5, 3.5, 4.0]), + [1.0, 1.0, 0.25, 1.5, 2.25/2, 0.75, 0.75]) xp, yp = f.get_plottable_data() -- cgit v1.2.3 From a61a14295e28e6e95fa510693a11ae8c78a552ab Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 17 May 2015 17:50:39 +0200 Subject: return correct values at exact spike times pwc and pwl function object return the average of the left and right limit as function value at the exact spike times. --- pyspike/PieceWiseConstFunc.py | 15 ++++++++----- pyspike/PieceWiseLinFunc.py | 51 +++++++++++++++++++++++++++++++++---------- test/test_function.py | 13 ++++++----- 3 files changed, 56 insertions(+), 23 deletions(-) diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index dea1a56..6d7a845 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -49,13 +49,16 @@ class PieceWiseConstFunc(object): ind_l = np.searchsorted(self.x, t, side='left') # if left and right side indices differ, the time t has to appear # in self.x - ind_at_spike = ind[np.logical_and(np.logical_and(ind != ind_l, - ind > 1), - ind < len(self.x))] - value[ind_at_spike] = 0.5 * (self.y[ind_at_spike-1] + - self.y[ind_at_spike-2]) + ind_at_spike = np.logical_and(np.logical_and(ind != ind_l, + ind > 1), + ind < len(self.x)) + # get the corresponding indices for the resulting value array + val_ind = np.arange(len(ind))[ind_at_spike] + # and for the arrays self.x, y1, y2 + xy_ind = ind[ind_at_spike] + value[val_ind] = 0.5 * (self.y[xy_ind-1] + self.y[xy_ind-2]) return value - else: + else: # t is a single value # specific check for interval edges if t == self.x[0]: return self.y[0] diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index b9787eb..03c2da2 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -44,20 +44,47 @@ class PieceWiseLinFunc: "Invalid time: " + str(t) ind = np.searchsorted(self.x, t, side='right') - # correct the cases t == x[0], t == x[-1] - try: + if isinstance(t, collections.Sequence): + # t is a sequence of values + # correct the cases t == x[0], t == x[-1] ind[ind == 0] = 1 ind[ind == len(self.x)] = len(self.x)-1 - except TypeError: - if ind == 0: - ind = 1 - if ind == len(self.x): - ind = len(self.x)-1 - return intermediate_value(self.x[ind-1], - self.x[ind], - self.y1[ind-1], - self.y2[ind-1], - t) + value = intermediate_value(self.x[ind-1], + self.x[ind], + self.y1[ind-1], + self.y2[ind-1], + t) + # correct the values at exact spike times: there the value should + # be the at half of the step + # obtain the 'left' side indices for t + ind_l = np.searchsorted(self.x, t, side='left') + # if left and right side indices differ, the time t has to appear + # in self.x + ind_at_spike = np.logical_and(np.logical_and(ind != ind_l, + ind > 1), + ind < len(self.x)) + # get the corresponding indices for the resulting value array + val_ind = np.arange(len(ind))[ind_at_spike] + # and for the values in self.x, y1, y2 + xy_ind = ind[ind_at_spike] + # the values are defined as the average of the left and right limit + value[val_ind] = 0.5 * (self.y1[xy_ind-1] + self.y2[xy_ind-2]) + return value + else: # t is a single value + # specific check for interval edges + if t == self.x[0]: + return self.y1[0] + if t == self.x[-1]: + return self.y2[-1] + # check if we are on any other exact spike time + if sum(self.x == t) > 0: + # use the middle of the left and right Spike value + return 0.5 * (self.y1[ind-1] + self.y2[ind-2]) + return intermediate_value(self.x[ind-1], + self.x[ind], + self.y1[ind-1], + self.y2[ind-1], + t) def copy(self): """ Returns a copy of itself diff --git a/test/test_function.py b/test/test_function.py index 8ad4b17..92d378d 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -26,13 +26,14 @@ def test_pwc(): assert_equal(f(0.0), 1.0) assert_equal(f(0.5), 1.0) assert_equal(f(1.0), 0.25) + assert_equal(f(2.0), 0.5) assert_equal(f(2.25), 1.5) assert_equal(f(2.5), 2.25/2) assert_equal(f(3.5), 0.75) assert_equal(f(4.0), 0.75) - assert_array_equal(f([0.0, 0.5, 1.0, 2.25, 2.5, 3.5, 4.0]), - [1.0, 1.0, 0.25, 1.5, 2.25/2, 0.75, 0.75]) + assert_array_equal(f([0.0, 0.5, 1.0, 2.0, 2.25, 2.5, 3.5, 4.0]), + [1.0, 1.0, 0.25, 0.5, 1.5, 2.25/2, 0.75, 0.75]) xp, yp = f.get_plottable_data() @@ -129,13 +130,15 @@ def test_pwl(): # function values assert_equal(f(0.0), 1.0) assert_equal(f(0.5), 1.25) + assert_equal(f(1.0), 0.5) + assert_equal(f(2.0), 1.1/2) assert_equal(f(2.25), 1.5) - assert_equal(f(2.5), 0.75) + assert_equal(f(2.5), 2.25/2) assert_equal(f(3.5), 0.75-0.5*1.0/1.5) assert_equal(f(4.0), 0.25) - assert_array_equal(f([0.0, 0.5, 2.25, 2.5, 3.5, 4.0]), - [1.0, 1.25, 1.5, 0.75, 0.75-0.5*1.0/1.5, 0.25]) + assert_array_equal(f([0.0, 0.5, 1.0, 2.0, 2.25, 2.5, 3.5, 4.0]), + [1.0, 1.25, 0.5, 0.55, 1.5, 2.25/2, 0.75-0.5/1.5, 0.25]) xp, yp = f.get_plottable_data() -- cgit v1.2.3 From c8f524db6e1add464aa3596ea46534c21096589e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 18 May 2015 15:08:24 +0200 Subject: cosmetics --- pyspike/PieceWiseConstFunc.py | 2 ++ pyspike/PieceWiseLinFunc.py | 1 + 2 files changed, 3 insertions(+) diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index 6d7a845..2705443 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -37,6 +37,7 @@ class PieceWiseConstFunc(object): "Invalid time: " + str(t) ind = np.searchsorted(self.x, t, side='right') + if isinstance(t, collections.Sequence): # t is a sequence of values # correct the cases t == x[0], t == x[-1] @@ -56,6 +57,7 @@ class PieceWiseConstFunc(object): val_ind = np.arange(len(ind))[ind_at_spike] # and for the arrays self.x, y1, y2 xy_ind = ind[ind_at_spike] + # use the middle of the left and right ISI value value[val_ind] = 0.5 * (self.y[xy_ind-1] + self.y[xy_ind-2]) return value else: # t is a single value diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index 03c2da2..c0dd475 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -44,6 +44,7 @@ class PieceWiseLinFunc: "Invalid time: " + str(t) ind = np.searchsorted(self.x, t, side='right') + if isinstance(t, collections.Sequence): # t is a sequence of values # correct the cases t == x[0], t == x[-1] -- cgit v1.2.3 From 81fc2c20e7360714f218e9bba729ec4387f59aef Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 18 May 2015 15:15:36 +0200 Subject: set version 0.3 --- Changelog | 4 +++- doc/conf.py | 4 ++-- setup.py | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Changelog b/Changelog index 798403e..519dd3b 100644 --- a/Changelog +++ b/Changelog @@ -1,4 +1,4 @@ -PySpike v0.2.1: +PySpike v0.3: * addition of __version__ attribute * restructured docs, Readme now only contains basic examples * performance improvements by introducing specific distance functions @@ -7,6 +7,8 @@ PySpike v0.2.1: * new example: performance.py * consistent treatment of empty spike trains * new example: profiles.py + * additional functionality: pwc and pwl function classes can now return + function values at given time(s) PySpike v0.2: diff --git a/doc/conf.py b/doc/conf.py index 9b994c2..8011ea9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,9 +64,9 @@ copyright = u'2014-2015, Mario Mulansky' # built documents. # # The short X.Y version. -version = '0.2' +version = '0.3' # The full version, including alpha/beta/rc tags. -release = '0.2.0' +release = '0.3.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 7902066..d853cdf 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ to compile cython files: python setup.py build_ext --inplace -Copyright 2014, Mario Mulansky +Copyright 2014-2015, Mario Mulansky Distributed under the BSD License @@ -49,7 +49,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.1.3', + version='0.3.0', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy.get_include()], @@ -59,7 +59,6 @@ train similarity', author_email='mario.mulanskygmx.net', license='BSD', url='https://github.com/mariomulansky/PySpike', - # download_url='https://github.com/mariomulansky/PySpike/tarball/0.1', install_requires=['numpy'], keywords=['data analysis', 'spike', 'neuroscience'], # arbitrary keywords classifiers=[ @@ -81,7 +80,8 @@ train similarity', 'Programming Language :: Python :: 2.7', ], package_data={ - 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c'], + 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c', + 'cython_distances.c'], 'test': ['Spike_testdata.txt'] } ) -- cgit v1.2.3 From d6462d271aeaf1be635cbc7c4317ae6a3b30b63f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 12 Jun 2015 14:55:07 +0200 Subject: implement __getitem__ and __len__ for SpikeTrain This allows to use SpikeTrain objects to be used in many applications as if they were arrays with spike times. --- pyspike/SpikeTrain.py | 15 +++++++++++++++ pyspike/spikes.py | 3 ++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index 9127b60..4b59a5d 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -32,6 +32,21 @@ class SpikeTrain(object): self.t_start = 0.0 self.t_end = float(edges) + def __getitem__(self, index): + """ Returns the time of the spike given by index. + + :param index: Index of the spike. + :return: spike time. + """ + return self.spikes[index] + + def __len__(self): + """ Returns the number of spikes. + + :return: Number of spikes. + """ + return len(self.spikes) + def sort(self): """ Sorts the spike times of this spike train using `np.sort` """ diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 35d8533..b18d7eb 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -28,7 +28,8 @@ def spike_train_from_string(s, edges, sep=' ', is_sorted=False): # load_spike_trains_txt ############################################################ def load_spike_trains_from_txt(file_name, edges, - separator=' ', comment='#', is_sorted=False): + separator=' ', comment='#', is_sorted=False, + ignore_empty_lines=True): """ Loads a number of spike trains from a text file. Each line of the text file should contain one spike train as a sequence of spike times separated by `separator`. Empty lines as well as lines starting with `comment` are -- cgit v1.2.3 From 7984d32e767e5833f1aaee06b6aeda8cc3f4500d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 12 Jun 2015 14:57:50 +0200 Subject: update example to use new SpikeTrain capability Make use of __getitem__ and __len__ of SpikeTrains in some examples. --- examples/multivariate.py | 2 +- examples/plot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/multivariate.py b/examples/multivariate.py index 53dbf0f..9a44758 100644 --- a/examples/multivariate.py +++ b/examples/multivariate.py @@ -23,7 +23,7 @@ spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", t_loading = time.clock() print("Number of spike trains: %d" % len(spike_trains)) -num_of_spikes = sum([len(spike_trains[i].spikes) +num_of_spikes = sum([len(spike_trains[i]) for i in xrange(len(spike_trains))]) print("Number of spikes: %d" % num_of_spikes) diff --git a/examples/plot.py b/examples/plot.py index 9670286..5841baf 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -19,9 +19,9 @@ import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", edges=(0, 4000)) -# plot the spike time +# plot the spike times for (i, spike_train) in enumerate(spike_trains): - plt.plot(spike_train.spikes, i*np.ones_like(spike_train.spikes), 'o') + plt.plot(spike_train, i*np.ones_like(spike_train), 'o') f = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() -- cgit v1.2.3 From 65883934996239907856b6e51641560dc7e1b7c7 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 24 Jun 2015 11:09:22 +0200 Subject: fixed typo --- pyspike/spike_distance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index ac2d260..dd6d4f8 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -54,7 +54,8 @@ Falling back to slow python backend.") ############################################################ def spike_distance(spike_train1, spike_train2, interval=None): """ Computes the spike-distance :math:`D_S` of the given spike trains. The - spike-distance is the integral over the isi distance profile :math:`S(t)`: + spike-distance is the integral over the spike distance profile + :math:`S(t)`: .. math:: D_S = \int_{T_0}^{T_1} S(t) dt. -- cgit v1.2.3 From 0ece782e1579660cdb71e077cbaaf9f76e97bef4 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 7 Jul 2015 18:06:12 +0200 Subject: better spike train plot (scatter) in plot.py --- examples/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/plot.py b/examples/plot.py index 5841baf..c44afd1 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -21,7 +21,7 @@ spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", # plot the spike times for (i, spike_train) in enumerate(spike_trains): - plt.plot(spike_train, i*np.ones_like(spike_train), 'o') + plt.scatter(spike_train, i*np.ones_like(spike_train), marker='|') f = spk.isi_profile(spike_trains[0], spike_trains[1]) x, y = f.get_plottable_data() -- cgit v1.2.3 From 5119d47d0f00c3f7203cf94460730b59a7e473ec Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 7 Jul 2015 18:55:32 +0200 Subject: add disable_backend_warning property Users can now disable the warning messages produced when the cython backend is not available by writing spk.disable_backend_warning = True in the beginning --- examples/performance.py | 3 +++ examples/plot.py | 1 + pyspike/DiscreteFunc.py | 4 +++- pyspike/PieceWiseConstFunc.py | 7 +++++-- pyspike/PieceWiseLinFunc.py | 9 ++++++--- pyspike/__init__.py | 2 ++ pyspike/isi_distance.py | 6 ++++-- pyspike/spike_distance.py | 4 +++- pyspike/spike_sync.py | 4 +++- 9 files changed, 30 insertions(+), 10 deletions(-) diff --git a/examples/performance.py b/examples/performance.py index 1c31e8f..d0c3b91 100644 --- a/examples/performance.py +++ b/examples/performance.py @@ -14,6 +14,9 @@ from datetime import datetime import cProfile import pstats +# in case you dont have the cython backends, disable the warnings as follows: +# spk.disable_backend_warning = True + M = 100 # number of spike trains r = 1.0 # rate of Poisson spike times T = 1E3 # length of spike trains diff --git a/examples/plot.py b/examples/plot.py index c44afd1..1922939 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -16,6 +16,7 @@ import matplotlib.pyplot as plt import pyspike as spk + spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", edges=(0, 4000)) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 17153ee..a8c054e 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -6,6 +6,7 @@ from __future__ import print_function import numpy as np import collections +import pyspike ############################################################## @@ -202,7 +203,8 @@ class DiscreteFunc(object): from cython.cython_add import add_discrete_function_cython as \ add_discrete_function_impl except ImportError: - print("Warning: add_discrete_function_cython not found. Make \ + if not(pyspike.disable_backend_warning): + print("Warning: add_discrete_function_cython not found. Make \ sure that PySpike is installed by running\n\ 'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index 2705443..23ff536 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -6,6 +6,7 @@ from __future__ import print_function import numpy as np import collections +import pyspike ############################################################## @@ -191,8 +192,10 @@ class PieceWiseConstFunc(object): from cython.cython_add import add_piece_wise_const_cython as \ add_piece_wise_const_impl except ImportError: - print("Warning: add_piece_wise_const_cython not found. Make sure \ -that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ + if not(pyspike.disable_backend_warning): + print("Warning: add_piece_wise_const_cython not found. Make \ +sure that PySpike is installed by running\n \ +'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") # use python backend from cython.python_backend import add_piece_wise_const_python as \ diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index c0dd475..0d51c76 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -6,6 +6,7 @@ from __future__ import print_function import numpy as np import collections +import pyspike ############################################################## @@ -230,9 +231,11 @@ class PieceWiseLinFunc: from cython.cython_add import add_piece_wise_lin_cython as \ add_piece_wise_lin_impl except ImportError: - print("Warning: add_piece_wise_lin_cython not found. Make sure \ -that PySpike is installed by running\n 'python setup.py build_ext --inplace'! \ -\n Falling back to slow python backend.") + if not(pyspike.disable_backend_warning): + print("Warning: add_piece_wise_lin_cython not found. Make \ +sure that PySpike is installed by running\n \ +'python setup.py build_ext --inplace'! \n \ +Falling back to slow python backend.") # use python backend from cython.python_backend import add_piece_wise_lin_python as \ add_piece_wise_lin_impl diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 3e836bd..2060f73 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -42,3 +42,5 @@ except DistributionNotFound: __version__ = 'Please install this project with setup.py' else: __version__ = _dist.version + +disable_backend_warning = False diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 5ea555d..e50f203 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -2,6 +2,7 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License +import pyspike from pyspike import PieceWiseConstFunc from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ _generic_distance_matrix @@ -34,8 +35,9 @@ def isi_profile(spike_train1, spike_train2): from cython.cython_profiles import isi_profile_cython \ as isi_profile_impl except ImportError: - print("Warning: isi_distance_cython not found. Make sure that PySpike \ -is installed by running\n 'python setup.py build_ext --inplace'!\n \ + if not(pyspike.disable_backend_warning): + print("Warning: isi_profile_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend from cython.python_backend import isi_distance_python \ diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index dd6d4f8..feea0c1 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -2,6 +2,7 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License +import pyspike from pyspike import PieceWiseLinFunc from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ _generic_distance_matrix @@ -34,7 +35,8 @@ def spike_profile(spike_train1, spike_train2): from cython.cython_profiles import spike_profile_cython \ as spike_profile_impl except ImportError: - print("Warning: spike_profile_cython not found. Make sure that \ + if not(pyspike.disable_backend_warning): + print("Warning: spike_profile_cython not found. Make sure that \ PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 40d98d2..10ebdc7 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -5,6 +5,7 @@ import numpy as np from functools import partial +import pyspike from pyspike import DiscreteFunc from pyspike.generic import _generic_profile_multi, _generic_distance_matrix @@ -39,7 +40,8 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): from cython.cython_profiles import coincidence_profile_cython \ as coincidence_profile_impl except ImportError: - print("Warning: spike_distance_cython not found. Make sure that \ + if not(pyspike.disable_backend_warning): + print("Warning: spike_distance_cython not found. Make sure that \ PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend -- cgit v1.2.3 From 748b4c5fcc2f41d8d414e3714b66dd6c237c8ff7 Mon Sep 17 00:00:00 2001 From: ImmanuelSamuel Date: Mon, 10 Aug 2015 17:11:32 -0400 Subject: Indices for matrix slicing needs to be integers Dividing the indices by 2 gives a float which cannot be used for slicing the matrix. Changed the type of the indices to integer to ensure that matrix slicing does not return error. --- pyspike/generic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyspike/generic.py b/pyspike/generic.py index 41affcb..4d70e65 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -37,13 +37,13 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): """ L1 = len(pairs1) if L1 > 1: - dist_prof1 = divide_and_conquer(pairs1[:L1/2], pairs1[L1/2:]) + dist_prof1 = divide_and_conquer(pairs1[:int(L1/2)], pairs1[int(L1/2):]) else: dist_prof1 = pair_distance_func(spike_trains[pairs1[0][0]], spike_trains[pairs1[0][1]]) L2 = len(pairs2) if L2 > 1: - dist_prof2 = divide_and_conquer(pairs2[:L2/2], pairs2[L2/2:]) + dist_prof2 = divide_and_conquer(pairs2[:int(L2/2)], pairs2[int(L2/2):]) else: dist_prof2 = pair_distance_func(spike_trains[pairs2[0][0]], spike_trains[pairs2[0][1]]) @@ -63,8 +63,8 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): L = len(pairs) if L > 1: # recursive iteration through the list of pairs to get average profile - avrg_dist = divide_and_conquer(pairs[:len(pairs)/2], - pairs[len(pairs)/2:]) + avrg_dist = divide_and_conquer(pairs[:int(len(pairs)/2]), + pairs[int(len(pairs)/2):]) else: avrg_dist = pair_distance_func(spike_trains[pairs[0][0]], spike_trains[pairs[0][1]]) -- cgit v1.2.3 From d95d021f91f165b80be94b11c7bb526a055a94ef Mon Sep 17 00:00:00 2001 From: ImmanuelSamuel Date: Mon, 10 Aug 2015 17:29:08 -0400 Subject: Syntax correction --- pyspike/generic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyspike/generic.py b/pyspike/generic.py index 4d70e65..9acba26 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -63,7 +63,7 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): L = len(pairs) if L > 1: # recursive iteration through the list of pairs to get average profile - avrg_dist = divide_and_conquer(pairs[:int(len(pairs)/2]), + avrg_dist = divide_and_conquer(pairs[:int(len(pairs)/2)], pairs[int(len(pairs)/2):]) else: avrg_dist = pair_distance_func(spike_trains[pairs[0][0]], -- cgit v1.2.3 From d97d1f798ea1b4fe90c0e3b6828a219c90996271 Mon Sep 17 00:00:00 2001 From: ImmanuelSamuel Date: Mon, 10 Aug 2015 18:39:06 -0400 Subject: Used // instead of type conversion --- pyspike/generic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyspike/generic.py b/pyspike/generic.py index 9acba26..723c90d 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -37,13 +37,13 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): """ L1 = len(pairs1) if L1 > 1: - dist_prof1 = divide_and_conquer(pairs1[:int(L1/2)], pairs1[int(L1/2):]) + dist_prof1 = divide_and_conquer(pairs1[:L1//2], pairs1[int(L1/2):]) else: dist_prof1 = pair_distance_func(spike_trains[pairs1[0][0]], spike_trains[pairs1[0][1]]) L2 = len(pairs2) if L2 > 1: - dist_prof2 = divide_and_conquer(pairs2[:int(L2/2)], pairs2[int(L2/2):]) + dist_prof2 = divide_and_conquer(pairs2[:L2//2], pairs2[int(L2/2):]) else: dist_prof2 = pair_distance_func(spike_trains[pairs2[0][0]], spike_trains[pairs2[0][1]]) @@ -63,8 +63,8 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): L = len(pairs) if L > 1: # recursive iteration through the list of pairs to get average profile - avrg_dist = divide_and_conquer(pairs[:int(len(pairs)/2)], - pairs[int(len(pairs)/2):]) + avrg_dist = divide_and_conquer(pairs[:len(pairs)//2], + pairs[len(pairs)//2:]) else: avrg_dist = pair_distance_func(spike_trains[pairs[0][0]], spike_trains[pairs[0][1]]) -- cgit v1.2.3 From 3c6cdfe943e54880325c5ee81ab0d96a6bb101de Mon Sep 17 00:00:00 2001 From: ImmanuelSamuel Date: Mon, 10 Aug 2015 19:06:50 -0400 Subject: Added more // --- pyspike/generic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyspike/generic.py b/pyspike/generic.py index 723c90d..2df34f1 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -37,13 +37,13 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): """ L1 = len(pairs1) if L1 > 1: - dist_prof1 = divide_and_conquer(pairs1[:L1//2], pairs1[int(L1/2):]) + dist_prof1 = divide_and_conquer(pairs1[:L1//2], pairs1[int(L1//2):]) else: dist_prof1 = pair_distance_func(spike_trains[pairs1[0][0]], spike_trains[pairs1[0][1]]) L2 = len(pairs2) if L2 > 1: - dist_prof2 = divide_and_conquer(pairs2[:L2//2], pairs2[int(L2/2):]) + dist_prof2 = divide_and_conquer(pairs2[:L2//2], pairs2[int(L2//2):]) else: dist_prof2 = pair_distance_func(spike_trains[pairs2[0][0]], spike_trains[pairs2[0][1]]) -- cgit v1.2.3 From 2d9e1ea84162c8f964221df6851e4a4fe5e955e3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 11 Aug 2015 15:38:08 +0200 Subject: add regression #11 test case --- test/regression/test_regression_11.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/regression/test_regression_11.py diff --git a/test/regression/test_regression_11.py b/test/regression/test_regression_11.py new file mode 100644 index 0000000..7e66bc2 --- /dev/null +++ b/test/regression/test_regression_11.py @@ -0,0 +1,28 @@ +""" test_regression_11.py + +Regression test for Issue 11 +https://github.com/mariomulansky/PySpike/issues/11 + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" + +# run as +# python -Qnew test_regression_11.py +# to check correct behavior with new division + +# import division to see if everythin works with new division operator +from __future__ import division + +import pyspike as spk + +M = 19 # uneven number of spike trains + +spike_trains = [spk.generate_poisson_spikes(1.0, [0, 100]) for i in xrange(M)] + +isi_prof = spk.isi_profile_multi(spike_trains) +isi = spk.isi_distance_multi(spike_trains) + +spike_prof = spk.spike_profile_multi(spike_trains) +spike = spk.spike_distance_multi(spike_trains) -- cgit v1.2.3 From 29e50478dcfc31ce04c4343fa585463abe96caae Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 12 Aug 2015 18:42:54 +0200 Subject: new spike delay asymmetry measures added first version of spike delay asymmetry functions. still incomplete and untested. --- pyspike/__init__.py | 3 +- pyspike/directionality/__init__.py | 12 ++ pyspike/directionality/cython/__init__.py | 0 .../cython/cython_directionality.pyx | 177 +++++++++++++++++ pyspike/directionality/spike_delay_asymmetry.py | 212 +++++++++++++++++++++ pyspike/generic.py | 6 +- setup.py | 28 ++- 7 files changed, 427 insertions(+), 11 deletions(-) create mode 100644 pyspike/directionality/__init__.py create mode 100644 pyspike/directionality/cython/__init__.py create mode 100644 pyspike/directionality/cython/cython_directionality.pyx create mode 100644 pyspike/directionality/spike_delay_asymmetry.py diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 2060f73..8d92ea4 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -6,7 +6,7 @@ Distributed under the BSD License __all__ = ["isi_distance", "spike_distance", "spike_sync", "psth", "spikes", "SpikeTrain", "PieceWiseConstFunc", "PieceWiseLinFunc", - "DiscreteFunc"] + "DiscreteFunc", "directionality"] from PieceWiseConstFunc import PieceWiseConstFunc from PieceWiseLinFunc import PieceWiseLinFunc @@ -24,6 +24,7 @@ from psth import psth from spikes import load_spike_trains_from_txt, spike_train_from_string, \ merge_spike_trains, generate_poisson_spikes +import directionality as drct # define the __version__ following # http://stackoverflow.com/questions/17583443 diff --git a/pyspike/directionality/__init__.py b/pyspike/directionality/__init__.py new file mode 100644 index 0000000..e6de1de --- /dev/null +++ b/pyspike/directionality/__init__.py @@ -0,0 +1,12 @@ +""" +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" + +__all__ = ["spike_delay_asymmetry"] + +from spike_delay_asymmetry import spike_delay_asymmetry_profile, \ + spike_delay_asymmetry, spike_delay_asymmetry_profile_multi, \ + spike_delay_asymmetry_matrix, optimal_asymmetry_order, \ + optimal_asymmetry_order_from_D, reorder_asymmetry_matrix diff --git a/pyspike/directionality/cython/__init__.py b/pyspike/directionality/cython/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pyspike/directionality/cython/cython_directionality.pyx b/pyspike/directionality/cython/cython_directionality.pyx new file mode 100644 index 0000000..f5ea752 --- /dev/null +++ b/pyspike/directionality/cython/cython_directionality.pyx @@ -0,0 +1,177 @@ +#cython: boundscheck=False +#cython: wraparound=False +#cython: cdivision=True + +""" +cython_directionality.pyx + +cython implementation of the spike delay asymmetry measures + +Copyright 2015, Mario Mulansky + +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_delay_asymmetry_profile_cython +############################################################ +def spike_delay_asymmetry_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_delay_asymmetry_cython +############################################################ +def spike_delay_asymmetry_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 asym = 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 + asym -= 1 + 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 + asym += 1 + 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 asym == 0 and mp == 0: + # empty spike trains -> spike sync = 1 by definition + asym = 1 + mp = 1 + + return asym, mp diff --git a/pyspike/directionality/spike_delay_asymmetry.py b/pyspike/directionality/spike_delay_asymmetry.py new file mode 100644 index 0000000..7d59601 --- /dev/null +++ b/pyspike/directionality/spike_delay_asymmetry.py @@ -0,0 +1,212 @@ +# Module containing functions to compute multivariate spike delay asymmetry +# Copyright 2015, Mario Mulansky +# Distributed under the BSD License + +import numpy as np +from math import exp +from functools import partial +# import pyspike +from pyspike import DiscreteFunc +from pyspike.generic import _generic_profile_multi + + +############################################################ +# spike_delay_asymmetry_profile +############################################################ +def spike_delay_asymmetry_profile(spike_train1, spike_train2, max_tau=None): + """ Computes the spike delay asymmetry profile A(t) of the two given + spike trains. Returns the profile as a DiscreteFunction object. + + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike-distance profile :math:`S_{sync}(t)`. + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + # check whether the spike trains are defined for the same interval + assert spike_train1.t_start == spike_train2.t_start, \ + "Given spike trains are not defined on the same interval!" + assert spike_train1.t_end == spike_train2.t_end, \ + "Given spike trains are not defined on the same interval!" + + # cython implementation + try: + from cython.cython_directionality import \ + spike_delay_asymmetry_profile_cython as \ + spike_delay_asymmetry_profile_impl + except ImportError: + raise NotImplementedError() +# if not(pyspike.disable_backend_warning): +# print("Warning: spike_distance_cython not found. Make sure that \ +# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +# Falling back to slow python backend.") +# # use python backend +# from cython.python_backend import coincidence_python \ +# as coincidence_profile_impl + + if max_tau is None: + max_tau = 0.0 + + times, coincidences, multiplicity \ + = spike_delay_asymmetry_profile_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + + return DiscreteFunc(times, coincidences, multiplicity) + + +############################################################ +# spike_delay_asymmetry +############################################################ +def spike_delay_asymmetry(spike_train1, spike_train2, + interval=None, max_tau=None): + """ Computes the overall spike delay asymmetry value for two spike trains. + """ + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from cython.cython_directionality import \ + spike_delay_asymmetry_cython as spike_delay_impl + if max_tau is None: + max_tau = 0.0 + c, mp = spike_delay_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + return c + except ImportError: + # Cython backend not available: fall back to profile averaging + raise NotImplementedError() + # return spike_sync_profile(spike_train1, spike_train2, + # max_tau).integral(interval) + else: + # some specific interval is provided: not yet implemented + raise NotImplementedError() + + +############################################################ +# spike_delay_asymmetry_profile_multi +############################################################ +def spike_delay_asymmetry_profile_multi(spike_trains, indices=None, + max_tau=None): + """ Computes the multi-variate spike delay asymmetry profile for a set of + spike trains. For each spike in the set of spike trains, the multi-variate + profile is defined as the sum of asymmetry values divided by the number of + spike trains pairs involving the spike train of containing this spike, + which is the number of spike trains minus one (N-1). + + :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The multi-variate spike sync profile :math:`(t)` + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + prof_func = partial(spike_delay_asymmetry_profile, max_tau=max_tau) + average_prof, M = _generic_profile_multi(spike_trains, prof_func, + indices) + # average_dist.mul_scalar(1.0/M) # no normalization here! + return average_prof + + +############################################################ +# spike_delay_asymmetry_matrix +############################################################ +def spike_delay_asymmetry_matrix(spike_trains, indices=None, + interval=None, max_tau=None): + """ Computes the spike delay asymmetry matrix for the given spike trains. + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + distance_matrix = np.zeros((len(indices), len(indices))) + for i, j in pairs: + d = spike_delay_asymmetry(spike_trains[i], spike_trains[j], + interval, max_tau=max_tau) + distance_matrix[i, j] = d + distance_matrix[j, i] = -d + return distance_matrix + + +############################################################ +# optimal_asymmetry_order_from_D +############################################################ +def optimal_asymmetry_order_from_D(D, full_output=False): + """ finds the best sorting via simulated annealing. + Returns the optimal permutation p and A value. + Internal function, don't call directly! Use optimal_asymmetry_order + instead. + """ + N = len(D) + A = np.sum(np.triu(D, 0)) + + p = np.arange(N) + + T = 2*np.max(D) # starting temperature + T_end = 1E-5 * T # final temperature + alpha = 0.9 # cooling factor + total_iter = 0 + while T > T_end: + iterations = 0 + succ_iter = 0 + while iterations < 100*N and succ_iter < 10*N: + # exchange two rows and cols + ind1 = np.random.randint(N-1) + delta_A = -2*D[p[ind1], p[ind1+1]] + if delta_A > 0.0 or exp(delta_A/T) > np.random.random(): + # swap indices + p[ind1], p[ind1+1] = p[ind1+1], p[ind1] + A += delta_A + succ_iter += 1 + iterations += 1 + total_iter += iterations + T *= alpha # cool down + if succ_iter == 0: + break + if full_output: + return p, A, total_iter + else: + return p, A + + +############################################################ +# _optimal_asymmetry_order +############################################################ +def optimal_asymmetry_order(spike_trains, indices=None, interval=None, + max_tau=None, full_output=False): + """ finds the best sorting of the given spike trains via simulated + annealing. + Returns the optimal permutation p and A value. + """ + D = spike_delay_asymmetry_matrix(spike_trains, indices, interval, max_tau) + return optimal_asymmetry_order_from_D(D, full_output) + + +############################################################ +# reorder_asymmetry_matrix +############################################################ +def reorder_asymmetry_matrix(D, p): + N = len(D) + D_p = np.empty_like(D) + for n in xrange(N): + for m in xrange(N): + D_p[n, m] = D[p[n], p[m]] + return D_p diff --git a/pyspike/generic.py b/pyspike/generic.py index 2df34f1..515cbf4 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -37,13 +37,15 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): """ L1 = len(pairs1) if L1 > 1: - dist_prof1 = divide_and_conquer(pairs1[:L1//2], pairs1[int(L1//2):]) + dist_prof1 = divide_and_conquer(pairs1[:L1//2], + pairs1[int(L1//2):]) else: dist_prof1 = pair_distance_func(spike_trains[pairs1[0][0]], spike_trains[pairs1[0][1]]) L2 = len(pairs2) if L2 > 1: - dist_prof2 = divide_and_conquer(pairs2[:L2//2], pairs2[int(L2//2):]) + dist_prof2 = divide_and_conquer(pairs2[:L2//2], + pairs2[int(L2//2):]) else: dist_prof2 = pair_distance_func(spike_trains[pairs2[0][0]], spike_trains[pairs2[0][1]]) diff --git a/setup.py b/setup.py index d853cdf..960c684 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,8 @@ else: if os.path.isfile("pyspike/cython/cython_add.c") and \ os.path.isfile("pyspike/cython/cython_profiles.c") and \ - os.path.isfile("pyspike/cython/cython_distances.c"): + os.path.isfile("pyspike/cython/cython_distances.c") and \ + os.path.isfile("pyspike/directionality/cython/cython_directionality.c"): use_c = True else: use_c = False @@ -33,16 +34,26 @@ ext_modules = [] if use_cython: # Cython is available, compile .pyx -> .c ext_modules += [ - Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.pyx"]), - Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.pyx"]), - Extension("pyspike.cython.cython_distances", ["pyspike/cython/cython_distances.pyx"]), + Extension("pyspike.cython.cython_add", + ["pyspike/cython/cython_add.pyx"]), + Extension("pyspike.cython.cython_profiles", + ["pyspike/cython/cython_profiles.pyx"]), + Extension("pyspike.cython.cython_distances", + ["pyspike/cython/cython_distances.pyx"]), + Extension("pyspike.directionality.cython.cython_directionality", + ["pyspike/directionality/cython/cython_directionality.pyx"]) ] cmdclass.update({'build_ext': build_ext}) elif use_c: # c files are there, compile to binaries ext_modules += [ - Extension("pyspike.cython.cython_add", ["pyspike/cython/cython_add.c"]), - Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.c"]), - Extension("pyspike.cython.cython_distances", ["pyspike/cython/cython_distances.c"]), + Extension("pyspike.cython.cython_add", + ["pyspike/cython/cython_add.c"]), + Extension("pyspike.cython.cython_profiles", + ["pyspike/cython/cython_profiles.c"]), + Extension("pyspike.cython.cython_distances", + ["pyspike/cython/cython_distances.c"]), + Extension("pyspike.directionality.cython.cython_directionality", + ["pyspike/directionality/cython/cython_directionality.c"]) ] # neither cython nor c files available -> automatic fall-back to python backend @@ -81,7 +92,8 @@ train similarity', ], package_data={ 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c', - 'cython_distances.c'], + 'cython/cython_distances.c', + 'directionality/cython/cython_directionality.c'], 'test': ['Spike_testdata.txt'] } ) -- cgit v1.2.3 From 5731dad11d6d7129864fa6273d780000b34fd8a9 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 13 Aug 2015 17:45:02 +0200 Subject: more directionality functions + spike delay asymmetry profile + spike delay dual profile --- pyspike/directionality/__init__.py | 5 +- .../cython/cython_directionality.pyx | 46 ++++++++++++++++++ pyspike/directionality/spike_delay_asymmetry.py | 55 +++++++++++++++++++++- 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/pyspike/directionality/__init__.py b/pyspike/directionality/__init__.py index e6de1de..9a45b54 100644 --- a/pyspike/directionality/__init__.py +++ b/pyspike/directionality/__init__.py @@ -8,5 +8,6 @@ __all__ = ["spike_delay_asymmetry"] from spike_delay_asymmetry import spike_delay_asymmetry_profile, \ spike_delay_asymmetry, spike_delay_asymmetry_profile_multi, \ - spike_delay_asymmetry_matrix, optimal_asymmetry_order, \ - optimal_asymmetry_order_from_D, reorder_asymmetry_matrix + spike_delay_asymmetry_matrix, spike_delay_asymmetry_full, \ + optimal_asymmetry_order, optimal_asymmetry_order_from_D, \ + permutate_asymmetry_matrix diff --git a/pyspike/directionality/cython/cython_directionality.pyx b/pyspike/directionality/cython/cython_directionality.pyx index f5ea752..00e3b86 100644 --- a/pyspike/directionality/cython/cython_directionality.pyx +++ b/pyspike/directionality/cython/cython_directionality.pyx @@ -129,6 +129,52 @@ def spike_delay_asymmetry_profile_cython(double[:] spikes1, double[:] spikes2, +############################################################ +# spike_delay_dual_profile_cython +############################################################ +def spike_delay_dual_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 double[:] a1 = np.zeros(N1) # asymmetry values + cdef double[:] a2 = np.zeros(N2) # asymmetry 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 + a1[i] = -1 + a2[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 + a1[i] = +1 + a2[j] = -1 + else: # spikes1[i+1] = spikes2[j+1] + # advance in both spike trains + j += 1 + i += 1 + # equal spike times: zero asymmetry value + a1[i] = 0 + a2[j] = 0 + + return a1, a2 + + ############################################################ # spike_delay_asymmetry_cython ############################################################ diff --git a/pyspike/directionality/spike_delay_asymmetry.py b/pyspike/directionality/spike_delay_asymmetry.py index 7d59601..0676f6d 100644 --- a/pyspike/directionality/spike_delay_asymmetry.py +++ b/pyspike/directionality/spike_delay_asymmetry.py @@ -146,6 +146,57 @@ def spike_delay_asymmetry_matrix(spike_trains, indices=None, return distance_matrix +############################################################ +# spike_delay_asymmetry_full +############################################################ +def spike_delay_asymmetry_full(spike_trains, indices=None, + interval=None, max_tau=None): + """ Computes the spike train symmetry value for each spike in each spike + train. + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # list of arrays for reulting asymmetry values + asymmetry_list = [np.zeros_like(st.spikes) for st in spike_trains] + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + # cython implementation + try: + from cython.cython_directionality import \ + spike_delay_dual_profile_cython as \ + sda_dual_profile_impl + except ImportError: + raise NotImplementedError() +# if not(pyspike.disable_backend_warning): +# print("Warning: spike_distance_cython not found. Make sure that \ +# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +# Falling back to slow python backend.") +# # use python backend +# from cython.python_backend import coincidence_python \ +# as coincidence_profile_impl + + if max_tau is None: + max_tau = 0.0 + + for i, j in pairs: + a1, a2 = sda_dual_profile_impl(spike_trains[i].spikes, + spike_trains[j].spikes, + spike_trains[i].t_start, + spike_trains[i].t_end, + max_tau) + asymmetry_list[i] += a1 + asymmetry_list[j] += a2 + for a in asymmetry_list: + a /= len(spike_trains)-1 + return asymmetry_list + + ############################################################ # optimal_asymmetry_order_from_D ############################################################ @@ -188,7 +239,7 @@ def optimal_asymmetry_order_from_D(D, full_output=False): ############################################################ -# _optimal_asymmetry_order +# optimal_asymmetry_order ############################################################ def optimal_asymmetry_order(spike_trains, indices=None, interval=None, max_tau=None, full_output=False): @@ -203,7 +254,7 @@ def optimal_asymmetry_order(spike_trains, indices=None, interval=None, ############################################################ # reorder_asymmetry_matrix ############################################################ -def reorder_asymmetry_matrix(D, p): +def permutate_asymmetry_matrix(D, p): N = len(D) D_p = np.empty_like(D) for n in xrange(N): -- cgit v1.2.3 From be74318a2b269ec0c1e16981e7286679746f1a49 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 17 Aug 2015 11:57:53 +0200 Subject: fix #15 add test case and fix for Issue #15 closes #15 --- pyspike/generic.py | 7 +-- test/regression/test_regression_11.py | 28 ----------- test/test_regression/test_regression_15.py | 78 ++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 31 deletions(-) delete mode 100644 test/regression/test_regression_11.py create mode 100644 test/test_regression/test_regression_15.py diff --git a/pyspike/generic.py b/pyspike/generic.py index 515cbf4..904c3c2 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -137,12 +137,13 @@ def _generic_distance_matrix(spike_trains, dist_function, assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] + pairs = [(i, j) for i in xrange(len(indices)) + for j in xrange(i+1, len(indices))] distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: - d = dist_function(spike_trains[i], spike_trains[j], interval) + d = dist_function(spike_trains[indices[i]], spike_trains[indices[j]], + interval) distance_matrix[i, j] = d distance_matrix[j, i] = d return distance_matrix diff --git a/test/regression/test_regression_11.py b/test/regression/test_regression_11.py deleted file mode 100644 index 7e66bc2..0000000 --- a/test/regression/test_regression_11.py +++ /dev/null @@ -1,28 +0,0 @@ -""" test_regression_11.py - -Regression test for Issue 11 -https://github.com/mariomulansky/PySpike/issues/11 - -Copyright 2015, Mario Mulansky - -Distributed under the BSD License -""" - -# run as -# python -Qnew test_regression_11.py -# to check correct behavior with new division - -# import division to see if everythin works with new division operator -from __future__ import division - -import pyspike as spk - -M = 19 # uneven number of spike trains - -spike_trains = [spk.generate_poisson_spikes(1.0, [0, 100]) for i in xrange(M)] - -isi_prof = spk.isi_profile_multi(spike_trains) -isi = spk.isi_distance_multi(spike_trains) - -spike_prof = spk.spike_profile_multi(spike_trains) -spike = spk.spike_distance_multi(spike_trains) diff --git a/test/test_regression/test_regression_15.py b/test/test_regression/test_regression_15.py new file mode 100644 index 0000000..1ce1290 --- /dev/null +++ b/test/test_regression/test_regression_15.py @@ -0,0 +1,78 @@ +""" test_regression_15.py + +Regression test for Issue #15 + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License + +""" + +import numpy as np +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_almost_equal + +import pyspike as spk + + +def test_regression_15_isi(): + # load spike trains + spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", + edges=[0, 4000]) + + N = len(spike_trains) + + dist_mat = spk.isi_distance_matrix(spike_trains) + assert_equal(dist_mat.shape, (N, N)) + + ind = np.arange(N/2) + dist_mat = spk.isi_distance_matrix(spike_trains, ind) + assert_equal(dist_mat.shape, (N/2, N/2)) + + ind = np.arange(N/2, N) + dist_mat = spk.isi_distance_matrix(spike_trains, ind) + assert_equal(dist_mat.shape, (N/2, N/2)) + + +def test_regression_15_spike(): + # load spike trains + spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", + edges=[0, 4000]) + + N = len(spike_trains) + + dist_mat = spk.spike_distance_matrix(spike_trains) + assert_equal(dist_mat.shape, (N, N)) + + ind = np.arange(N/2) + dist_mat = spk.spike_distance_matrix(spike_trains, ind) + assert_equal(dist_mat.shape, (N/2, N/2)) + + ind = np.arange(N/2, N) + dist_mat = spk.spike_distance_matrix(spike_trains, ind) + assert_equal(dist_mat.shape, (N/2, N/2)) + + +def test_regression_15_sync(): + # load spike trains + spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", + edges=[0, 4000]) + + N = len(spike_trains) + + dist_mat = spk.spike_sync_matrix(spike_trains) + assert_equal(dist_mat.shape, (N, N)) + + ind = np.arange(N/2) + dist_mat = spk.spike_sync_matrix(spike_trains, ind) + assert_equal(dist_mat.shape, (N/2, N/2)) + + ind = np.arange(N/2, N) + dist_mat = spk.spike_sync_matrix(spike_trains, ind) + assert_equal(dist_mat.shape, (N/2, N/2)) + + +if __name__ == "__main__": + test_regression_15_isi() + test_regression_15_spike() + test_regression_15_sync() -- cgit v1.2.3 From 0d7255c1fb7398720de10653efee617075c30892 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 17 Aug 2015 15:05:08 +0200 Subject: fix for #14 test case and fix for Issue #14. Spike-Sync function now correctly deal with empty intervals as well. --- pyspike/DiscreteFunc.py | 43 +++++++++++++++++++++++-------------------- test/test_empty.py | 12 ++++++++++++ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index a8c054e..4fd496d 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -137,9 +137,8 @@ class DiscreteFunc(object): :rtype: pair of float """ - if len(self.y) <= 2: - # no actual values in the profile, return spike sync of 1 - return 1.0, 1.0 + value = 0.0 + multiplicity = 0.0 def get_indices(ival): """ Retuns the indeces surrounding the given interval""" @@ -152,25 +151,29 @@ class DiscreteFunc(object): if interval is None: # no interval given, integrate over the whole spike train # don't count the first value, which is zero by definition - return (1.0 * np.sum(self.y[1:-1]), np.sum(self.mp[1:-1])) - - # check if interval is as sequence - assert isinstance(interval, collections.Sequence), \ - "Invalid value for `interval`. None, Sequence or Tuple expected." - # check if interval is a sequence of intervals - if not isinstance(interval[0], collections.Sequence): - # find the indices corresponding to the interval - start_ind, end_ind = get_indices(interval) - return (np.sum(self.y[start_ind:end_ind]), - np.sum(self.mp[start_ind:end_ind])) + value = 1.0 * np.sum(self.y[1:-1]) + multiplicity = np.sum(self.mp[1:-1]) else: - value = 0.0 - multiplicity = 0.0 - for ival in interval: + # check if interval is as sequence + assert isinstance(interval, collections.Sequence), \ + "Invalid value for `interval`. None, Sequence or Tuple \ +expected." + # check if interval is a sequence of intervals + if not isinstance(interval[0], collections.Sequence): # find the indices corresponding to the interval - start_ind, end_ind = get_indices(ival) - value += np.sum(self.y[start_ind:end_ind]) - multiplicity += np.sum(self.mp[start_ind:end_ind]) + start_ind, end_ind = get_indices(interval) + value = np.sum(self.y[start_ind:end_ind]) + multiplicity = np.sum(self.mp[start_ind:end_ind]) + else: + for ival in interval: + # find the indices corresponding to the interval + start_ind, end_ind = get_indices(ival) + value += np.sum(self.y[start_ind:end_ind]) + multiplicity += np.sum(self.mp[start_ind:end_ind]) + if multiplicity == 0.0: + # empty profile, return spike sync of 1 + value = 1.0 + multiplicity = 1.0 return (value, multiplicity) def avrg(self, interval=None): diff --git a/test/test_empty.py b/test/test_empty.py index 48be25d..af7fb36 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -139,6 +139,18 @@ def test_spike_sync_empty(): assert_array_almost_equal(prof.x, [0.0, 0.2, 0.8, 1.0], decimal=15) assert_array_almost_equal(prof.y, [0.0, 0.0, 0.0, 0.0], decimal=15) + # test with empty intervals + st1 = SpikeTrain([2.0, 5.0], [0, 10.0]) + st2 = SpikeTrain([2.1, 7.0], [0, 10.0]) + st3 = SpikeTrain([5.1, 6.0], [0, 10.0]) + res = spk.spike_sync_profile(st1, st2).avrg(interval=[3.0, 4.0]) + assert_equal(res, 1.0) + res = spk.spike_sync(st1, st2, interval=[3.0, 4.0]) + assert_equal(res, 1.0) + + sync_matrix = spk.spike_sync_matrix([st1, st2, st3], interval=[3.0, 4.0]) + assert_array_equal(sync_matrix, np.ones((3, 3)) - np.diag(np.ones(3))) + if __name__ == "__main__": test_get_non_empty() -- cgit v1.2.3 From 84f3333317e119cc87c4cb5f9c444ff66f1f7a23 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 17 Aug 2015 17:19:18 +0200 Subject: first unit tests for directionality --- .../cython/cython_directionality.pyx | 4 +-- pyspike/directionality/spike_delay_asymmetry.py | 11 +++--- test/test_spike_delay_asymmetry.py | 41 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 test/test_spike_delay_asymmetry.py diff --git a/pyspike/directionality/cython/cython_directionality.pyx b/pyspike/directionality/cython/cython_directionality.pyx index 00e3b86..3936372 100644 --- a/pyspike/directionality/cython/cython_directionality.pyx +++ b/pyspike/directionality/cython/cython_directionality.pyx @@ -198,7 +198,7 @@ def spike_delay_asymmetry_cython(double[:] spikes1, double[:] spikes2, # coincidence between the current spike and the previous spike # spike in spike train 2 appeared before spike in spike train 1 # mark with -1 - asym -= 1 + asym -= 2 elif (j < N2-1) and (i == N1-1 or spikes1[i+1] > spikes2[j+1]): j += 1 mp += 1 @@ -207,7 +207,7 @@ def spike_delay_asymmetry_cython(double[:] spikes1, double[:] spikes2, # coincidence between the current spike and the previous spike # spike in spike train 1 appeared before spike in spike train 2 # mark with +1 - asym += 1 + asym += 2 else: # spikes1[i+1] = spikes2[j+1] # advance in both spike trains j += 1 diff --git a/pyspike/directionality/spike_delay_asymmetry.py b/pyspike/directionality/spike_delay_asymmetry.py index 0676f6d..7da49ee 100644 --- a/pyspike/directionality/spike_delay_asymmetry.py +++ b/pyspike/directionality/spike_delay_asymmetry.py @@ -64,7 +64,7 @@ def spike_delay_asymmetry_profile(spike_train1, spike_train2, max_tau=None): ############################################################ # spike_delay_asymmetry ############################################################ -def spike_delay_asymmetry(spike_train1, spike_train2, +def spike_delay_asymmetry(spike_train1, spike_train2, normalize=True, interval=None, max_tau=None): """ Computes the overall spike delay asymmetry value for two spike trains. """ @@ -81,7 +81,10 @@ def spike_delay_asymmetry(spike_train1, spike_train2, spike_train1.t_start, spike_train1.t_end, max_tau) - return c + if normalize: + return 1.0*c/mp + else: + return c except ImportError: # Cython backend not available: fall back to profile averaging raise NotImplementedError() @@ -123,7 +126,7 @@ def spike_delay_asymmetry_profile_multi(spike_trains, indices=None, ############################################################ # spike_delay_asymmetry_matrix ############################################################ -def spike_delay_asymmetry_matrix(spike_trains, indices=None, +def spike_delay_asymmetry_matrix(spike_trains, normalize=True, indices=None, interval=None, max_tau=None): """ Computes the spike delay asymmetry matrix for the given spike trains. """ @@ -139,7 +142,7 @@ def spike_delay_asymmetry_matrix(spike_trains, indices=None, distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: - d = spike_delay_asymmetry(spike_trains[i], spike_trains[j], + d = spike_delay_asymmetry(spike_trains[i], spike_trains[j], normalize, interval, max_tau=max_tau) distance_matrix[i, j] = d distance_matrix[j, i] = -d diff --git a/test/test_spike_delay_asymmetry.py b/test/test_spike_delay_asymmetry.py new file mode 100644 index 0000000..93c18de --- /dev/null +++ b/test/test_spike_delay_asymmetry.py @@ -0,0 +1,41 @@ +""" test_spike_delay_asymmetry.py + +Tests the asymmetry functions + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License + +""" + +import numpy as np +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_equal + +import pyspike as spk +from pyspike import SpikeTrain, DiscreteFunc + + +def test_profile(): + st1 = SpikeTrain([100, 200, 300], [0, 1000]) + st2 = SpikeTrain([105, 205, 300], [0, 1000]) + expected_x = np.array([0, 100, 105, 200, 205, 300, 1000]) + expected_y = np.array([1, 1, 1, 1, 1, 0, 0]) + expected_mp = np.array([1, 1, 1, 1, 1, 2, 2]) + + f = spk.drct.spike_delay_asymmetry_profile(st1, st2) + + assert f.almost_equal(DiscreteFunc(expected_x, expected_y, expected_mp)) + assert_almost_equal(f.avrg(), 2.0/3.0) + assert_almost_equal(spk.drct.spike_delay_asymmetry(st1, st2), 2.0/3.0) + assert_almost_equal(spk.drct.spike_delay_asymmetry(st1, st2, + normalize=False), + 4.0) + + st3 = SpikeTrain([105, 195, 500], [0, 1000]) + expected_x = np.array([0, 100, 105, 195, 200, 300, 500, 1000]) + expected_y = np.array([1, 1, 1, -1, -1, 0, 0, 0]) + expected_mp = np.array([1, 1, 1, 1, 1, 1, 1, 1]) + + f = spk.drct.spike_delay_asymmetry_profile(st1, st3) + assert f.almost_equal(DiscreteFunc(expected_x, expected_y, expected_mp)) -- cgit v1.2.3 From 083a75679a2b15cceb13fa80ff1bb9277bd729d5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 17 Aug 2015 17:48:06 +0200 Subject: major renaming of spike train order functions --- pyspike/DiscreteFunc.py | 7 +- pyspike/directionality/__init__.py | 12 +- .../cython/cython_directionality.pyx | 24 +- pyspike/directionality/spike_delay_asymmetry.py | 266 --------------------- pyspike/directionality/spike_train_order.py | 265 ++++++++++++++++++++ test/test_spike_delay_asymmetry.py | 9 +- 6 files changed, 292 insertions(+), 291 deletions(-) delete mode 100644 pyspike/directionality/spike_delay_asymmetry.py create mode 100644 pyspike/directionality/spike_train_order.py diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 4fd496d..9cc7bd5 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -176,7 +176,7 @@ expected." multiplicity = 1.0 return (value, multiplicity) - def avrg(self, interval=None): + def avrg(self, interval=None, normalize=True): """ Computes the average of the interval sequence: :math:`a = 1/N \\sum f_n` where N is the number of intervals. @@ -189,7 +189,10 @@ expected." :rtype: float """ val, mp = self.integral(interval) - return val/mp + if normalize: + return val/mp + else: + return val def add(self, f): """ Adds another `DiscreteFunc` function to this function. diff --git a/pyspike/directionality/__init__.py b/pyspike/directionality/__init__.py index 9a45b54..6f74c50 100644 --- a/pyspike/directionality/__init__.py +++ b/pyspike/directionality/__init__.py @@ -4,10 +4,10 @@ Copyright 2015, Mario Mulansky Distributed under the BSD License """ -__all__ = ["spike_delay_asymmetry"] +__all__ = ["spike_train_order"] -from spike_delay_asymmetry import spike_delay_asymmetry_profile, \ - spike_delay_asymmetry, spike_delay_asymmetry_profile_multi, \ - spike_delay_asymmetry_matrix, spike_delay_asymmetry_full, \ - optimal_asymmetry_order, optimal_asymmetry_order_from_D, \ - permutate_asymmetry_matrix +from spike_train_order import spike_train_order_profile, \ + spike_train_order, spike_train_order_profile_multi, \ + spike_train_order_matrix, spike_order_values, \ + optimal_spike_train_order, optimal_spike_train_order_from_matrix, \ + permutate_matrix diff --git a/pyspike/directionality/cython/cython_directionality.pyx b/pyspike/directionality/cython/cython_directionality.pyx index 3936372..e1f63c4 100644 --- a/pyspike/directionality/cython/cython_directionality.pyx +++ b/pyspike/directionality/cython/cython_directionality.pyx @@ -61,11 +61,11 @@ cdef inline double get_tau(double[:] spikes1, double[:] spikes2, ############################################################ -# spike_delay_asymmetry_profile_cython +# spike_train_order_profile_cython ############################################################ -def spike_delay_asymmetry_profile_cython(double[:] spikes1, double[:] spikes2, - double t_start, double t_end, - double max_tau): +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) @@ -130,12 +130,12 @@ def spike_delay_asymmetry_profile_cython(double[:] spikes1, double[:] spikes2, ############################################################ -# spike_delay_dual_profile_cython +# spike_order_values_cython ############################################################ -def spike_delay_dual_profile_cython(double[:] spikes1, - double[:] spikes2, - double t_start, double t_end, - double max_tau): +def spike_order_values_cython(double[:] spikes1, + double[:] spikes2, + double t_start, double t_end, + double max_tau): cdef int N1 = len(spikes1) cdef int N2 = len(spikes2) @@ -176,10 +176,10 @@ def spike_delay_dual_profile_cython(double[:] spikes1, ############################################################ -# spike_delay_asymmetry_cython +# spike_train_order_cython ############################################################ -def spike_delay_asymmetry_cython(double[:] spikes1, double[:] spikes2, - double t_start, double t_end, double max_tau): +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) diff --git a/pyspike/directionality/spike_delay_asymmetry.py b/pyspike/directionality/spike_delay_asymmetry.py deleted file mode 100644 index 7da49ee..0000000 --- a/pyspike/directionality/spike_delay_asymmetry.py +++ /dev/null @@ -1,266 +0,0 @@ -# Module containing functions to compute multivariate spike delay asymmetry -# Copyright 2015, Mario Mulansky -# Distributed under the BSD License - -import numpy as np -from math import exp -from functools import partial -# import pyspike -from pyspike import DiscreteFunc -from pyspike.generic import _generic_profile_multi - - -############################################################ -# spike_delay_asymmetry_profile -############################################################ -def spike_delay_asymmetry_profile(spike_train1, spike_train2, max_tau=None): - """ Computes the spike delay asymmetry profile A(t) of the two given - spike trains. Returns the profile as a DiscreteFunction object. - - :param spike_train1: First spike train. - :type spike_train1: :class:`pyspike.SpikeTrain` - :param spike_train2: Second spike train. - :type spike_train2: :class:`pyspike.SpikeTrain` - :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. - :returns: The spike-distance profile :math:`S_{sync}(t)`. - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - # check whether the spike trains are defined for the same interval - assert spike_train1.t_start == spike_train2.t_start, \ - "Given spike trains are not defined on the same interval!" - assert spike_train1.t_end == spike_train2.t_end, \ - "Given spike trains are not defined on the same interval!" - - # cython implementation - try: - from cython.cython_directionality import \ - spike_delay_asymmetry_profile_cython as \ - spike_delay_asymmetry_profile_impl - except ImportError: - raise NotImplementedError() -# if not(pyspike.disable_backend_warning): -# print("Warning: spike_distance_cython not found. Make sure that \ -# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -# Falling back to slow python backend.") -# # use python backend -# from cython.python_backend import coincidence_python \ -# as coincidence_profile_impl - - if max_tau is None: - max_tau = 0.0 - - times, coincidences, multiplicity \ - = spike_delay_asymmetry_profile_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end, - max_tau) - - return DiscreteFunc(times, coincidences, multiplicity) - - -############################################################ -# spike_delay_asymmetry -############################################################ -def spike_delay_asymmetry(spike_train1, spike_train2, normalize=True, - interval=None, max_tau=None): - """ Computes the overall spike delay asymmetry value for two spike trains. - """ - if interval is None: - # distance over the whole interval is requested: use specific function - # for optimal performance - try: - from cython.cython_directionality import \ - spike_delay_asymmetry_cython as spike_delay_impl - if max_tau is None: - max_tau = 0.0 - c, mp = spike_delay_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end, - max_tau) - if normalize: - return 1.0*c/mp - else: - return c - except ImportError: - # Cython backend not available: fall back to profile averaging - raise NotImplementedError() - # return spike_sync_profile(spike_train1, spike_train2, - # max_tau).integral(interval) - else: - # some specific interval is provided: not yet implemented - raise NotImplementedError() - - -############################################################ -# spike_delay_asymmetry_profile_multi -############################################################ -def spike_delay_asymmetry_profile_multi(spike_trains, indices=None, - max_tau=None): - """ Computes the multi-variate spike delay asymmetry profile for a set of - spike trains. For each spike in the set of spike trains, the multi-variate - profile is defined as the sum of asymmetry values divided by the number of - spike trains pairs involving the spike train of containing this spike, - which is the number of spike trains minus one (N-1). - - :param spike_trains: list of :class:`pyspike.SpikeTrain` - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. - :returns: The multi-variate spike sync profile :math:`(t)` - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - prof_func = partial(spike_delay_asymmetry_profile, max_tau=max_tau) - average_prof, M = _generic_profile_multi(spike_trains, prof_func, - indices) - # average_dist.mul_scalar(1.0/M) # no normalization here! - return average_prof - - -############################################################ -# spike_delay_asymmetry_matrix -############################################################ -def spike_delay_asymmetry_matrix(spike_trains, normalize=True, indices=None, - interval=None, max_tau=None): - """ Computes the spike delay asymmetry matrix for the given spike trains. - """ - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - - distance_matrix = np.zeros((len(indices), len(indices))) - for i, j in pairs: - d = spike_delay_asymmetry(spike_trains[i], spike_trains[j], normalize, - interval, max_tau=max_tau) - distance_matrix[i, j] = d - distance_matrix[j, i] = -d - return distance_matrix - - -############################################################ -# spike_delay_asymmetry_full -############################################################ -def spike_delay_asymmetry_full(spike_trains, indices=None, - interval=None, max_tau=None): - """ Computes the spike train symmetry value for each spike in each spike - train. - """ - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # list of arrays for reulting asymmetry values - asymmetry_list = [np.zeros_like(st.spikes) for st in spike_trains] - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - - # cython implementation - try: - from cython.cython_directionality import \ - spike_delay_dual_profile_cython as \ - sda_dual_profile_impl - except ImportError: - raise NotImplementedError() -# if not(pyspike.disable_backend_warning): -# print("Warning: spike_distance_cython not found. Make sure that \ -# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -# Falling back to slow python backend.") -# # use python backend -# from cython.python_backend import coincidence_python \ -# as coincidence_profile_impl - - if max_tau is None: - max_tau = 0.0 - - for i, j in pairs: - a1, a2 = sda_dual_profile_impl(spike_trains[i].spikes, - spike_trains[j].spikes, - spike_trains[i].t_start, - spike_trains[i].t_end, - max_tau) - asymmetry_list[i] += a1 - asymmetry_list[j] += a2 - for a in asymmetry_list: - a /= len(spike_trains)-1 - return asymmetry_list - - -############################################################ -# optimal_asymmetry_order_from_D -############################################################ -def optimal_asymmetry_order_from_D(D, full_output=False): - """ finds the best sorting via simulated annealing. - Returns the optimal permutation p and A value. - Internal function, don't call directly! Use optimal_asymmetry_order - instead. - """ - N = len(D) - A = np.sum(np.triu(D, 0)) - - p = np.arange(N) - - T = 2*np.max(D) # starting temperature - T_end = 1E-5 * T # final temperature - alpha = 0.9 # cooling factor - total_iter = 0 - while T > T_end: - iterations = 0 - succ_iter = 0 - while iterations < 100*N and succ_iter < 10*N: - # exchange two rows and cols - ind1 = np.random.randint(N-1) - delta_A = -2*D[p[ind1], p[ind1+1]] - if delta_A > 0.0 or exp(delta_A/T) > np.random.random(): - # swap indices - p[ind1], p[ind1+1] = p[ind1+1], p[ind1] - A += delta_A - succ_iter += 1 - iterations += 1 - total_iter += iterations - T *= alpha # cool down - if succ_iter == 0: - break - if full_output: - return p, A, total_iter - else: - return p, A - - -############################################################ -# optimal_asymmetry_order -############################################################ -def optimal_asymmetry_order(spike_trains, indices=None, interval=None, - max_tau=None, full_output=False): - """ finds the best sorting of the given spike trains via simulated - annealing. - Returns the optimal permutation p and A value. - """ - D = spike_delay_asymmetry_matrix(spike_trains, indices, interval, max_tau) - return optimal_asymmetry_order_from_D(D, full_output) - - -############################################################ -# reorder_asymmetry_matrix -############################################################ -def permutate_asymmetry_matrix(D, p): - N = len(D) - D_p = np.empty_like(D) - for n in xrange(N): - for m in xrange(N): - D_p[n, m] = D[p[n], p[m]] - return D_p diff --git a/pyspike/directionality/spike_train_order.py b/pyspike/directionality/spike_train_order.py new file mode 100644 index 0000000..f8c8615 --- /dev/null +++ b/pyspike/directionality/spike_train_order.py @@ -0,0 +1,265 @@ +# Module containing functions to compute multivariate spike train order +# Copyright 2015, Mario Mulansky +# Distributed under the BSD License + +import numpy as np +from math import exp +from functools import partial +# import pyspike +from pyspike import DiscreteFunc +from pyspike.generic import _generic_profile_multi + + +############################################################ +# spike_train_order_profile +############################################################ +def spike_train_order_profile(spike_train1, spike_train2, max_tau=None): + """ Computes the spike delay asymmetry profile A(t) of the two given + spike trains. Returns the profile as a DiscreteFunction object. + + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike-distance profile :math:`S_{sync}(t)`. + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + # check whether the spike trains are defined for the same interval + assert spike_train1.t_start == spike_train2.t_start, \ + "Given spike trains are not defined on the same interval!" + assert spike_train1.t_end == spike_train2.t_end, \ + "Given spike trains are not defined on the same interval!" + + # cython implementation + try: + from cython.cython_directionality import \ + spike_train_order_profile_cython as \ + spike_train_order_profile_impl + except ImportError: + raise NotImplementedError() +# if not(pyspike.disable_backend_warning): +# print("Warning: spike_distance_cython not found. Make sure that \ +# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +# Falling back to slow python backend.") +# # use python backend +# from cython.python_backend import coincidence_python \ +# as coincidence_profile_impl + + if max_tau is None: + max_tau = 0.0 + + times, coincidences, multiplicity \ + = spike_train_order_profile_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + + return DiscreteFunc(times, coincidences, multiplicity) + + +############################################################ +# spike_train_order +############################################################ +def spike_train_order(spike_train1, spike_train2, normalize=True, + interval=None, max_tau=None): + """ Computes the overall spike delay asymmetry value for two spike trains. + """ + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from cython.cython_directionality import \ + spike_train_order_cython as spike_train_order_impl + if max_tau is None: + max_tau = 0.0 + c, mp = spike_train_order_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + if normalize: + return 1.0*c/mp + else: + return c + except ImportError: + # Cython backend not available: fall back to profile averaging + raise NotImplementedError() + # return spike_sync_profile(spike_train1, spike_train2, + # max_tau).integral(interval) + else: + # some specific interval is provided: not yet implemented + raise NotImplementedError() + + +############################################################ +# spike_train_order_profile_multi +############################################################ +def spike_train_order_profile_multi(spike_trains, indices=None, + max_tau=None): + """ Computes the multi-variate spike delay asymmetry profile for a set of + spike trains. For each spike in the set of spike trains, the multi-variate + profile is defined as the sum of asymmetry values divided by the number of + spike trains pairs involving the spike train of containing this spike, + which is the number of spike trains minus one (N-1). + + :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The multi-variate spike sync profile :math:`(t)` + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + prof_func = partial(spike_train_order_profile, max_tau=max_tau) + average_prof, M = _generic_profile_multi(spike_trains, prof_func, + indices) + # average_dist.mul_scalar(1.0/M) # no normalization here! + return average_prof + + +############################################################ +# spike_train_order_matrix +############################################################ +def spike_train_order_matrix(spike_trains, normalize=True, indices=None, + interval=None, max_tau=None): + """ Computes the spike delay asymmetry matrix for the given spike trains. + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + distance_matrix = np.zeros((len(indices), len(indices))) + for i, j in pairs: + d = spike_train_order(spike_trains[i], spike_trains[j], normalize, + interval, max_tau=max_tau) + distance_matrix[i, j] = d + distance_matrix[j, i] = -d + return distance_matrix + + +############################################################ +# spike_order_values +############################################################ +def spike_order_values(spike_trains, indices=None, + interval=None, max_tau=None): + """ Computes the spike train symmetry value for each spike in each spike + train. + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # list of arrays for reulting asymmetry values + asymmetry_list = [np.zeros_like(st.spikes) for st in spike_trains] + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + # cython implementation + try: + from cython.cython_directionality import \ + spike_order_values_cython as spike_order_values_impl + except ImportError: + raise NotImplementedError() +# if not(pyspike.disable_backend_warning): +# print("Warning: spike_distance_cython not found. Make sure that \ +# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +# Falling back to slow python backend.") +# # use python backend +# from cython.python_backend import coincidence_python \ +# as coincidence_profile_impl + + if max_tau is None: + max_tau = 0.0 + + for i, j in pairs: + a1, a2 = spike_order_values_impl(spike_trains[i].spikes, + spike_trains[j].spikes, + spike_trains[i].t_start, + spike_trains[i].t_end, + max_tau) + asymmetry_list[i] += a1 + asymmetry_list[j] += a2 + for a in asymmetry_list: + a /= len(spike_trains)-1 + return asymmetry_list + + +############################################################ +# optimal_asymmetry_order_from_matrix +############################################################ +def optimal_spike_train_order_from_matrix(D, full_output=False): + """ finds the best sorting via simulated annealing. + Returns the optimal permutation p and A value. + Internal function, don't call directly! Use optimal_asymmetry_order + instead. + """ + N = len(D) + A = np.sum(np.triu(D, 0)) + + p = np.arange(N) + + T = 2*np.max(D) # starting temperature + T_end = 1E-5 * T # final temperature + alpha = 0.9 # cooling factor + total_iter = 0 + while T > T_end: + iterations = 0 + succ_iter = 0 + while iterations < 100*N and succ_iter < 10*N: + # exchange two rows and cols + ind1 = np.random.randint(N-1) + delta_A = -2*D[p[ind1], p[ind1+1]] + if delta_A > 0.0 or exp(delta_A/T) > np.random.random(): + # swap indices + p[ind1], p[ind1+1] = p[ind1+1], p[ind1] + A += delta_A + succ_iter += 1 + iterations += 1 + total_iter += iterations + T *= alpha # cool down + if succ_iter == 0: + break + if full_output: + return p, A, total_iter + else: + return p, A + + +############################################################ +# optimal_spike_train_order +############################################################ +def optimal_spike_train_order(spike_trains, indices=None, interval=None, + max_tau=None, full_output=False): + """ finds the best sorting of the given spike trains via simulated + annealing. + Returns the optimal permutation p and A value. + """ + D = spike_train_order_matrix(spike_trains, indices, interval, max_tau) + return optimal_asymmetry_order_from_matrix(D, full_output) + + +############################################################ +# permutate_matrix +############################################################ +def permutate_matrix(D, p): + N = len(D) + D_p = np.empty_like(D) + for n in xrange(N): + for m in xrange(N): + D_p[n, m] = D[p[n], p[m]] + return D_p diff --git a/test/test_spike_delay_asymmetry.py b/test/test_spike_delay_asymmetry.py index 93c18de..9de16e5 100644 --- a/test/test_spike_delay_asymmetry.py +++ b/test/test_spike_delay_asymmetry.py @@ -23,13 +23,12 @@ def test_profile(): expected_y = np.array([1, 1, 1, 1, 1, 0, 0]) expected_mp = np.array([1, 1, 1, 1, 1, 2, 2]) - f = spk.drct.spike_delay_asymmetry_profile(st1, st2) + f = spk.drct.spike_train_order_profile(st1, st2) assert f.almost_equal(DiscreteFunc(expected_x, expected_y, expected_mp)) assert_almost_equal(f.avrg(), 2.0/3.0) - assert_almost_equal(spk.drct.spike_delay_asymmetry(st1, st2), 2.0/3.0) - assert_almost_equal(spk.drct.spike_delay_asymmetry(st1, st2, - normalize=False), + assert_almost_equal(spk.drct.spike_train_order(st1, st2), 2.0/3.0) + assert_almost_equal(spk.drct.spike_train_order(st1, st2, normalize=False), 4.0) st3 = SpikeTrain([105, 195, 500], [0, 1000]) @@ -37,5 +36,5 @@ def test_profile(): expected_y = np.array([1, 1, 1, -1, -1, 0, 0, 0]) expected_mp = np.array([1, 1, 1, 1, 1, 1, 1, 1]) - f = spk.drct.spike_delay_asymmetry_profile(st1, st3) + f = spk.drct.spike_train_order_profile(st1, st3) assert f.almost_equal(DiscreteFunc(expected_x, expected_y, expected_mp)) -- cgit v1.2.3 From 691bf73c06322a2c47c37a5c48d085b789c8e8bf Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 17 Aug 2015 18:02:24 +0200 Subject: add python backend for directionality --- .../cython/directionality_python_backend.py | 89 ++++++++++++++++++++++ pyspike/directionality/spike_train_order.py | 33 ++++---- 2 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 pyspike/directionality/cython/directionality_python_backend.py diff --git a/pyspike/directionality/cython/directionality_python_backend.py b/pyspike/directionality/cython/directionality_python_backend.py new file mode 100644 index 0000000..e14238f --- /dev/null +++ b/pyspike/directionality/cython/directionality_python_backend.py @@ -0,0 +1,89 @@ +""" directionality_python_backend.py + +Collection of python functions that can be used instead of the cython +implementation. + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License + +""" + +import numpy as np + + +############################################################ +# spike_train_order_python +############################################################ +def spike_train_order_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/directionality/spike_train_order.py b/pyspike/directionality/spike_train_order.py index f8c8615..892ffd0 100644 --- a/pyspike/directionality/spike_train_order.py +++ b/pyspike/directionality/spike_train_order.py @@ -5,7 +5,7 @@ import numpy as np from math import exp from functools import partial -# import pyspike +import pyspike from pyspike import DiscreteFunc from pyspike.generic import _generic_profile_multi @@ -39,14 +39,14 @@ def spike_train_order_profile(spike_train1, spike_train2, max_tau=None): spike_train_order_profile_cython as \ spike_train_order_profile_impl except ImportError: - raise NotImplementedError() -# if not(pyspike.disable_backend_warning): -# print("Warning: spike_distance_cython not found. Make sure that \ -# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -# Falling back to slow python backend.") -# # use python backend -# from cython.python_backend import coincidence_python \ -# as coincidence_profile_impl + # raise NotImplementedError() + if not(pyspike.disable_backend_warning): + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from cython.directionality_python_backend import \ + spike_train_order_python as spike_train_order_profile_impl if max_tau is None: max_tau = 0.0 @@ -81,15 +81,14 @@ def spike_train_order(spike_train1, spike_train2, normalize=True, spike_train1.t_start, spike_train1.t_end, max_tau) - if normalize: - return 1.0*c/mp - else: - return c except ImportError: # Cython backend not available: fall back to profile averaging - raise NotImplementedError() - # return spike_sync_profile(spike_train1, spike_train2, - # max_tau).integral(interval) + c, mp = spike_train_order_profile(spike_train1, spike_train2, + max_tau).integral(interval) + if normalize: + return 1.0*c/mp + else: + return c else: # some specific interval is provided: not yet implemented raise NotImplementedError() @@ -250,7 +249,7 @@ def optimal_spike_train_order(spike_trains, indices=None, interval=None, Returns the optimal permutation p and A value. """ D = spike_train_order_matrix(spike_trains, indices, interval, max_tau) - return optimal_asymmetry_order_from_matrix(D, full_output) + return optimal_spike_train_order_from_matrix(D, full_output) ############################################################ -- cgit v1.2.3 From eeb4918ec2181f136e85bce976ec46a35a74b8f1 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Sun, 13 Dec 2015 10:55:30 +0100 Subject: py3: absolute_import Signed-off-by: Igor Gnatenko --- pyspike/DiscreteFunc.py | 6 +++--- pyspike/PieceWiseConstFunc.py | 6 +++--- pyspike/PieceWiseLinFunc.py | 8 ++++---- pyspike/__init__.py | 22 ++++++++++++---------- pyspike/directionality/__init__.py | 4 +++- pyspike/directionality/spike_train_order.py | 12 +++++++----- pyspike/isi_distance.py | 8 +++++--- pyspike/spike_distance.py | 8 +++++--- pyspike/spike_sync.py | 8 +++++--- 9 files changed, 47 insertions(+), 35 deletions(-) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 9cc7bd5..55c0bc8 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -2,7 +2,7 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License -from __future__ import print_function +from __future__ import absolute_import, print_function import numpy as np import collections @@ -206,7 +206,7 @@ expected." # cython version try: - from cython.cython_add import add_discrete_function_cython as \ + from .cython.cython_add import add_discrete_function_cython as \ add_discrete_function_impl except ImportError: if not(pyspike.disable_backend_warning): @@ -215,7 +215,7 @@ sure that PySpike is installed by running\n\ 'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") # use python backend - from cython.python_backend import add_discrete_function_python as \ + from .cython.python_backend import add_discrete_function_python as \ add_discrete_function_impl self.x, self.y, self.mp = \ diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index 23ff536..5ce5f27 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -2,7 +2,7 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License -from __future__ import print_function +from __future__ import absolute_import, print_function import numpy as np import collections @@ -189,7 +189,7 @@ class PieceWiseConstFunc(object): # cython version try: - from cython.cython_add import add_piece_wise_const_cython as \ + from .cython.cython_add import add_piece_wise_const_cython as \ add_piece_wise_const_impl except ImportError: if not(pyspike.disable_backend_warning): @@ -198,7 +198,7 @@ sure that PySpike is installed by running\n \ 'python setup.py build_ext --inplace'! \ \n Falling back to slow python backend.") # use python backend - from cython.python_backend import add_piece_wise_const_python as \ + from .cython.python_backend import add_piece_wise_const_python as \ add_piece_wise_const_impl self.x, self.y = add_piece_wise_const_impl(self.x, self.y, f.x, f.y) diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index 0d51c76..8145e63 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -2,7 +2,7 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License -from __future__ import print_function +from __future__ import absolute_import, print_function import numpy as np import collections @@ -222,13 +222,13 @@ class PieceWiseLinFunc: assert self.x[-1] == f.x[-1], "The functions have different intervals" # python implementation - # from python_backend import add_piece_wise_lin_python + # from .python_backend import add_piece_wise_lin_python # self.x, self.y1, self.y2 = add_piece_wise_lin_python( # self.x, self.y1, self.y2, f.x, f.y1, f.y2) # cython version try: - from cython.cython_add import add_piece_wise_lin_cython as \ + from .cython.cython_add import add_piece_wise_lin_cython as \ add_piece_wise_lin_impl except ImportError: if not(pyspike.disable_backend_warning): @@ -237,7 +237,7 @@ sure that PySpike is installed by running\n \ 'python setup.py build_ext --inplace'! \n \ Falling back to slow python backend.") # use python backend - from cython.python_backend import add_piece_wise_lin_python as \ + from .cython.python_backend import add_piece_wise_lin_python as \ add_piece_wise_lin_impl self.x, self.y1, self.y2 = add_piece_wise_lin_impl( diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 8d92ea4..335b1d3 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -4,27 +4,29 @@ Copyright 2014-2015, Mario Mulansky Distributed under the BSD License """ +from __future__ import absolute_import + __all__ = ["isi_distance", "spike_distance", "spike_sync", "psth", "spikes", "SpikeTrain", "PieceWiseConstFunc", "PieceWiseLinFunc", "DiscreteFunc", "directionality"] -from PieceWiseConstFunc import PieceWiseConstFunc -from PieceWiseLinFunc import PieceWiseLinFunc -from DiscreteFunc import DiscreteFunc -from SpikeTrain import SpikeTrain +from .PieceWiseConstFunc import PieceWiseConstFunc +from .PieceWiseLinFunc import PieceWiseLinFunc +from .DiscreteFunc import DiscreteFunc +from .SpikeTrain import SpikeTrain -from isi_distance import isi_profile, isi_distance, isi_profile_multi,\ +from .isi_distance import isi_profile, isi_distance, isi_profile_multi,\ isi_distance_multi, isi_distance_matrix -from spike_distance import spike_profile, spike_distance, spike_profile_multi,\ +from .spike_distance import spike_profile, spike_distance, spike_profile_multi,\ spike_distance_multi, spike_distance_matrix -from spike_sync import spike_sync_profile, spike_sync,\ +from .spike_sync import spike_sync_profile, spike_sync,\ spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix -from psth import psth +from .psth import psth -from spikes import load_spike_trains_from_txt, spike_train_from_string, \ +from .spikes import load_spike_trains_from_txt, spike_train_from_string, \ merge_spike_trains, generate_poisson_spikes -import directionality as drct +from . import directionality as drct # define the __version__ following # http://stackoverflow.com/questions/17583443 diff --git a/pyspike/directionality/__init__.py b/pyspike/directionality/__init__.py index 6f74c50..6ea38b2 100644 --- a/pyspike/directionality/__init__.py +++ b/pyspike/directionality/__init__.py @@ -4,9 +4,11 @@ Copyright 2015, Mario Mulansky Distributed under the BSD License """ +from __future__ import absolute_import + __all__ = ["spike_train_order"] -from spike_train_order import spike_train_order_profile, \ +from .spike_train_order import spike_train_order_profile, \ spike_train_order, spike_train_order_profile_multi, \ spike_train_order_matrix, spike_order_values, \ optimal_spike_train_order, optimal_spike_train_order_from_matrix, \ diff --git a/pyspike/directionality/spike_train_order.py b/pyspike/directionality/spike_train_order.py index 892ffd0..44d931d 100644 --- a/pyspike/directionality/spike_train_order.py +++ b/pyspike/directionality/spike_train_order.py @@ -2,6 +2,8 @@ # Copyright 2015, Mario Mulansky # Distributed under the BSD License +from __future__ import absolute_import + import numpy as np from math import exp from functools import partial @@ -35,7 +37,7 @@ def spike_train_order_profile(spike_train1, spike_train2, max_tau=None): # cython implementation try: - from cython.cython_directionality import \ + from .cython.cython_directionality import \ spike_train_order_profile_cython as \ spike_train_order_profile_impl except ImportError: @@ -45,7 +47,7 @@ def spike_train_order_profile(spike_train1, spike_train2, max_tau=None): PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from cython.directionality_python_backend import \ + from .cython.directionality_python_backend import \ spike_train_order_python as spike_train_order_profile_impl if max_tau is None: @@ -72,7 +74,7 @@ def spike_train_order(spike_train1, spike_train2, normalize=True, # distance over the whole interval is requested: use specific function # for optimal performance try: - from cython.cython_directionality import \ + from .cython.cython_directionality import \ spike_train_order_cython as spike_train_order_impl if max_tau is None: max_tau = 0.0 @@ -170,7 +172,7 @@ def spike_order_values(spike_trains, indices=None, # cython implementation try: - from cython.cython_directionality import \ + from .cython.cython_directionality import \ spike_order_values_cython as spike_order_values_impl except ImportError: raise NotImplementedError() @@ -179,7 +181,7 @@ def spike_order_values(spike_trains, indices=None, # PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ # Falling back to slow python backend.") # # use python backend -# from cython.python_backend import coincidence_python \ +# from .cython.python_backend import coincidence_python \ # as coincidence_profile_impl if max_tau is None: diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index e50f203..0ae7393 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -2,6 +2,8 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License +from __future__ import absolute_import + import pyspike from pyspike import PieceWiseConstFunc from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ @@ -32,7 +34,7 @@ def isi_profile(spike_train1, spike_train2): # load cython implementation try: - from cython.cython_profiles import isi_profile_cython \ + from .cython.cython_profiles import isi_profile_cython \ as isi_profile_impl except ImportError: if not(pyspike.disable_backend_warning): @@ -40,7 +42,7 @@ def isi_profile(spike_train1, spike_train2): PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from cython.python_backend import isi_distance_python \ + from .cython.python_backend import isi_distance_python \ as isi_profile_impl times, values = isi_profile_impl(spike_train1.get_spikes_non_empty(), @@ -74,7 +76,7 @@ def isi_distance(spike_train1, spike_train2, interval=None): # distance over the whole interval is requested: use specific function # for optimal performance try: - from cython.cython_distances import isi_distance_cython \ + from .cython.cython_distances import isi_distance_cython \ as isi_distance_impl return isi_distance_impl(spike_train1.get_spikes_non_empty(), diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index feea0c1..e418283 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -2,6 +2,8 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License +from __future__ import absolute_import + import pyspike from pyspike import PieceWiseLinFunc from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ @@ -32,7 +34,7 @@ def spike_profile(spike_train1, spike_train2): # cython implementation try: - from cython.cython_profiles import spike_profile_cython \ + from .cython.cython_profiles import spike_profile_cython \ as spike_profile_impl except ImportError: if not(pyspike.disable_backend_warning): @@ -40,7 +42,7 @@ def spike_profile(spike_train1, spike_train2): PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from cython.python_backend import spike_distance_python \ + from .cython.python_backend import spike_distance_python \ as spike_profile_impl times, y_starts, y_ends = spike_profile_impl( @@ -76,7 +78,7 @@ def spike_distance(spike_train1, spike_train2, interval=None): # distance over the whole interval is requested: use specific function # for optimal performance try: - from cython.cython_distances import spike_distance_cython \ + from .cython.cython_distances import spike_distance_cython \ as spike_distance_impl return spike_distance_impl(spike_train1.get_spikes_non_empty(), spike_train2.get_spikes_non_empty(), diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 10ebdc7..3dc29ff 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -3,6 +3,8 @@ # Copyright 2014-2015, Mario Mulansky # Distributed under the BSD License +from __future__ import absolute_import + import numpy as np from functools import partial import pyspike @@ -37,7 +39,7 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): # cython implementation try: - from cython.cython_profiles import coincidence_profile_cython \ + from .cython.cython_profiles import coincidence_profile_cython \ as coincidence_profile_impl except ImportError: if not(pyspike.disable_backend_warning): @@ -45,7 +47,7 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ Falling back to slow python backend.") # use python backend - from cython.python_backend import coincidence_python \ + from .cython.python_backend import coincidence_python \ as coincidence_profile_impl if max_tau is None: @@ -73,7 +75,7 @@ def _spike_sync_values(spike_train1, spike_train2, interval, max_tau): # distance over the whole interval is requested: use specific function # for optimal performance try: - from cython.cython_distances import coincidence_value_cython \ + from .cython.cython_distances import coincidence_value_cython \ as coincidence_value_impl if max_tau is None: max_tau = 0.0 -- cgit v1.2.3 From 2c42e59e5097d3b9745e6eae2bee8f1ff27f7e09 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Sun, 13 Dec 2015 10:57:12 +0100 Subject: py3: xrange() -> range() Signed-off-by: Igor Gnatenko --- examples/multivariate.py | 2 +- examples/performance.py | 2 +- pyspike/DiscreteFunc.py | 4 ++-- pyspike/directionality/spike_train_order.py | 4 ++-- pyspike/generic.py | 4 ++-- pyspike/psth.py | 2 +- test/test_distance.py | 6 +++--- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/multivariate.py b/examples/multivariate.py index 9a44758..93f8516 100644 --- a/examples/multivariate.py +++ b/examples/multivariate.py @@ -24,7 +24,7 @@ t_loading = time.clock() print("Number of spike trains: %d" % len(spike_trains)) num_of_spikes = sum([len(spike_trains[i]) - for i in xrange(len(spike_trains))]) + for i in range(len(spike_trains))]) print("Number of spikes: %d" % num_of_spikes) # calculate the multivariate spike distance diff --git a/examples/performance.py b/examples/performance.py index d0c3b91..ec6c830 100644 --- a/examples/performance.py +++ b/examples/performance.py @@ -26,7 +26,7 @@ print("%d spike trains with %d spikes" % (M, int(r*T))) spike_trains = [] t_start = datetime.now() -for i in xrange(M): +for i in range(M): spike_trains.append(spk.generate_poisson_spikes(r, T)) t_end = datetime.now() runtime = (t_end-t_start).total_seconds() diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index 55c0bc8..fe97bc2 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -80,7 +80,7 @@ class DiscreteFunc(object): expected_mp = (averaging_window_size+1) * int(self.mp[0]) y_plot = np.zeros_like(self.y) # compute the values in a loop, could be done in cython if required - for i in xrange(len(y_plot)): + for i in range(len(y_plot)): if self.mp[i] >= expected_mp: # the current value contains already all the wanted @@ -244,7 +244,7 @@ def average_profile(profiles): assert len(profiles) > 1 avrg_profile = profiles[0].copy() - for i in xrange(1, len(profiles)): + for i in range(1, len(profiles)): avrg_profile.add(profiles[i]) avrg_profile.mul_scalar(1.0/len(profiles)) # normalize diff --git a/pyspike/directionality/spike_train_order.py b/pyspike/directionality/spike_train_order.py index 44d931d..e6c9830 100644 --- a/pyspike/directionality/spike_train_order.py +++ b/pyspike/directionality/spike_train_order.py @@ -260,7 +260,7 @@ def optimal_spike_train_order(spike_trains, indices=None, interval=None, def permutate_matrix(D, p): N = len(D) D_p = np.empty_like(D) - for n in xrange(N): - for m in xrange(N): + for n in range(N): + for m in range(N): D_p[n, m] = D[p[n], p[m]] return D_p diff --git a/pyspike/generic.py b/pyspike/generic.py index 904c3c2..81ae660 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -137,8 +137,8 @@ def _generic_distance_matrix(spike_trains, dist_function, assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ "Invalid index list." # generate a list of possible index pairs - pairs = [(i, j) for i in xrange(len(indices)) - for j in xrange(i+1, len(indices))] + pairs = [(i, j) for i in range(len(indices)) + for j in range(i+1, len(indices))] distance_matrix = np.zeros((len(indices), len(indices))) for i, j in pairs: diff --git a/pyspike/psth.py b/pyspike/psth.py index 4027215..7cf1140 100644 --- a/pyspike/psth.py +++ b/pyspike/psth.py @@ -24,7 +24,7 @@ def psth(spike_trains, bin_size): # N = len(spike_trains) combined_spike_train = spike_trains[0].spikes - for i in xrange(1, len(spike_trains)): + for i in range(1, len(spike_trains)): combined_spike_train = np.append(combined_spike_train, spike_trains[i].spikes) diff --git a/test/test_distance.py b/test/test_distance.py index e45ac16..d5bce30 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -309,10 +309,10 @@ def check_dist_matrix(dist_func, dist_matrix_func): f_matrix = dist_matrix_func(spike_trains) # check zero diagonal - for i in xrange(4): + for i in range(4): assert_equal(0.0, f_matrix[i, i]) - for i in xrange(4): - for j in xrange(i+1, 4): + for i in range(4): + for j in range(i+1, 4): assert_equal(f_matrix[i, j], f_matrix[j, i]) assert_equal(f12, f_matrix[1, 0]) assert_equal(f13, f_matrix[2, 0]) -- cgit v1.2.3 From fe1f6179cce645df2511bfedae3af90167308f5f Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Sun, 13 Dec 2015 11:01:34 +0100 Subject: py3: division Signed-off-by: Igor Gnatenko --- pyspike/generic.py | 5 +++-- test/test_regression/test_regression_15.py | 26 ++++++++++++++------------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/pyspike/generic.py b/pyspike/generic.py index 81ae660..5ad06f1 100644 --- a/pyspike/generic.py +++ b/pyspike/generic.py @@ -7,6 +7,7 @@ Copyright 2015, Mario Mulansky Distributed under the BSD License """ +from __future__ import division import numpy as np @@ -38,14 +39,14 @@ def _generic_profile_multi(spike_trains, pair_distance_func, indices=None): L1 = len(pairs1) if L1 > 1: dist_prof1 = divide_and_conquer(pairs1[:L1//2], - pairs1[int(L1//2):]) + pairs1[L1//2:]) else: dist_prof1 = pair_distance_func(spike_trains[pairs1[0][0]], spike_trains[pairs1[0][1]]) L2 = len(pairs2) if L2 > 1: dist_prof2 = divide_and_conquer(pairs2[:L2//2], - pairs2[int(L2//2):]) + pairs2[L2//2:]) else: dist_prof2 = pair_distance_func(spike_trains[pairs2[0][0]], spike_trains[pairs2[0][1]]) diff --git a/test/test_regression/test_regression_15.py b/test/test_regression/test_regression_15.py index 1ce1290..42a39ea 100644 --- a/test/test_regression/test_regression_15.py +++ b/test/test_regression/test_regression_15.py @@ -8,6 +8,8 @@ Distributed under the BSD License """ +from __future__ import division + import numpy as np from numpy.testing import assert_equal, assert_almost_equal, \ assert_array_almost_equal @@ -25,13 +27,13 @@ def test_regression_15_isi(): dist_mat = spk.isi_distance_matrix(spike_trains) assert_equal(dist_mat.shape, (N, N)) - ind = np.arange(N/2) + ind = np.arange(N//2) dist_mat = spk.isi_distance_matrix(spike_trains, ind) - assert_equal(dist_mat.shape, (N/2, N/2)) + assert_equal(dist_mat.shape, (N//2, N//2)) - ind = np.arange(N/2, N) + ind = np.arange(N//2, N) dist_mat = spk.isi_distance_matrix(spike_trains, ind) - assert_equal(dist_mat.shape, (N/2, N/2)) + assert_equal(dist_mat.shape, (N//2, N//2)) def test_regression_15_spike(): @@ -44,13 +46,13 @@ def test_regression_15_spike(): dist_mat = spk.spike_distance_matrix(spike_trains) assert_equal(dist_mat.shape, (N, N)) - ind = np.arange(N/2) + ind = np.arange(N//2) dist_mat = spk.spike_distance_matrix(spike_trains, ind) - assert_equal(dist_mat.shape, (N/2, N/2)) + assert_equal(dist_mat.shape, (N//2, N//2)) - ind = np.arange(N/2, N) + ind = np.arange(N//2, N) dist_mat = spk.spike_distance_matrix(spike_trains, ind) - assert_equal(dist_mat.shape, (N/2, N/2)) + assert_equal(dist_mat.shape, (N//2, N//2)) def test_regression_15_sync(): @@ -63,13 +65,13 @@ def test_regression_15_sync(): dist_mat = spk.spike_sync_matrix(spike_trains) assert_equal(dist_mat.shape, (N, N)) - ind = np.arange(N/2) + ind = np.arange(N//2) dist_mat = spk.spike_sync_matrix(spike_trains, ind) - assert_equal(dist_mat.shape, (N/2, N/2)) + assert_equal(dist_mat.shape, (N//2, N//2)) - ind = np.arange(N/2, N) + ind = np.arange(N//2, N) dist_mat = spk.spike_sync_matrix(spike_trains, ind) - assert_equal(dist_mat.shape, (N/2, N/2)) + assert_equal(dist_mat.shape, (N//2, N//2)) if __name__ == "__main__": -- cgit v1.2.3 From 61a9785822d402d5269bdff608f6259c1b6bffa4 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Sun, 13 Dec 2015 11:04:33 +0100 Subject: add metadata and testing under py3 Signed-off-by: Igor Gnatenko --- .travis.yml | 3 +++ setup.py | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1035775..1562d21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: python python: - "2.6" - "2.7" + - "3.3" + - "3.4" + - "3.5" env: - CYTHON_INSTALL="pip install -q cython" - CYTHON_INSTALL="" diff --git a/setup.py b/setup.py index 960c684..0bd9173 100644 --- a/setup.py +++ b/setup.py @@ -89,6 +89,11 @@ train similarity', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], package_data={ 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c', -- cgit v1.2.3 From 692a5773c85e08a690bc5406147e58c4f10b6628 Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Sun, 13 Dec 2015 11:17:37 +0100 Subject: stop using package_data Signed-off-by: Igor Gnatenko --- MANIFEST.in | 7 +++++++ setup.py | 8 +------- 2 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..aed0ae0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include *.rst +include *.txt +include pyspike/cython/*.c +include directionality/cython/*.c +recursive-include examples *.py *.txt +recursive-include test *.py *.txt +recursive-include doc * diff --git a/setup.py b/setup.py index 0bd9173..62c1951 100644 --- a/setup.py +++ b/setup.py @@ -94,11 +94,5 @@ train similarity', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', - ], - package_data={ - 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c', - 'cython/cython_distances.c', - 'directionality/cython/cython_directionality.c'], - 'test': ['Spike_testdata.txt'] - } + ] ) -- cgit v1.2.3 From 45d4fac7b207bb5ce98f17433a443837fce0455a Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Mon, 14 Dec 2015 01:53:21 +0100 Subject: tests: allow to run out-of-tree Signed-off-by: Igor Gnatenko --- test/test_distance.py | 14 ++++++++------ test/test_regression/test_regression_15.py | 12 ++++++------ test/test_spikes.py | 9 +++++---- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/test/test_distance.py b/test/test_distance.py index d5bce30..d405839 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -17,6 +17,8 @@ from numpy.testing import assert_equal, assert_almost_equal, \ import pyspike as spk from pyspike import SpikeTrain +import os +TEST_PATH = os.path.dirname(os.path.realpath(__file__)) def test_isi(): # generate two spike trains: @@ -275,8 +277,8 @@ def test_multi_spike_sync(): expected, decimal=15) # multivariate regression test - spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - edges=[0, 4000]) + spike_trains = spk.load_spike_trains_from_txt( + os.path.join(TEST_PATH, "SPIKE_Sync_Test.txt"), edges=[0, 4000]) # extract all spike times spike_times = np.array([]) for st in spike_trains: @@ -352,8 +354,8 @@ def test_regression_spiky(): # multivariate check - spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - (0.0, 4000.0)) + spike_trains = spk.load_spike_trains_from_txt( + os.path.join(TEST_PATH, "PySpike_testdata.txt"), (0.0, 4000.0)) isi_dist = spk.isi_distance_multi(spike_trains) # get the full precision from SPIKY assert_almost_equal(isi_dist, 0.17051816816999129656, decimal=15) @@ -371,8 +373,8 @@ def test_regression_spiky(): def test_multi_variate_subsets(): - spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - (0.0, 4000.0)) + spike_trains = spk.load_spike_trains_from_txt( + os.path.join(TEST_PATH, "PySpike_testdata.txt"), (0.0, 4000.0)) sub_set = [1, 3, 5, 7] spike_trains_sub_set = [spike_trains[i] for i in sub_set] diff --git a/test/test_regression/test_regression_15.py b/test/test_regression/test_regression_15.py index 42a39ea..dcacae2 100644 --- a/test/test_regression/test_regression_15.py +++ b/test/test_regression/test_regression_15.py @@ -16,11 +16,13 @@ from numpy.testing import assert_equal, assert_almost_equal, \ import pyspike as spk +import os +TEST_PATH = os.path.dirname(os.path.realpath(__file__)) +TEST_DATA = os.path.join(TEST_PATH, "..", "SPIKE_Sync_Test.txt") def test_regression_15_isi(): # load spike trains - spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - edges=[0, 4000]) + spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=[0, 4000]) N = len(spike_trains) @@ -38,8 +40,7 @@ def test_regression_15_isi(): def test_regression_15_spike(): # load spike trains - spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - edges=[0, 4000]) + spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=[0, 4000]) N = len(spike_trains) @@ -57,8 +58,7 @@ def test_regression_15_spike(): def test_regression_15_sync(): # load spike trains - spike_trains = spk.load_spike_trains_from_txt("test/SPIKE_Sync_Test.txt", - edges=[0, 4000]) + spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=[0, 4000]) N = len(spike_trains) diff --git a/test/test_spikes.py b/test/test_spikes.py index d4eb131..609a819 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -13,10 +13,12 @@ from numpy.testing import assert_equal import pyspike as spk +import os +TEST_PATH = os.path.dirname(os.path.realpath(__file__)) +TEST_DATA = os.path.join(TEST_PATH, "PySpike_testdata.txt") def test_load_from_txt(): - spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - edges=(0, 4000)) + spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=(0, 4000)) assert len(spike_trains) == 40 # check the first spike train @@ -48,8 +50,7 @@ def check_merged_spikes(merged_spikes, spike_trains): def test_merge_spike_trains(): # first load the data - spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", - edges=(0, 4000)) + spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=(0, 4000)) merged_spikes = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) # test if result is sorted -- cgit v1.2.3 From 8baf994af3c5c8f7c76ca5a2c06e5220ba33604f Mon Sep 17 00:00:00 2001 From: Igor Gnatenko Date: Mon, 14 Dec 2015 01:54:11 +0100 Subject: contributors: add myself Signed-off-by: Igor Gnatenko --- Contributors.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Contributors.txt b/Contributors.txt index 83563c7..512aa7f 100644 --- a/Contributors.txt +++ b/Contributors.txt @@ -1,5 +1,6 @@ Python/C Programming: - Mario Mulansky +- Igor Gnatenko Scientific Methods: - Thomas Kreuz -- cgit v1.2.3 From b970055641b215d30b671ee810e29c6a55e6214a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 14 Dec 2015 14:23:02 +0100 Subject: improved edge correction for spike distance Improvement following Eeros suggestions to use auxiliary spike at the edges consistently with the corresponding corrected ISI intervals. --- pyspike/cython/cython_distances.pyx | 55 +++++++++++++++++++++------------ pyspike/cython/cython_profiles.pyx | 61 +++++++++++++++++++++++++------------ test/test_distance.py | 50 ++++++++++++++++++++++++++---- test/test_empty.py | 4 +-- 4 files changed, 122 insertions(+), 48 deletions(-) diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index c4f2349..41baa60 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -176,6 +176,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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 @@ -184,19 +186,27 @@ def spike_distance_cython(double[:] t1, double[:] t2, with nogil: # release the interpreter to allow multithreading t_last = t_start - t_p1 = t_start - t_p2 = 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, t1[0]-(t1[1]-t1[0])) + t_aux1[1] = fmax(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) + t_aux2[0] = fmin(t_start, t2[0]-(t2[1]-t2[0])) + t_aux2[1] = fmax(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) + 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_start, t_end) + 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]) dt_p1 = dt_f1 - s1 = dt_p1*(t_f1-t_start)/isi1 + # s1 = dt_p1*(t_f1-t_start)/isi1 + s1 = dt_p1 index1 = -1 else: t_f1 = t1[1] - dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_start, t_end) + dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1]) dt_p1 = 0.0 isi1 = t1[1]-t1[0] s1 = dt_p1 @@ -204,14 +214,15 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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_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]) - s2 = dt_p2*(t_f2-t_start)/isi2 + # s2 = dt_p2*(t_f2-t_start)/isi2 + s2 = dt_p2 index2 = -1 else: t_f2 = t2[1] - dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_start, t_end) + dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1]) dt_p2 = 0.0 isi2 = t2[1]-t2[0] s2 = dt_p2 @@ -233,7 +244,7 @@ def spike_distance_cython(double[:] t1, double[:] t2, if index1 < N1-1: t_f1 = t1[index1+1] else: - t_f1 = t_end + 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) @@ -243,14 +254,16 @@ def spike_distance_cython(double[:] t1, double[:] t2, # 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) + 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]) # s1 needs adjustment due to change of isi1 - s1 = dt_p1*(t_end-t1[N1-1])/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) elif (index2 < N2-1) and (t_f1 > t_f2 or index1 == N1-1): @@ -264,7 +277,7 @@ def spike_distance_cython(double[:] t1, double[:] t2, if index2 < N2-1: t_f2 = t2[index2+1] else: - t_f2 = t_end + 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) @@ -274,14 +287,16 @@ def spike_distance_cython(double[:] t1, double[:] t2, # 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) + 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]) # s2 needs adjustment due to change of isi2 - s2 = dt_p2*(t_end-t2[N2-1])/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) else: # t_f1 == t_f2 - generate only one event @@ -298,27 +313,27 @@ def spike_distance_cython(double[:] t1, double[:] t2, if index1 < N1-1: t_f1 = t1[index1+1] dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2, - t_start, t_end) + t_aux2[0], t_aux2[1]) isi1 = t_f1 - t_p1 else: - t_f1 = t_end + t_f1 = t_aux1[1] 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) + t_aux1[0], t_aux1[1]) isi2 = t_f2 - t_p2 else: - t_f2 = t_end + t_f2 = t_aux2[1] 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 + 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) spike_value += 0.5*(y_start + y_end) * (t_end - t_last) # end nogil diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx index f9893eb..f08de6e 100644 --- a/pyspike/cython/cython_profiles.pyx +++ b/pyspike/cython/cython_profiles.pyx @@ -181,6 +181,8 @@ def spike_profile_cython(double[:] t1, double[:] t2, 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 @@ -189,6 +191,10 @@ def spike_profile_cython(double[:] t1, double[:] t2, N1 = len(t1) N2 = len(t2) + # we can assume at least two spikes per spike train + assert N1 > 1 + assert N2 > 1 + spike_events = np.empty(N1+N2+2) y_starts = np.empty(len(spike_events)-1) @@ -196,19 +202,27 @@ def spike_profile_cython(double[:] t1, double[:] t2, with nogil: # release the interpreter to allow multithreading spike_events[0] = t_start - t_p1 = t_start - t_p2 = 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, t1[0]-(t1[1]-t1[0])) + t_aux1[1] = fmax(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) + t_aux2[0] = fmin(t_start, t2[0]-(t2[1]-t2[0])) + t_aux2[1] = fmax(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) + 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_start, t_end) + 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]) dt_p1 = dt_f1 - s1 = dt_p1*(t_f1-t_start)/isi1 + # s1 = dt_p1*(t_f1-t_start)/isi1 + s1 = dt_p1 index1 = -1 else: t_f1 = t1[1] - dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_start, t_end) + dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1]) dt_p1 = 0.0 isi1 = t1[1]-t1[0] s1 = dt_p1 @@ -216,14 +230,15 @@ def spike_profile_cython(double[:] t1, double[:] t2, 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_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]) - s2 = dt_p2*(t_f2-t_start)/isi2 + # s2 = dt_p2*(t_f2-t_start)/isi2 + s2 = dt_p2 index2 = -1 else: t_f2 = t2[1] - dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_start, t_end) + dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1]) dt_p2 = 0.0 isi2 = t2[1]-t2[0] s2 = dt_p2 @@ -245,7 +260,7 @@ def spike_profile_cython(double[:] t1, double[:] t2, if index1 < N1-1: t_f1 = t1[index1+1] else: - t_f1 = t_end + 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, @@ -253,14 +268,16 @@ def spike_profile_cython(double[:] t1, double[:] t2, # 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) + 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]) # s1 needs adjustment due to change of isi1 - s1 = dt_p1*(t_end-t1[N1-1])/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) @@ -275,7 +292,7 @@ def spike_profile_cython(double[:] t1, double[:] t2, if index2 < N2-1: t_f2 = t2[index2+1] else: - t_f2 = t_end + 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, @@ -283,14 +300,16 @@ def spike_profile_cython(double[:] t1, double[:] t2, # 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) + 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]) # s2 needs adjustment due to change of isi2 - s2 = dt_p2*(t_end-t2[N2-1])/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 @@ -306,19 +325,19 @@ def spike_profile_cython(double[:] t1, double[:] t2, if index1 < N1-1: t_f1 = t1[index1+1] dt_f1 = get_min_dist_cython(t_f1, t2, N2, index2, - t_start, t_end) + t_aux2[0], t_aux2[1]) isi1 = t_f1 - t_p1 else: - t_f1 = t_end + t_f1 = t_aux1[1] 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) + t_aux1[0], t_aux1[1]) isi2 = t_f2 - t_p2 else: - t_f2 = t_end + t_f2 = t_aux2[1] dt_f2 = dt_p2 isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) index += 1 @@ -330,8 +349,10 @@ def spike_profile_cython(double[:] t1, double[:] t2, # the ending value of the last interval 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 + # s1 = dt_f1*(t_end-t1[N1-1])/isi1 + # s2 = dt_f2*(t_end-t2[N2-1])/isi2 + s1 = dt_f1 + s2 = dt_f2 y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) # end nogil diff --git a/test/test_distance.py b/test/test_distance.py index e45ac16..626b438 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -36,6 +36,7 @@ def test_isi(): f = spk.isi_profile(t1, t2) # print("ISI: ", f.y) + print("ISI value:", expected_isi_val) assert_equal(f.x, expected_times) assert_array_almost_equal(f.y, expected_isi, decimal=15) @@ -73,8 +74,19 @@ def test_spike(): assert_equal(f.x, expected_times) - assert_almost_equal(f.avrg(), 1.6624149659863946e-01, decimal=15) - assert_almost_equal(f.y2[-1], 0.1394558, decimal=6) + # from SPIKY: + y_all = np.array([0.000000000000000000, 0.555555555555555580, + 0.222222222222222210, 0.305555555555555580, + 0.255102040816326536, 0.000000000000000000, + 0.000000000000000000, 0.255102040816326536, + 0.255102040816326536, 0.285714285714285698, + 0.285714285714285698, 0.285714285714285698]) + + #assert_array_almost_equal(f.y1, y_all[::2]) + assert_array_almost_equal(f.y2, y_all[1::2]) + + assert_almost_equal(f.avrg(), 0.186309523809523814, decimal=15) + assert_equal(spk.spike_distance(t1, t2), f.avrg()) t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) @@ -99,6 +111,8 @@ def test_spike(): (expected_y1+expected_y2)/2) expected_spike_val /= (expected_times[-1]-expected_times[0]) + print("SPIKE value:", expected_spike_val) + f = spk.spike_profile(t1, t2) assert_equal(f.x, expected_times) @@ -117,9 +131,14 @@ def test_spike(): # for left and right values s1_r = np.array([0.1, (0.1*0.1+0.1*0.1)/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) s1_l = np.array([0.1, (0.1*0.1+0.1*0.1)/0.2, 0.1, 0.0, 0.0, 0.0, 0.0]) - s2_r = np.array([0.1*0.1/0.3, 0.1*0.3/0.3, 0.1*0.2/0.3, + # s2_r = np.array([0.1*0.1/0.3, 0.1*0.3/0.3, 0.1*0.2/0.3, + # 0.0, 0.1, 0.0, 0.0]) + # s2_l = np.array([0.1*0.1/0.3, 0.1*0.1/0.3, 0.1*0.2/0.3, 0.0, + # 0.1, 0.0, 0.0]) + # eero's edge correction: + s2_r = np.array([0.1, 0.1*0.3/0.3, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) - s2_l = np.array([0.1*0.1/0.3, 0.1*0.1/0.3, 0.1*0.2/0.3, 0.0, + s2_l = np.array([0.1, 0.1*0.3/0.3, 0.1*0.2/0.3, 0.0, 0.1, 0.0, 0.0]) isi1 = np.array([0.2, 0.2, 0.2, 0.2, 0.2, 0.4]) isi2 = np.array([0.3, 0.3, 0.3, 0.1, 0.1, 0.4]) @@ -345,7 +364,7 @@ def test_regression_spiky(): assert_equal(isi_profile.y, 0.1/1.1 * np.ones_like(isi_profile.y)) spike_dist = spk.spike_distance(st1, st2) - assert_equal(spike_dist, 2.1105878248735391e-01) + assert_equal(spike_dist, 0.211058782487353908) spike_sync = spk.spike_sync(st1, st2) assert_equal(spike_sync, 8.6956521739130432e-01) @@ -363,12 +382,31 @@ def test_regression_spiky(): spike_dist = spk.spike_distance_multi(spike_trains) # get the full precision from SPIKY - assert_almost_equal(spike_dist, 2.4432433330596512e-01, decimal=15) + assert_almost_equal(spike_dist, 0.25188056475463755, decimal=15) spike_sync = spk.spike_sync_multi(spike_trains) # get the full precision from SPIKY assert_equal(spike_sync, 0.7183531505298066) + # Eero's edge correction example + st1 = SpikeTrain([0.5, 1.5, 2.5], 6.0) + st2 = SpikeTrain([3.5, 4.5, 5.5], 6.0) + + f = spk.spike_profile(st1, st2) + + expected_times = np.array([0.0, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.0]) + y_all = np.array([0.271604938271605, 0.271604938271605, 0.271604938271605, + 0.617283950617284, 0.617283950617284, 0.444444444444444, + 0.285714285714286, 0.285714285714286, 0.444444444444444, + 0.617283950617284, 0.617283950617284, 0.271604938271605, + 0.271604938271605, 0.271604938271605]) + expected_y1 = y_all[::2] + expected_y2 = y_all[1::2] + + assert_equal(f.x, expected_times) + assert_array_almost_equal(f.y1, expected_y1, decimal=14) + assert_array_almost_equal(f.y2, expected_y2, decimal=14) + def test_multi_variate_subsets(): spike_trains = spk.load_spike_trains_from_txt("test/PySpike_testdata.txt", diff --git a/test/test_empty.py b/test/test_empty.py index af7fb36..5a0042f 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -70,8 +70,8 @@ def test_spike_empty(): st1 = SpikeTrain([], edges=(0.0, 1.0)) st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) d = spk.spike_distance(st1, st2) - assert_almost_equal(d, 0.4*0.4*1.0/(0.4+1.0)**2 + 0.6*0.4*1.0/(0.6+1.0)**2, - decimal=15) + d_expect = 0.4*0.4*1.0/(0.4+1.0)**2 + 0.6*0.4*1.0/(0.6+1.0)**2 + assert_almost_equal(d, d_expect, decimal=15) prof = spk.spike_profile(st1, st2) assert_equal(d, prof.avrg()) assert_array_equal(prof.x, [0.0, 0.4, 1.0]) -- cgit v1.2.3 From a37dc02a0f6c11a8773cba8a6137a2604222fa26 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 14 Dec 2015 16:17:28 +0100 Subject: python backend updated for new edge correction Eero's improved edge correction now also implemented in the python backend. --- pyspike/cython/python_backend.py | 53 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index 69a420f..a5f7ae4 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -144,35 +144,44 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): 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])) + t_aux1[1] = max(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) + t_aux2[0] = min(t_start, t2[0]-(t2[1]-t2[0])) + t_aux2[1] = max(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) + 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] + spike_events[0] = t_start - t_p1 = t_start - t_p2 = t_start if t1[0] > t_start: t_f1 = t1[0] - dt_f1 = get_min_dist(t_f1, t2, 0, t_start, t_end) + 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]) - s1 = dt_p1*(t_f1-t_start)/isi1 + # s1 = dt_p1*(t_f1-t_start)/isi1 + s1 = dt_p1 index1 = -1 else: dt_p1 = 0.0 t_f1 = t1[1] - dt_f1 = get_min_dist(t_f1, t2, 0, t_start, t_end) + dt_f1 = get_min_dist(t_f1, t2, 0, t_aux2[0], t_aux2[1]) 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(t_f2, t1, 0, t_start, t_end) + 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]) - s2 = dt_p2*(t_f2-t_start)/isi2 + # s2 = dt_p2*(t_f2-t_start)/isi2 + s2 = dt_p2 index2 = -1 else: dt_p2 = 0.0 t_f2 = t2[1] - dt_f2 = get_min_dist(t_f2, t1, 0, t_start, t_end) + dt_f2 = get_min_dist(t_f2, t1, 0, t_aux1[0], t_aux1[1]) isi2 = t2[1]-t2[0] s2 = dt_p2 index2 = 0 @@ -193,20 +202,22 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): if index1 < N1-1: t_f1 = t1[index1+1] else: - t_f1 = t_end + 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_start, t_end) + 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]) # s1 needs adjustment due to change of isi1 - s1 = dt_p1*(t_end-t1[N1-1])/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): @@ -220,20 +231,22 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): if index2 < N2-1: t_f2 = t2[index2+1] else: - t_f2 = t_end + 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_start, t_end) + 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]) # s2 needs adjustment due to change of isi2 - s2 = dt_p2*(t_end-t2[N2-1])/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 @@ -248,18 +261,18 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): y_starts[index] = 0.0 if index1 < N1-1: t_f1 = t1[index1+1] - dt_f1 = get_min_dist(t_f1, t2, index2, t_start, t_end) + dt_f1 = get_min_dist(t_f1, t2, index2, t_aux2[0], t_aux2[1]) isi1 = t_f1 - t_p1 else: - t_f1 = t_end + t_f1 = t_aux1[1] dt_f1 = dt_p1 isi1 = max(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(t_f2, t1, index1, t_start, t_end) + dt_f2 = get_min_dist(t_f2, t1, index1, t_aux1[0], t_aux1[1]) isi2 = t_f2 - t_p2 else: - t_f2 = t_end + t_f2 = t_aux2[1] dt_f2 = dt_p2 isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) index += 1 @@ -271,8 +284,8 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): # the ending value of the last interval 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 + 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 -- cgit v1.2.3 From 2958fac1f12dfc743214615a3e8252dae54ea784 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 14 Dec 2015 16:38:48 +0100 Subject: remove directionality from develop directionality development happens in separate branch. --- pyspike/directionality/__init__.py | 15 -- pyspike/directionality/cython/__init__.py | 0 .../cython/cython_directionality.pyx | 223 ----------------- .../cython/directionality_python_backend.py | 89 ------- pyspike/directionality/spike_train_order.py | 266 --------------------- test/test_spike_delay_asymmetry.py | 40 ---- 6 files changed, 633 deletions(-) delete mode 100644 pyspike/directionality/__init__.py delete mode 100644 pyspike/directionality/cython/__init__.py delete mode 100644 pyspike/directionality/cython/cython_directionality.pyx delete mode 100644 pyspike/directionality/cython/directionality_python_backend.py delete mode 100644 pyspike/directionality/spike_train_order.py delete mode 100644 test/test_spike_delay_asymmetry.py diff --git a/pyspike/directionality/__init__.py b/pyspike/directionality/__init__.py deleted file mode 100644 index 6ea38b2..0000000 --- a/pyspike/directionality/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Copyright 2015, Mario Mulansky - -Distributed under the BSD License -""" - -from __future__ import absolute_import - -__all__ = ["spike_train_order"] - -from .spike_train_order import spike_train_order_profile, \ - spike_train_order, spike_train_order_profile_multi, \ - spike_train_order_matrix, spike_order_values, \ - optimal_spike_train_order, optimal_spike_train_order_from_matrix, \ - permutate_matrix diff --git a/pyspike/directionality/cython/__init__.py b/pyspike/directionality/cython/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/pyspike/directionality/cython/cython_directionality.pyx b/pyspike/directionality/cython/cython_directionality.pyx deleted file mode 100644 index e1f63c4..0000000 --- a/pyspike/directionality/cython/cython_directionality.pyx +++ /dev/null @@ -1,223 +0,0 @@ -#cython: boundscheck=False -#cython: wraparound=False -#cython: cdivision=True - -""" -cython_directionality.pyx - -cython implementation of the spike delay asymmetry measures - -Copyright 2015, Mario Mulansky - -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_order_values_cython -############################################################ -def spike_order_values_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[:] a1 = np.zeros(N1) # asymmetry values - cdef double[:] a2 = np.zeros(N2) # asymmetry 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 - a1[i] = -1 - a2[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 - a1[i] = +1 - a2[j] = -1 - else: # spikes1[i+1] = spikes2[j+1] - # advance in both spike trains - j += 1 - i += 1 - # equal spike times: zero asymmetry value - a1[i] = 0 - a2[j] = 0 - - return a1, a2 - - -############################################################ -# 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 asym = 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 - asym -= 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 - asym += 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 asym == 0 and mp == 0: - # empty spike trains -> spike sync = 1 by definition - asym = 1 - mp = 1 - - return asym, mp diff --git a/pyspike/directionality/cython/directionality_python_backend.py b/pyspike/directionality/cython/directionality_python_backend.py deleted file mode 100644 index e14238f..0000000 --- a/pyspike/directionality/cython/directionality_python_backend.py +++ /dev/null @@ -1,89 +0,0 @@ -""" directionality_python_backend.py - -Collection of python functions that can be used instead of the cython -implementation. - -Copyright 2015, Mario Mulansky - -Distributed under the BSD License - -""" - -import numpy as np - - -############################################################ -# spike_train_order_python -############################################################ -def spike_train_order_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/directionality/spike_train_order.py b/pyspike/directionality/spike_train_order.py deleted file mode 100644 index e6c9830..0000000 --- a/pyspike/directionality/spike_train_order.py +++ /dev/null @@ -1,266 +0,0 @@ -# Module containing functions to compute multivariate spike train order -# Copyright 2015, Mario Mulansky -# Distributed under the BSD License - -from __future__ import absolute_import - -import numpy as np -from math import exp -from functools import partial -import pyspike -from pyspike import DiscreteFunc -from pyspike.generic import _generic_profile_multi - - -############################################################ -# spike_train_order_profile -############################################################ -def spike_train_order_profile(spike_train1, spike_train2, max_tau=None): - """ Computes the spike delay asymmetry profile A(t) of the two given - spike trains. Returns the profile as a DiscreteFunction object. - - :param spike_train1: First spike train. - :type spike_train1: :class:`pyspike.SpikeTrain` - :param spike_train2: Second spike train. - :type spike_train2: :class:`pyspike.SpikeTrain` - :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. - :returns: The spike-distance profile :math:`S_{sync}(t)`. - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - # check whether the spike trains are defined for the same interval - assert spike_train1.t_start == spike_train2.t_start, \ - "Given spike trains are not defined on the same interval!" - assert spike_train1.t_end == spike_train2.t_end, \ - "Given spike trains are not defined on the same interval!" - - # cython implementation - try: - from .cython.cython_directionality import \ - spike_train_order_profile_cython as \ - spike_train_order_profile_impl - except ImportError: - # raise NotImplementedError() - if not(pyspike.disable_backend_warning): - print("Warning: spike_distance_cython not found. Make sure that \ -PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -Falling back to slow python backend.") - # use python backend - from .cython.directionality_python_backend import \ - spike_train_order_python as spike_train_order_profile_impl - - if max_tau is None: - max_tau = 0.0 - - times, coincidences, multiplicity \ - = spike_train_order_profile_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end, - max_tau) - - return DiscreteFunc(times, coincidences, multiplicity) - - -############################################################ -# spike_train_order -############################################################ -def spike_train_order(spike_train1, spike_train2, normalize=True, - interval=None, max_tau=None): - """ Computes the overall spike delay asymmetry value for two spike trains. - """ - if interval is None: - # distance over the whole interval is requested: use specific function - # for optimal performance - try: - from .cython.cython_directionality import \ - spike_train_order_cython as spike_train_order_impl - if max_tau is None: - max_tau = 0.0 - c, mp = spike_train_order_impl(spike_train1.spikes, - spike_train2.spikes, - spike_train1.t_start, - spike_train1.t_end, - max_tau) - except ImportError: - # Cython backend not available: fall back to profile averaging - c, mp = spike_train_order_profile(spike_train1, spike_train2, - max_tau).integral(interval) - if normalize: - return 1.0*c/mp - else: - return c - else: - # some specific interval is provided: not yet implemented - raise NotImplementedError() - - -############################################################ -# spike_train_order_profile_multi -############################################################ -def spike_train_order_profile_multi(spike_trains, indices=None, - max_tau=None): - """ Computes the multi-variate spike delay asymmetry profile for a set of - spike trains. For each spike in the set of spike trains, the multi-variate - profile is defined as the sum of asymmetry values divided by the number of - spike trains pairs involving the spike train of containing this spike, - which is the number of spike trains minus one (N-1). - - :param spike_trains: list of :class:`pyspike.SpikeTrain` - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. - :returns: The multi-variate spike sync profile :math:`(t)` - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - prof_func = partial(spike_train_order_profile, max_tau=max_tau) - average_prof, M = _generic_profile_multi(spike_trains, prof_func, - indices) - # average_dist.mul_scalar(1.0/M) # no normalization here! - return average_prof - - -############################################################ -# spike_train_order_matrix -############################################################ -def spike_train_order_matrix(spike_trains, normalize=True, indices=None, - interval=None, max_tau=None): - """ Computes the spike delay asymmetry matrix for the given spike trains. - """ - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - - distance_matrix = np.zeros((len(indices), len(indices))) - for i, j in pairs: - d = spike_train_order(spike_trains[i], spike_trains[j], normalize, - interval, max_tau=max_tau) - distance_matrix[i, j] = d - distance_matrix[j, i] = -d - return distance_matrix - - -############################################################ -# spike_order_values -############################################################ -def spike_order_values(spike_trains, indices=None, - interval=None, max_tau=None): - """ Computes the spike train symmetry value for each spike in each spike - train. - """ - if indices is None: - indices = np.arange(len(spike_trains)) - indices = np.array(indices) - # check validity of indices - assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ - "Invalid index list." - # list of arrays for reulting asymmetry values - asymmetry_list = [np.zeros_like(st.spikes) for st in spike_trains] - # generate a list of possible index pairs - pairs = [(indices[i], j) for i in range(len(indices)) - for j in indices[i+1:]] - - # cython implementation - try: - from .cython.cython_directionality import \ - spike_order_values_cython as spike_order_values_impl - except ImportError: - raise NotImplementedError() -# if not(pyspike.disable_backend_warning): -# print("Warning: spike_distance_cython not found. Make sure that \ -# PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ -# Falling back to slow python backend.") -# # use python backend -# from .cython.python_backend import coincidence_python \ -# as coincidence_profile_impl - - if max_tau is None: - max_tau = 0.0 - - for i, j in pairs: - a1, a2 = spike_order_values_impl(spike_trains[i].spikes, - spike_trains[j].spikes, - spike_trains[i].t_start, - spike_trains[i].t_end, - max_tau) - asymmetry_list[i] += a1 - asymmetry_list[j] += a2 - for a in asymmetry_list: - a /= len(spike_trains)-1 - return asymmetry_list - - -############################################################ -# optimal_asymmetry_order_from_matrix -############################################################ -def optimal_spike_train_order_from_matrix(D, full_output=False): - """ finds the best sorting via simulated annealing. - Returns the optimal permutation p and A value. - Internal function, don't call directly! Use optimal_asymmetry_order - instead. - """ - N = len(D) - A = np.sum(np.triu(D, 0)) - - p = np.arange(N) - - T = 2*np.max(D) # starting temperature - T_end = 1E-5 * T # final temperature - alpha = 0.9 # cooling factor - total_iter = 0 - while T > T_end: - iterations = 0 - succ_iter = 0 - while iterations < 100*N and succ_iter < 10*N: - # exchange two rows and cols - ind1 = np.random.randint(N-1) - delta_A = -2*D[p[ind1], p[ind1+1]] - if delta_A > 0.0 or exp(delta_A/T) > np.random.random(): - # swap indices - p[ind1], p[ind1+1] = p[ind1+1], p[ind1] - A += delta_A - succ_iter += 1 - iterations += 1 - total_iter += iterations - T *= alpha # cool down - if succ_iter == 0: - break - if full_output: - return p, A, total_iter - else: - return p, A - - -############################################################ -# optimal_spike_train_order -############################################################ -def optimal_spike_train_order(spike_trains, indices=None, interval=None, - max_tau=None, full_output=False): - """ finds the best sorting of the given spike trains via simulated - annealing. - Returns the optimal permutation p and A value. - """ - D = spike_train_order_matrix(spike_trains, indices, interval, max_tau) - return optimal_spike_train_order_from_matrix(D, full_output) - - -############################################################ -# permutate_matrix -############################################################ -def permutate_matrix(D, p): - N = len(D) - D_p = np.empty_like(D) - for n in range(N): - for m in range(N): - D_p[n, m] = D[p[n], p[m]] - return D_p diff --git a/test/test_spike_delay_asymmetry.py b/test/test_spike_delay_asymmetry.py deleted file mode 100644 index 9de16e5..0000000 --- a/test/test_spike_delay_asymmetry.py +++ /dev/null @@ -1,40 +0,0 @@ -""" test_spike_delay_asymmetry.py - -Tests the asymmetry functions - -Copyright 2015, Mario Mulansky - -Distributed under the BSD License - -""" - -import numpy as np -from numpy.testing import assert_equal, assert_almost_equal, \ - assert_array_equal - -import pyspike as spk -from pyspike import SpikeTrain, DiscreteFunc - - -def test_profile(): - st1 = SpikeTrain([100, 200, 300], [0, 1000]) - st2 = SpikeTrain([105, 205, 300], [0, 1000]) - expected_x = np.array([0, 100, 105, 200, 205, 300, 1000]) - expected_y = np.array([1, 1, 1, 1, 1, 0, 0]) - expected_mp = np.array([1, 1, 1, 1, 1, 2, 2]) - - f = spk.drct.spike_train_order_profile(st1, st2) - - assert f.almost_equal(DiscreteFunc(expected_x, expected_y, expected_mp)) - assert_almost_equal(f.avrg(), 2.0/3.0) - assert_almost_equal(spk.drct.spike_train_order(st1, st2), 2.0/3.0) - assert_almost_equal(spk.drct.spike_train_order(st1, st2, normalize=False), - 4.0) - - st3 = SpikeTrain([105, 195, 500], [0, 1000]) - expected_x = np.array([0, 100, 105, 195, 200, 300, 500, 1000]) - expected_y = np.array([1, 1, 1, -1, -1, 0, 0, 0]) - expected_mp = np.array([1, 1, 1, 1, 1, 1, 1, 1]) - - f = spk.drct.spike_train_order_profile(st1, st3) - assert f.almost_equal(DiscreteFunc(expected_x, expected_y, expected_mp)) -- cgit v1.2.3 From 64b680238ac5bcafbf3ce67ed2a4d7487098b43d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 14 Dec 2015 16:40:49 +0100 Subject: update version strings and Changelog --- Changelog | 6 ++++++ doc/conf.py | 4 ++-- setup.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Changelog b/Changelog index 519dd3b..2be5e52 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,9 @@ +PySpike v0.4: + * Python 3 support (thanks to Igor Gnatenko) + * list interface to SpikeTrain class + * disable_backend_warning property + * several bugfixes + PySpike v0.3: * addition of __version__ attribute * restructured docs, Readme now only contains basic examples diff --git a/doc/conf.py b/doc/conf.py index 8011ea9..7e13eae 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,9 +64,9 @@ copyright = u'2014-2015, Mario Mulansky' # built documents. # # The short X.Y version. -version = '0.3' +version = '0.4' # The full version, including alpha/beta/rc tags. -release = '0.3.0' +release = '0.4.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 62c1951..a1ab122 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.3.0', + version='0.4.0', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy.get_include()], -- cgit v1.2.3 From 9061f2a0c13134e53f937d730295a421fd671ea3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Mon, 14 Dec 2015 16:50:55 +0100 Subject: removed directionality from __init__ and setup.py --- pyspike/__init__.py | 2 -- setup.py | 11 +++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 335b1d3..069090b 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -26,8 +26,6 @@ from .psth import psth from .spikes import load_spike_trains_from_txt, spike_train_from_string, \ merge_spike_trains, generate_poisson_spikes -from . import directionality as drct - # define the __version__ following # http://stackoverflow.com/questions/17583443 from pkg_resources import get_distribution, DistributionNotFound diff --git a/setup.py b/setup.py index a1ab122..8ef431a 100644 --- a/setup.py +++ b/setup.py @@ -23,8 +23,7 @@ else: if os.path.isfile("pyspike/cython/cython_add.c") and \ os.path.isfile("pyspike/cython/cython_profiles.c") and \ - os.path.isfile("pyspike/cython/cython_distances.c") and \ - os.path.isfile("pyspike/directionality/cython/cython_directionality.c"): + os.path.isfile("pyspike/cython/cython_distances.c"): use_c = True else: use_c = False @@ -39,9 +38,7 @@ if use_cython: # Cython is available, compile .pyx -> .c Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.pyx"]), Extension("pyspike.cython.cython_distances", - ["pyspike/cython/cython_distances.pyx"]), - Extension("pyspike.directionality.cython.cython_directionality", - ["pyspike/directionality/cython/cython_directionality.pyx"]) + ["pyspike/cython/cython_distances.pyx"]) ] cmdclass.update({'build_ext': build_ext}) elif use_c: # c files are there, compile to binaries @@ -51,9 +48,7 @@ elif use_c: # c files are there, compile to binaries Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.c"]), Extension("pyspike.cython.cython_distances", - ["pyspike/cython/cython_distances.c"]), - Extension("pyspike.directionality.cython.cython_directionality", - ["pyspike/directionality/cython/cython_directionality.c"]) + ["pyspike/cython/cython_distances.c"]) ] # neither cython nor c files available -> automatic fall-back to python backend -- cgit v1.2.3 From 619fdef014c44a89a7ecef9078905ee44e373b84 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 18 Dec 2015 14:37:45 +0100 Subject: bugfix for edge correction fixed bug within new edge correction (auxiliary spike was ignored in some cases) added regression test with 10000 random spike train sets --- pyspike/cython/cython_distances.pyx | 10 +- pyspike/cython/python_backend.py | 21 +++- test/test_distance.py | 1 + .../regression_random_results_cSPIKY.mat | Bin 0 -> 149130 bytes test/test_regression/regression_random_spikes.mat | Bin 0 -> 16241579 bytes .../test_regression_random_spikes.py | 113 +++++++++++++++++++++ 6 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 test/test_regression/regression_random_results_cSPIKY.mat create mode 100644 test/test_regression/regression_random_spikes.mat create mode 100644 test/test_regression/test_regression_random_spikes.py diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index 41baa60..6c6e7e5 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -184,7 +184,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, N1 = len(t1) N2 = len(t2) - with nogil: # release the interpreter to allow multithreading + # with nogil: # release the interpreter to allow multithreading + if True: t_last = t_start # t_p1 = t_start # t_p2 = t_start @@ -193,6 +194,7 @@ def spike_distance_cython(double[:] t1, double[:] t2, t_aux1[1] = fmax(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) t_aux2[0] = fmin(t_start, t2[0]-(t2[1]-t2[0])) t_aux2[1] = fmax(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) + # 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: @@ -207,7 +209,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, else: t_f1 = t1[1] dt_f1 = get_min_dist_cython(t_f1, t2, N2, 0, t_aux2[0], t_aux2[1]) - dt_p1 = 0.0 + # dt_p1 = t_start-t_p2 # 0.0 + dt_p1 = get_min_dist_cython(t_p1, t2, N2, 0, t_aux2[0], t_aux2[1]) isi1 = t1[1]-t1[0] s1 = dt_p1 index1 = 0 @@ -223,7 +226,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, else: t_f2 = t2[1] dt_f2 = get_min_dist_cython(t_f2, t1, N1, 0, t_aux1[0], t_aux1[1]) - dt_p2 = 0.0 + # 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 = t2[1]-t2[0] s2 = dt_p2 index2 = 0 diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index a5f7ae4..a007a7f 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -153,6 +153,8 @@ def spike_distance_python(spikes1, spikes2, t_start, 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] @@ -163,7 +165,8 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s1 = dt_p1 index1 = -1 else: - dt_p1 = 0.0 + # dt_p1 = t_start-t_p2 + dt_p1 = get_min_dist(t_p1, t2, 0, t_aux2[0], t_aux2[1]) t_f1 = t1[1] dt_f1 = get_min_dist(t_f1, t2, 0, t_aux2[0], t_aux2[1]) isi1 = t1[1]-t1[0] @@ -179,13 +182,18 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s2 = dt_p2 index2 = -1 else: - dt_p2 = 0.0 + dt_p2 = t_start-t_p1 + dt_p2 = get_min_dist(t_p2, t1, 0, t_aux1[0], t_aux1[1]) t_f2 = t2[1] dt_f2 = get_min_dist(t_f2, t1, 0, t_aux1[0], t_aux1[1]) isi2 = t2[1]-t2[0] s2 = dt_p2 index2 = 0 + print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) + print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) + print "s1: ", repr(s1), ", s2:", repr(s2) + y_starts[0] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) index = 1 @@ -276,6 +284,11 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): dt_f2 = dt_p2 isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) index += 1 + + print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) + print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) + print "s1: ", repr(s1), ", s2:", repr(s2) + # the last event is the interval end if spike_events[index-1] == t_end: index -= 1 @@ -288,6 +301,10 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s2 = dt_f2 # *(t_end-t2[N2-1])/isi2 y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) + print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) + print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) + print "s1: ", repr(s1), ", s2:", repr(s2) + # 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] diff --git a/test/test_distance.py b/test/test_distance.py index 8cf81e2..083d8a3 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -20,6 +20,7 @@ from pyspike import SpikeTrain import os TEST_PATH = os.path.dirname(os.path.realpath(__file__)) + def test_isi(): # generate two spike trains: t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) diff --git a/test/test_regression/regression_random_results_cSPIKY.mat b/test/test_regression/regression_random_results_cSPIKY.mat new file mode 100644 index 0000000..47c9b50 Binary files /dev/null and b/test/test_regression/regression_random_results_cSPIKY.mat differ diff --git a/test/test_regression/regression_random_spikes.mat b/test/test_regression/regression_random_spikes.mat new file mode 100644 index 0000000..e5ebeb1 Binary files /dev/null and b/test/test_regression/regression_random_spikes.mat differ diff --git a/test/test_regression/test_regression_random_spikes.py b/test/test_regression/test_regression_random_spikes.py new file mode 100644 index 0000000..757bab2 --- /dev/null +++ b/test/test_regression/test_regression_random_spikes.py @@ -0,0 +1,113 @@ +""" regression benchmark + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" +import numpy as np +from scipy.io import loadmat +import pyspike as spk + +from numpy.testing import assert_almost_equal + +spk.disable_backend_warning = True + + +def test_regression_random(): + + spike_file = "test/test_regression/regression_random_spikes.mat" + spikes_name = "spikes" + result_name = "Distances" + result_file = "test/test_regression/regression_random_results_cSPIKY.mat" + + spike_train_sets = loadmat(spike_file)[spikes_name][0] + results_cSPIKY = loadmat(result_file)[result_name] + + for i, spike_train_data in enumerate(spike_train_sets): + spike_trains = [] + for spikes in spike_train_data[0]: + spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + + isi = spk.isi_distance_multi(spike_trains) + spike = spk.spike_distance_multi(spike_trains) + # spike_sync = spk.spike_sync_multi(spike_trains) + + assert_almost_equal(isi, results_cSPIKY[i][0], decimal=14, + err_msg="Index: %d, ISI" % i) + assert_almost_equal(spike, results_cSPIKY[i][1], decimal=14, + err_msg="Index: %d, SPIKE" % i) + + +def check_regression_dataset(spike_file="benchmark.mat", + spikes_name="spikes", + result_file="results_cSPIKY.mat", + result_name="Distances"): + """ Debuging function """ + np.set_printoptions(precision=15) + + spike_train_sets = loadmat(spike_file)[spikes_name][0] + + results_cSPIKY = loadmat(result_file)[result_name] + + err_max = 0.0 + err_max_ind = -1 + err_count = 0 + + for i, spike_train_data in enumerate(spike_train_sets): + spike_trains = [] + for spikes in spike_train_data[0]: + spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + + isi = spk.isi_distance_multi(spike_trains) + spike = spk.spike_distance_multi(spike_trains) + # spike_sync = spk.spike_sync_multi(spike_trains) + + if abs(isi - results_cSPIKY[i][0]) > 1E-14: + print "Error in ISI:", i, isi, results_cSPIKY[i][0] + print "Spike trains:" + for st in spike_trains: + print st.spikes + + err = abs(spike - results_cSPIKY[i][1]) + if err > 1E-14: + err_count += 1 + if err > err_max: + err_max = err + err_max_ind = i + + print "Total Errors:", err_count + + if err_max_ind > -1: + print "Max SPIKE distance error:", err_max, "at index:", err_max_ind + spike_train_data = spike_train_sets[err_max_ind] + for spikes in spike_train_data[0]: + print spikes.flatten() + + +def check_single_spike_train_set(index): + """ Debuging function """ + np.set_printoptions(precision=15) + + spike_train_sets = loadmat("benchmark.mat")['spikes'][0] + + results_cSPIKY = loadmat("results_cSPIKY.mat")['Distances'] + + spike_train_data = spike_train_sets[index] + + spike_trains = [] + for spikes in spike_train_data[0]: + print "Spikes:", spikes.flatten() + spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + + print spk.spike_distance_multi(spike_trains) + + print results_cSPIKY[index][1] + + print spike_trains[1].spikes + + +if __name__ == "__main__": + + test_regression_random() + # check_regression_dataset() + # check_single_spike_train_set(7633) -- cgit v1.2.3 From 1e581a1d384e3e0a9b136b962052f2d711d84c4d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 18 Dec 2015 15:48:04 +0100 Subject: removed print outs from python backend --- pyspike/cython/python_backend.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index a007a7f..cf898d7 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -153,7 +153,7 @@ def spike_distance_python(spikes1, spikes2, t_start, 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 + # print "t_aux1", t_aux1, ", t_aux2:", t_aux2 spike_events[0] = t_start if t1[0] > t_start: @@ -190,9 +190,9 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s2 = dt_p2 index2 = 0 - print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) - print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) - print "s1: ", repr(s1), ", s2:", repr(s2) + # print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) + # print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) + # print "s1: ", repr(s1), ", s2:", repr(s2) y_starts[0] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) index = 1 @@ -285,9 +285,9 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) index += 1 - print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) - print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) - print "s1: ", repr(s1), ", s2:", repr(s2) + # print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) + # print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) + # print "s1: ", repr(s1), ", s2:", repr(s2) # the last event is the interval end if spike_events[index-1] == t_end: @@ -301,9 +301,9 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s2 = dt_f2 # *(t_end-t2[N2-1])/isi2 y_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) - print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) - print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) - print "s1: ", repr(s1), ", s2:", repr(s2) + # print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) + # print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) + # print "s1: ", repr(s1), ", s2:", repr(s2) # use only the data added above # could be less than original length due to equal spike times -- cgit v1.2.3 From 76e269d4b9fa081cba55f14bf121b16dc0f5ab3c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 18 Dec 2015 16:03:54 +0100 Subject: add scipy to travis --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index 1562d21..428e078 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,12 @@ python: env: - CYTHON_INSTALL="pip install -q cython" - CYTHON_INSTALL="" +before_install: + - sudo apt-get install libblas-dev + - sudo apt-get install liblapack-dev + - sudo apt-get install gfortran install: + - pip install scipy - $CYTHON_INSTALL script: -- cgit v1.2.3 From 1674f50df72052c2267b9850278ffcbd8d9c9982 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 18 Dec 2015 16:14:06 +0100 Subject: travis bugfix --- .travis.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 428e078..20b1a4d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,12 +8,11 @@ python: env: - CYTHON_INSTALL="pip install -q cython" - CYTHON_INSTALL="" +virtualenv: + system_site_packages: true before_install: - - sudo apt-get install libblas-dev - - sudo apt-get install liblapack-dev - - sudo apt-get install gfortran + - sudo apt-get install python-scipy install: - - pip install scipy - $CYTHON_INSTALL script: -- cgit v1.2.3 From a4a2d3813fcd20503f7d799237ac09dd354ab6ed Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 18 Dec 2015 16:18:10 +0100 Subject: travis bugfix --- .travis.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 20b1a4d..ed6dcf8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,8 @@ python: env: - CYTHON_INSTALL="pip install -q cython" - CYTHON_INSTALL="" -virtualenv: - system_site_packages: true -before_install: - - sudo apt-get install python-scipy install: + - pip install scipy - $CYTHON_INSTALL script: -- cgit v1.2.3 From 9b21f568833c0aa851579aacc191836ac00acc83 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Fri, 18 Dec 2015 16:27:17 +0100 Subject: travis bugfix --- .travis.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.travis.yml b/.travis.yml index ed6dcf8..2951d32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,11 @@ python: env: - CYTHON_INSTALL="pip install -q cython" - CYTHON_INSTALL="" +before_install: + - sudo apt-get update + - sudo apt-get install libblas-dev + - sudo apt-get install liblapack-dev + - sudo apt-get install gfortran install: - pip install scipy - $CYTHON_INSTALL -- cgit v1.2.3 From 94c5fd007d33a38f3c9d1121749cb6ffb162394c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sat, 19 Dec 2015 13:05:23 +0100 Subject: python3 print function in new regression test --- .../test_regression_random_spikes.py | 30 +++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/test/test_regression/test_regression_random_spikes.py b/test/test_regression/test_regression_random_spikes.py index 757bab2..dfd5488 100644 --- a/test/test_regression/test_regression_random_spikes.py +++ b/test/test_regression/test_regression_random_spikes.py @@ -4,6 +4,8 @@ Copyright 2015, Mario Mulansky Distributed under the BSD License """ +from __future__ import print_function + import numpy as np from scipy.io import loadmat import pyspike as spk @@ -63,10 +65,10 @@ def check_regression_dataset(spike_file="benchmark.mat", # spike_sync = spk.spike_sync_multi(spike_trains) if abs(isi - results_cSPIKY[i][0]) > 1E-14: - print "Error in ISI:", i, isi, results_cSPIKY[i][0] - print "Spike trains:" + print("Error in ISI:", i, isi, results_cSPIKY[i][0]) + print("Spike trains:") for st in spike_trains: - print st.spikes + print(st.spikes) err = abs(spike - results_cSPIKY[i][1]) if err > 1E-14: @@ -75,35 +77,39 @@ def check_regression_dataset(spike_file="benchmark.mat", err_max = err err_max_ind = i - print "Total Errors:", err_count + print("Total Errors:", err_count) if err_max_ind > -1: - print "Max SPIKE distance error:", err_max, "at index:", err_max_ind + print("Max SPIKE distance error:", err_max, "at index:", err_max_ind) spike_train_data = spike_train_sets[err_max_ind] for spikes in spike_train_data[0]: - print spikes.flatten() + print(spikes.flatten()) def check_single_spike_train_set(index): """ Debuging function """ np.set_printoptions(precision=15) + spike_file = "regression_random_spikes.mat" + spikes_name = "spikes" + result_name = "Distances" + result_file = "regression_random_results_cSPIKY.mat" - spike_train_sets = loadmat("benchmark.mat")['spikes'][0] + spike_train_sets = loadmat(spike_file)[spikes_name][0] - results_cSPIKY = loadmat("results_cSPIKY.mat")['Distances'] + results_cSPIKY = loadmat(result_file)[result_name] spike_train_data = spike_train_sets[index] spike_trains = [] for spikes in spike_train_data[0]: - print "Spikes:", spikes.flatten() + print("Spikes:", spikes.flatten()) spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) - print spk.spike_distance_multi(spike_trains) + print(spk.spike_distance_multi(spike_trains)) - print results_cSPIKY[index][1] + print(results_cSPIKY[index][1]) - print spike_trains[1].spikes + print(spike_trains[1].spikes) if __name__ == "__main__": -- cgit v1.2.3 From e32272f4540de347abcc548a94239b625458b3a6 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 22 Dec 2015 18:15:59 +0100 Subject: changed edge correction for single spikes Spike trains with single spikes now only get auxiliary spikes at the edges for the SPIKE distance instead of real spikes before. --- pyspike/SpikeTrain.py | 2 +- pyspike/cython/cython_distances.pyx | 89 ++++++++++++++++++++++--------------- pyspike/cython/cython_profiles.pyx | 67 +++++++++++++++------------- pyspike/cython/python_backend.py | 68 +++++++++++++--------------- test/test_empty.py | 16 ++++--- 5 files changed, 130 insertions(+), 112 deletions(-) diff --git a/pyspike/SpikeTrain.py b/pyspike/SpikeTrain.py index 4b59a5d..19f2419 100644 --- a/pyspike/SpikeTrain.py +++ b/pyspike/SpikeTrain.py @@ -68,7 +68,7 @@ class SpikeTrain(object): """Returns the spikes of this spike train with auxiliary spikes in case of empty spike trains. """ - if len(self.spikes) < 2: + if len(self.spikes) < 1: return np.unique(np.insert([self.t_start, self.t_end], 1, self.spikes)) else: diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index 6c6e7e5..7dc9cb9 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -55,20 +55,27 @@ def isi_distance_cython(double[:] s1, double[:] s2, 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 - nu1 = fmax(s1[0]-t_start, s1[1]-s1[0]) + # 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: - nu1 = s1[1]-s1[0] + # 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 - nu2 = fmax(s2[0]-t_start, s2[1]-s2[0]) + # 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] + nu2 = s2[1]-s2[0] if N2 > 1 else t_end-s2[0] index2 = 0 last_t = t_start @@ -86,8 +93,12 @@ def isi_distance_cython(double[:] s1, double[:] s2, if index1 < N1-1: nu1 = s1[index1+1]-s1[index1] else: - # edge correction - nu1 = fmax(t_end-s1[index1], nu1) + # 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 @@ -95,8 +106,9 @@ def isi_distance_cython(double[:] s1, double[:] s2, if index2 < N2-1: nu2 = s2[index2+1]-s2[index2] else: - # edge correction - nu2 = fmax(t_end-s2[index2], nu2) + # 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 @@ -104,13 +116,15 @@ def isi_distance_cython(double[:] s1, double[:] s2, if index1 < N1-1: nu1 = s1[index1+1]-s1[index1] else: - # edge correction - nu1 = fmax(t_end-s1[index1], nu1) + # 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 - nu2 = fmax(t_end-s2[index2], nu2) + # 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) @@ -184,16 +198,18 @@ def spike_distance_cython(double[:] t1, double[:] t2, N1 = len(t1) N2 = len(t2) - # with nogil: # release the interpreter to allow multithreading - if True: + # 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 - # t_p1 = t_start - # t_p2 = t_start # auxiliary spikes for edge correction - consistent with first/last ISI - t_aux1[0] = fmin(t_start, t1[0]-(t1[1]-t1[0])) - t_aux1[1] = fmax(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) - t_aux2[0] = fmin(t_start, t2[0]-(t2[1]-t2[0])) - t_aux2[1] = fmax(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) + 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] @@ -201,17 +217,16 @@ def spike_distance_cython(double[:] t1, double[:] t2, # 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]) + 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] + 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 = t_start-t_p2 # 0.0 dt_p1 = get_min_dist_cython(t_p1, t2, N2, 0, t_aux2[0], t_aux2[1]) - isi1 = t1[1]-t1[0] + isi1 = t_f1-t1[0] s1 = dt_p1 index1 = 0 if t2[0] > t_start: @@ -219,16 +234,16 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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]) + 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] + 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 = t2[1]-t2[0] + isi2 = t_f2-t2[0] s2 = dt_p2 index2 = 0 @@ -263,7 +278,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, s1 = dt_p1 else: dt_f1 = dt_p1 - isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) + 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 @@ -296,7 +312,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, s2 = dt_p2 else: dt_f2 = dt_p2 - isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) + 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 @@ -322,7 +339,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, else: t_f1 = t_aux1[1] dt_f1 = dt_p1 - isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) + 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, @@ -331,7 +349,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, else: t_f2 = t_aux2[1] dt_f2 = dt_p2 - isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) + 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]) diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx index f08de6e..4a42cdb 100644 --- a/pyspike/cython/cython_profiles.pyx +++ b/pyspike/cython/cython_profiles.pyx @@ -63,18 +63,18 @@ def isi_profile_cython(double[:] s1, double[:] s2, # 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]) + 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] + 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]) + 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] + nu2 = s2[1]-s2[0] if N2 > 1 else t_end-s2[0] index2 = 0 isi_values[0] = fabs(nu1-nu2)/fmax(nu1, nu2) @@ -92,7 +92,8 @@ def isi_profile_cython(double[:] s1, double[:] s2, nu1 = s1[index1+1]-s1[index1] else: # edge correction - nu1 = fmax(t_end-s1[index1], nu1) + 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 @@ -101,7 +102,8 @@ def isi_profile_cython(double[:] s1, double[:] s2, nu2 = s2[index2+1]-s2[index2] else: # edge correction - nu2 = fmax(t_end-s2[index2], nu2) + 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 @@ -110,12 +112,14 @@ def isi_profile_cython(double[:] s1, double[:] s2, nu1 = s1[index1+1]-s1[index1] else: # edge correction - nu1 = fmax(t_end-s1[index1], nu1) + 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) + 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 @@ -191,9 +195,9 @@ def spike_profile_cython(double[:] t1, double[:] t2, N1 = len(t1) N2 = len(t2) - # we can assume at least two spikes per spike train - assert N1 > 1 - assert N2 > 1 + # we can assume at least one spikes per spike train + assert N1 > 0 + assert N2 > 0 spike_events = np.empty(N1+N2+2) @@ -205,26 +209,26 @@ def spike_profile_cython(double[:] t1, double[:] t2, # t_p1 = t_start # t_p2 = t_start # auxiliary spikes for edge correction - consistent with first/last ISI - t_aux1[0] = fmin(t_start, t1[0]-(t1[1]-t1[0])) - t_aux1[1] = fmax(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) - t_aux2[0] = fmin(t_start, t2[0]-(t2[1]-t2[0])) - t_aux2[1] = fmax(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-2])) + 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]) + 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] + 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 = 0.0 - isi1 = t1[1]-t1[0] + 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: @@ -232,15 +236,15 @@ def spike_profile_cython(double[:] t1, double[:] t2, 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]) + 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] + 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 = 0.0 - isi2 = t2[1]-t2[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 @@ -273,7 +277,8 @@ def spike_profile_cython(double[:] t1, double[:] t2, s1 = dt_p1 else: dt_f1 = dt_p1 - isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) + 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 @@ -305,7 +310,8 @@ def spike_profile_cython(double[:] t1, double[:] t2, s2 = dt_p2 else: dt_f2 = dt_p2 - isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) + 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 @@ -330,7 +336,8 @@ def spike_profile_cython(double[:] t1, double[:] t2, else: t_f1 = t_aux1[1] dt_f1 = dt_p1 - isi1 = fmax(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) + 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, @@ -339,18 +346,14 @@ def spike_profile_cython(double[:] t1, double[:] t2, else: t_f2 = t_aux2[1] dt_f2 = dt_p2 - isi2 = fmax(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) + 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 - # the ending value of the last interval - 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 s1 = dt_f1 s2 = dt_f2 y_ends[index-1] = (s1*isi2 + s2*isi1) / isi_avrg_cython(isi1, isi2) diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index cf898d7..a1e3a65 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -28,17 +28,17 @@ def isi_distance_python(s1, s2, t_start, t_end): 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]) + 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] + 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]) + 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] + nu2 = s2[1] - s2[0] if N2 > 1 else t_end-s2[0] index2 = 0 isi_values[0] = abs(nu1 - nu2) / max(nu1, nu2) @@ -52,7 +52,8 @@ def isi_distance_python(s1, s2, t_start, t_end): nu1 = s1[index1+1]-s1[index1] else: # edge correction - nu1 = max(t_end-s1[N1-1], s1[N1-1]-s1[N1-2]) + 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]): @@ -62,7 +63,8 @@ def isi_distance_python(s1, s2, t_start, t_end): nu2 = s2[index2+1]-s2[index2] else: # edge correction - nu2 = max(t_end-s2[N2-1], s2[N2-1]-s2[N2-2]) + 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 @@ -72,12 +74,14 @@ def isi_distance_python(s1, s2, t_start, t_end): nu1 = s1[index1+1]-s1[index1] else: # edge correction - nu1 = max(t_end-s1[N1-1], s1[N1-1]-s1[N1-2]) + 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]) + 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) @@ -146,10 +150,10 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): t_aux1 = np.zeros(2) t_aux2 = np.zeros(2) - t_aux1[0] = min(t_start, t1[0]-(t1[1]-t1[0])) - t_aux1[1] = max(t_end, t1[N1-1]+(t1[N1-1]-t1[N1-2])) - t_aux2[0] = min(t_start, t2[0]-(t2[1]-t2[0])) - t_aux2[1] = max(t_end, t2[N2-1]+(t2[N2-1]-t2[N2-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] @@ -160,16 +164,16 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): 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]) + 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]) - t_f1 = t1[1] dt_f1 = get_min_dist(t_f1, t2, 0, t_aux2[0], t_aux2[1]) - isi1 = t1[1]-t1[0] + isi1 = t_f1-t1[0] s1 = dt_p1 index1 = 0 if t2[0] > t_start: @@ -177,23 +181,18 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): 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]) + 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: - dt_p2 = t_start-t_p1 + 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]) - t_f2 = t2[1] dt_f2 = get_min_dist(t_f2, t1, 0, t_aux1[0], t_aux1[1]) - isi2 = t2[1]-t2[0] + isi2 = t_f2-t2[0] s2 = dt_p2 index2 = 0 - # print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) - # print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) - # print "s1: ", repr(s1), ", s2:", repr(s2) - y_starts[0] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) index = 1 @@ -221,7 +220,8 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s1 = dt_p1 else: dt_f1 = dt_p1 - isi1 = max(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) + 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 @@ -250,7 +250,8 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): s2 = dt_p2 else: dt_f2 = dt_p2 - isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) + 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 @@ -274,7 +275,8 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): else: t_f1 = t_aux1[1] dt_f1 = dt_p1 - isi1 = max(t_end-t1[N1-1], t1[N1-1]-t1[N1-2]) + 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]) @@ -282,29 +284,19 @@ def spike_distance_python(spikes1, spikes2, t_start, t_end): else: t_f2 = t_aux2[1] dt_f2 = dt_p2 - isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) + isi2 = max(t_end-t2[N2-1], t2[N2-1]-t2[N2-2]) if N2 > 1 \ + else t_end-t2[N2-1] index += 1 - # print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) - # print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) - # print "s1: ", repr(s1), ", s2:", repr(s2) - # the last event is the interval end if spike_events[index-1] == t_end: index -= 1 else: spike_events[index] = t_end - # the ending value of the last interval - 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_ends[index-1] = (s1*isi2 + s2*isi1) / (0.5*(isi1+isi2)**2) - # print "t_p1:", repr(t_p1), ", t_f1:", repr(t_f1), ", dt_p1:", repr(dt_p1), ", dt_f1:", repr(dt_f1) - # print "t_p2:", repr(t_p2), ", t_f2:", repr(t_f2), ", dt_p2:", repr(dt_p2), ", dt_f2:", repr(dt_f2) - # print "s1: ", repr(s1), ", s2:", repr(s2) - # 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] diff --git a/test/test_empty.py b/test/test_empty.py index 5a0042f..4d0a5cf 100644 --- a/test/test_empty.py +++ b/test/test_empty.py @@ -24,7 +24,9 @@ def test_get_non_empty(): st = SpikeTrain([0.5, ], edges=(0.0, 1.0)) spikes = st.get_spikes_non_empty() - assert_array_equal(spikes, [0.0, 0.5, 1.0]) + # assert_array_equal(spikes, [0.0, 0.5, 1.0]) + # spike trains with one spike don't get edge spikes anymore + assert_array_equal(spikes, [0.5, ]) def test_isi_empty(): @@ -70,21 +72,23 @@ def test_spike_empty(): st1 = SpikeTrain([], edges=(0.0, 1.0)) st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) d = spk.spike_distance(st1, st2) - d_expect = 0.4*0.4*1.0/(0.4+1.0)**2 + 0.6*0.4*1.0/(0.6+1.0)**2 + d_expect = 2*0.4*0.4*1.0/(0.4+1.0)**2 + 2*0.6*0.4*1.0/(0.6+1.0)**2 assert_almost_equal(d, d_expect, decimal=15) prof = spk.spike_profile(st1, st2) assert_equal(d, prof.avrg()) assert_array_equal(prof.x, [0.0, 0.4, 1.0]) - assert_array_almost_equal(prof.y1, [0.0, 2*0.4*1.0/(0.6+1.0)**2], + assert_array_almost_equal(prof.y1, [2*0.4*1.0/(0.4+1.0)**2, + 2*0.4*1.0/(0.6+1.0)**2], decimal=15) - assert_array_almost_equal(prof.y2, [2*0.4*1.0/(0.4+1.0)**2, 0.0], + assert_array_almost_equal(prof.y2, [2*0.4*1.0/(0.4+1.0)**2, + 2*0.4*1.0/(0.6+1.0)**2], decimal=15) st1 = SpikeTrain([0.6, ], edges=(0.0, 1.0)) st2 = SpikeTrain([0.4, ], edges=(0.0, 1.0)) d = spk.spike_distance(st1, st2) - s1 = np.array([0.0, 0.4*0.2/0.6, 0.2, 0.0]) - s2 = np.array([0.0, 0.2, 0.2*0.4/0.6, 0.0]) + s1 = np.array([0.2, 0.2, 0.2, 0.2]) + s2 = np.array([0.2, 0.2, 0.2, 0.2]) isi1 = np.array([0.6, 0.6, 0.4]) isi2 = np.array([0.4, 0.6, 0.6]) expected_y1 = (s1[:-1]*isi2+s2[:-1]*isi1) / (0.5*(isi1+isi2)**2) -- cgit v1.2.3 From 47b9288d94db62a1e133bd1801522b2ede3dcdb7 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 22 Dec 2015 18:18:39 +0100 Subject: moved extensive random spike test to test/numeric --- test/numeric/regression_random_results_cSPIKY.mat | Bin 0 -> 149104 bytes test/numeric/regression_random_spikes.mat | Bin 0 -> 16241579 bytes test/numeric/test_regression_random_spikes.py | 119 +++++++++++++++++++++ .../regression_random_results_cSPIKY.mat | Bin 149130 -> 0 bytes test/test_regression/regression_random_spikes.mat | Bin 16241579 -> 0 bytes .../test_regression_random_spikes.py | 119 --------------------- 6 files changed, 119 insertions(+), 119 deletions(-) create mode 100644 test/numeric/regression_random_results_cSPIKY.mat create mode 100644 test/numeric/regression_random_spikes.mat create mode 100644 test/numeric/test_regression_random_spikes.py delete mode 100644 test/test_regression/regression_random_results_cSPIKY.mat delete mode 100644 test/test_regression/regression_random_spikes.mat delete mode 100644 test/test_regression/test_regression_random_spikes.py diff --git a/test/numeric/regression_random_results_cSPIKY.mat b/test/numeric/regression_random_results_cSPIKY.mat new file mode 100644 index 0000000..26f29ff Binary files /dev/null and b/test/numeric/regression_random_results_cSPIKY.mat differ diff --git a/test/numeric/regression_random_spikes.mat b/test/numeric/regression_random_spikes.mat new file mode 100644 index 0000000..e5ebeb1 Binary files /dev/null and b/test/numeric/regression_random_spikes.mat differ diff --git a/test/numeric/test_regression_random_spikes.py b/test/numeric/test_regression_random_spikes.py new file mode 100644 index 0000000..1441de1 --- /dev/null +++ b/test/numeric/test_regression_random_spikes.py @@ -0,0 +1,119 @@ +""" regression benchmark + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License +""" +from __future__ import print_function + +import numpy as np +from scipy.io import loadmat +import pyspike as spk + +from numpy.testing import assert_almost_equal + +spk.disable_backend_warning = True + + +def test_regression_random(): + + spike_file = "test/numeric/regression_random_spikes.mat" + spikes_name = "spikes" + result_name = "Distances" + result_file = "test/numeric/regression_random_results_cSPIKY.mat" + + spike_train_sets = loadmat(spike_file)[spikes_name][0] + results_cSPIKY = loadmat(result_file)[result_name] + + for i, spike_train_data in enumerate(spike_train_sets): + spike_trains = [] + for spikes in spike_train_data[0]: + spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + + isi = spk.isi_distance_multi(spike_trains) + spike = spk.spike_distance_multi(spike_trains) + # spike_sync = spk.spike_sync_multi(spike_trains) + + assert_almost_equal(isi, results_cSPIKY[i][0], decimal=14, + err_msg="Index: %d, ISI" % i) + assert_almost_equal(spike, results_cSPIKY[i][1], decimal=14, + err_msg="Index: %d, SPIKE" % i) + + +def check_regression_dataset(spike_file="benchmark.mat", + spikes_name="spikes", + result_file="results_cSPIKY.mat", + result_name="Distances"): + """ Debuging function """ + np.set_printoptions(precision=15) + + spike_train_sets = loadmat(spike_file)[spikes_name][0] + + results_cSPIKY = loadmat(result_file)[result_name] + + err_max = 0.0 + err_max_ind = -1 + err_count = 0 + + for i, spike_train_data in enumerate(spike_train_sets): + spike_trains = [] + for spikes in spike_train_data[0]: + spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + + isi = spk.isi_distance_multi(spike_trains) + spike = spk.spike_distance_multi(spike_trains) + # spike_sync = spk.spike_sync_multi(spike_trains) + + if abs(isi - results_cSPIKY[i][0]) > 1E-14: + print("Error in ISI:", i, isi, results_cSPIKY[i][0]) + print("Spike trains:") + for st in spike_trains: + print(st.spikes) + + err = abs(spike - results_cSPIKY[i][1]) + if err > 1E-14: + err_count += 1 + if err > err_max: + err_max = err + err_max_ind = i + + print("Total Errors:", err_count) + + if err_max_ind > -1: + print("Max SPIKE distance error:", err_max, "at index:", err_max_ind) + spike_train_data = spike_train_sets[err_max_ind] + for spikes in spike_train_data[0]: + print(spikes.flatten()) + + +def check_single_spike_train_set(index): + """ Debuging function """ + np.set_printoptions(precision=15) + spike_file = "regression_random_spikes.mat" + spikes_name = "spikes" + result_name = "Distances" + result_file = "regression_random_results_cSPIKY.mat" + + spike_train_sets = loadmat(spike_file)[spikes_name][0] + + results_cSPIKY = loadmat(result_file)[result_name] + + spike_train_data = spike_train_sets[index] + + spike_trains = [] + for spikes in spike_train_data[0]: + print("Spikes:", spikes.flatten()) + spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + + print(spk.spike_distance_multi(spike_trains)) + + print(results_cSPIKY[index][1]) + + print(spike_trains[1].spikes) + + +if __name__ == "__main__": + + test_regression_random() + # check_regression_dataset() + # check_single_spike_train_set(7633) diff --git a/test/test_regression/regression_random_results_cSPIKY.mat b/test/test_regression/regression_random_results_cSPIKY.mat deleted file mode 100644 index 47c9b50..0000000 Binary files a/test/test_regression/regression_random_results_cSPIKY.mat and /dev/null differ diff --git a/test/test_regression/regression_random_spikes.mat b/test/test_regression/regression_random_spikes.mat deleted file mode 100644 index e5ebeb1..0000000 Binary files a/test/test_regression/regression_random_spikes.mat and /dev/null differ diff --git a/test/test_regression/test_regression_random_spikes.py b/test/test_regression/test_regression_random_spikes.py deleted file mode 100644 index dfd5488..0000000 --- a/test/test_regression/test_regression_random_spikes.py +++ /dev/null @@ -1,119 +0,0 @@ -""" regression benchmark - -Copyright 2015, Mario Mulansky - -Distributed under the BSD License -""" -from __future__ import print_function - -import numpy as np -from scipy.io import loadmat -import pyspike as spk - -from numpy.testing import assert_almost_equal - -spk.disable_backend_warning = True - - -def test_regression_random(): - - spike_file = "test/test_regression/regression_random_spikes.mat" - spikes_name = "spikes" - result_name = "Distances" - result_file = "test/test_regression/regression_random_results_cSPIKY.mat" - - spike_train_sets = loadmat(spike_file)[spikes_name][0] - results_cSPIKY = loadmat(result_file)[result_name] - - for i, spike_train_data in enumerate(spike_train_sets): - spike_trains = [] - for spikes in spike_train_data[0]: - spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) - - isi = spk.isi_distance_multi(spike_trains) - spike = spk.spike_distance_multi(spike_trains) - # spike_sync = spk.spike_sync_multi(spike_trains) - - assert_almost_equal(isi, results_cSPIKY[i][0], decimal=14, - err_msg="Index: %d, ISI" % i) - assert_almost_equal(spike, results_cSPIKY[i][1], decimal=14, - err_msg="Index: %d, SPIKE" % i) - - -def check_regression_dataset(spike_file="benchmark.mat", - spikes_name="spikes", - result_file="results_cSPIKY.mat", - result_name="Distances"): - """ Debuging function """ - np.set_printoptions(precision=15) - - spike_train_sets = loadmat(spike_file)[spikes_name][0] - - results_cSPIKY = loadmat(result_file)[result_name] - - err_max = 0.0 - err_max_ind = -1 - err_count = 0 - - for i, spike_train_data in enumerate(spike_train_sets): - spike_trains = [] - for spikes in spike_train_data[0]: - spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) - - isi = spk.isi_distance_multi(spike_trains) - spike = spk.spike_distance_multi(spike_trains) - # spike_sync = spk.spike_sync_multi(spike_trains) - - if abs(isi - results_cSPIKY[i][0]) > 1E-14: - print("Error in ISI:", i, isi, results_cSPIKY[i][0]) - print("Spike trains:") - for st in spike_trains: - print(st.spikes) - - err = abs(spike - results_cSPIKY[i][1]) - if err > 1E-14: - err_count += 1 - if err > err_max: - err_max = err - err_max_ind = i - - print("Total Errors:", err_count) - - if err_max_ind > -1: - print("Max SPIKE distance error:", err_max, "at index:", err_max_ind) - spike_train_data = spike_train_sets[err_max_ind] - for spikes in spike_train_data[0]: - print(spikes.flatten()) - - -def check_single_spike_train_set(index): - """ Debuging function """ - np.set_printoptions(precision=15) - spike_file = "regression_random_spikes.mat" - spikes_name = "spikes" - result_name = "Distances" - result_file = "regression_random_results_cSPIKY.mat" - - spike_train_sets = loadmat(spike_file)[spikes_name][0] - - results_cSPIKY = loadmat(result_file)[result_name] - - spike_train_data = spike_train_sets[index] - - spike_trains = [] - for spikes in spike_train_data[0]: - print("Spikes:", spikes.flatten()) - spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) - - print(spk.spike_distance_multi(spike_trains)) - - print(results_cSPIKY[index][1]) - - print(spike_trains[1].spikes) - - -if __name__ == "__main__": - - test_regression_random() - # check_regression_dataset() - # check_single_spike_train_set(7633) -- cgit v1.2.3 From cffe3310b32eca3364319f656c235847069e8597 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 22 Dec 2015 18:19:54 +0100 Subject: add numeric tests to travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2951d32..d23d865 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ install: script: - python setup.py build_ext --inplace - nosetests + - nosetests test/numeric -- cgit v1.2.3 From 2f48f27b55f63726216b6e674fb88b3790b59147 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 28 Jan 2016 12:48:51 +0100 Subject: +numeric test of profiles --- test/numeric/test_regression_random_spikes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/numeric/test_regression_random_spikes.py b/test/numeric/test_regression_random_spikes.py index 1441de1..6156bb4 100644 --- a/test/numeric/test_regression_random_spikes.py +++ b/test/numeric/test_regression_random_spikes.py @@ -31,13 +31,21 @@ def test_regression_random(): spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) isi = spk.isi_distance_multi(spike_trains) + isi_prof = spk.isi_profile_multi(spike_trains).avrg() + spike = spk.spike_distance_multi(spike_trains) + spike_prof = spk.spike_profile_multi(spike_trains).avrg() # spike_sync = spk.spike_sync_multi(spike_trains) assert_almost_equal(isi, results_cSPIKY[i][0], decimal=14, err_msg="Index: %d, ISI" % i) + assert_almost_equal(isi_prof, results_cSPIKY[i][0], decimal=14, + err_msg="Index: %d, ISI" % i) + assert_almost_equal(spike, results_cSPIKY[i][1], decimal=14, err_msg="Index: %d, SPIKE" % i) + assert_almost_equal(spike_prof, results_cSPIKY[i][1], decimal=14, + err_msg="Index: %d, SPIKE" % i) def check_regression_dataset(spike_file="benchmark.mat", -- cgit v1.2.3 From 5a556a11fbf8434bd38fa73e05054d581018a4da Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 31 Jan 2016 15:05:21 +0100 Subject: generalized interface to isi profile and distance isi profile and distance functionc an now compute bi-variate and multi-variate results. Therefore, it can be called with different "overloads". --- pyspike/isi_distance.py | 116 +++++++++++++++++++++++++++++----------- test/test_generic_interfaces.py | 64 ++++++++++++++++++++++ 2 files changed, 148 insertions(+), 32 deletions(-) create mode 100644 test/test_generic_interfaces.py diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 0ae7393..a85028f 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -13,11 +13,40 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ ############################################################ # isi_profile ############################################################ -def isi_profile(spike_train1, spike_train2): +def isi_profile(*args, **kwargs): """ Computes the isi-distance profile :math:`I(t)` of the two given - spike trains. Retruns the profile as a PieceWiseConstFunc object. The + spike trains. Returns the profile as a PieceWiseConstFunc object. The ISI-values are defined positive :math:`I(t)>=0`. + Valid call structures:: + + isi_profile(st1, st2) # returns the bi-variate profile + isi_profile(st1, st2, st3) # multi-variate profile of 3 spike trains + spike_trains = [st1, st2, st3, st4] # list of spike trains + isi_profile(spike_trains) # return the profile the list of spike trains + isi_profile(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + :returns: The isi-distance profile :math:`I(t)` + :rtype: :class:`.PieceWiseConstFunc` + """ + if len(args) == 1: + return isi_profile_multi(args[0], **kwargs) + elif len(args) == 2: + return isi_profile_bi(args[0], args[1]) + else: + return isi_profile_multi(args) + + +############################################################ +# isi_profile_bi +############################################################ +def isi_profile_bi(spike_train1, spike_train2): + """ Bi-variate ISI-profile. + Computes the isi-distance profile :math:`I(t)` of the two given + spike trains. Returns the profile as a PieceWiseConstFunc object. + See :func:`.isi_profile`. + :param spike_train1: First spike train. :type spike_train1: :class:`.SpikeTrain` :param spike_train2: Second spike train. @@ -51,10 +80,57 @@ Falling back to slow python backend.") return PieceWiseConstFunc(times, values) +############################################################ +# isi_profile_multi +############################################################ +def isi_profile_multi(spike_trains, indices=None): + """ computes the multi-variate isi distance profile for a set of spike + trains. That is the average isi-distance of all pairs of spike-trains: + + .. math:: = \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, + + where the sum goes over all pairs + + :param spike_trains: list of :class:`.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type state: list or None + :returns: The averaged isi profile :math:`` + :rtype: :class:`.PieceWiseConstFunc` + """ + average_dist, M = _generic_profile_multi(spike_trains, isi_profile_bi, + indices) + average_dist.mul_scalar(1.0/M) # normalize + return average_dist + + ############################################################ # isi_distance ############################################################ -def isi_distance(spike_train1, spike_train2, interval=None): +def isi_distance(*args, **kwargs): + # spike_trains, spike_train2, interval=None): + """ Computes the ISI-distance :math:`D_I` of the given spike trains. The + isi-distance is the integral over the isi distance profile + :math:`I(t)`: + + .. math:: D_I = \\int_{T_0}^{T_1} I(t) dt. + + :returns: The isi-distance :math:`D_I`. + :rtype: double + """ + + if len(args) == 1: + return isi_distance_multi(args[0], **kwargs) + elif len(args) == 2: + return isi_distance_bi(args[0], args[1], **kwargs) + else: + return isi_distance_multi(args, **kwargs) + + +############################################################ +# isi_distance_bi +############################################################ +def isi_distance_bi(spike_train1, spike_train2, interval=None): """ Computes the ISI-distance :math:`D_I` of the given spike trains. The isi-distance is the integral over the isi distance profile :math:`I(t)`: @@ -84,34 +160,10 @@ def isi_distance(spike_train1, spike_train2, interval=None): spike_train1.t_start, spike_train1.t_end) except ImportError: # Cython backend not available: fall back to profile averaging - return isi_profile(spike_train1, spike_train2).avrg(interval) + return isi_profile_bi(spike_train1, spike_train2).avrg(interval) else: # some specific interval is provided: use profile - return isi_profile(spike_train1, spike_train2).avrg(interval) - - -############################################################ -# isi_profile_multi -############################################################ -def isi_profile_multi(spike_trains, indices=None): - """ computes the multi-variate isi distance profile for a set of spike - trains. That is the average isi-distance of all pairs of spike-trains: - - .. math:: = \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, - - where the sum goes over all pairs - - :param spike_trains: list of :class:`.SpikeTrain` - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type state: list or None - :returns: The averaged isi profile :math:`` - :rtype: :class:`.PieceWiseConstFunc` - """ - average_dist, M = _generic_profile_multi(spike_trains, isi_profile, - indices) - average_dist.mul_scalar(1.0/M) # normalize - return average_dist + return isi_profile_bi(spike_train1, spike_train2).avrg(interval) ############################################################ @@ -134,7 +186,7 @@ def isi_distance_multi(spike_trains, indices=None, interval=None): :returns: The time-averaged multivariate ISI distance :math:`D_I` :rtype: double """ - return _generic_distance_multi(spike_trains, isi_distance, indices, + return _generic_distance_multi(spike_trains, isi_distance_bi, indices, interval) @@ -155,5 +207,5 @@ def isi_distance_matrix(spike_trains, indices=None, interval=None): :math:`D_{I}^{ij}` :rtype: np.array """ - return _generic_distance_matrix(spike_trains, isi_distance, - indices, interval) + return _generic_distance_matrix(spike_trains, isi_distance_bi, + indices=indices, interval=interval) diff --git a/test/test_generic_interfaces.py b/test/test_generic_interfaces.py new file mode 100644 index 0000000..caa9ee4 --- /dev/null +++ b/test/test_generic_interfaces.py @@ -0,0 +1,64 @@ +""" test_isi_interface.py + +Tests the generic interfaces of the profile and distance functions + +Copyright 2016, Mario Mulansky + +Distributed under the BSD License + +""" + +from __future__ import print_function +from numpy.testing import assert_equal + +import pyspike as spk +from pyspike import SpikeTrain + + +class dist_from_prof: + """ Simple functor that turns profile function into distance function by + calling profile.avrg(). + """ + def __init__(self, prof_func): + self.prof_func = prof_func + + def __call__(self, *args, **kwargs): + return self.prof_func(*args, **kwargs).avrg() + + +def check_func(dist_func): + """ generic checker that tests the given distance function. + """ + # generate spike trains: + t1 = SpikeTrain([0.2, 0.4, 0.6, 0.7], 1.0) + t2 = SpikeTrain([0.3, 0.45, 0.8, 0.9, 0.95], 1.0) + t3 = SpikeTrain([0.2, 0.4, 0.6], 1.0) + t4 = SpikeTrain([0.1, 0.4, 0.5, 0.6], 1.0) + spike_trains = [t1, t2, t3, t4] + + isi12 = dist_func(t1, t2) + isi12_ = dist_func([t1, t2]) + assert_equal(isi12, isi12_) + + isi12_ = dist_func(spike_trains, indices=[0, 1]) + assert_equal(isi12, isi12_) + + isi123 = dist_func(t1, t2, t3) + isi123_ = dist_func([t1, t2, t3]) + assert_equal(isi123, isi123_) + + isi123_ = dist_func(spike_trains, indices=[0, 1, 2]) + assert_equal(isi123, isi123_) + + +def test_isi_profile(): + check_func(dist_from_prof(spk.isi_profile)) + + +def test_isi_distance(): + check_func(spk.isi_distance) + + +if __name__ == "__main__": + test_isi_profile() + test_isi_distance() -- cgit v1.2.3 From ea3709e2f4367cb539acc26ec8e05b686d6bf836 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 31 Jan 2016 16:40:47 +0100 Subject: generic interface for spike distance/profile spike_profile and spike_distance now have a generic interface that allows to compute bi-variate and multi-variate results with the same function. --- pyspike/isi_distance.py | 17 +++- pyspike/spike_distance.py | 124 +++++++++++++++++++++-------- test/test_generic_interfaces.py | 31 +++++++- test/test_regression/test_regression_15.py | 1 + 4 files changed, 138 insertions(+), 35 deletions(-) diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index a85028f..122e11d 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -14,7 +14,7 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ # isi_profile ############################################################ def isi_profile(*args, **kwargs): - """ Computes the isi-distance profile :math:`I(t)` of the two given + """ Computes the isi-distance profile :math:`I(t)` of the given spike trains. Returns the profile as a PieceWiseConstFunc object. The ISI-values are defined positive :math:`I(t)>=0`. @@ -22,8 +22,9 @@ def isi_profile(*args, **kwargs): isi_profile(st1, st2) # returns the bi-variate profile isi_profile(st1, st2, st3) # multi-variate profile of 3 spike trains + spike_trains = [st1, st2, st3, st4] # list of spike trains - isi_profile(spike_trains) # return the profile the list of spike trains + isi_profile(spike_trains) # profile of the list of spike trains isi_profile(spike_trains, indices=[0, 1]) # use only the spike trains # given by the indices @@ -108,13 +109,23 @@ def isi_profile_multi(spike_trains, indices=None): # isi_distance ############################################################ def isi_distance(*args, **kwargs): - # spike_trains, spike_train2, interval=None): """ Computes the ISI-distance :math:`D_I` of the given spike trains. The isi-distance is the integral over the isi distance profile :math:`I(t)`: .. math:: D_I = \\int_{T_0}^{T_1} I(t) dt. + + Valid call structures:: + + isi_distance(st1, st2) # returns the bi-variate distance + isi_distance(st1, st2, st3) # multi-variate distance of 3 spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + isi_distance(spike_trains) # distance of the list of spike trains + isi_distance(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + :returns: The isi-distance :math:`D_I`. :rtype: double """ diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index e418283..7acb959 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -13,7 +13,36 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_multi, \ ############################################################ # spike_profile ############################################################ -def spike_profile(spike_train1, spike_train2): +def spike_profile(*args, **kwargs): + """ Computes the spike-distance profile :math:`S(t)` of the given + spike trains. Returns the profile as a PieceWiseConstLin object. The + SPIKE-values are defined positive :math:`S(t)>=0`. + + Valid call structures:: + + spike_profile(st1, st2) # returns the bi-variate profile + spike_profile(st1, st2, st3) # multi-variate profile of 3 spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + spike_profile(spike_trains) # profile of the list of spike trains + spike_profile(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + :returns: The spike-distance profile :math:`S(t)` + :rtype: :class:`.PieceWiseConstLin` + """ + if len(args) == 1: + return spike_profile_multi(args[0], **kwargs) + elif len(args) == 2: + return spike_profile_bi(args[0], args[1]) + else: + return spike_profile_multi(args) + + +############################################################ +# spike_profile_bi +############################################################ +def spike_profile_bi(spike_train1, spike_train2): """ Computes the spike-distance profile :math:`S(t)` of the two given spike trains. Returns the profile as a PieceWiseLinFunc object. The SPIKE-values are defined positive :math:`S(t)>=0`. @@ -53,10 +82,68 @@ Falling back to slow python backend.") return PieceWiseLinFunc(times, y_starts, y_ends) +############################################################ +# spike_profile_multi +############################################################ +def spike_profile_multi(spike_trains, indices=None): + """ Computes the multi-variate spike distance profile for a set of spike + trains. That is the average spike-distance of all pairs of spike-trains: + + .. math:: = \\frac{2}{N(N-1)} \\sum_{} S^{i, j}`, + + where the sum goes over all pairs + + :param spike_trains: list of :class:`.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :returns: The averaged spike profile :math:`(t)` + :rtype: :class:`.PieceWiseLinFunc` + + """ + average_dist, M = _generic_profile_multi(spike_trains, spike_profile_bi, + indices) + average_dist.mul_scalar(1.0/M) # normalize + return average_dist + + ############################################################ # spike_distance ############################################################ -def spike_distance(spike_train1, spike_train2, interval=None): +def spike_distance(*args, **kwargs): + """ Computes the SPIKE-distance :math:`D_S` of the given spike trains. The + spike-distance is the integral over the spike distance profile + :math:`D(t)`: + + .. math:: D_S = \\int_{T_0}^{T_1} S(t) dt. + + + Valid call structures:: + + spike_distance(st1, st2) # returns the bi-variate distance + spike_distance(st1, st2, st3) # multi-variate distance of 3 spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + spike_distance(spike_trains) # distance of the list of spike trains + spike_distance(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + :returns: The spike-distance :math:`D_S`. + :rtype: double + """ + + if len(args) == 1: + return spike_distance_multi(args[0], **kwargs) + elif len(args) == 2: + return spike_distance_bi(args[0], args[1], **kwargs) + else: + return spike_distance_multi(args, **kwargs) + + +############################################################ +# spike_distance_bi +############################################################ +def spike_distance_bi(spike_train1, spike_train2, interval=None): """ Computes the spike-distance :math:`D_S` of the given spike trains. The spike-distance is the integral over the spike distance profile :math:`S(t)`: @@ -86,35 +173,10 @@ def spike_distance(spike_train1, spike_train2, interval=None): spike_train1.t_end) except ImportError: # Cython backend not available: fall back to average profile - return spike_profile(spike_train1, spike_train2).avrg(interval) + return spike_profile_bi(spike_train1, spike_train2).avrg(interval) else: # some specific interval is provided: compute the whole profile - return spike_profile(spike_train1, spike_train2).avrg(interval) - - -############################################################ -# spike_profile_multi -############################################################ -def spike_profile_multi(spike_trains, indices=None): - """ Computes the multi-variate spike distance profile for a set of spike - trains. That is the average spike-distance of all pairs of spike-trains: - - .. math:: = \\frac{2}{N(N-1)} \\sum_{} S^{i, j}`, - - where the sum goes over all pairs - - :param spike_trains: list of :class:`.SpikeTrain` - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :returns: The averaged spike profile :math:`(t)` - :rtype: :class:`.PieceWiseLinFunc` - - """ - average_dist, M = _generic_profile_multi(spike_trains, spike_profile, - indices) - average_dist.mul_scalar(1.0/M) # normalize - return average_dist + return spike_profile_bi(spike_train1, spike_train2).avrg(interval) ############################################################ @@ -139,7 +201,7 @@ def spike_distance_multi(spike_trains, indices=None, interval=None): :returns: The averaged multi-variate spike distance :math:`D_S`. :rtype: double """ - return _generic_distance_multi(spike_trains, spike_distance, indices, + return _generic_distance_multi(spike_trains, spike_distance_bi, indices, interval) @@ -160,5 +222,5 @@ def spike_distance_matrix(spike_trains, indices=None, interval=None): :math:`D_S^{ij}` :rtype: np.array """ - return _generic_distance_matrix(spike_trains, spike_distance, + return _generic_distance_matrix(spike_trains, spike_distance_bi, indices, interval) diff --git a/test/test_generic_interfaces.py b/test/test_generic_interfaces.py index caa9ee4..ee87be4 100644 --- a/test/test_generic_interfaces.py +++ b/test/test_generic_interfaces.py @@ -23,7 +23,12 @@ class dist_from_prof: self.prof_func = prof_func def __call__(self, *args, **kwargs): - return self.prof_func(*args, **kwargs).avrg() + if "interval" in kwargs: + # forward interval arg into avrg function + interval = kwargs.pop("interval") + return self.prof_func(*args, **kwargs).avrg(interval=interval) + else: + return self.prof_func(*args, **kwargs).avrg() def check_func(dist_func): @@ -50,6 +55,22 @@ def check_func(dist_func): isi123_ = dist_func(spike_trains, indices=[0, 1, 2]) assert_equal(isi123, isi123_) + # run the same test with an additional interval parameter + + isi12 = dist_func(t1, t2, interval=[0.0, 0.5]) + isi12_ = dist_func([t1, t2], interval=[0.0, 0.5]) + assert_equal(isi12, isi12_) + + isi12_ = dist_func(spike_trains, indices=[0, 1], interval=[0.0, 0.5]) + assert_equal(isi12, isi12_) + + isi123 = dist_func(t1, t2, t3, interval=[0.0, 0.5]) + isi123_ = dist_func([t1, t2, t3], interval=[0.0, 0.5]) + assert_equal(isi123, isi123_) + + isi123_ = dist_func(spike_trains, indices=[0, 1, 2], interval=[0.0, 0.5]) + assert_equal(isi123, isi123_) + def test_isi_profile(): check_func(dist_from_prof(spk.isi_profile)) @@ -59,6 +80,14 @@ def test_isi_distance(): check_func(spk.isi_distance) +def test_spike_profile(): + check_func(dist_from_prof(spk.spike_profile)) + + +def test_spike_distance(): + check_func(spk.spike_distance) + + if __name__ == "__main__": test_isi_profile() test_isi_distance() diff --git a/test/test_regression/test_regression_15.py b/test/test_regression/test_regression_15.py index dcacae2..54adf23 100644 --- a/test/test_regression/test_regression_15.py +++ b/test/test_regression/test_regression_15.py @@ -20,6 +20,7 @@ import os TEST_PATH = os.path.dirname(os.path.realpath(__file__)) TEST_DATA = os.path.join(TEST_PATH, "..", "SPIKE_Sync_Test.txt") + def test_regression_15_isi(): # load spike trains spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=[0, 4000]) -- cgit v1.2.3 From a57f3d51473b10d81752ad66e4c392563ca1c6f8 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Tue, 2 Feb 2016 17:11:12 +0100 Subject: new generic interface for spike_sync functions Similar to the isi and spike distance functions, also the spike sync functions now support the new generic interface. --- pyspike/spike_sync.py | 136 +++++++++++++++++++++++++++++----------- test/test_generic_interfaces.py | 14 ++++- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 3dc29ff..ccb09d9 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -15,7 +15,40 @@ from pyspike.generic import _generic_profile_multi, _generic_distance_matrix ############################################################ # spike_sync_profile ############################################################ -def spike_sync_profile(spike_train1, spike_train2, max_tau=None): +def spike_sync_profile(*args, **kwargs): + """ Computes the spike-synchronization profile S_sync(t) of the given + spike trains. Returns the profile as a DiscreteFunction object. In the + bivariate case, he S_sync values are either 1 or 0, indicating the presence + or absence of a coincidence. For multi-variate cases, each spike in the set + of spike trains, the profile is defined as the number of coincidences + divided by the number of spike trains pairs involving the spike train of + containing this spike, which is the number of spike trains minus one (N-1). + + Valid call structures:: + + spike_sync_profile(st1, st2) # returns the bi-variate profile + spike_sync_profile(st1, st2, st3) # multi-variate profile of 3 sts + + sts = [st1, st2, st3, st4] # list of spike trains + spike_sync_profile(sts) # profile of the list of spike trains + spike_sync_profile(sts, indices=[0, 1]) # use only the spike trains + # given by the indices + + :returns: The spike-sync profile :math:`S_{sync}(t)`. + :rtype: :class:`pyspike.function.DiscreteFunction` + """ + if len(args) == 1: + return spike_sync_profile_multi(args[0], **kwargs) + elif len(args) == 2: + return spike_sync_profile_bi(args[0], args[1]) + else: + return spike_sync_profile_multi(args) + + +############################################################ +# spike_sync_profile_bi +############################################################ +def spike_sync_profile_bi(spike_train1, spike_train2, max_tau=None): """ Computes the spike-synchronization profile S_sync(t) of the two given spike trains. Returns the profile as a DiscreteFunction object. The S_sync values are either 1 or 0, indicating the presence or absence of a @@ -27,7 +60,7 @@ def spike_sync_profile(spike_train1, spike_train2, max_tau=None): :type spike_train2: :class:`pyspike.SpikeTrain` :param max_tau: Maximum coincidence window size. If 0 or `None`, the coincidence window has no upper bound. - :returns: The spike-distance profile :math:`S_{sync}(t)`. + :returns: The spike-sync profile :math:`S_{sync}(t)`. :rtype: :class:`pyspike.function.DiscreteFunction` """ @@ -61,6 +94,33 @@ Falling back to slow python backend.") return DiscreteFunc(times, coincidences, multiplicity) +############################################################ +# spike_sync_profile_multi +############################################################ +def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): + """ Computes the multi-variate spike synchronization profile for a set of + spike trains. For each spike in the set of spike trains, the multi-variate + profile is defined as the number of coincidences divided by the number of + spike trains pairs involving the spike train of containing this spike, + which is the number of spike trains minus one (N-1). + + :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The multi-variate spike sync profile :math:`(t)` + :rtype: :class:`pyspike.function.DiscreteFunction` + + """ + prof_func = partial(spike_sync_profile_bi, max_tau=max_tau) + average_prof, M = _generic_profile_multi(spike_trains, prof_func, + indices) + # average_dist.mul_scalar(1.0/M) # no normalization here! + return average_prof + + ############################################################ # _spike_sync_values ############################################################ @@ -87,18 +147,51 @@ def _spike_sync_values(spike_train1, spike_train2, interval, max_tau): return c, mp except ImportError: # Cython backend not available: fall back to profile averaging - return spike_sync_profile(spike_train1, spike_train2, - max_tau).integral(interval) + return spike_sync_profile_bi(spike_train1, spike_train2, + max_tau).integral(interval) else: # some specific interval is provided: use profile - return spike_sync_profile(spike_train1, spike_train2, - max_tau).integral(interval) + return spike_sync_profile_bi(spike_train1, spike_train2, + max_tau).integral(interval) ############################################################ # spike_sync ############################################################ -def spike_sync(spike_train1, spike_train2, interval=None, max_tau=None): +def spike_sync(*args, **kwargs): + """ Computes the spike synchronization value SYNC of the given spike + trains. The spike synchronization value is the computed as the total number + of coincidences divided by the total number of spikes: + + .. math:: SYNC = \sum_n C_n / N. + + + Valid call structures:: + + spike_sync(st1, st2) # returns the bi-variate spike synchronization + spike_sync(st1, st2, st3) # multi-variate result for 3 spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + spike_sync(spike_trains) # spike-sync of the list of spike trains + spike_sync(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + :returns: The spike synchronization value. + :rtype: `double` + """ + + if len(args) == 1: + return spike_sync_multi(args[0], **kwargs) + elif len(args) == 2: + return spike_sync_bi(args[0], args[1], **kwargs) + else: + return spike_sync_multi(args, **kwargs) + + +############################################################ +# spike_sync_bi +############################################################ +def spike_sync_bi(spike_train1, spike_train2, interval=None, max_tau=None): """ Computes the spike synchronization value SYNC of the given spike trains. The spike synchronization value is the computed as the total number of coincidences divided by the total number of spikes: @@ -122,33 +215,6 @@ def spike_sync(spike_train1, spike_train2, interval=None, max_tau=None): return 1.0*c/mp -############################################################ -# spike_sync_profile_multi -############################################################ -def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): - """ Computes the multi-variate spike synchronization profile for a set of - spike trains. For each spike in the set of spike trains, the multi-variate - profile is defined as the number of coincidences divided by the number of - spike trains pairs involving the spike train of containing this spike, - which is the number of spike trains minus one (N-1). - - :param spike_trains: list of :class:`pyspike.SpikeTrain` - :param indices: list of indices defining which spike trains to use, - if None all given spike trains are used (default=None) - :type indices: list or None - :param max_tau: Maximum coincidence window size. If 0 or `None`, the - coincidence window has no upper bound. - :returns: The multi-variate spike sync profile :math:`(t)` - :rtype: :class:`pyspike.function.DiscreteFunction` - - """ - prof_func = partial(spike_sync_profile, max_tau=max_tau) - average_prof, M = _generic_profile_multi(spike_trains, prof_func, - indices) - # average_dist.mul_scalar(1.0/M) # no normalization here! - return average_prof - - ############################################################ # spike_sync_multi ############################################################ @@ -211,6 +277,6 @@ def spike_sync_matrix(spike_trains, indices=None, interval=None, max_tau=None): :rtype: np.array """ - dist_func = partial(spike_sync, max_tau=max_tau) + dist_func = partial(spike_sync_bi, max_tau=max_tau) return _generic_distance_matrix(spike_trains, dist_func, indices, interval) diff --git a/test/test_generic_interfaces.py b/test/test_generic_interfaces.py index ee87be4..7f08067 100644 --- a/test/test_generic_interfaces.py +++ b/test/test_generic_interfaces.py @@ -1,4 +1,4 @@ -""" test_isi_interface.py +""" test_generic_interface.py Tests the generic interfaces of the profile and distance functions @@ -88,6 +88,18 @@ def test_spike_distance(): check_func(spk.spike_distance) +def test_spike_sync_profile(): + check_func(dist_from_prof(spk.spike_sync_profile)) + + +def test_spike_sync(): + check_func(spk.spike_sync) + + if __name__ == "__main__": test_isi_profile() test_isi_distance() + test_spike_profile() + test_spike_distance() + test_spike_sync_profile() + test_spike_sync() -- cgit v1.2.3 From b09561705ab9c67c93a384248f7c3bc9ad5bdd32 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 3 Feb 2016 13:07:26 +0100 Subject: fixed spike-sync bug fixed ugly bugs in code for computing multi-variate spike sync profile and multi-variate spike sync value. --- pyspike/DiscreteFunc.py | 9 ++++---- pyspike/cython/cython_add.pyx | 33 ++++++++++++++------------- pyspike/cython/cython_distances.pyx | 5 ---- pyspike/cython/python_backend.py | 33 +++++++++++++++------------ pyspike/spike_sync.py | 10 ++++++-- test/numeric/test_regression_random_spikes.py | 27 +++++++++++++++------- test/test_distance.py | 33 +++++++++++++++++++++++++++ 7 files changed, 99 insertions(+), 51 deletions(-) diff --git a/pyspike/DiscreteFunc.py b/pyspike/DiscreteFunc.py index fe97bc2..caad290 100644 --- a/pyspike/DiscreteFunc.py +++ b/pyspike/DiscreteFunc.py @@ -170,10 +170,6 @@ expected." start_ind, end_ind = get_indices(ival) value += np.sum(self.y[start_ind:end_ind]) multiplicity += np.sum(self.mp[start_ind:end_ind]) - if multiplicity == 0.0: - # empty profile, return spike sync of 1 - value = 1.0 - multiplicity = 1.0 return (value, multiplicity) def avrg(self, interval=None, normalize=True): @@ -190,7 +186,10 @@ expected." """ val, mp = self.integral(interval) if normalize: - return val/mp + if mp > 0: + return val/mp + else: + return 1.0 else: return val diff --git a/pyspike/cython/cython_add.pyx b/pyspike/cython/cython_add.pyx index 8da1e53..25f1181 100644 --- a/pyspike/cython/cython_add.pyx +++ b/pyspike/cython/cython_add.pyx @@ -182,8 +182,8 @@ def add_discrete_function_cython(double[:] x1, double[:] y1, double[:] mp1, cdef int index1 = 0 cdef int index2 = 0 cdef int index = 0 - cdef int N1 = len(y1) - cdef int N2 = len(y2) + 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]: @@ -206,20 +206,21 @@ def add_discrete_function_cython(double[:] x1, double[:] y1, double[:] mp1, 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 < len(y1): - x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] - y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] - mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] - index += len(x1)-index1-1 - 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(x2)-index2-1] = y2[index2+1:] - mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] - index += len(x2)-index2-1 - # 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] + 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] diff --git a/pyspike/cython/cython_distances.pyx b/pyspike/cython/cython_distances.pyx index 7dc9cb9..ac5f226 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -428,9 +428,4 @@ def coincidence_value_cython(double[:] spikes1, double[:] spikes2, mp += 2 coinc += 2 - if coinc == 0 and mp == 0: - # empty spike trains -> spike sync = 1 by definition - coinc = 1 - mp = 1 - return coinc, mp diff --git a/pyspike/cython/python_backend.py b/pyspike/cython/python_backend.py index a1e3a65..6b7209a 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -560,7 +560,9 @@ def add_discrete_function_python(x1, y1, mp1, x2, y2, mp2): index1 = 0 index2 = 0 index = 0 - while (index1+1 < len(y1)) and (index2+1 < len(y2)): + 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 @@ -581,20 +583,21 @@ def add_discrete_function_python(x1, y1, mp1, x2, y2, mp2): 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 < len(y1): - x_new[index+1:index+1+len(x1)-index1-1] = x1[index1+1:] - y_new[index+1:index+1+len(x1)-index1-1] = y1[index1+1:] - mp_new[index+1:index+1+len(x1)-index1-1] = mp1[index1+1:] - index += len(x1)-index1-1 - 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(x2)-index2-1] = y2[index2+1:] - mp_new[index+1:index+1+len(x2)-index2-1] = mp2[index2+1:] - index += len(x2)-index2-1 - # 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] + 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] diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 3dc29ff..802b98c 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -119,7 +119,10 @@ def spike_sync(spike_train1, spike_train2, interval=None, max_tau=None): """ c, mp = _spike_sync_values(spike_train1, spike_train2, interval, max_tau) - return 1.0*c/mp + if mp == 0: + return 1.0 + else: + return 1.0*c/mp ############################################################ @@ -187,7 +190,10 @@ def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): coincidence += c mp += m - return coincidence/mp + if mp == 0.0: + return 1.0 + else: + return coincidence/mp ############################################################ diff --git a/test/numeric/test_regression_random_spikes.py b/test/numeric/test_regression_random_spikes.py index 6156bb4..73d39a6 100644 --- a/test/numeric/test_regression_random_spikes.py +++ b/test/numeric/test_regression_random_spikes.py @@ -35,7 +35,9 @@ def test_regression_random(): spike = spk.spike_distance_multi(spike_trains) spike_prof = spk.spike_profile_multi(spike_trains).avrg() - # spike_sync = spk.spike_sync_multi(spike_trains) + + spike_sync = spk.spike_sync_multi(spike_trains) + spike_sync_prof = spk.spike_sync_profile_multi(spike_trains).avrg() assert_almost_equal(isi, results_cSPIKY[i][0], decimal=14, err_msg="Index: %d, ISI" % i) @@ -47,6 +49,9 @@ def test_regression_random(): assert_almost_equal(spike_prof, results_cSPIKY[i][1], decimal=14, err_msg="Index: %d, SPIKE" % i) + assert_almost_equal(spike_sync, spike_sync_prof, decimal=14, + err_msg="Index: %d, SPIKE-Sync" % i) + def check_regression_dataset(spike_file="benchmark.mat", spikes_name="spikes", @@ -109,19 +114,25 @@ def check_single_spike_train_set(index): spike_train_data = spike_train_sets[index] spike_trains = [] + N = 0 for spikes in spike_train_data[0]: - print("Spikes:", spikes.flatten()) - spike_trains.append(spk.SpikeTrain(spikes.flatten(), 100.0)) + N += len(spikes.flatten()) + print("Spikes:", len(spikes.flatten())) + spikes_array = spikes.flatten() + if len(spikes_array > 0) and (spikes_array[-1] > 100.0): + spikes_array[-1] = 100.0 + spike_trains.append(spk.SpikeTrain(spikes_array, 100.0)) + print(spike_trains[-1].spikes) - print(spk.spike_distance_multi(spike_trains)) + print(N) - print(results_cSPIKY[index][1]) + print(spk.spike_sync_multi(spike_trains)) - print(spike_trains[1].spikes) + print(spk.spike_sync_profile_multi(spike_trains).integral()) if __name__ == "__main__": - test_regression_random() + # test_regression_random() # check_regression_dataset() - # check_single_spike_train_set(7633) + check_single_spike_train_set(4) diff --git a/test/test_distance.py b/test/test_distance.py index 083d8a3..fe09f34 100644 --- a/test/test_distance.py +++ b/test/test_distance.py @@ -217,6 +217,28 @@ def test_spike_sync(): assert_almost_equal(spk.spike_sync(spikes1, spikes2), 0.4, decimal=16) + spikes1 = SpikeTrain([1.0, 2.0, 4.0], 4.0) + spikes2 = SpikeTrain([3.8], 4.0) + spikes3 = SpikeTrain([3.9, ], 4.0) + + expected_x = np.array([0.0, 1.0, 2.0, 3.8, 4.0, 4.0]) + expected_y = np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]) + + f = spk.spike_sync_profile(spikes1, spikes2) + + assert_array_almost_equal(f.x, expected_x, decimal=16) + assert_array_almost_equal(f.y, expected_y, decimal=16) + + f2 = spk.spike_sync_profile(spikes2, spikes3) + + i1 = f.integral() + i2 = f2.integral() + f.add(f2) + i12 = f.integral() + + assert_equal(i1[0]+i2[0], i12[0]) + assert_equal(i1[1]+i2[1], i12[1]) + def check_multi_profile(profile_func, profile_func_multi, dist_func_multi): # generate spike trains: @@ -313,6 +335,17 @@ def test_multi_spike_sync(): assert_equal(np.sum(f.y[1:-1]), 39932) assert_equal(np.sum(f.mp[1:-1]), 85554) + # example with 2 empty spike trains + sts = [] + sts.append(SpikeTrain([1, 9], [0, 10])) + sts.append(SpikeTrain([1, 3], [0, 10])) + sts.append(SpikeTrain([], [0, 10])) + sts.append(SpikeTrain([], [0, 10])) + + assert_almost_equal(spk.spike_sync_multi(sts), 1.0/6.0, decimal=15) + assert_almost_equal(spk.spike_sync_profile_multi(sts).avrg(), 1.0/6.0, + decimal=15) + def check_dist_matrix(dist_func, dist_matrix_func): # generate spike trains: -- cgit v1.2.3 From 9f00431282ef2aae4b98a7a05fe5aa83b0e59673 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 9 Mar 2016 12:19:23 +0100 Subject: deprecated old multivariate functions with the new interface, the previous functions for computing multivariate profiles and distances are obsolete. This is now noted in the docs. --- Readme.rst | 16 ++++++++------ doc/tutorial.rst | 54 ++++++++++++++++++++++++++++++++--------------- pyspike/isi_distance.py | 54 +++++++++++++++++++++++++++-------------------- pyspike/spike_distance.py | 47 +++++++++++++++++++++++------------------ pyspike/spike_sync.py | 36 +++++++++++++++++-------------- 5 files changed, 123 insertions(+), 84 deletions(-) diff --git a/Readme.rst b/Readme.rst index 69a86e8..542f4b3 100644 --- a/Readme.rst +++ b/Readme.rst @@ -7,8 +7,8 @@ PySpike :target: https://travis-ci.org/mariomulansky/PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the bivariate ISI_ and SPIKE_ distance [#]_ [#]_ as well as SPIKE-Synchronization_ [#]_. -Additionally, it provides functions to compute multivariate profiles, distance matrices, as well as averaging and general spike train processing. +Its core functionality is the implementation of the ISI_ and SPIKE_ distance [#]_ [#]_ as well as SPIKE-Synchronization_ [#]_. +It provides functions to compute multivariate profiles, distance matrices, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). PySpike provides the same fundamental functionality as the SPIKY_ framework for Matlab, which additionally contains spike-train generators, more spike train distance measures and many visualization routines. @@ -24,6 +24,8 @@ All source codes are available on `Github `_. diff --git a/doc/tutorial.rst b/doc/tutorial.rst index f7fc20b..aff03a8 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -88,10 +88,9 @@ If you are only interested in the scalar ISI-distance and not the profile, you c .. code:: python - isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1], interval) - -where :code:`interval` is optional, as above, and if omitted the ISI-distance is computed for the complete spike trains. + isi_dist = spk.isi_distance(spike_trains[0], spike_trains[1], interval=(0, 1000)) +where :code:`interval` is optional, as above, and if omitted the ISI-distance is computed for the complete spike train. SPIKE-distance .............. @@ -113,19 +112,20 @@ But the general approach is very similar: plt.show() This short example computes and plots the SPIKE-profile of the first two spike trains in the file :code:`PySpike_testdata.txt`. + In contrast to the ISI-profile, a SPIKE-profile is a piece-wise *linear* function and is therefore represented by a :class:`.PieceWiseLinFunc` object. Just like the :class:`.PieceWiseConstFunc` for the ISI-profile, the :class:`.PieceWiseLinFunc` provides a :meth:`.PieceWiseLinFunc.get_plottable_data` member function that returns arrays that can be used directly to plot the function. Furthermore, the :meth:`.PieceWiseLinFunc.avrg` member function returns the average of the profile defined as the overall SPIKE distance. As above, you can provide an interval as a pair of floats as well as a sequence of such pairs to :code:`avrg` to specify the averaging interval if required. -Again, you can use +Again, you can use: .. code:: python - spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval) + spike_dist = spk.spike_distance(spike_trains[0], spike_trains[1], interval=ival) to compute the SPIKE distance directly, if you are not interested in the profile at all. -The parameter :code:`interval` is optional and if neglected the whole spike train is used. +The parameter :code:`interval` is optional and if neglected the whole time interval is used. SPIKE synchronization @@ -164,26 +164,47 @@ For the direct computation of the overall spike synchronization value within som .. code:: python - spike_sync = spk.spike_sync(spike_trains[0], spike_trains[1], interval) - + spike_sync = spk.spike_sync(spike_trains[0], spike_trains[1], interval=ival) Computing multivariate profiles and distances ---------------------------------------------- -To compute the multivariate ISI-profile, SPIKE-profile or SPIKE-Synchronization profile f a set of spike trains, PySpike provides multi-variate version of the profile function. -The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains using the :func:`.isi_profile_multi`, :func:`.spike_profile_multi`, :func:`.spike_sync_profile_multi` functions: +To compute the multivariate ISI-profile, SPIKE-profile or SPIKE-Synchronization profile for a set of spike trains, simply provide a list of spike trains to the profile or distance functions. +The following example computes the multivariate ISI-, SPIKE- and SPIKE-Sync-profile for a list of spike trains: .. code:: python spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", edges=(0, 4000)) - avrg_isi_profile = spk.isi_profile_multi(spike_trains) - avrg_spike_profile = spk.spike_profile_multi(spike_trains) - avrg_spike_sync_profile = spk.spike_sync_profile_multi(spike_trains) + avrg_isi_profile = spk.isi_profile(spike_trains) + avrg_spike_profile = spk.spike_profile(spike_trains) + avrg_spike_sync_profile = spk.spike_sync_profile(spike_trains) + +All functions also take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. +As before, if you are only interested in the distance values, and not in the profile, you can call the functions: :func:`.isi_distance`, :func:`.spike_distance` and :func:`.spike_sync` with a list of spike trains. +They return the scalar overall multivariate ISI-, SPIKE-distance or the SPIKE-Synchronization value. + +The following code is equivalent to the bivariate example above, computing the ISI-Distance between the first two spike trains in the given interval using the :code:`indices` parameter: + +.. code:: python + + isi_dist = spk.isi_distance(spike_trains, indices=[0, 1], interval=(0, 1000)) + +As you can see, the distance functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. + +**Note:** + +------------------------------ + + Instead of providing lists of spike trains to the profile or distance functions, you can also call those functions with many spike trains as (unnamed) parameters, e.g.: + + .. code:: python + + # st1, st2, st3, st4 are spike trains + spike_prof = spk.spike_profile(st1, st2, st3, st4) + +------------------------------ -All functions take an optional parameter :code:`indices`, a list of indices that allows to define the spike trains that should be used for the multivariate profile. -As before, if you are only interested in the distance values, and not in the profile, PySpike offers the functions: :func:`.isi_distance_multi`, :func:`.spike_distance_multi` and :func:`.spike_sync_multi`, that return the scalar overall multivariate ISI- and SPIKE-distance as well as the SPIKE-Synchronization value. -Those functions also accept an :code:`interval` parameter that can be used to specify the begin and end of the averaging interval as a pair of floats, if neglected the complete interval is used. Another option to characterize large sets of spike trains are distance matrices. Each entry in the distance matrix represents a bivariate distance (similarity for SPIKE-Synchronization) of two spike trains. @@ -210,4 +231,3 @@ The following example computes and plots the ISI- and SPIKE-distance matrix as w plt.title("SPIKE-Sync") plt.show() - diff --git a/pyspike/isi_distance.py b/pyspike/isi_distance.py index 122e11d..e91dce2 100644 --- a/pyspike/isi_distance.py +++ b/pyspike/isi_distance.py @@ -20,14 +20,22 @@ def isi_profile(*args, **kwargs): Valid call structures:: - isi_profile(st1, st2) # returns the bi-variate profile + isi_profile(st1, st2) # returns the bi-variate profile isi_profile(st1, st2, st3) # multi-variate profile of 3 spike trains spike_trains = [st1, st2, st3, st4] # list of spike trains - isi_profile(spike_trains) # profile of the list of spike trains + isi_profile(spike_trains) # profile of the list of spike trains isi_profile(spike_trains, indices=[0, 1]) # use only the spike trains # given by the indices + The multivariate ISI distance profile for a set of spike trains is defined + as the average ISI-profile of all pairs of spike-trains: + + .. math:: = \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, + + where the sum goes over all pairs + + :returns: The isi-distance profile :math:`I(t)` :rtype: :class:`.PieceWiseConstFunc` """ @@ -43,10 +51,9 @@ def isi_profile(*args, **kwargs): # isi_profile_bi ############################################################ def isi_profile_bi(spike_train1, spike_train2): - """ Bi-variate ISI-profile. - Computes the isi-distance profile :math:`I(t)` of the two given - spike trains. Returns the profile as a PieceWiseConstFunc object. - See :func:`.isi_profile`. + """ Specific function to compute a bivariate ISI-profile. This is a + deprecated function and should not be called directly. Use + :func:`.isi_profile` to compute ISI-profiles. :param spike_train1: First spike train. :type spike_train1: :class:`.SpikeTrain` @@ -85,12 +92,10 @@ Falling back to slow python backend.") # isi_profile_multi ############################################################ def isi_profile_multi(spike_trains, indices=None): - """ computes the multi-variate isi distance profile for a set of spike - trains. That is the average isi-distance of all pairs of spike-trains: + """ Specific function to compute the multivariate ISI-profile for a set of + spike trains. This is a deprecated function and should not be called + directly. Use :func:`.isi_profile` to compute ISI-profiles. - .. math:: = \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, - - where the sum goes over all pairs :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, @@ -115,6 +120,14 @@ def isi_distance(*args, **kwargs): .. math:: D_I = \\int_{T_0}^{T_1} I(t) dt. + In the multivariate case it is the integral over the multivariate + ISI-profile, i.e. the average profile over all spike train pairs: + + .. math:: D_I = \\int_0^T \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, + + where the sum goes over all pairs + + Valid call structures:: @@ -139,14 +152,12 @@ def isi_distance(*args, **kwargs): ############################################################ -# isi_distance_bi +# _isi_distance_bi ############################################################ def isi_distance_bi(spike_train1, spike_train2, interval=None): - """ Computes the ISI-distance :math:`D_I` of the given spike trains. The - isi-distance is the integral over the isi distance profile - :math:`I(t)`: - - .. math:: D_I = \\int_{T_0}^{T_1} I(t) dt. + """ Specific function to compute the bivariate ISI-distance. + This is a deprecated function and should not be called directly. Use + :func:`.isi_distance` to compute ISI-distances. :param spike_train1: First spike train. :type spike_train1: :class:`.SpikeTrain` @@ -181,12 +192,9 @@ def isi_distance_bi(spike_train1, spike_train2, interval=None): # isi_distance_multi ############################################################ def isi_distance_multi(spike_trains, indices=None, interval=None): - """ computes the multi-variate isi-distance for a set of spike-trains. - That is the time average of the multi-variate spike profile: - - .. math:: D_I = \\int_0^T \\frac{2}{N(N-1)} \\sum_{} I^{i,j}, - - where the sum goes over all pairs + """ Specific function to compute the multivariate ISI-distance. + This is a deprecfated function and should not be called directly. Use + :func:`.isi_distance` to compute ISI-distances. :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, diff --git a/pyspike/spike_distance.py b/pyspike/spike_distance.py index 7acb959..0fd86c1 100644 --- a/pyspike/spike_distance.py +++ b/pyspike/spike_distance.py @@ -28,6 +28,13 @@ def spike_profile(*args, **kwargs): spike_profile(spike_trains, indices=[0, 1]) # use only the spike trains # given by the indices + The multivariate spike-distance profile is defined as the average of all + pairs of spike-trains: + + .. math:: = \\frac{2}{N(N-1)} \\sum_{} S^{i, j}`, + + where the sum goes over all pairs + :returns: The spike-distance profile :math:`S(t)` :rtype: :class:`.PieceWiseConstLin` """ @@ -43,9 +50,9 @@ def spike_profile(*args, **kwargs): # spike_profile_bi ############################################################ def spike_profile_bi(spike_train1, spike_train2): - """ Computes the spike-distance profile :math:`S(t)` of the two given spike - trains. Returns the profile as a PieceWiseLinFunc object. The SPIKE-values - are defined positive :math:`S(t)>=0`. + """ Specific function to compute a bivariate SPIKE-profile. This is a + deprecated function and should not be called directly. Use + :func:`.spike_profile` to compute SPIKE-profiles. :param spike_train1: First spike train. :type spike_train1: :class:`.SpikeTrain` @@ -86,12 +93,9 @@ Falling back to slow python backend.") # spike_profile_multi ############################################################ def spike_profile_multi(spike_trains, indices=None): - """ Computes the multi-variate spike distance profile for a set of spike - trains. That is the average spike-distance of all pairs of spike-trains: - - .. math:: = \\frac{2}{N(N-1)} \\sum_{} S^{i, j}`, - - where the sum goes over all pairs + """ Specific function to compute a multivariate SPIKE-profile. This is a + deprecated function and should not be called directly. Use + :func:`.spike_profile` to compute SPIKE-profiles. :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, @@ -128,6 +132,13 @@ def spike_distance(*args, **kwargs): spike_distance(spike_trains, indices=[0, 1]) # use only the spike trains # given by the indices + In the multivariate case, the spike distance is given as the integral over + the multivariate profile, that is the average profile of all spike train + pairs: + + .. math:: D_S = \\int_0^T \\frac{2}{N(N-1)} \\sum_{} + S^{i, j} dt + :returns: The spike-distance :math:`D_S`. :rtype: double """ @@ -144,11 +155,9 @@ def spike_distance(*args, **kwargs): # spike_distance_bi ############################################################ def spike_distance_bi(spike_train1, spike_train2, interval=None): - """ Computes the spike-distance :math:`D_S` of the given spike trains. The - spike-distance is the integral over the spike distance profile - :math:`S(t)`: - - .. math:: D_S = \int_{T_0}^{T_1} S(t) dt. + """ Specific function to compute a bivariate SPIKE-distance. This is a + deprecated function and should not be called directly. Use + :func:`.spike_distance` to compute SPIKE-distances. :param spike_train1: First spike train. :type spike_train1: :class:`.SpikeTrain` @@ -183,13 +192,9 @@ def spike_distance_bi(spike_train1, spike_train2, interval=None): # spike_distance_multi ############################################################ def spike_distance_multi(spike_trains, indices=None, interval=None): - """ Computes the multi-variate spike distance for a set of spike trains. - That is the time average of the multi-variate spike profile: - - .. math:: D_S = \\int_0^T \\frac{2}{N(N-1)} \\sum_{} - S^{i, j} dt - - where the sum goes over all pairs + """ Specific function to compute a multivariate SPIKE-distance. This is a + deprecated function and should not be called directly. Use + :func:`.spike_distance` to compute SPIKE-distances. :param spike_trains: list of :class:`.SpikeTrain` :param indices: list of indices defining which spike trains to use, diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index ccb09d9..617dd86 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -34,6 +34,11 @@ def spike_sync_profile(*args, **kwargs): spike_sync_profile(sts, indices=[0, 1]) # use only the spike trains # given by the indices + In the multivariate case, the profile is defined as the number of + coincidences for each spike in the set of spike trains divided by the + number of spike trains pairs involving the spike train of containing this + spike, which is the number of spike trains minus one (N-1). + :returns: The spike-sync profile :math:`S_{sync}(t)`. :rtype: :class:`pyspike.function.DiscreteFunction` """ @@ -49,10 +54,9 @@ def spike_sync_profile(*args, **kwargs): # spike_sync_profile_bi ############################################################ def spike_sync_profile_bi(spike_train1, spike_train2, max_tau=None): - """ Computes the spike-synchronization profile S_sync(t) of the two given - spike trains. Returns the profile as a DiscreteFunction object. The S_sync - values are either 1 or 0, indicating the presence or absence of a - coincidence. + """ Specific function to compute a bivariate SPIKE-Sync-profile. This is a + deprecated function and should not be called directly. Use + :func:`.spike_sync_profile` to compute SPIKE-Sync-profiles. :param spike_train1: First spike train. :type spike_train1: :class:`pyspike.SpikeTrain` @@ -98,11 +102,9 @@ Falling back to slow python backend.") # spike_sync_profile_multi ############################################################ def spike_sync_profile_multi(spike_trains, indices=None, max_tau=None): - """ Computes the multi-variate spike synchronization profile for a set of - spike trains. For each spike in the set of spike trains, the multi-variate - profile is defined as the number of coincidences divided by the number of - spike trains pairs involving the spike train of containing this spike, - which is the number of spike trains minus one (N-1). + """ Specific function to compute a multivariate SPIKE-Sync-profile. + This is a deprecated function and should not be called directly. Use + :func:`.spike_sync_profile` to compute SPIKE-Sync-profiles. :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, @@ -176,6 +178,9 @@ def spike_sync(*args, **kwargs): spike_sync(spike_trains, indices=[0, 1]) # use only the spike trains # given by the indices + The multivariate SPIKE-Sync is again defined as the overall ratio of all + coincidence values divided by the total number of spikes. + :returns: The spike synchronization value. :rtype: `double` """ @@ -192,11 +197,9 @@ def spike_sync(*args, **kwargs): # spike_sync_bi ############################################################ def spike_sync_bi(spike_train1, spike_train2, interval=None, max_tau=None): - """ Computes the spike synchronization value SYNC of the given spike - trains. The spike synchronization value is the computed as the total number - of coincidences divided by the total number of spikes: - - .. math:: SYNC = \sum_n C_n / N. + """ Specific function to compute a bivariate SPIKE-Sync value. + This is a deprecated function and should not be called directly. Use + :func:`.spike_sync` to compute SPIKE-Sync values. :param spike_train1: First spike train. :type spike_train1: :class:`pyspike.SpikeTrain` @@ -219,8 +222,9 @@ def spike_sync_bi(spike_train1, spike_train2, interval=None, max_tau=None): # spike_sync_multi ############################################################ def spike_sync_multi(spike_trains, indices=None, interval=None, max_tau=None): - """ Computes the multi-variate spike synchronization value for a set of - spike trains. + """ Specific function to compute a multivariate SPIKE-Sync value. + This is a deprecated function and should not be called directly. Use + :func:`.spike_sync` to compute SPIKE-Sync values. :param spike_trains: list of :class:`pyspike.SpikeTrain` :param indices: list of indices defining which spike trains to use, -- cgit v1.2.3 From ee0e980b72c299eed12b7a3afc542fc470dd6d98 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 9 Mar 2016 12:30:35 +0100 Subject: updated examples to use new unified interface removed all occasions of *multi functions from examples as they are considered deprecated now. Uses unified interface everywhere. --- examples/averages.py | 2 +- examples/merge.py | 6 +++--- examples/multivariate.py | 4 ++-- examples/performance.py | 27 +++++++++++++++------------ examples/plot.py | 5 +++-- examples/profiles.py | 4 ++-- examples/spike_sync.py | 2 +- 7 files changed, 27 insertions(+), 23 deletions(-) diff --git a/examples/averages.py b/examples/averages.py index c3e81e2..8b405d0 100644 --- a/examples/averages.py +++ b/examples/averages.py @@ -12,7 +12,7 @@ from __future__ import print_function import pyspike as spk spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", - time_interval=(0, 4000)) + edges=(0, 4000)) f = spk.isi_profile(spike_trains[0], spike_trains[1]) diff --git a/examples/merge.py b/examples/merge.py index 2ea96ea..b4437a3 100644 --- a/examples/merge.py +++ b/examples/merge.py @@ -21,9 +21,9 @@ merged_spike_train = spk.merge_spike_trains([spike_trains[0], spike_trains[1]]) print(merged_spike_train.spikes) -plt.plot(spike_trains[0].spikes, np.ones_like(spike_trains[0].spikes), 'o') -plt.plot(spike_trains[1].spikes, np.ones_like(spike_trains[1].spikes), 'x') +plt.plot(spike_trains[0], np.ones_like(spike_trains[0]), 'o') +plt.plot(spike_trains[1], np.ones_like(spike_trains[1]), 'x') plt.plot(merged_spike_train.spikes, - 2*np.ones_like(merged_spike_train.spikes), 'o') + 2*np.ones_like(merged_spike_train), 'o') plt.show() diff --git a/examples/multivariate.py b/examples/multivariate.py index 93f8516..e9579a5 100644 --- a/examples/multivariate.py +++ b/examples/multivariate.py @@ -28,7 +28,7 @@ num_of_spikes = sum([len(spike_trains[i]) print("Number of spikes: %d" % num_of_spikes) # calculate the multivariate spike distance -f = spk.spike_profile_multi(spike_trains) +f = spk.spike_profile(spike_trains) t_spike = time.clock() @@ -39,7 +39,7 @@ print("Spike distance from average: %.8f" % avrg) t_avrg = time.clock() # compute average distance directly, should give the same result as above -spike_dist = spk.spike_distance_multi(spike_trains) +spike_dist = spk.spike_distance(spike_trains) print("Spike distance directly: %.8f" % spike_dist) t_dist = time.clock() diff --git a/examples/performance.py b/examples/performance.py index ec6c830..30691f8 100644 --- a/examples/performance.py +++ b/examples/performance.py @@ -31,38 +31,41 @@ for i in range(M): t_end = datetime.now() runtime = (t_end-t_start).total_seconds() +sort_by = 'tottime' +# sort_by = 'cumtime' + print("Spike generation runtime: %.3fs" % runtime) print() print("================ ISI COMPUTATIONS ================") print(" MULTIVARIATE DISTANCE") -cProfile.run('spk.isi_distance_multi(spike_trains)', 'performance.stat') +cProfile.run('spk.isi_distance(spike_trains)', 'performance.stat') p = pstats.Stats('performance.stat') -p.strip_dirs().sort_stats('tottime').print_stats(5) +p.strip_dirs().sort_stats(sort_by).print_stats(5) print(" MULTIVARIATE PROFILE") -cProfile.run('spk.isi_profile_multi(spike_trains)', 'performance.stat') +cProfile.run('spk.isi_profile(spike_trains)', 'performance.stat') p = pstats.Stats('performance.stat') -p.strip_dirs().sort_stats('tottime').print_stats(5) +p.strip_dirs().sort_stats(sort_by).print_stats(5) print("================ SPIKE COMPUTATIONS ================") print(" MULTIVARIATE DISTANCE") -cProfile.run('spk.spike_distance_multi(spike_trains)', 'performance.stat') +cProfile.run('spk.spike_distance(spike_trains)', 'performance.stat') p = pstats.Stats('performance.stat') -p.strip_dirs().sort_stats('tottime').print_stats(5) +p.strip_dirs().sort_stats(sort_by).print_stats(5) print(" MULTIVARIATE PROFILE") -cProfile.run('spk.spike_profile_multi(spike_trains)', 'performance.stat') +cProfile.run('spk.spike_profile(spike_trains)', 'performance.stat') p = pstats.Stats('performance.stat') -p.strip_dirs().sort_stats('tottime').print_stats(5) +p.strip_dirs().sort_stats(sort_by).print_stats(5) print("================ SPIKE-SYNC COMPUTATIONS ================") print(" MULTIVARIATE DISTANCE") -cProfile.run('spk.spike_sync_multi(spike_trains)', 'performance.stat') +cProfile.run('spk.spike_sync(spike_trains)', 'performance.stat') p = pstats.Stats('performance.stat') -p.strip_dirs().sort_stats('tottime').print_stats(5) +p.strip_dirs().sort_stats(sort_by).print_stats(5) print(" MULTIVARIATE PROFILE") -cProfile.run('spk.spike_sync_profile_multi(spike_trains)', 'performance.stat') +cProfile.run('spk.spike_sync_profile(spike_trains)', 'performance.stat') p = pstats.Stats('performance.stat') -p.strip_dirs().sort_stats('tottime').print_stats(5) +p.strip_dirs().sort_stats(sort_by).print_stats(5) diff --git a/examples/plot.py b/examples/plot.py index 1922939..a0e04da 100644 --- a/examples/plot.py +++ b/examples/plot.py @@ -24,7 +24,8 @@ spike_trains = spk.load_spike_trains_from_txt("PySpike_testdata.txt", for (i, spike_train) in enumerate(spike_trains): plt.scatter(spike_train, i*np.ones_like(spike_train), marker='|') -f = spk.isi_profile(spike_trains[0], spike_trains[1]) +# profile of the first two spike trains +f = spk.isi_profile(spike_trains, indices=[0, 1]) x, y = f.get_plottable_data() plt.figure() @@ -32,7 +33,7 @@ plt.plot(x, np.abs(y), '--k', label="ISI-profile") print("ISI-distance: %.8f" % f.avrg()) -f = spk.spike_profile(spike_trains[0], spike_trains[1]) +f = spk.spike_profile(spike_trains, indices=[0, 1]) x, y = f.get_plottable_data() plt.plot(x, y, '-b', label="SPIKE-profile") diff --git a/examples/profiles.py b/examples/profiles.py index 05494bd..8412ffb 100644 --- a/examples/profiles.py +++ b/examples/profiles.py @@ -29,7 +29,7 @@ print("Average ISI distance:", f.avrg()) print() # compute the multivariate ISI profile -f = spk.isi_profile_multi(spike_trains) +f = spk.isi_profile(spike_trains) t = 1200 print("Multivariate ISI value at t =", t, ":", f(t)) @@ -56,7 +56,7 @@ print("Average SPIKE distance:", f.avrg()) print() # compute the multivariate SPIKE profile -f = spk.spike_profile_multi(spike_trains) +f = spk.spike_profile(spike_trains) # SPIKE values at certain points t = 1200 diff --git a/examples/spike_sync.py b/examples/spike_sync.py index 37dbff4..13ca0ce 100644 --- a/examples/spike_sync.py +++ b/examples/spike_sync.py @@ -31,7 +31,7 @@ plt.figure() plt.subplot(211) -f = spk.spike_sync_profile_multi(spike_trains) +f = spk.spike_sync_profile(spike_trains) x, y = f.get_plottable_data() plt.plot(x, y, '-b', alpha=0.7, label="SPIKE-Sync profile") -- cgit v1.2.3 From c17cc8602414cec883c412008a4300b2c7ac7f80 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 9 Mar 2016 14:13:11 +0100 Subject: new version: 0.5.1 --- doc/conf.py | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 7e13eae..807dec6 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,9 +64,9 @@ copyright = u'2014-2015, Mario Mulansky' # built documents. # # The short X.Y version. -version = '0.4' +version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.4.0' +release = '0.5.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index 8ef431a..b53304b 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ to compile cython files: python setup.py build_ext --inplace -Copyright 2014-2015, Mario Mulansky +Copyright 2014-2016, Mario Mulansky Distributed under the BSD License @@ -55,7 +55,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.4.0', + version='0.5.1', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy.get_include()], -- cgit v1.2.3 From 0d8af2c97d766a4fa514f0232189bc17c31c67a0 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 24 Mar 2016 15:54:48 +0100 Subject: +function for saving spike trains to txt files save_spike_trains_to_txt allows to save spike train data into txt files which can then be loaded via load_spike_trains_from_txt again. --- pyspike/__init__.py | 4 ++-- pyspike/spikes.py | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 069090b..4d75786 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -23,8 +23,8 @@ from .spike_sync import spike_sync_profile, spike_sync,\ spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix from .psth import psth -from .spikes import load_spike_trains_from_txt, spike_train_from_string, \ - merge_spike_trains, generate_poisson_spikes +from .spikes import load_spike_trains_from_txt, save_spike_trains_to_txt, \ + spike_train_from_string, merge_spike_trains, generate_poisson_spikes # define the __version__ following # http://stackoverflow.com/questions/17583443 diff --git a/pyspike/spikes.py b/pyspike/spikes.py index b18d7eb..966ad69 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -2,6 +2,7 @@ # Copyright 2014, Mario Mulansky # Distributed under the BSD License + import numpy as np from pyspike import SpikeTrain @@ -25,7 +26,7 @@ def spike_train_from_string(s, edges, sep=' ', is_sorted=False): ############################################################ -# load_spike_trains_txt +# load_spike_trains_from_txt ############################################################ def load_spike_trains_from_txt(file_name, edges, separator=' ', comment='#', is_sorted=False, @@ -47,16 +48,36 @@ def load_spike_trains_from_txt(file_name, edges, :returns: list of :class:`.SpikeTrain` """ spike_trains = [] - spike_file = open(file_name, 'r') - for line in spike_file: - if len(line) > 1 and not line.startswith(comment): - # use only the lines with actual data and not commented - spike_train = spike_train_from_string(line, edges, - separator, is_sorted) - spike_trains.append(spike_train) + with open(file_name, 'r') as spike_file: + for line in spike_file: + if len(line) > 1 and not line.startswith(comment): + # use only the lines with actual data and not commented + spike_train = spike_train_from_string(line, edges, + separator, is_sorted) + spike_trains.append(spike_train) return spike_trains +############################################################ +# save_spike_trains_to_txt +############################################################ +def save_spike_trains_to_txt(spike_trains, file_name, + separator=' ', precision=8): + """ Saves the given spike trains into a file with the given file name. + Each spike train will be stored in one line in the text file with the times + separated by `separator`. + + :param spike_trains: List of :class:`.SpikeTrain` objects + :param file_name: The name of the text file. + """ + # format string to print the spike times with given precision + format_str = "{:0.%de}" % precision + with open(file_name, 'w') as spike_file: + for st in spike_trains: + s = separator.join(map(format_str.format, st.spikes)) + spike_file.write(s+'\n') + + ############################################################ # merge_spike_trains ############################################################ -- cgit v1.2.3 From f057e34ca084d78a655926f30d5df43e673714fb Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 24 Mar 2016 15:56:41 +0100 Subject: added test for save spike train functionality --- test/test_save_load.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/test_save_load.py diff --git a/test/test_save_load.py b/test/test_save_load.py new file mode 100644 index 0000000..ff222d3 --- /dev/null +++ b/test/test_save_load.py @@ -0,0 +1,42 @@ +""" test_save_load.py + +Tests saving and loading of spike trains + +Copyright 2016, Mario Mulansky + +Distributed under the BSD License + +""" + +from __future__ import print_function +from numpy.testing import assert_array_equal + +import tempfile +import os.path + +import pyspike as spk + + +def test_save_load(): + file_name = os.path.join(tempfile.mkdtemp(prefix='pyspike_'), + "save_load.txt") + + N = 10 + # generate some spike trains + spike_trains = [] + for n in xrange(N): + spike_trains.append(spk.generate_poisson_spikes(1.0, [0, 100])) + + # save them into txt file + spk.save_spike_trains_to_txt(spike_trains, file_name, precision=17) + + # load again + spike_trains_loaded = spk.load_spike_trains_from_txt(file_name, [0, 100]) + + for n in xrange(N): + assert_array_equal(spike_trains[n].spikes, + spike_trains_loaded[n].spikes) + + +if __name__ == "__main__": + test_save_load() -- cgit v1.2.3 From adab2aa6d573702ca685e8242fd7edccb841ff8c Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 24 Mar 2016 16:27:51 +0100 Subject: add empty spike trains when loading from txt treatment of empty lines was incorrect. now empty spike trains are created from empty lines in the txt file if parameter ignore_empty_lines=False is given. --- pyspike/spikes.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 966ad69..271adcb 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -50,11 +50,15 @@ def load_spike_trains_from_txt(file_name, edges, spike_trains = [] with open(file_name, 'r') as spike_file: for line in spike_file: - if len(line) > 1 and not line.startswith(comment): - # use only the lines with actual data and not commented - spike_train = spike_train_from_string(line, edges, - separator, is_sorted) - spike_trains.append(spike_train) + if not line.startswith(comment): # ignore comments + if len(line) > 1: + # ignore empty lines + spike_train = spike_train_from_string(line, edges, + separator, is_sorted) + spike_trains.append(spike_train) + elif not(ignore_empty_lines): + # add empty spike train + spike_trains.append(SpikeTrain([], edges)) return spike_trains -- cgit v1.2.3 From 04d25294ca65246d9397e981767c3d7c737626c3 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 24 Mar 2016 16:37:02 +0100 Subject: quick fix in format string --- pyspike/spikes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 271adcb..ab6d4c4 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -75,7 +75,7 @@ def save_spike_trains_to_txt(spike_trains, file_name, :param file_name: The name of the text file. """ # format string to print the spike times with given precision - format_str = "{:0.%de}" % precision + format_str = "{0:.%de}" % precision with open(file_name, 'w') as spike_file: for st in spike_trains: s = separator.join(map(format_str.format, st.spikes)) -- cgit v1.2.3 From d9e2125dcc76693056ab04264add29227f398f4f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 24 Mar 2016 16:53:59 +0100 Subject: bugfix for Python 3 (xrange->range) --- test/test_save_load.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_save_load.py b/test/test_save_load.py index ff222d3..33249b3 100644 --- a/test/test_save_load.py +++ b/test/test_save_load.py @@ -24,7 +24,7 @@ def test_save_load(): N = 10 # generate some spike trains spike_trains = [] - for n in xrange(N): + for n in range(N): spike_trains.append(spk.generate_poisson_spikes(1.0, [0, 100])) # save them into txt file @@ -33,7 +33,7 @@ def test_save_load(): # load again spike_trains_loaded = spk.load_spike_trains_from_txt(file_name, [0, 100]) - for n in xrange(N): + for n in range(N): assert_array_equal(spike_trains[n].spikes, spike_trains_loaded[n].spikes) -- cgit v1.2.3 From 4691d0e77a024fbc73d1098ee557d65f8f2ddc89 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sat, 18 Jun 2016 16:27:51 -0700 Subject: added function to import time series new function import_spike_trains_from_time_series that loads spike trains from time series. --- pyspike/__init__.py | 1 + pyspike/spikes.py | 25 +++++++++++++++++++++++++ test/test_spikes.py | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 069090b..1e879c4 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -24,6 +24,7 @@ from .spike_sync import spike_sync_profile, spike_sync,\ from .psth import psth from .spikes import load_spike_trains_from_txt, spike_train_from_string, \ + import_spike_trains_from_time_series, \ merge_spike_trains, generate_poisson_spikes # define the __version__ following diff --git a/pyspike/spikes.py b/pyspike/spikes.py index b18d7eb..1bf474c 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -57,6 +57,31 @@ def load_spike_trains_from_txt(file_name, edges, return spike_trains +def import_spike_trains_from_time_series(file_name, start_time, time_bin, + separator=None, comment='#'): + """ Imports spike trains from time series consisting of 0 and 1 denoting + the absence or presence of a spike. Each line in the data file represents + one spike train. + + :param file_name: The name of the data file containing the time series. + :param edges: A pair (T_start, T_end) of values representing the + start and end time of the spike train measurement + or a single value representing the end time, the + T_start is then assuemd as 0. + :param separator: The character used to seprate the values in the text file + :param comment: Lines starting with this character are ignored. + + """ + data = np.loadtxt(file_name, comments=comment, delimiter=separator) + time_points = start_time + time_bin + np.arange(len(data[0, :]))*time_bin + spike_trains = [] + for time_series in data: + spike_trains.append(SpikeTrain(time_points[time_series > 0], + edges=[start_time, + time_points[-1]])) + return spike_trains + + ############################################################ # merge_spike_trains ############################################################ diff --git a/test/test_spikes.py b/test/test_spikes.py index 609a819..bcface2 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -17,6 +17,10 @@ import os TEST_PATH = os.path.dirname(os.path.realpath(__file__)) TEST_DATA = os.path.join(TEST_PATH, "PySpike_testdata.txt") +TIME_SERIES_DATA = os.path.join(TEST_PATH, "time_series.txt") +TIME_SERIES_SPIKES = os.path.join(TEST_PATH, "time_series_spike_trains.txt") + + def test_load_from_txt(): spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=(0, 4000)) assert len(spike_trains) == 40 @@ -33,6 +37,21 @@ def test_load_from_txt(): assert spike_train.t_end == 4000 +def test_load_time_series(): + spike_trains = spk.import_spike_trains_from_time_series(TIME_SERIES_DATA, + start_time=0, + time_bin=1) + assert len(spike_trains) == 40 + spike_trains_check = spk.load_spike_trains_from_txt(TIME_SERIES_SPIKES, + edges=(0, 4000)) + + # check spike trains + for n in range(len(spike_trains)): + assert_equal(spike_trains[n].spikes, spike_trains_check[n].spikes) + assert_equal(spike_trains[n].t_start, 0) + assert_equal(spike_trains[n].t_end, 4000) + + def check_merged_spikes(merged_spikes, spike_trains): # create a flat array with all spike events all_spikes = np.array([]) -- cgit v1.2.3 From ce05e2b2f393bf68b8ad85806c7af934ee68a7f5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sat, 18 Jun 2016 16:35:14 -0700 Subject: added missing data files for tests --- test/time_series.txt | 80 +++++++++++++++++++++++++++++++++++++++ test/time_series_spike_trains.txt | 80 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 test/time_series.txt create mode 100644 test/time_series_spike_trains.txt diff --git a/test/time_series.txt b/test/time_series.txt new file mode 100644 index 0000000..0116c97 --- /dev/null +++ b/test/time_series.txt @@ -0,0 +1,80 @@ +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + +0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + diff --git a/test/time_series_spike_trains.txt b/test/time_series_spike_trains.txt new file mode 100644 index 0000000..43e2c76 --- /dev/null +++ b/test/time_series_spike_trains.txt @@ -0,0 +1,80 @@ +65 306 696 938 1060 1322 1576 1808 2121 2381 2729 2967 3224 3474 3644 3936 + +66 307 697 949 1070 1312 1713 1934 2118 2357 2727 2981 3227 3476 3726 3944 + +69 319 688 948 1072 1301 1697 1931 2139 2354 2724 2964 3221 3470 + +60 314 692 956 1070 1320 1682 1964 2151 2374 2729 2971 3220 3475 3632 3789 + +60 307 686 935 1060 1326 1543 1822 2150 2390 2724 2970 3223 3471 3576 3914 + +66 313 689 931 1052 1305 1556 1820 2151 2383 2723 2948 3197 3443 3575 3805 + +66 311 689 947 1059 1287 1708 1957 2125 2376 2709 2978 3191 3450 3590 3831 + +64 318 697 937 1059 1325 1688 1945 2133 2377 2713 2977 3197 3443 3742 3998 + +64 315 693 937 1066 1316 1584 1822 2126 2397 2709 2967 3197 3444 3733 3849 + +69 317 690 944 1072 1296 1655 1932 2127 2391 2709 2950 3195 3445 3670 3903 + +62 318 556 813 1199 1449 1687 1944 2061 2311 2658 2900 3167 3418 3617 3771 + +64 310 567 814 1182 1464 1577 1822 2063 2312 2656 2912 3168 3418 3586 4000 + +69 315 560 806 1182 1441 1567 1805 2075 2316 2655 2913 3166 3419 3648 3884 + +67 314 556 815 1183 1440 1701 1911 2070 2319 2662 2903 3167 3419 3545 3894 + +73 318 554 820 1187 1450 1676 1957 2051 2303 2658 2916 3169 3417 3570 3885 + +65 324 561 829 1175 1440 1564 1791 2068 2288 2657 2905 3139 3389 3508 3807 + +65 317 569 816 1198 1454 1711 1934 2091 2310 2661 2908 3137 3389 3617 + +63 315 554 827 1184 1458 1559 1808 2064 2337 2654 2897 3144 3386 3669 3804 + +62 315 564 812 1200 1449 1563 1839 2070 2309 2650 2920 3141 3390 3724 3882 + +70 312 565 805 1210 1451 1692 1932 2044 2329 2657 2909 3143 3391 3597 3991 + +183 431 562 810 1086 1309 1556 1831 2057 2327 2591 2831 3114 3368 3555 3956 + +188 442 572 811 1065 1327 1564 1803 2060 2322 2607 2824 3110 3364 3644 3820 + +177 438 570 820 1064 1309 1686 1957 2067 2314 2593 2847 3117 3365 3727 3882 + +194 442 587 805 1062 1312 1542 1793 2074 2315 2588 2846 3112 3360 + +193 440 556 814 1056 1315 1690 1961 2049 2305 2594 2847 3111 3361 3712 3915 + +195 438 566 807 1069 1315 1683 1942 2062 2305 2608 2842 3083 3330 3680 3848 + +185 441 571 794 1064 1310 1679 1930 2058 2321 2607 2845 3085 3337 3640 3952 + +190 444 561 817 1070 1303 1550 1816 2058 2324 2587 2843 3087 3334 3618 3815 + +190 441 569 809 1074 1322 1686 1953 2069 2336 2596 2845 3086 3333 3636 3939 + +181 441 578 824 1052 1322 1578 1822 2079 2309 2597 2852 3083 3335 3531 3771 + +181 435 687 943 1193 1444 1699 1942 2195 2446 2549 2785 3056 3308 3620 3933 + +187 447 688 943 1186 1442 1688 1922 2197 2455 2535 2777 3060 3309 3514 3809 + +197 446 681 948 1196 1433 1699 1933 2201 2461 2547 2778 3056 3307 3591 3953 + +201 427 696 946 1179 1440 1538 1809 2200 2433 2532 2793 3057 3309 3511 3928 + +191 430 699 931 1191 1429 1698 1935 2177 2425 2530 2767 3062 3310 3690 + +181 442 682 943 1190 1459 1571 1820 2190 2438 2543 2783 3026 3280 3581 3856 + +191 436 703 936 1188 1438 1564 1824 2191 2445 2532 2782 3031 3276 3678 3829 + +192 434 686 933 1183 1433 1564 1827 2214 2437 2530 2778 3028 3282 3582 3863 + +190 453 691 941 1180 1430 1567 1835 2199 2448 2527 2774 3031 3280 3576 3894 + +191 435 693 941 1190 1449 1575 1824 2191 2426 2531 2783 3033 3280 3733 3839 + -- cgit v1.2.3 From bb9345d3edc7f178f01057c521fa721a4e4034c2 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 19 Jun 2016 09:35:02 -0700 Subject: increased travis wait time for numeric tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d23d865..476813e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,4 @@ install: script: - python setup.py build_ext --inplace - nosetests - - nosetests test/numeric + - travis_wait 30 nosetests test/numeric -- cgit v1.2.3 From 7a2a38c7538434fadff26402daea46294a92bc9f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 27 Oct 2016 20:27:00 -0700 Subject: Added SoftwareX reference and Spike-Order note --- Readme.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Readme.rst b/Readme.rst index 542f4b3..d60d154 100644 --- a/Readme.rst +++ b/Readme.rst @@ -15,6 +15,13 @@ PySpike provides the same fundamental functionality as the SPIKY_ framework for All source codes are available on `Github `_ and are published under the BSD_License_. +Citing PySpike +---------------------------- +If you use PySpike in your research, please cite our SoftwareX publication on PySpike: + Mario Mulansky, Thomas Kreuz, *PySpike - A Python library for analyzing spike train synchrony*, SoftwareX, (2016), ISSN 2352-7110, http://dx.doi.org/10.1016/j.softx.2016.07.006. + +Additionally, depending on the methods you are using you should consider citation one or more of the following articles: + .. [#] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) `[pdf] `_ .. [#] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) `[pdf] `_ @@ -31,6 +38,12 @@ This is a breaking change in the function interfaces. Hence, programs written for older versions of PySpike (0.1.x) will not run with newer versions. +Upcoming Functionality +------------------------- + +In an upcoming release, new functionality for analyzing Synfire patterns based on the new measures SPIKE-Order and Spike-Train-Order method will become part of the PySpike library. +The new measures and algorithms are described in `this preprint `_. + Requirements and Installation ----------------------------- -- cgit v1.2.3 From 1b01bcfefbc4ccfc0f108aeedf2a518421333ea5 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 30 Oct 2016 13:09:16 -0700 Subject: updated references --- Readme.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.rst b/Readme.rst index d60d154..41e9c6c 100644 --- a/Readme.rst +++ b/Readme.rst @@ -7,7 +7,7 @@ PySpike :target: https://travis-ci.org/mariomulansky/PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the ISI_ and SPIKE_ distance [#]_ [#]_ as well as SPIKE-Synchronization_ [#]_. +Its core functionality is the implementation of the ISI_\-distance [#]_ and SPIKE_ distance [#]_ as well as SPIKE-Synchronization_ [#]_. It provides functions to compute multivariate profiles, distance matrices, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). @@ -20,13 +20,13 @@ Citing PySpike If you use PySpike in your research, please cite our SoftwareX publication on PySpike: Mario Mulansky, Thomas Kreuz, *PySpike - A Python library for analyzing spike train synchrony*, SoftwareX, (2016), ISSN 2352-7110, http://dx.doi.org/10.1016/j.softx.2016.07.006. -Additionally, depending on the methods you are using you should consider citation one or more of the following articles: +Additionally, depending on the used methods: ISI-distance [1], SPIKE-distance [2] or SPIKE-Synchronization [3], please cite one or more of the following publications: .. [#] Kreuz T, Haas JS, Morelli A, Abarbanel HDI, Politi A, *Measuring spike train synchrony.* J Neurosci Methods 165, 151 (2007) `[pdf] `_ .. [#] Kreuz T, Chicharro D, Houghton C, Andrzejak RG, Mormann F, *Monitoring spike train synchrony.* J Neurophysiol 109, 1457 (2013) `[pdf] `_ -.. [#] Kreuz T, Mulansky M and Bozanic N, *SPIKY: A graphical user interface for monitoring spike train synchrony*, J Neurophysiol, in press (2015) +.. [#] Kreuz T, Mulansky M and Bozanic N, *SPIKY: A graphical user interface for monitoring spike train synchrony*, J Neurophysiol, JNeurophysiol 113, 3432 (2015) Important Changelog ----------------------------- -- cgit v1.2.3 From 50b3d0bd1ceea236d8b4e95578cebf7c78a5035e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Sun, 30 Oct 2016 13:13:38 -0700 Subject: fixed typo --- Readme.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.rst b/Readme.rst index 41e9c6c..0422dad 100644 --- a/Readme.rst +++ b/Readme.rst @@ -7,7 +7,7 @@ PySpike :target: https://travis-ci.org/mariomulansky/PySpike PySpike is a Python library for the numerical analysis of spike train similarity. -Its core functionality is the implementation of the ISI_\-distance [#]_ and SPIKE_ distance [#]_ as well as SPIKE-Synchronization_ [#]_. +Its core functionality is the implementation of the ISI_\-distance [#]_ and SPIKE_\-distance [#]_ as well as SPIKE-Synchronization_ [#]_. It provides functions to compute multivariate profiles, distance matrices, as well as averaging and general spike train processing. All computation intensive parts are implemented in C via cython_ to reach a competitive performance (factor 100-200 over plain Python). -- cgit v1.2.3 From 564f9ba9df3c6837b102f370ad32a65c233c209f Mon Sep 17 00:00:00 2001 From: Dan Meliza Date: Mon, 2 Oct 2017 12:15:36 -0400 Subject: defer numpy import to allow install_requires to do its job (fixes #24) --- setup.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b53304b..76a27e4 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ Distributed under the BSD License from setuptools import setup, find_packages from distutils.extension import Extension import os.path -import numpy try: from Cython.Distutils import build_ext @@ -21,6 +20,14 @@ except ImportError: else: use_cython = True + +class numpy_include(object): + """Defers import of numpy until install_requires is through""" + def __str__(self): + import numpy + return numpy.get_include() + + if os.path.isfile("pyspike/cython/cython_add.c") and \ os.path.isfile("pyspike/cython/cython_profiles.c") and \ os.path.isfile("pyspike/cython/cython_distances.c"): @@ -58,7 +65,7 @@ setup( version='0.5.1', cmdclass=cmdclass, ext_modules=ext_modules, - include_dirs=[numpy.get_include()], + include_dirs=[numpy_include()], description='A Python library for the numerical analysis of spike\ train similarity', author='Mario Mulansky', -- cgit v1.2.3 From 2c50e4c203af45387df4b69f91be39ad6193de08 Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 4 Oct 2017 19:02:34 -0700 Subject: Remove Python2.6, add Python3.6 from/to CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 476813e..bcf447e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: python python: - - "2.6" - "2.7" - "3.3" - "3.4" - "3.5" + - "3.6" env: - CYTHON_INSTALL="pip install -q cython" - CYTHON_INSTALL="" -- cgit v1.2.3 From b5d6cd7bfab62fd7fb96570cf99b87aeed419a4d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 4 Oct 2017 20:03:08 -0700 Subject: Bump version --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index b53304b..f2315f2 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,7 @@ to compile cython files: python setup.py build_ext --inplace -Copyright 2014-2016, Mario Mulansky +Copyright 2014-2017, Mario Mulansky Distributed under the BSD License @@ -55,7 +55,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.5.1', + version='0.5.2', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy.get_include()], @@ -72,7 +72,7 @@ train similarity', # 3 - Alpha # 4 - Beta # 5 - Production/Stable - 'Development Status :: 3 - Alpha', + 'Development Status :: 3 - Beta', # Indicate who your project is intended for 'Intended Audience :: Science/Research', @@ -82,12 +82,12 @@ train similarity', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6' ] ) -- cgit v1.2.3 From ecd4b5f0f7e93859c1262593e2c09e1eb6775819 Mon Sep 17 00:00:00 2001 From: Dan Meliza Date: Mon, 2 Oct 2017 12:15:36 -0400 Subject: defer numpy import to allow install_requires to do its job (fixes #24) --- Changelog | 6 ++++++ setup.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Changelog b/Changelog index 2be5e52..21b7cb0 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,9 @@ +PySpike v0.5: + * First beta release + * Python 2.6 support removed + * Python 3.6 support added + * several bugfixes + PySpike v0.4: * Python 3 support (thanks to Igor Gnatenko) * list interface to SpikeTrain class diff --git a/setup.py b/setup.py index f2315f2..afcfa16 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,6 @@ Distributed under the BSD License from setuptools import setup, find_packages from distutils.extension import Extension import os.path -import numpy try: from Cython.Distutils import build_ext @@ -21,6 +20,14 @@ except ImportError: else: use_cython = True + +class numpy_include(object): + """Defers import of numpy until install_requires is through""" + def __str__(self): + import numpy + return numpy.get_include() + + if os.path.isfile("pyspike/cython/cython_add.c") and \ os.path.isfile("pyspike/cython/cython_profiles.c") and \ os.path.isfile("pyspike/cython/cython_distances.c"): @@ -58,7 +65,7 @@ setup( version='0.5.2', cmdclass=cmdclass, ext_modules=ext_modules, - include_dirs=[numpy.get_include()], + include_dirs=[numpy_include()], description='A Python library for the numerical analysis of spike\ train similarity', author='Mario Mulansky', -- cgit v1.2.3 From 9fa10fe30e3216170f41ce47797c7a3a56e17a5f Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 4 Oct 2017 21:57:27 -0700 Subject: Update version in docs --- doc/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 807dec6..f9ec320 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -57,7 +57,7 @@ master_doc = 'index' # General information about the project. project = u'PySpike' -copyright = u'2014-2015, Mario Mulansky' +copyright = u'2014-2017, Mario Mulansky' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -66,7 +66,7 @@ copyright = u'2014-2015, Mario Mulansky' # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.5.1' +release = '0.5.2' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -- cgit v1.2.3 From 7d661660f143c1e690efa6dc2d526db635dc079a Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 4 Oct 2017 22:08:35 -0700 Subject: Fix typo in email --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index afcfa16..76fce07 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ setup( description='A Python library for the numerical analysis of spike\ train similarity', author='Mario Mulansky', - author_email='mario.mulanskygmx.net', + author_email='mario.mulansky@gmx.net', license='BSD', url='https://github.com/mariomulansky/PySpike', install_requires=['numpy'], -- cgit v1.2.3 From ece06870ab729f28fa1771f49725f25c6a25576d Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 4 Oct 2017 22:27:44 -0700 Subject: Fix library status string --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 76fce07..be5d209 100644 --- a/setup.py +++ b/setup.py @@ -79,7 +79,7 @@ train similarity', # 3 - Alpha # 4 - Beta # 5 - Production/Stable - 'Development Status :: 3 - Beta', + 'Development Status :: 4 - Beta', # Indicate who your project is intended for 'Intended Audience :: Science/Research', -- cgit v1.2.3 From 16040c3fb9f61f7753172f6d67ac7b22c961dcaa Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 4 Oct 2017 22:35:08 -0700 Subject: Version bump --- doc/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index f9ec320..25aa9c9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -66,7 +66,7 @@ copyright = u'2014-2017, Mario Mulansky' # The short X.Y version. version = '0.5' # The full version, including alpha/beta/rc tags. -release = '0.5.2' +release = '0.5.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.py b/setup.py index be5d209..5b9e677 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,7 @@ elif use_c: # c files are there, compile to binaries setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.5.2', + version='0.5.3', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy_include()], -- cgit v1.2.3 From fb1e0d5fd4dfc332298668a225415c5b795c0192 Mon Sep 17 00:00:00 2001 From: Jonathan Jouty Date: Sun, 4 Feb 2018 11:06:06 +0000 Subject: Make merge_spike_trains work with empty spike trains, and faster 1. Fixes https://github.com/mariomulansky/PySpike/issues/30 2. Code is faster 3. Add test case --- pyspike/spikes.py | 22 ++++------------------ test/test_spikes.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/pyspike/spikes.py b/pyspike/spikes.py index 486a4a0..cf47043 100644 --- a/pyspike/spikes.py +++ b/pyspike/spikes.py @@ -116,24 +116,10 @@ def merge_spike_trains(spike_trains): :param spike_trains: list of :class:`.SpikeTrain` :returns: spike train with the merged spike times """ - # get the lengths of the spike trains - lens = np.array([len(st.spikes) for st in spike_trains]) - merged_spikes = np.empty(np.sum(lens)) - index = 0 # the index for merged_spikes - indices = np.zeros_like(lens) # indices of the spike trains - index_list = np.arange(len(indices)) # indices of indices of spike trains - # that have not yet reached the end - # list of the possible events in the spike trains - vals = [spike_trains[i].spikes[indices[i]] for i in index_list] - while len(index_list) > 0: - i = np.argmin(vals) # the next spike is the minimum - merged_spikes[index] = vals[i] # put it to the merged spike train - i = index_list[i] - index += 1 # next index of merged spike train - indices[i] += 1 # next index for the chosen spike train - if indices[i] >= lens[i]: # remove spike train index if ended - index_list = index_list[index_list != i] - vals = [spike_trains[n].spikes[indices[n]] for n in index_list] + # concatenating and sorting with numpy is fast, it also means we can handle + # empty spike trains + merged_spikes = np.concatenate([st.spikes for st in spike_trains]) + merged_spikes.sort() return SpikeTrain(merged_spikes, [spike_trains[0].t_start, spike_trains[0].t_end]) diff --git a/test/test_spikes.py b/test/test_spikes.py index bcface2..ee505b5 100644 --- a/test/test_spikes.py +++ b/test/test_spikes.py @@ -85,6 +85,16 @@ def test_merge_spike_trains(): check_merged_spikes(merged_spikes.spikes, [st.spikes for st in spike_trains]) +def test_merge_empty_spike_trains(): + # first load the data + spike_trains = spk.load_spike_trains_from_txt(TEST_DATA, edges=(0, 4000)) + # take two non-empty trains, and one empty one + empty = spk.SpikeTrain([],[spike_trains[0].t_start,spike_trains[0].t_end]) + merged_spikes = spk.merge_spike_trains([spike_trains[0], empty, spike_trains[1]]) + # test if result is sorted + assert((merged_spikes.spikes == np.sort(merged_spikes.spikes)).all()) + # we don't need to check more, that's done by test_merge_spike_trains + if __name__ == "main": test_load_from_txt() -- cgit v1.2.3 From 751218d18f4f3b5b7c137c739df73fa521d87dbd Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Wed, 7 Feb 2018 20:02:44 -0800 Subject: Remove Python 3.3 from travis Python 3.3 is not supported by scipy anymore. This removes Python 3.3 from the CI environments. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index bcf447e..2877331 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: python python: - "2.7" - - "3.3" - "3.4" - "3.5" - "3.6" -- cgit v1.2.3 From 34bd30415dd93a2425ce566627e24ee9483ada3e Mon Sep 17 00:00:00 2001 From: Mario Mulansky Date: Thu, 20 Sep 2018 10:49:42 -0700 Subject: Spike Order support (#39) * reorganized directionality module * further refactoring of directionality * completed python directionality backend * added SPIKE-Sync based filtering new function filter_by_spike_sync removes spikes that have a multi-variate Spike Sync value below some threshold not yet fully tested, python backend missing. * spike sync filtering, cython sim ann Added function for filtering out events based on a threshold for the spike sync values. Usefull for focusing on synchronous events during directionality analysis. Also added cython version of simulated annealing for performance. * added coincidence single profile to python backend missing function in python backend added, identified and fixed a bug in the implementation as well * updated test case to new spike sync behavior * python3 fixes * another python3 fix * reorganized directionality module * further refactoring of directionality * completed python directionality backend * added SPIKE-Sync based filtering new function filter_by_spike_sync removes spikes that have a multi-variate Spike Sync value below some threshold not yet fully tested, python backend missing. * spike sync filtering, cython sim ann Added function for filtering out events based on a threshold for the spike sync values. Usefull for focusing on synchronous events during directionality analysis. Also added cython version of simulated annealing for performance. * added coincidence single profile to python backend missing function in python backend added, identified and fixed a bug in the implementation as well * updated test case to new spike sync behavior * python3 fixes * another python3 fix * Fix absolute imports in directionality measures * remove commented code * Add directionality to docs, bump version * Clean up directionality module, add doxy. * Remove debug print from tests * Fix bug in calling Python backend * Fix incorrect integrals in PieceWiseConstFunc (#36) * Add (some currently failing) tests for PieceWiseConstFunc.integral * Fix implementation of PieceWiseConstFunc.integral Just by adding a special condition for when we are only taking an integral "between" two edges of a PieceWiseConstFunc All tests now pass. Fixes #33. * Add PieceWiseConstFunc.integral tests for ValueError * Add testing bounds of integral * Raise ValueError in function implementation * Fix incorrect integrals in PieceWiseLinFunc (#38) Integrals of piece-wise linear functions were incorrect if the requested interval lies completely between two support points. This has been fixed, and a unit test exercising this behavior was added. Fixes #38 * Add Spike Order example and Tutorial section Adds an example computing spike order profile and the optimal spike train order. Also adds a section on spike train order to the tutorial. --- Changelog | 3 + Readme.rst | 9 +- doc/pyspike.rst | 6 + doc/tutorial.rst | 66 +++ examples/spike_train_order.py | 52 +++ pyspike/PieceWiseConstFunc.py | 32 +- pyspike/PieceWiseLinFunc.py | 42 +- pyspike/__init__.py | 16 +- pyspike/cython/cython_directionality.pyx | 262 ++++++++++++ pyspike/cython/cython_distances.pyx | 200 +++++++++ pyspike/cython/cython_profiles.pyx | 33 ++ pyspike/cython/cython_simulated_annealing.pyx | 82 ++++ pyspike/cython/directionality_python_backend.py | 144 +++++++ pyspike/cython/python_backend.py | 67 ++- pyspike/spike_directionality.py | 522 ++++++++++++++++++++++++ pyspike/spike_sync.py | 55 ++- setup.py | 28 +- test/test_directionality.py | 97 +++++ test/test_function.py | 62 +++ test/test_sync_filter.py | 95 +++++ 20 files changed, 1812 insertions(+), 61 deletions(-) create mode 100644 examples/spike_train_order.py create mode 100644 pyspike/cython/cython_directionality.pyx create mode 100644 pyspike/cython/cython_simulated_annealing.pyx create mode 100644 pyspike/cython/directionality_python_backend.py create mode 100644 pyspike/spike_directionality.py create mode 100644 test/test_directionality.py create mode 100644 test/test_sync_filter.py diff --git a/Changelog b/Changelog index 21b7cb0..88e16cc 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,6 @@ +PySpike v0.6: + * Support for computing spike directionality and spike train order + PySpike v0.5: * First beta release * Python 2.6 support removed diff --git a/Readme.rst b/Readme.rst index 0422dad..74b014b 100644 --- a/Readme.rst +++ b/Readme.rst @@ -31,19 +31,14 @@ Additionally, depending on the used methods: ISI-distance [1], SPIKE-distance [2 Important Changelog ----------------------------- +With version 0.6.0, the spike directionality and spike train order function have been added. + With version 0.5.0, the interfaces have been unified and the specific functions for multivariate computations have become deprecated. With version 0.2.0, the :code:`SpikeTrain` class has been introduced to represent spike trains. This is a breaking change in the function interfaces. Hence, programs written for older versions of PySpike (0.1.x) will not run with newer versions. - -Upcoming Functionality -------------------------- - -In an upcoming release, new functionality for analyzing Synfire patterns based on the new measures SPIKE-Order and Spike-Train-Order method will become part of the PySpike library. -The new measures and algorithms are described in `this preprint `_. - Requirements and Installation ----------------------------- diff --git a/doc/pyspike.rst b/doc/pyspike.rst index 74ab439..3b10d2a 100644 --- a/doc/pyspike.rst +++ b/doc/pyspike.rst @@ -64,6 +64,12 @@ PSTH :undoc-members: :show-inheritance: +Directionality +........................................ +.. automodule:: pyspike.spike_directionality + :members: + :undoc-members: + :show-inheritance: Helper functions ........................................ diff --git a/doc/tutorial.rst b/doc/tutorial.rst index aff03a8..377c0a2 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -231,3 +231,69 @@ The following example computes and plots the ISI- and SPIKE-distance matrix as w plt.title("SPIKE-Sync") plt.show() + + +Quantifying Leaders and Followers: Spike Train Order +--------------------------------------- + +PySpike provides functionality to quantify how much a set of spike trains +resembles a synfire pattern (ie perfect leader-follower pattern). For details +on the algorithms please see +`our article in NJP `_. + +The following example computes the Spike Order profile and Synfire Indicator +of two Poissonian spike trains. + +.. code:: python + import numpy as np + from matplotlib import pyplot as plt + import pyspike as spk + + + st1 = spk.generate_poisson_spikes(1.0, [0, 20]) + st2 = spk.generate_poisson_spikes(1.0, [0, 20]) + + d = spk.spike_directionality(st1, st2) + + print "Spike Directionality of two Poissonian spike trains:", d + + E = spk.spike_train_order_profile(st1, st2) + + plt.figure() + x, y = E.get_plottable_data() + plt.plot(x, y, '-ob') + plt.ylim(-1.1, 1.1) + plt.xlabel("t") + plt.ylabel("E") + plt.title("Spike Train Order Profile") + + plt.show() + +Additionally, PySpike can also compute the optimal ordering of the spike trains, +ie the ordering that most resembles a synfire pattern. The following example +computes the optimal order of a set of 20 Poissonian spike trains: + +.. code:: python + + M = 20 + spike_trains = [spk.generate_poisson_spikes(1.0, [0, 100]) for m in xrange(M)] + + F_init = spk.spike_train_order(spike_trains) + print "Initial Synfire Indicator for 20 Poissonian spike trains:", F_init + + D_init = spk.spike_directionality_matrix(spike_trains) + phi, _ = spk.optimal_spike_train_sorting(spike_trains) + F_opt = spk.spike_train_order(spike_trains, indices=phi) + print "Synfire Indicator of optimized spike train sorting:", F_opt + + D_opt = spk.permutate_matrix(D_init, phi) + + plt.figure() + plt.imshow(D_init) + plt.title("Initial Directionality Matrix") + + plt.figure() + plt.imshow(D_opt) + plt.title("Optimized Directionality Matrix") + + plt.show() diff --git a/examples/spike_train_order.py b/examples/spike_train_order.py new file mode 100644 index 0000000..3a42472 --- /dev/null +++ b/examples/spike_train_order.py @@ -0,0 +1,52 @@ +import numpy as np +from matplotlib import pyplot as plt +import pyspike as spk + + +st1 = spk.generate_poisson_spikes(1.0, [0, 20]) +st2 = spk.generate_poisson_spikes(1.0, [0, 20]) + +d = spk.spike_directionality(st1, st2) + +print "Spike Directionality of two Poissonian spike trains:", d + +E = spk.spike_train_order_profile(st1, st2) + +plt.figure() +x, y = E.get_plottable_data() +plt.plot(x, y, '-ob') +plt.ylim(-1.1, 1.1) +plt.xlabel("t") +plt.ylabel("E") +plt.title("Spike Train Order Profile") + + +###### Optimize spike train order of 20 Random spike trains ####### + +M = 20 + +spike_trains = [spk.generate_poisson_spikes(1.0, [0, 100]) for m in xrange(M)] + +F_init = spk.spike_train_order(spike_trains) + +print "Initial Synfire Indicator for 20 Poissonian spike trains:", F_init + +D_init = spk.spike_directionality_matrix(spike_trains) + +phi, _ = spk.optimal_spike_train_sorting(spike_trains) + +F_opt = spk.spike_train_order(spike_trains, indices=phi) + +print "Synfire Indicator of optimized spike train sorting:", F_opt + +D_opt = spk.permutate_matrix(D_init, phi) + +plt.figure() +plt.imshow(D_init) +plt.title("Initial Directionality Matrix") + +plt.figure() +plt.imshow(D_opt) +plt.title("Optimized Directionality Matrix") + +plt.show() diff --git a/pyspike/PieceWiseConstFunc.py b/pyspike/PieceWiseConstFunc.py index 5ce5f27..17fdd3f 100644 --- a/pyspike/PieceWiseConstFunc.py +++ b/pyspike/PieceWiseConstFunc.py @@ -129,19 +129,31 @@ class PieceWiseConstFunc(object): # no interval given, integrate over the whole spike train a = np.sum((self.x[1:]-self.x[:-1]) * self.y) else: + if interval[0]>interval[1]: + raise ValueError("Invalid averaging interval: interval[0]>=interval[1]") + if interval[0]self.x[-1]: + raise ValueError("Invalid averaging interval: interval[0] 0 and end_ind < len(self.x), \ - "Invalid averaging interval" - # first the contribution from between the indices - a = np.sum((self.x[start_ind+1:end_ind+1] - - self.x[start_ind:end_ind]) * - self.y[start_ind:end_ind]) - # correction from start to first index - a += (self.x[start_ind]-interval[0]) * self.y[start_ind-1] - # correction from last index to end - a += (interval[1]-self.x[end_ind]) * self.y[end_ind] + if start_ind > end_ind: + # contribution from between two closest edges + a = (self.x[start_ind]-self.x[end_ind]) * self.y[end_ind] + # minus the part that is not within the interval + a -= ((interval[0]-self.x[end_ind])+(self.x[start_ind]-interval[1])) * self.y[end_ind] + else: + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + # first the contribution from between the indices + a = np.sum((self.x[start_ind+1:end_ind+1] - + self.x[start_ind:end_ind]) * + self.y[start_ind:end_ind]) + # correction from start to first index + a += (self.x[start_ind]-interval[0]) * self.y[start_ind-1] + # correction from last index to end + a += (interval[1]-self.x[end_ind]) * self.y[end_ind] return a def avrg(self, interval=None): diff --git a/pyspike/PieceWiseLinFunc.py b/pyspike/PieceWiseLinFunc.py index 8145e63..8faaec4 100644 --- a/pyspike/PieceWiseLinFunc.py +++ b/pyspike/PieceWiseLinFunc.py @@ -146,31 +146,47 @@ class PieceWiseLinFunc: if interval is None: # no interval given, integrate over the whole spike train - integral = np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) + return np.sum((self.x[1:]-self.x[:-1]) * 0.5*(self.y1+self.y2)) + + # find the indices corresponding to the interval + start_ind = np.searchsorted(self.x, interval[0], side='right') + end_ind = np.searchsorted(self.x, interval[1], side='left')-1 + assert start_ind > 0 and end_ind < len(self.x), \ + "Invalid averaging interval" + if start_ind > end_ind: + print(start_ind, end_ind, self.x[start_ind]) + # contribution from between two closest edges + y_x0 = intermediate_value(self.x[start_ind-1], + self.x[start_ind], + self.y1[start_ind-1], + self.y2[start_ind-1], + interval[0]) + y_x1 = intermediate_value(self.x[start_ind-1], + self.x[start_ind], + self.y1[start_ind-1], + self.y2[start_ind-1], + interval[1]) + print(y_x0, y_x1, interval[1] - interval[0]) + integral = (y_x0 + y_x1) * 0.5 * (interval[1] - interval[0]) + print(integral) else: - # find the indices corresponding to the interval - start_ind = np.searchsorted(self.x, interval[0], side='right') - end_ind = np.searchsorted(self.x, interval[1], side='left')-1 - assert start_ind > 0 and end_ind < len(self.x), \ - "Invalid averaging interval" # first the contribution from between the indices integral = np.sum((self.x[start_ind+1:end_ind+1] - - self.x[start_ind:end_ind]) * - 0.5*(self.y1[start_ind:end_ind] + - self.y2[start_ind:end_ind])) + self.x[start_ind:end_ind]) * + 0.5*(self.y1[start_ind:end_ind] + + self.y2[start_ind:end_ind])) # correction from start to first index integral += (self.x[start_ind]-interval[0]) * 0.5 * \ (self.y2[start_ind-1] + - intermediate_value(self.x[start_ind-1], + intermediate_value(self.x[start_ind-1], self.x[start_ind], self.y1[start_ind-1], self.y2[start_ind-1], - interval[0] - )) + interval[0])) # correction from last index to end integral += (interval[1]-self.x[end_ind]) * 0.5 * \ (self.y1[end_ind] + - intermediate_value(self.x[end_ind], self.x[end_ind+1], + intermediate_value(self.x[end_ind], self.x[end_ind+1], self.y1[end_ind], self.y2[end_ind], interval[1] )) diff --git a/pyspike/__init__.py b/pyspike/__init__.py index 08253fb..3897d18 100644 --- a/pyspike/__init__.py +++ b/pyspike/__init__.py @@ -1,5 +1,5 @@ """ -Copyright 2014-2015, Mario Mulansky +Copyright 2014-2018, Mario Mulansky Distributed under the BSD License """ @@ -7,8 +7,8 @@ Distributed under the BSD License from __future__ import absolute_import __all__ = ["isi_distance", "spike_distance", "spike_sync", "psth", - "spikes", "SpikeTrain", "PieceWiseConstFunc", "PieceWiseLinFunc", - "DiscreteFunc", "directionality"] + "spikes", "spike_directionality", "SpikeTrain", + "PieceWiseConstFunc", "PieceWiseLinFunc", "DiscreteFunc"] from .PieceWiseConstFunc import PieceWiseConstFunc from .PieceWiseLinFunc import PieceWiseLinFunc @@ -20,13 +20,21 @@ from .isi_distance import isi_profile, isi_distance, isi_profile_multi,\ from .spike_distance import spike_profile, spike_distance, spike_profile_multi,\ spike_distance_multi, spike_distance_matrix from .spike_sync import spike_sync_profile, spike_sync,\ - spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix + spike_sync_profile_multi, spike_sync_multi, spike_sync_matrix,\ + filter_by_spike_sync from .psth import psth from .spikes import load_spike_trains_from_txt, save_spike_trains_to_txt, \ spike_train_from_string, import_spike_trains_from_time_series, \ merge_spike_trains, generate_poisson_spikes +from .spike_directionality import spike_directionality, \ + spike_directionality_values, spike_directionality_matrix, \ + spike_train_order_profile, spike_train_order_profile_bi, \ + spike_train_order_profile_multi, spike_train_order, \ + spike_train_order_bi, spike_train_order_multi, \ + optimal_spike_train_sorting, permutate_matrix + # define the __version__ following # http://stackoverflow.com/questions/17583443 from pkg_resources import get_distribution, DistributionNotFound 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 + +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 index ac5f226..d4070ae 100644 --- a/pyspike/cython/cython_distances.pyx +++ b/pyspike/cython/cython_distances.pyx @@ -178,6 +178,8 @@ cdef inline double isi_avrg_cython(double isi1, double isi2) nogil: return 0.5*(isi1+isi2)*(isi1+isi2) # alternative definition to obtain ~ 0.5 for Poisson spikes # return 0.5*(isi1*isi1+isi2*isi2) + # another alternative definition without second normalization + # return 0.5*(isi1+isi2) ############################################################ @@ -248,6 +250,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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: @@ -267,6 +271,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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) @@ -286,6 +292,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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 @@ -301,6 +309,8 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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) @@ -320,6 +330,9 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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 @@ -358,6 +371,193 @@ def spike_distance_cython(double[:] t1, double[:] t2, 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 diff --git a/pyspike/cython/cython_profiles.pyx b/pyspike/cython/cython_profiles.pyx index 4a42cdb..aa24db4 100644 --- a/pyspike/cython/cython_profiles.pyx +++ b/pyspike/cython/cython_profiles.pyx @@ -450,3 +450,36 @@ def coincidence_profile_cython(double[:] spikes1, double[:] spikes2, 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 + +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 + +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 index 6b7209a..e75f181 100644 --- a/pyspike/cython/python_backend.py +++ b/pyspike/cython/python_backend.py @@ -3,7 +3,7 @@ Collection of python functions that can be used instead of the cython implementation. -Copyright 2014, Mario Mulansky +Copyright 2014-2015, Mario Mulansky Distributed under the BSD License @@ -356,26 +356,27 @@ def cumulative_sync_python(spikes1, spikes2): 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): - 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 @@ -388,7 +389,7 @@ def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau): 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) + 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 @@ -398,7 +399,7 @@ def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau): 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) + 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 @@ -433,6 +434,36 @@ def coincidence_python(spikes1, spikes2, t_start, t_end, max_tau): 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 ############################################################ diff --git a/pyspike/spike_directionality.py b/pyspike/spike_directionality.py new file mode 100644 index 0000000..248862c --- /dev/null +++ b/pyspike/spike_directionality.py @@ -0,0 +1,522 @@ +# Module containing functions to compute the SPIKE directionality and the +# spike train order profile +# Copyright 2015, Mario Mulansky +# Distributed under the BSD License + +from __future__ import absolute_import + +import numpy as np +import pyspike +from pyspike import DiscreteFunc +from functools import partial +from pyspike.generic import _generic_profile_multi + + +############################################################ +# spike_directionality_values +############################################################ +def spike_directionality_values(*args, **kwargs): + """ Computes the spike directionality value for each spike in + each spike train. Returns a list containing an array of spike directionality + values for every given spike train. + + Valid call structures:: + + spike_directionality_values(st1, st2) # returns the bi-variate profile + spike_directionality_values(st1, st2, st3) # multi-variate profile of 3 + # spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + spike_directionality_values(spike_trains) # profile of the list of spike trains + spike_directionality_values(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + Additonal arguments: + :param max_tau: Upper bound for coincidence window (default=None). + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + + :returns: The spike directionality values :math:`D^n_i` as a list of arrays. + """ + if len(args) == 1: + return _spike_directionality_values_impl(args[0], **kwargs) + else: + return _spike_directionality_values_impl(args, **kwargs) + + +def _spike_directionality_values_impl(spike_trains, indices=None, + interval=None, max_tau=None): + """ Computes the multi-variate spike directionality profile + of the given spike trains. + + :param spike_trains: List of spike trains. + :type spike_trains: List of :class:`pyspike.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike-directionality values. + """ + if interval is not None: + raise NotImplementedError("Parameter `interval` not supported.") + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # list of arrays for resulting asymmetry values + asymmetry_list = [np.zeros_like(spike_trains[n].spikes) for n in indices] + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + # cython implementation + try: + from .cython.cython_directionality import \ + spike_directionality_profiles_cython as profile_impl + except ImportError: + if not(pyspike.disable_backend_warning): + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from .cython.directionality_python_backend import \ + spike_directionality_profile_python as profile_impl + + if max_tau is None: + max_tau = 0.0 + + for i, j in pairs: + d1, d2 = profile_impl(spike_trains[i].spikes, spike_trains[j].spikes, + spike_trains[i].t_start, spike_trains[i].t_end, + max_tau) + asymmetry_list[i] += d1 + asymmetry_list[j] += d2 + for a in asymmetry_list: + a /= len(spike_trains)-1 + return asymmetry_list + + +############################################################ +# spike_directionality +############################################################ +def spike_directionality(spike_train1, spike_train2, normalize=True, + interval=None, max_tau=None): + """ Computes the overall spike directionality of the first spike train with + respect to the second spike train. + + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` + :param normalize: Normalize by the number of spikes (multiplicity). + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike train order profile :math:`E(t)`. + """ + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from .cython.cython_directionality import \ + spike_directionality_cython as spike_directionality_impl + if max_tau is None: + max_tau = 0.0 + d = spike_directionality_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + c = len(spike_train1.spikes) + except ImportError: + if not(pyspike.disable_backend_warning): + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use profile. + d1, x = spike_directionality_values([spike_train1, spike_train2], + interval=interval, + max_tau=max_tau) + d = np.sum(d1) + c = len(spike_train1.spikes) + if normalize: + return 1.0*d/c + else: + return d + else: + # some specific interval is provided: not yet implemented + raise NotImplementedError("Parameter `interval` not supported.") + + +############################################################ +# spike_directionality_matrix +############################################################ +def spike_directionality_matrix(spike_trains, normalize=True, indices=None, + interval=None, max_tau=None): + """ Computes the spike directionality matrix for the given spike trains. + + :param spike_trains: List of spike trains. + :type spike_trains: List of :class:`pyspike.SpikeTrain` + :param normalize: Normalize by the number of spikes (multiplicity). + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike-directionality values. + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + distance_matrix = np.zeros((len(indices), len(indices))) + for i, j in pairs: + d = spike_directionality(spike_trains[i], spike_trains[j], normalize, + interval, max_tau=max_tau) + distance_matrix[i, j] = d + distance_matrix[j, i] = -d + return distance_matrix + + +############################################################ +# spike_train_order_profile +############################################################ +def spike_train_order_profile(*args, **kwargs): + """ Computes the spike train order profile :math:`E(t)` of the given + spike trains. Returns the profile as a DiscreteFunction object. + + Valid call structures:: + + spike_train_order_profile(st1, st2) # returns the bi-variate profile + spike_train_order_profile(st1, st2, st3) # multi-variate profile of 3 + # spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + spike_train_order_profile(spike_trains) # profile of the list of spike trains + spike_train_order_profile(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + Additonal arguments: + :param max_tau: Upper bound for coincidence window, `default=None`. + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + + :returns: The spike train order profile :math:`E(t)` + :rtype: :class:`.DiscreteFunction` + """ + if len(args) == 1: + return spike_train_order_profile_multi(args[0], **kwargs) + elif len(args) == 2: + return spike_train_order_profile_bi(args[0], args[1], **kwargs) + else: + return spike_train_order_profile_multi(args, **kwargs) + + +############################################################ +# spike_train_order_profile_bi +############################################################ +def spike_train_order_profile_bi(spike_train1, spike_train2, max_tau=None): + """ Computes the spike train order profile P(t) of the two given + spike trains. Returns the profile as a DiscreteFunction object. + + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike train order profile :math:`E(t)`. + :rtype: :class:`pyspike.function.DiscreteFunction` + """ + # check whether the spike trains are defined for the same interval + assert spike_train1.t_start == spike_train2.t_start, \ + "Given spike trains are not defined on the same interval!" + assert spike_train1.t_end == spike_train2.t_end, \ + "Given spike trains are not defined on the same interval!" + + # cython implementation + try: + from .cython.cython_directionality import \ + spike_train_order_profile_cython as \ + spike_train_order_profile_impl + except ImportError: + # raise NotImplementedError() + if not(pyspike.disable_backend_warning): + print("Warning: spike_distance_cython not found. Make sure that \ +PySpike is installed by running\n 'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from .cython.directionality_python_backend import \ + spike_train_order_profile_python as spike_train_order_profile_impl + + if max_tau is None: + max_tau = 0.0 + + times, coincidences, multiplicity \ + = spike_train_order_profile_impl(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + + return DiscreteFunc(times, coincidences, multiplicity) + + +############################################################ +# spike_train_order_profile_multi +############################################################ +def spike_train_order_profile_multi(spike_trains, indices=None, + max_tau=None): + """ Computes the multi-variate spike train order profile for a set of + spike trains. For each spike in the set of spike trains, the multi-variate + profile is defined as the sum of asymmetry values divided by the number of + spike trains pairs involving the spike train of containing this spike, + which is the number of spike trains minus one (N-1). + + :param spike_trains: list of :class:`pyspike.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The multi-variate spike sync profile :math:`(t)` + :rtype: :class:`pyspike.function.DiscreteFunction` + """ + prof_func = partial(spike_train_order_profile_bi, max_tau=max_tau) + average_prof, M = _generic_profile_multi(spike_trains, prof_func, + indices) + return average_prof + + + +############################################################ +# _spike_train_order_impl +############################################################ +def _spike_train_order_impl(spike_train1, spike_train2, + interval=None, max_tau=None): + """ Implementation of bi-variatae spike train order value (Synfire Indicator). + + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike train order value (Synfire Indicator) + """ + if interval is None: + # distance over the whole interval is requested: use specific function + # for optimal performance + try: + from .cython.cython_directionality import \ + spike_train_order_cython as spike_train_order_func + if max_tau is None: + max_tau = 0.0 + c, mp = spike_train_order_func(spike_train1.spikes, + spike_train2.spikes, + spike_train1.t_start, + spike_train1.t_end, + max_tau) + except ImportError: + # Cython backend not available: fall back to profile averaging + c, mp = spike_train_order_profile(spike_train1, spike_train2, + max_tau=max_tau).integral(interval) + return c, mp + else: + # some specific interval is provided: not yet implemented + raise NotImplementedError("Parameter `interval` not supported.") + + +############################################################ +# spike_train_order +############################################################ +def spike_train_order(*args, **kwargs): + """ Computes the spike train order (Synfire Indicator) of the given + spike trains. + + Valid call structures:: + + spike_train_order(st1, st2, normalize=True) # normalized bi-variate + # spike train order + spike_train_order(st1, st2, st3) # multi-variate result of 3 spike trains + + spike_trains = [st1, st2, st3, st4] # list of spike trains + spike_train_order(spike_trains) # result for the list of spike trains + spike_train_order(spike_trains, indices=[0, 1]) # use only the spike trains + # given by the indices + + Additonal arguments: + - `max_tau` Upper bound for coincidence window, `default=None`. + - `normalize` Flag indicating if the reslut should be normalized by the + number of spikes , default=`False` + + + :returns: The spike train order value (Synfire Indicator) + """ + if len(args) == 1: + return spike_train_order_multi(args[0], **kwargs) + elif len(args) == 2: + return spike_train_order_bi(args[0], args[1], **kwargs) + else: + return spike_train_order_multi(args, **kwargs) + + +############################################################ +# spike_train_order_bi +############################################################ +def spike_train_order_bi(spike_train1, spike_train2, normalize=True, + interval=None, max_tau=None): + """ Computes the overall spike train order value (Synfire Indicator) + for two spike trains. + + :param spike_train1: First spike train. + :type spike_train1: :class:`pyspike.SpikeTrain` + :param spike_train2: Second spike train. + :type spike_train2: :class:`pyspike.SpikeTrain` + :param normalize: Normalize by the number of spikes (multiplicity). + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: The spike train order value (Synfire Indicator) + """ + c, mp = _spike_train_order_impl(spike_train1, spike_train2, interval, max_tau) + if normalize: + return 1.0*c/mp + else: + return c + +############################################################ +# spike_train_order_multi +############################################################ +def spike_train_order_multi(spike_trains, indices=None, normalize=True, + interval=None, max_tau=None): + """ Computes the overall spike train order value (Synfire Indicator) + for many spike trains. + + :param spike_trains: list of :class:`.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :param normalize: Normalize by the number of spike (multiplicity). + :param interval: averaging interval given as a pair of floats, if None + the average over the whole function is computed. + :type interval: Pair of floats or None. + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound. + :returns: Spike train order values (Synfire Indicator) F for the given spike trains. + :rtype: double + """ + if indices is None: + indices = np.arange(len(spike_trains)) + indices = np.array(indices) + # check validity of indices + assert (indices < len(spike_trains)).all() and (indices >= 0).all(), \ + "Invalid index list." + # generate a list of possible index pairs + pairs = [(indices[i], j) for i in range(len(indices)) + for j in indices[i+1:]] + + e_total = 0.0 + m_total = 0.0 + for (i, j) in pairs: + e, m = _spike_train_order_impl(spike_trains[i], spike_trains[j], + interval, max_tau) + e_total += e + m_total += m + + if m == 0.0: + return 1.0 + else: + return e_total/m_total + + + +############################################################ +# optimal_spike_train_sorting_from_matrix +############################################################ +def _optimal_spike_train_sorting_from_matrix(D, full_output=False): + """ Finds the best sorting via simulated annealing. + Returns the optimal permutation p and A value. + Not for direct use, call :func:`.optimal_spike_train_sorting` instead. + + :param D: The directionality (Spike-ORDER) matrix. + :param full_output: If true, then function will additionally return the + number of performed iterations (default=False) + :return: (p, F) - tuple with the optimal permutation and synfire indicator. + if `full_output=True` , (p, F, iter) is returned. + """ + N = len(D) + A = np.sum(np.triu(D, 0)) + + p = np.arange(N) + + T_start = 2*np.max(D) # starting temperature + T_end = 1E-5 * T_start # final temperature + alpha = 0.9 # cooling factor + + try: + from .cython.cython_simulated_annealing import sim_ann_cython as sim_ann + except ImportError: + raise NotImplementedError("PySpike with Cython required for computing spike train" + " sorting!") + + p, A, total_iter = sim_ann(D, T_start, T_end, alpha) + + if full_output: + return p, A, total_iter + else: + return p, A + + +############################################################ +# optimal_spike_train_sorting +############################################################ +def optimal_spike_train_sorting(spike_trains, indices=None, interval=None, + max_tau=None, full_output=False): + """ Finds the best sorting of the given spike trains by computing the spike + directionality matrix and optimize the order using simulated annealing. + For a detailed description of the algorithm see: + `http://iopscience.iop.org/article/10.1088/1367-2630/aa68c3/meta` + + :param spike_trains: list of :class:`.SpikeTrain` + :param indices: list of indices defining which spike trains to use, + if None all given spike trains are used (default=None) + :type indices: list or None + :param interval: time interval filter given as a pair of floats, if None + the full spike trains are used (default=None). + :type interval: Pair of floats or None. + :param max_tau: Maximum coincidence window size. If 0 or `None`, the + coincidence window has no upper bound (default=None). + :param full_output: If true, then function will additionally return the + number of performed iterations (default=False) + :return: (p, F) - tuple with the optimal permutation and synfire indicator. + if `full_output=True` , (p, F, iter) is returned. + """ + D = spike_directionality_matrix(spike_trains, normalize=False, + indices=indices, interval=interval, + max_tau=max_tau) + return _optimal_spike_train_sorting_from_matrix(D, full_output) + +############################################################ +# permutate_matrix +############################################################ +def permutate_matrix(D, p): + """ Helper function that applies the permutation p to the columns and rows + of matrix D. Return the permutated matrix :math:`D'[n,m] = D[p[n], p[m]]`. + + :param D: The matrix. + :param d: The permutation. + :return: The permuated matrix D', ie :math:`D'[n,m] = D[p[n], p[m]]` + """ + N = len(D) + D_p = np.empty_like(D) + for n in range(N): + for m in range(N): + D_p[n, m] = D[p[n], p[m]] + return D_p diff --git a/pyspike/spike_sync.py b/pyspike/spike_sync.py index 80f7805..95ef454 100644 --- a/pyspike/spike_sync.py +++ b/pyspike/spike_sync.py @@ -8,7 +8,7 @@ from __future__ import absolute_import import numpy as np from functools import partial import pyspike -from pyspike import DiscreteFunc +from pyspike import DiscreteFunc, SpikeTrain from pyspike.generic import _generic_profile_multi, _generic_distance_matrix @@ -45,9 +45,9 @@ def spike_sync_profile(*args, **kwargs): if len(args) == 1: return spike_sync_profile_multi(args[0], **kwargs) elif len(args) == 2: - return spike_sync_profile_bi(args[0], args[1]) + return spike_sync_profile_bi(args[0], args[1], **kwargs) else: - return spike_sync_profile_multi(args) + return spike_sync_profile_multi(args, **kwargs) ############################################################ @@ -290,3 +290,52 @@ def spike_sync_matrix(spike_trains, indices=None, interval=None, max_tau=None): dist_func = partial(spike_sync_bi, max_tau=max_tau) return _generic_distance_matrix(spike_trains, dist_func, indices, interval) + + +############################################################ +# filter_by_spike_sync +############################################################ +def filter_by_spike_sync(spike_trains, threshold, indices=None, max_tau=None, + return_removed_spikes=False): + """ Removes the spikes with a multi-variate spike_sync value below + threshold. + """ + N = len(spike_trains) + filtered_spike_trains = [] + removed_spike_trains = [] + + # cython implementation + try: + from .cython.cython_profiles import coincidence_single_profile_cython \ + as coincidence_impl + except ImportError: + if not(pyspike.disable_backend_warning): + print("Warning: coincidence_single_profile_cython not found. Make \ +sure that PySpike is installed by running\n \ +'python setup.py build_ext --inplace'!\n \ +Falling back to slow python backend.") + # use python backend + from .cython.python_backend import coincidence_single_python \ + as coincidence_impl + + if max_tau is None: + max_tau = 0.0 + + for i, st in enumerate(spike_trains): + coincidences = np.zeros_like(st) + for j in range(N): + if i == j: + continue + coincidences += coincidence_impl(st.spikes, spike_trains[j].spikes, + st.t_start, st.t_end, max_tau) + filtered_spikes = st[coincidences > threshold*(N-1)] + filtered_spike_trains.append(SpikeTrain(filtered_spikes, + [st.t_start, st.t_end])) + if return_removed_spikes: + removed_spikes = st[coincidences <= threshold*(N-1)] + removed_spike_trains.append(SpikeTrain(removed_spikes, + [st.t_start, st.t_end])) + if return_removed_spikes: + return [filtered_spike_trains, removed_spike_trains] + else: + return filtered_spike_trains diff --git a/setup.py b/setup.py index 5b9e677..b5b01a6 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,9 @@ class numpy_include(object): if os.path.isfile("pyspike/cython/cython_add.c") and \ os.path.isfile("pyspike/cython/cython_profiles.c") and \ - os.path.isfile("pyspike/cython/cython_distances.c"): + os.path.isfile("pyspike/cython/cython_distances.c") and \ + os.path.isfile("pyspike/cython/cython_directionality.c") and \ + os.path.isfile("pyspike/cython/cython_simulated_annealing.c"): use_c = True else: use_c = False @@ -45,7 +47,11 @@ if use_cython: # Cython is available, compile .pyx -> .c Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.pyx"]), Extension("pyspike.cython.cython_distances", - ["pyspike/cython/cython_distances.pyx"]) + ["pyspike/cython/cython_distances.pyx"]), + Extension("pyspike.cython.cython_directionality", + ["pyspike/cython/cython_directionality.pyx"]), + Extension("pyspike.cython.cython_simulated_annealing", + ["pyspike/cython/cython_simulated_annealing.pyx"]) ] cmdclass.update({'build_ext': build_ext}) elif use_c: # c files are there, compile to binaries @@ -55,14 +61,18 @@ elif use_c: # c files are there, compile to binaries Extension("pyspike.cython.cython_profiles", ["pyspike/cython/cython_profiles.c"]), Extension("pyspike.cython.cython_distances", - ["pyspike/cython/cython_distances.c"]) + ["pyspike/cython/cython_distances.c"]), + Extension("pyspike.cython.cython_directionality", + ["pyspike/cython/cython_directionality.c"]), + Extension("pyspike.cython.cython_simulated_annealing", + ["pyspike/cython/cython_simulated_annealing.c"]) ] # neither cython nor c files available -> automatic fall-back to python backend setup( name='pyspike', packages=find_packages(exclude=['doc']), - version='0.5.3', + version='0.6.0', cmdclass=cmdclass, ext_modules=ext_modules, include_dirs=[numpy_include()], @@ -90,11 +100,17 @@ train similarity', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6' - ] + ], + package_data={ + 'pyspike': ['cython/cython_add.c', 'cython/cython_profiles.c', + 'cython/cython_distances.c', + 'cython/cython_directionality.c', + 'cython/cython_simulated_annealing.c'], + 'test': ['Spike_testdata.txt'] + } ) diff --git a/test/test_directionality.py b/test/test_directionality.py new file mode 100644 index 0000000..c2e9bfe --- /dev/null +++ b/test/test_directionality.py @@ -0,0 +1,97 @@ +""" test_directionality.py + +Tests the directionality functions + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License + +""" + +import numpy as np +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_equal + +import pyspike as spk +from pyspike import SpikeTrain, DiscreteFunc + + +def test_spike_directionality(): + st1 = SpikeTrain([100, 200, 300], [0, 1000]) + st2 = SpikeTrain([105, 205, 300], [0, 1000]) + assert_almost_equal(spk.spike_directionality(st1, st2), 2.0/3.0) + assert_almost_equal(spk.spike_directionality(st1, st2, normalize=False), + 2.0) + + # exchange order of spike trains should give exact negative profile + assert_almost_equal(spk.spike_directionality(st2, st1), -2.0/3.0) + assert_almost_equal(spk.spike_directionality(st2, st1, normalize=False), + -2.0) + + st3 = SpikeTrain([105, 195, 500], [0, 1000]) + assert_almost_equal(spk.spike_directionality(st1, st3), 0.0) + assert_almost_equal(spk.spike_directionality(st1, st3, normalize=False), + 0.0) + assert_almost_equal(spk.spike_directionality(st3, st1), 0.0) + + D = spk.spike_directionality_matrix([st1, st2, st3], normalize=False) + D_expected = np.array([[0, 2.0, 0.0], [-2.0, 0.0, -1.0], [0.0, 1.0, 0.0]]) + assert_array_equal(D, D_expected) + + dir_profs = spk.spike_directionality_values([st1, st2, st3]) + assert_array_equal(dir_profs[0], [1.0, 0.0, 0.0]) + assert_array_equal(dir_profs[1], [-0.5, -1.0, 0.0]) + + +def test_spike_train_order(): + st1 = SpikeTrain([100, 200, 300], [0, 1000]) + st2 = SpikeTrain([105, 205, 300], [0, 1000]) + st3 = SpikeTrain([105, 195, 500], [0, 1000]) + + expected_x12 = np.array([0, 100, 105, 200, 205, 300, 1000]) + expected_y12 = np.array([1, 1, 1, 1, 1, 0, 0]) + expected_mp12 = np.array([1, 1, 1, 1, 1, 2, 2]) + + f = spk.spike_train_order_profile(st1, st2) + + assert f.almost_equal(DiscreteFunc(expected_x12, expected_y12, + expected_mp12)) + assert_almost_equal(f.avrg(), 2.0/3.0) + assert_almost_equal(f.avrg(normalize=False), 4.0) + assert_almost_equal(spk.spike_train_order(st1, st2), 2.0/3.0) + assert_almost_equal(spk.spike_train_order(st1, st2, normalize=False), 4.0) + + expected_x23 = np.array([0, 105, 195, 205, 300, 500, 1000]) + expected_y23 = np.array([0, 0, -1, -1, 0, 0, 0]) + expected_mp23 = np.array([2, 2, 1, 1, 1, 1, 1]) + + f = spk.spike_train_order_profile(st2, st3) + + assert_array_equal(f.x, expected_x23) + assert_array_equal(f.y, expected_y23) + assert_array_equal(f.mp, expected_mp23) + assert f.almost_equal(DiscreteFunc(expected_x23, expected_y23, + expected_mp23)) + assert_almost_equal(f.avrg(), -1.0/3.0) + assert_almost_equal(f.avrg(normalize=False), -2.0) + assert_almost_equal(spk.spike_train_order(st2, st3), -1.0/3.0) + assert_almost_equal(spk.spike_train_order(st2, st3, normalize=False), -2.0) + + f = spk.spike_train_order_profile_multi([st1, st2, st3]) + + expected_x = np.array([0, 100, 105, 195, 200, 205, 300, 500, 1000]) + expected_y = np.array([2, 2, 2, -2, 0, 0, 0, 0, 0]) + expected_mp = np.array([2, 2, 4, 2, 2, 2, 4, 2, 2]) + + assert_array_equal(f.x, expected_x) + assert_array_equal(f.y, expected_y) + assert_array_equal(f.mp, expected_mp) + + # Averaging the profile should be the same as computing the synfire indicator directly. + assert_almost_equal(f.avrg(), spk.spike_train_order([st1, st2, st3])) + + # We can also compute the synfire indicator from the Directionality Matrix: + D_matrix = spk.spike_directionality_matrix([st1, st2, st3], normalize=False) + num_spikes = np.sum(len(st) for st in [st1, st2, st3]) + syn_fire = np.sum(np.triu(D_matrix)) / num_spikes + assert_almost_equal(f.avrg(), syn_fire) diff --git a/test/test_function.py b/test/test_function.py index 92d378d..6c04839 100644 --- a/test/test_function.py +++ b/test/test_function.py @@ -10,6 +10,7 @@ Distributed under the BSD License from __future__ import print_function import numpy as np from copy import copy +from nose.tools import raises from numpy.testing import assert_equal, assert_almost_equal, \ assert_array_equal, assert_array_almost_equal @@ -49,6 +50,8 @@ def test_pwc(): assert_almost_equal(a, (0.5-0.5+0.5*1.5+1.0*0.75)/3.0, decimal=16) a = f.avrg([1.5, 3.5]) assert_almost_equal(a, (-0.5*0.5+0.5*1.5+1.0*0.75)/2.0, decimal=16) + a = f.avrg([1.0, 2.0]) + assert_almost_equal(a, (1.0*-0.5)/1.0, decimal=16) a = f.avrg([1.0, 3.5]) assert_almost_equal(a, (-0.5*1.0+0.5*1.5+1.0*0.75)/2.5, decimal=16) a = f.avrg([1.0, 4.0]) @@ -120,6 +123,53 @@ def test_pwc_avrg(): assert_array_almost_equal(f1.x, x_expected, decimal=16) assert_array_almost_equal(f1.y, y_expected, decimal=16) +def test_pwc_integral(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f1 = spk.PieceWiseConstFunc(x, y) + + # test full interval + full = 1.0*1.0 + 1.0*-0.5 + 0.5*1.5 + 1.5*0.75; + assert_equal(f1.integral(), full) + assert_equal(f1.integral((np.min(x),np.max(x))), full) + # test part interval, spanning an edge + assert_equal(f1.integral((0.5,1.5)), 0.5*1.0 + 0.5*-0.5) + # test part interval, just over two edges + assert_almost_equal(f1.integral((1.0-1e-16,2+1e-16)), 1.0*-0.5, decimal=14) + # test part interval, between two edges + assert_equal(f1.integral((1.0,2.0)), 1.0*-0.5) + assert_equal(f1.integral((1.2,1.7)), (1.7-1.2)*-0.5) + # test part interval, start to before and after edge + assert_equal(f1.integral((0.0,0.7)), 0.7*1.0) + assert_equal(f1.integral((0.0,1.1)), 1.0*1.0+0.1*-0.5) + # test part interval, before and after edge till end + assert_equal(f1.integral((2.6,4.0)), (4.0-2.6)*0.75) + assert_equal(f1.integral((2.4,4.0)), (2.5-2.4)*1.5+(4-2.5)*0.75) + +@raises(ValueError) +def test_pwc_integral_bad_bounds_inv(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f1 = spk.PieceWiseConstFunc(x, y) + f1.integral((3,2)) + +@raises(ValueError) +def test_pwc_integral_bad_bounds_oob_1(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f1 = spk.PieceWiseConstFunc(x, y) + f1.integral((1,6)) + +@raises(ValueError) +def test_pwc_integral_bad_bounds_oob_2(): + # some random data + x = [0.0, 1.0, 2.0, 2.5, 4.0] + y = [1.0, -0.5, 1.5, 0.75] + f1 = spk.PieceWiseConstFunc(x, y) + f1.integral((-1,3)) def test_pwl(): x = [0.0, 1.0, 2.0, 2.5, 4.0] @@ -162,6 +212,18 @@ def test_pwl(): a = f.avrg([1.0, 4.0]) assert_almost_equal(a, (-0.45 + 0.75 + 1.5*0.5) / 3.0, decimal=16) + # interval between support points + a = f.avrg([1.1, 1.5]) + assert_almost_equal(a, (-0.5+0.1*0.1 - 0.45) * 0.5, decimal=14) + + # starting at a support point + a = f.avrg([1.0, 1.5]) + assert_almost_equal(a, (-0.5 - 0.45) * 0.5, decimal=14) + + # start and end at support point + a = f.avrg([1.0, 2.0]) + assert_almost_equal(a, (-0.5 - 0.4) * 0.5, decimal=14) + # averaging over multiple intervals a = f.avrg([(0.5, 1.5), (1.5, 2.5)]) assert_almost_equal(a, (1.375*0.5 - 0.45 + 0.75)/2.0, decimal=16) diff --git a/test/test_sync_filter.py b/test/test_sync_filter.py new file mode 100644 index 0000000..e259903 --- /dev/null +++ b/test/test_sync_filter.py @@ -0,0 +1,95 @@ +""" test_sync_filter.py + +Tests the spike sync based filtering + +Copyright 2015, Mario Mulansky + +Distributed under the BSD License + +""" + +from __future__ import print_function +import numpy as np +from numpy.testing import assert_equal, assert_almost_equal, \ + assert_array_almost_equal + +import pyspike as spk +from pyspike import SpikeTrain + + +def test_single_prof(): + st1 = np.array([1.0, 2.0, 3.0, 4.0]) + st2 = np.array([1.1, 2.1, 3.8]) + st3 = np.array([0.9, 3.1, 4.1]) + + # cython implementation + try: + from pyspike.cython.cython_profiles import \ + coincidence_single_profile_cython as coincidence_impl + except ImportError: + from pyspike.cython.python_backend import \ + coincidence_single_python as coincidence_impl + + sync_prof = spk.spike_sync_profile(SpikeTrain(st1, 5.0), + SpikeTrain(st2, 5.0)) + + coincidences = np.array(coincidence_impl(st1, st2, 0, 5.0, 0.0)) + print(coincidences) + for i, t in enumerate(st1): + assert_equal(coincidences[i], sync_prof.y[sync_prof.x == t], + "At index %d" % i) + + coincidences = np.array(coincidence_impl(st2, st1, 0, 5.0, 0.0)) + for i, t in enumerate(st2): + assert_equal(coincidences[i], sync_prof.y[sync_prof.x == t], + "At index %d" % i) + + sync_prof = spk.spike_sync_profile(SpikeTrain(st1, 5.0), + SpikeTrain(st3, 5.0)) + + coincidences = np.array(coincidence_impl(st1, st3, 0, 5.0, 0.0)) + for i, t in enumerate(st1): + assert_equal(coincidences[i], sync_prof.y[sync_prof.x == t], + "At index %d" % i) + + st1 = np.array([1.0, 2.0, 3.0, 4.0]) + st2 = np.array([1.0, 2.0, 4.0]) + + sync_prof = spk.spike_sync_profile(SpikeTrain(st1, 5.0), + SpikeTrain(st2, 5.0)) + + coincidences = np.array(coincidence_impl(st1, st2, 0, 5.0, 0.0)) + for i, t in enumerate(st1): + expected = sync_prof.y[sync_prof.x == t]/sync_prof.mp[sync_prof.x == t] + assert_equal(coincidences[i], expected, + "At index %d" % i) + + +def test_filter(): + st1 = SpikeTrain(np.array([1.0, 2.0, 3.0, 4.0]), 5.0) + st2 = SpikeTrain(np.array([1.1, 2.1, 3.8]), 5.0) + st3 = SpikeTrain(np.array([0.9, 3.1, 4.1]), 5.0) + + # filtered_spike_trains = spk.filter_by_spike_sync([st1, st2], 0.5) + + # assert_equal(filtered_spike_trains[0].spikes, [1.0, 2.0, 4.0]) + # assert_equal(filtered_spike_trains[1].spikes, [1.1, 2.1, 3.8]) + + # filtered_spike_trains = spk.filter_by_spike_sync([st2, st1], 0.5) + + # assert_equal(filtered_spike_trains[0].spikes, [1.1, 2.1, 3.8]) + # assert_equal(filtered_spike_trains[1].spikes, [1.0, 2.0, 4.0]) + + filtered_spike_trains = spk.filter_by_spike_sync([st1, st2, st3], 0.75) + + for st in filtered_spike_trains: + print(st.spikes) + + assert_equal(filtered_spike_trains[0].spikes, [1.0, 4.0]) + assert_equal(filtered_spike_trains[1].spikes, [1.1, 3.8]) + assert_equal(filtered_spike_trains[2].spikes, [0.9, 4.1]) + + +if __name__ == "main": + test_single_prof() + test_filter() -- cgit v1.2.3