Skip to content
Snippets Groups Projects
firstorderiirfilter.py 6.19 KiB
Newer Older
  • Learn to ignore specific revisions
  • """
    ======================================
    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:
    
    
    # %%
    # 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()
    
    # %%
    
    Oscar Gustafsson's avatar
    Oscar Gustafsson committed
    # Executing ``firstorderiir.precedence_graph`` will show something like
    
    #
    # .. graphviz::
    #
    
    #   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
    
    freqz_fir(sim.results["0"])
    
    # 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:
    
    
    freqz_fir(sim.results["0"])
    
    plt.show()