Skip to content

Coupling MTK with time discrete control #1180

Closed
@ValentinKaisermayer

Description

@ValentinKaisermayer

Let's say I want to simulate an MPC controller where the plant is an MTK system.
I would need to access the current measurements and would have to modify control inputs via a callback in every time step according to some optimization problem.

For example a modified RC circuit:

using ModelingToolkit, Plots, DifferentialEquations

@parameters t
@connector function Pin(;name)
    sts = @variables v(t) = 1.0 i(t) = 1.0
    ODESystem(Equation[], t, sts, []; name=name)
end

function ModelingToolkit.connect(::Type{Pin}, ps...)
    eqs = [
           0 ~ sum(p -> p.i, ps) # KCL
          ]
    # KVL
    for i in 1:length(ps) - 1
        push!(eqs, ps[i].v ~ ps[i + 1].v)
    end

    return eqs
end

function Ground(;name)
    @named g = Pin()
    eqs = [g.v ~ 0]
    compose(ODESystem(eqs, t, [], []; name), g)
end

function OnePort(;name)
    @named p = Pin()
    @named n = Pin()
    sts = @variables v(t) = 1.0 i(t) = 1.0
    eqs = [
           v ~ p.v - n.v
           0 ~ p.i + n.i
           i ~ p.i
          ]
    compose(ODESystem(eqs, t, sts, []; name), p, n)
end

function Resistor(;name, R=1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    ps = @parameters R = R
    eqs = [
           v ~ i * R
          ]
    extend(ODESystem(eqs, t, [], ps; name), oneport)
end

function Capacitor(;name, C=1.0)
    @named oneport = OnePort()
    @unpack v, i = oneport
    ps = @parameters C = C
    D = Differential(t)
    eqs = [
           D(v) ~ i / C
          ]
    extend(ODESystem(eqs, t, [], ps; name), oneport)
end

function ControlledVoltageSource(;name, V=1.0)
    @named oneport = OnePort()
    @unpack v = oneport
    vars = @variables V(t) = V
    D = Differential(t)
    eqs = [
           V ~ v
           D(V) ~ 0 # is constant 
          ]
    extend(ODESystem(eqs, t, vars, []; name), oneport)
end

@named resistor = Resistor(R=1)
@named capacitor = Capacitor(C=1)
@named source = ControlledVoltageSource(V=1)
@named ground = Ground()

rc_eqs = [
    connect(source.p, resistor.p)
    connect(resistor.n, capacitor.p)
    connect(capacitor.n, source.n, ground.g)
]

@named rc_model = compose(ODESystem(rc_eqs, t), [resistor, capacitor, source, ground])
sys = structural_simplify(rc_model)
u0 = [
    capacitor.v => 0.0
]
prob = ODAEProblem(sys, u0, (0, 10.0))

@inline indexof(sym, syms) = findfirst(isequal(sym), syms)
function cb!(int) # the callback function
    int.u[indexof(source.V, states(sys))] = sin(int.t) # this is where the actual MPC problem would be solved
    return
end

sol = solve(prob, Tsit5(); callback=PeriodicCallback(cb!, 1.))
plot(sol, vars=[capacitor.v, resistor.v, source.v])

Notice the ControlledVoltageSource implementation:

function ControlledVoltageSource(;name, V=1.0)
    @named oneport = OnePort()
    @unpack v = oneport
    vars = @variables V(t) = V
    D = Differential(t)
    eqs = [
           V ~ v
           D(V) ~ 0 # is constant 
          ]
    extend(ODESystem(eqs, t, vars, []; name), oneport)
end

where I had to make V a state in order to be able to access its value in the solution.
For this to work the equation D(V) ~ 0 has to be added.
This is actually the solution from: https://discourse.julialang.org/t/update-parameters-in-modelingtoolkit-using-callbacks/63770
An unexperienced user might not be able to do this.

The control input can not be a parameter or else I can not access it correctly in the solution but at the same time the controls kwarg requires it to be one.
For a linear MPC I would need the system matrix A, input matrix B and output matrix C (and very rarely feedthrough matrix D), i.e. the jacobi matrices with respect to the states, inputs and outputs of the sate and output equations at some operating point. MTK should be able to provide those.

If I where to implement a time discrete PID controller or any other time discrete control strategy it would be pretty much the same.

A more Modelicaesk solution would be to define a connector for the inputs and outputs like:

@connector function RealInput(;name, u0=0)
    sts = @variables u(t) = u0
    ODESystem([Differential(t)(u) ~ 0], t, sts, []; name)
end

and use it in the component like:

function ControlledVoltageSource(;name, V=1.0)
    @named oneport = OnePort()
    @named input = RealInput(u0=V)
    @unpack v = oneport
    eqs = [
        v ~ input.u
    ]
    extend(compose(ODESystem(eqs, t, [], []; name), input), oneport)
end

This would at least solve the issue of the additional equation, that one has to add for this to work.
However, maybe I am doing something wrong but I have to access the variable in a funny way in the callback:

function cb!(int) # the callback function
    int.u[indexof(source.input₊u, states(sys))] = sin(int.t)
    return
end

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions