JuliaControl / ControlSystems.jl

A Control Systems Toolbox for Julia
https://juliacontrol.github.io/ControlSystems.jl/stable/
Other
509 stars 85 forks source link

delay*tf being recognized as improper even if it is not. #830

Closed freemin7 closed 1 year ago

freemin7 commented 1 year ago
⌃ [a6e380b2] ControlSystems v1.7.1
plot(step(delay(5)*((s+1))/((s+2)*(s+0.5))))
ERROR: System is improper, a state-space representation is impossible
Stacktrace:
 [1] error(s::String)
   @ Base .\error.jl:35
 [2] convert(::Type{StateSpace{Continuous, Float64}}, G::TransferFunction{Continuous, ControlSystemsBase.SisoRational{Int64}}; balance::Bool)
   @ ControlSystemsBase C:\Users\joto\.julia\packages\ControlSystemsBase\MHo5M\src\types\conversion.jl:92
 [3] convert
   @ C:\Users\joto\.julia\packages\ControlSystemsBase\MHo5M\src\types\conversion.jl:90 [inlined]
 [4] convert
   @ C:\Users\joto\.julia\packages\ControlSystemsBase\MHo5M\src\types\DelayLtiSystem.jl:62 [inlined]
 [5] _promote
   @ .\promotion.jl:336 [inlined]
 [6] promote
   @ .\promotion.jl:359 [inlined]
 [7] *(sys1::DelayLtiSystem{Float64, Float64}, sys2::TransferFunction{Continuous, ControlSystemsBase.SisoRational{Int64}})
   @ ControlSystemsBase C:\Users\joto\.julia\packages\ControlSystemsBase\MHo5M\src\types\Lti.jl:4
 [8] top-level scope
   @ REPL[28]:1

While both plot(step(delay(5))) and plot(step(((s+1))/((s+2)*(s+0.5)))) are proper and do plot.

baggepinnen commented 1 year ago

You need parenthesis around the Tf due to the Julia operator precedence

freemin7 commented 1 year ago

I am not sure what you mean but t(step((delay(5))*((s+1))/((s+2)*(s+0.5)))) did fail with the same error

albheim commented 1 year ago

The problem us that the multiplication of the delay with the nominator happens first, creating a temporary value that is invalid. So putting parenthesis to force the division in the tf to happen before the delay multiplication should help.

I believe this happens since the delay is represented using state space it will try to promote the denominator to state space before multiplying, which does not work.

baggepinnen commented 1 year ago

c * b / a is in julia parsed as (c*a) / b)

julia> Meta.@lower c * b / a
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = c * b
│   %2 = %1 / a
└──      return %2
))))
baggepinnen commented 1 year ago
julia> delay(5)*((s+1)/((s+2)*(s+0.5)))
DelayLtiSystem{Float64, Float64}

P: StateSpace{Continuous, Float64}
A = 
  0.0   1.0
 -1.0  -2.5
B = 
 0.0  0.0
 1.0  0.0
C = 
 0.0  0.0
 1.0  1.0
D = 
 0.0  1.0
 0.0  0.0

Continuous-time state-space model

Delays: [5.0]
freemin7 commented 1 year ago

The problem us that the multiplication of the delay with the nominator happens first, creating a temporary value that is invalid. So putting parenthesis to force the division in the tf to happen before the delay multiplication should help.

There are plenty of algorithms in control engineering where improper transfer functions play an important role as intermidiary steps. That those are not supported when the delays are involved together with the unhelpfull error messages makes me quiet unhappy that the issue is remarked as resolved.

I realise that that would require a new type of object to be handled properly something like a partial fraction decomposition with power of the delays (and their possible cross terms?) as a basis could work

baggepinnen commented 1 year ago

It would of course be good to have better support for non-proper systems, but it requires a descriptor-system type, something we don't have at the moment. See DescriptorSystems.jl for such an implementation, albeit without support for delays.

freemin7 commented 1 year ago

In my naive understanding if transfer function with delays are not converted into state space models eagerly all the existing machinery should mostly work fine. Atleast in my superficial reading Descriptor Systems allows handling of DAEs. I don't immediatly see how having them would allow for a more straightforward implementation of this functionality. Addition, multiplication and division of transfer function handling of delays are obvious, however handling the infinite roots delays introduces might complicate things.

However i get the impression that this does not look like a fruitful approach to you. Let me know if i am mistaken. Also thank you for that reference to descriptor systems. DEAs came up today and i was not away of that fomralism.

baggepinnen commented 1 year ago

Descriptor systems can represent non-proper systems, we use this in ControlSystemsMTK, see an example here.

I vastly prefer state-space realizations for several reasons, polynomial models have very poor numerics, even 6-8 order systems suffer from numerical roundoff when represented as rational functions with polynomials. The transfer-function approach would also have a hard time representing things like feedback(P) = $\dfrac{P}{1 + P}$ if $P$ contains delays. For that, you'd have to be able to represent polynomials not only in $s$, but also in $e^{sL_i} \quad \forall \quad i$. Even if you can represent such a system, how would you simulate it? You can kind-of simulate them using the Fourier-transform, but to properly simulate them in the time domain you need a delayed state-space representation.

All this to say that I think implementing a transfer-function type with delay support would have a very low ratio of "inspiration / transpiration", or put in other words, poor bang for the buck. Implementing <: LTISystem types is rather tedious and has a lot of corner cases that must be handled.


Related:

Transfer functions can be represented symbolically as well

using Symbolics
@variables s

julia> P = exp(-s) * (s+3) / ((s+1)*(s+2))
((3 + s)*exp(-s)) / ((1 + s)*(2 + s))

julia> P / (1 + P)
((3 + s)*exp(-s)) / ((1 + s)*(1 + ((3 + s)*exp(-s)) / ((1 + s)*(2 + s)))*(2 + s))

julia> P / (1 + P) |> simplify
((3 + s)*exp(-s)) / (2 + s^2 + 3s + 3exp(-s) + s*exp(-s))

can represent a much broader class of non-rational transfer functions than only time delays. You can also evaluate the frequency-response of a general transfer function as a regular julia function

tf(s) = exp(-sqrt(s))
tf(3.14im)

So unless you need to simulate the system, there are several options that allow you to compute the frequency response of arbitrarily complicated non-rational transfer functions.

baggepinnen commented 1 year ago

I added a better (hopefully) error message

julia> delay(5)*((s+1))/((s+2)*(s+0.5))
ERROR: The transfer function is not proper and can not be converted to a DelayLtiSystem type.
If you tried to form the system `exp(sL) * B / A` where `B / A` is proper, add parenthesis to make it `exp(sL) * (B / A)`.