Skip to content
LIBRARY
Examples.LinearMPCOffsetFreeExample.md

Examples.LinearMPCOffsetFreeExample ​

Offset-free tracking, one closed loop per method, around identical double-integrator plants (DoubleIntegratorExamplePlant) driven by the same reference and the same unmeasured load disturbance.

The four loops are OffsetFreeMethod.None (the baseline, no disturbance model), StateDisturbance, Velocity and InputDisturbance. OffsetFreeMethod.OutputDisturbance has no loop here: an output-disturbance model is not detectable on a plant that already has integral action, so building it fails the rank condition rank([F - I Bd; C Cd]) = nx + nd.

The disturbance has two phases, so one run separates two distinct effects.

A saturating pulse (2.0 for one second from t = 5). It is twice the control authority umax = 1, so the position runs away to about 3 while every loop sits at its limit and none of them can do anything about it. What the phase shows is that none of these loops overshoots on the way back: their integrating element is the disturbance estimate, which integrates the estimation error, and saturation does not inflate that — the estimates peak between 0.14 and 0.52 against steady-state values of 0.057 and 0.5. A method that instead integrates the tracking error has no such protection, which is why an error-integrating variant was tried and rejected (see JuliaComputing/MPCComponents.jl#40).

A sustained offset (0.5 from t = 6 onward) exposes the steady-state error. The baseline loop settles about 0.61 above the reference, since a direct penalty on u trades tracking against control effort and nothing tells it the plant is being pushed. Every offset-free loop removes that: InputDisturbance to machine precision, StateDisturbance and Velocity to about 1.5e-3 — their disturbance direction is not in the range of B, so the cross term Sd is the least-squares input equivalent rather than the exact one. Penalizing the control rate (Q3) instead of its level removes the baseline offset too, and the Sd residual with it.

Velocity and StateDisturbance produce identical trajectories, and their loops coincide on the plot.

Usage ​

MPCComponents.Examples.LinearMPCOffsetFreeExample()

Parameters: ​

NameDescriptionUnitsDefault value
NpPrediction horizon of every controller–15
Q1Output weight of every controller–[1.0]
Q2Control weight of every controller; a nonzero value is what leaves a steady-state error without an offset-free method–[0.1]
uminLower control limit of every controller–[-1.0]
umaxUpper control limit of every controller; the load pulse is twice this, so every loop saturates–[1.0]
R1Process-noise covariance of the plant state, shared by every observer–[0.1, 0.1]
R1_inputProcess-noise covariance of the augmented state of the InputDisturbance observer: the plant states of R1, then the disturbance state–[0.1, 0.1, 0.01]
R2Measurement-noise covariance of every observer–[0.01]
TsSample interval; fixed by the periodic clock inside DoubleIntegratorExamplePlant–0.1
AState matrix of the plant model, the double integrator discretized at Ts (derived)–[1.0 Ts; 0.0 1.0]
BInput matrix of the plant model (derived)–[Ts * Ts / 2; Ts;;]
COutput matrix of the plant model: the plant measures and tracks the position (derived)–[1.0 0.0]

Variables ​

NameDescriptionUnits
y1Position, no disturbance model–
u1Control, no disturbance model–
y2Position, state-disturbance model–
u2Control, state-disturbance model–
dhat2Estimated disturbance, state-disturbance model–
y3Position, velocity form–
u3Control, velocity form–
dhat3Estimated disturbance, velocity form–
y4Position, input-disturbance model–
u4Control, input-disturbance model–
dhat4Estimated disturbance, input-disturbance model–

Behavior ​

Behavior of this component cannot be rendered because it includes path variables.

Source ​

dyad
"""
Offset-free tracking, one closed loop per method, around identical double-integrator plants
(`DoubleIntegratorExamplePlant`) driven by the same reference and the same unmeasured load
disturbance.

The four loops are `OffsetFreeMethod.None` (the baseline, no disturbance model),
`StateDisturbance`, `Velocity` and `InputDisturbance`. `OffsetFreeMethod.OutputDisturbance` has no
loop here: an output-disturbance model is not detectable on a plant that already has integral
action, so building it fails the rank condition `rank([F - I  Bd; C  Cd]) = nx + nd`.

The disturbance has two phases, so one run separates two distinct effects.

**A saturating pulse (2.0 for one second from t = 5).** It is twice the control authority
`umax = 1`, so the position runs away to about 3 while every loop sits at its limit and none of them
can do anything about it. What the phase shows is that none of these loops overshoots on the way
back: their integrating element is the disturbance estimate, which integrates the *estimation*
error, and saturation does not inflate that — the estimates peak between 0.14 and 0.52 against
steady-state values of 0.057 and 0.5. A method that instead integrates the *tracking* error has no
such protection, which is why an error-integrating variant was tried and rejected (see
JuliaComputing/MPCComponents.jl#40).

**A sustained offset (0.5 from t = 6 onward) exposes the steady-state error.** The baseline loop
settles about 0.61 above the reference, since a direct penalty on `u` trades tracking against
control effort and nothing tells it the plant is being pushed. Every offset-free loop removes that:
`InputDisturbance` to machine precision, `StateDisturbance` and `Velocity` to about 1.5e-3 — their
disturbance direction is not in the range of `B`, so the cross term `Sd` is the least-squares input
equivalent rather than the exact one. Penalizing the control rate (`Q3`) instead of its level
removes the baseline offset too, and the `Sd` residual with it.

`Velocity` and `StateDisturbance` produce identical trajectories, and their loops coincide on the
plot.
"""
example component LinearMPCOffsetFreeExample
  "Prediction horizon of every controller"
  structural parameter Np::Integer = 15
  "Output weight of every controller"
  structural parameter Q1::Real[:] = [1.0]
  "Control weight of every controller; a nonzero value is what leaves a steady-state error without an offset-free method"
  structural parameter Q2::Real[:] = [0.1]
  "Lower control limit of every controller"
  structural parameter umin::Real[:] = [-1.0]
  "Upper control limit of every controller; the load pulse is twice this, so every loop saturates"
  structural parameter umax::Real[:] = [1.0]
  "Process-noise covariance of the plant state, shared by every observer"
  structural parameter R1::Real[:] = [0.1, 0.1]
  "Process-noise covariance of the augmented state of the InputDisturbance observer: the plant states of R1, then the disturbance state"
  structural parameter R1_input::Real[:] = [0.1, 0.1, 0.01]
  "Measurement-noise covariance of every observer"
  structural parameter R2::Real[:] = [0.01]
  "Sample interval; fixed by the periodic clock inside DoubleIntegratorExamplePlant"
  final structural parameter Ts::Real = 0.1
  "State matrix of the plant model, the double integrator discretized at Ts (derived)"
  final structural parameter A::Real[:, :] = [[1.0, Ts], [0.0, 1.0]]
  "Input matrix of the plant model (derived)"
  final structural parameter B::Real[:, :] = [[Ts * Ts / 2], [Ts]]
  "Output matrix of the plant model: the plant measures and tracks the position (derived)"
  final structural parameter C::Real[:, :] = [[1.0, 0.0]]
  "Position reference"
  ref = BlockComponents.Sources.Constant(k = 1.0) {
    "Dyad": {"placement": {"diagram": {"x1": 60, "x2": 160, "y1": 400, "y2": 500}}}
  }
  sr = DiscreteComponents.Sampler() {
    "Dyad": {"placement": {"diagram": {"x1": 250, "x2": 350, "y1": 400, "y2": 500}}}
  }
  "Load pulse: 2.0 for one second from t = 5, twice the control authority"
  dpulse = BlockComponents.Sources.Step(start_time = 5.0, height = 2.0) {
    "Dyad": {"placement": {"diagram": {"x1": 60, "x2": 160, "y1": 1060, "y2": 1160}}}
  }
  "End of the pulse, leaving 0.5 of sustained load"
  dstep = BlockComponents.Sources.Step(start_time = 6.0, height = -1.5) {
    "Dyad": {"placement": {"diagram": {"x1": 60, "x2": 160, "y1": 1200, "y2": 1300}}}
  }
  dsum = BlockComponents.Math.Add() {
    "Dyad": {"placement": {"diagram": {"x1": 250, "x2": 350, "y1": 1130, "y2": 1230}}}
  }
  "Plant of the baseline loop, no disturbance model"
  plant1 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 100, "y2": 200}}}
  }
  mpc1 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.None(), final R1 = R1) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 100, "y2": 200}}}
  }
  "Plant of the state-disturbance loop"
  plant2 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 340, "y2": 440}}}
  }
  mpc2 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.StateDisturbance(), final R1 = R1) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 340, "y2": 440}}}
  }
  "Plant of the velocity-form loop"
  plant3 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 580, "y2": 680}}}
  }
  mpc3 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.Velocity(), final R1 = R1) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 580, "y2": 680}}}
  }
  "Plant of the input-disturbance loop"
  plant4 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 820, "y2": 920}}}
  }
  mpc4 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.InputDisturbance(), final R1 = R1_input) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 820, "y2": 920}}}
  }
  "Position, no disturbance model"
  variable y1::Real
  "Control, no disturbance model"
  variable u1::Real
  "Position, state-disturbance model"
  variable y2::Real
  "Control, state-disturbance model"
  variable u2::Real
  "Estimated disturbance, state-disturbance model"
  variable dhat2::Real
  "Position, velocity form"
  variable y3::Real
  "Control, velocity form"
  variable u3::Real
  "Estimated disturbance, velocity form"
  variable dhat3::Real
  "Position, input-disturbance model"
  variable y4::Real
  "Control, input-disturbance model"
  variable u4::Real
  "Estimated disturbance, input-disturbance model"
  variable dhat4::Real
