Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d0cbafd
Update README.md
WeihangGuo Dec 14, 2025
f71f00d
Merge branch 'clean-doc'
WeihangGuo Dec 15, 2025
2c09b9d
Remove `docs.yml` and `docs/`
WeihangGuo Dec 15, 2025
5cdf604
fix: Ruff format
WeihangGuo Dec 15, 2025
a33b780
Merge branch 'clean-doc'
WeihangGuo Dec 15, 2025
b70f9e0
Add badges and rename github action
WeihangGuo Dec 15, 2025
9348ecc
Implement collision detection methods with exclusion of specified links
WeihangGuo Dec 15, 2025
dcddcc2
Remove commented-out code for compute_world_collision_distance method
WeihangGuo Dec 15, 2025
2118a2d
Update collision distance calculation example
WeihangGuo Dec 15, 2025
68f3bb0
Add colllision benchmark. pyronot sphere checking 0.1048ms/config
WeihangGuo Dec 15, 2025
bfd228a
Refactor collision benchmark
WeihangGuo Dec 15, 2025
ffd966a
Padding 1D collision checking
WeihangGuo Dec 15, 2025
376bdfc
Padding 1D collision checking working version
WeihangGuo Dec 15, 2025
1f63d5b
No padding collision checking
WeihangGuo Dec 15, 2025
4fc7325
Fix typo
WeihangGuo Dec 15, 2025
9f315ef
Merge commit '4fc7325b49b6c6581c851544165566367cb0b6b8' into robot-coll
WeihangGuo Dec 15, 2025
ec5285b
Refactor collision benchmark
WeihangGuo Dec 15, 2025
85af7d5
Use CPU and better format
WeihangGuo Dec 15, 2025
2a17bf1
Add /tmp to .gitignore
WeihangGuo Dec 15, 2025
d157f39
Remove additional `at_config()`
WeihangGuo Dec 16, 2025
cfa3020
Update .gitignore to include 'tmp/' directory
WeihangGuo Dec 18, 2025
796c3ad
Update _sphere_sphere_dist function to calculate squared distance bet…
WeihangGuo Dec 18, 2025
635f0eb
Merge branch 'sphere-sphere-opt' into robot-coll
WeihangGuo Dec 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions .github/workflows/docs.yml

This file was deleted.

File renamed without changes.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ htmlcov
.envrc
.vite
build
tmp/
31 changes: 6 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,13 @@
# `PyRoNot`: A Better Python Robot Kinematics Library
# `PyRoNot`: A Python Library for Robot KinematicsUsing Spherical Approximations


