ACADOSMPC ​
Nonlinear model-predictive controller built on acados (https://github.com/acados/acados), run as a clocked component. The C library is called directly from Julia; the prediction model is given symbolically and integrated by acados.
Model ​
The prediction model is the continuous-time system
with state ContinuousDynamics object (see continuous_dynamics: from Symbolics expressions, from a traceable Julia function, or from a continuous-time Dyad/ModelingToolkit model whose inputs are named). Every parameter of such a model is substituted by its value when the controller is built, so that the compiled solver carries numbers rather than symbols; those named by tunable_parameters are the exception and stay symbolic as the online parameters p_values (see Tunable parameters below). acados discretizes it over Np shooting intervals of length Ts with an explicit (ERK, default RK4) or implicit (IRK) Runge-Kutta integrator (integrator_stages stages, integrator_steps sub-steps per interval; collocation selects Gauss-Legendre or the L-stable Radau IIA scheme for the implicit one, and integrator_newton_iter the Newton iterations it takes per step). time_steps replaces that uniform grid by one of Np intervals of the given lengths: the first is always Ts (its control is the one that is applied and held for a clock period) and the later ones may be longer, which covers a long prediction horizon with few decision variables. geometric_time_steps(Ts, Np, Tf) and linear_time_steps(Ts, Np, Tf) lay out such a grid over the horizon integrator_steps sub-steps as a short one unless integrator_steps_per_interval supplies a vector of sub-step counts. Choose enough sub-steps to preserve integration accuracy over every interval. The prediction model must not contain events. The controlled outputs are outputs).
Objective ​
with reference_preview provides one per prediction step. With time_steps the term of stage Q1, Q2, Qf and Q3 are full matrices (diagonal([1.0, 10.0]) builds a diagonal one) and ordinary, tunable parameters; by default Q1 = Q2 = I, Qf = Q1 and Q3 = 0. terminal_lqr_cost = true replaces the terminal term by operating_point (as DyadControlSystems' QMPCProblem does by default, at the origin), which lets a short horizon behave like the infinite-horizon regulator around that point; Qf is then ignored. The operating point (an equilibrium, e.g. the upright state of an inverted pendulum) defaults to the origin, is tunable, and is independent of the tracked reference r, so terminal_lqr_cost may be combined with reference_tracking. u_operating_point is the control there (fixed at build). Without the augmentation (penalize_increments or a control-rate bound, see below), Q3 must be zero and Q1 and Q2 must be positive definite. With penalize_increments = true the model is augmented with the plant's own control as a state and the control rate is the decision variable, so the increment over a shooting interval is weighted by Q3 (positive definite) and Q1 and Q2 may then be singular (Q2 = 0 penalizes the increments only). Either integrator works; the augmented model represents the control as ramping across a shooting interval where the actuator holds it, a mismatch that vanishes as the solution converges. On a non-uniform time_steps grid, increments and their bounds refer to each complete shooting interval. normalize_increment_penalty = true scales the increment weight by (Ts/time_steps[k])^2, so Q3 penalizes the average control rate expressed as an increment over Ts. With the stage duration weight time_steps[k]/Ts, this preserves the uniform-grid rate-penalty interpretation. The default false penalizes complete-interval increments. The option affects neither increment bounds nor endpoint input costs or constraints; both choices coincide on a uniform grid. Stage input costs and input-dependent constraints use the ramp endpoint. The augmentation on a non-uniform grid requires the Julia backend. A Discrete prediction model applies one period of its map per shooting interval and requires a uniform grid. With reference_tracking = false the controller regulates to the origin and has no r input.
eu and Eu add the linear control cost Q2 whenever the cost is nonzero. With increment augmentation both backends evaluate the economic term directly, so Q2 = 0 is supported. Both economic weights are tunable. Nonlinear stage and terminal constraints, including soft constraints, and nonlinear least-squares outputs can be combined with increments on both backends. Stage constraints are evaluated at the stage boundary using the ramp endpoint command. The first ramp starts at the previously applied command; the returned command is its endpoint.
Constraints ​
Control bounds umin ≤ u_k ≤ umax (hard, all stages), control-rate bounds dumin ≤ u_k - u_{k-1} ≤ dumax (hard, all stages, the first taken against the control last applied) and state bounds xmin ≤ x_k ≤ xmax for state_bounds_soft) with the quadratic slack penalty soft_weight. General linear constraints lb ≤ Ax x_k + Au u_k ≤ ub are hard and enforced at Au is empty). Terminal constraints lbN ≤ AxN x_{N_p} ≤ ubN (given by terminal_constrained/terminal_constrained_min/terminal_constrained_max, or the matrix form AxN/lbN/ubN) are enforced only at the terminal state — a terminal set — and are hard unless terminal_constraints_soft. Nonlinear constraints lh ≤ h(x, u, z) ≤ uh (nl_constrained, and nl_terminal_constrained for the terminal state) are arbitrary nonlinear functions of the signals (acados BGH), supported by both backends; both can be softened with slack variables (nl_constraints_soft, nl_terminal_constraints_soft). Empty bounds mean "none".
A stage constraint of the form nl_convex_inner (the signals nl_convex_outer (a Julia function of the symbolic vector nl_convex_min/nl_convex_max. acados then adds the curvature of nl_constrained at the stages (acados has one nonlinear constraint module per stage; a plain constraint is the convex form with the identity outer function), obeys nl_constraints_soft and nl_constraints_from_stage alike, and is not available with C code export.
Scaling ​
x_scale and u_scale give the typical magnitude of each state variable (in the state_variables order) and of each control. The optimization runs in the scaled variables Q2 and Q3 are given in physical units and apply to the physical control. du_scale sets the typical physical increment per interval independently of u_scale; the rate decision is the physical increment divided by du_scale. Empty uses u_scale * Ts for continuous models and u_scale for discrete models. The physical ramp, cost and bounds are unchanged.
The rate bounds need the same augmentation as the increment penalty and switch it on by themselves: dumin/dumax alone leave Q3 at zero, so an actuator rate limit does not impose an increment penalty (and Q3 stays tunable afterwards). They carry the restrictions the augmentation carries — no non-uniform grid, no DAE model, no nonlinear outputs or constraints.
Signals and timing ​
At every tick the state x (measured or estimated), the reference r (a setpoint, or a trajectory with reference_preview) and the parameters p (current values, or a trajectory with parameter_preview) are read, the NLP is solved with the previous solution as the initial guess (warm_start; at the first tick of a simulation the solver starts afresh from the current state, or from x_guess when it is given, so that a compiled model can be simulated again without inheriting the trajectories of the previous run), and the first control of the optimal sequence is output as u. The control must be held (ZeroOrderHold) outside this component, and Ts must equal the period of the clock. exitflag is the acados status of the last solve: 0 success, 1 NaN detected, 2 maximum iterations, 3 minimum step, 4 QP failure, 5 ready, 6 unbounded, 7 timeout; on failure u is the solver's last iterate.
The default warm_start = Shift moves the previous solution one stage back and repeats its last stage. When the last control of a solution influences nothing the cost weights, it is determined only by the control weight and differs from the controls before it, and the shift places it where it does matter. This is the case when the outputs are algebraic variables, since the terminal stage carries none. On a strongly nonlinear model the SQP iterations that correct it can then diverge, and warm_start = Keep is the better initial guess (see PHNeutralizationMPCExample).
Previews ​
With reference_preview the r input carries a whole horizon as a flat column-major vector of ny*Np entries, in the layout of the LinearMPC components: r[(j-1)*ny + i] is the reference for output i at prediction step j, i.e. for the predicted state j; the terminal cost uses block Np. A block is tied to a shooting interval, not to a fixed span of time, so with a non-uniform time_steps grid the blocks are the values at the shooting nodes rather than samples of the trajectory at the clock rate (a DelayLine, which taps the signal every Ts, produces the latter). Likewise parameter_preview widens p to np*Np entries, p[(j-1)*np + i] being the value of parameter i over shooting interval j (the terminal stage reuses block Np). A DelayLine turns a signal known ahead of time into such a vector in one connection (see ACADOSMPCPreviewExample). With terminal_lqr_cost and reference_preview the terminal cost is centered on the steady state of the linearized model for the final reference block, ny ≤ nu) and a full-rank steady-state map, checked at build. The terminal weight itself stays the one of the operating point, and in exported C both the weight and
With output_trajectories = true the component also records, at every tick, what the solver predicted and how well it converged, in the variables x_pred (the predicted states at stages 0..Np, column-major nx × (Np+1), in the order of the x input), u_pred (the predicted controls at stages 0..Np-1, column-major nu × Np), residuals (the stationarity, equality, inequality and complementarity residuals of the last iteration, to compare with tol) and iterations (SQP iterations of the tick). Read them from a solution as sol[model.mpc.x_pred] (one vector per tick), or visualize them with mpc_gui (a Makie backend must be loaded). This is a debugging aid for the Julia backend (backend = C is refused). With SQP the residuals are those of the returned iterate; with SQP_RTI acados computes (only then) the residuals of the iterate the tick linearized at, i.e. how far from optimal the real-time iteration started.
Solver ​
SQP iterates to the tolerance tol (at most max_iter iterations); SQP_RTI performs one iteration per tick (real-time iteration, relies on warm_start = Shift). The QP is solved by HPIPM after partial condensing to qp_cond_N stages (-1 keeps the horizon) or full condensing, or by DAQP after full condensing (FullCondensingDAQP, in an acados built with it). The Hessian is the Gauss-Newton approximation of the least-squares cost, plus the curvature of the outer function of the convex-over-nonlinear constraints when there are any.
tol is the default absolute stopping threshold for stationarity, dynamics defects, inequality violations and complementarity in the scaled optimization coordinates, and must be positive. Independent structural thresholds nlp_tol_stat, nlp_tol_eq, nlp_tol_ineq and nlp_tol_comp override the corresponding residual threshold when positive; zero follows the tunable tol. Positive overrides are unaffected by changes to tol, and require nlp_solver = SQP, since SQP_RTI performs a single iteration without a stopping test. With qp_tol_strategy = Fixed, the QP tolerances qp_tol_stat, qp_tol_eq, qp_tol_ineq and qp_tol_comp are independent positive thresholds; a zero value follows the effective NLP threshold of the same residual. With qp_tol_strategy = AdaptiveCurrentResidualJoint, SQP sets the QP accuracy from the largest NLP residual: the joint threshold is capped at 0.01 and reduced by qp_tol_reduction_factor, with qp_tol_safety_factor times the effective NLP threshold of each residual as a lower bound. The cap does not apply to this lower bound, so an NLP threshold above 0.01 / qp_tol_safety_factor yields a QP threshold above 0.01, and a warning is issued when the controller is constructed. Positive fixed QP tolerances cannot be combined with this strategy. The adaptive strategy requires SQP.
with_adaptive_levenberg_marquardt = true replaces constant regularization by the current objective times adaptive_levenberg_marquardt_obj_scalar times a damping factor. The damping starts at adaptive_levenberg_marquardt_mu0 for each solve, decreases after full steps, increases after reduced steps, and is bounded below by adaptive_levenberg_marquardt_mu_min. The multiplier adaptive_levenberg_marquardt_lam determines these changes. The constant tunable levenberg_marquardt is ignored in this mode. These options and the QP accuracy strategy are structural parameters. globalization_alpha_reduction controls backtracking, and globalization_full_step_dual selects a full multiplier update when the primal step is reduced.
Robustness ​
reset_on_failure = true retries a solve that failed (NaN, QP failure) once from a fresh initial guess at the current state, which is what keeps a shifted warm start from carrying a broken iterate from tick to tick.
Tunable parameters ​
The weights Q1, Q2, Q3, Qf, the linear control cost eu, Eu, the solver settings tol, levenberg_marquardt and max_iter, and the operating_point are ordinary parameters, so a parameter of an enclosing component may be passed down to them: they can be changed after the model is compiled, and in exported C code they are fields of the parameter struct. acados is updated only when a value changes. max_iter can be lowered but not raised above the value the model was built with (acados sizes its memory by it; a larger value is clamped with a warning). With terminal_lqr_cost the terminal weight follows Q1, Q2 and the operating point in Julia (a new operating point re-linearizes the model and re-solves the Riccati equation) but stays at its export-time value in C. The horizon, constraints and solver choices are structural and fixed at build time, and so is the structure of the prediction model.
Parameters of the prediction model are tunable when they are named by tunable_parameters:
mpc = ACADOSMPC(dynamics = plant, inputs = [\"tau\"], tunable_parameters = [\"m\", \"c\"], ...)
prob = ODEProblem(ssys, [ssys.mpc.p_values => [2.0, 0.2]], (0.0, 5.0))p_values then holds their values (by default the ones the model carries), is an ordinary parameter like the weights, and is a field of the parameter struct in exported C. It is written to acados before every solve, so the compiled controller predicts with the values the problem was built with; being a parameter, it does not change during a simulation. To vary the parameters over a simulation, or to give each shooting interval its own values, connect the p input instead (it exists whenever the model has online parameters and parameter_input is true, which is the default unless tunable_parameters names them; parameter_preview widens it to one block per interval). With terminal_lqr_cost the terminal weight is linearized at the values the model was built with and does not follow p_values.
A parameter of a subcomponent is named hierarchically, tunable_parameters = [\"link.m\"], and state_variables, outputs and the other signal lists take the same spelling (ACADOSMPCNestedParameterExample). Only a parameter that survives into the compiled model can be named: one bound to a parameter of an enclosing component (link = Plant(final m = m)) is substituted before the equations are built, so the parameter to name is the one it is bound to, and naming the bound one is rejected rather than left as an entry of p that the dynamics ignore. The values reach acados as doubles, which limits what a parameter may be: an integer-valued parameter of the model is kept online, but nothing constrains its entry of p_values to integers, and a parameter the model uses as a Boolean (the condition of an ifelse) cannot be online at all. An array-valued parameter named whole (\"k\") contributes each of its elements, in order; named by element (\"k[1]\"), the elements not named keep their values.
Measured disturbances ​
An input of the plant whose value is measured rather than decided, such as a measured inflow, is named by known_disturbance_inputs. The controller does not optimize it; it becomes an online parameter of the prediction model, whose value is the corresponding entry of p, after the entries of tunable_parameters, and is held over the horizon unless parameter_preview supplies a trajectory. The same plant component can then serve as the simulated plant, as the prediction model and, with the measured input listed in its inputs, as the model of a UKFObserver (PHNeutralizationMPCExample).
Notes ​
acados comes from DyadAcados_jll; on a platform without JLL binaries MPCComponents.install_acados!() builds it into a scratch space, and ACADOS_INSTALL_DIR points at an existing installation. Models containing this component are exported to C with the CCodeExport analysis (backend = C). Further acados features (exact Hessians, the QP solvers other than HPIPM and DAQP) are not exposed yet; build the controller with build_acados_mpc in Julia to pass options as keywords.
Usage ​
MPCComponents.ACADOSMPC(max_iter=100, u0=_acados_u0(mpc), Q1=_acados_Q1(mpc), Q2=_acados_Q2(mpc), Q3=_acados_Q3(mpc), Qf=_acados_Qf(mpc, Q1), eu=_acados_eu(mpc), Eu=_acados_Eu(mpc), tol=_acados_tol(mpc), levenberg_marquardt=_acados_levenberg_marquardt(mpc), operating_point=_acados_operating_point(mpc), r_dummy=fill(0.0, nr_eff), p_values=_acados_p_default(mpc, np_eff))
Parameters: ​
| Name | Description | Units | Default value |
|---|---|---|---|
dynamics | Continuous-time prediction model ẋ = f(x, u, p): a ContinuousDynamics (see continuous_dynamics), or a continuous-time system/plant component whose control inputs are named by inputs: a subcomponent of the model (e.g. the plant), or a component constructor (e.g. MyPlant, or component_constructor(MyPlant, m = m) with modified arguments), which is instantiated | – | |
inputs | Names of the control inputs of dynamics when it is given as a system/plant component (e.g. ["tau"]); empty when dynamics is already a ContinuousDynamics | – | [] |
disturbance_inputs | Names of the unknown disturbance inputs of dynamics when it is given as a system/plant component, e.g. the input w of an integrating disturbance state der(d) = w: they are zero in the prediction, and the states of their disturbance model are kept (see continuous_dynamics) | – | [] |
known_disturbance_inputs | Names of the measured disturbance inputs of dynamics when it is given as a system/plant component, e.g. a measured inflow: they are not decision variables but online parameters, whose values are the entries of p after those of tunable_parameters | – | [] |
tunable_parameters | Names of the parameters of dynamics that stay symbolic in the compiled solver (e.g. ["m", "l"], or ["link.m"] for a parameter of a subcomponent); their values are the tunable parameter p_values and may be changed on a compiled problem. Every other parameter of the model is substituted by its value when the controller is built | – | [] |
parameter_input | Take the online parameters from the p input connector instead of the tunable parameter p_values; the default is the connector unless tunable_parameters names the parameters and known_disturbance_inputs is empty | – | _acados_def...nce_inputs) |
state_variables | Names of the state variables carried by the entries of the x input, in the order they are connected (e.g. ["theta", "omega"], or ["link.th"] for a variable of a subcomponent); empty uses the model's own state order, which for a compiled system is chosen by the compiler | – | [] |
Ts | Shooting interval; must equal the clock period | – | |
Np | Prediction horizon (number of shooting intervals) | – | 10 |
time_steps | Lengths of the Np shooting intervals; empty means Np uniform intervals of length Ts. The first entry must equal Ts, the clock period, because the control of the first interval is the one that is applied. geometric_time_steps(Ts, Np, Tf) and linear_time_steps(Ts, Np, Tf) lay out a grid that starts at Ts and grows to cover the horizon Tf with few intervals | – | fill(0.0, 0) |
outputs | Controlled outputs, named signals of the model (states or output connectors, e.g. ["theta"]); the cost weights y in this order and C is derived as ∂y/∂state. A signal that is a nonlinear function of the state (an energy, say) makes the cost a nonlinear least squares on y(x) in place of C x (C is then the Jacobian at the operating point, for the LQR terminal cost). Empty uses C (or the identity) | – | [] |
C | Output matrix of the controlled outputs (ny × nx), for a model whose state order is known (e.g. dynamics given as expressions); empty for the identity. Use outputs instead when the model is a system | – | fill(0.0, 0, 0) |
terminal_lqr_cost | Use the infinite-horizon LQR cost of the model linearized at operating_point as terminal weight (on the full state) instead of Qf, and center the terminal cost on that point (with reference_preview: on the steady state of the linearization for the final reference block). In Julia the weight follows Q1, Q2 and the operating point; in exported C it stays at its export-time value | – | false |
u_operating_point | Control at the operating point the LQR terminal weight is linearized at (nu entries); empty means zero. Fixed at build time (unlike operating_point, which is tunable) | – | fill(0.0, 0) |
penalize_increments | Penalize the control increments u@clk - u@(clk-1) with Q3: the prediction model is augmented with the plant's control as a state and the control rate becomes the decision variable. Non-uniform time_steps requires the Julia backend | – | false |
normalize_increment_penalty | Scale each stage's increment penalty by (Ts/time_steps[k])^2 to penalize the average control rate expressed over Ts; false penalizes complete-interval increments. Endpoint costs, constraints and increment bounds are unchanged | – | false |
u_guess | Control the solver's trajectories are initialized with (nu entries, also the default of u0); empty means zero | – | fill(0.0, 0) |
x_guess | State the solver's trajectories start from at the first tick of a simulation, at every stage (nx entries in the state_variables order); empty starts from the measured state. Give it when a constant trajectory at the measured state is a poor start for the iterations; the C backend starts the first solve after its library is loaded from it as well | – | fill(0.0, 0) |
x_scale | Typical magnitude of each state variable (nx entries in the state_variables order); empty means one. The solver works in x ./ x_scale, so a state spanning kilometres next to one spanning milliradians is well conditioned; every other parameter, weight and bound stays in physical units | – | fill(0.0, 0) |
u_scale | Typical magnitude of each control (nu entries); empty means one. The solver works in u ./ u_scale | – | fill(0.0, 0) |
du_scale | Typical physical control increment per interval (nu positive entries); empty uses u_scale * Ts for continuous models and u_scale for discrete models. Applies to rate augmentation independently of u_scale | – | fill(0.0, 0) |
reference_tracking | Track references (adds the r input); false regulates to the origin | – | true |
reference_preview | Take a reference trajectory over the horizon (ny_Np entries, r[(j-1)_ny + i] the reference for output i at prediction step j) instead of a setpoint; requires reference_tracking | – | false |
parameter_preview | Take a parameter trajectory over the horizon (np_Np entries, p[(j-1)_np + i] the value of parameter i over shooting interval j) instead of the current values | – | false |
umin | Lower control bounds (nu); empty for none | – | fill(0.0, 0) |
umax | Upper control bounds (nu); empty for none | – | fill(0.0, 0) |
dumin | Lower bound on the control increment u_k - u_{k-1} (nu); empty for none | – | fill(0.0, 0) |
dumax | Upper bound on the control increment u_k - u_{k-1} (nu); empty for none | – | fill(0.0, 0) |
xmin | Lower state bounds (nx), enforced at stages 1..Np; empty for none | – | fill(0.0, 0) |
xmax | Upper state bounds (nx), enforced at stages 1..Np; empty for none | – | fill(0.0, 0) |
constrained | Constrained signals, named signals of the model (e.g. ["omega"]); a signal that is a state becomes a state bound, any other becomes a general linear constraint (coefficients derived by linearization). Use instead of xmin/xmax/Ax/Au/lb/ub when the model is a system | – | [] |
constrained_min | Lower bounds of the constrained signals (same length as constrained) | – | fill(0.0, 0) |
constrained_max | Upper bounds of the constrained signals (same length as constrained) | – | fill(0.0, 0) |
state_bounds_soft | Soften the state bounds with slack variables | – | true |
soft_weight | Quadratic penalty weight on the slack variables of every softened constraint: the state bounds, the terminal rows and the nonlinear stage and terminal constraints | – | 1e6 |
Ax | State coefficient of the general constraints (m × nx); empty for zero | – | fill(0.0, 0, 0) |
Au | Control coefficient of the general constraints (m × nu); empty for zero | – | fill(0.0, 0, 0) |
lb | Lower bounds of the general constraints (m); empty for none | – | fill(0.0, 0) |
ub | Upper bounds of the general constraints (m); empty for none | – | fill(0.0, 0) |
terminal_constrained | Terminal constrained signals, applied only at the terminal state x_Np (e.g. ["omega"]); each must be a function of the state only and becomes a hard general linear constraint (a terminal set). Use instead of AxN/lbN/ubN when the model is a system | – | [] |
terminal_constrained_min | Lower bounds of the terminal constrained signals (same length as terminal_constrained) | – | fill(0.0, 0) |
terminal_constrained_max | Upper bounds of the terminal constrained signals (same length as terminal_constrained) | – | fill(0.0, 0) |
AxN | State coefficient of the terminal general constraints lbN ≤ AxN x_Np ≤ ubN (mN × nx), for a model whose state order is known; empty for none | – | fill(0.0, 0, 0) |
lbN | Lower bounds of the terminal general constraints (mN); empty for none | – | fill(0.0, 0) |
ubN | Upper bounds of the terminal general constraints (mN); empty for none | – | fill(0.0, 0) |
terminal_constraints_soft | Soften the terminal constraints with slack variables (penalized by soft_weight), keeping the problem solvable when the terminal set is unreachable within the horizon | – | false |
nl_constrained | Nonlinear constrained signals, arbitrary (nonlinear) functions of the state, algebraic variables and control, enforced as lh ≤ h(x,u,z) ≤ uh at stages 0..Np-1 (acados BGH). Not available with C code export | – | [] |
nl_constrained_min | Lower bounds of the nonlinear constraints (same length as nl_constrained) | – | fill(0.0, 0) |
nl_constrained_max | Upper bounds of the nonlinear constraints (same length as nl_constrained) | – | fill(0.0, 0) |
nl_convex_inner | Inner signals r of the convex-over-nonlinear stage constraints φ(r(x,u,z)), whose curvature acados adds to the QP (BGP); replaces nl_constrained at the stages. Not available with C code export | – | [] |
nl_convex_outer | Outer function φ of the convex-over-nonlinear constraints: a Julia function of the vector of inner signals returning the constrained expressions, convex in that vector | – | |
nl_convex_min | Lower bounds of the convex-over-nonlinear constraints (one per expression nl_convex_outer returns; empty for none) | – | fill(0.0, 0) |
nl_convex_max | Upper bounds of the convex-over-nonlinear constraints (one per expression nl_convex_outer returns; empty for none) | – | fill(0.0, 0) |
nl_terminal_constrained | Terminal nonlinear constrained signals, functions of the differential state only, enforced only at x_Np. Not available with C code export | – | [] |
nl_terminal_constrained_min | Lower bounds of the terminal nonlinear constraints (same length as nl_terminal_constrained) | – | fill(0.0, 0) |
nl_terminal_constrained_max | Upper bounds of the terminal nonlinear constraints (same length as nl_terminal_constrained) | – | fill(0.0, 0) |
nl_terminal_constraints_soft | Soften the terminal nonlinear constraints with slack variables (penalized by soft_weight), keeping the problem solvable when the terminal set is unreachable within the horizon – e.g. an energy level the state is to reach by the end of the horizon | – | false |
nl_constraints_soft | Soften the nonlinear stage constraints with slack variables (penalized by soft_weight), so that a constraint the current state already violates, after a disturbance or a model error, does not make the problem infeasible | – | false |
nl_constraints_from_stage | First prediction stage the nonlinear stage constraints are enforced at. At stage 0 the state is the measured one, so a constraint that depends on the state alone (the controls being rates, say) is either satisfied or infeasible there; 1 leaves it out, which lets the constraints be hard where the plant is the model | – | 0 |
nlp_solver | NLP solver: SQP to convergence, or one real-time iteration per tick | – | ACADOSSolver.SQP() |
qp_solver | QP solver: HPIPM after partial condensing (to qp_cond_N stages) or full condensing, or DAQP after full condensing (only in an acados built with it, see ACADOSQPSolver) | – | ACADOSQPSol...singHPIPM() |
integrator | Integrator of the shooting intervals | – | ACADOSIntegrator.ERK() |
jacobian_backend | How the dynamics Jacobian is obtained: Symbolic (default) or an AD backend that differentiates a numeric evaluation of the model, handling linear-solve/multibody models; the Sparse variants exploit a detected sparsity pattern. Only affects the Julia solver, not the exported C | – | ACADOSJacob....Symbolic() |
integrator_stages | Runge-Kutta stages of the integrator (ERK: 1, 2 or 4; IRK: 1 to 4) | – | 4 |
collocation | Collocation scheme of the IRK integrator; RadauIIA for a stiff model integrated over long shooting intervals | – | ACADOSCollo...sLegendre() |
integrator_steps | Integration steps per shooting interval | – | 1 |
integrator_steps_per_interval | Integration sub-steps of each shooting interval (Np entries), for a non-uniform time_steps grid whose long intervals need more; empty uses integrator_steps for every interval | – | fill(0, 0) |
integrator_newton_tol | IRK early termination threshold for the infinity norm of the Newton update to scaled internal state derivatives and algebraic variables; zero uses the fixed iteration count | – | 0.0 |
integrator_newton_iter | Newton iterations of the implicit integrator per integration step, with the Jacobian reused across them unless integrator_jac_reuse is false (IRK only). Every SQP iteration starts the integrator from the stage values of the previous iterate, so one or two iterations are often enough once the iterations are warm | – | 3 |
integrator_jac_reuse | Evaluate the Jacobian of the implicit integrator once per shooting interval and reuse it across its Newton iterations and sub-steps (IRK only). Set to false for a model whose Jacobian changes by orders of magnitude within an interval, such as the charge balance of a pH neutralization, where the reused Jacobian makes the iterations diverge | – | true |
qp_cond_N | Horizon of the partially condensed QP; -1 for the full horizon Np | – | -1 |
qp_warm_start | Warm start of each QP from the solution of the previous one: 0 cold (the acados default), 1 primal, 2 primal and dual. Level 2 halves the QP time of a problem with many soft constraints, whose slack variables and multipliers change little between the SQP iterations | – | 0 |
nlp_tol_stat | NLP stationarity tolerance override; zero follows the tunable tol parameter | – | 0.0 |
nlp_tol_eq | NLP equality tolerance override; zero follows the tunable tol parameter | – | 0.0 |
nlp_tol_ineq | NLP inequality tolerance override; zero follows the tunable tol parameter | – | 0.0 |
nlp_tol_comp | NLP complementarity tolerance override; zero follows the tunable tol parameter | – | 0.0 |
qp_tol_strategy | QP tolerance strategy; adaptive accuracy uses the current NLP residuals and requires SQP | – | ACADOSQPTol...egy.Fixed() |
qp_tol_stat | Fixed QP stationarity tolerance; zero follows the effective NLP stationarity tolerance | – | 0.0 |
qp_tol_eq | Fixed QP equality tolerance; zero follows the effective NLP equality tolerance | – | 0.0 |
qp_tol_ineq | Fixed QP inequality tolerance; zero follows the effective NLP inequality tolerance | – | 0.0 |
qp_tol_comp | Fixed QP complementarity tolerance; zero follows the effective NLP complementarity tolerance | – | 0.0 |
qp_tol_reduction_factor | Residual reduction factor for the adaptive joint QP tolerance | – | 0.1 |
qp_tol_safety_factor | Lower bound on adaptive QP tolerances relative to the effective NLP tolerance of each residual | – | 0.1 |
with_adaptive_levenberg_marquardt | Adapt Levenberg-Marquardt regularization using accepted step lengths and the objective; ignores the constant levenberg_marquardt parameter | – | false |
adaptive_levenberg_marquardt_mu0 | Initial adaptive Levenberg-Marquardt damping factor at each NLP solve | – | 1e-3 |
adaptive_levenberg_marquardt_mu_min | Minimum adaptive Levenberg-Marquardt damping factor | – | 1e-16 |
adaptive_levenberg_marquardt_lam | Multiplicative factor for adaptive Levenberg-Marquardt damping; must exceed one | – | 5.0 |
adaptive_levenberg_marquardt_obj_scalar | Objective multiplier for adaptive Levenberg-Marquardt regularization; regularization is objective times this multiplier times damping | – | 2.0 |
globalization_alpha_reduction | Reduction factor for the line-search step length | – | 0.7 |
globalization_full_step_dual | Apply a full dual step when the line search reduces the primal step | – | false |
globalization | Globalization of the SQP iterations | – | ACADOSGloba...FixedStep() |
print_level | Verbosity of acados (0 silent) | – | 0 |
warm_start | Initial guess of the NLP at every tick | – | ACADOSWarmStart.Shift() |
backend | Solver backend; Auto and Julia call libacados in-process, C compiles a standalone acados solver library (required for C code export). The C solver keeps its state in a process-global capsule, so use it for export and single-loop deployment rather than repeated simulation | – | Backend.Auto() |
reset_on_failure | Retry a failed solve (NaN detected, minimum step, QP failure, unbounded) once from a fresh initial guess at the current state, so that a shifted warm start cannot carry a broken iterate from tick to tick; costs one extra solve on the failing tick only | – | false |
output_trajectories | Also record the predicted state and control trajectories, the KKT residuals and the iteration count of every tick (variables x_pred, u_pred, residuals, iterations), for debugging and mpc_gui; Julia backend only | – | false |
mpc | acados solver built from the parameters above; pass build_acados_mpc(...) to bypass them | – | build_acado...on_failure) |
nx | State dimension of the model (derived from mpc) | – | _acados_nx(mpc) |
nu | Number of controls (derived from mpc) | – | _acados_nu(mpc) |
ny | Number of controlled outputs (derived from mpc) | – | _acados_ny(mpc) |
nr_in | Width of the reference input: 0, ny or ny*Np with reference_preview (derived from mpc) | – | _acados_nr_in(mpc) |
np_in | Width of the parameter input: the number of online parameters of the model, np, or np_Np with parameter_preview; zero when the values come from p*values instead (derived) | – | acados_np...eter_input) |
has_r | Whether the r connector exists (derived) | – | nr_in > 0 |
has_p | Whether the p connector exists (derived) | – | np_in > 0 |
nr_eff | Width of the internal reference vector, max(nr_in, 1) (derived) | – | max(nr_in, 1) |
np_eff | Width of the internal parameter vector and of p_values, max(np, 1) or max(np_Np, 1) with parameter*preview; independent of whether the p connector exists (derived) | – | max(_acados...ec(mpc), 1) |
nxp | Length of the predicted state trajectory, (Np + 1) nx (derived from mpc) | – | _acados_nxp(mpc) |
nup | Length of the predicted control trajectory, Np nu (derived from mpc) | – | _acados_nup(mpc) |
nyf | Side of the terminal weight Qf: ny, or nx with terminal_lqr_cost (derived from mpc) | – | _acados_nyf(mpc) |
nEu | Number of columns of Eu, max(np, 1) (derived from mpc) | – | _acados_nEu(mpc) |
use_c | Whether the compiled C solver is used (resolved from backend; the library is built on first use with the weights and options above) | – | _acados_use...ting_point) |
use_julia | Whether the in-process Julia solver is used (derived) | – | use_c == false |
max_iter | Maximum SQP iterations per tick (SQP only); tunable, but acados sizes its memory by the value the model is built with, so it can later be lowered but not raised | – | 100 |
u0 | Control assumed applied before the first tick, by default zero | – | _acados_u0(mpc) |
Q1 | Output weight on C*x - r (ny × ny, e.g. diagonal([1.0, 10.0])); tunable. Default: the identity, or the weight the mpc object was built with | – | _acados_Q1(mpc) |
Q2 | Control weight (nu × nu); tunable. Default: the identity, or the weight the mpc object was built with | – | _acados_Q2(mpc) |
Q3 | Penalty on the control increments (nu × nu); tunable, must be zero unless penalize_increments. Default: the identity with penalize_increments, else zero | – | _acados_Q3(mpc) |
Qf | Terminal output weight (ny × ny); tunable, ignored with terminal_lqr_cost (then nx × nx and following Q1 and Q2). Default: Q1, or the weight the mpc object was built with | – | _acados_Qf(mpc, Q1) |
eu | Constant term of the linear control cost (Eu p + eu)' u at every stage (nu), in LinearMPC's convention next to the stage cost ½ u' Q2 u, so that eu = -Q2 u_ref centers the control penalty on u_ref; a nonzero cost requires positive definite Q2 unless the increment formulation is used; tunable. Default: zero, or the cost the mpc object was built with | – | _acados_eu(mpc) |
Eu | Gain of the online parameters p in the linear control cost (nu × np, p of the stage's shooting interval); a model without online parameters has a single column, which must be zero; tunable. Default: zero, or the gain the mpc object was built with | – | _acados_Eu(mpc) |
tol | Default SQP convergence tolerance for residuals without a positive nlp*tol** override; tunable | – | _acados_tol(mpc) |
levenberg_marquardt | Levenberg-Marquardt regularization added to the Gauss-Newton Hessian; tunable | – | _acados_lev...quardt(mpc) |
operating_point | State (in the state_variables order) the LQR terminal weight is linearized at and centered on; tunable, used only with terminal_lqr_cost. Changing it re-linearizes the model and re-solves the Riccati equation. Default: the origin | – | _acados_ope..._point(mpc) |
p_values | Values of the parameters named by tunable_parameters, used when the p connector is absent; tunable. Default: the values the model carries. np entries, or np*Np column-major entries with parameter_preview | – | _acados_p_d...pc, np_eff) |
Connectors ​
x- This connector represents a real signal as an input to a component (RealInput)r- This connector represents a real signal as an input to a component (RealInput)p- This connector represents a real signal as an input to a component (RealInput)u- This connector represents a real signal as an output from a component (RealOutput)exitflag- This connector represents a real signal as an output from a component (RealOutput)
Variables ​
| Name | Description | Units |
|---|---|---|
ntick | Number of ticks solved so far in the simulation; zero at the first tick, where the solver's warm start is reset so that a compiled model can be simulated again (Julia backend only: the C backend keeps its solver in a capsule that lives for the whole process and is meant for export and single-loop deployment, see backend) | – |
r_int | Reference vector handed to the step function | – |
p_int | Parameter vector handed to the step function | – |
x_pred | Predicted states at stages 0..Np, column-major nx × (Np+1) in the order of the x input (only with output_trajectories) | – |
u_pred | Predicted controls at stages 0..Np-1, column-major nu × Np (only with output_trajectories) | – |
residuals | KKT residuals of the last iteration: stationarity, equality, inequality, complementarity (only with output_trajectories) | – |
iterations | SQP iterations of the tick (only with output_trajectories) | – |
Behavior ​
Behavior of this component cannot be rendered because it includes path variables.
Source ​
"""
Nonlinear model-predictive controller built on acados (https://github.com/acados/acados), run as a
clocked component. The C library is called directly from Julia; the prediction model is given
symbolically and integrated by acados.
# Model
The prediction model is the continuous-time system
```math
\\dot x = f(x, u, p)
```
with state ``x`` (nx), control ``u`` (nu) and optional online parameters ``p`` (np), given as a
`ContinuousDynamics` object (see `continuous_dynamics`: from Symbolics expressions, from a traceable
Julia function, or from a continuous-time Dyad/ModelingToolkit model whose inputs are named). Every
parameter of such a model is substituted by its value when the controller is built, so that the
compiled solver carries numbers rather than symbols; those named by `tunable_parameters` are the
exception and stay symbolic as the online parameters ``p``, whose values are the tunable parameter
`p_values` (see *Tunable parameters* below). acados
discretizes it over `Np` shooting intervals of length `Ts` with an explicit (`ERK`, default RK4) or
implicit (`IRK`) Runge-Kutta integrator (`integrator_stages` stages, `integrator_steps` sub-steps per
interval; `collocation` selects Gauss-Legendre or the L-stable Radau IIA scheme for the implicit one,
and `integrator_newton_iter` the Newton iterations it takes per step). `time_steps` replaces that uniform grid by one of `Np` intervals of the given lengths: the
first is always `Ts` (its control is the one that is applied and held for a clock period) and the
later ones may be longer, which covers a long prediction horizon with few decision variables.
`geometric_time_steps(Ts, Np, Tf)` and `linear_time_steps(Ts, Np, Tf)` lay out such a grid over the
horizon ``T_f``. A long interval is integrated with the same `integrator_steps` sub-steps as a short
one unless `integrator_steps_per_interval` supplies a vector of sub-step counts. Choose enough
sub-steps to preserve integration accuracy over every interval. The prediction model must not contain events. The controlled outputs are ``y = C x``, or
``y = y(x)`` when an output signal is a nonlinear function of the state (see `outputs`).
# Objective
```math
\\sum_{k=0}^{N_p-1} \\left( (C x_k - r_k)^T Q_1 (C x_k - r_k) + u_k^T Q_2 u_k + \\Delta u_k^T Q_3 \\Delta u_k \\right) + (C x_{N_p} - r_{N_p})^T Q_f (C x_{N_p} - r_{N_p})
```
with ``\\Delta u_k = u_k - u_{k-1}``, ``u_{-1}`` the control applied at the previous tick, and ``r_k = r`` the
reference of the tick unless `reference_preview` provides one per prediction step. With `time_steps`
the term of stage ``k`` is weighted by ``\\Delta t_k / T_s``, the length of its shooting interval
relative to the first, so that a long interval counts for as much as the short ones it replaces; on
the uniform grid every factor is 1 and the objective is the plain sum written above. `Q1`, `Q2`, `Qf`
and `Q3` are full matrices (`diagonal([1.0, 10.0])` builds a diagonal one) and ordinary, tunable
parameters; by default `Q1 = Q2 = I`, `Qf = Q1` and `Q3 = 0`. `terminal_lqr_cost = true` replaces the
terminal term by ``(x_{N_p} - x_{op})^T P (x_{N_p} - x_{op})`` with ``P`` the infinite-horizon LQR
cost-to-go of the model linearized at the operating point ``x_{op}`` = `operating_point` (as
DyadControlSystems' `QMPCProblem` does by default, at the origin), which lets a short horizon behave
like the infinite-horizon regulator around that point; `Qf` is then ignored. The operating point (an
equilibrium, e.g. the upright state of an inverted pendulum) defaults to the origin, is tunable, and
is independent of the tracked reference `r`, so `terminal_lqr_cost` may be combined with
`reference_tracking`. `u_operating_point` is the control there (fixed at build). Without the
augmentation (`penalize_increments` or a control-rate bound, see below), `Q3` must be zero and `Q1`
and `Q2` must be positive definite. With
`penalize_increments = true` the model is augmented with the plant's own control as a state and the
control rate is the decision variable, so the increment over a shooting interval is weighted by `Q3`
(positive definite) and `Q1` and `Q2` may then be singular (`Q2 = 0` penalizes the increments only).
Either integrator works; the augmented model represents the control as ramping across a shooting
interval where the actuator holds it, a mismatch that vanishes as the solution converges. On a non-uniform
`time_steps` grid, increments and their bounds refer to each complete shooting interval.
`normalize_increment_penalty = true` scales the increment weight by `(Ts/time_steps[k])^2`, so
`Q3` penalizes the average control rate expressed as an increment over `Ts`. With the stage duration
weight `time_steps[k]/Ts`, this preserves the uniform-grid rate-penalty interpretation. The default
`false` penalizes complete-interval increments. The option affects neither increment bounds nor
endpoint input costs or constraints; both choices coincide on a uniform grid. Stage
input costs and input-dependent constraints use the ramp endpoint. The augmentation on a non-uniform
grid requires the Julia backend. A `Discrete` prediction model applies one period of its map per shooting interval
and requires a uniform grid. With
`reference_tracking = false` the controller regulates to the origin and has no `r` input.
`eu` and `Eu` add the linear control cost ``(E_u p_k + e_u)^T u_k`` to each stage, in the convention of
LinearMPC, whose stage cost is ``\\tfrac12 u^T Q_2 u + (E_u p + e_u)^T u``, so that ``e_u = -Q_2 u_{ref}``
centers the control penalty on ``u_{ref}`` (a trim control, say), and ``E_u`` lets that center follow the
online parameters ``p_k`` of the stage. Without increment augmentation the economic cost is
represented by the control reference ``-Q_2^{-1} (E_u p_k + e_u)``, which requires positive definite
`Q2` whenever the cost is nonzero. With increment augmentation both backends evaluate the
economic term directly, so `Q2 = 0` is supported. Both economic weights are tunable.
Nonlinear stage and terminal constraints, including soft constraints, and nonlinear least-squares
outputs can be combined with increments on both backends. Stage constraints are evaluated at
the stage boundary using the ramp endpoint command. The first ramp starts at the previously applied
command; the returned command is its endpoint.
# Constraints
Control bounds `umin ≤ u_k ≤ umax` (hard, all stages), control-rate bounds
`dumin ≤ u_k - u_{k-1} ≤ dumax` (hard, all stages, the first taken against the control last applied)
and state bounds `xmin ≤ x_k ≤ xmax` for
``k = 1..N_p``, soft by default (`state_bounds_soft`) with the quadratic slack penalty `soft_weight`.
General linear constraints `lb ≤ Ax x_k + Au u_k ≤ ub` are hard and enforced at ``k = 0..N_p-1``
(and at ``N_p`` when `Au` is empty). Terminal constraints `lbN ≤ AxN x_{N_p} ≤ ubN` (given by
`terminal_constrained`/`terminal_constrained_min`/`terminal_constrained_max`, or the matrix form
`AxN`/`lbN`/`ubN`) are enforced only at the terminal state — a terminal set — and are hard unless
`terminal_constraints_soft`. Nonlinear constraints `lh ≤ h(x, u, z) ≤ uh` (`nl_constrained`, and
`nl_terminal_constrained` for the terminal state) are arbitrary nonlinear functions of the signals
(acados BGH), supported by both backends; both can be softened with slack variables
(`nl_constraints_soft`, `nl_terminal_constraints_soft`). Empty bounds mean "none".
A stage constraint of the form ``\\varphi(r(x, u, z))`` with ``\\varphi`` convex in the inner vector
``r`` (a squared norm of nonlinear signals, say) may instead be given through `nl_convex_inner` (the
signals ``r``) and `nl_convex_outer` (a Julia function of the symbolic vector ``r`` returning the
constrained expressions), bounded by `nl_convex_min`/`nl_convex_max`. acados then adds the curvature of
``\\varphi`` to the Hessian of the QP (its BGP constraint module), which is what a Gauss-Newton SQP
lacks on a constraint whose gradient vanishes where the iterations start, such as a squared norm at
the origin: with the plain form the linearization carries no information and the steps overshoot the
constraint by a large factor. The convex form replaces `nl_constrained` at the stages (acados has one
nonlinear constraint module per stage; a plain constraint is the convex form with the identity outer
function), obeys `nl_constraints_soft` and `nl_constraints_from_stage` alike, and is not available with
C code export.
# Scaling
`x_scale` and `u_scale` give the typical magnitude of each state variable (in the `state_variables`
order) and of each control. The optimization runs in the scaled variables ``\\tilde x = x / x_\\mathrm{scale}``,
``\\tilde u = u / u_\\mathrm{scale}``: the prediction model, the bounds, the outputs and the constraints
are rewritten in them, while every parameter of the component -- weights, bounds, references, guesses,
the operating point -- and every signal it exchanges remain in physical units. A model whose state
variables span very different magnitudes (a position in kilometres next to an angle in milliradians)
is otherwise handed to the solver ill-conditioned: the SQP steps are damped or fail in
the small variables, one tolerance means different things for different variables, and the line search
stalls. `Q2` and `Q3` are given in physical units and apply to the physical control.
`du_scale` sets the typical physical increment per interval independently of `u_scale`; the rate
decision is the physical increment divided by `du_scale`. Empty uses `u_scale * Ts` for continuous
models and `u_scale` for discrete models. The physical ramp, cost and bounds are unchanged.
The rate bounds need the same augmentation as the increment penalty and switch it on by themselves:
`dumin`/`dumax` alone leave `Q3` at zero, so an actuator rate limit does not impose an increment
penalty (and `Q3` stays tunable afterwards). They carry the restrictions the augmentation carries —
no non-uniform grid, no DAE model, no nonlinear outputs or constraints.
# Signals and timing
At every tick the state `x` (measured or estimated), the reference `r` (a setpoint, or a trajectory with
`reference_preview`) and the parameters `p` (current values, or a trajectory with `parameter_preview`) are
read, the NLP is solved with the previous solution as the initial guess (`warm_start`; at the first
tick of a simulation the solver starts afresh from the current state, or from `x_guess` when it is
given, so that a compiled model can be simulated again without inheriting the trajectories of the
previous run), and the first control of
the optimal sequence is output as `u`. The control must be held (`ZeroOrderHold`) outside
this component, and `Ts` must equal the period of the clock. `exitflag` is the acados status of the
last solve: 0 success, 1 NaN detected, 2 maximum iterations, 3 minimum step, 4 QP failure, 5 ready,
6 unbounded, 7 timeout; on failure `u` is the solver's last iterate.
The default `warm_start = Shift` moves the previous solution one stage back and repeats its last
stage. When the last control of a solution influences nothing the cost weights, it is determined only
by the control weight and differs from the controls before it, and the shift places it where it does
matter. This is the case when the outputs are algebraic variables, since the terminal stage carries
none. On a strongly nonlinear model the SQP iterations that correct it can then diverge, and
`warm_start = Keep` is the better initial guess (see `PHNeutralizationMPCExample`).
# Previews
With `reference_preview` the `r` input carries a whole horizon as a flat column-major vector of `ny*Np`
entries, in the layout of the LinearMPC components: `r[(j-1)*ny + i]` is the reference for output `i` at
prediction step `j`, i.e. for the predicted state ``x_j`` at the end of shooting interval `j`; the terminal
cost uses block `Np`. A block is tied to a shooting interval, not to a fixed span of time, so with a
non-uniform `time_steps` grid the blocks are the values at the shooting nodes rather than samples of
the trajectory at the clock rate (a `DelayLine`, which taps the signal every `Ts`, produces the
latter). Likewise `parameter_preview` widens `p` to `np*Np` entries, `p[(j-1)*np + i]` being the
value of parameter `i` over shooting interval `j` (the terminal stage reuses block `Np`). A `DelayLine` turns
a signal known ahead of time into such a vector in one connection (see `ACADOSMPCPreviewExample`). With
`terminal_lqr_cost` and `reference_preview` the terminal cost is centered on the steady state of the
linearized model for the final reference block, ``x_{op} + M_r (r_{N_p} - C x_{op})``, where ``M_r`` solves
the steady-state equations of the linearization at the operating point; this requires at most as many
controlled outputs as controls (`ny ≤ nu`) and a full-rank steady-state map, checked at build. The terminal
weight itself stays the one of the operating point, and in exported C both the weight and ``M_r`` are frozen
at their export-time values.
With `output_trajectories = true` the component also records, at every tick, what the solver predicted
and how well it converged, in the variables `x_pred` (the predicted states at stages 0..Np, column-major
nx × (Np+1), in the order of the `x` input), `u_pred` (the predicted controls at stages 0..Np-1,
column-major nu × Np), `residuals` (the stationarity, equality, inequality and complementarity residuals
of the last iteration, to compare with `tol`) and `iterations` (SQP iterations of the tick). Read them
from a solution as `sol[model.mpc.x_pred]` (one vector per tick), or visualize them with `mpc_gui` (a
Makie backend must be loaded). This is a debugging aid for the Julia backend (`backend = C` is refused).
With `SQP` the residuals are those of the returned iterate; with `SQP_RTI` acados computes (only then) the
residuals of the iterate the tick linearized at, i.e. how far from optimal the real-time iteration started.
# Solver
`SQP` iterates to the tolerance `tol` (at most `max_iter` iterations); `SQP_RTI` performs one
iteration per tick (real-time iteration, relies on `warm_start = Shift`). The QP is solved by HPIPM
after partial condensing to `qp_cond_N` stages (-1 keeps the horizon) or full condensing, or by DAQP
after full condensing (`FullCondensingDAQP`, in an acados built with it). The Hessian is the
Gauss-Newton approximation of the least-squares cost, plus the curvature of the outer function of the
convex-over-nonlinear constraints when there are any.
`tol` is the default absolute stopping threshold for stationarity, dynamics defects, inequality
violations and complementarity in the scaled optimization coordinates, and must be positive.
Independent structural thresholds `nlp_tol_stat`, `nlp_tol_eq`, `nlp_tol_ineq` and `nlp_tol_comp`
override the corresponding residual threshold when positive; zero follows the tunable `tol`. Positive
overrides are unaffected by changes to `tol`, and require `nlp_solver = SQP`, since `SQP_RTI` performs
a single iteration without a stopping test. With `qp_tol_strategy = Fixed`, the QP tolerances
`qp_tol_stat`, `qp_tol_eq`, `qp_tol_ineq` and `qp_tol_comp` are independent positive thresholds; a zero
value follows the effective NLP threshold of the same residual. With
`qp_tol_strategy = AdaptiveCurrentResidualJoint`, SQP sets the QP accuracy from the largest NLP
residual: the joint threshold is capped at 0.01 and reduced by `qp_tol_reduction_factor`, with
`qp_tol_safety_factor` times the effective NLP threshold of each residual as a lower bound. The cap
does not apply to this lower bound, so an NLP threshold above `0.01 / qp_tol_safety_factor` yields a
QP threshold above 0.01, and a warning is issued when the controller is constructed. Positive fixed
QP tolerances cannot be combined with this strategy. The adaptive strategy requires SQP.
`with_adaptive_levenberg_marquardt = true` replaces constant regularization by the current
objective times `adaptive_levenberg_marquardt_obj_scalar` times a damping factor. The damping
starts at `adaptive_levenberg_marquardt_mu0` for each solve, decreases after full steps, increases
after reduced steps, and is bounded below by `adaptive_levenberg_marquardt_mu_min`. The multiplier
`adaptive_levenberg_marquardt_lam` determines these changes. The constant tunable
`levenberg_marquardt` is ignored in this mode. These options and the QP accuracy strategy are
structural parameters. `globalization_alpha_reduction` controls backtracking, and
`globalization_full_step_dual` selects a full multiplier update when the primal step is reduced.
# Robustness
`reset_on_failure = true` retries a solve that failed (NaN, QP failure) once from a fresh initial
guess at the current state, which is what keeps a shifted warm start from carrying a broken iterate
from tick to tick.
# Tunable parameters
The weights `Q1`, `Q2`, `Q3`, `Qf`, the linear control cost `eu`, `Eu`, the solver settings `tol`,
`levenberg_marquardt` and `max_iter`, and the `operating_point` are ordinary parameters, so a parameter
of an enclosing component may be passed down to them: they can be changed after the model is compiled, and
in exported C code they are fields of the parameter struct. acados is updated only when a value changes.
`max_iter` can be lowered but not raised above the value the model was built with (acados sizes its
memory by it; a larger value is clamped with a warning). With `terminal_lqr_cost` the terminal weight
follows `Q1`, `Q2` and the operating point in Julia (a new operating point re-linearizes the model and
re-solves the Riccati equation) but stays at its export-time value in C. The horizon, constraints and solver
choices are structural and fixed at build time, and so is the *structure* of the prediction model.
Parameters of the prediction model are tunable when they are named by `tunable_parameters`:
```
mpc = ACADOSMPC(dynamics = plant, inputs = [\"tau\"], tunable_parameters = [\"m\", \"c\"], ...)
prob = ODEProblem(ssys, [ssys.mpc.p_values => [2.0, 0.2]], (0.0, 5.0))
```
`p_values` then holds their values (by default the ones the model carries), is an ordinary parameter
like the weights, and is a field of the parameter struct in exported C. It is written to acados
before every solve, so the compiled controller predicts with the values the problem was built with;
being a parameter, it does not change during a simulation. To vary the parameters over a simulation,
or to give each shooting interval its own values, connect the `p` input instead (it exists whenever
the model has online parameters and `parameter_input` is true, which is the default unless
`tunable_parameters` names them; `parameter_preview` widens it to one block per interval). With
`terminal_lqr_cost` the terminal weight is linearized at the values the model was built with and does
not follow `p_values`.
A parameter of a subcomponent is named hierarchically, `tunable_parameters = [\"link.m\"]`, and
`state_variables`, `outputs` and the other signal lists take the same spelling
(`ACADOSMPCNestedParameterExample`). Only a parameter that survives into the compiled model can be
named: one bound to a parameter of an enclosing component (`link = Plant(final m = m)`) is
substituted before the equations are built, so the parameter to name is the one it is bound to, and
naming the bound one is rejected rather than left as an entry of `p` that the dynamics ignore. The
values reach acados as doubles, which limits what a parameter may be: an integer-valued parameter of
the model is kept online, but nothing constrains its entry of `p_values` to integers, and a parameter
the model uses as a Boolean (the condition of an `ifelse`) cannot be online at all. An array-valued
parameter named whole (`\"k\"`) contributes each of its elements, in order; named by element
(`\"k[1]\"`), the elements not named keep their values.
# Measured disturbances
An input of the plant whose value is measured rather than decided, such as a measured inflow, is named
by `known_disturbance_inputs`. The controller does not optimize it; it becomes an online parameter of
the prediction model, whose value is the corresponding entry of `p`, after the entries of
`tunable_parameters`, and is held over the horizon unless `parameter_preview` supplies a trajectory.
The same plant component can then serve as the simulated plant, as the prediction model and, with
the measured input listed in its `inputs`, as the model of a `UKFObserver`
(`PHNeutralizationMPCExample`).
# Notes
acados comes from `DyadAcados_jll`; on a
platform without JLL binaries `MPCComponents.install_acados!()` builds it into a scratch space, and
`ACADOS_INSTALL_DIR` points at an existing installation. Models containing this component are exported
to C with the `CCodeExport` analysis (`backend = C`). Further acados features
(exact Hessians, the QP solvers other than HPIPM and DAQP) are not exposed yet; build the controller
with `build_acados_mpc` in Julia to pass options as keywords.
"""
component ACADOSMPC@[input clk extends Discrete]
"State (measured or estimated) at the current tick, in the order given by `state_variables`"
x = [RealInput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 785, "x2": 0, "y2": 885}}}
}
"References for the controlled outputs y = C x; ny entries, or ny*Np column-major entries with reference_preview (only with reference_tracking)"
r = [RealInput@[clk]() for i in 1:nr_in] if has_r {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 115, "x2": 0, "y2": 215}}}
}
"Online parameters of the prediction model; np entries, or np*Np column-major entries with parameter_preview (only when the dynamics have parameters)"
p = [RealInput@[clk]() for i in 1:np_in] if has_p {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 450, "x2": 0, "y2": 550}}}
}
"Control signal (first move of the optimal sequence)"
u = [RealOutput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 700, "x2": 1100, "y2": 800}}}
}
"acados status of the last solve: 0 success, -1 nonzero economic cost with singular Q2 and no increment augmentation (C backend), 1 NaN detected, 2 maximum iterations, 3 minimum step, 4 QP failure, 5 ready, 6 unbounded, 7 timeout"
exitflag = RealOutput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 200, "x2": 1100, "y2": 300}}}
}
"Number of ticks solved so far in the simulation; zero at the first tick, where the solver's warm start is reset so that a compiled model can be simulated again (Julia backend only: the C backend keeps its solver in a capsule that lives for the whole process and is meant for export and single-loop deployment, see `backend`)"
variable ntick::Real if use_julia
"Continuous-time prediction model ẋ = f(x, u, p): a ContinuousDynamics (see continuous_dynamics), or a continuous-time system/plant component whose control inputs are named by `inputs`: a subcomponent of the model (e.g. the plant), or a component constructor (e.g. MyPlant, or component_constructor(MyPlant, m = m) with modified arguments), which is instantiated"
structural parameter dynamics::MPCDynamics
"""
Names of the control inputs of `dynamics` when it is given as a system/plant component (e.g. ["tau"]); empty when `dynamics` is already a ContinuousDynamics
"""
structural parameter inputs::String[:] = []
"Names of the unknown disturbance inputs of `dynamics` when it is given as a system/plant component, e.g. the input `w` of an integrating disturbance state `der(d) = w`: they are zero in the prediction, and the states of their disturbance model are kept (see continuous_dynamics)"
structural parameter disturbance_inputs::String[:] = []
"Names of the measured disturbance inputs of `dynamics` when it is given as a system/plant component, e.g. a measured inflow: they are not decision variables but online parameters, whose values are the entries of `p` after those of `tunable_parameters`"
structural parameter known_disturbance_inputs::String[:] = []
"""
Names of the parameters of `dynamics` that stay symbolic in the compiled solver (e.g. ["m", "l"], or ["link.m"] for a parameter of a subcomponent); their values are the tunable parameter `p_values` and may be changed on a compiled problem. Every other parameter of the model is substituted by its value when the controller is built
"""
structural parameter tunable_parameters::String[:] = []
"Take the online parameters from the `p` input connector instead of the tunable parameter `p_values`; the default is the connector unless `tunable_parameters` names the parameters and `known_disturbance_inputs` is empty"
structural parameter parameter_input::Boolean = _acados_default_parameter_input(tunable_parameters, known_disturbance_inputs)
"""
Names of the state variables carried by the entries of the x input, in the order they are connected (e.g. ["theta", "omega"], or ["link.th"] for a variable of a subcomponent); empty uses the model's own state order, which for a compiled system is chosen by the compiler
"""
structural parameter state_variables::String[:] = []
"Shooting interval; must equal the clock period"
structural parameter Ts::Real
"Prediction horizon (number of shooting intervals)"
structural parameter Np::Integer = 10
"Lengths of the Np shooting intervals; empty means Np uniform intervals of length Ts. The first entry must equal Ts, the clock period, because the control of the first interval is the one that is applied. `geometric_time_steps(Ts, Np, Tf)` and `linear_time_steps(Ts, Np, Tf)` lay out a grid that starts at Ts and grows to cover the horizon Tf with few intervals"
structural parameter time_steps::Real[:] = fill(0.0, 0)
"""
Controlled outputs, named signals of the model (states or output connectors, e.g. ["theta"]); the cost weights y in this order and C is derived as ∂y/∂state. A signal that is a nonlinear function of the state (an energy, say) makes the cost a nonlinear least squares on y(x) in place of C x (C is then the Jacobian at the operating point, for the LQR terminal cost). Empty uses C (or the identity)
"""
structural parameter outputs::String[:] = []
"Output matrix of the controlled outputs (ny × nx), for a model whose state order is known (e.g. dynamics given as expressions); empty for the identity. Use `outputs` instead when the model is a system"
structural parameter C::Real[:, :] = fill(0.0, 0, 0)
"Use the infinite-horizon LQR cost of the model linearized at `operating_point` as terminal weight (on the full state) instead of Qf, and center the terminal cost on that point (with reference_preview: on the steady state of the linearization for the final reference block). In Julia the weight follows Q1, Q2 and the operating point; in exported C it stays at its export-time value"
structural parameter terminal_lqr_cost::Boolean = false
"Control at the operating point the LQR terminal weight is linearized at (nu entries); empty means zero. Fixed at build time (unlike operating_point, which is tunable)"
structural parameter u_operating_point::Real[:] = fill(0.0, 0)
"Penalize the control increments u@clk - u@(clk-1) with Q3: the prediction model is augmented with the plant's control as a state and the control rate becomes the decision variable. Non-uniform time_steps requires the Julia backend"
structural parameter penalize_increments::Boolean = false
"Scale each stage's increment penalty by (Ts/time_steps[k])^2 to penalize the average control rate expressed over Ts; false penalizes complete-interval increments. Endpoint costs, constraints and increment bounds are unchanged"
structural parameter normalize_increment_penalty::Boolean = false
"Control the solver's trajectories are initialized with (nu entries, also the default of u0); empty means zero"
structural parameter u_guess::Real[:] = fill(0.0, 0)
"State the solver's trajectories start from at the first tick of a simulation, at every stage (nx entries in the `state_variables` order); empty starts from the measured state. Give it when a constant trajectory at the measured state is a poor start for the iterations; the C backend starts the first solve after its library is loaded from it as well"
structural parameter x_guess::Real[:] = fill(0.0, 0)
"Typical magnitude of each state variable (nx entries in the `state_variables` order); empty means one. The solver works in x ./ x_scale, so a state spanning kilometres next to one spanning milliradians is well conditioned; every other parameter, weight and bound stays in physical units"
structural parameter x_scale::Real[:] = fill(0.0, 0)
"Typical magnitude of each control (nu entries); empty means one. The solver works in u ./ u_scale"
structural parameter u_scale::Real[:] = fill(0.0, 0)
"Typical physical control increment per interval (nu positive entries); empty uses u_scale * Ts for continuous models and u_scale for discrete models. Applies to rate augmentation independently of u_scale"
structural parameter du_scale::Real[:] = fill(0.0, 0)
"Track references (adds the r input); false regulates to the origin"
structural parameter reference_tracking::Boolean = true
"Take a reference trajectory over the horizon (ny*Np entries, r[(j-1)*ny + i] the reference for output i at prediction step j) instead of a setpoint; requires reference_tracking"
structural parameter reference_preview::Boolean = false
"Take a parameter trajectory over the horizon (np*Np entries, p[(j-1)*np + i] the value of parameter i over shooting interval j) instead of the current values"
structural parameter parameter_preview::Boolean = false
"Lower control bounds (nu); empty for none"
structural parameter umin::Real[:] = fill(0.0, 0)
"Upper control bounds (nu); empty for none"
structural parameter umax::Real[:] = fill(0.0, 0)
"Lower bound on the control increment u_k - u_{k-1} (nu); empty for none"
structural parameter dumin::Real[:] = fill(0.0, 0)
"Upper bound on the control increment u_k - u_{k-1} (nu); empty for none"
structural parameter dumax::Real[:] = fill(0.0, 0)
"Lower state bounds (nx), enforced at stages 1..Np; empty for none"
structural parameter xmin::Real[:] = fill(0.0, 0)
"Upper state bounds (nx), enforced at stages 1..Np; empty for none"
structural parameter xmax::Real[:] = fill(0.0, 0)
"""
Constrained signals, named signals of the model (e.g. ["omega"]); a signal that is a state becomes a state bound, any other becomes a general linear constraint (coefficients derived by linearization). Use instead of xmin/xmax/Ax/Au/lb/ub when the model is a system
"""
structural parameter constrained::String[:] = []
"Lower bounds of the constrained signals (same length as constrained)"
structural parameter constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the constrained signals (same length as constrained)"
structural parameter constrained_max::Real[:] = fill(0.0, 0)
"Soften the state bounds with slack variables"
structural parameter state_bounds_soft::Boolean = true
"Quadratic penalty weight on the slack variables of every softened constraint: the state bounds, the terminal rows and the nonlinear stage and terminal constraints"
structural parameter soft_weight::Real = 1e6
"State coefficient of the general constraints (m × nx); empty for zero"
structural parameter Ax::Real[:, :] = fill(0.0, 0, 0)
"Control coefficient of the general constraints (m × nu); empty for zero"
structural parameter Au::Real[:, :] = fill(0.0, 0, 0)
"Lower bounds of the general constraints (m); empty for none"
structural parameter lb::Real[:] = fill(0.0, 0)
"Upper bounds of the general constraints (m); empty for none"
structural parameter ub::Real[:] = fill(0.0, 0)
"""
Terminal constrained signals, applied only at the terminal state x_Np (e.g. ["omega"]); each must be a function of the state only and becomes a hard general linear constraint (a terminal set). Use instead of AxN/lbN/ubN when the model is a system
"""
structural parameter terminal_constrained::String[:] = []
"Lower bounds of the terminal constrained signals (same length as terminal_constrained)"
structural parameter terminal_constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the terminal constrained signals (same length as terminal_constrained)"
structural parameter terminal_constrained_max::Real[:] = fill(0.0, 0)
"State coefficient of the terminal general constraints lbN ≤ AxN x_Np ≤ ubN (mN × nx), for a model whose state order is known; empty for none"
structural parameter AxN::Real[:, :] = fill(0.0, 0, 0)
"Lower bounds of the terminal general constraints (mN); empty for none"
structural parameter lbN::Real[:] = fill(0.0, 0)
"Upper bounds of the terminal general constraints (mN); empty for none"
structural parameter ubN::Real[:] = fill(0.0, 0)
"Soften the terminal constraints with slack variables (penalized by soft_weight), keeping the problem solvable when the terminal set is unreachable within the horizon"
structural parameter terminal_constraints_soft::Boolean = false
"Nonlinear constrained signals, arbitrary (nonlinear) functions of the state, algebraic variables and control, enforced as lh ≤ h(x,u,z) ≤ uh at stages 0..Np-1 (acados BGH). Not available with C code export"
structural parameter nl_constrained::String[:] = []
"Lower bounds of the nonlinear constraints (same length as nl_constrained)"
structural parameter nl_constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the nonlinear constraints (same length as nl_constrained)"
structural parameter nl_constrained_max::Real[:] = fill(0.0, 0)
"Inner signals r of the convex-over-nonlinear stage constraints φ(r(x,u,z)), whose curvature acados adds to the QP (BGP); replaces nl_constrained at the stages. Not available with C code export"
structural parameter nl_convex_inner::String[:] = []
"Outer function φ of the convex-over-nonlinear constraints: a Julia function of the vector of inner signals returning the constrained expressions, convex in that vector"
structural parameter nl_convex_outer::ConvexOuter
"Lower bounds of the convex-over-nonlinear constraints (one per expression nl_convex_outer returns; empty for none)"
structural parameter nl_convex_min::Real[:] = fill(0.0, 0)
"Upper bounds of the convex-over-nonlinear constraints (one per expression nl_convex_outer returns; empty for none)"
structural parameter nl_convex_max::Real[:] = fill(0.0, 0)
"Terminal nonlinear constrained signals, functions of the differential state only, enforced only at x_Np. Not available with C code export"
structural parameter nl_terminal_constrained::String[:] = []
"Lower bounds of the terminal nonlinear constraints (same length as nl_terminal_constrained)"
structural parameter nl_terminal_constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the terminal nonlinear constraints (same length as nl_terminal_constrained)"
structural parameter nl_terminal_constrained_max::Real[:] = fill(0.0, 0)
"Soften the terminal nonlinear constraints with slack variables (penalized by soft_weight), keeping the problem solvable when the terminal set is unreachable within the horizon -- e.g. an energy level the state is to reach by the end of the horizon"
structural parameter nl_terminal_constraints_soft::Boolean = false
"Soften the nonlinear stage constraints with slack variables (penalized by soft_weight), so that a constraint the current state already violates, after a disturbance or a model error, does not make the problem infeasible"
structural parameter nl_constraints_soft::Boolean = false
"First prediction stage the nonlinear stage constraints are enforced at. At stage 0 the state is the measured one, so a constraint that depends on the state alone (the controls being rates, say) is either satisfied or infeasible there; 1 leaves it out, which lets the constraints be hard where the plant is the model"
structural parameter nl_constraints_from_stage::Integer = 0
"NLP solver: SQP to convergence, or one real-time iteration per tick"
structural parameter nlp_solver::ACADOSSolver = ACADOSSolver.SQP()
"QP solver: HPIPM after partial condensing (to qp_cond_N stages) or full condensing, or DAQP after full condensing (only in an acados built with it, see ACADOSQPSolver)"
structural parameter qp_solver::ACADOSQPSolver = ACADOSQPSolver.PartialCondensingHPIPM()
"Integrator of the shooting intervals"
structural parameter integrator::ACADOSIntegrator = ACADOSIntegrator.ERK()
"How the dynamics Jacobian is obtained: Symbolic (default) or an AD backend that differentiates a numeric evaluation of the model, handling linear-solve/multibody models; the Sparse variants exploit a detected sparsity pattern. Only affects the Julia solver, not the exported C"
structural parameter jacobian_backend::ACADOSJacobianBackend = ACADOSJacobianBackend.Symbolic()
"Runge-Kutta stages of the integrator (ERK: 1, 2 or 4; IRK: 1 to 4)"
structural parameter integrator_stages::Integer = 4
"Collocation scheme of the IRK integrator; RadauIIA for a stiff model integrated over long shooting intervals"
structural parameter collocation::ACADOSCollocation = ACADOSCollocation.GaussLegendre()
"Integration steps per shooting interval"
structural parameter integrator_steps::Integer = 1
"Integration sub-steps of each shooting interval (Np entries), for a non-uniform time_steps grid whose long intervals need more; empty uses integrator_steps for every interval"
structural parameter integrator_steps_per_interval::Integer[:] = fill(0, 0)
"IRK early termination threshold for the infinity norm of the Newton update to scaled internal state derivatives and algebraic variables; zero uses the fixed iteration count"
structural parameter integrator_newton_tol::Real = 0.0
"Newton iterations of the implicit integrator per integration step, with the Jacobian reused across them unless `integrator_jac_reuse` is false (IRK only). Every SQP iteration starts the integrator from the stage values of the previous iterate, so one or two iterations are often enough once the iterations are warm"
structural parameter integrator_newton_iter::Integer = 3
"Evaluate the Jacobian of the implicit integrator once per shooting interval and reuse it across its Newton iterations and sub-steps (IRK only). Set to false for a model whose Jacobian changes by orders of magnitude within an interval, such as the charge balance of a pH neutralization, where the reused Jacobian makes the iterations diverge"
structural parameter integrator_jac_reuse::Boolean = true
"Maximum SQP iterations per tick (SQP only); tunable, but acados sizes its memory by the value the model is built with, so it can later be lowered but not raised"
parameter max_iter::Integer = 100
"Horizon of the partially condensed QP; -1 for the full horizon Np"
structural parameter qp_cond_N::Integer = -1
"Warm start of each QP from the solution of the previous one: 0 cold (the acados default), 1 primal, 2 primal and dual. Level 2 halves the QP time of a problem with many soft constraints, whose slack variables and multipliers change little between the SQP iterations"
structural parameter qp_warm_start::Integer = 0
"NLP stationarity tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_stat::Real = 0.0
"NLP equality tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_eq::Real = 0.0
"NLP inequality tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_ineq::Real = 0.0
"NLP complementarity tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_comp::Real = 0.0
"QP tolerance strategy; adaptive accuracy uses the current NLP residuals and requires SQP"
structural parameter qp_tol_strategy::ACADOSQPToleranceStrategy = ACADOSQPToleranceStrategy.Fixed()
"Fixed QP stationarity tolerance; zero follows the effective NLP stationarity tolerance"
structural parameter qp_tol_stat::Real = 0.0
"Fixed QP equality tolerance; zero follows the effective NLP equality tolerance"
structural parameter qp_tol_eq::Real = 0.0
"Fixed QP inequality tolerance; zero follows the effective NLP inequality tolerance"
structural parameter qp_tol_ineq::Real = 0.0
"Fixed QP complementarity tolerance; zero follows the effective NLP complementarity tolerance"
structural parameter qp_tol_comp::Real = 0.0
"Residual reduction factor for the adaptive joint QP tolerance"
structural parameter qp_tol_reduction_factor::Real = 0.1
"Lower bound on adaptive QP tolerances relative to the effective NLP tolerance of each residual"
structural parameter qp_tol_safety_factor::Real = 0.1
"Adapt Levenberg-Marquardt regularization using accepted step lengths and the objective; ignores the constant levenberg_marquardt parameter"
structural parameter with_adaptive_levenberg_marquardt::Boolean = false
"Initial adaptive Levenberg-Marquardt damping factor at each NLP solve"
structural parameter adaptive_levenberg_marquardt_mu0::Real = 1e-3
"Minimum adaptive Levenberg-Marquardt damping factor"
structural parameter adaptive_levenberg_marquardt_mu_min::Real = 1e-16
"Multiplicative factor for adaptive Levenberg-Marquardt damping; must exceed one"
structural parameter adaptive_levenberg_marquardt_lam::Real = 5.0
"Objective multiplier for adaptive Levenberg-Marquardt regularization; regularization is objective times this multiplier times damping"
structural parameter adaptive_levenberg_marquardt_obj_scalar::Real = 2.0
"Reduction factor for the line-search step length"
structural parameter globalization_alpha_reduction::Real = 0.7
"Apply a full dual step when the line search reduces the primal step"
structural parameter globalization_full_step_dual::Boolean = false
"Globalization of the SQP iterations"
structural parameter globalization::ACADOSGlobalization = ACADOSGlobalization.FixedStep()
"Verbosity of acados (0 silent)"
structural parameter print_level::Integer = 0
"Initial guess of the NLP at every tick"
structural parameter warm_start::ACADOSWarmStart = ACADOSWarmStart.Shift()
"Solver backend; Auto and Julia call libacados in-process, C compiles a standalone acados solver library (required for C code export). The C solver keeps its state in a process-global capsule, so use it for export and single-loop deployment rather than repeated simulation"
structural parameter backend::Backend = Backend.Auto()
"Retry a failed solve (NaN detected, minimum step, QP failure, unbounded) once from a fresh initial guess at the current state, so that a shifted warm start cannot carry a broken iterate from tick to tick; costs one extra solve on the failing tick only"
structural parameter reset_on_failure::Boolean = false
"Also record the predicted state and control trajectories, the KKT residuals and the iteration count of every tick (variables x_pred, u_pred, residuals, iterations), for debugging and mpc_gui; Julia backend only"
structural parameter output_trajectories::Boolean = false
"acados solver built from the parameters above; pass build_acados_mpc(...) to bypass them"
structural parameter mpc::MPCRef = build_acados_mpc(dynamics = dynamics, inputs = inputs, disturbance_inputs = disturbance_inputs, known_disturbance_inputs = known_disturbance_inputs, tunable_parameters = tunable_parameters, state_variables = state_variables, Ts = Ts, Np = Np, time_steps = time_steps, outputs = outputs, C = C, constrained = constrained, constrained_min = constrained_min, constrained_max = constrained_max, terminal_lqr_cost = terminal_lqr_cost, u_operating_point = u_operating_point, penalize_increments = penalize_increments, normalize_increment_penalty = normalize_increment_penalty, u0 = u_guess, x0 = x_guess, x_scale = x_scale, u_scale = u_scale, du_scale = du_scale, reference_tracking = reference_tracking, reference_preview = reference_preview, parameter_preview = parameter_preview, umin = umin, umax = umax, dumin = dumin, dumax = dumax, xmin = xmin, xmax = xmax, state_bounds_soft = state_bounds_soft, soft_weight = soft_weight, Ax = Ax, Au = Au, lb = lb, ub = ub, terminal_constrained = terminal_constrained, terminal_constrained_min = terminal_constrained_min, terminal_constrained_max = terminal_constrained_max, AxN = AxN, lbN = lbN, ubN = ubN, terminal_constraints_soft = terminal_constraints_soft, nl_constrained = nl_constrained, nl_constrained_min = nl_constrained_min, nl_constrained_max = nl_constrained_max, nl_terminal_constrained = nl_terminal_constrained, nl_terminal_constrained_min = nl_terminal_constrained_min, nl_terminal_constrained_max = nl_terminal_constrained_max, nl_terminal_constraints_soft = nl_terminal_constraints_soft, nl_constraints_soft = nl_constraints_soft, nl_constraints_from_stage = nl_constraints_from_stage, nl_convex_inner = nl_convex_inner, nl_convex_outer = nl_convex_outer, nl_convex_min = nl_convex_min, nl_convex_max = nl_convex_max, nlp_solver = nlp_solver, qp_solver = qp_solver, integrator = integrator, collocation = collocation, jacobian_backend = jacobian_backend, integrator_stages = integrator_stages, integrator_steps = _acados_component_integrator_steps(integrator_steps, integrator_steps_per_interval), integrator_newton_tol = integrator_newton_tol, integrator_newton_iter = integrator_newton_iter, integrator_jac_reuse = integrator_jac_reuse, max_iter = max_iter, qp_cond_N = qp_cond_N, qp_warm_start = qp_warm_start, nlp_tol_stat = nlp_tol_stat, nlp_tol_eq = nlp_tol_eq, nlp_tol_ineq = nlp_tol_ineq, nlp_tol_comp = nlp_tol_comp, qp_tol_strategy = qp_tol_strategy, qp_tol_stat = qp_tol_stat, qp_tol_eq = qp_tol_eq, qp_tol_ineq = qp_tol_ineq, qp_tol_comp = qp_tol_comp, qp_tol_reduction_factor = qp_tol_reduction_factor, qp_tol_safety_factor = qp_tol_safety_factor, with_adaptive_levenberg_marquardt = with_adaptive_levenberg_marquardt, adaptive_levenberg_marquardt_mu0 = adaptive_levenberg_marquardt_mu0, adaptive_levenberg_marquardt_mu_min = adaptive_levenberg_marquardt_mu_min, adaptive_levenberg_marquardt_lam = adaptive_levenberg_marquardt_lam, adaptive_levenberg_marquardt_obj_scalar = adaptive_levenberg_marquardt_obj_scalar, globalization_alpha_reduction = globalization_alpha_reduction, globalization_full_step_dual = globalization_full_step_dual, globalization = globalization, print_level = print_level, warm_start = warm_start, log_residuals = output_trajectories, reset_on_failure = reset_on_failure)
"State dimension of the model (derived from mpc)"
final structural parameter nx::Integer = _acados_nx(mpc)
"Number of controls (derived from mpc)"
final structural parameter nu::Integer = _acados_nu(mpc)
"Number of controlled outputs (derived from mpc)"
final structural parameter ny::Integer = _acados_ny(mpc)
"Width of the reference input: 0, ny or ny*Np with reference_preview (derived from mpc)"
final structural parameter nr_in::Integer = _acados_nr_in(mpc)
"Width of the parameter input: the number of online parameters of the model, np, or np*Np with parameter_preview; zero when the values come from p_values instead (derived)"
final structural parameter np_in::Integer = _acados_np_in(mpc, parameter_input)
"Whether the r connector exists (derived)"
final structural parameter has_r::Boolean = nr_in > 0
"Whether the p connector exists (derived)"
final structural parameter has_p::Boolean = np_in > 0
"Width of the internal reference vector, max(nr_in, 1) (derived)"
final structural parameter nr_eff::Integer = max(nr_in, 1)
"Width of the internal parameter vector and of p_values, max(np, 1) or max(np*Np, 1) with parameter_preview; independent of whether the p connector exists (derived)"
final structural parameter np_eff::Integer = max(_acados_np_vec(mpc), 1)
"Length of the predicted state trajectory, (Np + 1) nx (derived from mpc)"
final structural parameter nxp::Integer = _acados_nxp(mpc)
"Length of the predicted control trajectory, Np nu (derived from mpc)"
final structural parameter nup::Integer = _acados_nup(mpc)
"Side of the terminal weight Qf: ny, or nx with terminal_lqr_cost (derived from mpc)"
final structural parameter nyf::Integer = _acados_nyf(mpc)
"Control assumed applied before the first tick, by default zero"
parameter u0::Real[nu] = _acados_u0(mpc)
"Output weight on C*x - r (ny × ny, e.g. diagonal([1.0, 10.0])); tunable. Default: the identity, or the weight the mpc object was built with"
parameter Q1::Real[ny, ny] = _acados_Q1(mpc)
"Control weight (nu × nu); tunable. Default: the identity, or the weight the mpc object was built with"
parameter Q2::Real[nu, nu] = _acados_Q2(mpc)
"Penalty on the control increments (nu × nu); tunable, must be zero unless penalize_increments. Default: the identity with penalize_increments, else zero"
parameter Q3::Real[nu, nu] = _acados_Q3(mpc)
"Terminal output weight (ny × ny); tunable, ignored with terminal_lqr_cost (then nx × nx and following Q1 and Q2). Default: Q1, or the weight the mpc object was built with"
parameter Qf::Real[nyf, nyf] = _acados_Qf(mpc, Q1)
"Constant term of the linear control cost (Eu p + eu)' u at every stage (nu), in LinearMPC's convention next to the stage cost ½ u' Q2 u, so that eu = -Q2 u_ref centers the control penalty on u_ref; a nonzero cost requires positive definite Q2 unless the increment formulation is used; tunable. Default: zero, or the cost the mpc object was built with"
parameter eu::Real[nu] = _acados_eu(mpc)
"Number of columns of Eu, max(np, 1) (derived from mpc)"
final structural parameter nEu::Integer = _acados_nEu(mpc)
"Gain of the online parameters p in the linear control cost (nu × np, p of the stage's shooting interval); a model without online parameters has a single column, which must be zero; tunable. Default: zero, or the gain the mpc object was built with"
parameter Eu::Real[nu, nEu] = _acados_Eu(mpc)
"Default SQP convergence tolerance for residuals without a positive nlp_tol_* override; tunable"
parameter tol::Real = _acados_tol(mpc)
"Levenberg-Marquardt regularization added to the Gauss-Newton Hessian; tunable"
parameter levenberg_marquardt::Real = _acados_levenberg_marquardt(mpc)
"State (in the `state_variables` order) the LQR terminal weight is linearized at and centered on; tunable, used only with terminal_lqr_cost. Changing it re-linearizes the model and re-solves the Riccati equation. Default: the origin"
parameter operating_point::Real[nx] = _acados_operating_point(mpc)
"Whether the compiled C solver is used (resolved from backend; the library is built on first use with the weights and options above)"
final structural parameter use_c::Boolean = _acados_use_c(mpc, backend, output_trajectories, Q1, Q2, Q3, Qf, eu, Eu, tol, levenberg_marquardt, max_iter, operating_point)
"Whether the in-process Julia solver is used (derived)"
final structural parameter use_julia::Boolean = use_c == false
"Zero placeholder passed as the reference argument when r is absent"
final parameter r_dummy::Real[nr_eff] = fill(0.0, nr_eff)
"Values of the parameters named by `tunable_parameters`, used when the p connector is absent; tunable. Default: the values the model carries. np entries, or np*Np column-major entries with parameter_preview"
parameter p_values::Real[np_eff] = _acados_p_default(mpc, np_eff)
"Reference vector handed to the step function"
variable r_int::Real[nr_eff]
"Parameter vector handed to the step function"
variable p_int::Real[np_eff]
"Predicted states at stages 0..Np, column-major nx × (Np+1) in the order of the x input (only with output_trajectories)"
variable x_pred::Real[nxp] if output_trajectories
"Predicted controls at stages 0..Np-1, column-major nu × Np (only with output_trajectories)"
variable u_pred::Real[nup] if output_trajectories
"KKT residuals of the last iteration: stationarity, equality, inequality, complementarity (only with output_trajectories)"
variable residuals::Real[4] if output_trajectories
"SQP iterations of the tick (only with output_trajectories)"
variable iterations::Real if output_trajectories
relations
if has_r
r_int = r
else
r_int = r_dummy
end
if has_p
p_int = p
else
p_int = p_values
end
initial u@(clk-1) = u0
# One solve per tick, on both backends: the scalar status is the only result of the call, and
# everything else is read back through accessors that depend on it, so that no consumer can
# duplicate the solve. The recorded trajectories and residuals are pure reads and are taken a whole
# buffer at a time: the clocked compiler keeps an array-valued equation intact and emits a single
# call for it, so their cost does not grow with the prediction horizon.
if use_c
exitflag@clk = acados_solve_c(x@clk, r_int@clk, p_int@clk, u@(clk-1), Q1, Q2, Q3, Qf, eu, Eu, tol, levenberg_marquardt, max_iter, operating_point, mpc)
for i in 1:nu
u[i] = acados_u_c(exitflag, i, mpc)
end
else
initial ntick@(clk-1) = 0
ntick@clk = ntick@(clk-1) + 1
exitflag@clk = acados_solve(x@clk, r_int@clk, p_int@clk, u@(clk-1), ntick@(clk-1), Q1, Q2, Q3, Qf, eu, Eu, tol, levenberg_marquardt, max_iter, operating_point, mpc)
for i in 1:nu
u[i] = acados_u(exitflag, i, mpc)
end
if output_trajectories
x_pred = acados_x_pred_vec(exitflag, mpc)
u_pred = acados_u_pred_vec(exitflag, mpc)
residuals = acados_residuals_vec(exitflag, mpc)
iterations = acados_iterations(exitflag, mpc)
end
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/ACADOSMPC.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Dynamics": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"disturbance_inputs",
"known_disturbance_inputs",
"tunable_parameters",
"p_values",
"parameter_input",
"state_variables",
"Ts",
"Np",
"time_steps",
"C",
"jacobian_backend"
]
},
"Cost function": {
"control": "fieldset",
"contents": [
"outputs",
"Q1",
"Q2",
"Qf",
"eu",
"Eu",
"terminal_lqr_cost",
"operating_point",
"u_operating_point",
"penalize_increments",
"normalize_increment_penalty",
"Q3",
"reference_tracking",
"reference_preview",
"parameter_preview",
"u_guess",
"x_guess",
"u0",
"x_scale",
"u_scale",
"du_scale"
]
},
"Constraints": {
"control": "fieldset",
"contents": [
"umin",
"umax",
"dumin",
"dumax",
"constrained",
"constrained_min",
"constrained_max",
"nl_constrained",
"nl_constrained_min",
"nl_constrained_max",
"nl_constraints_soft"
],
"children": {
"Advanced": {
"contents": [
"state_bounds_soft",
"soft_weight",
"xmin",
"xmax",
"Ax",
"Au",
"lb",
"ub",
"nl_constraints_from_stage",
"nl_convex_inner",
"nl_convex_outer",
"nl_convex_min",
"nl_convex_max"
]
},
"Terminal": {
"contents": [
"terminal_constrained",
"terminal_constrained_min",
"terminal_constrained_max",
"terminal_constraints_soft",
"AxN",
"lbN",
"ubN",
"nl_terminal_constrained",
"nl_terminal_constrained_min",
"nl_terminal_constrained_max",
"nl_terminal_constraints_soft"
]
}
}
},
"Solver options": {
"control": "fieldset",
"contents": [
"nlp_solver",
"qp_solver",
"integrator",
"max_iter",
"tol",
"warm_start",
"reset_on_failure",
"backend",
"output_trajectories"
],
"children": {
"Advanced": {
"contents": [
"integrator_stages",
"collocation",
"integrator_steps",
"integrator_steps_per_interval",
"integrator_newton_iter",
"integrator_newton_tol",
"integrator_jac_reuse",
"qp_cond_N",
"qp_warm_start",
"nlp_tol_stat",
"nlp_tol_eq",
"nlp_tol_ineq",
"nlp_tol_comp",
"qp_tol_strategy",
"qp_tol_stat",
"qp_tol_eq",
"qp_tol_ineq",
"qp_tol_comp",
"qp_tol_reduction_factor",
"qp_tol_safety_factor",
"with_adaptive_levenberg_marquardt",
"adaptive_levenberg_marquardt_mu0",
"adaptive_levenberg_marquardt_mu_min",
"adaptive_levenberg_marquardt_lam",
"adaptive_levenberg_marquardt_obj_scalar",
"globalization_alpha_reduction",
"globalization_full_step_dual",
"globalization",
"levenberg_marquardt",
"print_level",
"mpc"
]
}
}
},
"Derived": {
"control": "fieldset",
"contents": ["nx", "nu", "ny", "nr_in", "np_in", "has_r", "has_p"]
}
}
}
}
}
endFlattened Source
"""
Nonlinear model-predictive controller built on acados (https://github.com/acados/acados), run as a
clocked component. The C library is called directly from Julia; the prediction model is given
symbolically and integrated by acados.
# Model
The prediction model is the continuous-time system
```math
\\dot x = f(x, u, p)
```
with state ``x`` (nx), control ``u`` (nu) and optional online parameters ``p`` (np), given as a
`ContinuousDynamics` object (see `continuous_dynamics`: from Symbolics expressions, from a traceable
Julia function, or from a continuous-time Dyad/ModelingToolkit model whose inputs are named). Every
parameter of such a model is substituted by its value when the controller is built, so that the
compiled solver carries numbers rather than symbols; those named by `tunable_parameters` are the
exception and stay symbolic as the online parameters ``p``, whose values are the tunable parameter
`p_values` (see *Tunable parameters* below). acados
discretizes it over `Np` shooting intervals of length `Ts` with an explicit (`ERK`, default RK4) or
implicit (`IRK`) Runge-Kutta integrator (`integrator_stages` stages, `integrator_steps` sub-steps per
interval; `collocation` selects Gauss-Legendre or the L-stable Radau IIA scheme for the implicit one,
and `integrator_newton_iter` the Newton iterations it takes per step). `time_steps` replaces that uniform grid by one of `Np` intervals of the given lengths: the
first is always `Ts` (its control is the one that is applied and held for a clock period) and the
later ones may be longer, which covers a long prediction horizon with few decision variables.
`geometric_time_steps(Ts, Np, Tf)` and `linear_time_steps(Ts, Np, Tf)` lay out such a grid over the
horizon ``T_f``. A long interval is integrated with the same `integrator_steps` sub-steps as a short
one unless `integrator_steps_per_interval` supplies a vector of sub-step counts. Choose enough
sub-steps to preserve integration accuracy over every interval. The prediction model must not contain events. The controlled outputs are ``y = C x``, or
``y = y(x)`` when an output signal is a nonlinear function of the state (see `outputs`).
# Objective
```math
\\sum_{k=0}^{N_p-1} \\left( (C x_k - r_k)^T Q_1 (C x_k - r_k) + u_k^T Q_2 u_k + \\Delta u_k^T Q_3 \\Delta u_k \\right) + (C x_{N_p} - r_{N_p})^T Q_f (C x_{N_p} - r_{N_p})
```
with ``\\Delta u_k = u_k - u_{k-1}``, ``u_{-1}`` the control applied at the previous tick, and ``r_k = r`` the
reference of the tick unless `reference_preview` provides one per prediction step. With `time_steps`
the term of stage ``k`` is weighted by ``\\Delta t_k / T_s``, the length of its shooting interval
relative to the first, so that a long interval counts for as much as the short ones it replaces; on
the uniform grid every factor is 1 and the objective is the plain sum written above. `Q1`, `Q2`, `Qf`
and `Q3` are full matrices (`diagonal([1.0, 10.0])` builds a diagonal one) and ordinary, tunable
parameters; by default `Q1 = Q2 = I`, `Qf = Q1` and `Q3 = 0`. `terminal_lqr_cost = true` replaces the
terminal term by ``(x_{N_p} - x_{op})^T P (x_{N_p} - x_{op})`` with ``P`` the infinite-horizon LQR
cost-to-go of the model linearized at the operating point ``x_{op}`` = `operating_point` (as
DyadControlSystems' `QMPCProblem` does by default, at the origin), which lets a short horizon behave
like the infinite-horizon regulator around that point; `Qf` is then ignored. The operating point (an
equilibrium, e.g. the upright state of an inverted pendulum) defaults to the origin, is tunable, and
is independent of the tracked reference `r`, so `terminal_lqr_cost` may be combined with
`reference_tracking`. `u_operating_point` is the control there (fixed at build). Without the
augmentation (`penalize_increments` or a control-rate bound, see below), `Q3` must be zero and `Q1`
and `Q2` must be positive definite. With
`penalize_increments = true` the model is augmented with the plant's own control as a state and the
control rate is the decision variable, so the increment over a shooting interval is weighted by `Q3`
(positive definite) and `Q1` and `Q2` may then be singular (`Q2 = 0` penalizes the increments only).
Either integrator works; the augmented model represents the control as ramping across a shooting
interval where the actuator holds it, a mismatch that vanishes as the solution converges. On a non-uniform
`time_steps` grid, increments and their bounds refer to each complete shooting interval.
`normalize_increment_penalty = true` scales the increment weight by `(Ts/time_steps[k])^2`, so
`Q3` penalizes the average control rate expressed as an increment over `Ts`. With the stage duration
weight `time_steps[k]/Ts`, this preserves the uniform-grid rate-penalty interpretation. The default
`false` penalizes complete-interval increments. The option affects neither increment bounds nor
endpoint input costs or constraints; both choices coincide on a uniform grid. Stage
input costs and input-dependent constraints use the ramp endpoint. The augmentation on a non-uniform
grid requires the Julia backend. A `Discrete` prediction model applies one period of its map per shooting interval
and requires a uniform grid. With
`reference_tracking = false` the controller regulates to the origin and has no `r` input.
`eu` and `Eu` add the linear control cost ``(E_u p_k + e_u)^T u_k`` to each stage, in the convention of
LinearMPC, whose stage cost is ``\\tfrac12 u^T Q_2 u + (E_u p + e_u)^T u``, so that ``e_u = -Q_2 u_{ref}``
centers the control penalty on ``u_{ref}`` (a trim control, say), and ``E_u`` lets that center follow the
online parameters ``p_k`` of the stage. Without increment augmentation the economic cost is
represented by the control reference ``-Q_2^{-1} (E_u p_k + e_u)``, which requires positive definite
`Q2` whenever the cost is nonzero. With increment augmentation both backends evaluate the
economic term directly, so `Q2 = 0` is supported. Both economic weights are tunable.
Nonlinear stage and terminal constraints, including soft constraints, and nonlinear least-squares
outputs can be combined with increments on both backends. Stage constraints are evaluated at
the stage boundary using the ramp endpoint command. The first ramp starts at the previously applied
command; the returned command is its endpoint.
# Constraints
Control bounds `umin ≤ u_k ≤ umax` (hard, all stages), control-rate bounds
`dumin ≤ u_k - u_{k-1} ≤ dumax` (hard, all stages, the first taken against the control last applied)
and state bounds `xmin ≤ x_k ≤ xmax` for
``k = 1..N_p``, soft by default (`state_bounds_soft`) with the quadratic slack penalty `soft_weight`.
General linear constraints `lb ≤ Ax x_k + Au u_k ≤ ub` are hard and enforced at ``k = 0..N_p-1``
(and at ``N_p`` when `Au` is empty). Terminal constraints `lbN ≤ AxN x_{N_p} ≤ ubN` (given by
`terminal_constrained`/`terminal_constrained_min`/`terminal_constrained_max`, or the matrix form
`AxN`/`lbN`/`ubN`) are enforced only at the terminal state — a terminal set — and are hard unless
`terminal_constraints_soft`. Nonlinear constraints `lh ≤ h(x, u, z) ≤ uh` (`nl_constrained`, and
`nl_terminal_constrained` for the terminal state) are arbitrary nonlinear functions of the signals
(acados BGH), supported by both backends; both can be softened with slack variables
(`nl_constraints_soft`, `nl_terminal_constraints_soft`). Empty bounds mean "none".
A stage constraint of the form ``\\varphi(r(x, u, z))`` with ``\\varphi`` convex in the inner vector
``r`` (a squared norm of nonlinear signals, say) may instead be given through `nl_convex_inner` (the
signals ``r``) and `nl_convex_outer` (a Julia function of the symbolic vector ``r`` returning the
constrained expressions), bounded by `nl_convex_min`/`nl_convex_max`. acados then adds the curvature of
``\\varphi`` to the Hessian of the QP (its BGP constraint module), which is what a Gauss-Newton SQP
lacks on a constraint whose gradient vanishes where the iterations start, such as a squared norm at
the origin: with the plain form the linearization carries no information and the steps overshoot the
constraint by a large factor. The convex form replaces `nl_constrained` at the stages (acados has one
nonlinear constraint module per stage; a plain constraint is the convex form with the identity outer
function), obeys `nl_constraints_soft` and `nl_constraints_from_stage` alike, and is not available with
C code export.
# Scaling
`x_scale` and `u_scale` give the typical magnitude of each state variable (in the `state_variables`
order) and of each control. The optimization runs in the scaled variables ``\\tilde x = x / x_\\mathrm{scale}``,
``\\tilde u = u / u_\\mathrm{scale}``: the prediction model, the bounds, the outputs and the constraints
are rewritten in them, while every parameter of the component -- weights, bounds, references, guesses,
the operating point -- and every signal it exchanges remain in physical units. A model whose state
variables span very different magnitudes (a position in kilometres next to an angle in milliradians)
is otherwise handed to the solver ill-conditioned: the SQP steps are damped or fail in
the small variables, one tolerance means different things for different variables, and the line search
stalls. `Q2` and `Q3` are given in physical units and apply to the physical control.
`du_scale` sets the typical physical increment per interval independently of `u_scale`; the rate
decision is the physical increment divided by `du_scale`. Empty uses `u_scale * Ts` for continuous
models and `u_scale` for discrete models. The physical ramp, cost and bounds are unchanged.
The rate bounds need the same augmentation as the increment penalty and switch it on by themselves:
`dumin`/`dumax` alone leave `Q3` at zero, so an actuator rate limit does not impose an increment
penalty (and `Q3` stays tunable afterwards). They carry the restrictions the augmentation carries —
no non-uniform grid, no DAE model, no nonlinear outputs or constraints.
# Signals and timing
At every tick the state `x` (measured or estimated), the reference `r` (a setpoint, or a trajectory with
`reference_preview`) and the parameters `p` (current values, or a trajectory with `parameter_preview`) are
read, the NLP is solved with the previous solution as the initial guess (`warm_start`; at the first
tick of a simulation the solver starts afresh from the current state, or from `x_guess` when it is
given, so that a compiled model can be simulated again without inheriting the trajectories of the
previous run), and the first control of
the optimal sequence is output as `u`. The control must be held (`ZeroOrderHold`) outside
this component, and `Ts` must equal the period of the clock. `exitflag` is the acados status of the
last solve: 0 success, 1 NaN detected, 2 maximum iterations, 3 minimum step, 4 QP failure, 5 ready,
6 unbounded, 7 timeout; on failure `u` is the solver's last iterate.
The default `warm_start = Shift` moves the previous solution one stage back and repeats its last
stage. When the last control of a solution influences nothing the cost weights, it is determined only
by the control weight and differs from the controls before it, and the shift places it where it does
matter. This is the case when the outputs are algebraic variables, since the terminal stage carries
none. On a strongly nonlinear model the SQP iterations that correct it can then diverge, and
`warm_start = Keep` is the better initial guess (see `PHNeutralizationMPCExample`).
# Previews
With `reference_preview` the `r` input carries a whole horizon as a flat column-major vector of `ny*Np`
entries, in the layout of the LinearMPC components: `r[(j-1)*ny + i]` is the reference for output `i` at
prediction step `j`, i.e. for the predicted state ``x_j`` at the end of shooting interval `j`; the terminal
cost uses block `Np`. A block is tied to a shooting interval, not to a fixed span of time, so with a
non-uniform `time_steps` grid the blocks are the values at the shooting nodes rather than samples of
the trajectory at the clock rate (a `DelayLine`, which taps the signal every `Ts`, produces the
latter). Likewise `parameter_preview` widens `p` to `np*Np` entries, `p[(j-1)*np + i]` being the
value of parameter `i` over shooting interval `j` (the terminal stage reuses block `Np`). A `DelayLine` turns
a signal known ahead of time into such a vector in one connection (see `ACADOSMPCPreviewExample`). With
`terminal_lqr_cost` and `reference_preview` the terminal cost is centered on the steady state of the
linearized model for the final reference block, ``x_{op} + M_r (r_{N_p} - C x_{op})``, where ``M_r`` solves
the steady-state equations of the linearization at the operating point; this requires at most as many
controlled outputs as controls (`ny ≤ nu`) and a full-rank steady-state map, checked at build. The terminal
weight itself stays the one of the operating point, and in exported C both the weight and ``M_r`` are frozen
at their export-time values.
With `output_trajectories = true` the component also records, at every tick, what the solver predicted
and how well it converged, in the variables `x_pred` (the predicted states at stages 0..Np, column-major
nx × (Np+1), in the order of the `x` input), `u_pred` (the predicted controls at stages 0..Np-1,
column-major nu × Np), `residuals` (the stationarity, equality, inequality and complementarity residuals
of the last iteration, to compare with `tol`) and `iterations` (SQP iterations of the tick). Read them
from a solution as `sol[model.mpc.x_pred]` (one vector per tick), or visualize them with `mpc_gui` (a
Makie backend must be loaded). This is a debugging aid for the Julia backend (`backend = C` is refused).
With `SQP` the residuals are those of the returned iterate; with `SQP_RTI` acados computes (only then) the
residuals of the iterate the tick linearized at, i.e. how far from optimal the real-time iteration started.
# Solver
`SQP` iterates to the tolerance `tol` (at most `max_iter` iterations); `SQP_RTI` performs one
iteration per tick (real-time iteration, relies on `warm_start = Shift`). The QP is solved by HPIPM
after partial condensing to `qp_cond_N` stages (-1 keeps the horizon) or full condensing, or by DAQP
after full condensing (`FullCondensingDAQP`, in an acados built with it). The Hessian is the
Gauss-Newton approximation of the least-squares cost, plus the curvature of the outer function of the
convex-over-nonlinear constraints when there are any.
`tol` is the default absolute stopping threshold for stationarity, dynamics defects, inequality
violations and complementarity in the scaled optimization coordinates, and must be positive.
Independent structural thresholds `nlp_tol_stat`, `nlp_tol_eq`, `nlp_tol_ineq` and `nlp_tol_comp`
override the corresponding residual threshold when positive; zero follows the tunable `tol`. Positive
overrides are unaffected by changes to `tol`, and require `nlp_solver = SQP`, since `SQP_RTI` performs
a single iteration without a stopping test. With `qp_tol_strategy = Fixed`, the QP tolerances
`qp_tol_stat`, `qp_tol_eq`, `qp_tol_ineq` and `qp_tol_comp` are independent positive thresholds; a zero
value follows the effective NLP threshold of the same residual. With
`qp_tol_strategy = AdaptiveCurrentResidualJoint`, SQP sets the QP accuracy from the largest NLP
residual: the joint threshold is capped at 0.01 and reduced by `qp_tol_reduction_factor`, with
`qp_tol_safety_factor` times the effective NLP threshold of each residual as a lower bound. The cap
does not apply to this lower bound, so an NLP threshold above `0.01 / qp_tol_safety_factor` yields a
QP threshold above 0.01, and a warning is issued when the controller is constructed. Positive fixed
QP tolerances cannot be combined with this strategy. The adaptive strategy requires SQP.
`with_adaptive_levenberg_marquardt = true` replaces constant regularization by the current
objective times `adaptive_levenberg_marquardt_obj_scalar` times a damping factor. The damping
starts at `adaptive_levenberg_marquardt_mu0` for each solve, decreases after full steps, increases
after reduced steps, and is bounded below by `adaptive_levenberg_marquardt_mu_min`. The multiplier
`adaptive_levenberg_marquardt_lam` determines these changes. The constant tunable
`levenberg_marquardt` is ignored in this mode. These options and the QP accuracy strategy are
structural parameters. `globalization_alpha_reduction` controls backtracking, and
`globalization_full_step_dual` selects a full multiplier update when the primal step is reduced.
# Robustness
`reset_on_failure = true` retries a solve that failed (NaN, QP failure) once from a fresh initial
guess at the current state, which is what keeps a shifted warm start from carrying a broken iterate
from tick to tick.
# Tunable parameters
The weights `Q1`, `Q2`, `Q3`, `Qf`, the linear control cost `eu`, `Eu`, the solver settings `tol`,
`levenberg_marquardt` and `max_iter`, and the `operating_point` are ordinary parameters, so a parameter
of an enclosing component may be passed down to them: they can be changed after the model is compiled, and
in exported C code they are fields of the parameter struct. acados is updated only when a value changes.
`max_iter` can be lowered but not raised above the value the model was built with (acados sizes its
memory by it; a larger value is clamped with a warning). With `terminal_lqr_cost` the terminal weight
follows `Q1`, `Q2` and the operating point in Julia (a new operating point re-linearizes the model and
re-solves the Riccati equation) but stays at its export-time value in C. The horizon, constraints and solver
choices are structural and fixed at build time, and so is the *structure* of the prediction model.
Parameters of the prediction model are tunable when they are named by `tunable_parameters`:
```
mpc = ACADOSMPC(dynamics = plant, inputs = [\"tau\"], tunable_parameters = [\"m\", \"c\"], ...)
prob = ODEProblem(ssys, [ssys.mpc.p_values => [2.0, 0.2]], (0.0, 5.0))
```
`p_values` then holds their values (by default the ones the model carries), is an ordinary parameter
like the weights, and is a field of the parameter struct in exported C. It is written to acados
before every solve, so the compiled controller predicts with the values the problem was built with;
being a parameter, it does not change during a simulation. To vary the parameters over a simulation,
or to give each shooting interval its own values, connect the `p` input instead (it exists whenever
the model has online parameters and `parameter_input` is true, which is the default unless
`tunable_parameters` names them; `parameter_preview` widens it to one block per interval). With
`terminal_lqr_cost` the terminal weight is linearized at the values the model was built with and does
not follow `p_values`.
A parameter of a subcomponent is named hierarchically, `tunable_parameters = [\"link.m\"]`, and
`state_variables`, `outputs` and the other signal lists take the same spelling
(`ACADOSMPCNestedParameterExample`). Only a parameter that survives into the compiled model can be
named: one bound to a parameter of an enclosing component (`link = Plant(final m = m)`) is
substituted before the equations are built, so the parameter to name is the one it is bound to, and
naming the bound one is rejected rather than left as an entry of `p` that the dynamics ignore. The
values reach acados as doubles, which limits what a parameter may be: an integer-valued parameter of
the model is kept online, but nothing constrains its entry of `p_values` to integers, and a parameter
the model uses as a Boolean (the condition of an `ifelse`) cannot be online at all. An array-valued
parameter named whole (`\"k\"`) contributes each of its elements, in order; named by element
(`\"k[1]\"`), the elements not named keep their values.
# Measured disturbances
An input of the plant whose value is measured rather than decided, such as a measured inflow, is named
by `known_disturbance_inputs`. The controller does not optimize it; it becomes an online parameter of
the prediction model, whose value is the corresponding entry of `p`, after the entries of
`tunable_parameters`, and is held over the horizon unless `parameter_preview` supplies a trajectory.
The same plant component can then serve as the simulated plant, as the prediction model and, with
the measured input listed in its `inputs`, as the model of a `UKFObserver`
(`PHNeutralizationMPCExample`).
# Notes
acados comes from `DyadAcados_jll`; on a
platform without JLL binaries `MPCComponents.install_acados!()` builds it into a scratch space, and
`ACADOS_INSTALL_DIR` points at an existing installation. Models containing this component are exported
to C with the `CCodeExport` analysis (`backend = C`). Further acados features
(exact Hessians, the QP solvers other than HPIPM and DAQP) are not exposed yet; build the controller
with `build_acados_mpc` in Julia to pass options as keywords.
"""
component ACADOSMPC
"State (measured or estimated) at the current tick, in the order given by `state_variables`"
x = [RealInput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 785, "x2": 0, "y2": 885}}}
}
"References for the controlled outputs y = C x; ny entries, or ny*Np column-major entries with reference_preview (only with reference_tracking)"
r = [RealInput@[clk]() for i in 1:nr_in] if has_r {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 115, "x2": 0, "y2": 215}}}
}
"Online parameters of the prediction model; np entries, or np*Np column-major entries with parameter_preview (only when the dynamics have parameters)"
p = [RealInput@[clk]() for i in 1:np_in] if has_p {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 450, "x2": 0, "y2": 550}}}
}
"Control signal (first move of the optimal sequence)"
u = [RealOutput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 700, "x2": 1100, "y2": 800}}}
}
"acados status of the last solve: 0 success, -1 nonzero economic cost with singular Q2 and no increment augmentation (C backend), 1 NaN detected, 2 maximum iterations, 3 minimum step, 4 QP failure, 5 ready, 6 unbounded, 7 timeout"
exitflag = RealOutput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 200, "x2": 1100, "y2": 300}}}
}
"Number of ticks solved so far in the simulation; zero at the first tick, where the solver's warm start is reset so that a compiled model can be simulated again (Julia backend only: the C backend keeps its solver in a capsule that lives for the whole process and is meant for export and single-loop deployment, see `backend`)"
variable ntick::Real if use_julia
"Continuous-time prediction model ẋ = f(x, u, p): a ContinuousDynamics (see continuous_dynamics), or a continuous-time system/plant component whose control inputs are named by `inputs`: a subcomponent of the model (e.g. the plant), or a component constructor (e.g. MyPlant, or component_constructor(MyPlant, m = m) with modified arguments), which is instantiated"
structural parameter dynamics::MPCDynamics
"""
Names of the control inputs of `dynamics` when it is given as a system/plant component (e.g. ["tau"]); empty when `dynamics` is already a ContinuousDynamics
"""
structural parameter inputs::String[:] = []
"Names of the unknown disturbance inputs of `dynamics` when it is given as a system/plant component, e.g. the input `w` of an integrating disturbance state `der(d) = w`: they are zero in the prediction, and the states of their disturbance model are kept (see continuous_dynamics)"
structural parameter disturbance_inputs::String[:] = []
"Names of the measured disturbance inputs of `dynamics` when it is given as a system/plant component, e.g. a measured inflow: they are not decision variables but online parameters, whose values are the entries of `p` after those of `tunable_parameters`"
structural parameter known_disturbance_inputs::String[:] = []
"""
Names of the parameters of `dynamics` that stay symbolic in the compiled solver (e.g. ["m", "l"], or ["link.m"] for a parameter of a subcomponent); their values are the tunable parameter `p_values` and may be changed on a compiled problem. Every other parameter of the model is substituted by its value when the controller is built
"""
structural parameter tunable_parameters::String[:] = []
"Take the online parameters from the `p` input connector instead of the tunable parameter `p_values`; the default is the connector unless `tunable_parameters` names the parameters and `known_disturbance_inputs` is empty"
structural parameter parameter_input::Boolean = _acados_default_parameter_input(tunable_parameters, known_disturbance_inputs)
"""
Names of the state variables carried by the entries of the x input, in the order they are connected (e.g. ["theta", "omega"], or ["link.th"] for a variable of a subcomponent); empty uses the model's own state order, which for a compiled system is chosen by the compiler
"""
structural parameter state_variables::String[:] = []
"Shooting interval; must equal the clock period"
structural parameter Ts::Real
"Prediction horizon (number of shooting intervals)"
structural parameter Np::Integer = 10
"Lengths of the Np shooting intervals; empty means Np uniform intervals of length Ts. The first entry must equal Ts, the clock period, because the control of the first interval is the one that is applied. `geometric_time_steps(Ts, Np, Tf)` and `linear_time_steps(Ts, Np, Tf)` lay out a grid that starts at Ts and grows to cover the horizon Tf with few intervals"
structural parameter time_steps::Real[:] = fill(0.0, 0)
"""
Controlled outputs, named signals of the model (states or output connectors, e.g. ["theta"]); the cost weights y in this order and C is derived as ∂y/∂state. A signal that is a nonlinear function of the state (an energy, say) makes the cost a nonlinear least squares on y(x) in place of C x (C is then the Jacobian at the operating point, for the LQR terminal cost). Empty uses C (or the identity)
"""
structural parameter outputs::String[:] = []
"Output matrix of the controlled outputs (ny × nx), for a model whose state order is known (e.g. dynamics given as expressions); empty for the identity. Use `outputs` instead when the model is a system"
structural parameter C::Real[:, :] = fill(0.0, 0, 0)
"Use the infinite-horizon LQR cost of the model linearized at `operating_point` as terminal weight (on the full state) instead of Qf, and center the terminal cost on that point (with reference_preview: on the steady state of the linearization for the final reference block). In Julia the weight follows Q1, Q2 and the operating point; in exported C it stays at its export-time value"
structural parameter terminal_lqr_cost::Boolean = false
"Control at the operating point the LQR terminal weight is linearized at (nu entries); empty means zero. Fixed at build time (unlike operating_point, which is tunable)"
structural parameter u_operating_point::Real[:] = fill(0.0, 0)
"Penalize the control increments u@clk - u@(clk-1) with Q3: the prediction model is augmented with the plant's control as a state and the control rate becomes the decision variable. Non-uniform time_steps requires the Julia backend"
structural parameter penalize_increments::Boolean = false
"Scale each stage's increment penalty by (Ts/time_steps[k])^2 to penalize the average control rate expressed over Ts; false penalizes complete-interval increments. Endpoint costs, constraints and increment bounds are unchanged"
structural parameter normalize_increment_penalty::Boolean = false
"Control the solver's trajectories are initialized with (nu entries, also the default of u0); empty means zero"
structural parameter u_guess::Real[:] = fill(0.0, 0)
"State the solver's trajectories start from at the first tick of a simulation, at every stage (nx entries in the `state_variables` order); empty starts from the measured state. Give it when a constant trajectory at the measured state is a poor start for the iterations; the C backend starts the first solve after its library is loaded from it as well"
structural parameter x_guess::Real[:] = fill(0.0, 0)
"Typical magnitude of each state variable (nx entries in the `state_variables` order); empty means one. The solver works in x ./ x_scale, so a state spanning kilometres next to one spanning milliradians is well conditioned; every other parameter, weight and bound stays in physical units"
structural parameter x_scale::Real[:] = fill(0.0, 0)
"Typical magnitude of each control (nu entries); empty means one. The solver works in u ./ u_scale"
structural parameter u_scale::Real[:] = fill(0.0, 0)
"Typical physical control increment per interval (nu positive entries); empty uses u_scale * Ts for continuous models and u_scale for discrete models. Applies to rate augmentation independently of u_scale"
structural parameter du_scale::Real[:] = fill(0.0, 0)
"Track references (adds the r input); false regulates to the origin"
structural parameter reference_tracking::Boolean = true
"Take a reference trajectory over the horizon (ny*Np entries, r[(j-1)*ny + i] the reference for output i at prediction step j) instead of a setpoint; requires reference_tracking"
structural parameter reference_preview::Boolean = false
"Take a parameter trajectory over the horizon (np*Np entries, p[(j-1)*np + i] the value of parameter i over shooting interval j) instead of the current values"
structural parameter parameter_preview::Boolean = false
"Lower control bounds (nu); empty for none"
structural parameter umin::Real[:] = fill(0.0, 0)
"Upper control bounds (nu); empty for none"
structural parameter umax::Real[:] = fill(0.0, 0)
"Lower bound on the control increment u_k - u_{k-1} (nu); empty for none"
structural parameter dumin::Real[:] = fill(0.0, 0)
"Upper bound on the control increment u_k - u_{k-1} (nu); empty for none"
structural parameter dumax::Real[:] = fill(0.0, 0)
"Lower state bounds (nx), enforced at stages 1..Np; empty for none"
structural parameter xmin::Real[:] = fill(0.0, 0)
"Upper state bounds (nx), enforced at stages 1..Np; empty for none"
structural parameter xmax::Real[:] = fill(0.0, 0)
"""
Constrained signals, named signals of the model (e.g. ["omega"]); a signal that is a state becomes a state bound, any other becomes a general linear constraint (coefficients derived by linearization). Use instead of xmin/xmax/Ax/Au/lb/ub when the model is a system
"""
structural parameter constrained::String[:] = []
"Lower bounds of the constrained signals (same length as constrained)"
structural parameter constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the constrained signals (same length as constrained)"
structural parameter constrained_max::Real[:] = fill(0.0, 0)
"Soften the state bounds with slack variables"
structural parameter state_bounds_soft::Boolean = true
"Quadratic penalty weight on the slack variables of every softened constraint: the state bounds, the terminal rows and the nonlinear stage and terminal constraints"
structural parameter soft_weight::Real = 1e6
"State coefficient of the general constraints (m × nx); empty for zero"
structural parameter Ax::Real[:, :] = fill(0.0, 0, 0)
"Control coefficient of the general constraints (m × nu); empty for zero"
structural parameter Au::Real[:, :] = fill(0.0, 0, 0)
"Lower bounds of the general constraints (m); empty for none"
structural parameter lb::Real[:] = fill(0.0, 0)
"Upper bounds of the general constraints (m); empty for none"
structural parameter ub::Real[:] = fill(0.0, 0)
"""
Terminal constrained signals, applied only at the terminal state x_Np (e.g. ["omega"]); each must be a function of the state only and becomes a hard general linear constraint (a terminal set). Use instead of AxN/lbN/ubN when the model is a system
"""
structural parameter terminal_constrained::String[:] = []
"Lower bounds of the terminal constrained signals (same length as terminal_constrained)"
structural parameter terminal_constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the terminal constrained signals (same length as terminal_constrained)"
structural parameter terminal_constrained_max::Real[:] = fill(0.0, 0)
"State coefficient of the terminal general constraints lbN ≤ AxN x_Np ≤ ubN (mN × nx), for a model whose state order is known; empty for none"
structural parameter AxN::Real[:, :] = fill(0.0, 0, 0)
"Lower bounds of the terminal general constraints (mN); empty for none"
structural parameter lbN::Real[:] = fill(0.0, 0)
"Upper bounds of the terminal general constraints (mN); empty for none"
structural parameter ubN::Real[:] = fill(0.0, 0)
"Soften the terminal constraints with slack variables (penalized by soft_weight), keeping the problem solvable when the terminal set is unreachable within the horizon"
structural parameter terminal_constraints_soft::Boolean = false
"Nonlinear constrained signals, arbitrary (nonlinear) functions of the state, algebraic variables and control, enforced as lh ≤ h(x,u,z) ≤ uh at stages 0..Np-1 (acados BGH). Not available with C code export"
structural parameter nl_constrained::String[:] = []
"Lower bounds of the nonlinear constraints (same length as nl_constrained)"
structural parameter nl_constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the nonlinear constraints (same length as nl_constrained)"
structural parameter nl_constrained_max::Real[:] = fill(0.0, 0)
"Inner signals r of the convex-over-nonlinear stage constraints φ(r(x,u,z)), whose curvature acados adds to the QP (BGP); replaces nl_constrained at the stages. Not available with C code export"
structural parameter nl_convex_inner::String[:] = []
"Outer function φ of the convex-over-nonlinear constraints: a Julia function of the vector of inner signals returning the constrained expressions, convex in that vector"
structural parameter nl_convex_outer::ConvexOuter
"Lower bounds of the convex-over-nonlinear constraints (one per expression nl_convex_outer returns; empty for none)"
structural parameter nl_convex_min::Real[:] = fill(0.0, 0)
"Upper bounds of the convex-over-nonlinear constraints (one per expression nl_convex_outer returns; empty for none)"
structural parameter nl_convex_max::Real[:] = fill(0.0, 0)
"Terminal nonlinear constrained signals, functions of the differential state only, enforced only at x_Np. Not available with C code export"
structural parameter nl_terminal_constrained::String[:] = []
"Lower bounds of the terminal nonlinear constraints (same length as nl_terminal_constrained)"
structural parameter nl_terminal_constrained_min::Real[:] = fill(0.0, 0)
"Upper bounds of the terminal nonlinear constraints (same length as nl_terminal_constrained)"
structural parameter nl_terminal_constrained_max::Real[:] = fill(0.0, 0)
"Soften the terminal nonlinear constraints with slack variables (penalized by soft_weight), keeping the problem solvable when the terminal set is unreachable within the horizon -- e.g. an energy level the state is to reach by the end of the horizon"
structural parameter nl_terminal_constraints_soft::Boolean = false
"Soften the nonlinear stage constraints with slack variables (penalized by soft_weight), so that a constraint the current state already violates, after a disturbance or a model error, does not make the problem infeasible"
structural parameter nl_constraints_soft::Boolean = false
"First prediction stage the nonlinear stage constraints are enforced at. At stage 0 the state is the measured one, so a constraint that depends on the state alone (the controls being rates, say) is either satisfied or infeasible there; 1 leaves it out, which lets the constraints be hard where the plant is the model"
structural parameter nl_constraints_from_stage::Integer = 0
"NLP solver: SQP to convergence, or one real-time iteration per tick"
structural parameter nlp_solver::ACADOSSolver = ACADOSSolver.SQP()
"QP solver: HPIPM after partial condensing (to qp_cond_N stages) or full condensing, or DAQP after full condensing (only in an acados built with it, see ACADOSQPSolver)"
structural parameter qp_solver::ACADOSQPSolver = ACADOSQPSolver.PartialCondensingHPIPM()
"Integrator of the shooting intervals"
structural parameter integrator::ACADOSIntegrator = ACADOSIntegrator.ERK()
"How the dynamics Jacobian is obtained: Symbolic (default) or an AD backend that differentiates a numeric evaluation of the model, handling linear-solve/multibody models; the Sparse variants exploit a detected sparsity pattern. Only affects the Julia solver, not the exported C"
structural parameter jacobian_backend::ACADOSJacobianBackend = ACADOSJacobianBackend.Symbolic()
"Runge-Kutta stages of the integrator (ERK: 1, 2 or 4; IRK: 1 to 4)"
structural parameter integrator_stages::Integer = 4
"Collocation scheme of the IRK integrator; RadauIIA for a stiff model integrated over long shooting intervals"
structural parameter collocation::ACADOSCollocation = ACADOSCollocation.GaussLegendre()
"Integration steps per shooting interval"
structural parameter integrator_steps::Integer = 1
"Integration sub-steps of each shooting interval (Np entries), for a non-uniform time_steps grid whose long intervals need more; empty uses integrator_steps for every interval"
structural parameter integrator_steps_per_interval::Integer[:] = fill(0, 0)
"IRK early termination threshold for the infinity norm of the Newton update to scaled internal state derivatives and algebraic variables; zero uses the fixed iteration count"
structural parameter integrator_newton_tol::Real = 0.0
"Newton iterations of the implicit integrator per integration step, with the Jacobian reused across them unless `integrator_jac_reuse` is false (IRK only). Every SQP iteration starts the integrator from the stage values of the previous iterate, so one or two iterations are often enough once the iterations are warm"
structural parameter integrator_newton_iter::Integer = 3
"Evaluate the Jacobian of the implicit integrator once per shooting interval and reuse it across its Newton iterations and sub-steps (IRK only). Set to false for a model whose Jacobian changes by orders of magnitude within an interval, such as the charge balance of a pH neutralization, where the reused Jacobian makes the iterations diverge"
structural parameter integrator_jac_reuse::Boolean = true
"Maximum SQP iterations per tick (SQP only); tunable, but acados sizes its memory by the value the model is built with, so it can later be lowered but not raised"
parameter max_iter::Integer = 100
"Horizon of the partially condensed QP; -1 for the full horizon Np"
structural parameter qp_cond_N::Integer = -1
"Warm start of each QP from the solution of the previous one: 0 cold (the acados default), 1 primal, 2 primal and dual. Level 2 halves the QP time of a problem with many soft constraints, whose slack variables and multipliers change little between the SQP iterations"
structural parameter qp_warm_start::Integer = 0
"NLP stationarity tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_stat::Real = 0.0
"NLP equality tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_eq::Real = 0.0
"NLP inequality tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_ineq::Real = 0.0
"NLP complementarity tolerance override; zero follows the tunable tol parameter"
structural parameter nlp_tol_comp::Real = 0.0
"QP tolerance strategy; adaptive accuracy uses the current NLP residuals and requires SQP"
structural parameter qp_tol_strategy::ACADOSQPToleranceStrategy = ACADOSQPToleranceStrategy.Fixed()
"Fixed QP stationarity tolerance; zero follows the effective NLP stationarity tolerance"
structural parameter qp_tol_stat::Real = 0.0
"Fixed QP equality tolerance; zero follows the effective NLP equality tolerance"
structural parameter qp_tol_eq::Real = 0.0
"Fixed QP inequality tolerance; zero follows the effective NLP inequality tolerance"
structural parameter qp_tol_ineq::Real = 0.0
"Fixed QP complementarity tolerance; zero follows the effective NLP complementarity tolerance"
structural parameter qp_tol_comp::Real = 0.0
"Residual reduction factor for the adaptive joint QP tolerance"
structural parameter qp_tol_reduction_factor::Real = 0.1
"Lower bound on adaptive QP tolerances relative to the effective NLP tolerance of each residual"
structural parameter qp_tol_safety_factor::Real = 0.1
"Adapt Levenberg-Marquardt regularization using accepted step lengths and the objective; ignores the constant levenberg_marquardt parameter"
structural parameter with_adaptive_levenberg_marquardt::Boolean = false
"Initial adaptive Levenberg-Marquardt damping factor at each NLP solve"
structural parameter adaptive_levenberg_marquardt_mu0::Real = 1e-3
"Minimum adaptive Levenberg-Marquardt damping factor"
structural parameter adaptive_levenberg_marquardt_mu_min::Real = 1e-16
"Multiplicative factor for adaptive Levenberg-Marquardt damping; must exceed one"
structural parameter adaptive_levenberg_marquardt_lam::Real = 5.0
"Objective multiplier for adaptive Levenberg-Marquardt regularization; regularization is objective times this multiplier times damping"
structural parameter adaptive_levenberg_marquardt_obj_scalar::Real = 2.0
"Reduction factor for the line-search step length"
structural parameter globalization_alpha_reduction::Real = 0.7
"Apply a full dual step when the line search reduces the primal step"
structural parameter globalization_full_step_dual::Boolean = false
"Globalization of the SQP iterations"
structural parameter globalization::ACADOSGlobalization = ACADOSGlobalization.FixedStep()
"Verbosity of acados (0 silent)"
structural parameter print_level::Integer = 0
"Initial guess of the NLP at every tick"
structural parameter warm_start::ACADOSWarmStart = ACADOSWarmStart.Shift()
"Solver backend; Auto and Julia call libacados in-process, C compiles a standalone acados solver library (required for C code export). The C solver keeps its state in a process-global capsule, so use it for export and single-loop deployment rather than repeated simulation"
structural parameter backend::Backend = Backend.Auto()
"Retry a failed solve (NaN detected, minimum step, QP failure, unbounded) once from a fresh initial guess at the current state, so that a shifted warm start cannot carry a broken iterate from tick to tick; costs one extra solve on the failing tick only"
structural parameter reset_on_failure::Boolean = false
"Also record the predicted state and control trajectories, the KKT residuals and the iteration count of every tick (variables x_pred, u_pred, residuals, iterations), for debugging and mpc_gui; Julia backend only"
structural parameter output_trajectories::Boolean = false
"acados solver built from the parameters above; pass build_acados_mpc(...) to bypass them"
structural parameter mpc::MPCRef = build_acados_mpc(dynamics = dynamics, inputs = inputs, disturbance_inputs = disturbance_inputs, known_disturbance_inputs = known_disturbance_inputs, tunable_parameters = tunable_parameters, state_variables = state_variables, Ts = Ts, Np = Np, time_steps = time_steps, outputs = outputs, C = C, constrained = constrained, constrained_min = constrained_min, constrained_max = constrained_max, terminal_lqr_cost = terminal_lqr_cost, u_operating_point = u_operating_point, penalize_increments = penalize_increments, normalize_increment_penalty = normalize_increment_penalty, u0 = u_guess, x0 = x_guess, x_scale = x_scale, u_scale = u_scale, du_scale = du_scale, reference_tracking = reference_tracking, reference_preview = reference_preview, parameter_preview = parameter_preview, umin = umin, umax = umax, dumin = dumin, dumax = dumax, xmin = xmin, xmax = xmax, state_bounds_soft = state_bounds_soft, soft_weight = soft_weight, Ax = Ax, Au = Au, lb = lb, ub = ub, terminal_constrained = terminal_constrained, terminal_constrained_min = terminal_constrained_min, terminal_constrained_max = terminal_constrained_max, AxN = AxN, lbN = lbN, ubN = ubN, terminal_constraints_soft = terminal_constraints_soft, nl_constrained = nl_constrained, nl_constrained_min = nl_constrained_min, nl_constrained_max = nl_constrained_max, nl_terminal_constrained = nl_terminal_constrained, nl_terminal_constrained_min = nl_terminal_constrained_min, nl_terminal_constrained_max = nl_terminal_constrained_max, nl_terminal_constraints_soft = nl_terminal_constraints_soft, nl_constraints_soft = nl_constraints_soft, nl_constraints_from_stage = nl_constraints_from_stage, nl_convex_inner = nl_convex_inner, nl_convex_outer = nl_convex_outer, nl_convex_min = nl_convex_min, nl_convex_max = nl_convex_max, nlp_solver = nlp_solver, qp_solver = qp_solver, integrator = integrator, collocation = collocation, jacobian_backend = jacobian_backend, integrator_stages = integrator_stages, integrator_steps = _acados_component_integrator_steps(integrator_steps, integrator_steps_per_interval), integrator_newton_tol = integrator_newton_tol, integrator_newton_iter = integrator_newton_iter, integrator_jac_reuse = integrator_jac_reuse, max_iter = max_iter, qp_cond_N = qp_cond_N, qp_warm_start = qp_warm_start, nlp_tol_stat = nlp_tol_stat, nlp_tol_eq = nlp_tol_eq, nlp_tol_ineq = nlp_tol_ineq, nlp_tol_comp = nlp_tol_comp, qp_tol_strategy = qp_tol_strategy, qp_tol_stat = qp_tol_stat, qp_tol_eq = qp_tol_eq, qp_tol_ineq = qp_tol_ineq, qp_tol_comp = qp_tol_comp, qp_tol_reduction_factor = qp_tol_reduction_factor, qp_tol_safety_factor = qp_tol_safety_factor, with_adaptive_levenberg_marquardt = with_adaptive_levenberg_marquardt, adaptive_levenberg_marquardt_mu0 = adaptive_levenberg_marquardt_mu0, adaptive_levenberg_marquardt_mu_min = adaptive_levenberg_marquardt_mu_min, adaptive_levenberg_marquardt_lam = adaptive_levenberg_marquardt_lam, adaptive_levenberg_marquardt_obj_scalar = adaptive_levenberg_marquardt_obj_scalar, globalization_alpha_reduction = globalization_alpha_reduction, globalization_full_step_dual = globalization_full_step_dual, globalization = globalization, print_level = print_level, warm_start = warm_start, log_residuals = output_trajectories, reset_on_failure = reset_on_failure)
"State dimension of the model (derived from mpc)"
final structural parameter nx::Integer = _acados_nx(mpc)
"Number of controls (derived from mpc)"
final structural parameter nu::Integer = _acados_nu(mpc)
"Number of controlled outputs (derived from mpc)"
final structural parameter ny::Integer = _acados_ny(mpc)
"Width of the reference input: 0, ny or ny*Np with reference_preview (derived from mpc)"
final structural parameter nr_in::Integer = _acados_nr_in(mpc)
"Width of the parameter input: the number of online parameters of the model, np, or np*Np with parameter_preview; zero when the values come from p_values instead (derived)"
final structural parameter np_in::Integer = _acados_np_in(mpc, parameter_input)
"Whether the r connector exists (derived)"
final structural parameter has_r::Boolean = nr_in > 0
"Whether the p connector exists (derived)"
final structural parameter has_p::Boolean = np_in > 0
"Width of the internal reference vector, max(nr_in, 1) (derived)"
final structural parameter nr_eff::Integer = max(nr_in, 1)
"Width of the internal parameter vector and of p_values, max(np, 1) or max(np*Np, 1) with parameter_preview; independent of whether the p connector exists (derived)"
final structural parameter np_eff::Integer = max(_acados_np_vec(mpc), 1)
"Length of the predicted state trajectory, (Np + 1) nx (derived from mpc)"
final structural parameter nxp::Integer = _acados_nxp(mpc)
"Length of the predicted control trajectory, Np nu (derived from mpc)"
final structural parameter nup::Integer = _acados_nup(mpc)
"Side of the terminal weight Qf: ny, or nx with terminal_lqr_cost (derived from mpc)"
final structural parameter nyf::Integer = _acados_nyf(mpc)
"Control assumed applied before the first tick, by default zero"
parameter u0::Real[nu] = _acados_u0(mpc)
"Output weight on C*x - r (ny × ny, e.g. diagonal([1.0, 10.0])); tunable. Default: the identity, or the weight the mpc object was built with"
parameter Q1::Real[ny, ny] = _acados_Q1(mpc)
"Control weight (nu × nu); tunable. Default: the identity, or the weight the mpc object was built with"
parameter Q2::Real[nu, nu] = _acados_Q2(mpc)
"Penalty on the control increments (nu × nu); tunable, must be zero unless penalize_increments. Default: the identity with penalize_increments, else zero"
parameter Q3::Real[nu, nu] = _acados_Q3(mpc)
"Terminal output weight (ny × ny); tunable, ignored with terminal_lqr_cost (then nx × nx and following Q1 and Q2). Default: Q1, or the weight the mpc object was built with"
parameter Qf::Real[nyf, nyf] = _acados_Qf(mpc, Q1)
"Constant term of the linear control cost (Eu p + eu)' u at every stage (nu), in LinearMPC's convention next to the stage cost ½ u' Q2 u, so that eu = -Q2 u_ref centers the control penalty on u_ref; a nonzero cost requires positive definite Q2 unless the increment formulation is used; tunable. Default: zero, or the cost the mpc object was built with"
parameter eu::Real[nu] = _acados_eu(mpc)
"Number of columns of Eu, max(np, 1) (derived from mpc)"
final structural parameter nEu::Integer = _acados_nEu(mpc)
"Gain of the online parameters p in the linear control cost (nu × np, p of the stage's shooting interval); a model without online parameters has a single column, which must be zero; tunable. Default: zero, or the gain the mpc object was built with"
parameter Eu::Real[nu, nEu] = _acados_Eu(mpc)
"Default SQP convergence tolerance for residuals without a positive nlp_tol_* override; tunable"
parameter tol::Real = _acados_tol(mpc)
"Levenberg-Marquardt regularization added to the Gauss-Newton Hessian; tunable"
parameter levenberg_marquardt::Real = _acados_levenberg_marquardt(mpc)
"State (in the `state_variables` order) the LQR terminal weight is linearized at and centered on; tunable, used only with terminal_lqr_cost. Changing it re-linearizes the model and re-solves the Riccati equation. Default: the origin"
parameter operating_point::Real[nx] = _acados_operating_point(mpc)
"Whether the compiled C solver is used (resolved from backend; the library is built on first use with the weights and options above)"
final structural parameter use_c::Boolean = _acados_use_c(mpc, backend, output_trajectories, Q1, Q2, Q3, Qf, eu, Eu, tol, levenberg_marquardt, max_iter, operating_point)
"Whether the in-process Julia solver is used (derived)"
final structural parameter use_julia::Boolean = use_c == false
"Zero placeholder passed as the reference argument when r is absent"
final parameter r_dummy::Real[nr_eff] = fill(0.0, nr_eff)
"Values of the parameters named by `tunable_parameters`, used when the p connector is absent; tunable. Default: the values the model carries. np entries, or np*Np column-major entries with parameter_preview"
parameter p_values::Real[np_eff] = _acados_p_default(mpc, np_eff)
"Reference vector handed to the step function"
variable r_int::Real[nr_eff]
"Parameter vector handed to the step function"
variable p_int::Real[np_eff]
"Predicted states at stages 0..Np, column-major nx × (Np+1) in the order of the x input (only with output_trajectories)"
variable x_pred::Real[nxp] if output_trajectories
"Predicted controls at stages 0..Np-1, column-major nu × Np (only with output_trajectories)"
variable u_pred::Real[nup] if output_trajectories
"KKT residuals of the last iteration: stationarity, equality, inequality, complementarity (only with output_trajectories)"
variable residuals::Real[4] if output_trajectories
"SQP iterations of the tick (only with output_trajectories)"
variable iterations::Real if output_trajectories
relations
if has_r
r_int = r
else
r_int = r_dummy
end
if has_p
p_int = p
else
p_int = p_values
end
initial u@(clk-1) = u0
# One solve per tick, on both backends: the scalar status is the only result of the call, and
# everything else is read back through accessors that depend on it, so that no consumer can
# duplicate the solve. The recorded trajectories and residuals are pure reads and are taken a whole
# buffer at a time: the clocked compiler keeps an array-valued equation intact and emits a single
# call for it, so their cost does not grow with the prediction horizon.
if use_c
exitflag@clk = acados_solve_c(x@clk, r_int@clk, p_int@clk, u@(clk-1), Q1, Q2, Q3, Qf, eu, Eu, tol, levenberg_marquardt, max_iter, operating_point, mpc)
for i in 1:nu
u[i] = acados_u_c(exitflag, i, mpc)
end
else
initial ntick@(clk-1) = 0
ntick@clk = ntick@(clk-1) + 1
exitflag@clk = acados_solve(x@clk, r_int@clk, p_int@clk, u@(clk-1), ntick@(clk-1), Q1, Q2, Q3, Qf, eu, Eu, tol, levenberg_marquardt, max_iter, operating_point, mpc)
for i in 1:nu
u[i] = acados_u(exitflag, i, mpc)
end
if output_trajectories
x_pred = acados_x_pred_vec(exitflag, mpc)
u_pred = acados_u_pred_vec(exitflag, mpc)
residuals = acados_residuals_vec(exitflag, mpc)
iterations = acados_iterations(exitflag, mpc)
end
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/ACADOSMPC.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Dynamics": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"disturbance_inputs",
"known_disturbance_inputs",
"tunable_parameters",
"p_values",
"parameter_input",
"state_variables",
"Ts",
"Np",
"time_steps",
"C",
"jacobian_backend"
]
},
"Cost function": {
"control": "fieldset",
"contents": [
"outputs",
"Q1",
"Q2",
"Qf",
"eu",
"Eu",
"terminal_lqr_cost",
"operating_point",
"u_operating_point",
"penalize_increments",
"normalize_increment_penalty",
"Q3",
"reference_tracking",
"reference_preview",
"parameter_preview",
"u_guess",
"x_guess",
"u0",
"x_scale",
"u_scale",
"du_scale"
]
},
"Constraints": {
"control": "fieldset",
"contents": [
"umin",
"umax",
"dumin",
"dumax",
"constrained",
"constrained_min",
"constrained_max",
"nl_constrained",
"nl_constrained_min",
"nl_constrained_max",
"nl_constraints_soft"
],
"children": {
"Advanced": {
"contents": [
"state_bounds_soft",
"soft_weight",
"xmin",
"xmax",
"Ax",
"Au",
"lb",
"ub",
"nl_constraints_from_stage",
"nl_convex_inner",
"nl_convex_outer",
"nl_convex_min",
"nl_convex_max"
]
},
"Terminal": {
"contents": [
"terminal_constrained",
"terminal_constrained_min",
"terminal_constrained_max",
"terminal_constraints_soft",
"AxN",
"lbN",
"ubN",
"nl_terminal_constrained",
"nl_terminal_constrained_min",
"nl_terminal_constrained_max",
"nl_terminal_constraints_soft"
]
}
}
},
"Solver options": {
"control": "fieldset",
"contents": [
"nlp_solver",
"qp_solver",
"integrator",
"max_iter",
"tol",
"warm_start",
"reset_on_failure",
"backend",
"output_trajectories"
],
"children": {
"Advanced": {
"contents": [
"integrator_stages",
"collocation",
"integrator_steps",
"integrator_steps_per_interval",
"integrator_newton_iter",
"integrator_newton_tol",
"integrator_jac_reuse",
"qp_cond_N",
"qp_warm_start",
"nlp_tol_stat",
"nlp_tol_eq",
"nlp_tol_ineq",
"nlp_tol_comp",
"qp_tol_strategy",
"qp_tol_stat",
"qp_tol_eq",
"qp_tol_ineq",
"qp_tol_comp",
"qp_tol_reduction_factor",
"qp_tol_safety_factor",
"with_adaptive_levenberg_marquardt",
"adaptive_levenberg_marquardt_mu0",
"adaptive_levenberg_marquardt_mu_min",
"adaptive_levenberg_marquardt_lam",
"adaptive_levenberg_marquardt_obj_scalar",
"globalization_alpha_reduction",
"globalization_full_step_dual",
"globalization",
"levenberg_marquardt",
"print_level",
"mpc"
]
}
}
},
"Derived": {
"control": "fieldset",
"contents": ["nx", "nu", "ny", "nr_in", "np_in", "has_r", "has_p"]
}
}
}
}
}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses