embodik 0.3.0__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {embodik-0.3.0 → embodik-0.4.1}/CHANGELOG.md +68 -0
  2. {embodik-0.3.0 → embodik-0.4.1}/PKG-INFO +105 -7
  3. {embodik-0.3.0 → embodik-0.4.1}/README.md +100 -5
  4. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/include/embodik/kinematics_solver.hpp +29 -18
  5. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/include/embodik/robot_model.hpp +20 -0
  6. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/src/kinematics_solver.cpp +32 -1
  7. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/src/robot_model.cpp +71 -0
  8. {embodik-0.3.0 → embodik-0.4.1}/docs/api/index.md +7 -0
  9. {embodik-0.3.0 → embodik-0.4.1}/docs/api/kinematics_solver.md +10 -0
  10. {embodik-0.3.0 → embodik-0.4.1}/docs/examples/index.md +10 -0
  11. embodik-0.4.1/docs/gpu_solvers.md +83 -0
  12. {embodik-0.3.0 → embodik-0.4.1}/docs/index.md +1 -0
  13. {embodik-0.3.0 → embodik-0.4.1}/docs/installation.md +45 -16
  14. {embodik-0.3.0 → embodik-0.4.1}/mkdocs.yml +1 -0
  15. {embodik-0.3.0 → embodik-0.4.1}/pixi.toml +6 -2
  16. {embodik-0.3.0 → embodik-0.4.1}/pyproject.toml +14 -5
  17. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/__init__.py +5 -5
  18. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/gpu/__init__.py +33 -5
  19. embodik-0.4.1/python/embodik/gpu/casadi_pph_sns.py +432 -0
  20. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/gpu/warp_collision.py +37 -103
  21. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/utils.py +13 -15
  22. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/viser_helpers.py +2 -3
  23. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/visualization.py +5 -27
  24. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/visualization_pinocchio.py +2 -3
  25. {embodik-0.3.0 → embodik-0.4.1}/python_bindings/src/kinematics_solver_bindings.cpp +11 -0
  26. {embodik-0.3.0 → embodik-0.4.1}/python_bindings/src/robot_model_bindings.cpp +79 -0
  27. embodik-0.4.1/scripts/benchmark_pph_sns_batched.py +155 -0
  28. embodik-0.4.1/scripts/benchmark_pph_sns_comparison.py +299 -0
  29. embodik-0.4.1/scripts/export_pph_sns.py +43 -0
  30. embodik-0.4.1/scripts/test_pph_sns_cusadi.py +149 -0
  31. {embodik-0.3.0 → embodik-0.4.1}/test/test_gpu_collision.py +1 -1
  32. embodik-0.4.1/test/test_joint_limit_recovery.py +189 -0
  33. embodik-0.3.0/python/embodik/_runtime_deps.py +0 -47
  34. {embodik-0.3.0 → embodik-0.4.1}/.gitattributes +0 -0
  35. {embodik-0.3.0 → embodik-0.4.1}/.github/workflows/ci.yml +0 -0
  36. {embodik-0.3.0 → embodik-0.4.1}/.github/workflows/docs.yml +0 -0
  37. {embodik-0.3.0 → embodik-0.4.1}/.github/workflows/release.yml +0 -0
  38. {embodik-0.3.0 → embodik-0.4.1}/.gitignore +0 -0
  39. {embodik-0.3.0 → embodik-0.4.1}/CMakeLists.txt +0 -0
  40. {embodik-0.3.0 → embodik-0.4.1}/CONTRIBUTING.md +0 -0
  41. {embodik-0.3.0 → embodik-0.4.1}/LICENSE +0 -0
  42. {embodik-0.3.0 → embodik-0.4.1}/MANIFEST.in +0 -0
  43. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/include/embodik/constraints.hpp +0 -0
  44. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/include/embodik/ik_baseline.hpp +0 -0
  45. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/include/embodik/tasks.hpp +0 -0
  46. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/include/embodik/types.hpp +0 -0
  47. {embodik-0.3.0 → embodik-0.4.1}/cpp_core/src/tasks.cpp +0 -0
  48. {embodik-0.3.0 → embodik-0.4.1}/docs/api/robot_model.md +0 -0
  49. {embodik-0.3.0 → embodik-0.4.1}/docs/api/tasks.md +0 -0
  50. {embodik-0.3.0 → embodik-0.4.1}/docs/api/utils.md +0 -0
  51. {embodik-0.3.0 → embodik-0.4.1}/docs/api/visualization.md +0 -0
  52. {embodik-0.3.0 → embodik-0.4.1}/docs/development.md +0 -0
  53. {embodik-0.3.0 → embodik-0.4.1}/docs/examples/basic_ik.md +0 -0
  54. {embodik-0.3.0 → embodik-0.4.1}/docs/examples/multi_task_ik.md +0 -0
  55. {embodik-0.3.0 → embodik-0.4.1}/docs/quickstart.md +0 -0
  56. {embodik-0.3.0 → embodik-0.4.1}/docs/transforms.md +0 -0
  57. {embodik-0.3.0 → embodik-0.4.1}/examples/01_basic_ik_simple.py +0 -0
  58. {embodik-0.3.0 → embodik-0.4.1}/examples/02_collision_aware_IK.py +0 -0
  59. {embodik-0.3.0 → embodik-0.4.1}/examples/03_teleop_ik.py +0 -0
  60. {embodik-0.3.0 → embodik-0.4.1}/examples/04_gpu_batch_ik.py +0 -0
  61. {embodik-0.3.0 → embodik-0.4.1}/examples/05_gpu_collision_batch.py +0 -0
  62. {embodik-0.3.0 → embodik-0.4.1}/examples/06_gpu_solver_demo.py +0 -0
  63. {embodik-0.3.0 → embodik-0.4.1}/examples/07_parallel_trajectory_tracking.py +0 -0
  64. {embodik-0.3.0 → embodik-0.4.1}/examples/example_helpers/__init__.py +0 -0
  65. {embodik-0.3.0 → embodik-0.4.1}/examples/example_helpers/dual_arm_ik_helper.py +0 -0
  66. {embodik-0.3.0 → embodik-0.4.1}/examples/example_helpers/limit_profiles/alpha_extended.yaml +0 -0
  67. {embodik-0.3.0 → embodik-0.4.1}/examples/example_helpers/limit_profiles/alpha_extended_all.yaml +0 -0
  68. {embodik-0.3.0 → embodik-0.4.1}/examples/example_helpers/limit_profiles/beta_uniform_plus21.yaml +0 -0
  69. {embodik-0.3.0 → embodik-0.4.1}/examples/robot_model_example.py +0 -0
  70. {embodik-0.3.0 → embodik-0.4.1}/examples/utils/__init__.py +0 -0
  71. {embodik-0.3.0 → embodik-0.4.1}/examples/utils/robot_models.py +0 -0
  72. {embodik-0.3.0 → embodik-0.4.1}/examples/visualization_example.py +0 -0
  73. {embodik-0.3.0 → embodik-0.4.1}/fn_velocity_solve.casadi +0 -0
  74. {embodik-0.3.0 → embodik-0.4.1}/pixi.lock +0 -0
  75. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/cli.py +0 -0
  76. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/examples/__init__.py +0 -0
  77. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/examples/basic_ik.py +0 -0
  78. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/examples/robot_model.py +0 -0
  79. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/gpu/casadi_fi_pesns.py +0 -0
  80. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/gpu/export_casadi_velocity_solve.py +0 -0
  81. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/gpu/torch_kinematics.py +0 -0
  82. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/gpu_solver.py +0 -0
  83. {embodik-0.3.0 → embodik-0.4.1}/python/embodik/robot_visualizer.py +0 -0
  84. {embodik-0.3.0 → embodik-0.4.1}/python_bindings/CMakeLists.txt +0 -0
  85. {embodik-0.3.0 → embodik-0.4.1}/python_bindings/src/bindings.cpp +0 -0
  86. {embodik-0.3.0 → embodik-0.4.1}/python_bindings/src/tasks_bindings.cpp +0 -0
  87. {embodik-0.3.0 → embodik-0.4.1}/scripts/benchmark_fi_pesns.py +0 -0
  88. {embodik-0.3.0 → embodik-0.4.1}/scripts/benchmark_gpu_batched.py +0 -0
  89. {embodik-0.3.0 → embodik-0.4.1}/scripts/install_cusadi.sh +0 -0
  90. {embodik-0.3.0 → embodik-0.4.1}/scripts/microbench_solve_velocity.py +0 -0
  91. {embodik-0.3.0 → embodik-0.4.1}/scripts/patch_qhull_cmake.py +0 -0
  92. {embodik-0.3.0 → embodik-0.4.1}/scripts/setup_pypirc.sh +0 -0
  93. {embodik-0.3.0 → embodik-0.4.1}/scripts/test_cpp_extension.py +0 -0
  94. {embodik-0.3.0 → embodik-0.4.1}/scripts/test_extension_direct.py +0 -0
  95. {embodik-0.3.0 → embodik-0.4.1}/scripts/test_gpu_accuracy.py +0 -0
  96. {embodik-0.3.0 → embodik-0.4.1}/scripts/test_import.py +0 -0
  97. {embodik-0.3.0 → embodik-0.4.1}/scripts/test_python_utils.py +0 -0
  98. {embodik-0.3.0 → embodik-0.4.1}/scripts/upload_pypi.sh +0 -0
  99. {embodik-0.3.0 → embodik-0.4.1}/scripts/upload_testpypi.sh +0 -0
  100. {embodik-0.3.0 → embodik-0.4.1}/scripts/version.py +0 -0
  101. {embodik-0.3.0 → embodik-0.4.1}/test/CMakeLists.txt +0 -0
  102. {embodik-0.3.0 → embodik-0.4.1}/test/test_embodik.py +0 -0
  103. {embodik-0.3.0 → embodik-0.4.1}/test/test_fi_pesns.py +0 -0
  104. {embodik-0.3.0 → embodik-0.4.1}/test/test_gpu_solver.py +0 -0
  105. {embodik-0.3.0 → embodik-0.4.1}/test/test_robot_model.cpp +0 -0
  106. {embodik-0.3.0 → embodik-0.4.1}/test/test_robot_model.py +0 -0
  107. {embodik-0.3.0 → embodik-0.4.1}/test/test_tasks.py +0 -0
