Sources.Tests.KinematicPTP ​
MSL cross-validation harness for KinematicPTP.
Part A mirrors ModelicaTest.Blocks.KinematicPTP — two single-axis moves (both qd_max = 3, qdd_max = 4, startTime = 0.5):
ptpA— moveq0 = 1 -> q1 = 2(distance 1): a triangular profile (never reaches the speed limit).ptpB— moveq0 = 1 -> q1 = 5(distance 4): a trapezoidal profile.
Part B keeps the Modelica.Blocks.Examples.PID_Controller reference: ptpC reproduces that example's motion (q0 = 0 -> q1 = pi/2, qd_max = qdd_max = 1, startTime = 0.5). The example drives a KinematicPTP acceleration into an Integrator; integratorC.y is that open-loop velocity trajectory, only zero again at ~3.07 s.
Part C exercises the multi-axis synchronization: all axes share one trapezoidal profile scaled by their own travel, so every axis finishes together at the bottleneck axis' endTime (~5 s here).
ptpSync2(nout = 2): axis 1 (0 -> 12) is the bottleneck, axis 2 (0 -> 3) is slowed; peak velocities 4 and 1.ptpSync3(nout = 3): axes move 0 -> 12 / 3 / 6 with equal limits; peak velocities 4, 1, 2 — all reached and returned to zero at the same instant.
Each axis' acceleration output feeds an Integrator, which gives the otherwise- stateless source harness a differential state and exposes a scalar velocity signal (integrator.y = ∫qdd) for the snapshot tests. The blocks' own q/qd outputs are cross-validated in Julia via symbolic indexing (model.ptpA.qd[1]), see test/msl_validation/kinematic_ptp.jl.
Usage ​
BlockComponents.Sources.Tests.KinematicPTP()
Behavior ​
Source ​
"""
MSL cross-validation harness for KinematicPTP.
Part A mirrors `ModelicaTest.Blocks.KinematicPTP` — two single-axis moves (both
`qd_max = 3`, `qdd_max = 4`, `startTime = 0.5`):
- `ptpA` — move `q0 = 1 -> q1 = 2` (distance 1): a **triangular** profile (never
reaches the speed limit).
- `ptpB` — move `q0 = 1 -> q1 = 5` (distance 4): a **trapezoidal** profile.
Part B keeps the `Modelica.Blocks.Examples.PID_Controller` reference: `ptpC`
reproduces that example's motion (`q0 = 0 -> q1 = pi/2`, `qd_max = qdd_max = 1`,
`startTime = 0.5`). The example drives a KinematicPTP acceleration into an
`Integrator`; `integratorC.y` is that open-loop velocity trajectory, only zero
again at ~3.07 s.
Part C exercises the multi-axis synchronization: all axes share one trapezoidal
profile scaled by their own travel, so every axis finishes together at the
bottleneck axis' `endTime` (~5 s here).
- `ptpSync2` (`nout = 2`): axis 1 (0 -> 12) is the bottleneck, axis 2 (0 -> 3) is
slowed; peak velocities 4 and 1.
- `ptpSync3` (`nout = 3`): axes move 0 -> 12 / 3 / 6 with equal limits; peak
velocities 4, 1, 2 — all reached and returned to zero at the same instant.
Each axis' acceleration output feeds an `Integrator`, which gives the otherwise-
stateless source harness a differential state and exposes a scalar velocity signal
(`integrator.y = ∫qdd`) for the snapshot tests. The blocks' own `q`/`qd` outputs
are cross-validated in Julia via symbolic indexing (`model.ptpA.qd[1]`), see
`test/msl_validation/kinematic_ptp.jl`.
"""
test component KinematicPTP
"PTP with a short (triangular) move, 1 -> 2"
ptpA = BlockComponents.Sources.KinematicPTP(nout = 1, q0 = [1.0], q1 = [2.0], qd_max = [3.0], qdd_max = [4.0], startTime = 0.5) {"Dyad": {"placement": {"diagram": {"x1": 20, "y1": 20, "x2": 120, "y2": 120}}}}
"PTP with a longer (trapezoidal) move, 1 -> 5"
ptpB = BlockComponents.Sources.KinematicPTP(nout = 1, q0 = [1.0], q1 = [5.0], qd_max = [3.0], qdd_max = [4.0], startTime = 0.5) {
"Dyad": {"placement": {"diagram": {"x1": 20, "y1": 220, "x2": 120, "y2": 320}}}
}
"PID_Controller reference move, 0 -> pi/2 (qd_max = qdd_max = 1)"
ptpC = BlockComponents.Sources.KinematicPTP(nout = 1, q0 = [0.0], q1 = [1.570796326794897], qd_max = [1.0], qdd_max = [1.0], startTime = 0.5) {
"Dyad": {"placement": {"diagram": {"x1": 10, "y1": 450, "x2": 110, "y2": 550}}}
}
"Synchronized 2-axis move (axis 1 bottleneck 0 -> 12, axis 2 slowed 0 -> 3)"
ptpSync2 = BlockComponents.Sources.KinematicPTP(nout = 2, q0 = [0.0, 0.0], q1 = [12.0, 3.0], qd_max = [4.0, 4.0], qdd_max = [2.0, 2.0]) {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 20, "x2": 600, "y2": 120}}}
}
"Synchronized 3-axis move (axis 1 bottleneck 0 -> 12, axes 2/3 slowed 0 -> 3/6)"
ptpSync3 = BlockComponents.Sources.KinematicPTP(nout = 3, q0 = [0.0, 0.0, 0.0], q1 = [12.0, 3.0, 6.0], qd_max = [4.0, 4.0, 4.0], qdd_max = [2.0, 2.0, 2.0]) {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 350, "x2": 600, "y2": 450}}}
}
"Integrates ptpA acceleration -> velocity A (state provider + scalar signal)"
integratorA = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 240, "y1": 20, "x2": 340, "y2": 120}}}
}
"Integrates ptpB acceleration -> velocity B"
integratorB = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 240, "y1": 220, "x2": 340, "y2": 320}}}
}
"Integrates ptpC acceleration -> velocity C (PID reference)"
integratorC = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 240, "y1": 450, "x2": 340, "y2": 550}}}
}
"Integrates ptpSync2 axis 1 acceleration -> velocity"
int2A = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 20, "x2": 820, "y2": 120}}}
}
"Integrates ptpSync2 axis 2 acceleration -> velocity"
int2B = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 160, "x2": 820, "y2": 260}}}
}
"Integrates ptpSync3 axis 1 acceleration -> velocity"
int3A = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 320, "x2": 820, "y2": 420}}}
}
"Integrates ptpSync3 axis 2 acceleration -> velocity"
int3B = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 460, "x2": 820, "y2": 560}}}
}
"Integrates ptpSync3 axis 3 acceleration -> velocity"
int3C = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 600, "x2": 820, "y2": 700}}}
}
relations
connect(ptpA.qdd[1], integratorA.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(ptpB.qdd[1], integratorB.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(ptpC.qdd[1], integratorC.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(ptpSync2.qdd[1], int2A.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(ptpSync2.qdd[2], int2B.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 680, "y": 70}, {"x": 680, "y": 210}], "E": 2}],
"path": {}
}
}
connect(ptpSync3.qdd[1], int3A.u) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 641.6666666666666, "y": 400}, {"x": 641.6666666666666, "y": 370}],
"E": 2
}
],
"path": {}
}
}
connect(ptpSync3.qdd[2], int3B.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 640, "y": 400}, {"x": 640, "y": 510}], "E": 2}],
"path": {}
}
}
connect(ptpSync3.qdd[3], int3C.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 640, "y": 400}, {"x": 640, "y": 650}], "E": 2}],
"path": {}
}
}
metadata {
"Dyad": {
"icons": {"default": "dyad://BlockComponents/Example.svg"},
"tests": {
"case1": {
"stop": 3,
"atol": {"integratorA.y": 0.001, "integratorB.y": 0.001},
"expect": {"signals": ["integratorA.y", "integratorB.y"]}
},
"case2": {
"stop": 4,
"atol": {"integratorC.y": 0.001},
"expect": {"signals": ["integratorC.y"]}
},
"case3": {
"stop": 6,
"atol": {
"int2A.y": 0.001,
"int2B.y": 0.001,
"int3A.y": 0.001,
"int3B.y": 0.001,
"int3C.y": 0.001
},
"expect": {"signals": ["int2A.y", "int2B.y", "int3A.y", "int3B.y", "int3C.y"]}
}
}
}
}
endFlattened Source
"""
MSL cross-validation harness for KinematicPTP.
Part A mirrors `ModelicaTest.Blocks.KinematicPTP` — two single-axis moves (both
`qd_max = 3`, `qdd_max = 4`, `startTime = 0.5`):
- `ptpA` — move `q0 = 1 -> q1 = 2` (distance 1): a **triangular** profile (never
reaches the speed limit).
- `ptpB` — move `q0 = 1 -> q1 = 5` (distance 4): a **trapezoidal** profile.
Part B keeps the `Modelica.Blocks.Examples.PID_Controller` reference: `ptpC`
reproduces that example's motion (`q0 = 0 -> q1 = pi/2`, `qd_max = qdd_max = 1`,
`startTime = 0.5`). The example drives a KinematicPTP acceleration into an
`Integrator`; `integratorC.y` is that open-loop velocity trajectory, only zero
again at ~3.07 s.
Part C exercises the multi-axis synchronization: all axes share one trapezoidal
profile scaled by their own travel, so every axis finishes together at the
bottleneck axis' `endTime` (~5 s here).
- `ptpSync2` (`nout = 2`): axis 1 (0 -> 12) is the bottleneck, axis 2 (0 -> 3) is
slowed; peak velocities 4 and 1.
- `ptpSync3` (`nout = 3`): axes move 0 -> 12 / 3 / 6 with equal limits; peak
velocities 4, 1, 2 — all reached and returned to zero at the same instant.
Each axis' acceleration output feeds an `Integrator`, which gives the otherwise-
stateless source harness a differential state and exposes a scalar velocity signal
(`integrator.y = ∫qdd`) for the snapshot tests. The blocks' own `q`/`qd` outputs
are cross-validated in Julia via symbolic indexing (`model.ptpA.qd[1]`), see
`test/msl_validation/kinematic_ptp.jl`.
"""
test component KinematicPTP
"PTP with a short (triangular) move, 1 -> 2"
ptpA = BlockComponents.Sources.KinematicPTP(nout = 1, q0 = [1.0], q1 = [2.0], qd_max = [3.0], qdd_max = [4.0], startTime = 0.5) {"Dyad": {"placement": {"diagram": {"x1": 20, "y1": 20, "x2": 120, "y2": 120}}}}
"PTP with a longer (trapezoidal) move, 1 -> 5"
ptpB = BlockComponents.Sources.KinematicPTP(nout = 1, q0 = [1.0], q1 = [5.0], qd_max = [3.0], qdd_max = [4.0], startTime = 0.5) {
"Dyad": {"placement": {"diagram": {"x1": 20, "y1": 220, "x2": 120, "y2": 320}}}
}
"PID_Controller reference move, 0 -> pi/2 (qd_max = qdd_max = 1)"
ptpC = BlockComponents.Sources.KinematicPTP(nout = 1, q0 = [0.0], q1 = [1.570796326794897], qd_max = [1.0], qdd_max = [1.0], startTime = 0.5) {
"Dyad": {"placement": {"diagram": {"x1": 10, "y1": 450, "x2": 110, "y2": 550}}}
}
"Synchronized 2-axis move (axis 1 bottleneck 0 -> 12, axis 2 slowed 0 -> 3)"
ptpSync2 = BlockComponents.Sources.KinematicPTP(nout = 2, q0 = [0.0, 0.0], q1 = [12.0, 3.0], qd_max = [4.0, 4.0], qdd_max = [2.0, 2.0]) {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 20, "x2": 600, "y2": 120}}}
}
"Synchronized 3-axis move (axis 1 bottleneck 0 -> 12, axes 2/3 slowed 0 -> 3/6)"
ptpSync3 = BlockComponents.Sources.KinematicPTP(nout = 3, q0 = [0.0, 0.0, 0.0], q1 = [12.0, 3.0, 6.0], qd_max = [4.0, 4.0, 4.0], qdd_max = [2.0, 2.0, 2.0]) {
"Dyad": {"placement": {"diagram": {"x1": 500, "y1": 350, "x2": 600, "y2": 450}}}
}
"Integrates ptpA acceleration -> velocity A (state provider + scalar signal)"
integratorA = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 240, "y1": 20, "x2": 340, "y2": 120}}}
}
"Integrates ptpB acceleration -> velocity B"
integratorB = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 240, "y1": 220, "x2": 340, "y2": 320}}}
}
"Integrates ptpC acceleration -> velocity C (PID reference)"
integratorC = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 240, "y1": 450, "x2": 340, "y2": 550}}}
}
"Integrates ptpSync2 axis 1 acceleration -> velocity"
int2A = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 20, "x2": 820, "y2": 120}}}
}
"Integrates ptpSync2 axis 2 acceleration -> velocity"
int2B = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 160, "x2": 820, "y2": 260}}}
}
"Integrates ptpSync3 axis 1 acceleration -> velocity"
int3A = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 320, "x2": 820, "y2": 420}}}
}
"Integrates ptpSync3 axis 2 acceleration -> velocity"
int3B = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 460, "x2": 820, "y2": 560}}}
}
"Integrates ptpSync3 axis 3 acceleration -> velocity"
int3C = BlockComponents.Continuous.Integrator(x0 = 0.0) {
"Dyad": {"placement": {"diagram": {"x1": 720, "y1": 600, "x2": 820, "y2": 700}}}
}
relations
connect(ptpA.qdd[1], integratorA.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(ptpB.qdd[1], integratorB.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(ptpC.qdd[1], integratorC.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(ptpSync2.qdd[1], int2A.u) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "renderStyle": "standard"}}
connect(ptpSync2.qdd[2], int2B.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 680, "y": 70}, {"x": 680, "y": 210}], "E": 2}],
"path": {}
}
}
connect(ptpSync3.qdd[1], int3A.u) {
"Dyad": {
"edges": [
{
"S": 1,
"M": [{"x": 641.6666666666666, "y": 400}, {"x": 641.6666666666666, "y": 370}],
"E": 2
}
],
"path": {}
}
}
connect(ptpSync3.qdd[2], int3B.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 640, "y": 400}, {"x": 640, "y": 510}], "E": 2}],
"path": {}
}
}
connect(ptpSync3.qdd[3], int3C.u) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 640, "y": 400}, {"x": 640, "y": 650}], "E": 2}],
"path": {}
}
}
metadata {
"Dyad": {
"icons": {"default": "dyad://BlockComponents/Example.svg"},
"tests": {
"case1": {
"stop": 3,
"atol": {"integratorA.y": 0.001, "integratorB.y": 0.001},
"expect": {"signals": ["integratorA.y", "integratorB.y"]}
},
"case2": {
"stop": 4,
"atol": {"integratorC.y": 0.001},
"expect": {"signals": ["integratorC.y"]}
},
"case3": {
"stop": 6,
"atol": {
"int2A.y": 0.001,
"int2B.y": 0.001,
"int3A.y": 0.001,
"int3B.y": 0.001,
"int3C.y": 0.001
},
"expect": {"signals": ["int2A.y", "int2B.y", "int3A.y", "int3B.y", "int3C.y"]}
}
}
}
}
endTest Cases ​
Test Case case1 ​
pltpltTest Case case2 ​
pltTest Case case3 ​
pltpltpltpltpltRelated ​
Examples
Experiments
Analyses
Tests