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. Jusdl 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, Jusdl 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
where
Note that there exist an algebraic loop consisting of adder
and gain
. Solving this algebraic loop, we have
The following script constructs and simulates the model.
using Jusdl
# Construct an empty model
t0, dt, tf = 0, 1 / 64, 1.
model = Model(clock=Clock(t0, dt, tf))
# Add nodes to the model
addnode!(model, RampGenerator(), label=:gen)
addnode!(model, Adder((+,-)), label=:adder)
addnode!(model, Gain(), label=:gain)
addnode!(model, Writer(), label=:writerout)
addnode!(model, Writer(), label=:writerin)
# Add branches to the model
addbranch!(model, :gen => :adder, 1 => 1)
addbranch!(model, :adder => :gain, 1 => 1)
addbranch!(model, :gain => :adder, 1 => 2)
addbranch!(model, :gen => :writerin, 1 => 1)
addbranch!(model, :gain => :writerout, 1 => 1)
# Simulate the model
sim = simulate!(model, 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: 2020-05-08T01:33:40.147 Started simulation...
[ Info: 2020-05-08T01:33:40.168 Inspecting model...
┌ Info: The model has algrebraic loops:[[2, 3]]
└ Trying to break these loops...
[ Info: Loop [2, 3] is broken
[ Info: 2020-05-08T01:33:40.751 Done.
[ Info: 2020-05-08T01:33:40.751 Initializing the model...
[ Info: 2020-05-08T01:33:41.354 Done...
[ Info: 2020-05-08T01:33:41.354 Running the simulation...
[ Info: 2020-05-08T01:33:48.535 Done...
[ Info: 2020-05-08T01:33:48.535 Terminating the simulation...
[ Info: 2020-05-08T01:33:48.693 Done.
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.
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 Jusdl
# Construct the model
ti, dt, tf = 0, 1 / 64, 1.
model = Model(clock=Clock(ti, dt, tf))
# Adding nodes to model
addnode!(model, RampGenerator(), label=:gen)
addnode!(model, Adder((+, -)), label=:adder)
addnode!(model, Gain(), label=:gain)
addnode!(model, Memory(dt, t0=tf, dt=dt, initial=zeros(1)), label=:mem)
addnode!(model, Writer(Inport(2)), label=:writer)
addbranch!(model, :gen => :adder, 1 => 1)
addbranch!(model, :adder => :gain, 1 => 1)
addbranch!(model, :gain => :mem, 1 => 1)
addbranch!(model, :mem => :adder, 1 => 2)
addbranch!(model, :gen => :writer, 1 => 1)
addbranch!(model, :gain => :writer, 1 => 2)
# Simulate the model
sim = simulate!(model, withbar=false)
# Plot the simulation data
using Plots
t, x = read(getnode(model, :writer).component)
plot(t, x[:, 1], label="r(t)", marker=(:circle, 3))
plot!(t, x[:, 2], label="y(t)", marker=(:circle, 3))
[ Info: 2020-05-08T01:34:21.249 Started simulation...
[ Info: 2020-05-08T01:34:21.249 Inspecting model...
┌ Info: The model has algrebraic loops:[[2, 3, 4]]
└ Trying to break these loops...
[ Info: Loop [2, 3, 4] has a Memory component. The loops is broken
[ Info: 2020-05-08T01:34:21.249 Done.
[ Info: 2020-05-08T01:34:21.249 Initializing the model...
[ Info: 2020-05-08T01:34:21.749 Done...
[ Info: 2020-05-08T01:34:21.749 Running the simulation...
[ Info: 2020-05-08T01:34:21.89 Done...
[ Info: 2020-05-08T01:34:21.891 Terminating the simulation...
[ Info: 2020-05-08T01:34:21.893 Done.
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
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 Jusdl
# Construct the model
ti, dt, tf = 0, 1 / 64, 1.
model = Model(clock=Clock(ti, dt, tf))
# Adding nodes to model
addnode!(model, RampGenerator(), label=:gen)
addnode!(model, Adder((+, -)), label=:adder)
addnode!(model, Gain(), label=:gain)
addnode!(model, Memory(dt, t0=tf, dt=dt, initial=rand(1)), label=:mem)
addnode!(model, Writer(Inport(2)), label=:writer)
addbranch!(model, :gen => :adder, 1 => 1)
addbranch!(model, :adder => :gain, 1 => 1)
addbranch!(model, :gain => :mem, 1 => 1)
addbranch!(model, :mem => :adder, 1 => 2)
addbranch!(model, :gen => :writer, 1 => 1)
addbranch!(model, :gain => :writer, 1 => 2)
# Simulate the model
sim = simulate!(model)
# Plot the results
using Plots
t, x = read(getnode(model, :writer).component)
plot(t, x[:, 1], label="r(t)", marker=(:circle, 3))
plot!(t, x[:, 2], label="y(t)", marker=(:circle, 3))
[ Info: 2020-05-08T01:34:22.701 Started simulation...
[ Info: 2020-05-08T01:34:22.701 Inspecting model...
┌ Info: The model has algrebraic loops:[[2, 3, 4]]
└ Trying to break these loops...
[ Info: Loop [2, 3, 4] has a Memory component. The loops is broken
[ Info: 2020-05-08T01:34:22.702 Done.
[ Info: 2020-05-08T01:34:22.702 Initializing the model...
[ Info: 2020-05-08T01:34:22.703 Done...
[ Info: 2020-05-08T01:34:22.703 Running the simulation...
[ Info: 2020-05-08T01:34:22.754 Done...
[ Info: 2020-05-08T01:34:22.754 Terminating the simulation...
[ Info: 2020-05-08T01:34:22.777 Done.