tinympc-rs
Rust port of TinyMPC with support for no_std environments
Showcase
Given some discrete time state space model $x_{k+1} = A x_k + B u_k$ with $N_x$ states and $N_u$ inputs, we may write this in code using consts and macros (from nalgebra) for convenience.
use nalgebra as na;
// Number of states and inputs
const NX: usize = .. ;
const NU: usize = .. ;
// System dynamics
const A: SMatrix = matrix!;
const B: SMatrix = vector!;
For LQR and MPC we typically have a cost matrix associated with the state and input deviating from their references. For simplicity these are defined using vectors representing the diagonal of such matrices.
// State and input error cost vectors
const Q: SVector = vector!;
const R: SVector = vector!;
We define the prediction horizon length $H_x$ and control horizon length $H_u$ where $H_x > H_u$. We also choose an ADMM penalty parameter $\rho$ (rho), and choose a caching strategy. Here we use the const-sized LookupCache with 5 elements.
// Set the prediction and control horizon length
const HX: usize = .. ;
const HU: usize = .. ;
// The ADMM penalty parameter. Does not have to be const
let rho = 2.0;
// Define shorthands for cache and mpc types
type Cache = ;
type Mpc = ;
// Run all precomputations, and unwrap on error if
// something went wrong or was misconfigured.
let mut mpc = new.unwrap;
// Run a maximum of --vv-- iterations per solve
mpc.config.max_iter = 15;
The power of MPC comes from its ability to handle constraints. Constraints in tinympc-rs are flexible and composable thanks to rusts Trait system:
- A single constraint holds dual/slack variables and a "projector"
- Projectors implement the
Projecttrait and push points into feasible regions - Multiple projectors can be combined using tuples or arrays
// The constraints are defined by so called "projectors"
// which are just types that are able to push the state
// or inputs back into the feasible domain.
let x_projector = ;
// Or we can just use a single projector
let u_projector = Box ;
// The constraint() method automatically creates and manages
// the dual and slack variables needed by the optimizer
// internally. These are arraya since we can also provide
// multiple individual constraints.
let mut x_con = ;
let mut u_con = ;
With everything set up, we just need to run it! For this example we want our states to track a reference trajectory defined by the matrix x_ref below. We also have the option to provide a reference to the inputs. Now, all of the magic (math and optimizations) lies under the hood of that call to solve().
loop