braintools.analysis.spike.spike_distance#
- braintools.analysis.spike.spike_distance(a, b, t_start=None, t_stop=None)#
Compute the SPIKE-distance between two populations, neuron by neuron.
The SPIKE-distance of Kreuz et al. [1] compares spike timing. For a train \(x\) at time \(t\), let \(t_P^x \le t < t_F^x\) be the previous and following spikes, \(x_{ISI} = t_F^x - t_P^x\), and \(\Delta t_P^x\), \(\Delta t_F^x\) the distances of those spikes to the nearest spike of the other train. The local spike-time difference is interpolated between them,
\[S_x(t) = \frac{\Delta t_P^x (t_F^x - t) + \Delta t_F^x (t - t_P^x)}{x_{ISI}},\]and the profile weights both trains by their local ISIs,
\[S(t) = \frac{S_a(t)\, b_{ISI} + S_b(t)\, a_{ISI}} {2 \langle x_{ISI} \rangle^2}, \qquad \langle x_{ISI} \rangle = \frac{a_{ISI} + b_{ISI}}{2}.\]The distance is the time average of \(S(t)\) over the recording. It is 0 for identical trains and at most 1.
- Parameters:
a (
SpikeTrains) – The two populations, with the same number of neuronsN(their capacities may differ). Neuroniofais compared with neuroniofb. Both must carry compatible time units, or both none.b (
SpikeTrains) – The two populations, with the same number of neuronsN(their capacities may differ). Neuroniofais compared with neuroniofb. Both must carry compatible time units, or both none.t_start (
Any|None) – Averaging interval. A Quantity is converted to the unit of the spike times; a plain number is in milliseconds (thebrainstateconvention), so it is used as is for unitless trains and converted from milliseconds for trains that carry units. Defaults tomin(a.t_start, b.t_start)andmax(a.t_stop, b.t_stop), as PySpike does. Spikes outside the interval are ignored.t_stop (
Any|None) – Averaging interval. A Quantity is converted to the unit of the spike times; a plain number is in milliseconds (thebrainstateconvention), so it is used as is for unitless trains and converted from milliseconds for trains that carry units. Defaults tomin(a.t_start, b.t_start)andmax(a.t_stop, b.t_stop), as PySpike does. Spikes outside the interval are ignored.
- Returns:
SPIKE-distances, shape
(N,), dimensionless.nanwhent_stop <= t_start.- Return type:
Array- Raises:
TypeError – If
aorbis not aSpikeTrains, if only one carries units, or ift_start/t_stopis a Quantity that is not a time.ValueError – If the numbers of neurons differ, or a
SpikeTrainsis batched.
See also
isi_distanceThe ISI-distance, sensitive to local firing rates.
van_rossum_distanceKernel-based distance between two spike trains.
braintools.spike.SpikeTrainsThe padded spike-time container.
Notes
\(S(t)\) is linear on every interval between the merged spike times of the two trains, so the time average is integrated exactly with the trapezoid rule over those intervals.
The edge conventions are those of PySpike 0.9 [3], which follow Kreuz et al. [2], so the values agree with
pyspike.spike_distanceto round-off:auxiliary spikes are placed at
min(t_start, s_0 - (s_1 - s_0))andmax(t_stop, s_{n-1} + (s_{n-1} - s_{n-2}))(att_startandt_stopfor a single spike); they serve as the previous/following spikes at the edges and as candidates for the nearest spike of the other train;before the first and after the last spike, \(S_x\) is held at the distance of that first or last spike, and the edge ISIs are those of
isi_distance();a train whose only spike is at
t_startusest_stopas its following spike;an empty train is replaced by spikes at
t_startandt_stop; repeated spike times within a train count once.
One deviation: PySpike returns
nanwhen both trains have a single spike exactly att_stop; here the distance is 0.The function works under
jax.jitandjax.vmap(over trials, or over neurons to build a pair matrix, see the examples). Gradients with respect to the spike times are not supported (the nearest-neighbour search is piecewise). Memory and time areO(N (K_a + K_b) log(K_a + K_b)).References
Examples
>>> import jax >>> import jax.numpy as jnp >>> import numpy as np >>> from braintools.spike import SpikeTrains >>> from braintools.analysis.spike import spike_distance >>> a = SpikeTrains.from_list([[1., 5., 9.], [3.]], t_start=0., t_stop=10.) >>> b = SpikeTrains.from_list([[1., 5., 9.], [6.]], t_start=0., t_stop=10.) >>> np.round(np.asarray(spike_distance(a, b)), 4) array([0. , 0.5566], dtype=float32) >>> >>> # From a dense raster (time on axis 0), with units. >>> import brainunit as u >>> raster = jnp.zeros((100, 2)).at[::10, 0].set(1.).at[5::10, 1].set(1.) >>> st = SpikeTrains.from_dense(raster, dt=1. * u.ms) >>> d = spike_distance(st.select(0), st.select(1)) >>> d.shape (1,) >>> >>> # All pairs of neurons of one population: vmap over the first neuron. >>> n = st.n_neurons >>> pair = jax.vmap(lambda i: spike_distance(st.select(jnp.full((n,), i)), st)) >>> pair(jnp.arange(n)).shape (2, 2)