Breaking Algebraic Loops

It this tutorial, we will simulate model consisting a closed loop feedback system. The model has an algebraic loop.

Algebraic Loops

An algebraic loop is a closed-loop consisting of one or more components whose outputs are directly dependent on their inputs. If algebraic loops exist in a model, the simulation gets stuck because none of the components in the loop can generate output to break the loop. Such a problem can be broken by rearranging the model without algebraic loops, solving the feed-forward algebraic equation of the loop, or inserting a memory component with a certain initial condition anywhere in the loop. Causal provides all these loop-breaking solutions. During the inspection stage, in case they are detected, all the loops are broken. Otherwise, a report is printed to notify the user to insert memory components to break the loops.

Breaking Algebraic Loops Automatically

Before initializing and running the simulation, Causal inspects the model first. See Simulation Stages for more information of simulation stages. In case the they exist in the model, all the algebraic loops are tried to be broken automatically without requiring a user intervention. Consider the following model

model

where

\[\begin{array}{l} r(t) = t \\[0.25cm] u(t) = r(t) - y(t) \\[0.25cm] y(t) = u(t) \end{array}\]

Note that there exist an algebraic loop consisting of adder and gain. Solving this algebraic loop, we have

\[ y(t) = u(t) = r(t) - y(t) \quad \Rightarrow \quad y(t) = \dfrac{r(t)}{2} = \dfrac{t}{2}\]

The following script constructs and simulates the model.

using Causal

# Describe the model
@defmodel model begin
    @nodes begin
        gen = RampGenerator()
        adder = Adder(signs=(+,-))
        gain = Gain()
        writerout = Writer()
        writerin = Writer()
    end
    @branches begin
        gen[1] => adder[1]
        adder[1] => gain[1]
        gain[1] => adder[2]
        gen[1] => writerin[1]
        gain[1] => writerout[1]
    end
end

# Simulate the model
ti, dt, tf = 0., 1. / 64., 1.
sim = simulate!(model, ti, dt, tf, withbar=false)

# Read the simulation data and plot
using Plots
t, y = read(getnode(model, :writerout).component)
t, r = read(getnode(model, :writerin).component)
plot(t, r, label="r(t)", marker=(:circle, 3))
plot!(t, y, label="y(t)", marker=(:circle, 3))
[ Info: 2021-05-10T16:12:05.479 Started simulation...
[ Info: 2021-05-10T16:12:05.562 Inspecting model...
┌ Info: 	The model has algrebraic loops:[[2, 3]]
└ 		Trying to break these loops...
[ Info: 	Loop [2, 3] is broken
[ Info: 2021-05-10T16:12:07.14 Done.
[ Info: 2021-05-10T16:12:07.14 Initializing the model...
[ Info: 2021-05-10T16:12:08.131 Done...
[ Info: 2021-05-10T16:12:08.576 Running the simulation...
[ Info: 2021-05-10T16:12:17.81 Done...
[ Info: 2021-05-10T16:12:17.811 Terminating the simulation...
[ Info: 2021-05-10T16:12:18.001 Done.
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: linuxfb, minimal, offscreen, vnc, xcb.

Aborted (core dumped)
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

Breaking Algebraic Loops With a Memory

It is also possible to break algebraic loops by inserting a Memory component at some point the loop. For example, consider the model consider following the model which is the model in which a memory component is inserted in the feedback path.

model

Note that the input to adder is not $y(t)$, but instead is $\hat{y}(t)$ which is one sample delayed form of $y(t)$. That is, we have, $\hat{y}(t) = y(t - dt)$ where $dt$ is the step size of the simulation. If $dt$ is small enough, $\hat{y}(t) \approx y(t)$.

The script given below simulates this case.

using Causal

# Simulation time settings.
ti, dt, tf = 0., 1. / 64., 1.

# Describe the model
@defmodel model begin
    @nodes begin
        gen = RampGenerator()
        adder = Adder(signs=(+,-))
        gain = Gain()
        writerout = Writer()
        writerin = Writer()
        mem = Memory(delay=dt, initial=zeros(1))
    end
    @branches begin
        gen[1] => adder[1]
        adder[1] => gain[1]
        gain[1] => mem[1]
        mem[1] => adder[2]
        gen[1] => writerin[1]
        gain[1] => writerout[1]
    end
end

# Simulate the model
sim = simulate!(model, ti, dt, tf, withbar=false)

# Plot the simulation data
using Plots
t, r = read(getnode(model, :writerin).component)
t, y = read(getnode(model, :writerout).component)
plot(t, r, label="r(t)", marker=(:circle, 3))
plot!(t, y, label="y(t)", marker=(:circle, 3))
[ Info: 2021-05-10T16:14:55.631 Started simulation...
[ Info: 2021-05-10T16:14:55.631 Inspecting model...
┌ Info: 	The model has algrebraic loops:[[2, 3, 6]]
└ 		Trying to break these loops...
[ Info: 	Loop [2, 3, 6] has a Memory component.  The loops is broken
[ Info: 2021-05-10T16:14:55.657 Done.
[ Info: 2021-05-10T16:14:55.657 Initializing the model...
[ Info: 2021-05-10T16:14:55.793 Done...
[ Info: 2021-05-10T16:14:56.085 Running the simulation...
[ Info: 2021-05-10T16:14:56.091 Done...
[ Info: 2021-05-10T16:14:56.091 Terminating the simulation...
[ Info: 2021-05-10T16:14:56.092 Done.
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: linuxfb, minimal, offscreen, vnc, xcb.

Aborted (core dumped)
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS

The fluctuation in $y(t)$ because of one-sample-time delay introduced by the mem component is apparent. The smaller the step size is, the smaller the amplitude of the fluctuation introduced by the mem component.

One other important issue with using the memory component is that the initial value of mem directly affects the accuracy of the simulation. By solving the loop equation, we know that

\[ y(t) = \dfrac{r(t)}{2} = \dfrac{t}{2} \quad \Rightarrow \quad y(0) = 0\]

That is the memory should be initialized with an initial value of zero, which is the case in the script above. To observe that how incorrect initialization of a memory to break an algebraic loop, consider the following example in which memory is initialized randomly.

using Causal
using Plots

# Simulation time settings.
ti, dt, tf = 0., 1. / 64., 1.

# Describe the model
@defmodel model begin
    @nodes begin
        gen = RampGenerator()
        adder = Adder(signs=(+,-))
        gain = Gain()
        writerout = Writer()
        writerin = Writer()
        mem = Memory(delay=dt, initial=rand(1))
    end
    @branches begin
        gen[1] => adder[1]
        adder[1] => gain[1]
        gain[1] => mem[1]
        mem[1] => adder[2]
        gen[1] => writerin[1]
        gain[1] => writerout[1]
    end
end

# Simulate the model
sim = simulate!(model, ti, dt, tf, withbar=false)

# Plot the results
using Plots
t, r = read(getnode(model, :writerin).component)
t, y = read(getnode(model, :writerout).component)
plot(t, r, label="r(t)", marker=(:circle, 3))
plot!(t, y, label="y(t)", marker=(:circle, 3))
[ Info: 2021-05-10T16:14:59.206 Started simulation...
[ Info: 2021-05-10T16:14:59.206 Inspecting model...
┌ Info: 	The model has algrebraic loops:[[2, 3, 6]]
└ 		Trying to break these loops...
[ Info: 	Loop [2, 3, 6] has a Memory component.  The loops is broken
[ Info: 2021-05-10T16:14:59.206 Done.
[ Info: 2021-05-10T16:14:59.206 Initializing the model...
[ Info: 2021-05-10T16:14:59.209 Done...
[ Info: 2021-05-10T16:14:59.209 Running the simulation...
[ Info: 2021-05-10T16:14:59.216 Done...
[ Info: 2021-05-10T16:14:59.216 Terminating the simulation...
[ Info: 2021-05-10T16:14:59.217 Done.
qt.qpa.xcb: could not connect to display
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: linuxfb, minimal, offscreen, vnc, xcb.

Aborted (core dumped)
connect: Connection refused
GKS: can't connect to GKS socket application

GKS: Open failed in routine OPEN_WS
GKS: GKS not in proper state. GKS must be either in the state WSOP or WSAC in routine ACTIVATE_WS