Skip to content

Commit 5c00751

Browse files
authored
Merge branch 'dev' into simresult
2 parents b59bb36 + 2cb8d86 commit 5c00751

20 files changed

+128
-93
lines changed

src/analysis.jl

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,17 @@ function det(sys::Matrix{S}) where {S<:SisoZpk}
8989
end
9090

9191
"""
92-
dcgain(sys)
92+
dcgain(sys, ϵ=0)
9393
9494
Compute the dcgain of system `sys`.
9595
96-
equal to G(0) for continuous-time systems and G(1) for discrete-time systems."""
97-
function dcgain(sys::LTISystem)
98-
return iscontinuous(sys) ? evalfr(sys, 0) : evalfr(sys, 1)
96+
equal to G(0) for continuous-time systems and G(1) for discrete-time systems.
97+
98+
`ϵ` can be provided to evaluate the dcgain with a small perturbation into
99+
the stability region of the complex plane.
100+
"""
101+
function dcgain(sys::LTISystem, ϵ=0)
102+
return iscontinuous(sys) ? evalfr(sys, -ϵ) : evalfr(sys, exp(-ϵ*sys.Ts))
99103
end
100104

101105
"""
@@ -141,7 +145,7 @@ poles, `ps`, of `sys`"""
141145
function damp(sys::LTISystem)
142146
ps = pole(sys)
143147
if isdiscrete(sys)
144-
ps = log.(ps)/sys.Ts
148+
ps = log.(complex.(ps))/sys.Ts
145149
end
146150
Wn = abs.(ps)
147151
order = sortperm(Wn; by=z->(abs(z), real(z), imag(z)))
@@ -160,20 +164,29 @@ function dampreport(io::IO, sys::LTISystem)
160164
Wn, zeta, ps = damp(sys)
161165
t_const = 1 ./ (Wn.*zeta)
162166
header =
163-
("| Pole | Damping | Frequency | Frequency | Time Constant |\n"*
164-
"| | Ratio | (rad/sec) | (Hz) | (sec) |\n"*
165-
"+---------------+---------------+---------------+---------------+---------------+")
167+
("| Pole | Damping | Frequency | Frequency | Time Constant |\n"*
168+
"| | Ratio | (rad/sec) | (Hz) | (sec) |\n"*
169+
"+--------------------+---------------+---------------+---------------+---------------+")
166170
println(io, header)
167171
if all(isreal, ps)
168172
for i=eachindex(ps)
169173
p, z, w, t = ps[i], zeta[i], Wn[i], t_const[i]
170-
Printf.@printf(io, "| %-13.3e| %-13.3e| %-13.3e| %-13.3e| %-13.3e|\n", real(p), z, w, w/(2π), t)
174+
Printf.@printf(io, "| %-+18.3g | %-13.3g| %-13.3g| %-13.3g| %-13.3g|\n", real(p), z, w, w/(2π), t)
171175
end
172-
else
176+
elseif numeric_type(sys) <: Real # real-coeff system with complex conj. poles
177+
for i=eachindex(ps)
178+
p, z, w, t = ps[i], zeta[i], Wn[i], t_const[i]
179+
imag(p) < 0 && (continue) # use only the positive complex pole to print with the ± operator
180+
if imag(p) == 0 # no ± operator for real pole
181+
Printf.@printf(io, "| %-+18.3g | %-13.3g| %-13.3g| %-13.3g| %-13.3g|\n", real(p), z, w, w/(2π), t)
182+
else
183+
Printf.@printf(io, "| %-+7.3g ± %6.3gim | %-13.3g| %-13.3g| %-13.3g| %-13.3g|\n", real(p), imag(p), z, w, w/(2π), t)
184+
end
185+
end
186+
else # complex-coeff system
173187
for i=eachindex(ps)
174188
p, z, w, t = ps[i], zeta[i], Wn[i], t_const[i]
175-
Printf.@printf(io, "| %-13.3e| %-13.3e| %-13.3e| %-13.3e| %-13.3e|\n", real(p), z, w, w/(2π), t)
176-
Printf.@printf(io, "| %-+11.3eim| | | | |\n", imag(p))
189+
Printf.@printf(io, "| %-+7.3g %+7.3gim | %-13.3g| %-13.3g| %-13.3g| %-13.3g|\n", real(p), imag(p), z, w, w/(2π), t)
177190
end
178191
end
179192
end
@@ -521,7 +534,7 @@ function gangofseven(P::TransferFunction, C::TransferFunction, F::TransferFuncti
521534
end
522535
S, PS, CS, T = gangoffour(P,C)
523536
RY = T*F
524-
RU = N*F
537+
RU = CS*F
525538
RE = S*F
526539
return S, PS, CS, T, RY, RU, RE
527540
end

src/connections.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ feedback2dof(B,A,R,S,T) = tf(conv(B,T),zpconv(A,R,B,S))
347347
Return the transfer function
348348
`P(F+C)/(1+PC)`
349349
which is the closed-loop system with process `P`, controller `C` and feedforward filter `F` from reference to control signal (by-passing `C`).
350-
350+
```
351351
+-------+
352352
| |
353353
+-----> F +----+
@@ -360,6 +360,7 @@ r | - | | | | | y
360360
| +-------+ +-------+ |
361361
| |
362362
+--------------------------------+
363+
```
363364
"""
364365
function feedback2dof(P::TransferFunction{TE}, C::TransferFunction{TE}, F::TransferFunction{TE}) where TE
365366
!issiso(P) && error("Feedback not implemented for MIMO systems")

