From 914ec2d1d5469e7b5e878ac6edcf0fa02bbd48f4 Mon Sep 17 00:00:00 2001 From: Roberto Nobrega Date: Sun, 3 Nov 2024 21:54:44 -0300 Subject: [PATCH] Add demo support with Streamlit and remove legacy demo scripts --- README.md | 8 + demo/binary_sequences.ipynb | 193 --------------------- demo/binary_sequences.py | 93 ----------- demo/constellations.ipynb | 279 ------------------------------- demo/constellations.py | 197 ---------------------- demo/index.py | 53 ++++++ demo/jupyter_notebook_config.py | 13 -- demo/pages/1_Binary_sequences.py | 144 ++++++++++++++++ demo/pages/2_Constellations.py | 181 ++++++++++++++++++++ demo/pages/3_Pulse_formatting.py | 154 +++++++++++++++++ demo/pulse_formatting.ipynb | 153 ++--------------- demo/pulse_formatting.py | 113 ------------- demo/utils.py | 27 +++ pyproject.toml | 6 + 14 files changed, 586 insertions(+), 1028 deletions(-) delete mode 100644 demo/binary_sequences.ipynb delete mode 100644 demo/binary_sequences.py delete mode 100644 demo/constellations.ipynb delete mode 100644 demo/constellations.py create mode 100644 demo/index.py delete mode 100644 demo/jupyter_notebook_config.py create mode 100644 demo/pages/1_Binary_sequences.py create mode 100644 demo/pages/2_Constellations.py create mode 100644 demo/pages/3_Pulse_formatting.py delete mode 100644 demo/pulse_formatting.py create mode 100644 demo/utils.py diff --git a/README.md b/README.md index 8a546d4d..dc4de587 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,11 @@ The documentation is built using [MkDocs](https://www.mkdocs.org/) with the [Mat ```bash mkdocs serve ``` + +### Run demos + +There are some demos available in the `demo` directory. They are written using [Streamlit](https://streamlit.io/). To run them, execute: + +```bash +streamlit run demo/index.py +``` diff --git a/demo/binary_sequences.ipynb b/demo/binary_sequences.ipynb deleted file mode 100644 index f8987a21..00000000 --- a/demo/binary_sequences.ipynb +++ /dev/null @@ -1,193 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Komm demo: Binary sequences" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "import numpy as np\n", - "import matplotlib.pylab as plt\n", - "import ipywidgets\n", - "import komm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Barker sequence" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "e7a324769b09448ebb51c5354313130e", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(SelectionSlider(description='length', options=(2, 3, 4, 5, 7, 11, 13), value=2), Output(…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def barker_demo(length):\n", - " barker = komm.BarkerSequence(length=length)\n", - " shifts = np.arange(-2*length + 1, 2*length)\n", - " _, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4))\n", - " ax0.stem(np.arange(length), barker.polar_sequence)\n", - " ax0.set_title(repr(barker))\n", - " ax0.set_xlabel('$n$')\n", - " ax0.set_ylabel('$a[n]$')\n", - " ax0.set_xticks(np.arange(length))\n", - " ax0.set_yticks([-1, 0, 1])\n", - " ax1.stem(shifts, barker.autocorrelation(shifts))\n", - " ax1.set_title('Autocorrelation')\n", - " ax1.set_xlabel('$\\\\ell$')\n", - " ax1.set_ylabel('$R[\\\\ell]$')\n", - " ax1.set_xticks([-length, 0, length])\n", - " ax1.set_yticks(np.arange(-1, length + 1))\n", - " plt.show()\n", - "\n", - "ipywidgets.interact(barker_demo, length=ipywidgets.SelectionSlider(options=[2, 3, 4, 5, 7, 11, 13]));" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Walsh-Hadamard sequence" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "904394d8bf014b7b83444c85e9a8998b", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(SelectionSlider(description='length', options=(2, 4, 8, 16, 32, 64, 128), value=2), Drop…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def walsh_hadamard_demo(length, ordering, index):\n", - " walsh_hadamard = komm.WalshHadamardSequence(length=length, ordering=ordering, index=index)\n", - " ax = plt.axes()\n", - " ax.stem(np.arange(length), walsh_hadamard.polar_sequence)\n", - " ax.set_title(repr(walsh_hadamard))\n", - " ax.set_xlabel('$n$')\n", - " ax.set_ylabel('$a[n]$')\n", - " ax.set_yticks([-1, 0, 1])\n", - " ax.set_ylim([-1.2, 1.2])\n", - " plt.show()\n", - "\n", - "length_widget = ipywidgets.SelectionSlider(options=[2**i for i in range(1, 8)])\n", - "index_widget = ipywidgets.IntSlider(min=0, max=1, step=1, value=0)\n", - "\n", - "def update_index_widget(*args):\n", - " index_widget.max = length_widget.value - 1\n", - "length_widget.observe(update_index_widget, 'value')\n", - "\n", - "ipywidgets.interact(walsh_hadamard_demo, length=length_widget, ordering=['natural', 'sequency'], index=index_widget);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Linear-feedback shift register (LFSR) sequence -- Maximum-length sequence (MLS)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "fc3d24037759470cb64046480f9c769c", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(IntSlider(value=4, description='degree', max=7, min=2), Output()), _dom_classes=('widget…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def lfsr_demo(degree):\n", - " lfsr = komm.LFSRSequence.maximum_length_sequence(degree=degree)\n", - " length = lfsr.length\n", - " shifts = np.arange(-2*length + 1, 2*length)\n", - " _, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4))\n", - " ax0.stem(np.arange(length), lfsr.polar_sequence)\n", - " ax0.set_title(repr(lfsr))\n", - " ax0.set_xlabel('$n$')\n", - " ax0.set_ylabel('$a[n]$')\n", - " ax0.set_yticks([-1, 0, 1])\n", - " ax1.stem(shifts, lfsr.cyclic_autocorrelation(shifts, normalized=True))\n", - " ax1.set_title('Cyclic autocorrelation (normalized)')\n", - " ax1.set_xlabel('$\\\\ell$')\n", - " ax1.set_ylabel('$R[\\\\ell]$')\n", - " ax1.set_xticks([-length, 0, length])\n", - " ax1.set_ylim([-0.5, 1.1])\n", - " plt.show()\n", - "\n", - "ipywidgets.interact(lfsr_demo, degree=(2, 7));" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/demo/binary_sequences.py b/demo/binary_sequences.py deleted file mode 100644 index 7cbdffef..00000000 --- a/demo/binary_sequences.py +++ /dev/null @@ -1,93 +0,0 @@ - -# coding: utf-8 - -# # Komm demo: Binary sequences - -# In[1]: - - -get_ipython().run_line_magic('matplotlib', 'inline') - -import numpy as np -import matplotlib.pylab as plt -import ipywidgets -import komm - - -# ## Barker sequence - -# In[2]: - - -def barker_demo(length): - barker = komm.BarkerSequence(length=length) - shifts = np.arange(-2*length + 1, 2*length) - _, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4)) - ax0.stem(np.arange(length), barker.polar_sequence) - ax0.set_title(repr(barker)) - ax0.set_xlabel('$n$') - ax0.set_ylabel('$a[n]$') - ax0.set_xticks(np.arange(length)) - ax0.set_yticks([-1, 0, 1]) - ax1.stem(shifts, barker.autocorrelation(shifts)) - ax1.set_title('Autocorrelation') - ax1.set_xlabel('$\\ell$') - ax1.set_ylabel('$R[\\ell]$') - ax1.set_xticks([-length, 0, length]) - ax1.set_yticks(np.arange(-1, length + 1)) - plt.show() - -ipywidgets.interact(barker_demo, length=ipywidgets.SelectionSlider(options=[2, 3, 4, 5, 7, 11, 13])); - - -# ## Walsh-Hadamard sequence - -# In[3]: - - -def walsh_hadamard_demo(length, ordering, index): - walsh_hadamard = komm.WalshHadamardSequence(length=length, ordering=ordering, index=index) - ax = plt.axes() - ax.stem(np.arange(length), walsh_hadamard.polar_sequence) - ax.set_title(repr(walsh_hadamard)) - ax.set_xlabel('$n$') - ax.set_ylabel('$a[n]$') - ax.set_yticks([-1, 0, 1]) - ax.set_ylim([-1.2, 1.2]) - plt.show() - -length_widget = ipywidgets.SelectionSlider(options=[2**i for i in range(1, 8)]) -index_widget = ipywidgets.IntSlider(min=0, max=1, step=1, value=0) - -def update_index_widget(*args): - index_widget.max = length_widget.value - 1 -length_widget.observe(update_index_widget, 'value') - -ipywidgets.interact(walsh_hadamard_demo, length=length_widget, ordering=['natural', 'sequency'], index=index_widget); - - -# ## Linear-feedback shift register (LFSR) sequence -- Maximum-length sequence (MLS) - -# In[4]: - - -def lfsr_demo(degree): - lfsr = komm.LFSRSequence.maximum_length_sequence(degree=degree) - length = lfsr.length - shifts = np.arange(-2*length + 1, 2*length) - _, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4)) - ax0.stem(np.arange(length), lfsr.polar_sequence) - ax0.set_title(repr(lfsr)) - ax0.set_xlabel('$n$') - ax0.set_ylabel('$a[n]$') - ax0.set_yticks([-1, 0, 1]) - ax1.stem(shifts, lfsr.cyclic_autocorrelation(shifts, normalized=True)) - ax1.set_title('Cyclic autocorrelation (normalized)') - ax1.set_xlabel('$\\ell$') - ax1.set_ylabel('$R[\\ell]$') - ax1.set_xticks([-length, 0, length]) - ax1.set_ylim([-0.5, 1.1]) - plt.show() - -ipywidgets.interact(lfsr_demo, degree=(2, 7)); - diff --git a/demo/constellations.ipynb b/demo/constellations.ipynb deleted file mode 100644 index a1ba59ae..00000000 --- a/demo/constellations.ipynb +++ /dev/null @@ -1,279 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Komm demo: Constellations" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "import numpy as np\n", - "import matplotlib.pylab as plt\n", - "import ipywidgets\n", - "import komm" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "def constellation_demo(modulation, noise_power_db, xlim, ylim):\n", - " awgn = komm.AWGNChannel()\n", - "\n", - " num_symbols = 10000\n", - " noise_power = 10**(noise_power_db / 10)\n", - " awgn.signal_power = modulation.energy_per_symbol\n", - " awgn.snr = awgn.signal_power / noise_power\n", - " num_bits = modulation.bits_per_symbol * num_symbols\n", - " bits = np.random.randint(2, size=num_bits)\n", - " sentword = modulation.modulate(bits)\n", - " recvword = awgn(sentword)\n", - "\n", - " _, ax = plt.subplots(figsize=(16, 10))\n", - " ax.scatter(recvword.real, recvword.imag, color='xkcd:light blue', s=1)\n", - " ax.scatter(modulation.constellation.real, modulation.constellation.imag, color='xkcd:blue', s=8**2)\n", - " for (i, point) in enumerate(modulation.constellation):\n", - " binary_label = ''.join(str(b) for b in komm.int2binlist(modulation.labeling[i], width=modulation.bits_per_symbol))\n", - " ax.text(point.real, point.imag + 0.075 * xlim[0], binary_label, horizontalalignment='center')\n", - " ax.set_title(repr(modulation))\n", - " ax.set_xlabel('Re')\n", - " ax.set_ylabel('Im')\n", - " ax.axis('square')\n", - " ax.set_xlim(xlim)\n", - " ax.set_ylim(ylim)\n", - " ax.grid()\n", - " info_text = 'SNR = {:.1f} dB\\n'.format(10*np.log10(awgn.snr))\n", - " info_text += 'Eb/N0 = {:.1f} dB'.format(10*np.log10(awgn.snr / modulation.bits_per_symbol))\n", - " ax.text(1.125 * xlim[1], 0.0, info_text, horizontalalignment='left', verticalalignment='center')\n", - " plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Phase-shift keying (PSK)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": { - "scrolled": false - }, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "2b6693ca78054f07980a3f7789ab1de3", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(HBox(children=(SelectionSlider(continuous_update=False, description='Order:', options=(2, 4, 8,…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def psk_constellation_demo(order, amplitude, phase_offset, labeling, noise_power_db):\n", - " psk_modulation = komm.PSKModulation(order, amplitude, phase_offset, labeling)\n", - " constellation_demo(psk_modulation, noise_power_db, xlim=[-3.0, 3.0], ylim=[-3.0, 3.0])\n", - "\n", - "order_widget = ipywidgets.SelectionSlider(\n", - " options=[2, 4, 8, 16, 32],\n", - " continuous_update=False,\n", - " description='Order:',\n", - ")\n", - "\n", - "amplitude_widget = ipywidgets.FloatSlider(\n", - " min=0.1,\n", - " max=2.01,\n", - " step=0.1,\n", - " value=1.0,\n", - " continuous_update=False,\n", - " description='Amplitude:',\n", - ")\n", - "\n", - "phase_offset_widget = ipywidgets.SelectionSlider(\n", - " options=[('{:.2f}π'.format(x), np.pi*x) for x in np.arange(0.0, 2.001, step=0.01)],\n", - " value=0.0,\n", - " continuous_update=False,\n", - " description='Phase offset:',\n", - ")\n", - "\n", - "labeling_widget = ipywidgets.Dropdown(\n", - " options={'Natural': 'natural', 'Reflected (Gray)': 'reflected'},\n", - " value='reflected',\n", - " description='Labeling:',\n", - ")\n", - "\n", - "noise_power_db_widget = ipywidgets.FloatSlider(\n", - " value=-40.0,\n", - " min=-40.0,\n", - " max=10.0,\n", - " step=1.0,\n", - " continuous_update=False,\n", - " description='Noise power (dB):',\n", - ")\n", - "\n", - "interactive_output = ipywidgets.interactive_output(\n", - " psk_constellation_demo,\n", - " dict(\n", - " order=order_widget,\n", - " amplitude=amplitude_widget,\n", - " phase_offset=phase_offset_widget,\n", - " labeling=labeling_widget,\n", - " noise_power_db=noise_power_db_widget,\n", - " ),\n", - ")\n", - "\n", - "ipywidgets.VBox(\n", - " [\n", - " ipywidgets.HBox(\n", - " [\n", - " order_widget,\n", - " amplitude_widget,\n", - " phase_offset_widget,\n", - " labeling_widget,\n", - " ]\n", - " ),\n", - " noise_power_db_widget,\n", - " interactive_output,\n", - " ],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Quadrature Amplitude Modulation (QAM)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b52ada03c2e34d3ba254b0e085ac7298", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "VBox(children=(HBox(children=(SelectionSlider(continuous_update=False, description='Order:', options=(4, 16, 6…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def qam_constellation_demo(order, base_amplitude, phase_offset, labeling, noise_power_db):\n", - " qam_modulation = komm.QAModulation(order, base_amplitude, phase_offset, labeling)\n", - " lim = [-2.125*np.sqrt(order), 2.125*np.sqrt(order)]\n", - " constellation_demo(qam_modulation, noise_power_db, xlim=lim, ylim=lim)\n", - "\n", - "order_widget = ipywidgets.SelectionSlider(\n", - " options=[4, 16, 64, 256],\n", - " continuous_update=False,\n", - " description='Order:',\n", - ")\n", - "\n", - "base_amplitude_widget = ipywidgets.FloatSlider(\n", - " min=0.1,\n", - " max=2.01,\n", - " step=0.1,\n", - " value=1.0,\n", - " continuous_update=False,\n", - " description='Base amplitude:',\n", - ")\n", - "\n", - "phase_offset_widget = ipywidgets.SelectionSlider(\n", - " options=[('{:.2f}π'.format(x), np.pi*x) for x in np.arange(0.0, 2.001, step=0.01)],\n", - " value=0.0,\n", - " continuous_update=False,\n", - " description='Phase offset:',\n", - ")\n", - "\n", - "labeling_widget = ipywidgets.Dropdown(\n", - " options={'Natural': 'natural', 'Reflected 2D (Gray)': 'reflected_2d'},\n", - " value='reflected_2d',\n", - " description='Labeling:',\n", - ")\n", - "\n", - "\n", - "noise_power_db_widget = ipywidgets.FloatSlider(\n", - " value=-40.0,\n", - " min=-40.0,\n", - " max=10.0,\n", - " step=1.0,\n", - " continuous_update=False,\n", - " description='Noise power (dB):',\n", - ")\n", - "\n", - "interactive_output = ipywidgets.interactive_output(\n", - " qam_constellation_demo,\n", - " dict(\n", - " order=order_widget,\n", - " base_amplitude=base_amplitude_widget,\n", - " phase_offset=phase_offset_widget,\n", - " labeling=labeling_widget,\n", - " noise_power_db=noise_power_db_widget,\n", - " ),\n", - ")\n", - "\n", - "ipywidgets.VBox(\n", - " [\n", - " ipywidgets.HBox(\n", - " [\n", - " order_widget,\n", - " base_amplitude_widget,\n", - " phase_offset_widget,\n", - " labeling_widget,\n", - " ]\n", - " ),\n", - " noise_power_db_widget,\n", - " interactive_output,\n", - " ],\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.7.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/demo/constellations.py b/demo/constellations.py deleted file mode 100644 index 493c99e0..00000000 --- a/demo/constellations.py +++ /dev/null @@ -1,197 +0,0 @@ - -# coding: utf-8 - -# # Komm demo: Constellations - -# In[1]: - - -get_ipython().run_line_magic('matplotlib', 'inline') - -import numpy as np -import matplotlib.pylab as plt -import ipywidgets -import komm - - -# In[2]: - - -def constellation_demo(modulation, noise_power_db, xlim, ylim): - awgn = komm.AWGNChannel() - - num_symbols = 10000 - noise_power = 10**(noise_power_db / 10) - awgn.signal_power = modulation.energy_per_symbol - awgn.snr = awgn.signal_power / noise_power - num_bits = modulation.bits_per_symbol * num_symbols - bits = np.random.randint(2, size=num_bits) - sentword = modulation.modulate(bits) - recvword = awgn(sentword) - - _, ax = plt.subplots(figsize=(16, 10)) - ax.scatter(recvword.real, recvword.imag, color='xkcd:light blue', s=1) - ax.scatter(modulation.constellation.real, modulation.constellation.imag, color='xkcd:blue', s=8**2) - for (i, point) in enumerate(modulation.constellation): - binary_label = ''.join(str(b) for b in komm.int2binlist(modulation.labeling[i], width=modulation.bits_per_symbol)) - ax.text(point.real, point.imag + 0.075 * xlim[0], binary_label, horizontalalignment='center') - ax.set_title(repr(modulation)) - ax.set_xlabel('Re') - ax.set_ylabel('Im') - ax.axis('square') - ax.set_xlim(xlim) - ax.set_ylim(ylim) - ax.grid() - info_text = 'SNR = {:.1f} dB\n'.format(10*np.log10(awgn.snr)) - info_text += 'Eb/N0 = {:.1f} dB'.format(10*np.log10(awgn.snr / modulation.bits_per_symbol)) - ax.text(1.125 * xlim[1], 0.0, info_text, horizontalalignment='left', verticalalignment='center') - plt.show() - - -# ## Phase-shift keying (PSK) - -# In[3]: - - -def psk_constellation_demo(order, amplitude, phase_offset, labeling, noise_power_db): - psk_modulation = komm.PSKModulation(order, amplitude, phase_offset, labeling) - constellation_demo(psk_modulation, noise_power_db, xlim=[-3.0, 3.0], ylim=[-3.0, 3.0]) - -order_widget = ipywidgets.SelectionSlider( - options=[2, 4, 8, 16, 32], - continuous_update=False, - description='Order:', -) - -amplitude_widget = ipywidgets.FloatSlider( - min=0.1, - max=2.01, - step=0.1, - value=1.0, - continuous_update=False, - description='Amplitude:', -) - -phase_offset_widget = ipywidgets.SelectionSlider( - options=[('{:.2f}π'.format(x), np.pi*x) for x in np.arange(0.0, 2.001, step=0.01)], - value=0.0, - continuous_update=False, - description='Phase offset:', -) - -labeling_widget = ipywidgets.Dropdown( - options={'Natural': 'natural', 'Reflected (Gray)': 'reflected'}, - value='reflected', - description='Labeling:', -) - -noise_power_db_widget = ipywidgets.FloatSlider( - value=-40.0, - min=-40.0, - max=10.0, - step=1.0, - continuous_update=False, - description='Noise power (dB):', -) - -interactive_output = ipywidgets.interactive_output( - psk_constellation_demo, - dict( - order=order_widget, - amplitude=amplitude_widget, - phase_offset=phase_offset_widget, - labeling=labeling_widget, - noise_power_db=noise_power_db_widget, - ), -) - -ipywidgets.VBox( - [ - ipywidgets.HBox( - [ - order_widget, - amplitude_widget, - phase_offset_widget, - labeling_widget, - ] - ), - noise_power_db_widget, - interactive_output, - ], -) - - -# ## Quadrature Amplitude Modulation (QAM) - -# In[4]: - - -def qam_constellation_demo(order, base_amplitude, phase_offset, labeling, noise_power_db): - qam_modulation = komm.QAModulation(order, base_amplitude, phase_offset, labeling) - lim = [-2.125*np.sqrt(order), 2.125*np.sqrt(order)] - constellation_demo(qam_modulation, noise_power_db, xlim=lim, ylim=lim) - -order_widget = ipywidgets.SelectionSlider( - options=[4, 16, 64, 256], - continuous_update=False, - description='Order:', -) - -base_amplitude_widget = ipywidgets.FloatSlider( - min=0.1, - max=2.01, - step=0.1, - value=1.0, - continuous_update=False, - description='Base amplitude:', -) - -phase_offset_widget = ipywidgets.SelectionSlider( - options=[('{:.2f}π'.format(x), np.pi*x) for x in np.arange(0.0, 2.001, step=0.01)], - value=0.0, - continuous_update=False, - description='Phase offset:', -) - -labeling_widget = ipywidgets.Dropdown( - options={'Natural': 'natural', 'Reflected 2D (Gray)': 'reflected_2d'}, - value='reflected_2d', - description='Labeling:', -) - - -noise_power_db_widget = ipywidgets.FloatSlider( - value=-40.0, - min=-40.0, - max=10.0, - step=1.0, - continuous_update=False, - description='Noise power (dB):', -) - -interactive_output = ipywidgets.interactive_output( - qam_constellation_demo, - dict( - order=order_widget, - base_amplitude=base_amplitude_widget, - phase_offset=phase_offset_widget, - labeling=labeling_widget, - noise_power_db=noise_power_db_widget, - ), -) - -ipywidgets.VBox( - [ - ipywidgets.HBox( - [ - order_widget, - base_amplitude_widget, - phase_offset_widget, - labeling_widget, - ] - ), - noise_power_db_widget, - interactive_output, - ], -) - diff --git a/demo/index.py b/demo/index.py new file mode 100644 index 00000000..cff4ae23 --- /dev/null +++ b/demo/index.py @@ -0,0 +1,53 @@ +import streamlit as st + +# Local import +from utils import show_about + +st.set_page_config(page_title="Komm Demo", layout="wide") + + +st.title("Komm Demo") + +st.markdown( + """ + This interactive demo showcases various features of the Komm library, a toolkit for analysis and simulation of analog and digital communication systems. +""" +) + +st.header("Available demos") + +col1, col2 = st.columns(2) + +with col1: + st.subheader("1. Binary sequences") + st.markdown( + """ + Explore different types of binary sequences commonly used in communications: + - Barker sequencesfrom st_pages import Page, add_page_title, show_pages + - Walsh-Hadamard sequences + - Linear-feedback shift register (LFSR) sequences + """ + ) + + st.subheader("2. Constellations") + st.markdown( + """ + Interactive visualization of digital modulation constellations: + - Phase-shift keying (PSK) + - Quadrature amplitude modulation (QAM) + """ + ) + +with col2: + st.subheader("3. Pulse formatting") + st.markdown( + """ + Visualize various pulse shaping techniques: + - Sinc pulse + - Raised cosine pulse + - Gaussian pulse + """ + ) + + +show_about() diff --git a/demo/jupyter_notebook_config.py b/demo/jupyter_notebook_config.py deleted file mode 100644 index f1359fdc..00000000 --- a/demo/jupyter_notebook_config.py +++ /dev/null @@ -1,13 +0,0 @@ -# Reference: https://svds.com/jupyter-notebook-best-practices-for-data-science/ -import os -from subprocess import check_call - -def post_save(model, os_path, contents_manager): - """post-save hook for converting notebooks to .py scripts""" - if model['type'] != 'notebook': - return # only do this for notebooks - d, fname = os.path.split(os_path) - check_call(['jupyter', 'nbconvert', '--to', 'script', fname], cwd=d) - #check_call(['jupyter', 'nbconvert', '--to', 'html', fname], cwd=d) - -c.FileContentsManager.post_save_hook = post_save diff --git a/demo/pages/1_Binary_sequences.py b/demo/pages/1_Binary_sequences.py new file mode 100644 index 00000000..3b2e12bf --- /dev/null +++ b/demo/pages/1_Binary_sequences.py @@ -0,0 +1,144 @@ +import matplotlib.pyplot as plt +import numpy as np +import streamlit as st + +# Local import +from utils import show_about, show_code + +import komm + +st.set_page_config(page_title="Komm Demo: Binary Sequences", layout="wide") + + +def plot_barker(length): + barker = komm.BarkerSequence(length=length) + shifts = np.arange(-2 * length + 1, 2 * length) + + fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4)) + + ax0.stem(np.arange(length), barker.polar_sequence) + ax0.set_title(repr(barker)) + ax0.set_xlabel("$n$") + ax0.set_ylabel("$a[n]$") + ax0.set_xticks(np.arange(length)) + ax0.set_yticks([-1, 0, 1]) + + ax1.stem(shifts, barker.autocorrelation(shifts)) + ax1.set_title("Autocorrelation") + ax1.set_xlabel("$\\ell$") + ax1.set_ylabel("$R[\\ell]$") + ax1.set_xticks([-length, 0, length]) + ax1.set_yticks(np.arange(-1, length + 1)) + + return fig + + +def plot_walsh_hadamard(length, ordering, index): + walsh_hadamard = komm.WalshHadamardSequence( + length=length, + ordering=ordering, + index=index, + ) + + fig, ax = plt.subplots(figsize=(12, 4)) + ax.stem(np.arange(length), walsh_hadamard.polar_sequence) + ax.set_title(repr(walsh_hadamard)) + ax.set_xlabel("$n$") + ax.set_ylabel("$a[n]$") + ax.set_yticks([-1, 0, 1]) + ax.set_ylim((-1.2, 1.2)) + + return fig + + +def plot_lfsr(degree): + lfsr = komm.LFSRSequence.maximum_length_sequence(degree=degree) + length = lfsr.length + shifts = np.arange(-2 * length + 1, 2 * length) + + fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(12, 4)) + + ax0.stem(np.arange(length), lfsr.polar_sequence) + ax0.set_title(repr(lfsr)) + ax0.set_xlabel("$n$") + ax0.set_ylabel("$a[n]$") + ax0.set_yticks([-1, 0, 1]) + + ax1.stem(shifts, lfsr.cyclic_autocorrelation(shifts, normalized=True)) + ax1.set_title("Cyclic autocorrelation (normalized)") + ax1.set_xlabel("$\\ell$") + ax1.set_ylabel("$\\tilde{R}[\\ell]$") + ax1.set_xticks([-length, 0, length]) + ax1.set_ylim((-0.5, 1.1)) + + return fig + + +st.sidebar.title("Sequence type") +sequence_type = st.sidebar.radio( + "Select sequence type:", + ["Barker", "Walsh–Hadamard", "LFSR"], + label_visibility="collapsed", +) + +if sequence_type == "Barker": + st.title("Barker sequence") + + length = st.select_slider( + label="Length", + options=[2, 3, 4, 5, 7, 11, 13], + value=2, + ) + + st.pyplot(plot_barker(length)) + + with st.expander("Source code"): + st.code(show_code(plot_barker), language="python") + +elif sequence_type == "Walsh–Hadamard": + st.title("Walsh–Hadamard sequence") + + col1, col2, col3 = st.columns(3) + with col1: + length = st.select_slider( + label="Length", + options=[2**i for i in range(1, 8)], + value=2, + ) + with col2: + ordering = st.selectbox( + label="Ordering", + options=["natural", "sequency"], + index=0, + ) + with col3: + index = st.slider( + label="Index", + min_value=0, + max_value=length - 1, + value=0, + ) + + st.pyplot(plot_walsh_hadamard(length, ordering, index)) + + with st.expander("Source code"): + st.code(show_code(plot_walsh_hadamard), language="python") + +else: # LFSR Sequence + st.title("Linear-feedback shift register (LFSR) sequence") + st.header("Maximum-length sequence (MLS)") + + degree = st.slider( + label="Degree", + min_value=2, + max_value=7, + value=4, + ) + + st.pyplot(plot_lfsr(degree)) + + with st.expander("Source code"): + st.code(show_code(plot_lfsr), language="python") + +st.sidebar.divider() +show_about() diff --git a/demo/pages/2_Constellations.py b/demo/pages/2_Constellations.py new file mode 100644 index 00000000..bf380534 --- /dev/null +++ b/demo/pages/2_Constellations.py @@ -0,0 +1,181 @@ +import matplotlib.pyplot as plt +import numpy as np +import streamlit as st + +# Local import +from utils import show_about, show_code + +import komm + +st.set_page_config(page_title="Komm Demo: Constellations", layout="wide") + + +def plot_constellation(modulation, noise_power_db, xlim, ylim): + """Base function for plotting constellation diagrams.""" + signal_power = modulation.energy_per_symbol + noise_power = 10 ** (noise_power_db / 10) + snr = signal_power / noise_power + awgn = komm.AWGNChannel(signal_power=signal_power, snr=snr) + + num_symbols = 10000 + num_bits = modulation.bits_per_symbol * num_symbols + bits = np.random.randint(2, size=num_bits) + sentword = modulation.modulate(bits) + recvword = awgn(sentword) + + fig, ax = plt.subplots(figsize=(16, 10)) + ax.grid(linestyle="--") + + ax.scatter(recvword.real, recvword.imag, color="xkcd:light blue", s=1) + ax.scatter( + modulation.constellation.real, + modulation.constellation.imag, + color="xkcd:blue", + s=8**2, + ) + + for i, point in enumerate(modulation.constellation): + label = "".join(str(b) for b in modulation.labeling[i]) + ax.text( + point.real, + point.imag + 0.075 * xlim[0], + label, + horizontalalignment="center", + ) + + ax.set_title(repr(modulation)) + ax.set_xlabel("Re") + ax.set_ylabel("Im") + ax.axis("square") + ax.set_xlim(xlim) + ax.set_ylim(ylim) + + info_text = f"SNR = {10*np.log10(awgn.snr):.1f} dB\n" + info_text += f"Eb/N0 = {10*np.log10(awgn.snr / modulation.bits_per_symbol):.1f} dB" + ax.text( + 1.125 * xlim[1], + 0.0, + info_text, + horizontalalignment="left", + verticalalignment="center", + ) + + return fig + + +def plot_psk(order, amplitude, phase_offset, labeling, noise_power_db): + """Plot PSK constellation.""" + psk_modulation = komm.PSKModulation(order, amplitude, phase_offset, labeling) + return plot_constellation( + psk_modulation, noise_power_db, xlim=[-3.0, 3.0], ylim=[-3.0, 3.0] + ) + + +def plot_qam(order, base_amplitude, phase_offset, labeling, noise_power_db): + """Plot QAM constellation.""" + qam_modulation = komm.QAModulation(order, base_amplitude, phase_offset, labeling) + lim = [-2.125 * np.sqrt(order), 2.125 * np.sqrt(order)] + return plot_constellation(qam_modulation, noise_power_db, xlim=lim, ylim=lim) + + +st.sidebar.title("Constellation type") +constellation_type = st.sidebar.radio( + "Select constellation type:", + ["PSK", "QAM"], + label_visibility="collapsed", +) + +if constellation_type == "PSK": + st.title("Phase-shift keying (PSK)") + + col1, col2 = st.columns([3, 5]) + with col1: + order = st.select_slider( + label="Order", + options=[2, 4, 8, 16, 32], + value=4, + ) + amplitude = st.slider( + label="Amplitude", + min_value=0.1, + max_value=2.0, + value=1.0, + step=0.1, + ) + phase_offset = st.slider( + label="Phase offset (2π rad)", + min_value=0.0, + max_value=1.0, + value=0.0, + step=0.01, + ) * (2 * np.pi) + labeling = st.selectbox( + label="Labeling", + options=["natural", "reflected"], + format_func=lambda x: "Natural" if x == "natural" else "Reflected (Gray)", + index=1, + ) + noise_power_db = st.slider( + label="Noise power (dB)", + min_value=-40, + max_value=10, + value=-40, + step=1, + ) + + with col2: + st.pyplot(plot_psk(order, amplitude, phase_offset, labeling, noise_power_db)) + + with st.expander("Source code"): + st.code(show_code(plot_psk), language="python") + +else: # QAM + st.title("Quadrature amplitude modulation (QAM)") + + col1, col2 = st.columns([3, 5]) + with col1: + order = st.select_slider( + label="Order", + options=[4, 16, 64, 256], + value=16, + ) + base_amplitude = st.slider( + label="Base amplitude", + min_value=0.1, + max_value=2.0, + value=1.0, + step=0.1, + ) + phase_offset = st.slider( + label="Phase offset (2π rad)", + min_value=0.0, + max_value=2.0, + value=0.0, + step=0.01, + ) * (2 * np.pi) + labeling = st.selectbox( + label="Labeling", + options=["natural", "reflected_2d"], + format_func=lambda x: ( + "Natural" if x == "natural" else "Reflected 2D (Gray)" + ), + index=1, + ) + noise_power_db = st.slider( + label="Noise power (dB)", + min_value=-40.0, + max_value=10.0, + value=-40.0, + step=1.0, + ) + + with col2: + st.pyplot( + plot_qam(order, base_amplitude, phase_offset, labeling, noise_power_db) + ) + + with st.expander("Source code"): + st.code(show_code(plot_qam), language="python") + +st.sidebar.divider() +show_about() diff --git a/demo/pages/3_Pulse_formatting.py b/demo/pages/3_Pulse_formatting.py new file mode 100644 index 00000000..160fd1f8 --- /dev/null +++ b/demo/pages/3_Pulse_formatting.py @@ -0,0 +1,154 @@ +import matplotlib.pyplot as plt +import numpy as np +import streamlit as st + +# Local import +from utils import show_about, show_code + +import komm + +st.set_page_config(page_title="Komm Demo: Pulse Formatting", layout="wide") + + +def plot_sinc(): + pulse = komm.SincPulse(length_in_symbols=20) + h = pulse.impulse_response + H = pulse.frequency_response + t = np.linspace(-8.0, 8.0, 1000) + f = np.linspace(-1.5, 1.5, 200) + + fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5)) + ax0.plot(t, h(t), "b") + ax0.axis([-7.1, 7.1, -0.3, 1.1]) + ax0.set_title("Sinc pulse (waveform)") + ax0.set_xlabel("$t$") + ax0.set_ylabel("$h(t)$") + ax0.grid() + + ax1.plot(f, H(f), "r") + ax1.axis([-1.1, 1.1, -0.1, 1.1]) + ax1.set_title("Sinc pulse (spectrum)") + ax1.set_xlabel("$f$") + ax1.set_ylabel("$H(f)$") + ax1.grid() + + return fig + + +def plot_raised_cosine(rolloff): + pulse = komm.RaisedCosinePulse(rolloff, length_in_symbols=16) + h = pulse.impulse_response + H = pulse.frequency_response + t = np.linspace(-8.0, 8.0, 1000) + f = np.linspace(-1.5, 1.5, 200) + + fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5)) + ax0.plot(t, h(t), "b") + ax0.axis([-7.1, 7.1, -0.3, 1.1]) + ax0.set_title("Raised cosine pulse (waveform)") + ax0.set_xlabel("$t$") + ax0.set_ylabel("$h(t)$") + ax0.grid() + + ax1.plot(f, H(f), "r") + ax1.axis([-1.1, 1.1, -0.1, 1.1]) + ax1.set_title("Raised cosine pulse (spectrum)") + ax1.set_xlabel("$f$") + ax1.set_ylabel("$H(f)$") + ax1.grid() + + return fig + + +def plot_gaussian(half_power_bandwidth): + pulse = komm.GaussianPulse(half_power_bandwidth, length_in_symbols=4) + h = pulse.impulse_response + H = pulse.frequency_response + t = np.linspace(-8.0, 8.0, 1000) + f = np.linspace(-4.0, 4.0, 500) + + fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5)) + ax0.plot(t, h(t), "b") + ax0.axis([-7.1, 7.1, -0.1, 1.1]) + ax0.set_title("Gaussian pulse (waveform)") + ax0.set_xlabel("$t$") + ax0.set_ylabel("$h(t)$") + ax0.grid() + + ax1.plot(f, H(f), "r") + ax1.plot( + [-4.0, 4.0], + [H(0) / np.sqrt(2), H(0) / np.sqrt(2)], + linestyle="dashed", + color="gray", + ) + ax1.plot( + [half_power_bandwidth, half_power_bandwidth], + [-0.1 * H(0), 1.1 * H(0)], + linestyle="dashed", + color="gray", + ) + ax1.plot( + [-half_power_bandwidth, -half_power_bandwidth], + [-0.1 * H(0), 1.1 * H(0)], + linestyle="dashed", + color="gray", + ) + ax1.axis([-2.0, 2.0, -0.1 * H(0), 1.1 * H(0)]) + ax1.set_title("Gaussian pulse (spectrum)") + ax1.set_xlabel("$f$") + ax1.set_ylabel("$H(f)$") + ax1.grid() + + return fig + + +st.sidebar.title("Pulse type") +pulse_type = st.sidebar.radio( + "Select pulse type:", + ["Sinc", "Raised cosine", "Gaussian"], + label_visibility="collapsed", +) + +if pulse_type == "Sinc": + st.title("Sinc pulse") + + st.pyplot(plot_sinc()) + + with st.expander("Source code"): + st.code(show_code(plot_sinc), language="python") + +elif pulse_type == "Raised cosine": + st.title("Raised cosine pulse") + + rolloff = st.slider( + label="Roll-off factor", + min_value=0.0, + max_value=1.0, + value=0.5, + step=0.01, + ) + + st.pyplot(plot_raised_cosine(rolloff)) + + with st.expander("Source code"): + st.code(show_code(plot_raised_cosine), language="python") + +else: # Gaussian + st.title("Gaussian pulse") + + half_power_bandwidth = st.slider( + label="Half-power bandwidth", + min_value=0.05, + max_value=1.0, + value=0.5, + step=0.01, + ) + + st.pyplot(plot_gaussian(half_power_bandwidth)) + + with st.expander("Source code"): + st.code(show_code(plot_gaussian), language="python") + +st.sidebar.divider() +show_about() diff --git a/demo/pulse_formatting.ipynb b/demo/pulse_formatting.ipynb index 0e7d618e..1d25a320 100644 --- a/demo/pulse_formatting.ipynb +++ b/demo/pulse_formatting.ipynb @@ -4,39 +4,18 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Komm demo: Pulse formatting" + "# Sinc pulse (zero ISI)" ] }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "\n", - "import numpy as np\n", - "import matplotlib.pylab as plt\n", - "import ipywidgets\n", - "import komm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Sinc pulse (zero ISI)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "be5ff1c0e3b34495826cb4f74d42d726", + "model_id": "12012896aefa43eea49b294b430cf5ab", "version_major": 2, "version_minor": 0 }, @@ -49,8 +28,15 @@ } ], "source": [ + "%matplotlib inline\n", + "\n", + "import numpy as np\n", + "import matplotlib.pylab as plt\n", + "import ipywidgets\n", + "import komm\n", + "\n", "def sinc_demo(show_individual, show_signal):\n", - " info = [1, -1, 1, 1, -1, -1, 1]\n", + " info = np.array([1, -1, 1, 1, -1, -1, 1])\n", " pulse = komm.SincPulse(length_in_symbols=20)\n", " t0, t1 = pulse.interval\n", " tx_filter = komm.TransmitFilter(pulse, samples_per_symbol=32)\n", @@ -67,126 +53,13 @@ " ax.set_xlabel('$t$')\n", " ax.set_ylabel('$s(t)$')\n", " ax.set_xticks(np.arange(-2.0, 11.0))\n", - " ax.set_xlim([-2.0, 10.0])\n", - " ax.set_ylim([-1.75, 1.75])\n", + " ax.set_xlim((-2.0, 10.0))\n", + " ax.set_ylim((-1.75, 1.75))\n", " ax.grid()\n", " plt.show()\n", "\n", "ipywidgets.interact(sinc_demo, show_individual=False, show_signal=False);" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Raised cosine pulse" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4c3d27bad03946c288623fae0759bb79", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(FloatSlider(value=0.5, description='rolloff', max=1.0), Output()), _dom_classes=('widget…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def raised_cosine_demo(rolloff):\n", - " pulse = komm.RaisedCosinePulse(rolloff, length_in_symbols=20)\n", - " h = pulse.impulse_response\n", - " H = pulse.frequency_response\n", - " t = np.linspace(-8.0, 8.0, 1000)\n", - " f = np.linspace(-1.5, 1.5, 200)\n", - "\n", - " _, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5))\n", - " ax0.plot(t, h(t), 'b')\n", - " ax0.axis([-7.1, 7.1, -.3, 1.1])\n", - " ax0.set_title('Raised cosine pulse (waveform)')\n", - " ax0.set_xlabel('$t$')\n", - " ax0.set_ylabel('$h(t)$')\n", - " ax0.grid()\n", - " ax1.plot(f, H(f), 'r')\n", - " ax1.axis([-1.1, 1.1, -.1, 1.1])\n", - " ax1.set_title('Raised cosine pulse (spectrum)')\n", - " ax1.set_xlabel('$f$')\n", - " ax1.set_ylabel('$H(f)$')\n", - " ax1.grid()\n", - " plt.show()\n", - "\n", - "rolloff_widget = ipywidgets.FloatSlider(min=0, max=1.0, step=0.1, value=0.5)\n", - "\n", - "ipywidgets.interact(raised_cosine_demo, rolloff=rolloff_widget);" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Gaussian pulse" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "ba24bc0ad05c4f129ccda5d8d64ff85e", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "interactive(children=(FloatSlider(value=0.5, description='half_power_bandwidth', max=1.0, min=0.05, step=0.01)…" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "def gaussian_pulse_demo(half_power_bandwidth):\n", - " pulse = komm.GaussianPulse(half_power_bandwidth, length_in_symbols=4)\n", - " h = pulse.impulse_response\n", - " H = pulse.frequency_response\n", - " t = np.linspace(-8.0, 8.0, 1000)\n", - " f = np.linspace(-4.0, 4.0, 500)\n", - "\n", - " _, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5))\n", - " ax0.plot(t, h(t), 'b')\n", - " ax0.axis([-7.1, 7.1, -.1, 1.1])\n", - " ax0.set_title('Gaussian pulse (waveform)')\n", - " ax0.set_xlabel('$t$')\n", - " ax0.set_ylabel('$h(t)$')\n", - " ax0.grid()\n", - " ax1.plot(f, H(f), 'r')\n", - " ax1.plot([-4.0, 4.0], [H(0) / np.sqrt(2), H(0) / np.sqrt(2)], linestyle='dashed', color='gray')\n", - " ax1.plot([half_power_bandwidth, half_power_bandwidth], [-0.1*H(0), 1.1*H(0)], linestyle='dashed', color='gray')\n", - " ax1.plot([-half_power_bandwidth, -half_power_bandwidth], [-0.1*H(0), 1.1*H(0)], linestyle='dashed', color='gray')\n", - " ax1.axis([-2.0, 2.0, -0.1*H(0), 1.1*H(0)])\n", - " ax1.set_title('Gaussian pulse (spectrum)')\n", - " ax1.set_xlabel('$f$')\n", - " ax1.set_ylabel('$H(f)$')\n", - " ax1.grid()\n", - " plt.show()\n", - "\n", - "half_power_bandwidth_widget = ipywidgets.FloatSlider(min=0.05, max=1.0, step=0.01, value=0.5)\n", - "\n", - "ipywidgets.interact(gaussian_pulse_demo, half_power_bandwidth=half_power_bandwidth_widget);" - ] } ], "metadata": { diff --git a/demo/pulse_formatting.py b/demo/pulse_formatting.py deleted file mode 100644 index 2b1f532a..00000000 --- a/demo/pulse_formatting.py +++ /dev/null @@ -1,113 +0,0 @@ - -# coding: utf-8 - -# # Komm demo: Pulse formatting - -# In[1]: - - -get_ipython().run_line_magic('matplotlib', 'inline') - -import numpy as np -import matplotlib.pylab as plt -import ipywidgets -import komm - - -# ## Sinc pulse (zero ISI) - -# In[2]: - - -def sinc_demo(show_individual, show_signal): - info = [1, -1, 1, 1, -1, -1, 1] - pulse = komm.SincPulse(length_in_symbols=20) - t0, t1 = pulse.interval - tx_filter = komm.TransmitFilter(pulse, samples_per_symbol=32) - signal = tx_filter(info) - t = np.arange(t0, t1 + len(info) - 1, step=1/tx_filter.samples_per_symbol) - - _, ax = plt.subplots(figsize=(16, 10)) - if show_individual: - for k, a in enumerate(info): - ax.plot(t, a*pulse.impulse_response(t - k), 'k--') - if show_signal: - ax.plot(t, signal, 'b', linewidth=3) - ax.stem(info, linefmt='r', markerfmt='ro') - ax.set_xlabel('$t$') - ax.set_ylabel('$s(t)$') - ax.set_xticks(np.arange(-2.0, 11.0)) - ax.set_xlim([-2.0, 10.0]) - ax.set_ylim([-1.75, 1.75]) - ax.grid() - plt.show() - -ipywidgets.interact(sinc_demo, show_individual=False, show_signal=False); - - -# ## Raised cosine pulse - -# In[3]: - - -def raised_cosine_demo(rolloff): - pulse = komm.RaisedCosinePulse(rolloff, length_in_symbols=20) - h = pulse.impulse_response - H = pulse.frequency_response - t = np.linspace(-8.0, 8.0, 1000) - f = np.linspace(-1.5, 1.5, 200) - - _, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5)) - ax0.plot(t, h(t), 'b') - ax0.axis([-7.1, 7.1, -.3, 1.1]) - ax0.set_title('Raised cosine pulse (waveform)') - ax0.set_xlabel('$t$') - ax0.set_ylabel('$h(t)$') - ax0.grid() - ax1.plot(f, H(f), 'r') - ax1.axis([-1.1, 1.1, -.1, 1.1]) - ax1.set_title('Raised cosine pulse (spectrum)') - ax1.set_xlabel('$f$') - ax1.set_ylabel('$H(f)$') - ax1.grid() - plt.show() - -rolloff_widget = ipywidgets.FloatSlider(min=0, max=1.0, step=0.1, value=0.5) - -ipywidgets.interact(raised_cosine_demo, rolloff=rolloff_widget); - - -# ## Gaussian pulse - -# In[4]: - - -def gaussian_pulse_demo(half_power_bandwidth): - pulse = komm.GaussianPulse(half_power_bandwidth, length_in_symbols=4) - h = pulse.impulse_response - H = pulse.frequency_response - t = np.linspace(-8.0, 8.0, 1000) - f = np.linspace(-4.0, 4.0, 500) - - _, (ax0, ax1) = plt.subplots(1, 2, figsize=(16, 5)) - ax0.plot(t, h(t), 'b') - ax0.axis([-7.1, 7.1, -.1, 1.1]) - ax0.set_title('Gaussian pulse (waveform)') - ax0.set_xlabel('$t$') - ax0.set_ylabel('$h(t)$') - ax0.grid() - ax1.plot(f, H(f), 'r') - ax1.plot([-4.0, 4.0], [H(0) / np.sqrt(2), H(0) / np.sqrt(2)], linestyle='dashed', color='gray') - ax1.plot([half_power_bandwidth, half_power_bandwidth], [-0.1*H(0), 1.1*H(0)], linestyle='dashed', color='gray') - ax1.plot([-half_power_bandwidth, -half_power_bandwidth], [-0.1*H(0), 1.1*H(0)], linestyle='dashed', color='gray') - ax1.axis([-2.0, 2.0, -0.1*H(0), 1.1*H(0)]) - ax1.set_title('Gaussian pulse (spectrum)') - ax1.set_xlabel('$f$') - ax1.set_ylabel('$H(f)$') - ax1.grid() - plt.show() - -half_power_bandwidth_widget = ipywidgets.FloatSlider(min=0.05, max=1.0, step=0.01, value=0.5) - -ipywidgets.interact(gaussian_pulse_demo, half_power_bandwidth=half_power_bandwidth_widget); - diff --git a/demo/utils.py b/demo/utils.py new file mode 100644 index 00000000..0327de80 --- /dev/null +++ b/demo/utils.py @@ -0,0 +1,27 @@ +import inspect + +import streamlit as st + + +def show_about(): + st.sidebar.markdown( + """ + ### About Komm + + Komm is an open-source library for Python 3 providing tools for analysis and simulation of analog and digital communication systems. + + [![PyPI page](https://badge.fury.io/py/komm.svg)](https://pypi.org/project/komm/) + [![Documentation](https://img.shields.io/badge/docs-komm.dev-blue)](https://komm.dev/) + [![GitHub](https://img.shields.io/badge/github-rwnobrega%2Fkomm-black)](https://github.com/rwnobrega/komm) + """ + ) + + +def show_code(func): + source = inspect.getsource(func) + lines = source.split("\n") + for i, line in enumerate(lines): + if line.startswith("def "): + source = "\n".join(lines[i:]) + break + return source diff --git a/pyproject.toml b/pyproject.toml index 0f2923ae..0c9716fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,3 +48,9 @@ debug = [ "ipykernel>=6.28.0", "matplotlib>=3.8.2", ] + +[dependency-groups] +demo = [ + "streamlit==1.39.0", + "matplotlib==3.9.2" +]