Examples.Binary.SemiContinuousInputMPCExample ​
Level control of a first-order plant whose actuator output range is semi-continuous: the input is either off or somewhere in an interval that excludes zero,
a pump or a compressor that cannot run below its minimum capacity. The forbidden gap (0, u_on_min) is not a convex set, so it cannot be expressed with input bounds; it is encoded with one binary and one real control instead:
δ says whether the actuator runs and z is the capacity above the minimum. The last inequality is what couples them: with δ = 0 it forces z = 0 (the actuator is off), and with δ = 1 it is slack, so u sweeps [u_on_min, u_on_max] freely. Nothing maps to the gap, and the encoding is one-to-one, so the two-dimensional control has no more degrees of freedom than the original scalar one.
In the LinearMPCController parameters this is nu = 2 with the prediction model B = [u_on_min*b, b], where b is the input column of the scalar-input plant (both channels enter the plant through the same physical input), binary_controls = [1] with umin = [0, 0], umax = [1, u_on_max - u_on_min], and the coupling inequality as the general constraint block Au u ≤ ub with Au = [-(u_on_max - u_on_min), 1], ub = [0]. Note constraint_ks = 1:Np: the default 2:Np leaves the applied control u_0 out of the constraint block, which is exactly the one that must respect the gap. The cost is a plain quadratic in the physical input, Q2 = q (δ + z)^2. That weight is rank one and leaves the QP without curvature in the (δ, -z) direction, hence the 1e-6 on its diagonal — LinearMPC regularizes binary controls with δ^2 - δ (zero on {0, 1}, so it does not change the solution), which covers the same direction, so this is belt and braces. No tolerance is needed in the coupling inequality itself: DAQP's branch and bound resolves the degenerate 0 ≤ z ≤ 0 case at δ = 0 within its primal tolerance, and a relaxed bound z ≤ tol would instead leak exactly tol of input while the actuator is nominally off.
Two loops around identical plants (k = 1, T = 0.5, sampled at Ts = 0.1) follow the same reference r = 1.5 → 0.5 → 0, one with the semi-continuous encoding and one with a plain continuous control in [0, u_on_max] for comparison. The first setpoint is reachable in steady state with the actuator running (u = 1.5), and both loops track it identically. The second is not: r = 0.5 would need u = 0.5, in the middle of the forbidden gap. The comparison loop simply parks there, whereas the semi-continuous loop duty-cycles between off and minimum capacity, so its output ripples around the reference while its input never enters the gap. At r = 0 both shut off.
The duty cycling is the optimal answer to a setpoint the actuator cannot hold, not a numerical artifact; a control-increment weight Q3 on the physical input, or move_block, trades tracking for fewer switches. A minimum on-time longer than one step is out of reach: the constraint block reaches the controls of one prediction step and, through Aup, its predecessor, but not the binary over a longer window. examples/semicontinuous_mpc.jl simulates this model and plots the comparison.
Usage ​
MPCComponents.Examples.Binary.SemiContinuousInputMPCExample()
Parameters: ​
| Name | Description | Units | Default value |
|---|---|---|---|
Np | – | 10 |
Variables ​
| Name | Description | Units |
|---|---|---|
r | Reference | – |
y1 | Plant output, semi-continuous actuator | – |
u1 | Physical input, semi-continuous actuator | – |
delta | On/off decision of the semi-continuous actuator | – |
z | Capacity above the minimum of the semi-continuous actuator | – |
y2 | Plant output, continuous actuator | – |
u2 | Physical input, continuous actuator | – |
Behavior ​
Behavior of this component cannot be rendered because it includes path variables.
Source ​
"""
Level control of a first-order plant whose actuator output range is *semi-continuous*: the input is either
off or somewhere in an interval that excludes zero,
```math
u \\in \\{0\\} \\cup [u_{on}^{min}, u_{on}^{max}] = \\{0\\} \\cup [1, 2]
```
a pump or a compressor that cannot run below its minimum capacity. The forbidden gap
`(0, u_on_min)` is not a convex set, so it cannot be expressed with input bounds; it is encoded
with one binary and one real control instead:
```math
u = u_{on}^{min} \\delta + z, \\quad \\delta \\in \\{0, 1\\}, \\quad
0 \\le z \\le u_{on}^{max} - u_{on}^{min}, \\quad z \\le (u_{on}^{max} - u_{on}^{min}) \\delta
```
`δ` says whether the actuator runs and `z` is the capacity above the minimum. The last
inequality is what couples them: with `δ = 0` it forces `z = 0` (the actuator is off), and with
`δ = 1` it is slack, so `u` sweeps `[u_on_min, u_on_max]` freely. Nothing maps to the gap, and
the encoding is one-to-one, so the two-dimensional control has no more degrees of freedom than
the original scalar one.
In the `LinearMPCController` parameters this is `nu = 2` with the prediction model
`B = [u_on_min*b, b]`, where `b` is the input column of the scalar-input plant (both channels
enter the plant through the same physical input),
`binary_controls = [1]` with `umin = [0, 0]`, `umax = [1, u_on_max - u_on_min]`, and the coupling
inequality as the general constraint block `Au u ≤ ub` with
`Au = [-(u_on_max - u_on_min), 1]`, `ub = [0]`. Note `constraint_ks = 1:Np`: the default
`2:Np` leaves the *applied* control `u_0` out of the constraint block, which is exactly the one
that must respect the gap. The cost is a plain quadratic in the physical input,
`Q2 = q (δ + z)^2`. That weight is rank one and leaves the QP without curvature in the
`(δ, -z)` direction, hence the `1e-6` on its diagonal — LinearMPC regularizes binary controls
with `δ^2 - δ` (zero on `{0, 1}`, so it does not change the solution), which covers the same
direction, so this is belt and braces. No tolerance is needed in the coupling inequality
itself: DAQP's branch and bound resolves the degenerate `0 ≤ z ≤ 0` case at `δ = 0` within its
primal tolerance, and a relaxed bound `z ≤ tol` would instead leak exactly `tol` of input while
the actuator is nominally off.
Two loops around identical plants (`k = 1`, `T = 0.5`, sampled at `Ts = 0.1`) follow the same
reference `r = 1.5 → 0.5 → 0`, one with the semi-continuous encoding and one with a plain
continuous control in `[0, u_on_max]` for comparison. The first setpoint is reachable in steady
state with the actuator running (`u = 1.5`), and both loops track it identically. The second is
not: `r = 0.5` would need `u = 0.5`, in the middle of the forbidden gap. The comparison loop
simply parks there, whereas the semi-continuous loop duty-cycles between off and minimum
capacity, so its output ripples around the reference while its input never enters the gap. At
`r = 0` both shut off.
The duty cycling is the optimal answer to a setpoint the actuator cannot hold, not a numerical
artifact; a control-increment weight `Q3` on the physical input, or `move_block`, trades
tracking for fewer switches. A minimum on-time longer than one step is out of reach: the
constraint block reaches the controls of one prediction step and, through `Aup`, its
predecessor, but not the binary over a longer window. `examples/semicontinuous_mpc.jl` simulates this model
and plots the comparison.
"""
example component SemiContinuousInputMPCExample
"Reference: 1.5, stepping down to 0.5 at t = 2"
step1 = BlockComponents.Sources.Step(height = -1.0, start_time = 2.0, offset = 1.5) {
"Dyad": {"placement": {"diagram": {"x1": -720, "x2": -620, "y1": 150, "y2": 250}}}
}
"Reference: a further step down to 0 at t = 4"
step2 = BlockComponents.Sources.Step(height = -0.5, start_time = 4.0) {
"Dyad": {"placement": {"diagram": {"x1": -720, "x2": -620, "y1": 290, "y2": 390}}}
}
"Reference r = 1.5 -> 0.5 -> 0"
refsum = BlockComponents.Math.Add() {
"Dyad": {"placement": {"diagram": {"x1": -570, "x2": -470, "y1": 220, "y2": 320}}}
}
clock1 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": -240, "x2": -340, "y1": 240, "y2": 340}}}
}
"Plant driven by the semi-continuous actuator"
plant1 = BlockComponents.Continuous.FirstOrder(k = 1.0, T = 0.5, x(initial = 0)) {
"Dyad": {"placement": {"diagram": {"x1": 480, "x2": 580, "y1": 60, "y2": 160}}}
}
sy1 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": 630, "x2": 730, "y1": 60, "y2": 160}}}
}
sr1 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": -380, "x2": -280, "y1": 60, "y2": 160}}}
}
"Semi-continuous MPC: u = u_on_min*delta + z with delta binary"
mpc1 = MPCComponents.LinearMPCController(A = [[0.8187307530779818]], B = [[0.18126924692201818, 0.18126924692201818]], Np = 10, Q1 = [1.0], Q2 = [[0.001001, 0.001], [0.001, 0.001001]], umin = [0.0, 0.0], umax = [1.0, 1.0], binary_controls = [1], Au = [[-1.0, 1.0]], ub = [0.0], constraint_ks = range(1, Np), R1 = [0.01], R2 = [0.01]) {
"Dyad": {"placement": {"diagram": {"x1": -160, "x2": -60, "y1": 60, "y2": 160}}}
}
"Hold of the on/off decision"
zohd = DiscreteComponents.ZeroOrderHold() {"Dyad": {"placement": {"diagram": {"x1": 90, "x2": 190, "y1": 0, "y2": 100}}}}
"Hold of the capacity above the minimum"
zohz = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 90, "x2": 190, "y1": 140, "y2": 240}}}
}
"Physical input u = u_on_min*delta + z"
usum = BlockComponents.Math.Add(k1 = 1.0, k2 = 1.0) {
"Dyad": {"placement": {"diagram": {"x1": 300, "x2": 400, "y1": 60, "y2": 160}}}
}
clock2 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": -130, "x2": -30, "y1": 680, "y2": 780}}}
}
"Identical plant driven by a plain continuous actuator"
plant2 = BlockComponents.Continuous.FirstOrder(k = 1.0, T = 0.5, x(initial = 0)) {
"Dyad": {"placement": {"diagram": {"x1": 480, "x2": 580, "y1": 520, "y2": 620}}}
}
sy2 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": 630, "x2": 730, "y1": 520, "y2": 620}}}
}
sr2 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": -380, "x2": -280, "y1": 520, "y2": 620}}}
}
"Comparison MPC with the control relaxed to the interval [0, u_on_max]"
mpc2 = MPCComponents.LinearMPCController(A = [[0.8187307530779818]], B = [[0.18126924692201818]], final Np = Np, Q1 = [1.0], Q2 = [0.001], umin = [0.0], umax = [2.0], R1 = [0.01], R2 = [0.01]) {
"Dyad": {"placement": {"diagram": {"x1": -160, "x2": -60, "y1": 520, "y2": 620}}}
}
zoh2 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 300, "x2": 400, "y1": 520, "y2": 620}}}
}
structural parameter Np::Integer = 10
"Reference"
variable r::Real
"Plant output, semi-continuous actuator"
variable y1::Real
"Physical input, semi-continuous actuator"
variable u1::Real
"On/off decision of the semi-continuous actuator"
variable delta::Real
"Capacity above the minimum of the semi-continuous actuator"
variable z::Real
"Plant output, continuous actuator"
variable y2::Real
"Physical input, continuous actuator"
variable u2::Real
relations
r = refsum.y
y1 = plant1.y
u1 = usum.y
delta = zohd.y
z = zohz.y
y2 = plant2.y
u2 = zoh2.y
connect(step1.y, refsum.u1) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": -601.6666666666666, "y": 200}, {"x": -601.6666666666666, "y": 240}],
"E": 2
}
],
"path": {}
}
}
connect(step2.y, refsum.u2) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": -601.6666666666666, "y": 340}, {"x": -601.6666666666666, "y": 300}],
"E": 2
}
],
"path": {}
}
}
connect(refsum.y, sr1.u, sr2.u) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": -420, "y": 270}, {"x": -420, "y": 110}], "E": 2},
{"S": 3, "M": [{"x": -420, "y": 570}], "E": -1}
],
"junctions": [{"x": -420, "y": 270}],
"path": {}
}
}
connect(sr1.y, mpc1.r[1]) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -203, "y": 110}, {"x": -203, "y": 72}], "E": 2}],
"path": {}
}
}
connect(plant1.y, sy1.u) {}
connect(sy1.y, mpc1.y[1], clock1.y) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 760, "y": 110},
{"x": 760, "y": 280},
{"x": -190, "y": 280},
{"x": -190, "y": 144}
],
"E": 2
},
{"S": 2, "M": [{"x": -220, "y": 144}, {"x": -220, "y": 290}], "E": 3}
],
"path": {}
}
}
connect(mpc1.u[1], zohd.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -15, "y": 98}, {"x": -15, "y": 50}], "E": 2}],
"path": {}
}
}
connect(mpc1.u[2], zohz.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -15, "y": 98}, {"x": -15, "y": 190}], "E": 2}],
"path": {}
}
}
connect(zohd.y, usum.u1) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 225.66666666666666, "y": 50}, {"x": 225.66666666666666, "y": 80}],
"E": 2
}
],
"path": {}
}
}
connect(zohz.y, usum.u2) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 225.66666666666666, "y": 190}, {"x": 225.66666666666666, "y": 140}],
"E": 2
}
],
"path": {}
}
}
connect(usum.y, plant1.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(sr2.y, mpc2.r[1]) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -203, "y": 570}, {"x": -203, "y": 532}], "E": 2}],
"path": {}
}
}
connect(plant2.y, sy2.u) {}
connect(sy2.y, mpc2.y[1], clock2.y) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 750, "y": 570},
{"x": 750, "y": 660},
{"x": -180, "y": 660},
{"x": -180, "y": 604}
],
"E": 2
},
{"S": 2, "M": [{"x": -230, "y": 604}, {"x": -230, "y": 730}], "E": 3}
],
"path": {}
}
}
connect(mpc2.u[1], zoh2.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -15, "y": 558}, {"x": -15, "y": 570}], "E": 2}],
"path": {}
}
}
connect(zoh2.y, plant2.u) {}
metadata {"Dyad": {"doc": {"behavior": false}}}
endFlattened Source
"""
Level control of a first-order plant whose actuator output range is *semi-continuous*: the input is either
off or somewhere in an interval that excludes zero,
```math
u \\in \\{0\\} \\cup [u_{on}^{min}, u_{on}^{max}] = \\{0\\} \\cup [1, 2]
```
a pump or a compressor that cannot run below its minimum capacity. The forbidden gap
`(0, u_on_min)` is not a convex set, so it cannot be expressed with input bounds; it is encoded
with one binary and one real control instead:
```math
u = u_{on}^{min} \\delta + z, \\quad \\delta \\in \\{0, 1\\}, \\quad
0 \\le z \\le u_{on}^{max} - u_{on}^{min}, \\quad z \\le (u_{on}^{max} - u_{on}^{min}) \\delta
```
`δ` says whether the actuator runs and `z` is the capacity above the minimum. The last
inequality is what couples them: with `δ = 0` it forces `z = 0` (the actuator is off), and with
`δ = 1` it is slack, so `u` sweeps `[u_on_min, u_on_max]` freely. Nothing maps to the gap, and
the encoding is one-to-one, so the two-dimensional control has no more degrees of freedom than
the original scalar one.
In the `LinearMPCController` parameters this is `nu = 2` with the prediction model
`B = [u_on_min*b, b]`, where `b` is the input column of the scalar-input plant (both channels
enter the plant through the same physical input),
`binary_controls = [1]` with `umin = [0, 0]`, `umax = [1, u_on_max - u_on_min]`, and the coupling
inequality as the general constraint block `Au u ≤ ub` with
`Au = [-(u_on_max - u_on_min), 1]`, `ub = [0]`. Note `constraint_ks = 1:Np`: the default
`2:Np` leaves the *applied* control `u_0` out of the constraint block, which is exactly the one
that must respect the gap. The cost is a plain quadratic in the physical input,
`Q2 = q (δ + z)^2`. That weight is rank one and leaves the QP without curvature in the
`(δ, -z)` direction, hence the `1e-6` on its diagonal — LinearMPC regularizes binary controls
with `δ^2 - δ` (zero on `{0, 1}`, so it does not change the solution), which covers the same
direction, so this is belt and braces. No tolerance is needed in the coupling inequality
itself: DAQP's branch and bound resolves the degenerate `0 ≤ z ≤ 0` case at `δ = 0` within its
primal tolerance, and a relaxed bound `z ≤ tol` would instead leak exactly `tol` of input while
the actuator is nominally off.
Two loops around identical plants (`k = 1`, `T = 0.5`, sampled at `Ts = 0.1`) follow the same
reference `r = 1.5 → 0.5 → 0`, one with the semi-continuous encoding and one with a plain
continuous control in `[0, u_on_max]` for comparison. The first setpoint is reachable in steady
state with the actuator running (`u = 1.5`), and both loops track it identically. The second is
not: `r = 0.5` would need `u = 0.5`, in the middle of the forbidden gap. The comparison loop
simply parks there, whereas the semi-continuous loop duty-cycles between off and minimum
capacity, so its output ripples around the reference while its input never enters the gap. At
`r = 0` both shut off.
The duty cycling is the optimal answer to a setpoint the actuator cannot hold, not a numerical
artifact; a control-increment weight `Q3` on the physical input, or `move_block`, trades
tracking for fewer switches. A minimum on-time longer than one step is out of reach: the
constraint block reaches the controls of one prediction step and, through `Aup`, its
predecessor, but not the binary over a longer window. `examples/semicontinuous_mpc.jl` simulates this model
and plots the comparison.
"""
example component SemiContinuousInputMPCExample
"Reference: 1.5, stepping down to 0.5 at t = 2"
step1 = BlockComponents.Sources.Step(height = -1.0, start_time = 2.0, offset = 1.5) {
"Dyad": {"placement": {"diagram": {"x1": -720, "x2": -620, "y1": 150, "y2": 250}}}
}
"Reference: a further step down to 0 at t = 4"
step2 = BlockComponents.Sources.Step(height = -0.5, start_time = 4.0) {
"Dyad": {"placement": {"diagram": {"x1": -720, "x2": -620, "y1": 290, "y2": 390}}}
}
"Reference r = 1.5 -> 0.5 -> 0"
refsum = BlockComponents.Math.Add() {
"Dyad": {"placement": {"diagram": {"x1": -570, "x2": -470, "y1": 220, "y2": 320}}}
}
clock1 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": -240, "x2": -340, "y1": 240, "y2": 340}}}
}
"Plant driven by the semi-continuous actuator"
plant1 = BlockComponents.Continuous.FirstOrder(k = 1.0, T = 0.5, x(initial = 0)) {
"Dyad": {"placement": {"diagram": {"x1": 480, "x2": 580, "y1": 60, "y2": 160}}}
}
sy1 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": 630, "x2": 730, "y1": 60, "y2": 160}}}
}
sr1 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": -380, "x2": -280, "y1": 60, "y2": 160}}}
}
"Semi-continuous MPC: u = u_on_min*delta + z with delta binary"
mpc1 = MPCComponents.LinearMPCController(A = [[0.8187307530779818]], B = [[0.18126924692201818, 0.18126924692201818]], Np = 10, Q1 = [1.0], Q2 = [[0.001001, 0.001], [0.001, 0.001001]], umin = [0.0, 0.0], umax = [1.0, 1.0], binary_controls = [1], Au = [[-1.0, 1.0]], ub = [0.0], constraint_ks = range(1, Np), R1 = [0.01], R2 = [0.01]) {
"Dyad": {"placement": {"diagram": {"x1": -160, "x2": -60, "y1": 60, "y2": 160}}}
}
"Hold of the on/off decision"
zohd = DiscreteComponents.ZeroOrderHold() {"Dyad": {"placement": {"diagram": {"x1": 90, "x2": 190, "y1": 0, "y2": 100}}}}
"Hold of the capacity above the minimum"
zohz = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 90, "x2": 190, "y1": 140, "y2": 240}}}
}
"Physical input u = u_on_min*delta + z"
usum = BlockComponents.Math.Add(k1 = 1.0, k2 = 1.0) {
"Dyad": {"placement": {"diagram": {"x1": 300, "x2": 400, "y1": 60, "y2": 160}}}
}
clock2 = DiscreteComponents.PeriodicClock(dt = 0.1) {
"Dyad": {"placement": {"diagram": {"x1": -130, "x2": -30, "y1": 680, "y2": 780}}}
}
"Identical plant driven by a plain continuous actuator"
plant2 = BlockComponents.Continuous.FirstOrder(k = 1.0, T = 0.5, x(initial = 0)) {
"Dyad": {"placement": {"diagram": {"x1": 480, "x2": 580, "y1": 520, "y2": 620}}}
}
sy2 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": 630, "x2": 730, "y1": 520, "y2": 620}}}
}
sr2 = DiscreteComponents.Sampler() {
"Dyad": {"placement": {"diagram": {"x1": -380, "x2": -280, "y1": 520, "y2": 620}}}
}
"Comparison MPC with the control relaxed to the interval [0, u_on_max]"
mpc2 = MPCComponents.LinearMPCController(A = [[0.8187307530779818]], B = [[0.18126924692201818]], final Np = Np, Q1 = [1.0], Q2 = [0.001], umin = [0.0], umax = [2.0], R1 = [0.01], R2 = [0.01]) {
"Dyad": {"placement": {"diagram": {"x1": -160, "x2": -60, "y1": 520, "y2": 620}}}
}
zoh2 = DiscreteComponents.ZeroOrderHold() {
"Dyad": {"placement": {"diagram": {"x1": 300, "x2": 400, "y1": 520, "y2": 620}}}
}
structural parameter Np::Integer = 10
"Reference"
variable r::Real
"Plant output, semi-continuous actuator"
variable y1::Real
"Physical input, semi-continuous actuator"
variable u1::Real
"On/off decision of the semi-continuous actuator"
variable delta::Real
"Capacity above the minimum of the semi-continuous actuator"
variable z::Real
"Plant output, continuous actuator"
variable y2::Real
"Physical input, continuous actuator"
variable u2::Real
relations
r = refsum.y
y1 = plant1.y
u1 = usum.y
delta = zohd.y
z = zohz.y
y2 = plant2.y
u2 = zoh2.y
connect(step1.y, refsum.u1) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": -601.6666666666666, "y": 200}, {"x": -601.6666666666666, "y": 240}],
"E": 2
}
],
"path": {}
}
}
connect(step2.y, refsum.u2) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": -601.6666666666666, "y": 340}, {"x": -601.6666666666666, "y": 300}],
"E": 2
}
],
"path": {}
}
}
connect(refsum.y, sr1.u, sr2.u) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [{"x": -420, "y": 270}, {"x": -420, "y": 110}], "E": 2},
{"S": 3, "M": [{"x": -420, "y": 570}], "E": -1}
],
"junctions": [{"x": -420, "y": 270}],
"path": {}
}
}
connect(sr1.y, mpc1.r[1]) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -203, "y": 110}, {"x": -203, "y": 72}], "E": 2}],
"path": {}
}
}
connect(plant1.y, sy1.u) {}
connect(sy1.y, mpc1.y[1], clock1.y) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 760, "y": 110},
{"x": 760, "y": 280},
{"x": -190, "y": 280},
{"x": -190, "y": 144}
],
"E": 2
},
{"S": 2, "M": [{"x": -220, "y": 144}, {"x": -220, "y": 290}], "E": 3}
],
"path": {}
}
}
connect(mpc1.u[1], zohd.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -15, "y": 98}, {"x": -15, "y": 50}], "E": 2}],
"path": {}
}
}
connect(mpc1.u[2], zohz.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -15, "y": 98}, {"x": -15, "y": 190}], "E": 2}],
"path": {}
}
}
connect(zohd.y, usum.u1) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 225.66666666666666, "y": 50}, {"x": 225.66666666666666, "y": 80}],
"E": 2
}
],
"path": {}
}
}
connect(zohz.y, usum.u2) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 225.66666666666666, "y": 190}, {"x": 225.66666666666666, "y": 140}],
"E": 2
}
],
"path": {}
}
}
connect(usum.y, plant1.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(sr2.y, mpc2.r[1]) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -203, "y": 570}, {"x": -203, "y": 532}], "E": 2}],
"path": {}
}
}
connect(plant2.y, sy2.u) {}
connect(sy2.y, mpc2.y[1], clock2.y) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [
{"x": 750, "y": 570},
{"x": 750, "y": 660},
{"x": -180, "y": 660},
{"x": -180, "y": 604}
],
"E": 2
},
{"S": 2, "M": [{"x": -230, "y": 604}, {"x": -230, "y": 730}], "E": 3}
],
"path": {}
}
}
connect(mpc2.u[1], zoh2.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": -15, "y": 558}, {"x": -15, "y": 570}], "E": 2}],
"path": {}
}
}
connect(zoh2.y, plant2.u) {}
metadata {"Dyad": {"doc": {"behavior": false}}}
endTest Cases ​
No test cases defined.
Related ​
Examples
Experiments
Analyses