diff --git a/Dockerfile b/Dockerfile index a87e00bc27f8b00d367658953f919713e718fa05..c7af1913699ee4e0240f14174ddca850e20e5c54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,5 +2,5 @@ ARG BASE_IMAGE=registry.git.rwth-aachen.de/jupyter/profiles/rwth-courses:latest FROM ${BASE_IMAGE} RUN conda install --quiet --yes \ - 'scipy==1.7.1' && \ + 'scipy==1.7.1' && \ conda clean --all diff --git a/GDET3 Diskrete Fourier-Transformation GUI.ipynb b/GDET3 Diskrete Fourier-Transformation GUI.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..d0af393fabca77eaa9ea2e2b81c5a05a4edead53 --- /dev/null +++ b/GDET3 Diskrete Fourier-Transformation GUI.ipynb @@ -0,0 +1,248 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + } + }, + "outputs": [], + "source": [ + "# Copyright 2020 Institut für Nachrichtentechnik, RWTH Aachen University\n", + "%matplotlib widget\n", + "\n", + "from ipywidgets import interact, interactive, fixed, HBox, VBox\n", + "import ipywidgets as widgets\n", + "from IPython.display import clear_output, display, HTML\n", + "\n", + "import matplotlib.pyplot as plt\n", + "from rwth_nb.plots import colors\n", + "import rwth_nb.plots.mpl_decorations as rwth_plt\n", + "from rwth_nb.misc.signals import *\n", + "from rwth_nb.misc.transforms import *\n", + "\n", + "t,deltat = np.linspace(-10,10,50001, retstep=True) # t-axis\n", + "f,deltaf = np.linspace(-10,10,len(t), retstep=True) # f-axis\n", + "kMax = 16 # number of k values in sum for Sa(f)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "<div>\n", + " <img src=\"figures/rwth_ient_logo@2x.png\" style=\"float: right;height: 5em;\">\n", + "</div>\n", + "\n", + "# Demonstrator Diskrete Fourier-Transformation\n", + "\n", + "Zum Starten: Im Menü: Run <span class=\"fa-chevron-right fa\"></span> Run All Cells auswählen.\n", + "\n", + "## Einleitung\n", + "\n", + "$s(t)$ wird ideal mit Abtastrate $r$ abgetastet.\n", + "\n", + "Diskrete Fourier-Transformation mit $F = \\frac{1}{M}$ resultiert in diskreter Fourier-Transformierten\n", + "$$\n", + "S_\\mathrm{d}(k) \n", + "= \n", + "\\sum_{n=0}^{M-1} s_\\mathrm{d}(n) \\mathrm{e}^{-\\mathrm{j}2\\pi kFn} \\quad k=0,\\ldots,M-1 \\text{ ,}\n", + "$$\n", + "wobei $s_\\mathrm{d}(n)$ um $M$ periodisches, diskretes Zeitsignal mit $t = n \\cdot \\frac{1}{r}$.\n", + "\n", + "Für die Rücktransformation in den Zeitbereich gilt dann\n", + "$$\n", + "s_\\mathrm{d}(n)\n", + "=\n", + "\\frac{1}{M} \\sum_{k=0}^{M-1}S_\\mathrm{d}(k) \\mathrm{e}^{\\mathrm{j}2\\pi kFn} \\quad n=0,\\ldots,M-1 \\text{ ,}\n", + "$$\n", + "wobei\n", + "$f = k \\cdot \\frac{r}{M}$.\n", + "\n", + "## Beispiel\n", + "\n", + "Zeige eine Periode mit $M=24$ Werten" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "M = 24; # period of discrete signal\n", + "r = 4; # sampling rate\n", + "\n", + "n = np.arange(M) # discrete time axis\n", + "sd = tri(n/r) + tri((n-M)/r) # discrete signal sd(n)\n", + "\n", + "F = 1/M \n", + "\n", + "# Very slow DFT computation for teaching purposes!\n", + "# Fasten your seatbelts with FFF by uncommenting the line with \"fft.fft\" below\n", + "Sd = np.zeros_like(sd, dtype=complex)\n", + "for k in np.arange(M): \n", + " Sd[k] = np.sum(sd * np.exp(-2j*np.pi*k*F*n))\n", + "#Sd = np.fft.fft(sd) # this would be so much faster!\n", + "Sd = np.real(Sd) # discard super small imaginary part (1e-16), works \"only\" for examples like this\n", + "\n", + "# Plot\n", + "fig,axs = plt.subplots(2,1)\n", + "\n", + "## Plot discrete time domain\n", + "ax = axs[0]; rwth_plt.stem(ax, n, sd); \n", + "ax.set_xlabel(r'$\\rightarrow n$'); ax.set_ylabel(r'$\\uparrow s_\\mathrm{d}(n)$')\n", + "rwth_plt.axis(ax);\n", + "\n", + "# Plot discrete Fourier domain\n", + "ax = axs[1]; rwth_plt.stem(ax, np.arange(M), Sd/r); \n", + "ax.set_xlabel(r'$\\rightarrow k$'); ax.set_ylabel(r'$\\uparrow S_\\mathrm{d}(k)/r$')\n", + "rwth_plt.axis(ax); fig.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Demonstrator\n", + "\n", + "Signal $s(t) = \\Lambda(t)$ mit zugehörigem Spektrum $S(f) = \\mathrm{si}^2(\\pi f)$ kann zunächst mit Rate $r$ abgetastet werden. Weiter lässt sich das diskrete Signal periodisch fortsetzen mit Periode $M$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [] + }, + "outputs": [], + "source": [ + "def decorate_second_axis(ax):\n", + " ax.spines['top'].set_position(('axes',.9)); ax.spines['right'].set_color('none'); ax.spines['left'].set_color('none'); ax.spines['bottom'].set_color('none');\n", + " ax.xaxis.set_label_coords(.98,1); ax.xaxis.label.set_horizontalalignment('right') \n", + "\n", + "fig, axs = plt.subplots(2, 1, **rwth_plt.landscape); #fig.tight_layout()\n", + "highlight_rect_style = {'facecolor': \"none\", 'edgecolor': 'rwth:orange', 'linestyle':'--', 'linewidth':2.0}\n", + "\n", + "@widgets.interact( r = widgets.IntSlider(min=0, max=10, step=2, value=0, description='$r$ [Hz]'),\n", + " Mbr = widgets.IntSlider(min=0, max=10, step=2, value=0, description='$M/r$'))\n", + "def update_plots(r, Mbr):\n", + " M = r*Mbr\n", + " global n, snT, k, SkF\n", + " # Continuous functions s(t) and S(f)\n", + " s = tri\n", + " S = lambda f: si(np.pi*f) ** 2\n", + " \n", + " # Ideal sampling\n", + " Sa_f = np.zeros_like(S(f))\n", + " nT = []; snT = nT; n = nT;\n", + " if r > 0:\n", + " T = 1/r\n", + " \n", + " # Sampled function s(n)\n", + " nT, snT = sample(t, s(t), T); n = nT/T;\n", + " \n", + " # Sampled continuous spectrum S_a(f)\n", + " for k in np.arange(-kMax, kMax+1): # evaluate infinite sum only for 2*kMax+1 elements \n", + " Sa_f += S(f-k/T)\n", + " #Sa_f = Sa_f/T # normalize here\n", + " \n", + " # Periodic copies of s(n)\n", + " kF = []; SkF = kF; k = kF;\n", + " sd = np.zeros_like(snT)\n", + " if M > 0:\n", + " F = 1/M # in Hz\n", + " kF, SkF = sample(f, Sa_f, r*F); k = kF/F/r\n", + " for d in np.arange(-kMax, kMax+1): # evaluate infinite sum only for 2*kMax+1 elements \n", + " sd += s((n-d*M)*T)\n", + " display('M={}'.format(M))\n", + " \n", + " # Plot\n", + " if not axs[0].lines: # Call plot() and decorate axes. Usually, these functions take some processing time\n", + " ax = axs[0]; ax.set_title('Zeitbereich');\n", + " ax.plot(t, s(t), 'k', label='$s(t)$');\n", + " ax.set_xlabel(r'$\\rightarrow t $ [s]')\n", + " ax.set_xlim([-5,10]); ax.set_ylim([-.09, 1.19]); ax.legend(loc='upper right', bbox_to_anchor=(1.0, 0.8)); rwth_plt.axis(ax);\n", + " \n", + " axn = ax.twiny(); rwth_plt.stem(axn, [1], [1], color='rwth:blue', label=r'$s(n)$'); \n", + " axn.set_xlabel(r'$\\rightarrow n$ [Samples]'); decorate_second_axis(axn);\n", + " axn.legend(loc='upper right', bbox_to_anchor=(1.0, 0.6));\n", + " axn.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style) \n", + " \n", + " ax = axs[1]; ax.set_title('Frequenzbereich');\n", + " ax.plot(f, S(f), 'k', label='$S(f)$')\n", + " ax.plot(f, Sa_f, 'rwth:red', label='$S_\\mathrm{a}(f)/r$')\n", + " ax.set_xlabel(r'$\\rightarrow f $ [Hz]')\n", + " ax.set_xlim([-5,10]); ax.set_ylim([-.09, 1.19]); ax.legend(loc='upper right', bbox_to_anchor=(1.0, 0.8)); rwth_plt.axis(ax);\n", + " \n", + " axk = ax.twiny(); rwth_plt.stem(axk, [1], [1], label=r'$S_\\mathrm{d}(k)/r$');\n", + " axk.set_xlabel(r'$\\rightarrow k$'); decorate_second_axis(axk);\n", + " axk.legend(loc='upper right', bbox_to_anchor=(1.0, 0.5));\n", + " axk.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style) \n", + " \n", + " else: # Update only the plot lines themselves. Should not take too much processing time\n", + " ax = axs[0] # time domain\n", + " axn = fig.axes[2];\n", + " if r > 0:\n", + " if len(axn.collections) > 1: axn.collections[1].remove()\n", + " if M == 0:\n", + " rwth_plt.stem_set_data(axn.containers[0], n, snT); #axn.containers[0].set_label(r'$s(n)$')\n", + " else:\n", + " rwth_plt.stem_set_data(axn.containers[0], n, sd); #axn.containers[0].set_label(r'$s_\\mathrm{d}(n)$')\n", + " axn.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style) \n", + " \n", + " ax = axs[1] # frequency domain\n", + " ax.lines[1].set_ydata(Sa_f)\n", + " axk = fig.axes[3];\n", + " if M > 0 and r > 0:\n", + " if len(axk.collections) > 1: axk.collections[1].remove()\n", + " axk.fill_between([-.1, M-1+.1], -.05, 1.05, **highlight_rect_style) \n", + " rwth_plt.stem_set_data(axk.containers[0], k, SkF)\n", + " \n", + " axn = fig.axes[2]; axn.set_visible(r > 0)\n", + " axk = fig.axes[3]; axk.set_visible(M > 0 and r > 0)\n", + " if r > 0:\n", + " axn.set_xlim((ax.get_xlim()[0]*r,ax.get_xlim()[1]*r))\n", + " if M > 0:\n", + " axk.set_xlim((ax.get_xlim()[0]*M/r,ax.get_xlim()[1]*M/r))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "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). \n", + "\n", + "Please attribute the work as follows: \n", + "*Christian Rohlfing, Emin Kosar, Übungsbeispiele zur Vorlesung \"Grundgebiete der Elektrotechnik 3 - Signale und Systeme\"*, gehalten von Jens-Rainer Ohm, 2021, Institut für Nachrichtentechnik, RWTH Aachen University." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/GDET3 Faltung GUI.ipynb b/GDET3 Faltung GUI.ipynb index 63a06540f59b5577ce9200e1e9b47a58830a39f4..536377d252aa62894f15d5014b3c481c790566da 100644 --- a/GDET3 Faltung GUI.ipynb +++ b/GDET3 Faltung GUI.ipynb @@ -138,7 +138,12 @@ " \n", " except: pass\n", " rwth_plots.update_ylim(axs0[0], s(tau), 0.19, 5); rwth_plots.update_ylim(axs0[1], h(tau), 0.19, 5);\n", - " \n", + "\n", + "def swap_signals(b):\n", + " s_type = w_s_type.value; h_type = w_h_type.value; w_s_type.value = h_type; w_h_type.value = s_type;\n", + " s_T = w_s_T.value; h_T = w_h_T.value; w_s_T.value = h_T; w_h_T.value = s_T;\n", + " s_t0 = w_s_t0.value; h_t0 = w_h_t0.value; w_s_t0.value = h_t0; w_h_t0.value = s_t0;\n", + "\n", "# Widgets\n", "w_s_type = widgets.Dropdown(options=list(signal_types.keys()), description=r'Wähle $s(t)$:')\n", "w_s_T = widgets.FloatSlider(min=0.5, max=4, value=1, step=.1, description=r'Dehnung T', style=rwth_plots.wdgtl_style)\n", @@ -147,7 +152,10 @@ "w_h_T = widgets.FloatSlider(min=0.5, max=4, value=1, step=.1, description=r'Dehnung T', style=rwth_plots.wdgtl_style)\n", "w_h_t0 = s_t0=widgets.FloatSlider(min=-2, max=2, value=0, step=.1, description=r'Verschiebung $t_0$', style=rwth_plots.wdgtl_style)\n", "w = widgets.interactive(update_signals, s_type=w_s_type, s_T=w_s_T, s_t0=w_s_t0, h_type=w_h_type, h_T=w_h_T, h_t0 = w_h_t0)\n", - "display(widgets.HBox((widgets.VBox((w_s_type, w_s_T, w_s_t0)), widgets.VBox((w_h_type, w_h_T, w_h_t0), layout=Layout(margin='0 0 0 100px'))))); w.update();" + "swap_s_h = widgets.Button(icon='arrows-h', description='Austauschen'); swap_s_h.on_click(swap_signals)\n", + "display(widgets.HBox((widgets.VBox((w_s_type, w_s_T, w_s_t0), layout=Layout(margin='0 50px 0 0')), \n", + " swap_s_h, \n", + " widgets.VBox((w_h_type, w_h_T, w_h_t0), layout=Layout(margin='0 0 0 50px'))))); w.update();" ] }, { @@ -219,13 +227,6 @@ "update_plot_intervals()" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Verschiebung $t$ kann automatisch abgespielt werden. Eine schrittweise Betrachtung ist ebenfalls möglich." - ] - }, { "cell_type": "code", "execution_count": null, @@ -283,6 +284,13 @@ "widgets.HBox([play, stepwise, reset, intslider])#, status" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verschiebung $t$ kann automatisch abgespielt werden. Eine schrittweise Betrachtung ist ebenfalls möglich." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -327,7 +335,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.1" + "version": "3.8.3" } }, "nbformat": 4, diff --git a/GDET3 Faltungsbeispiel.ipynb b/GDET3 Faltungsbeispiel.ipynb index ed333d3c84458207c48466d01ff2ec280768a734..6c79b19452dbe962f0d640ba35677d1e12bece02 100644 --- a/GDET3 Faltungsbeispiel.ipynb +++ b/GDET3 Faltungsbeispiel.ipynb @@ -313,7 +313,7 @@ }, "outputs": [], "source": [ - "fig, axs = plt.subplots(2, 1, figsize=(8,6))\n", + "fig, axs = plt.subplots(2, 1, figsize=(8,6)); fig.tight_layout();\n", "gt = g(t)\n", "\n", "@widgets.interact(t=widgets.FloatSlider(min=-5, max=15, step=.2, value=5,description='$t$', continuous_update=True))\n", @@ -383,7 +383,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.8.3" } }, "nbformat": 4, diff --git a/GDET3 RLC-System.ipynb b/GDET3 RLC-System.ipynb index 8f999ca4ed345b2507c9c4a3a626a08b2ad6813a..9ef543fbcabf6c21892db285f637ecdcd1fb8bb5 100644 --- a/GDET3 RLC-System.ipynb +++ b/GDET3 RLC-System.ipynb @@ -6,7 +6,8 @@ "metadata": { "jupyter": { "source_hidden": true - } + }, + "tags": [] }, "outputs": [], "source": [ @@ -56,7 +57,7 @@ "outputs": [], "source": [ "# Exemplary values\n", - "R = 16 # Ohm\n", + "R = 16 # Ohm, Ω\n", "L = 1.5E-3 # Henry, mH\n", "C = 1E-6 # Farad, myF" ] @@ -245,7 +246,8 @@ "metadata": { "jupyter": { "source_hidden": true - } + }, + "tags": [] }, "outputs": [], "source": [ @@ -276,6 +278,30 @@ " display(HTML('{}<br />{}'.format('a={0:.3f}'.format(a), 'b={0:.3f}'.format(b))))" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Experiment\n", + "\n", + "Mit $R=0 \\,\\Omega$, $L=47\\,\\mathrm{mH}$ und $C=0,68\\,\\mu\\mathrm{F}$ liegt die entsprechende Grenzfrequenz bei $f_0 \\approx 900\\,Hz$. Rauchentwicklung ab Sekunde 20 ;)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "source_hidden": true + }, + "tags": [] + }, + "outputs": [], + "source": [ + "from IPython.display import Video\n", + "Video(\"figures/RLC_Experiment.mp4\", width=480, height=270 )" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -284,13 +310,13 @@ "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). \n", "\n", "Please attribute the work as follows: \n", - "*Christian Rohlfing, Übungsbeispiele zur Vorlesung \"Grundgebiete der Elektrotechnik 3 - Signale und Systeme\"*, gehalten von Jens-Rainer Ohm, 2020, Institut für Nachrichtentechnik, RWTH Aachen University." + "*Christian Rohlfing, Übungsbeispiele zur Vorlesung \"Grundgebiete der Elektrotechnik 3 - Signale und Systeme\"*, gehalten von Jens-Rainer Ohm, 2021, Institut für Nachrichtentechnik, RWTH Aachen University." ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -304,7 +330,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.1" + "version": "3.9.7" } }, "nbformat": 4, diff --git a/README.md b/README.md index 3f09480a40d33fbac9ef9407eff50cae6d77b5e6..a440a3669969c1bea3a6b514a0bd82073a073206 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ # Übungsbeispiele zur Vorlesung Grundgebiete der Elektrotechnik 3 – Signale und Systeme +[](https://jupyter.rwth-aachen.de/hub/spawn?profile=gdet3&next=/user-redirect/lab/tree/gdet3%2Findex.ipynb) [](https://mybinder.org/v2/git/https%3A%2F%2Fgit.rwth-aachen.de%2FIENT%2Fgdet3-demos/master?urlpath=lab/tree/index.ipynb) ## Introduction @@ -12,10 +13,10 @@ Visit the notebook [index.ipynb](index.ipynb) for a table of contents. ## Quick Start -Run the notebooks directly online [](https://mybinder.org/v2/git/https%3A%2F%2Fgit.rwth-aachen.de%2FIENT%2Fgdet3-demos/master?urlpath=lab/tree/index.ipynb). Please note the following limitations: +Run the notebooks directly online with either [](https://jupyter.rwth-aachen.de/hub/spawn?profile=gdet3&next=/user-redirect/lab/tree/gdet3%2Findex.ipynb) or [](https://mybinder.org/v2/git/https%3A%2F%2Fgit.rwth-aachen.de%2FIENT%2Fgdet3-demos/master?urlpath=lab/tree/index.ipynb). Please note the following limitations: * The starting process of the session may take up to one minute. -* Please note that the session will be cancelled after 10 minutes of user inactivity. +* Please note that the Binder session will be cancelled after 10 minutes of user inactivity. ## Usage @@ -48,14 +49,16 @@ To run the notebooks on your local machine, you may use [Anaconda](https://www.a ### Docker -For advanced users only: If you happen to have Docker installed, you can start a local dockerized JupyterLab with enabled GDET3-Demos with +For advanced users only: If you happen to have Docker installed, you can start a local dockerized JupyterLab ```bash -docker run --name='gdet3-demos' --rm -it -p 8888:8888 -e JUPYTER_ENABLE_LAB=yes registry.git.rwth-aachen.de/ient/gdet3-demos:master +git clone --recurse-submodules https://git.rwth-aachen.de/IENT/gdet3-demos.git +docker run --name='gdet3-demos' --rm -it -p 8888:8888 -v ${pwd}/gdet3-demos:/home/jovyan/work registry.git.rwth-aachen.de/ient/gdet3-demos:master ``` Copy and paste the displayed link to your favorite browser. + ## Contact * If you found a bug, please use the [issue tracker](https://git.rwth-aachen.de/IENT/gdet3-demos/issues). diff --git a/figures/RLC_Experiment.mp4 b/figures/RLC_Experiment.mp4 new file mode 100755 index 0000000000000000000000000000000000000000..a541af0aa4954849d204cf568798f7b3a961fd84 Binary files /dev/null and b/figures/RLC_Experiment.mp4 differ diff --git a/index.ipynb b/index.ipynb index e55871b886c8f07b22896022277e540a464109d6..9131cb350a153b82bdf36c2b293b3810f37dfe0c 100644 --- a/index.ipynb +++ b/index.ipynb @@ -44,6 +44,7 @@ " * [Reale Abtastung](GDET3%20Reale%20Abtastung.ipynb)\n", " * [Demo Diskretes Faltungsintegral](GDET3%20Diskrete%20Faltung%20GUI.ipynb)\n", " * [Diskrete Fourier-Transformation](GDET3%20Diskrete%20Fourier-Transformation.ipynb)\n", + " * [Demo Diskrete Fourier-Transformation](GDET3%20Diskrete%20Fourier-Transformation%20GUI.ipynb)\n", " * [Demo $z$-Transformation](GDET3%20z-Transformation%20GUI.ipynb)\n", "5. Systemtheorie der Tiefpass- und Bandpasssysteme\n", " * [Äquivalenter Tiefpass](GDET3%20Äquivalenter%20Tiefpass.ipynb)\n", diff --git a/src/laplace/laplace_plot.py b/src/laplace/laplace_plot.py index cd1f5612c661e379bec7d677662022a3973bbc04..b77af2acf2cb4e64a4a984788ed789228dae5a76 100644 --- a/src/laplace/laplace_plot.py +++ b/src/laplace/laplace_plot.py @@ -25,7 +25,7 @@ class pzPoint(): def clear(self): if self.h_point is not None: self.h_point.remove(); self.h_point = None if self.h_point_conj is not None: self.h_point_conj.remove(); self.h_point_conj = None - if self.h_order is not None: self.h_order.remove(); self.h_order = None + if self.h_order is not None: self.h_order[0].remove(); self.h_order[1].remove(); self.h_order = None class pzPlot(): @@ -106,26 +106,96 @@ class pzPlot(): self.ax.set_title('Pol- /Nullstellen Diagramm', fontsize='12') self.ax.set_aspect('equal', adjustable='datalim') - def onclick(event): - if self.action == 'add' and event.key == 'shift': - self.action = 'del' + # * move indicates whether a drag is happening + # * selected_coords indicates selected coords when button is pressed + # * ghost_hpoint, ghost_hpoint_conj are the half transparent poles/zeroes to indicate where the newly created will be + self.move = False + self.selected_coords = None + self.ghost_hpoint = None + self.ghost_hpoint_conj = None + def on_btn_press(event): if event.inaxes != self.ax: return if self.filter != 'man': return + + # button press, reset move + self.move = False + + # find closest point and set selected if there are any valid points nearby p = event.xdata + 1j * event.ydata - self.update_point(p, self.mode) + self.selected_coords = p + + def on_btn_release(event): + if event.inaxes != self.ax: + if self.ghost_hpoint is not None: + self.ghost_hpoint.remove() + self.ghost_hpoint_conj.remove() + self.ghost_hpoint = None + self.ghost_hpoint_conj = None + return + if self.filter != 'man': return + + # press + no movement + release = click + if not self.move: + on_click(event) + + # press + movement + release = drag + # if dragging process is finished + if self.move and self.find_closest_point(self.selected_coords, self.mode)[0] is not None: + # release move + self.move = False + + # remove any ghosts from the figure and clear variables + if self.ghost_hpoint is not None: + self.ghost_hpoint.remove() + self.ghost_hpoint_conj.remove() + self.ghost_hpoint = None + self.ghost_hpoint_conj = None + + # delete at press selected point + tmp_action = self.action + self.action = 'del' + self.update_point(self.selected_coords, self.mode) + + # add new point at drag point + self.action = 'add' + on_click(event) + self.action = tmp_action - def onkeypress(event): - self.action_locked = True if self.action == 'del' else False - self.w_action_type.children[0].value = 'Löschen' + def on_motion(event): + if event.inaxes != self.ax: return + if self.filter != 'man': return + + # if button is pressed + if event.button == 1: + # lock move + self.move = True + + # if a point was selected + if self.find_closest_point(self.selected_coords, self.mode)[0] is not None: + if self.ghost_hpoint is None: + # draw ghost point + if self.mode == 'p': + plotstyle = rwth_plots.style_poles + else: + plotstyle = rwth_plots.style_zeros + + self.ghost_hpoint, = self.ax.plot(event.xdata, event.ydata, **plotstyle, alpha=.5) - def onkeyrelease(event): - if not self.action_locked: - self.w_action_type.children[0].value = 'Hinzufügen' + if np.abs(event.ydata) > 0: # plot complex conjugated poles/zeroes + self.ghost_hpoint_conj, = self.ax.plot(event.xdata, -event.ydata, **plotstyle, alpha=.5) + else: + self.ghost_hpoint.set_data(event.xdata, event.ydata) + self.ghost_hpoint_conj.set_data(event.xdata, -event.ydata) + + def on_click(event): + # update point + p = event.xdata + 1j * event.ydata + self.update_point(p, self.mode) - self.fig.canvas.mpl_connect('button_press_event', onclick) - self.fig.canvas.mpl_connect('key_press_event', onkeypress) - self.fig.canvas.mpl_connect('key_release_event', onkeyrelease) + self.fig.canvas.mpl_connect('button_press_event', on_btn_press) + self.fig.canvas.mpl_connect('button_release_event', on_btn_release) + self.fig.canvas.mpl_connect('motion_notify_event', on_motion) self.handles['axh'] = plt.subplot(gs[0, 1]) self.handles['axh'].set_title('Impulsantwort', fontsize='12') @@ -433,7 +503,7 @@ class pzPlot(): if not (roc[0] <= 0 <= roc[1]): S_f = np.full(self.f.shape, np.NaN) else: - S_f = rwth_transforms.ilaplace_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, dB=True) + S_f = rwth_transforms.ilaplace_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, dB=True).round(8) # process signals # delete existing dirac diff --git a/src/z_transform/z_transform.py b/src/z_transform/z_transform.py index 91449076817400576ad339d0831cc383279e6f8f..3fcee1a63777a290f3d65686cd04f24d1ea46e47 100644 --- a/src/z_transform/z_transform.py +++ b/src/z_transform/z_transform.py @@ -25,7 +25,7 @@ class pzPoint(): def clear(self): if self.h_point is not None: self.h_point.remove(); self.h_point = None if self.h_point_conj is not None: self.h_point_conj.remove(); self.h_point_conj = None - if self.h_order is not None: self.h_order.remove(); self.h_order = None + if self.h_order is not None: self.h_order[0].remove(); self.h_order[1].remove(); self.h_order = None class zPlot(): @@ -103,26 +103,99 @@ class zPlot(): self.ax.set_title('Pol- /Nullstellen Diagramm', fontsize='12') self.ax.set_aspect('equal') - def onclick(event): - if self.action == 'add' and event.key == 'shift': - self.action = 'del' + # * move indicates whether a drag is happening + # * selected_coords indicates selected coords when button is pressed + # * ghost_hpoint, ghost_hpoint_conj are the half transparent poles/zeroes to indicate where the newly created will be + self.move = False + self.selected_coords = None + self.ghost_hpoint = None + self.ghost_hpoint_conj = None + def on_btn_press(event): if event.inaxes != self.ax: return if self.filter != 'man': return + + # button press, reset move + self.move = False + + # find closest point and set selected if there are any valid points nearby p = event.xdata + 1j * event.ydata - self.update_point(p, self.mode) + self.selected_coords = p + + def on_btn_release(event): + if event.inaxes != self.ax: + if self.ghost_hpoint is not None: + self.ghost_hpoint.remove() + self.ghost_hpoint_conj.remove() + self.ghost_hpoint = None + self.ghost_hpoint_conj = None + return + if self.filter != 'man': return - def onkeypress(event): - self.action_locked = self.action == 'del' - self.w_action_type.children[0].value = 'Löschen' + # press + no movement + release = click + if not self.move: + on_click(event) + + # press + movement + release = drag + # if dragging process is finished + if self.move and self.find_closest_point(self.selected_coords, self.mode)[0] is not None: + # release move + self.move = False + + # remove any ghosts from the figure and clear variables + if self.ghost_hpoint is not None: + self.ghost_hpoint.remove() + self.ghost_hpoint_conj.remove() + self.ghost_hpoint = None + self.ghost_hpoint_conj = None + + # delete at press selected point + tmp_action = self.action + self.action = 'del' + self.update_point(self.selected_coords, self.mode) + + # add new point at drag point + self.action = 'add' + on_click(event) + self.action = tmp_action + + def on_motion(event): + if event.inaxes != self.ax: return + if self.filter != 'man': return - def onkeyrelease(event): - if not self.action_locked: - self.w_action_type.children[0].value = 'Hinzufügen' + # if button is pressed + if event.button == 1: + # lock move + self.move = True + + # if a point was selected + if self.find_closest_point(self.selected_coords, self.mode)[0] is not None: + if self.ghost_hpoint is None: + # draw ghost point + if self.mode == 'p': + plotstyle = rwth_plots.style_poles + else: + plotstyle = rwth_plots.style_zeros + + self.ghost_hpoint, = self.ax.plot(event.xdata, event.ydata, **plotstyle, alpha=.5) + + if np.abs(event.ydata) > 0: # plot complex conjugated poles/zeroes + self.ghost_hpoint_conj, = self.ax.plot(event.xdata, -event.ydata, **plotstyle, alpha=.5) + else: + self.ghost_hpoint.set_data(event.xdata, event.ydata) + self.ghost_hpoint_conj.set_data(event.xdata, -event.ydata) + + def on_click(event): + if self.filter != 'man': return + if event.inaxes != self.ax: return + + # update point + p = event.xdata + 1j * event.ydata + self.update_point(p, self.mode) - self.fig.canvas.mpl_connect('button_press_event', onclick) - self.fig.canvas.mpl_connect('key_press_event', onkeypress) - self.fig.canvas.mpl_connect('key_release_event', onkeyrelease) + self.fig.canvas.mpl_connect('button_press_event', on_btn_press) + self.fig.canvas.mpl_connect('button_release_event', on_btn_release) + self.fig.canvas.mpl_connect('motion_notify_event', on_motion) self.handles['axh'] = plt.subplot(gs[0, 1]) self.handles['axh'].set_xlim(-15, 15); @@ -442,11 +515,13 @@ class zPlot(): # update f-domain if self.systemIsStable: - H_f = np.abs(rwth_transforms.iz_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, True)) + H_f = np.abs(rwth_transforms.iz_Hf(self.f, self.H0, poles, zeroes, poles_order, zeroes_order, True)).round(8) + rwth_plots.update_ylim(self.handles['axH'], H_f, 1.9, ymax=1e5) else: - H_f = np.ones(self.f.shape) * -.5 + H_f = np.empty(self.f.shape) + H_f[:] = np.nan + rwth_plots.update_ylim(self.handles['axH'], [-0.5, 0.5], 0.1) self.stabilitytxt.set_visible(not self.systemIsStable) self.handles['lineH'].set_ydata(H_f) - rwth_plots.update_ylim(self.handles['axH'], H_f, 1.9, ymax=1e5)