Skip to content

Restructuring ControlSystems

Mattias Fält edited this page Jun 8, 2017 · 6 revisions

Background

For the last couple of months there has been extensive discussions on how to restructure the ControlSystems toolbox to create a more extensible package, simpler and more efficient code and lately on how we should prepare to integrate support for the upcoming Modia implementation of the Modelica language. This page will contain some of the discussed proposals and their strengths and weaknesses. Feel free to update and contribute to this page. There will be a corresponding issue in which we are able to discuss the different approaches, but this page should be limited to the objective aspects of the proposals and the reasoning behind them.

Proposals

ControlCore.jl

We propose that the ControlSystems.jl toolbox is split into two separate packages; ControlCore.jl (or ControlSystemsCore.jl) and ControlSystems.jl. The ControlCore.jl will only contain the basic elements of the toolbox such as the types (currently LTISystem, TransferFunction, StateSpace, SisoTf and its concrete types), as well as the basic operations and promotions between these types. The ControlSystems.jl package would contain the rest of the features such as analysis, synthesis and plotting. The ControlSystems.jl package would have ControlCore.jl as a dependency.

Pros

  • This would enable several packages to use the same basic structures without requiring the full set of features and code in ControlSystems.jl and any future packages. This would currently include a System Identification toolbox and could in the future be extended to packages for MPC, nonlinear systems, hardware and control for embedded systems.
  • Does not break any current external functionality.
  • Would create a coherent set of functions (interfaces) that should be required for all concrete types
  • Would enable us to ensure that we keep type stability of the systems and the types they are defined over (such as Integers, Float64 and Float32).

Cons

  • As far as we can see there is no real downsides and everyone seems to be on board with this.

Polynomials

We plan to replace all uses of polynomials to be of the types Polynomials.jl instead of the current internal type. It should be possible to create systems using polynomials, for example sys = tf(Poly([1,2]), Poly([1,2,1])). This would make it easier to work with other packages that use polynomials and there is no opposition as far as we know, and should have minimal breakage. This work has already started.

Immutable types

Force all concrete types to be immutable.

Pros

  • Since for example the size of a MIMO system is stored in the type, this could create inconsistencies if the user does for example sys.matrix = someOtherMatrix or sys.ts = 1.
  • There might be some computational benifits (plese fill these in here).

Cons

  • If the user knows what (s)he is doing, we would inhibit the user to modify the values without copying the types.

Restructuring of types

The current set of types are not easily expendable since the type tree consist of mostly concrete types, we therefore propose to add several Abstract types to the tree. The current structure is as follows, where the SisoTf tree is only an internal representation of an SISO frequency response, that is not available to the user:

Any --> LTISystem --> TransferFunction{S<:SisoTf}
                  \-> StateSpace

               /-> SisoRational
Any --> SisoTf --> SisoZpk
               \-> SisoGeneralized

Proposal 1 - Adding more types and splitting Continuous and Discrete systems into different types

To allow for new types such as transfer functions with noise models, nonlinear systems, LTV (Linear Time Varying), and to reflect the inherent difference between Discrete and Continuous systems we propose to add the following types. The SisoFunction tree is an internal representation of a SISO frequency response, that is never handled directly by the user:

Any --> System -->  LTISystem --> TransferFunction{S<:SisoFunction} --> DTf{S}
                              \                                     \-> CTf{S}
                               \-> StateSpace --> DSs{T<:Real}
                                              \-> CSs{T<:Real}

Any --> SisoFunction{T<:Real} --> SisoTf{T<:AbstractFloat} -->  SisoRational{T}
                                                           \->  SisoZpk{T}

DTf/CTf and Dss/CSs represent Discrete or Continuous, Transfer-function and State-space.

Pros

  • Will have minimal breakage.
  • The user will never have to think about if the system is of a SISO or MIMO type since the output is the same (as compared to Proposal 2).
  • The internal types only correspond to a function (frequency response) and would therefore not have to contain any data about sample time, allowing this to be represented only once, in the DTf.

