Experimental.LinearMPCScheduledController ​
Gain-scheduled linear model-predictive controller built on LinearMPC.jl: a bank of controllers, each built for one value (knot) of a scalar scheduling variable rho, scheduled online by LinearMPCScheduler, with LinearMPCScheduledObserver blending the members' state observers and LinearMPCScheduledOptimizer solving and blending the members' QPs.
Bank ​
The bank is built in Julia with build_linear_mpc_bank and passed as mpc, e.g.
bank = build_linear_mpc_bank([0.5, 1.0, 2.0], rho -> (; A = A(rho), B = B(rho)); C, Q1, Q2, umin, umax, Np)where the function returns the build_linear_mpc keyword arguments that depend on the scheduling variable and the remaining keywords are shared. The members must agree in their dimensions, horizons, tracking/preview settings and initial state; the component's connectors are sized from them like those of LinearMPCController. The knots are read from the bank.
Starting from an operating point ​
The initial observer state and the control assumed applied before the first tick are shared properties of the members, set as the x0, d0 and u0 keywords of build_linear_mpc_bank and read from the bank by this component. A loop started from a trim condition therefore needs no wiring beyond building the bank with them. For a bank built elsewhere, the parameters xaug0 and u0 override the members' values; leaving u0 at zero with a control-rate limit costs the first ticks walking the command up from zero.
Scheduling ​
LinearMPC.jl condenses the QP and generates its C code for fixed model and cost matrices, so a model that depends on an operating condition is handled by switching or interpolating between controllers built up front. With scheduling = Linear the controls of the two members whose knots enclose rho are interpolated linearly (two QP solves per tick, one at a knot); with Nearest the closest member acts alone (one solve, a control that jumps at the cell edges). The blend is exact at the knots. Every member is handed the applied (blended) previous control, so control-increment penalties act on what the plant received. rho is sampled on the controller's clock like the measurements and clamped to the grid.
Signals, solver and C export ​
As for LinearMPCController: measurements and references must be sampled before this component, the control held after it, and preview inputs are flat column-major vectors. The observer is plain matrix arithmetic on the stacked member matrices, the member QPs are solved by their generated C code, and the scheduling logic is Julia code the synchronous code generator cross-compiles; CCodeExport exports the whole controller with one library per member. w outputs the member weights, exitflag the composite DAQP exit flag (see LinearMPCScheduledOptimizer).
This component extends from LinearMPCBankPart
Usage ​
MPCComponents.Experimental.LinearMPCScheduledController()
Parameters: ​
| Name | Description | Units | Default value |
|---|---|---|---|
mpc | Set-up LinearMPC.jl MPC object wrapped in an MPCRef (see build_linear_mpc) | – | |
nx | State dimension of the model (derived from mpc) | – | MPCComponen...mpc_nx(mpc) |
nu | Number of controls (derived from mpc) | – | MPCComponen...mpc_nu(mpc) |
ny_meas | Number of measured outputs, the rows of the observer's measurement matrix; 0 without an observer (derived from mpc) | – | MPCComponen...y_meas(mpc) |
ndo | Number of estimated offset-free disturbances, 0 for a plain Kalman filter (derived from mpc) | – | MPCComponen...pc_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) | – | MPCComponen..._nr_in(mpc) |
nd_in | Width of the measured-disturbance input: 0, nd_meas or nd_meas*Np (derived from mpc) | – | MPCComponen..._nd_in(mpc) |
np_in | Width of the generalized-parameter input: 0, np or np*Np (derived from mpc) | – | MPCComponen..._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 |
N | Number of controllers in the bank (derived from mpc) | – | _lmpc_bank_N(mpc) |
scheduling | Scheduling law: Linear interpolation between the neighbouring members, or the Nearest member | – | MPCScheduling.Linear() |
backend | QP solver backend; Auto uses the compiled C code when a C compiler is available | – | MPCComponen...kend.Auto() |
precision | Float type of the generated QP code (C backend only) | – | MPCComponen...on.Double() |
u0 | Control assumed applied before the first tick, by default the operating-point control of the members (a build_linear_mpc_bank keyword); set it here to start a bank built elsewhere from a nonzero command | – | _lmpc_u0(mpc) |
xaug0 | Initial augmented state estimate [x0; d0], by default the one stored in the members (the x0 and d0 keywords of build_linear_mpc_bank) | – | _lmpc_xaug0(mpc) |
Connectors ​
rho- This connector represents a real signal as an input to a component (RealInput)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)w- 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)
Behavior ​
Behavior of this component cannot be rendered because it includes path variables.
Source ​
"""
Gain-scheduled linear model-predictive controller built on LinearMPC.jl: a bank of controllers,
each built for one value (knot) of a scalar scheduling variable `rho`, scheduled online by
`LinearMPCScheduler`, with `LinearMPCScheduledObserver` blending the members' state observers and
`LinearMPCScheduledOptimizer` solving and blending the members' QPs.
# Bank
The bank is built in Julia with `build_linear_mpc_bank` and passed as `mpc`, e.g.
```julia
bank = build_linear_mpc_bank([0.5, 1.0, 2.0], rho -> (; A = A(rho), B = B(rho)); C, Q1, Q2, umin, umax, Np)
```
where the function returns the `build_linear_mpc` keyword arguments that depend on the scheduling
variable and the remaining keywords are shared. The members must agree in their dimensions,
horizons, tracking/preview settings and initial state; the component's connectors are sized from
them like those of `LinearMPCController`. The knots are read from the bank.
# Starting from an operating point
The initial observer state and the control assumed applied before the first tick are shared
properties of the members, set as the `x0`, `d0` and `u0` keywords of `build_linear_mpc_bank`
and read from the bank by this component. A loop started from a trim condition therefore needs no
wiring beyond building the bank with them. For a bank built elsewhere, the parameters `xaug0` and
`u0` override the members' values; leaving `u0` at zero with a control-rate limit costs the
first ticks walking the command up from zero.
# Scheduling
LinearMPC.jl condenses the QP and generates its C code for fixed model and cost matrices, so a
model that depends on an operating condition is handled by switching or interpolating between
controllers built up front. With `scheduling = Linear` the controls of the two members whose
knots enclose `rho` are interpolated linearly (two QP solves per tick, one at a knot); with
`Nearest` the closest member acts alone (one solve, a control that jumps at the cell edges).
The blend is exact at the knots. Every member is handed the applied (blended) previous control,
so control-increment penalties act on what the plant received. `rho` is sampled on the
controller's clock like the measurements and clamped to the grid.
# Signals, solver and C export
As for `LinearMPCController`: measurements and references must be sampled before this component,
the control held after it, and preview inputs are flat column-major vectors. The observer is
plain matrix arithmetic on the stacked member matrices, the member QPs are solved by their
generated C code, and the scheduling logic is Julia code the synchronous code generator
cross-compiles; `CCodeExport` exports the whole controller with one library per member. `w`
outputs the member weights, `exitflag` the composite DAQP exit flag (see
`LinearMPCScheduledOptimizer`).
"""
component LinearMPCScheduledController
extends LinearMPCBankPart()
"Scheduling variable (sampled)"
rho = RealInput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 450, "x2": 0, "y2": 550}}}
}
"Sampled measurements"
y = [RealInput@[clk]() for i in 1:ny_meas] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 850, "x2": 0, "y2": 950}}}
}
"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": 50, "x2": 0, "y2": 150}}}}
"Measured disturbances; nd entries, or nd*Np column-major entries with disturbance preview (only when the members have Bd or Dd)"
d = [RealInput@[clk]() for i in 1:nd_in] if has_d {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 650, "x2": 0, "y2": 750}}}
}
"Generalized parameters; np entries, or np*Np column-major entries with parameter preview (only when the members have Ex, Eu or Ap columns)"
p = [RealInput@[clk]() for i in 1:np_in] if has_p {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 250, "x2": 0, "y2": 350}}}
}
"Control signal"
u = [RealOutput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 250, "x2": 1100, "y2": 350}}}
}
"State estimate"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 450, "x2": 1100, "y2": 550}}}
}
"Estimated offset-free disturbance (only when the members have an offset-free observer)"
dhat = [RealOutput@[clk]() for i in 1:ndo] if has_dhat {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 650, "x2": 1100, "y2": 750}}}
}
"Member weights of the current tick (sum to 1)"
w = [RealOutput@[clk]() for j in 1:N] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 850, "x2": 1100, "y2": 950}}}
}
"Composite solver status of the last step (see LinearMPCScheduledOptimizer)"
exitflag = RealOutput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 50, "x2": 1100, "y2": 150}}}
}
"Scheduler: scheduling variable to member weights"
scheduler = LinearMPCScheduler@[clk = clk](rho_grid = _lmpc_bank_grid(mpc), scheduling = scheduling) {
"Dyad": {"placement": {"diagram": {"x1": 400, "y1": 50, "x2": 600, "y2": 250}}}
}
"Gain-scheduled state observer"
observer = LinearMPCScheduledObserver@[clk = clk](mpc = mpc, xaug0 = xaug0) {
"Dyad": {"placement": {"diagram": {"x1": 100, "y1": 600, "x2": 400, "y2": 900}}}
}
"Gain-scheduled constrained optimizer"
optimizer = LinearMPCScheduledOptimizer@[clk = clk](mpc = mpc, backend = backend, precision = precision, u0 = u0) {
"Dyad": {"placement": {"diagram": {"x1": 600, "y1": 350, "x2": 900, "y2": 650}}}
}
"Scheduling law: Linear interpolation between the neighbouring members, or the Nearest member"
structural parameter scheduling::MPCScheduling = MPCScheduling.Linear()
"QP solver backend; Auto uses the compiled C code when a C compiler is available"
structural parameter backend::MPCComponents.Backend = MPCComponents.Backend.Auto()
"Float type of the generated QP code (C backend only)"
structural parameter precision::MPCComponents.MPCPrecision = MPCComponents.MPCPrecision.Double()
"Control assumed applied before the first tick, by default the operating-point control of the members (a build_linear_mpc_bank keyword); set it here to start a bank built elsewhere from a nonzero command"
structural parameter u0::Real[:] = _lmpc_u0(mpc)
"Initial augmented state estimate [x0; d0], by default the one stored in the members (the x0 and d0 keywords of build_linear_mpc_bank)"
structural parameter xaug0::Real[:] = _lmpc_xaug0(mpc)
relations
connect(rho, scheduler.rho) {}
connect(scheduler.w, observer.w, optimizer.w, w) {}
connect(y, observer.y) {}
connect(optimizer.xhat, observer.xhat, xhat) {}
if has_r
connect(r, optimizer.r) {}
end
if has_d
connect(d, observer.d) {}
connect(d, optimizer.d) {}
end
if has_p
connect(p, optimizer.p) {}
end
if has_dhat
connect(observer.dhat, optimizer.dhat) {}
connect(observer.dhat, dhat) {}
end
connect(optimizer.u, u, observer.u) {}
connect(optimizer.exitflag, exitflag) {}
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/LinearMPCScheduledController.svg"},
"doc": {"behavior": false}
}
}
endFlattened Source
"""
Gain-scheduled linear model-predictive controller built on LinearMPC.jl: a bank of controllers,
each built for one value (knot) of a scalar scheduling variable `rho`, scheduled online by
`LinearMPCScheduler`, with `LinearMPCScheduledObserver` blending the members' state observers and
`LinearMPCScheduledOptimizer` solving and blending the members' QPs.
# Bank
The bank is built in Julia with `build_linear_mpc_bank` and passed as `mpc`, e.g.
```julia
bank = build_linear_mpc_bank([0.5, 1.0, 2.0], rho -> (; A = A(rho), B = B(rho)); C, Q1, Q2, umin, umax, Np)
```
where the function returns the `build_linear_mpc` keyword arguments that depend on the scheduling
variable and the remaining keywords are shared. The members must agree in their dimensions,
horizons, tracking/preview settings and initial state; the component's connectors are sized from
them like those of `LinearMPCController`. The knots are read from the bank.
# Starting from an operating point
The initial observer state and the control assumed applied before the first tick are shared
properties of the members, set as the `x0`, `d0` and `u0` keywords of `build_linear_mpc_bank`
and read from the bank by this component. A loop started from a trim condition therefore needs no
wiring beyond building the bank with them. For a bank built elsewhere, the parameters `xaug0` and
`u0` override the members' values; leaving `u0` at zero with a control-rate limit costs the
first ticks walking the command up from zero.
# Scheduling
LinearMPC.jl condenses the QP and generates its C code for fixed model and cost matrices, so a
model that depends on an operating condition is handled by switching or interpolating between
controllers built up front. With `scheduling = Linear` the controls of the two members whose
knots enclose `rho` are interpolated linearly (two QP solves per tick, one at a knot); with
`Nearest` the closest member acts alone (one solve, a control that jumps at the cell edges).
The blend is exact at the knots. Every member is handed the applied (blended) previous control,
so control-increment penalties act on what the plant received. `rho` is sampled on the
controller's clock like the measurements and clamped to the grid.
# Signals, solver and C export
As for `LinearMPCController`: measurements and references must be sampled before this component,
the control held after it, and preview inputs are flat column-major vectors. The observer is
plain matrix arithmetic on the stacked member matrices, the member QPs are solved by their
generated C code, and the scheduling logic is Julia code the synchronous code generator
cross-compiles; `CCodeExport` exports the whole controller with one library per member. `w`
outputs the member weights, `exitflag` the composite DAQP exit flag (see
`LinearMPCScheduledOptimizer`).
"""
component LinearMPCScheduledController
"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
"Number of controllers in the bank (derived from mpc)"
final structural parameter N::Integer = _lmpc_bank_N(mpc)
"Scheduling variable (sampled)"
rho = RealInput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 450, "x2": 0, "y2": 550}}}
}
"Sampled measurements"
y = [RealInput@[clk]() for i in 1:ny_meas] {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 850, "x2": 0, "y2": 950}}}
}
"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": 50, "x2": 0, "y2": 150}}}}
"Measured disturbances; nd entries, or nd*Np column-major entries with disturbance preview (only when the members have Bd or Dd)"
d = [RealInput@[clk]() for i in 1:nd_in] if has_d {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 650, "x2": 0, "y2": 750}}}
}
"Generalized parameters; np entries, or np*Np column-major entries with parameter preview (only when the members have Ex, Eu or Ap columns)"
p = [RealInput@[clk]() for i in 1:np_in] if has_p {
"Dyad": {"placement": {"diagram": {"x1": -100, "y1": 250, "x2": 0, "y2": 350}}}
}
"Control signal"
u = [RealOutput@[clk]() for i in 1:nu] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 250, "x2": 1100, "y2": 350}}}
}
"State estimate"
xhat = [RealOutput@[clk]() for i in 1:nx] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 450, "x2": 1100, "y2": 550}}}
}
"Estimated offset-free disturbance (only when the members have an offset-free observer)"
dhat = [RealOutput@[clk]() for i in 1:ndo] if has_dhat {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 650, "x2": 1100, "y2": 750}}}
}
"Member weights of the current tick (sum to 1)"
w = [RealOutput@[clk]() for j in 1:N] {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 850, "x2": 1100, "y2": 950}}}
}
"Composite solver status of the last step (see LinearMPCScheduledOptimizer)"
exitflag = RealOutput@[clk]() {
"Dyad": {"placement": {"diagram": {"x1": 1000, "y1": 50, "x2": 1100, "y2": 150}}}
}
"Scheduler: scheduling variable to member weights"
scheduler = LinearMPCScheduler@[clk = clk](rho_grid = _lmpc_bank_grid(mpc), scheduling = scheduling) {
"Dyad": {"placement": {"diagram": {"x1": 400, "y1": 50, "x2": 600, "y2": 250}}}
}
"Gain-scheduled state observer"
observer = LinearMPCScheduledObserver@[clk = clk](mpc = mpc, xaug0 = xaug0) {
"Dyad": {"placement": {"diagram": {"x1": 100, "y1": 600, "x2": 400, "y2": 900}}}
}
"Gain-scheduled constrained optimizer"
optimizer = LinearMPCScheduledOptimizer@[clk = clk](mpc = mpc, backend = backend, precision = precision, u0 = u0) {
"Dyad": {"placement": {"diagram": {"x1": 600, "y1": 350, "x2": 900, "y2": 650}}}
}
"Scheduling law: Linear interpolation between the neighbouring members, or the Nearest member"
structural parameter scheduling::MPCScheduling = MPCScheduling.Linear()
"QP solver backend; Auto uses the compiled C code when a C compiler is available"
structural parameter backend::MPCComponents.Backend = MPCComponents.Backend.Auto()
"Float type of the generated QP code (C backend only)"
structural parameter precision::MPCComponents.MPCPrecision = MPCComponents.MPCPrecision.Double()
"Control assumed applied before the first tick, by default the operating-point control of the members (a build_linear_mpc_bank keyword); set it here to start a bank built elsewhere from a nonzero command"
structural parameter u0::Real[:] = _lmpc_u0(mpc)
"Initial augmented state estimate [x0; d0], by default the one stored in the members (the x0 and d0 keywords of build_linear_mpc_bank)"
structural parameter xaug0::Real[:] = _lmpc_xaug0(mpc)
relations
connect(rho, scheduler.rho) {}
connect(scheduler.w, observer.w, optimizer.w, w) {}
connect(y, observer.y) {}
connect(optimizer.xhat, observer.xhat, xhat) {}
if has_r
connect(r, optimizer.r) {}
end
if has_d
connect(d, observer.d) {}
connect(d, optimizer.d) {}
end
if has_p
connect(p, optimizer.p) {}
end
if has_dhat
connect(observer.dhat, optimizer.dhat) {}
connect(observer.dhat, dhat) {}
end
connect(optimizer.u, u, observer.u) {}
connect(optimizer.exitflag, exitflag) {}
metadata {
"Dyad": {
"icons": {"default": "dyad://MPCComponents/LinearMPCScheduledController.svg"},
"doc": {"behavior": false}
}
}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses