Newer
Older
"""
======================================
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 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 # noqa: E402
# 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.
# For now, we can print the precedence relations of the SFG
firstorderiir.print_precedence_graph()
# %%
# Executing ``firstorderiir.precedence_graph`` will show something like
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# digraph {
# rankdir=LR
# subgraph cluster_0 {
# label=N0
# "in0.0" [label=in0 height=0.1 shape=rectangle width=0.1]
# "t0.0" [label=t0 height=0.1 shape=rectangle width=0.1]
# }
# subgraph cluster_1 {
# label=N1
# "cmul1.0" [label=cmul1 height=0.1 shape=rectangle width=0.1]
# "cmul0.0" [label=cmul0 height=0.1 shape=rectangle width=0.1]
# }
# subgraph cluster_2 {
# label=N2
# "add0.0" [label=add0 height=0.1 shape=rectangle width=0.1]
# }
# subgraph cluster_3 {
# label=N3
# "add1.0" [label=add1 height=0.1 shape=rectangle width=0.1]
# }
# "in0.0" -> add0
# add0 [label=add0 shape=ellipse]
# in0 -> "in0.0"
# in0 [label=in0 shape=cds]
# "t0.0" -> cmul1
# cmul1 [label=cmul1 shape=ellipse]
# "t0.0" -> cmul0
# cmul0 [label=cmul0 shape=ellipse]
# t0Out -> "t0.0"
# t0Out [label=t0 shape=square]
# "cmul1.0" -> add1
# add1 [label=add1 shape=ellipse]
# cmul1 -> "cmul1.0"
# cmul1 [label=cmul1 shape=ellipse]
# "cmul0.0" -> add0
# add0 [label=add0 shape=ellipse]
# cmul0 -> "cmul0.0"
# cmul0 [label=cmul0 shape=ellipse]
# "add0.0" -> t0In
# t0In [label=t0 shape=square]
# "add0.0" -> add1
# add1 [label=add1 shape=ellipse]
# add0 -> "add0.0"
# add0 [label=add0 shape=ellipse]
# "add1.0" -> out0
# out0 [label=out0 shape=cds]
# add1 -> "add1.0"
# add1 [label=add1 shape=ellipse]
# }
# 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('cmul0')
# 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 # noqa: E402
# 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
# 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 # noqa: E402
plt.plot(sim.results['0'], label="Output")
plt.plot(sim.results['add0'], label="After first addition")
plt.legend()
plt.show()
# %%
# To compute and plot the frequency response, it is possible to use mplsignal
from mplsignal.freq_plots import freqz_fir # noqa: E402
# 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 # noqa: E402
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: