kernels.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. # -*- coding: utf-8 -*-
  2. """
  3. Definition of a hierarchy of classes for kernel functions to be used
  4. in convolution, e.g., for data smoothing (low pass filtering) or
  5. firing rate estimation.
  6. Symmetric kernels
  7. ~~~~~~~~~~~~~~~~~
  8. .. autosummary::
  9. :toctree: toctree/kernels/
  10. RectangularKernel
  11. TriangularKernel
  12. EpanechnikovLikeKernel
  13. GaussianKernel
  14. LaplacianKernel
  15. Asymmetric kernels
  16. ~~~~~~~~~~~~~~~~~~
  17. .. autosummary::
  18. :toctree: toctree/kernels/
  19. ExponentialKernel
  20. AlphaKernel
  21. Examples
  22. --------
  23. >>> import quantities as pq
  24. >>> kernel1 = GaussianKernel(sigma=100*pq.ms)
  25. >>> kernel2 = ExponentialKernel(sigma=8*pq.ms, invert=True)
  26. :copyright: Copyright 2016 by the Elephant team, see `doc/authors.rst`.
  27. :license: Modified BSD, see LICENSE.txt for details.
  28. """
  29. from __future__ import division, print_function, unicode_literals
  30. import math
  31. import numpy as np
  32. import quantities as pq
  33. import scipy.optimize
  34. import scipy.special
  35. import scipy.stats
  36. from elephant.utils import deprecated_alias
  37. __all__ = [
  38. 'RectangularKernel', 'TriangularKernel', 'EpanechnikovLikeKernel',
  39. 'GaussianKernel', 'LaplacianKernel', 'ExponentialKernel', 'AlphaKernel'
  40. ]
  41. class Kernel(object):
  42. r"""
  43. This is the base class for commonly used kernels.
  44. **General definition of a kernel:**
  45. A function :math:`K(x, y)` is called a kernel function if
  46. :math:`\int{K(x, y) g(x) g(y) \textrm{d}x \textrm{d}y} \ \geq 0 \quad
  47. \forall g \in L_2`
  48. **Currently implemented kernels are:**
  49. * rectangular
  50. * triangular
  51. * epanechnikovlike
  52. * gaussian
  53. * laplacian
  54. * exponential (asymmetric)
  55. * alpha function (asymmetric)
  56. In neuroscience, a popular application of kernels is in performing
  57. smoothing operations via convolution. In this case, the kernel has the
  58. properties of a probability density, i.e., it is positive and normalized
  59. to one. Popular choices are the rectangular or Gaussian kernels.
  60. Exponential and alpha kernels may also be used to represent the
  61. postsynaptic current/potentials in a linear (current-based) model.
  62. Parameters
  63. ----------
  64. sigma : pq.Quantity
  65. Standard deviation of the kernel.
  66. invert : bool, optional
  67. If True, asymmetric kernels (e.g., exponential or alpha kernels) are
  68. inverted along the time axis.
  69. Default: False.
  70. Raises
  71. ------
  72. TypeError
  73. If `sigma` is not `pq.Quantity`.
  74. If `sigma` is negative.
  75. If `invert` is not `bool`.
  76. """
  77. def __init__(self, sigma, invert=False):
  78. if not isinstance(sigma, pq.Quantity):
  79. raise TypeError("'sigma' must be a quantity")
  80. if sigma.magnitude < 0:
  81. raise ValueError("'sigma' cannot be negative")
  82. if not isinstance(invert, bool):
  83. raise ValueError("'invert' must be bool")
  84. self.sigma = sigma
  85. self.invert = invert
  86. def __repr__(self):
  87. return "{cls}(sigma={sigma}, invert={invert})".format(
  88. cls=self.__class__.__name__, sigma=self.sigma, invert=self.invert)
  89. @deprecated_alias(t='times')
  90. def __call__(self, times):
  91. """
  92. Evaluates the kernel at all points in the array `times`.
  93. Parameters
  94. ----------
  95. times : pq.Quantity
  96. A vector with time intervals on which the kernel is evaluated.
  97. Returns
  98. -------
  99. pq.Quantity
  100. Vector with the result of the kernel evaluations.
  101. Raises
  102. ------
  103. TypeError
  104. If `times` is not `pq.Quantity`.
  105. If the dimensionality of `times` and :attr:`sigma` are different.
  106. """
  107. self._check_time_input(times)
  108. return self._evaluate(times)
  109. def _evaluate(self, times):
  110. """
  111. Evaluates the kernel Probability Density Function, PDF.
  112. Parameters
  113. ----------
  114. times : pq.Quantity
  115. Vector with the interval on which the kernel is evaluated, not
  116. necessarily a time interval.
  117. Returns
  118. -------
  119. pq.Quantity
  120. Vector with the result of the kernel evaluation.
  121. """
  122. raise NotImplementedError(
  123. "The Kernel class should not be used directly, "
  124. "instead the subclasses for the single kernels.")
  125. def boundary_enclosing_area_fraction(self, fraction):
  126. """
  127. Calculates the boundary :math:`b` so that the integral from
  128. :math:`-b` to :math:`b` encloses a certain fraction of the
  129. integral over the complete kernel.
  130. By definition the returned value is hence non-negative, even if the
  131. whole probability mass of the kernel is concentrated over negative
  132. support for inverted kernels.
  133. Parameters
  134. ----------
  135. fraction : float
  136. Fraction of the whole area which has to be enclosed.
  137. Returns
  138. -------
  139. pq.Quantity
  140. Boundary of the kernel containing area `fraction` under the
  141. kernel density.
  142. Raises
  143. ------
  144. ValueError
  145. If `fraction` was chosen too close to one, such that in
  146. combination with integral approximation errors the calculation of
  147. a boundary was not possible.
  148. """
  149. raise NotImplementedError(
  150. "The Kernel class should not be used directly, "
  151. "instead the subclasses for the single kernels.")
  152. def _check_fraction(self, fraction):
  153. """
  154. Checks the input variable of the method
  155. :attr:`boundary_enclosing_area_fraction` for validity of type and
  156. value.
  157. Parameters
  158. ----------
  159. fraction : float or int
  160. Fraction of the area under the kernel function.
  161. Raises
  162. ------
  163. TypeError
  164. If `fraction` is neither a float nor an int.
  165. If `fraction` is not in the interval [0, 1).
  166. """
  167. if not isinstance(fraction, (float, int)):
  168. raise TypeError("`fraction` must be float or integer")
  169. if isinstance(self, (TriangularKernel, RectangularKernel)):
  170. valid = 0 <= fraction <= 1
  171. bracket = ']'
  172. else:
  173. valid = 0 <= fraction < 1
  174. bracket = ')'
  175. if not valid:
  176. raise ValueError("`fraction` must be in the interval "
  177. "[0, 1{}".format(bracket))
  178. def _check_time_input(self, t):
  179. if not isinstance(t, pq.Quantity):
  180. raise TypeError("The argument 't' of the kernel callable must be "
  181. "of type Quantity")
  182. if t.dimensionality.simplified != self.sigma.dimensionality.simplified:
  183. raise TypeError("The dimensionality of sigma and the input array "
  184. "to the callable kernel object must be the same. "
  185. "Otherwise a normalization to 1 of the kernel "
  186. "cannot be performed.")
  187. @deprecated_alias(t='time')
  188. def cdf(self, time):
  189. r"""
  190. Cumulative Distribution Function, CDF.
  191. Parameters
  192. ----------
  193. time : pq.Quantity
  194. The input time scalar.
  195. Returns
  196. -------
  197. float
  198. CDF at `time`.
  199. """
  200. raise NotImplementedError
  201. def icdf(self, fraction):
  202. r"""
  203. Inverse Cumulative Distribution Function, ICDF, also known as a
  204. quantile.
  205. Parameters
  206. ----------
  207. fraction : float
  208. The fraction of CDF to compute the quantile from.
  209. Returns
  210. -------
  211. pq.Quantity
  212. The time scalar `times` such that `CDF(t) = fraction`.
  213. """
  214. raise NotImplementedError
  215. @deprecated_alias(t='times')
  216. def median_index(self, times):
  217. r"""
  218. Estimates the index of the Median of the kernel.
  219. We define the Median index :math:`i` of a kernel as:
  220. .. math::
  221. t_i = \text{ICDF}\left( \frac{\text{CDF}(t_0) +
  222. \text{CDF}(t_{N-1})}{2} \right)
  223. where :math:`t_0` and :math:`t_{N-1}` are the first and last entries of
  224. the input array, CDF and ICDF stand for Cumulative Distribution
  225. Function and its Inverse, respectively.
  226. This function is not mandatory for symmetrical kernels but it is
  227. required when asymmetrical kernels have to be aligned at their median.
  228. Parameters
  229. ----------
  230. times : pq.Quantity
  231. Vector with the interval on which the kernel is evaluated.
  232. Returns
  233. -------
  234. int
  235. Index of the estimated value of the kernel median.
  236. Raises
  237. ------
  238. TypeError
  239. If the input array is not a time pq.Quantity array.
  240. ValueError
  241. If the input array is empty.
  242. If the input array is not sorted.
  243. See Also
  244. --------
  245. Kernel.cdf : cumulative distribution function
  246. Kernel.icdf : inverse cumulative distribution function
  247. """
  248. self._check_time_input(times)
  249. if len(times) == 0:
  250. raise ValueError("The input time array is empty.")
  251. if len(times) <= 2:
  252. # either left or right; choose left
  253. return 0
  254. is_sorted = (np.diff(times.magnitude) >= 0).all()
  255. if not is_sorted:
  256. raise ValueError("The input time array must be sorted (in "
  257. "ascending order).")
  258. cdf_mean = 0.5 * (self.cdf(times[0]) + self.cdf(times[-1]))
  259. if cdf_mean == 0.:
  260. # any index of the kernel non-support is valid; choose median
  261. return len(times) // 2
  262. icdf = self.icdf(fraction=cdf_mean)
  263. icdf = icdf.rescale(times.units).magnitude
  264. # icdf is guaranteed to be in (t_start, t_end) interval
  265. median_index = np.nonzero(times.magnitude >= icdf)[0][0]
  266. return median_index
  267. def is_symmetric(self):
  268. r"""
  269. True for symmetric kernels and False otherwise (asymmetric kernels).
  270. A kernel is symmetric if its PDF is symmetric w.r.t. time:
  271. .. math::
  272. \text{pdf}(-t) = \text{pdf}(t)
  273. Returns
  274. -------
  275. bool
  276. Whether the kernels is symmetric or not.
  277. """
  278. return isinstance(self, SymmetricKernel)
  279. @property
  280. def min_cutoff(self):
  281. """
  282. Half width of the kernel.
  283. Returns
  284. -------
  285. float
  286. The returned value varies according to the kernel type.
  287. """
  288. raise NotImplementedError
  289. class SymmetricKernel(Kernel):
  290. """
  291. Base class for symmetric kernels.
  292. """
  293. class RectangularKernel(SymmetricKernel):
  294. r"""
  295. Class for rectangular kernels.
  296. .. math::
  297. K(t) = \left\{\begin{array}{ll} \frac{1}{2 \tau}, & |t| < \tau \\
  298. 0, & |t| \geq \tau \end{array} \right.
  299. with :math:`\tau = \sqrt{3} \sigma` corresponding to the half width
  300. of the kernel.
  301. The parameter `invert` has no effect on symmetric kernels.
  302. Examples
  303. --------
  304. .. plot::
  305. :include-source:
  306. from elephant import kernels
  307. import quantities as pq
  308. import numpy as np
  309. import matplotlib.pyplot as plt
  310. time_array = np.linspace(-3, 3, num=100) * pq.s
  311. kernel = kernels.RectangularKernel(sigma=1*pq.s)
  312. kernel_time = kernel(time_array)
  313. plt.plot(time_array, kernel_time)
  314. plt.title("RectangularKernel with sigma=1s")
  315. plt.xlabel("time, s")
  316. plt.ylabel("kernel, 1/s")
  317. plt.show()
  318. """
  319. @property
  320. def min_cutoff(self):
  321. min_cutoff = np.sqrt(3.0)
  322. return min_cutoff
  323. def _evaluate(self, times):
  324. t_units = times.units
  325. t_abs = np.abs(times.magnitude)
  326. tau = math.sqrt(3) * self.sigma.rescale(t_units).magnitude
  327. kernel = (t_abs < tau) * 1 / (2 * tau)
  328. kernel = pq.Quantity(kernel, units=1 / t_units)
  329. return kernel
  330. @deprecated_alias(t='time')
  331. def cdf(self, time):
  332. self._check_time_input(time)
  333. tau = math.sqrt(3) * self.sigma.rescale(time.units).magnitude
  334. time = np.clip(time.magnitude, a_min=-tau, a_max=tau)
  335. cdf = (time + tau) / (2 * tau)
  336. return cdf
  337. def icdf(self, fraction):
  338. self._check_fraction(fraction)
  339. tau = math.sqrt(3) * self.sigma
  340. icdf = tau * (2 * fraction - 1)
  341. return icdf
  342. def boundary_enclosing_area_fraction(self, fraction):
  343. self._check_fraction(fraction)
  344. return np.sqrt(3.0) * self.sigma * fraction
  345. class TriangularKernel(SymmetricKernel):
  346. r"""
  347. Class for triangular kernels.
  348. .. math::
  349. K(t) = \left\{ \begin{array}{ll} \frac{1}{\tau} (1
  350. - \frac{|t|}{\tau}), & |t| < \tau \\
  351. 0, & |t| \geq \tau \end{array} \right.
  352. with :math:`\tau = \sqrt{6} \sigma` corresponding to the half width of
  353. the kernel.
  354. The parameter `invert` has no effect on symmetric kernels.
  355. Examples
  356. --------
  357. .. plot::
  358. :include-source:
  359. from elephant import kernels
  360. import quantities as pq
  361. import numpy as np
  362. import matplotlib.pyplot as plt
  363. time_array = np.linspace(-3, 3, num=1000) * pq.s
  364. kernel = kernels.TriangularKernel(sigma=1*pq.s)
  365. kernel_time = kernel(time_array)
  366. plt.plot(time_array, kernel_time)
  367. plt.title("TriangularKernel with sigma=1s")
  368. plt.xlabel("time, s")
  369. plt.ylabel("kernel, 1/s")
  370. plt.show()
  371. """
  372. @property
  373. def min_cutoff(self):
  374. min_cutoff = np.sqrt(6.0)
  375. return min_cutoff
  376. def _evaluate(self, times):
  377. tau = math.sqrt(6) * self.sigma.rescale(times.units).magnitude
  378. kernel = scipy.stats.triang.pdf(times.magnitude, c=0.5, loc=-tau,
  379. scale=2 * tau)
  380. kernel = pq.Quantity(kernel, units=1 / times.units)
  381. return kernel
  382. @deprecated_alias(t='time')
  383. def cdf(self, time):
  384. self._check_time_input(time)
  385. tau = math.sqrt(6) * self.sigma.rescale(time.units).magnitude
  386. cdf = scipy.stats.triang.cdf(time.magnitude, c=0.5, loc=-tau,
  387. scale=2 * tau)
  388. return cdf
  389. def icdf(self, fraction):
  390. self._check_fraction(fraction)
  391. tau = math.sqrt(6) * self.sigma.magnitude
  392. icdf = scipy.stats.triang.ppf(fraction, c=0.5, loc=-tau, scale=2 * tau)
  393. return icdf * self.sigma.units
  394. def boundary_enclosing_area_fraction(self, fraction):
  395. self._check_fraction(fraction)
  396. return np.sqrt(6.0) * self.sigma * (1 - np.sqrt(1 - fraction))
  397. class EpanechnikovLikeKernel(SymmetricKernel):
  398. r"""
  399. Class for Epanechnikov-like kernels.
  400. .. math::
  401. K(t) = \left\{\begin{array}{ll} (3 /(4 d)) (1 - (t / d)^2),
  402. & |t| < d \\
  403. 0, & |t| \geq d \end{array} \right.
  404. with :math:`d = \sqrt{5} \sigma` being the half width of the kernel.
  405. The Epanechnikov kernel under full consideration of its axioms has a half
  406. width of :math:`\sqrt{5}`. Ignoring one axiom also the respective kernel
  407. with half width = 1 can be called Epanechnikov kernel [1]_.
  408. However, arbitrary width of this type of kernel is here preferred to be
  409. called 'Epanechnikov-like' kernel.
  410. The parameter `invert` has no effect on symmetric kernels.
  411. References
  412. ----------
  413. .. [1] https://de.wikipedia.org/wiki/Epanechnikov-Kern
  414. Examples
  415. --------
  416. .. plot::
  417. :include-source:
  418. from elephant import kernels
  419. import quantities as pq
  420. import numpy as np
  421. import matplotlib.pyplot as plt
  422. time_array = np.linspace(-3, 3, num=100) * pq.s
  423. kernel = kernels.EpanechnikovLikeKernel(sigma=1*pq.s)
  424. kernel_time = kernel(time_array)
  425. plt.plot(time_array, kernel_time)
  426. plt.title("EpanechnikovLikeKernel with sigma=1s")
  427. plt.xlabel("time, s")
  428. plt.ylabel("kernel, 1/s")
  429. plt.show()
  430. """
  431. @property
  432. def min_cutoff(self):
  433. min_cutoff = np.sqrt(5.0)
  434. return min_cutoff
  435. def _evaluate(self, times):
  436. tau = math.sqrt(5) * self.sigma.rescale(times.units).magnitude
  437. t_div_tau = np.clip(times.magnitude / tau, a_min=-1, a_max=1)
  438. kernel = 3. / (4. * tau) * np.maximum(0., 1 - t_div_tau ** 2)
  439. kernel = pq.Quantity(kernel, units=1 / times.units)
  440. return kernel
  441. @deprecated_alias(t='time')
  442. def cdf(self, time):
  443. self._check_time_input(time)
  444. tau = math.sqrt(5) * self.sigma.rescale(time.units).magnitude
  445. t_div_tau = np.clip(time.magnitude / tau, a_min=-1, a_max=1)
  446. cdf = 3. / 4 * (t_div_tau - t_div_tau ** 3 / 3.) + 0.5
  447. return cdf
  448. def icdf(self, fraction):
  449. self._check_fraction(fraction)
  450. # CDF(t) = -1/4 t^3 + 3/4 t + 1/2
  451. coefs = [-1. / 4, 0, 3. / 4, 0.5 - fraction]
  452. roots = np.roots(coefs)
  453. icdf = next(root for root in roots if -1 <= root <= 1)
  454. tau = math.sqrt(5) * self.sigma
  455. return icdf * tau
  456. def boundary_enclosing_area_fraction(self, fraction):
  457. r"""
  458. Refer to :func:`Kernel.boundary_enclosing_area_fraction` for the
  459. documentation.
  460. Notes
  461. -----
  462. For Epanechnikov-like kernels, integration of its density within
  463. the boundaries 0 and :math:`b`, and then solving for :math:`b` leads
  464. to the problem of finding the roots of a polynomial of third order.
  465. The implemented formulas are based on the solution of this problem
  466. given in [1]_, where the following 3 solutions are given:
  467. * :math:`u_1 = 1`, solution on negative side;
  468. * :math:`u_2 = \frac{-1 + i\sqrt{3}}{2}`, solution for larger
  469. values than zero crossing of the density;
  470. * :math:`u_3 = \frac{-1 - i\sqrt{3}}{2}`, solution for smaller
  471. values than zero crossing of the density.
  472. The solution :math:`u_3` is the relevant one for the problem at hand,
  473. since it involves only positive area contributions.
  474. References
  475. ----------
  476. .. [1] https://en.wikipedia.org/wiki/Cubic_function
  477. """
  478. self._check_fraction(fraction)
  479. # Python's complex-operator cannot handle quantities, hence the
  480. # following construction on quantities is necessary:
  481. Delta_0 = complex(1.0 / (5.0 * self.sigma.magnitude ** 2), 0) / \
  482. self.sigma.units ** 2
  483. Delta_1 = complex(2.0 * np.sqrt(5.0) * fraction /
  484. (25.0 * self.sigma.magnitude ** 3), 0) / \
  485. self.sigma.units ** 3
  486. C = ((Delta_1 + (Delta_1 ** 2.0 - 4.0 * Delta_0 ** 3.0) ** (
  487. 1.0 / 2.0)) /
  488. 2.0) ** (1.0 / 3.0)
  489. u_3 = complex(-1.0 / 2.0, -np.sqrt(3.0) / 2.0)
  490. b = -5.0 * self.sigma ** 2 * (u_3 * C + Delta_0 / (u_3 * C))
  491. return b.real
  492. class GaussianKernel(SymmetricKernel):
  493. r"""
  494. Class for gaussian kernels.
  495. .. math::
  496. K(t) = (\frac{1}{\sigma \sqrt{2 \pi}}) \exp(-\frac{t^2}{2 \sigma^2})
  497. with :math:`\sigma` being the standard deviation.
  498. The parameter `invert` has no effect on symmetric kernels.
  499. Examples
  500. --------
  501. .. plot::
  502. :include-source:
  503. from elephant import kernels
  504. import quantities as pq
  505. import numpy as np
  506. import matplotlib.pyplot as plt
  507. time_array = np.linspace(-3, 3, num=100) * pq.s
  508. kernel = kernels.GaussianKernel(sigma=1*pq.s)
  509. kernel_time = kernel(time_array)
  510. plt.plot(time_array, kernel_time)
  511. plt.title("GaussianKernel with sigma=1s")
  512. plt.xlabel("time, s")
  513. plt.ylabel("kernel, 1/s")
  514. plt.show()
  515. """
  516. @property
  517. def min_cutoff(self):
  518. min_cutoff = 3.0
  519. return min_cutoff
  520. def _evaluate(self, times):
  521. sigma = self.sigma.rescale(times.units).magnitude
  522. kernel = scipy.stats.norm.pdf(times.magnitude, loc=0, scale=sigma)
  523. kernel = pq.Quantity(kernel, units=1 / times.units)
  524. return kernel
  525. @deprecated_alias(t='time')
  526. def cdf(self, time):
  527. self._check_time_input(time)
  528. sigma = self.sigma.rescale(time.units).magnitude
  529. cdf = scipy.stats.norm.cdf(time, loc=0, scale=sigma)
  530. return cdf
  531. def icdf(self, fraction):
  532. self._check_fraction(fraction)
  533. icdf = scipy.stats.norm.ppf(fraction, loc=0,
  534. scale=self.sigma.magnitude)
  535. return icdf * self.sigma.units
  536. def boundary_enclosing_area_fraction(self, fraction):
  537. self._check_fraction(fraction)
  538. return self.sigma * np.sqrt(2.0) * scipy.special.erfinv(fraction)
  539. class LaplacianKernel(SymmetricKernel):
  540. r"""
  541. Class for laplacian kernels.
  542. .. math::
  543. K(t) = \frac{1}{2 \tau} \exp\left(-\left|\frac{t}{\tau}\right|\right)
  544. with :math:`\tau = \sigma / \sqrt{2}`.
  545. The parameter `invert` has no effect on symmetric kernels.
  546. Examples
  547. --------
  548. .. plot::
  549. :include-source:
  550. from elephant import kernels
  551. import quantities as pq
  552. import numpy as np
  553. import matplotlib.pyplot as plt
  554. time_array = np.linspace(-3, 3, num=1000) * pq.s
  555. kernel = kernels.LaplacianKernel(sigma=1*pq.s)
  556. kernel_time = kernel(time_array)
  557. plt.plot(time_array, kernel_time)
  558. plt.title("LaplacianKernel with sigma=1s")
  559. plt.xlabel("time, s")
  560. plt.ylabel("kernel, 1/s")
  561. plt.show()
  562. """
  563. @property
  564. def min_cutoff(self):
  565. min_cutoff = 3.0
  566. return min_cutoff
  567. def _evaluate(self, times):
  568. tau = self.sigma.rescale(times.units).magnitude / math.sqrt(2)
  569. kernel = scipy.stats.laplace.pdf(times.magnitude, loc=0, scale=tau)
  570. kernel = pq.Quantity(kernel, units=1 / times.units)
  571. return kernel
  572. @deprecated_alias(t='time')
  573. def cdf(self, time):
  574. self._check_time_input(time)
  575. tau = self.sigma.rescale(time.units).magnitude / math.sqrt(2)
  576. cdf = scipy.stats.laplace.cdf(time.magnitude, loc=0, scale=tau)
  577. return cdf
  578. def icdf(self, fraction):
  579. self._check_fraction(fraction)
  580. tau = self.sigma.magnitude / math.sqrt(2)
  581. icdf = scipy.stats.laplace.ppf(fraction, loc=0, scale=tau)
  582. return icdf * self.sigma.units
  583. def boundary_enclosing_area_fraction(self, fraction):
  584. self._check_fraction(fraction)
  585. return -self.sigma * np.log(1.0 - fraction) / np.sqrt(2.0)
  586. # Potential further symmetric kernels from Wiki Kernels (statistics):
  587. # Quartic (biweight), Triweight, Tricube, Cosine, Logistics, Silverman
  588. class ExponentialKernel(Kernel):
  589. r"""
  590. Class for exponential kernels.
  591. .. math::
  592. K(t) = \left\{\begin{array}{ll} (1 / \tau) \exp{(-t / \tau)},
  593. & t \geq 0 \\
  594. 0, & t < 0 \end{array} \right.
  595. with :math:`\tau = \sigma`.
  596. Examples
  597. --------
  598. .. plot::
  599. :include-source:
  600. from elephant import kernels
  601. import quantities as pq
  602. import numpy as np
  603. import matplotlib.pyplot as plt
  604. time_array = np.linspace(-1, 4, num=100) * pq.s
  605. kernel = kernels.ExponentialKernel(sigma=1*pq.s)
  606. kernel_time = kernel(time_array)
  607. plt.plot(time_array, kernel_time)
  608. plt.title("ExponentialKernel with sigma=1s")
  609. plt.xlabel("time, s")
  610. plt.ylabel("kernel, 1/s")
  611. plt.show()
  612. """
  613. @property
  614. def min_cutoff(self):
  615. min_cutoff = 3.0
  616. return min_cutoff
  617. def _evaluate(self, times):
  618. tau = self.sigma.rescale(times.units).magnitude
  619. if self.invert:
  620. times = -times
  621. kernel = scipy.stats.expon.pdf(times.magnitude, loc=0, scale=tau)
  622. kernel = pq.Quantity(kernel, units=1 / times.units)
  623. return kernel
  624. @deprecated_alias(t='time')
  625. def cdf(self, time):
  626. self._check_time_input(time)
  627. tau = self.sigma.rescale(time.units).magnitude
  628. time = time.magnitude
  629. if self.invert:
  630. time = np.minimum(time, 0)
  631. return np.exp(time / tau)
  632. time = np.maximum(time, 0)
  633. return 1. - np.exp(-time / tau)
  634. def icdf(self, fraction):
  635. self._check_fraction(fraction)
  636. if self.invert:
  637. return self.sigma * np.log(fraction)
  638. return -self.sigma * np.log(1.0 - fraction)
  639. def boundary_enclosing_area_fraction(self, fraction):
  640. # the boundary b, which encloses a 'fraction' of CDF in [-b, b] range,
  641. # does not depend on the invert, if the kernel is cut at zero.
  642. # It's easier to compute 'b' for a kernel that has not been inverted.
  643. kernel = self.__class__(sigma=self.sigma, invert=False)
  644. return kernel.icdf(fraction)
  645. class AlphaKernel(Kernel):
  646. r"""
  647. Class for alpha kernels.
  648. .. math::
  649. K(t) = \left\{\begin{array}{ll} (1 / \tau^2)
  650. \ t\ \exp{(-t / \tau)}, & t > 0 \\
  651. 0, & t \leq 0 \end{array} \right.
  652. with :math:`\tau = \sigma / \sqrt{2}`.
  653. Examples
  654. --------
  655. .. plot::
  656. :include-source:
  657. from elephant import kernels
  658. import quantities as pq
  659. import numpy as np
  660. import matplotlib.pyplot as plt
  661. time_array = np.linspace(-1, 4, num=100) * pq.s
  662. kernel = kernels.AlphaKernel(sigma=1*pq.s)
  663. kernel_time = kernel(time_array)
  664. plt.plot(time_array, kernel_time)
  665. plt.title("AlphaKernel with sigma=1s")
  666. plt.xlabel("time, s")
  667. plt.ylabel("kernel, 1/s")
  668. plt.show()
  669. """
  670. @property
  671. def min_cutoff(self):
  672. min_cutoff = 3.0
  673. return min_cutoff
  674. def _evaluate(self, times):
  675. t_units = times.units
  676. tau = self.sigma.rescale(t_units).magnitude / math.sqrt(2)
  677. times = times.magnitude
  678. if self.invert:
  679. times = -times
  680. kernel = (times >= 0) * 1 / tau ** 2 * times * np.exp(-times / tau)
  681. kernel = pq.Quantity(kernel, units=1 / t_units)
  682. return kernel
  683. @deprecated_alias(t='time')
  684. def cdf(self, time):
  685. self._check_time_input(time)
  686. tau = self.sigma.rescale(time.units).magnitude / math.sqrt(2)
  687. cdf = self._cdf_stripped(time.magnitude, tau)
  688. return cdf
  689. def _cdf_stripped(self, t, tau):
  690. # CDF without time units
  691. if self.invert:
  692. t = np.minimum(t, 0)
  693. return np.exp(t / tau) * (tau - t) / tau
  694. t = np.maximum(t, 0)
  695. return 1 - np.exp(-t / tau) * (t + tau) / tau
  696. def icdf(self, fraction):
  697. self._check_fraction(fraction)
  698. tau = self.sigma.magnitude / math.sqrt(2)
  699. def cdf(x):
  700. # CDF fof the AlphaKernel, subtracted 'fraction'
  701. # evaluates the error of the root of cdf(x) = fraction
  702. return self._cdf_stripped(x, tau) - fraction
  703. # fraction is a good starting point for CDF approximation
  704. x0 = fraction if not self.invert else fraction - 1
  705. x_quantile = scipy.optimize.fsolve(cdf, x0=x0, xtol=1e-7)[0]
  706. x_quantile = pq.Quantity(x_quantile, units=self.sigma.units)
  707. return x_quantile
  708. def boundary_enclosing_area_fraction(self, fraction):
  709. # the boundary b, which encloses a 'fraction' of CDF in [-b, b] range,
  710. # does not depend on the invert, if the kernel is cut at zero.
  711. # It's easier to compute 'b' for a kernel that has not been inverted.
  712. kernel = self.__class__(sigma=self.sigma, invert=False)
  713. return kernel.icdf(fraction)