@@ -5,9 +5,77 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.0] - 2026-02-05
9
+
10
+ ### Breaking Changes
11
+ - **Removed Python `pin` package from runtime dependencies**: EmbodiK now uses native C++ bindings
12
+ exclusively for all Pinocchio functionality. The `pip pinocchio` (`pin`) package is no longer
13
+ required at runtime, only at build time.
14
+ - This resolves numpy dependency conflicts when using EmbodiK with packages like `hmnd_robot`
15
+ that have different numpy version requirements
16
+ - All rotation utilities (log3, exp3, quaternion conversions) now use native bindings
17
+ - Collision distance computation now uses native `RobotModel.compute_min_collision_distance()`
18
+ - Visualization defaults to direct Viser (no Pinocchio ViserVisualizer dependency)
19
+
20
+ ### Added
21
+ - **Native math utilities via nanobind**: New C++ bindings for rotation/pose math
22
+ - `embodik.log3(R)`: Compute axis-angle from rotation matrix (replaces `pin.log3`)
23
+ - `embodik.exp3(omega)`: Compute rotation matrix from axis-angle (replaces `pin.exp3`)
24
+ - `embodik.matrix_to_quaternion_wxyz(R)`: Convert rotation matrix to (w,x,y,z) quaternion
25
+ - `embodik.quaternion_wxyz_to_matrix(w,x,y,z)`: Convert quaternion to rotation matrix
26
+ - **Native collision distance API**: RobotModel now exposes collision distance methods
27
+ - `RobotModel.compute_min_collision_distance()`: Get minimum distance across all collision pairs
28
+ - `RobotModel.compute_collision_distances()`: Get distances for all collision pairs
29
+ - **Optional Pinocchio visualization**: `pip install embodik[visualization-pinocchio]` for
30
+ Pinocchio's ViserVisualizer (requires `pin>=3.8.0`)
31
+
32
+ ### Changed
33
+ - Visualization now defaults to direct Viser implementation (no `pin` needed)
34
+ - `utils.py` functions (`get_pose_error_vector`, `compute_pose_error`, `Rt`) use native bindings
35
+ - `visualization.py` quaternion functions use native bindings
36
+ - GPU collision fallback uses native `RobotModel.compute_min_collision_distance()`
37
+ - Removed `_runtime_deps.py` (lazy Pinocchio import no longer needed)
38
+
39
+ ### Migration Guide
40
+ If you were using Pinocchio Python bindings directly through EmbodiK:
41
+
42
+ ```python
43
+ # Old (v0.3.x) - required pip pinocchio
44
+ import pinocchio as pin
45
+ R_error = pose_goal.rotation @ pose_current.rotation.T
46
+ error = pin.log3(R_error)
47
+ q = pin.Quaternion(R)
48
+
49
+ # New (v0.4.0) - no pip pinocchio needed
50
+ import embodik as eik
51
+ R_error = pose_goal.rotation @ pose_current.rotation.T
52
+ error = eik.log3(R_error)
53
+ w, x, y, z = eik.matrix_to_quaternion_wxyz(R)
54
+ ```
55
+
56
+ For collision distance computation:
57
+
58
+ ```python
59
+ # Old (v0.3.x) - required pip pinocchio
60
+ import pinocchio as pin
61
+ pin.updateGeometryPlacements(model, data, collision_model, collision_data)
62
+ for i in range(len(collision_model.collisionPairs)):
63
+ pin.computeDistance(collision_model, collision_data, i)
64
+ dist = collision_data.distanceResults[i].min_distance
65
+
66
+ # New (v0.4.0) - native API
67
+ robot_model.update_configuration(q)
68
+ dist = robot_model.compute_min_collision_distance()
69
+ ```
70
+
8
71
  ## [0.3.0] - 2026-02-03
9
72
 
10
73
  ### Added
74
+ - **PPH-SNS Solver**: Alternative GPU-optimized solver (Parallel Penalized Hierarchical SNS)
75
+ - Soft top-k violation selection using softmax weights
76
+ - Limited rank-1 projector updates (1–2 violators per iteration)
77
+ - Achieves ~632,000 IK solves/second at batch size 10,000
78
+ - Benchmark comparison scripts: `benchmark-solver-comparison`, `benchmark-solver-batched`
11
79
  - **GPU Acceleration**: Batched velocity IK solving with massive parallelism (100-500x speedup)
12
80
  - Achieves ~670,000 IK solves/second at batch size 10,000
13
81
  - Ideal for RL training (4096+ parallel environments), motion planning, and dataset generation
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: embodik
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: High-performance inverse kinematics solver optimized for cross-embodiment VLA/AI applications
5
5
  Keywords: robotics,inverse-kinematics,optimization,motion-planning
6
6
  Author-Email: Andy Park <andypark.purdue@gmail.com>
@@ -23,13 +23,16 @@ Project-URL: Documentation, https://embodik.readthedocs.io
23
23
  Project-URL: Bug Reports, https://github.com/embodik/embodik/issues
24
24
  Requires-Python: >=3.10
25
25
  Requires-Dist: numpy>=1.26.0
26
- Requires-Dist: pin>=3.8.0
27
26
  Provides-Extra: visualization
28
27
  Requires-Dist: viser>=0.1.0; extra == "visualization"
29
28
  Requires-Dist: trimesh>=3.0.0; extra == "visualization"
30
29
  Provides-Extra: visualization-legacy
31
30
  Requires-Dist: viser>=0.1.0; extra == "visualization-legacy"
32
31
  Requires-Dist: yourdfpy>=0.0.52; extra == "visualization-legacy"
32
+ Provides-Extra: visualization-pinocchio
33
+ Requires-Dist: pin>=3.8.0; extra == "visualization-pinocchio"
34
+ Requires-Dist: viser>=0.1.0; extra == "visualization-pinocchio"
35
+ Requires-Dist: trimesh>=3.0.0; extra == "visualization-pinocchio"
33
36
  Provides-Extra: examples
34
37
  Requires-Dist: pyyaml>=6.0; extra == "examples"
35
38
  Requires-Dist: robot_descriptions>=1.0.0; extra == "examples"
@@ -67,6 +70,7 @@ EmbodiK is a high-performance inverse kinematics (IK) library for cross-embodime
67
70
  - **Multiple Solvers**: Single-step and full multi-task velocity IK
68
71
  - **Singularity Robust**: Advanced inverse methods for stable solutions
69
72
  - **Constraint Support**: Joint limits and operational space constraints
73
+ - **Limit Recovery**: Configurable joint limit recovery gain when outside bounds
70
74
  - **Collision Avoidance**: Self-collision detection and avoidance
71
75
  - **Visualization**: Interactive 3D visualization with Viser
72
76
  - **Robot Models**: Built-in support for common robots (Panda, IIWA)
@@ -74,6 +78,10 @@ EmbodiK is a high-performance inverse kinematics (IK) library for cross-embodime
74
78
 
75
79
  ## Installation
76
80
 
81
+ > **Note (v0.4.0+)**: EmbodiK no longer requires the Python `pin` package at runtime.
82
+ > All Pinocchio functionality is exposed through native C++ bindings. This resolves
83
+ > numpy dependency conflicts when using EmbodiK alongside packages like `hmnd_robot`.
84
+
77
85
  ### Option A: Fresh Environment (No existing Pinocchio)
78
86
 
79
87
  If you don't have Pinocchio/Boost installed locally, installation is straightforward:
@@ -83,14 +91,14 @@ python3 -m venv .venv
83
91
  source .venv/bin/activate
84
92
  pip install -U pip
85
93
 
86
- # Install build dependencies and Pinocchio
94
+ # Install build dependencies (pin is needed for build only, not runtime)
87
95
  pip install pin scikit-build-core nanobind cmake ninja
88
96
 
89
97
  # Set CMAKE_PREFIX_PATH and install
90
98
  export CMAKE_PREFIX_PATH=$(python -c "import pinocchio, pathlib; print(pathlib.Path(pinocchio.__file__).resolve().parents[4])")
91
99
  pip install --no-build-isolation embodik
92
100
 
93
- # Verify
101
+ # Verify (no pin import needed!)
94
102
  python -c "import embodik; print(embodik.__version__, embodik.RobotModel)"
95
103
  ```
96
104
 
@@ -106,7 +114,7 @@ pip install -U pip
106
114
  # IMPORTANT: Clear local Pinocchio paths to avoid library conflicts
107
115
  unset LD_LIBRARY_PATH CMAKE_PREFIX_PATH pinocchio_DIR
108
116
 
109
- # Install build dependencies and Pinocchio from PyPI
117
+ # Install build dependencies (pin is needed for build only, not runtime)
110
118
  pip install pin scikit-build-core nanobind cmake ninja
111
119
 
112
120
  # Set CMAKE_PREFIX_PATH to the PyPI pin package
@@ -115,7 +123,7 @@ export CMAKE_PREFIX_PATH=$(python -c "import pinocchio, pathlib; print(pathlib.P
115
123
  # Install embodik
116
124
  pip install --no-build-isolation embodik
117
125
 
118
- # Verify
126
+ # Verify (no pin import needed!)
119
127
  python -c "import embodik; print(embodik.__version__, embodik.RobotModel)"
120
128
  ```
121
129
 
@@ -173,6 +181,37 @@ if result.status == embodik.SolverStatus.SUCCESS:
173
181
 
174
182
  ## API Overview
175
183
 
184
+ ### Native Math Utilities
185
+
186
+ EmbodiK provides native bindings for rotation and pose math (no Python `pin` package needed):
187
+
188
+ ```python
189
+ import embodik as eik
190
+ import numpy as np
191
+
192
+ # Rotation matrix to axis-angle (replaces pin.log3)
193
+ R = np.eye(3)
194
+ omega = eik.log3(R) # Returns [0, 0, 0]
195
+
196
+ # Axis-angle to rotation matrix (replaces pin.exp3)
197
+ omega = np.array([0, 0, np.pi/4])
198
+ R = eik.exp3(omega)
199
+
200
+ # Rotation matrix to quaternion (wxyz format)
201
+ w, x, y, z = eik.matrix_to_quaternion_wxyz(R)
202
+
203
+ # Quaternion to rotation matrix
204
+ R = eik.quaternion_wxyz_to_matrix(w, x, y, z)
205
+
206
+ # Create SE3 transform
207
+ T = eik.Rt(R=R, t=np.array([1, 0, 0]))
208
+
209
+ # Collision distance (no pin needed)
210
+ robot = eik.RobotModel("robot.urdf")
211
+ robot.update_configuration(q)
212
+ min_distance = robot.compute_min_collision_distance()
213
+ ```
214
+
176
215
  ### High-Level API (Recommended)
177
216
 
178
217
  EmbodiK provides a high-level API built on top of Pinocchio for easy robot modeling and IK solving:
@@ -247,6 +286,8 @@ The repository includes several example scripts:
247
286
  | `robot_model_example.py` | Robot model usage and configuration |
248
287
  | `visualization_example.py` | Interactive 3D visualization examples |
249
288
  | `scripts/benchmark_fi_pesns.py` | FI-PeSNS vs CPU accuracy and performance benchmark |
289
+ | `scripts/benchmark_pph_sns_comparison.py` | FI-PeSNS vs PPH-SNS solver comparison (CPU + GPU) |
290
+ | `scripts/benchmark_pph_sns_batched.py` | Batched GPU benchmark for both solvers |
250
291
 
251
292
  ### Running Examples
252
293
 
@@ -286,6 +327,8 @@ See the [Examples Documentation](docs/examples/index.md) for detailed guides.
286
327
 
287
328
  ## GPU Acceleration
288
329
 
330
+ > **Note:** GPU solvers (FI-PeSNS, PPH-SNS) are **experimental** and require further validation. Use with caution in production systems.
331
+
289
332
  EmbodiK supports GPU-accelerated batched velocity IK solving for massive parallelism, ideal for:
290
333
 
291
334
  - **RL Training**: 4096+ parallel environments in Isaac Gym/Orbit
@@ -370,6 +413,9 @@ velocities = result.velocities # (batch_size, n_dof)
370
413
  | `pixi run -e cuda check-gpu` | Verify CasADi + CusADi + CUDA |
371
414
  | `pixi run -e cuda install-cusadi` | Install CusADi from GitHub |
372
415
  | `pixi run -e cuda export-casadi` | Export FI-PeSNS velocity solve function |
416
+ | `pixi run -e cuda export-pph-sns` | Export PPH-SNS velocity solve function |
417
+ | `pixi run -e cuda benchmark-solver-comparison` | Compare FI-PeSNS vs PPH-SNS (CPU + GPU) |
418
+ | `pixi run -e cuda benchmark-solver-batched` | Batched GPU benchmark for both solvers |
373
419
  | `pixi run -e cuda demo-gpu` | Run GPU solver demo/benchmark |
374
420
  | `pixi run -e cuda demo-ik-gpu` | Interactive IK with GPU benchmark panel |
375
421
  | `pixi run -e cuda benchmark-gpu` | Batch IK performance benchmark |
@@ -379,9 +425,33 @@ velocities = result.velocities # (batch_size, n_dof)
379
425
  | `pixi run -e cuda demo-parallel-tracking` | 100 robots tracking trajectories in parallel |
380
426
  | `pixi run -e cuda test-gpu` | Run GPU-specific tests |
381
427
 
428
+ ### GPU Solvers: FI-PeSNS and PPH-SNS
429
+
430
+ EmbodiK provides two GPU-optimized velocity IK solvers, both suitable for CusADi compilation:
431
+
432
+ | Solver | Description | Best For |
433
+ |--------|-------------|----------|
434
+ | **FI-PeSNS** | Fixed-Iteration Penalized eSNS | Default choice, proven accuracy |
435
+ | **PPH-SNS** | Parallel Penalized Hierarchical SNS | Alternative with soft top-k violation selection |
436
+
437
+ Both achieve **100% constraint satisfaction** with zero violations. FI-PeSNS is typically ~7% faster at large batch sizes; PPH-SNS offers a different formulation with limited rank-1 projector updates.
438
+
439
+ **Benchmark (10,000 instances, 7-DOF Panda):**
440
+
441
+ | Solver | Time | Throughput |
442
+ |--------|------|------------|
443
+ | FI-PeSNS | 14.8 ms | **675,000 solves/sec** |
444
+ | PPH-SNS | 15.8 ms | **632,000 solves/sec** |
445
+
446
+ ```bash
447
+ # Compare both solvers
448
+ pixi run -e cuda benchmark-solver-comparison
449
+ pixi run -e cuda benchmark-solver-batched
450
+ ```
451
+
382
452
  ### FI-PeSNS: Fixed-Iteration Penalized eSNS
383
453
 
384
- EmbodiK includes **FI-PeSNS**, a GPU-optimized variant of the eSNS algorithm that trades exact constraint saturation for simpler, parallelizable penalty-based enforcement:
454
+ **FI-PeSNS** is the primary GPU solver—a variant of eSNS that trades exact constraint saturation for simpler, parallelizable penalty-based enforcement:
385
455
 
386
456
  **Key Features:**
387
457
  - **SRINV**: Singularity-Robust Inverse for numerical stability
@@ -418,6 +488,33 @@ for i in range(k_max):
418
488
 
419
489
  *GPU benchmarks on NVIDIA RTX A2000 8GB with CusADi-compiled CUDA kernels.*
420
490
 
491
+ ### PPH-SNS: Parallel Penalized Hierarchical SNS
492
+
493
+ **PPH-SNS** is an alternative GPU-native design with:
494
+
495
+ - **Soft top-k violation selection** using softmax weights
496
+ - **Limited rank-1 projector updates** (1–2 violators per iteration)
497
+ - **Aggressive penalty ramping** (γ=3.0)
498
+ - **Fixed-depth unrolling** for CusADi compilation
499
+
500
+ ```bash
501
+ # Export PPH-SNS (writes to ~/.local/cusadi/src/casadi_functions/)
502
+ pixi run -e cuda export-pph-sns
503
+
504
+ # Compile to CUDA kernel
505
+ cd ~/.local/cusadi && python run_codegen.py --fn=fn_pph_sns_velocity_solve
506
+ ```
507
+
508
+ ```python
509
+ from embodik.gpu.casadi_pph_sns import build_pph_sns_single_task
510
+
511
+ fn = build_pph_sns_single_task(
512
+ n_dof=7, task_dim=6, n_constraints=7,
513
+ k_max=14, m_max=2, # Outer iterations, max saturations per iteration
514
+ )
515
+ velocity, scales = fn(target, jacobian.flatten(), C, lower, upper)
516
+ ```
517
+
421
518
  ### Parallel Trajectory Tracking Demo
422
519
 
423
520
  Visualize GPU parallelization with 100 robot instances simultaneously tracking different trajectories:
@@ -518,6 +615,7 @@ Full documentation is available at: **https://embodik.github.io/embodik/**
518
615
 
519
616
  - [Installation Guide](docs/installation.md) - Detailed installation instructions
520
617
  - [Quickstart](docs/quickstart.md) - Get started in 5 minutes
618
+ - [GPU Solvers](docs/gpu_solvers.md) - FI-PeSNS and PPH-SNS GPU-accelerated solvers
521
619
  - [API Reference](docs/api/index.md) - Complete API documentation
522
620
  - [Examples](docs/examples/index.md) - Example code and tutorials
523
621
  - [Development Guide](docs/development.md) - Contributing and development
@@ -18,6 +18,7 @@ EmbodiK is a high-performance inverse kinematics (IK) library for cross-embodime
18
18
  - **Multiple Solvers**: Single-step and full multi-task velocity IK
19
19
  - **Singularity Robust**: Advanced inverse methods for stable solutions
20
20
  - **Constraint Support**: Joint limits and operational space constraints
21
+ - **Limit Recovery**: Configurable joint limit recovery gain when outside bounds
21
22
  - **Collision Avoidance**: Self-collision detection and avoidance
22
23
  - **Visualization**: Interactive 3D visualization with Viser
23
24
  - **Robot Models**: Built-in support for common robots (Panda, IIWA)
@@ -25,6 +26,10 @@ EmbodiK is a high-performance inverse kinematics (IK) library for cross-embodime
25
26
 
26
27
  ## Installation
27
28
 
29
+ > **Note (v0.4.0+)**: EmbodiK no longer requires the Python `pin` package at runtime.
30
+ > All Pinocchio functionality is exposed through native C++ bindings. This resolves
31
+ > numpy dependency conflicts when using EmbodiK alongside packages like `hmnd_robot`.
32
+
28
33
  ### Option A: Fresh Environment (No existing Pinocchio)