relations
  y1 = plant1.y
  u1 = mpc1.u[1]
  y2 = plant2.y
  u2 = mpc2.u[1]
  dhat2 = mpc2.dhat[1]
  y3 = plant3.y
  u3 = mpc3.u[1]
  dhat3 = mpc3.dhat[1]
  y4 = plant4.y
  u4 = mpc4.u[1]
  dhat4 = mpc4.dhat[1]
  connect(dpulse.y, dsum.u1) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(dstep.y, dsum.u2) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(dsum.y, plant1.d, plant2.d, plant3.d, plant4.d) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [{"x": 980, "y": 1180}], "E": -4},
        {"S": -1, "M": [{"x": 850, "y": 60}], "E": 2},
        {"S": -2, "M": [{"x": 850, "y": 300}], "E": 3},
        {"S": -3, "M": [{"x": 850, "y": 540}], "E": 4},
        {"S": -4, "M": [{"x": 850, "y": 780}], "E": 5},
        {"S": -4, "M": [], "E": -3},
        {"S": -3, "M": [], "E": -2},
        {"S": -2, "M": [], "E": -1}
      ],
      "junctions": [
        {"x": 980, "y": 60},
        {"x": 980, "y": 300},
        {"x": 980, "y": 540},
        {"x": 980, "y": 780}
      ],
      "path": {}
    }
  }
  connect(ref.y, sr.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(sr.y, mpc1.r[1], mpc2.r[1], mpc3.r[1], mpc4.r[1]) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [{"x": 480, "y": 450}], "E": -2},
        {"S": -1, "M": [], "E": 2},
        {"S": -2, "M": [], "E": 3},
        {"S": -3, "M": [], "E": 4},
        {"S": -4, "M": [], "E": 5},
        {"S": -4, "M": [], "E": -3},
        {"S": -3, "M": [], "E": -2},
        {"S": -2, "M": [], "E": -1}
      ],
      "junctions": [
        {"x": 480, "y": 150},
        {"x": 480, "y": 390},
        {"x": 480, "y": 630},
        {"x": 480, "y": 870}
      ],
      "path": {}
    }
  }
  connect(plant1.ym, mpc1.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 150},
            {"x": 930, "y": 270},
            {"x": 540, "y": 270},
            {"x": 540, "y": 150}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc1.u[1], plant1.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(plant2.ym, mpc2.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 390},
            {"x": 930, "y": 510},
            {"x": 540, "y": 510},
            {"x": 540, "y": 390}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc2.u[1], plant2.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(plant3.ym, mpc3.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 630},
            {"x": 930, "y": 750},
            {"x": 540, "y": 750},
            {"x": 540, "y": 630}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc3.u[1], plant3.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(plant4.ym, mpc4.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 870},
            {"x": 930, "y": 990},
            {"x": 540, "y": 990},
            {"x": 540, "y": 870}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc4.u[1], plant4.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
