Flying Knots in Newton

Replicating the mid-air overhand "flying knot" of Suresh & Atkeson (flying-knots.github.io, arXiv:2602.21302) in Newton with SolverVBD cable joints: an xArm7 bolted to a fixed pedestal replays the paper's throw trajectory and a simulated weighted rope ties itself into an overhand knot in under a second of motion.

Generated 2026-07-03 from CUDA runs on NVIDIA L40; revised 2026-07-07: high-resolution 72-segment cable preset resolves the knot shape (9/9); 2026-07-06: the MuJoCo-coupled variant ties the knot fully dynamically (12/12). Earlier revisions: 2026-07-05 MuJoCo-coupled variant, 2026-07-03 a fixed pedestal (see Revision). Newton branch flying-knots off origin/main 31b06fe0.

Result

Full sequence, real time: settle → 0.56 s throw → free flight → slow lift. The rope root replays the digitized end-effector command of the paper; the xArm7 tracks the same trajectory through Newton's IK module and is animated kinematically. The knot cinches against the tip weight during the lift and remains in the hanging rope.

Throw at 4× slow motion, side view: the rope is thrown over the top, opens into a loop, and the weighted tip whips through the loop — the paper's "collision" critical point.

Close-up of the lift phase: the loose knot travels down the rope and tightens against the weighted tip.

Knot-tracking camera, 4× slow motion: the camera follows the smoothed centroid of the rope's self-contact cluster, keeping the evolving knot centered through the whole tying sequence — loop formation, the tip threading it, and the knotted rope swinging down. This clip uses the 0.53 s-throw variant of the tuned command (time scale 0.75, 60 g tip, bend 5×10−4; also 100% knot success in our trials), which leaves the finished knot higher up the rope — final knot position 0.78 along the arc length instead of 0.88, so a clear free tail hangs below it instead of the knot cinching directly against the end weight. (The static side view above loses the knot below the frame as the rope falls; here the knot cluster stays fully in view for all 120 recorded frames, max |NDC| 0.88.)

Human demo vs Newton simulation phase comparison
Phase-by-phase comparison: the paper's slow-motion human demonstration (top, © Suresh & Atkeson, flying-knots.github.io) against the Newton SolverVBD replication (bottom, 4× slow-motion side view). Rest → upswing → loop opens → tip threads the loop → knot in flight → knot in the hanging rope.
Knot success (tuned command, 20 trials)
17/20
Knot success (full example, fixed-base arm, 8 trials)
7/8
Verbatim demonstration replay
0 knots
Fixed-base IK error (mean / max)
0.04 / 13.5 mm

What the paper released, and what it didn't

The public code (flying_knots_public) contains the full learning pipeline (mocap capture, IK, task-level ILC, three rope models) but ships no recorded demonstrations or commands — all trial data lives under a local $FLYING_KNOT_DATA directory that was never published, and the repo has no releases. The project site hosts videos only.

The usable quantitative record of the throw is the paper's follow-through figure: the executed end-effector x/y/z position trajectory of the overhand throw over 0.7 s. I digitized the three curves by color extraction, calibrated the axes from the tick marks, and refit the paper's own command parametrization (Appendix D): an 8-control-point Bézier per axis. Fit residual is below 3.6 cm everywhere (below 1.5 cm on z, the dominant axis). The figure plots both the demonstration and the learned command; they differ by a few centimeters, and the extraction blends them where they overlap.

Digitized trajectory with Bezier fit
Digitized end-effector trajectory (dots) and the 8-knot Bézier refit (lines) used as the throw command. Compare with the follow-through figure at flying-knots.github.io.