29
34
 
30
35
  If you don't have Pinocchio/Boost installed locally, installation is straightforward:
@@ -34,14 +39,14 @@ python3 -m venv .venv
34
39
  source .venv/bin/activate
35
40
  pip install -U pip
36
41
 
37
- # Install build dependencies and Pinocchio
42
+ # Install build dependencies (pin is needed for build only, not runtime)
38
43
  pip install pin scikit-build-core nanobind cmake ninja
39
44
 
40
45
  # Set CMAKE_PREFIX_PATH and install
41
46
  export CMAKE_PREFIX_PATH=$(python -c "import pinocchio, pathlib; print(pathlib.Path(pinocchio.__file__).resolve().parents[4])")
42
47
  pip install --no-build-isolation embodik
43
48
 
44
- # Verify
49
+ # Verify (no pin import needed!)
45
50
  python -c "import embodik; print(embodik.__version__, embodik.RobotModel)"
46
51
  ```
47
52
 
@@ -57,7 +62,7 @@ pip install -U pip
57
62
  # IMPORTANT: Clear local Pinocchio paths to avoid library conflicts
58
63
  unset LD_LIBRARY_PATH CMAKE_PREFIX_PATH pinocchio_DIR
59
64
 
60
- # Install build dependencies and Pinocchio from PyPI
65
+ # Install build dependencies (pin is needed for build only, not runtime)
61
66
  pip install pin scikit-build-core nanobind cmake ninja
62
67
 
63
68
  # Set CMAKE_PREFIX_PATH to the PyPI pin package
@@ -66,7 +71,7 @@ export CMAKE_PREFIX_PATH=$(python -c "import pinocchio, pathlib; print(pathlib.P
66
71
  # Install embodik
67
72
  pip install --no-build-isolation embodik
68
73
 
69
- # Verify
74
+ # Verify (no pin import needed!)
70
75
  python -c "import embodik; print(embodik.__version__, embodik.RobotModel)"
71
76
  ```
72
77
 
@@ -124,6 +129,37 @@ if result.status == embodik.SolverStatus.SUCCESS:
124
129
 
125
130
  ## API Overview
126
131
 
132
+ ### Native Math Utilities
133
+
134
+ EmbodiK provides native bindings for rotation and pose math (no Python `pin` package needed):
135
+
136
+ ```python
137
+ import embodik as eik
138
+ import numpy as np
139
+
140
+ # Rotation matrix to axis-angle (replaces pin.log3)
141
+ R = np.eye(3)
142
+ omega = eik.log3(R) # Returns [0, 0, 0]
143
+
144
+ # Axis-angle to rotation matrix (replaces pin.exp3)
145
+ omega = np.array([0, 0, np.pi/4])
146
+ R = eik.exp3(omega)
147
+
148
+ # Rotation matrix to quaternion (wxyz format)
149
+ w, x, y, z = eik.matrix_to_quaternion_wxyz(R)
150
+
151
+ # Quaternion to rotation matrix
152
+ R = eik.quaternion_wxyz_to_matrix(w, x, y, z)
153
+
154
+ # Create SE3 transform
155
+ T = eik.Rt(R=R, t=np.array([1, 0, 0]))
156
+
157
+ # Collision distance (no pin needed)
158
+ robot = eik.RobotModel("robot.urdf")
159
+ robot.update_configuration(q)
160
+ min_distance = robot.compute_min_collision_distance()
161
+ ```
162
+
127
163
  ### High-Level API (Recommended)
128
164
 
129
165
  EmbodiK provides a high-level API built on top of Pinocchio for easy robot modeling and IK solving:
@@ -198,6 +234,8 @@ The repository includes several example scripts:
198
234
  | `robot_model_example.py` | Robot model usage and configuration |
199
235
  | `visualization_example.py` | Interactive 3D visualization examples |
200
236
  | `scripts/benchmark_fi_pesns.py` | FI-PeSNS vs CPU accuracy and performance benchmark |
237
+ | `scripts/benchmark_pph_sns_comparison.py` | FI-PeSNS vs PPH-SNS solver comparison (CPU + GPU) |
238
+ | `scripts/benchmark_pph_sns_batched.py` | Batched GPU benchmark for both solvers |
201
239
 
202
240
  ### Running Examples
203
241
 
@@ -237,6 +275,8 @@ See the [Examples Documentation](docs/examples/index.md) for detailed guides.
237
275
 
238
276
  ## GPU Acceleration
239
277
 
278
+ > **Note:** GPU solvers (FI-PeSNS, PPH-SNS) are **experimental** and require further validation. Use with caution in production systems.
279
+
240
280
  EmbodiK supports GPU-accelerated batched velocity IK solving for massive parallelism, ideal for:
241
281
 
242
282
  - **RL Training**: 4096+ parallel environments in Isaac Gym/Orbit
@@ -321,6 +361,9 @@ velocities = result.velocities # (batch_size, n_dof)
321
361
  | `pixi run -e cuda check-gpu` | Verify CasADi + CusADi + CUDA |
322
362
  | `pixi run -e cuda install-cusadi` | Install CusADi from GitHub |
323
363
  | `pixi run -e cuda export-casadi` | Export FI-PeSNS velocity solve function |
364
+ | `pixi run -e cuda export-pph-sns` | Export PPH-SNS velocity solve function |
365
+ | `pixi run -e cuda benchmark-solver-comparison` | Compare FI-PeSNS vs PPH-SNS (CPU + GPU) |
366
+ | `pixi run -e cuda benchmark-solver-batched` | Batched GPU benchmark for both solvers |
324
367
  | `pixi run -e cuda demo-gpu` | Run GPU solver demo/benchmark |
325
368
  | `pixi run -e cuda demo-ik-gpu` | Interactive IK with GPU benchmark panel |
