Examples.Binary.IntegerControlMPCExample
Regulation of a double integrator to the origin with the control restricted to the integer values u ∈ {-1, 0, 1} — the Dyad version of the DyadControlSystems MINLP MPC tutorial (https://help.juliahub.com/dyad/DyadControlSystems.jl/stable/examples/minlp_mpc/). Three parallel closed loops around the same plant
from the initial state [10, 0] compare real-valued control (loop 1), integer control enforced over the full horizon (loop 2), and integer control enforced over only the first two prediction steps (loop 3, Nc_binary = 2 — the tutorial's Nint). The plants are exact discrete models (DiscreteStateSpace) and the full state is fed to a LinearMPCOptimizer directly, the Dyad version of the tutorial's StateFeedback observer. The cost is the tutorial's: unit weight on both states, 0.1 u^2 on the control and ten times the state weight as terminal cost.
DAQP's binary controls take only the two values {umin, umax}, so the three-valued integer set is encoded with two binary inputs u = u1 - u2, u1, u2 ∈ {0, 1}: the integer-controlled plants take both inputs with input matrix [B, -B], and the control weight is the rank-one 0.1 (u1 - u2)^2 plus 1e-6 on the diagonal, which keeps the MIQP Hessian positive definite and makes (0, 0) the preferred encoding of u = 0.
The three state trajectories are nearly identical (the plant integrates, so it low-pass filters the control), while the control signals differ markedly: smooth for loop 1, a {-1, 0, 1} staircase for loops 2 and 3. Loop 3 tracks loop 2 closely at a fraction of the computational cost — the branch-and-bound tree grows exponentially in the number of binary steps, and two steps suffice because control decisions far into the future hardly affect the first move. examples/minlp_mpc.jl simulates this model and reproduces the tutorial's comparison plot.
Usage
MPCComponents.Examples.Binary.IntegerControlMPCExample()
Parameters:
| Name | Description | Units | Default value |
|---|---|---|---|
ctrl_real | Real-valued controller, u ∈ [-1, 1] | – | build_linea...king=false) |
ctrl_binary | Integer controller, u1 - u2 ∈ {-1, 0, 1} over the full horizon | – | build_linea...king=false) |
ctrl_binary2 | Integer controller, binary only over the first two steps, relaxed to [0, 1] after | – | build_linea...king=false) |
Variables
| Name | Description | Units |
|---|---|---|
p1 | Position, real-valued control | – |
v1 | Velocity, real-valued control | – |
u1 | Control signal, real-valued | – |
p2 | Position, full binary horizon | – |
v2 | Velocity, full binary horizon | – |
u2 | Control signal u1 - u2, full binary horizon | – |
p3 | Position, binary horizon 2 | – |
v3 | Velocity, binary horizon 2 | – |
u3 | Control signal u1 - u2, binary horizon 2 | – |
Behavior
Behavior of this component cannot be rendered because it includes path variables.
Source
"""
Regulation of a double integrator to the origin with the control restricted to the integer
values `u ∈ {-1, 0, 1}` — the Dyad version of the DyadControlSystems MINLP MPC tutorial
(https://help.juliahub.com/dyad/DyadControlSystems.jl/stable/examples/minlp_mpc/). Three
parallel closed loops around the same plant
```math
\\begin{aligned}
p^+ &= p + T_s v \\\\
v^+ &= v + T_s u
\\end{aligned}
```
from the initial state `[10, 0]` compare real-valued control (loop 1), integer control enforced
over the full horizon (loop 2), and integer control enforced over only the first two prediction
steps (loop 3, `Nc_binary = 2` — the tutorial's `Nint`). The plants are exact discrete models
(`DiscreteStateSpace`) and the full state is fed to a `LinearMPCOptimizer` directly, the Dyad
version of the tutorial's `StateFeedback` observer. The cost is the tutorial's:
unit weight on both states, `0.1 u^2` on the control and ten times the state weight as terminal
cost.
DAQP's binary controls take only the two values `{umin, umax}`, so the three-valued integer set
is encoded with two binary inputs `u = u1 - u2`, `u1, u2 ∈ {0, 1}`: the integer-controlled
plants take both inputs with input matrix `[B, -B]`, and the control weight is the rank-one
`0.1 (u1 - u2)^2` plus `1e-6` on the diagonal, which keeps the MIQP Hessian positive definite
and makes `(0, 0)` the preferred encoding of `u = 0`.
The three state trajectories are nearly identical (the plant integrates, so it low-pass filters
the control), while the control signals differ markedly: smooth for loop 1, a `{-1, 0, 1}`
staircase for loops 2 and 3. Loop 3 tracks loop 2 closely at a fraction of the computational
cost — the branch-and-bound tree grows exponentially in the number of binary steps, and two
steps suffice because control decisions far into the future hardly affect the first move.
`examples/minlp_mpc.jl` simulates this model and reproduces the tutorial's comparison plot.
"""
example component IntegerControlMPCExample
clock1 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 70, "y2": 170}}}
}
"Plant with real-valued control"
plant1 = DiscreteComponents.DiscreteStateSpace(nx = 2, nu = 1, ny = 2, A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0], [0.1]], C = [[1.0, 0.0], [0.0, 1.0]], with_D = false, initialization = DiscreteComponents.InitialCondition.InitialStateArray(x0 = [10.0, 0.0])) {
"Dyad": {"placement": {"diagram": {"x1": 340, "x2": 440, "y1": 40, "y2": 140}}}
}
"Real-valued MPC"
mpc1 = MPCComponents.LinearMPCOptimizer(mpc = ctrl_real) {
"Dyad": {"placement": {"diagram": {"x1": 180, "x2": 280, "y1": 40, "y2": 140}}}
}
"Continuous-time hold of the control; a model with no continuous part fails to initialize"
zoh1 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": -50, "y2": 50}}}
}
"Plant with integer control, u = u1 - u2"
plant2 = DiscreteComponents.DiscreteStateSpace(nx = 2, nu = 2, ny = 2, A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], C = [[1.0, 0.0], [0.0, 1.0]], with_D = false, initialization = DiscreteComponents.InitialCondition.InitialStateArray(x0 = [10.0, 0.0])) {
"Dyad": {"placement": {"diagram": {"x1": 340, "x2": 440, "y1": 340, "y2": 440}}}
}
"MPC with binary controls over the full horizon"
mpc2 = MPCComponents.LinearMPCOptimizer(mpc = ctrl_binary) {
"Dyad": {"placement": {"diagram": {"x1": 180, "x2": 280, "y1": 340, "y2": 440}}}
}
"Continuous-time hold of the control; a model with no continuous part fails to initialize"
zoh2 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 250, "y2": 350}}}
}
"Plant with integer control, u = u1 - u2"
plant3 = DiscreteComponents.DiscreteStateSpace(nx = 2, nu = 2, ny = 2, A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], C = [[1.0, 0.0], [0.0, 1.0]], with_D = false, initialization = DiscreteComponents.InitialCondition.InitialStateArray(x0 = [10.0, 0.0])) {
"Dyad": {"placement": {"diagram": {"x1": 340, "x2": 440, "y1": 600, "y2": 700}}}
}
"MPC with binary controls over the first two prediction steps only"
mpc3 = MPCComponents.LinearMPCOptimizer(mpc = ctrl_binary2) {
"Dyad": {"placement": {"diagram": {"x1": 180, "x2": 280, "y1": 600, "y2": 700}}}
}
"Continuous-time hold of the control; a model with no continuous part fails to initialize"
zoh3 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 510, "y2": 610}}}
}
periodicclock = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 370, "y2": 470}}}
}
periodicclock1 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 630, "y2": 730}}}
}
"Real-valued controller, u ∈ [-1, 1]"
structural parameter ctrl_real::MPCComponents.MPCRef = build_linear_mpc(A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0], [0.1]], Np = 10, Q1 = [1.0, 1.0], Q2 = [0.1], Qf = [10.0, 10.0], umin = [-1.0], umax = [1.0], reference_tracking = false)
"Integer controller, u1 - u2 ∈ {-1, 0, 1} over the full horizon"
structural parameter ctrl_binary::MPCComponents.MPCRef = build_linear_mpc(A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], Np = 10, Q1 = [1.0, 1.0], Q2 = [[0.100001, -0.1], [-0.1, 0.100001]], Qf = [10.0, 10.0], umin = [0.0, 0.0], umax = [1.0, 1.0], binary_controls = [1, 2], reference_tracking = false)
"Integer controller, binary only over the first two steps, relaxed to [0, 1] after"
structural parameter ctrl_binary2::MPCComponents.MPCRef = build_linear_mpc(A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], Np = 10, Q1 = [1.0, 1.0], Q2 = [[0.100001, -0.1], [-0.1, 0.100001]], Qf = [10.0, 10.0], umin = [0.0, 0.0], umax = [1.0, 1.0], binary_controls = [1, 2], Nc_binary = 2, reference_tracking = false)
"Position, real-valued control"
variable p1::Real
"Velocity, real-valued control"
variable v1::Real
"Control signal, real-valued"
variable u1::Real
"Position, full binary horizon"
variable p2::Real
"Velocity, full binary horizon"
variable v2::Real
"Control signal u1 - u2, full binary horizon"
variable u2::Real
"Position, binary horizon 2"
variable p3::Real
"Velocity, binary horizon 2"
variable v3::Real
"Control signal u1 - u2, binary horizon 2"
variable u3::Real
relations
p1 = plant1.y[1]
v1 = plant1.y[2]
u1 = mpc1.u[1]
p2 = plant2.y[1]
v2 = plant2.y[2]
u2 = mpc2.u[1] - mpc2.u[2]
p3 = plant3.y[1]
v3 = plant3.y[2]
u3 = mpc3.u[1] - mpc3.u[2]
connect(mpc1.u[1], zoh1.u, clock1.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 310, "y": 113}, {"x": 310, "y": 0}], "E": 2},
{"S": 2, "M": [], "E": 3}
],
"path": {}
}
}
connect(mpc2.u[1], zoh2.u, periodicclock.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 310, "y": 413}, {"x": 310, "y": 300}], "E": 2},
{"S": 2, "M": [], "E": 3}
],
"path": {}
}
}
connect(mpc3.u[1], zoh3.u, periodicclock1.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 310, "y": 673}, {"x": 310, "y": 560}], "E": 2},
{"S": 2, "M": [], "E": 3}
],
"path": {}
}
}
connect(plant1.y, mpc1.xhat) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 460, "y": 90},
{"x": 460, "y": 180},
{"x": 160, "y": 180},
{"x": 160, "y": 90}
],
"E": 2
}
],
"path": {}
}
}
connect(plant2.y, mpc2.xhat) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 460, "y": 390},
{"x": 460, "y": 470},
{"x": 160, "y": 470},
{"x": 160, "y": 390}
],
"E": 2
}
],
"path": {}
}
}
connect(mpc3.xhat, plant3.y) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 160, "y": 650},
{"x": 160, "y": 730},
{"x": 460, "y": 730},
{"x": 460, "y": 650}
],
"E": 2
}
],
"path": {}
}
}
connect(mpc1.u, plant1.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 310, "y": 113}, {"x": 310, "y": 90}], "E": 2}],
"path": {}
}
}
connect(mpc2.u, plant2.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 310, "y": 413}, {"x": 310, "y": 390}], "E": 2}],
"path": {}
}
}
connect(mpc3.u, plant3.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 310, "y": 673}, {"x": 310, "y": 650}], "E": 2}],
"path": {}
}
}
metadata {"Dyad": {"doc": {"behavior": false}}}
endFlattened Source
"""
Regulation of a double integrator to the origin with the control restricted to the integer
values `u ∈ {-1, 0, 1}` — the Dyad version of the DyadControlSystems MINLP MPC tutorial
(https://help.juliahub.com/dyad/DyadControlSystems.jl/stable/examples/minlp_mpc/). Three
parallel closed loops around the same plant
```math
\\begin{aligned}
p^+ &= p + T_s v \\\\
v^+ &= v + T_s u
\\end{aligned}
```
from the initial state `[10, 0]` compare real-valued control (loop 1), integer control enforced
over the full horizon (loop 2), and integer control enforced over only the first two prediction
steps (loop 3, `Nc_binary = 2` — the tutorial's `Nint`). The plants are exact discrete models
(`DiscreteStateSpace`) and the full state is fed to a `LinearMPCOptimizer` directly, the Dyad
version of the tutorial's `StateFeedback` observer. The cost is the tutorial's:
unit weight on both states, `0.1 u^2` on the control and ten times the state weight as terminal
cost.
DAQP's binary controls take only the two values `{umin, umax}`, so the three-valued integer set
is encoded with two binary inputs `u = u1 - u2`, `u1, u2 ∈ {0, 1}`: the integer-controlled
plants take both inputs with input matrix `[B, -B]`, and the control weight is the rank-one
`0.1 (u1 - u2)^2` plus `1e-6` on the diagonal, which keeps the MIQP Hessian positive definite
and makes `(0, 0)` the preferred encoding of `u = 0`.
The three state trajectories are nearly identical (the plant integrates, so it low-pass filters
the control), while the control signals differ markedly: smooth for loop 1, a `{-1, 0, 1}`
staircase for loops 2 and 3. Loop 3 tracks loop 2 closely at a fraction of the computational
cost — the branch-and-bound tree grows exponentially in the number of binary steps, and two
steps suffice because control decisions far into the future hardly affect the first move.
`examples/minlp_mpc.jl` simulates this model and reproduces the tutorial's comparison plot.
"""
example component IntegerControlMPCExample
clock1 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 70, "y2": 170}}}
}
"Plant with real-valued control"
plant1 = DiscreteComponents.DiscreteStateSpace(nx = 2, nu = 1, ny = 2, A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0], [0.1]], C = [[1.0, 0.0], [0.0, 1.0]], with_D = false, initialization = DiscreteComponents.InitialCondition.InitialStateArray(x0 = [10.0, 0.0])) {
"Dyad": {"placement": {"diagram": {"x1": 340, "x2": 440, "y1": 40, "y2": 140}}}
}
"Real-valued MPC"
mpc1 = MPCComponents.LinearMPCOptimizer(mpc = ctrl_real) {
"Dyad": {"placement": {"diagram": {"x1": 180, "x2": 280, "y1": 40, "y2": 140}}}
}
"Continuous-time hold of the control; a model with no continuous part fails to initialize"
zoh1 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": -50, "y2": 50}}}
}
"Plant with integer control, u = u1 - u2"
plant2 = DiscreteComponents.DiscreteStateSpace(nx = 2, nu = 2, ny = 2, A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], C = [[1.0, 0.0], [0.0, 1.0]], with_D = false, initialization = DiscreteComponents.InitialCondition.InitialStateArray(x0 = [10.0, 0.0])) {
"Dyad": {"placement": {"diagram": {"x1": 340, "x2": 440, "y1": 340, "y2": 440}}}
}
"MPC with binary controls over the full horizon"
mpc2 = MPCComponents.LinearMPCOptimizer(mpc = ctrl_binary) {
"Dyad": {"placement": {"diagram": {"x1": 180, "x2": 280, "y1": 340, "y2": 440}}}
}
"Continuous-time hold of the control; a model with no continuous part fails to initialize"
zoh2 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 250, "y2": 350}}}
}
"Plant with integer control, u = u1 - u2"
plant3 = DiscreteComponents.DiscreteStateSpace(nx = 2, nu = 2, ny = 2, A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], C = [[1.0, 0.0], [0.0, 1.0]], with_D = false, initialization = DiscreteComponents.InitialCondition.InitialStateArray(x0 = [10.0, 0.0])) {
"Dyad": {"placement": {"diagram": {"x1": 340, "x2": 440, "y1": 600, "y2": 700}}}
}
"MPC with binary controls over the first two prediction steps only"
mpc3 = MPCComponents.LinearMPCOptimizer(mpc = ctrl_binary2) {
"Dyad": {"placement": {"diagram": {"x1": 180, "x2": 280, "y1": 600, "y2": 700}}}
}
"Continuous-time hold of the control; a model with no continuous part fails to initialize"
zoh3 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 510, "y2": 610}}}
}
periodicclock = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 370, "y2": 470}}}
}
periodicclock1 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": 530, "x2": 630, "y1": 630, "y2": 730}}}
}
"Real-valued controller, u ∈ [-1, 1]"
structural parameter ctrl_real::MPCComponents.MPCRef = build_linear_mpc(A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0], [0.1]], Np = 10, Q1 = [1.0, 1.0], Q2 = [0.1], Qf = [10.0, 10.0], umin = [-1.0], umax = [1.0], reference_tracking = false)
"Integer controller, u1 - u2 ∈ {-1, 0, 1} over the full horizon"
structural parameter ctrl_binary::MPCComponents.MPCRef = build_linear_mpc(A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], Np = 10, Q1 = [1.0, 1.0], Q2 = [[0.100001, -0.1], [-0.1, 0.100001]], Qf = [10.0, 10.0], umin = [0.0, 0.0], umax = [1.0, 1.0], binary_controls = [1, 2], reference_tracking = false)
"Integer controller, binary only over the first two steps, relaxed to [0, 1] after"
structural parameter ctrl_binary2::MPCComponents.MPCRef = build_linear_mpc(A = [[1.0, 0.1], [0.0, 1.0]], B = [[0.0, 0.0], [0.1, -0.1]], Np = 10, Q1 = [1.0, 1.0], Q2 = [[0.100001, -0.1], [-0.1, 0.100001]], Qf = [10.0, 10.0], umin = [0.0, 0.0], umax = [1.0, 1.0], binary_controls = [1, 2], Nc_binary = 2, reference_tracking = false)
"Position, real-valued control"
variable p1::Real
"Velocity, real-valued control"
variable v1::Real
"Control signal, real-valued"
variable u1::Real
"Position, full binary horizon"
variable p2::Real
"Velocity, full binary horizon"
variable v2::Real
"Control signal u1 - u2, full binary horizon"
variable u2::Real
"Position, binary horizon 2"
variable p3::Real
"Velocity, binary horizon 2"
variable v3::Real
"Control signal u1 - u2, binary horizon 2"
variable u3::Real
relations
p1 = plant1.y[1]
v1 = plant1.y[2]
u1 = mpc1.u[1]
p2 = plant2.y[1]
v2 = plant2.y[2]
u2 = mpc2.u[1] - mpc2.u[2]
p3 = plant3.y[1]
v3 = plant3.y[2]
u3 = mpc3.u[1] - mpc3.u[2]
connect(mpc1.u[1], zoh1.u, clock1.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 310, "y": 113}, {"x": 310, "y": 0}], "E": 2},
{"S": 2, "M": [], "E": 3}
],
"path": {}
}
}
connect(mpc2.u[1], zoh2.u, periodicclock.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 310, "y": 413}, {"x": 310, "y": 300}], "E": 2},
{"S": 2, "M": [], "E": 3}
],
"path": {}
}
}
connect(mpc3.u[1], zoh3.u, periodicclock1.y) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 310, "y": 673}, {"x": 310, "y": 560}], "E": 2},
{"S": 2, "M": [], "E": 3}
],
"path": {}
}
}
connect(plant1.y, mpc1.xhat) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 460, "y": 90},
{"x": 460, "y": 180},
{"x": 160, "y": 180},
{"x": 160, "y": 90}
],
"E": 2
}
],
"path": {}
}
}
connect(plant2.y, mpc2.xhat) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 460, "y": 390},
{"x": 460, "y": 470},
{"x": 160, "y": 470},
{"x": 160, "y": 390}
],
"E": 2
}
],
"path": {}
}
}
connect(mpc3.xhat, plant3.y) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 160, "y": 650},
{"x": 160, "y": 730},
{"x": 460, "y": 730},
{"x": 460, "y": 650}
],
"E": 2
}
],
"path": {}
}
}
connect(mpc1.u, plant1.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 310, "y": 113}, {"x": 310, "y": 90}], "E": 2}],
"path": {}
}
}
connect(mpc2.u, plant2.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 310, "y": 413}, {"x": 310, "y": 390}], "E": 2}],
"path": {}
}
}
connect(mpc3.u, plant3.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 310, "y": 673}, {"x": 310, "y": 650}], "E": 2}],
"path": {}
}
}
metadata {"Dyad": {"doc": {"behavior": false}}}
endTest Cases
No test cases defined.
Related
Examples
Experiments
Analyses