Skip to content

Commit 1b44cd2

Browse files
committed
use external package for matrix equations
1 parent 4a6d813 commit 1b44cd2

6 files changed

+99
-91
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ ss, tf, zpk
6666
##### Analysis
6767
pole, tzero, norm, hinfnorm, linfnorm, ctrb, obsv, gangoffour, margin, markovparam, damp, dampreport, zpkdata, dcgain, covar, gram, sigma, sisomargin
6868
##### Synthesis
69-
care, dare, dlyap, lqr, dlqr, place, leadlink, laglink, leadlinkat, rstd, rstc, dab, balreal, baltrunc
69+
arec, ared, lyapc, lyapd, lqr, dlqr, place, leadlink, laglink, leadlinkat, rstd, rstc, dab, balreal, baltrunc
7070
###### PID design
7171
pid, stabregionPID, loopshapingPI, pidplots
7272
##### Time and Frequency response

src/ControlSystems.jl

+9-1
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,15 @@ import Base: +, -, *, /, (==), (!=), isapprox, convert, promote_op
103103
import Base: getproperty, getindex
104104
import Base: exp # for exp(-s)
105105
import LinearAlgebra: BlasFloat
106-
export lyap # Make sure LinearAlgebra.lyap is available
106+
import ControlMatrixEquations: ared, arec, lyapc, lyapd
107+
export lyapd, lyapc
108+
export ared, arec
107109
import Printf, Colors
108110
import DSP: conv
109111
using MacroTools
110112

113+
114+
111115
abstract type AbstractSystem end
112116

113117
include("types/TimeEvolution.jl")
@@ -169,6 +173,10 @@ include("plotting.jl")
169173
@deprecate norminf hinfnorm
170174
@deprecate diagonalize(s::AbstractStateSpace, digits) diagonalize(s::AbstractStateSpace)
171175

176+
@deprecate dlyap lyapd
177+
@deprecate dare ared
178+
@deprecate care arec
179+
172180
function covar(D::Union{AbstractMatrix,UniformScaling}, R)
173181
@warn "This call is deprecated due to ambiguity, use covar(ss(D), R) or covar(ss(D, Ts), R) instead"
174182
D*R*D'

src/matrix_comps.jl

+61-71
Original file line numberDiff line numberDiff line change
@@ -7,75 +7,65 @@ Algorithm taken from:
77
Laub, "A Schur Method for Solving Algebraic Riccati Equations."
88
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
99
"""
10-
function care(A, B, Q, R)
11-
G = try
12-
B*inv(R)*B'
13-
catch y
14-
if y isa SingularException
15-
error("R must be non-singular in care.")
16-
else
17-
throw(y)
18-
end
19-
end
20-
21-
Z = [A -G;
22-
-Q -A']
23-
24-
S = schur(Z)
25-
S = ordschur(S, real(S.values).<0)
26-
U = S.Z
27-
28-
(m, n) = size(U)
29-
U11 = U[1:div(m, 2), 1:div(n,2)]
30-
U21 = U[div(m,2)+1:m, 1:div(n,2)]
31-
return U21/U11
32-
end
33-
34-
"""`dare(A, B, Q, R)`
35-
36-
Compute `X`, the solution to the discrete-time algebraic Riccati equation,
37-
defined as A'XA - X - (A'XB)(B'XB + R)^-1(B'XA) + Q = 0, where Q>=0 and R>0
38-
39-
Algorithm taken from:
40-
Laub, "A Schur Method for Solving Algebraic Riccati Equations."
41-
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
42-
"""
43-
function dare(A, B, Q, R)
44-
if !issemiposdef(Q)
45-
error("Q must be positive-semidefinite.");
46-
end
47-
if (!isposdef(R))
48-
error("R must be positive definite.");
49-
end
50-
51-
n = size(A, 1);
52-
53-
E = [
54-
Matrix{Float64}(I, n, n) B*(R\B');
55-
zeros(size(A)) A'
56-
];
57-
F = [
58-
A zeros(size(A));
59-
-Q Matrix{Float64}(I, n, n)
60-
];
61-
62-
QZ = schur(F, E);
63-
QZ = ordschur(QZ, abs.(QZ.alpha./QZ.beta) .< 1);
10+
# function care(A, B, Q, R)
11+
# G = try
12+
# B*inv(R)*B'
13+
# catch y
14+
# if y isa SingularException
15+
# error("R must be non-singular in care.")
16+
# else
17+
# throw(y)
18+
# end
19+
# end
20+
#
21+
# Z = [A -G;
22+
# -Q -A']
23+
#
24+
# S = schur(Z)
25+
# S = ordschur(S, real(S.values).<0)
26+
# U = S.Z
27+
#
28+
# (m, n) = size(U)
29+
# U11 = U[1:div(m, 2), 1:div(n,2)]
30+
# U21 = U[div(m,2)+1:m, 1:div(n,2)]
31+
# return U21/U11
32+
# end
33+
#
34+
# """`dare(A, B, Q, R)`
35+
#
36+
# Compute `X`, the solution to the discrete-time algebraic Riccati equation,
37+
# defined as A'XA - X - (A'XB)(B'XB + R)^-1(B'XA) + Q = 0, where Q>=0 and R>0
38+
#
39+
# Algorithm taken from:
40+
# Laub, "A Schur Method for Solving Algebraic Riccati Equations."
41+
# http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
42+
# """
43+
# function dare(A, B, Q, R)
44+
# if !ishermitian(Q)# !issemiposdef(Q)
45+
# error("Q must be Hermitian");
46+
# end
47+
# if (!isposdef(R))
48+
# error("R must be positive definite");
49+
# end
50+
#
51+
# n = size(A, 1);
52+
#
53+
# E = [
54+
# Matrix{Float64}(I, n, n) B*(R\B');
55+
# zeros(size(A)) A'
56+
# ];
57+
# F = [
58+
# A zeros(size(A));
59+
# -Q Matrix{Float64}(I, n, n)
60+
# ];
61+
#
62+
# QZ = schur(F, E);
63+
# QZ = ordschur(QZ, abs.(QZ.alpha./QZ.beta) .< 1);
64+
#
65+
# return QZ.Z[(n+1):end, 1:n]/QZ.Z[1:n, 1:n];
66+
# end
6467

65-
return QZ.Z[(n+1):end, 1:n]/QZ.Z[1:n, 1:n];
66-
end
67-
68-
"""`dlyap(A, Q)`
6968

70-
Compute the solution `X` to the discrete Lyapunov equation
71-
`AXA' - X + Q = 0`.
72-
"""
73-
function dlyap(A::AbstractMatrix, Q)
74-
lhs = kron(A, conj(A))
75-
lhs = I - lhs
76-
x = lhs\reshape(Q, prod(size(Q)), 1)
77-
return reshape(x, size(Q))
78-
end
7969

8070
"""`gram(sys, opt)`
8171
@@ -86,7 +76,7 @@ function gram(sys::AbstractStateSpace, opt::Symbol)
8676
if !isstable(sys)
8777
error("gram only valid for stable A")
8878
end
89-
func = iscontinuous(sys) ? lyap : dlyap
79+
func = iscontinuous(sys) ? lyapc : lyapd
9080
if opt === :c
9181
# TODO probably remove type check in julia 0.7.0
9282
return func(sys.A, sys.B*sys.B')#::Array{numeric_type(sys),2} # lyap is type-unstable
@@ -162,14 +152,14 @@ function covar(sys::AbstractStateSpace, W)
162152
if !isstable(sys)
163153
return fill(Inf,(size(C,1),size(C,1)))
164154
end
165-
func = iscontinuous(sys) ? lyap : dlyap
155+
166156
Q = try
167-
func(A, B*W*B')
157+
iscontinuous(sys) ? lyapc(A, B*W*B') : lyapd(A, B*W*B')
168158
catch
169159
error("No solution to the Lyapunov equation was found in covar")
170160
end
171161
P = C*Q*C'
172-
if !isdiscrete(sys)
162+
if iscontinuous(sys)
173163
#Variance and covariance infinite for direct terms
174164
direct_noise = D*W*D'
175165
for i in 1:size(C,1)

src/synthesis.jl

+2-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ plot(t,x, lab=["Position" "Velocity"], xlabel="Time [s]")
3434
```
3535
"""
3636
function lqr(A, B, Q, R)
37-
S = care(A, B, Q, R)
37+
S = care(A, B, Q, R)[1]
3838
K = R\B'*S
3939
return K
4040
end
@@ -99,7 +99,7 @@ plot(t,x, lab=["Position" "Velocity"], xlabel="Time [s]")
9999
```
100100
"""
101101
function dlqr(A, B, Q, R)
102-
S = dare(A, B, Q, R)
102+
S = dare(A, B, Q, R)[1]
103103
K = (B'*S*B + R)\(B'S*A)
104104
return K
105105
end

test/runtests.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ eye_(n) = Matrix{Int64}(I, n, n)
1111
my_tests = [
1212
"test_timeevol",
1313
"test_statespace",
14-
"test_transferfunction",
14+
"test_transferfunction",
1515
"test_zpk",
1616
"test_promotion",
1717
"test_connections",

test/test_linalg.jl

+25-15
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,34 @@ b = [0 1]'
44
c = [1 -1]
55
r = 3
66

7-
@test care(a, b, c'*c, r) [0.5895174372762604 1.8215747248860816; 1.8215747248860823 8.818839806923107]
8-
@test dare(a, b, c'*c, r) [240.73344504496302 -131.09928700089387; -131.0992870008943 75.93413176750603]
7+
@test care(a, b, c'*c, r)[1] [0.5895174372762604 1.8215747248860816; 1.8215747248860823 8.818839806923107]
8+
@test dare(a, b, c'*c, r)[1] [240.73344504496302 -131.09928700089387; -131.0992870008943 75.93413176750603]
99

1010
## Test dare method for non invertible matrices
11-
A = [0 1; 0 0];
12-
B0 = [0; 1];
13-
Q = Matrix{Float64}(I, 2, 2);
11+
A = [0 1; 0 0]
12+
B0 = [0; 1]
13+
Q = Matrix{Float64}(I, 2, 2)
1414
R0 = 1
15-
X = dare(A, B0, Q, R0);
15+
X = dare(A, B0, Q, R0)[1]
1616
# Reshape for matrix expression
1717
B = reshape(B0, 2, 1)
1818
R = fill(R0, 1, 1)
1919
@test norm(A'X*A - X - (A'X*B)*((B'X*B + R)\(B'X*A)) + Q) < 1e-14
2020
## Test dare for scalars
21-
A = 1.0;
22-
B = 1.0;
23-
Q = 1.0;
24-
R = 1.0;
25-
X0 = dare(A, B, Q, R);
21+
A = 1.0
22+
B = 1.0
23+
Q = 1.0
24+
R = 1.0
25+
X0 = dare(A, B, Q, R)[1]
2626
X = X0[1]
2727
@test norm(A'X*A - X - (A'X*B)*((B'X*B + R)\(B'X*A)) + Q) < 1e-14
2828
# Test for complex matrices
2929
I2 = Matrix{Float64}(I, 2, 2)
30-
@test dare([1.0 im; im 1.0], I2, I2, I2) [1 + sqrt(2) 0; 0 1 + sqrt(2)]
30+
@test dare([1.0 im; im 1.0], I2, I2, I2)[1] [1 + sqrt(2) 0; 0 1 + sqrt(2)]
3131
# And complex scalars
32-
@test dare(1.0, 1, 1, 1) fill((1 + sqrt(5))/2, 1, 1)
33-
@test dare(1.0im, 1, 1, 1) fill((1 + sqrt(5))/2, 1, 1)
34-
@test dare(1.0, 1im, 1, 1) fill((1 + sqrt(5))/2, 1, 1)
32+
@test dare(1.0, 1, 1, 1)[1] fill((1 + sqrt(5))/2, 1, 1)
33+
@test dare(1.0im, 1, 1, 1)[1] fill((1 + sqrt(5))/2, 1, 1)
34+
@test dare(1.0, 1im, 1, 1)[1] fill((1 + sqrt(5))/2, 1, 1)
3535

3636
## Test gram, ctrb, obsv
3737
a_2 = [-5 -3; 2 -9]
@@ -109,6 +109,16 @@ D_static = ss([0.704473 1.56483; -1.6661 -2.16041], 0.07)
109109
@test norm(D_221) 3.4490083195926426
110110
@test norm(ss([1],[2],[3],[4])) == Inf
111111

112+
113+
# Test discrete time 2 norms
114+
c = [1,2,-1,3,5,-2]
115+
@test norm(DemoSystems.ssfir(c)) == norm(c)
116+
c = 1:100
117+
@test norm(DemoSystems.ssfir(c)) == norm(c)
118+
c = 1:300 # typically enough to break a "naive" Lyapunov solver
119+
@test norm(DemoSystems.ssfir(c)) == norm(c)
120+
121+
112122
#
113123
## Test Hinfinity norm computations
114124
#

0 commit comments

Comments
 (0)