Miguel Arpa Perozo

Simulink the Unix Way

May 20, 2024

Motivations and Goal

The main purpose of this project was to improve my C programming skills and to have fun with a small project. So why did I choose to work on this particular topic? On the one hand, I had lost my access to Simulink, so I wanted to build the simplest Simulink that I could think of. On the other hand, I was inspired by the presentation of Anders Damsgaard. You can find the source code of the project here.

A great idea of Simulink is its block abstraction: you build your simulation by connecting/building different blocks together. A similar idea can be found in Unix with its concept of pipes. In Unix systems, streams of text work as the universal interface between programs. The output of a program A can be used as the input of another program B by executing in the shell: ./A | ./B. I wanted to leverage Unix pipes to build my Simulink. As a result, each block becomes an executable that takes its input from the standard input, and writes its output to the standard output.

Below, you can find what I think are some advantages and disadvantages of this approach to simulation:

Advantages:

Disadvantages:

The following sections present the conventions used in the project.

Transfer Function Representations

The files z-utils.h and z-utils.c provide useful abstractions and functions to work with discrete transfer functions.

Considering the following transfer function: $$ G(z) = \frac{Y(z)}{U(z)} = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2} + \ldots + b_m z^{-m}} {a_0 + a_1 z^{-1} + a_2 z^{-2} + \ldots + a_n z^{-n}} $$

where \(m \leq n\).

To use \(G(z)\) in a program, the coefficient \(b_i\) and \(a_i\) must be written into a file. The file format used to encode transfer function is better understood with an example. Let us consider the following transfer function:

$$ G(z) = \frac{1 + 2 z^{-1}} {1 + 3 z^{-1} + 4z^{-2} + 5 z^{-3}} $$

To load the transfer function \(G\) in our programs, we would create a file with the following content:

N 1 2 
D 1 3 4 5

Time and Input/Output Conventions

Currently, all the tools work only with discrete time systems. Each input/output line corresponds to the value(s) of the block at a time step. This first column of an input/output file is reserved to represent the simulation time. Again, this is better understood with an example. Let us consider a “trivial block” stored in a file z_trv.txt. This block computes its output by adding its input to its previous output:

$$ T(z) = \frac{Y(z)}{U(z)} = \frac{1}{1 - z^{-1}} $$ which gives us in the time domain: $$ y_k = y_{k-1} + u_{k} $$

We assume that the block’s initial condition, passed as a parameter to the program, is equal to \(7\). Suppose that the input file input.txt given to this block is:

1    4
2    5
3    6

where the first column represents the simulation time, and the second column represents the input given to the transfer function block. To execute the simulation, we run z-tf -f z_trv.txt -i 7.0 < input.txt, which gives the following output:

1    4    11
2    5    16
3    6    22

Here the program adds a third column corresponding to the output of the transfer function. As a general rule, each block only adds columns to the standard output. As a consequence, the simulation data from all the blocks is never lost.

Feedback Loops

Some image text!

The z-cl program computes the response of a system specified by the control block diagram above. The user must specify the transfer function for the controller \(C\) and the system \(G\). At each time-step \(t_k\), the program outputs the signal values of \(r_k\), \(e_k\), \(u_k\), and \(y_k\) separated by tabs.

Lessons Learned

The list below is a note for my future self, it enumerates different elements to consider for future projects: