# \[ANN\] ModelPredictiveControl.jl

**URL:** <https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147>\
**Category:** Package Announcements\
**Tags:** control, jump, controlsystems, nmpc\
**Created:** [June 10, 2023, 6:10pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147 "2023-06-10T18:10:43Z")\
**Posts on this page:** 20\
**Page:** 3

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [March 20, 2025, 9:05pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/41 "2025-03-20T21:05:43Z")

</div>

**ModelPredictiveControl v1.5.0**

An update to announce the migration to DifferentiationInterface.jl. Many thanks to @gdalle for all the help! 😄

In addition to a simpler and more maintainable codebase, it allows to switch the differentiation backend for gradients and Jacobians inside [`NonLinMPC`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/predictive_control/#NonLinMPC), [`MovingHorizonEstimator`](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/public/state_estim/#ModelPredictiveControl.MovingHorizonEstimator), [`linearize`](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/public/sim_model/#ModelPredictiveControl.linearize) and [`ExtendedKalmanFilter`](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/public/state_estim/#ModelPredictiveControl.ExtendedKalmanFilter). Sparse Jacobians are also supported with `AutoSparse`. Dense ForwardDiff.jl computation are used everywhere by default, except for the `MultipleShooting` transcription that uses sparse computations. Note that for small problems like the inverted pendulum with H\_p=20 and H\_c=2, dense Jacobians may be slightly faster than sparse matrices, even with a `MultipleShooting` transcription. At least, that’s what I benchmarked for this case study.

Note that the implementation rely on the [`Cache`](https://juliadiff.org/DifferentiationInterface.jl/DifferentiationInterface/stable/explanation/backends/#Features) feature of DI.jl to reduce the allocations, and some backend does not support it for now.

The change log since my last post is:

- added: migration to `DifferentiationInterface.jl`
- added: new `gradient` and `jacobian` keyword arguments for `NonLinMPC`
- added: new `gradient` and `jacobian` keyword arguments for `MovingHorizonEstimator`
- added: new `jacobian` keyword argument for `NonLinModel` (for linearization)
- added: new `jacobian` keyword argument for `ExtendedKalmanFilter`
- added: `ExtendedKalmanFilter` is now allocation-free at runtime
- changed: deprecate `preparestate!(::SimModel,_,_)`, replaced by `preparestate!(::SimModel)`
- debug: nonlinear inequality constraint with `MultipleShooting` now work as expected (custom + output + terminal constraints)
- debug: `x_noise` argument in `sim!` now works as expected
- doc: now using DocumenterInterLinks.jl to ease the maintenance
- test: many new test with `AutoFiniteDiff` backend
- test: new test to cover nonlinear inequality constraint with `MultipleShooting` corner cases

I will release the update soon.

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [May 26, 2025, 3:18pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/42 "2025-05-26T15:18:43Z")

</div>

**ModelPredictiveControl v1.7.0**

A quick update on the new stuff in the package since my last post.

First, the newest release introduces the [`ManualEstimator`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/state_estim/#ManualEstimator) to turn off built-in state estimation and provide your own estimate. A first use case is to implement a linear MPC (with an approximate plant model, for the speed) with a nonlinear state estimator (with a high fidelity plant model, for accuracy). A second use case is using the exclusive observers from LowLevelParticleFilters.jl to estimate the state of the plant model and its disturbances.

Also, a significant performance boost for `NonLinMPC` and `MovingHorizonEstimator` was introduced in v1.6.0 by the more efficient `value_and_gradient!` and `value_and_jacobian!` of DI.jl. This is equivalent of using DiffResults.jl, but agnostic of the differentiation backend. I benchmarked about a 1.25x speed boost on the pendulum example of the manual.

Lastly, the `nint_u` option for the `MovingHorizonEstimator` was not working well because of a bug when the observation window is not filled (at the beginning). The bug was corrected in v1.6.2 (with new unit tests).

The next release will introduce custom move blocking, which is a way of specifying long control horizon H\_c without increasing the number of decision variables in the optimization problem.

The changelog since my last post is:

- added: `ManualEstimator` to turn off built-in state estimation and provide your own estimate \mathbf{\hat{x}}\_{k}(k) or \mathbf{\hat{x}}\_{k-1}(k)
- added: slightly improve `NonLinMPC` performances with specialized conversion and weight matrices
- added: significant performance boost of `NonLinMPC` and `MovingHorizonEstimator` using `value_and_gradient!`/`jacobian!` of `DifferentiationInterface.jl` instead of individual calls
- added: `setstate!` now allows manual modifications of the estimation error covariance \mathbf{\hat{P}} (if computed by the estimator)
- changed: `M_Hp`, `N_Hc` and `L_Hp` keyword arguments now default to `Diagonal` instead of `diagm` matrices for all `PredictiveController` constructors
- changed: moved `lastu0` inside `PredictiveController` objects
- removed: `DiffCache`s in `RungeKutta` solver
- debug: force update of gradient/jacobian in `MovingHorzionEstimator` when window not filled
- debug: remove `.data` in `KalmanFilter` matrix products
- debug: do not call `jacobian!` if `nd==0` in `linearize`
- debug: no more noisy `@warn` about `DiffCache` chunk size
- test: new tests for `ManualEstimator`
- test: added allocations tests for types that are known to be allocation-free (SKIP THEM FOR NOW)
- test: adapt tests for the new automatically balancing `minreal` function

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [July 16, 2025, 7:48pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/43 "2025-07-16T19:48:23Z")

</div>

**ModelPredictiveControl v1.8.2**

Here’s a quick update on the new stuff in the package.

First v1.8.0 introduces custom move blocking patterns. This parameter changes the duration in time steps of each move blocks in the profile of the manipulated inputs \mathbf{u}, which is more general than classical control horizons H\_c. See the `H_c` argument in e.g. [`LinMPC`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/predictive_control/#ModelPredictiveControl.LinMPC) for detail. Note that an intrinsic side-effect of custom move blocking is a reduced accuracy for warm-starting the optimizer.

Also, the last two versions incorporate some debugging and performance improvements for [`MovingHorizonEstimator`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/state_estim/#ModelPredictiveControl.MovingHorizonEstimator). It is common that covariance matrices are strictly diagonal, especially with trial-and-error tuning. The code now preserves `Diagonal` types to accelerate computations in the objective function. Also, problematic termination status at optimization will not crash anymore (it will emits an `@error` and return the open-loop estimation).

Last but not the least, I worked hard to implement a quite comprehensive benchmark suite that [runs automatically on CI](https://github.com/JuliaControl/ModelPredictiveControl.jl/pull/227#issuecomment-3079663807) with AirspeedVelocity.jl (excellent job @MilesCranmer! btw excited for the feature at [Emojis in ratio column by MilesCranmer · Pull Request #91 · MilesCranmer/AirspeedVelocity.jl · GitHub](https://github.com/MilesCranmer/AirspeedVelocity.jl/pull/91), will help for the overview). It will certainly help me to track the performance of the package over time. And now there will be proof that the future development incorporate performance improvements, and no regression, hopefully!

For now, I continuously monitor the the performance of OSQP.jl, DAQP.jl, Ipopt.jl and MadNLP.jl. Note that MadNLP.jl is generally 2-3 times faster than Ipopt.jl, but it does not work well for tightly constrained problem (e.g.: multiple shooting). It’s probably related to its L-BFGS approximation. I would be curious to see the performance with exact Hessians, but it’s not supported by ModelPredictiveControl.jl for now. For differentiation, only dense and sparse ForwardDiff.jl is monitored, but I may add other backend in the future.

BTW @MilesCranmer, is there a way to preserve the order of definition in `SUITE` in the outputted table? Or maybe specify the order manually? Some benchmark are interlinked and I would like them to be near each other.

I will register the version soon.

Change log:

- added: move blocking feature in `LinMPC`, `ExplicitMPC` and `NonLinMPC` (see `Hc` argument)
- added: new `KalmanCovariance` parametric struct to handle covariance sparsity efficiently
- added: dispatch on `repeatdiag` to preserve `Diagonnal`s
- added: in-place and allocation-free `inv!` for MHE covariance matrices
- added: new benchmark suite that runs automatically on each PR with AirspeedVelocity.jl
- changed: store `P̃Δu`, `P̃u` and `Tu` conversion matrices as `SparseMatrixCSC`
- debug: support `Hermitian` weights in `PredictiveController`
- debug: use dummy `b` vector in MHE `setmodel!` to avoid `±Inf` values
- debug: `MovingHorizonEstimator` no longer crash with error termination flag
- test: verify `setmodel!` with `He>1` for MHE
- test: new integration with `ManualEstimator` and MPCs
- test: improve coverage with error termination status
- doc: various improvements

---

<div class="post-metadata">

**Author:** ![MilesCranmer](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/milescranmer/32/21070_2.png) [@MilesCranmer](https://discourse.julialang.org/u/MilesCranmer)\
**Post date:** [July 17, 2025, 10:43am UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/44 "2025-07-17T10:43:13Z")

</div>

> [@franckgaga](#):
>
> BTW @MilesCranmer, is there a way to preserve the order of definition in `SUITE` in the outputted table? Or maybe specify the order manually? Some benchmark are interlinked and I would like them to be near each other.

It’s sorted by key, alphabetically: [AirspeedVelocity.jl/src/Utils.jl at master · MilesCranmer/AirspeedVelocity.jl · GitHub](https://github.com/MilesCranmer/AirspeedVelocity.jl/blob/master/src/Utils.jl#L534)

What I do is have a nested SUITE, which groups the benchmarks and results in the output being clustered in a sensible way. Like:

```julia-auto
const SUITE = BenchmarkGroup()

for param in [1, 2, 3]
    SUITE["eval"]["type_1"]["param=$(param)"] = @benchmarkable ..
end

```

Note that a BenchmarkGroup will automatically instantiate the nested BenchmarkGroup when you do the `get`, so no need to worry about doing it manually.

```julia-auto

```

---

<div class="post-metadata">

**Author:** ![bernalde](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bernalde/32/4267_2.png) [@bernalde](https://discourse.julialang.org/u/bernalde)\
**Post date:** [July 31, 2025, 12:47am UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/45 "2025-07-31T00:47:10Z")

</div>

I just stumbled upon this post today and spent the whole 17 minutes reading through the developments of the package. I just wanted to say that it was a pleasure tracking all your development and looking forward to what comes up afterwards!  
My only 2 cents, since you were already looking into coming up with other integrators natively beyond RK4, orthogonal collocation using Gauss-Legendre quadrature is not much more complicated and might earn you some extra stability at not a significant cost.  
Another thing that might be of interest is including robust (N)MPC via scenario trees, which has been done here [https://www.do-mpc.com](https://www.do-mpc.com)

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [August 1, 2025, 1:25pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/46 "2025-08-01T13:25:06Z")

</div>

Thanks for the feedback, really appreciated! Yes my next step is supporting collocation as a new transcription method. I will probably implement trapezoidal integration first for its simplicity, but Gauss-Legendre will come after.

For robust MPC, it is not planned in the short term but maybe if I have time in the mid-long term.

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [August 30, 2025, 8:47pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/47 "2025-08-30T20:47:48Z")

</div>

**ModelPredictiveControl v1.9.0**

The new release introduces a new [`TrapezoidalCollocation`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/predictive_control/#ModelPredictiveControl.TrapezoidalCollocation) to handle moderately stiff `NonLinModel`! 🍾🍾🍾

This is presumably the simplest form of all the direct collocation methods. It internally uses the implicit trapezoidal rule, which is efficiently solved in the nonlinear equality constraints of the NLP. This is the transcription method in MATLAB’s `nlmpc` when the [dynamics are continuous](https://www.mathworks.com/help/mpc/ug/specify-prediction-model-for-nonlinear-mpc.html) by the way. It currently assumes piecewise constant manipulated inputs \mathbf{u} (a.k.a. ZOH, like all the other integrators in the package), but I will add linear interpolation soon. The number of decision variables is identical to `MultipleShooting`, so the computational cost are similar. As a matter of fact, the performances are even a little bit better than `MultipleShooting` with `RungeKutta(4)`:

| | main |
| --- | --- |
| Pendulum/NonLinMPC/Custom constraints/Ipopt/ **MultipleShooting** | **1.05 ± 0.0072 s** |
| Pendulum/NonLinMPC/Custom constraints/Ipopt/SingleShooting | 0.866 ± 0.017 s |
| Pendulum/NonLinMPC/Custom constraints/Ipopt/ **TrapezoidalCollocation** | **1.06 ± 0.024 s** |
| Pendulum/NonLinMPC/Economic/Ipopt/ **MultipleShooting** | **0.515 ± 0.0067 s** |
| Pendulum/NonLinMPC/Economic/Ipopt/SingleShooting | 0.242 ± 0.019 s |
| Pendulum/NonLinMPC/Economic/Ipopt/ **TrapezoidalCollocation** | **0.485 ± 0.0044 s** |
| Pendulum/NonLinMPC/Noneconomic/Ipopt/ **MultipleShooting** | **0.486 ± 0.017 s** |
| Pendulum/NonLinMPC/Noneconomic/Ipopt/SingleShooting | 0.245 ± 0.016 s |
| Pendulum/NonLinMPC/Noneconomic/Ipopt/ **TrapezoidalCollocation** | **0.458 ± 0.0061 s** |

I also did some tests with the new experimental `Ipopt._VectorNonlinearOracle`. This is very promising since I benchmarked a 2.5-fold speedup on `NonLinMPC` with `MultipleShooting` and custom constraints. It will also allow exact Hessians for the Lagragian function, which in turn can improve the performance if the second-order differentiation is not too heavy (likely the case with `AutoSparse`). I will wait for an official release from JuMP.jl, but I’m super excited to incorporate these changes on the main branch!

This release helps me a lot in understanding collocation methods. I should be able to add orthogonal collocation with Gauss-Legendre in the next releases.

The changelog since my last post:

- added: `TrapezoidalCollocation` method for continuous nonlinear systems
- added: `dependabot.yml` file to help with the CI dependencies
- changed: new fields with the continuous state-space function `model.f!` and `model.h!` in `NonLinModel`s (instead of `model.solver_f!` and `model.solver_h!`)
- test: new test with `TrapezoidalCollocation` and `Ipopt`
- bench: new case studies with `TrapezoidalCollocation` on the pendulum

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [September 19, 2025, 2:23am UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/48 "2025-09-19T02:23:58Z")

</div>

**ModelPredictiveControl v1.11.0**

A quick update on the last features.

First, the version 1.9.1 introduced first-order interpolation on the manipulated inputs \mathbf{u} with `TrapezodialCollocation(1)`, saving one call of the state derivative function \mathbf{f} per time step (without the new multi-threading option). Note that the other ODE solvers in the package assume a zero-order hold, so there will be a slight mismatch between the controller model and a plant simulated by these integrators.

The 1.10 version added the support for a constant arrival covariance \mathbf{\bar{P}} in the `MovingHorizonEstimator` by constructing it with `covestim=SteadyKalmanFilter(model)`. Doing so will fix it at the steady-state covariance \mathbf{\hat{P}}(\infty), a common choice in the litterature, but its also possible to customize the matrix.

Lastly, the version 1.11 introduces multi-threading supports for the [`MultipleShooting`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/predictive_control/#ModelPredictiveControl.MultipleShooting) and [`TrapezoidalCollocation`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/predictive_control/#ModelPredictiveControl.TrapezoidalCollocation) transcription, in the case of `NonLinModel` (linear plant model will be multithreaded if the quadratic optimizer is multithreaded). There are new `f_threads` and `h_threads` keyword arguments to activate multi-threading when calling the state \mathbf{f} and output \mathbf{h} functions in the optimization, respectively. These options are interesting if they are expensive to evaluate. This can be the case when calling a fancy implicit solver from OrdinaryDiffEq.jl in the \mathbf{f} function, for example.

I will register the release soon.

Change log:

- added: multithreading with `MultipleShooting` and `TrapezoidalCollocation`
- added: `covestim` in `MovingHorizonEstimator` now supports `SteadyKalmanFilter` for a constant arrival covariance \mathbf{\bar{P}}
- added: clearer pretty-print of objects with a tree structure
- added: print differentiation backends in `NonLinModel`, `ExtendedKalmanFilter`, `MovingHorizonEstimator` and `NonLinMPC`
- added: piecewise linear manipulated inputs \mathbf{u} with `TrapezoidalCollocation(1)`
- debug: `setmodel!` now works with `LinMPC` based on `MultipleShooting`
- debug: `linearize!` on `NonLinModel` with non-zero operating points now works
- test: multithreading with `MultipleShooting` and `TrapezoidalCollocation`
- test: new test for `setmodel!` with `MultipleShooting`
- bench: case studies with multithreading on the pendulum
- doc: updated the pendulum tutorial to ModelingToolkit.jl v10 in the manual
- doc: using `MultipleShooting` in the manual on the unstable pendulum system
- doc: describing the internal of the various prediction and transcription methods

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [October 23, 2025, 1:02pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/49 "2025-10-23T13:02:33Z")

</div>

**ModelPredictiveControl v1.12.0**

The latest update now supports the new [`MathOptInterface.VectorNonlinearOracle`](https://jump.dev/MathOptInterface.jl/stable/reference/standard_form/#MathOptInterface.VectorNonlinearOracle) for the nonlinear constraints. There is a new `oracle` keyword argument in [`NonLinMPC`](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/public/predictive_control/#ModelPredictiveControl.NonLinMPC) and [`MovingHorizonEstimator`](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/public/state_estim/#MovingHorizonEstimator) to activate this feature. It is activated only for Ipopt.jl by default, since many optimizers do not support it for now. It’s about 2.5 times faster under nonlinear constraints for the pendulum case study 🚀🚀🚀

> **Benchmarks**
>
> | | v1.11.1 | v1.12.0 | v1.11.1 / v1.12.0 |
> | --- | --- | --- | --- |
> | NonLinMPC/Custom constraints/Ipopt/MultipleShooting | 1.07 ± 0.02 s | 0.36 ± 0.03 s | **2.98 ± 0.22** |
> | NonLinMPC/Custom constraints/Ipopt/SingleShooting | 0.98 ± 0.01 s | 0.78 ± 0.02 s | **1.26 ± 0.04** |
> | NonLinMPC/Custom constraints/Ipopt/TrapezoidalCollocation | 1.06 ± 0.02 s | 0.36 ± 0.03 s | **2.96 ± 0.25** |
> | NonLinMPC/Economic/Ipopt/MultipleShooting | 0.52 ± 0.01 s | 0.19 ± 0.01 s | **2.83 ± 0.10** |
> | NonLinMPC/Economic/Ipopt/SingleShooting | 0.25 ± 0.02 s | 0.25 ± 0.02 s | **1.00 ± 0.10** |
> | NonLinMPC/Economic/Ipopt/TrapezoidalCollocation | 0.49 ± 0.02 s | 0.17 ± 0.01 s | **2.85 ± 0.15** |
> | NonLinMPC/Noneconomic/Ipopt/MultipleShooting | 0.52 ± 0.01 s | 0.17 ± 0.01 s | **2.99 ± 0.10** |
> | NonLinMPC/Noneconomic/Ipopt/MultipleShooting (threaded) | 0.54 ± 0.01 s | 0.19 ± 0.02 s | **2.89 ± 0.33** |
> | NonLinMPC/Noneconomic/Ipopt/SingleShooting | 0.26 ± 0.01 s | 0.25 ± 0.02 s | **1.01 ± 0.09** |
> | NonLinMPC/Noneconomic/Ipopt/TrapezoidalCollocation | 0.45 ± 0.01 s | 0.17 ± 0.01 s | **2.64 ± 0.08** |
> | NonLinMPC/Noneconomic/Ipopt/TrapezoidalCollocation (threaded) | 0.47 ± 0.01 s | 0.19 ± 0.02 s | **2.51 ± 0.26** |
> | MovingHorizonEstimator/Ipopt/Current form | 3.3 ± 0.03 s | 2.35 ± 0.01 s | **1.40 ± 0.02** |
> | MovingHorizonEstimator/Ipopt/Prediction form | 1.89 ± 0.01 s | 1.41 ± 0.02 s | **1.34 ± 0.02** |

It’s obviously not faster with single shooting and w/o custom constraint (since there are no NL constraints), but it is worth noting that the multiple shooting is now faster than the single shooting transcription, thus the benchmarks are now in phase with the literature!

Next step is adding the support for exact Hessians.

Change log:

- added: `oracle=true` option in `NonLinMPC` to significantly improve performances under nonlinear constraints 🚀 (default with `Ipopt.jl`)
- added: `oracle=true` option in `MovingHorizonEstimator` to significantly improve performances under nonlinear constraints 🚀 (default with `Ipopt.jl`)
- added: ill-conditioned QP warning at controller construction
- added: `progress` keyword argument in `sim!` to disable progress logs
- removed: less useful workload in `precompile.jl` to accelerate precompilation
- doc: minor precision in strictly proper system assumption

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [January 2, 2026, 10:25pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/50 "2026-01-02T22:25:49Z")

</div>

**ModelPredictiveControl v1.14.4**

It’s been a long time since my last post, so let’s post a little update on the new features and bugfixes.

The support for exact Hessian was added in v1.13, and the following patches introduced performance tweaks on sparse matrix colouring. Exact Hessians can be activated in `NonLinMPC` and `MovingHorizonEstimator` at construction with the option `hessian=true`. Otherwise, the internal quasi-Newton method of the optimizer will be used, as it was the case before. It is also now possible to inspect the gradients, Jacobians and Hessians at the optimum using the [`getinfo!`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/generic_func/#ModelPredictiveControl.getinfo) method.

The last two patches included one bugfix on the default softness parameters of the terminal constraints \mathbf{c\_{\hat{x}\_{min}}} and \mathbf{c\_{\hat{x}\_{max}}}. They can lead to infeasible problems so they should be soft by default, and they were hard by default because of a bug that is now fixed.

The last bugfix was related to custom move blocking patterns using `LinModel`, affecting both `LinMPC` and `NonLinMPC` controllers. The solution of the optimization problem was simply dead wrong, because of a mistake at the construction of the prediction equations and matrices. Turns out that the math are way more complex than I first thought. I needed to revise my notation to rigorously keep track of all the various indices in the block matrices. The new notation is documented in [`init_predmat`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/internals/predictive_control/#ModelPredictiveControl.init_predmat) and [`init_defectmat`](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/internals/predictive_control/#ModelPredictiveControl.init_defectmat), in the extended help sections. BTW, it took me around 50 hours to derive the math correctly, implement it, and add new tests with custom move blocking!

I hit the last bug while I was working on an interface with LinearMPC.jl, to enable lightweight C-code generation for embedded device. The next release should introduce this new interface!

I will register the latest release soon. Happy new year to everyone ! 🎉🍾

The change log since my last post:

- added: `hessian` argument in `NonLinMPC` for exact Hessian of Lagrangian 🎯
- added: `hessian` argument in `MovingHorizonEstimator` for exact Hessian of Lagrangian 🎯
- added: `trunc_cov` function for `MovingHorizonEstimator` (to optimize perf. of `dot` in objective function)
- added: `lastu` keyword argument to `moveinput!`
- added: derivatives in `getinfo` dictionnary for `NonLinMPC` and `MovingHorizonEstimator`
- added: optimal nonlinear constraint vectors in `getinfo`
- changed: new default sparse backend for `jacobian` and `hessian` in `NonLinMPC` and `MovingHorizonEstimator`
- debug: prepare AD with `rand` constant contexts
- debug: only fills non-zeros in `∇²f` of `@operator`
- debug: default terminal constraint softness parameters `c_x̂min`/`max` equal to 1
- debug: correctly handle custom move blocking for `LinModel`

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [January 19, 2026, 1:44pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/51 "2026-01-19T13:44:20Z")

</div>

**ModelPredictiveControl v1.15.0**

The new release introduces an interface with LinearMPC.jl for the `LinMPC` controllers, enabling lightweight C code generation for embedded platforms. These features are implemented as a [package extension](https://pkgdocs.julialang.org/v1/creating-packages/#Conditional-loading-of-code-in-packages-(Extensions)) that will be loaded if `LinearMPC` is present in the current active environment. Many thanks to @darnstrom for all the help during the development!

To do so, one first creates a `ModelPredictiveControl.LinMPC` object, convert it to `LinearMPC.MPC` object, and call `codegen` on the latter:

```julia
using ModelPredictiveControl, ControlSystemsBase, JuMP
import LinearMPC, DAQP
model = setop!(LinModel(tf(2, [10, 1]), 1.0), uop=[50], yop=[20])
mpc1 = LinMPC(model; optim=JuMP.Model(DAQP.Optimizer))
mpc1 = setconstraint!(mpc1, ymax=[45])
mpc2 = LinearMPC.MPC(mpc1)
LinearMPC.codegen(mpc2, dir="code_dir")

```

Note that not all features of ModelPredictiveControl.jl are supported, see the [documentation](https://juliacontrol.github.io/ModelPredictiveControl.jl/dev/public/predictive_control/#Conversion-to-LinearMPC.jl-(code-generation)) for details on this aspect. But the reverse is also true: LinMPC.jl offers exclusive features such as binary manipulated inputs and constrained explicit MPC.

I will probably work on new collocation methods for the next releases.

Change log:

- added: C code generation via `LinearMPC.jl` 🚀
- doc: various improvement in the manual and function sections
- test: new closed-loop tests with `LinMPC` converted to `LinearMPC.MPC`

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [January 19, 2026, 1:49pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/52 "2026-01-19T13:49:42Z")

</div>

Very cool! Have you done any profiling of flash/ram requirements for the generated C code?

For a 500 variable model I currently need about 300 kB flash / RAM on an STM32 where the C code is generated by cvxpygen (and the MPC is defined in cvxpy), on float64.

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [January 19, 2026, 1:57pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/53 "2026-01-19T13:57:51Z")

</div>

No I did not, maybe @darnstrom has some information on this aspect ? I do have an old STM32 lying around somewhere at home, that could be an interesting project for me! It’s been a long time since I’ve worked on this platform. What do you use for the IDE/compiler/uploader @langestefan (preferably on linux) ?

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [January 19, 2026, 2:08pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/54 "2026-01-19T14:08:49Z")

</div>

> [@franckgaga](#):
>
> What do you use for the IDE/compiler/uploader @langestefan (preferably on linux) ?

Just the classic STM32CubeIDE. From here: [https://www.st.com/en/development-tools/stm32cubeide.html#st\_all-features\_sec-nav-tab](https://www.st.com/en/development-tools/stm32cubeide.html#st_all-features_sec-nav-tab)

Works great on linux!

---

<div class="post-metadata">

**Author:** ![darnstrom](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/darnstrom/32/216558_2.png) [@darnstrom](https://discourse.julialang.org/u/darnstrom)\
**Post date:** [January 19, 2026, 5:11pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/55 "2026-01-19T17:11:33Z")

</div>

Do those 500 decision variables include states? LinearMPC.jl eliminates the states, so only the controls are decision variables, but from my experience this is not so easy to do with the current version of CVXPY/CVXPYgen (which can lead to an inflated memory footprint.)

From my experience, the memory footprint and execution time from the code generated by LinearMPC.jl can be significantly faster than code from CVXPYgen for MPC problems, but producing concrete results to strengthen this is still a TODO (I just have some MPC applications I have run myself where I have observed this) I have plans to do a more formal comparison of the generated code with, for example, CVXPYgen, acados, and other packages that support code generation)

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [January 19, 2026, 8:38pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/56 "2026-01-19T20:38:46Z")

</div>

> [@darnstrom](#):
>
> Do those 500 decision variables include states?

Yes they do.

> [@darnstrom](#):
>
> [LinearMPC.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/LinearMPC) eliminates the states, so only the controls are decision variables, but from my experience this is not so easy to do with the current version of CVXPY/CVXPYgen (which can lead to an inflated memory footprint.)

But there is a trade-off here right? Eliminating the state variables changes the structure of your problem matrices. I found that with a good sparse QP solver extra variables are not necessarily a problem. The nice thing about cvxpy+cvxpygen is that you can try a bunch of solvers and the codegen works all the same.

> [@darnstrom](#):
>
> From my experience, the memory footprint and execution time from the code generated by [LinearMPC.jl](https://juliaregistries.github.io/General/packages/redirect_to_repo/LinearMPC) can be significantly faster than code from CVXPYgen for MPC problems, but producing concrete results to strengthen this is still a TODO (I just have some MPC applications I have run myself where I have observed this) I have plans to do a more formal comparison of the generated code with, for example, CVXPYgen, acados, and other packages that support code generation)

For the particular application I am working on robustness of the solver and code size are the most important criteria. The dynamics are slow, so solve time is not important.

I am very curious now to see how LinearMPC.jl + ModelPredictiveControl.jl compares to my current setup 🙂

---

<div class="post-metadata">

**Author:** ![baggepinnen](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/baggepinnen/32/693_2.png) [@baggepinnen](https://discourse.julialang.org/u/baggepinnen)\
**Post date:** [January 20, 2026, 10:03am UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/57 "2026-01-20T10:03:53Z")

</div>

> [@langestefan](#):
>
> I found that with a good sparse QP solver extra variables are not necessarily a problem.

Maybe not a problem for the solution time, but they still lead to large (even if sparse) matrices that must be stored? Take the extreme case of a 1 horizon problem with 1 control input and 500-dimensional state. The QP in LinearMPC would be 1x1, while a sparse problem with state among the decision variables will have at least 500 non-zeros in the hessian?

Sparse arrays take up some memory

```julia-auto
julia> Base.summarysize(spdiagm(randn(500)))
12168

```

but not quite close to your observed memory usage

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [January 20, 2026, 1:57pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/58 "2026-01-20T13:57:43Z")

</div>

> [@langestefan](#):
>
> For the particular application I am working on robustness of the solver and code size are the most important criteria. The dynamics are slow, so solve time is not important.

In terms of numerical robustness, the [original paper of DAQP](https://cse.lab.imtlucca.it/~bemporad/publications/papers/ieeetac_daqp.pdf) showed that it performed better when the condition number of the Hessian is fairly large (\> 10^{10}) and when the prediction horizon is not too high (as it should be in most cases). It also aligns with my experience, although sparse solvers like OSQP can be “robustified” by introducing the state in the decision variables (a.k.a. multiple shoothing, I think all case studies were in a single shooting form in the paper). See [here](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/manual/nonlinmpc/#Model-Linearization) in which I compare:

1. OSQP.jl in single shooting
2. DAQP.jl in single shooting
3. OSQP.jl in multiple shooting

The results of 2. and 3. are very similar on this simple case study. I expect that the robustness will be similar to your current setup.

---

<div class="post-metadata">

**Author:** ![langestefan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/langestefan/32/207923_2.png) [@langestefan](https://discourse.julialang.org/u/langestefan)\
**Post date:** [January 22, 2026, 4:15pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/59 "2026-01-22T16:15:57Z")

</div>

Not sure if I am missing something here, but does ModelPredictiveControl.jl (and LinearMPC.jl) not support time-varying constraints on inputs and states?

---

<div class="post-metadata">

**Author:** ![franckgaga](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/franckgaga/32/218241_2.png) [@franckgaga](https://discourse.julialang.org/u/franckgaga)\
**Post date:** [January 22, 2026, 4:27pm UTC](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147/60 "2026-01-22T16:27:58Z")

</div>

Yes both does. For MPC.jl, you need to call `setconstraint!` with keyword arguments starting with a capital letter. Details [here](https://juliacontrol.github.io/ModelPredictiveControl.jl/stable/public/generic_func/#Extended-Help-b7a0f6a55d4d3bee).

[Previous page](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147.md?page=2)

[Next page](https://discourse.julialang.org/t/ann-modelpredictivecontrol-jl/100147.md?page=4)
