From 380f501d364f4e7941f21a6109d79d83827384ce Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Sat, 4 Feb 2023 13:54:06 +0100 Subject: [PATCH] Add examples to documentation --- README.md | 2 + docs_sphinx/conf.py | 13 ++- docs_sphinx/index.rst | 3 +- examples/README.rst | 7 ++ examples/firstorderiirfilter.py | 153 +++++++++++++++++++++++++++ examples/secondorderdirectformiir.py | 23 ++-- examples/thirdorderblwdf.py | 4 + examples/threepointwinograddft.py | 11 +- examples/twotapfirsfg.py | 6 +- requirements_doc.txt | 2 + 10 files changed, 205 insertions(+), 19 deletions(-) create mode 100644 examples/README.rst create mode 100644 examples/firstorderiirfilter.py diff --git a/README.md b/README.md index 16031753..671e5c08 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,8 @@ To generate the documentation, the following additional packages are required: - [Sphinx](https://www.sphinx-doc.org/) - [Furo](https://pradyunsg.me/furo/) - [numpydoc](https://numpydoc.readthedocs.io/) + - [Sphinx-Gallery](https://sphinx-gallery.github.io/) + - [SciPy](https://scipy.org/) ### Using CMake directly diff --git a/docs_sphinx/conf.py b/docs_sphinx/conf.py index 90154ef0..0f266797 100644 --- a/docs_sphinx/conf.py +++ b/docs_sphinx/conf.py @@ -22,7 +22,8 @@ extensions = [ 'sphinx.ext.autosummary', 'sphinx.ext.inheritance_diagram', 'sphinx.ext.intersphinx', - 'numpydoc', + 'sphinx_gallery.gen_gallery', + 'numpydoc', # Needs to be loaded *after* autodoc. ] templates_path = ['_templates'] @@ -50,3 +51,13 @@ graphviz_dot = shutil.which('dot') html_theme = 'furo' html_static_path = ['_static'] + +# -- Options for sphinx-gallery -- +sphinx_gallery_conf = { + 'examples_dirs': '../examples', # path to your example scripts + 'gallery_dirs': 'examples', # path to where to save gallery generated output + 'plot_gallery': 'True', # sphinx-gallery/913 + 'filename_pattern': '.', + 'doc_module': ('b_asic',), + 'reference_url': {'b_asic': None}, +} diff --git a/docs_sphinx/index.rst b/docs_sphinx/index.rst index cf71767a..9ebbd2b4 100644 --- a/docs_sphinx/index.rst +++ b/docs_sphinx/index.rst @@ -22,9 +22,10 @@ Indices and tables Table of Contents ^^^^^^^^^^^^^^^^^ .. toctree:: - :maxdepth: 2 + :maxdepth: 1 self api/index GUI scheduler_gui + examples/index diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 00000000..a4d5fdd6 --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,7 @@ +.. _examples: + +=============== +B-ASIC Examples +=============== + +These are examples of how B-ASIC can be used. diff --git a/examples/firstorderiirfilter.py b/examples/firstorderiirfilter.py new file mode 100644 index 00000000..18149472 --- /dev/null +++ b/examples/firstorderiirfilter.py @@ -0,0 +1,153 @@ +""" +====================================== +First-order IIR Filter with Simulation +====================================== + +In this example, a direct form first-order IIR filter is designed. + +First, we need to import the operations that will be used in the example: +""" +from b_asic.core_operations import Addition, ConstantMultiplication +from b_asic.special_operations import Delay, Input, Output + +# %% +# Then, we continue by defining the input and delay element, which we can optionally name. + +input = Input(name="My input") +delay = Delay(name="The only delay") + +# %% +# There are a few ways to connect signals. Either explicitly, by instantiating them: + +a1 = ConstantMultiplication(0.5, delay) + +# %% +# By operator overloading: + +first_addition = a1 + input + +# %% +# Or by creating them, but connecting the input later. Each operation has a function :func:`~b_asic.operation.Operation.input` +# that is used to access a specific input (or output, by using :func:`~b_asic.operation.Operation.output`). + +b1 = ConstantMultiplication(0.7) +b1.input(0).connect(delay) + +# %% +# The latter is useful when there is not a single order to create the signal flow graph, e.g., for recursive algorithms. +# In this example, we could not connect the output of the delay as that was not yet available. +# +# There is also a shorthand form to connect signals using the ``<<`` operator: + +delay << first_addition + +# %% +# Naturally, it is also possible to write expressions when instantiating operations: + +output = Output(b1 + first_addition) + +# %% +# Now, we should create a signal flow graph, but first it must be imported (normally, this should go at the top of the file). + +from b_asic.signal_flow_graph import SFG + +# %% +# The signal flow graph is defined by its inputs and outputs, so these must be provided. As, in general, there can be +# multiple inputs and outputs, there should be provided as a list or a tuple. + +firstorderiir = SFG([input], [output]) + +# %% +# If this is executed in an enriched terminal, such as a Jupyter Notebook, Jupyter QtConsole, or Spyder, just typing +# the variable name will return a graphical representation of the signal flow graph. + +firstorderiir + +# %% +# For now, we can print the precendence relations of the SFG +firstorderiir.print_precedence_graph() + +# %% +# As seen, each operation has an id, in addition to the optional name. This can be used to access the operation. +# For example, +firstorderiir.find_by_id('cmul1') + +# %% +# Note that this operation differs from ``a1`` defined above as the operations are copied and recreated once inserted +# into a signal flow graph. +# +# The signal flow graph can also be simulated. For this, we must import :class:`.Simulation`. + +from b_asic.simulation import Simulation + +# %% +# The :class:`.Simulation` class require that we provide inputs. These can either be arrays of values or we can use functions +# that provides the values when provided a time index. +# +# Let us create a simulation that simulates a short impulse response: + +sim = Simulation(firstorderiir, [[1, 0, 0, 0, 0]]) + +# %% +# To run the simulation for all input samples, we do: + +sim.run() + +# %% +# The returned value is the output after the final iteration. However, we may often be interested in the results from +# the whole simulation. +# The results from the simulation, which is a dictionary of all the nodes in the signal flow graph, +# can be obtained as + +sim.results + +# %% +# Hence, we can obtain the results that we are interested in and, for example, plot the output and the value after the +# first addition: + +import matplotlib.pyplot as plt + +plt.plot(sim.results['0'], label="Output") +plt.plot(sim.results['add1'], label="After first addition") +plt.legend() +plt.show() + + +# %% +# To compute and plot the frequency response, it is possible to use SciPy and NumPy as + +import numpy as np +import scipy.signal + +w, h = scipy.signal.freqz(sim.results['0']) +plt.plot(w, 20 * np.log10(np.abs(h))) +plt.show() + +# %% +# As seen, the output has not converged to zero, leading to that the frequency-response may not be correct, so we want +# to simulate for a longer time. +# Instead of just adding zeros to the input array, we can use a function that generates the impulse response instead. +# There are a number of those defined in B-ASIC for convenience, and the one for an impulse response is called :class:`.Impulse`. + +from b_asic.signal_generator import Impulse + +sim = Simulation(firstorderiir, [Impulse()]) + +# %% +# Now, as the functions will not have an end, we must run the simulation for a given number of cycles, say 30. +# This is done using :func:`~b_asic.simulation.Simulation.run_for` instead: + +sim.run_for(30) + +# %% +# Now, plotting the impulse results gives: + +plt.plot(sim.results['0']) +plt.show() + +# %% +# And the frequency-response: + +w, h = scipy.signal.freqz(sim.results['0']) +plt.plot(w, 20 * np.log10(np.abs(h))) +plt.show() diff --git a/examples/secondorderdirectformiir.py b/examples/secondorderdirectformiir.py index b30f8bf7..2f2536cf 100644 --- a/examples/secondorderdirectformiir.py +++ b/examples/secondorderdirectformiir.py @@ -1,14 +1,8 @@ -"""A sfg with delays and interesting layout for precedence list generation. - . . -IN1>--->C0>--->ADD1>----------+--->A0>--->ADD4>--->OUT1 - . ^ | ^ . - . | T1 | . - . | | | . - . ADD2<---<B1<---+--->A1>--->ADD3 . - . ^ | ^ . - . | T2 | . - . | | | . - . +-----<B2<---+--->A2>-----+ . +""" +===================================== +Second-order IIR Filter with Schedule +===================================== + """ from b_asic.core_operations import Addition, ConstantMultiplication @@ -37,10 +31,15 @@ sfg = SFG( inputs=[in1], outputs=[out1], name="Second-order direct form IIR filter" ) -# Set latencies and exection times +# %% +# Set latencies and execution times sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) sfg.set_latency_of_type(Addition.type_name(), 1) sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) sfg.set_execution_time_of_type(Addition.type_name(), 1) +# %% +# Create schedule + schedule = Schedule(sfg, cyclic=True) +schedule.plot_schedule() diff --git a/examples/thirdorderblwdf.py b/examples/thirdorderblwdf.py index af59aff9..663d1314 100644 --- a/examples/thirdorderblwdf.py +++ b/examples/thirdorderblwdf.py @@ -1,4 +1,8 @@ """ +============================= +Third-order Bireciprocal LWDF +============================= + Small bireciprocal lattice wave digital filter. """ from b_asic.core_operations import Addition, SymmetricTwoportAdaptor diff --git a/examples/threepointwinograddft.py b/examples/threepointwinograddft.py index 927cc555..2d661467 100644 --- a/examples/threepointwinograddft.py +++ b/examples/threepointwinograddft.py @@ -1,4 +1,7 @@ -"""Three-point Winograd DFT. +""" +======================== +Three-point Winograd DFT +======================== """ from math import cos, pi, sin @@ -38,7 +41,8 @@ sfg = SFG( name="3-point Winograd DFT", ) -# Set latencies and exection times +# %% +# Set latencies and execution times sfg.set_latency_of_type(ConstantMultiplication.type_name(), 2) sfg.set_latency_of_type(Addition.type_name(), 1) sfg.set_latency_of_type(Subtraction.type_name(), 1) @@ -46,4 +50,7 @@ sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1) sfg.set_execution_time_of_type(Addition.type_name(), 1) sfg.set_execution_time_of_type(Subtraction.type_name(), 1) +# %% +# Generate schedule schedule = Schedule(sfg, cyclic=True) +schedule.plot_schedule() diff --git a/examples/twotapfirsfg.py b/examples/twotapfirsfg.py index b172e4fa..e111e2a3 100644 --- a/examples/twotapfirsfg.py +++ b/examples/twotapfirsfg.py @@ -1,7 +1,7 @@ """ -B-ASIC automatically generated SFG file. -Name: twotapfir -Last saved: 2023-01-24 14:38:17.654639. +================== +Two-tap FIR filter +================== """ from b_asic import ( SFG, diff --git a/requirements_doc.txt b/requirements_doc.txt index bd3577ef..6aacbb63 100644 --- a/requirements_doc.txt +++ b/requirements_doc.txt @@ -1,3 +1,5 @@ sphinx furo numpydoc +sphinx-gallery +scipy -- GitLab