Skip to content

Commit 76082d7

Browse files
committed
place command for pole placement
1 parent 4fcefc1 commit 76082d7

File tree

5 files changed

+364
-0
lines changed

5 files changed

+364
-0
lines changed

src/design/place.jl

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"""
2+
place(A, B, p[; rtol = 1e-3, α::Real = 0, iter::Integer=200]) -> K
3+
4+
Places the closed-loop poles of the single- or multi-input system
5+
6+
x = Ax + Bu
7+
8+
at the poles of the vector `p` of desired self-conjugate closed-loop pole
9+
locations. Computes a gain matrix `K` such that the state feedback `u = –Kx`
10+
places the closed-loop poles at the locations `p`. In other words, the
11+
eigenvalues of `A – BK` match the entries of `p` (up to the ordering).
12+
Place works for multi-input systems and is based on the algorithm from [1].
13+
A closed loop pole `pₖ` in `p` that is close enough to an eigenvalue of `A`,
14+
`λₖ` will not be moved by the feedback. The tolerance is set `|λₖ-pₖ|< rtol`.
15+
16+
The non-uniqueness of solutions is used to minimize the sensitivity of
17+
closed-loop eigenvalues and the norm of the corresponding state feedback matrix
18+
`K`. The parameter `α ∈ [0,1]` determines the relative weight of these
19+
criteria. `α=0` only minimizes the norm of `K` and `α=1` only minimizes the
20+
sensitivity of the closed loop eigenvalues measured by the condition number of
21+
the eigenvectors of the closed loop eigenvalues.
22+
23+
You can also use place for estimator gain selection by transposing the `A` matrix
24+
and substituting `C.'` for `B`.
25+
26+
l = place(A.',C.',p).'
27+
28+
# Examples
29+
```julia
30+
julia> A = [0 0 0 0 ;
31+
1 10 100 1000;
32+
0 1 10 100 ;
33+
0 0 1 10 ];
34+
35+
julia> B = [1 0;
36+
0 1;
37+
0 0;
38+
0 0];
39+
40+
julia> p = [-1, -2, -1+1im, -1-1im];
41+
42+
julia> K = place(A, B, p);
43+
44+
julia> λ, V = eig(A+B*K);
45+
46+
julia> λ
47+
Complex{Float64}[4]
48+
-2.00 + 0.00im
49+
-1.00… + 1.00…im
50+
-1.00… - 1.00…im
51+
-1.00… + 0.00im
52+
```
53+
54+
# References
55+
56+
- [1]: Varga, A. "Robust pole assignment via Sylvester equation based state
57+
feedback parametrization." IEEE International Symposium on
58+
Computer-Aided Control System Design, 2000.
59+
"""
60+
function place{M1<:AbstractMatrix,M2<:AbstractMatrix,M3<:AbstractVector}(
61+
A::M1, B::M2, p::M3; α::Real = 0.99, rtol::Real=1e-3, iter::Integer=200)
62+
63+
# /TODO check (A,B) controllable
64+
if length(p) != size(A,1)
65+
warn("The number of poles in `p` does not match match the size of `A`")
66+
throw(DomainError())
67+
end
68+
69+
# schur factorization of A
70+
Rₐ, Qₐ, λ = schur(A)
71+
72+
# find eigenvalues not moved
73+
select = zeros(Bool, length(p))
74+
for i in eachindex(p)
75+
j = findfirst(y->isapprox(p[i],y; rtol=rtol), λ)
76+
if (j > 0)
77+
splice!(λ, j)
78+
select[i] = true
79+
end
80+
end
81+
82+
m = sum(select) # number of eigenvalues not moved
83+
p = float(p[!select]) # convert to float (A and B are handled by schur)
84+
= realjordanform(p)
85+
86+
# move eigenvalues to be moved to the lower right corner of the real schur
87+
# form, i.e. in A₃
88+
# QₐᵀAQₐ = Rₐ = [A₁ A₂; QₐᵀB = QB = [B₁;
89+
# 0 A₃] B₂]
90+
ordschur!(Rₐ, Qₐ, select)
91+
QB = Qₐ.'*B
92+
A₃ = Rₐ[m+1:end,m+1:end]
93+
B₂ = QB[m+1:end,:]
94+
95+
# find good G that parametrizes the freedom of the state feedback
96+
G = randn(eltype(A₃), size(B₂,2), size(A₃,2))
97+
# /TODO check (Ã,G) is observable
98+
99+
# minimize Frobenius norm of state feedback K, ‖K‖ and condition number
100+
# of X, κ(X) according to cost function J = ακ(X) + (1-α)‖K‖²
101+
last_g = zeros(length(G))
102+
X = zeros(A₃)
103+
Xi = zeros(A₃)
104+
K = zeros(G*Xi)
105+
106+
df = OnceDifferentiable(x -> place_cost(x, last_g, X, Xi, K, A₃, Ã, B₂, α),
107+
(x, stor) -> place_gradient!(stor, x, last_g, X, Xi, K, A₃, Ã, B₂, α))
108+
res = optimize(df,
109+
vec(G),
110+
Optim.BFGS(),
111+
Optim.Options(iterations = iter))
112+
113+
# transform controller according to the factorization of A
114+
K = hcat(zeros(size(K,1), m), K)*Qₐ.'
115+
K
116+
end
117+
118+
function place_cost(x, last_x, X, Xi, K, A, Ã, B, α)
119+
common_first_sylvester!(x, last_x, X, Xi, K, A, Ã, B)
120+
α*(sumabs2(X) + sumabs2(Xi))/2 + (1-α)*sumabs2(K)/2
121+
end
122+
123+
function place_gradient!(storage, x, last_x, X, Xi, K, A, Ã, B, α)
124+
common_first_sylvester!(x, last_x, X, Xi, K, A, Ã, B)
125+
126+
# solve ÃU - UA = -S
127+
H = Xi*K'
128+
S = α*(-X.' + Xi*Xi.'*Xi) + (1-α)*H*K # Xi*Xi.'*Xi (X.'*X)\Xi
129+
U,scale = LAPACK.trsyl!('N', 'N', Ã, A, -S, -1)
130+
scale!(U, inv(scale)) # this scale is always 1?
131+
132+
# calculate gradient
133+
storage[1:length(x)] = vec((1-α)*H' + B.'*U.')
134+
end
135+
136+
# helper function to save work done in both function call and gradient call
137+
function common_first_sylvester!(x, last_x, X, Xi, K, A, Ã, B)
138+
if x != last_x
139+
copy!(last_x, x)
140+
G = reshape(x, size(B,2), size(A,2))
141+
142+
# solve AX - XÃ = -B*G
143+
X[:], scale = LAPACK.trsyl!('N', 'N', A, Ã, -B*G, -1)
144+
scale!(X, inv(scale)) # this scale is always 1?
145+
Xi[:] = inv(X)
146+
K[:] = G*Xi
147+
end
148+
end

src/matrix_comps/realjordanform.jl

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
realjordanform(D) -> J
3+
4+
Returns the real Jordan canonical form of the eigenvalues specified in
5+
the vector D.
6+
7+
# Examples
8+
```julia
9+
julia> D = [-1, -1+2im, -1-2im, -1+2im, -1-2im];
10+
julia> realjordanform(D)
11+
5x5 Array{Int64,2}:
12+
-1 0 0 0 0
13+
0 -1 -2 1 0
14+
0 2 -1 0 1
15+
0 0 0 -1 -2
16+
0 0 0 2 -1
17+
```
18+
"""
19+
20+
function realjordanform{T<:Number}(D::AbstractVector{T})
21+
sort!(D, by=imag)
22+
sort!(D, by=x->abs(imag(x))) # make sure conjugated pairs are next to eachother
23+
sort!(D, by=real)
24+
25+
J = zeros(real(T), length(D), length(D))
26+
i = 1
27+
while i <= length(D)
28+
d = D[i]
29+
if isreal(d)
30+
n = findlast(D, d) - i + 1 # multiplicity of real eigenvalue
31+
@assert all(D[i+j] == d for j = 1:n-1) "There should be $n eigenvalues equal to $d"
32+
33+
# fill jordan block for real eigenvalue
34+
J[i,i] = d
35+
for k = 1:n-1
36+
J[i+k-1,i+k] = 1
37+
J[i+k,i+k] = d
38+
end
39+
i += n
40+
else
41+
n = findlast(D, d) - i + 1 # multiplicity of complex eigenvalue
42+
@assert all(D[i+j] == d for j = 1:n-1) "There should be $n eigenvalues equal to $d"
43+
@assert all(D[i+n+j] == conj(d) for j = 1:n-1) "There should be $n eigenvalues equal to $(conj(d))"
44+
# construct real block C
45+
a = real(d)
46+
b = imag(d)
47+
C = [a b; -b a]
48+
49+
# fill jordan block for complex eigenvalue
50+
J[i:i+1,i:i+1] = C
51+
for k = 1:n-1
52+
J[i+2(k-1)+(0:1),i+2k+(0:1)] = eye(C)
53+
J[i+2k+(0:1),i+2k+(0:1)] = C
54+
end
55+
i += 2n
56+
end
57+
end
58+
J
59+
end

test/design/place.jl

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# These examples are mostly taken from [1] and the references therein.
2+
# References
3+
#
4+
# [1]: Chu, E.K. "Pole assignment via the Schur form." Systems & control
5+
# letters 56.4 (2007): 303-314.
6+
# example 1
7+
A₁ = [1.38 -2.077e-1 6.715 -5.67 ;
8+
-5.814e-1 -4.29 0 6.77e-1;
9+
1.067 4.273 -6.654 5.893 ;
10+
4.8e-2 4.273 1.343 -2.104 ]
11+
B₁ = [0 0 ;
12+
5.679 0 ;
13+
1.136 -3.146;
14+
1.136 0 ]
15+
p₁ = [-0.2, -0.5, -5.0566, -8.6659]
16+
κ₁ = 5
17+
c₁ = 5
18+
19+
# example 2
20+
A₂ = [-1.094e-1 6.28e-2 0 0 0 ;
21+
1.306 -2.132 9.87e-1 0 0 ;
22+
0 1.595 -3.149 1.547 0 ;
23+
0 3.55e-2 2.632 -4.257 1.855 ;
24+
0 2.3e-3 0 1.636e-1 -1.625e-1]
25+
B₂ = [0 0 ;
26+
6.38e-2 0 ;
27+
8.38e-2 -1.396e-1;
28+
1.004e-1 -2.06e-1 ;
29+
6.3e-3 -1.28e-2 ]
30+
p₂ = [-0.2, -0.5, -1, -1+im, -1-im]
31+
κ₂ = 1_000
32+
c₂ = 200
33+
34+
# example 3
35+
A₃ = [-65 65 -19.5 19.4;
36+
0.1 -0.1 0 0 ;
37+
1 0 -0.5 -1 ;
38+
0 0 0.4 0 ]
39+
B₃ = [65 0 ;
40+
0 0 ;
41+
0 0.4;
42+
0 0 ]
43+
p₃ = [-1, -2, -3, -4.0]
44+
κ₃ = 50
45+
c₃ = 100
46+
47+
# example 4
48+
A₄ = [ 0 1 0;
49+
0 0 1;
50+
-6 -11 -6]
51+
B₄ = [1 1;
52+
0 1;
53+
1 1]
54+
p₄ = [-1, -2, -3]
55+
κ₄ = 1_000
56+
c₄ = 1
57+
58+
# example 5
59+
D = diagm([1,10,0.1,0.1,10])
60+
D₁ = diagm([1,0.1,10,10,0.1])
61+
D₂ = diagm([1e5,1e2])
62+
A₅ = D*[-1.29e-1 0 3.96e-2 2.5e-2 1.91e-2;
63+
3.29e-3 0 -7.79e-5 1.22e-4 -6.21e-1;
64+
7.18e-2 0 -1e-1 8.87e-4 -3.85 ;
65+
4.11e-2 0 0 -8.22e-2 0 ;
66+
3.51e-4 0 3.5e-5 4.26e-5 -7.43e-2]*D₁
67+
B₅ = D*[0 1.39e-3;
68+
0 3.59e-5;
69+
0 -9.89e-3;
70+
2.49e-5 0 ;
71+
0 -5.34e-6]*D₂
72+
p₅ = [-0.01, -0.02, -0.03, -0.04, -0.05]
73+
κ₅ = 1_000
74+
c₅ = 5
75+
76+
# example 6
77+
A₆ = [5.8765 9.3456 4.5634 9.3520;
78+
6.6526 0.5867 3.5829 0.6534;
79+
0 9.6738 7.4876 4.7654;
80+
0 0 6.6784 2.5678]
81+
B₆ = [3.9878 0.5432;
82+
0 2.7650;
83+
0 0 ;
84+
0 0 ]
85+
p₆ = [-29.4986, -10.0922, 2.5201+6.891im, 2.5201-6.891im]
86+
κ₆ = 5
87+
c₆ = 50
88+
89+
# example 7 (pole of multiplicity 4 > rank(B) = 2)
90+
A₇ = [1.38 -2.077e-1 6.715 -5.67 ;
91+
-5.814e-1 -4.29 0 6.77e-1;
92+
1.067 4.273 -6.654 5.893 ;
93+
4.8e-2 4.273 1.343 -2.104 ]
94+
B₇ = [0 0 ;
95+
5.679 0 ;
96+
1.136 -3.146;
97+
1.136 0 ]
98+
p₇ = [-0.5+1im, -0.5-1im, -0.5+1im, -0.5-1im]
99+
κ₇ = 1e12
100+
c₇ = 5
101+
102+
# example 8
103+
A₈ = [ 0 1 0 0 ;
104+
5.32e-7 -4.18e-1 -0.12 2.32e-2;
105+
-4.62e-9 1 -0.752 -2.39e-2;
106+
-5.61e-1 0 -0.3 -1.74e-2]
107+
B₈ = [ 0 0 ;
108+
-1.72e-1 7.45e-6;
109+
-2.38e-2 -7.78e-5;
110+
0 3.69e-3]
111+
p₈ = [-1,-2,-3,-4]
112+
κ₈ = 1_000
113+
c₈ = 1_000
114+
115+
# example 9 (ill-conditioned example)
116+
A₉ = [0 0 0 0 ;
117+
1 10 100 1000;
118+
0 1 10 100 ;
119+
0 0 1 10 ]
120+
B₉ = [1 0;
121+
0 1;
122+
0 0;
123+
0 0]
124+
p₉ = [-1,-2,-3,-4]
125+
κ₉ = 1e6
126+
c₉ = 10_000
127+
128+
for (A,B,p,κ,c) in ((A₁,B₁,p₁,κ₁,c₁), (A₂,B₂,p₂,κ₂,c₂), (A₃,B₃,p₃,κ₃,c₃),
129+
(A₄,B₄,p₄,κ₄,c₄), (A₅,B₅,p₅,κ₅,c₅), (A₆,B₆,p₆,κ₆,c₆),
130+
(A₇,B₇,p₇,κ₇,c₇), (A₈,B₈,p₈,κ₈,c₈), (A₉,B₉,p₉,κ₉,c₉))
131+
F = place(A, B, p)
132+
λ, V = eig(A+B*F)
133+
134+
@test cond(V) < κ
135+
@test vecnorm(F) < c
136+
for i in 1:length(p)
137+
j = findfirst(y->isapprox(y, p[i], rtol=1e-3), λ)
138+
if j > 0
139+
splice!(λ,j)
140+
end
141+
end
142+
@test isempty(λ)
143+
end

test/matrix_comps/realjordanform.jl

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# test real jordan form
2+
D = [-1, -1+2im, -1+1im, -1-1im,-1-2im, -1+2im, -1-2im]
3+
C1 = [-1 -1; 1 -1]
4+
C2 = [-1 -2; 2 -1]
5+
JD = zeros(Int, 7,7)
6+
JD[1,1] = -1
7+
JD[2:3,2:3] = C1
8+
JD[4:5,4:5] = JD[6:7,6:7] = C2
9+
JD[4:5,6:7] = eye(Int, 2)
10+
11+
@test realjordanform(D) == JD

test/runtests.jl

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ using Base.Test
33

44
# write your own tests here
55
@test 1 == 1
6+
7+
#include("matrix_comps/realjordanform.jl")
8+
#include("design/place.jl")

0 commit comments

Comments
 (0)