Skip to content
LIBRARY
Experimental.LinearMPCScheduledController.md

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.

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).

This component extends from LinearMPCBankPart

Usage ​

MPCComponents.Experimental.LinearMPCScheduledController()

Parameters: ​

NameDescriptionUnitsDefault value
mpcSet-up LinearMPC.jl MPC object wrapped in an MPCRef (see build_linear_mpc)–
nxState dimension of the model (derived from mpc)–MPCComponen...mpc_nx(mpc)
nuNumber of controls (derived from mpc)–MPCComponen...mpc_nu(mpc)
ny_measNumber of measured outputs, the rows of the observer's measurement matrix; 0 without an observer (derived from mpc)–MPCComponen...y_meas(mpc)
ndoNumber of estimated offset-free disturbances, 0 for a plain Kalman filter (derived from mpc)–MPCComponen...pc_ndo(mpc)
nxaLength of the augmented observer state, nx + ndo (derived)–nx + ndo
nr_inWidth of the reference input: 0, ny or ny*Np (derived from mpc)–MPCComponen..._nr_in(mpc)
nd_inWidth of the measured-disturbance input: 0, nd_meas or nd_meas*Np (derived from mpc)–MPCComponen..._nd_in(mpc)
np_inWidth of the generalized-parameter input: 0, np or np*Np (derived from mpc)–MPCComponen..._np_in(mpc)
has_rWhether the r connector exists (derived)–nr_in > 0
has_dWhether the d connector exists (derived)–nd_in > 0
has_dhatWhether the dhat connector exists (derived)–ndo > 0
has_pWhether the p connector exists (derived)–np_in > 0
NNumber of controllers in the bank (derived from mpc)–_lmpc_bank_N(mpc)
schedulingScheduling law: Linear interpolation between the neighbouring members, or the Nearest member–MPCScheduling.Linear()
backendQP solver backend; Auto uses the compiled C code when a C compiler is available–MPCComponen...kend.Auto()
precisionFloat type of the generated QP code (C backend only)–MPCComponen...on.Double()
u0Control 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)
xaug0Initial 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 ​

dyad
"""
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}
  }
}
end
Flattened Source
dyad
"""
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}
  }
}
end


Test Cases ​

No test cases defined.

  • Examples

  • Experiments

  • Analyses