From 41297645d202ae3ee4f23d84029ddd8c255c0811 Mon Sep 17 00:00:00 2001 From: Fredrik Bagge Carlson Date: Fri, 10 Apr 2020 07:00:11 +0800 Subject: [PATCH 01/18] Revert "CompatHelper: bump compat for "Polynomials" to "0.7"" --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index a15e2f201..70031fde4 100644 --- a/Project.toml +++ b/Project.toml @@ -27,5 +27,5 @@ IterTools = "1.0" LaTeXStrings = "1.0" OrdinaryDiffEq = "5.2" Plots = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 1.0" -Polynomials = "0.6.0, 0.7" +Polynomials = "0.6.0" julia = "1.0" From 98589a6f91d52ce2d65399079a11f9ab1585af90 Mon Sep 17 00:00:00 2001 From: olof3 Date: Tue, 14 Apr 2020 11:36:25 +0200 Subject: [PATCH 02/18] Alternative without Static. --- src/ControlSystems.jl | 1 - src/freqresp.jl | 8 ++++---- src/types/DelayLtiSystem.jl | 2 -- src/types/Lti.jl | 1 - src/types/StateSpace.jl | 12 +++++------- src/types/TimeType.jl | 35 ++++++++++++----------------------- src/types/tf.jl | 2 +- src/types/zpk.jl | 2 +- test/test_statespace.jl | 2 +- 9 files changed, 24 insertions(+), 41 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index 282ddd919..72e6b6086 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -8,7 +8,6 @@ export LTISystem, DelayLtiSystem, Continuous, Discrete, - Static, ss, tf, zpk, diff --git a/src/freqresp.jl b/src/freqresp.jl index 19bab97e6..4552be0af 100644 --- a/src/freqresp.jl +++ b/src/freqresp.jl @@ -7,11 +7,11 @@ Evaluate the frequency response of a linear system of system `sys` over the frequency vector `w`.""" function freqresp(sys::LTISystem, w_vec::AbstractVector{<:Real}) # Create imaginary freq vector s - if !iscontinuous(sys) - Ts = isstatic(sys) ? 1.0 : sampletime(sys) - s_vec = exp.(w_vec*(im*Ts)) - else + if iscontinuous(sys) s_vec = im*w_vec + else + Ts = sampletime(sys) + s_vec = exp.(w_vec*(im*Ts)) end if isa(sys, StateSpace) sys = _preprocess_for_freqresp(sys) diff --git a/src/types/DelayLtiSystem.jl b/src/types/DelayLtiSystem.jl index de36e885d..3478fd00f 100644 --- a/src/types/DelayLtiSystem.jl +++ b/src/types/DelayLtiSystem.jl @@ -58,8 +58,6 @@ DelayLtiSystem(sys::StateSpace{Continuous,T,MT}) where {T, MT} = DelayLtiSystem{ # From TransferFunction, infer type TODO Use proper constructor instead of convert here when defined DelayLtiSystem(sys::TransferFunction{TimeT,S}) where {TimeT,T,S<:SisoTf{T}} = DelayLtiSystem{T}(convert(StateSpace{Continuous,T, Matrix{T}}, sys)) -# To handle cases where StateSpace is static -DelayLtiSystem(sys::StateSpace{Static,T,MT}, args...) where {T, MT,S} = DelayLtiSystem(StateSpace{Continuous,T,MT}(sys), args...) # TODO: Think through these promotions and conversions Base.promote_rule(::Type{AbstractMatrix{T1}}, ::Type{DelayLtiSystem{T2,S}}) where {T1<:Number,T2<:Number,S} = DelayLtiSystem{promote_type(T1,T2),S} diff --git a/src/types/Lti.jl b/src/types/Lti.jl index 47aea4d99..be23c4acb 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -53,7 +53,6 @@ isdiscrete(sys::LTISystem) = isdiscrete(sys.time) """`isstatic(sys)` Returns `true` if `sys` is static, else returns `false`.""" -isstatic(sys::LTISystem) = isstatic(sys.time) """`sampletime(sys)` diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index 330afb78a..0ac815c42 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -102,10 +102,10 @@ function StateSpace(D::AbstractArray{T}, Ts::TimeType) where {T<:Number} return StateSpace(A, B, C, D, Ts) end StateSpace(D::AbstractArray, Ts::Number) = StateSpace(D, Discrete(Ts)) -StateSpace(D::AbstractArray) = StateSpace(D, Static()) +StateSpace(D::AbstractArray) = StateSpace(D, Continuous()) StateSpace(d::Number, Ts::Number; kwargs...) = StateSpace([d], Discrete(Ts)) -StateSpace(d::Number; kwargs...) = StateSpace([d], Static()) +StateSpace(d::Number; kwargs...) = StateSpace([d], Continuous()) # StateSpace(sys) converts to StateSpace @@ -179,10 +179,10 @@ function HeteroStateSpace(D::AbstractArray{T}, Ts::TimeType) where {T<:Number} end HeteroStateSpace(D::AbstractArray{T}, Ts::Number) where {T<:Number} = HeteroStateSpace(D, Discrete(Ts)) -HeteroStateSpace(D::AbstractArray{T}) where {T<:Number} = HeteroStateSpace(D, Static()) +HeteroStateSpace(D::AbstractArray{T}) where {T<:Number} = HeteroStateSpace(D, Continuous()) HeteroStateSpace(d::Number, Ts::Number; kwargs...) = HeteroStateSpace([d], Discrete(Ts)) -HeteroStateSpace(d::Number; kwargs...) = HeteroStateSpace([d], Static()) +HeteroStateSpace(d::Number; kwargs...) = HeteroStateSpace([d], Continuous()) # HeteroStateSpace(sys) converts to HeteroStateSpace HeteroStateSpace(sys::LTISystem) = convert(HeteroStateSpace, sys) @@ -363,9 +363,7 @@ function Base.show(io::IO, sys::AbstractStateSpace) println(io, "Sample Time: ", sampletime(sys), " (seconds)") end # Print model type - if isstatic(sys) - print(io, "Static gain state-space model") - elseif iscontinuous(sys) + if iscontinuous(sys) print(io, "Continuous-time state-space model") else print(io, "Discrete-time state-space model") diff --git a/src/types/TimeType.jl b/src/types/TimeType.jl index 68385a951..25ebed257 100644 --- a/src/types/TimeType.jl +++ b/src/types/TimeType.jl @@ -15,33 +15,30 @@ end Discrete{T}(x) where T = Discrete{T}(T(x)) struct Continuous <: TimeType end -struct Static <: TimeType end # Basic Constructors Discrete(x::T) where T<:Number = Discrete{T}(x) Continuous(x::Continuous) = x -Static(x::Static) = x # Simple converseion Discrete{T}(x::Discrete) where T = Discrete{T}(x.Ts) -Discrete{T}(::Static) where T = Discrete{T}(UNDEF_SAMPLETIME) -Continuous(::Static) = Continuous() +#Discrete{T}(::Static) where T = Discrete{T}(UNDEF_SAMPLETIME) +#Continuous(::Static) = Continuous() # Default to checkng type iscontinuous(x::TimeType) = x isa Continuous isdiscrete(x::TimeType) = x isa Discrete -isstatic(x::TimeType) = x isa Static +#isstatic(x::TimeType) = x isa Static # Interface for getting sample time sampletime(x::Discrete) = x.Ts -sampletime(x::Continuous) = error("Continuous system has no sample time") -sampletime(x::Static) = error("Static system has no sample time") +sampletime(x::Continuous) = error("Continuous system has no sample time") # what system ?! +#sampletime(x::Static) = error("Static system has no sample time") unknown_time(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_SAMPLETIME) unknown_time(::Type{Continuous}) where T = Continuous() -unknown_time(::Type{Static}) where T = Static() + # Promotion # Fallback to give useful error Base.promote_rule(::Type{<:Discrete}, ::Type{Continuous}) = throw(ErrorException("Sampling time mismatch")) -Base.promote_rule(::Type{T}, ::Type{Static}) where T <: TimeType = T Base.promote_rule(::Type{Discrete{T1}}, ::Type{Discrete{T2}}) where {T1,T2}= Discrete{promote_type(T1,T2)} Base.convert(::Type{Discrete{T1}}, x::Discrete{T2}) where {T1,T2} = Discrete{T1}(x.Ts) @@ -54,7 +51,7 @@ common_sample_time(a::Base.Generator) = reduce(common_sample_time, a) function common_sample_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} if x != y && x.Ts != UNDEF_SAMPLETIME && y.Ts != UNDEF_SAMPLETIME - throw(ErrorException("Sampling time mismatch")) + throw(ErrorException("Sampling time mismatch")) end if x.Ts == UNDEF_SAMPLETIME @@ -64,28 +61,20 @@ function common_sample_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} end end -common_sample_time(x::Continuous, ys::Continuous...) = Continuous() -common_sample_time(x::Static, ys::Static...) = Static() -# Combinations with static -common_sample_time(x::TimeType, ys::Static) = x -common_sample_time(x::Static, y::TimeType) = y + + +common_sample_time(x::Continuous, ys::Continuous...) = Continuous() # Splatting needed? + # Check equality and similarity ==(x::TimeType, y::TimeType) = false ==(x::Discrete, y::Discrete) = (x.Ts == y.Ts) ==(::Continuous, ::Continuous) = true -==(::Static, ::Static) = true + # Equality with static is true -==(::Static, ::TimeType) = true -==(::TimeType, ::Static) = true isapprox(x::TimeType, y::TimeType, args...; kwargs...) = false isapprox(x::Discrete, y::Discrete, args...; kwargs...) = isapprox(x.Ts, y.Ts, args...; kwargs...) isapprox(::Continuous, ::Continuous, args...; kwargs...) = true -isapprox(::Static, ::Static, args...; kwargs...) = true - -# Equality with static is true -isapprox(::Static, ::TimeType, args...; kwargs...) = true -isapprox(::TimeType, ::Static, args...; kwargs...) = true diff --git a/src/types/tf.jl b/src/types/tf.jl index ef39c8f48..dd3b68bee 100644 --- a/src/types/tf.jl +++ b/src/types/tf.jl @@ -62,7 +62,7 @@ function tf(D::AbstractArray{T}, Ts::TimeT) where {TimeT<:TimeType, T<:Number} return TransferFunction{TimeT,SisoRational{T}}(matrix, Ts) end tf(D::AbstractArray{T}, Ts::Number) where T = tf(D, Discrete(Ts)) -tf(D::AbstractArray{T}) where T = tf(D, Static()) +tf(D::AbstractArray{T}) where T = tf(D, Continuous()) tf(n::Number, args...; kwargs...) = tf([n], args...; kwargs...) diff --git a/src/types/zpk.jl b/src/types/zpk.jl index 80d16d764..89ffd974d 100644 --- a/src/types/zpk.jl +++ b/src/types/zpk.jl @@ -86,4 +86,4 @@ zpk(z, p, k, Ts::Number) = zpk(z, p, k, Discrete(Ts)) zpk(z, p, k) = zpk(z, p, k, Continuous()) # Catch all 1(2) argument versions zpk(gain, Ts::Number; kwargs...) where {T <: Number} = zpk(gain, Discrete(Ts)) -zpk(gain; kwargs...) where {T <: Number} = zpk(gain, Static()) +zpk(gain; kwargs...) where {T <: Number} = zpk(gain, Continuous()) diff --git a/test/test_statespace.jl b/test/test_statespace.jl index 369e8da1e..c690ed756 100644 --- a/test/test_statespace.jl +++ b/test/test_statespace.jl @@ -109,7 +109,7 @@ res = ("StateSpace{Discrete{Float64},Float64,Array{Float64,2}}\nA = \n 0.2 -0.8 \n -0.8 0.07\nB = \n 1.0 0.0\n 0.0 2.0\nC = \n 1.0 0.0\n 0.0 1.0\nD = \n 0.0 0.0\n 0.0 0.0\n\nSample Time: 0.005 (seconds)\nDiscrete-time state-space model") end @test sprint(show, D_222) == res - res = ("StateSpace{Static,Float64,Array{Float64,2}}\nD = \n 4.0 0.0\n 0.0 4.0\n\nStatic gain state-space model") + res = ("StateSpace{Continuous,Float64,Array{Float64,2}}\nD = \n 4.0 0.0\n 0.0 4.0\n\nContinuous-time state-space model") @test sprint(show, C_022) == res res = "StateSpace{Discrete{Float64},Float64,Array{Float64,2}}\nD = \n 4.0 0.0\n 0.0 4.0\n\nSample Time: 0.005 (seconds)\nDiscrete-time state-space model" @test sprint(show, D_022) == res From 05e19c4416331f8a676a4604365d3ba185de38d9 Mon Sep 17 00:00:00 2001 From: olof3 Date: Tue, 14 Apr 2020 13:00:14 +0200 Subject: [PATCH 03/18] Various re-writes, e.g., LTISystem{<:TimeTime}. --- src/ControlSystems.jl | 4 ++-- src/matrix_comps.jl | 9 ++------- src/types/DelayLtiSystem.jl | 2 +- src/types/Lti.jl | 6 +++--- src/types/StateSpace.jl | 7 ++++--- src/types/TimeType.jl | 19 +++---------------- src/types/TransferFunction.jl | 2 +- src/types/conversion.jl | 10 +++++----- 8 files changed, 21 insertions(+), 38 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index 72e6b6086..2e166c36e 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -175,8 +175,8 @@ end function Base.getproperty(sys::Union{StateSpace,HeteroStateSpace,TransferFunction}, s::Symbol) if s === :Ts - # if !isdiscrete(sys) # NOTE this line seems to be breaking inference of isdiscrete - if !isdiscrete(sys.time) # We use this instead + # if !isdiscrete(sys) # NOTE this line seems to be breaking inference of isdiscrete (is there a test for this?) + if !isdiscrete(sys) @warn "Getting sampletime 0.0 for non-discrete systems is deprecated. Check `isdiscrete` before trying to access sampletime." return 0.0 else diff --git a/src/matrix_comps.jl b/src/matrix_comps.jl index c6b8e7fa5..9fef4b223 100644 --- a/src/matrix_comps.jl +++ b/src/matrix_comps.jl @@ -241,13 +241,8 @@ state space systems in continuous and discrete time', American Control Conferenc See also [`linfnorm`](@ref). """ -function hinfnorm(sys::AbstractStateSpace; tol=1e-6) - if !isdiscrete(sys) # Continuous or Static - return _infnorm_two_steps_ct(sys, :hinf, tol) - else - return _infnorm_two_steps_dt(sys, :hinf, tol) - end -end +hinfnorm(sys::AbstractStateSpace{<:Continuous}; tol=1e-6) = _infnorm_two_steps_ct(sys, :hinf, tol) +hinfnorm(sys::AbstractStateSpace{<:Discrete}; tol=1e-6) = _infnorm_two_steps_dt(sys, :hinf, tol) hinfnorm(sys::TransferFunction; tol=1e-6) = hinfnorm(ss(sys); tol=tol) """ diff --git a/src/types/DelayLtiSystem.jl b/src/types/DelayLtiSystem.jl index 3478fd00f..15c18b373 100644 --- a/src/types/DelayLtiSystem.jl +++ b/src/types/DelayLtiSystem.jl @@ -3,7 +3,7 @@ Represents an LTISystem with internal time-delay. See `?delay` for a convenience constructor. """ -struct DelayLtiSystem{T,S<:Real} <: LTISystem +struct DelayLtiSystem{T,S<:Real} <: LTISystem{Continuous} P::PartionedStateSpace{StateSpace{Continuous,T,Matrix{T}}} Tau::Vector{S} # The length of the vector tau implicitly defines the partitionging of P diff --git a/src/types/Lti.jl b/src/types/Lti.jl index be23c4acb..f20aca938 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -1,4 +1,4 @@ -abstract type LTISystem <: AbstractSystem end +abstract type LTISystem{T <:TimeType} <: AbstractSystem end +(sys1::LTISystem, sys2::LTISystem) = +(promote(sys1, sys2)...) -(sys1::LTISystem, sys2::LTISystem) = -(promote(sys1, sys2)...) *(sys1::LTISystem, sys2::LTISystem) = *(promote(sys1, sys2)...) @@ -45,11 +45,11 @@ end """`iscontinuous(sys)` Returns `true` if `sys` is continuous, else returns `false`.""" -iscontinuous(sys::LTISystem) = iscontinuous(sys.time) +iscontinuous(sys::LTISystem) = sys.time isa Continuous """`isdiscrete(sys)` Returns `true` if `sys` is discrete, else returns `false`.""" -isdiscrete(sys::LTISystem) = isdiscrete(sys.time) +isdiscrete(sys::LTISystem) = sys.time isa Discrete """`isstatic(sys)` Returns `true` if `sys` is static, else returns `false`.""" diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index 0ac815c42..e11246750 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -22,8 +22,9 @@ function state_space_validation(A,B,C,D,Ts::TimeType) nx,nu,ny end -abstract type AbstractStateSpace <: LTISystem end -struct StateSpace{TimeT<:TimeType, T, MT<:AbstractMatrix{T}} <: AbstractStateSpace +abstract type AbstractStateSpace{TimeT} <: LTISystem{TimeT} end + +struct StateSpace{TimeT, T, MT<:AbstractMatrix{T}} <: AbstractStateSpace{TimeT} A::MT B::MT C::MT @@ -125,7 +126,7 @@ Otherwise, this is a discrete-time model with sampling period Ts. ss(args...;kwargs...) = StateSpace(args...;kwargs...) -struct HeteroStateSpace{TimeT<:TimeType, AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:AbstractMatrix,DT<:AbstractMatrix} <: AbstractStateSpace +struct HeteroStateSpace{TimeT, AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:AbstractMatrix,DT<:AbstractMatrix} <: AbstractStateSpace{TimeT} A::AT B::BT C::CT diff --git a/src/types/TimeType.jl b/src/types/TimeType.jl index 25ebed257..8c194cebb 100644 --- a/src/types/TimeType.jl +++ b/src/types/TimeType.jl @@ -20,20 +20,14 @@ Discrete(x::T) where T<:Number = Discrete{T}(x) Continuous(x::Continuous) = x # Simple converseion Discrete{T}(x::Discrete) where T = Discrete{T}(x.Ts) -#Discrete{T}(::Static) where T = Discrete{T}(UNDEF_SAMPLETIME) -#Continuous(::Static) = Continuous() -# Default to checkng type -iscontinuous(x::TimeType) = x isa Continuous -isdiscrete(x::TimeType) = x isa Discrete -#isstatic(x::TimeType) = x isa Static # Interface for getting sample time sampletime(x::Discrete) = x.Ts sampletime(x::Continuous) = error("Continuous system has no sample time") # what system ?! #sampletime(x::Static) = error("Static system has no sample time") -unknown_time(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_SAMPLETIME) -unknown_time(::Type{Continuous}) where T = Continuous() +undef_sampletime(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_SAMPLETIME) +undef_sampletime(::Type{Continuous}) where T = Continuous() # Promotion @@ -61,20 +55,13 @@ function common_sample_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} end end - - - common_sample_time(x::Continuous, ys::Continuous...) = Continuous() # Splatting needed? - -# Check equality and similarity +# Check equality ==(x::TimeType, y::TimeType) = false ==(x::Discrete, y::Discrete) = (x.Ts == y.Ts) ==(::Continuous, ::Continuous) = true - -# Equality with static is true - isapprox(x::TimeType, y::TimeType, args...; kwargs...) = false isapprox(x::Discrete, y::Discrete, args...; kwargs...) = isapprox(x.Ts, y.Ts, args...; kwargs...) isapprox(::Continuous, ::Continuous, args...; kwargs...) = true diff --git a/src/types/TransferFunction.jl b/src/types/TransferFunction.jl index a3787e120..b8e57bcea 100644 --- a/src/types/TransferFunction.jl +++ b/src/types/TransferFunction.jl @@ -1,4 +1,4 @@ -struct TransferFunction{TimeT<:TimeType, S<:SisoTf{T} where T} <: LTISystem +struct TransferFunction{TimeT, S<:SisoTf{T} where T} <: LTISystem{TimeT} matrix::Matrix{S} time::TimeT nu::Int diff --git a/src/types/conversion.jl b/src/types/conversion.jl index a144dd9cd..08fef1d3e 100644 --- a/src/types/conversion.jl +++ b/src/types/conversion.jl @@ -20,9 +20,9 @@ # Base.convert(::Type{<:TransferFunction{<:SisoZpk}}, b::Number) = zpk(b) # Base.convert(::Type{TransferFunction{TimeT,SisoZpk{T1, TR1}}}, b::AbstractMatrix{T2}) where {TimeT, T1, TR1, T2<:Number} = - zpk(T1.(b), unknown_time(TimeT)) + zpk(T1.(b), undef_sampletime(TimeT)) Base.convert(::Type{TransferFunction{TimeT,SisoRational{T1}}}, b::AbstractMatrix{T2}) where {TimeT, T1, T2<:Number} = - tf(T1.(b), unknown_time(TimeT)) + tf(T1.(b), undef_sampletime(TimeT)) function convert(::Type{StateSpace{TimeT,T,MT}}, D::AbstractMatrix{<:Number}) where {TimeT,T, MT} (ny, nu) = size(D) @@ -30,14 +30,14 @@ function convert(::Type{StateSpace{TimeT,T,MT}}, D::AbstractMatrix{<:Number}) wh B = MT(fill(zero(T), (0,nu))) C = MT(fill(zero(T), (ny,0))) D = convert(MT, D) - return StateSpace{TimeT,T,MT}(A,B,C,D,unknown_time(TimeT)) + return StateSpace{TimeT,T,MT}(A,B,C,D,undef_sampletime(TimeT)) end # TODO We still dont have TransferFunction{..}(number/array) constructors Base.convert(::Type{TransferFunction{TimeT,SisoRational{T}}}, b::Number) where {TimeT, T} = - tf(T(b), unknown_time(TimeT)) + tf(T(b), undef_sampletime(TimeT)) Base.convert(::Type{TransferFunction{TimeT,SisoZpk{T,TR}}}, b::Number) where {TimeT, T, TR} = - zpk(T(b), unknown_time(TimeT)) + zpk(T(b), undef_sampletime(TimeT)) Base.convert(::Type{StateSpace{TimeT,T,MT}}, b::Number) where {TimeT, T, MT} = convert(StateSpace{TimeT,T,MT}, fill(b, (1,1))) # From 2584c53e455d2146a406549f868d3ae3a0b1035e Mon Sep 17 00:00:00 2001 From: olof3 Date: Tue, 14 Apr 2020 15:36:59 +0200 Subject: [PATCH 04/18] common_sample_time for systems instead of timetype --- src/connections.jl | 16 ++++++++-------- src/types/Lti.jl | 14 ++++++++++---- src/types/PartionedStateSpace.jl | 16 ++++++---------- src/types/StateSpace.jl | 8 ++++---- src/types/TimeType.jl | 6 +----- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/src/connections.jl b/src/connections.jl index c8c55b51f..7a6a7b35e 100644 --- a/src/connections.jl +++ b/src/connections.jl @@ -22,7 +22,7 @@ Append systems in block diagonal form """ function append(systems::(ST where ST<:AbstractStateSpace)...) ST = promote_type(typeof.(systems)...) - Ts = common_sample_time(s.time for s in systems) + Ts = common_sample_time(systems...) A = blockdiag([s.A for s in systems]...) B = blockdiag([s.B for s in systems]...) C = blockdiag([s.C for s in systems]...) @@ -31,7 +31,7 @@ function append(systems::(ST where ST<:AbstractStateSpace)...) end function append(systems::TransferFunction...) - Ts = common_sample_time(s.time for s in systems) + Ts = common_sample_time(systems...) mat = blockdiag([s.matrix for s in systems]...) return TransferFunction(mat, Ts) end @@ -62,7 +62,7 @@ function Base.vcat(systems::ST...) where ST <: AbstractStateSpace B = vcat([s.B for s in systems]...) C = blockdiag([s.C for s in systems]...) D = vcat([s.D for s in systems]...) - Ts = common_sample_time(s.time for s in systems) + Ts = common_sample_time(systems...) return ST(A, B, C, D, Ts) end @@ -72,7 +72,7 @@ function Base.vcat(systems::TransferFunction...) if !all(s.nu == nu for s in systems) error("All systems must have same input dimension") end - Ts = common_sample_time(s.time for s in systems) + Ts = common_sample_time(systems...) mat = vcat([s.matrix for s in systems]...) return TransferFunction(mat, Ts) @@ -86,7 +86,7 @@ function Base.hcat(systems::ST...) where ST <: AbstractStateSpace if !all(s.ny == ny for s in systems) error("All systems must have same output dimension") end - Ts = common_sample_time(s.time for s in systems) + Ts = common_sample_time(systems...) A = blockdiag([s.A for s in systems]...) B = blockdiag([s.B for s in systems]...) C = hcat([s.C for s in systems]...) @@ -101,7 +101,7 @@ function Base.hcat(systems::TransferFunction...) if !all(s.ny == ny for s in systems) error("All systems must have same output dimension") end - Ts = common_sample_time(s.time for s in systems) + Ts = common_sample_time(systems...) mat = hcat([s.matrix for s in systems]...) return TransferFunction(mat, Ts) @@ -201,7 +201,7 @@ function feedback(sys::Union{StateSpace, DelayLtiSystem}) end function feedback(sys1::StateSpace,sys2::StateSpace) - Ts = common_sample_time(sys1.time,sys2.time) + Ts = common_sample_time(sys1,sys2) !(iszero(sys1.D) || iszero(sys2.D)) && error("There cannot be a direct term (D) in both sys1 and sys2") A = [sys1.A+sys1.B*(-sys2.D)*sys1.C sys1.B*(-sys2.C); sys2.B*sys1.C sys2.A+sys2.B*sys1.D*(-sys2.C)] @@ -230,7 +230,7 @@ See Zhou etc. for similar (somewhat less symmetric) formulas. U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[], Wperm=:, Zperm=:, pos_feedback::Bool=false) - Ts = common_sample_time(sys1.time,sys2.time) + Ts = common_sample_time(sys1,sys2) if !(isa(Y1, Colon) || allunique(Y1)); @warn "Connecting single output to multiple inputs Y1=$Y1"; end if !(isa(Y2, Colon) || allunique(Y2)); @warn "Connecting single output to multiple inputs Y2=$Y2"; end diff --git a/src/types/Lti.jl b/src/types/Lti.jl index f20aca938..4b343ede2 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -45,11 +45,13 @@ end """`iscontinuous(sys)` Returns `true` if `sys` is continuous, else returns `false`.""" -iscontinuous(sys::LTISystem) = sys.time isa Continuous +iscontinuous(::LTISystem) = false +iscontinuous(::LTISystem{<:Continuous}) = true """`isdiscrete(sys)` Returns `true` if `sys` is discrete, else returns `false`.""" -isdiscrete(sys::LTISystem) = sys.time isa Discrete +isdiscrete(::LTISystem) = false +isdiscrete(::LTISystem{<:Discrete}) = true """`isstatic(sys)` Returns `true` if `sys` is static, else returns `false`.""" @@ -58,11 +60,15 @@ Returns `true` if `sys` is static, else returns `false`.""" Returns the sampletime of a discrete time system, throws error if the system is continuous time or static. Always ensure `isdiscrete` before using.""" -sampletime(sys::LTISystem) = sampletime(sys.time) +sampletime(sys::LTISystem{<:Discrete}) = sys.time.Ts +sampletime(::LTISystem{<:Continuous}) = error("Continuous system has no sample time") """`sampletype(sys)` Get the sampletype of system. Usually typeof(sys.time).""" -sampletype(sys) = typeof(sys.time) +sampletype(sys) = typeof(sys.time) # QUESTION: Perhaps timetype or sampletimetype instead? + +common_sample_time(systems::LTISystem...) = common_sample_time(s.time for s in systems) + """`isstable(sys)` diff --git a/src/types/PartionedStateSpace.jl b/src/types/PartionedStateSpace.jl index 8c9fca649..0da76cf25 100644 --- a/src/types/PartionedStateSpace.jl +++ b/src/types/PartionedStateSpace.jl @@ -11,7 +11,7 @@ u = [u1 u2]^T y = [y1 y2]^T """ -struct PartionedStateSpace{S} +struct PartionedStateSpace{TimeT,S<:AbstractStateSpace{TimeT}} <: LTISystem{TimeT} P::S nu1::Int ny1::Int @@ -58,13 +58,9 @@ function getproperty(sys::PartionedStateSpace, d::Symbol) end end -sampletime(sys::PartionedStateSpace) = sampletime(sys.P) -iscontinuous(sys::PartionedStateSpace) = iscontinuous(sys.P) -isdiscrete(sys::PartionedStateSpace) = isdiscrete(sys.P) -isstatic(sys::PartionedStateSpace) = isstatic(sys.P) function +(s1::PartionedStateSpace, s2::PartionedStateSpace) - Ts = common_sample_time(s1.P.time,s2.P.time) + Ts = common_sample_time(s1.P,s2.P) A = blockdiag(s1.A, s2.A) @@ -88,7 +84,7 @@ end Series connection of partioned StateSpace systems. """ function *(s1::PartionedStateSpace, s2::PartionedStateSpace) - Ts = common_sample_time(s1.P.time,s2.P.time) + Ts = common_sample_time(s1.P,s2.P) A = [s1.A s1.B1*s2.C1; zeros(size(s2.A,1),size(s1.A,2)) s2.A] @@ -112,7 +108,7 @@ end # QUESTION: What about algebraic loops and well-posedness?! Perhaps issue warning if P1(∞)*P2(∞) > 1 function feedback(s1::PartionedStateSpace, s2::PartionedStateSpace) - Ts = common_sample_time(s1.P.time,s2.P.time) + Ts = common_sample_time(s1.P,s2.P) X_11 = (I + s2.D11*s1.D11)\[-s2.D11*s1.C1 -s2.C1] X_21 = (I + s1.D11*s2.D11)\[s1.C1 -s1.D11*s2.C1] @@ -164,7 +160,7 @@ end """ function vcat_1(systems::PartionedStateSpace...) # Perform checks - Ts = common_sample_time(sys.P.time for sys in systems) + Ts = common_sample_time(sys.P for sys in systems) nu1 = systems[1].nu1 if !all(s.nu1 == nu1 for s in systems) @@ -198,7 +194,7 @@ end """ function hcat_1(systems::PartionedStateSpace...) # Perform checks - Ts = common_sample_time(sys.P.time for sys in systems) + Ts = common_sample_time(sys.P for sys in systems) ny1 = systems[1].ny1 if !all(s.ny1 == ny1 for s in systems) diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index e11246750..2be8926b1 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -221,7 +221,7 @@ function +(s1::StateSpace{TimeT,T,MT}, s2::StateSpace{TimeT,T,MT}) where {TimeT, if size(s1) != size(s2) error("Systems have different shapes.") end - Ts = common_sample_time(s1.time,s2.time) + Ts = common_sample_time(s1,s2) A = [s1.A fill(zero(T), nstates(s1), nstates(s2)); fill(zero(T), nstates(s2), nstates(s1)) s2.A] @@ -237,7 +237,7 @@ function +(s1::HeteroStateSpace, s2::HeteroStateSpace) if size(s1) != size(s2) error("Systems have different shapes.") end - Ts = common_sample_time(s1.time,s2.time) + Ts = common_sample_time(s1,s2) T = promote_type(eltype(s1.A),eltype(s2.A)) A = [s1.A fill(zero(T), nstates(s1), nstates(s2)); fill(zero(T), nstates(s2), nstates(s1)) s2.A] @@ -266,7 +266,7 @@ function *(sys1::StateSpace{TimeT,T,MT}, sys2::StateSpace{TimeT,T,MT}) where {Ti if sys1.nu != sys2.ny error("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs") end - Ts = common_sample_time(sys1.time,sys2.time) + Ts = common_sample_time(sys1,sys2) A = [sys1.A sys1.B*sys2.C; fill(zero(T), sys2.nx, sys1.nx) sys2.A] @@ -282,7 +282,7 @@ function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) if sys1.nu != sys2.ny error("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs") end - Ts = common_sample_time(sys1.time,sys2.time) + Ts = common_sample_time(sys1,sys2) T = promote_type(eltype(sys1.A),eltype(sys2.A)) A = [sys1.A sys1.B*sys2.C; fill(zero(T), sys2.nx, sys1.nx) sys2.A] diff --git a/src/types/TimeType.jl b/src/types/TimeType.jl index 8c194cebb..08fc3e239 100644 --- a/src/types/TimeType.jl +++ b/src/types/TimeType.jl @@ -21,10 +21,6 @@ Continuous(x::Continuous) = x # Simple converseion Discrete{T}(x::Discrete) where T = Discrete{T}(x.Ts) -# Interface for getting sample time -sampletime(x::Discrete) = x.Ts -sampletime(x::Continuous) = error("Continuous system has no sample time") # what system ?! -#sampletime(x::Static) = error("Static system has no sample time") undef_sampletime(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_SAMPLETIME) undef_sampletime(::Type{Continuous}) where T = Continuous() @@ -55,7 +51,7 @@ function common_sample_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} end end -common_sample_time(x::Continuous, ys::Continuous...) = Continuous() # Splatting needed? +common_sample_time(x::Continuous, ys::Continuous...) = Continuous() # Check equality ==(x::TimeType, y::TimeType) = false From 8a3cf9a392f0ca75dc144cd58b9e75ced5676bde Mon Sep 17 00:00:00 2001 From: olof3 Date: Tue, 14 Apr 2020 15:37:23 +0200 Subject: [PATCH 05/18] Fix for PartionedStateSpace. --- src/types/DelayLtiSystem.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/DelayLtiSystem.jl b/src/types/DelayLtiSystem.jl index 15c18b373..20672da4c 100644 --- a/src/types/DelayLtiSystem.jl +++ b/src/types/DelayLtiSystem.jl @@ -4,7 +4,7 @@ Represents an LTISystem with internal time-delay. See `?delay` for a convenience constructor. """ struct DelayLtiSystem{T,S<:Real} <: LTISystem{Continuous} - P::PartionedStateSpace{StateSpace{Continuous,T,Matrix{T}}} + P::PartionedStateSpace{Continuous,StateSpace{Continuous,T,Matrix{T}}} Tau::Vector{S} # The length of the vector tau implicitly defines the partitionging of P # function DelayLtiSystem(P::StateSpace{Continuous,T, MT}, Tau::Vector{T}) @@ -44,7 +44,7 @@ function DelayLtiSystem{T,S}(sys::StateSpace, Tau::AbstractVector{S} = Float64[] throw(ArgumentError("The delay vector of length $length(Tau) is too long.")) end - psys = PartionedStateSpace{StateSpace{Continuous,T,Matrix{T}}}(sys, nu, ny) + psys = PartionedStateSpace{Continuous,StateSpace{Continuous,T,Matrix{T}}}(sys, nu, ny) DelayLtiSystem{T,S}(psys, Tau) end # For converting DelayLtiSystem{T,S} to different T From 02eabc7409cb2b3184759b1f79999db7fd7ff5dd Mon Sep 17 00:00:00 2001 From: olof3 Date: Tue, 14 Apr 2020 16:35:21 +0200 Subject: [PATCH 06/18] Minor fixes to doc strings. --- src/types/Lti.jl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/types/Lti.jl b/src/types/Lti.jl index 4b343ede2..8615e5fc8 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -44,28 +44,25 @@ end """`iscontinuous(sys)` -Returns `true` if `sys` is continuous, else returns `false`.""" +Returns `true` for a continuous-time system `sys`, else returns `false`.""" iscontinuous(::LTISystem) = false iscontinuous(::LTISystem{<:Continuous}) = true """`isdiscrete(sys)` -Returns `true` if `sys` is discrete, else returns `false`.""" +Returns `true` for a discrete-time system `sys`, else returns `false`.""" isdiscrete(::LTISystem) = false isdiscrete(::LTISystem{<:Discrete}) = true -"""`isstatic(sys)` - -Returns `true` if `sys` is static, else returns `false`.""" """`sampletime(sys)` -Returns the sampletime of a discrete time system, throws error if the system is continuous time or static. +Returns the sampletime of a discrete-time system, throws error if the system is continuous-time. Always ensure `isdiscrete` before using.""" sampletime(sys::LTISystem{<:Discrete}) = sys.time.Ts -sampletime(::LTISystem{<:Continuous}) = error("Continuous system has no sample time") +sampletime(::LTISystem{<:Continuous}) = error("Continuous-time system has no sample time") """`sampletype(sys)` Get the sampletype of system. Usually typeof(sys.time).""" -sampletype(sys) = typeof(sys.time) # QUESTION: Perhaps timetype or sampletimetype instead? +timetype(sys) = typeof(sys.time) common_sample_time(systems::LTISystem...) = common_sample_time(s.time for s in systems) From 1f9fcc6a85c2e51b7108431d59c93213c286ae71 Mon Sep 17 00:00:00 2001 From: olof3 Date: Tue, 14 Apr 2020 16:47:20 +0200 Subject: [PATCH 07/18] sampletype -> timetype --- src/ControlSystems.jl | 2 +- src/analysis.jl | 2 +- src/types/Lti.jl | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index 2e166c36e..bde51ec13 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -112,7 +112,7 @@ abstract type AbstractSystem end include("types/TimeType.jl") ## Added interface: # sampletime(Lti) -> Number -# sampletype(Lti) -> TimeType +# timetype(Lti) -> TimeType include("types/Lti.jl") diff --git a/src/analysis.jl b/src/analysis.jl index 41abc2bd9..93c087f3a 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -118,7 +118,7 @@ Compute the zeros, poles, and gains of system `sys`. `k` : Matrix{Float64}, (ny x nu)""" function zpkdata(sys::LTISystem) - G = convert(TransferFunction{sampletype(sys),SisoZpk}, sys) + G = convert(TransferFunction{timetype(sys),SisoZpk}, sys) zs = map(x -> x.z, G.matrix) ps = map(x -> x.p, G.matrix) diff --git a/src/types/Lti.jl b/src/types/Lti.jl index 8615e5fc8..3753565f0 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -60,8 +60,8 @@ Always ensure `isdiscrete` before using.""" sampletime(sys::LTISystem{<:Discrete}) = sys.time.Ts sampletime(::LTISystem{<:Continuous}) = error("Continuous-time system has no sample time") -"""`sampletype(sys)` -Get the sampletype of system. Usually typeof(sys.time).""" +"""`timetype(sys)` +Get the timetype of system. Usually typeof(sys.time).""" timetype(sys) = typeof(sys.time) common_sample_time(systems::LTISystem...) = common_sample_time(s.time for s in systems) From 2be212c188ad5f875d2ae1afcd11e43dce9791c7 Mon Sep 17 00:00:00 2001 From: olof3 Date: Wed, 15 Apr 2020 09:47:24 +0200 Subject: [PATCH 08/18] Ideas for name changes. --- src/ControlSystems.jl | 18 +------ src/analysis.jl | 11 ++-- src/connections.jl | 38 +++++++------- src/discrete.jl | 10 ++-- src/freqresp.jl | 17 +++--- src/matrix_comps.jl | 26 ++++----- src/plotting.jl | 8 +-- src/simplification.jl | 2 +- src/simulators.jl | 2 +- src/synthesis.jl | 4 +- src/timeresp.jl | 20 +++---- src/types/DelayLtiSystem.jl | 23 +++----- src/types/Lti.jl | 39 ++++++++------ src/types/PartionedStateSpace.jl | 27 +++++----- src/types/StateSpace.jl | 90 ++++++++++++++++---------------- src/types/TimeType.jl | 12 ++--- src/types/TransferFunction.jl | 49 ++++++++--------- src/types/conversion.jl | 16 +++--- test/runtests.jl | 1 + test/test_connections.jl | 3 +- 20 files changed, 201 insertions(+), 215 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index bde51ec13..5527a15fb 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -88,8 +88,8 @@ export LTISystem, numpoly, denpoly, sampletime, - iscontinuous, - isdiscrete, + is_continuous_time, + is_discrete_time, isstatic @@ -173,20 +173,6 @@ function covar(D::Union{AbstractMatrix,UniformScaling}, R) D*R*D' end -function Base.getproperty(sys::Union{StateSpace,HeteroStateSpace,TransferFunction}, s::Symbol) - if s === :Ts - # if !isdiscrete(sys) # NOTE this line seems to be breaking inference of isdiscrete (is there a test for this?) - if !isdiscrete(sys) - @warn "Getting sampletime 0.0 for non-discrete systems is deprecated. Check `isdiscrete` before trying to access sampletime." - return 0.0 - else - return sampletime(sys) - end - else - return getfield(sys, s) - end -end - # The path has to be evaluated upon initial import const __CONTROLSYSTEMS_SOURCE_DIR__ = dirname(Base.source_path()) diff --git a/src/analysis.jl b/src/analysis.jl index 93c087f3a..00a62c473 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -91,7 +91,7 @@ Compute the dcgain of system `sys`. equal to G(0) for continuous-time systems and G(1) for discrete-time systems.""" function dcgain(sys::LTISystem) - return iscontinuous(sys) ? evalfr(sys, 0) : evalfr(sys, 1) + return is_continuous_time(sys) ? evalfr(sys, 0) : evalfr(sys, 1) end """`markovparam(sys, n)` @@ -133,9 +133,8 @@ Compute the natural frequencies, `Wn`, and damping ratios, `zeta`, of the poles, `ps`, of `sys`""" function damp(sys::LTISystem) ps = pole(sys) - if !iscontinuous(sys) - Ts = isstatic(sys) ? 1 : sampletime(sys) - ps = log(ps)/Ts + if is_discrete_time(sys) + ps = log(ps)/sys.Ts end Wn = abs.(ps) order = sortperm(Wn; by=z->(abs(z), real(z), imag(z))) @@ -446,8 +445,8 @@ function delaymargin(G::LTISystem) ϕₘ *= π/180 ωϕₘ = m[3][i] dₘ = ϕₘ/ωϕₘ - if isdiscrete(G) - dₘ /= sampletime(G) # Give delay margin in number of sample times, as matlab does + if is_discrete_time(G) + dₘ /= G.Ts # Give delay margin in number of sample times, as matlab does end dₘ end diff --git a/src/connections.jl b/src/connections.jl index 7a6a7b35e..e4d19e74f 100644 --- a/src/connections.jl +++ b/src/connections.jl @@ -22,18 +22,18 @@ Append systems in block diagonal form """ function append(systems::(ST where ST<:AbstractStateSpace)...) ST = promote_type(typeof.(systems)...) - Ts = common_sample_time(systems...) + sampletime = common_sampletime(systems...) A = blockdiag([s.A for s in systems]...) B = blockdiag([s.B for s in systems]...) C = blockdiag([s.C for s in systems]...) D = blockdiag([s.D for s in systems]...) - return ST(A, B, C, D, Ts) + return ST(A, B, C, D, sampletime) end function append(systems::TransferFunction...) - Ts = common_sample_time(systems...) + sampletime = common_sampletime(systems...) mat = blockdiag([s.matrix for s in systems]...) - return TransferFunction(mat, Ts) + return TransferFunction(mat, sampletime) end append(systems::LTISystem...) = append(promote(systems...)...) @@ -62,8 +62,8 @@ function Base.vcat(systems::ST...) where ST <: AbstractStateSpace B = vcat([s.B for s in systems]...) C = blockdiag([s.C for s in systems]...) D = vcat([s.D for s in systems]...) - Ts = common_sample_time(systems...) - return ST(A, B, C, D, Ts) + sampletime = common_sampletime(systems...) + return ST(A, B, C, D, sampletime) end function Base.vcat(systems::TransferFunction...) @@ -72,10 +72,10 @@ function Base.vcat(systems::TransferFunction...) if !all(s.nu == nu for s in systems) error("All systems must have same input dimension") end - Ts = common_sample_time(systems...) + sampletime = common_sampletime(systems...) mat = vcat([s.matrix for s in systems]...) - return TransferFunction(mat, Ts) + return TransferFunction(mat, sampletime) end Base.vcat(systems::LTISystem...) = vcat(promote(systems...)...) @@ -86,13 +86,13 @@ function Base.hcat(systems::ST...) where ST <: AbstractStateSpace if !all(s.ny == ny for s in systems) error("All systems must have same output dimension") end - Ts = common_sample_time(systems...) + sampletime = common_sampletime(systems...) A = blockdiag([s.A for s in systems]...) B = blockdiag([s.B for s in systems]...) C = hcat([s.C for s in systems]...) D = hcat([s.D for s in systems]...) - return ST(A, B, C, D, Ts) + return ST(A, B, C, D, sampletime) end function Base.hcat(systems::TransferFunction...) @@ -101,10 +101,10 @@ function Base.hcat(systems::TransferFunction...) if !all(s.ny == ny for s in systems) error("All systems must have same output dimension") end - Ts = common_sample_time(systems...) + sampletime = common_sampletime(systems...) mat = hcat([s.matrix for s in systems]...) - return TransferFunction(mat, Ts) + return TransferFunction(mat, sampletime) end Base.hcat(systems::LTISystem...) = hcat(promote(systems...)...) @@ -166,7 +166,7 @@ function feedback(L::TransferFunction{<:TimeType,T}) where T<:SisoRational end P = numpoly(L) Q = denpoly(L) - tf(P, P+Q, L.time) + tf(P, P+Q, L.sampletime) end function feedback(L::TransferFunction{TimeT, T}) where {TimeT<:TimeType, T<:SisoZpk} @@ -179,7 +179,7 @@ function feedback(L::TransferFunction{TimeT, T}) where {TimeT<:TimeType, T<:Siso kden = denpol[end] # Get coeff of s^n # Create siso system sisozpk = T(L.matrix[1].z, roots(denpol), k/kden) - return TransferFunction{TimeT,T}(fill(sisozpk,1,1), L.time) + return TransferFunction{TimeT,T}(fill(sisozpk,1,1), L.sampletime) end """ @@ -201,13 +201,13 @@ function feedback(sys::Union{StateSpace, DelayLtiSystem}) end function feedback(sys1::StateSpace,sys2::StateSpace) - Ts = common_sample_time(sys1,sys2) + sampletime = common_sampletime(sys1,sys2) !(iszero(sys1.D) || iszero(sys2.D)) && error("There cannot be a direct term (D) in both sys1 and sys2") A = [sys1.A+sys1.B*(-sys2.D)*sys1.C sys1.B*(-sys2.C); sys2.B*sys1.C sys2.A+sys2.B*sys1.D*(-sys2.C)] B = [sys1.B; sys2.B*sys1.D] C = [sys1.C sys1.D*(-sys2.C)] - ss(A, B, C, sys1.D, Ts) + ss(A, B, C, sys1.D, sampletime) end @@ -230,7 +230,7 @@ See Zhou etc. for similar (somewhat less symmetric) formulas. U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[], Wperm=:, Zperm=:, pos_feedback::Bool=false) - Ts = common_sample_time(sys1,sys2) + sampletime = common_sampletime(sys1,sys2) if !(isa(Y1, Colon) || allunique(Y1)); @warn "Connecting single output to multiple inputs Y1=$Y1"; end if !(isa(Y2, Colon) || allunique(Y2)); @warn "Connecting single output to multiple inputs Y2=$Y2"; end @@ -299,7 +299,7 @@ See Zhou etc. for similar (somewhat less symmetric) formulas. s2_D12*R2*s1_D21 s2_D11 + α*s2_D12*R2*s1_D22*s2_D21] end - return StateSpace(A, B[:, Wperm], C[Zperm,:], D[Zperm, Wperm], Ts) + return StateSpace(A, B[:, Wperm], C[Zperm,:], D[Zperm, Wperm], sampletime) end @@ -309,7 +309,7 @@ end """ function feedback2dof(P::TransferFunction,R,S,T) !issiso(P) && error("Feedback not implemented for MIMO systems") - tf(conv(poly2vec(numpoly(P)[1]),T),zpconv(poly2vec(denpoly(P)[1]),R,poly2vec(numpoly(P)[1]),S), P.time) + tf(conv(poly2vec(numpoly(P)[1]),T),zpconv(poly2vec(denpoly(P)[1]),R,poly2vec(numpoly(P)[1]),S), P.sampletime) end feedback2dof(B,A,R,S,T) = tf(conv(B,T),zpconv(A,R,B,S)) diff --git a/src/discrete.jl b/src/discrete.jl index 10cc172e0..9e5f14c82 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -10,7 +10,7 @@ Returns the discrete system `sysd`, and a matrix `x0map` that transforms the initial conditions to the discrete domain by `x0_discrete = x0map*[x0; u0]`""" function c2d(sys::StateSpace, Ts::Real, method::Symbol=:zoh) - if isdiscrete(sys) + if is_discrete_time(sys) error("sys must be a continuous time system") end A, B, C, D = ssdata(sys) @@ -189,7 +189,7 @@ end function c2d(G::TransferFunction, h;kwargs...) - @assert iscontinuous(G) + @assert is_continuous_time(G) ny, nu = size(G) @assert (ny + nu == 2) "c2d(G::TransferFunction, h) not implemented for MIMO systems" sys = ss(G) @@ -218,11 +218,11 @@ function lsima(sys::StateSpace, t::AbstractVector, r::AbstractVector, control_si end dt = Float64(t[2] - t[1]) - if !iscontinuous(sys) || method == :zoh - if iscontinuous(sys) + if !is_continuous_time(sys) || method == :zoh + if is_continuous_time(sys) dsys = c2d(sys, dt, :zoh)[1] else - if sampletime(sys) != dt + if sys.Ts != dt error("Time vector must match sample time for discrete system") end dsys = sys diff --git a/src/freqresp.jl b/src/freqresp.jl index 4552be0af..91939949a 100644 --- a/src/freqresp.jl +++ b/src/freqresp.jl @@ -7,11 +7,10 @@ Evaluate the frequency response of a linear system of system `sys` over the frequency vector `w`.""" function freqresp(sys::LTISystem, w_vec::AbstractVector{<:Real}) # Create imaginary freq vector s - if iscontinuous(sys) + if is_continuous_time(sys) s_vec = im*w_vec else - Ts = sampletime(sys) - s_vec = exp.(w_vec*(im*Ts)) + s_vec = exp.(w_vec*(im*sys.Ts)) end if isa(sys, StateSpace) sys = _preprocess_for_freqresp(sys) @@ -36,7 +35,7 @@ function _preprocess_for_freqresp(sys::StateSpace) P = C*T Q = T\B # TODO Type stability? # T is unitary, so mutliplication with T' should do the trick # FIXME; No performance improvement from Hessienberg structure, also weired renaming of matrices - StateSpace(F.H, Q, P, D, sys.time) + StateSpace(F.H, Q, P, D, sys.sampletime) end @@ -73,16 +72,16 @@ function (sys::TransferFunction)(s) end function (sys::TransferFunction)(z_or_omega::Number, map_to_unit_circle::Bool) - @assert isdiscrete(sys) "It only makes no sense to call this function with discrete systems" + @assert is_discrete_time(sys) "It only makes no sense to call this function with discrete systems" if map_to_unit_circle - isreal(z_or_omega) ? evalfr(sys,exp(im*z_or_omega.*sampletime(sys))) : error("To map to the unit circle, omega should be real") + isreal(z_or_omega) ? evalfr(sys,exp(im*z_or_omega.*sys.Ts)) : error("To map to the unit circle, omega should be real") else evalfr(sys,z_or_omega) end end function (sys::TransferFunction)(z_or_omegas::AbstractVector, map_to_unit_circle::Bool) - @assert isdiscrete(sys) "It only makes no sense to call this function with discrete systems" + @assert is_discrete_time(sys) "It only makes no sense to call this function with discrete systems" vals = sys.(z_or_omegas, map_to_unit_circle)# evalfr.(sys,exp.(evalpoints)) # Reshape from vector of evalfr matrizes, to (in,out,freq) Array nu,ny = size(vals[1]) @@ -169,8 +168,8 @@ function _bounds_and_features(sys::LTISystem, plot::Val) w1 = 0.0 w2 = 2.0 end - if !iscontinuous(sys) && !isstatic(sys) # Do not draw above Nyquist freq for disc. systems - w2 = min(w2, log10(π/sampletime(sys))) + if !is_continuous_time(sys) && !isstatic(sys) # Do not draw above Nyquist freq for disc. systems + w2 = min(w2, log10(π/sys.Ts)) end return [w1, w2], zp end diff --git a/src/matrix_comps.jl b/src/matrix_comps.jl index 9fef4b223..e12befc1c 100644 --- a/src/matrix_comps.jl +++ b/src/matrix_comps.jl @@ -86,7 +86,7 @@ function gram(sys::AbstractStateSpace, opt::Symbol) if !isstable(sys) error("gram only valid for stable A") end - func = iscontinuous(sys) ? lyap : dlyap + func = is_continuous_time(sys) ? lyap : dlyap if opt == :c # TODO probably remove type check in julia 0.7.0 return func(sys.A, sys.B*sys.B')#::Array{numeric_type(sys),2} # lyap is type-unstable @@ -162,14 +162,14 @@ function covar(sys::AbstractStateSpace, W) if !isstable(sys) return fill(Inf,(size(C,1),size(C,1))) end - func = iscontinuous(sys) ? lyap : dlyap + func = is_continuous_time(sys) ? lyap : dlyap Q = try func(A, B*W*B') catch error("No solution to the Lyapunov equation was found in covar") end P = C*Q*C' - if !isdiscrete(sys) + if !is_discrete_time(sys) #Variance and covariance infinite for direct terms direct_noise = D*W*D' for i in 1:size(C,1) @@ -269,7 +269,7 @@ state space systems in continuous and discrete time', American Control Conferenc See also [`hinfnorm`](@ref). """ function linfnorm(sys::AbstractStateSpace; tol=1e-6) - if iscontinuous(sys) + if is_continuous_time(sys) return _infnorm_two_steps_ct(sys, :linf, tol) else return _infnorm_two_steps_dt(sys, :linf, tol) @@ -369,8 +369,8 @@ function _infnorm_two_steps_dt(sys::AbstractStateSpace, normtype::Symbol, tol=1e on_unit_circle = z -> abs(abs(z) - 1) < approxcirc # Helper fcn for readability - T = promote_type(real(numeric_type(sys)), Float64, typeof(true/sampletime(sys))) - Tw = typeof(one(T)/sampletime(sys)) + T = promote_type(real(numeric_type(sys)), Float64, typeof(true/sys.Ts)) + Tw = typeof(one(T)/sys.Ts) if sys.nx == 0 # static gain return (T(opnorm(sys.D)), Tw(0)) @@ -381,7 +381,7 @@ function _infnorm_two_steps_dt(sys::AbstractStateSpace, normtype::Symbol, tol=1e # Check if there is a pole on the unit circle pidx = findfirst(on_unit_circle, pole_vec) if !(pidx isa Nothing) - return T(Inf), Tw(angle(pole_vec[pidx])/sampletime(sys)) + return T(Inf), Tw(angle(pole_vec[pidx])/sys.Ts) end if normtype == :hinf && any(z -> abs(z) > 1, pole_vec) @@ -434,7 +434,7 @@ function _infnorm_two_steps_dt(sys::AbstractStateSpace, normtype::Symbol, tol=1e sort!(θ_vec) if isempty(θ_vec) - return T((1+tol)*lb), Tw(θ_peak/sampletime(sys)) + return T((1+tol)*lb), Tw(θ_peak/sys.Ts) end # Improve the lower bound @@ -526,7 +526,7 @@ function balreal(sys::ST) where ST <: AbstractStateSpace display(Σ) end - sysr = ST(T*sys.A/T, T*sys.B, sys.C/T, sys.D, sys.time), diagm(0 => Σ) + sysr = ST(T*sys.A/T, T*sys.B, sys.C/T, sys.D, sys.sampletime), diagm(0 => Σ) end @@ -553,7 +553,7 @@ function baltrunc(sys::ST; atol = sqrt(eps()), rtol = 1e-3, unitgain = true) whe D = D/(C*inv(-A)*B) end - return ST(A,B,C,D,sys.time), diagm(0 => S) + return ST(A,B,C,D,sys.sampletime), diagm(0 => S) end """ @@ -572,7 +572,7 @@ function similarity_transform(sys::ST, T) where ST <: AbstractStateSpace B = Tf\sys.B C = sys.C*T D = sys.D - ST(A,B,C,D,sys.time) + ST(A,B,C,D,sys.sampletime) end """ @@ -597,10 +597,10 @@ See Stochastic Control, Chapter 4, Åström """ function innovation_form(sys::ST, R1, R2) where ST <: AbstractStateSpace K = kalman(sys, R1, R2) - ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.time) + ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.sampletime) end # Set D = I to get transfer function H = I + C(sI-A)\ K function innovation_form(sys::ST; sysw=I, syse=I, R1=I, R2=I) where ST <: AbstractStateSpace K = kalman(sys, covar(sysw,R1), covar(syse, R2)) - ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.time) + ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.sampletime) end diff --git a/src/plotting.jl b/src/plotting.jl index dc23a0a86..2dfe25078 100644 --- a/src/plotting.jl +++ b/src/plotting.jl @@ -141,7 +141,7 @@ lsimplot s = systems[si] y = length(p.args) >= 4 ? lsim(s, u, t, x0=p.args[4], method=method)[1] : lsim(s, u, t, method=method)[1] styledict = getStyleSys(si,length(systems)) - seriestype := iscontinuous(s) ? :path : :steppost + seriestype := is_continuous_time(s) ? :path : :steppost for i=1:ny ytext = (ny > 1) ? "Amplitude to: y($i)" : "Amplitude" @series begin @@ -205,7 +205,7 @@ for (func, title, typ) = ((step, "Step Response", Stepplot), (impulse, "Impulse for i=1:ny for j=1:nu ydata = reshape(y[:, i, j], size(t, 1)) - style = iscontinuous(s) ? :path : :steppost + style = is_continuous_time(s) ? :path : :steppost ttext = (nu > 1 && i==1) ? title*" from: u($j) " : title titles[s2i(i,j)] = ttext ytext = (ny > 1 && j==1) ? "Amplitude to: y($i)" : "Amplitude" @@ -470,7 +470,7 @@ nicholsplot systems, w = _processfreqplot(Val{:nyquist}(), p.args...) ny, nu = size(systems[1]) - if isdiscrete(systems[1]) + if is_discrete_time(systems[1]) w_nyquist = 2π/sampletime(systems[1]) w = w[w.<= w_nyquist] end @@ -744,7 +744,7 @@ pzmap end end - if isdiscrete(system) + if is_discrete_time(system) v = range(0,stop=2π,length=100) S,C = sin.(v),cos.(v) @series begin diff --git a/src/simplification.jl b/src/simplification.jl index 219c84556..ef668c3cd 100644 --- a/src/simplification.jl +++ b/src/simplification.jl @@ -6,7 +6,7 @@ determined to be uncontrollable and unobservable based on the location of 0s in `sys` are removed.""" function sminreal(sys::StateSpace) A, B, C, inds = struct_ctrb_obsv(sys) - return StateSpace(A, B, C, sys.D, sys.time) + return StateSpace(A, B, C, sys.D, sys.sampletime) end # Determine the structurally controllable and observable realization for the system diff --git a/src/simulators.jl b/src/simulators.jl index 6d89dc3c8..f1f27845e 100644 --- a/src/simulators.jl +++ b/src/simulators.jl @@ -37,7 +37,7 @@ plot(t, s.y(sol, t)[:], lab="Open loop step response") ``` """ function Simulator(P::AbstractStateSpace, u::F = (x,t) -> 0) where F - @assert iscontinuous(P) "Simulator only supports continuous-time system. See function `lsim` for simulation of discrete-time systems." + @assert is_continuous_time(P) "Simulator only supports continuous-time system. See function `lsim` for simulation of discrete-time systems." @assert all(P.D .== 0) "Can not simulate systems with direct term D != 0" f = (dx,x,p,t) -> dx .= P.A*x .+ P.B*u(x,t) y(x,t) = P.C*x #.+ P.D*u(x,t) diff --git a/src/synthesis.jl b/src/synthesis.jl index 2999f24a5..5d75f4067 100644 --- a/src/synthesis.jl +++ b/src/synthesis.jl @@ -48,7 +48,7 @@ See also `LQG` kalman(A, C, R1,R2) = Matrix(lqr(A',C',R1,R2)') function lqr(sys::StateSpace, Q, R) - if iscontinuous(sys) + if is_continuous_time(sys) return lqr(sys.A, sys.B, Q, R) else return dlqr(sys.A, sys.B, Q, R) @@ -56,7 +56,7 @@ function lqr(sys::StateSpace, Q, R) end function kalman(sys::StateSpace, R1,R2) - if iscontinuous(sys) + if is_continuous_time(sys) return Matrix(lqr(sys.A', sys.C', R1,R2)') else return Matrix(dlqr(sys.A', sys.C', R1,R2)') diff --git a/src/timeresp.jl b/src/timeresp.jl index be2bfa48d..00c42d361 100644 --- a/src/timeresp.jl +++ b/src/timeresp.jl @@ -43,14 +43,14 @@ function impulse(sys::StateSpace, t::AbstractVector; method=:cont) lt = length(t) ny, nu = size(sys) nx = sys.nx - if iscontinuous(sys) || isstatic(sys) #&& method == :cont + if is_continuous_time(sys) || isstatic(sys) #&& method == :cont u = (x,i) -> [zero(T)] # impulse response equivalent to unforced response of # ss(A, 0, C, 0) with x0 = B. imp_sys = ss(sys.A, zeros(T, nx, 1), sys.C, zeros(T, ny, 1)) x0s = sys.B else - u = (x,i) -> i == t[1] ? [one(T)]/sampletime(sys) : [zero(T)] + u = (x,i) -> i == t[1] ? [one(T)]/sys.Ts : [zero(T)] imp_sys = sys x0s = zeros(T, nx, nu) end @@ -118,11 +118,11 @@ function lsim(sys::StateSpace, u::AbstractVecOrMat, t::AbstractVector; end dt = Float64(t[2] - t[1]) - if !iscontinuous(sys) || method == :zoh - if !isdiscrete(sys) + if !is_continuous_time(sys) || method == :zoh + if !is_discrete_time(sys) dsys = c2d(sys, dt, :zoh)[1] else - if sampletime(sys) != dt + if sys.Ts != dt error("Time vector must match sample time for discrete system") end dsys = sys @@ -151,11 +151,11 @@ function lsim(sys::StateSpace, u::Function, t::AbstractVector; T = promote_type(Float64, eltype(x0)) dt = T(t[2] - t[1]) - if !iscontinuous(sys) || method == :zoh - if !isdiscrete(sys) + if !is_continuous_time(sys) || method == :zoh + if !is_discrete_time(sys) dsys = c2d(sys, dt, :zoh)[1] else - if sampletime(sys) != dt + if sys.Ts != dt error("Time vector must match sample time for discrete system") end dsys = sys @@ -226,8 +226,8 @@ function _default_time_vector(sys::LTISystem, Tf::Real=-1) end function _default_Ts(sys::LTISystem) - if isdiscrete(sys) - Ts = sampletime(sys) + if is_discrete_time(sys) + Ts = sys.Ts elseif !isstable(sys) Ts = 0.05 else diff --git a/src/types/DelayLtiSystem.jl b/src/types/DelayLtiSystem.jl index 20672da4c..31ac961ba 100644 --- a/src/types/DelayLtiSystem.jl +++ b/src/types/DelayLtiSystem.jl @@ -3,8 +3,8 @@ Represents an LTISystem with internal time-delay. See `?delay` for a convenience constructor. """ -struct DelayLtiSystem{T,S<:Real} <: LTISystem{Continuous} - P::PartionedStateSpace{Continuous,StateSpace{Continuous,T,Matrix{T}}} +struct DelayLtiSystem{T,S<:Real} <: LTISystem + P::PartionedStateSpace{StateSpace{Continuous,T,Matrix{T}}} Tau::Vector{S} # The length of the vector tau implicitly defines the partitionging of P # function DelayLtiSystem(P::StateSpace{Continuous,T, MT}, Tau::Vector{T}) @@ -16,18 +16,7 @@ struct DelayLtiSystem{T,S<:Real} <: LTISystem{Continuous} # end end -# Fallback since system is always continuous -function getproperty(sys::DelayLtiSystem, s::Symbol) - if s == :Ts - return sys.P.Ts # Will throw deprecation until removed # DEPRECATED - end - return getfield(sys, s) -end - sampletime(sys::DelayLtiSystem) = sampletime(sys.P) -iscontinuous(sys::DelayLtiSystem) = iscontinuous(sys.P) -isdiscrete(sys::DelayLtiSystem) = isdiscrete(sys.P) -isstatic(sys::DelayLtiSystem) = isstatic(sys.P) # QUESTION: would psys be a good standard variable name for a PartionedStateSpace # and perhaps dsys for a delayed system, (ambigous with discrete system though) @@ -44,7 +33,7 @@ function DelayLtiSystem{T,S}(sys::StateSpace, Tau::AbstractVector{S} = Float64[] throw(ArgumentError("The delay vector of length $length(Tau) is too long.")) end - psys = PartionedStateSpace{Continuous,StateSpace{Continuous,T,Matrix{T}}}(sys, nu, ny) + psys = PartionedStateSpace{StateSpace{Continuous,T,Matrix{T}}}(sys, nu, ny) DelayLtiSystem{T,S}(psys, Tau) end # For converting DelayLtiSystem{T,S} to different T @@ -87,7 +76,7 @@ Base.convert(::Type{V}, sys::DelayLtiSystem) where {T, V<:DelayLtiSystem{T}} = function *(sys::DelayLtiSystem, n::Number) new_C = [sys.P.C1*n; sys.P.C2] new_D = [sys.P.D11*n sys.P.D12*n; sys.P.D21 sys.P.D22] - return DelayLtiSystem(StateSpace(sys.P.A, sys.P.B, new_C, new_D, sys.P.time), sys.Tau) + return DelayLtiSystem(StateSpace(sys.P.A, sys.P.B, new_C, new_D, sys.P.sampletime), sys.Tau) end *(n::Number, sys::DelayLtiSystem) = *(sys, n) @@ -155,7 +144,7 @@ function Base.getindex(sys::DelayLtiSystem, i::AbstractArray, j::AbstractArray) sys.P.B[:, colidx], sys.P.C[rowidx, :], sys.P.D[rowidx, colidx], - sys.P.time), sys.Tau) + sys.P.sampletime), sys.Tau) end function Base.show(io::IO, sys::DelayLtiSystem) @@ -217,7 +206,7 @@ Create a time delay of length `tau` with `exp(-τ*s)` where `s=tf("s")` and `τ` See also: [`delay`](@ref) which is arguably more conenient than this function. """ function Base.exp(G::TransferFunction{Continuous,<:SisoRational}) - if size(G.matrix) != (1,1) && iscontinuous(G) + if size(G.matrix) != (1,1) && is_continuous_time(G) error("G must be a continuous-time scalar transfer function. Consider using `delay` instead.") end G_siso = G.matrix[1,1] diff --git a/src/types/Lti.jl b/src/types/Lti.jl index 3753565f0..f798ae231 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -1,4 +1,4 @@ -abstract type LTISystem{T <:TimeType} <: AbstractSystem end +abstract type LTISystem <: AbstractSystem end +(sys1::LTISystem, sys2::LTISystem) = +(promote(sys1, sys2)...) -(sys1::LTISystem, sys2::LTISystem) = -(promote(sys1, sys2)...) *(sys1::LTISystem, sys2::LTISystem) = *(promote(sys1, sys2)...) @@ -42,36 +42,43 @@ function issiso(sys::LTISystem) end -"""`iscontinuous(sys)` +"""`is_continuous_time(sys)` Returns `true` for a continuous-time system `sys`, else returns `false`.""" -iscontinuous(::LTISystem) = false -iscontinuous(::LTISystem{<:Continuous}) = true -"""`isdiscrete(sys)` +is_continuous_time(sys::LTISystem) = sampletime(sys) isa Continuous +"""`is_discrete_time(sys)` Returns `true` for a discrete-time system `sys`, else returns `false`.""" -isdiscrete(::LTISystem) = false -isdiscrete(::LTISystem{<:Discrete}) = true +is_discrete_time(sys::LTISystem) = sampletime(sys) isa Discrete -"""`sampletime(sys)` -Returns the sampletime of a discrete-time system, throws error if the system is continuous-time. -Always ensure `isdiscrete` before using.""" -sampletime(sys::LTISystem{<:Discrete}) = sys.time.Ts -sampletime(::LTISystem{<:Continuous}) = error("Continuous-time system has no sample time") +function Base.getproperty(sys::LTISystem, s::Symbol) + if s === :Ts + # if !is_discrete_time(sys) # NOTE this line seems to be breaking inference of is_discrete_time (is there a test for this?) + if !is_discrete_time(sys) + @warn "Getting sampletime 0.0 for non-discrete systems is deprecated. Check `is_discrete_time` before trying to access sampletime." + return 0.0 + else + return sampletime(sys).Ts + end + else + return getfield(sys, s) + end +end + """`timetype(sys)` -Get the timetype of system. Usually typeof(sys.time).""" -timetype(sys) = typeof(sys.time) +Get the timetype of system. Usually typeof(sys.sampletime).""" +timetype(sys) = typeof(sys.sampletime) -common_sample_time(systems::LTISystem...) = common_sample_time(s.time for s in systems) +common_sampletime(systems::LTISystem...) = common_sampletime(sampletime(sys) for sys in systems) """`isstable(sys)` Returns `true` if `sys` is stable, else returns `false`.""" function isstable(sys::LTISystem) - if iscontinuous(sys) + if is_continuous_time(sys) if any(real.(pole(sys)).>=0) return false end diff --git a/src/types/PartionedStateSpace.jl b/src/types/PartionedStateSpace.jl index 0da76cf25..05053d600 100644 --- a/src/types/PartionedStateSpace.jl +++ b/src/types/PartionedStateSpace.jl @@ -11,7 +11,7 @@ u = [u1 u2]^T y = [y1 y2]^T """ -struct PartionedStateSpace{TimeT,S<:AbstractStateSpace{TimeT}} <: LTISystem{TimeT} +struct PartionedStateSpace{S<:AbstractStateSpace} <: LTISystem P::S nu1::Int ny1::Int @@ -27,8 +27,8 @@ function getproperty(sys::PartionedStateSpace, d::Symbol) if d === :Ts return P.Ts # Will throw deprecation until removed # DEPRECATED - elseif d === :time - return P.time + elseif d === :sampletime + return P.sampletime elseif d === :P return P elseif d === :nu1 @@ -58,9 +58,10 @@ function getproperty(sys::PartionedStateSpace, d::Symbol) end end +sampletime(sys::PartionedStateSpace) = sampletime(sys.P) function +(s1::PartionedStateSpace, s2::PartionedStateSpace) - Ts = common_sample_time(s1.P,s2.P) + sampletime = common_sampletime(s1,s2) A = blockdiag(s1.A, s2.A) @@ -72,7 +73,7 @@ function +(s1::PartionedStateSpace, s2::PartionedStateSpace) D = [(s1.D11 + s2.D11) s1.D12 s2.D12; [s1.D21; s2.D21] blockdiag(s1.D22, s2.D22)] - P = StateSpace(A, B, C, D, Ts) # How to handle discrete? + P = StateSpace(A, B, C, D, sampletime) # How to handle discrete? PartionedStateSpace(P, s1.nu1 + s2.nu1, s1.ny1 + s2.ny1) end @@ -84,7 +85,7 @@ end Series connection of partioned StateSpace systems. """ function *(s1::PartionedStateSpace, s2::PartionedStateSpace) - Ts = common_sample_time(s1.P,s2.P) + sampletime = common_sampletime(s1,s2) A = [s1.A s1.B1*s2.C1; zeros(size(s2.A,1),size(s1.A,2)) s2.A] @@ -100,7 +101,7 @@ function *(s1::PartionedStateSpace, s2::PartionedStateSpace) s1.D21*s2.D11 s1.D22 s1.D21*s2.D12; s2.D21 zeros(size(s2.D22,1),size(s1.D22,2)) s2.D22 ] - P = StateSpace(A, B, C, D, Ts) + P = StateSpace(A, B, C, D, sampletime) PartionedStateSpace(P, s2.nu1, s1.ny1) end @@ -108,7 +109,7 @@ end # QUESTION: What about algebraic loops and well-posedness?! Perhaps issue warning if P1(∞)*P2(∞) > 1 function feedback(s1::PartionedStateSpace, s2::PartionedStateSpace) - Ts = common_sample_time(s1.P,s2.P) + sampletime = common_sampletime(s1,s2) X_11 = (I + s2.D11*s1.D11)\[-s2.D11*s1.C1 -s2.C1] X_21 = (I + s1.D11*s2.D11)\[s1.C1 -s1.D11*s2.C1] @@ -147,7 +148,7 @@ function feedback(s1::PartionedStateSpace, s2::PartionedStateSpace) #tmp = [blockdiag(s1.D12, s2.D12); blockdiag(s1.D22, s2.D22)] #D[:, end-size(tmp,2)+1:end] .+= tmp - P = StateSpace(A, B, C, D, Ts) + P = StateSpace(A, B, C, D, sampletime) PartionedStateSpace(P, s2.nu1, s1.ny1) end @@ -160,7 +161,7 @@ end """ function vcat_1(systems::PartionedStateSpace...) # Perform checks - Ts = common_sample_time(sys.P for sys in systems) + sampletime = common_sampletime(systems...) nu1 = systems[1].nu1 if !all(s.nu1 == nu1 for s in systems) @@ -180,7 +181,7 @@ function vcat_1(systems::PartionedStateSpace...) D21 = reduce(vcat, [s.D21 for s in systems]) D22 = blockdiag([s.D22 for s in systems]...) - sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], Ts) + sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], sampletime) return PartionedStateSpace(sysnew, nu1, sum(s -> s.ny1, systems)) end @@ -194,7 +195,7 @@ end """ function hcat_1(systems::PartionedStateSpace...) # Perform checks - Ts = common_sample_time(sys.P for sys in systems) + sampletime = common_sampletime(systems...) ny1 = systems[1].ny1 if !all(s.ny1 == ny1 for s in systems) @@ -214,7 +215,7 @@ function hcat_1(systems::PartionedStateSpace...) D21 = blockdiag([s.D21 for s in systems]...) D22 = blockdiag([s.D22 for s in systems]...) - sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], Ts) + sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], sampletime) return PartionedStateSpace(sysnew, sum(s -> s.nu1, systems), ny1) end diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index 2be8926b1..63dba1e12 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -2,7 +2,7 @@ ## Data Type Declarations ## ##################################################################### -function state_space_validation(A,B,C,D,Ts::TimeType) +function state_space_validation(A,B,C,D,sampletime::TimeType) nx = size(A, 1) nu = size(B, 2) ny = size(C, 1) @@ -22,24 +22,26 @@ function state_space_validation(A,B,C,D,Ts::TimeType) nx,nu,ny end -abstract type AbstractStateSpace{TimeT} <: LTISystem{TimeT} end +abstract type AbstractStateSpace{TimeT<:TimeType} <: LTISystem end + +sampletime(sys::AbstractStateSpace) = sys.sampletime struct StateSpace{TimeT, T, MT<:AbstractMatrix{T}} <: AbstractStateSpace{TimeT} A::MT B::MT C::MT D::MT - time::TimeT + sampletime::TimeT nx::Int nu::Int ny::Int - function StateSpace{TimeT, T, MT}(A::MT, B::MT, C::MT, D::MT, Ts::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} - nx,nu,ny = state_space_validation(A,B,C,D,Ts) - new{TimeT, T, MT}(A, B, C, D, Ts, nx, nu, ny) + function StateSpace{TimeT, T, MT}(A::MT, B::MT, C::MT, D::MT, sampletime::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} + nx,nu,ny = state_space_validation(A,B,C,D,sampletime) + new{TimeT, T, MT}(A, B, C, D, sampletime, nx, nu, ny) end end -function StateSpace(A::MT, B::MT, C::MT, D::MT, Ts::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} - StateSpace{TimeT, T, MT}(A, B, C, D, Ts) +function StateSpace(A::MT, B::MT, C::MT, D::MT, sampletime::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} + StateSpace{TimeT, T, MT}(A, B, C, D, sampletime) end # Constructor for Discrete system function StateSpace(A::MT, B::MT, C::MT, D::MT, Ts::Number) where {T, MT <: AbstractMatrix{T}} @@ -72,19 +74,19 @@ end const AbstractNumOrArray = Union{Number, AbstractVecOrMat} # Explicit construction with different types -function StateSpace{TimeT,T,MT}(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::TimeType) where {TimeT, T, MT <: AbstractMatrix{T}} +function StateSpace{TimeT,T,MT}(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, sampletime::TimeType) where {TimeT, T, MT <: AbstractMatrix{T}} D = fix_D_matrix(T, B, C, D) - return StateSpace{TimeT, T,Matrix{T}}(MT(to_matrix(T, A)), MT(to_matrix(T, B)), MT(to_matrix(T, C)), MT(D), TimeT(Ts)) + return StateSpace{TimeT, T,Matrix{T}}(MT(to_matrix(T, A)), MT(to_matrix(T, B)), MT(to_matrix(T, C)), MT(D), TimeT(sampletime)) end # Explicit conversion function StateSpace{TimeT,T,MT}(sys::StateSpace) where {TimeT, T, MT <: AbstractMatrix{T}} - StateSpace{TimeT,T,MT}(sys.A,sys.B,sys.C,sys.D,sys.time) + StateSpace{TimeT,T,MT}(sys.A,sys.B,sys.C,sys.D,sys.sampletime) end -function StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::TimeType) +function StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, sampletime::TimeType) A, B, C, D, T = to_similar_matrices(A,B,C,D) - return StateSpace{typeof(Ts),T,Matrix{T}}(A, B, C, D, Ts) + return StateSpace{typeof(sampletime),T,Matrix{T}}(A, B, C, D, sampletime) end # General Discrete constructor StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::Number) = @@ -94,13 +96,13 @@ StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, StateSpace(A, B, C, D, Continuous()) # Function for creation of static gain -function StateSpace(D::AbstractArray{T}, Ts::TimeType) where {T<:Number} +function StateSpace(D::AbstractArray{T}, sampletime::TimeType) where {T<:Number} ny, nu = size(D, 1), size(D, 2) A = fill(zero(T), 0, 0) B = fill(zero(T), 0, nu) C = fill(zero(T), ny, 0) D = reshape(D, (ny,nu)) - return StateSpace(A, B, C, D, Ts) + return StateSpace(A, B, C, D, sampletime) end StateSpace(D::AbstractArray, Ts::Number) = StateSpace(D, Discrete(Ts)) StateSpace(D::AbstractArray) = StateSpace(D, Continuous()) @@ -131,27 +133,27 @@ struct HeteroStateSpace{TimeT, AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:Abstrac B::BT C::CT D::DT - time::TimeT + sampletime::TimeT nx::Int nu::Int ny::Int end function HeteroStateSpace(A::AT, B::BT, - C::CT, D::DT, Ts::TimeT) where {TimeT<:TimeType,AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:AbstractMatrix,DT<:AbstractMatrix} - nx,nu,ny = state_space_validation(A,B,C,D,Ts) - HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, Ts, nx, nu, ny) + C::CT, D::DT, sampletime::TimeT) where {TimeT<:TimeType,AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:AbstractMatrix,DT<:AbstractMatrix} + nx,nu,ny = state_space_validation(A,B,C,D,sampletime) + HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, sampletime, nx, nu, ny) end # Explicit constructor -function HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, Ts) where {TimeT,AT,BT,CT,DT} - nx,nu,ny = state_space_validation(A,B,C,D,Ts) - HeteroStateSpace{TimeT,AT,BT,CT,DT}(AT(A), BT(B), CT(C), DT(D), TimeT(Ts), nx, nu, ny) +function HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, sampletime) where {TimeT,AT,BT,CT,DT} + nx,nu,ny = state_space_validation(A,B,C,D,sampletime) + HeteroStateSpace{TimeT,AT,BT,CT,DT}(AT(A), BT(B), CT(C), DT(D), TimeT(sampletime), nx, nu, ny) end -HeteroStateSpace(s::AbstractStateSpace) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.time) +HeteroStateSpace(s::AbstractStateSpace) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.sampletime) # Base constructor -function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::TimeType) +function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, sampletime::TimeType) A = to_abstract_matrix(A) B = to_abstract_matrix(B) C = to_abstract_matrix(C) @@ -160,7 +162,7 @@ function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::Abstr else D = to_abstract_matrix(D) end - return HeteroStateSpace{typeof(Ts),typeof(A),typeof(B),typeof(C),typeof(D)}(A, B, C, D, Ts) + return HeteroStateSpace{typeof(sampletime),typeof(A),typeof(B),typeof(C),typeof(D)}(A, B, C, D, sampletime) end HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::Number) = @@ -170,13 +172,13 @@ HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrA # Function for creation of static gain -function HeteroStateSpace(D::AbstractArray{T}, Ts::TimeType) where {T<:Number} +function HeteroStateSpace(D::AbstractArray{T}, sampletime::TimeType) where {T<:Number} ny, nu = size(D, 1), size(D, 2) A = fill(zero(T), 0, 0) B = fill(zero(T), 0, nu) C = fill(zero(T), ny, 0) - return HeteroStateSpace(A, B, C, D, Ts) + return HeteroStateSpace(A, B, C, D, sampletime) end HeteroStateSpace(D::AbstractArray{T}, Ts::Number) where {T<:Number} = HeteroStateSpace(D, Discrete(Ts)) @@ -221,7 +223,7 @@ function +(s1::StateSpace{TimeT,T,MT}, s2::StateSpace{TimeT,T,MT}) where {TimeT, if size(s1) != size(s2) error("Systems have different shapes.") end - Ts = common_sample_time(s1,s2) + sampletime = common_sampletime(s1,s2) A = [s1.A fill(zero(T), nstates(s1), nstates(s2)); fill(zero(T), nstates(s2), nstates(s1)) s2.A] @@ -229,7 +231,7 @@ function +(s1::StateSpace{TimeT,T,MT}, s2::StateSpace{TimeT,T,MT}) where {TimeT, C = [s1.C s2.C;] D = [s1.D + s2.D;] - return StateSpace{TimeT,T,MT}(A, B, C, D, Ts) + return StateSpace{TimeT,T,MT}(A, B, C, D, sampletime) end function +(s1::HeteroStateSpace, s2::HeteroStateSpace) @@ -237,7 +239,7 @@ function +(s1::HeteroStateSpace, s2::HeteroStateSpace) if size(s1) != size(s2) error("Systems have different shapes.") end - Ts = common_sample_time(s1,s2) + sampletime = common_sampletime(s1,s2) T = promote_type(eltype(s1.A),eltype(s2.A)) A = [s1.A fill(zero(T), nstates(s1), nstates(s2)); fill(zero(T), nstates(s2), nstates(s1)) s2.A] @@ -245,10 +247,10 @@ function +(s1::HeteroStateSpace, s2::HeteroStateSpace) C = [s1.C s2.C;] D = [s1.D + s2.D;] - return HeteroStateSpace(A, B, C, D, Ts) + return HeteroStateSpace(A, B, C, D, sampletime) end -+(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C, sys.D .+ n, sys.time) ++(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C, sys.D .+ n, sys.sampletime) +(n::Number, sys::ST) where ST <: AbstractStateSpace = +(sys, n) ## SUBTRACTION ## @@ -257,7 +259,7 @@ end -(n::Number, sys::AbstractStateSpace) = +(-sys, n) ## NEGATION ## --(sys::ST) where ST <: AbstractStateSpace = ST(sys.A, sys.B, -sys.C, -sys.D, sys.time) +-(sys::ST) where ST <: AbstractStateSpace = ST(sys.A, sys.B, -sys.C, -sys.D, sys.sampletime) ## MULTIPLICATION ## function *(sys1::StateSpace{TimeT,T,MT}, sys2::StateSpace{TimeT,T,MT}) where {TimeT,T, MT} @@ -266,14 +268,14 @@ function *(sys1::StateSpace{TimeT,T,MT}, sys2::StateSpace{TimeT,T,MT}) where {Ti if sys1.nu != sys2.ny error("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs") end - Ts = common_sample_time(sys1,sys2) + sampletime = common_sampletime(sys1,sys2) A = [sys1.A sys1.B*sys2.C; fill(zero(T), sys2.nx, sys1.nx) sys2.A] B = [sys1.B*sys2.D ; sys2.B] C = [sys1.C sys1.D*sys2.C;] D = [sys1.D*sys2.D;] - return StateSpace{TimeT,T,MT}(A, B, C, D, Ts) + return StateSpace{TimeT,T,MT}(A, B, C, D, sampletime) end function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) @@ -282,7 +284,7 @@ function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) if sys1.nu != sys2.ny error("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs") end - Ts = common_sample_time(sys1,sys2) + sampletime = common_sampletime(sys1,sys2) T = promote_type(eltype(sys1.A),eltype(sys2.A)) A = [sys1.A sys1.B*sys2.C; fill(zero(T), sys2.nx, sys1.nx) sys2.A] @@ -290,10 +292,10 @@ function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) C = [sys1.C sys1.D*sys2.C;] D = [sys1.D*sys2.D;] - return HeteroStateSpace(A, B, C, D, Ts) + return HeteroStateSpace(A, B, C, D, sampletime) end -*(sys::ST, n::Number) where ST <: AbstractStateSpace = StateSpace(sys.A, sys.B, sys.C*n, sys.D*n, sys.time) +*(sys::ST, n::Number) where ST <: AbstractStateSpace = StateSpace(sys.A, sys.B, sys.C*n, sys.D*n, sys.sampletime) *(n::Number, sys::AbstractStateSpace) = *(sys, n) ## DIVISION ## @@ -307,11 +309,11 @@ function /(n::Number, sys::ST) where ST <: AbstractStateSpace catch error("D isn't invertible") end - return ST(A - B*Dinv*C, B*Dinv, -n*Dinv*C, n*Dinv, sys.time) + return ST(A - B*Dinv*C, B*Dinv, -n*Dinv*C, n*Dinv, sys.sampletime) end Base.inv(sys::AbstractStateSpace) = 1/sys -/(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C/n, sys.D/n, sys.time) +/(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C/n, sys.D/n, sys.sampletime) Base.:^(sys::AbstractStateSpace, p::Integer) = Base.power_by_squaring(sys, p) @@ -329,7 +331,7 @@ function Base.getindex(sys::ST, inds...) where ST <: AbstractStateSpace error("Must specify 2 indices to index statespace model") end rows, cols = index2range(inds...) # FIXME: ControlSystems.index2range(inds...) - return ST(copy(sys.A), sys.B[:, cols], sys.C[rows, :], sys.D[rows, cols], sys.time) + return ST(copy(sys.A), sys.B[:, cols], sys.C[rows, :], sys.D[rows, cols], sys.sampletime) end ##################################################################### @@ -360,11 +362,11 @@ function Base.show(io::IO, sys::AbstractStateSpace) end println(io, "D = \n", _string_mat_with_headers(sys.D), "\n") # Print sample time - if isdiscrete(sys) - println(io, "Sample Time: ", sampletime(sys), " (seconds)") + if is_discrete_time(sys) + println(io, "Sample Time: ", sys.Ts, " (seconds)") end # Print model type - if iscontinuous(sys) + if is_continuous_time(sys) print(io, "Continuous-time state-space model") else print(io, "Discrete-time state-space model") diff --git a/src/types/TimeType.jl b/src/types/TimeType.jl index 08fc3e239..7b6d51fe2 100644 --- a/src/types/TimeType.jl +++ b/src/types/TimeType.jl @@ -34,12 +34,12 @@ Base.promote_rule(::Type{Discrete{T1}}, ::Type{Discrete{T2}}) where {T1,T2}= Dis Base.convert(::Type{Discrete{T1}}, x::Discrete{T2}) where {T1,T2} = Discrete{T1}(x.Ts) # Promoting two or more systems systems should promote sample times -common_sample_time(x::TimeType) = x -common_sample_time(x::TimeType, y::TimeType) = throw(ErrorException("Sampling time mismatch")) -common_sample_time(x::TimeType, y::TimeType, z...) = common_sample_time(common_sample_time(x, y), z...) -common_sample_time(a::Base.Generator) = reduce(common_sample_time, a) +common_sampletime(x::TimeType) = x +common_sampletime(x::TimeType, y::TimeType) = throw(ErrorException("Sampling time mismatch")) +common_sampletime(x::TimeType, y::TimeType, z...) = common_sampletime(common_sampletime(x, y), z...) +common_sampletime(a::Base.Generator) = reduce(common_sampletime, a) -function common_sample_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} +function common_sampletime(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} if x != y && x.Ts != UNDEF_SAMPLETIME && y.Ts != UNDEF_SAMPLETIME throw(ErrorException("Sampling time mismatch")) end @@ -51,7 +51,7 @@ function common_sample_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} end end -common_sample_time(x::Continuous, ys::Continuous...) = Continuous() +common_sampletime(x::Continuous, ys::Continuous...) = Continuous() # Check equality ==(x::TimeType, y::TimeType) = false diff --git a/src/types/TransferFunction.jl b/src/types/TransferFunction.jl index b8e57bcea..eb3c23ff0 100644 --- a/src/types/TransferFunction.jl +++ b/src/types/TransferFunction.jl @@ -1,16 +1,16 @@ -struct TransferFunction{TimeT, S<:SisoTf{T} where T} <: LTISystem{TimeT} +struct TransferFunction{TimeT, S<:SisoTf{T} where T} <: LTISystem matrix::Matrix{S} - time::TimeT + sampletime::TimeT nu::Int ny::Int - function TransferFunction{TimeT,S}(matrix::Matrix{S}, Ts::TimeT) where {S,TimeT} + function TransferFunction{TimeT,S}(matrix::Matrix{S}, sampletime::TimeT) where {S,TimeT} # Validate size of input and output names ny, nu = size(matrix) - return new{TimeT,S}(matrix, Ts, nu, ny) + return new{TimeT,S}(matrix, sampletime, nu, ny) end end -function TransferFunction(matrix::Matrix{S}, Ts::TimeT) where {TimeT<:TimeType, T<:Number, S<:SisoTf{T}} - TransferFunction{TimeT, S}(matrix, Ts) +function TransferFunction(matrix::Matrix{S}, sampletime::TimeT) where {TimeT<:TimeType, T<:Number, S<:SisoTf{T}} + TransferFunction{TimeT, S}(matrix, sampletime) end # # Constructor for Discrete time system @@ -22,6 +22,8 @@ end # return TransferFunction(matrix, Continuous()) # end +sampletime(G) = G.sampletime + noutputs(G::TransferFunction) = size(G.matrix, 1) ninputs(G::TransferFunction) = size(G.matrix, 2) @@ -41,11 +43,11 @@ function Base.getindex(G::TransferFunction{TimeT,S}, inds...) where {TimeT,S<:Si rows, cols = index2range(inds...) mat = Matrix{S}(undef, length(rows), length(cols)) mat[:, :] = G.matrix[rows, cols] - return TransferFunction(mat, G.time) + return TransferFunction(mat, G.sampletime) end function Base.copy(G::TransferFunction) - return TransferFunction(copy(G.matrix), G.time) + return TransferFunction(copy(G.matrix), G.sampletime) end numvec(G::TransferFunction) = map(numvec, G.matrix) @@ -63,7 +65,7 @@ function minreal(G::TransferFunction, eps::Real=sqrt(eps())) for i = eachindex(G.matrix) matrix[i] = minreal(G.matrix[i], eps) end - return TransferFunction(matrix, G.time) + return TransferFunction(matrix, G.sampletime) end """`isproper(tf)` @@ -80,7 +82,7 @@ end ## EQUALITY ## function ==(G1::TransferFunction, G2::TransferFunction) - fields = [:time, :ny, :nu, :matrix] + fields = [:sampletime, :ny, :nu, :matrix] for field in fields if getfield(G1, field) != getfield(G2, field) return false @@ -92,7 +94,7 @@ end ## Approximate ## function isapprox(G1::TransferFunction, G2::TransferFunction; kwargs...) G1, G2 = promote(G1, G2) - fieldsApprox = [:time, :matrix] + fieldsApprox = [:sampletime, :matrix] for field in fieldsApprox if !(isapprox(getfield(G1, field), getfield(G2, field); kwargs...)) return false @@ -110,36 +112,35 @@ function +(G1::TransferFunction, G2::TransferFunction) if size(G1) != size(G2) error("Systems have different shapes.") end - Ts = common_sample_time(G1.time,G2.time) + sampletime = common_sampletime(G1,G2) matrix = G1.matrix + G2.matrix - return TransferFunction(matrix, Ts) + return TransferFunction(matrix, sampletime) end -+(G::TransferFunction, n::Number) = TransferFunction(G.matrix .+ n, G.time) ++(G::TransferFunction, n::Number) = TransferFunction(G.matrix .+ n, G.sampletime) +(n::Number, G::TransferFunction) = +(G, n) ## SUBTRACTION ## --(n::Number, G::TransferFunction) = TransferFunction(n .- G.matrix, G.time) +-(n::Number, G::TransferFunction) = TransferFunction(n .- G.matrix, G.sampletime) -(G1::TransferFunction, G2::TransferFunction) = +(G1, -G2) -(G::TransferFunction, n::Number) = +(G, -n) ## NEGATION ## --(G::TransferFunction) = TransferFunction(-G.matrix, G.time) +-(G::TransferFunction) = TransferFunction(-G.matrix, G.sampletime) ## MULTIPLICATION ## function *(G1::TransferFunction, G2::TransferFunction) # Note: G1*G2 = y <- G1 <- G2 <- u - Ts = common_sample_time(G1.time,G2.time) + sampletime = common_sampletime(G1,G2) if G1.nu != G2.ny error("G1*G2: G1 must have same number of inputs as G2 has outputs") end - Ts = common_sample_time(G1.time,G2.time) matrix = G1.matrix * G2.matrix - return TransferFunction(matrix, Ts) + return TransferFunction(matrix, sampletime) end -*(G::TransferFunction, n::Number) = TransferFunction(n*G.matrix, G.time) +*(G::TransferFunction, n::Number) = TransferFunction(n*G.matrix, G.sampletime) *(n::Number, G::TransferFunction) = *(G, n) ## DIVISION ## @@ -149,7 +150,7 @@ function /(n::Number, G::TransferFunction) else error("MIMO TransferFunction inversion isn't implemented yet") end - return TransferFunction(matrix, G.time) + return TransferFunction(matrix, G.sampletime) end /(G::TransferFunction, n::Number) = G*(1/n) /(G1::TransferFunction, G2::TransferFunction) = G1*(1/G2) @@ -167,7 +168,7 @@ function Base.show(io::IO, G::TransferFunction) # Compose the name vectors #println(io, "TransferFunction:") println(io, typeof(G)) - var = iscontinuous(G) ? :s : :z + var = is_continuous_time(G) ? :s : :z for i=1:G.nu for o=1:G.ny if !issiso(G) @@ -179,9 +180,9 @@ function Base.show(io::IO, G::TransferFunction) end end end - if iscontinuous(G) + if is_continuous_time(G) print(io, "\nContinuous-time transfer function model") - elseif isdiscrete(G) + elseif is_discrete_time(G) print(io, "\nSample Time: ", sampletime(G), " (seconds)") print(io, "\nDiscrete-time transfer function model") else diff --git a/src/types/conversion.jl b/src/types/conversion.jl index 08fef1d3e..347965662 100644 --- a/src/types/conversion.jl +++ b/src/types/conversion.jl @@ -8,7 +8,7 @@ # # function Base.convert{T<:AbstractMatrix{<:Number}}(::Type{StateSpace{T}}, s::StateSpace) # AT = promote_type(T, arraytype(s)) -# StateSpace{AT}(AT(s.A),AT(s.B),AT(s.C),AT(s.D), s.time, s.statenames, s.inputnames, s.outputnames) +# StateSpace{AT}(AT(s.A),AT(s.B),AT(s.C),AT(s.D), s.sampletime, s.statenames, s.inputnames, s.outputnames) # end # TODO Fix these to use proper constructors @@ -61,20 +61,20 @@ Base.convert(::Type{StateSpace{TimeT,T,MT}}, b::Number) where {TimeT, T, MT} = function convert(::Type{TransferFunction{TimeT,S}}, G::TransferFunction) where {TimeT,S} Gnew_matrix = convert.(S, G.matrix) - return TransferFunction{TimeT,eltype(Gnew_matrix)}(Gnew_matrix, TimeT(G.time)) + return TransferFunction{TimeT,eltype(Gnew_matrix)}(Gnew_matrix, TimeT(G.sampletime)) end function convert(::Type{S}, sys::StateSpace) where {T, MT, TimeT, S <:StateSpace{TimeT,T,MT}} if sys isa S return sys else - return StateSpace{TimeT, T,MT}(convert(MT, sys.A), convert(MT, sys.B), convert(MT, sys.C), convert(MT, sys.D), TimeT(sys.time)) + return StateSpace{TimeT, T,MT}(convert(MT, sys.A), convert(MT, sys.B), convert(MT, sys.C), convert(MT, sys.D), TimeT(sys.sampletime)) end end # TODO Maybe add convert on matrices Base.convert(::Type{HeteroStateSpace{TimeT1,AT,BT,CT,DT}}, s::StateSpace{TimeT2,T,MT}) where {TimeT1,TimeT2,T,MT,AT,BT,CT,DT} = - HeteroStateSpace{TimeT1,AT,BT,CT,DT}(s.A,s.B,s.C,s.D,TimeT1(s.time)) + HeteroStateSpace{TimeT1,AT,BT,CT,DT}(s.A,s.B,s.C,s.D,TimeT1(s.sampletime)) Base.convert(::Type{HeteroStateSpace}, s::StateSpace) = HeteroStateSpace(s) @@ -118,7 +118,7 @@ function Base.convert(::Type{StateSpace{TimeT,T,MT}}, G::TransferFunction) where end end # A, B, C = balance_statespace(A, B, C)[1:3] NOTE: Use balance? - return StateSpace{TimeT,T,MT}(A, B, C, D, TimeT(G.time)) + return StateSpace{TimeT,T,MT}(A, B, C, D, TimeT(G.sampletime)) end siso_tf_to_ss(T::Type, f::SisoTf) = siso_tf_to_ss(T, convert(SisoRational, f)) @@ -186,7 +186,7 @@ function balance_statespace(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMat function balance_statespace(sys::StateSpace, perm::Bool=false) A, B, C, T = balance_statespace(sys.A,sys.B,sys.C, perm) - return ss(A,B,C,sys.D,sys.time), T + return ss(A,B,C,sys.D,sys.sampletime), T end # Method that might fail for some exotic types, such as TrackedArrays @@ -259,7 +259,7 @@ function convert(::Type{TransferFunction{TimeT,SisoRational{T}}}, sys::StateSpac num = charpoly(A-B[:,i:i]*C[j:j,:]) - charpolyA + D[j, i]*charpolyA matrix[j, i] = SisoRational{T}(num, charpolyA) end - TransferFunction{TimeT,SisoRational{T}}(matrix, TimeT(sys.time)) + TransferFunction{TimeT,SisoRational{T}}(matrix, TimeT(sys.sampletime)) end function convert(::Type{TransferFunction{TimeT1,SisoRational}}, sys::StateSpace{TimeT2,T0}) where {TimeT1,TimeT2,T0<:Number} T = typeof(one(T0)/one(T0)) @@ -276,7 +276,7 @@ function convert(::Type{TransferFunction{TimeT,SisoZpk{T,TR}}}, sys::StateSpace) z, p, k = siso_ss_to_zpk(sys, i, j) matrix[i, j] = SisoZpk{T,TR}(z, p, k) end - TransferFunction{TimeT,SisoZpk{T,TR}}(matrix, TimeT(sys.time)) + TransferFunction{TimeT,SisoZpk{T,TR}}(matrix, TimeT(sys.sampletime)) end function convert(::Type{TransferFunction{TimeT1,SisoZpk}}, sys::StateSpace{TimeT2,T0}) where {TimeT1,TimeT2,T0<:Number} T = typeof(one(T0)/one(T0)) diff --git a/test/runtests.jl b/test/runtests.jl index ddfd5f836..83f3be091 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -9,6 +9,7 @@ include("framework.jl") eye_(n) = Matrix{Int64}(I, n, n) my_tests = [ + "test_statespace", "test_transferfunction", "test_delayed_systems", diff --git a/test/test_connections.jl b/test/test_connections.jl index de0e46482..56a7e8197 100644 --- a/test/test_connections.jl +++ b/test/test_connections.jl @@ -156,7 +156,8 @@ arr4[1] = ss(0); arr4[2] = ss(1); arr4[3] = ss(2) @test [1.0 D_111] == ss([1.0], [0.0 2.0], [3.0], [1.0 4.0], 0.005) # Type and sample time @test [D_111 1.0] isa StateSpace{Discrete{Float64},Float64,Array{Float64,2}} -@test sampletime([D_111 1.0]) == 0.005 +@test sampletime([D_111 1.0]) == Discrete(0.005) +@test [D_111 1.0].Ts == 0.005 # Continuous version @test [C_111 1.0] == ss([1.0], [2.0 0.0], [3.0], [4.0 1.0]) @test [1.0 C_111] == ss([1.0], [0.0 2.0], [3.0], [1.0 4.0]) From 73fa735ab75324f98d5a41bef1affb66b6b32e4e Mon Sep 17 00:00:00 2001 From: olof3 Date: Wed, 15 Apr 2020 19:55:37 +0200 Subject: [PATCH 09/18] Minor name changes. --- docs/src/man/creating_systems.md | 8 ++++---- src/plotting.jl | 26 +++++++++++++------------- src/types/TimeType.jl | 12 ++++++------ src/types/conversion.jl | 10 +++++----- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/src/man/creating_systems.md b/docs/src/man/creating_systems.md index fc5ab05bc..e815e5dd6 100644 --- a/docs/src/man/creating_systems.md +++ b/docs/src/man/creating_systems.md @@ -10,7 +10,7 @@ The syntax for creating a transfer function is ```julia tf(num, den, Ts=0) ``` -where `num` and `den` are the polinomial coefficients of the numerator and denominator of the polynomial and `Ts` is the sample time. +where `num` and `den` are the polynomial coefficients of the numerator and denominator of the polynomial and `Ts` is the sample time. ### Example: ```julia tf([1.0],[1,2,1]) @@ -29,7 +29,7 @@ Continuous-time transfer function model The transfer functions created using this method will be of type `TransferFunction{SisoRational}`. ## zpk - Pole-Zero-Gain Representation -Sometimes it's better to represent the transferfunction by its poles, zeros and gain, this can be done using +Sometimes it's better to represent the transfer function by its poles, zeros and gain, this can be done using ```julia zpk(zeros, poles, gain, Ts=0) ``` @@ -72,7 +72,7 @@ A state-space system is created using ```julia ss(A,B,C,D,Ts=0) ``` -and they behave similarily to transfer functions. State-space systems with heterogeneous matrix types are also available, which can be used to create systems with static or sized matrices, e.g., +and they behave similarly to transfer functions. State-space systems with heterogeneous matrix types are also available, which can be used to create systems with static or sized matrices, e.g., ```jldoctest HSS using StaticArrays import ControlSystems.HeteroStateSpace @@ -83,7 +83,7 @@ import ControlSystems.HeteroStateSpace function HeteroStateSpace(A,B,C,D,Ts=0,f::F=to_static) where F HeteroStateSpace(f(A),f(B),f(C),f(D),Ts) end -@inline HeteroStateSpace(s,f) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.time,f) +@inline HeteroStateSpace(s,f) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.sampletime,f) ControlSystems._string_mat_with_headers(a::SizedArray) = ControlSystems._string_mat_with_headers(Matrix(a)); # Overload for printing purposes ``` Notice the different matrix types used diff --git a/src/plotting.jl b/src/plotting.jl index 2dfe25078..22aa2e4df 100644 --- a/src/plotting.jl +++ b/src/plotting.jl @@ -161,15 +161,15 @@ end @userplot Stepplot @userplot Impulseplot """ - stepplot(sys[, Tf[, Ts]]) -Plot step response of `sys` with optional final time `Tf` and discretization time `Ts`. + stepplot(sys[, tfinal[, dt]]) +Plot step response of `sys` with optional final time `tfinal` and discretization time `dt`. If not defined, suitable values are chosen based on `sys`. """ stepplot """ - impulseplot(sys[, Tf[, Ts]]) -Plot step response of `sys` with optional final time `Tf` and discretization time `Ts`. + impulseplot(sys[, tfinal[, dt]]) +Plot step response of `sys` with optional final time `tfinal` and discretization time `dt`. If not defined, suitable values are chosen based on `sys`. """ impulseplot @@ -183,12 +183,12 @@ for (func, title, typ) = ((step, "Step Response", Stepplot), (impulse, "Impulse systems = [systems] end if length(p.args) < 2 - Ts_list, Tf = _default_time_data(systems) + dt_list, tfinal = _default_time_data(systems) elseif length(p.args) == 2 - Ts_list = _default_Ts.(systems) - Tf = p.args[2] + dt_list = _default_dt.(systems) + tfinal = p.args[2] else - Tf, Ts_list = p.args[2:3] + tfinal, dt_list = p.args[2:3] end if !_same_io_dims(systems...) error("All systems must have the same input/output dimensions") @@ -198,8 +198,8 @@ for (func, title, typ) = ((step, "Step Response", Stepplot), (impulse, "Impulse titles = fill("", 1, ny*nu) title --> titles s2i(i,j) = LinearIndices((ny,nu))[i,j] - for (si,(s, Ts)) in enumerate(zip(systems, Ts_list)) - t = 0:Ts:Tf + for (si,(s, dt)) in enumerate(zip(systems, dt_list)) + t = 0:dt:tfinal y = func(s, t)[1] styledict = getStyleSys(si,length(systems)) for i=1:ny @@ -706,9 +706,9 @@ function _same_io_dims(systems::LTISystem...) end function _default_time_data(systems::Vector{T}) where T<:LTISystem - sample_times = [_default_Ts(i) for i in systems] - Tf = 100*maximum(sample_times) - return sample_times, Tf + sample_times = [_default_dt(i) for i in systems] + tfinal = 100*maximum(sample_times) + return sample_times, tfinal end _default_time_data(sys::LTISystem) = _default_time_data(LTISystem[sys]) diff --git a/src/types/TimeType.jl b/src/types/TimeType.jl index 7b6d51fe2..961c49cb2 100644 --- a/src/types/TimeType.jl +++ b/src/types/TimeType.jl @@ -1,12 +1,12 @@ abstract type TimeType end -const UNDEF_SAMPLETIME = -1 +const UNDEF_TS = -1 # For handling promotion of Matrix to LTISystem struct Discrete{T} <: TimeType Ts::T function Discrete{T}(Ts::T) where T - if Ts <= 0 && Ts != UNDEF_SAMPLETIME + if Ts <= 0 && Ts != UNDEF_TS throw(ErrorException("Creating a continuous time system by setting sample time to 0 is no longer supported.")) end new{T}(Ts) @@ -22,8 +22,8 @@ Continuous(x::Continuous) = x Discrete{T}(x::Discrete) where T = Discrete{T}(x.Ts) -undef_sampletime(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_SAMPLETIME) -undef_sampletime(::Type{Continuous}) where T = Continuous() +undef_Ts(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_TS) +undef_Ts(::Type{Continuous}) where T = Continuous() # Promotion @@ -40,11 +40,11 @@ common_sampletime(x::TimeType, y::TimeType, z...) = common_sampletime(common_sam common_sampletime(a::Base.Generator) = reduce(common_sampletime, a) function common_sampletime(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} - if x != y && x.Ts != UNDEF_SAMPLETIME && y.Ts != UNDEF_SAMPLETIME + if x != y && x.Ts != UNDEF_TS && y.Ts != UNDEF_TS throw(ErrorException("Sampling time mismatch")) end - if x.Ts == UNDEF_SAMPLETIME + if x.Ts == UNDEF_TS return Discrete{promote_type(T1,T2)}(y) else return Discrete{promote_type(T1,T2)}(x) diff --git a/src/types/conversion.jl b/src/types/conversion.jl index 347965662..af21e0cbd 100644 --- a/src/types/conversion.jl +++ b/src/types/conversion.jl @@ -20,9 +20,9 @@ # Base.convert(::Type{<:TransferFunction{<:SisoZpk}}, b::Number) = zpk(b) # Base.convert(::Type{TransferFunction{TimeT,SisoZpk{T1, TR1}}}, b::AbstractMatrix{T2}) where {TimeT, T1, TR1, T2<:Number} = - zpk(T1.(b), undef_sampletime(TimeT)) + zpk(T1.(b), undef_Ts(TimeT)) Base.convert(::Type{TransferFunction{TimeT,SisoRational{T1}}}, b::AbstractMatrix{T2}) where {TimeT, T1, T2<:Number} = - tf(T1.(b), undef_sampletime(TimeT)) + tf(T1.(b), undef_Ts(TimeT)) function convert(::Type{StateSpace{TimeT,T,MT}}, D::AbstractMatrix{<:Number}) where {TimeT,T, MT} (ny, nu) = size(D) @@ -30,14 +30,14 @@ function convert(::Type{StateSpace{TimeT,T,MT}}, D::AbstractMatrix{<:Number}) wh B = MT(fill(zero(T), (0,nu))) C = MT(fill(zero(T), (ny,0))) D = convert(MT, D) - return StateSpace{TimeT,T,MT}(A,B,C,D,undef_sampletime(TimeT)) + return StateSpace{TimeT,T,MT}(A,B,C,D,undef_Ts(TimeT)) end # TODO We still dont have TransferFunction{..}(number/array) constructors Base.convert(::Type{TransferFunction{TimeT,SisoRational{T}}}, b::Number) where {TimeT, T} = - tf(T(b), undef_sampletime(TimeT)) + tf(T(b), undef_Ts(TimeT)) Base.convert(::Type{TransferFunction{TimeT,SisoZpk{T,TR}}}, b::Number) where {TimeT, T, TR} = - zpk(T(b), undef_sampletime(TimeT)) + zpk(T(b), undef_Ts(TimeT)) Base.convert(::Type{StateSpace{TimeT,T,MT}}, b::Number) where {TimeT, T, MT} = convert(StateSpace{TimeT,T,MT}, fill(b, (1,1))) # From ccc5f5ae25abc32142e86f86ba9034fffa06c7df Mon Sep 17 00:00:00 2001 From: olof3 Date: Wed, 15 Apr 2020 20:48:34 +0200 Subject: [PATCH 10/18] More minor fixes. --- src/delay_systems.jl | 2 +- src/timeresp.jl | 21 +++++++++++---------- src/types/tf.jl | 18 +++++++++--------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/delay_systems.jl b/src/delay_systems.jl index 3dc18b2d0..c6cd203b3 100644 --- a/src/delay_systems.jl +++ b/src/delay_systems.jl @@ -161,7 +161,7 @@ function _bounds_and_features(sys::DelayLtiSystem, plot::Val) end # Againm we have to do something for default vectors, more or less a copy from timeresp.jl -function _default_Ts(sys::DelayLtiSystem) +function _default_dt(sys::DelayLtiSystem) if !isstable(sys.P.P) return 0.05 # Something small else diff --git a/src/timeresp.jl b/src/timeresp.jl index 00c42d361..998f639e4 100644 --- a/src/timeresp.jl +++ b/src/timeresp.jl @@ -217,31 +217,32 @@ end # TODO: This is a poor heuristic to estimate a "good" time vector to use for # simulation, in cases when one isn't provided. -function _default_time_vector(sys::LTISystem, Tf::Real=-1) - Ts = _default_Ts(sys) - if Tf == -1 - Tf = 100*Ts +function _default_time_vector(sys::LTISystem, tfinal::Real=-1) + dt = _default_dt(sys) + if tfinal == -1 + tfinal = 100*dt end - return 0:Ts:Tf + return 0:dt:tfinal end -function _default_Ts(sys::LTISystem) +function _default_dt(sys::LTISystem) if is_discrete_time(sys) - Ts = sys.Ts + dt = sys.Ts elseif !isstable(sys) - Ts = 0.05 + dt = 0.05 else ps = pole(sys) r = minimum([abs.(real.(ps));0]) if r == 0.0 r = 1.0 end - Ts = 0.07/r + dt = 0.07/r end - return Ts + return dt end + #TODO a reasonable check _issmooth(u::Function) = false diff --git a/src/types/tf.jl b/src/types/tf.jl index dd3b68bee..06cde1852 100644 --- a/src/types/tf.jl +++ b/src/types/tf.jl @@ -1,4 +1,4 @@ -""" `sys = tf(num, den[, Ts=0]), sys = tf(gain[, Ts])` +""" `sys = tf(num, den[, Ts]), sys = tf(gain[, Ts])` Create as a fraction of polynomials: @@ -40,9 +40,9 @@ tf(num::AbstractVecOrMat{<:AbstractVector{T1}}, den::AbstractVecOrMat{<:Abstract tf(num::AbstractVecOrMat{<:AbstractVector{T1}}, den::AbstractVecOrMat{<:AbstractVector{T2}}) where {T1,T2} = tf(num, den, Continuous()) -function tf(num::AbstractVector{T1}, den::AbstractVector{T2}, Ts::TimeT) where {TimeT<:TimeType,T1<:Number, T2<:Number} +function tf(num::AbstractVector{T1}, den::AbstractVector{T2}, sampletime::TimeT) where {TimeT<:TimeType,T1<:Number, T2<:Number} T = promote_type(T1, T2) - return TransferFunction{TimeT,SisoRational{T}}(fill(SisoRational{T}(num, den), 1, 1), Ts) + return TransferFunction{TimeT,SisoRational{T}}(fill(SisoRational{T}(num, den), 1, 1), sampletime) end tf(num::AbstractVector{T1}, den::AbstractVector{T2}, Ts::Number) where {T1<:Number, T2<:Number} = tf(num, den, Discrete(Ts)) @@ -52,14 +52,14 @@ tf(num::AbstractVector{T1}, den::AbstractVector{T2}) where {T1<:Number, T2<:Numb tf(num::Number, den::Vector, args...) = tf([num], den, args...) # Cases for just static gain -function tf(D::AbstractArray{T}, Ts::TimeT) where {TimeT<:TimeType, T<:Number} +function tf(D::AbstractArray{T}, sampletime::TimeT) where {TimeT<:TimeType, T<:Number} ny, nu = size(D, 1), size(D, 2) matrix = Matrix{SisoRational{T}}(undef, ny, nu) for i in eachindex(D) matrix[i] = SisoRational{T}([D[i]], [one(T)]) end - return TransferFunction{TimeT,SisoRational{T}}(matrix, Ts) + return TransferFunction{TimeT,SisoRational{T}}(matrix, sampletime) end tf(D::AbstractArray{T}, Ts::Number) where T = tf(D, Discrete(Ts)) tf(D::AbstractArray{T}) where T = tf(D, Continuous()) @@ -84,7 +84,7 @@ function tf(var::AbstractString, Ts::Real) end ## Constructors for polynomial inputs -function tf(num::AbstractArray{PT}, den::AbstractArray{PT}, Ts::TimeT) where {TimeT<:TimeType,T<:Number, PT <: Polynomials.Poly{T}} +function tf(num::AbstractArray{PT}, den::AbstractArray{PT}, sampletime::TimeT) where {TimeT<:TimeType,T<:Number, PT <: Polynomials.Poly{T}} ny, nu = size(num, 1), size(num, 2) if (ny, nu) != (size(den, 1), size(den, 2)) error("num and den dimensions must match") @@ -96,15 +96,15 @@ function tf(num::AbstractArray{PT}, den::AbstractArray{PT}, Ts::TimeT) where {T matrix[o, i] = SisoRational{T}(num[o, i], den[o, i]) end end - return TransferFunction{TimeT,SisoRational{T}}(matrix, Ts) + return TransferFunction{TimeT,SisoRational{T}}(matrix, sampletime) end tf(num::AbstractArray{PT}, den::AbstractArray{PT}, Ts::Number) where {T<:Number, PT <: Polynomials.Poly{T}} = tf(num, den, Discrete(Ts)) tf(num::AbstractArray{PT}, den::AbstractArray{PT}) where {T<:Number, PT <: Polynomials.Poly{T}} = tf(num, den, Continuous()) -function tf(num::PT, den::PT, Ts::TimeT) where {TimeT<:TimeType, T<:Number, PT <: Polynomials.Poly{T}} - tf(fill(num,1,1), fill(den,1,1), Ts) +function tf(num::PT, den::PT, sampletime::TimeT) where {TimeT<:TimeType, T<:Number, PT <: Polynomials.Poly{T}} + tf(fill(num,1,1), fill(den,1,1), sampletime) end tf(num::PT, den::PT, Ts::Number) where {T<:Number, PT <: Polynomials.Poly{T}} = tf(num, den, Discrete(Number)) From 488e3ba6c6b941c209bbde9dbf145a3b285feb9f Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 08:47:26 +0200 Subject: [PATCH 11/18] Another suggestion. --- src/ControlSystems.jl | 9 ++- src/analysis.jl | 6 +- src/connections.jl | 38 +++++------ src/discrete.jl | 22 +++--- src/freqresp.jl | 12 ++-- src/matrix_comps.jl | 18 ++--- src/plotting.jl | 10 +-- src/simplification.jl | 2 +- src/simulators.jl | 2 +- src/synthesis.jl | 4 +- src/timeresp.jl | 12 ++-- src/types/DelayLtiSystem.jl | 8 +-- src/types/Lti.jl | 24 +++---- src/types/PartionedStateSpace.jl | 26 +++---- src/types/StateSpace.jl | 112 +++++++++++++++---------------- src/types/TimeType.jl | 24 +++---- src/types/TransferFunction.jl | 48 ++++++------- src/types/conversion.jl | 26 +++---- src/types/tf.jl | 16 ++--- test/test_connections.jl | 1 - 20 files changed, 209 insertions(+), 211 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index 5527a15fb..95d905766 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -87,9 +87,9 @@ export LTISystem, denvec, numpoly, denpoly, - sampletime, - is_continuous_time, - is_discrete_time, + time, + iscontinuous, + isdiscrete, isstatic @@ -111,8 +111,7 @@ abstract type AbstractSystem end include("types/TimeType.jl") ## Added interface: -# sampletime(Lti) -> Number -# timetype(Lti) -> TimeType +# time(Lti) -> Number include("types/Lti.jl") diff --git a/src/analysis.jl b/src/analysis.jl index 00a62c473..3785d05e2 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -91,7 +91,7 @@ Compute the dcgain of system `sys`. equal to G(0) for continuous-time systems and G(1) for discrete-time systems.""" function dcgain(sys::LTISystem) - return is_continuous_time(sys) ? evalfr(sys, 0) : evalfr(sys, 1) + return iscontinuous(sys) ? evalfr(sys, 0) : evalfr(sys, 1) end """`markovparam(sys, n)` @@ -133,7 +133,7 @@ Compute the natural frequencies, `Wn`, and damping ratios, `zeta`, of the poles, `ps`, of `sys`""" function damp(sys::LTISystem) ps = pole(sys) - if is_discrete_time(sys) + if isdiscrete(sys) ps = log(ps)/sys.Ts end Wn = abs.(ps) @@ -445,7 +445,7 @@ function delaymargin(G::LTISystem) ϕₘ *= π/180 ωϕₘ = m[3][i] dₘ = ϕₘ/ωϕₘ - if is_discrete_time(G) + if isdiscrete(G) dₘ /= G.Ts # Give delay margin in number of sample times, as matlab does end dₘ diff --git a/src/connections.jl b/src/connections.jl index e4d19e74f..55dd8e701 100644 --- a/src/connections.jl +++ b/src/connections.jl @@ -22,18 +22,18 @@ Append systems in block diagonal form """ function append(systems::(ST where ST<:AbstractStateSpace)...) ST = promote_type(typeof.(systems)...) - sampletime = common_sampletime(systems...) + time = common_time(systems...) A = blockdiag([s.A for s in systems]...) B = blockdiag([s.B for s in systems]...) C = blockdiag([s.C for s in systems]...) D = blockdiag([s.D for s in systems]...) - return ST(A, B, C, D, sampletime) + return ST(A, B, C, D, time) end function append(systems::TransferFunction...) - sampletime = common_sampletime(systems...) + time = common_time(systems...) mat = blockdiag([s.matrix for s in systems]...) - return TransferFunction(mat, sampletime) + return TransferFunction(mat, time) end append(systems::LTISystem...) = append(promote(systems...)...) @@ -62,8 +62,8 @@ function Base.vcat(systems::ST...) where ST <: AbstractStateSpace B = vcat([s.B for s in systems]...) C = blockdiag([s.C for s in systems]...) D = vcat([s.D for s in systems]...) - sampletime = common_sampletime(systems...) - return ST(A, B, C, D, sampletime) + time = common_time(systems...) + return ST(A, B, C, D, time) end function Base.vcat(systems::TransferFunction...) @@ -72,10 +72,10 @@ function Base.vcat(systems::TransferFunction...) if !all(s.nu == nu for s in systems) error("All systems must have same input dimension") end - sampletime = common_sampletime(systems...) + time = common_time(systems...) mat = vcat([s.matrix for s in systems]...) - return TransferFunction(mat, sampletime) + return TransferFunction(mat, time) end Base.vcat(systems::LTISystem...) = vcat(promote(systems...)...) @@ -86,13 +86,13 @@ function Base.hcat(systems::ST...) where ST <: AbstractStateSpace if !all(s.ny == ny for s in systems) error("All systems must have same output dimension") end - sampletime = common_sampletime(systems...) + time = common_time(systems...) A = blockdiag([s.A for s in systems]...) B = blockdiag([s.B for s in systems]...) C = hcat([s.C for s in systems]...) D = hcat([s.D for s in systems]...) - return ST(A, B, C, D, sampletime) + return ST(A, B, C, D, time) end function Base.hcat(systems::TransferFunction...) @@ -101,10 +101,10 @@ function Base.hcat(systems::TransferFunction...) if !all(s.ny == ny for s in systems) error("All systems must have same output dimension") end - sampletime = common_sampletime(systems...) + time = common_time(systems...) mat = hcat([s.matrix for s in systems]...) - return TransferFunction(mat, sampletime) + return TransferFunction(mat, time) end Base.hcat(systems::LTISystem...) = hcat(promote(systems...)...) @@ -166,7 +166,7 @@ function feedback(L::TransferFunction{<:TimeType,T}) where T<:SisoRational end P = numpoly(L) Q = denpoly(L) - tf(P, P+Q, L.sampletime) + tf(P, P+Q, L.time) end function feedback(L::TransferFunction{TimeT, T}) where {TimeT<:TimeType, T<:SisoZpk} @@ -179,7 +179,7 @@ function feedback(L::TransferFunction{TimeT, T}) where {TimeT<:TimeType, T<:Siso kden = denpol[end] # Get coeff of s^n # Create siso system sisozpk = T(L.matrix[1].z, roots(denpol), k/kden) - return TransferFunction{TimeT,T}(fill(sisozpk,1,1), L.sampletime) + return TransferFunction{TimeT,T}(fill(sisozpk,1,1), L.time) end """ @@ -201,13 +201,13 @@ function feedback(sys::Union{StateSpace, DelayLtiSystem}) end function feedback(sys1::StateSpace,sys2::StateSpace) - sampletime = common_sampletime(sys1,sys2) + time = common_time(sys1,sys2) !(iszero(sys1.D) || iszero(sys2.D)) && error("There cannot be a direct term (D) in both sys1 and sys2") A = [sys1.A+sys1.B*(-sys2.D)*sys1.C sys1.B*(-sys2.C); sys2.B*sys1.C sys2.A+sys2.B*sys1.D*(-sys2.C)] B = [sys1.B; sys2.B*sys1.D] C = [sys1.C sys1.D*(-sys2.C)] - ss(A, B, C, sys1.D, sampletime) + ss(A, B, C, sys1.D, time) end @@ -230,7 +230,7 @@ See Zhou etc. for similar (somewhat less symmetric) formulas. U1=:, Y1=:, U2=:, Y2=:, W1=:, Z1=:, W2=Int[], Z2=Int[], Wperm=:, Zperm=:, pos_feedback::Bool=false) - sampletime = common_sampletime(sys1,sys2) + time = common_time(sys1,sys2) if !(isa(Y1, Colon) || allunique(Y1)); @warn "Connecting single output to multiple inputs Y1=$Y1"; end if !(isa(Y2, Colon) || allunique(Y2)); @warn "Connecting single output to multiple inputs Y2=$Y2"; end @@ -299,7 +299,7 @@ See Zhou etc. for similar (somewhat less symmetric) formulas. s2_D12*R2*s1_D21 s2_D11 + α*s2_D12*R2*s1_D22*s2_D21] end - return StateSpace(A, B[:, Wperm], C[Zperm,:], D[Zperm, Wperm], sampletime) + return StateSpace(A, B[:, Wperm], C[Zperm,:], D[Zperm, Wperm], time) end @@ -309,7 +309,7 @@ end """ function feedback2dof(P::TransferFunction,R,S,T) !issiso(P) && error("Feedback not implemented for MIMO systems") - tf(conv(poly2vec(numpoly(P)[1]),T),zpconv(poly2vec(denpoly(P)[1]),R,poly2vec(numpoly(P)[1]),S), P.sampletime) + tf(conv(poly2vec(numpoly(P)[1]),T),zpconv(poly2vec(denpoly(P)[1]),R,poly2vec(numpoly(P)[1]),S), P.time) end feedback2dof(B,A,R,S,T) = tf(conv(B,T),zpconv(A,R,B,S)) diff --git a/src/discrete.jl b/src/discrete.jl index 9e5f14c82..043a837d8 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -1,23 +1,23 @@ export rstd, rstc, dab, c2d_roots2poly, c2d_poly2poly, zpconv#, lsima, indirect_str -"""`[sysd, x0map] = c2d(sys, Ts, method=:zoh)` +"""`[sysd, x0map] = c2d(sys, ts, method=:zoh)` Convert the continuous system `sys` into a discrete system with sample time -`Ts`, using the provided method. Currently only `:zoh` and `:foh` are provided. +`ts`, using the provided method. Currently only `:zoh` and `:foh` are provided. Returns the discrete system `sysd`, and a matrix `x0map` that transforms the initial conditions to the discrete domain by `x0_discrete = x0map*[x0; u0]`""" -function c2d(sys::StateSpace, Ts::Real, method::Symbol=:zoh) - if is_discrete_time(sys) +function c2d(sys::StateSpace, ts::Real, method::Symbol=:zoh) + if isdiscrete(sys) error("sys must be a continuous time system") end A, B, C, D = ssdata(sys) ny, nu = size(sys) nx = nstates(sys) if method == :zoh - M = exp([A*Ts B*Ts; + M = exp([A*ts B*ts; zeros(nu, nx + nu)]) Ad = M[1:nx, 1:nx] Bd = M[1:nx, nx+1:nx+nu] @@ -25,7 +25,7 @@ function c2d(sys::StateSpace, Ts::Real, method::Symbol=:zoh) Dd = D x0map = [Matrix{Float64}(I, nx, nx) zeros(nx, nu)] # Cant use I if nx==0 elseif method == :foh - M = exp([A*Ts B*Ts zeros(nx, nu); + M = exp([A*ts B*ts zeros(nx, nu); zeros(nu, nx + nu) Matrix{Float64}(I, nu, nu); zeros(nu, nx + 2*nu)]) M1 = M[1:nx, nx+1:nx+nu] @@ -40,7 +40,7 @@ function c2d(sys::StateSpace, Ts::Real, method::Symbol=:zoh) else error("Unsupported method: ", method) end - return StateSpace(Ad, Bd, Cd, Dd, Ts), x0map + return StateSpace(Ad, Bd, Cd, Dd, ts), x0map end @@ -189,7 +189,7 @@ end function c2d(G::TransferFunction, h;kwargs...) - @assert is_continuous_time(G) + @assert iscontinuous(G) ny, nu = size(G) @assert (ny + nu == 2) "c2d(G::TransferFunction, h) not implemented for MIMO systems" sys = ss(G) @@ -218,11 +218,11 @@ function lsima(sys::StateSpace, t::AbstractVector, r::AbstractVector, control_si end dt = Float64(t[2] - t[1]) - if !is_continuous_time(sys) || method == :zoh - if is_continuous_time(sys) + if !iscontinuous(sys) || method == :zoh + if iscontinuous(sys) dsys = c2d(sys, dt, :zoh)[1] else - if sys.Ts != dt + if sys.ts != dt error("Time vector must match sample time for discrete system") end dsys = sys diff --git a/src/freqresp.jl b/src/freqresp.jl index 91939949a..459211348 100644 --- a/src/freqresp.jl +++ b/src/freqresp.jl @@ -7,7 +7,7 @@ Evaluate the frequency response of a linear system of system `sys` over the frequency vector `w`.""" function freqresp(sys::LTISystem, w_vec::AbstractVector{<:Real}) # Create imaginary freq vector s - if is_continuous_time(sys) + if iscontinuous(sys) s_vec = im*w_vec else s_vec = exp.(w_vec*(im*sys.Ts)) @@ -35,7 +35,7 @@ function _preprocess_for_freqresp(sys::StateSpace) P = C*T Q = T\B # TODO Type stability? # T is unitary, so mutliplication with T' should do the trick # FIXME; No performance improvement from Hessienberg structure, also weired renaming of matrices - StateSpace(F.H, Q, P, D, sys.sampletime) + StateSpace(F.H, Q, P, D, sys.time) end @@ -64,7 +64,7 @@ end Notation for frequency response evaluation. - F(s) evaluates the continuous-time transfer function F at s. -- F(omega,true) evaluates the discrete-time transfer function F at exp(i*Ts*omega) +- F(omega,true) evaluates the discrete-time transfer function F at exp(im*ts*omega) - F(z,false) evaluates the discrete-time transfer function F at z """ function (sys::TransferFunction)(s) @@ -72,7 +72,7 @@ function (sys::TransferFunction)(s) end function (sys::TransferFunction)(z_or_omega::Number, map_to_unit_circle::Bool) - @assert is_discrete_time(sys) "It only makes no sense to call this function with discrete systems" + @assert isdiscrete(sys) "It only makes no sense to call this function with discrete systems" if map_to_unit_circle isreal(z_or_omega) ? evalfr(sys,exp(im*z_or_omega.*sys.Ts)) : error("To map to the unit circle, omega should be real") else @@ -81,7 +81,7 @@ function (sys::TransferFunction)(z_or_omega::Number, map_to_unit_circle::Bool) end function (sys::TransferFunction)(z_or_omegas::AbstractVector, map_to_unit_circle::Bool) - @assert is_discrete_time(sys) "It only makes no sense to call this function with discrete systems" + @assert isdiscrete(sys) "It only makes no sense to call this function with discrete systems" vals = sys.(z_or_omegas, map_to_unit_circle)# evalfr.(sys,exp.(evalpoints)) # Reshape from vector of evalfr matrizes, to (in,out,freq) Array nu,ny = size(vals[1]) @@ -168,7 +168,7 @@ function _bounds_and_features(sys::LTISystem, plot::Val) w1 = 0.0 w2 = 2.0 end - if !is_continuous_time(sys) && !isstatic(sys) # Do not draw above Nyquist freq for disc. systems + if !iscontinuous(sys) && !isstatic(sys) # Do not draw above Nyquist freq for disc. systems w2 = min(w2, log10(π/sys.Ts)) end return [w1, w2], zp diff --git a/src/matrix_comps.jl b/src/matrix_comps.jl index e12befc1c..53abbfd84 100644 --- a/src/matrix_comps.jl +++ b/src/matrix_comps.jl @@ -86,7 +86,7 @@ function gram(sys::AbstractStateSpace, opt::Symbol) if !isstable(sys) error("gram only valid for stable A") end - func = is_continuous_time(sys) ? lyap : dlyap + func = iscontinuous(sys) ? lyap : dlyap if opt == :c # TODO probably remove type check in julia 0.7.0 return func(sys.A, sys.B*sys.B')#::Array{numeric_type(sys),2} # lyap is type-unstable @@ -162,14 +162,14 @@ function covar(sys::AbstractStateSpace, W) if !isstable(sys) return fill(Inf,(size(C,1),size(C,1))) end - func = is_continuous_time(sys) ? lyap : dlyap + func = iscontinuous(sys) ? lyap : dlyap Q = try func(A, B*W*B') catch error("No solution to the Lyapunov equation was found in covar") end P = C*Q*C' - if !is_discrete_time(sys) + if !isdiscrete(sys) #Variance and covariance infinite for direct terms direct_noise = D*W*D' for i in 1:size(C,1) @@ -269,7 +269,7 @@ state space systems in continuous and discrete time', American Control Conferenc See also [`hinfnorm`](@ref). """ function linfnorm(sys::AbstractStateSpace; tol=1e-6) - if is_continuous_time(sys) + if iscontinuous(sys) return _infnorm_two_steps_ct(sys, :linf, tol) else return _infnorm_two_steps_dt(sys, :linf, tol) @@ -526,7 +526,7 @@ function balreal(sys::ST) where ST <: AbstractStateSpace display(Σ) end - sysr = ST(T*sys.A/T, T*sys.B, sys.C/T, sys.D, sys.sampletime), diagm(0 => Σ) + sysr = ST(T*sys.A/T, T*sys.B, sys.C/T, sys.D, sys.time), diagm(0 => Σ) end @@ -553,7 +553,7 @@ function baltrunc(sys::ST; atol = sqrt(eps()), rtol = 1e-3, unitgain = true) whe D = D/(C*inv(-A)*B) end - return ST(A,B,C,D,sys.sampletime), diagm(0 => S) + return ST(A,B,C,D,sys.time), diagm(0 => S) end """ @@ -572,7 +572,7 @@ function similarity_transform(sys::ST, T) where ST <: AbstractStateSpace B = Tf\sys.B C = sys.C*T D = sys.D - ST(A,B,C,D,sys.sampletime) + ST(A,B,C,D,sys.time) end """ @@ -597,10 +597,10 @@ See Stochastic Control, Chapter 4, Åström """ function innovation_form(sys::ST, R1, R2) where ST <: AbstractStateSpace K = kalman(sys, R1, R2) - ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.sampletime) + ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.time) end # Set D = I to get transfer function H = I + C(sI-A)\ K function innovation_form(sys::ST; sysw=I, syse=I, R1=I, R2=I) where ST <: AbstractStateSpace K = kalman(sys, covar(sysw,R1), covar(syse, R2)) - ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.sampletime) + ST(sys.A, K, sys.C, Matrix{eltype(sys.A)}(I, sys.ny, sys.ny), sys.time) end diff --git a/src/plotting.jl b/src/plotting.jl index 22aa2e4df..f5edebd50 100644 --- a/src/plotting.jl +++ b/src/plotting.jl @@ -141,7 +141,7 @@ lsimplot s = systems[si] y = length(p.args) >= 4 ? lsim(s, u, t, x0=p.args[4], method=method)[1] : lsim(s, u, t, method=method)[1] styledict = getStyleSys(si,length(systems)) - seriestype := is_continuous_time(s) ? :path : :steppost + seriestype := iscontinuous(s) ? :path : :steppost for i=1:ny ytext = (ny > 1) ? "Amplitude to: y($i)" : "Amplitude" @series begin @@ -205,7 +205,7 @@ for (func, title, typ) = ((step, "Step Response", Stepplot), (impulse, "Impulse for i=1:ny for j=1:nu ydata = reshape(y[:, i, j], size(t, 1)) - style = is_continuous_time(s) ? :path : :steppost + style = iscontinuous(s) ? :path : :steppost ttext = (nu > 1 && i==1) ? title*" from: u($j) " : title titles[s2i(i,j)] = ttext ytext = (ny > 1 && j==1) ? "Amplitude to: y($i)" : "Amplitude" @@ -470,8 +470,8 @@ nicholsplot systems, w = _processfreqplot(Val{:nyquist}(), p.args...) ny, nu = size(systems[1]) - if is_discrete_time(systems[1]) - w_nyquist = 2π/sampletime(systems[1]) + if isdiscrete(systems[1]) + w_nyquist = 2π/time(systems[1]) w = w[w.<= w_nyquist] end nw = length(w) @@ -744,7 +744,7 @@ pzmap end end - if is_discrete_time(system) + if isdiscrete(system) v = range(0,stop=2π,length=100) S,C = sin.(v),cos.(v) @series begin diff --git a/src/simplification.jl b/src/simplification.jl index ef668c3cd..219c84556 100644 --- a/src/simplification.jl +++ b/src/simplification.jl @@ -6,7 +6,7 @@ determined to be uncontrollable and unobservable based on the location of 0s in `sys` are removed.""" function sminreal(sys::StateSpace) A, B, C, inds = struct_ctrb_obsv(sys) - return StateSpace(A, B, C, sys.D, sys.sampletime) + return StateSpace(A, B, C, sys.D, sys.time) end # Determine the structurally controllable and observable realization for the system diff --git a/src/simulators.jl b/src/simulators.jl index f1f27845e..6d89dc3c8 100644 --- a/src/simulators.jl +++ b/src/simulators.jl @@ -37,7 +37,7 @@ plot(t, s.y(sol, t)[:], lab="Open loop step response") ``` """ function Simulator(P::AbstractStateSpace, u::F = (x,t) -> 0) where F - @assert is_continuous_time(P) "Simulator only supports continuous-time system. See function `lsim` for simulation of discrete-time systems." + @assert iscontinuous(P) "Simulator only supports continuous-time system. See function `lsim` for simulation of discrete-time systems." @assert all(P.D .== 0) "Can not simulate systems with direct term D != 0" f = (dx,x,p,t) -> dx .= P.A*x .+ P.B*u(x,t) y(x,t) = P.C*x #.+ P.D*u(x,t) diff --git a/src/synthesis.jl b/src/synthesis.jl index 5d75f4067..2999f24a5 100644 --- a/src/synthesis.jl +++ b/src/synthesis.jl @@ -48,7 +48,7 @@ See also `LQG` kalman(A, C, R1,R2) = Matrix(lqr(A',C',R1,R2)') function lqr(sys::StateSpace, Q, R) - if is_continuous_time(sys) + if iscontinuous(sys) return lqr(sys.A, sys.B, Q, R) else return dlqr(sys.A, sys.B, Q, R) @@ -56,7 +56,7 @@ function lqr(sys::StateSpace, Q, R) end function kalman(sys::StateSpace, R1,R2) - if is_continuous_time(sys) + if iscontinuous(sys) return Matrix(lqr(sys.A', sys.C', R1,R2)') else return Matrix(dlqr(sys.A', sys.C', R1,R2)') diff --git a/src/timeresp.jl b/src/timeresp.jl index 998f639e4..10b46209d 100644 --- a/src/timeresp.jl +++ b/src/timeresp.jl @@ -43,7 +43,7 @@ function impulse(sys::StateSpace, t::AbstractVector; method=:cont) lt = length(t) ny, nu = size(sys) nx = sys.nx - if is_continuous_time(sys) || isstatic(sys) #&& method == :cont + if iscontinuous(sys) || isstatic(sys) #&& method == :cont u = (x,i) -> [zero(T)] # impulse response equivalent to unforced response of # ss(A, 0, C, 0) with x0 = B. @@ -118,8 +118,8 @@ function lsim(sys::StateSpace, u::AbstractVecOrMat, t::AbstractVector; end dt = Float64(t[2] - t[1]) - if !is_continuous_time(sys) || method == :zoh - if !is_discrete_time(sys) + if !iscontinuous(sys) || method == :zoh + if !isdiscrete(sys) dsys = c2d(sys, dt, :zoh)[1] else if sys.Ts != dt @@ -151,8 +151,8 @@ function lsim(sys::StateSpace, u::Function, t::AbstractVector; T = promote_type(Float64, eltype(x0)) dt = T(t[2] - t[1]) - if !is_continuous_time(sys) || method == :zoh - if !is_discrete_time(sys) + if !iscontinuous(sys) || method == :zoh + if !isdiscrete(sys) dsys = c2d(sys, dt, :zoh)[1] else if sys.Ts != dt @@ -226,7 +226,7 @@ function _default_time_vector(sys::LTISystem, tfinal::Real=-1) end function _default_dt(sys::LTISystem) - if is_discrete_time(sys) + if isdiscrete(sys) dt = sys.Ts elseif !isstable(sys) dt = 0.05 diff --git a/src/types/DelayLtiSystem.jl b/src/types/DelayLtiSystem.jl index 31ac961ba..296bce061 100644 --- a/src/types/DelayLtiSystem.jl +++ b/src/types/DelayLtiSystem.jl @@ -16,7 +16,7 @@ struct DelayLtiSystem{T,S<:Real} <: LTISystem # end end -sampletime(sys::DelayLtiSystem) = sampletime(sys.P) +time(sys::DelayLtiSystem) = time(sys.P) # QUESTION: would psys be a good standard variable name for a PartionedStateSpace # and perhaps dsys for a delayed system, (ambigous with discrete system though) @@ -76,7 +76,7 @@ Base.convert(::Type{V}, sys::DelayLtiSystem) where {T, V<:DelayLtiSystem{T}} = function *(sys::DelayLtiSystem, n::Number) new_C = [sys.P.C1*n; sys.P.C2] new_D = [sys.P.D11*n sys.P.D12*n; sys.P.D21 sys.P.D22] - return DelayLtiSystem(StateSpace(sys.P.A, sys.P.B, new_C, new_D, sys.P.sampletime), sys.Tau) + return DelayLtiSystem(StateSpace(sys.P.A, sys.P.B, new_C, new_D, sys.P.time), sys.Tau) end *(n::Number, sys::DelayLtiSystem) = *(sys, n) @@ -144,7 +144,7 @@ function Base.getindex(sys::DelayLtiSystem, i::AbstractArray, j::AbstractArray) sys.P.B[:, colidx], sys.P.C[rowidx, :], sys.P.D[rowidx, colidx], - sys.P.sampletime), sys.Tau) + sys.P.time), sys.Tau) end function Base.show(io::IO, sys::DelayLtiSystem) @@ -206,7 +206,7 @@ Create a time delay of length `tau` with `exp(-τ*s)` where `s=tf("s")` and `τ` See also: [`delay`](@ref) which is arguably more conenient than this function. """ function Base.exp(G::TransferFunction{Continuous,<:SisoRational}) - if size(G.matrix) != (1,1) && is_continuous_time(G) + if size(G.matrix) != (1,1) && iscontinuous(G) error("G must be a continuous-time scalar transfer function. Consider using `delay` instead.") end G_siso = G.matrix[1,1] diff --git a/src/types/Lti.jl b/src/types/Lti.jl index f798ae231..23883282e 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -42,24 +42,24 @@ function issiso(sys::LTISystem) end -"""`is_continuous_time(sys)` +"""`iscontinuous(sys)` Returns `true` for a continuous-time system `sys`, else returns `false`.""" -is_continuous_time(sys::LTISystem) = sampletime(sys) isa Continuous -"""`is_discrete_time(sys)` +iscontinuous(sys::LTISystem) = time(sys) isa Continuous +"""`isdiscrete(sys)` Returns `true` for a discrete-time system `sys`, else returns `false`.""" -is_discrete_time(sys::LTISystem) = sampletime(sys) isa Discrete +isdiscrete(sys::LTISystem) = time(sys) isa Discrete function Base.getproperty(sys::LTISystem, s::Symbol) if s === :Ts - # if !is_discrete_time(sys) # NOTE this line seems to be breaking inference of is_discrete_time (is there a test for this?) - if !is_discrete_time(sys) - @warn "Getting sampletime 0.0 for non-discrete systems is deprecated. Check `is_discrete_time` before trying to access sampletime." + # if !isdiscrete(sys) # NOTE this line seems to be breaking inference of isdiscrete (is there a test for this?) + if !isdiscrete(sys) + @warn "Getting time 0.0 for non-discrete systems is deprecated. Check `isdiscrete` before trying to access time." return 0.0 else - return sampletime(sys).Ts + return time(sys).Ts end else return getfield(sys, s) @@ -68,17 +68,17 @@ end """`timetype(sys)` -Get the timetype of system. Usually typeof(sys.sampletime).""" -timetype(sys) = typeof(sys.sampletime) +Get the timetype of system. Usually typeof(sys.time).""" +timetype(sys) = typeof(sys.time) -common_sampletime(systems::LTISystem...) = common_sampletime(sampletime(sys) for sys in systems) +common_time(systems::LTISystem...) = common_time(time(sys) for sys in systems) """`isstable(sys)` Returns `true` if `sys` is stable, else returns `false`.""" function isstable(sys::LTISystem) - if is_continuous_time(sys) + if iscontinuous(sys) if any(real.(pole(sys)).>=0) return false end diff --git a/src/types/PartionedStateSpace.jl b/src/types/PartionedStateSpace.jl index 05053d600..5cf413393 100644 --- a/src/types/PartionedStateSpace.jl +++ b/src/types/PartionedStateSpace.jl @@ -27,8 +27,8 @@ function getproperty(sys::PartionedStateSpace, d::Symbol) if d === :Ts return P.Ts # Will throw deprecation until removed # DEPRECATED - elseif d === :sampletime - return P.sampletime + elseif d === :time + return P.time elseif d === :P return P elseif d === :nu1 @@ -58,10 +58,10 @@ function getproperty(sys::PartionedStateSpace, d::Symbol) end end -sampletime(sys::PartionedStateSpace) = sampletime(sys.P) +time(sys::PartionedStateSpace) = time(sys.P) function +(s1::PartionedStateSpace, s2::PartionedStateSpace) - sampletime = common_sampletime(s1,s2) + time = common_time(s1,s2) A = blockdiag(s1.A, s2.A) @@ -73,7 +73,7 @@ function +(s1::PartionedStateSpace, s2::PartionedStateSpace) D = [(s1.D11 + s2.D11) s1.D12 s2.D12; [s1.D21; s2.D21] blockdiag(s1.D22, s2.D22)] - P = StateSpace(A, B, C, D, sampletime) # How to handle discrete? + P = StateSpace(A, B, C, D, time) # How to handle discrete? PartionedStateSpace(P, s1.nu1 + s2.nu1, s1.ny1 + s2.ny1) end @@ -85,7 +85,7 @@ end Series connection of partioned StateSpace systems. """ function *(s1::PartionedStateSpace, s2::PartionedStateSpace) - sampletime = common_sampletime(s1,s2) + time = common_time(s1,s2) A = [s1.A s1.B1*s2.C1; zeros(size(s2.A,1),size(s1.A,2)) s2.A] @@ -101,7 +101,7 @@ function *(s1::PartionedStateSpace, s2::PartionedStateSpace) s1.D21*s2.D11 s1.D22 s1.D21*s2.D12; s2.D21 zeros(size(s2.D22,1),size(s1.D22,2)) s2.D22 ] - P = StateSpace(A, B, C, D, sampletime) + P = StateSpace(A, B, C, D, time) PartionedStateSpace(P, s2.nu1, s1.ny1) end @@ -109,7 +109,7 @@ end # QUESTION: What about algebraic loops and well-posedness?! Perhaps issue warning if P1(∞)*P2(∞) > 1 function feedback(s1::PartionedStateSpace, s2::PartionedStateSpace) - sampletime = common_sampletime(s1,s2) + time = common_time(s1,s2) X_11 = (I + s2.D11*s1.D11)\[-s2.D11*s1.C1 -s2.C1] X_21 = (I + s1.D11*s2.D11)\[s1.C1 -s1.D11*s2.C1] @@ -148,7 +148,7 @@ function feedback(s1::PartionedStateSpace, s2::PartionedStateSpace) #tmp = [blockdiag(s1.D12, s2.D12); blockdiag(s1.D22, s2.D22)] #D[:, end-size(tmp,2)+1:end] .+= tmp - P = StateSpace(A, B, C, D, sampletime) + P = StateSpace(A, B, C, D, time) PartionedStateSpace(P, s2.nu1, s1.ny1) end @@ -161,7 +161,7 @@ end """ function vcat_1(systems::PartionedStateSpace...) # Perform checks - sampletime = common_sampletime(systems...) + time = common_time(systems...) nu1 = systems[1].nu1 if !all(s.nu1 == nu1 for s in systems) @@ -181,7 +181,7 @@ function vcat_1(systems::PartionedStateSpace...) D21 = reduce(vcat, [s.D21 for s in systems]) D22 = blockdiag([s.D22 for s in systems]...) - sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], sampletime) + sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], time) return PartionedStateSpace(sysnew, nu1, sum(s -> s.ny1, systems)) end @@ -195,7 +195,7 @@ end """ function hcat_1(systems::PartionedStateSpace...) # Perform checks - sampletime = common_sampletime(systems...) + time = common_time(systems...) ny1 = systems[1].ny1 if !all(s.ny1 == ny1 for s in systems) @@ -215,7 +215,7 @@ function hcat_1(systems::PartionedStateSpace...) D21 = blockdiag([s.D21 for s in systems]...) D22 = blockdiag([s.D22 for s in systems]...) - sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], sampletime) + sysnew = StateSpace(A, [B1 B2], [C1; C2], [D11 D12; D21 D22], time) return PartionedStateSpace(sysnew, sum(s -> s.nu1, systems), ny1) end diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index 63dba1e12..fcb62e870 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -2,7 +2,7 @@ ## Data Type Declarations ## ##################################################################### -function state_space_validation(A,B,C,D,sampletime::TimeType) +function state_space_validation(A,B,C,D,time::TimeType) nx = size(A, 1) nu = size(B, 2) ny = size(C, 1) @@ -24,28 +24,28 @@ end abstract type AbstractStateSpace{TimeT<:TimeType} <: LTISystem end -sampletime(sys::AbstractStateSpace) = sys.sampletime +time(sys::AbstractStateSpace) = sys.time struct StateSpace{TimeT, T, MT<:AbstractMatrix{T}} <: AbstractStateSpace{TimeT} A::MT B::MT C::MT D::MT - sampletime::TimeT + time::TimeT nx::Int nu::Int ny::Int - function StateSpace{TimeT, T, MT}(A::MT, B::MT, C::MT, D::MT, sampletime::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} - nx,nu,ny = state_space_validation(A,B,C,D,sampletime) - new{TimeT, T, MT}(A, B, C, D, sampletime, nx, nu, ny) + function StateSpace{TimeT, T, MT}(A::MT, B::MT, C::MT, D::MT, time::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} + nx,nu,ny = state_space_validation(A,B,C,D,time) + new{TimeT, T, MT}(A, B, C, D, time, nx, nu, ny) end end -function StateSpace(A::MT, B::MT, C::MT, D::MT, sampletime::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} - StateSpace{TimeT, T, MT}(A, B, C, D, sampletime) +function StateSpace(A::MT, B::MT, C::MT, D::MT, time::TimeT) where {TimeT<:TimeType, T, MT <: AbstractMatrix{T}} + StateSpace{TimeT, T, MT}(A, B, C, D, time) end # Constructor for Discrete system -function StateSpace(A::MT, B::MT, C::MT, D::MT, Ts::Number) where {T, MT <: AbstractMatrix{T}} - StateSpace(A, B, C, D, Discrete(Ts)) +function StateSpace(A::MT, B::MT, C::MT, D::MT, ts::Number) where {T, MT <: AbstractMatrix{T}} + StateSpace(A, B, C, D, Discrete(ts)) end # Constructor for Continuous system function StateSpace(A::MT, B::MT, C::MT, D::MT) where {T, MT <: AbstractMatrix{T}} @@ -74,40 +74,40 @@ end const AbstractNumOrArray = Union{Number, AbstractVecOrMat} # Explicit construction with different types -function StateSpace{TimeT,T,MT}(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, sampletime::TimeType) where {TimeT, T, MT <: AbstractMatrix{T}} +function StateSpace{TimeT,T,MT}(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, time::TimeType) where {TimeT, T, MT <: AbstractMatrix{T}} D = fix_D_matrix(T, B, C, D) - return StateSpace{TimeT, T,Matrix{T}}(MT(to_matrix(T, A)), MT(to_matrix(T, B)), MT(to_matrix(T, C)), MT(D), TimeT(sampletime)) + return StateSpace{TimeT, T,Matrix{T}}(MT(to_matrix(T, A)), MT(to_matrix(T, B)), MT(to_matrix(T, C)), MT(D), TimeT(time)) end # Explicit conversion function StateSpace{TimeT,T,MT}(sys::StateSpace) where {TimeT, T, MT <: AbstractMatrix{T}} - StateSpace{TimeT,T,MT}(sys.A,sys.B,sys.C,sys.D,sys.sampletime) + StateSpace{TimeT,T,MT}(sys.A,sys.B,sys.C,sys.D,sys.time) end -function StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, sampletime::TimeType) +function StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, time::TimeType) A, B, C, D, T = to_similar_matrices(A,B,C,D) - return StateSpace{typeof(sampletime),T,Matrix{T}}(A, B, C, D, sampletime) + return StateSpace{typeof(time),T,Matrix{T}}(A, B, C, D, time) end # General Discrete constructor -StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::Number) = - StateSpace(A, B, C, D, Discrete(Ts)) +StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, ts::Number) = + StateSpace(A, B, C, D, Discrete(ts)) # General continuous constructor StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray) = StateSpace(A, B, C, D, Continuous()) # Function for creation of static gain -function StateSpace(D::AbstractArray{T}, sampletime::TimeType) where {T<:Number} +function StateSpace(D::AbstractArray{T}, time::TimeType) where {T<:Number} ny, nu = size(D, 1), size(D, 2) A = fill(zero(T), 0, 0) B = fill(zero(T), 0, nu) C = fill(zero(T), ny, 0) D = reshape(D, (ny,nu)) - return StateSpace(A, B, C, D, sampletime) + return StateSpace(A, B, C, D, time) end -StateSpace(D::AbstractArray, Ts::Number) = StateSpace(D, Discrete(Ts)) +StateSpace(D::AbstractArray, ts::Number) = StateSpace(D, Discrete(ts)) StateSpace(D::AbstractArray) = StateSpace(D, Continuous()) -StateSpace(d::Number, Ts::Number; kwargs...) = StateSpace([d], Discrete(Ts)) +StateSpace(d::Number, ts::Number; kwargs...) = StateSpace([d], Discrete(ts)) StateSpace(d::Number; kwargs...) = StateSpace([d], Continuous()) @@ -115,15 +115,15 @@ StateSpace(d::Number; kwargs...) = StateSpace([d], Continuous()) StateSpace(sys::LTISystem) = convert(StateSpace, sys) """ - `sys = ss(A, B, C, D [,Ts])` + `sys = ss(A, B, C, D [,ts])` Create a state-space model `sys::StateSpace{TimeT, T, MT<:AbstractMatrix{T}}` where `MT` is the type of matrixes `A,B,C,D`, `T` the element type and TimeT is Continuous or Discrete. -This is a continuous-time model if Ts is omitted. +This is a continuous-time model if `ts` is omitted. Otherwise, this is a discrete-time model with sampling period Ts. -`sys = ss(D [, Ts])` specifies a static gain matrix D. +`sys = ss(D [, ts])` specifies a static gain matrix D. """ ss(args...;kwargs...) = StateSpace(args...;kwargs...) @@ -133,27 +133,27 @@ struct HeteroStateSpace{TimeT, AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:Abstrac B::BT C::CT D::DT - sampletime::TimeT + time::TimeT nx::Int nu::Int ny::Int end function HeteroStateSpace(A::AT, B::BT, - C::CT, D::DT, sampletime::TimeT) where {TimeT<:TimeType,AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:AbstractMatrix,DT<:AbstractMatrix} - nx,nu,ny = state_space_validation(A,B,C,D,sampletime) - HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, sampletime, nx, nu, ny) + C::CT, D::DT, time::TimeT) where {TimeT<:TimeType,AT<:AbstractMatrix,BT<:AbstractMatrix,CT<:AbstractMatrix,DT<:AbstractMatrix} + nx,nu,ny = state_space_validation(A,B,C,D,time) + HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, time, nx, nu, ny) end # Explicit constructor -function HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, sampletime) where {TimeT,AT,BT,CT,DT} - nx,nu,ny = state_space_validation(A,B,C,D,sampletime) - HeteroStateSpace{TimeT,AT,BT,CT,DT}(AT(A), BT(B), CT(C), DT(D), TimeT(sampletime), nx, nu, ny) +function HeteroStateSpace{TimeT,AT,BT,CT,DT}(A, B, C, D, time) where {TimeT,AT,BT,CT,DT} + nx,nu,ny = state_space_validation(A,B,C,D,time) + HeteroStateSpace{TimeT,AT,BT,CT,DT}(AT(A), BT(B), CT(C), DT(D), TimeT(time), nx, nu, ny) end -HeteroStateSpace(s::AbstractStateSpace) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.sampletime) +HeteroStateSpace(s::AbstractStateSpace) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.time) # Base constructor -function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, sampletime::TimeType) +function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, time::TimeType) A = to_abstract_matrix(A) B = to_abstract_matrix(B) C = to_abstract_matrix(C) @@ -162,29 +162,29 @@ function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::Abstr else D = to_abstract_matrix(D) end - return HeteroStateSpace{typeof(sampletime),typeof(A),typeof(B),typeof(C),typeof(D)}(A, B, C, D, sampletime) + return HeteroStateSpace{typeof(time),typeof(A),typeof(B),typeof(C),typeof(D)}(A, B, C, D, time) end -HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::Number) = - HeteroStateSpace(A, B, C, D, Discrete(Ts)) +HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, ts::Number) = + HeteroStateSpace(A, B, C, D, Discrete(ts)) HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray) = HeteroStateSpace(A, B, C, D, Continuous()) # Function for creation of static gain -function HeteroStateSpace(D::AbstractArray{T}, sampletime::TimeType) where {T<:Number} +function HeteroStateSpace(D::AbstractArray{T}, time::TimeType) where {T<:Number} ny, nu = size(D, 1), size(D, 2) A = fill(zero(T), 0, 0) B = fill(zero(T), 0, nu) C = fill(zero(T), ny, 0) - return HeteroStateSpace(A, B, C, D, sampletime) + return HeteroStateSpace(A, B, C, D, time) end -HeteroStateSpace(D::AbstractArray{T}, Ts::Number) where {T<:Number} = HeteroStateSpace(D, Discrete(Ts)) +HeteroStateSpace(D::AbstractArray{T}, ts::Number) where {T<:Number} = HeteroStateSpace(D, Discrete(ts)) HeteroStateSpace(D::AbstractArray{T}) where {T<:Number} = HeteroStateSpace(D, Continuous()) -HeteroStateSpace(d::Number, Ts::Number; kwargs...) = HeteroStateSpace([d], Discrete(Ts)) +HeteroStateSpace(d::Number, ts::Number; kwargs...) = HeteroStateSpace([d], Discrete(ts)) HeteroStateSpace(d::Number; kwargs...) = HeteroStateSpace([d], Continuous()) # HeteroStateSpace(sys) converts to HeteroStateSpace @@ -223,7 +223,7 @@ function +(s1::StateSpace{TimeT,T,MT}, s2::StateSpace{TimeT,T,MT}) where {TimeT, if size(s1) != size(s2) error("Systems have different shapes.") end - sampletime = common_sampletime(s1,s2) + time = common_time(s1,s2) A = [s1.A fill(zero(T), nstates(s1), nstates(s2)); fill(zero(T), nstates(s2), nstates(s1)) s2.A] @@ -231,7 +231,7 @@ function +(s1::StateSpace{TimeT,T,MT}, s2::StateSpace{TimeT,T,MT}) where {TimeT, C = [s1.C s2.C;] D = [s1.D + s2.D;] - return StateSpace{TimeT,T,MT}(A, B, C, D, sampletime) + return StateSpace{TimeT,T,MT}(A, B, C, D, time) end function +(s1::HeteroStateSpace, s2::HeteroStateSpace) @@ -239,7 +239,7 @@ function +(s1::HeteroStateSpace, s2::HeteroStateSpace) if size(s1) != size(s2) error("Systems have different shapes.") end - sampletime = common_sampletime(s1,s2) + time = common_time(s1,s2) T = promote_type(eltype(s1.A),eltype(s2.A)) A = [s1.A fill(zero(T), nstates(s1), nstates(s2)); fill(zero(T), nstates(s2), nstates(s1)) s2.A] @@ -247,10 +247,10 @@ function +(s1::HeteroStateSpace, s2::HeteroStateSpace) C = [s1.C s2.C;] D = [s1.D + s2.D;] - return HeteroStateSpace(A, B, C, D, sampletime) + return HeteroStateSpace(A, B, C, D, time) end -+(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C, sys.D .+ n, sys.sampletime) ++(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C, sys.D .+ n, sys.time) +(n::Number, sys::ST) where ST <: AbstractStateSpace = +(sys, n) ## SUBTRACTION ## @@ -259,7 +259,7 @@ end -(n::Number, sys::AbstractStateSpace) = +(-sys, n) ## NEGATION ## --(sys::ST) where ST <: AbstractStateSpace = ST(sys.A, sys.B, -sys.C, -sys.D, sys.sampletime) +-(sys::ST) where ST <: AbstractStateSpace = ST(sys.A, sys.B, -sys.C, -sys.D, sys.time) ## MULTIPLICATION ## function *(sys1::StateSpace{TimeT,T,MT}, sys2::StateSpace{TimeT,T,MT}) where {TimeT,T, MT} @@ -268,14 +268,14 @@ function *(sys1::StateSpace{TimeT,T,MT}, sys2::StateSpace{TimeT,T,MT}) where {Ti if sys1.nu != sys2.ny error("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs") end - sampletime = common_sampletime(sys1,sys2) + time = common_time(sys1,sys2) A = [sys1.A sys1.B*sys2.C; fill(zero(T), sys2.nx, sys1.nx) sys2.A] B = [sys1.B*sys2.D ; sys2.B] C = [sys1.C sys1.D*sys2.C;] D = [sys1.D*sys2.D;] - return StateSpace{TimeT,T,MT}(A, B, C, D, sampletime) + return StateSpace{TimeT,T,MT}(A, B, C, D, time) end function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) @@ -284,7 +284,7 @@ function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) if sys1.nu != sys2.ny error("sys1*sys2: sys1 must have same number of inputs as sys2 has outputs") end - sampletime = common_sampletime(sys1,sys2) + time = common_time(sys1,sys2) T = promote_type(eltype(sys1.A),eltype(sys2.A)) A = [sys1.A sys1.B*sys2.C; fill(zero(T), sys2.nx, sys1.nx) sys2.A] @@ -292,10 +292,10 @@ function *(sys1::HeteroStateSpace, sys2::HeteroStateSpace) C = [sys1.C sys1.D*sys2.C;] D = [sys1.D*sys2.D;] - return HeteroStateSpace(A, B, C, D, sampletime) + return HeteroStateSpace(A, B, C, D, time) end -*(sys::ST, n::Number) where ST <: AbstractStateSpace = StateSpace(sys.A, sys.B, sys.C*n, sys.D*n, sys.sampletime) +*(sys::ST, n::Number) where ST <: AbstractStateSpace = StateSpace(sys.A, sys.B, sys.C*n, sys.D*n, sys.time) *(n::Number, sys::AbstractStateSpace) = *(sys, n) ## DIVISION ## @@ -309,11 +309,11 @@ function /(n::Number, sys::ST) where ST <: AbstractStateSpace catch error("D isn't invertible") end - return ST(A - B*Dinv*C, B*Dinv, -n*Dinv*C, n*Dinv, sys.sampletime) + return ST(A - B*Dinv*C, B*Dinv, -n*Dinv*C, n*Dinv, sys.time) end Base.inv(sys::AbstractStateSpace) = 1/sys -/(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C/n, sys.D/n, sys.sampletime) +/(sys::ST, n::Number) where ST <: AbstractStateSpace = ST(sys.A, sys.B, sys.C/n, sys.D/n, sys.time) Base.:^(sys::AbstractStateSpace, p::Integer) = Base.power_by_squaring(sys, p) @@ -331,7 +331,7 @@ function Base.getindex(sys::ST, inds...) where ST <: AbstractStateSpace error("Must specify 2 indices to index statespace model") end rows, cols = index2range(inds...) # FIXME: ControlSystems.index2range(inds...) - return ST(copy(sys.A), sys.B[:, cols], sys.C[rows, :], sys.D[rows, cols], sys.sampletime) + return ST(copy(sys.A), sys.B[:, cols], sys.C[rows, :], sys.D[rows, cols], sys.time) end ##################################################################### @@ -362,11 +362,11 @@ function Base.show(io::IO, sys::AbstractStateSpace) end println(io, "D = \n", _string_mat_with_headers(sys.D), "\n") # Print sample time - if is_discrete_time(sys) + if isdiscrete(sys) println(io, "Sample Time: ", sys.Ts, " (seconds)") end # Print model type - if is_continuous_time(sys) + if iscontinuous(sys) print(io, "Continuous-time state-space model") else print(io, "Discrete-time state-space model") diff --git a/src/types/TimeType.jl b/src/types/TimeType.jl index 961c49cb2..46dfd30c0 100644 --- a/src/types/TimeType.jl +++ b/src/types/TimeType.jl @@ -1,12 +1,12 @@ abstract type TimeType end -const UNDEF_TS = -1 # For handling promotion of Matrix to LTISystem +const UNDEF_SAMPLEPERIOD = -1 # For handling promotion of Matrix to LTISystem struct Discrete{T} <: TimeType Ts::T function Discrete{T}(Ts::T) where T - if Ts <= 0 && Ts != UNDEF_TS + if Ts <= 0 && Ts != UNDEF_SAMPLEPERIOD throw(ErrorException("Creating a continuous time system by setting sample time to 0 is no longer supported.")) end new{T}(Ts) @@ -22,8 +22,8 @@ Continuous(x::Continuous) = x Discrete{T}(x::Discrete) where T = Discrete{T}(x.Ts) -undef_Ts(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_TS) -undef_Ts(::Type{Continuous}) where T = Continuous() +undef_sampleperiod(::Type{Discrete{T}}) where T = Discrete{T}(UNDEF_SAMPLEPERIOD) +undef_sampleperiod(::Type{Continuous}) where T = Continuous() # Promotion @@ -34,24 +34,24 @@ Base.promote_rule(::Type{Discrete{T1}}, ::Type{Discrete{T2}}) where {T1,T2}= Dis Base.convert(::Type{Discrete{T1}}, x::Discrete{T2}) where {T1,T2} = Discrete{T1}(x.Ts) # Promoting two or more systems systems should promote sample times -common_sampletime(x::TimeType) = x -common_sampletime(x::TimeType, y::TimeType) = throw(ErrorException("Sampling time mismatch")) -common_sampletime(x::TimeType, y::TimeType, z...) = common_sampletime(common_sampletime(x, y), z...) -common_sampletime(a::Base.Generator) = reduce(common_sampletime, a) +common_time(x::TimeType) = x +common_time(x::TimeType, y::TimeType) = throw(ErrorException("Sampling time mismatch")) +common_time(x::TimeType, y::TimeType, z...) = common_time(common_time(x, y), z...) +common_time(a::Base.Generator) = reduce(common_time, a) -function common_sampletime(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} - if x != y && x.Ts != UNDEF_TS && y.Ts != UNDEF_TS +function common_time(x::Discrete{T1}, y::Discrete{T2}) where {T1,T2} + if x != y && x.Ts != UNDEF_SAMPLEPERIOD && y.Ts != UNDEF_SAMPLEPERIOD throw(ErrorException("Sampling time mismatch")) end - if x.Ts == UNDEF_TS + if x.Ts == UNDEF_SAMPLEPERIOD return Discrete{promote_type(T1,T2)}(y) else return Discrete{promote_type(T1,T2)}(x) end end -common_sampletime(x::Continuous, ys::Continuous...) = Continuous() +common_time(x::Continuous, ys::Continuous...) = Continuous() # Check equality ==(x::TimeType, y::TimeType) = false diff --git a/src/types/TransferFunction.jl b/src/types/TransferFunction.jl index eb3c23ff0..7e20ef17b 100644 --- a/src/types/TransferFunction.jl +++ b/src/types/TransferFunction.jl @@ -1,16 +1,16 @@ struct TransferFunction{TimeT, S<:SisoTf{T} where T} <: LTISystem matrix::Matrix{S} - sampletime::TimeT + time::TimeT nu::Int ny::Int - function TransferFunction{TimeT,S}(matrix::Matrix{S}, sampletime::TimeT) where {S,TimeT} + function TransferFunction{TimeT,S}(matrix::Matrix{S}, time::TimeT) where {S,TimeT} # Validate size of input and output names ny, nu = size(matrix) - return new{TimeT,S}(matrix, sampletime, nu, ny) + return new{TimeT,S}(matrix, time, nu, ny) end end -function TransferFunction(matrix::Matrix{S}, sampletime::TimeT) where {TimeT<:TimeType, T<:Number, S<:SisoTf{T}} - TransferFunction{TimeT, S}(matrix, sampletime) +function TransferFunction(matrix::Matrix{S}, time::TimeT) where {TimeT<:TimeType, T<:Number, S<:SisoTf{T}} + TransferFunction{TimeT, S}(matrix, time) end # # Constructor for Discrete time system @@ -22,7 +22,7 @@ end # return TransferFunction(matrix, Continuous()) # end -sampletime(G) = G.sampletime +time(G) = G.time noutputs(G::TransferFunction) = size(G.matrix, 1) ninputs(G::TransferFunction) = size(G.matrix, 2) @@ -43,11 +43,11 @@ function Base.getindex(G::TransferFunction{TimeT,S}, inds...) where {TimeT,S<:Si rows, cols = index2range(inds...) mat = Matrix{S}(undef, length(rows), length(cols)) mat[:, :] = G.matrix[rows, cols] - return TransferFunction(mat, G.sampletime) + return TransferFunction(mat, G.time) end function Base.copy(G::TransferFunction) - return TransferFunction(copy(G.matrix), G.sampletime) + return TransferFunction(copy(G.matrix), G.time) end numvec(G::TransferFunction) = map(numvec, G.matrix) @@ -65,7 +65,7 @@ function minreal(G::TransferFunction, eps::Real=sqrt(eps())) for i = eachindex(G.matrix) matrix[i] = minreal(G.matrix[i], eps) end - return TransferFunction(matrix, G.sampletime) + return TransferFunction(matrix, G.time) end """`isproper(tf)` @@ -82,7 +82,7 @@ end ## EQUALITY ## function ==(G1::TransferFunction, G2::TransferFunction) - fields = [:sampletime, :ny, :nu, :matrix] + fields = [:time, :ny, :nu, :matrix] for field in fields if getfield(G1, field) != getfield(G2, field) return false @@ -94,7 +94,7 @@ end ## Approximate ## function isapprox(G1::TransferFunction, G2::TransferFunction; kwargs...) G1, G2 = promote(G1, G2) - fieldsApprox = [:sampletime, :matrix] + fieldsApprox = [:time, :matrix] for field in fieldsApprox if !(isapprox(getfield(G1, field), getfield(G2, field); kwargs...)) return false @@ -112,35 +112,35 @@ function +(G1::TransferFunction, G2::TransferFunction) if size(G1) != size(G2) error("Systems have different shapes.") end - sampletime = common_sampletime(G1,G2) + time = common_time(G1,G2) matrix = G1.matrix + G2.matrix - return TransferFunction(matrix, sampletime) + return TransferFunction(matrix, time) end -+(G::TransferFunction, n::Number) = TransferFunction(G.matrix .+ n, G.sampletime) ++(G::TransferFunction, n::Number) = TransferFunction(G.matrix .+ n, G.time) +(n::Number, G::TransferFunction) = +(G, n) ## SUBTRACTION ## --(n::Number, G::TransferFunction) = TransferFunction(n .- G.matrix, G.sampletime) +-(n::Number, G::TransferFunction) = TransferFunction(n .- G.matrix, G.time) -(G1::TransferFunction, G2::TransferFunction) = +(G1, -G2) -(G::TransferFunction, n::Number) = +(G, -n) ## NEGATION ## --(G::TransferFunction) = TransferFunction(-G.matrix, G.sampletime) +-(G::TransferFunction) = TransferFunction(-G.matrix, G.time) ## MULTIPLICATION ## function *(G1::TransferFunction, G2::TransferFunction) # Note: G1*G2 = y <- G1 <- G2 <- u - sampletime = common_sampletime(G1,G2) + time = common_time(G1,G2) if G1.nu != G2.ny error("G1*G2: G1 must have same number of inputs as G2 has outputs") end matrix = G1.matrix * G2.matrix - return TransferFunction(matrix, sampletime) + return TransferFunction(matrix, time) end -*(G::TransferFunction, n::Number) = TransferFunction(n*G.matrix, G.sampletime) +*(G::TransferFunction, n::Number) = TransferFunction(n*G.matrix, G.time) *(n::Number, G::TransferFunction) = *(G, n) ## DIVISION ## @@ -150,7 +150,7 @@ function /(n::Number, G::TransferFunction) else error("MIMO TransferFunction inversion isn't implemented yet") end - return TransferFunction(matrix, G.sampletime) + return TransferFunction(matrix, G.time) end /(G::TransferFunction, n::Number) = G*(1/n) /(G1::TransferFunction, G2::TransferFunction) = G1*(1/G2) @@ -168,7 +168,7 @@ function Base.show(io::IO, G::TransferFunction) # Compose the name vectors #println(io, "TransferFunction:") println(io, typeof(G)) - var = is_continuous_time(G) ? :s : :z + var = iscontinuous(G) ? :s : :z for i=1:G.nu for o=1:G.ny if !issiso(G) @@ -180,10 +180,10 @@ function Base.show(io::IO, G::TransferFunction) end end end - if is_continuous_time(G) + if iscontinuous(G) print(io, "\nContinuous-time transfer function model") - elseif is_discrete_time(G) - print(io, "\nSample Time: ", sampletime(G), " (seconds)") + elseif isdiscrete(G) + print(io, "\nSample Time: ", time(G), " (seconds)") print(io, "\nDiscrete-time transfer function model") else print(io, "\nStatic gain transfer function model") diff --git a/src/types/conversion.jl b/src/types/conversion.jl index af21e0cbd..6e4e49665 100644 --- a/src/types/conversion.jl +++ b/src/types/conversion.jl @@ -8,7 +8,7 @@ # # function Base.convert{T<:AbstractMatrix{<:Number}}(::Type{StateSpace{T}}, s::StateSpace) # AT = promote_type(T, arraytype(s)) -# StateSpace{AT}(AT(s.A),AT(s.B),AT(s.C),AT(s.D), s.sampletime, s.statenames, s.inputnames, s.outputnames) +# StateSpace{AT}(AT(s.A),AT(s.B),AT(s.C),AT(s.D), s.time, s.statenames, s.inputnames, s.outputnames) # end # TODO Fix these to use proper constructors @@ -20,9 +20,9 @@ # Base.convert(::Type{<:TransferFunction{<:SisoZpk}}, b::Number) = zpk(b) # Base.convert(::Type{TransferFunction{TimeT,SisoZpk{T1, TR1}}}, b::AbstractMatrix{T2}) where {TimeT, T1, TR1, T2<:Number} = - zpk(T1.(b), undef_Ts(TimeT)) + zpk(T1.(b), undef_sampleperiod(TimeT)) Base.convert(::Type{TransferFunction{TimeT,SisoRational{T1}}}, b::AbstractMatrix{T2}) where {TimeT, T1, T2<:Number} = - tf(T1.(b), undef_Ts(TimeT)) + tf(T1.(b), undef_sampleperiod(TimeT)) function convert(::Type{StateSpace{TimeT,T,MT}}, D::AbstractMatrix{<:Number}) where {TimeT,T, MT} (ny, nu) = size(D) @@ -30,14 +30,14 @@ function convert(::Type{StateSpace{TimeT,T,MT}}, D::AbstractMatrix{<:Number}) wh B = MT(fill(zero(T), (0,nu))) C = MT(fill(zero(T), (ny,0))) D = convert(MT, D) - return StateSpace{TimeT,T,MT}(A,B,C,D,undef_Ts(TimeT)) + return StateSpace{TimeT,T,MT}(A,B,C,D,undef_sampleperiod(TimeT)) end # TODO We still dont have TransferFunction{..}(number/array) constructors Base.convert(::Type{TransferFunction{TimeT,SisoRational{T}}}, b::Number) where {TimeT, T} = - tf(T(b), undef_Ts(TimeT)) + tf(T(b), undef_sampleperiod(TimeT)) Base.convert(::Type{TransferFunction{TimeT,SisoZpk{T,TR}}}, b::Number) where {TimeT, T, TR} = - zpk(T(b), undef_Ts(TimeT)) + zpk(T(b), undef_sampleperiod(TimeT)) Base.convert(::Type{StateSpace{TimeT,T,MT}}, b::Number) where {TimeT, T, MT} = convert(StateSpace{TimeT,T,MT}, fill(b, (1,1))) # @@ -61,20 +61,20 @@ Base.convert(::Type{StateSpace{TimeT,T,MT}}, b::Number) where {TimeT, T, MT} = function convert(::Type{TransferFunction{TimeT,S}}, G::TransferFunction) where {TimeT,S} Gnew_matrix = convert.(S, G.matrix) - return TransferFunction{TimeT,eltype(Gnew_matrix)}(Gnew_matrix, TimeT(G.sampletime)) + return TransferFunction{TimeT,eltype(Gnew_matrix)}(Gnew_matrix, TimeT(G.time)) end function convert(::Type{S}, sys::StateSpace) where {T, MT, TimeT, S <:StateSpace{TimeT,T,MT}} if sys isa S return sys else - return StateSpace{TimeT, T,MT}(convert(MT, sys.A), convert(MT, sys.B), convert(MT, sys.C), convert(MT, sys.D), TimeT(sys.sampletime)) + return StateSpace{TimeT, T,MT}(convert(MT, sys.A), convert(MT, sys.B), convert(MT, sys.C), convert(MT, sys.D), TimeT(sys.time)) end end # TODO Maybe add convert on matrices Base.convert(::Type{HeteroStateSpace{TimeT1,AT,BT,CT,DT}}, s::StateSpace{TimeT2,T,MT}) where {TimeT1,TimeT2,T,MT,AT,BT,CT,DT} = - HeteroStateSpace{TimeT1,AT,BT,CT,DT}(s.A,s.B,s.C,s.D,TimeT1(s.sampletime)) + HeteroStateSpace{TimeT1,AT,BT,CT,DT}(s.A,s.B,s.C,s.D,TimeT1(s.time)) Base.convert(::Type{HeteroStateSpace}, s::StateSpace) = HeteroStateSpace(s) @@ -118,7 +118,7 @@ function Base.convert(::Type{StateSpace{TimeT,T,MT}}, G::TransferFunction) where end end # A, B, C = balance_statespace(A, B, C)[1:3] NOTE: Use balance? - return StateSpace{TimeT,T,MT}(A, B, C, D, TimeT(G.sampletime)) + return StateSpace{TimeT,T,MT}(A, B, C, D, TimeT(G.time)) end siso_tf_to_ss(T::Type, f::SisoTf) = siso_tf_to_ss(T, convert(SisoRational, f)) @@ -186,7 +186,7 @@ function balance_statespace(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMat function balance_statespace(sys::StateSpace, perm::Bool=false) A, B, C, T = balance_statespace(sys.A,sys.B,sys.C, perm) - return ss(A,B,C,sys.D,sys.sampletime), T + return ss(A,B,C,sys.D,sys.time), T end # Method that might fail for some exotic types, such as TrackedArrays @@ -259,7 +259,7 @@ function convert(::Type{TransferFunction{TimeT,SisoRational{T}}}, sys::StateSpac num = charpoly(A-B[:,i:i]*C[j:j,:]) - charpolyA + D[j, i]*charpolyA matrix[j, i] = SisoRational{T}(num, charpolyA) end - TransferFunction{TimeT,SisoRational{T}}(matrix, TimeT(sys.sampletime)) + TransferFunction{TimeT,SisoRational{T}}(matrix, TimeT(sys.time)) end function convert(::Type{TransferFunction{TimeT1,SisoRational}}, sys::StateSpace{TimeT2,T0}) where {TimeT1,TimeT2,T0<:Number} T = typeof(one(T0)/one(T0)) @@ -276,7 +276,7 @@ function convert(::Type{TransferFunction{TimeT,SisoZpk{T,TR}}}, sys::StateSpace) z, p, k = siso_ss_to_zpk(sys, i, j) matrix[i, j] = SisoZpk{T,TR}(z, p, k) end - TransferFunction{TimeT,SisoZpk{T,TR}}(matrix, TimeT(sys.sampletime)) + TransferFunction{TimeT,SisoZpk{T,TR}}(matrix, TimeT(sys.time)) end function convert(::Type{TransferFunction{TimeT1,SisoZpk}}, sys::StateSpace{TimeT2,T0}) where {TimeT1,TimeT2,T0<:Number} T = typeof(one(T0)/one(T0)) diff --git a/src/types/tf.jl b/src/types/tf.jl index 06cde1852..ce1d6256f 100644 --- a/src/types/tf.jl +++ b/src/types/tf.jl @@ -40,9 +40,9 @@ tf(num::AbstractVecOrMat{<:AbstractVector{T1}}, den::AbstractVecOrMat{<:Abstract tf(num::AbstractVecOrMat{<:AbstractVector{T1}}, den::AbstractVecOrMat{<:AbstractVector{T2}}) where {T1,T2} = tf(num, den, Continuous()) -function tf(num::AbstractVector{T1}, den::AbstractVector{T2}, sampletime::TimeT) where {TimeT<:TimeType,T1<:Number, T2<:Number} +function tf(num::AbstractVector{T1}, den::AbstractVector{T2}, time::TimeT) where {TimeT<:TimeType,T1<:Number, T2<:Number} T = promote_type(T1, T2) - return TransferFunction{TimeT,SisoRational{T}}(fill(SisoRational{T}(num, den), 1, 1), sampletime) + return TransferFunction{TimeT,SisoRational{T}}(fill(SisoRational{T}(num, den), 1, 1), time) end tf(num::AbstractVector{T1}, den::AbstractVector{T2}, Ts::Number) where {T1<:Number, T2<:Number} = tf(num, den, Discrete(Ts)) @@ -52,14 +52,14 @@ tf(num::AbstractVector{T1}, den::AbstractVector{T2}) where {T1<:Number, T2<:Numb tf(num::Number, den::Vector, args...) = tf([num], den, args...) # Cases for just static gain -function tf(D::AbstractArray{T}, sampletime::TimeT) where {TimeT<:TimeType, T<:Number} +function tf(D::AbstractArray{T}, time::TimeT) where {TimeT<:TimeType, T<:Number} ny, nu = size(D, 1), size(D, 2) matrix = Matrix{SisoRational{T}}(undef, ny, nu) for i in eachindex(D) matrix[i] = SisoRational{T}([D[i]], [one(T)]) end - return TransferFunction{TimeT,SisoRational{T}}(matrix, sampletime) + return TransferFunction{TimeT,SisoRational{T}}(matrix, time) end tf(D::AbstractArray{T}, Ts::Number) where T = tf(D, Discrete(Ts)) tf(D::AbstractArray{T}) where T = tf(D, Continuous()) @@ -84,7 +84,7 @@ function tf(var::AbstractString, Ts::Real) end ## Constructors for polynomial inputs -function tf(num::AbstractArray{PT}, den::AbstractArray{PT}, sampletime::TimeT) where {TimeT<:TimeType,T<:Number, PT <: Polynomials.Poly{T}} +function tf(num::AbstractArray{PT}, den::AbstractArray{PT}, time::TimeT) where {TimeT<:TimeType,T<:Number, PT <: Polynomials.Poly{T}} ny, nu = size(num, 1), size(num, 2) if (ny, nu) != (size(den, 1), size(den, 2)) error("num and den dimensions must match") @@ -96,15 +96,15 @@ function tf(num::AbstractArray{PT}, den::AbstractArray{PT}, sampletime::TimeT) matrix[o, i] = SisoRational{T}(num[o, i], den[o, i]) end end - return TransferFunction{TimeT,SisoRational{T}}(matrix, sampletime) + return TransferFunction{TimeT,SisoRational{T}}(matrix, time) end tf(num::AbstractArray{PT}, den::AbstractArray{PT}, Ts::Number) where {T<:Number, PT <: Polynomials.Poly{T}} = tf(num, den, Discrete(Ts)) tf(num::AbstractArray{PT}, den::AbstractArray{PT}) where {T<:Number, PT <: Polynomials.Poly{T}} = tf(num, den, Continuous()) -function tf(num::PT, den::PT, sampletime::TimeT) where {TimeT<:TimeType, T<:Number, PT <: Polynomials.Poly{T}} - tf(fill(num,1,1), fill(den,1,1), sampletime) +function tf(num::PT, den::PT, time::TimeT) where {TimeT<:TimeType, T<:Number, PT <: Polynomials.Poly{T}} + tf(fill(num,1,1), fill(den,1,1), time) end tf(num::PT, den::PT, Ts::Number) where {T<:Number, PT <: Polynomials.Poly{T}} = tf(num, den, Discrete(Number)) diff --git a/test/test_connections.jl b/test/test_connections.jl index 56a7e8197..fe1290a81 100644 --- a/test/test_connections.jl +++ b/test/test_connections.jl @@ -156,7 +156,6 @@ arr4[1] = ss(0); arr4[2] = ss(1); arr4[3] = ss(2) @test [1.0 D_111] == ss([1.0], [0.0 2.0], [3.0], [1.0 4.0], 0.005) # Type and sample time @test [D_111 1.0] isa StateSpace{Discrete{Float64},Float64,Array{Float64,2}} -@test sampletime([D_111 1.0]) == Discrete(0.005) @test [D_111 1.0].Ts == 0.005 # Continuous version @test [C_111 1.0] == ss([1.0], [2.0 0.0], [3.0], [4.0 1.0]) From 9bf6f2fd9025cb57d0f0ff67d6361158244e595b Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 09:12:17 +0200 Subject: [PATCH 12/18] Fixed typos. --- src/discrete.jl | 14 +++++++------- src/freqresp.jl | 2 +- src/types/StateSpace.jl | 26 +++++++++++++------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/discrete.jl b/src/discrete.jl index 043a837d8..617944cf9 100644 --- a/src/discrete.jl +++ b/src/discrete.jl @@ -1,15 +1,15 @@ export rstd, rstc, dab, c2d_roots2poly, c2d_poly2poly, zpconv#, lsima, indirect_str -"""`[sysd, x0map] = c2d(sys, ts, method=:zoh)` +"""`[sysd, x0map] = c2d(sys, Ts, method=:zoh)` Convert the continuous system `sys` into a discrete system with sample time -`ts`, using the provided method. Currently only `:zoh` and `:foh` are provided. +`Ts`, using the provided method. Currently only `:zoh` and `:foh` are provided. Returns the discrete system `sysd`, and a matrix `x0map` that transforms the initial conditions to the discrete domain by `x0_discrete = x0map*[x0; u0]`""" -function c2d(sys::StateSpace, ts::Real, method::Symbol=:zoh) +function c2d(sys::StateSpace, Ts::Real, method::Symbol=:zoh) if isdiscrete(sys) error("sys must be a continuous time system") end @@ -17,7 +17,7 @@ function c2d(sys::StateSpace, ts::Real, method::Symbol=:zoh) ny, nu = size(sys) nx = nstates(sys) if method == :zoh - M = exp([A*ts B*ts; + M = exp([A*Ts B*Ts; zeros(nu, nx + nu)]) Ad = M[1:nx, 1:nx] Bd = M[1:nx, nx+1:nx+nu] @@ -25,7 +25,7 @@ function c2d(sys::StateSpace, ts::Real, method::Symbol=:zoh) Dd = D x0map = [Matrix{Float64}(I, nx, nx) zeros(nx, nu)] # Cant use I if nx==0 elseif method == :foh - M = exp([A*ts B*ts zeros(nx, nu); + M = exp([A*Ts B*Ts zeros(nx, nu); zeros(nu, nx + nu) Matrix{Float64}(I, nu, nu); zeros(nu, nx + 2*nu)]) M1 = M[1:nx, nx+1:nx+nu] @@ -40,7 +40,7 @@ function c2d(sys::StateSpace, ts::Real, method::Symbol=:zoh) else error("Unsupported method: ", method) end - return StateSpace(Ad, Bd, Cd, Dd, ts), x0map + return StateSpace(Ad, Bd, Cd, Dd, Ts), x0map end @@ -222,7 +222,7 @@ function lsima(sys::StateSpace, t::AbstractVector, r::AbstractVector, control_si if iscontinuous(sys) dsys = c2d(sys, dt, :zoh)[1] else - if sys.ts != dt + if sys.Ts != dt error("Time vector must match sample time for discrete system") end dsys = sys diff --git a/src/freqresp.jl b/src/freqresp.jl index 459211348..ab14dfb0f 100644 --- a/src/freqresp.jl +++ b/src/freqresp.jl @@ -64,7 +64,7 @@ end Notation for frequency response evaluation. - F(s) evaluates the continuous-time transfer function F at s. -- F(omega,true) evaluates the discrete-time transfer function F at exp(im*ts*omega) +- F(omega,true) evaluates the discrete-time transfer function F at exp(im*Ts*omega) - F(z,false) evaluates the discrete-time transfer function F at z """ function (sys::TransferFunction)(s) diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index fcb62e870..f5b9b4cb0 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -44,8 +44,8 @@ function StateSpace(A::MT, B::MT, C::MT, D::MT, time::TimeT) where {TimeT<:TimeT StateSpace{TimeT, T, MT}(A, B, C, D, time) end # Constructor for Discrete system -function StateSpace(A::MT, B::MT, C::MT, D::MT, ts::Number) where {T, MT <: AbstractMatrix{T}} - StateSpace(A, B, C, D, Discrete(ts)) +function StateSpace(A::MT, B::MT, C::MT, D::MT, Ts::Number) where {T, MT <: AbstractMatrix{T}} + StateSpace(A, B, C, D, Discrete(Ts)) end # Constructor for Continuous system function StateSpace(A::MT, B::MT, C::MT, D::MT) where {T, MT <: AbstractMatrix{T}} @@ -89,8 +89,8 @@ function StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNum return StateSpace{typeof(time),T,Matrix{T}}(A, B, C, D, time) end # General Discrete constructor -StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, ts::Number) = - StateSpace(A, B, C, D, Discrete(ts)) +StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::Number) = + StateSpace(A, B, C, D, Discrete(Ts)) # General continuous constructor StateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray) = StateSpace(A, B, C, D, Continuous()) @@ -104,10 +104,10 @@ function StateSpace(D::AbstractArray{T}, time::TimeType) where {T<:Number} D = reshape(D, (ny,nu)) return StateSpace(A, B, C, D, time) end -StateSpace(D::AbstractArray, ts::Number) = StateSpace(D, Discrete(ts)) +StateSpace(D::AbstractArray, Ts::Number) = StateSpace(D, Discrete(Ts)) StateSpace(D::AbstractArray) = StateSpace(D, Continuous()) -StateSpace(d::Number, ts::Number; kwargs...) = StateSpace([d], Discrete(ts)) +StateSpace(d::Number, Ts::Number; kwargs...) = StateSpace([d], Discrete(Ts)) StateSpace(d::Number; kwargs...) = StateSpace([d], Continuous()) @@ -115,15 +115,15 @@ StateSpace(d::Number; kwargs...) = StateSpace([d], Continuous()) StateSpace(sys::LTISystem) = convert(StateSpace, sys) """ - `sys = ss(A, B, C, D [,ts])` + `sys = ss(A, B, C, D [,Ts])` Create a state-space model `sys::StateSpace{TimeT, T, MT<:AbstractMatrix{T}}` where `MT` is the type of matrixes `A,B,C,D`, `T` the element type and TimeT is Continuous or Discrete. -This is a continuous-time model if `ts` is omitted. +This is a continuous-time model if `Ts` is omitted. Otherwise, this is a discrete-time model with sampling period Ts. -`sys = ss(D [, ts])` specifies a static gain matrix D. +`sys = ss(D [, Ts])` specifies a static gain matrix D. """ ss(args...;kwargs...) = StateSpace(args...;kwargs...) @@ -165,8 +165,8 @@ function HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::Abstr return HeteroStateSpace{typeof(time),typeof(A),typeof(B),typeof(C),typeof(D)}(A, B, C, D, time) end -HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, ts::Number) = - HeteroStateSpace(A, B, C, D, Discrete(ts)) +HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray, Ts::Number) = + HeteroStateSpace(A, B, C, D, Discrete(Ts)) HeteroStateSpace(A::AbstractNumOrArray, B::AbstractNumOrArray, C::AbstractNumOrArray, D::AbstractNumOrArray) = HeteroStateSpace(A, B, C, D, Continuous()) @@ -181,10 +181,10 @@ function HeteroStateSpace(D::AbstractArray{T}, time::TimeType) where {T<:Number} return HeteroStateSpace(A, B, C, D, time) end -HeteroStateSpace(D::AbstractArray{T}, ts::Number) where {T<:Number} = HeteroStateSpace(D, Discrete(ts)) +HeteroStateSpace(D::AbstractArray{T}, Ts::Number) where {T<:Number} = HeteroStateSpace(D, Discrete(Ts)) HeteroStateSpace(D::AbstractArray{T}) where {T<:Number} = HeteroStateSpace(D, Continuous()) -HeteroStateSpace(d::Number, ts::Number; kwargs...) = HeteroStateSpace([d], Discrete(ts)) +HeteroStateSpace(d::Number, Ts::Number; kwargs...) = HeteroStateSpace([d], Discrete(Ts)) HeteroStateSpace(d::Number; kwargs...) = HeteroStateSpace([d], Continuous()) # HeteroStateSpace(sys) converts to HeteroStateSpace From bfa0f55b1cd4e1ed681a7cf1133f3fbedfe3a485 Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 09:17:32 +0200 Subject: [PATCH 13/18] Another typo. --- docs/src/man/creating_systems.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/man/creating_systems.md b/docs/src/man/creating_systems.md index e815e5dd6..cfd1a6783 100644 --- a/docs/src/man/creating_systems.md +++ b/docs/src/man/creating_systems.md @@ -83,7 +83,7 @@ import ControlSystems.HeteroStateSpace function HeteroStateSpace(A,B,C,D,Ts=0,f::F=to_static) where F HeteroStateSpace(f(A),f(B),f(C),f(D),Ts) end -@inline HeteroStateSpace(s,f) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.sampletime,f) +@inline HeteroStateSpace(s,f) = HeteroStateSpace(s.A,s.B,s.C,s.D,s.time,f) ControlSystems._string_mat_with_headers(a::SizedArray) = ControlSystems._string_mat_with_headers(Matrix(a)); # Overload for printing purposes ``` Notice the different matrix types used From e621da5b016ec72145a7ed9ffb0806b9587c7eb4 Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 09:35:59 +0200 Subject: [PATCH 14/18] Another fix. --- src/plotting.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plotting.jl b/src/plotting.jl index f5edebd50..fc8a84ae1 100644 --- a/src/plotting.jl +++ b/src/plotting.jl @@ -471,7 +471,7 @@ nicholsplot ny, nu = size(systems[1]) if isdiscrete(systems[1]) - w_nyquist = 2π/time(systems[1]) + w_nyquist = 2π/systems[1].Ts w = w[w.<= w_nyquist] end nw = length(w) From 3648a17e241d49ac6738f97d2c577fb83ea37a5d Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 11:34:29 +0200 Subject: [PATCH 15/18] Another typo. --- src/types/TransferFunction.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/TransferFunction.jl b/src/types/TransferFunction.jl index 7e20ef17b..5b6ddb3b3 100644 --- a/src/types/TransferFunction.jl +++ b/src/types/TransferFunction.jl @@ -183,7 +183,7 @@ function Base.show(io::IO, G::TransferFunction) if iscontinuous(G) print(io, "\nContinuous-time transfer function model") elseif isdiscrete(G) - print(io, "\nSample Time: ", time(G), " (seconds)") + print(io, "\nSample Time: ", G.Ts, " (seconds)") print(io, "\nDiscrete-time transfer function model") else print(io, "\nStatic gain transfer function model") From afcb42778782a322898eb9e62ea8416103481791 Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 14:07:40 +0200 Subject: [PATCH 16/18] Updates, including propertynames for Ts. --- src/ControlSystems.jl | 1 - src/types/Lti.jl | 16 +++++++++++++--- test/test_delayed_systems.jl | 3 +++ test/test_partitioned_statespace.jl | 2 ++ test/test_statespace.jl | 6 ++++++ test/test_transferfunction.jl | 8 ++++++++ 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index 95d905766..3f791a284 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -87,7 +87,6 @@ export LTISystem, denvec, numpoly, denpoly, - time, iscontinuous, isdiscrete, isstatic diff --git a/src/types/Lti.jl b/src/types/Lti.jl index 23883282e..ce497f6d2 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -55,17 +55,27 @@ isdiscrete(sys::LTISystem) = time(sys) isa Discrete function Base.getproperty(sys::LTISystem, s::Symbol) if s === :Ts # if !isdiscrete(sys) # NOTE this line seems to be breaking inference of isdiscrete (is there a test for this?) - if !isdiscrete(sys) + if isdiscrete(sys) + return time(sys).Ts + else @warn "Getting time 0.0 for non-discrete systems is deprecated. Check `isdiscrete` before trying to access time." return 0.0 - else - return time(sys).Ts end else return getfield(sys, s) end end +function Base.propertynames(sys::LTISystem, private::Bool=false) + names = if private + fieldnames(typeof(sys)) + else + filter(!=(:time), fieldnames(typeof(sys))) + end + + (names..., (isdiscrete(sys) ? (:Ts,) : ())...) +end + """`timetype(sys)` Get the timetype of system. Usually typeof(sys.time).""" diff --git a/test/test_delayed_systems.jl b/test/test_delayed_systems.jl index 2be92062e..2921ba8c5 100644 --- a/test/test_delayed_systems.jl +++ b/test/test_delayed_systems.jl @@ -132,6 +132,9 @@ w = 10 .^ (-2:0.1:2) @test freqresp(s11, w) ≈ freqresp(f2[1,1], w) rtol=1e-15 +@test propertynames(delay(1.0)) == (:P, :Tau) + + #FIXME: A lot more tests, including MIMO systems in particular # Test step diff --git a/test/test_partitioned_statespace.jl b/test/test_partitioned_statespace.jl index 03dd68a1c..ee771ae7d 100644 --- a/test/test_partitioned_statespace.jl +++ b/test/test_partitioned_statespace.jl @@ -38,3 +38,5 @@ sys2 = ControlSystems.PartionedStateSpace(ss(fill(1.0, 2, 2), fill(2.0, 2, 5), f # TODO: Add some tests for interconnections, implicitly tested through delay system implementations though @test (sys1 + sys1).P[1, 1] == (sys1.P[1,1] + sys1.P[1,1]) + +@test propertynames(sys1) == (:P, :nu1, :ny1) diff --git a/test/test_statespace.jl b/test/test_statespace.jl index c690ed756..fa7f62438 100644 --- a/test/test_statespace.jl +++ b/test/test_statespace.jl @@ -98,6 +98,12 @@ @test -sys == SS(A, B, -C, -D) + # Accessing Ts through .Ts + @test D_111.Ts == 0.005 + + # propertynames + propertynames(C_111) = (:A, :B, :C, :D, :nx, :nu, :ny, :Ts) + propertynames(D_111) = (:A, :B, :C, :D, :nx, :nu, :ny) # Printing if SS <: StateSpace diff --git a/test/test_transferfunction.jl b/test/test_transferfunction.jl index 8741bfb7c..4f1a8da11 100644 --- a/test/test_transferfunction.jl +++ b/test/test_transferfunction.jl @@ -90,6 +90,14 @@ tf(vecarray(1, 2, [0], [0]), vecarray(1, 2, [1], [1]), 0.005) @test C_222[1,1:2] == C_221 @test size(C_222[1,[]]) == (1,0) +# Accessing Ts through .Ts +@test D_111.Ts == 0.005 + +# propertynames +@test propertynames(C_111) == (:matrix, :nu, :ny) +@test propertynames(D_111) == (:matrix, :nu, :ny, :Ts) + + # Errors @test_throws ErrorException tf(vecarray(1, 1, [1,7,13,15]), vecarray(2, 1, [1,10,31,30], [1,10,31,30])) From d523d820778f53a5f2b5bd09aa9fda811261ccd1 Mon Sep 17 00:00:00 2001 From: olof3 Date: Thu, 16 Apr 2020 15:05:05 +0200 Subject: [PATCH 17/18] Minor fix. --- src/ControlSystems.jl | 3 +-- src/freqresp.jl | 2 +- src/timeresp.jl | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index 3f791a284..f8e4ca374 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -88,8 +88,7 @@ export LTISystem, numpoly, denpoly, iscontinuous, - isdiscrete, - isstatic + isdiscrete # QUESTION: are these used? LaTeXStrings, Requires, IterTools diff --git a/src/freqresp.jl b/src/freqresp.jl index ab14dfb0f..fad4c63d7 100644 --- a/src/freqresp.jl +++ b/src/freqresp.jl @@ -168,7 +168,7 @@ function _bounds_and_features(sys::LTISystem, plot::Val) w1 = 0.0 w2 = 2.0 end - if !iscontinuous(sys) && !isstatic(sys) # Do not draw above Nyquist freq for disc. systems + if isdiscrete(sys) # Do not draw above Nyquist freq for disc. systems w2 = min(w2, log10(π/sys.Ts)) end return [w1, w2], zp diff --git a/src/timeresp.jl b/src/timeresp.jl index 10b46209d..38f08740f 100644 --- a/src/timeresp.jl +++ b/src/timeresp.jl @@ -43,7 +43,7 @@ function impulse(sys::StateSpace, t::AbstractVector; method=:cont) lt = length(t) ny, nu = size(sys) nx = sys.nx - if iscontinuous(sys) || isstatic(sys) #&& method == :cont + if iscontinuous(sys) #&& method == :cont u = (x,i) -> [zero(T)] # impulse response equivalent to unforced response of # ss(A, 0, C, 0) with x0 = B. From 3690cc8c8af6be77b0a0f35b0de9504f1fff1b17 Mon Sep 17 00:00:00 2001 From: olof3 Date: Fri, 17 Apr 2020 12:39:55 +0200 Subject: [PATCH 18/18] Added a few suggestions from mfalts branch. --- src/ControlSystems.jl | 3 ++- src/analysis.jl | 2 +- src/timeresp.jl | 7 +++---- src/types/Lti.jl | 16 +++++----------- src/types/PartionedStateSpace.jl | 2 -- src/types/StateSpace.jl | 2 -- src/types/TransferFunction.jl | 1 - test/test_connections.jl | 4 ++++ test/test_statespace.jl | 4 ++-- test/test_transferfunction.jl | 4 ++-- 10 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/ControlSystems.jl b/src/ControlSystems.jl index f8e4ca374..95c9f2b0f 100644 --- a/src/ControlSystems.jl +++ b/src/ControlSystems.jl @@ -109,7 +109,8 @@ abstract type AbstractSystem end include("types/TimeType.jl") ## Added interface: -# time(Lti) -> Number +# time(Lti) -> TimeType (not exported) + include("types/Lti.jl") diff --git a/src/analysis.jl b/src/analysis.jl index 3785d05e2..299c62385 100644 --- a/src/analysis.jl +++ b/src/analysis.jl @@ -118,7 +118,7 @@ Compute the zeros, poles, and gains of system `sys`. `k` : Matrix{Float64}, (ny x nu)""" function zpkdata(sys::LTISystem) - G = convert(TransferFunction{timetype(sys),SisoZpk}, sys) + G = convert(TransferFunction{typeof(time(sys)),SisoZpk}, sys) zs = map(x -> x.z, G.matrix) ps = map(x -> x.p, G.matrix) diff --git a/src/timeresp.jl b/src/timeresp.jl index 38f08740f..d3db3f2c6 100644 --- a/src/timeresp.jl +++ b/src/timeresp.jl @@ -227,18 +227,17 @@ end function _default_dt(sys::LTISystem) if isdiscrete(sys) - dt = sys.Ts + return sys.Ts elseif !isstable(sys) - dt = 0.05 + return 0.05 else ps = pole(sys) r = minimum([abs.(real.(ps));0]) if r == 0.0 r = 1.0 end - dt = 0.07/r + return 0.07/r end - return dt end diff --git a/src/types/Lti.jl b/src/types/Lti.jl index ce497f6d2..477cc6cec 100644 --- a/src/types/Lti.jl +++ b/src/types/Lti.jl @@ -41,6 +41,9 @@ function issiso(sys::LTISystem) return ninputs(sys) == 1 && noutputs(sys) == 1 end +"""`time(sys::LTISystem)` +Get the time::TimeType from system `sys`, usually sys.time """ +time(sys::LTISystem) = sys.time """`iscontinuous(sys)` @@ -66,20 +69,11 @@ function Base.getproperty(sys::LTISystem, s::Symbol) end end -function Base.propertynames(sys::LTISystem, private::Bool=false) - names = if private - fieldnames(typeof(sys)) - else - filter(!=(:time), fieldnames(typeof(sys))) - end +Base.propertynames(sys::LTISystem, private::Bool=false) = + (fieldnames(typeof(sys))..., (isdiscrete(sys) ? (:Ts,) : ())...) - (names..., (isdiscrete(sys) ? (:Ts,) : ())...) -end -"""`timetype(sys)` -Get the timetype of system. Usually typeof(sys.time).""" -timetype(sys) = typeof(sys.time) common_time(systems::LTISystem...) = common_time(time(sys) for sys in systems) diff --git a/src/types/PartionedStateSpace.jl b/src/types/PartionedStateSpace.jl index 5cf413393..ee267f2f2 100644 --- a/src/types/PartionedStateSpace.jl +++ b/src/types/PartionedStateSpace.jl @@ -27,8 +27,6 @@ function getproperty(sys::PartionedStateSpace, d::Symbol) if d === :Ts return P.Ts # Will throw deprecation until removed # DEPRECATED - elseif d === :time - return P.time elseif d === :P return P elseif d === :nu1 diff --git a/src/types/StateSpace.jl b/src/types/StateSpace.jl index f5b9b4cb0..4bb63449f 100644 --- a/src/types/StateSpace.jl +++ b/src/types/StateSpace.jl @@ -24,8 +24,6 @@ end abstract type AbstractStateSpace{TimeT<:TimeType} <: LTISystem end -time(sys::AbstractStateSpace) = sys.time - struct StateSpace{TimeT, T, MT<:AbstractMatrix{T}} <: AbstractStateSpace{TimeT} A::MT B::MT diff --git a/src/types/TransferFunction.jl b/src/types/TransferFunction.jl index 5b6ddb3b3..5967cdd33 100644 --- a/src/types/TransferFunction.jl +++ b/src/types/TransferFunction.jl @@ -22,7 +22,6 @@ end # return TransferFunction(matrix, Continuous()) # end -time(G) = G.time noutputs(G::TransferFunction) = size(G.matrix, 1) ninputs(G::TransferFunction) = size(G.matrix, 2) diff --git a/test/test_connections.jl b/test/test_connections.jl index fe1290a81..6bd7fe646 100644 --- a/test/test_connections.jl +++ b/test/test_connections.jl @@ -161,6 +161,10 @@ arr4[1] = ss(0); arr4[2] = ss(1); arr4[3] = ss(2) @test [C_111 1.0] == ss([1.0], [2.0 0.0], [3.0], [4.0 1.0]) @test [1.0 C_111] == ss([1.0], [0.0 2.0], [3.0], [1.0 4.0]) @test [C_111 1.0] isa StateSpace{Continuous,Float64,Array{Float64,2}} +@test [C_111 1.0].Ts == 0.0 +@test_logs (:warn, + "Getting time 0.0 for non-discrete systems is deprecated. Check `isdiscrete` before trying to access time." + ) [C_111 1.0].Ts # Concatenation of discrete system with matrix @test [D_222 fill(1.5, 2, 2)] == [D_222 ss(fill(1.5, 2, 2),0.005)] @test [C_222 fill(1.5, 2, 2)] == [C_222 ss(fill(1.5, 2, 2))] diff --git a/test/test_statespace.jl b/test/test_statespace.jl index fa7f62438..584882892 100644 --- a/test/test_statespace.jl +++ b/test/test_statespace.jl @@ -102,8 +102,8 @@ @test D_111.Ts == 0.005 # propertynames - propertynames(C_111) = (:A, :B, :C, :D, :nx, :nu, :ny, :Ts) - propertynames(D_111) = (:A, :B, :C, :D, :nx, :nu, :ny) + propertynames(C_111) = (:A, :B, :C, :D, :time, :nx, :nu, :ny, :Ts) + propertynames(D_111) = (:A, :B, :C, :D, :time, :nx, :nu, :ny) # Printing if SS <: StateSpace diff --git a/test/test_transferfunction.jl b/test/test_transferfunction.jl index 4f1a8da11..84d103198 100644 --- a/test/test_transferfunction.jl +++ b/test/test_transferfunction.jl @@ -94,8 +94,8 @@ tf(vecarray(1, 2, [0], [0]), vecarray(1, 2, [1], [1]), 0.005) @test D_111.Ts == 0.005 # propertynames -@test propertynames(C_111) == (:matrix, :nu, :ny) -@test propertynames(D_111) == (:matrix, :nu, :ny, :Ts) +@test propertynames(C_111) == (:matrix, :time, :nu, :ny) +@test propertynames(D_111) == (:matrix, :time, :nu, :ny, :Ts) # Errors