_waveforms.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. # Author: Travis Oliphant
  2. # 2003
  3. #
  4. # Feb. 2010: Updated by Warren Weckesser:
  5. # Rewrote much of chirp()
  6. # Added sweep_poly()
  7. import numpy as np
  8. from numpy import asarray, zeros, place, nan, mod, pi, extract, log, sqrt, \
  9. exp, cos, sin, polyval, polyint
  10. __all__ = ['sawtooth', 'square', 'gausspulse', 'chirp', 'sweep_poly',
  11. 'unit_impulse']
  12. def sawtooth(t, width=1):
  13. """
  14. Return a periodic sawtooth or triangle waveform.
  15. The sawtooth waveform has a period ``2*pi``, rises from -1 to 1 on the
  16. interval 0 to ``width*2*pi``, then drops from 1 to -1 on the interval
  17. ``width*2*pi`` to ``2*pi``. `width` must be in the interval [0, 1].
  18. Note that this is not band-limited. It produces an infinite number
  19. of harmonics, which are aliased back and forth across the frequency
  20. spectrum.
  21. Parameters
  22. ----------
  23. t : array_like
  24. Time.
  25. width : array_like, optional
  26. Width of the rising ramp as a proportion of the total cycle.
  27. Default is 1, producing a rising ramp, while 0 produces a falling
  28. ramp. `width` = 0.5 produces a triangle wave.
  29. If an array, causes wave shape to change over time, and must be the
  30. same length as t.
  31. Returns
  32. -------
  33. y : ndarray
  34. Output array containing the sawtooth waveform.
  35. Examples
  36. --------
  37. A 5 Hz waveform sampled at 500 Hz for 1 second:
  38. >>> import numpy as np
  39. >>> from scipy import signal
  40. >>> import matplotlib.pyplot as plt
  41. >>> t = np.linspace(0, 1, 500)
  42. >>> plt.plot(t, signal.sawtooth(2 * np.pi * 5 * t))
  43. """
  44. t, w = asarray(t), asarray(width)
  45. w = asarray(w + (t - t))
  46. t = asarray(t + (w - w))
  47. y = zeros(t.shape, dtype="d")
  48. # width must be between 0 and 1 inclusive
  49. mask1 = (w > 1) | (w < 0)
  50. place(y, mask1, nan)
  51. # take t modulo 2*pi
  52. tmod = mod(t, 2 * pi)
  53. # on the interval 0 to width*2*pi function is
  54. # tmod / (pi*w) - 1
  55. mask2 = (1 - mask1) & (tmod < w * 2 * pi)
  56. tsub = extract(mask2, tmod)
  57. wsub = extract(mask2, w)
  58. place(y, mask2, tsub / (pi * wsub) - 1)
  59. # on the interval width*2*pi to 2*pi function is
  60. # (pi*(w+1)-tmod) / (pi*(1-w))
  61. mask3 = (1 - mask1) & (1 - mask2)
  62. tsub = extract(mask3, tmod)
  63. wsub = extract(mask3, w)
  64. place(y, mask3, (pi * (wsub + 1) - tsub) / (pi * (1 - wsub)))
  65. return y
  66. def square(t, duty=0.5):
  67. """
  68. Return a periodic square-wave waveform.
  69. The square wave has a period ``2*pi``, has value +1 from 0 to
  70. ``2*pi*duty`` and -1 from ``2*pi*duty`` to ``2*pi``. `duty` must be in
  71. the interval [0,1].
  72. Note that this is not band-limited. It produces an infinite number
  73. of harmonics, which are aliased back and forth across the frequency
  74. spectrum.
  75. Parameters
  76. ----------
  77. t : array_like
  78. The input time array.
  79. duty : array_like, optional
  80. Duty cycle. Default is 0.5 (50% duty cycle).
  81. If an array, causes wave shape to change over time, and must be the
  82. same length as t.
  83. Returns
  84. -------
  85. y : ndarray
  86. Output array containing the square waveform.
  87. Examples
  88. --------
  89. A 5 Hz waveform sampled at 500 Hz for 1 second:
  90. >>> import numpy as np
  91. >>> from scipy import signal
  92. >>> import matplotlib.pyplot as plt
  93. >>> t = np.linspace(0, 1, 500, endpoint=False)
  94. >>> plt.plot(t, signal.square(2 * np.pi * 5 * t))
  95. >>> plt.ylim(-2, 2)
  96. A pulse-width modulated sine wave:
  97. >>> plt.figure()
  98. >>> sig = np.sin(2 * np.pi * t)
  99. >>> pwm = signal.square(2 * np.pi * 30 * t, duty=(sig + 1)/2)
  100. >>> plt.subplot(2, 1, 1)
  101. >>> plt.plot(t, sig)
  102. >>> plt.subplot(2, 1, 2)
  103. >>> plt.plot(t, pwm)
  104. >>> plt.ylim(-1.5, 1.5)
  105. """
  106. t, w = asarray(t), asarray(duty)
  107. w = asarray(w + (t - t))
  108. t = asarray(t + (w - w))
  109. y = zeros(t.shape, dtype="d")
  110. # width must be between 0 and 1 inclusive
  111. mask1 = (w > 1) | (w < 0)
  112. place(y, mask1, nan)
  113. # on the interval 0 to duty*2*pi function is 1
  114. tmod = mod(t, 2 * pi)
  115. mask2 = (1 - mask1) & (tmod < w * 2 * pi)
  116. place(y, mask2, 1)
  117. # on the interval duty*2*pi to 2*pi function is
  118. # (pi*(w+1)-tmod) / (pi*(1-w))
  119. mask3 = (1 - mask1) & (1 - mask2)
  120. place(y, mask3, -1)
  121. return y
  122. def gausspulse(t, fc=1000, bw=0.5, bwr=-6, tpr=-60, retquad=False,
  123. retenv=False):
  124. """
  125. Return a Gaussian modulated sinusoid:
  126. ``exp(-a t^2) exp(1j*2*pi*fc*t).``
  127. If `retquad` is True, then return the real and imaginary parts
  128. (in-phase and quadrature).
  129. If `retenv` is True, then return the envelope (unmodulated signal).
  130. Otherwise, return the real part of the modulated sinusoid.
  131. Parameters
  132. ----------
  133. t : ndarray or the string 'cutoff'
  134. Input array.
  135. fc : float, optional
  136. Center frequency (e.g. Hz). Default is 1000.
  137. bw : float, optional
  138. Fractional bandwidth in frequency domain of pulse (e.g. Hz).
  139. Default is 0.5.
  140. bwr : float, optional
  141. Reference level at which fractional bandwidth is calculated (dB).
  142. Default is -6.
  143. tpr : float, optional
  144. If `t` is 'cutoff', then the function returns the cutoff
  145. time for when the pulse amplitude falls below `tpr` (in dB).
  146. Default is -60.
  147. retquad : bool, optional
  148. If True, return the quadrature (imaginary) as well as the real part
  149. of the signal. Default is False.
  150. retenv : bool, optional
  151. If True, return the envelope of the signal. Default is False.
  152. Returns
  153. -------
  154. yI : ndarray
  155. Real part of signal. Always returned.
  156. yQ : ndarray
  157. Imaginary part of signal. Only returned if `retquad` is True.
  158. yenv : ndarray
  159. Envelope of signal. Only returned if `retenv` is True.
  160. Examples
  161. --------
  162. Plot real component, imaginary component, and envelope for a 5 Hz pulse,
  163. sampled at 100 Hz for 2 seconds:
  164. >>> import numpy as np
  165. >>> from scipy import signal
  166. >>> import matplotlib.pyplot as plt
  167. >>> t = np.linspace(-1, 1, 2 * 100, endpoint=False)
  168. >>> i, q, e = signal.gausspulse(t, fc=5, retquad=True, retenv=True)
  169. >>> plt.plot(t, i, t, q, t, e, '--')
  170. """
  171. if fc < 0:
  172. raise ValueError(f"Center frequency (fc={fc:.2f}) must be >=0.")
  173. if bw <= 0:
  174. raise ValueError(f"Fractional bandwidth (bw={bw:.2f}) must be > 0.")
  175. if bwr >= 0:
  176. raise ValueError(f"Reference level for bandwidth (bwr={bwr:.2f}) "
  177. "must be < 0 dB")
  178. # exp(-a t^2) <-> sqrt(pi/a) exp(-pi^2/a * f^2) = g(f)
  179. ref = pow(10.0, bwr / 20.0)
  180. # fdel = fc*bw/2: g(fdel) = ref --- solve this for a
  181. #
  182. # pi^2/a * fc^2 * bw^2 /4=-log(ref)
  183. a = -(pi * fc * bw) ** 2 / (4.0 * log(ref))
  184. if isinstance(t, str):
  185. if t == 'cutoff': # compute cut_off point
  186. # Solve exp(-a tc**2) = tref for tc
  187. # tc = sqrt(-log(tref) / a) where tref = 10^(tpr/20)
  188. if tpr >= 0:
  189. raise ValueError("Reference level for time cutoff must "
  190. "be < 0 dB")
  191. tref = pow(10.0, tpr / 20.0)
  192. return sqrt(-log(tref) / a)
  193. else:
  194. raise ValueError("If `t` is a string, it must be 'cutoff'")
  195. yenv = exp(-a * t * t)
  196. yI = yenv * cos(2 * pi * fc * t)
  197. yQ = yenv * sin(2 * pi * fc * t)
  198. if not retquad and not retenv:
  199. return yI
  200. if not retquad and retenv:
  201. return yI, yenv
  202. if retquad and not retenv:
  203. return yI, yQ
  204. if retquad and retenv:
  205. return yI, yQ, yenv
  206. def chirp(t, f0, t1, f1, method='linear', phi=0, vertex_zero=True, *,
  207. complex=False):
  208. r"""Frequency-swept cosine generator.
  209. In the following, 'Hz' should be interpreted as 'cycles per unit';
  210. there is no requirement here that the unit is one second. The
  211. important distinction is that the units of rotation are cycles, not
  212. radians. Likewise, `t` could be a measurement of space instead of time.
  213. Parameters
  214. ----------
  215. t : array_like
  216. Times at which to evaluate the waveform.
  217. f0 : float
  218. Frequency (e.g. Hz) at time t=0.
  219. t1 : float
  220. Time at which `f1` is specified.
  221. f1 : float
  222. Frequency (e.g. Hz) of the waveform at time `t1`.
  223. method : {'linear', 'quadratic', 'logarithmic', 'hyperbolic'}, optional
  224. Kind of frequency sweep. If not given, `linear` is assumed. See
  225. Notes below for more details.
  226. phi : float, optional
  227. Phase offset, in degrees. Default is 0.
  228. vertex_zero : bool, optional
  229. This parameter is only used when `method` is 'quadratic'.
  230. It determines whether the vertex of the parabola that is the graph
  231. of the frequency is at t=0 or t=t1.
  232. complex : bool, optional
  233. This parameter creates a complex-valued analytic signal instead of a
  234. real-valued signal. It allows the use of complex baseband (in communications
  235. domain). Default is False.
  236. .. versionadded:: 1.15.0
  237. Returns
  238. -------
  239. y : ndarray
  240. A numpy array containing the signal evaluated at `t` with the requested
  241. time-varying frequency. More precisely, the function returns
  242. ``exp(1j*phase + 1j*(pi/180)*phi) if complex else cos(phase + (pi/180)*phi)``
  243. where `phase` is the integral (from 0 to `t`) of ``2*pi*f(t)``.
  244. The instantaneous frequency ``f(t)`` is defined below.
  245. See Also
  246. --------
  247. sweep_poly
  248. Notes
  249. -----
  250. There are four possible options for the parameter `method`, which have a (long)
  251. standard form and some allowed abbreviations. The formulas for the instantaneous
  252. frequency :math:`f(t)` of the generated signal are as follows:
  253. 1. Parameter `method` in ``('linear', 'lin', 'li')``:
  254. .. math::
  255. f(t) = f_0 + \beta\, t \quad\text{with}\quad
  256. \beta = \frac{f_1 - f_0}{t_1}
  257. Frequency :math:`f(t)` varies linearly over time with a constant rate
  258. :math:`\beta`.
  259. 2. Parameter `method` in ``('quadratic', 'quad', 'q')``:
  260. .. math::
  261. f(t) =
  262. \begin{cases}
  263. f_0 + \beta\, t^2 & \text{if vertex_zero is True,}\\
  264. f_1 + \beta\, (t_1 - t)^2 & \text{otherwise,}
  265. \end{cases}
  266. \quad\text{with}\quad
  267. \beta = \frac{f_1 - f_0}{t_1^2}
  268. The graph of the frequency f(t) is a parabola through :math:`(0, f_0)` and
  269. :math:`(t_1, f_1)`. By default, the vertex of the parabola is at
  270. :math:`(0, f_0)`. If `vertex_zero` is ``False``, then the vertex is at
  271. :math:`(t_1, f_1)`.
  272. To use a more general quadratic function, or an arbitrary
  273. polynomial, use the function `scipy.signal.sweep_poly`.
  274. 3. Parameter `method` in ``('logarithmic', 'log', 'lo')``:
  275. .. math::
  276. f(t) = f_0 \left(\frac{f_1}{f_0}\right)^{t/t_1}
  277. :math:`f_0` and :math:`f_1` must be nonzero and have the same sign.
  278. This signal is also known as a geometric or exponential chirp.
  279. 4. Parameter `method` in ``('hyperbolic', 'hyp')``:
  280. .. math::
  281. f(t) = \frac{\alpha}{\beta\, t + \gamma} \quad\text{with}\quad
  282. \alpha = f_0 f_1 t_1, \ \beta = f_0 - f_1, \ \gamma = f_1 t_1
  283. :math:`f_0` and :math:`f_1` must be nonzero.
  284. Examples
  285. --------
  286. For the first example, a linear chirp ranging from 6 Hz to 1 Hz over 10 seconds is
  287. plotted:
  288. >>> import numpy as np
  289. >>> from matplotlib.pyplot import tight_layout
  290. >>> from scipy.signal import chirp, square, ShortTimeFFT
  291. >>> from scipy.signal.windows import gaussian
  292. >>> import matplotlib.pyplot as plt
  293. ...
  294. >>> N, T = 1000, 0.01 # number of samples and sampling interval for 10 s signal
  295. >>> t = np.arange(N) * T # timestamps
  296. ...
  297. >>> x_lin = chirp(t, f0=6, f1=1, t1=10, method='linear')
  298. ...
  299. >>> fg0, ax0 = plt.subplots()
  300. >>> ax0.set_title(r"Linear Chirp from $f(0)=6\,$Hz to $f(10)=1\,$Hz")
  301. >>> ax0.set(xlabel="Time $t$ in Seconds", ylabel=r"Amplitude $x_\text{lin}(t)$")
  302. >>> ax0.plot(t, x_lin)
  303. >>> plt.show()
  304. The following four plots each show the short-time Fourier transform of a chirp
  305. ranging from 45 Hz to 5 Hz with different values for the parameter `method`
  306. (and `vertex_zero`):
  307. >>> x_qu0 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=True)
  308. >>> x_qu1 = chirp(t, f0=45, f1=5, t1=N*T, method='quadratic', vertex_zero=False)
  309. >>> x_log = chirp(t, f0=45, f1=5, t1=N*T, method='logarithmic')
  310. >>> x_hyp = chirp(t, f0=45, f1=5, t1=N*T, method='hyperbolic')
  311. ...
  312. >>> win = gaussian(50, std=12, sym=True)
  313. >>> SFT = ShortTimeFFT(win, hop=2, fs=1/T, mfft=800, scale_to='magnitude')
  314. >>> ts = ("'quadratic', vertex_zero=True", "'quadratic', vertex_zero=False",
  315. ... "'logarithmic'", "'hyperbolic'")
  316. >>> fg1, ax1s = plt.subplots(2, 2, sharex='all', sharey='all',
  317. ... figsize=(6, 5), layout="constrained")
  318. >>> for x_, ax_, t_ in zip([x_qu0, x_qu1, x_log, x_hyp], ax1s.ravel(), ts):
  319. ... aSx = abs(SFT.stft(x_))
  320. ... im_ = ax_.imshow(aSx, origin='lower', aspect='auto', extent=SFT.extent(N),
  321. ... cmap='plasma')
  322. ... ax_.set_title(t_)
  323. ... if t_ == "'hyperbolic'":
  324. ... fg1.colorbar(im_, ax=ax1s, label='Magnitude $|S_z(t,f)|$')
  325. >>> _ = fg1.supxlabel("Time $t$ in Seconds") # `_ =` is needed to pass doctests
  326. >>> _ = fg1.supylabel("Frequency $f$ in Hertz")
  327. >>> plt.show()
  328. Finally, the short-time Fourier transform of a complex-valued linear chirp
  329. ranging from -30 Hz to 30 Hz is depicted:
  330. >>> z_lin = chirp(t, f0=-30, f1=30, t1=N*T, method="linear", complex=True)
  331. >>> SFT.fft_mode = 'centered' # needed to work with complex signals
  332. >>> aSz = abs(SFT.stft(z_lin))
  333. ...
  334. >>> fg2, ax2 = plt.subplots()
  335. >>> ax2.set_title(r"Linear Chirp from $-30\,$Hz to $30\,$Hz")
  336. >>> ax2.set(xlabel="Time $t$ in Seconds", ylabel="Frequency $f$ in Hertz")
  337. >>> im2 = ax2.imshow(aSz, origin='lower', aspect='auto',
  338. ... extent=SFT.extent(N), cmap='viridis')
  339. >>> fg2.colorbar(im2, label='Magnitude $|S_z(t,f)|$')
  340. >>> plt.show()
  341. Note that using negative frequencies makes only sense with complex-valued signals.
  342. Furthermore, the magnitude of the complex exponential function is one whereas the
  343. magnitude of the real-valued cosine function is only 1/2.
  344. """
  345. # 'phase' is computed in _chirp_phase, to make testing easier.
  346. phase = _chirp_phase(t, f0, t1, f1, method, vertex_zero) + np.deg2rad(phi)
  347. return np.exp(1j*phase) if complex else np.cos(phase)
  348. def _chirp_phase(t, f0, t1, f1, method='linear', vertex_zero=True):
  349. """
  350. Calculate the phase used by `chirp` to generate its output.
  351. See `chirp` for a description of the arguments.
  352. """
  353. t = asarray(t)
  354. f0 = float(f0)
  355. t1 = float(t1)
  356. f1 = float(f1)
  357. if method in ['linear', 'lin', 'li']:
  358. beta = (f1 - f0) / t1
  359. phase = 2 * pi * (f0 * t + 0.5 * beta * t * t)
  360. elif method in ['quadratic', 'quad', 'q']:
  361. beta = (f1 - f0) / (t1 ** 2)
  362. if vertex_zero:
  363. phase = 2 * pi * (f0 * t + beta * t ** 3 / 3)
  364. else:
  365. phase = 2 * pi * (f1 * t + beta * ((t1 - t) ** 3 - t1 ** 3) / 3)
  366. elif method in ['logarithmic', 'log', 'lo']:
  367. if f0 * f1 <= 0.0:
  368. raise ValueError("For a logarithmic chirp, f0 and f1 must be "
  369. "nonzero and have the same sign.")
  370. if f0 == f1:
  371. phase = 2 * pi * f0 * t
  372. else:
  373. beta = t1 / log(f1 / f0)
  374. phase = 2 * pi * beta * f0 * (pow(f1 / f0, t / t1) - 1.0)
  375. elif method in ['hyperbolic', 'hyp']:
  376. if f0 == 0 or f1 == 0:
  377. raise ValueError("For a hyperbolic chirp, f0 and f1 must be "
  378. "nonzero.")
  379. if f0 == f1:
  380. # Degenerate case: constant frequency.
  381. phase = 2 * pi * f0 * t
  382. else:
  383. # Singular point: the instantaneous frequency blows up
  384. # when t == sing.
  385. sing = -f1 * t1 / (f0 - f1)
  386. phase = 2 * pi * (-sing * f0) * log(np.abs(1 - t/sing))
  387. else:
  388. raise ValueError("method must be 'linear', 'quadratic', 'logarithmic', "
  389. f"or 'hyperbolic', but a value of {method!r} was given.")
  390. return phase
  391. def sweep_poly(t, poly, phi=0):
  392. """
  393. Frequency-swept cosine generator, with a time-dependent frequency.
  394. This function generates a sinusoidal function whose instantaneous
  395. frequency varies with time. The frequency at time `t` is given by
  396. the polynomial `poly`.
  397. Parameters
  398. ----------
  399. t : ndarray
  400. Times at which to evaluate the waveform.
  401. poly : 1-D array_like or instance of numpy.poly1d
  402. The desired frequency expressed as a polynomial. If `poly` is
  403. a list or ndarray of length n, then the elements of `poly` are
  404. the coefficients of the polynomial, and the instantaneous
  405. frequency is
  406. ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]``
  407. If `poly` is an instance of numpy.poly1d, then the
  408. instantaneous frequency is
  409. ``f(t) = poly(t)``
  410. phi : float, optional
  411. Phase offset, in degrees, Default: 0.
  412. Returns
  413. -------
  414. sweep_poly : ndarray
  415. A numpy array containing the signal evaluated at `t` with the
  416. requested time-varying frequency. More precisely, the function
  417. returns ``cos(phase + (pi/180)*phi)``, where `phase` is the integral
  418. (from 0 to t) of ``2 * pi * f(t)``; ``f(t)`` is defined above.
  419. See Also
  420. --------
  421. chirp
  422. Notes
  423. -----
  424. .. versionadded:: 0.8.0
  425. If `poly` is a list or ndarray of length `n`, then the elements of
  426. `poly` are the coefficients of the polynomial, and the instantaneous
  427. frequency is:
  428. ``f(t) = poly[0]*t**(n-1) + poly[1]*t**(n-2) + ... + poly[n-1]``
  429. If `poly` is an instance of `numpy.poly1d`, then the instantaneous
  430. frequency is:
  431. ``f(t) = poly(t)``
  432. Finally, the output `s` is:
  433. ``cos(phase + (pi/180)*phi)``
  434. where `phase` is the integral from 0 to `t` of ``2 * pi * f(t)``,
  435. ``f(t)`` as defined above.
  436. Examples
  437. --------
  438. Compute the waveform with instantaneous frequency::
  439. f(t) = 0.025*t**3 - 0.36*t**2 + 1.25*t + 2
  440. over the interval 0 <= t <= 10.
  441. >>> import numpy as np
  442. >>> from scipy.signal import sweep_poly
  443. >>> p = np.poly1d([0.025, -0.36, 1.25, 2.0])
  444. >>> t = np.linspace(0, 10, 5001)
  445. >>> w = sweep_poly(t, p)
  446. Plot it:
  447. >>> import matplotlib.pyplot as plt
  448. >>> plt.subplot(2, 1, 1)
  449. >>> plt.plot(t, w)
  450. >>> plt.title("Sweep Poly\\nwith frequency " +
  451. ... "$f(t) = 0.025t^3 - 0.36t^2 + 1.25t + 2$")
  452. >>> plt.subplot(2, 1, 2)
  453. >>> plt.plot(t, p(t), 'r', label='f(t)')
  454. >>> plt.legend()
  455. >>> plt.xlabel('t')
  456. >>> plt.tight_layout()
  457. >>> plt.show()
  458. """
  459. # 'phase' is computed in _sweep_poly_phase, to make testing easier.
  460. phase = _sweep_poly_phase(t, poly)
  461. # Convert to radians.
  462. phi *= pi / 180
  463. return cos(phase + phi)
  464. def _sweep_poly_phase(t, poly):
  465. """
  466. Calculate the phase used by sweep_poly to generate its output.
  467. See `sweep_poly` for a description of the arguments.
  468. """
  469. # polyint handles lists, ndarrays and instances of poly1d automatically.
  470. intpoly = polyint(poly)
  471. phase = 2 * pi * polyval(intpoly, t)
  472. return phase
  473. def unit_impulse(shape, idx=None, dtype=float):
  474. r"""
  475. Unit impulse signal (discrete delta function) or unit basis vector.
  476. Parameters
  477. ----------
  478. shape : int or tuple of int
  479. Number of samples in the output (1-D), or a tuple that represents the
  480. shape of the output (N-D).
  481. idx : None or int or tuple of int or 'mid', optional
  482. Index at which the value is 1. If None, defaults to the 0th element.
  483. If ``idx='mid'``, the impulse will be centered at ``shape // 2`` in
  484. all dimensions. If an int, the impulse will be at `idx` in all
  485. dimensions.
  486. dtype : data-type, optional
  487. The desired data-type for the array, e.g., ``numpy.int8``. Default is
  488. ``numpy.float64``.
  489. Returns
  490. -------
  491. y : ndarray
  492. Output array containing an impulse signal.
  493. Notes
  494. -----
  495. In digital signal processing literature the unit impulse signal is often
  496. represented by the Kronecker delta. [1]_ I.e., a signal :math:`u_k[n]`,
  497. which is zero everywhere except being one at the :math:`k`-th sample,
  498. can be expressed as
  499. .. math::
  500. u_k[n] = \delta[n-k] \equiv \delta_{n,k}\ .
  501. Furthermore, the unit impulse is frequently interpreted as the discrete-time
  502. version of the continuous-time Dirac distribution. [2]_
  503. References
  504. ----------
  505. .. [1] "Kronecker delta", *Wikipedia*,
  506. https://en.wikipedia.org/wiki/Kronecker_delta#Digital_signal_processing
  507. .. [2] "Dirac delta function" *Wikipedia*,
  508. https://en.wikipedia.org/wiki/Dirac_delta_function#Relationship_to_the_Kronecker_delta
  509. .. versionadded:: 0.19.0
  510. Examples
  511. --------
  512. An impulse at the 0th element (:math:`\\delta[n]`):
  513. >>> from scipy import signal
  514. >>> signal.unit_impulse(8)
  515. array([ 1., 0., 0., 0., 0., 0., 0., 0.])
  516. Impulse offset by 2 samples (:math:`\\delta[n-2]`):
  517. >>> signal.unit_impulse(7, 2)
  518. array([ 0., 0., 1., 0., 0., 0., 0.])
  519. 2-dimensional impulse, centered:
  520. >>> signal.unit_impulse((3, 3), 'mid')
  521. array([[ 0., 0., 0.],
  522. [ 0., 1., 0.],
  523. [ 0., 0., 0.]])
  524. Impulse at (2, 2), using broadcasting:
  525. >>> signal.unit_impulse((4, 4), 2)
  526. array([[ 0., 0., 0., 0.],
  527. [ 0., 0., 0., 0.],
  528. [ 0., 0., 1., 0.],
  529. [ 0., 0., 0., 0.]])
  530. Plot the impulse response of a 4th-order Butterworth lowpass filter:
  531. >>> imp = signal.unit_impulse(100, 'mid')
  532. >>> b, a = signal.butter(4, 0.2)
  533. >>> response = signal.lfilter(b, a, imp)
  534. >>> import numpy as np
  535. >>> import matplotlib.pyplot as plt
  536. >>> plt.plot(np.arange(-50, 50), imp)
  537. >>> plt.plot(np.arange(-50, 50), response)
  538. >>> plt.margins(0.1, 0.1)
  539. >>> plt.xlabel('Time [samples]')
  540. >>> plt.ylabel('Amplitude')
  541. >>> plt.grid(True)
  542. >>> plt.show()
  543. """
  544. out = zeros(shape, dtype)
  545. shape = np.atleast_1d(shape)
  546. if idx is None:
  547. idx = (0,) * len(shape)
  548. elif idx == 'mid':
  549. idx = tuple(shape // 2)
  550. elif not hasattr(idx, "__iter__"):
  551. idx = (idx,) * len(shape)
  552. out[idx] = 1
  553. return out