JuliaControl / ModelPredictiveControl.jl

An open source model predictive control package for Julia.
https://juliacontrol.github.io/ModelPredictiveControl.jl/stable
MIT License
70 stars 0 forks source link

Support for current estimators and V1 soon? #89

Closed franckgaga closed 3 months ago

franckgaga commented 3 months ago

Hello @baggepinnen,

I'm working in implementing the current form for all the estimators in the package. Two points about that:

1- The predict and correct is a good idea except for the MovingHorizonEstimator: this separation is impossible afaik. It needs to be a single function that solve the full optimization problem over the window $N_k$. I prefer to have a uniform API for all the state estimators. About the possible confusion between the current and delayed form: I'm hesitating between keeping the updatestate! method for both forms (and it should be called before or after moveintput, depending on the chosen form, a bit more confusing IMO but simpler API). The other option would be to create a new method for the current form e.g. evalstate!. Do you have a preference or other ideas ?

2- After these new features are merged, I feel like it would be a good timing to move to v1. The user base seems to be growing according to stars and I'm not a fan of keeping packages in v0 eternally.

baggepinnen commented 3 months ago

The way I have it setup in LowLevelParticleFilters.jl is to offer predict! and correct!, but also an update! that performs them both. This package could do something similar, in which case all estimators would support predict/correct except for MHE which only supports the combined update. Having them separate when possible is nice since it offers a lot of flexibility in how the estimator is used, enabling things like

In principle, a moving-horizon estimator can also perform both of those steps, the correction involves solving the optimization problem, while the prediction only advances the state using the internal prediction model.

It's not quite clear to me what the function evalstate! does, would postupdate! and preupdate! be descriptive names? Alternatively, predict_and_correct! / correct_and_predict! to make it abundantly clear what's happening, at the expense of some verbosity?

franckgaga commented 3 months ago

Alright thanks for your tips!

Clearly, I will implement the predict! and correct! functions, but it will be in a new minor version, a bit later.

For now, I'll just code the current-form for all the observers in the package.

franckgaga commented 3 months ago

@baggepinnen Another point, I'm thinking on switching to the current form by default (to remove the one sample delay). Do you agree with this breaking change ? And about that, why did you chose the the delayed form by default in kalman and place function of ControlSystemsBase.jl ?

baggepinnen commented 3 months ago

Do you agree with this breaking change ?

I do

why did you chose the the delayed form by default in kalman and place function of ControlSystemsBase.jl ?

I didn't, this precedes my involvement in the package. The choice was likely made to conform to other similar toolboxes. The solution to the standard Ricatti equation also gives you the delayed form, so it makes sense to have that be the default there I guess.

franckgaga commented 3 months ago

Okay after some deep reflection (sadly also in the night, the static gain on my brain is sometimes zero haha) here is what would be the new API for state estimation:

I purposefully avoided the "correct" and "predict" keyword in the API, to avoid possible confusion with your toolbox and similar toolboxes. Moreover, I will presumably add these two functions latter in the API.

For now, my goal is to create a simple API that works for all the state estimators, no matter if it's in the current or delayed form. This way, a user can write its own for loop with e.g. setpoint bumps and disturbances, and switch the estimator inside the constructor, before the for loop. The code inside the for loop will still work without any change. Here's the new API:

for i = 1:100
    ry = ... # current output setpoints
    ym, d = ... # current measured outputs and disturbances
    preparestate!(mpc, ym, d) # compute x̂(k|k) in the current form, do nothing in the delayed form
    u = moveinput!(mpc, ry, d)
    # sending the current manipulated inputs to the plant...
    updatestate!(mpc, u, ym, d) # compute x̂(k+1|k), this method will always do something (with more computations in the delayed form)
end

My initial idea was to move everything in the moveinput! function (that's what MATLAB do for linear MPC). But I don't like this idea since it mixes the concept of estimation and control, meaning that the estimator cannot be used without a mpc controller. I also like that updatestate! is always called at the end of the for loop, no matter if its a StateEstimator or a SimModel object.

P.S.:

In principle, a moving-horizon estimator can also perform both of those steps, the correction involves solving the optimization problem, while the prediction only advances the state using the internal prediction model.

Doing that would neglect the constraints on the state estimate during the prediction step.

franckgaga commented 3 months ago

I finished the implementation for all the estimators except the MHE. Thanks for the tips, it does clearly improve the closed-loop performance for e.g. disturbance rejection. The detailed equations are also documented in the internal documentation.

baggepinnen commented 3 months ago

Cool, Nice work!

Disturbance rejection when there is no measurement of the disturbance relies solely on the measurement feedback, so it makes sense that reacting to this one sample earlier improves the rejection 😊