326
369
  | `pixi run -e cuda benchmark-gpu` | Batch IK performance benchmark |
@@ -330,9 +373,33 @@ velocities = result.velocities # (batch_size, n_dof)
330
373
  | `pixi run -e cuda demo-parallel-tracking` | 100 robots tracking trajectories in parallel |
331
374
  | `pixi run -e cuda test-gpu` | Run GPU-specific tests |
332
375
 
376
+ ### GPU Solvers: FI-PeSNS and PPH-SNS
377
+
378
+ EmbodiK provides two GPU-optimized velocity IK solvers, both suitable for CusADi compilation:
379
+
380
+ | Solver | Description | Best For |
381
+ |--------|-------------|----------|
382
+ | **FI-PeSNS** | Fixed-Iteration Penalized eSNS | Default choice, proven accuracy |
383
+ | **PPH-SNS** | Parallel Penalized Hierarchical SNS | Alternative with soft top-k violation selection |
384
+
385
+ Both achieve **100% constraint satisfaction** with zero violations. FI-PeSNS is typically ~7% faster at large batch sizes; PPH-SNS offers a different formulation with limited rank-1 projector updates.
386
+
387
+ **Benchmark (10,000 instances, 7-DOF Panda):**
388
+
389
+ | Solver | Time | Throughput |
390
+ |--------|------|------------|
391
+ | FI-PeSNS | 14.8 ms | **675,000 solves/sec** |
392
+ | PPH-SNS | 15.8 ms | **632,000 solves/sec** |
393
+
394
+ ```bash
395
+ # Compare both solvers
396
+ pixi run -e cuda benchmark-solver-comparison
397
+ pixi run -e cuda benchmark-solver-batched
398
+ ```
399
+
333
400
  ### FI-PeSNS: Fixed-Iteration Penalized eSNS
334
401
 
335
- EmbodiK includes **FI-PeSNS**, a GPU-optimized variant of the eSNS algorithm that trades exact constraint saturation for simpler, parallelizable penalty-based enforcement:
402
+ **FI-PeSNS** is the primary GPU solver—a variant of eSNS that trades exact constraint saturation for simpler, parallelizable penalty-based enforcement:
336
403
 
337
404
  **Key Features:**
338
405
  - **SRINV**: Singularity-Robust Inverse for numerical stability
@@ -369,6 +436,33 @@ for i in range(k_max):
369
436
 
370
437
  *GPU benchmarks on NVIDIA RTX A2000 8GB with CusADi-compiled CUDA kernels.*
371
438
 
439
+ ### PPH-SNS: Parallel Penalized Hierarchical SNS
440
+
441
+ **PPH-SNS** is an alternative GPU-native design with:
442
+
443
+ - **Soft top-k violation selection** using softmax weights
444
+ - **Limited rank-1 projector updates** (1–2 violators per iteration)
445
+ - **Aggressive penalty ramping** (γ=3.0)
446
+ - **Fixed-depth unrolling** for CusADi compilation
447
+
448
+ ```bash
449
+ # Export PPH-SNS (writes to ~/.local/cusadi/src/casadi_functions/)
450
+ pixi run -e cuda export-pph-sns
451
+
452
+ # Compile to CUDA kernel
453
+ cd ~/.local/cusadi && python run_codegen.py --fn=fn_pph_sns_velocity_solve
454
+ ```
455
+
456
+ ```python
457
+ from embodik.gpu.casadi_pph_sns import build_pph_sns_single_task
458
+
459
+ fn = build_pph_sns_single_task(
460
+ n_dof=7, task_dim=6, n_constraints=7,
461
+ k_max=14, m_max=2, # Outer iterations, max saturations per iteration
462
+ )
463
+ velocity, scales = fn(target, jacobian.flatten(), C, lower, upper)
464
+ ```
465
+
372
466
  ### Parallel Trajectory Tracking Demo
373
467
 
374
468
  Visualize GPU parallelization with 100 robot instances simultaneously tracking different trajectories:
@@ -469,6 +563,7 @@ Full documentation is available at: **https://embodik.github.io/embodik/**
469
563
 
470
564
  - [Installation Guide](docs/installation.md) - Detailed installation instructions
471
565
  - [Quickstart](docs/quickstart.md) - Get started in 5 minutes
566
+ - [GPU Solvers](docs/gpu_solvers.md) - FI-PeSNS and PPH-SNS GPU-accelerated solvers
472
567
  - [API Reference](docs/api/index.md) - Complete API documentation
473
568
  - [Examples](docs/examples/index.md) - Example code and tutorials
474
569
  - [Development Guide](docs/development.md) - Contributing and development
@@ -8,6 +8,7 @@
8
8
 
9
9
  #pragma once
10
10
 
11
+ #include <algorithm>
11
12
  #include <cstdint>
12
13
  #include <embodik/robot_model.hpp>
13
14
  #include <embodik/tasks.hpp>
@@ -189,6 +190,15 @@ public:
189
190
  */
190
191
  void set_damping(double damping) { damping_ = damping; }
191
192
 
193
+ /**
194
+ * @brief Set recovery gain for joint limit violations.
195
+ *
196
+ * Values are clamped to [0, 1]. Higher values recover faster.
197
+ */
198
+ void set_limit_recovery_gain(double gain) {
199
+ limit_recovery_gain_ = std::clamp(gain, 0.0, 1.0);
200
+ }
201
+
192
202
  /**
193
203
  * @brief Enable verbose debugging for position IK iterations.
194
204
  * @param enable True to print/log per-iteration errors and store traces.
@@ -280,6 +290,24 @@ public:
280
290
  std::vector<std::pair<std::string, std::string>>
281
291
  get_active_collision_pairs() const;
282
292
 
293
+ /**
294
+ * @brief Calculate velocity box constraints based on position, velocity, and
295
+ * acceleration limits.
296
+ *
297
+ * Velocity limits are computed as:
298
+ * min(position_margin/dt, velocity_limit, sqrt(2*accel*margin))
299
+ *
300
+ * @param position_margin_lower Distance from current position to lower limit
301
+ * @param position_margin_upper Distance from current position to upper limit
302
+ * @param velocity_limit Maximum allowed velocity
303
+ * @param acceleration_limit Maximum allowed acceleration
304
+ * @param dt Time step
305
+ * @return Pair of (lower_velocity_limit, upper_velocity_limit)
306
+ */
307
+ std::pair<double, double> calculate_velocity_box_constraint(
308
+ double position_margin_lower, double position_margin_upper,
309
+ double velocity_limit, double acceleration_limit, double dt) const;
310
+
283
311
  private:
