Skip to content

Support nonlinear estimator with linear model predictive control #205

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1-Bart-1 opened this issue May 20, 2025 · 10 comments · Fixed by #206
Closed

Support nonlinear estimator with linear model predictive control #205

1-Bart-1 opened this issue May 20, 2025 · 10 comments · Fixed by #206
Assignees

Comments

@1-Bart-1
Copy link

1-Bart-1 commented May 20, 2025

Is there any reason this cannot be done? It would improve the state estimation of non-measured states while still keeping good performance.

https://github.com/JuliaControl/ModelPredictiveControl.jl/blob/db2827a5ee43b207a7d1bea46d612d1bbc079831/src/controller/linmpc.jl#L259C1-L259C80

@franckgaga
Copy link
Member

franckgaga commented May 20, 2025

It is possible right now only if the plant model is identical in the state estimator and in the linear mpc, hence only with linear plant model.

The only reason was to simplify the constructor (I fetch the plant model at estim.model, thus no need for an additional model argument when you specify a custom state estimator). Otherwise it could be done yes.

My only concern is how to "enforce" that the state-space realization in the state estimator is close enough to the state-space realization of the controller model (if there is two arguments at construction). The only thing we can check is the state vector length, it seems to me.

Another solution would be #142. The CustomEstimator would include the necessary information about the linear plant model and the unmeasured disturbances (i.e. the augmentation scheme) to allow the construction of the linear MPC. And then calling preparestate! and updatestate! would do nothing except receiving an external $\mathbf{\hat{x}}$ value computed by the user.

@baggepinnen I think this feature is supported in JuliaSimControl ? How do you deal these aspects if that's the case?

@1-Bart-1
Copy link
Author

Both a CustomEstimator and a nonlinear estimator in LinMPC would be really useful to me.

@franckgaga
Copy link
Member

Yeah so the CustomEstimator solution would kill two birds with one stone. Seems to be best solution (versus adding an additional argument to the LinMPC(estim::StateEstimator, <keyword arguments>) constructor)

@franckgaga
Copy link
Member

I suppose it would be for a successive linearization MPC? What I have in mind is an API like this:

model = NonLinModel( ... )
x_init, u_init = ...      # specify the initial linearization point here
linModel = linearize(model, x=x_init, u=u_init)
nint_ym = ...             # specify a custom model augmentation scheme here if needed
custom = CustomEstimator(linModel, nint_ym)
mpc = LinearMPC(custom)
estim = UnscentedKalmanFilter(model, nint_ym)
for i=1:10
    y = ...                             # get current measured outputs= preparestate!(estim, y)         # correct UKF state estimate
    preparestate!(mpc, y)               # do nothing at all, can be omitted
    setstate!(mpc, x̂)                   # update MPC with the UKF corrected state 
    u = moveinput!(mpc)
    x = x̂[1:end-sum(nint_ym)]
    linearize!(linModel, model, x, u)
    setmodel!(mpc, linModel)
    x̂ = updatestate!(estim, u, y)       # update UKF state estimate
    updatestate!(mpc, u, y)             # do nothing except store u as the lastu value
    setstate!(mpc, x̂)                   # update MPC with the UKF updated state
end

Would it be something that match your need ?

@baggepinnen
Copy link
Member

I think this feature is supported in JuliaSimControl ? How do you deal these aspects if that's the case?

The optimizer isn't concerned with how the state estimate is produced, it can come from anywhere. In our higher-level simulation routines, we allow any estimator that produces a state of the correct dimension. In practice, it will of course only work if the state represents the same thing in the controller and optimizer model. The state of the "actual" system and the state of the estimator are separate things in JSC, making it easy to use any estimator with any model.

@1-Bart-1
Copy link
Author

1-Bart-1 commented May 21, 2025

I suppose it would be for a successive linearization MPC?

Yes

Would it be something that match your need ?

Yes I think that makes a lot of sense

@franckgaga
Copy link
Member

franckgaga commented May 21, 2025

I'm shedding bike here, but CustomEstimator might not be the most descriptive name, comes to think of it.

In essence, the new type would behave like this: "when you will call preparestate! and updatestate! it won't change the state estimate at all, you need to manually call setstate! on it yourself". Maybe DummyEstimator or LazyEstimator ?

You can vote:

👍 CustomEstimator
❤️ DummyEstimator
🚀 LazyEstimator
👀 other

@baggepinnen
Copy link
Member

baggepinnen commented May 21, 2025

NoEstimator or ManualEstimator? It sounds like the option turns off the built in estimator?

@franckgaga
Copy link
Member

Yes it turn off the builtin estimator. The object is only there to pass the necessary information to construct the predictive controller (e.g. the state augmentation scheme).

I like ManualEstimator, it says what it does. Go for it!

@franckgaga franckgaga self-assigned this May 21, 2025
@franckgaga
Copy link
Member

franckgaga commented May 24, 2025

FYI @1-Bart-1

I decided to move the storing of lastu field in moveinput! instead of updatestate! (this value is needed to convert input increments to inputs). This change will slightly simplifies the API for the upcoming ManualEstimator, that is, updatestate!(mpc, u, y) will be no longer needed:

model = NonLinModel( ... )
x_init, u_init = ...      # specify the initial linearization point here
linModel = linearize(model, x=x_init, u=u_init)
nint_ym = ...             # specify a custom model augmentation scheme here if needed
man = ManualEstimator(linModel, nint_ym)
mpc = LinearMPC(man)
estim = UnscentedKalmanFilter(model, nint_ym)
for i=1:10
    y = ...                             # get current measured outputs= preparestate!(estim, y)         # correct UKF state estimate
    preparestate!(mpc, y)               # do nothing at all, can be omitted
    setstate!(mpc, x̂)                   # update MPC with the UKF corrected state 
    u = moveinput!(mpc)
    x = x̂[1:end-sum(nint_ym)]
    linearize!(linModel, model, x, u)
    setmodel!(mpc, linModel)
    x̂ = updatestate!(estim, u, y)       # update UKF state estimate
    updatestate!(mpc, u, y)             # CHANGED: do nothing at all, can be omitted
    setstate!(mpc, x̂)                   # update MPC with the UKF updated state
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants