Skip to content
LIBRARY
Sources.Tests.KinematicPTP.md

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

Usage ​

BlockComponents.Sources.Tests.KinematicPTP()

Behavior ​

Source ​

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


Test Cases ​

Test Case case1 ​

julia
plt

julia
plt

Test Case case2 ​

julia
plt

Test Case case3 ​

julia
plt

julia
plt

julia
plt

julia
plt

julia
plt