284
312
  std::shared_ptr<RobotModel> robot_;
285
313
  std::vector<std::shared_ptr<Task>> tasks_;
@@ -294,6 +322,7 @@ private:
294
322
  double norm_threshold_ = 1e10;
295
323
  int max_zero_scale_iterations_ = 2;
296
324
  bool position_ik_debug_ = false;
325
+ double limit_recovery_gain_ = 0.5;
297
326
 
298
327
  // Constraint options
299
328
  bool use_velocity_limits_ = true;
@@ -349,24 +378,6 @@ private:
349
378
  bool collision_pair_allowed(const std::string &a, const std::string &b) const;
350
379
  std::optional<CollisionConstraintResult> compute_collision_constraint();
351
380
 
352
- /**
353
- * @brief Calculate velocity box constraints based on position, velocity, and
354
- * acceleration limits
355
- *
356
- * Velocity limits are computed as:
357
- * min(position_margin/dt, velocity_limit, sqrt(2*accel*margin))
358
- *
359
- * @param position_margin_lower Distance from current position to lower limit
360
- * @param position_margin_upper Distance from current position to upper limit
361
- * @param velocity_limit Maximum allowed velocity
362
- * @param acceleration_limit Maximum allowed acceleration
363
- * @param dt Time step
364
- * @return Pair of (lower_velocity_limit, upper_velocity_limit)
365
- */
366
- std::pair<double, double> calculate_velocity_box_constraint(
367
- double position_margin_lower, double position_margin_upper,
368
- double velocity_limit, double acceleration_limit, double dt) const;
369
-
370
381
  public:
371
382
  // ========== Position IK Methods ==========
372
383
 
@@ -259,6 +259,26 @@ public:
259
259
  return collision_model_ != nullptr && collision_data_ != nullptr;
260
260
  }
261
261
 
262
+ /**
263
+ * @brief Compute minimum collision distance at current configuration.
264
+ *
265
+ * Updates geometry placements and computes distances for all collision pairs,
266
+ * returning the minimum distance found.
267
+ *
268
+ * @return Minimum collision distance, or infinity if no collision geometry
269
+ */
270
+ double compute_min_collision_distance() const;
271
+
272
+ /**
273
+ * @brief Compute collision distances for all pairs at current configuration.
274
+ *
275
+ * Updates geometry placements and computes distances for all collision pairs.
276
+ *
277
+ * @return Vector of distances for each collision pair, in order of
278
+ * collision_model.collisionPairs
279
+ */
280
+ std::vector<double> compute_collision_distances() const;
281
+
262
282
  // Access to URDF path for visualization
263
283
  const std::string &urdf_path() const { return urdf_path_; }
264
284
 
@@ -691,8 +691,17 @@ KinematicsSolver::compute_collision_constraint() {
691
691
  std::pair<double, double> KinematicsSolver::calculate_velocity_box_constraint(
692
692
  double position_margin_lower, double position_margin_upper,
693
693
  double velocity_limit, double acceleration_limit, double dt) const {
694
+ constexpr double kMarginEpsilon = 1e-4;
695
+ const double raw_margin_lower = position_margin_lower;
696
+ const double raw_margin_upper = position_margin_upper;
697
+ const bool outside_lower = raw_margin_lower < -kMarginEpsilon;
698
+ const bool outside_upper = raw_margin_upper < -kMarginEpsilon;
699
+
700
+ if (outside_lower && outside_upper) {
701
+ return std::make_pair(-velocity_limit, velocity_limit);
702
+ }
694
703
 
695
- // Clamp margins to be non-negative
704
+ // Clamp margins to be non-negative for nominal bounds.
696
705
  position_margin_lower = std::max(0.0, position_margin_lower);
697
706
  position_margin_upper = std::max(0.0, position_margin_upper);
698
707
 
@@ -712,6 +721,28 @@ std::pair<double, double> KinematicsSolver::calculate_velocity_box_constraint(
712
721
  double upper_limit =
713
722
  std::min({vel_from_pos_upper, velocity_limit, vel_from_accel_upper});
714
723
 
724
+ if (outside_lower) {
725
+ const double violation = -raw_margin_lower;
726
+ const double recovery_min = (limit_recovery_gain_ * violation) / dt;
727
+ const double recovery_lower = std::min(recovery_min, velocity_limit);
728
+ if (recovery_lower > lower_limit) {
729
+ lower_limit = recovery_lower;
730
+ }
731
+ } else if (outside_upper) {
732
+ const double violation = -raw_margin_upper;
733
+ const double recovery_max = -(limit_recovery_gain_ * violation) / dt;
734
+ const double recovery_upper = std::max(recovery_max, -velocity_limit);
735
+ if (recovery_upper < upper_limit) {
736
+ upper_limit = recovery_upper;
737
+ }
738
+ }
739
+
740
+ if (lower_limit > upper_limit) {
741
+ const double midpoint = 0.5 * (lower_limit + upper_limit);
742
+ lower_limit = midpoint;
743
+ upper_limit = midpoint;
744
+ }
745
+
715
746
  return std::make_pair(lower_limit, upper_limit);
716
747
  }
717
748