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 # 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?