Skip to content

Commit 3ad3158

Browse files
committed
debug: force gradient/jacobian update of MovingHorizonEstimator when not filled
1 parent 0b11288 commit 3ad3158

3 files changed

Lines changed: 62 additions & 39 deletions

File tree

docs/src/internals/state_estim.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ ModelPredictiveControl.linconstraint!(::MovingHorizonEstimator, ::LinModel)
3939

4040
```@docs
4141
ModelPredictiveControl.optim_objective!(::MovingHorizonEstimator)
42+
ModelPredictiveControl.set_warmstart_mhe!
4243
```
4344

4445
## Remove Operating Points

src/controller/transcription.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -954,7 +954,7 @@ linconstrainteq!(::PredictiveController, ::SimModel, ::MultipleShooting) = nothi
954954
@doc raw"""
955955
set_warmstart!(mpc::PredictiveController, transcription::SingleShooting, Z̃var) -> Z̃s
956956
957-
Set and return the warm start value of `Z̃var` for [`SingleShooting`](@ref) transcription.
957+
Set and return the warm-start value of `Z̃var` for [`SingleShooting`](@ref) transcription.
958958
959959
If supported by `mpc.optim`, it warm-starts the solver at:
960960
```math
@@ -985,7 +985,7 @@ end
985985
@doc raw"""
986986
set_warmstart!(mpc::PredictiveController, transcription::MultipleShooting, Z̃var) -> Z̃s
987987
988-
Set and return the warm start value of `Z̃var` for [`MultipleShooting`](@ref) transcription.
988+
Set and return the warm-start value of `Z̃var` for [`MultipleShooting`](@ref) transcription.
989989
990990
It warm-starts the solver at:
991991
```math

src/estimator/mhe/execute.jl

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -362,43 +362,20 @@ end
362362
363363
Optimize objective of `estim` [`MovingHorizonEstimator`](@ref) and return the solution `Z̃`.
364364
365-
If supported by `estim.optim`, it warm-starts the solver at:
366-
```math
367-
\mathbf{Z̃_s} =
368-
\begin{bmatrix}
369-
ϵ_{k-1} \\
370-
\mathbf{x̂}_{k-1}(k-N_k+p) \\
371-
\mathbf{ŵ}_{k-1}(k-N_k+p+0) \\
372-
\mathbf{ŵ}_{k-1}(k-N_k+p+1) \\
373-
\vdots \\
374-
\mathbf{ŵ}_{k-1}(k-p-2) \\
375-
\mathbf{0} \\
376-
\end{bmatrix}
377-
```
378-
where ``\mathbf{ŵ}_{k-1}(k-j)`` is the input increment for time ``k-j`` computed at the
379-
last time step ``k-1``. It then calls `JuMP.optimize!(estim.optim)` and extract the
380-
solution. A failed optimization prints an `@error` log in the REPL and returns the
381-
warm-start value. A failed optimization also prints [`getinfo`](@ref) results in
382-
the debug log [if activated](@extref Julia Example:-Enable-debug-level-messages).
365+
If first warm-starts the solver with [`set_warmstart_mhe!`](@ref). It then calls
366+
`JuMP.optimize!(estim.optim)` and extract the solution. A failed optimization prints an
367+
`@error` log in the REPL and returns the warm-start value. A failed optimization also prints
368+
[`getinfo`](@ref) results in the debug log [if activated](@extref Julia Example:-Enable-debug-level-messages).
383369
"""
384370
function optim_objective!(estim::MovingHorizonEstimator{NT}) where NT<:Real
385371
model, optim, buffer = estim.model, estim.optim, estim.buffer
386-
nu, ny, nk = model.nu, model.ny, model.nk
387-
nx̂, nym, nŵ, nϵ, Nk = estim.nx̂, estim.nym, estim.nx̂, estim.nϵ, estim.Nk[]
372+
nym, nx̂, nŵ, nϵ, Nk = estim.nym, estim.nx̂, estim.nx̂, estim.nϵ, estim.Nk[]
388373
nx̃ =+ nx̂
389374
Z̃var::Vector{JuMP.VariableRef} = optim[:Z̃var]
390-
= Vector{NT}(undef, nym*Nk)
391-
X̂0 = Vector{NT}(undef, nx̂*Nk)
392-
û0, ŷ0, x̄, k0 = buffer.û, buffer.ŷ, buffer.x̂, buffer.k
393-
ϵ_0 = estim. 0 ? estim.Z̃[begin] : empty(estim.Z̃)
394-
Z̃s = [ϵ_0; estim.x̂0arr_old; estim.Ŵ]
395-
V̂, X̂0 = predict!(V̂, X̂0, û0, k0, ŷ0, estim, model, Z̃s)
396-
J_0 = obj_nonlinprog!(x̄, estim, model, V̂, Z̃s)
397-
# warm-start Z̃s with Ŵ=0 if objective or constraint function not finite :
398-
isfinite(J_0) || (Z̃s = [ϵ_0; estim.x̂0arr_old; zeros(NT, nŵ*estim.He)])
399-
JuMP.set_start_value.(Z̃var, Z̃s)
375+
= Vector{NT}(undef, nym*Nk) # TODO: remove this allocation
376+
X̂0 = Vector{NT}(undef, nx̂*Nk) # TODO: remove this allocation
377+
Z̃s = set_warmstart_mhe!(V̂, X̂0, estim, Z̃var)
400378
# ------- solve optimization problem --------------
401-
println("optimizing!")
402379
try
403380
JuMP.optimize!(optim)
404381
catch err
@@ -434,15 +411,64 @@ function optim_objective!(estim::MovingHorizonEstimator{NT}) where NT<:Real
434411
estim.Z̃ .= JuMP.value.(Z̃var)
435412
end
436413
# --------- update estimate -----------------------
414+
û0, ŷ0, k0 = buffer.û, buffer.ŷ, buffer.k
437415
estim.Ŵ[1:nŵ*Nk] .= @views estim.Z̃[nx̃+1:nx̃+nŵ*Nk] # update Ŵ with optimum for warm-start
438-
println("solved, needs to call predict! one more time.")
439416
V̂, X̂0 = predict!(V̂, X̂0, û0, k0, ŷ0, estim, model, estim.Z̃)
440417
x̂0next = @views X̂0[end-nx̂+1:end]
441418
estim.x̂0 .= x̂0next
442-
@show estim.Ŵ[1:nŵ*Nk]
443419
return estim.
444420
end
445421

422+
@doc raw"""
423+
set_warmstart_mhe!(V̂, X̂0, estim::MovingHorizonEstimator, Z̃var) -> Z̃s
424+
425+
Set and return the warm-start value of `Z̃var` for [`MovingHorizonEstimator`](@ref).
426+
427+
If supported by `estim.optim`, it warm-starts the solver at:
428+
```math
429+
\mathbf{Z̃_s} =
430+
\begin{bmatrix}
431+
ϵ_{k-1} \\
432+
\mathbf{x̂}_{k-1}(k-N_k+p) \\
433+
\mathbf{ŵ}_{k-1}(k-N_k+p+0) \\
434+
\mathbf{ŵ}_{k-1}(k-N_k+p+1) \\
435+
\vdots \\
436+
\mathbf{ŵ}_{k-1}(k-p-2) \\
437+
\mathbf{0} \\
438+
\end{bmatrix}
439+
```
440+
where ``ϵ(k-1)``, ``\mathbf{x̂}_{k-1}(k-N_k+p)`` and ``\mathbf{ŵ}_{k-1}(k-j)`` are
441+
respectively the slack variable, the arrival state estimate and the process noise estimates
442+
computed at the last time step ``k-1``. If the objective function is not finite at this
443+
point, all the process noises ``\mathbf{ŵ}_{k-1}(k-j)`` are warm-started at zeros. The
444+
method mutates all the arguments.
445+
"""
446+
function set_warmstart_mhe!(V̂, X̂0, estim::MovingHorizonEstimator{NT}, Z̃var) where NT<:Real
447+
model, buffer = estim.model, estim.buffer
448+
nϵ, nx̂, nŵ, nZ̃, Nk = estim.nϵ, estim.nx̂, estim.nx̂, length(estim.Z̃), estim.Nk[]
449+
nx̃ =+ nx̂
450+
Z̃s = Vector{NT}(undef, nZ̃) # TODO: remove this allocation
451+
û0, ŷ0, x̄, k0 = buffer.û, buffer.ŷ, buffer.x̂, buffer.k
452+
# --- slack variable ϵ ---
453+
estim.== 1 && (Z̃s[begin] = estim.Z̃[begin])
454+
# --- arrival state estimate x̂0arr ---
455+
Z̃s[nϵ+1:nx̃] = estim.x̂0arr_old
456+
# --- process noise estimates Ŵ ---
457+
Z̃s[nx̃+1:end] = estim.
458+
# verify definiteness of objective function:
459+
V̂, X̂0 = predict!(V̂, X̂0, û0, k0, ŷ0, estim, model, Z̃s)
460+
Js = obj_nonlinprog!(x̄, estim, model, V̂, Z̃s)
461+
if !isfinite(Js)
462+
Z̃s[nx̃+nx̂+1:end] = 0
463+
end
464+
# --- unused variable in Z̃ (applied only when Nk ≠ He) ---
465+
# We force the update of the NLP gradient and jacobian by warm-starting the unused
466+
# variable in Z̃ at 1. Since estim.Ŵ is initialized with 0s, at least 1 variable in Z̃s
467+
# will be inevitably different at the following time step.
468+
Z̃s[nx̃+Nk*nŵ+1:end] .= 1
469+
JuMP.set_start_value.(Z̃var, Z̃s)
470+
end
471+
446472
"Correct the covariance estimate at arrival using `covestim` [`StateEstimator`](@ref)."
447473
function correct_cov!(estim::MovingHorizonEstimator)
448474
nym, nd = estim.nym, estim.model.nd
@@ -600,10 +626,6 @@ function predict!(V̂, X̂0, û0, k0, ŷ0, estim::MovingHorizonEstimator, mode
600626
x̂0 = x̂0next
601627
end
602628
end
603-
if eltype(X̂0) == Float64
604-
@show X̂0
605-
@show
606-
end
607629
return V̂, X̂0
608630
end
609631

0 commit comments

Comments
 (0)