metadata {
  "Dyad": {
    "tests": {"case1": {"stop": 30}},
    "doc": {"behavior": false},
    "labels": [
      {
        "label": "None (baseline)",
        "x": 1160,
        "y": 150,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "StateDisturbance",
        "x": 1160,
        "y": 390,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "Velocity",
        "x": 1160,
        "y": 630,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "InputDisturbance",
        "x": 1160,
        "y": 870,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "Reference",
        "x": 250,
        "y": 350,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "Unmeasured load: pulse, then sustained",
        "x": 250,
        "y": 1010,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      }
    ]
  }
}
end
Flattened Source
dyad
"""
Offset-free tracking, one closed loop per method, around identical double-integrator plants
(`DoubleIntegratorExamplePlant`) driven by the same reference and the same unmeasured load
disturbance.

The four loops are `OffsetFreeMethod.None` (the baseline, no disturbance model),
`StateDisturbance`, `Velocity` and `InputDisturbance`. `OffsetFreeMethod.OutputDisturbance` has no
loop here: an output-disturbance model is not detectable on a plant that already has integral
action, so building it fails the rank condition `rank([F - I  Bd; C  Cd]) = nx + nd`.

The disturbance has two phases, so one run separates two distinct effects.

**A saturating pulse (2.0 for one second from t = 5).** It is twice the control authority
`umax = 1`, so the position runs away to about 3 while every loop sits at its limit and none of them
can do anything about it. What the phase shows is that none of these loops overshoots on the way
back: their integrating element is the disturbance estimate, which integrates the *estimation*
error, and saturation does not inflate that — the estimates peak between 0.14 and 0.52 against
steady-state values of 0.057 and 0.5. A method that instead integrates the *tracking* error has no
such protection, which is why an error-integrating variant was tried and rejected (see
JuliaComputing/MPCComponents.jl#40).

**A sustained offset (0.5 from t = 6 onward) exposes the steady-state error.** The baseline loop
settles about 0.61 above the reference, since a direct penalty on `u` trades tracking against
control effort and nothing tells it the plant is being pushed. Every offset-free loop removes that:
`InputDisturbance` to machine precision, `StateDisturbance` and `Velocity` to about 1.5e-3 — their
disturbance direction is not in the range of `B`, so the cross term `Sd` is the least-squares input
equivalent rather than the exact one. Penalizing the control rate (`Q3`) instead of its level
removes the baseline offset too, and the `Sd` residual with it.

`Velocity` and `StateDisturbance` produce identical trajectories, and their loops coincide on the
plot.
"""
example component LinearMPCOffsetFreeExample
  "Prediction horizon of every controller"
  structural parameter Np::Integer = 15
  "Output weight of every controller"
  structural parameter Q1::Real[:] = [1.0]
  "Control weight of every controller; a nonzero value is what leaves a steady-state error without an offset-free method"
  structural parameter Q2::Real[:] = [0.1]
  "Lower control limit of every controller"
  structural parameter umin::Real[:] = [-1.0]
  "Upper control limit of every controller; the load pulse is twice this, so every loop saturates"
  structural parameter umax::Real[:] = [1.0]
  "Process-noise covariance of the plant state, shared by every observer"
  structural parameter R1::Real[:] = [0.1, 0.1]
  "Process-noise covariance of the augmented state of the InputDisturbance observer: the plant states of R1, then the disturbance state"
  structural parameter R1_input::Real[:] = [0.1, 0.1, 0.01]
  "Measurement-noise covariance of every observer"
  structural parameter R2::Real[:] = [0.01]
  "Sample interval; fixed by the periodic clock inside DoubleIntegratorExamplePlant"
  final structural parameter Ts::Real = 0.1
  "State matrix of the plant model, the double integrator discretized at Ts (derived)"
  final structural parameter A::Real[:, :] = [[1.0, Ts], [0.0, 1.0]]
  "Input matrix of the plant model (derived)"
  final structural parameter B::Real[:, :] = [[Ts * Ts / 2], [Ts]]
  "Output matrix of the plant model: the plant measures and tracks the position (derived)"
  final structural parameter C::Real[:, :] = [[1.0, 0.0]]
  "Position reference"
  ref = BlockComponents.Sources.Constant(k = 1.0) {
    "Dyad": {"placement": {"diagram": {"x1": 60, "x2": 160, "y1": 400, "y2": 500}}}
  }
  sr = DiscreteComponents.Sampler() {
    "Dyad": {"placement": {"diagram": {"x1": 250, "x2": 350, "y1": 400, "y2": 500}}}
  }
  "Load pulse: 2.0 for one second from t = 5, twice the control authority"
  dpulse = BlockComponents.Sources.Step(start_time = 5.0, height = 2.0) {
    "Dyad": {"placement": {"diagram": {"x1": 60, "x2": 160, "y1": 1060, "y2": 1160}}}
  }
  "End of the pulse, leaving 0.5 of sustained load"
  dstep = BlockComponents.Sources.Step(start_time = 6.0, height = -1.5) {
    "Dyad": {"placement": {"diagram": {"x1": 60, "x2": 160, "y1": 1200, "y2": 1300}}}
  }
  dsum = BlockComponents.Math.Add() {
    "Dyad": {"placement": {"diagram": {"x1": 250, "x2": 350, "y1": 1130, "y2": 1230}}}
  }
  "Plant of the baseline loop, no disturbance model"
  plant1 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 100, "y2": 200}}}
  }
  mpc1 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.None(), final R1 = R1) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 100, "y2": 200}}}
  }
  "Plant of the state-disturbance loop"
  plant2 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 340, "y2": 440}}}
  }
  mpc2 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.StateDisturbance(), final R1 = R1) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 340, "y2": 440}}}
  }
  "Plant of the velocity-form loop"
  plant3 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 580, "y2": 680}}}
  }
  mpc3 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.Velocity(), final R1 = R1) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 580, "y2": 680}}}
  }
  "Plant of the input-disturbance loop"
  plant4 = MPCComponents.Examples.DoubleIntegratorExamplePlant() {
    "Dyad": {"placement": {"diagram": {"x1": 800, "x2": 900, "y1": 820, "y2": 920}}}
  }
  mpc4 = MPCComponents.LinearMPCController(final A = A, final B = B, final C = C, final Ts = Ts, final Q1 = Q1, final Q2 = Q2, final umin = umin, final umax = umax, final Np = Np, final R2 = R2, final offset_free = MPCComponents.OffsetFreeMethod.InputDisturbance(), final R1 = R1_input) {
    "Dyad": {"placement": {"diagram": {"x1": 580, "x2": 680, "y1": 820, "y2": 920}}}
  }
  "Position, no disturbance model"
  variable y1::Real
  "Control, no disturbance model"
  variable u1::Real
  "Position, state-disturbance model"
  variable y2::Real
  "Control, state-disturbance model"
  variable u2::Real
  "Estimated disturbance, state-disturbance model"
  variable dhat2::Real
  "Position, velocity form"
  variable y3::Real
  "Control, velocity form"
  variable u3::Real
  "Estimated disturbance, velocity form"
  variable dhat3::Real
  "Position, input-disturbance model"
  variable y4::Real
  "Control, input-disturbance model"
  variable u4::Real
  "Estimated disturbance, input-disturbance model"
  variable dhat4::Real
