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

01_open_world_solve

Batch-solve one 6-DOF target; inspect the best returned solutions

built hjcdik

02_collision_free_solve

Collision-free solve against a MotionBenchMaker scene (obstacles on GPU)

grasptarget build

03_batch_sweep

How the best-solution accuracy improves with batch size

built hjcdik

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)   # (num_solutions, DOF)
print("best position error (m):   ", float(out["pos_errors"].min()))
print("best orientation error (rad):", float(out["ori_errors"].min()))

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,
    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")
print("best position error (m):   ", float(out["pos_errors"].min()))
print("best orientation error (rad):", float(out["ori_errors"].min()))

03 — Batch-size sweep#

"""03 — Batch-size sweep: accuracy vs. batch size.

Larger batches explore more candidates in parallel, so the best returned solution is usually more
accurate — at a modest extra cost, since it is all one GPU block per problem. This sweeps the batch size
over a fixed target and reports the best position error at each.

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 (m)':>18}  {'best ori err (rad)':>20}")
for batch in (1, 10, 100, 1000, 2000):
    out = generate_solutions(target, batch_size=batch, num_solutions=1)
    print(f"{batch:>8}  {float(out['pos_errors'].min()):>18.3e}  {float(out['ori_errors'].min()):>20.3e}")

Results#

HJCD-IK generates large batches of IK solutions in parallel and stays on or near the accuracy–latency Pareto frontier across every batch size and degree-of-freedom count, with order-of-magnitude gains over the GPU baselines cuRobo, PyRoki, and IKFlow, while returning the most diverse (lowest-MMD) solution set.

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

Open-world accuracy–latency Pareto frontier across batch sizes

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

Collision-free accuracy–latency Pareto frontier

Fig. 3 Collision-free frontier on the box_panda scene (Fig. 4, Table II).#

Note

Collision-free validation (methodology). The benchmark harness reports a collision_free / success_both rate for every solver by validating each returned configuration post-hoc against the same 59-sphere Panda collision model HJCD-IK itself filters against (benchmark/panda_collision.py, sourced from the frozen paper model benchmark/reference/panda_collision_model.cuh). HJCD-IK’s kernel now filters via GRiD’s URDF-driven grid_collision (the identical spheres, baked into grid.cuh from the foam spherized URDF), so this independent numpy oracle stays a fair cross-check. Because all solvers are judged by one shared geometry — not each tool’s own collision notion — the column is apples-to-apples, and the check is pure-numpy (no cuRobo dependency). success_both is pose-success and collision-free. Regenerate with 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).

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

DoF-scaling accuracy–latency Pareto frontier

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.

Distribution of collision-free IK solutions: cuRobo, PyRoki, HJCD-IK

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)
RUN_DOF=1 RUN_MMD=1 ./scripts/bench/run_paper_experiments.sh   # Tables I–IV + Pareto figures into benchmark/results/
# HJCD-IK only (no baselines):  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 solved-rate, mean position / orientation error, and timing per batch size — the metrics tests/test_regression.py asserts against a committed baseline. Isolate timing runs (no concurrent GPU load).

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

-t target (fixed joint)

EE_FIXED_FRAME_IDX

Panda 7-DoF

panda.urdf

panda_grasptarget_hand

10

Panda 12-DoF

panda_ext_12dof.urdf

panda_hand_joint

14

Panda 18-DoF

panda_ext_18dof.urdf

panda_hand_joint

20

Panda 24-DoF

panda_ext_24dof.urdf

panda_grasptarget_hand

27

Fetch 7-DoF

fetch.urdf

ee_fixed (→ ee_link)

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