|
| 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 |
0 commit comments