Reproducible study of motion-jerk outcomes — explore how STEP, RAMP, and S-velocity profiles trade smoothness for responsiveness on a simulated omnidirectional waiter robot.
New to motion-jerk control? Run python demo.py — the interactive sandbox is designed for students, roboticists, and generalists to learn, build on, and experiment with the four velocity profiles in a unified scene.
| Profile | What it does | Smoothness class |
|---|---|---|
STEP |
Snap each axis to the commanded velocity on the next tick. | Discontinuous v — high jerk. |
RAMP |
Linear-rate ramp on linear axes; yaw snaps. | Continuous v, jumpy a. |
SPLINE_X |
Jerk-bounded concave-then-convex S-curve on linear_x only; yaw snaps with a CRUISE-only cap. |
Continuous a, bounded j. |
SPLINE_PLANAR |
Same S-curve on the resultant planar speed magnitude; direction tracks cmd_vel. |
Continuous a, bounded j; spillage-safe. |
Cycle order in the sandbox: press M → STEP → RAMP → SPLINE_X → SPLINE_PLANAR.
The SPLINE_X and SPLINE_PLANAR profiles implement the jerk-limited S-velocity profile from Wan et al., Waiter Robots Conveying Drinks, Technologies 8(3):44, MDPI, 2020. SPLINE_PLANAR is an engineering extension developed by Alis Lin during her SIT capstone project. See smoovel/README.md for the full paper citation, BibTeX, and funding details.
This codebase is a simplified simulation adaptation of the original work performed at Singapore Institute of Technology — Newcastle University (SIT-NU) on physical waiter robot platforms. The simulation is intended as a reference implementation of the algorithms; some aspects of the deployed system are abstracted, simplified, or omitted entirely.
In particular, the simulation may not fully represent:
- Behavioural recovery — failure detection, fallback strategies, and re-planning under fault are not represented.
- Empirical jerk-spill tolerance — the literature offers limited study on the exact jerk magnitudes that cause spills for a given liquid, container, or conveyor geometry. The original work focused on mitigating jerk rather than characterising spill tolerance; this sim uses a single configurable threshold (
JERK_RED_THRESHOLD = 99 m/s³) as a stand-in. - Hardware dynamics — chattering from omniwheel rollers, rolling friction, motor torque limits, caster physics, sensor noise, body-damping behaviour, and other real-world effects are not modelled. The sim uses a single first-order body-damping time constant (
body_tau = 0.10 s) as a coarse stand-in.
Treat this codebase as the algorithmic core of the published work — useful for understanding, replicating, and building on the algorithms and the computational-dynamics problem — but not as a faithful representation of the deployed hardware system.
- Python 3.9 or newer — check with
python3 --version - pip — bundled with modern Python
- git — for cloning the repository
The algorithm class (SmoothServer) itself depends only on the Python standard library. The interactive demo additionally needs pygame.
git clone https://github.com/AshWan13/jerk-computation.git
cd jerk-computation
pip install -e ".[pygame]"Other install variants:
pip install -e .— algorithm class only. Use this if you just want to importSmoothServerinto your own project.pip install -e ".[pygame]"— adds pygame for the interactive teleop sandbox.pip install -e ".[dev]"— adds pytest, ruff, black for development.
From the repository root:
python demo.pyThis launches the interactive pygame sandbox. You'll see one waiter robot in the centre of a tiled floor; drop a Bar Set from the right-hand toolbar, place a Human or two, and drive the robot through the bar workflow using the controls below. The HUD across the top shows live state (selected robot, velocity scale, motion-state bars, peak jerk).
from smoovel.smooth_run import SmoothServer, Twist, VelocityProfile
s = SmoothServer(robot_id=1, profile=VelocityProfile.SPLINE_PLANAR)
s.ingest_cmd_vel(Twist(linear_x=0.30, angular_z=0.10))
xfm, state = s.step()macOS (Apple Silicon and Intel)
Tested on macOS 12+ with Python 3.9–3.12. The cleanest setup uses a virtualenv inside the cloned repo so pip is fresh enough for PEP 660 editable installs:
cd jerk-computation
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
pip install -e ".[pygame]"
python demo.pyTo leave the venv: deactivate. To re-enter next time: source .venv/bin/activate from the repo root.
Anaconda users: the conda (base) env often ships with an older pip that fails on PEP 660 editable installs. Use a dedicated conda env instead:
conda create -n jerk-comp python=3.11 -y
conda activate jerk-comp
pip install -e ".[pygame]"
python demo.pyIf pygame fails to load with an SDL error, install the system SDL2 dependency via Homebrew:
brew install sdl2 sdl2_image sdl2_ttf
pip install --force-reinstall pygameWindows (10 / 11)
Tested with Python 3.9–3.12 installed from python.org. pygame installs cleanly via pip wheels — no extra system packages needed.
In PowerShell:
python --version
pip install -e ".[pygame]"
python demo.pyIf the demo window doesn't appear, confirm you're not running inside WSL without a display server (use a native Windows Python install, or set up an X server like VcXsrv if you must use WSL).
Linux (Ubuntu / Debian)
Install Python tooling, the venv module, and SDL2 display libraries in one go:
sudo apt update
sudo apt install -y python3 python3-pip python3-venv python-is-python3 \
libsdl2-dev libsdl2-image-dev libsdl2-ttf-devThen create a virtualenv inside the repo and install:
cd jerk-computation
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
pip install -e ".[pygame]"
python demo.pyIf running over SSH or in a container, enable X11 forwarding (ssh -X user@host) or use a virtual display (xvfb-run python demo.py).
Without launching pygame, verify the package is importable:
python -c "from smoovel.smooth_run import SmoothServer; print('OK')"SmoothServer is a transport-free Python object — it has no dependency on pygame, ROS, or restaurant_objects. Drop it between your existing velocity-command source (joystick / planner / move_base) and your base controller to get jerk-bounded output without changing anything else in your pipeline.
One SmoothServer instance per robot. Each tick, push the latest inputs through ingest_* callbacks and read back the smoothed twist from step():
from smoovel.smooth_run import SmoothServer, Twist, Pose, VelocityProfile
# Construct once
s = SmoothServer(
robot_id=1,
dt=1/30.0, # match your control loop rate
profile=VelocityProfile.SPLINE_PLANAR, # or STEP / RAMP / SPLINE_X
max_jerk_si=1.0, # Smoovel jerk cap, m/s^3
body_tau=0.10, # drivetrain damping, s
)
# Per tick (e.g. in a ROS2 timer at 30 Hz)
def on_tick():
s.ingest_cmd_vel(Twist(linear_x=cmd.vx,
linear_y=cmd.vy,
angular_z=cmd.wz))
s.ingest_pose(Pose(x=amcl.x, y=amcl.y, yaw=amcl.yaw)) # optional
s.ingest_goal(Pose(x=goal.x, y=goal.y)) # optional
xfm, state = s.step()
publish_base_controller(linear_x=xfm.linear_x,
linear_y=xfm.linear_y,
angular_z=xfm.angular_z)| Call | Required? | Effect if omitted |
|---|---|---|
ingest_cmd_vel(twist) |
required | Nothing to smooth — output stays at zero. |
ingest_pose(pose) |
optional | Skip if you don't use ingest_goal either. Pose is only consulted for the TERMINAL approach. |
ingest_goal(pose) |
optional | Without a goal the FSM never enters TERMINAL — the controller stays in CRUISE / THROTTLE / BRAKE around your cmd_vel. Pass None to clear. |
set_payload(mode) |
optional | Purely informational unless you bind profile-by-payload via set_profile_for_payload. |
set_profile(profile) |
optional | Defaults to SPLINE_PLANAR. Toggle at runtime to compare profiles. |
ingest_collision_event(blocked_dv) |
optional | Feed in depth / dt when your collision resolver pushes the robot back; the jerk readout will reflect it. |
reset() |
optional | Zeros all internal ramps and returns to DOCK. Use on E-stop, teleop-mode-switch, or robot re-localisation. |
Twist and Pose carry built-in lazy adapters for the standard geometry_msgs types — the ROS imports only happen when you call the methods, so the rest of the algorithm runs anywhere:
from smoovel.smooth_run import Twist, Pose
# geometry_msgs.msg.Twist <-> smoovel.Twist
cmd_smoovel = Twist.from_ros2(cmd_ros)
cmd_ros_out = xfm_smoovel.to_ros2()
# geometry_msgs.msg.Pose2D <-> smoovel.Pose
pose_smoovel = Pose.from_ros2(pose2d_ros)
pose2d_ros = pose_smoovel.to_ros2()
# geometry_msgs.msg.Pose (3D, quaternion) <-> smoovel.Pose
pose_smoovel = Pose.from_ros2_pose(pose_ros)
pose_ros = pose_smoovel.to_ros2_pose()A minimal rclpy node skeleton then looks like:
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist as RosTwist, Pose2D
from smoovel.smooth_run import SmoothServer, Twist, Pose, VelocityProfile
class SmoovelNode(Node):
def __init__(self):
super().__init__("smoovel_wm1")
self.s = SmoothServer(robot_id=1, dt=1/30.0,
profile=VelocityProfile.SPLINE_PLANAR)
self.create_subscription(RosTwist, "/wm1/cmd_vel",
self._on_cmd, 10)
self.create_subscription(Pose2D, "/wm1/amcl_pose",
self._on_pose, 10)
self.pub = self.create_publisher(RosTwist, "/wm1/xfm_vel", 10)
self.create_timer(1/30.0, self._tick)
def _on_cmd(self, msg): self.s.ingest_cmd_vel(Twist.from_ros2(msg))
def _on_pose(self, msg): self.s.ingest_pose(Pose.from_ros2(msg))
def _tick(self):
xfm, _ = self.s.step()
self.pub.publish(xfm.to_ros2())When you construct SmoothServer(verbose=True), FSM transitions and profile / payload changes are emitted through the standard library logger at INFO level under the name "smoovel". Opt in via:
import logging
logging.basicConfig(level=logging.INFO) # or configure the "smoovel" loggerBy default the messages are silently dropped — importing this package produces no stdout output.
The SimBus class in smoovel.smooth_run is an in-process bus used by the pygame demo to step many SmoothServer instances against a toy pose integrator. It is not part of the integration surface and should not be wired into a real nav stack — instantiate SmoothServer directly instead.
Once demo.py is launched, the interactive sandbox accepts the keyboard and mouse controls below.
Number keys 1–5 select which robot is currently active for control. Up to five robots may operate simultaneously in the same scene; all share the same control set.
| Input | Action |
|---|---|
W A S D |
Drive forward / rotate left / reverse / rotate right (differential) |
↑ ↓ ← → (arrow keys) |
Drive forward / reverse / strafe left / strafe right (holonomic) |
Q |
Increase speed scale |
E |
Decrease speed scale |
LMB-click (empty floor) |
Set point-to-point nav goal (HYBRID autopilot) |
Combine WASD and the arrow keys to compose a hybrid differential + holonomic command in a single chord.
M— cycle the active velocity profile: STEP → RAMP → SPLINE_X → SPLINE_PLANAR.
P— toggle pathfinding algorithm between A* and Dijkstra's.
The right-hand toolbar is split into two palettes:
- Operations — Bar Set (drops a complete bar counter + worker + 3 spawn / despawn tables) and Human (consumer agent that picks up filled cups from any robot in range).
- Obstacles — Table, Chair, Wall, Pillar, S.Door, P.Door.
Spawning rules:
- Click an obstacle button, then click in the scene for fixed-size obstacles, or click-and-drag for variable-size obstacles (walls, pillars) and drag-orientation obstacles (Bar Set — drag direction sets the bar's facing direction, snapped to 0° / 90° / 180° / 270°).
- Double-click any obstacle to despawn it. Double-clicking any bar-set component (bar, worker, or one of the three back-row tables) despawns the whole set.
- Single-click a coffee / cocktail spawn table within a Bar Set to spawn a fresh drink on top of it; the bar worker will pick it up automatically and deliver to a robot parked in front of the bar.
SPACE— emergency stop the selected robot.X— cancel the current navigation goal for the selected robot.C— clear all obstacles from the scene.R— reset the scenario (back to one robot, empty scene).Esc— quit.
ModuleNotFoundError: No module named 'smoovel'— Run from the repository root afterpip install -e ".[pygame]". Thepythonyou invoke must be the same interpreter where you ran pip.- pygame window won't open /
No available video deviceon Linux — Installlibsdl2-dev, then either runpython demo.pyfrom a desktop session or enable X11 forwarding (ssh -X user@host) over SSH. - Slow rendering / unresponsive demo — Close inactive windows, reduce the robot count, or shrink the bar-set count.
pip install -e .fails on Apple Silicon — Update pip first (pip install -U pip setuptools wheel), then re-run.
Machine-readable citation metadata is in CITATION.cff. The per-module BibTeX for the underlying paper lives in smoovel/README.md.
Released under the BSD 3-Clause License. Copyright © 2026 Singapore Institute of Technology — Newcastle University and Ash Wan Yaw Sang.
jerk-computation/
├── README.md ← you are here
├── LICENSE BSD 3-Clause
├── CITATION.cff
├── pyproject.toml
├── requirements.txt
├── .gitignore
│
├── demo.py unified pygame teleop sandbox
│
├── assets/
│ └── hero.png sandbox screenshot used at the top of this README
│
├── common/ shared utilities
│ ├── __init__.py
│ └── restaurant_objects.py obstacle palette + drink-bar workflow + human AI
│
└── smoovel/ Jerk-Bounded S-Velocity FSM (Technologies 2020)
├── __init__.py
├── smooth_run.py SmoothServer + SVelocityProfile + SimBus
└── README.md paper details + BibTeX + DOI
This work has been adapted into a simplified simulation. The original work was carried out at Singapore Institute of Technology — Newcastle University (SIT-NU) under the supervision of Dr. Michael Lau Wai Shing. The original platform development was undertaken as part of the SIT Capstone Project; no external funding supported this codebase.
The SPLINE_PLANAR profile and the smoothed planar-motion behaviour it enables were developed by Alis Lin as part of her SIT capstone project, extending the published S-velocity profile to the planar-magnitude axis with live direction tracking.
The codebase builds on the open-source scientific Python community — pygame in particular.
Dr. Ash Wan Yaw Sang — yaw_sang94@hotmail.com · LinkedIn