src/delay_systems.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ function _lsim(sys::DelayLtiSystem{T,S}, Base.@nospecialize(u!), t::AbstractArra
180180
p = (A, B1, B2, C1, C2, D11, D12, D21, D22, y, Tau, u!, uout, hout, tmpy, t)
181181

182182
# This callback computes and stores the delay term
183-
sv = SavedValues(Float64, Tuple{Vector{Float64},Vector{Float64}})
183+
sv = SavedValues(eltype(t), Tuple{Vector{T},Vector{T}})
184184
cb = SavingCallback(dde_saver, sv, saveat = t)
185185

186186
# History function, only used for d

src/freqresp.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function evalfr(sys::AbstractStateSpace, s::Number)
5757
R = s*I - sys.A
5858
sys.D + sys.C*((R\sys.B))
5959
catch e
60-
@warn "Got exception $e, returning Inf" max_log=1
60+
@warn "Got exception $e, returning Inf" maxlog=1
6161
fill(convert(T, Inf), size(sys))
6262
end
6363
end

src/matrix_comps.jl

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,9 @@ function covar(sys::AbstractStateSpace, W)
168168
func = iscontinuous(sys) ? lyap : dlyap
169169
Q = try
170170
func(A, B*W*B')
171-
catch
172-
error("No solution to the Lyapunov equation was found in covar")
171+
catch e
172+
@error("No solution to the Lyapunov equation was found in covar.")
173+
rethrow(e)
173174
end
174175
P = C*Q*C'
175176
if !isdiscrete(sys)
@@ -493,9 +494,9 @@ end
493494

494495

495496
"""
496-
`sysr, G = balreal(sys::StateSpace)`
497+
`sysr, G, T = balreal(sys::StateSpace)`
497498
498-
Calculates a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal `G`
499+
Calculates a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal `G`. `T` is the similarity transform between the old state `x` and the new state `z` such that `Tz = x`.
499500
500501
See also `gram`, `baltrunc`
501502
@@ -531,23 +532,25 @@ function balreal(sys::ST) where ST <: AbstractStateSpace
531532
display(Σ)
532533
end
533534

534-
sysr = ST(T*sys.A/T, T*sys.B, sys.C/T, sys.D, sys.timeevol), diagm(0 => Σ)
535+
sysr = ST(T*sys.A/T, T*sys.B, sys.C/T, sys.D, sys.timeevol), diagm(Σ), T
535536
end
536537

537538

538539
"""
539-
sysr, G = baltrunc(sys::StateSpace; atol = √ϵ, rtol=1e-3, unitgain=true, n = nothing)
540+
sysr, G, T = baltrunc(sys::StateSpace; atol = √ϵ, rtol=1e-3, unitgain=true, n = nothing)
540541
541542
Reduces the state dimension by calculating a balanced realization of the system sys, such that the observability and reachability gramians of the balanced system are equal and diagonal `G`, and truncating it to order `n`. If `n` is not provided, it's chosen such that all states corresponding to singular values less than `atol` and less that `rtol σmax` are removed.
542543
544+
`T` is the similarity transform between the old state `x` and the newstate `z` such that `Tz = x`.
545+
543546
If `unitgain=true`, the matrix `D` is chosen such that unit static gain is achieved.
544547
545548
See also `gram`, `balreal`
546549
547550
Glad, Ljung, Reglerteori: Flervariabla och Olinjära metoder
548551
"""
549552
function baltrunc(sys::ST; atol = sqrt(eps()), rtol = 1e-3, unitgain = true, n = nothing) where ST <: AbstractStateSpace
550-
sysbal, S = balreal(sys)
553+
sysbal, S, T = balreal(sys)
551554
S = diag(S)
552555
if n === nothing
553556
S = S[S .>= atol]
@@ -564,7 +567,7 @@ function baltrunc(sys::ST; atol = sqrt(eps()), rtol = 1e-3, unitgain = true, n =
564567
D = D/(C*inv(-A)*B)
565568
end
566569

567-
return ST(A,B,C,D,sys.timeevol), diagm(0 => S)
570+
return ST(A,B,C,D,sys.timeevol), diagm(S), T
568571
end
569572

570573
"""

src/plotting.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -538,21 +538,24 @@ end
538538

539539
@userplot Sigmaplot
540540
"""
541-
sigmaplot(sys, args...)
542-
sigmaplot(LTISystem[sys1, sys2...], args...)
541+
sigmaplot(sys, args...; hz=false)
542+
sigmaplot(LTISystem[sys1, sys2...], args...; hz=false)
543543
544544
Plot the singular values of the frequency response of the `LTISystem`(s). A
545545
frequency vector `w` can be optionally provided.
546546
547+
If `hz=true`, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
548+
547549
`kwargs` is sent as argument to Plots.plot.
548550
"""
549551
sigmaplot
550-
@recipe function sigmaplot(p::Sigmaplot)
552+
@recipe function sigmaplot(p::Sigmaplot; hz=false)
551553
systems, w = _processfreqplot(Val{:sigma}(), p.args...)
554+
ws = (hz ? 1/(2π) : 1) .* w
552555
ny, nu = size(systems[1])
553556
nw = length(w)
554557
title --> "Sigma Plot"
555-
xguide --> "Frequency (rad/s)",
558+
xguide --> (hz ? "Frequency [Hz]" : "Frequency [rad/s]")
556559
yguide --> "Singular Values $_PlotScaleStr"
557560
for (si, s) in enumerate(systems)
558561
sv = sigma(s, w)[1]
@@ -564,7 +567,7 @@ sigmaplot
564567
xscale --> :log10
565568
yscale --> _PlotScaleFunc
566569
seriescolor --> si
567-
w, sv[:, i]
570+
ws, sv[:, i]
568571
end
569572
end
570573
end
@@ -666,9 +669,6 @@ Create a pole-zero map of the `LTISystem`(s) in figure `fig`, `args` and `kwargs
666669
pzmap
667670
@recipe function pzmap(p::Pzmap)
668671
systems = p.args[1]
669-
if systems[1].nu + systems[1].ny > 2
670-
@warn("pzmap currently only supports SISO systems. Only transfer function from u₁ to y₁ will be shown")
671-
end
672672
seriestype := :scatter
673673
framestyle --> :zerolines
674674
title --> "Pole-zero map"

src/simplification.jl

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,24 @@
44
Compute the structurally minimal realization of the state-space system `sys`. A
55
structurally minimal realization is one where only states that can be
66
determined to be uncontrollable and unobservable based on the location of 0s in
7-
`sys` are removed."""
7+
`sys` are removed.
8+
9+
Systems with numerical noise in the coefficients, e.g., noise on the order of `eps` require truncation to zero to be
10+
affected by structural simplification, e.g.,
11+
```julia
12+
trunc_zero!(A) = A[abs.(A) .< 10eps(maximum(abs, A))] .= 0
13+
trunc_zero!(sys.A); trunc_zero!(sys.B); trunc_zero!(sys.C)
14+
sminreal(sys)
15+
```
16+
"""
817
function sminreal(sys::StateSpace)
918
A, B, C, inds = struct_ctrb_obsv(sys)
1019
return StateSpace(A, B, C, sys.D, sys.timeevol)
1120
end
1221

1322
# Determine the structurally controllable and observable realization for the system
1423
struct_ctrb_obsv(sys::StateSpace) = struct_ctrb_obsv(sys.A, sys.B, sys.C)
24+
1525
function struct_ctrb_obsv(A::AbstractVecOrMat, B::AbstractVecOrMat, C::AbstractVecOrMat)
1626
costates = struct_ctrb_states(A, B) .& struct_ctrb_states(A', C')
1727
if !all(costates)
@@ -22,15 +32,17 @@ function struct_ctrb_obsv(A::AbstractVecOrMat, B::AbstractVecOrMat, C::AbstractV
2232
end
2333
end
2434

25-
# Compute a bit-vector, expressing whether a state of the pair (A, B) is
26-
# structurally controllable.
35+
"""
36+
struct_ctrb_states(A::AbstractVecOrMat, B::AbstractVecOrMat)
37+
38+
Compute a bit-vector, expressing whether a state of the pair (A, B) is
39+
structurally controllable based on the location of zeros in the matrices.
40+
"""
2741
function struct_ctrb_states(A::AbstractVecOrMat, B::AbstractVecOrMat)
28-
bitA = A .!= 0
29-
d_cvec = cvec = vec(any(B .!= 0, dims=2))
30-
while any(d_cvec .!= 0)
31-
Adcvec = vec(any(bitA[:, findall(d_cvec)], dims=2))
32-
cvec = cvec .| Adcvec
33-
d_cvec = Adcvec .& .~cvec
42+
bitA = .!iszero.(A)
43+
x = vec(any(B .!= 0, dims=2)) # index vector indicating states that have been affected by input
44+
for i = 1:size(A, 1) # apply A nx times, similar to controllability matrix
45+
x = x .| .!iszero.(bitA * x)
3446
end
35-
return cvec
47+
x
3648
end

src/timeresp.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function Base.step(sys::AbstractStateSpace, t::AbstractVector; method=:cont, kwa
1717
nx = nstates(sys)
1818
u_element = [one(eltype(t))] # to avoid allocating this multiple times
1919
u = (x,t)->u_element
20-
x0 = zeros(nx)
20+
x0 = zeros(T, nx)
2121
if nu == 1
2222
y, tout, x, uout = lsim(sys, u, t; x0, method, kwargs...)
2323
else

src/types/DelayLtiSystem.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ function Base.show(io::IO, sys::DelayLtiSystem)
160160
print(io, "\nP: ")
161161
show(io, sys.P.P)
162162

163-
println(io, "\n\nDelays: $(sys.Tau)")
163+
print(io, "\n\nDelays: $(sys.Tau)")
164164
end
165165

166166

src/types/StateSpace.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ function isapprox(sys1::ST1, sys2::ST2; kwargs...) where {ST1<:AbstractStateSpac
217217
end
218218

219219
## ADDITION ##
220+
Base.zero(sys::AbstractStateSpace) = ss(zero(sys.D), sys.timeevol)
221+
220222
function +(s1::StateSpace{TE,T}, s2::StateSpace{TE,T}) where {TE,T}
221223
#Ensure systems have same dimensions
222224
if size(s1) != size(s2)

0 commit comments

Comments
 (0)