def test_triangular(self): def evaluate_old(t): t_units = t.units t_abs = np.abs(t.magnitude) tau = math.sqrt(6) * kernel.sigma.rescale(t_units).magnitude kernel_pdf = (t_abs < tau) * 1 / tau * (1 - t_abs / tau) kernel_pdf = pq.Quantity(kernel_pdf, units=1 / t_units) return kernel_pdf for invert in (False, True): kernel = kernels.TriangularKernel(self.sigma, invert=invert) assert_array_almost_equal(kernel(self.time_input), evaluate_old(self.time_input))
def setUp(self): # create a poisson spike train: self.st_tr = (0, 20.0) # seconds self.st_dur = self.st_tr[1] - self.st_tr[0] # seconds self.st_margin = 5.0 # seconds self.st_rate = 10.0 # Hertz st_num_spikes = np.random.poisson(self.st_rate*(self.st_dur-2*self.st_margin)) spike_train = np.random.rand(st_num_spikes) * (self.st_dur-2*self.st_margin) + self.st_margin spike_train.sort() # convert spike train into neo objects self.spike_train = neo.SpikeTrain(spike_train*pq.s, t_start=self.st_tr[0]*pq.s, t_stop=self.st_tr[1]*pq.s) # generation of a multiply used specific kernel self.kernel = kernels.TriangularKernel(sigma = 0.03*pq.s)
def test_wrong_input(self): self.assertRaises(TypeError, stds.victor_purpura_distance, [self.array1, self.array2], self.q3) self.assertRaises(TypeError, stds.victor_purpura_distance, [self.qarray1, self.qarray2], self.q3) self.assertRaises(TypeError, stds.victor_purpura_distance, [self.qarray1, self.qarray2], 5.0 * ms) self.assertRaises(TypeError, stds.victor_purpura_distance, [self.array1, self.array2], self.q3, algorithm='intuitive') self.assertRaises(TypeError, stds.victor_purpura_distance, [self.qarray1, self.qarray2], self.q3, algorithm='intuitive') self.assertRaises(TypeError, stds.victor_purpura_distance, [self.qarray1, self.qarray2], 5.0 * ms, algorithm='intuitive') self.assertRaises(TypeError, stds.van_rossum_distance, [self.array1, self.array2], self.tau3) self.assertRaises(TypeError, stds.van_rossum_distance, [self.qarray1, self.qarray2], self.tau3) self.assertRaises(TypeError, stds.van_rossum_distance, [self.qarray1, self.qarray2], 5.0 * Hz) self.assertRaises(TypeError, stds.victor_purpura_distance, [self.st11, self.st13], self.tau2) self.assertRaises(TypeError, stds.victor_purpura_distance, [self.st11, self.st13], 5.0) self.assertRaises(TypeError, stds.victor_purpura_distance, [self.st11, self.st13], self.tau2, algorithm='intuitive') self.assertRaises(TypeError, stds.victor_purpura_distance, [self.st11, self.st13], 5.0, algorithm='intuitive') self.assertRaises(TypeError, stds.van_rossum_distance, [self.st11, self.st13], self.q4) self.assertRaises(TypeError, stds.van_rossum_distance, [self.st11, self.st13], 5.0) self.assertRaises(NotImplementedError, stds.victor_purpura_distance, [self.st01, self.st02], self.q3, kernel=kernels.Kernel(2.0 / self.q3)) self.assertRaises(NotImplementedError, stds.victor_purpura_distance, [self.st01, self.st02], self.q3, kernel=kernels.SymmetricKernel(2.0 / self.q3)) self.assertEqual( stds.victor_purpura_distance( [self.st01, self.st02], self.q1, kernel=kernels.TriangularKernel( 2.0 / (np.sqrt(6.0) * self.q2)))[0, 1], stds.victor_purpura_distance( [self.st01, self.st02], self.q3, kernel=kernels.TriangularKernel( 2.0 / (np.sqrt(6.0) * self.q2)))[0, 1]) self.assertEqual( stds.victor_purpura_distance( [self.st01, self.st02], kernel=kernels.TriangularKernel( 2.0 / (np.sqrt(6.0) * self.q2)))[0, 1], 1.0) self.assertNotEqual( stds.victor_purpura_distance( [self.st01, self.st02], kernel=kernels.AlphaKernel(2.0 / (np.sqrt(6.0) * self.q2)))[0, 1], 1.0) self.assertRaises(NameError, stds.victor_purpura_distance, [self.st11, self.st13], self.q2, algorithm='slow')
def victor_purpura_distance(spiketrains, cost_factor=1.0 * pq.Hz, kernel=None, sort=True, algorithm='fast'): """ Calculates the Victor-Purpura's (VP) distance. It is often denoted as :math:`D^{\\text{spike}}[q]`. It is defined as the minimal cost of transforming spike train `a` into spike train `b` by using the following operations: * Inserting or deleting a spike (cost 1.0). * Shifting a spike from :math:`t` to :math:`t'` (cost :math:`q \\cdot |t - t'|`). A detailed description can be found in *Victor, J. D., & Purpura, K. P. (1996). Nature and precision of temporal coding in visual cortex: a metric-space analysis. Journal of Neurophysiology.* Given the average number of spikes :math:`n` in a spike train and :math:`N` spike trains the run-time complexity of this function is :math:`O(N^2 n^2)` and :math:`O(N^2 + n^2)` memory will be needed. Parameters ---------- spiketrains : list of neo.SpikeTrain Spike trains to calculate pairwise distance. cost_factor : pq.Quantity, optional A cost factor :math:`q` for spike shifts as inverse time scalar. Extreme values :math:`q=0` meaning no cost for any shift of spikes, or :math: `q=np.inf` meaning infinite cost for any spike shift and hence exclusion of spike shifts, are explicitly allowed. If `kernel` is not `None`, :math:`q` will be ignored. Default: 1.0 * pq.Hz kernel : elephant.kernels.Kernel or None, optional Kernel to use in the calculation of the distance. If `kernel` is `None`, an unnormalized triangular kernel with standard deviation of :math:'2.0/(q * sqrt(6.0))' corresponding to a half width of :math:`2.0/q` will be used. Usage of the default value calculates the Victor-Purpura distance correctly with a triangular kernel of the suitable width. The choice of another kernel is enabled, but this leaves the framework of Victor-Purpura distances. Default: None sort : bool, optional Spike trains with sorted spike times will be needed for the calculation. You can set `sort` to `False` if you know that your spike trains are already sorted to decrease calculation time. Default: True algorithm : str, optional Allowed values are 'fast' or 'intuitive', each selecting an algorithm with which to calculate the pairwise Victor-Purpura distance. Typically 'fast' should be used, because while giving always the same result as 'intuitive', within the temporary structure of Python and add-on modules as numpy it is faster. Default: 'fast' Returns ------- np.ndarray 2-D Matrix containing the VP distance of all pairs of spike trains. Examples -------- >>> import quantities as pq >>> from elephant.spike_train_dissimilarity import victor_purpura_distance >>> q = 1.0 / (10.0 * pq.ms) >>> st_a = SpikeTrain([10, 20, 30], units='ms', t_stop= 1000.0) >>> st_b = SpikeTrain([12, 24, 30], units='ms', t_stop= 1000.0) >>> vp_f = victor_purpura_distance([st_a, st_b], q)[0, 1] >>> vp_i = victor_purpura_distance([st_a, st_b], q, ... algorithm='intuitive')[0, 1] """ for train in spiketrains: if not (isinstance(train, (pq.quantity.Quantity, SpikeTrain)) and train.dimensionality.simplified == pq.Quantity( 1, "s").dimensionality.simplified): raise TypeError("Spike trains must have a time unit.") if not (isinstance(cost_factor, pq.quantity.Quantity) and cost_factor.dimensionality.simplified == pq.Quantity( 1, "Hz").dimensionality.simplified): raise TypeError("cost_factor must be a rate quantity.") if kernel is None: if cost_factor == 0.0: num_spikes = np.atleast_2d([st.size for st in spiketrains]) return np.absolute(num_spikes.T - num_spikes) if cost_factor == np.inf: num_spikes = np.atleast_2d([st.size for st in spiketrains]) return num_spikes.T + num_spikes kernel = kernels.TriangularKernel(sigma=2.0 / (np.sqrt(6.0) * cost_factor)) if sort: spiketrains = [ np.sort(st.view(type=pq.Quantity)) for st in spiketrains ] def compute(i, j): if i == j: return 0.0 if algorithm == 'fast': return _victor_purpura_dist_for_st_pair_fast( spiketrains[i], spiketrains[j], kernel) if algorithm == 'intuitive': return _victor_purpura_dist_for_st_pair_intuitive( spiketrains[i], spiketrains[j], cost_factor) raise NameError("The algorithm must be either 'fast' or 'intuitive'.") return _create_matrix_from_indexed_function( (len(spiketrains), len(spiketrains)), compute, kernel.is_symmetric())