LinearMPCController
Linear model-predictive controller built on LinearMPC.jl: a LinearMPCObserver and a LinearMPCOptimizer sharing one MPC object that is constructed from the parameters below when the model is built.
Model
The controller model is the discrete-time linear system
with state x, control u, measured disturbance d, controlled output y (the quantity the references apply to), and affine offsets f, h. With continuous = true, A, B, B_d and f are continuous-time and are ZOH-discretized with the sample time Ts, which must then equal the period of the clock the component is connected to. Feedthrough from u is not supported by LinearMPC.jl.
Prediction model from a plant
Instead of the matrices, the plant itself may be given as dynamics: a subcomponent of the enclosing model (dynamics = plant, declared before the controller) or a component constructor (dynamics = MyPlant, for a model that does not contain the plant, such as one exported to C). The plant is linearized at an operating point into the continuous-time affine model above, which is ZOH-discretized with Ts; A, B, Bd, C, Dd, f_offset, h_offset and continuous are then left empty. The signals are named, and each name list fixes the order of what it describes: inputs the manipulated inputs (the columns of B, the order of u), known_disturbance_inputs the measured disturbances (the columns of B_d, the order of d), outputs the controlled and measured outputs (the rows of C, the order of y, r, Q1, ymin), and disturbance_inputs the unmeasured inputs, which are zero in the prediction and whose disturbance-model states are kept in the state (an integrating disturbance der(d) = w with w named here is estimated as a constant). The operating point is selected by linearization_point: the initial state of the plant with its inputs held at u_op and d_op (InitialState), the steady state at those inputs (SteadyState), or the steady state and the inputs at which the outputs take the values y_op (OutputTarget). The steady state is computed by MTK's initialization of the plant; the initial values of its states must then be initial conditions (variable x::Real(initial = x0)), not initialization equations. x0 and u0 default to the operating point.
The state of the linearized model is the state the compiler selected for the plant, whose order is unspecified and may change between compiler versions; the parameters indexed by the state (x0, R1, Q12, Qfx, Ex, ex, Ax, K, C_meas) and the output xhat then have no defined meaning, and the former are refused. state_variables names the signals that form the state, in the order of those parameters and of xhat, e.g. state_variables = ["h1", "h2"], the list ACADOSMPC and UKFObserver take. The named signals need not be the ones the compiler selected: the model is transformed to their coordinates, which requires that they determine the state. Bounds and references are in absolute units, not deviations from the operating point.
Objective
Q1, Q2, Q3, Qf and Qfx may also be given as vectors of diagonal entries. Qf defaults to Q1; terminal_lqr_cost = true sets Qfx to the infinite-horizon LQR cost (regulation only), and requires a positive definite Q2 + Q3, as does prestabilizing_feedback. p is the generalized parameter input (present when Ex, Eu or Ap have columns).
Q2 defaults to zero: a penalty on the control level is minimized at u = 0, while holding the output at a nonzero reference needs a nonzero input, so the two trade off and the loop settles short of the setpoint — by Q1 / (Q1 + Q2 K_dc^2) of the reference for a scalar plant, which is most of it on a plant whose inputs are physical commands far from zero. Penalize the control increments with Q3 instead: at steady state u_k - u_{k-1} = 0, so the rate term contributes nothing and the minimum of what remains is the reference. A small Q2 alongside Q3 leaves an error in proportion to itself, which is the way to keep a leash on the control level. The builder warns when a nonzero Q2 is combined with reference tracking on a plant without integral action.
Constraints
Hard input bounds umin <= u <= umax, hard control-rate bounds dumin <= u_k - u_{k-1} <= dumax (enforced from the first move, which is taken against the control last applied, through the control horizon Nc), output bounds ymin <= y <= ymax (soft by default), and one block of general constraints lb <= Ax x_k + Au u_k + Ar r_k + Ad d_k + Aup u_{k-1} + Ap p_k <= ub enforced for the prediction steps constraint_ks (default 2:Np), optionally soft or binary (one of the bounds holds with equality). Soft-constraint violations are penalized with soft_weight. Control inputs listed in binary_controls are restricted to {umin, umax}; together with the general constraint block they encode richer discrete input sets, see the examples in dyad/Examples/Binary/ (an integer-valued control, and a semi-continuous one that is either off or above a minimum capacity). A general constraint block that references the control at steps beyond the control horizon warns: LinearMPC condenses those rows with the control taken as zero rather than held, so they do not constrain what they read as. move_block keeps the control constant over blocks of steps — it sets the control horizon itself, Nc becoming the last step at which the blocked control still changes, and the control-rate bounds follow it — and prestabilizing_feedback reparametrizes u = -K x + v.
Robust constraint tightening
The constraints are tightened so that they hold for every disturbance and every error of the state estimate within given bounds (LinearMPC.jl set_disturbance! and set_x0_uncertainty!). The disturbance inputs w enter the discrete-time model as x_{k+1} = A x_k + B u_k + ... + B_w w_k, bounded by disturbance_input_min <= w_k <= disturbance_input_max and held over the sample. With dynamics, w are the disturbance_inputs of the plant, which is then linearized also with respect to them. With a model given as matrices, B_w is Bw, continuous time when continuous = true like B; Bw = B[:, i] makes w a disturbance added to the control u_i, and Bw = I bounds the increment of each state variable over one sample. A disturbance added to a control of dynamics is modeled in the plant: an additional input w, which the actuator adds to the control, named in disturbance_inputs (a control itself cannot be a disturbance input, since the latter is zero in the prediction).
An empty lower bound is the negated upper bound and vice versa. The error of the state estimate is bounded by -δ <= x - xhat <= δ, with δ = x0_uncertainty, or δ = κ σ with κ = x0_uncertainty_sigma and σ the standard deviations of the stationary error of the filtered estimate of the observer, computed from R1 and R2 when the model is built. κ σ bounds a Gaussian error only with a probability that increases with κ, not with certainty.
The bounds of a constraint at a prediction step are tightened by the worst case, over these bounds, of the deviation of the constrained quantity from its prediction under the feedback u = v - K x (K of prestabilizing_feedback, zero otherwise). The control of the current step is computed from the estimate and carries no error, and a constraint on the current state is not tightened. Without prestabilizing feedback the tightening of an unstable or integrating plant grows with the prediction horizon, so enable prestabilizing_feedback for such a plant. A tightening that leaves a two-sided constraint, soft or hard, without feasible values is refused when the model is built. The estimate of an offset-free disturbance (dhat) is treated as exact.
Observer
The state is estimated by a steady-state Kalman filter with process- and measurement-noise covariances R1, R2 from the measurements y = C_meas x + Dd_meas d (default: the controlled outputs). With offset_free != None the model is augmented with constant disturbance channels and both the state and the disturbance are estimated (LinearMPC.jl set_offset_free_observer!); the estimate is output as dhat and used in the prediction, giving offset-free tracking under constant disturbances. The measured outputs are then the controlled outputs. StateDisturbance and Velocity are fixed-gain realizations whose disturbance estimate takes up the whole innovation every step (fast, not tunable); InputDisturbance (a constant load disturbance on every input, Bd = B, nd = nu) and OutputDisturbance (a constant bias on every output, nd = ny) design a Kalman filter on the augmented model, and R1 may then have nx + nd entries whose disturbance part sets how fast the disturbance is estimated, i.e. the effective integral gain (with nx entries the disturbance covariance is 1).
observer_type selects the observer component. SteadyState (the default) is LinearMPCObserver with the stationary gain stored in the MPC object. TimeVarying is SqKalmanObserver, a square-root Kalman filter that propagates the covariance of the estimate and uses at every tick only the measurements that are available: finite and, with use_valid, flagged true on the y_valid input, so that a missing or stale measurement is left out of the correction. It is designed from R1 and R2 as above (the disturbance states with covariance 1 when R1 has nx entries), and the covariance of its initial estimate is the stationary prediction covariance (kalman_observer.R0, tunable), so that with every measurement available it equals the steady-state observer for a plain Kalman filter, InputDisturbance and OutputDisturbance. For StateDisturbance and Velocity it is the Kalman filter of the augmented model rather than the fixed-gain realization. TimeVarying runs in Julia and is not exported to C. Both observers are standalone components for the model x⁺ = A x + B u, y = C x: the controller gives them the (augmented) estimator of the MPC object with the input [u; d; 1] (B = [G Gd f]) and the measurement y - Dd d - h, where d are the measured disturbances of the current step (the first entries of a disturbance preview), so the size of the observer does not depend on the horizon.
Signals and timing
At tick k the observer corrects its prediction with y_k (and d_k), the optimizer computes u_k from the corrected estimate, r_k, d_k, dhat_k, p_k and u_{k-1}, and the observer predicts the estimate for tick k+1 using u_k. Measurements and references must be sampled before this component and the control held after it (Sampler, ZeroOrderHold). With reference_preview, disturbance_preview or parameter_preview, the corresponding input carries a whole horizon as a flat column-major vector (r[(j-1)*ny + i] is reference i at prediction step j).
Solver and C code export
The QP is solved by the C code LinearMPC.jl generates for this controller, compiled once per controller into a shared library (cached by content) and called from the clocked equation through a ccall; the steady-state observer is plain matrix arithmetic. Both are exported to standalone C by the CCodeExport analysis, so what is simulated is what is deployed. backend = Backend.Auto() falls back to LinearMPC.jl's Julia solver (same DAQP solver, results agree to solver tolerance) with a warning when no C compiler is found; such a model cannot be exported. precision selects the float type of the generated QP code (Single compiles DAQP from its sources); the observer equations are Real, i.e. double, and so is the Julia fallback. The exitflag output carries the solver's DAQP exit flag on either backend.
Notes
All matrices and options are structural: the QP is condensed once when the model is built, so they are not tunable parameters. Empty arrays mean "not given" and select LinearMPC's defaults. Features that are not surfaced here (several general constraint blocks with different horizons or softness, logic constraints, :general disturbance models, per-input move blocking) are available by building the DyadLinearMPC.MPC in Julia and passing it as mpc = build_linear_mpc(my_mpc); all other parameters are then ignored, u0 among them, so a hand-built object that must start from an operating-point control takes it as build_linear_mpc(my_mpc, u0 = u_trim). Use one MPC object per simulated model instance.
This component extends from LinearMPCPart
Usage
MPCComponents.LinearMPCController(d_dummy=fill(0.0, nd_obs), Dd_o=_lmpc_obs_Dd(mpc, nd_obs), h_o=_lmpc_obs_h(mpc))
Parameters:
| Name | Description | Units | Default value |
|---|---|---|---|
mpc | Set-up LinearMPC.jl MPC object wrapped in an MPCRef (see build_linear_mpc) | – | build_linea...inty_sigma) |
nx | State dimension of the model (derived from mpc) | – | _lmpc_nx(mpc) |
nu | Number of controls (derived from mpc) | – | _lmpc_nu(mpc) |
ny_meas | Number of measured outputs, the rows of the observer's measurement matrix; 0 without an observer (derived from mpc) | – | _lmpc_ny_meas(mpc) |
ndo | Number of estimated offset-free disturbances, 0 for a plain Kalman filter (derived from mpc) | – | _lmpc_ndo(mpc) |
nxa | Length of the augmented observer state, nx + ndo (derived) | – | nx + ndo |
nr_in | Width of the reference input: 0, ny or ny*Np (derived from mpc) | – | _lmpc_nr_in(mpc) |
nd_in | Width of the measured-disturbance input: 0, nd_meas or nd_meas*Np (derived from mpc) | – | _lmpc_nd_in(mpc) |
np_in | Width of the generalized-parameter input: 0, np or np*Np (derived from mpc) | – | _lmpc_np_in(mpc) |
has_r | Whether the r connector exists (derived) | – | nr_in > 0 |
has_d | Whether the d connector exists (derived) | – | nd_in > 0 |
has_dhat | Whether the dhat connector exists (derived) | – | ndo > 0 |
has_p | Whether the p connector exists (derived) | – | np_in > 0 |
dynamics | Plant whose linearization is the prediction model: a subcomponent of the enclosing model (dynamics = plant) or a component constructor (dynamics = MyPlant); leave empty to give the model as the matrices A, B, ... | – | |
inputs | Names of the manipulated inputs of dynamics, in the order of u (e.g. ["power"]) | – | [] |
known_disturbance_inputs | Names of the measured disturbance inputs of dynamics, in the order of d; the columns of Bd | – | [] |
disturbance_inputs | Names of the unmeasured inputs of dynamics, zero in the prediction; the states of their disturbance model are kept in the state | – | [] |
outputs | Names of the controlled and measured outputs of dynamics, in the order of y, r and Q1 | – | [] |
state_variables | Names of the signals of dynamics that form the state, in the order of x0, R1 and xhat; empty for the state the compiler selects, in an unspecified order | – | [] |
linearization_point | Operating point at which dynamics is linearized | – | Linearizati...tialState() |
u_op | Inputs at the operating point, in the order of inputs; empty for zero. With OutputTarget, the initial guess of the inputs | – | fill(0.0, 0) |
d_op | Measured disturbances at the operating point, in the order of known_disturbance_inputs; empty for zero | – | fill(0.0, 0) |
y_op | Output targets that define the operating point with OutputTarget, in the order of outputs | – | fill(0.0, 0) |
linearization | The linearization of dynamics (see linearize_prediction_model); nothing without dynamics | – | linearize_p...t_max) > 0) |
A | State matrix (nx × nx); discrete-time unless continuous = true. Empty when the model is given as dynamics | – | fill(0.0, 0, 0) |
B | Input matrix (nx × nu) | – | fill(0.0, 0, 0) |
Bd | Measured-disturbance input matrix (nx × nd); empty for no measured disturbances | – | fill(0.0, 0, 0) |
C | Output matrix of the controlled outputs (ny × nx); empty for the identity | – | fill(0.0, 0, 0) |
Dd | Disturbance feedthrough to the controlled outputs (ny × nd); empty for zero | – | fill(0.0, 0, 0) |
f_offset | Affine offset of the state update (nx); empty for zero | – | fill(0.0, 0) |
h_offset | Affine offset of the outputs (ny); empty for zero | – | fill(0.0, 0) |
continuous | Interpret A, B, Bd and f_offset as continuous-time and ZOH-discretize them with Ts | – | false |
Ts | Sample time used for the ZOH discretization when continuous = true; must equal the clock period | – | 1.0 |
Np | Prediction horizon | – | 10 |
Nc | Control horizon | – | Np |
Q1 | Output weight on C*x - r (ny × ny, or ny diagonal entries); empty for the identity | – | fill(0.0, 0, 0) |
Q2 | Control weight (nu × nu, or nu diagonal entries); empty for zero. A penalty on the control level leaves a steady-state error when tracking a plant with a finite static gain, so penalize the control increments with Q3 instead, or give a Q2 small enough that the error it leaves is acceptable | – | fill(0.0, 0, 0) |
Q3 | Control-increment weight on u_k - u_{k-1} (nu × nu, or nu diagonal entries); empty for zero | – | fill(0.0, 0, 0) |
Q12 | State-control cross weight (nx × nu); empty for zero | – | fill(0.0, 0, 0) |
Qf | Terminal output weight (ny × ny, or ny diagonal entries); empty for Q1 | – | fill(0.0, 0, 0) |
Qfx | Terminal state weight (nx × nx, or nx diagonal entries); empty for zero | – | fill(0.0, 0, 0) |
Ex | Gain of the generalized parameter in the linear state cost (Ex p + ex)' x (nx × np); empty for zero | – | fill(0.0, 0, 0) |
ex | Constant linear state cost (nx); empty for zero | – | fill(0.0, 0) |
Eu | Gain of the generalized parameter in the linear control cost (Eu p + eu)' u (nu × np); empty for zero | – | fill(0.0, 0, 0) |
eu | Constant linear control cost (nu); empty for zero | – | fill(0.0, 0) |
terminal_lqr_cost | Set Qfx to the infinite-horizon LQR cost (requires reference_tracking = false) | – | 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) |
ymin | Lower output bounds (ny); empty for none | – | fill(0.0, 0) |
ymax | Upper output bounds (ny); empty for none | – | fill(0.0, 0) |
output_bounds_ks | Prediction steps on which the output bounds are enforced; empty for 2:Np | – | [] |
output_bounds_soft | Soften the output bounds | – | true |
output_bounds_binary | Enforce the output bounds as binary (one of the bounds holds with equality) | – | false |
output_bounds_prio | Priority of the output bounds | – | 0 |
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) |
Ar | Reference coefficient of the general constraints (m × ny); empty for zero | – | fill(0.0, 0, 0) |
Ad | Measured-disturbance coefficient of the general constraints (m × nd); empty for zero | – | fill(0.0, 0, 0) |
Aup | Previous-control coefficient of the general constraints (m × nu); empty for zero | – | fill(0.0, 0, 0) |
Ap | Generalized-parameter coefficient of the general constraints (m × np); 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) |
constraint_ks | Prediction steps on which the general constraints are enforced; empty for 2:Np | – | [] |
constraint_soft | Soften the general constraints | – | false |
constraint_binary | Enforce the general constraints as binary (one of the bounds holds with equality) | – | false |
constraint_prio | Priority of the general constraints | – | 0 |
binary_controls | Indices of the controls restricted to the values {umin, umax}; empty for none | – | [] |
Nc_binary | Number of initial horizon steps over which the binary controls are restricted to {umin, umax}, the rest are relaxed to continuous; -1 for the whole control horizon | – | -1 |
move_block | Move-blocking pattern, the number of steps each control move is held (applied to all controls); empty for none. It sets the control horizon itself, so a Nc given alongside it is ignored | – | [] |
prestabilizing_feedback | Reparametrize the control as u = -K x + v with a prestabilizing feedback | – | false |
K | Prestabilizing feedback gain (nu × nx); empty for the LQR gain of the objective | – | fill(0.0, 0, 0) |
Bw | Input matrix of the unmeasured disturbance inputs w of the robust constraint tightening (nx × nw; continuous-time when continuous = true). Only with a model given as matrices; with dynamics, the columns of disturbance_inputs are linearized | – | fill(0.0, 0, 0) |
disturbance_input_min | Lower bounds of the disturbance inputs w for the robust constraint tightening (one entry per disturbance input: per name in disturbance_inputs, or per column of Bw); empty for -disturbance_input_max, both empty for none | – | fill(0.0, 0) |
disturbance_input_max | Upper bounds of the disturbance inputs w for the robust constraint tightening (as disturbance_input_min); empty for -disturbance_input_min | – | fill(0.0, 0) |
x0_uncertainty | Bound δ on the error of the state estimate, -δ <= x - xhat <= δ (nx), for the robust constraint tightening; empty for none | – | fill(0.0, 0) |
x0_uncertainty_sigma | Bound on the error of the state estimate as a multiple κ of the standard deviations of the stationary estimation error of the observer (from R1 and R2), δ = κ σ, for the robust constraint tightening; 0 for none | – | 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) instead of a setpoint | – | false |
disturbance_preview | Take a measured-disturbance trajectory over the horizon (nd*Np entries) instead of the current value | – | false |
parameter_preview | Take a generalized-parameter trajectory over the horizon (np*Np entries) instead of the current value | – | false |
soft_weight | Penalty weight on soft-constraint violations | – | 1e6 |
primal_tol | DAQP primal feasibility tolerance; -1 keeps the solver default. Applied to the Julia solver and embedded in the generated C | – | -1.0 |
dual_tol | DAQP dual feasibility (optimality) tolerance; -1 keeps the solver default | – | -1.0 |
iter_limit | DAQP iteration limit; -1 keeps the solver default | – | -1 |
backend | QP solver backend; Auto uses the C code generated by LinearMPC.jl when a C compiler is available (required for C code export), otherwise the Julia solver | – | Backend.Auto() |
precision | Float type of the C code generated for the QP (C backend only): Double, or Single for 32-bit targets; the observer equations are Real | – | MPCPrecision.Double() |
output_trajectories | Also record the predicted state, control and output trajectories and the QP statistics of every tick (variables optimizer.x_pred, optimizer.u_pred, optimizer.y_pred, optimizer.stats), for debugging and mpc_gui; Julia backend only. The readbacks are four equations whose number does not grow with the prediction horizon, though the trajectories themselves are stored in the solution; output_statistics records the statistics alone | – | false |
output_statistics | Record the QP statistics of every tick (variable optimizer.stats: objective value, solve time in seconds, active-set iterations, branch-and-bound nodes) without the predicted trajectories; Julia backend only. One equation. Implied by output_trajectories | – | false |
offset_free | Offset-free disturbance model of the observer; None for a plain Kalman filter | – | OffsetFreeMethod.None() |
observer_type | Observer: SteadyState (LinearMPCObserver, exported to C) or TimeVarying (SqKalmanObserver, Julia only) | – | ObserverTyp...eadyState() |
use_valid | Whether the y_valid input exists (observer_type = TimeVarying only); without it, every finite measurement is used | – | false |
R1 | Process-noise covariance of the Kalman filter (nx × nx, or nx diagonal entries; with InputDisturbance/OutputDisturbance also (nx + nd) × (nx + nd), the disturbance part setting the disturbance-estimate speed); empty for the identity | – | fill(0.0, 0, 0) |
R2 | Measurement-noise covariance of the Kalman filter (ny_meas × ny_meas, or ny_meas diagonal entries); empty for the identity | – | fill(0.0, 0, 0) |
C_meas | Measurement matrix of the observer when the measured outputs differ from C (ny_meas × nx, only with offset_free = None); empty for C | – | fill(0.0, 0, 0) |
Dd_meas | Disturbance feedthrough to the measured outputs (ny_meas × nd, only with offset_free = None); empty for Dd | – | fill(0.0, 0, 0) |
x0 | Initial state estimate (nx); empty for zero, or for the operating point of dynamics | – | fill(0.0, 0) |
d0 | Initial offset-free disturbance estimate (ny entries; nu with InputDisturbance); empty for zero | – | fill(0.0, 0) |
u0 | Control assumed applied before the first tick, LinearMPC's uprev (nu); empty for zero, or for the operating-point input of dynamics. It enters the QP only when the previous control does (Q3, or a general constraint with Aup): setting it to the operating-point control keeps a rate-limited loop from walking the command up from zero over the first ticks | – | fill(0.0, 0) |
Sd | Disturbance-control cross term d' Sd u (nd × nu) of the objective. The default is (pinv(B) Bd)' Q2 with an offset-free observer (with dynamics, from the ZOH discretization of the linearization), which makes Q2 penalize u relative to the input cancelling the measured disturbances instead of relative to zero (offset-free tracking), and zero for offset_free = None or for the default Q2 = 0; rows for the observer-estimated disturbances are appended with the same formula. Empty for zero | – | (__dyad_isa...d, Q2, Ts)) |
steady_observer | Whether the steady-state observer is used (derived from observer_type) | – | _lmpc_obs_s... use_valid) |
n_valid | Width of the y_valid input: ny_meas with use_valid, else 0 (derived) | – | (use_valid)...meas) : (0) |
nd_meas | Number of measured disturbances of the current step, nd_meas (without the preview) (derived from mpc) | – | _lmpc_nd_meas(mpc) |
nd_obs | Width of the disturbance vector of the observer input, max(nd_meas, 1): the estimator uses only the current step of a disturbance preview (derived) | – | max(nd_meas, 1) |
nu_obs | Width of the observer input [u; d_int; 1], nu + nd_obs + 1 (derived) | – | nu + nd_obs + 1 |
Connectors
y- 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)d- 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)xhat- This connector represents a real signal as an output from a component (RealOutput)dhat- 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)y_valid- This connector represents a boolean signal as an input to a component (BooleanInput)
Variables
| Name | Description | Units |
|---|---|---|
d_int | Disturbance vector of the observer input: the measured disturbances of the current step | – |
u_obs | Input of the observer, [u; d_int; 1] | – |
y_obs | Measurement of the observer, y - Dd d_int - h | – |
x_obs | Estimate of the augmented state [x; d_offset] of the estimator | – |
Behavior
Behavior of this component cannot be rendered because it includes path variables.
Source
"""
Linear model-predictive controller built on LinearMPC.jl: a `LinearMPCObserver` and a
`LinearMPCOptimizer` sharing one MPC object that is constructed from the parameters below when
the model is built.
# Model
The controller model is the discrete-time linear system
```math
\\begin{aligned}
x_{k+1} &= A x_k + B u_k + B_d d_k + f \\\\
y_k &= C x_k + D_d d_k + h
\\end{aligned}
```
with state `x`, control `u`, measured disturbance `d`, controlled output `y` (the quantity the
references apply to), and affine offsets `f`, `h`. With `continuous = true`, `A`, `B`, `B_d` and
`f` are continuous-time and are ZOH-discretized with the sample time `Ts`, which must then equal
the period of the clock the component is connected to. Feedthrough from `u` is not supported by
LinearMPC.jl.
# Prediction model from a plant
Instead of the matrices, the plant itself may be given as `dynamics`: a subcomponent of the enclosing
model (`dynamics = plant`, declared before the controller) or a component constructor
(`dynamics = MyPlant`, for a model that does not contain the plant, such as one exported to C). The
plant is linearized at an operating point into the continuous-time affine model above, which is
ZOH-discretized with `Ts`; `A`, `B`, `Bd`, `C`, `Dd`, `f_offset`, `h_offset` and `continuous` are
then left empty. The signals are named, and each name list fixes the order of what it describes:
`inputs` the manipulated inputs (the columns of `B`, the order of `u`), `known_disturbance_inputs`
the measured disturbances (the columns of `B_d`, the order of `d`), `outputs` the controlled and
measured outputs (the rows of `C`, the order of `y`, `r`, `Q1`, `ymin`), and `disturbance_inputs`
the unmeasured inputs, which are zero in the prediction and whose disturbance-model states are kept
in the state (an integrating disturbance `der(d) = w` with `w` named here is estimated as a constant).
The operating point is selected by `linearization_point`: the initial state of the plant with its
inputs held at `u_op` and `d_op` (`InitialState`), the steady state at those inputs (`SteadyState`),
or the steady state and the inputs at which the outputs take the values `y_op` (`OutputTarget`). The
steady state is computed by MTK's initialization of the plant; the initial values of its states must
then be initial conditions (`variable x::Real(initial = x0)`), not initialization equations. `x0` and
`u0` default to the operating point.
The state of the linearized model is the state the compiler selected for the plant, whose order is
unspecified and may change between compiler versions; the parameters indexed by the state (`x0`,
`R1`, `Q12`, `Qfx`, `Ex`, `ex`, `Ax`, `K`, `C_meas`) and the output `xhat` then have no defined
meaning, and the former are refused. `state_variables` names the signals that form the state, in the
order of those parameters and of `xhat`, e.g. `state_variables = ["h1", "h2"]`, the list `ACADOSMPC`
and `UKFObserver` take. The named signals need not be the ones the compiler selected: the model is
transformed to their coordinates, which requires that they determine the state. Bounds and
references are in absolute units, not deviations from the operating point.
# Objective
```math
\\sum_{k=0}^{N_p-1} \\Big( (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
+ x_k^T Q_{12} u_k + (E_x p_k + e_x)^T x_k + (E_u p_k + e_u)^T u_k \\Big)
+ (C x_{N_p} - r)^T Q_f (C x_{N_p} - r) + x_{N_p}^T Q_{fx} x_{N_p}
```
`Q1`, `Q2`, `Q3`, `Qf` and `Qfx` may also be given as vectors of diagonal entries. `Qf` defaults to
`Q1`; `terminal_lqr_cost = true` sets `Qfx` to the infinite-horizon LQR cost (regulation only), and
requires a positive definite `Q2 + Q3`, as does `prestabilizing_feedback`. `p` is the generalized
parameter input (present when `Ex`, `Eu` or `Ap` have columns).
`Q2` defaults to zero: a penalty on the control *level* is minimized at `u = 0`, while holding the
output at a nonzero reference needs a nonzero input, so the two trade off and the loop settles
short of the setpoint — by `Q1 / (Q1 + Q2 K_dc^2)` of the reference for a scalar plant, which is
most of it on a plant whose inputs are physical commands far from zero. Penalize the control
increments with `Q3` instead: at steady state `u_k - u_{k-1} = 0`, so the rate term contributes
nothing and the minimum of what remains is the reference. A small `Q2` alongside `Q3` leaves an
error in proportion to itself, which is the way to keep a leash on the control level. The builder
warns when a nonzero `Q2` is combined with reference tracking on a plant without integral action.
# Constraints
Hard input bounds `umin <= u <= umax`, hard control-rate bounds
`dumin <= u_k - u_{k-1} <= dumax` (enforced from the first move, which is taken against the control
last applied, through the control horizon `Nc`), output bounds `ymin <= y <= ymax` (soft by
default), and one block of general constraints
`lb <= Ax x_k + Au u_k + Ar r_k + Ad d_k + Aup u_{k-1} + Ap p_k <= ub` enforced for the
prediction steps `constraint_ks` (default `2:Np`), optionally soft or binary (one of the bounds
holds with equality). Soft-constraint violations are penalized with `soft_weight`. Control inputs listed
in `binary_controls` are restricted to `{umin, umax}`; together with the general constraint block
they encode richer discrete input sets, see the examples in `dyad/Examples/Binary/` (an
integer-valued control, and a semi-continuous one that is either off or above a minimum capacity).
A general constraint block that references the control at steps beyond the control horizon warns:
LinearMPC condenses those rows with the control taken as zero rather than held, so they do not
constrain what they read as.
`move_block` keeps the control constant
over blocks of steps — it sets the control horizon itself, `Nc` becoming the last step at which the
blocked control still changes, and the control-rate bounds follow it — and
`prestabilizing_feedback` reparametrizes `u = -K x + v`.
# Robust constraint tightening
The constraints are tightened so that they hold for every disturbance and every error of the state
estimate within given bounds (LinearMPC.jl `set_disturbance!` and `set_x0_uncertainty!`). The
disturbance inputs `w` enter the discrete-time model as `x_{k+1} = A x_k + B u_k + ... + B_w w_k`,
bounded by `disturbance_input_min <= w_k <= disturbance_input_max` and held over the sample. With
`dynamics`, `w` are the `disturbance_inputs` of the plant, which is then linearized also with respect
to them. With a model given as matrices, `B_w` is `Bw`, continuous time when `continuous = true` like
`B`; `Bw = B[:, i]` makes `w` a disturbance added to the control `u_i`, and `Bw = I` bounds the
increment of each state variable over one sample. A disturbance added to a control of `dynamics` is
modeled in the plant: an additional input `w`, which the actuator adds to the control, named in
`disturbance_inputs` (a control itself cannot be a disturbance input, since the latter is zero in the
prediction).
An empty lower bound is the negated upper bound and vice versa. The error of the state estimate is
bounded by `-δ <= x - xhat <= δ`, with `δ = x0_uncertainty`, or `δ = κ σ` with
`κ = x0_uncertainty_sigma` and `σ` the standard deviations of the stationary error of the filtered
estimate of the observer, computed from `R1` and `R2` when the model is built. `κ σ` bounds a
Gaussian error only with a probability that increases with `κ`, not with certainty.
The bounds of a constraint at a prediction step are tightened by the worst case, over these bounds,
of the deviation of the constrained quantity from its prediction under the feedback `u = v - K x`
(`K` of `prestabilizing_feedback`, zero otherwise). The control of the current step is computed
from the estimate and carries no error, and a constraint on the current state is not tightened.
Without prestabilizing feedback the tightening of an unstable or integrating plant grows with the
prediction horizon, so enable `prestabilizing_feedback` for such a plant. A tightening that leaves a
two-sided constraint, soft or hard, without feasible values is refused when the model is built. The estimate
of an offset-free disturbance (`dhat`) is treated as exact.
# Observer
The state is estimated by a steady-state Kalman filter with process- and measurement-noise
covariances `R1`, `R2` from the
measurements `y = C_meas x + Dd_meas d` (default: the controlled outputs). With
`offset_free != None` the model is augmented with constant disturbance channels and both the state
and the disturbance are estimated (LinearMPC.jl `set_offset_free_observer!`); the estimate is
output as `dhat` and used in the prediction, giving offset-free tracking under constant
disturbances. The measured outputs are then the controlled outputs. `StateDisturbance` and
`Velocity` are fixed-gain realizations whose disturbance estimate takes up the whole innovation
every step (fast, not tunable); `InputDisturbance` (a constant load disturbance on every input,
`Bd = B`, `nd = nu`) and `OutputDisturbance` (a constant bias on every output, `nd = ny`) design a
Kalman filter on the augmented model, and `R1` may then have `nx + nd` entries whose disturbance
part sets how fast the disturbance is estimated, i.e. the effective integral gain (with `nx`
entries the disturbance covariance is 1).
`observer_type` selects the observer component. `SteadyState` (the default) is `LinearMPCObserver`
with the stationary gain stored in the MPC object. `TimeVarying` is `SqKalmanObserver`, a
square-root Kalman filter that propagates the covariance of the estimate and uses at every tick only
the measurements that are available: finite and, with `use_valid`, flagged true on the `y_valid`
input, so that a missing or stale measurement is left out of the correction. It is designed from
`R1` and `R2` as above (the disturbance states with covariance 1 when `R1` has `nx` entries), and the
covariance of its initial estimate is the stationary prediction covariance (`kalman_observer.R0`,
tunable), so that with every measurement available it equals the steady-state observer for a plain
Kalman filter, `InputDisturbance` and `OutputDisturbance`. For `StateDisturbance` and `Velocity` it
is the Kalman filter of the augmented model rather than the fixed-gain realization. `TimeVarying`
runs in Julia and is not exported to C. Both observers are standalone components for the model
`x⁺ = A x + B u`, `y = C x`: the controller gives them the (augmented) estimator of the MPC object with
the input `[u; d; 1]` (`B = [G Gd f]`) and the measurement `y - Dd d - h`, where `d` are the measured
disturbances of the current step (the first entries of a disturbance preview), so the size of the
observer does not depend on the horizon.
# Signals and timing
At tick `k` the observer corrects its prediction with `y_k` (and `d_k`), the optimizer computes
`u_k` from the corrected estimate, `r_k`, `d_k`, `dhat_k`, `p_k` and `u_{k-1}`, and the observer
predicts the estimate for tick `k+1` using `u_k`. Measurements and references must be sampled
before this component and the control held after it (`Sampler`, `ZeroOrderHold`). With
`reference_preview`, `disturbance_preview` or `parameter_preview`, the corresponding input carries
a whole horizon as a flat column-major vector (`r[(j-1)*ny + i]` is reference `i` at prediction
step `j`).
# Solver and C code export
The QP is solved by the C code LinearMPC.jl generates for this controller, compiled once per
controller into a shared library (cached by content) and called from the clocked equation through
a `ccall`; the steady-state observer is plain matrix arithmetic. Both are exported to standalone C by the
`CCodeExport` analysis, so what is simulated is what is deployed. `backend = Backend.Auto()`
falls back to LinearMPC.jl's Julia solver (same DAQP solver, results agree to solver tolerance)
with a warning when no C compiler is found; such a model cannot be exported. `precision` selects
the float type of the generated QP code (`Single` compiles DAQP from its sources); the observer
equations are `Real`, i.e. double, and so is the Julia fallback. The `exitflag` output carries the
solver's DAQP exit flag on either backend.
# Notes
All matrices and options are structural: the QP is condensed once when the model is built, so
they are not tunable parameters. Empty arrays mean "not given" and select LinearMPC's defaults.
Features that are not surfaced here (several general constraint blocks with different horizons
or softness, logic constraints, `:general` disturbance models,
per-input move blocking) are available by building the `DyadLinearMPC.MPC` in Julia and passing it
as `mpc = build_linear_mpc(my_mpc)`; all other parameters are then ignored, `u0` among them,
so a hand-built object that must start from an operating-point control takes it as
`build_linear_mpc(my_mpc, u0 = u_trim)`. Use one MPC object per simulated model instance.
"""
component LinearMPCController
"The LinearMPC.jl MPC object is built from the parameters below; pass build_linear_mpc(m) of a hand-built MPC m as mpc to bypass them"
extends LinearMPCPart(mpc = build_linear_mpc(model = linearization, A = A, B = B, Bd = Bd, C = C, Dd = Dd, f_offset = f_offset, h_offset = h_offset, continuous = continuous, Ts = Ts, Np = Np, Nc = Nc, Q1 = Q1, Q2 = Q2, Q3 = Q3, Q12 = Q12, Sd = Sd, Qf = Qf, Qfx = Qfx, Ex = Ex, ex = ex, Eu = Eu, eu = eu, terminal_lqr_cost = terminal_lqr_cost, umin = umin, umax = umax, dumin = dumin, dumax = dumax, ymin = ymin, ymax = ymax, output_bounds_ks = output_bounds_ks, output_bounds_soft = output_bounds_soft, output_bounds_binary = output_bounds_binary, output_bounds_prio = output_bounds_prio, Ax = Ax, Au = Au, Ar = Ar, Ad = Ad, Aup = Aup, Ap = Ap, lb = lb, ub = ub, constraint_ks = constraint_ks, constraint_soft = constraint_soft, constraint_binary = constraint_binary, constraint_prio = constraint_prio, binary_controls = binary_controls, Nc_binary = Nc_binary, move_block = move_block, prestabilizing_feedback = prestabilizing_feedback, K = K, reference_tracking = reference_tracking, reference_preview = reference_preview, disturbance_preview = disturbance_preview, parameter_preview = parameter_preview, soft_weight = soft_weight, primal_tol = primal_tol, dual_tol = dual_tol, iter_limit = iter_limit, offset_free = offset_free, R1 = R1, R2 = R2, C_meas = C_meas, Dd_meas = Dd_meas, x0 = x0, d0 = d0, u0 = u0, Bw = Bw, disturbance_input_min = disturbance_input_min, disturbance_input_max = disturbance_input_max, x0_uncertainty = x0_uncertainty, x0_uncertainty_sigma = x0_uncertainty_sigma))
"Sampled measurements"
y = [RealInput@[clk]() for i in 1:ny_meas] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 825, "x2": 0, "y2": 925}}}
}
"References; 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": 75, "x2": 0, "y2": 175}}}}
"Measured disturbances; nd entries, or nd*Np column-major entries with disturbance preview (only when Bd or Dd are given)"
d = [RealInput@[clk]() for i in 1:nd_in] if has_d {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 575, "x2": 0, "y2": 675}}}
}
"Generalized parameters; np entries, or np*Np column-major entries with parameter preview (only when Ex, Eu or Ap have columns)"
p = [RealInput@[clk]() for i in 1:np_in] if has_p {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 325, "x2": 0, "y2": 425}}}
}
"Control signal"
u = [RealOutput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 325, "x2": 1100, "y2": 425}}}
}
"State estimate"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 575, "x2": 1100, "y2": 675}}}
}
"Estimated offset-free disturbance (only when offset_free is not None)"
dhat = [RealOutput@[clk]() for i in 1:ndo] if has_dhat {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 825, "x2": 1100, "y2": 925}}}
}
"Solver status of the last step (DAQP exit flag: 1 optimal, 2 with active soft constraints, negative on failure); lmpc_status_name names it"
exitflag = RealOutput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 75, "x2": 1100, "y2": 175}}}
}
"Validity of the measurements: y[i] is used when y_valid[i] is true (only with use_valid, which requires observer_type = TimeVarying)"
y_valid = [BooleanInput@[clk]() for i in 1:n_valid] if use_valid {}
"Steady-state observer (observer_type = SteadyState)"
observer = LinearMPCObserver@[clk = clk](final A = _lmpc_obs_F(mpc), final B = _lmpc_obs_B(mpc, nd_obs), final C = _lmpc_obs_C(mpc), final K = _lmpc_obs_K(mpc), x0 = _lmpc_xaug0(mpc)) if steady_observer {
"Dyad": {"placement": {"diagram": {"x1": 180, "y1": 400, "x2": 380, "y2": 600}}}
}
"Time-varying square-root Kalman filter (observer_type = TimeVarying)"
kalman_observer = SqKalmanObserver@[clk = clk](final A = _lmpc_obs_F(mpc), final B = _lmpc_obs_B(mpc, nd_obs), final C = _lmpc_obs_C(mpc), final use_valid = use_valid, R1 = _lmpc_kf_R1(mpc, R1), R2 = _lmpc_kf_R2(mpc, R2), x0 = _lmpc_xaug0(mpc), R0 = _lmpc_kf_R0(mpc, R1, R2)) if !steady_observer {
"Dyad": {"placement": {"diagram": {"x1": 180, "y1": 400, "x2": 380, "y2": 600}}}
}
"Constrained optimizer"
optimizer = LinearMPCOptimizer@[clk = clk](mpc = mpc, backend = backend, precision = precision, output_trajectories = output_trajectories, output_statistics = output_statistics) {
"Dyad": {"placement": {"diagram": {"x1": 590, "y1": 360, "x2": 790, "y2": 560}}}
}
"Plant whose linearization is the prediction model: a subcomponent of the enclosing model (dynamics = plant) or a component constructor (dynamics = MyPlant); leave empty to give the model as the matrices A, B, ..."
structural parameter dynamics::MPCDynamics
"""
Names of the manipulated inputs of dynamics, in the order of u (e.g. ["power"])
"""
structural parameter inputs::String[:] = []
"Names of the measured disturbance inputs of dynamics, in the order of d; the columns of Bd"
structural parameter known_disturbance_inputs::String[:] = []
"Names of the unmeasured inputs of dynamics, zero in the prediction; the states of their disturbance model are kept in the state"
structural parameter disturbance_inputs::String[:] = []
"Names of the controlled and measured outputs of dynamics, in the order of y, r and Q1"
structural parameter outputs::String[:] = []
"Names of the signals of dynamics that form the state, in the order of x0, R1 and xhat; empty for the state the compiler selects, in an unspecified order"
structural parameter state_variables::String[:] = []
"Operating point at which dynamics is linearized"
structural parameter linearization_point::LinearizationPoint = LinearizationPoint.InitialState()
"Inputs at the operating point, in the order of inputs; empty for zero. With OutputTarget, the initial guess of the inputs"
structural parameter u_op::Real[:] = fill(0.0, 0)
"Measured disturbances at the operating point, in the order of known_disturbance_inputs; empty for zero"
structural parameter d_op::Real[:] = fill(0.0, 0)
"Output targets that define the operating point with OutputTarget, in the order of outputs"
structural parameter y_op::Real[:] = fill(0.0, 0)
"The linearization of dynamics (see linearize_prediction_model); nothing without dynamics"
structural parameter linearization::LinearizedModelRef = linearize_prediction_model(dynamics = dynamics, inputs = inputs, known_disturbance_inputs = known_disturbance_inputs, disturbance_inputs = disturbance_inputs, outputs = outputs, state_variables = state_variables, linearization_point = linearization_point, u_op = u_op, d_op = d_op, y_op = y_op, disturbance_columns = length(disturbance_input_min) + length(disturbance_input_max) > 0)
"State matrix (nx × nx); discrete-time unless continuous = true. Empty when the model is given as dynamics"
structural parameter A::Real[:, :] = fill(0.0, 0, 0)
"Input matrix (nx × nu)"
structural parameter B::Real[:, :] = fill(0.0, 0, 0)
"Measured-disturbance input matrix (nx × nd); empty for no measured disturbances"
structural parameter Bd::Real[:, :] = fill(0.0, 0, 0)
"Output matrix of the controlled outputs (ny × nx); empty for the identity"
structural parameter C::Real[:, :] = fill(0.0, 0, 0)
"Disturbance feedthrough to the controlled outputs (ny × nd); empty for zero"
structural parameter Dd::Real[:, :] = fill(0.0, 0, 0)
"Affine offset of the state update (nx); empty for zero"
structural parameter f_offset::Real[:] = fill(0.0, 0)
"Affine offset of the outputs (ny); empty for zero"
structural parameter h_offset::Real[:] = fill(0.0, 0)
"Interpret A, B, Bd and f_offset as continuous-time and ZOH-discretize them with Ts"
structural parameter continuous::Boolean = false
"Sample time used for the ZOH discretization when continuous = true; must equal the clock period"
structural parameter Ts::Real = 1.0
"Prediction horizon"
structural parameter Np::Integer = 10
"Control horizon"
structural parameter Nc::Integer = Np
"Output weight on C*x - r (ny × ny, or ny diagonal entries); empty for the identity"
structural parameter Q1::Real[:, :] = fill(0.0, 0, 0)
"Control weight (nu × nu, or nu diagonal entries); empty for zero. A penalty on the control level leaves a steady-state error when tracking a plant with a finite static gain, so penalize the control increments with Q3 instead, or give a Q2 small enough that the error it leaves is acceptable"
structural parameter Q2::Real[:, :] = fill(0.0, 0, 0)
"Control-increment weight on u_k - u_{k-1} (nu × nu, or nu diagonal entries); empty for zero"
structural parameter Q3::Real[:, :] = fill(0.0, 0, 0)
"State-control cross weight (nx × nu); empty for zero"
structural parameter Q12::Real[:, :] = fill(0.0, 0, 0)
"Terminal output weight (ny × ny, or ny diagonal entries); empty for Q1"
structural parameter Qf::Real[:, :] = fill(0.0, 0, 0)
"Terminal state weight (nx × nx, or nx diagonal entries); empty for zero"
structural parameter Qfx::Real[:, :] = fill(0.0, 0, 0)
"Gain of the generalized parameter in the linear state cost (Ex p + ex)' x (nx × np); empty for zero"
structural parameter Ex::Real[:, :] = fill(0.0, 0, 0)
"Constant linear state cost (nx); empty for zero"
structural parameter ex::Real[:] = fill(0.0, 0)
"Gain of the generalized parameter in the linear control cost (Eu p + eu)' u (nu × np); empty for zero"
structural parameter Eu::Real[:, :] = fill(0.0, 0, 0)
"Constant linear control cost (nu); empty for zero"
structural parameter eu::Real[:] = fill(0.0, 0)
"Set Qfx to the infinite-horizon LQR cost (requires reference_tracking = false)"
structural parameter terminal_lqr_cost::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 output bounds (ny); empty for none"
structural parameter ymin::Real[:] = fill(0.0, 0)
"Upper output bounds (ny); empty for none"
structural parameter ymax::Real[:] = fill(0.0, 0)
"Prediction steps on which the output bounds are enforced; empty for 2:Np"
structural parameter output_bounds_ks::Integer[:] = []
"Soften the output bounds"
structural parameter output_bounds_soft::Boolean = true
"Enforce the output bounds as binary (one of the bounds holds with equality)"
structural parameter output_bounds_binary::Boolean = false
"Priority of the output bounds"
structural parameter output_bounds_prio::Integer = 0
"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)
"Reference coefficient of the general constraints (m × ny); empty for zero"
structural parameter Ar::Real[:, :] = fill(0.0, 0, 0)
"Measured-disturbance coefficient of the general constraints (m × nd); empty for zero"
structural parameter Ad::Real[:, :] = fill(0.0, 0, 0)
"Previous-control coefficient of the general constraints (m × nu); empty for zero"
structural parameter Aup::Real[:, :] = fill(0.0, 0, 0)
"Generalized-parameter coefficient of the general constraints (m × np); empty for zero"
structural parameter Ap::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)
"Prediction steps on which the general constraints are enforced; empty for 2:Np"
structural parameter constraint_ks::Integer[:] = []
"Soften the general constraints"
structural parameter constraint_soft::Boolean = false
"Enforce the general constraints as binary (one of the bounds holds with equality)"
structural parameter constraint_binary::Boolean = false
"Priority of the general constraints"
structural parameter constraint_prio::Integer = 0
"Indices of the controls restricted to the values {umin, umax}; empty for none"
structural parameter binary_controls::Integer[:] = []
"Number of initial horizon steps over which the binary controls are restricted to {umin, umax}, the rest are relaxed to continuous; -1 for the whole control horizon"
structural parameter Nc_binary::Integer = -1
"Move-blocking pattern, the number of steps each control move is held (applied to all controls); empty for none. It sets the control horizon itself, so a Nc given alongside it is ignored"
structural parameter move_block::Integer[:] = []
"Reparametrize the control as u = -K x + v with a prestabilizing feedback"
structural parameter prestabilizing_feedback::Boolean = false
"Prestabilizing feedback gain (nu × nx); empty for the LQR gain of the objective"
structural parameter K::Real[:, :] = fill(0.0, 0, 0)
"Input matrix of the unmeasured disturbance inputs w of the robust constraint tightening (nx × nw; continuous-time when continuous = true). Only with a model given as matrices; with dynamics, the columns of disturbance_inputs are linearized"
structural parameter Bw::Real[:, :] = fill(0.0, 0, 0)
"Lower bounds of the disturbance inputs w for the robust constraint tightening (one entry per disturbance input: per name in disturbance_inputs, or per column of Bw); empty for -disturbance_input_max, both empty for none"
structural parameter disturbance_input_min::Real[:] = fill(0.0, 0)
"Upper bounds of the disturbance inputs w for the robust constraint tightening (as disturbance_input_min); empty for -disturbance_input_min"
structural parameter disturbance_input_max::Real[:] = fill(0.0, 0)
"Bound δ on the error of the state estimate, -δ <= x - xhat <= δ (nx), for the robust constraint tightening; empty for none"
structural parameter x0_uncertainty::Real[:] = fill(0.0, 0)
"Bound on the error of the state estimate as a multiple κ of the standard deviations of the stationary estimation error of the observer (from R1 and R2), δ = κ σ, for the robust constraint tightening; 0 for none"
structural parameter x0_uncertainty_sigma::Real = 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) instead of a setpoint"
structural parameter reference_preview::Boolean = false
"Take a measured-disturbance trajectory over the horizon (nd*Np entries) instead of the current value"
structural parameter disturbance_preview::Boolean = false
"Take a generalized-parameter trajectory over the horizon (np*Np entries) instead of the current value"
structural parameter parameter_preview::Boolean = false
"Penalty weight on soft-constraint violations"
structural parameter soft_weight::Real = 1e6
"DAQP primal feasibility tolerance; -1 keeps the solver default. Applied to the Julia solver and embedded in the generated C"
structural parameter primal_tol::Real = -1.0
"DAQP dual feasibility (optimality) tolerance; -1 keeps the solver default"
structural parameter dual_tol::Real = -1.0
"DAQP iteration limit; -1 keeps the solver default"
structural parameter iter_limit::Integer = -1
"QP solver backend; Auto uses the C code generated by LinearMPC.jl when a C compiler is available (required for C code export), otherwise the Julia solver"
structural parameter backend::Backend = Backend.Auto()
"Float type of the C code generated for the QP (C backend only): Double, or Single for 32-bit targets; the observer equations are Real"
structural parameter precision::MPCPrecision = MPCPrecision.Double()
"Also record the predicted state, control and output trajectories and the QP statistics of every tick (variables optimizer.x_pred, optimizer.u_pred, optimizer.y_pred, optimizer.stats), for debugging and mpc_gui; Julia backend only. The readbacks are four equations whose number does not grow with the prediction horizon, though the trajectories themselves are stored in the solution; output_statistics records the statistics alone"
structural parameter output_trajectories::Boolean = false
"Record the QP statistics of every tick (variable optimizer.stats: objective value, solve time in seconds, active-set iterations, branch-and-bound nodes) without the predicted trajectories; Julia backend only. One equation. Implied by output_trajectories"
structural parameter output_statistics::Boolean = false
"Offset-free disturbance model of the observer; None for a plain Kalman filter"
structural parameter offset_free::OffsetFreeMethod = OffsetFreeMethod.None()
"Observer: SteadyState (LinearMPCObserver, exported to C) or TimeVarying (SqKalmanObserver, Julia only)"
structural parameter observer_type::ObserverType = ObserverType.SteadyState()
"Whether the y_valid input exists (observer_type = TimeVarying only); without it, every finite measurement is used"
structural parameter use_valid::Boolean = false
"Process-noise covariance of the Kalman filter (nx × nx, or nx diagonal entries; with InputDisturbance/OutputDisturbance also (nx + nd) × (nx + nd), the disturbance part setting the disturbance-estimate speed); empty for the identity"
structural parameter R1::Real[:, :] = fill(0.0, 0, 0)
"Measurement-noise covariance of the Kalman filter (ny_meas × ny_meas, or ny_meas diagonal entries); empty for the identity"
structural parameter R2::Real[:, :] = fill(0.0, 0, 0)
"Measurement matrix of the observer when the measured outputs differ from C (ny_meas × nx, only with offset_free = None); empty for C"
structural parameter C_meas::Real[:, :] = fill(0.0, 0, 0)
"Disturbance feedthrough to the measured outputs (ny_meas × nd, only with offset_free = None); empty for Dd"
structural parameter Dd_meas::Real[:, :] = fill(0.0, 0, 0)
"Initial state estimate (nx); empty for zero, or for the operating point of dynamics"
structural parameter x0::Real[:] = fill(0.0, 0)
"Initial offset-free disturbance estimate (ny entries; nu with InputDisturbance); empty for zero"
structural parameter d0::Real[:] = fill(0.0, 0)
"Control assumed applied before the first tick, LinearMPC's uprev (nu); empty for zero, or for the operating-point input of dynamics. It enters the QP only when the previous control does (Q3, or a general constraint with Aup): setting it to the operating-point control keeps a rate-limited loop from walking the command up from zero over the first ticks"
structural parameter u0::Real[:] = fill(0.0, 0)
"Disturbance-control cross term d' Sd u (nd × nu) of the objective. The default is (pinv(B) Bd)' Q2 with an offset-free observer (with dynamics, from the ZOH discretization of the linearization), which makes Q2 penalize u relative to the input cancelling the measured disturbances instead of relative to zero (offset-free tracking), and zero for offset_free = None or for the default Q2 = 0; rows for the observer-estimated disturbances are appended with the same formula. Empty for zero"
structural parameter Sd::Real[:, :] = case(offset_free, OffsetFreeMethod.None) ? fill(0.0, 0, 0) : _lmpc_offset_free_Sd(linearization, B, Bd, Q2, Ts)
"Whether the steady-state observer is used (derived from observer_type)"
final structural parameter steady_observer::Boolean = _lmpc_obs_steady(observer_type, use_valid)
"Width of the y_valid input: ny_meas with use_valid, else 0 (derived)"
final structural parameter n_valid::Integer = use_valid ? ny_meas : 0
"Number of measured disturbances of the current step, nd_meas (without the preview) (derived from mpc)"
final structural parameter nd_meas::Integer = _lmpc_nd_meas(mpc)
"Width of the disturbance vector of the observer input, max(nd_meas, 1): the estimator uses only the current step of a disturbance preview (derived)"
final structural parameter nd_obs::Integer = max(nd_meas, 1)
"Width of the observer input [u; d_int; 1], nu + nd_obs + 1 (derived)"
final structural parameter nu_obs::Integer = nu + nd_obs + 1
"Zero placeholder used as the disturbance vector when d is absent"
final parameter d_dummy::Real[nd_obs] = fill(0.0, nd_obs)
"Measured-disturbance feedthrough of the estimator, padded to the width of the disturbance vector"
final parameter Dd_o::Real[ny_meas, nd_obs] = _lmpc_obs_Dd(mpc, nd_obs)
"Affine offset of the measurement equation of the estimator"
final parameter h_o::Real[ny_meas] = _lmpc_obs_h(mpc)
"Disturbance vector of the observer input: the measured disturbances of the current step"
variable d_int::Real[nd_obs]
"Input of the observer, [u; d_int; 1]"
variable u_obs::Real[nu_obs]
"Measurement of the observer, y - Dd d_int - h"
variable y_obs::Real[ny_meas]
"Estimate of the augmented state [x; d_offset] of the estimator"
variable x_obs::Real[nxa]
relations
if has_r
connect(r, optimizer.r) {}
end
if has_d
connect(d, optimizer.d) {}
# The first nd_meas entries of d, the current step of a preview (column-major)
d_int = _lmpc_obs_current(d, nd_obs)
else
d_int = d_dummy
end
if has_p
connect(p, optimizer.p) {}
end
connect(optimizer.u, u) {}
connect(optimizer.exitflag, exitflag) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 800, "y": 410}, {"x": 890, "y": 410}, {"x": 890, "y": 125}],
"E": 2
}
],
"path": {}
}
}
# The observers are standalone components for x⁺ = A x + B u, y = C x; the measured disturbances
# and the affine offsets of the estimator enter through the input [u; d_int; 1] (B = [G Gd f])
# and the measurement y - Dd d_int - h
# One vector equation each, so that every entry is clocked with u and y (see _lmpc_obs_input)
y_obs = y - Dd_o * d_int - h_o
u_obs = _lmpc_obs_input(u, d_int)
if steady_observer
for i in 1:ny_meas
observer.y[i] = y_obs[i]
end
for i in 1:nu_obs
observer.u[i] = u_obs[i]
end
for i in 1:nxa
x_obs[i] = observer.xhat[i]
end
else
for i in 1:ny_meas
kalman_observer.y[i] = y_obs[i]
end
for i in 1:nu_obs
kalman_observer.u[i] = u_obs[i]
end
for i in 1:nxa
x_obs[i] = kalman_observer.xhat[i]
end
if use_valid
connect(y_valid, kalman_observer.y_valid) {}
end
end
# The augmented state is [x; d_offset]
for i in 1:nx
xhat[i] = x_obs[i]
optimizer.xhat[i] = x_obs[i]
end
if has_dhat
for i in 1:ndo
dhat[i] = x_obs[nx + i]
optimizer.dhat[i] = x_obs[nx + i]
end
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/LinearMPCController.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Dynamics": {
"control": "fieldset",
"contents": ["A", "B", "C", "continuous", "Ts"],
"children": {"Advanced": {"contents": ["Bd", "Dd", "f_offset", "h_offset"]}}
},
"Prediction model": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"known_disturbance_inputs",
"outputs",
"linearization_point",
"u_op",
"d_op",
"y_op"
],
"children": {"Advanced": {"contents": ["disturbance_inputs", "state_variables"]}}
},
"Cost function": {
"control": "fieldset",
"contents": ["Np", "Nc", "Q1", "Q2", "Q3", "Sd", "terminal_lqr_cost"],
"children": {"Advanced": {"contents": ["Q12", "Qf", "Qfx", "Ex", "ex", "Eu", "eu"]}}
},
"Constraints": {
"control": "fieldset",
"contents": ["umin", "umax", "dumin", "dumax", "ymin", "ymax"],
"children": {
"Advanced": {
"contents": [
"output_bounds_ks",
"output_bounds_soft",
"output_bounds_binary",
"output_bounds_prio",
"soft_weight",
"Ax",
"Au",
"Ar",
"Ad",
"Aup",
"Ap",
"lb",
"ub",
"constraint_ks",
"constraint_soft",
"constraint_binary",
"constraint_prio",
"binary_controls",
"Nc_binary"
]
}
}
},
"Robustness": {
"control": "fieldset",
"contents": [
"disturbance_input_min",
"disturbance_input_max",
"x0_uncertainty",
"x0_uncertainty_sigma"
],
"children": {"Advanced": {"contents": ["Bw"]}}
},
"Observer": {
"control": "fieldset",
"contents": ["offset_free", "observer_type", "use_valid", "R1", "R2"],
"children": {"Advanced": {"contents": ["C_meas", "Dd_meas", "x0", "d0", "u0"]}}
},
"Solver options": {
"control": "fieldset",
"contents": [
"reference_tracking",
"reference_preview",
"disturbance_preview",
"parameter_preview",
"backend",
"precision",
"output_trajectories",
"output_statistics"
],
"children": {
"Advanced": {"contents": ["move_block", "prestabilizing_feedback", "K", "mpc"]}
}
},
"Derived": {
"control": "fieldset",
"contents": [
"nx",
"nu",
"ny_meas",
"nr_in",
"nd_in",
"np_in",
"ndo",
"has_r",
"has_d",
"has_p",
"has_dhat"
]
}
}
}
}
}
endFlattened Source
"""
Linear model-predictive controller built on LinearMPC.jl: a `LinearMPCObserver` and a
`LinearMPCOptimizer` sharing one MPC object that is constructed from the parameters below when
the model is built.
# Model
The controller model is the discrete-time linear system
```math
\\begin{aligned}
x_{k+1} &= A x_k + B u_k + B_d d_k + f \\\\
y_k &= C x_k + D_d d_k + h
\\end{aligned}
```
with state `x`, control `u`, measured disturbance `d`, controlled output `y` (the quantity the
references apply to), and affine offsets `f`, `h`. With `continuous = true`, `A`, `B`, `B_d` and
`f` are continuous-time and are ZOH-discretized with the sample time `Ts`, which must then equal
the period of the clock the component is connected to. Feedthrough from `u` is not supported by
LinearMPC.jl.
# Prediction model from a plant
Instead of the matrices, the plant itself may be given as `dynamics`: a subcomponent of the enclosing
model (`dynamics = plant`, declared before the controller) or a component constructor
(`dynamics = MyPlant`, for a model that does not contain the plant, such as one exported to C). The
plant is linearized at an operating point into the continuous-time affine model above, which is
ZOH-discretized with `Ts`; `A`, `B`, `Bd`, `C`, `Dd`, `f_offset`, `h_offset` and `continuous` are
then left empty. The signals are named, and each name list fixes the order of what it describes:
`inputs` the manipulated inputs (the columns of `B`, the order of `u`), `known_disturbance_inputs`
the measured disturbances (the columns of `B_d`, the order of `d`), `outputs` the controlled and
measured outputs (the rows of `C`, the order of `y`, `r`, `Q1`, `ymin`), and `disturbance_inputs`
the unmeasured inputs, which are zero in the prediction and whose disturbance-model states are kept
in the state (an integrating disturbance `der(d) = w` with `w` named here is estimated as a constant).
The operating point is selected by `linearization_point`: the initial state of the plant with its
inputs held at `u_op` and `d_op` (`InitialState`), the steady state at those inputs (`SteadyState`),
or the steady state and the inputs at which the outputs take the values `y_op` (`OutputTarget`). The
steady state is computed by MTK's initialization of the plant; the initial values of its states must
then be initial conditions (`variable x::Real(initial = x0)`), not initialization equations. `x0` and
`u0` default to the operating point.
The state of the linearized model is the state the compiler selected for the plant, whose order is
unspecified and may change between compiler versions; the parameters indexed by the state (`x0`,
`R1`, `Q12`, `Qfx`, `Ex`, `ex`, `Ax`, `K`, `C_meas`) and the output `xhat` then have no defined
meaning, and the former are refused. `state_variables` names the signals that form the state, in the
order of those parameters and of `xhat`, e.g. `state_variables = ["h1", "h2"]`, the list `ACADOSMPC`
and `UKFObserver` take. The named signals need not be the ones the compiler selected: the model is
transformed to their coordinates, which requires that they determine the state. Bounds and
references are in absolute units, not deviations from the operating point.
# Objective
```math
\\sum_{k=0}^{N_p-1} \\Big( (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
+ x_k^T Q_{12} u_k + (E_x p_k + e_x)^T x_k + (E_u p_k + e_u)^T u_k \\Big)
+ (C x_{N_p} - r)^T Q_f (C x_{N_p} - r) + x_{N_p}^T Q_{fx} x_{N_p}
```
`Q1`, `Q2`, `Q3`, `Qf` and `Qfx` may also be given as vectors of diagonal entries. `Qf` defaults to
`Q1`; `terminal_lqr_cost = true` sets `Qfx` to the infinite-horizon LQR cost (regulation only), and
requires a positive definite `Q2 + Q3`, as does `prestabilizing_feedback`. `p` is the generalized
parameter input (present when `Ex`, `Eu` or `Ap` have columns).
`Q2` defaults to zero: a penalty on the control *level* is minimized at `u = 0`, while holding the
output at a nonzero reference needs a nonzero input, so the two trade off and the loop settles
short of the setpoint — by `Q1 / (Q1 + Q2 K_dc^2)` of the reference for a scalar plant, which is
most of it on a plant whose inputs are physical commands far from zero. Penalize the control
increments with `Q3` instead: at steady state `u_k - u_{k-1} = 0`, so the rate term contributes
nothing and the minimum of what remains is the reference. A small `Q2` alongside `Q3` leaves an
error in proportion to itself, which is the way to keep a leash on the control level. The builder
warns when a nonzero `Q2` is combined with reference tracking on a plant without integral action.
# Constraints
Hard input bounds `umin <= u <= umax`, hard control-rate bounds
`dumin <= u_k - u_{k-1} <= dumax` (enforced from the first move, which is taken against the control
last applied, through the control horizon `Nc`), output bounds `ymin <= y <= ymax` (soft by
default), and one block of general constraints
`lb <= Ax x_k + Au u_k + Ar r_k + Ad d_k + Aup u_{k-1} + Ap p_k <= ub` enforced for the
prediction steps `constraint_ks` (default `2:Np`), optionally soft or binary (one of the bounds
holds with equality). Soft-constraint violations are penalized with `soft_weight`. Control inputs listed
in `binary_controls` are restricted to `{umin, umax}`; together with the general constraint block
they encode richer discrete input sets, see the examples in `dyad/Examples/Binary/` (an
integer-valued control, and a semi-continuous one that is either off or above a minimum capacity).
A general constraint block that references the control at steps beyond the control horizon warns:
LinearMPC condenses those rows with the control taken as zero rather than held, so they do not
constrain what they read as.
`move_block` keeps the control constant
over blocks of steps — it sets the control horizon itself, `Nc` becoming the last step at which the
blocked control still changes, and the control-rate bounds follow it — and
`prestabilizing_feedback` reparametrizes `u = -K x + v`.
# Robust constraint tightening
The constraints are tightened so that they hold for every disturbance and every error of the state
estimate within given bounds (LinearMPC.jl `set_disturbance!` and `set_x0_uncertainty!`). The
disturbance inputs `w` enter the discrete-time model as `x_{k+1} = A x_k + B u_k + ... + B_w w_k`,
bounded by `disturbance_input_min <= w_k <= disturbance_input_max` and held over the sample. With
`dynamics`, `w` are the `disturbance_inputs` of the plant, which is then linearized also with respect
to them. With a model given as matrices, `B_w` is `Bw`, continuous time when `continuous = true` like
`B`; `Bw = B[:, i]` makes `w` a disturbance added to the control `u_i`, and `Bw = I` bounds the
increment of each state variable over one sample. A disturbance added to a control of `dynamics` is
modeled in the plant: an additional input `w`, which the actuator adds to the control, named in
`disturbance_inputs` (a control itself cannot be a disturbance input, since the latter is zero in the
prediction).
An empty lower bound is the negated upper bound and vice versa. The error of the state estimate is
bounded by `-δ <= x - xhat <= δ`, with `δ = x0_uncertainty`, or `δ = κ σ` with
`κ = x0_uncertainty_sigma` and `σ` the standard deviations of the stationary error of the filtered
estimate of the observer, computed from `R1` and `R2` when the model is built. `κ σ` bounds a
Gaussian error only with a probability that increases with `κ`, not with certainty.
The bounds of a constraint at a prediction step are tightened by the worst case, over these bounds,
of the deviation of the constrained quantity from its prediction under the feedback `u = v - K x`
(`K` of `prestabilizing_feedback`, zero otherwise). The control of the current step is computed
from the estimate and carries no error, and a constraint on the current state is not tightened.
Without prestabilizing feedback the tightening of an unstable or integrating plant grows with the
prediction horizon, so enable `prestabilizing_feedback` for such a plant. A tightening that leaves a
two-sided constraint, soft or hard, without feasible values is refused when the model is built. The estimate
of an offset-free disturbance (`dhat`) is treated as exact.
# Observer
The state is estimated by a steady-state Kalman filter with process- and measurement-noise
covariances `R1`, `R2` from the
measurements `y = C_meas x + Dd_meas d` (default: the controlled outputs). With
`offset_free != None` the model is augmented with constant disturbance channels and both the state
and the disturbance are estimated (LinearMPC.jl `set_offset_free_observer!`); the estimate is
output as `dhat` and used in the prediction, giving offset-free tracking under constant
disturbances. The measured outputs are then the controlled outputs. `StateDisturbance` and
`Velocity` are fixed-gain realizations whose disturbance estimate takes up the whole innovation
every step (fast, not tunable); `InputDisturbance` (a constant load disturbance on every input,
`Bd = B`, `nd = nu`) and `OutputDisturbance` (a constant bias on every output, `nd = ny`) design a
Kalman filter on the augmented model, and `R1` may then have `nx + nd` entries whose disturbance
part sets how fast the disturbance is estimated, i.e. the effective integral gain (with `nx`
entries the disturbance covariance is 1).
`observer_type` selects the observer component. `SteadyState` (the default) is `LinearMPCObserver`
with the stationary gain stored in the MPC object. `TimeVarying` is `SqKalmanObserver`, a
square-root Kalman filter that propagates the covariance of the estimate and uses at every tick only
the measurements that are available: finite and, with `use_valid`, flagged true on the `y_valid`
input, so that a missing or stale measurement is left out of the correction. It is designed from
`R1` and `R2` as above (the disturbance states with covariance 1 when `R1` has `nx` entries), and the
covariance of its initial estimate is the stationary prediction covariance (`kalman_observer.R0`,
tunable), so that with every measurement available it equals the steady-state observer for a plain
Kalman filter, `InputDisturbance` and `OutputDisturbance`. For `StateDisturbance` and `Velocity` it
is the Kalman filter of the augmented model rather than the fixed-gain realization. `TimeVarying`
runs in Julia and is not exported to C. Both observers are standalone components for the model
`x⁺ = A x + B u`, `y = C x`: the controller gives them the (augmented) estimator of the MPC object with
the input `[u; d; 1]` (`B = [G Gd f]`) and the measurement `y - Dd d - h`, where `d` are the measured
disturbances of the current step (the first entries of a disturbance preview), so the size of the
observer does not depend on the horizon.
# Signals and timing
At tick `k` the observer corrects its prediction with `y_k` (and `d_k`), the optimizer computes
`u_k` from the corrected estimate, `r_k`, `d_k`, `dhat_k`, `p_k` and `u_{k-1}`, and the observer
predicts the estimate for tick `k+1` using `u_k`. Measurements and references must be sampled
before this component and the control held after it (`Sampler`, `ZeroOrderHold`). With
`reference_preview`, `disturbance_preview` or `parameter_preview`, the corresponding input carries
a whole horizon as a flat column-major vector (`r[(j-1)*ny + i]` is reference `i` at prediction
step `j`).
# Solver and C code export
The QP is solved by the C code LinearMPC.jl generates for this controller, compiled once per
controller into a shared library (cached by content) and called from the clocked equation through
a `ccall`; the steady-state observer is plain matrix arithmetic. Both are exported to standalone C by the
`CCodeExport` analysis, so what is simulated is what is deployed. `backend = Backend.Auto()`
falls back to LinearMPC.jl's Julia solver (same DAQP solver, results agree to solver tolerance)
with a warning when no C compiler is found; such a model cannot be exported. `precision` selects
the float type of the generated QP code (`Single` compiles DAQP from its sources); the observer
equations are `Real`, i.e. double, and so is the Julia fallback. The `exitflag` output carries the
solver's DAQP exit flag on either backend.
# Notes
All matrices and options are structural: the QP is condensed once when the model is built, so
they are not tunable parameters. Empty arrays mean "not given" and select LinearMPC's defaults.
Features that are not surfaced here (several general constraint blocks with different horizons
or softness, logic constraints, `:general` disturbance models,
per-input move blocking) are available by building the `DyadLinearMPC.MPC` in Julia and passing it
as `mpc = build_linear_mpc(my_mpc)`; all other parameters are then ignored, `u0` among them,
so a hand-built object that must start from an operating-point control takes it as
`build_linear_mpc(my_mpc, u0 = u_trim)`. Use one MPC object per simulated model instance.
"""
component LinearMPCController
"Set-up LinearMPC.jl MPC object wrapped in an MPCRef (see build_linear_mpc)"
structural parameter mpc::MPCRef
"State dimension of the model (derived from mpc)"
final structural parameter nx::Integer = _lmpc_nx(mpc)
"Number of controls (derived from mpc)"
final structural parameter nu::Integer = _lmpc_nu(mpc)
"Number of measured outputs, the rows of the observer's measurement matrix; 0 without an observer (derived from mpc)"
final structural parameter ny_meas::Integer = _lmpc_ny_meas(mpc)
"Number of estimated offset-free disturbances, 0 for a plain Kalman filter (derived from mpc)"
final structural parameter ndo::Integer = _lmpc_ndo(mpc)
"Length of the augmented observer state, nx + ndo (derived)"
final structural parameter nxa::Integer = nx + ndo
"Width of the reference input: 0, ny or ny*Np (derived from mpc)"
final structural parameter nr_in::Integer = _lmpc_nr_in(mpc)
"Width of the measured-disturbance input: 0, nd_meas or nd_meas*Np (derived from mpc)"
final structural parameter nd_in::Integer = _lmpc_nd_in(mpc)
"Width of the generalized-parameter input: 0, np or np*Np (derived from mpc)"
final structural parameter np_in::Integer = _lmpc_np_in(mpc)
"Whether the r connector exists (derived)"
final structural parameter has_r::Boolean = nr_in > 0
"Whether the d connector exists (derived)"
final structural parameter has_d::Boolean = nd_in > 0
"Whether the dhat connector exists (derived)"
final structural parameter has_dhat::Boolean = ndo > 0
"Whether the p connector exists (derived)"
final structural parameter has_p::Boolean = np_in > 0
"Sampled measurements"
y = [RealInput@[clk]() for i in 1:ny_meas] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 825, "x2": 0, "y2": 925}}}
}
"References; 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": 75, "x2": 0, "y2": 175}}}}
"Measured disturbances; nd entries, or nd*Np column-major entries with disturbance preview (only when Bd or Dd are given)"
d = [RealInput@[clk]() for i in 1:nd_in] if has_d {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 575, "x2": 0, "y2": 675}}}
}
"Generalized parameters; np entries, or np*Np column-major entries with parameter preview (only when Ex, Eu or Ap have columns)"
p = [RealInput@[clk]() for i in 1:np_in] if has_p {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 325, "x2": 0, "y2": 425}}}
}
"Control signal"
u = [RealOutput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 325, "x2": 1100, "y2": 425}}}
}
"State estimate"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 575, "x2": 1100, "y2": 675}}}
}
"Estimated offset-free disturbance (only when offset_free is not None)"
dhat = [RealOutput@[clk]() for i in 1:ndo] if has_dhat {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 825, "x2": 1100, "y2": 925}}}
}
"Solver status of the last step (DAQP exit flag: 1 optimal, 2 with active soft constraints, negative on failure); lmpc_status_name names it"
exitflag = RealOutput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 75, "x2": 1100, "y2": 175}}}
}
"Validity of the measurements: y[i] is used when y_valid[i] is true (only with use_valid, which requires observer_type = TimeVarying)"
y_valid = [BooleanInput@[clk]() for i in 1:n_valid] if use_valid {}
"Steady-state observer (observer_type = SteadyState)"
observer = LinearMPCObserver@[clk = clk](final A = _lmpc_obs_F(mpc), final B = _lmpc_obs_B(mpc, nd_obs), final C = _lmpc_obs_C(mpc), final K = _lmpc_obs_K(mpc), x0 = _lmpc_xaug0(mpc)) if steady_observer {
"Dyad": {"placement": {"diagram": {"x1": 180, "y1": 400, "x2": 380, "y2": 600}}}
}
"Time-varying square-root Kalman filter (observer_type = TimeVarying)"
kalman_observer = SqKalmanObserver@[clk = clk](final A = _lmpc_obs_F(mpc), final B = _lmpc_obs_B(mpc, nd_obs), final C = _lmpc_obs_C(mpc), final use_valid = use_valid, R1 = _lmpc_kf_R1(mpc, R1), R2 = _lmpc_kf_R2(mpc, R2), x0 = _lmpc_xaug0(mpc), R0 = _lmpc_kf_R0(mpc, R1, R2)) if !steady_observer {
"Dyad": {"placement": {"diagram": {"x1": 180, "y1": 400, "x2": 380, "y2": 600}}}
}
"Constrained optimizer"
optimizer = LinearMPCOptimizer@[clk = clk](mpc = mpc, backend = backend, precision = precision, output_trajectories = output_trajectories, output_statistics = output_statistics) {
"Dyad": {"placement": {"diagram": {"x1": 590, "y1": 360, "x2": 790, "y2": 560}}}
}
"Plant whose linearization is the prediction model: a subcomponent of the enclosing model (dynamics = plant) or a component constructor (dynamics = MyPlant); leave empty to give the model as the matrices A, B, ..."
structural parameter dynamics::MPCDynamics
"""
Names of the manipulated inputs of dynamics, in the order of u (e.g. ["power"])
"""
structural parameter inputs::String[:] = []
"Names of the measured disturbance inputs of dynamics, in the order of d; the columns of Bd"
structural parameter known_disturbance_inputs::String[:] = []
"Names of the unmeasured inputs of dynamics, zero in the prediction; the states of their disturbance model are kept in the state"
structural parameter disturbance_inputs::String[:] = []
"Names of the controlled and measured outputs of dynamics, in the order of y, r and Q1"
structural parameter outputs::String[:] = []
"Names of the signals of dynamics that form the state, in the order of x0, R1 and xhat; empty for the state the compiler selects, in an unspecified order"
structural parameter state_variables::String[:] = []
"Operating point at which dynamics is linearized"
structural parameter linearization_point::LinearizationPoint = LinearizationPoint.InitialState()
"Inputs at the operating point, in the order of inputs; empty for zero. With OutputTarget, the initial guess of the inputs"
structural parameter u_op::Real[:] = fill(0.0, 0)
"Measured disturbances at the operating point, in the order of known_disturbance_inputs; empty for zero"
structural parameter d_op::Real[:] = fill(0.0, 0)
"Output targets that define the operating point with OutputTarget, in the order of outputs"
structural parameter y_op::Real[:] = fill(0.0, 0)
"The linearization of dynamics (see linearize_prediction_model); nothing without dynamics"
structural parameter linearization::LinearizedModelRef = linearize_prediction_model(dynamics = dynamics, inputs = inputs, known_disturbance_inputs = known_disturbance_inputs, disturbance_inputs = disturbance_inputs, outputs = outputs, state_variables = state_variables, linearization_point = linearization_point, u_op = u_op, d_op = d_op, y_op = y_op, disturbance_columns = length(disturbance_input_min) + length(disturbance_input_max) > 0)
"State matrix (nx × nx); discrete-time unless continuous = true. Empty when the model is given as dynamics"
structural parameter A::Real[:, :] = fill(0.0, 0, 0)
"Input matrix (nx × nu)"
structural parameter B::Real[:, :] = fill(0.0, 0, 0)
"Measured-disturbance input matrix (nx × nd); empty for no measured disturbances"
structural parameter Bd::Real[:, :] = fill(0.0, 0, 0)
"Output matrix of the controlled outputs (ny × nx); empty for the identity"
structural parameter C::Real[:, :] = fill(0.0, 0, 0)
"Disturbance feedthrough to the controlled outputs (ny × nd); empty for zero"
structural parameter Dd::Real[:, :] = fill(0.0, 0, 0)
"Affine offset of the state update (nx); empty for zero"
structural parameter f_offset::Real[:] = fill(0.0, 0)
"Affine offset of the outputs (ny); empty for zero"
structural parameter h_offset::Real[:] = fill(0.0, 0)
"Interpret A, B, Bd and f_offset as continuous-time and ZOH-discretize them with Ts"
structural parameter continuous::Boolean = false
"Sample time used for the ZOH discretization when continuous = true; must equal the clock period"
structural parameter Ts::Real = 1.0
"Prediction horizon"
structural parameter Np::Integer = 10
"Control horizon"
structural parameter Nc::Integer = Np
"Output weight on C*x - r (ny × ny, or ny diagonal entries); empty for the identity"
structural parameter Q1::Real[:, :] = fill(0.0, 0, 0)
"Control weight (nu × nu, or nu diagonal entries); empty for zero. A penalty on the control level leaves a steady-state error when tracking a plant with a finite static gain, so penalize the control increments with Q3 instead, or give a Q2 small enough that the error it leaves is acceptable"
structural parameter Q2::Real[:, :] = fill(0.0, 0, 0)
"Control-increment weight on u_k - u_{k-1} (nu × nu, or nu diagonal entries); empty for zero"
structural parameter Q3::Real[:, :] = fill(0.0, 0, 0)
"State-control cross weight (nx × nu); empty for zero"
structural parameter Q12::Real[:, :] = fill(0.0, 0, 0)
"Terminal output weight (ny × ny, or ny diagonal entries); empty for Q1"
structural parameter Qf::Real[:, :] = fill(0.0, 0, 0)
"Terminal state weight (nx × nx, or nx diagonal entries); empty for zero"
structural parameter Qfx::Real[:, :] = fill(0.0, 0, 0)
"Gain of the generalized parameter in the linear state cost (Ex p + ex)' x (nx × np); empty for zero"
structural parameter Ex::Real[:, :] = fill(0.0, 0, 0)
"Constant linear state cost (nx); empty for zero"
structural parameter ex::Real[:] = fill(0.0, 0)
"Gain of the generalized parameter in the linear control cost (Eu p + eu)' u (nu × np); empty for zero"
structural parameter Eu::Real[:, :] = fill(0.0, 0, 0)
"Constant linear control cost (nu); empty for zero"
structural parameter eu::Real[:] = fill(0.0, 0)
"Set Qfx to the infinite-horizon LQR cost (requires reference_tracking = false)"
structural parameter terminal_lqr_cost::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 output bounds (ny); empty for none"
structural parameter ymin::Real[:] = fill(0.0, 0)
"Upper output bounds (ny); empty for none"
structural parameter ymax::Real[:] = fill(0.0, 0)
"Prediction steps on which the output bounds are enforced; empty for 2:Np"
structural parameter output_bounds_ks::Integer[:] = []
"Soften the output bounds"
structural parameter output_bounds_soft::Boolean = true
"Enforce the output bounds as binary (one of the bounds holds with equality)"
structural parameter output_bounds_binary::Boolean = false
"Priority of the output bounds"
structural parameter output_bounds_prio::Integer = 0
"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)
"Reference coefficient of the general constraints (m × ny); empty for zero"
structural parameter Ar::Real[:, :] = fill(0.0, 0, 0)
"Measured-disturbance coefficient of the general constraints (m × nd); empty for zero"
structural parameter Ad::Real[:, :] = fill(0.0, 0, 0)
"Previous-control coefficient of the general constraints (m × nu); empty for zero"
structural parameter Aup::Real[:, :] = fill(0.0, 0, 0)
"Generalized-parameter coefficient of the general constraints (m × np); empty for zero"
structural parameter Ap::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)
"Prediction steps on which the general constraints are enforced; empty for 2:Np"
structural parameter constraint_ks::Integer[:] = []
"Soften the general constraints"
structural parameter constraint_soft::Boolean = false
"Enforce the general constraints as binary (one of the bounds holds with equality)"
structural parameter constraint_binary::Boolean = false
"Priority of the general constraints"
structural parameter constraint_prio::Integer = 0
"Indices of the controls restricted to the values {umin, umax}; empty for none"
structural parameter binary_controls::Integer[:] = []
"Number of initial horizon steps over which the binary controls are restricted to {umin, umax}, the rest are relaxed to continuous; -1 for the whole control horizon"
structural parameter Nc_binary::Integer = -1
"Move-blocking pattern, the number of steps each control move is held (applied to all controls); empty for none. It sets the control horizon itself, so a Nc given alongside it is ignored"
structural parameter move_block::Integer[:] = []
"Reparametrize the control as u = -K x + v with a prestabilizing feedback"
structural parameter prestabilizing_feedback::Boolean = false
"Prestabilizing feedback gain (nu × nx); empty for the LQR gain of the objective"
structural parameter K::Real[:, :] = fill(0.0, 0, 0)
"Input matrix of the unmeasured disturbance inputs w of the robust constraint tightening (nx × nw; continuous-time when continuous = true). Only with a model given as matrices; with dynamics, the columns of disturbance_inputs are linearized"
structural parameter Bw::Real[:, :] = fill(0.0, 0, 0)
"Lower bounds of the disturbance inputs w for the robust constraint tightening (one entry per disturbance input: per name in disturbance_inputs, or per column of Bw); empty for -disturbance_input_max, both empty for none"
structural parameter disturbance_input_min::Real[:] = fill(0.0, 0)
"Upper bounds of the disturbance inputs w for the robust constraint tightening (as disturbance_input_min); empty for -disturbance_input_min"
structural parameter disturbance_input_max::Real[:] = fill(0.0, 0)
"Bound δ on the error of the state estimate, -δ <= x - xhat <= δ (nx), for the robust constraint tightening; empty for none"
structural parameter x0_uncertainty::Real[:] = fill(0.0, 0)
"Bound on the error of the state estimate as a multiple κ of the standard deviations of the stationary estimation error of the observer (from R1 and R2), δ = κ σ, for the robust constraint tightening; 0 for none"
structural parameter x0_uncertainty_sigma::Real = 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) instead of a setpoint"
structural parameter reference_preview::Boolean = false
"Take a measured-disturbance trajectory over the horizon (nd*Np entries) instead of the current value"
structural parameter disturbance_preview::Boolean = false
"Take a generalized-parameter trajectory over the horizon (np*Np entries) instead of the current value"
structural parameter parameter_preview::Boolean = false
"Penalty weight on soft-constraint violations"
structural parameter soft_weight::Real = 1e6
"DAQP primal feasibility tolerance; -1 keeps the solver default. Applied to the Julia solver and embedded in the generated C"
structural parameter primal_tol::Real = -1.0
"DAQP dual feasibility (optimality) tolerance; -1 keeps the solver default"
structural parameter dual_tol::Real = -1.0
"DAQP iteration limit; -1 keeps the solver default"
structural parameter iter_limit::Integer = -1
"QP solver backend; Auto uses the C code generated by LinearMPC.jl when a C compiler is available (required for C code export), otherwise the Julia solver"
structural parameter backend::Backend = Backend.Auto()
"Float type of the C code generated for the QP (C backend only): Double, or Single for 32-bit targets; the observer equations are Real"
structural parameter precision::MPCPrecision = MPCPrecision.Double()
"Also record the predicted state, control and output trajectories and the QP statistics of every tick (variables optimizer.x_pred, optimizer.u_pred, optimizer.y_pred, optimizer.stats), for debugging and mpc_gui; Julia backend only. The readbacks are four equations whose number does not grow with the prediction horizon, though the trajectories themselves are stored in the solution; output_statistics records the statistics alone"
structural parameter output_trajectories::Boolean = false
"Record the QP statistics of every tick (variable optimizer.stats: objective value, solve time in seconds, active-set iterations, branch-and-bound nodes) without the predicted trajectories; Julia backend only. One equation. Implied by output_trajectories"
structural parameter output_statistics::Boolean = false
"Offset-free disturbance model of the observer; None for a plain Kalman filter"
structural parameter offset_free::OffsetFreeMethod = OffsetFreeMethod.None()
"Observer: SteadyState (LinearMPCObserver, exported to C) or TimeVarying (SqKalmanObserver, Julia only)"
structural parameter observer_type::ObserverType = ObserverType.SteadyState()
"Whether the y_valid input exists (observer_type = TimeVarying only); without it, every finite measurement is used"
structural parameter use_valid::Boolean = false
"Process-noise covariance of the Kalman filter (nx × nx, or nx diagonal entries; with InputDisturbance/OutputDisturbance also (nx + nd) × (nx + nd), the disturbance part setting the disturbance-estimate speed); empty for the identity"
structural parameter R1::Real[:, :] = fill(0.0, 0, 0)
"Measurement-noise covariance of the Kalman filter (ny_meas × ny_meas, or ny_meas diagonal entries); empty for the identity"
structural parameter R2::Real[:, :] = fill(0.0, 0, 0)
"Measurement matrix of the observer when the measured outputs differ from C (ny_meas × nx, only with offset_free = None); empty for C"
structural parameter C_meas::Real[:, :] = fill(0.0, 0, 0)
"Disturbance feedthrough to the measured outputs (ny_meas × nd, only with offset_free = None); empty for Dd"
structural parameter Dd_meas::Real[:, :] = fill(0.0, 0, 0)
"Initial state estimate (nx); empty for zero, or for the operating point of dynamics"
structural parameter x0::Real[:] = fill(0.0, 0)
"Initial offset-free disturbance estimate (ny entries; nu with InputDisturbance); empty for zero"
structural parameter d0::Real[:] = fill(0.0, 0)
"Control assumed applied before the first tick, LinearMPC's uprev (nu); empty for zero, or for the operating-point input of dynamics. It enters the QP only when the previous control does (Q3, or a general constraint with Aup): setting it to the operating-point control keeps a rate-limited loop from walking the command up from zero over the first ticks"
structural parameter u0::Real[:] = fill(0.0, 0)
"Disturbance-control cross term d' Sd u (nd × nu) of the objective. The default is (pinv(B) Bd)' Q2 with an offset-free observer (with dynamics, from the ZOH discretization of the linearization), which makes Q2 penalize u relative to the input cancelling the measured disturbances instead of relative to zero (offset-free tracking), and zero for offset_free = None or for the default Q2 = 0; rows for the observer-estimated disturbances are appended with the same formula. Empty for zero"
structural parameter Sd::Real[:, :] = case(offset_free, OffsetFreeMethod.None) ? fill(0.0, 0, 0) : _lmpc_offset_free_Sd(linearization, B, Bd, Q2, Ts)
"Whether the steady-state observer is used (derived from observer_type)"
final structural parameter steady_observer::Boolean = _lmpc_obs_steady(observer_type, use_valid)
"Width of the y_valid input: ny_meas with use_valid, else 0 (derived)"
final structural parameter n_valid::Integer = use_valid ? ny_meas : 0
"Number of measured disturbances of the current step, nd_meas (without the preview) (derived from mpc)"
final structural parameter nd_meas::Integer = _lmpc_nd_meas(mpc)
"Width of the disturbance vector of the observer input, max(nd_meas, 1): the estimator uses only the current step of a disturbance preview (derived)"
final structural parameter nd_obs::Integer = max(nd_meas, 1)
"Width of the observer input [u; d_int; 1], nu + nd_obs + 1 (derived)"
final structural parameter nu_obs::Integer = nu + nd_obs + 1
"Zero placeholder used as the disturbance vector when d is absent"
final parameter d_dummy::Real[nd_obs] = fill(0.0, nd_obs)
"Measured-disturbance feedthrough of the estimator, padded to the width of the disturbance vector"
final parameter Dd_o::Real[ny_meas, nd_obs] = _lmpc_obs_Dd(mpc, nd_obs)
"Affine offset of the measurement equation of the estimator"
final parameter h_o::Real[ny_meas] = _lmpc_obs_h(mpc)
"Disturbance vector of the observer input: the measured disturbances of the current step"
variable d_int::Real[nd_obs]
"Input of the observer, [u; d_int; 1]"
variable u_obs::Real[nu_obs]
"Measurement of the observer, y - Dd d_int - h"
variable y_obs::Real[ny_meas]
"Estimate of the augmented state [x; d_offset] of the estimator"
variable x_obs::Real[nxa]
relations
if has_r
connect(r, optimizer.r) {}
end
if has_d
connect(d, optimizer.d) {}
# The first nd_meas entries of d, the current step of a preview (column-major)
d_int = _lmpc_obs_current(d, nd_obs)
else
d_int = d_dummy
end
if has_p
connect(p, optimizer.p) {}
end
connect(optimizer.u, u) {}
connect(optimizer.exitflag, exitflag) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 800, "y": 410}, {"x": 890, "y": 410}, {"x": 890, "y": 125}],
"E": 2
}
],
"path": {}
}
}
# The observers are standalone components for x⁺ = A x + B u, y = C x; the measured disturbances
# and the affine offsets of the estimator enter through the input [u; d_int; 1] (B = [G Gd f])
# and the measurement y - Dd d_int - h
# One vector equation each, so that every entry is clocked with u and y (see _lmpc_obs_input)
y_obs = y - Dd_o * d_int - h_o
u_obs = _lmpc_obs_input(u, d_int)
if steady_observer
for i in 1:ny_meas
observer.y[i] = y_obs[i]
end
for i in 1:nu_obs
observer.u[i] = u_obs[i]
end
for i in 1:nxa
x_obs[i] = observer.xhat[i]
end
else
for i in 1:ny_meas
kalman_observer.y[i] = y_obs[i]
end
for i in 1:nu_obs
kalman_observer.u[i] = u_obs[i]
end
for i in 1:nxa
x_obs[i] = kalman_observer.xhat[i]
end
if use_valid
connect(y_valid, kalman_observer.y_valid) {}
end
end
# The augmented state is [x; d_offset]
for i in 1:nx
xhat[i] = x_obs[i]
optimizer.xhat[i] = x_obs[i]
end
if has_dhat
for i in 1:ndo
dhat[i] = x_obs[nx + i]
optimizer.dhat[i] = x_obs[nx + i]
end
end
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/LinearMPCController.svg"},
"doc": {"behavior": false},
"panel": {
"control": "tabs",
"children": {
"Dynamics": {
"control": "fieldset",
"contents": ["A", "B", "C", "continuous", "Ts"],
"children": {"Advanced": {"contents": ["Bd", "Dd", "f_offset", "h_offset"]}}
},
"Prediction model": {
"control": "fieldset",
"contents": [
"dynamics",
"inputs",
"known_disturbance_inputs",
"outputs",
"linearization_point",
"u_op",
"d_op",
"y_op"
],
"children": {"Advanced": {"contents": ["disturbance_inputs", "state_variables"]}}
},
"Cost function": {
"control": "fieldset",
"contents": ["Np", "Nc", "Q1", "Q2", "Q3", "Sd", "terminal_lqr_cost"],
"children": {"Advanced": {"contents": ["Q12", "Qf", "Qfx", "Ex", "ex", "Eu", "eu"]}}
},
"Constraints": {
"control": "fieldset",
"contents": ["umin", "umax", "dumin", "dumax", "ymin", "ymax"],
"children": {
"Advanced": {
"contents": [
"output_bounds_ks",
"output_bounds_soft",
"output_bounds_binary",
"output_bounds_prio",
"soft_weight",
"Ax",
"Au",
"Ar",
"Ad",
"Aup",
"Ap",
"lb",
"ub",
"constraint_ks",
"constraint_soft",
"constraint_binary",
"constraint_prio",
"binary_controls",
"Nc_binary"
]
}
}
},
"Robustness": {
"control": "fieldset",
"contents": [
"disturbance_input_min",
"disturbance_input_max",
"x0_uncertainty",
"x0_uncertainty_sigma"
],
"children": {"Advanced": {"contents": ["Bw"]}}
},
"Observer": {
"control": "fieldset",
"contents": ["offset_free", "observer_type", "use_valid", "R1", "R2"],
"children": {"Advanced": {"contents": ["C_meas", "Dd_meas", "x0", "d0", "u0"]}}
},
"Solver options": {
"control": "fieldset",
"contents": [
"reference_tracking",
"reference_preview",
"disturbance_preview",
"parameter_preview",
"backend",
"precision",
"output_trajectories",
"output_statistics"
],
"children": {
"Advanced": {"contents": ["move_block", "prestabilizing_feedback", "K", "mpc"]}
}
},
"Derived": {
"control": "fieldset",
"contents": [
"nx",
"nu",
"ny_meas",
"nr_in",
"nd_in",
"np_in",
"ndo",
"has_r",
"has_d",
"has_p",
"has_dhat"
]
}
}
}
}
}
endTest Cases
No test cases defined.
Related
Examples
Experiments
Analyses