Cons

  • Some users only work with SISO systems and by assuming that any system is MIMO (with the special case of 1x1) the output of most functions would be an Array. For example dcgain for any system is dcgain(sys)::Array{2,T} and freqresp is of size nw*ny*nu which sometimes result in that the user has to splice the output to get the desired result. This is mitigated to some part by letting the dimensions of the system be the last two indices, which in current versions of julia can be dropped when indexed into, for example freqresp(sys)[:] and freqresp(sys)[:,1,1] has identical behavior for 1x1 systems.

Other properties

Just as in the following proposals, we should consider allowing any Number (or Any) to be stored in the types, to allow for complex Transfer-functions and automatic differentiation.

Proposal 2 - Proposal 1 + splitting SISO and MIMO

This is similar to Proposal 1 but with the additional property that a SISO system is itself a System that the user has access to. This proposal can be found here (and was slightly modified on this page). The tree was split on this page to make it fit on one line. The main difference here compared to the previous proposal is that the SisoSystem tree is is fully available to the user and contains all the information to represent a transfer function, such as sampling time:

Any --> System --> LinearSystem --> abstract SisoSystem{T<:Real}
                                \-> abstract MimoSystem{S<:SisoSystem}

SisoSystem{T<:Real} ------> SisoTf{T<:AbstractFloat} -->  DCSisoTf{T} --> DSisoRational{T}
                    \                                \                \-> DSisoRational{T}
                     \                                \-> CSisoTf{T}  --> CSisoZpk{T}
                      \                                               \-> CSisoRational{T}
                       \
                        \-> SisoSs{T} --> DSs{T<:Real}
                                      \-> CSs{T<:Real}

MimoSystem{S<:SisoSystem} -->   CMimo{T<:CSiso}
                          \->   CMimoSs{T<:Real}
                           \->  DMimo{T<:DSiso}
                            \-> DMimoSs{T<:Real}

with the additional definitions

CSiso = Union{CSisoTf,CSisoSs}
DSiso = Union{DSisoTf,DSisoSs}

This would mean that all MIMO systems are a collection of SISO systems that can be extracted using sys[i,j].

Pros

  • The output from functions can be simplified when working with SISO systems without having undesired type instability.
  • The properties of a SISO/MIMO system can be viewed as as analogy to the properties of Reals/Arrays.
  • It would be possible(?) to create a MIMO system of dimension larger than 2, e.g with an array of signals as input or output at each time point instead of just a vector.