## Citation
[![Format Check](https://github.com/CoMMALab/pyronot/actions/workflows/formatting.yml/badge.svg)](https://github.com/CoMMALab/pyronot/actions/workflows/formatting.yml)
[![Pyright](https://github.com/CoMMALab/pyronot/actions/workflows/pyright.yml/badge.svg)](https://github.com/CoMMALab/pyronot/actions/workflows/pyright.yml)
[![Pytest](https://github.com/CoMMALab/pyronot/actions/workflows/pytest.yml/badge.svg)](https://github.com/CoMMALab/pyronot/actions/workflows/pytest.yml)
[![PyPI - Version](https://img.shields.io/pypi/v/pyronot)](https://pypi.org/project/pyronot/)

This repository is based on pyroki.

<table><tr><td>
Chung Min Kim*, Brent Yi*, Hongsuk Choi, Yi Ma, Ken Goldberg, Angjoo Kanazawa.
<strong>PyRoki: A Modular Toolkit for Robot Kinematic Optimization</strong>
arXiV, 2025.
</td></tr>
</table>

<sup>\*</sup><em>Equal Contribution</em>, <em>UC Berkeley</em>.

Please cite PyRoki if you find this work useful for your research:

```
@inproceedings{kim2025pyroki,
title={PyRoki: A Modular Toolkit for Robot Kinematic Optimization},
author={Kim*, Chung Min and Yi*, Brent and Choi, Hongsuk and Ma, Yi and Goldberg, Ken and Kanazawa, Angjoo},
booktitle={2025 IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)},
year={2025},
url={https://arxiv.org/abs/2505.03728},
}
```

Thanks!
This repository is based on [pyroki](https://github.com/chungmin99/pyroki).

## Installation
```
Expand Down
195 changes: 195 additions & 0 deletions benchmark/robot_coll_benchmark.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import jax
jax.config.update('jax_platform_name', 'cpu')
print(f"================================================")
print(f"Jax platform: {jax.lib.xla_bridge.get_backend().platform}")
print(f"================================================")
import numpy as np
import pyronot as prn
import pyroki as pk

from pyroki.collision import Sphere, RobotCollision
from pyronot.collision import Sphere as SphereNot, RobotCollisionSpherized as RobotCollisionSpherizedNot
import yourdfpy
import pinocchio as pin
import hppfcl
import time

np.random.seed(41)

NUM_SAMPLES = 1000

SPHERE_CENTERS = [
[0.55, 0, 0.25],
[0.35, 0.35, 0.25],
[0, 0.55, 0.25],
[-0.55, 0, 0.25],
[-0.35, -0.35, 0.25],
[0, -0.55, 0.25],
[0.35, -0.35, 0.25],
[0.35, 0.35, 0.8],
[0, 0.55, 0.8],
[-0.35, 0.35, 0.8],
[-0.55, 0, 0.8],
[-0.35, -0.35, 0.8],
[0, -0.55, 0.8],
[0.35, -0.35, 0.8],
]

SPHERE_R = [0.2] * len(SPHERE_CENTERS)

urdf_path = "resources/ur5/ur5_spherized.urdf"
mesh_dir = "resources/ur5/meshes"



# ============ Pinocchio Ground Truth Setup ============
def setup_pinocchio_collision(urdf_path, mesh_dir, sphere_centers, sphere_radii):
"""Setup Pinocchio collision model with environment spheres."""
pin_model = pin.buildModelFromUrdf(urdf_path)
pin_geom_model = pin.buildGeomFromUrdf(pin_model, urdf_path, pin.COLLISION, mesh_dir)

num_robot_geoms = len(pin_geom_model.geometryObjects)

# Add obstacle spheres to the geometry model
for i, (center, radius) in enumerate(zip(sphere_centers, sphere_radii)):
sphere_shape = hppfcl.Sphere(radius)
placement = pin.SE3(np.eye(3), np.array(center))
geom_obj = pin.GeometryObject(
f"obstacle_sphere_{i}",
0, # parent frame (universe)
0, # parent joint (universe)
sphere_shape,
placement,
)
pin_geom_model.addGeometryObject(geom_obj)

# Add collision pairs: robot geometries vs obstacle spheres
num_obstacles = len(sphere_centers)
for robot_geom_id in range(num_robot_geoms):
for obs_idx in range(num_obstacles):
obs_geom_id = num_robot_geoms + obs_idx
pin_geom_model.addCollisionPair(
pin.CollisionPair(robot_geom_id, obs_geom_id)
)

pin_data = pin_model.createData()
pin_geom_data = pin.GeometryData(pin_geom_model)

return pin_model, pin_data, pin_geom_model, pin_geom_data


def check_collision_pinocchio(pin_model, pin_data, pin_geom_model, pin_geom_data, q):
"""Check if configuration q is in collision using Pinocchio (ground truth)."""
pin.updateGeometryPlacements(pin_model, pin_data, pin_geom_model, pin_geom_data, q)
return pin.computeCollisions(pin_geom_model, pin_geom_data, True)


# Setup Pinocchio collision checker
pin_model, pin_data, pin_geom_model, pin_geom_data = setup_pinocchio_collision(
urdf_path, mesh_dir, SPHERE_CENTERS, SPHERE_R
)

world_coll_not = SphereNot.from_center_and_radius(SPHERE_CENTERS, SPHERE_R)
world_coll = Sphere.from_center_and_radius(SPHERE_CENTERS, SPHERE_R)

urdf = yourdfpy.URDF.load(urdf_path, mesh_dir=mesh_dir)
robot_not = prn.Robot.from_urdf(urdf)
robot = pk.Robot.from_urdf(urdf)
robot_coll = RobotCollision.from_urdf(urdf)
robot_coll_not = RobotCollisionSpherizedNot.from_urdf(urdf)

def generate_dataset(num_samples):
q_batch = []
robot_lower_limits = robot.joints.lower_limits
robot_upper_limits = robot.joints.upper_limits
for _ in range(num_samples):
q = np.random.uniform(robot_lower_limits, robot_upper_limits)
q_batch.append(q)
return np.array(q_batch)

q_batch = generate_dataset(NUM_SAMPLES)
print(f"Generated {q_batch.shape[0]} samples")

# ============ Generate Ground Truth with Pinocchio ============
print("\n=== Generating Pinocchio Ground Truth ===")
start_time = time.time()
ground_truth = []
for q in q_batch:
collision = check_collision_pinocchio(pin_model, pin_data, pin_geom_model, pin_geom_data, q)
ground_truth.append(collision)
ground_truth = np.array(ground_truth)
end_time = time.time()
time_taken_ms = (end_time - start_time) * 1000
print(f"Time taken for Pinocchio ground truth (ms): {time_taken_ms:.2f}")
print(f"Time per collision check (ms): {time_taken_ms/NUM_SAMPLES:.4f}")
print(f"Collision rate: {ground_truth.sum()}/{NUM_SAMPLES} ({100*ground_truth.mean():.1f}%)")

# ============ Benchmark pyronot collision methods ============
print("\n=== Benchmarking pyronot Collision Methods ===")
# Warmup for JIT
q = q_batch[0]
robot_coll.at_config(robot, q)
jax.block_until_ready(robot_coll.compute_world_collision_distance(robot, q, world_coll))
print(f"Type of robot_coll.compute_world_collision_distance: {type(robot_coll.compute_world_collision_distance)}")
try:
print(f"Capsule cache: {robot_coll.compute_world_collision_distance._cache_size()}")
except AttributeError:
print("Capsule method is NOT JIT compiled")

robot_coll_not.at_config(robot_not, q)
jax.block_until_ready(robot_coll_not.compute_world_collision_distance(robot_not, q, world_coll_not))
print(f"Type of robot_coll_not.compute_world_collision_distance: {type(robot_coll_not.compute_world_collision_distance)}")
try:
print(f"Sphere cache: {robot_coll_not.compute_world_collision_distance._cache_size()}")
except AttributeError:
print("Sphere method is NOT JIT compiled")
# End of warmup

with jax.profiler.trace("./tmp/sphere_trace"):
q = q_batch[0]
result = robot_coll_not.compute_world_collision_distance(robot_not, q, world_coll_not)
jax.block_until_ready(result)

print(f"=== Result ===")
print(f"Time taken for pinocchio for a signle collision check (ms): \t{time_taken_ms/NUM_SAMPLES:.4f}")
start_time = time.time()
for q in q_batch:
# robot_coll_not.at_config(robot_not, q)
robot_coll_not.compute_world_collision_distance(robot_not, q, world_coll_not)
end_time = time.time()
time_taken_ms = (end_time - start_time) * 1000
print(f"Time taken for sphere for single collision check (ms): \t\t{time_taken_ms/NUM_SAMPLES:.4f}")

start_time = time.time()
for q in q_batch:
# robot_coll.at_config(robot, q)
robot_coll.compute_world_collision_distance(robot, q, world_coll)
end_time = time.time()
time_taken_ms = (end_time - start_time) * 1000
print(f"Time taken for capsule for single collision check (ms): \t{time_taken_ms/NUM_SAMPLES:.4f}")

# ============ Compare with Ground Truth ============
print("\n=== Accuracy Comparison with Ground Truth ===")

# Check sphere model accuracy
sphere_predictions = []
for q in q_batch:
dist = robot_coll_not.compute_world_collision_distance(robot_not, q, world_coll_not)
# Collision if any distance is negative
in_collision = (np.array(dist) < 0).any()
sphere_predictions.append(in_collision)
sphere_predictions = np.array(sphere_predictions)
sphere_accuracy = (sphere_predictions == ground_truth).mean()
print(f"Sphere model accuracy: {100*sphere_accuracy:.2f}%")

# Check capsule model accuracy
capsule_predictions = []
for q in q_batch:
dist = robot_coll.compute_world_collision_distance(robot, q, world_coll)
# Collision if any distance is negative
in_collision = (np.array(dist) < 0).any()
capsule_predictions.append(in_collision)
capsule_predictions = np.array(capsule_predictions)
capsule_accuracy = (capsule_predictions == ground_truth).mean()
print(f"Capsule model accuracy: {100*capsule_accuracy:.2f}%")

20 changes: 0 additions & 20 deletions docs/Makefile

This file was deleted.

61 changes: 0 additions & 61 deletions docs/README.md

This file was deleted.

10 changes: 0 additions & 10 deletions docs/requirements.txt

This file was deleted.

Binary file removed docs/source/_static/basic_ik.mov
Binary file not shown.
4 changes: 0 additions & 4 deletions docs/source/_static/css/custom.css

This file was deleted.

Loading
Loading