Examples & Results#
Runnable examples for the Python API, then the published benchmark results and how to reproduce them.
Examples#
Self-contained, runnable programs live in the repository’s examples/ directory. Each is included in
full below (so the docs never drift from the code). Run any with the project’s virtual environment
active, e.g. python examples/01_open_world_solve.py.
Example |
Shows |
Needs |
|---|---|---|
|
Batch-solve one 6-DOF target; inspect the best returned solutions |
built |
|
Collision-free solve against a MotionBenchMaker scene (obstacles on GPU) |
|
|
How the best-solution accuracy improves with batch size |
built |
01 — Open-world solve#
"""01 — Open-world IK: batch-solve a single 6-DOF target.
Generate many candidate solutions in parallel for one end-effector pose, then inspect the best ones.
Run: ``python examples/01_open_world_solve.py``
"""
from hjcdik import generate_solutions, sample_targets, num_joints
print(f"robot DOF: {num_joints()}")
# A reachable target pose: [x, y, z, qw, qx, qy, qz] (position + unit quaternion).
target = sample_targets(num_targets=1, seed=0)[0]
print("target:", target)
# Explore 2000 candidates in parallel; return the 4 best distinct solutions.
out = generate_solutions(target, batch_size=2000, num_solutions=4)
print(f"returned {out['count']} solutions")
print("joint configs shape:", out["joint_config"].shape) # (count, DOF)
if out["count"]:
print("best position error (mm): ", float(out["pos_errors"].min()))
print("best orientation error (rad):", float(out["ori_errors"].min()))
else:
print("No candidate was returned.")
# A returned candidate is approximate: check BOTH errors against your tolerances.
02 — Collision-free solve#
The scene and goal come from tests/mb_problems.json; the GPU filters candidates against the obstacles
in the chosen problem set (the Results section below covers the benchmark harness and reproduction).
"""02 — Collision-free IK against a MotionBenchMaker scene.
Solve IK while the GPU filters candidates against the obstacles in a problem set. The scene + goal come
from ``tests/mb_problems.json`` (the same sets the benchmark uses). Requires HJCD-IK built with the
``panda_grasptarget_hand`` frame (the committed default), which is the frame these problems are posed in.
Run: ``python examples/02_collision_free_solve.py``
"""
import json
from pathlib import Path
from hjcdik import generate_solutions
PROBLEMS = Path(__file__).resolve().parents[1] / "tests" / "mb_problems.json"
PROBLEM_SET = "box_panda"
PROBLEM_IDX = 0
problems_text = PROBLEMS.read_text()
problem = json.loads(problems_text)["problems"][PROBLEM_SET][PROBLEM_IDX]
# Goal pose for this problem: [x, y, z, qw, qx, qy, qz].
gp = problem["goal_pose"]
target = [*gp["position_xyz"], *gp["quaternion_wxyz"]]
out = generate_solutions(
target,
batch_size=2000,
num_solutions=4,
collision_free=True,
collision_mode="hard",
problems_json_text=problems_text, # the GPU reads obstacles from this set...
problem_set_name=PROBLEM_SET,
problem_idx=PROBLEM_IDX, # ...for this specific scene
)
print(f"set={PROBLEM_SET} idx={PROBLEM_IDX}: {out['count']} collision-free solutions")
if out["count"]:
print("best position error (mm): ", float(out["pos_errors"].min()))
print("best orientation error (rad):", float(out["ori_errors"].min()))
else:
print("No collision-free candidate was found in this batch.")
03 — Batch-size sweep#
"""03 — Batch-size sweep: accuracy vs. batch size.
Larger batches explore more candidates for ONE target. Coarse search uses one block per
candidate; refinement uses one warp per candidate. This reports accuracy, not performance:
accuracy need not improve monotonically, and runtime depends on hardware and settings.
Run: ``python examples/03_batch_sweep.py``
"""
from hjcdik import generate_solutions, sample_targets
target = sample_targets(num_targets=1, seed=0)[0]
print(f"{'batch':>8} {'best pos err (mm)':>18} {'best ori err (rad)':>20}")
for batch in (1, 10, 100, 1000, 2000):
out = generate_solutions(target, batch_size=batch, num_solutions=1)
if out["count"]:
print(f"{batch:>8} {float(out['pos_errors'].min()):>18.3e} {float(out['ori_errors'].min()):>20.3e}")
else:
print(f"{batch:>8} no candidate returned")
Results#
In the published experiments below, HJCD-IK stayed on or near the accuracy–latency Pareto frontier across the evaluated batch sizes and degree-of-freedom counts, with order-of-magnitude gains in some comparisons and the lowest measured MMD. These are historical paper results, not new measurements of the current release.
Note
All numbers below are from the camera-ready paper (arXiv:2510.07514, IROS 2026) — the single source of truth. They were collected on an NVIDIA RTX 4060 (Intel i7-14700HX, WSL Ubuntu 24.04, CUDA 12.5) over 100 Halton open-world poses and the box_panda MotionBenchMaker scene. Benchmarks you run locally (see Reproducing these results, below) are for your own validation and will differ with hardware. Position error is in mm, orientation error in rad, time in ms; bold marks the best (HJCD-IK) value.
Open-world IK — Panda (Table I)#
Batch |
HJCD-IK Time |
HJCD-IK Pos |
HJCD-IK Ori |
PyRoki Time |
PyRoki Pos |
PyRoki Ori |
cuRobo Time |
cuRobo Pos |
cuRobo Ori |
IKFlow Time |
IKFlow Pos |
IKFlow Ori |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
4.04 |
7.04e-2 |
2.04e-3 |
14.86 |
1.39e-2 |
1.12e-5 |
5.33 |
2.56e1 |
1.11e-1 |
18.48 |
4.67e0 |
2.28e-2 |
10 |
3.82 |
1.21e-4 |
6.74e-7 |
14.62 |
1.39e-2 |
1.12e-5 |
5.55 |
2.49e-3 |
3.95e-6 |
18.95 |
1.38e0 |
6.21e-3 |
100 |
4.07 |
2.25e-5 |
8.95e-8 |
14.20 |
1.39e-2 |
1.12e-5 |
6.01 |
9.16e-4 |
2.83e-6 |
22.29 |
5.94e-1 |
2.76e-3 |
1000 |
4.22 |
1.60e-5 |
9.15e-8 |
13.96 |
1.39e-2 |
1.12e-5 |
19.80 |
3.67e-4 |
1.68e-6 |
49.78 |
2.06e0 |
5.43e-3 |
2000 |
4.37 |
1.81e-5 |
5.15e-8 |
13.97 |
1.39e-2 |
1.12e-5 |
30.30 |
2.65e-4 |
1.33e-6 |
99.98 |
1.92e0 |
6.59e-3 |
Open-world IK — Fetch (Table I)#
Batch |
HJCD-IK Time |
HJCD-IK Pos |
HJCD-IK Ori |
PyRoki Time |
PyRoki Pos |
PyRoki Ori |
cuRobo Time |
cuRobo Pos |
cuRobo Ori |
IKFlow Time |
IKFlow Pos |
IKFlow Ori |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
2.59 |
5.79e-1 |
1.20e-3 |
13.70 |
2.10e-5 |
3.12e-8 |
5.30 |
4.48e0 |
3.70e-3 |
17.40 |
1.92e1 |
6.67e-2 |
10 |
2.41 |
1.40e-6 |
9.56e-9 |
13.48 |
2.10e-5 |
3.12e-8 |
5.52 |
6.74e-4 |
1.08e-6 |
16.36 |
9.60e0 |
3.66e-2 |
100 |
2.52 |
1.67e-6 |
8.97e-9 |
13.16 |
2.10e-5 |
3.12e-8 |
7.57 |
1.61e-4 |
8.87e-7 |
19.75 |
1.65e1 |
7.24e-2 |
1000 |
2.59 |
1.67e-6 |
6.10e-9 |
12.92 |
2.10e-5 |
3.12e-8 |
11.32 |
5.17e-5 |
6.43e-7 |
48.68 |
2.05e1 |
6.03e-2 |
2000 |
2.73 |
1.66e-6 |
9.70e-9 |
13.37 |
2.10e-5 |
3.12e-8 |
14.62 |
3.96e-5 |
5.94e-7 |
87.89 |
1.52e1 |
4.87e-2 |
Fig. 2 Open-world accuracy–latency frontier (Table I) — HJCD-IK (orange), cuRobo (blue), PyRoki (green).#
Collision-free IK — Panda, box_panda (Table II)#
Batch |
HJCD-IK Time |
HJCD-IK Pos |
HJCD-IK Ori |
HJCD-IK Succ |
PyRoki Time |
PyRoki Pos |
PyRoki Ori |
PyRoki Succ |
cuRobo Time |
cuRobo Pos |
cuRobo Ori |
cuRobo Succ |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
5.44 |
8.17 |
1.96e-2 |
89.0 |
34.04 |
5.18e2 |
3.96e-1 |
6.0 |
23.76 |
7.85 |
2.61e-3 |
97.0 |
10 |
4.19 |
7.11e-4 |
5.34e-7 |
98.0 |
46.29 |
9.90e-5 |
1.69e-7 |
89.0 |
29.31 |
2.43e-3 |
4.00e-6 |
100.0 |
100 |
4.42 |
8.83e-5 |
3.56e-8 |
100.0 |
48.98 |
9.80e-5 |
1.41e-7 |
93.0 |
30.76 |
6.99e-4 |
2.00e-6 |
100.0 |
1000 |
5.04 |
2.03e-5 |
9.06e-9 |
100.0 |
46.98 |
8.70e-5 |
1.36e-7 |
92.0 |
28.50 |
2.93e-4 |
2.00e-6 |
100.0 |
2000 |
5.35 |
1.71e-5 |
7.16e-9 |
100.0 |
35.74 |
8.80e-5 |
1.49e-7 |
90.0 |
61.96 |
2.47e-4 |
2.00e-6 |
100.0 |
Fig. 3 Collision-free frontier on the box_panda scene (Fig. 4, Table II).#
Note
Collision-free validation (methodology). The local benchmark tools validate returned
configurations post-hoc against the legacy paper 59-sphere Panda model
(benchmark/panda_collision.py, sourced from the frozen
benchmark/reference/panda_collision_model.cuh). This is an environment-only check.
It is not identical to HJCD-IK’s compiled URDF-driven model: the paper reference uses
+/-65 mm finger-joint origins, whereas the compiled kinematic URDF uses +/-40 mm.
Four finger-sphere centers consequently differ by 25 mm. Self-collision exclusions
also follow the generated topology rather than this environment-only oracle.
Shared geometry makes the environment predicate comparable, but does not make all
reported percentages equivalent: HJCD’s collision column is per returned solution;
a baseline success_both combines pose success and collision freedom. Account
for missing outputs and align denominators/accuracy thresholds before making a
cross-solver success-rate table. The check is pure NumPy (no cuRobo dependency). Run
benchmark/baseline_bench.py --mode {pyroki,curobo} --collision_free (per-run CSV/YAML land
under the gitignored benchmark/results/; the time/accuracy numbers above are the
camera-ready values).
HJCD’s harness preserves this comparison with --collision-validation-model paper
(default). Select --collision-validation-model hjcd for an independent CPU check
of its current URDF-bound sphere geometry instead; this does not change the solver model.
Do not mix those rates in one cross-solver table. Collision CSV/YAML outputs also write
<output>.metadata.json with validation model, source hashes, finger origins, and
the compiled header identity. Historical result files without that sidecar retain
their original paper-model interpretation.
DoF scalability — Panda variants, B = 1000 (Table III)#
DoF |
HJCD-IK Time |
HJCD-IK Pos |
HJCD-IK Ori |
PyRoki Time |
PyRoki Pos |
PyRoki Ori |
cuRobo Time |
cuRobo Pos |
cuRobo Ori |
|---|---|---|---|---|---|---|---|---|---|
7 |
4.25 |
1.71e-5 |
4.11e-8 |
15.09 |
2.63e-2 |
3.70e-5 |
9.11 |
3.38e-4 |
1.59e-6 |
12 |
4.55 |
1.94e-5 |
6.91e-8 |
16.29 |
1.99e-2 |
1.86e-5 |
12.66 |
7.78e-1 |
2.57e-2 |
18 |
4.62 |
3.76e-5 |
6.95e-8 |
20.82 |
2.15e-2 |
2.14e-5 |
16.26 |
8.41e-1 |
3.03e-2 |
24 |
4.66 |
3.84e-5 |
7.32e-8 |
24.34 |
1.84e-2 |
1.99e-5 |
19.55 |
7.50e-1 |
3.58e-2 |
Fig. 4 DoF scaling, 7–24 DoF (Fig. 5, Table III) — HJCD-IK keeps the lowest error and latency at every DoF.#
Solution diversity — MMD vs. TRAC-IK (Table IV)#
Maximum Mean Discrepancy between each solver’s 50 best configurations (of a batch of 2000) and 50 ground-truth samples, over 100 target poses — lower is a closer match to the full IK manifold.
Fig. 5 Distribution of collision-free IK solutions for a representative target — cuRobo (left), PyRoki (center), HJCD-IK (right). HJCD-IK returns a broader, more diverse spread of locally-optimal solutions.#
Reproducing these results#
The numbers above are the paper’s; you can regenerate the HJCD-IK columns on your own GPU (absolute timings will differ — see the note at the top). The competitor baselines are optional and heavy.
One command (all tables, all installed solvers):
./scripts/setup/install_baselines.sh # optional: PyRoki, cuRobo, IKFlow, TRAC-IK (each skippable)
HJCD_REGEN=1 RUN_FETCH=1 RUN_DOF=1 RUN_MMD=1 ./scripts/bench/run_paper_experiments.sh
# HJCD-IK only (no baselines):
# HJCD_REGEN=1 SKIP_CUROBO=1 SKIP_PYROKI=1 SKIP_IKFLOW=1 ./scripts/bench/run_paper_experiments.sh
The baselines (PyRoki / cuRobo v2 / IKFlow / TRAC-IK) install behind the optional baselines extra plus
some git/source steps; scripts/setup/install_baselines.sh handles each (and documents the per-solver
gotchas — cuRobo’s cuda-core backend, IKFlow’s offline weights, TRAC-IK’s ROS-free build). Each stage
is independently skippable, and every solver runs the same shared open-world Halton targets for a fair
comparison.
HJCD-IK on its own (no baselines needed) — open-world or a collision-free MotionBenchMaker scene:
python benchmark/hjcd_ik_bench.py --skip-grid-codegen --batches 1,10,100,1000,2000 --num-targets 100
# collision-free (Panda box_panda, the paper's Table II scene):
python benchmark/hjcd_ik_bench.py --skip-grid-codegen --collision-free \
--problems-json tests/mb_problems.json --problem-set box_panda --batches 1,10,100,1000
The harness reports position/orientation errors and timing per batch size. The collision-free
column is a per-returned-solution environment check, not a per-query success rate; zero-output
queries must be counted separately when comparing success rates. tests/test_regression.py
checks candidate-return rate and accuracy against a recorded baseline, not runtime.
Isolate timing runs (no concurrent GPU load).
Current code has stricter collision filtering and explicit paper versus hjcd validation
geometry; see Upgrading and verified scope. Match target sets, EE frames, requested output count,
precision, collision model/policy, and aggregation before comparing to the paper. Local
regression timings on another GPU do not establish a new speedup over the published results.
The paper harness rebuilds several robot/frame variants; after an interrupted run, restore
the collision-enabled Panda header and rebuild before running the default tests/examples.
Per-robot end-effector frame#
The end-effector is a named fixed-joint frame, robot-specific. GRiD’s codegen places it at an
s_XmatsHom index that shifts with DoF, so scripts/codegen/generate_grid.py resolves that index
and injects grid::EE_FIXED_FRAME_IDX into grid.cuh (csrc/kernel/hjcd_settings.h consumes it — never
hardcode the index). To switch robots: regenerate, then rebuild.
Robot |
URDF |
|
|
|---|---|---|---|
Panda 7-DoF |
|
|
10 |
Panda 12-DoF |
|
|
14 |
Panda 18-DoF |
|
|
20 |
Panda 24-DoF |
|
|
27 |
Fetch 7-DoF |
|
|
7 |
python scripts/codegen/generate_grid.py csrc/urdf/<robot>.urdf -t <target> # injects EE_FIXED_FRAME_IDX
bash scripts/setup/rebuild.sh # ninja + install (NOT ninja alone)
The hardware results (Fig. 6) require the physical Franka Research 3 setup and are not reproducible from this repository.
Metric |
HJCD-IK |
PyRoki |
cuRobo |
IKFlow |
|---|---|---|---|---|
MMD ↓ |
0.02261 |
0.04514 |
0.05348 |
0.03670 |
MMD² ↓ |
0.00051 |
0.00203 |
0.00286 |
0.00134 |