Skip to content

Commit b608582

Browse files
committed
added: inv! for MHE covariance matrices
allocation-free with `LinearAlgebra` standard types (and numerically robust since it relies on a cholesky factorization) there is also a fallback method with other `AbstractMatrix` with some allocations
1 parent 20a5ed0 commit b608582

File tree

3 files changed

+45
-16
lines changed

3 files changed

+45
-16
lines changed

src/estimator/mhe/construct.jl

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,10 +159,16 @@ struct MovingHorizonEstimator{
159159
buffer = StateEstimatorBuffer{NT}(nu, nx̂, nym, ny, nd, nk)
160160
P̂_0 = Hermitian(P̂_0, :L)
161161
Q̂, R̂ = Hermitian(Q̂, :L), Hermitian(R̂, :L)
162-
P̂_0 = Hermitian(P̂_0, :L)
163-
invP̄ = inv_cholesky!(buffer.P̂, P̂_0)
164-
invQ̂ = inv_cholesky!(buffer.Q̂, Q̂)
165-
invR̂ = inv_cholesky!(buffer.R̂, R̂)
162+
163+
invP̄ = Hermitian(buffer.P̂, :L)
164+
invP̄ .= P̂_0
165+
inv!(invP̄)
166+
invQ̂ = Hermitian(buffer.Q̂, :L)
167+
invQ̂ .=
168+
inv!(invQ̂)
169+
invR̂ = Hermitian(buffer.R̂, :L)
170+
invR̂ .=
171+
inv!(invR̂)
166172
invQ̂_He = Hermitian(repeatdiag(invQ̂, He), :L)
167173
invR̂_He = Hermitian(repeatdiag(invR̂, He), :L)
168174
x̂0arr_old = zeros(NT, nx̂)

src/estimator/mhe/execute.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,8 +528,10 @@ end
528528

529529
"Invert the covariance estimate at arrival `P̄`."
530530
function invert_cov!(estim::MovingHorizonEstimator, P̄)
531+
invP̄ = Hermitian(estim.buffer.P̂, :L)
532+
invP̄ .=
531533
try
532-
estim.invP̄ .= inv_cholesky!(estim.buffer.P̂, P̄)
534+
inv!(invP̄)
533535
catch err
534536
if err isa PosDefException
535537
@error("Arrival covariance P̄ is not invertible: keeping the old one")
@@ -792,11 +794,17 @@ function setmodel_estimator!(
792794
# --- covariance matrices ---
793795
if !isnothing(Q̂)
794796
estim.Q̂ .= to_hermitian(Q̂)
795-
estim.invQ̂_He .= repeatdiag(inv(estim.Q̂), He)
797+
invQ̂ = Hermitian(estim.buffer.Q̂, :L)
798+
invQ̂ .= estim.
799+
inv!(invQ̂)
800+
estim.invQ̂_He .= Hermitian(repeatdiag(invQ̂, He), :L)
796801
end
797802
if !isnothing(R̂)
798803
estim.R̂ .= to_hermitian(R̂)
799-
estim.invR̂_He .= repeatdiag(inv(estim.R̂), He)
804+
invR̂ = Hermitian(estim.buffer.R̂, :L)
805+
invR̂ .= estim.
806+
inv!(invR̂)
807+
estim.invR̂_He .= Hermitian(repeatdiag(invR̂, He), :L)
800808
end
801809
return nothing
802810
end

src/general.jl

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,30 @@ to_hermitian(A::Hermitian) = A
8484
to_hermitian(A) = A
8585

8686
"""
87-
Compute the inverse of a the Hermitian positive definite matrix `A` using `cholesky`.
87+
Compute the inverse of a the Hermitian positive definite matrix `A` in-place and return it.
8888
89-
Builtin `inv` function uses LU factorization which is not the best choice for Hermitian
90-
positive definite matrices. The function will mutate `buffer` to reduce memory allocations.
89+
There is 3 methods for this function:
90+
- If `A` is a `Hermitian{<Real, Matrix{<:Real}}`, it uses `LAPACK.potrf!` and
91+
`LAPACK.potri!` functions to compute the Cholesky factor and then the inverse. This is
92+
allocation-free. See <https://tinyurl.com/4pwdwbcj> for the source.
93+
- If `A` is a `Hermitian{<Real, Diagonal{<:Real, Vector{<:Real}}}`, it computes the
94+
inverse of the diagonal elements in-place (allocation-free).
95+
- Else if `A` is a `Hermitian{<:Real, <:AbstractMatrix}`, it computes the Cholesky factor
96+
with `cholesky!` and then the inverse with `inv`, which allocates memory.
9197
"""
92-
function inv_cholesky!(buffer::Matrix, A::Hermitian)
93-
Achol = Hermitian(buffer, :L)
94-
Achol .= A
95-
chol_obj = cholesky!(Achol)
96-
invA = Hermitian(inv(chol_obj), :L)
97-
return invA
98+
function inv!(A::Hermitian{NT, Matrix{NT}}) where {NT<:Real}
99+
_, info = LAPACK.potrf!(A.uplo, A.data)
100+
(info == 0) || throw(PosDefException(info))
101+
LAPACK.potri!(A.uplo, A.data)
102+
return A
103+
end
104+
function inv!(A::Hermitian{NT, Diagonal{NT, Vector{NT}}}) where {NT<:Real}
105+
A.data.diag .= 1 ./ A.data.diag
106+
return A
107+
end
108+
function inv!(A::Hermitian{<:Real, <:AbstractMatrix})
109+
Achol = cholesky!(A)
110+
invA = inv(Achol)
111+
A .= Hermitian(invA, :L)
112+
return A
98113
end

0 commit comments

Comments
 (0)