Using symbolic variables in control systems

Is it possible to use symbolic variables when defining a discrete time control system?

The following code fails:

using ControlSystemsBase, ModelingToolkit

@parameters Ts

# Ts = 0.01
Ku = 200/5e6
K2 = 5e6
num = [Ku * Ts]
den = [1, -1]
P = tf(num, den, Ts) # plant

with the error message:

ERROR: LoadError: TypeError: non-boolean (Num) used in boolean context
Stacktrace:
 [1] Discrete
   @ ~/.julia/packages/ControlSystemsBase/4kZY4/src/types/TimeEvolution.jl:9 [inlined]
 [2] Discrete
   @ ~/.julia/packages/ControlSystemsBase/4kZY4/src/types/TimeEvolution.jl:19 [inlined]
 [3] tf(num::Vector{Num}, den::Vector{Int64}, Ts::Num)
   @ ControlSystemsBase ~/.julia/packages/ControlSystemsBase/4kZY4/src/types/tf.jl:49
 [4] top-level scope
   @ ~/repos/WindSpeedEstimators/mwes/mwe_09.jl:10
 [5] include(fname::String)
   @ Base.MainInclude ./client.jl:489
 [6] top-level scope
   @ REPL[2]:1
in expression starting at /home/ufechner/repos/WindSpeedEstimators/mwes/mwe_09.jl:10

Found the answer myself: Yes, it is possible using: GitHub - JuliaControl/SymbolicControlSystems.jl: C-code generation and an interface between ControlSystems.jl and SymPy.jl

This works:

using ControlSystemsBase, ModelingToolkit, SymbolicControlSystems

@vars Ts Ku  K2

num = [Ku * Ts]
den = [1, -1]
P = tf(num, den, Ts) # plant

and has as output:

TransferFunction{ControlSystemsBase.Discrete{SymPy.Sym}, ControlSystemsBase.SisoRational{SymPy.Sym}}
  (Ku*Ts)
-----------
(1)z + (-1)

Sample Time: Ts (seconds)
Discrete-time transfer function model

But the following code fails:

using ControlSystemsBase, ModelingToolkit, SymbolicControlSystems

@vars Ts Ku K2

num = [Ku * Ts]
den = [1, -1]
P = tf(num, den, Ts) # plant
C = K2
sys = feedback(P, C)

with the error message:

[ Info: Precompiling SymbolicControlSystems [886cb795-8fd3-4b11-92f6-8071e46f71c5]
[ Info: Skipping precompilation since __precompile__(false). Importing SymbolicControlSystems [886cb795-8fd3-4b11-92f6-8071e46f71c5].
ERROR: LoadError: PyError ($(Expr(:escape, :(ccall(#= /home/ufechner/.julia/packages/PyCall/1gn3u/src/pyfncall.jl:43 =# @pysym(:PyObject_Call), PyPtr, (PyPtr, PyPtr, PyPtr), o, pyargsptr, kw))))) <class 'sympy.polys.polyerrors.GeneratorsNeeded'>
GeneratorsNeeded("Cannot initialize from 'dict' without generators")
  File "/home/ufechner/.julia/conda/3/x86_64/lib/python3.10/site-packages/sympy/polys/polytools.py", line 182, in __new__
    return cls._from_expr(rep, opt)
  File "/home/ufechner/.julia/conda/3/x86_64/lib/python3.10/site-packages/sympy/polys/polytools.py", line 312, in _from_expr
    return cls._from_dict(rep, opt)
  File "/home/ufechner/.julia/conda/3/x86_64/lib/python3.10/site-packages/sympy/polys/polytools.py", line 249, in _from_dict
    raise GeneratorsNeeded(

Stacktrace:
  [1] pyerr_check
    @ PyCall ~/.julia/packages/PyCall/1gn3u/src/exception.jl:75 [inlined]
  [2] pyerr_check
    @ PyCall ~/.julia/packages/PyCall/1gn3u/src/exception.jl:79 [inlined]
  [3] _handle_error(msg::String)
    @ PyCall ~/.julia/packages/PyCall/1gn3u/src/exception.jl:96
  [4] macro expansion
    @ ~/.julia/packages/PyCall/1gn3u/src/exception.jl:110 [inlined]
  [5] #107
    @ ~/.julia/packages/PyCall/1gn3u/src/pyfncall.jl:43 [inlined]
  [6] disable_sigint
    @ ./c.jl:473 [inlined]
  [7] __pycall!
    @ ~/.julia/packages/PyCall/1gn3u/src/pyfncall.jl:42 [inlined]
  [8] _pycall!(ret::PyCall.PyObject, o::PyCall.PyObject, args::Tuple{SymPy.Sym}, nargs::Int64, kw::Ptr{Nothing})
    @ PyCall ~/.julia/packages/PyCall/1gn3u/src/pyfncall.jl:29
  [9] _pycall!
    @ PyCall ~/.julia/packages/PyCall/1gn3u/src/pyfncall.jl:11 [inlined]
 [10] (::PyCall.PyObject)(args::SymPy.Sym)
    @ PyCall ~/.julia/packages/PyCall/1gn3u/src/pyfncall.jl:86
 [11] tf(sys::SymPy.Sym, h::ControlSystemsBase.Discrete{SymPy.Sym})
    @ SymbolicControlSystems ~/.julia/packages/SymbolicControlSystems/wPScY/src/SymbolicControlSystems.jl:86
 [12] convert(::Type{TransferFunction{…}}, G::TransferFunction{ControlSystemsBase.Discrete{…}, ControlSystemsBase.SisoRational{…}})
    @ ControlSystemsBase ~/.julia/packages/ControlSystemsBase/4kZY4/src/types/conversion.jl:37 [inlined]
 [13] _promote
    @ ./promotion.jl:370 [inlined]
 [14] promote
    @ ./promotion.jl:393 [inlined]
 [15] feedback(sys1::TransferFunction{…}, sys2::SymPy.Sym; kwargs::@Kwargs{})
    @ ControlSystemsBase ~/.julia/packages/ControlSystemsBase/4kZY4/src/types/Lti.jl:33
 [16] top-level scope
    @ ~/repos/WindSpeedEstimators/mwes/mwe_09.jl:12
 [17] include(fname::String)
    @ Base.MainInclude ./client.jl:489
 [18] top-level scope
    @ REPL[1]:1
in expression starting at /home/ufechner/repos/WindSpeedEstimators/mwes/mwe_09.jl:12
Some type information was truncated. Use `show(err)` to see complete types.

Any idea?

Depending on what you want to do, you might not need SymbolicContorlSystems. You can pass Ts = 1 here tf(num, den, 1) if you’re only interested in algebraic manipulations. The only time the Ts there is relevant is when you need to compute the response in frequency or time.