@@ -362,41 +362,19 @@ end
362362
363363Optimize 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- \m athbf{Z̃_s} =
368- \b egin{bmatrix}
369- ϵ_{k-1} \\
370- \m athbf{x̂}_{k-1}(k-N_k+p) \\
371- \m athbf{ŵ}_{k-1}(k-N_k+p+0) \\
372- \m athbf{ŵ}_{k-1}(k-N_k+p+1) \\
373- \v dots \\
374- \m athbf{ŵ}_{k-1}(k-p-2) \\
375- \m athbf{0} \\
376- \e nd{bmatrix}
377- ```
378- where ``\m athbf{ŵ}_{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"""
384370function 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̃ = nϵ + nx̂
389374 Z̃var:: Vector{JuMP.VariableRef} = optim[:Z̃var ]
390- V̂ = 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. nϵ ≠ 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+ V̂ = 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 --------------
401379 try
402380 JuMP. optimize! (optim)
@@ -433,13 +411,64 @@ function optim_objective!(estim::MovingHorizonEstimator{NT}) where NT<:Real
433411 estim. Z̃ .= JuMP. value .(Z̃var)
434412 end
435413 # --------- update estimate -----------------------
414+ û0, ŷ0, k0 = buffer. û, buffer. ŷ, buffer. k
436415 estim. Ŵ[1 : nŵ* Nk] .= @views estim. Z̃[nx̃+ 1 : nx̃+ nŵ* Nk] # update Ŵ with optimum for warm-start
437416 V̂, X̂0 = predict! (V̂, X̂0, û0, k0, ŷ0, estim, model, estim. Z̃)
438417 x̂0next = @views X̂0[end - nx̂+ 1 : end ]
439418 estim. x̂0 .= x̂0next
440419 return estim. Z̃
441420end
442421
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+ \m athbf{Z̃_s} =
430+ \b egin{bmatrix}
431+ ϵ_{k-1} \\
432+ \m athbf{x̂}_{k-1}(k-N_k+p) \\
433+ \m athbf{ŵ}_{k-1}(k-N_k+p+0) \\
434+ \m athbf{ŵ}_{k-1}(k-N_k+p+1) \\
435+ \v dots \\
436+ \m athbf{ŵ}_{k-1}(k-p-2) \\
437+ \m athbf{0} \\
438+ \e nd{bmatrix}
439+ ```
440+ where ``ϵ(k-1)``, ``\m athbf{x̂}_{k-1}(k-N_k+p)`` and ``\m athbf{ŵ}_{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 ``\m athbf{ŵ}_{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̃ = nϵ + 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. nϵ == 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̃+ 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+
443472" Correct the covariance estimate at arrival using `covestim` [`StateEstimator`](@ref)."
444473function correct_cov! (estim:: MovingHorizonEstimator )
445474 nym, nd = estim. nym, estim. model. nd
0 commit comments