Skip to content

Commit c2c65dc

Browse files
authored
use MatrixEquations.jl (#494)
* use MatrixEquations.jl * add ME to Project.toml * care/dare for scalars * args and kwargs forwarding to ME * ME handles vector B
1 parent 11f2f5b commit c2c65dc

File tree

5 files changed

+43
-77
lines changed

5 files changed

+43
-77
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ IterTools = "c8e1da08-722c-5040-9ed9-7db0dc04731e"
1313
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
1414
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1515
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
16+
MatrixEquations = "99c1a7ee-ab34-5fd5-8076-27c950a045f4"
1617
MatrixPencils = "48965c70-4690-11ea-1f13-43a2532b2fa8"
1718
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
1819
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
@@ -29,6 +30,7 @@ DiffEqCallbacks = "2.16"
2930
IterTools = "1.0"
3031
LaTeXStrings = "1.0"
3132
MacroTools = "0.5"
33+
MatrixEquations = "1"
3234
MatrixPencils = "1.6"
3335
OrdinaryDiffEq = "5.2, 6.0"
3436
Plots = "0.24, 0.25, 0.26, 0.27, 0.28, 0.29, 1.0"

src/ControlSystems.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import DiffEqCallbacks: SavingCallback, SavedValues
114114
import MatrixPencils
115115
using DelayDiffEq
116116
using MacroTools
117+
using MatrixEquations
117118

118119
abstract type AbstractSystem end
119120

src/matrix_comps.jl

Lines changed: 12 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,34 @@
33
Compute 'X', the solution to the continuous-time algebraic Riccati equation,
44
defined as A'X + XA - (XB)R^-1(B'X) + Q = 0, where R is non-singular.
55
6-
Algorithm taken from:
7-
Laub, "A Schur Method for Solving Algebraic Riccati Equations."
8-
http://dspace.mit.edu/bitstream/handle/1721.1/1301/R-0859-05666488.pdf
6+
Uses `MatrixEquations.arec`.
97
"""
108
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
9+
arec(A, B, R, Q)[1]
3210
end
3311

12+
care(A::Number, B::Number, Q::Number, R::Number) = care(fill(A,1,1),fill(B,1,1),fill(Q,1,1),fill(R,1,1))
13+
3414
"""`dare(A, B, Q, R)`
3515
3616
Compute `X`, the solution to the discrete-time algebraic Riccati equation,
3717
defined as A'XA - X - (A'XB)(B'XB + R)^-1(B'XA) + Q = 0, where Q>=0 and R>0
3818
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
19+
Uses `MatrixEquations.ared`.
4220
"""
4321
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);
64-
65-
return QZ.Z[(n+1):end, 1:n]/QZ.Z[1:n, 1:n];
22+
ared(A, B, R, Q)[1]
6623
end
6724

25+
dare(A::Number, B::Number, Q::Number, R::Number) = dare(fill(A,1,1),fill(B,1,1),fill(Q,1,1),fill(R,1,1))
26+
6827
"""`dlyap(A, Q)`
6928
7029
Compute the solution `X` to the discrete Lyapunov equation
7130
`AXA' - X + Q = 0`.
7231
"""
7332
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))
33+
lyapd(A, Q)
7834
end
7935

8036
"""`gram(sys, opt)`
@@ -86,12 +42,11 @@ function gram(sys::AbstractStateSpace, opt::Symbol)
8642
if !isstable(sys)
8743
error("gram only valid for stable A")
8844
end
89-
func = iscontinuous(sys) ? lyap : dlyap
45+
lyapf = iscontinuous(sys) ? lyapc : lyapd
9046
if opt === :c
91-
# TODO probably remove type check in julia 0.7.0
92-
return func(sys.A, sys.B*sys.B')#::Array{numeric_type(sys),2} # lyap is type-unstable
47+
return lyapf(sys.A, sys.B*sys.B')
9348
elseif opt === :o
94-
return func(Matrix(sys.A'), sys.C'*sys.C)#::Array{numeric_type(sys),2} # lyap is type-unstable
49+
return lyapf(Matrix(sys.A'), sys.C'*sys.C)
9550
else
9651
error("opt must be either :c for controllability grammian, or :o for
9752
observability grammian")

src/synthesis.jl

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
lqr(A, B, Q, R)
2+
lqr(A, B, Q, R, args...; kwargs...)
33
44
Calculate the optimal gain matrix `K` for the state-feedback law `u = -K*x` that
55
minimizes the cost function:
@@ -13,6 +13,9 @@ For the continuous time model `dx = Ax + Bu`.
1313
Solve the LQR problem for state-space system `sys`. Works for both discrete
1414
and continuous time systems.
1515
16+
The `args...; kwargs...` are sent to the Riccati solver, allowing specification of cross-covariance etc. See `?MatrixEquations.arec` for more help.
17+
18+
See also `LQG`
1619
Usage example:
1720
```julia
1821
using LinearAlgebra # For identity matrix I
@@ -31,9 +34,8 @@ y, t, x, uout = lsim(sys,u,t,x0=x0)
3134
plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]")
3235
```
3336
"""
34-
function lqr(A, B, Q, R)
35-
S = care(A, B, Q, R)
36-
K = R\B'*S
37+
function lqr(A, B, Q, R, args...; kwargs...)
38+
S, _, K = arec(A, B, R, Q, args...; kwargs...)
3739
return K
3840
end
3941

@@ -42,29 +44,33 @@ end
4244
kalman(sys, R1, R2)
4345
4446
Calculate the optimal Kalman gain
47+
48+
The `args...; kwargs...` are sent to the Riccati solver, allowing specification of cross-covariance etc. See `?MatrixEquations.arec/ared` for more help.
49+
50+
See also `LQG`
4551
"""
46-
kalman(A, C, R1,R2) = Matrix(lqr(A',C',R1,R2)')
52+
kalman(A, C, R1,R2, args...; kwargs...) = Matrix(lqr(A',C',R1,R2, args...; kwargs...)')
4753

48-
function lqr(sys::AbstractStateSpace, Q, R)
54+
function lqr(sys::AbstractStateSpace, Q, R, args...; kwargs...)
4955
if iscontinuous(sys)
50-
return lqr(sys.A, sys.B, Q, R)
56+
return lqr(sys.A, sys.B, Q, R, args...; kwargs...)
5157
else
5258
return dlqr(sys.A, sys.B, Q, R)
5359
end
5460
end
5561

56-
function kalman(sys::AbstractStateSpace, R1,R2)
62+
function kalman(sys::AbstractStateSpace, R1, R2, args...; kwargs...)
5763
if iscontinuous(sys)
58-
return Matrix(lqr(sys.A', sys.C', R1,R2)')
64+
return Matrix(lqr(sys.A', sys.C', R1,R2, args...; kwargs...)')
5965
else
60-
return Matrix(dlqr(sys.A', sys.C', R1,R2)')
66+
return Matrix(dlqr(sys.A', sys.C', R1,R2, args...; kwargs...)')
6167
end
6268
end
6369

6470

6571
"""
66-
dlqr(A, B, Q, R)
67-
dlqr(sys, Q, R)
72+
dlqr(A, B, Q, R, args...; kwargs...)
73+
dlqr(sys, Q, R, args...; kwargs...)
6874
6975
Calculate the optimal gain matrix `K` for the state-feedback law `u[k] = -K*x[k]` that
7076
minimizes the cost function:
@@ -75,6 +81,8 @@ For the discrte time model `x[k+1] = Ax[k] + Bu[k]`.
7581
7682
See also `lqg`
7783
84+
The `args...; kwargs...` are sent to the Riccati solver, allowing specification of cross-covariance etc. See `?MatrixEquations.ared` for more help.
85+
7886
Usage example:
7987
```julia
8088
using LinearAlgebra # For identity matrix I
@@ -94,15 +102,14 @@ y, t, x, uout = lsim(sys,u,t,x0=x0)
94102
plot(t,x', lab=["Position" "Velocity"], xlabel="Time [s]")
95103
```
96104
"""
97-
function dlqr(A, B, Q, R)
98-
S = dare(A, B, Q, R)
99-
K = (B'*S*B + R)\(B'S*A)
105+
function dlqr(A, B, Q, R, args...; kwargs...)
106+
S, _, K = ared(A, B, R, Q, args...; kwargs...)
100107
return K
101108
end
102109

103-
function dlqr(sys::AbstractStateSpace, Q, R)
110+
function dlqr(sys::AbstractStateSpace, Q, R, args...; kwargs...)
104111
!isdiscrete(sys) && throw(ArgumentError("Input argument sys must be discrete-time system"))
105-
return dlqr(sys.A, sys.B, Q, R)
112+
return dlqr(sys.A, sys.B, Q, R, args...; kwargs...)
106113
end
107114

108115
"""
@@ -111,8 +118,9 @@ end
111118
112119
Calculate the optimal Kalman gain for discrete time systems
113120
121+
The `args...; kwargs...` are sent to the Riccati solver, allowing specification of cross-covariance etc. See `?MatrixEquations.ared` for more help.
114122
"""
115-
dkalman(A, C, R1,R2) = Matrix(dlqr(A',C',R1,R2)')
123+
dkalman(A, C, R1,R2, args...; kwargs...) = Matrix(dlqr(A',C',R1,R2, args...; kwargs...)')
116124

117125
"""
118126
place(A, B, p, opt=:c)

test/test_synthesis.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,10 @@ end
126126
L = dlqr(A,B,Q,R)
127127
@test L [0.5890881713787511 0.7118839434795103]
128128

129-
B = [0;1] # Note B is vector, B'B is scalar and INcompatible with matrix
129+
B = [0;1] # Note B is vector, B'B is scalar
130130
Q = eye_(2)
131131
R = eye_(1)
132-
@test_throws MethodError L dlqr(A,B,Q,R)
132+
L dlqr(A,B,Q,R)
133133
#L ≈ [0.5890881713787511 0.7118839434795103]
134134
end
135135

0 commit comments

Comments
 (0)