relations
  y1 = plant1.y
  u1 = mpc1.u[1]
  y2 = plant2.y
  u2 = mpc2.u[1]
  dhat2 = mpc2.dhat[1]
  y3 = plant3.y
  u3 = mpc3.u[1]
  dhat3 = mpc3.dhat[1]
  y4 = plant4.y
  u4 = mpc4.u[1]
  dhat4 = mpc4.dhat[1]
  connect(dpulse.y, dsum.u1) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(dstep.y, dsum.u2) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(dsum.y, plant1.d, plant2.d, plant3.d, plant4.d) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [{"x": 980, "y": 1180}], "E": -4},
        {"S": -1, "M": [{"x": 850, "y": 60}], "E": 2},
        {"S": -2, "M": [{"x": 850, "y": 300}], "E": 3},
        {"S": -3, "M": [{"x": 850, "y": 540}], "E": 4},
        {"S": -4, "M": [{"x": 850, "y": 780}], "E": 5},
        {"S": -4, "M": [], "E": -3},
        {"S": -3, "M": [], "E": -2},
        {"S": -2, "M": [], "E": -1}
      ],
      "junctions": [
        {"x": 980, "y": 60},
        {"x": 980, "y": 300},
        {"x": 980, "y": 540},
        {"x": 980, "y": 780}
      ],
      "path": {}
    }
  }
  connect(ref.y, sr.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(sr.y, mpc1.r[1], mpc2.r[1], mpc3.r[1], mpc4.r[1]) {
    "Dyad": {
      "edges": [
        {"S": 1, "M": [{"x": 480, "y": 450}], "E": -2},
        {"S": -1, "M": [], "E": 2},
        {"S": -2, "M": [], "E": 3},
        {"S": -3, "M": [], "E": 4},
        {"S": -4, "M": [], "E": 5},
        {"S": -4, "M": [], "E": -3},
        {"S": -3, "M": [], "E": -2},
        {"S": -2, "M": [], "E": -1}
      ],
      "junctions": [
        {"x": 480, "y": 150},
        {"x": 480, "y": 390},
        {"x": 480, "y": 630},
        {"x": 480, "y": 870}
      ],
      "path": {}
    }
  }
  connect(plant1.ym, mpc1.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 150},
            {"x": 930, "y": 270},
            {"x": 540, "y": 270},
            {"x": 540, "y": 150}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc1.u[1], plant1.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(plant2.ym, mpc2.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 390},
            {"x": 930, "y": 510},
            {"x": 540, "y": 510},
            {"x": 540, "y": 390}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc2.u[1], plant2.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(plant3.ym, mpc3.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 630},
            {"x": 930, "y": 750},
            {"x": 540, "y": 750},
            {"x": 540, "y": 630}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc3.u[1], plant3.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
  connect(plant4.ym, mpc4.y[1]) {
    "Dyad": {
      "edges": [
        {
          "S": 1,
          "M": [
            {"x": 930, "y": 870},
            {"x": 930, "y": 990},
            {"x": 540, "y": 990},
            {"x": 540, "y": 870}
          ],
          "E": 2
        }
      ],
      "path": {}
    }
  }
  connect(mpc4.u[1], plant4.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
metadata {
  "Dyad": {
    "tests": {"case1": {"stop": 30}},
    "doc": {"behavior": false},
    "labels": [
      {
        "label": "None (baseline)",
        "x": 1160,
        "y": 150,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "StateDisturbance",
        "x": 1160,
        "y": 390,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "Velocity",
        "x": 1160,
        "y": 630,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "InputDisturbance",
        "x": 1160,
        "y": 870,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "Reference",
        "x": 250,
        "y": 350,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      },
      {
        "label": "Unmeasured load: pulse, then sustained",
        "x": 250,
        "y": 1010,
        "rot": 0,
        "layer": "diagram",
        "attrs": {
          "fill": "black",
          "font-size": "20",
          "dominant-baseline": "central",
          "text-anchor": "middle"
        }
      }
    ]
  }
}
end


Test Cases ​

Test Case case1 ​

  • Examples

  • Experiments

  • Analyses