Physical parameters come from the paper: rope 1 (#10 sash spot cord) is 1.1 m long, 9 mm diameter, 0.040 kg/m, with an affixed end weight; the handle is 15 cm flange-to-tip; the command space is 10-DOF (7 arm joints + 3 base translations); the initial hand height must let the rope dangle freely (zmin = 1.2 m).

Newton scene

IK tracking error
IK flange tracking error over the sequence with the fixed base (7-DOF). Sub-millimeter everywhere except a single 13.5 mm peak inside the 0.56 s throw window, where the commanded end-effector speed exceeds 4 m/s at the edge of the fixed-base workspace — a one-frame handle/rope-root offset that is invisible at speed.

Revision: keeping the pedestal fixed

The first version of this report gave the arm the paper's 3-DOF translating base (their Bézier command is 10-DOF: 7 joints + 3 base translations) as a D6 joint with ±0.2 m limits, and parented the pedestal to the base link. The IK solver exploited this freely: the solved base trajectory slid 0.25 m in x and rail-to-rail 0.40 m in y and z during the throw, so the whole robot — pedestal included — visibly flew around the scene. Physically plausible for their gantry-style setup, but it read as an unrealistic floating robot.

The fix removes the base joint entirely (the URDF root is welded to the world) and instead searches for a mount pose from which the fixed-base arm can reach the entire command: candidate mounts are pruned by a workspace annulus around the shoulder, then scored by solving the full 7-DOF IK schedule (scripts/flying_knot/fixed_base_search.py). The selected mount (x = 0.30, y = −0.20, flange height 1.30 m) tracks the whole trajectory with mean 0.03 mm / max 13.5 mm flange error (the movable base achieved 8.7 mm max — the fixed base costs 5 mm at one whip frame). The pedestal is now static world geometry with collision enabled, so the rope rests against it rather than passing through. Enabling that contact exposed a real failure mode — the loose knot could snag on the column and be stripped off while the rope is lifted (4/8 with-arm trials) — so the final lift now drifts 0.12 m away from the pedestal, restoring 7/8.

Verification: scripts/flying_knot/verify_fixed_base.py steps the full animation and asserts the base link's world transform never changes and the pedestal is static geometry (shape parent body −1). Result: max transform drift 0.000e+00 over 484 frames. The same assertion now runs inside the example's test_final() on every test execution. The rope simulation itself is untouched by this change — the rope root replays the same command — so the standalone success statistics remain valid; the with-arm trials below were re-run on the fixed-base scene.

Tuning: replaying the demonstration is not enough

Replaying the digitized command verbatim on the paper's rope-1 parameters (0 knots) reproduces the paper's own headline observation: direct tracking of a demonstration fails, because the rope dynamics differ between the demonstrator's rope and the executing system. In the paper the gap is human→robot hardware; here it is real rope→VBD cable. Their answer is task-level ILC over the command; mine is a parameter search over command timing/amplitude and rope properties, playing the same role offline.

A broad sweep (54 runs) showed the failure mode clearly: the rope coils to a transient writhe of ±3 and the loop collapses before the weighted tip threads it, or the loose knot slides off the free end during flight. Two knobs move the collision point decisively: a slightly faster throw (time scale 0.75–0.8, i.e. 0.53–0.56 s instead of 0.7 s) and a heavier tip weight (50–70 g vs. the paper's 18 g) that carries momentum through the loop and later cinches the knot.

Failure mode, 4× slow motion: the verbatim digitized command with the paper's exact rope-1 parameters (18 g tip, 0.7 s throw). The rope coils but the tip never threads the loop, and the rope lands unknotted.

Success, same view: with the tuned command (0.56 s throw, 50 g tip) the tip carries through the loop and the knot survives the landing.

Writhe over time
Rope centerline writhe over time. With the tuned command the writhe jumps to +3.5 as the tip threads the loop 0.4 s after throw onset, then locks near +2.5 as the knot tightens — the signature of a trapped overhand (trefoil) knot. The verbatim command's writhe returns to zero.

Success rates

Because VBD contact resolution is not bitwise deterministic across runs, single successes are meaningless near the critical manifold — the same parameters can knot in one run and miss in the next. All configurations were therefore scored by repeated trials (the paper analogously reports a 40-trial robustness evaluation). Success = final |writhe| > 2 and end-to-end/arc-length ratio < 0.95 in the hanging rope.

Throw time scaleTip mass [g]Bend stiffness [N·m/rad]Success

The success basin is sharp — neighboring configurations flip from 5/5 to 0/5 with a 10 g tip change — which matches the paper's premise that the flying knot is a precision event requiring iteration-level correction rather than robust open-loop replay. The selected default (time scale 0.8, 50 g tip, bend stiffness 2×10−3) achieved 17/20 standalone and 7/8 in the full fixed-base arm example.

Dynamically driven variant: MuJoCo arm + coupled VBD rope

The kinematic example's robot motion is choppy: its per-frame IK flips elbow/wrist branches freely in the 7-DOF nullspace (peak joint velocity 112 rad/s, acceleration 7,800 rad/s²), and per-frame interpolation steps the velocity at 60 Hz. example_cable_flying_knot_mujoco replaces this with a dynamically simulated arm on the experimental coupled-solver framework merged in #2848:

MuJoCo-coupled variant with the searched throw command (--command searched): the dynamically driven arm executes the whip with smooth, physical joint motion, and the rope ties an overhand knot that locks in the hanging rope — fully dynamic end to end.

Throw and capture at 4× slow motion, side view: loop opens, the tip threads it, and the knot survives the landing.

Knot-tracking camera through formation, flight, and the lift that cinches the knot against the tip weight.

Throw at 5× slow motion, kinematic example (left) vs MuJoCo-coupled example (right): the branch-flip twitches of the raw per-frame IK vanish under the C² reference and dynamic tracking.

Smoothness comparison

Max per-joint velocity/acceleration (log scale): peak joint velocity drops 112→11 rad/s, acceleration 7,780→181 rad/s², jerk by 123×.

Getting the knot back: command search against the coupled dynamics

The tuned kinematic command does not knot under the coupled dynamics, and the reason is instructive: the kinematic example's coil formation secretly relied on prescribing the rope-root orientation along the trailing-velocity axis — effectively an unbounded wrist that snaps the root axis at up to 4,800 °/s. A physical free-pivot attachment cannot transmit that input, and the xArm7's wrist cannot reproduce it kinematically (adding an orientation objective drives IK to 100+ rad/s branch flips). The throw command itself had to be re-derived for the coupled dynamics.

A cross-entropy search over Bézier control-point deltas on all seven free control points plus throw timing and tip mass (command_search.py, roughly 300 rollouts total — the offline analogue of the paper's task-level ILC) found it. Two objective terms made the difference over a first, failed search that only rewarded transient coiling: a threading-persistence term (mean |writhe| held in the second after the throw — a coil that collapses scores low, so the optimizer is pushed toward capture, not just coiling) and a dynamic-feasibility penalty on the arm's joint tracking error, which keeps candidate commands executable by the torque-limited MuJoCo arm. The winning command (bundled as --command searched) throws in 0.55 s with a 40 g tip — much closer to the paper's 18 g than the kinematic champion's 50 g — and tracks with 0.058 rad peak joint error.

Coupled-variant knot success (12 trials, incl. 9 under GPU contention)
12/12
Final writhe (locked overhand)
+3.2–3.3
Peak joint tracking error
0.058 rad
Throw / tip weight
0.55 s / 40 g

One sensitivity worth recording: the command is tuned to its own C² reference, and re-solving the IK at a finer target subdivision shifts the reference by millimeters — enough to fall off the knot manifold. The IK discretization is therefore pinned alongside the command in the searched preset: the reference trajectory is the command. This mirrors the paper's central observation that flying-knot commands are precision events; their answer is per-system ILC, ours is a per-system offline search with the same role.

Coupled-framework observations (all on 195454cc, experimental API):

Higher-resolution cable: resolving the knot shape

The 36-segment rope ties reliably but renders the knot bundle as a dozen angular nodes. A high-resolution preset (--command searched-hires) doubles the discretization to 72 segments (15.3 mm at 5 mm radius). Three things were required:

Knot centerline at 36 vs 72 segments
Final hanging knot centerline, node-resolved: the 36-segment bundle is a coarse polygon (12 nodes through the knot); at 72 segments the bundle is a smooth, well-formed overhand loop (24 nodes). Same rope, same physics — twice the fidelity.

High-resolution variant, full sequence: 72 segments, 0.43 s throw, 30 g tip, cinch yank; fully dynamic MuJoCo arm.

Knot-tracking camera at 72 segments: the knot bundle forms, tightens against the tip weight, and reads as a smooth overhand knot rather than a polygon.

In-flight cable PRs

This branch runs on plain origin/main (31b06fe0). JC Chang's (jumyungc) merged VBD-cable work is already in that baseline and this example depends on it (capsule COM origin #2670, absolute VBD damping #2877, rigid-contact energy fix #3147). His open PRs were evaluated but not required: #3122 (split cable stretch/shear + bend/twist constraints) was additionally merged into a scratch branch and re-scored — it shifts the cable dynamics enough to move the tuned command off the knotting manifold (the loop still forms, max writhe 3.2, but the tip no longer threads it), and a small retune restores full success; #3180 (separate rest/initial poses) is unnecessary because the rope starts at its rest shape; and #3316 (masked rigid resets) targets resets, which this example does not use.

ConfigurationChampion successNotes
origin/main 31b06fe017/20report baseline
+ PR #3122, same command0/10loop forms (max writhe 3.2) but tip misses; knot never locks
+ PR #3122, retuned (time scale 0.78, tip 45 g)10/10one-step retune inside the same search grid recovers the knot

Verifying the knot

Success is scored topologically on the rope centerline, not visually:

The example's test_final() asserts these criteria when run with --expect-knot.

Deviations from the paper

Newton observations along the way:

Reproduction

The complete branch (example, digitization, sweep/recording tooling, and specs) is published at eric-heiden/newton@flying-knots; the example lives at newton/examples/cable/example_cable_flying_knot.py with the analysis scripts under scripts/flying_knot/.

git clone -b flying-knots https://github.com/eric-heiden/newton.git
# Newton branch: flying-knots (off origin/main 31b06fe0)
uv run -m newton.examples cable_flying_knot                      # interactive, knots by default
uv run -m newton.examples cable_flying_knot_mujoco --command searched --expect-knot  # fully dynamic knot
uv run -m newton.examples cable_flying_knot_mujoco --command searched-hires --expect-knot  # 72-segment knot
uv run python scripts/flying_knot/command_search.py    # re-derive the coupled-dynamics command
uv run -m newton.examples cable_flying_knot --viewer null --test --expect-knot
uv run python scripts/flying_knot/digitize_fttraj.py             # rebuild the command from the figure
uv run python scripts/flying_knot/sweep.py --grid basin --repeats 3   # success-rate sweeps
uv run python scripts/flying_knot/record_video.py out.mp4        # ViewerGL headless rendering (xvfb)
uv run python scripts/flying_knot/record_video.py knot.mp4 --camera track \
    --start 1.6 --end 3.6 --slowmo 4 --time-scale 0.75 --tip-mass 0.06 --bend-stiffness 5e-4 \
    --require-knot --verify-in-frame --max-knot-pos 0.80   # knot-tracking slow-mo, knot ends high
uv run python scripts/flying_knot/fixed_base_search.py           # base-motion diagnosis + mount search
uv run python scripts/flying_knot/verify_fixed_base.py           # probe: base/pedestal must not move
  

Assets: xArm7 URDF + meshes are read from a clone of flying_knots_public (override with FLYING_KNOTS_XARM_DIR); without it the example runs handle-only. Videos rendered with ViewerGL offscreen capture piped to ffmpeg.