ControlSystems -- transfer functions with delay?

From the documentation:

ControlSystems.tf — Function.

sys = tf(num, den, Ts=0), sys = tf(gain, Ts=0)

→ Is it possible to define a time delay for functions tf and zpk?

I’m trying to “sell” Julia to a friend, and he currently works with a system that has a time delay.

→ If not possible, should I propose to use a Padé approximation?

The package has a delay() function. See examples here.

Thanks! That looks perfect – can also be used when the model is posed in the time delay :-).

Shouldn’t this be listed under Constructors? I didn’t find it in the documentation.

1 Like

Follow-up… lsimplot works without time delay, but doesn’t seem to work with time delay??

using Plots, ControlSystems, DataInterpolations
#
sys = tf([2],[0.5,1])*tf([1],[3,1])#*delay(2)
#
tval() = rand(range(2,10,length=5))
uval() = rand(range(-1,1,length=5))
#
Nchanges = 20
tvec = [tval() for i in 1:Nchanges] |> cumsum
uvec = [uval() for i in 1:Nchanges]
uinterp =  ConstantInterpolation(uvec,tvec)
u(x,t) = uinterp(t)
#
tfin = tvec[end]
plot(t->u(0,t),0,tfin,label="u_exp")

gives the “experimental input”:

I can finally simulate the system with experiment:

tsim = range(0,tfin,length=200)
lsimplot(sys,u,tsim)

leading to:

Here I have started with default states at zero.

If I change the line sys = tf([2],[0.5,1])*tf([1],[3,1])#*delay(2) to sys = tf([2],[0.5,1])*tf([1],[3,1])*delay(2), I can still do stepplot of the system, but lsimplot with input as above leads to zero output for all time…

Questions:

  1. How can I make lsim work with time delay?
  2. What initial states could I possibly choose when I have time-delay? Would it be reasonable to assume that x0 refers to the states of the system without time delay, and that no states should be chosen for the (discretization of) time delay?

So one thing is that the lsim called for a delayed system seems to expect u that only depends on time, and if you send a function that takes two variables it assumes it is the inplace version u(uout, t) = uout .= f(t) and this would result in the uout sent in during the simulation to always be 0, which explains the output being 0.

So instead doing u(t) = uinterp(u) should solve the first problem I think.

This seems very confusing since it for non-delayed systems is u(x,t) as you used, so maybe something should be changed here.

For the states I’m pretty sure the internal states would be the same with a delayed input in this case, x’(t)=Ax(t)+Bu(t-tau), though that might depend on how delay is multiplied into the rest.

1 Like

Thanks for your suggestions and explanation (I didn’t understand it completely, though).

Do you mean I should do u(t) = uinterp(t)? Or do you mean u(t) = uinterp(u)? Remember: uinterp is an interpolation function with t as argument, not with u as argument.

If you mean that I should change u(x,t) = uinterp(t) to u(t) = uinterp(t), then the statement:

lsimplot(sys,uinterp,tsim)

should also work… but it doesn’t: it throws a lengthy error message.

It would indeed be confusing to the user if in lsim, u should have arguments u(x,t) when there is no time delay, and u(t) when there is time delay. If this is the case, then that would certainly be confusing.

Yeah I meant u(t)=uinterp(t) which works for me when copying your example. The reason that lsimplot(sys, uinterp, tsim) does not work is that uinterp is an AbstractVector type, and lsim checks the type of u and depending on type will handle it slightly differently. Since it is interpreted as a vector type it is called like a vector which causes an error. Not sure if this is intended, have never used the Interpolation types before so not sure how they interact with stuff.

Internally it looks like this

function lsim(sys::DelayLtiSystem{T,S}, u, t::AbstractArray{<:Real}; x0=fill(zero(T), nstates(sys)), alg=MethodOfSteps(Tsit5()), kwargs...) where {T,S}
    # Make u! in-place function of u
    u! = if isa(u, Number) || isa(u,AbstractVector) # Allow for u to be a constant number or vector
        (uout, t) -> uout .= u
    elseif DiffEqBase.isinplace(u, 2)               # If u is an inplace (more than 1 argument function)
        u
    else                                            # If u is a regular u(t) function
        (out, t) -> (out .= u(t))
    end
    _lsim(sys, u!, t, x0, alg; kwargs...)
end

where if it was handled under the last else clause it should have been fine to call it with uinterp, but as it is now it is caught by the first clause and errors. While the function u(t)=uinterp(t) will be hendled by the third clause and should hopefully work.

Yes it is weird as it is now, and I have opened an issue regarding this so will hopefully be looked over.

2 Likes

Thanks for following it up. The arguments for u is not documented in the documentation. And even if they were, I agree with you that they should be the same irrespective of a delay or not.

Also, it would make things clearer if the documentation states whether x0 includes states for the delay approximation, or not. Probably x0 should not include delay states… but I haven’t thought through what they should be if they are automatically chosen.

Another problem: named argument x0 in lsim doesn’t work. So if I do lsim(...; x0 = [1,1]), I get an error message. However, if I do lsim(...; x = [1,1]) then the simulation runs.

Could it be that the named argument has been changed from x0 to x without updating the documentation?

1 Like

Checking help?> lsim in the repl shows me this information, though it seems like it is missing from the online docs. Have added that to the issue about lsim for delay system.

Looking at the code it seems x0 is the name and it does not include the delay states, the delay states seems to be initialized to zero if I understand correctly.

Does the simulation do anything different with x=[1, 1]? Because there is a catchall kwarg so I assume it is just the same as not sending anything in. The problem with the x0=[1,1] is that the dispatch is not perfectly made so it does not find the method since you make an int array, try x0=[1.0, 1] and it should work. Will also add this to the rest of the issues you found and hopefully it can be fixed soon.

2 Likes

Will test to use floats in x0. Problem is I set x0 = zeros(2), and then setting x0 = x0, the code complained that the “symbol” didn’t find a zero, or something really weird.

Yeah, I got that too. If you try with only lsim and not lsimplot it seems to be slightly more reasonable error message, so I assume that is from the plottingrecepies being what they are (dark magic).

1 Like

You are right. The problem with keyword x0 is only in lsimplot… And your interpretation of me using x is probably correct – that lsimplot just neglects it.

To sum up, I think there are the following two or three problems with lsim/lsimplot:

  • The weird thing that u is supposed to have arguments u(x,t) when there is no delay, and that it is supposed to be u(t) when there is delay. This difference doesn’t make much sense to me, and in my view it is better if it is u(x,t) in both cases or u(t) in both cases.
  • The problem of keyword argument x0 giving error message in lsimplot, possibly due to a plot recipe problem.
  • Some uncertainty about what x0 means when there is a delay. Most likely, it means the finite states (of system without delay). The interesting thing is what value is chosen for the “delay states” when x0 is specified to be different from zero. If the delay is assumed to be on the input, the delay states should probably have the same values as the initial time inputs, or something.
1 Like

Yeah, thanks for noting all this. Have linked this thread in the issue here, feel free to add stuff there also.

2 Likes