Commit 452625d4 authored by Christian Rohlfing's avatar Christian Rohlfing
Browse files

Merge branch 'development-lab' of git.rwth-aachen.de:IENT/gdet3-demos into development-lab

parents c0956aff 53582065
......@@ -72,8 +72,8 @@
"n = np.linspace(-10, 10, 21)\n",
"m = np.linspace(-40, 40, 81) # m Achse\n",
"s_type = h_type = ''; s_n0 = h_n0 = 0\n",
"M = 1\n",
"b = .5\n",
"M_s = M_h = 1\n",
"b_s = b_h = .5\n",
"\n",
"n0 = -2\n",
"\n",
......@@ -81,25 +81,37 @@
" # Convolve s and h numerically\n",
" return signal.convolve(s(m), h(m), mode='same')\n",
"\n",
"signal_types = {'Dirac-Impuls' : lambda n: np.where(n==0, 1, 0),\n",
" 'Sprungfunktion' : unitstep,\n",
" 'Exponentialimpuls' : lambda n: unitstep(n)*b**n,\n",
" 'Rechteck' : lambda n: unitstep(n+M) - unitstep(n-M-1)\n",
"signal_types = {'Dirac-Impuls' : lambda n, b, M: np.where(n==0, 1, 0),\n",
" 'Sprungfunktion' : lambda n, b, M: np.where(n>=0, 1, 0),\n",
" 'Exponentialimpuls' : lambda n, b, M: unitstep(n)*b**n,\n",
" 'Rechteck' : lambda n, b, M: unitstep(n+M) - unitstep(n-M-1)\n",
" }\n",
"\n",
"def _update_b(_b):\n",
" global b\n",
" b = _b\n",
"def _update_b_s(_b_s):\n",
" global b_s\n",
" b_s = _b_s\n",
" update_signals(s_type, s_n0, h_type, h_n0)\n",
"\n",
"def _update_M(_M):\n",
" global M\n",
" M = _M\n",
"def _update_M_s(_M_s):\n",
" global M_s\n",
" M_s = _M_s\n",
" update_signals(s_type, s_n0, h_type, h_n0)\n",
"\n",
"def _update_b_h(_b_h):\n",
" global b_h\n",
" b_h = _b_h\n",
" update_signals(s_type, s_n0, h_type, h_n0)\n",
"\n",
"def _update_M_h(_M_h):\n",
" global M_h\n",
" M_h = _M_h\n",
" update_signals(s_type, s_n0, h_type, h_n0)\n",
"\n",
"# widgets for setting parameters b and M\n",
"w_b = interactive(_update_b, _b=widgets.FloatSlider(min=.1, max=1, value=.5, step=.1, description=r'$b$', style=ient_wdgtl_style))\n",
"w_M = interactive(_update_M, _M=widgets.FloatSlider(min=0, max=5, value=1, step=1, description=r'$M$', style=ient_wdgtl_style))\n",
"w_b_s = interactive(_update_b_s, _b_s=widgets.FloatSlider(min=.1, max=1, value=.5, step=.1, description=r'$b_s$', style=ient_wdgtl_style))\n",
"w_b_h = interactive(_update_b_h, _b_h=widgets.FloatSlider(min=.1, max=1, value=.5, step=.1, description=r'$b_h$', style=ient_wdgtl_style))\n",
"w_M_s = interactive(_update_M_s, _M_s=widgets.FloatSlider(min=0, max=5, value=1, step=1, description=r'$M_s$', style=ient_wdgtl_style))\n",
"w_M_h = interactive(_update_M_h, _M_h=widgets.FloatSlider(min=0, max=5, value=1, step=1, description=r'$M_h$', style=ient_wdgtl_style))\n",
"box = HBox([])\n",
"\n",
"fig0, axs0 = plt.subplots(1, 2, figsize=(8,2)); container_s = container_h = None\n",
......@@ -113,8 +125,8 @@
" s_type = _s_type; s_n0 = _s_n0; h_type = _h_type; h_n0 = _h_n0\n",
" \n",
" global s, h, gn, container_s, container_h # reused in second interactive plot\n",
" s = lambda m: signal_types[_s_type]((m-_s_n0)); # s(m-n0)\n",
" h = lambda m: signal_types[_h_type]((m-_h_n0)); # h(m-n0)\n",
" s = lambda m: signal_types[_s_type]((m-_s_n0), b_s, M_s); # s(m-n0)\n",
" h = lambda m: signal_types[_h_type]((m-_h_n0), b_h, M_h); # h(m-n0)\n",
" gn = convolution(s, h) # numerical convolution\n",
" \n",
" # update second plot if existing\n",
......@@ -125,16 +137,18 @@
" pass\n",
" \n",
" # show widgets according to chosen s and h\n",
" if _s_type == 'Exponentialimpuls' and _h_type == 'Rechteck':\n",
" box.children = [w_b, w_M]\n",
" elif _s_type == 'Rechteck' and _h_type == 'Exponentialimpuls':\n",
" box.children = [w_M, w_b]\n",
" elif _s_type == 'Exponentialimpuls' or _h_type == 'Exponentialimpuls':\n",
" box.children = [w_b]\n",
" elif _s_type == 'Rechteck' or _h_type == 'Rechteck':\n",
" box.children = [w_M]\n",
" else:\n",
" box.children = []\n",
" children = []\n",
" if _s_type == 'Exponentialimpuls':\n",
" children.append(w_b_s)\n",
" elif _s_type == 'Rechteck':\n",
" children.append(w_M_s)\n",
" \n",
" if _h_type == 'Rechteck':\n",
" children.append(w_M_h)\n",
" elif _h_type == 'Exponentialimpuls':\n",
" children.append(w_b_h)\n",
" \n",
" box.children = children\n",
" \n",
" # display s and h plots\n",
" if container_s is None:\n",
......
%% Cell type:code id: tags:
``` python
# Copyright 2019 Institut für Nachrichtentechnik, RWTH Aachen University
%matplotlib widget
import ipywidgets as widgets
from ipywidgets import interact, interactive, fixed, Layout, HBox, VBox
from IPython.display import clear_output, display, HTML
from scipy import signal # convolution
from ient_nb.ient_plots import *
from ient_nb.ient_signals import *
```
%% Cell type:markdown id: tags:
<div>
<img src="ient_nb/figures/rwth_ient_logo@2x.png" style="float: right;height: 5em;">
</div>
# Demonstrator Diskrete Faltung
Zum Starten: Im Menü: Run <span class="fa-chevron-right fa"></span> Run All Cells auswählen.
%% Cell type:markdown id: tags:
## Einleitung
Ein diskretes Signal ist ein Signal, welches nur für ganzzahlige Werte $n$ einen Wert annimmt und sonst Null ist. Auch für die Elementarsignale existieren jeweils zeitdiskrete Versionen. Diese Demonstration nutzt einige davon.
Diskrete Signale können genau wie kontinuierliche Signale gefaltet werden.
Im Folgenden wird die diskrete Faltung
$$g(n)=s(n)\ast h(n)=\sum_{m=-\infty}^{\infty}s(m)h(n-m)$$
betrachtet und veranschaulicht.
## Demo
In der Demonstration stehen verschiedene Signale für $s(n)$ und $h(n)$ zur Verfügung.
$s(n)$ und $h(n)$ können gewählt werden als zeitdiskrete Varianten von:
* Dirac-Impuls $\delta(n)$
* Sprungfunktion $\epsilon(n)$
* Exponentialimpuls $\epsilon(n)\cdot\mathrm{b}^{n}$
* Rechteckfunktion $rect(n) = \epsilon(n+M)-\epsilon(n-M-1)$
Unter der folgenden Abbildungen können diese Funktionen ausgewählt werden und, falls gewünscht, eine Verschiebung um $n_0$ eingestellt werden. Für den Exponentialimpuls ist ebenfalls der Faktor $b$ variierbar, für die Rechteckfunktion ist die Breite $M$ wählbar. In der Abbildung können dann die gewählten Funktionen mit ihren Parametern betrachtet werden.
%% Cell type:code id: tags:
``` python
n = np.linspace(-10, 10, 21)
m = np.linspace(-40, 40, 81) # m Achse
s_type = h_type = ''; s_n0 = h_n0 = 0
M = 1
b = .5
M_s = M_h = 1
b_s = b_h = .5
n0 = -2
def convolution(s, h):
# Convolve s and h numerically
return signal.convolve(s(m), h(m), mode='same')
signal_types = {'Dirac-Impuls' : lambda n: np.where(n==0, 1, 0),
'Sprungfunktion' : unitstep,
'Exponentialimpuls' : lambda n: unitstep(n)*b**n,
'Rechteck' : lambda n: unitstep(n+M) - unitstep(n-M-1)
signal_types = {'Dirac-Impuls' : lambda n, b, M: np.where(n==0, 1, 0),
'Sprungfunktion' : lambda n, b, M: np.where(n>=0, 1, 0),
'Exponentialimpuls' : lambda n, b, M: unitstep(n)*b**n,
'Rechteck' : lambda n, b, M: unitstep(n+M) - unitstep(n-M-1)
}
def _update_b(_b):
global b
b = _b
def _update_b_s(_b_s):
global b_s
b_s = _b_s
update_signals(s_type, s_n0, h_type, h_n0)
def _update_M(_M):
global M
M = _M
def _update_M_s(_M_s):
global M_s
M_s = _M_s
update_signals(s_type, s_n0, h_type, h_n0)
def _update_b_h(_b_h):
global b_h
b_h = _b_h
update_signals(s_type, s_n0, h_type, h_n0)
def _update_M_h(_M_h):
global M_h
M_h = _M_h
update_signals(s_type, s_n0, h_type, h_n0)
# widgets for setting parameters b and M
w_b = interactive(_update_b, _b=widgets.FloatSlider(min=.1, max=1, value=.5, step=.1, description=r'$b$', style=ient_wdgtl_style))
w_M = interactive(_update_M, _M=widgets.FloatSlider(min=0, max=5, value=1, step=1, description=r'$M$', style=ient_wdgtl_style))
w_b_s = interactive(_update_b_s, _b_s=widgets.FloatSlider(min=.1, max=1, value=.5, step=.1, description=r'$b_s$', style=ient_wdgtl_style))
w_b_h = interactive(_update_b_h, _b_h=widgets.FloatSlider(min=.1, max=1, value=.5, step=.1, description=r'$b_h$', style=ient_wdgtl_style))
w_M_s = interactive(_update_M_s, _M_s=widgets.FloatSlider(min=0, max=5, value=1, step=1, description=r'$M_s$', style=ient_wdgtl_style))
w_M_h = interactive(_update_M_h, _M_h=widgets.FloatSlider(min=0, max=5, value=1, step=1, description=r'$M_h$', style=ient_wdgtl_style))
box = HBox([])
fig0, axs0 = plt.subplots(1, 2, figsize=(8,2)); container_s = container_h = None
@widgets.interact(_s_type=widgets.Dropdown(options=list(signal_types.keys()), description=r'Wähle $s(n)$:'),
_s_n0=widgets.FloatSlider(min=-5, max=5, value=0, step=1, description=r'Verschiebung $n_0$', style=ient_wdgtl_style),
_h_type=widgets.Dropdown(options=list(signal_types.keys()), description=r'Wähle $h(n)$:'),
_h_n0=widgets.FloatSlider(min=-5, max=5, value=0, step=1, description=r'Verschiebung $n_0$', style=ient_wdgtl_style))
def update_signals(_s_type, _s_n0, _h_type, _h_n0):
# set global variables
global s_type, s_n0, h_type, h_n0
s_type = _s_type; s_n0 = _s_n0; h_type = _h_type; h_n0 = _h_n0
global s, h, gn, container_s, container_h # reused in second interactive plot
s = lambda m: signal_types[_s_type]((m-_s_n0)); # s(m-n0)
h = lambda m: signal_types[_h_type]((m-_h_n0)); # h(m-n0)
s = lambda m: signal_types[_s_type]((m-_s_n0), b_s, M_s); # s(m-n0)
h = lambda m: signal_types[_h_type]((m-_h_n0), b_h, M_h); # h(m-n0)
gn = convolution(s, h) # numerical convolution
# update second plot if existing
try:
global n0
update_plot(n0)
except NameError:
pass
# show widgets according to chosen s and h
if _s_type == 'Exponentialimpuls' and _h_type == 'Rechteck':
box.children = [w_b, w_M]
elif _s_type == 'Rechteck' and _h_type == 'Exponentialimpuls':
box.children = [w_M, w_b]
elif _s_type == 'Exponentialimpuls' or _h_type == 'Exponentialimpuls':
box.children = [w_b]
elif _s_type == 'Rechteck' or _h_type == 'Rechteck':
box.children = [w_M]
else:
box.children = []
children = []
if _s_type == 'Exponentialimpuls':
children.append(w_b_s)
elif _s_type == 'Rechteck':
children.append(w_M_s)
if _h_type == 'Rechteck':
children.append(w_M_h)
elif _h_type == 'Exponentialimpuls':
children.append(w_b_h)
box.children = children
# display s and h plots
if container_s is None:
ax = axs0[0];
container_s = ient_stem(ax, m, s(m), 'rwth')
ax.set_xticks(np.arange(-10, 11, step=2))
ax.set_xlabel(r'$\rightarrow n$'); ax.set_ylabel(r'$\uparrow s(n)$')
ax.set_xlim([-10.9, 10.9]); ax.set_ylim([-1.19, 1.19]); ient_axis(ax); ient_grid(ax);
ax = axs0[1];
container_h = ient_stem(ax, m, h(m), 'rwth')
ax.set_xticks(np.arange(-10, 11, step=2))
ax.set_xlabel(r'$\rightarrow n$'); ax.set_ylabel(r'$\uparrow h(n)$')
ax.set_xlim(axs0[0].get_xlim()); ax.set_ylim(axs0[0].get_ylim()); ient_axis(ax); ient_grid(ax);
else:
ient_stem_set_ydata(container_s, s(m))
ient_stem_set_ydata(container_h, h(m))
display(box)
```
%% Cell type:markdown id: tags:
Anschließend kann hier die Faltung $g(n)=s(n)\ast h(n)$ der zuvor eingestellten Funktionen betrachtet werden.
Über den Schieberegler kann der Wert für $n$ verändert werden und die Funktion $h(n-m)$ bewegt sich in der oberen Grafik. In der unteren Grafik ist das resultierende Ausgangssignal $g(n)$ zu sehen.
%% Cell type:code id: tags:
``` python
fig, axs = plt.subplots(2, 1, figsize=(8,6),) # gridspec_kw = {'width_ratios':[3, 1]}
global n0
container_ss = container_hh = container_gg = None
@widgets.interact(n=widgets.FloatSlider(min=-10, max=10, value=n0, step=1, description='Verschiebung $n$', style=ient_wdgtl_style))
def update_plot(n):
global container_ss, container_hh, container_gg
global n0
n0 = n
n_ind = np.where(m>=n); n_ind = n_ind[0][0]; g_plot = gn.copy(); g_plot[n_ind+1:] = 0; # hide g(n') with n'>n
if container_gg is None:
ax = axs[1]
container_gg = ient_stem(ax, m, g_plot)
if container_ss is not None:
ient_stem_set_ydata(container_ss, s(m))
ient_stem_set_ydata(container_hh, h(n-m))
ient_stem_set_ydata(container_gg, g_plot)
ax = axs[0]
ax.texts[0].set_x(n); ax.lines[3].set_xdata([n,n]) # update labels
ax = axs[1]
ient_update_ylim(ax, gn, 0.19, 20);
else:
ax = axs[0];
container_ss = ient_stem(ax, m, s(m), 'grun', label=r'$s(m)$')
container_ss[0].set_markerfacecolor('none');
container_ss[0].set_markersize(8);
container_ss[0].set_markeredgewidth(2);
container_hh = ient_stem(ax, m, h(n-m), 'rwth', label=r'$h(n-m)$')
ient_annotate_xtick(ax, r'$n$', n, -0.1, 'rwth', 15); # mark n on m axis
ax.set_xlabel(r'$\rightarrow m$');
ax.set_xlim([-10.2,10.2]); ient_update_ylim(ax, np.concatenate((h(m), s(m))), 0.19, 5);
ax.set_xticks(np.arange(-10, 11, step=2))
ax.legend(); ient_grid(ax); ient_axis(ax);
ax = axs[1]
ax.set_xlabel(r'$\rightarrow n$'); ax.set_ylabel(r'$\uparrow g(n)=s(n)\ast h(n)$');
ax.set_xlim(axs[0].get_xlim()); ient_update_ylim(ax, gn, 0.19, 20);
ax.set_xticks(np.arange(-10, 11, step=2))
ient_grid(ax); ient_axis(ax);
```
%% Cell type:markdown id: tags:
## Aufgaben
Wähle zunächst für $s(n)$ den Dirac-Impuls und für $h(n)$ verschiedene Funktionen aus.
* Beobachte das Faltungsergebnis. Ab welchem $n$ ist ein Ergebnis zu sehen? Was passiert, wenn du eine der Funktionen verschiebst?
* Wie sieht das Ergebnis für zwei Rechteckfunktionen aus? Wie für zwei Sprungfunktionen?
Wähle nun zwei Rechteckfunktionen mit $M>2$.
* Bewege den Schieberegler langsam von links nach rechts. An welcher Stelle tritt der erste Wert des Faltungsergebnisses auf? Wie hoch ist dieser Wert?
* Bewege den Schieberegler eins weiter nach rechts. Nun überlagern sich die beiden Signalen an zwei Positionen. Wie hoch ist der Wert des Faltungsergebnisses nun?
* Bewege den Schieberegler weiter, bis die beiden Funktionen sich vollständig überlagern. Was passiert, wenn nun der Schieberegler weiter nach rechts geschoben wird? Wie hoch ist der Wert des Faltungsergebnisses? Wie berechnen sich die Höhen des Faltungsergebnisses?
%% Cell type:markdown id: tags:
This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources) (OER). Feel free to use the notebook for your own purposes. The code is licensed under the [MIT license](https://opensource.org/licenses/MIT).
Please attribute the work as follows:
*Christian Rohlfing, Emin Kosar, Übungsbeispiele zur Vorlesung "Grundgebiete der Elektrotechnik 3 - Signale und Systeme"*, gehalten von Jens-Rainer Ohm, 2019, Institut für Nachrichtentechnik, RWTH Aachen University.
......
......@@ -45,29 +45,30 @@
"\n",
"# Reale Abtastung\n",
"\n",
"Im Gegensatz zur [idealen Abtastung](GDET3%20Ideale%20Abtastung.ipynb) werden hier zwei Verfahren zur realen Abtastung betrachtet.\n",
"Im Gegensatz zur [idealen Abtastung](GDET3%20Ideale%20Abtastung.ipynb) werden hier zwei Verfahren zur realen Abtastung betrachtet. Tatsächlich kann nicht mit einem idealen Dirac an einem definierten Zeitpunkt abgetastet werden. Im Folgenden werden zwei Verfahren der Abtastung, die *Shape-top Abtastung* und die *Flat-top Abtastung* beschrieben.\n",
"\n",
"## Shape-top Abtastung\n",
"\n",
"Abtastung in Intervallen endlicher Dauer $T_0$ mit Abstand $T=\\frac{1}{r}$\n",
"Bei der Shape-top Abtastung wird das kontinuierliche Signal $s(t)$ mit Abstand $T=\\frac{1}{r}$ abgetastet. \n",
"Anstatt einer Diracfolge wird eine Folge schmaler Rechteckimpulse mit endlicher Dauer $T_0$ verwendet. Das abgetastete Signal $s_0(t)$ ergibt sich zu\n",
"\n",
"$$\n",
"s_0(t) \n",
"= s(t) \\cdot \\sum\\limits_{n=-\\infty}^\\infty \\mathrm{rect}\\left(\\frac{t-nT}{T_0}\\right) \n",
"= s(t) \\cdot \\left[\\mathrm{rect}\\left(\\frac{t}{T_0}\\right) \\ast \\sum\\limits_{n=-\\infty}^\\infty \\delta(t-nT) \\right]\n",
"= s(t) \\cdot \\left[\\mathrm{rect}\\left(\\frac{t}{T_0}\\right) \\ast \\sum\\limits_{n=-\\infty}^\\infty \\delta(t-nT) \\right].\n",
"$$\n",
"\n",
"Im Frequenzbereich\n",
"Im Frequenzbereich folgt durch Transformation\n",
"$$\n",
"S_0(f) \n",
"= S(f) \\ast \\left[T_0 \\mathrm{si}\\left(\\pi T_0 f\\right) \\cdot \\frac{1}{T}\\sum_{k=-\\infty}^\\infty \\delta(f-kr)\\right]\n",
"=\n",
"S(f) \\ast \\left[\\frac{T_0}{T} \\sum_{k=-\\infty}^\\infty \\delta\\left(f-\\frac{k}{T}\\right) \\mathrm{si} \\left(\\pi T_0 \\frac{k}{T}\\right) \\right]\n",
"S(f) \\ast \\left[\\frac{T_0}{T} \\sum_{k=-\\infty}^\\infty \\delta\\left(f-\\frac{k}{T}\\right) \\mathrm{si} \\left(\\pi T_0 \\frac{k}{T}\\right) \\right]\\text{.}\n",
"$$\n",
"\n",
"Jede spektrale Kopie, zentriert bei $\\frac{k}{T}$, wird skaliert mit von $f$ unabhängigen Faktor $\\mathrm{si} \\left(\\pi T_0 \\frac{k}{T}\\right)$.\n",
"Auch hier entstehen wie bei der idealen Abtastung spektrale Kopien, welche um $\\frac{k}{T}$ zentriert sind. Jede spektrale Kopie wird mit einem von $f$ unabhängigen Faktor $\\mathrm{si} \\left(\\pi T_0 \\frac{k}{T}\\right)$ skaliert.\n",
"\n",
"Grenzübergang liefert ideale Abtastung: $\\lim\\limits_{T_0\\rightarrow 0}\\left(\\frac{1}{T_0}s_0(t)\\right) = s_\\mathrm{a}(t)$."
"Der Grenzübergang $T_0\\rightarrow 0$ liefert hier die ideale Abtastung: $\\lim\\limits_{T_0\\rightarrow 0}\\left(\\frac{1}{T_0}s_0(t)\\right) = s_\\mathrm{a}(t)$."
]
},
{
......@@ -75,41 +76,48 @@
"metadata": {},
"source": [
"## Flat-top Abtastung\n",
"\n",
"Abtastung in Intervallen endlicher Dauer und um $T=\\frac{1}{r}$ gehaltene Signalwerte\n",
"Bei der Flat-top Abtastung wird in Intervallen endlicher Dauer abgetastet und der Signalwert dann um $T=\\frac{1}{r}$ gehalten. Dieses Verfahren wird häufig in Analog-Digital-Wandlern eingesetzt. Das abgetastete Signal $s_0(t)$ ergibt sich somit zu\n",
"\n",
"$$\n",
"s_0(t) \n",
"= \\sum\\limits_{n=-\\infty}^\\infty s(nT) \\mathrm{rect}\\left(\\frac{t}{T}-\\frac{1}{2}-nT\\right)\n",
"= s_\\mathrm{a}(t) \\ast \\mathrm{rect}\\left(\\frac{t}{T}-\\frac{1}{2}\\right)\n",
"= \\left[s(t) \\cdot \\sum\\limits_{n=-\\infty}^\\infty \\delta(t-nT)\\right] \\ast \\mathrm{rect}\\left(\\frac{t}{T}\\right)\\ast \\delta\\left(t-\\frac{T}{2}\\right)\n",
"= \\left[s(t) \\cdot \\sum\\limits_{n=-\\infty}^\\infty \\delta(t-nT)\\right] \\ast \\mathrm{rect}\\left(\\frac{t}{T}\\right)\\ast \\delta\\left(t-\\frac{T}{2}\\right) \\text{.}\n",
"$$\n",
"\n",
"Im Frequenzbereich\n",
"Im Frequenzbereich folgt dann\n",
"$$\n",
"S_0(f) \n",
"= S_\\mathrm{a}(f) \\cdot T \\cdot \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-\\mathrm{j}\\pi f T}\n",
"= \\left[S(f) \\ast \\sum\\limits_{k=-\\infty}^\\infty \\delta(f-kr)\\right] \\cdot \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-\\mathrm{j}\\pi f T}\n",
"= \\left[S(f) \\ast \\sum\\limits_{k=-\\infty}^\\infty \\delta(f-kr)\\right] \\cdot \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-\\mathrm{j}\\pi f T}\\text{.}\n",
"$$\n",
"\n",
"Dieses Verfahren wird häufig in Analog-Digital-Wandlern eingesetzt."
"In der Demonstration kann der Unterschied zwischen der Shape-top und der Flat-top Abtastung nocheinmal anschaulich betrachtet werden."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Beispiel\n",
"### Abtastung\n",
"\n",
"Zunächst wird [ideale Abtastung](GDET3%20Ideale%20Abtastung.ipynb) wiederholt mit \n",
"$s_\\mathrm{a}(t) = \\sum\\limits_{n=-\\infty}^\\infty s(nT)\\cdot\\delta(t-nT)$ und Spektrum \n",
"$S_\\mathrm{a}(f) = \\frac{1}{T} \\sum\\limits_{k=-\\infty}^\\infty S(f-kr)$."
"Zunächst wird nocheinmal die [ideale Abtastung](GDET3%20Ideale%20Abtastung.ipynb) wiederholt. In der Abbildung ist als gestrichelte Linie das Signal $s(t)$ dargestellt, das zugehörige Spektrum in blau. Das Signal wird mit Abtastrate $r=2$ abgetastet.\n",
"Das abgetastete Signal \n",
"$s_\\mathrm{a}(t) = \\sum\\limits_{n=-\\infty}^\\infty s(nT)\\cdot\\delta(t-nT)$ und das Spektrum des abgetasteten Signals \n",
"$S_\\mathrm{a}(f) = \\frac{1}{T} \\sum\\limits_{k=-\\infty}^\\infty S(f-kr)$ sind ebenfalls abgebildet. \n",
"\n",
"Das Spektrum des abgetasteten Signals zeigt die für die Abtastung typischen spektralen Wiederholungen. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"# Construction of s(t) and corresponding spectrum S(f)\n",
......@@ -153,18 +161,25 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Nun wird das abgetastete Signal $s_0(t)$ im Zeitbereich betrachtet, welches mittels Flat-top Abtastung erzeugt wurde.\n",
"Nun wird das abgetastete Signal $s_0(t)$ im Zeitbereich betrachtet, welches mittels Flat-top Abtastung erzeugt wurde:\n",
"$$\n",
"s_0(t) \n",
"= s_\\mathrm{a}(t) \\ast \\mathrm{rect}\\left(\\frac{t}{T}-\\frac{1}{2}\\right)\n",
"= \\sum\\limits_{n=-\\infty}^\\infty s(nT) \\mathrm{rect}\\left(\\frac{t}{T}-\\frac{1}{2}-nT\\right)\n",
"$$"
"= \\sum\\limits_{n=-\\infty}^\\infty s(nT) \\mathrm{rect}\\left(\\frac{t}{T}-\\frac{1}{2}-nT\\right).\n",
"$$\n",
"\n",
"Die Abtastrate ist ebenfalls $r=2$.\n",
"Für die Flat-top Abtastung charakteristisch wird der Signalwert bis zum nächsten Abtastwert gehalten. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"# Time-Domain\n",
......@@ -183,17 +198,23 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Nun $S_0(f)$ im Frequenzbereich\n",
"Dies hat im Frequenzbereich zur Konsequenz, dass die spektralen Kopien mit $T \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-j\\pi f T}$ gewichtet werden. \n",
"$S_0(f)$ ergibt sich also zu\n",
"$$\n",
"S_0(f) \n",
"= S_\\mathrm{a}(f) \\cdot T \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-j\\pi f T}\n",
"$$"
"= S_\\mathrm{a}(f) \\cdot T \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-j\\pi f T}.\n",
"$$\n",
"Die nachfolgende Abbildung verdeutlicht diesen Effekt."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"# Frequency domain\n",
......@@ -222,7 +243,7 @@
"source": [
"### Rekonstruktion\n",
"\n",
"Durch die Multiplikation von $S_\\mathrm{a}(f)$ mit $T \\cdot \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-j\\pi f T}$ wird das Spektrum $S_0(f)$ im Basisband verzerrt. So ist es nicht möglich, $S(f)$ mittels eines einfachen idealen Tiefpasses zu rekonstruieren. Zusätzlich ist ein Filter zum Ausgleich nötig\n",
"Durch die Multiplikation von $S_\\mathrm{a}(f)$ mit $T \\cdot \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-j\\pi f T}$ wird das Spektrum $S_0(f)$ im Basisband verzerrt. So ist es nicht möglich, $S(f)$ mittels eines einfachen idealen Tiefpasses zu rekonstruieren. Zusätzlich ist ein Filter nötig, welches diesen Faktor ausgleicht, dieses ist in der folgenden Abbildung dargestellt.\n",
"$$\n",
"H_\\mathrm{eq}(f) = \\frac{1}{T \\cdot \\mathrm{si}(\\pi f T) \\cdot \\mathrm{e}^{-j\\pi f T}}\n",
"$$"
......@@ -231,7 +252,11 @@
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"jupyter": {
"source_hidden": true
}
},
"outputs": [],
"source": [
"# Reconstruction filters #plt.close('all')\n",
......@@ -261,7 +286,8 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Dieses Filter wird nun zur Rekonstruktion angewendet:"
"Dieses Filter wird nun zur Rekonstruktion angewendet. \n",
"Das rekonstruierte Signal unter Verwendung des ausgleichenden Filters ist unten abgebildet. Das Originalsignal konnte erfolgreich rekonstruiert werden. "
]
},
{
......@@ -290,7 +316,16 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"## Demo"
"## Demo\n",
"In der folgenden interaktiven Demonstration kann nun betrachtet werden, was der Unterschied zwischen der Flat-top und der Shape-top Abtastung ist. \n",
"Dargestellt sind untereinander:\n",
"* der Zeitbereich mit dem Originalsignal und dem abgestasteten Signal\n",
"* der zugehörige Frequenzbereich mit dem Originalspektrum, dem Spektrum des abgetasteten Signal und dem Rekonstruktionsfilter\n",
"* das rekonstruierte Spektrum\n",
"* das rekonstruierte Signal im Zeitbereich.\n",
"\n",
"Im Drop-Down-Menü kann die Art der Abtastung (Shape-top oder Flat-top) sowie die abzutastende Funktion (Cosinus-, Sinus-und si-Funktion, sowie Rechteck- oder Dreieckimpuls) ausgewählt werden. \n",
"Über den Schieberegler kann $F$ geändert werden, was bei Cosinus-, Sinus- und si-Funktion die Frequenz und bei Rechteck- und Dreieckimpuls die Breite beeinflusst. "
]
},
{
......@@ -428,6 +463,19 @@
" ient_update_ylim(axs[3], np.concatenate((s(t),g)), 0.19, np.max(np.concatenate((s(t),g))));"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Aufgaben\n",
"Wähle eine beliebige Funktion aus.\n",
"* Wie sieht das abgetastete Signal mit Shape-top Abtastung aus und wie mit Flat-top Abtastung?\n",
"* Welche Auswirkungen hat die Abtastungsart auf das Spektrum und die Rekonstruktion?\n",
"* Variiere $F$. Was passiert?\n",
"* Führe diese Beobachtung für unterschiedliche Funktionen aus. \n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
......
%% Cell type:code id: tags:
``` python
# Copyright 2019 Institut für Nachrichtentechnik, RWTH Aachen University
%matplotlib widget
from ipywidgets import interact, interactive, fixed, HBox, VBox
import ipywidgets as widgets
from IPython.display import clear_output, display, HTML
from ient_nb.ient_plots import *
from ient_nb.ient_transforms import *
from ient_nb.ient_signals import *
signals_t = {'cos-Funktion': lambda t: np.cos(2 * np.pi * t),
'sin-Funktion': lambda t: np.sin(2 * np.pi * t),
'si-Funktion': lambda t: si(2 * np.pi * t),
'Rechteckimpuls': lambda t: rect(t / 1.05),
'Dreieckimpuls': tri}
signals_f = {'cos-Funktion': lambda f, F: np.isin(f/F, f[findIndOfLeastDiff(f/F, [-1, 1])]/F) * 0.5,
'sin-Funktion': lambda f, F: np.isin(f/F, f[findIndOfLeastDiff(f/F, [1])]/F) / 2j - np.isin(f/F, f[findIndOfLeastDiff(f/F, [-1])]/F) / 2j,
'si-Funktion': lambda f, F: 1/(2*np.abs(F))*rect(f / (2*F)),
'Rechteckimpuls': lambda f, F: 1/np.abs(F)*si(np.pi * f / F),
'Dreieckimpuls': lambda f, F: 1/np.abs(F)*si(np.pi * f/F) ** 2}
```
%% Cell type:markdown id: tags:
<div>
<img src="ient_nb/figures/rwth_ient_logo@2x.png" style="float: right;height: 5em;">
</div>
# Reale Abtastung
Im Gegensatz zur [idealen Abtastung](GDET3%20Ideale%20Abtastung.ipynb) werden hier zwei Verfahren zur realen Abtastung betrachtet.
Im Gegensatz zur [idealen Abtastung](GDET3%20Ideale%20Abtastung.ipynb) werden hier zwei Verfahren zur realen Abtastung betrachtet. Tatsächlich kann nicht mit einem idealen Dirac an einem definierten Zeitpunkt abgetastet werden. Im Folgenden werden zwei Verfahren der Abtastung, die *Shape-top Abtastung* und die *Flat-top Abtastung* beschrieben.
## Shape-top Abtastung
Abtastung in Intervallen endlicher Dauer $T_0$ mit Abstand $T=\frac{1}{r}$
Bei der Shape-top Abtastung wird das kontinuierliche Signal $s(t)$ mit Abstand $T=\frac{1}{r}$ abgetastet.
Anstatt einer Diracfolge wird eine Folge schmaler Rechteckimpulse mit endlicher Dauer $T_0$ verwendet. Das abgetastete Signal $s_0(t)$ ergibt sich zu
$$
s_0(t)
= s(t) \cdot \sum\limits_{n=-\infty}^\infty \mathrm{rect}\left(\frac{t-nT}{T_0}\right)
= s(t) \cdot \left[\mathrm{rect}\left(\frac{t}{T_0}\right) \ast \sum\limits_{n=-\infty}^\infty \delta(t-nT) \right]
= s(t) \cdot \left[\mathrm{rect}\left(\frac{t}{T_0}\right) \ast \sum\limits_{n=-\infty}^\infty \delta(t-nT) \right].
$$
Im Frequenzbereich
Im Frequenzbereich folgt durch Transformation
$$
S_0(f)
= S(f) \ast \left[T_0 \mathrm{si}\left(\pi T_0 f\right) \cdot \frac{1}{T}\sum_{k=-\infty}^\infty \delta(f-kr)\right]
=
S(f) \ast \left[\frac{T_0}{T} \sum_{k=-\infty}^\infty \delta\left(f-\frac{k}{T}\right) \mathrm{si} \left(\pi T_0 \frac{k}{T}\right) \right]
S(f) \ast \left[\frac{T_0}{T} \sum_{k=-\infty}^\infty \delta\left(f-\frac{k}{T}\right) \mathrm{si} \left(\pi T_0 \frac{k}{T}\right) \right]\text{.}
$$
Jede spektrale Kopie, zentriert bei $\frac{k}{T}$, wird skaliert mit von $f$ unabhängigen Faktor $\mathrm{si} \left(\pi T_0 \frac{k}{T}\right)$.
Auch hier entstehen wie bei der idealen Abtastung spektrale Kopien, welche um $\frac{k}{T}$ zentriert sind. Jede spektrale Kopie wird mit einem von $f$ unabhängigen Faktor $\mathrm{si} \left(\pi T_0 \frac{k}{T}\right)$ skaliert.
Grenzübergang liefert ideale Abtastung: $\lim\limits_{T_0\rightarrow 0}\left(\frac{1}{T_0}s_0(t)\right) = s_\mathrm{a}(t)$.
Der Grenzübergang $T_0\rightarrow 0$ liefert hier die ideale Abtastung: $\lim\limits_{T_0\rightarrow 0}\left(\frac{1}{T_0}s_0(t)\right) = s_\mathrm{a}(t)$.
%% Cell type:markdown id: tags:
## Flat-top Abtastung
Abtastung in Intervallen endlicher Dauer und um $T=\frac{1}{r}$ gehaltene Signalwerte
Bei der Flat-top Abtastung wird in Intervallen endlicher Dauer abgetastet und der Signalwert dann um $T=\frac{1}{r}$ gehalten. Dieses Verfahren wird häufig in Analog-Digital-Wandlern eingesetzt. Das abgetastete Signal $s_0(t)$ ergibt sich somit zu
$$
s_0(t)
= \sum\limits_{n=-\infty}^\infty s(nT) \mathrm{rect}\left(\frac{t}{T}-\frac{1}{2}-nT\right)
= s_\mathrm{a}(t) \ast \mathrm{rect}\left(\frac{t}{T}-\frac{1}{2}\right)
= \left[s(t) \cdot \sum\limits_{n=-\infty}^\infty \delta(t-nT)\right] \ast \mathrm{rect}\left(\frac{t}{T}\right)\ast \delta\left(t-\frac{T}{2}\right)
= \left[s(t) \cdot \sum\limits_{n=-\infty}^\infty \delta(t-nT)\right] \ast \mathrm{rect}\left(\frac{t}{T}\right)\ast \delta\left(t-\frac{T}{2}\right) \text{.}
$$
Im Frequenzbereich
Im Frequenzbereich folgt dann
$$
S_0(f)
= S_\mathrm{a}(f) \cdot T \cdot \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-\mathrm{j}\pi f T}
= \left[S(f) \ast \sum\limits_{k=-\infty}^\infty \delta(f-kr)\right] \cdot \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-\mathrm{j}\pi f T}
= \left[S(f) \ast \sum\limits_{k=-\infty}^\infty \delta(f-kr)\right] \cdot \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-\mathrm{j}\pi f T}\text{.}
$$
Dieses Verfahren wird häufig in Analog-Digital-Wandlern eingesetzt.
In der Demonstration kann der Unterschied zwischen der Shape-top und der Flat-top Abtastung nocheinmal anschaulich betrachtet werden.
%% Cell type:markdown id: tags:
## Beispiel
### Abtastung
Zunächst wird [ideale Abtastung](GDET3%20Ideale%20Abtastung.ipynb) wiederholt mit
$s_\mathrm{a}(t) = \sum\limits_{n=-\infty}^\infty s(nT)\cdot\delta(t-nT)$ und Spektrum
$S_\mathrm{a}(f) = \frac{1}{T} \sum\limits_{k=-\infty}^\infty S(f-kr)$.
Zunächst wird nocheinmal die [ideale Abtastung](GDET3%20Ideale%20Abtastung.ipynb) wiederholt. In der Abbildung ist als gestrichelte Linie das Signal $s(t)$ dargestellt, das zugehörige Spektrum in blau. Das Signal wird mit Abtastrate $r=2$ abgetastet.
Das abgetastete Signal
$s_\mathrm{a}(t) = \sum\limits_{n=-\infty}^\infty s(nT)\cdot\delta(t-nT)$ und das Spektrum des abgetasteten Signals
$S_\mathrm{a}(f) = \frac{1}{T} \sum\limits_{k=-\infty}^\infty S(f-kr)$ sind ebenfalls abgebildet.
Das Spektrum des abgetasteten Signals zeigt die für die Abtastung typischen spektralen Wiederholungen.
%% Cell type:code id: tags:
``` python
# Construction of s(t) and corresponding spectrum S(f)
t,deltat = np.linspace(-10,10,50001, retstep=True) # t-axis
f,deltaf = np.linspace(-50,50,len(t), retstep=True) # f-axis
F = 0.9 # frequency of the signal
s = lambda t: signals_t['cos-Funktion'](t*F);
S = lambda f: signals_f['cos-Funktion'](f, F);
# Ideal sampling: Construction of sa(t) and Sa(f)
r = 2; T = 1/r; # sampling rate
## Time domain
nT, snT = ient_sample(t, s(t), T)
## Frequency domain
Sa = np.zeros_like(S(f))
kMax = 16 # number of k values in sum for Sa(f), should be infinity :)
for k in np.arange(-kMax, kMax+1): # evaluate infinite sum only for 2*kMax+1 elements
Sa += S(f-k/T)
Sa = Sa/T
fSadirac = f[np.where(Sa)]; Sadirac = Sa[np.where(Sa)]
# Plot
## Time domain
fig, axs = plt.subplots(2,1)
ax = axs[0]; ax.set_title('Zeitbereich');
ax.plot(t, s(t), color='rwth', linestyle='--', label=r'$s(t)$');
ient_plot_dirac(ax, nT, snT, 'rot', label=r'$s_\mathrm{a}(t)$')
ax.set_xlabel(r'$\rightarrow t$'); ax.set_xlim([-7.5,7.5]); ax.legend(loc=2); ient_grid(ax); ient_axis(ax);
## Frequency domain
ax = axs[1]; ax.set_title('Frequenzbereich');
ient_plot_dirac(ax, fSadirac, Sadirac, 'rot', label=r'$S_\mathrm{a}(f)$');
ient_plot_dirac(ax, f[np.where(S(f))], S(f)[np.where(S(f))], 'rwth', label=r'$S(f)$');
ax.set_xlim([-7.5,7.5]); ax.legend(loc=2); ax.set_xlabel(r'$\rightarrow f$'); ient_grid(ax); ient_axis(ax);
txt,_=ient_annotate_xtick(ax, r'$r=2$', r, -.15, 'black'); txt.get_bbox_patch().set_alpha(1);
```
%% Cell type:markdown id: tags:
Nun wird das abgetastete Signal $s_0(t)$ im Zeitbereich betrachtet, welches mittels Flat-top Abtastung erzeugt wurde.
Nun wird das abgetastete Signal $s_0(t)$ im Zeitbereich betrachtet, welches mittels Flat-top Abtastung erzeugt wurde:
$$
s_0(t)
= s_\mathrm{a}(t) \ast \mathrm{rect}\left(\frac{t}{T}-\frac{1}{2}\right)
= \sum\limits_{n=-\infty}^\infty s(nT) \mathrm{rect}\left(\frac{t}{T}-\frac{1}{2}-nT\right)
= \sum\limits_{n=-\infty}^\infty s(nT) \mathrm{rect}\left(\frac{t}{T}-\frac{1}{2}-nT\right).
$$
Die Abtastrate ist ebenfalls $r=2$.
Für die Flat-top Abtastung charakteristisch wird der Signalwert bis zum nächsten Abtastwert gehalten.
%% Cell type:code id: tags:
``` python
# Time-Domain
s0 = np.zeros_like(t) # sum of rects
Nmax = np.ceil(t[-1]/T) # Parts of the infinite rect sum, should be infinity :)
for n in np.arange(-Nmax,Nmax+1):
s0 = s0 + rect((t-n*T)/T-0.5) * s(n*T)
# Plot
fig, ax = plt.subplots(); ax.set_title('Zeitbereich')
ax.plot(t, s(t), 'rwth', linestyle='--', label=r'$s(t)$'); ax.plot(t, s0, 'rot', label=r'$s_0(t)$')
ax.set_xlabel(r'$\rightarrow t$'); ax.set_xlim([-2.9, 2.9]); ax.legend(loc=2); ient_grid(ax); ient_axis(ax);
```
%% Cell type:markdown id: tags:
Nun $S_0(f)$ im Frequenzbereich
Dies hat im Frequenzbereich zur Konsequenz, dass die spektralen Kopien mit $T \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-j\pi f T}$ gewichtet werden.
$S_0(f)$ ergibt sich also zu
$$
S_0(f)
= S_\mathrm{a}(f) \cdot T \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-j\pi f T}
= S_\mathrm{a}(f) \cdot T \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-j\pi f T}.
$$
Die nachfolgende Abbildung verdeutlicht diesen Effekt.
%% Cell type:code id: tags:
``` python
# Frequency domain
S_si = T*si(np.pi*T*f)
S_exp = np.exp(-1j*np.pi*f*T)
S0 = Sa * S_si * S_exp
# Plot
## Magnitude
fig, axs = plt.subplots(2,1);
ax = axs[0]; ax.set_title('Betrag')
ax.plot(f, np.abs(S_si*S_exp), 'k--', label=r'$|T\mathrm{si}(\pi f T)e^{-\mathrm{j}\pi f T}|$')
fS0dirac = f[np.where(S0)]; S0dirac = S0[np.where(S0)] # sample S0 and f
ient_plot_dirac(ax, fS0dirac, np.abs(S0dirac), 'rot', label=r'$|S_0(f)$|');
ax.set_xlim([-7.5,7.5]); ax.legend(loc=2); ax.set_xlabel(r'$\rightarrow f$'); ient_grid(ax); ient_axis(ax);
## Phase
ax = axs[1]; ax.set_title('Phase')
ax.plot(f, np.angle(S_si*S_exp), 'k--', label=r'$\angle T\mathrm{si}(\pi f T)e^{-\mathrm{j}\pi f T}$')
ient_stem(ax, fS0dirac, np.angle(S0dirac), 'rot', label=r'$\angle S_0(f)$')
ax.set_xlim([-7.5,7.5]); ax.legend(loc=2); ax.set_xlabel(r'$\rightarrow f$'); ient_grid(ax); ient_axis(ax);
```
%% Cell type:markdown id: tags:
### Rekonstruktion
Durch die Multiplikation von $S_\mathrm{a}(f)$ mit $T \cdot \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-j\pi f T}$ wird das Spektrum $S_0(f)$ im Basisband verzerrt. So ist es nicht möglich, $S(f)$ mittels eines einfachen idealen Tiefpasses zu rekonstruieren. Zusätzlich ist ein Filter zum Ausgleich nötig
Durch die Multiplikation von $S_\mathrm{a}(f)$ mit $T \cdot \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-j\pi f T}$ wird das Spektrum $S_0(f)$ im Basisband verzerrt. So ist es nicht möglich, $S(f)$ mittels eines einfachen idealen Tiefpasses zu rekonstruieren. Zusätzlich ist ein Filter nötig, welches diesen Faktor ausgleicht, dieses ist in der folgenden Abbildung dargestellt.
$$
H_\mathrm{eq}(f) = \frac{1}{T \cdot \mathrm{si}(\pi f T) \cdot \mathrm{e}^{-j\pi f T}}
$$
%% Cell type:code id: tags:
``` python
# Reconstruction filters #plt.close('all')
## Ideal Low pass to crop the base band
H_lp = rect(f/(r+0.001)) # ideal low pass between -r/2 and r/2
## Equalizing filter to compensate the influence of si(...) and exp(...) terms in S_0(f)
H_eq = 1/(T*si(np.pi*T*f) * np.exp(-1j*np.pi*f*T))
## Overall reconstruction filter
H = H_lp * H_eq
# Plot
fig,axs = plt.subplots(2,1)
ax = axs[0]
ax.plot(f, np.abs(H_eq), 'k--', linewidth=1, label=r'$|H_\mathrm{eq}(f)|$')
ax.plot(f, np.abs(H), 'k', label=r'$|H(f)|$')
ax.set_xlim([-7.5,7.5]); ax.legend(loc=2); ax.set_ylim([-.1,21]);
ax.set_xlabel(r'$\rightarrow f$'); ient_grid(ax); ient_axis(ax);
ax = axs[1]
ax.plot(f, np.angle(H_eq), 'k--', linewidth=1, label=r'$\angle H_\mathrm{eq}(f)$')
ax.plot(f, np.angle(H)*H_lp, 'k', label=r'$\angle H(f)$')
ax.set_xlim([-7.5,7.5]); ax.legend(loc=2); #ax.set_ylim([-.1,11]);
ax.set_xlabel(r'$\rightarrow f$'); ient_grid(ax); ient_axis(ax);
```
%% Cell type:markdown id: tags: