Hello,
I was trying to create a simple control loop with modelingtoolkit. The idea was to have a generic controller whose setpoint can be changed using callbacks during a dynamic simulation. For example:
using ModelingToolkit, DifferentialEquations, Plots
# Define first order system
function firstOrder(;name, K, tau)
@parameters t
@variables f(t), y(t)
Dt = Differential(t)
eqs = [
Dt(y) ~ (K*f - y)/tau
]
ODESystem(eqs, t, [f, y], []; name=name)
end
@named model = firstOrder(K=2, tau=10)
# Define controller
function controller(;name, Kp, Ki, Kd)
@parameters t, setpoint
@variables error(t), integralError(t), manipulatedVariable(t), controlledVariable(t)
Dt = Differential(t)
equations = [
error ~ setpoint - controlledVariable
Dt(integralError) ~ error
Dt(manipulatedVariable) ~ expand_derivatives(Dt(setpoint)) - (manipulatedVariable - Kp * error - Ki * integralError) / Kd
]
ODESystem(equations, t, [error, integralError, manipulatedVariable, controlledVariable], [setpoint]; name=name)
end
@named myController = controller(Kp = 10, Ki = 1, Kd = .1)
# Create control loop
@named loop = ODESystem([
model.y ~ myController.controlledVariable,
model.f ~ myController.manipulatedVariable
], systems=[model, myController])
# Initial conditions and parameters
x0 = [
model.f => 0,
model.y => 0,
myController.error => 0,
myController.integralError => 0,
myController.controlledVariable => 0,
myController.manipulatedVariable => 0,
]
par = [myController.setpoint => 1]
# Create problem
sys = structural_simplify(loop)
prob = ODEProblem(sys, x0, (0.0, 20.0), par)
# Define callback
function condition(u, t, integrator)
t - 10
end
function affect!(integrator)
integrator.p[1] += 1 # Setpoint = 2 after 10 seconds
end
cb = ContinuousCallback(condition, affect!)
# Solve
sol = solve(prob, Tsit5(), callback=cb)
# Plot
plot(sol, vars=[model.y, myController.error])
This results in:
While the controller behaves correctly, i.e. the controlled variable goes firstly to 1 and after 10 seconds moves up to 2 (the setpoint changes at this point thanks to the callback function), the error does not. The error is setpoint - controlledVariable
, but it seems that because of the callback, which makes it 2 after 10 seconds, it is constantly 2 throughout the simulation.
The error without a callback starts at 1, as it should:
Why does this happen and how can I actually change the value of my setpoint so the error is 0 whenever I reach the desired steady-state?