import numpy as npimport iqplotimport bokeh.iobokeh.io.output_notebook()
Loading BokehJS ...
We have previously modeled spiking using a noisy leaky integrate-and-fire model. We saw that in some limits, the noisy LIF model gives Poissonian spiking such that the interspike intervals are Exponentially distributed and the number of spikes in a given time interval are Poisson distributed. Poisson process-based models of spiking are widely used to good effect.
However, though well-modelled as Poisson, the rate of spiking can vary over time in what we call a nonhomogeneous Poisson process. Here, we derive the PDF for ISIs in this context and show how to sample ISIs.
16.1 The PDF for arrival times of a nonhomogenous Poisson process
Consider the waiting time for an arrival of a Poisson process that arrives at rate \(\beta\). We know that the waiting time is Exponentially distributed.
The probability that a Poisson process has not arrived at or before time \(t\) is given by the complementary cumulative distribution function (CCDF) of the Exponential distribution.
\[\begin{align}
P(\text{not arrived by time }t) = 1 - P(\text{arrived by time }t) = 1 - \int_0^t\mathrm{d}t'\,f(t\mid\beta) = 1 - (1 - \mathrm{e}^{-\beta t}) = \mathrm{e}^{-\beta t}.
\end{align}
\tag{16.2}\]
Now, consider the case where the arrival rate varies with time; \(\beta = \beta(t)\). Consider some small interval, \([0, \Delta t]\). The probability that no Poisson process has arrived in time \(\Delta t\) is
\[\begin{align}
P(\text{not arrived by time } \Delta t) \approx \mathrm{e}^{-\beta(\Delta t) \Delta t},
\end{align}
\tag{16.3}\]
where \(\Delta t\) is small so that \(\beta(t)\) does not change appreciably over the time interval and \(\beta(t) \approx \beta(t+\Delta t)\). We will soon take the \(\Delta t\to 0\) limit, so this approximation is justified.
Now consider another interval of length \(\Delta t\) right after the one we just considered. The probability that no process arrives in that interval is
\[\begin{align}
P(\text{not arrived in time window } [\Delta t, 2\Delta t]) \approx \mathrm{e}^{-\beta(2\Delta t) \Delta t}.
\end{align}
\tag{16.4}\]
Therefore, owing to the memorylessness of Poisson processes, the probability that no processes arrived in the interval \([0, 2\Delta t]\) is the product of the probabilities of not arriving in the respective subintervals,
\[\begin{align}
P(\text{not arrived in time } 2\Delta t) \approx \mathrm{e}^{-\beta(\Delta t) \Delta t}\,\mathrm{e}^{-\beta(2\Delta t) \Delta t}.
\end{align}
\tag{16.5}\]
We can consider \(m\) such intervals.
\[\begin{align}
P(\text{not arrived between in time } m\Delta t) \approx \prod_{k=1}^m \mathrm{e}^{-\beta(k\Delta t) \Delta t} = \exp\left[-\sum_{k=1}^m \Delta t \,\beta(k\Delta t)\right].
\end{align}
\tag{16.6}\]
In the limit of \(\Delta t\to 0\), the Riemann sum becomes an integral. Taking \(t = m\Delta t\), we have
\[\begin{align}
P(\text{not arrived by time } t) = \exp\left[- \int_0^t\mathrm{d}t'\,\beta(t')\right].
\end{align}
\tag{16.7}\]
This is the CCDF of the distribution desribing the arrival of a Poisson process with variable rate \(\beta(t)\). The CDF is then \(1 - \text{CCDF}\),
Say we observe arrivals of a Poisson process one after another. If we have \(n\) arrivals of a Poisson process with a variable rate occuring at times \(\mathbf{t} = \{t_1, t_2, \ldots, t_n\}\), where the times are ordered, the PDF is given by the product of the PDFs for each waiting time.
where \(t_0\) is when we started observing (it is not a time of an arrival of a Poisson process), and we take \(t_0 = 0\). The integrals add together, giving
In practice, we start observing at time \(t = 0\) and end at time \(t = T\). While \(t_n\) is the time of the last Poisson process we saw arrive, we should also take into account that we continued watching for time \(T - t_n\) and saw no arrivals during that time. We therefore have
In this case, the PDF is not dependent on the times, but only on the number of arrivals in the time interval. We could have derived by noting that the interrarrival times are Exponentially distributed.
Consider now a time interval from \(0\) to \(T\). We break this time interval into \(N\) small intervals, all of which are small enough such that, as before, \(\beta(t)\) does not change appreciably across a small interval and furthermore that the probability of more than one spike landing in a given small interval is zero. Let \(\Delta t_i = t_{i+1}-t_i\) be the width of small interval \(i\). For reasons that will become clear momentarily, we choose the widths of the small intervals such that the small interval width times the rate is the same for all intervals (which we will define as \(\xi\) for convenience), or
\[\begin{align}
\beta(t_{i+1}) \,\Delta t_i = \xi \text{ for all }i.
\end{align}
\tag{16.15}\]
Since every \(\beta(t_{i+1}) \,\Delta t_i\) is the same, we can write
where we have considered small \(\Delta t_i\) and evaluated the Riemann sum, which is valid for unequal intervals provided the intervals are small enough. For convenience, we have defined
The probability of a spike not arriving in a small interval spanning from \(t_i\) to \(t_{i+1}\), as we have already worked out, is
\[\begin{align}
P(\text{not arrived between } t_i \text{ and } t_{i+1}) \approx \mathrm{e}^{-\beta(t_{i+1})\,\Delta t_i} = \mathrm{e}^{-\xi},
\end{align}
\tag{16.18}\]
such that the probability a spike does arrive in that interval is
\[\begin{align}
P(\text{arrived between } t_i \text{ and } t_{i+1}) \approx 1 - \mathrm{e}^{-\beta(t_{i+1}) \Delta t_i} = 1-\mathrm{e}^{-\xi}.
\end{align}
\tag{16.19}\]
Again owing the memorylessness of spiking, the probability of getting a spike in a given small interval constitutes a Bernoulli trial with probability \(\theta\) of success. Then, the probability of getting \(n\) spikes in the interval \([0, T]\) is Binomially distributed,
giving the PMF for the number of arrivals of a nonhomogenous Poisson process in an interval of length \(T\).
16.3 Sampling arrval times of a nonhomogeneous Poisson process
We can easily sample arrival times of a homogeneous Poisson process by noting that the interarrival times are Exponentially distributed. We keep drawing interarrival times from an Exponential distribution and cumulatively sum them to get the arrival times.
rng = np.random.default_rng()def sample_homogeneous_poisson_process(beta, T):"""Draw samples of arrival times of a homogeneous Poisson process."""# Intialize arrival times arrival_times = []# Typical arrival time (Numpy likes this) tau =1/ beta# Draw interarrival times and keep adding t = rng.exponential(tau)while t < T: arrival_times.append(t) t += rng.exponential(tau)return np.array(arrival_times)# Give it a whirlsample_homogeneous_poisson_process(1, 20)
However, sampling arrival times of a nonhomogeneous Poisson is more challenging. The arrival time varies, so we cannot directly draw out of an Exponential distribution. To perform the sampling, we can use a thinning method. This works as follows. We set a fast rate \(\beta_u\) that is greater than \(\beta(t)\) everywhere on the interval \([0, T]\). We draw a time \(t_1\) out of an Exponential distribution with rate \(\beta_u\). We accept \(t_1\) as a sample out of the nonhomogeneous distribution with probability \(\beta(t_1)/\beta_u\). We then draw an interarrival time \(\delta t_2\) from an Exponential distribution parametrized with rate \(\beta_u\). We accept \(t_1 + \delta t_2\) as a draw from the nonhomogeneous distribution with probability \(\beta(t_1 + \delta t_2) / \beta_u\). We continue in this manner until the total time of the draws meets or exceeds \(T\). This is implemented in the function below.
def sample_nhpp(beta, beta_u, T, beta_args=()):"""Draw arrival times of a nonhomogeneous Poisson process. Parameters ---------- beta : function, call signature beta(t, *beta_args) The function of the rate of arrivals as a function of time. beta_u : float A value of beta that is greater than beta(t) for all time. T : float The maximum time of observation. beta_args : tuple, default () Any other arguments passed to the function `beta()`. Returns ------- output : Numpy array Times for arrivals of the nonhomogeneous Poisson process. Notes ----- .. This is an implementation of the algorithm on page 85 of Simulation, 5th Ed., by Sheldon Ross, Academic Press, 2013. """ samples = [] tau =1/ beta_u t = rng.exponential(tau)while t < T: r = beta(t, *beta_args) * tauif r >1:raiseRuntimeError('beta_u is less than beta; sampling invalid.')if np.random.uniform() <= r: samples.append(t) t += rng.exponential(tau)return np.array(samples)
Let’s take this for a spin. We will have \(\beta(t)\) given by a sinusoid.
If we want to speed things up, we can break the interval up into smaller segments and for each interval we make sure that we choose a \(\beta_u\) specific to that interval that is close to the maximum \(\beta(t)\) on that interval. The code (which also includes other speed enhancements) to achieve this is below.
def sample_nhpp(beta, beta_j, t_j, beta_args=()):"""Draw arrival times of a nonhomogeneous Poisson process. Parameters ---------- beta : function, call signature beta(t, *beta_args) The function of the rate of arrivals as a function of time. beta_j : scalar or array_like If scalar, a value of beta that is greater than beta(t) for all time. If array_like, then beta_j[j] > beta(t) for all times between t_j[j-1] and t_j[j]. t_j : scalar or array_like If scalar, the maximum time of observation. If array_like, must be the same length of `beta_j`. beta_j[j] is the value of the the upper bound of the rate for the interval between t[j-1] and t[j]. beta_args : tuple, default () Any other arguments passed to beta. Returns ------- output : Numpy array Times for arrivals of the nonhomogeneous Poisson process. Notes ----- .. This is an implementation of the algorithm on page 86 of Simulation, 5th Ed., by Sheldon Ross, Academic Press, 2013. """# Convert scalar inputs to arraysif np.isscalar(beta_j): beta_j = np.array([beta_j])if np.isscalar(t_j): t_j = np.array([t_j])# Make sure dimensions matchiflen(beta_j) !=len(t_j):raiseRuntimeError(f'`beta_j` is length {len(beta_j)} 'f'and `t_j` is length {len(t_j)}. ''They must have the same length.' )return _sample_nhpp(beta, beta_j, t_j, beta_args)def _sample_nhpp(beta, beta_j, t_j, beta_args=()):""" Parameters ---------- beta : function, call signature beta(t, *beta_args) The function of the rate of arrivals as a function of time. beta_j : Numpy array If scalar, a value of beta that is greater than beta(t) for all time. If array_like, then beta_j[j] > beta(t) for all times between t_j[j-1] and t_j[j]. t_j : Numpy array Must be the same length of `beta_j`. beta_j[j] is the value of the the upper bound of the rate for the interval between t[j-1] and t[j]. beta_args : tuple, default () Any other arguments passed to beta. Returns ------- output : Numpy array Times for arrivals of the nonhomogeneous Poisson process. Notes ----- .. This is an implementation of the algorithm on page 86 of Simulation, 5th Ed., by Sheldon Ross, Academic Press, 2013. """# Number of samples to take before concatenating arrays n_samples =1000# Initializations t =0.0# time j =0# Index in beta_j and t_j arrays i =0# index in sample array n =0# total number of samples drawn x_from_boundary =False# If we've hit a boundary of# Array for storing subtrajectory samples = np.empty(n_samples, dtype=float)# Output array for all samples samples_output = np.array([], dtype=float)# Loop until done (we exceed final time point) not_done =Truewhile not_done:# Take samples until we fill array# We do it this way for speed to avoid list append operationswhile not_done and i < n_samples:if x_from_boundary: x = (x - t_j[j] + t) * beta_j[j] / beta_j[j+1] t = t_j[j] j +=1else: x = np.random.exponential(1/ beta_j[j])if t + x > t_j[j]:# If we got here, we went past the edge of this intervalif j ==len(t_j) -1: not_done =Falseelse: x_from_boundary =Trueelse: t += x x_from_bounday =Falseif np.random.uniform() <= beta(t, *beta_args) / beta_j[j]: samples[i] = t i +=1 n +=1 samples_output = np.concatenate((samples_output, samples)) i =0return np.array(samples_output[:n])