Cons

  • SIMO and MISO would also have to be types to produce the expected result of dropping dimensions corresponding to singular dimensions.
  • The user has to keep track on if they are working with SISO or MIMO systems because of the different sized output.
    • Actually not. If you are thinking of simulation environments or some other things, numoutputs and numinputs interface is provided just for this purpose. if you want to add a siso to a mimo or do some interconnection, the promote_type can promote the siso to a mimo. Then, the interconnection will be transparent to the user.
    • This does not address the issue that I was referring to. It was regarding the output of for example dcgain being either a scalal or matrix for the different types, instead of being consistent. A direct reference to the possible "Pro" of simpler output above.
  • This might lead to more code when implementing, which could deter contributions, but this is debatable.
    • Contribution is not about the amount of code, I believe. It is about how you represent the work and showcase your program with concrete examples/needs. Some goodreads about it are here: 1, 2, 3. Well, you might buy these arguments, or not, I do not know. But if you check the references and such therein, you will see that good documentation and some guidelines are the ones to attract contributors. Remember, we are ourselves contributors to the julia society, and we have not read the codebase, but the manuals.
  • A SISO system can be seen as a special case of a MIMO so its need to exist as a separate type is debatable. This would result in two different possible representations of the same theoretical construct.
    • For a siso system, there is no direction information for the zeros, for example, whereas for mimo systems there is direction information. Apparently there is a difference between a siso and a mimo.
      • Most, if not all properties of a MIMO has SISO as a special case. By providing the function zeros (for input/output zeros) and tzeros (for multivariate zeros) both the input/output zeros and the multivariate zeros can be extracted transparently.
  • A SISO and a MIMO system would need to be created using different methods.
    • We have ss, zpk and tf implementations for creating siso and mimo systems. The output depends on what you give to these functions.
  • Properties such as sampling time would be kept in each SISO system and would be required to be consistent.
    • As a remark from our talk {Mattias and Arda}, even if you keep one Ts information in a mimo construct, the user is still able to assign a different sampled system in an element of the matrix provided at the time of construction.
      • There would be no sample-time information in the SisoFunction from Proposal 1, they are just functions representing a frequency response.
  • (Edit: This is an implementation detail and not very relevant to the proposal) A SisoSs is proposed to have the B and C "matrices" as vectors which would inhibit using the same code for the two types, or require promotion from SISO to MIMO and then stripping dimensions from the output.
    • Actually, right now B and C "matrices" are not "matrices" anymore, as I have explained in our discussion. Now they all inherit from abstractarrays of dimension 0 <= n < 3, which, together with the sanity check in the constructor, allows for any meaningful sparse, dense and subarray constructs available to julia. please bear with us a bit more to see the latest construct.
      • Yes, thus the quotes for "matrices". There is nothing inhibiting having sparse arrays in Proposal 1, using a similar construction if desired. However this proposal seems to inhibit working with A,B and C as matrices of dimension 2 (for example CAB) for the (possible?) computational and memory reduction of allowing them to be of dimension 1. This is to some part a different discussion that might not be relevant to the rest of the proposal.

Other properties

  • This enables the user to create MIMO systems that consist of both SISOTf and SISOSs. Whether this is a Pro or Con is debatable.
  • Multiplication (sys1*sys2) of a MIMO and a SISO could be interpreted as a broadcast implicitly without having to check the dimensions of the systems. If everything is MIMO this has to be done either using sys1.*sys2 (which requires the dimensions to be checked so that one of them is SISO) or sys1*sys2 (requiring the multiplication to consider the dimensions and doing implicit broadcast).

Proposal 2.1 - Remove array syntax for creating MIMO systems

This point is included in the proposal on the link for Proposal 2 but is kept in a separate point since it seems it is not necessary in the proposal above and there are different ways to implement this. To allow for easier creation of collections of transfer functions, the proposal is to use the syntax

sys = tf([1,2],[1,2,3])

for creating SISO systems and for example

sys = tf([1,2],[1,2,3])
mimoSys = tf([1*sys 2*sys; 3*sys 4*sys])

to create MIMO systems. I.e. when a collection of systems is input a MIMO system is created.

Pros

  • It keeps consistency (and simplifies) in creating 2 dimensional Arrays of systems, for example [1*sys 2*sys; 3*sys 4*sys] would still be an array, just like (in the current implementation) [1*sys, 2*sys, 3*sys] is a vector of systems.

Cons

  • It breaks the syntax that users are used to from example MATLAB where [1*sys 2*sys; 3*sys 4*sys] creates a MIMO system.
  • Any package that does analysis of systems would have to consider whether or not to treat an Array of systems as a MIMO system. It adds the question if tf([1*sys, 2*sys, 3*sys]) and [1*sys, 2*sys, 3*sys] should be treated as SIMO or MISO or not be allowed at all.
  • If a collection of SISO/MIMO systems is accepted as inputs to analysis/synthesis functions, then compatibility between the dimensions would have to be checked at every call.

Proposal 2.2

Instead of not allowing the array syntax for creating a MIMO, a separate constructor could be used for SISO and MIMO systems (for example tfs for SISO and tf for MIMO , where the syntax [1*sys 2*sys; 3*sys 4*sys] would create an Array for SISO systems sys and a MIMO system for MIMO sys.

Pros and Cons

Similar for the alternative above.

  • The additional downside of having multiple constructors, possibly confusing the user.
  • Has the benefit of allowing the syntax that the user is used to, when not explicitly working with MIMO systems.