batches 0.11.0b1__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.
- batches-0.11.0b1/AGENTS.md +130 -0
- batches-0.11.0b1/CHANGELOG.md +253 -0
- batches-0.11.0b1/CONTRIBUTING.md +121 -0
- batches-0.11.0b1/Cargo.lock +2293 -0
- batches-0.11.0b1/Cargo.toml +86 -0
- batches-0.11.0b1/LICENSE-APACHE +202 -0
- batches-0.11.0b1/LICENSE-MIT +21 -0
- batches-0.11.0b1/PKG-INFO +25 -0
- batches-0.11.0b1/PYPI_README.md +13 -0
- batches-0.11.0b1/README.md +330 -0
- batches-0.11.0b1/assets/README.md +35 -0
- batches-0.11.0b1/assets/franka/LICENSE +178 -0
- batches-0.11.0b1/assets/franka/franka_panda.urdf +318 -0
- batches-0.11.0b1/assets/franka/meshes/collision/finger.obj +152 -0
- batches-0.11.0b1/assets/franka/meshes/collision/hand.obj +622 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link0.obj +568 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link1.obj +712 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link2.obj +716 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link3.obj +702 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link4.obj +702 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link5.obj +728 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link6.obj +502 -0
- batches-0.11.0b1/assets/franka/meshes/collision/link7.obj +576 -0
- batches-0.11.0b1/assets/franka/panda_collision.json +464 -0
- batches-0.11.0b1/assets/franka/usd/Payload/Contents.usda +20 -0
- batches-0.11.0b1/assets/franka/usd/Payload/Geometry.usda +408 -0
- batches-0.11.0b1/assets/franka/usd/Payload/GeometryLibrary.usdc +0 -0
- batches-0.11.0b1/assets/franka/usd/Payload/Physics.usda +450 -0
- batches-0.11.0b1/assets/franka/usd/panda.usda +30 -0
- batches-0.11.0b1/assets/menagerie/franka_emika_panda/LICENSE +176 -0
- batches-0.11.0b1/assets/menagerie/franka_emika_panda/panda.xml +287 -0
- batches-0.11.0b1/assets/menagerie/franka_emika_panda/scene.xml +21 -0
- batches-0.11.0b1/assets/menagerie/robotiq_2f85/2f85.xml +192 -0
- batches-0.11.0b1/assets/menagerie/robotiq_2f85/LICENSE +23 -0
- batches-0.11.0b1/assets/menagerie/universal_robots_ur5e/LICENSE +26 -0
- batches-0.11.0b1/assets/menagerie/universal_robots_ur5e/ur5e.xml +136 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/LICENSE +23 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Dual_wrist_camera.usda +29 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Contents.usda +20 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Geometry.usda +662 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Payload/GeometryLibrary.usdc +0 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Materials.usda +150 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Payload/MaterialsLibrary.usdc +0 -0
- batches-0.11.0b1/assets/newton/robotiq_2f85_v4/usd_structured/Payload/Physics.usda +593 -0
- batches-0.11.0b1/assets/newton/universal_robots_ur5e/LICENSE +26 -0
- batches-0.11.0b1/assets/newton/universal_robots_ur5e/usd_structured/Payload/Contents.usda +20 -0
- batches-0.11.0b1/assets/newton/universal_robots_ur5e/usd_structured/Payload/Geometry.usda +687 -0
- batches-0.11.0b1/assets/newton/universal_robots_ur5e/usd_structured/Payload/Materials.usda +205 -0
- batches-0.11.0b1/assets/newton/universal_robots_ur5e/usd_structured/Payload/Physics.usda +405 -0
- batches-0.11.0b1/assets/newton/universal_robots_ur5e/usd_structured/ur5e.usda +29 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/LICENSE +29 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_finger.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_finger_tip.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_inner_knuckle.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/left_knuckle.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_finger.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_finger_tip.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_inner_knuckle.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/right_knuckle.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/robotiq_base.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/meshes/collision/2f_85/ur_to_robotiq_adapter.stl +0 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/package.xml +34 -0
- batches-0.11.0b1/assets/robotiq_2f85/robotiq_description/urdf/robotiq_2f_85.urdf +302 -0
- batches-0.11.0b1/assets/so101/LICENSE +201 -0
- batches-0.11.0b1/assets/so101/assets/base_motor_holder_so101_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/base_so101_v2_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/motor_holder_so101_base_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/motor_holder_so101_wrist_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/moving_jaw_so101_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/rotation_pitch_so101_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/sts3215_03a_no_horn_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/sts3215_03a_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/under_arm_so101_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/upper_arm_so101_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/waveshare_mounting_plate_so101_v2_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/wrist_roll_follower_so101_v1_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/assets/wrist_roll_pitch_so101_v2_hull.stl +0 -0
- batches-0.11.0b1/assets/so101/so101_new_calib.urdf +453 -0
- batches-0.11.0b1/assets/ur5e/ur_description/LICENSE +25 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/base.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/forearm.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/shoulder.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/upperarm.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/wrist1.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/wrist2.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/meshes/ur5e/collision/wrist3.stl +0 -0
- batches-0.11.0b1/assets/ur5e/ur_description/package.xml +61 -0
- batches-0.11.0b1/assets/ur5e/ur_description/urdf/ur5e.urdf +297 -0
- batches-0.11.0b1/assets/usd/arm.usda +28 -0
- batches-0.11.0b1/assets/usd/arm_parts.usda +138 -0
- batches-0.11.0b1/assets/usd/scene.usda +48 -0
- batches-0.11.0b1/batchplan/__init__.py +141 -0
- batches-0.11.0b1/clippy.toml +6 -0
- batches-0.11.0b1/docs/README.md +26 -0
- batches-0.11.0b1/docs/api.md +146 -0
- batches-0.11.0b1/docs/architecture.md +150 -0
- batches-0.11.0b1/docs/benchmarks.md +771 -0
- batches-0.11.0b1/docs/devices.md +69 -0
- batches-0.11.0b1/docs/perception.md +151 -0
- batches-0.11.0b1/docs/planning.md +453 -0
- batches-0.11.0b1/docs/python.md +100 -0
- batches-0.11.0b1/docs/robots.md +172 -0
- batches-0.11.0b1/docs/training-data.md +227 -0
- batches-0.11.0b1/docs/verification.md +163 -0
- batches-0.11.0b1/examples/bench.rs +81 -0
- batches-0.11.0b1/examples/benchmark.rs +862 -0
- batches-0.11.0b1/examples/common/mod.rs +512 -0
- batches-0.11.0b1/examples/compare.rs +719 -0
- batches-0.11.0b1/examples/constraints.rs +185 -0
- batches-0.11.0b1/examples/control_loop.rs +120 -0
- batches-0.11.0b1/examples/datagen.rs +133 -0
- batches-0.11.0b1/examples/depth.rs +151 -0
- batches-0.11.0b1/examples/handover/main.rs +206 -0
- batches-0.11.0b1/examples/handover/rebot.rs +265 -0
- batches-0.11.0b1/examples/join.rs +170 -0
- batches-0.11.0b1/examples/so101_tracking.rs +231 -0
- batches-0.11.0b1/examples/tasks.rs +184 -0
- batches-0.11.0b1/examples/timing.rs +156 -0
- batches-0.11.0b1/examples/tool_paths.rs +86 -0
- batches-0.11.0b1/examples/usd_fk.rs +61 -0
- batches-0.11.0b1/pyproject.toml +53 -0
- batches-0.11.0b1/python/Cargo.lock +1789 -0
- batches-0.11.0b1/python/Cargo.toml +46 -0
- batches-0.11.0b1/python/LICENSE-APACHE +202 -0
- batches-0.11.0b1/python/LICENSE-MIT +21 -0
- batches-0.11.0b1/python/PYPI_README.md +13 -0
- batches-0.11.0b1/python/src/device.rs +224 -0
- batches-0.11.0b1/python/src/error.rs +84 -0
- batches-0.11.0b1/python/src/field.rs +361 -0
- batches-0.11.0b1/python/src/held.rs +125 -0
- batches-0.11.0b1/python/src/ik.rs +339 -0
- batches-0.11.0b1/python/src/lib.rs +46 -0
- batches-0.11.0b1/python/src/plan.rs +466 -0
- batches-0.11.0b1/python/src/robot.rs +475 -0
- batches-0.11.0b1/python/src/solved.rs +48 -0
- batches-0.11.0b1/python/src/task.rs +501 -0
- batches-0.11.0b1/python/src/timing.rs +282 -0
- batches-0.11.0b1/python/src/tool_path.rs +241 -0
- batches-0.11.0b1/python/src/world.rs +210 -0
- batches-0.11.0b1/python/tests/conftest.py +63 -0
- batches-0.11.0b1/python/tests/test_docs.py +27 -0
- batches-0.11.0b1/python/tests/test_planning.py +568 -0
- batches-0.11.0b1/python/tests/test_tasks.py +194 -0
- batches-0.11.0b1/python/tests/test_values.py +135 -0
- batches-0.11.0b1/python/uv.lock +436 -0
- batches-0.11.0b1/rustfmt.toml +4 -0
- batches-0.11.0b1/scripts/bench_model.py +364 -0
- batches-0.11.0b1/scripts/build_compare_cpp.sh +27 -0
- batches-0.11.0b1/scripts/check.sh +29 -0
- batches-0.11.0b1/scripts/compare.py +596 -0
- batches-0.11.0b1/scripts/compare_cpp.cc +378 -0
- batches-0.11.0b1/scripts/compare_planners.py +564 -0
- batches-0.11.0b1/scripts/cpp_planners.cc +298 -0
- batches-0.11.0b1/scripts/fetch_benchmark.sh +36 -0
- batches-0.11.0b1/scripts/fetch_panda_meshes.sh +46 -0
- batches-0.11.0b1/scripts/fetch_rebot.sh +86 -0
- batches-0.11.0b1/scripts/parity.py +334 -0
- batches-0.11.0b1/scripts/replay_mujoco.py +207 -0
- batches-0.11.0b1/scripts/report.py +219 -0
- batches-0.11.0b1/scripts/report_suite.sh +27 -0
- batches-0.11.0b1/scripts/robokit/batchplan_ik.py +130 -0
- batches-0.11.0b1/scripts/robokit/batchplan_mp.py +57 -0
- batches-0.11.0b1/scripts/robokit/batchplan_plugin.py +145 -0
- batches-0.11.0b1/scripts/run_comparison.sh +68 -0
- batches-0.11.0b1/scripts/run_suite.sh +29 -0
- batches-0.11.0b1/scripts/so101_tracking.py +153 -0
- batches-0.11.0b1/scripts/sync.sh +15 -0
- batches-0.11.0b1/scripts/test_bench.py +190 -0
- batches-0.11.0b1/scripts/validate.py +197 -0
- batches-0.11.0b1/scripts/validate_lerobot.py +68 -0
- batches-0.11.0b1/scripts/validate_usd.py +199 -0
- batches-0.11.0b1/src/constraint.rs +995 -0
- batches-0.11.0b1/src/cpu.rs +1999 -0
- batches-0.11.0b1/src/datagen.rs +1547 -0
- batches-0.11.0b1/src/description.rs +577 -0
- batches-0.11.0b1/src/device.rs +636 -0
- batches-0.11.0b1/src/edges.rs +604 -0
- batches-0.11.0b1/src/error.rs +70 -0
- batches-0.11.0b1/src/gpu.rs +2233 -0
- batches-0.11.0b1/src/grid.wgsl +44 -0
- batches-0.11.0b1/src/grids.wgsl +343 -0
- batches-0.11.0b1/src/held.rs +385 -0
- batches-0.11.0b1/src/ik.rs +507 -0
- batches-0.11.0b1/src/join.rs +813 -0
- batches-0.11.0b1/src/kernels.wgsl +1148 -0
- batches-0.11.0b1/src/lanes.rs +1347 -0
- batches-0.11.0b1/src/lerobot.rs +866 -0
- batches-0.11.0b1/src/lib.rs +120 -0
- batches-0.11.0b1/src/live.rs +809 -0
- batches-0.11.0b1/src/loops.rs +351 -0
- batches-0.11.0b1/src/meshes.rs +355 -0
- batches-0.11.0b1/src/mjcf.rs +658 -0
- batches-0.11.0b1/src/npy.rs +213 -0
- batches-0.11.0b1/src/rng.rs +51 -0
- batches-0.11.0b1/src/robot.rs +2058 -0
- batches-0.11.0b1/src/rrt/ahead.rs +1125 -0
- batches-0.11.0b1/src/rrt/connect.rs +1628 -0
- batches-0.11.0b1/src/rrt.rs +26 -0
- batches-0.11.0b1/src/sdf.rs +1221 -0
- batches-0.11.0b1/src/shortcut.rs +606 -0
- batches-0.11.0b1/src/spheres.rs +1014 -0
- batches-0.11.0b1/src/spline.rs +236 -0
- batches-0.11.0b1/src/task.rs +1534 -0
- batches-0.11.0b1/src/timing.rs +844 -0
- batches-0.11.0b1/src/tool_path.rs +783 -0
- batches-0.11.0b1/src/topp.rs +823 -0
- batches-0.11.0b1/src/trajopt.rs +2267 -0
- batches-0.11.0b1/src/types.rs +148 -0
- batches-0.11.0b1/src/urdf.rs +213 -0
- batches-0.11.0b1/src/usd.rs +812 -0
- batches-0.11.0b1/src/world.rs +664 -0
- batches-0.11.0b1/tests/allocations.rs +131 -0
- batches-0.11.0b1/tests/attach.rs +370 -0
- batches-0.11.0b1/tests/budget.rs +151 -0
- batches-0.11.0b1/tests/constraints.rs +820 -0
- batches-0.11.0b1/tests/cpu.rs +297 -0
- batches-0.11.0b1/tests/device.rs +1604 -0
- batches-0.11.0b1/tests/export.rs +638 -0
- batches-0.11.0b1/tests/failures.rs +224 -0
- batches-0.11.0b1/tests/goals.rs +409 -0
- batches-0.11.0b1/tests/gpu.rs +954 -0
- batches-0.11.0b1/tests/hands.rs +607 -0
- batches-0.11.0b1/tests/held.rs +432 -0
- batches-0.11.0b1/tests/join.rs +629 -0
- batches-0.11.0b1/tests/live.rs +1201 -0
- batches-0.11.0b1/tests/meshes.rs +366 -0
- batches-0.11.0b1/tests/mjcf.rs +478 -0
- batches-0.11.0b1/tests/pick_place.rs +932 -0
- batches-0.11.0b1/tests/robot.rs +1788 -0
- batches-0.11.0b1/tests/rrt.rs +792 -0
- batches-0.11.0b1/tests/sdf.rs +1246 -0
- batches-0.11.0b1/tests/task.rs +1506 -0
- batches-0.11.0b1/tests/threads.rs +107 -0
- batches-0.11.0b1/tests/tool_path.rs +595 -0
- batches-0.11.0b1/tests/touch.rs +414 -0
- batches-0.11.0b1/tests/trajectory.rs +1100 -0
- batches-0.11.0b1/tests/usd.rs +383 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Agent instructions
|
|
2
|
+
|
|
3
|
+
This file is for coding agents that use batchplan in a program. To change batchplan itself, read [`CLAUDE.md`](CLAUDE.md): its design rules, invariants and working rules. The guides under [`docs/`](docs/README.md) explain each topic in full; this page gathers what an agent needs first and links to them. From Python, the `batchplan` package makes the same calls by the same names; [Python](docs/python.md) says how they map and what it binds so far.
|
|
4
|
+
|
|
5
|
+
## Use batchplan: make once, warm up, reuse
|
|
6
|
+
|
|
7
|
+
Build the robot and its device once, warm the device, upload each scene once, and reuse all three for every request. Results are per problem: read each one's answer or its reason, never assume a batch succeeded together.
|
|
8
|
+
|
|
9
|
+
```rust,no_run
|
|
10
|
+
use batchplan::glam::{Quat, Vec3};
|
|
11
|
+
use batchplan::{
|
|
12
|
+
Device, Error, Obstacle, PlanFailure, PlanOptions, PlanProblem, Pose, Robot, RobotOptions,
|
|
13
|
+
Trajectory, World,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
fn main() -> Result<(), Error> {
|
|
17
|
+
// Once per robot: load it, check its spheres, make its device and warm it up.
|
|
18
|
+
let options = RobotOptions {
|
|
19
|
+
ee_links: vec!["tool0".into()],
|
|
20
|
+
..Default::default()
|
|
21
|
+
};
|
|
22
|
+
let robot = Robot::load("assets/ur5e/ur_description/urdf/ur5e.urdf", &options)?;
|
|
23
|
+
for link in robot.qualify()?.links {
|
|
24
|
+
println!("{}: spheres reach {:.1} mm past it", link.link, link.overhang * 1e3);
|
|
25
|
+
}
|
|
26
|
+
let device = Device::gpu(&robot).or_else(|_| Device::cpu(&robot))?;
|
|
27
|
+
device.warmup()?;
|
|
28
|
+
|
|
29
|
+
// Once per scene, and again whenever it changes: obstacles in the robot's frame, with the
|
|
30
|
+
// links that always touch the table it stands on allowed to.
|
|
31
|
+
let table = Obstacle::cuboid(Vec3::new(0.3, 0.0, -0.02), Vec3::new(0.6, 0.6, 0.02));
|
|
32
|
+
let mut world = World::new(vec![table]);
|
|
33
|
+
let mounted = world.always_touching(&robot);
|
|
34
|
+
world.touch.extend(mounted);
|
|
35
|
+
let worlds = device.upload(&[world])?;
|
|
36
|
+
|
|
37
|
+
// Per request: a pose in the robot's frame (quaternions x, y, z, w), from the measured state
|
|
38
|
+
// in `robot.joint_names()` order.
|
|
39
|
+
let target = Pose::new(Vec3::new(0.4, 0.2, 0.3), Quat::from_xyzw(1.0, 0.0, 0.0, 0.0));
|
|
40
|
+
let start = robot.default_q();
|
|
41
|
+
// IK from the start finds where the path ends: the nearest collision-free solution.
|
|
42
|
+
let problem = PlanProblem::new(0, start, target);
|
|
43
|
+
let plans = batchplan::plan(&device, &worlds, &[problem], &PlanOptions::default())?;
|
|
44
|
+
match (plans.best(0), &plans.failures[0]) {
|
|
45
|
+
(Some(path), _) => {
|
|
46
|
+
let trajectory = Trajectory::try_new(&robot, path, 1.0)?;
|
|
47
|
+
trajectory.check(&robot)?;
|
|
48
|
+
let samples = trajectory.sample(100.0)?; // positions, velocities, accelerations
|
|
49
|
+
println!("{} samples over {:.2} s", samples.len(), trajectory.duration());
|
|
50
|
+
}
|
|
51
|
+
(None, Some(PlanFailure::StartCollides { contacts })) => {
|
|
52
|
+
println!("the start collides: {contacts:?}");
|
|
53
|
+
}
|
|
54
|
+
(None, Some(PlanFailure::GoalUnreachable)) => println!("no IK seed reaches the pose"),
|
|
55
|
+
(None, why) => println!("no plan: {why:?}"),
|
|
56
|
+
}
|
|
57
|
+
Ok(())
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Conventions
|
|
62
|
+
|
|
63
|
+
- **Units.** Meters, radians (meters for prismatic joints), seconds, `f32` throughout.
|
|
64
|
+
- **Poses** are `Pose { position, rotation }` with [glam](https://docs.rs/glam) types, re-exported as `batchplan::glam`. glam's quaternions are x, y, z, w (`Quat::from_xyzw`), as ROS messages are; MuJoCo and Eigen's constructor are w, x, y, z. Rotations must be unit quaternions.
|
|
65
|
+
- **Frames.** Targets, obstacles and depth-camera poses are all in the robot's frame: its description's root link, or `world` for a robot made with `Robot::assemble`. Transform camera and map data into it first.
|
|
66
|
+
- **Joint order** is `robot.joint_names()`: the actuated joints after locking, numbered breadth first, so two arms' joints interleave. Map a controller's names to it; never assume the description's order. Planar, ball and floating joints become one-axis joints named `<joint>_x`, `_theta` and so on ([robots](docs/robots.md#kinematics)).
|
|
67
|
+
- **Hands** are the IK frames of `RobotOptions::ee_links`, numbered in that order. For a gripper, use a frame between the fingertips, z out of the hand and y along the closing direction: what `grasps` assumes.
|
|
68
|
+
|
|
69
|
+
### Lifecycle
|
|
70
|
+
|
|
71
|
+
- **Robot.** `Robot::load` fits collision spheres unless given a model: a fraction of a second for an arm at the default budget, seconds for thousands of spheres; save `robot.collision_model()` and pass it back as `RobotOptions::collision_model` to load the same spheres every time.
|
|
72
|
+
- **Device.** One per robot (`Device::gpu`, falling back to `Device::cpu`), made once: never inside a control loop or per request. `device.warmup()` before timing anything ([devices](docs/devices.md#make-once-warm-up-reuse)).
|
|
73
|
+
- **Worlds.** `device.upload(&worlds)` once per scene. Uploads are cheap (0.1–0.2 ms for 512 tabletop worlds), so a scene that changes is uploaded again; there is no partial update ([changing a scene](docs/perception.md#changing-a-scene)).
|
|
74
|
+
- **Locked joints that move**, as a torso or head the robot plans without: `robot.relock(&values)` then `device.with_robot(&relocked)`, which compiles nothing and keeps the uploaded worlds. Fit the collision model with those joints free and load with it: a model records the joints it was fitted with locked, whose pairs that meet only at other values would go unchecked, and `relock` refuses to move them ([robots](docs/robots.md#kinematics)).
|
|
75
|
+
- **Held objects.** `holding: Some(held)` on a `PlanProblem`, `IkProblem` or `ToolPath`, with the device without the object: one call takes problems holding different objects or grasps, a batch per hold ([holding objects](docs/robots.md#holding-objects)). On a GPU, warm up a device holding any object in each hand you will hold in: the first call holding there builds its kernels. For a device that holds one, `robot.attach(&AttachedObject)` then `device.with_robot(&held)`: the same GPU or threads, and the uploaded worlds still work. The held robot's GPU kernels are its own: warm its device up too before timing it. `robot.detach(name)` reverses it. Attachment is collision only: there is no payload mass or inertia, so timing a heavy carry ignores the load; slow it with a speed scale ([trajectories](docs/planning.md#executable-trajectories)).
|
|
76
|
+
- **Live loops** call `Follower::tick` or `servo` each period and nothing that builds: pass the measured state to `tick` so a robot held back or pushed is followed from where it is (held there, `Blocked`, with `FollowerOptions::replan` off). A controller in another process takes each replan from `FollowStatus::Replanned { at, trajectory }`, with `FollowerOptions::lead` for the time sending it takes. `Stopping { contacts }` and `Collides { at, contacts }` name what is in the way, links and obstacles as `PlanFailure::StartCollides` names them ([live motion](docs/planning.md#live-motion)).
|
|
77
|
+
|
|
78
|
+
### Choose the call
|
|
79
|
+
|
|
80
|
+
| Task | Call | Guide |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| Forward kinematics | `robot.ee_pose(q, hand)`, `robot.link_pose(q, link)`, `robot.spheres(q)` | [robots](docs/robots.md) |
|
|
83
|
+
| Configurations for hand poses | `solve_ik`; `ik::failures` for why one has none | [IK](docs/planning.md#inverse-kinematics) |
|
|
84
|
+
| A collision-free path to a configuration or a hand pose | `plan`, then `Trajectory::try_new` | [planning](docs/planning.md#planning-a-path) |
|
|
85
|
+
| The hand along straight lines | `follow` | [tool paths](docs/planning.md#moving-the-hand-along-a-path) |
|
|
86
|
+
| A hand kept upright, in a box or relative to another hand | `HandConstraint` on the problem | [constraints](docs/planning.md#constraints-along-the-path) |
|
|
87
|
+
| Picking up and setting down | `grasps`, `pick`, `place` | [tasks](docs/planning.md#picking-up-and-setting-down) |
|
|
88
|
+
| Motions run without stopping between them | `join` | [joins](docs/planning.md#joining-motions) |
|
|
89
|
+
| Running a motion while the world changes | `Follower`, `servo` | [live motion](docs/planning.md#live-motion) |
|
|
90
|
+
| Obstacles from depth images or point clouds | `SdfGrid`, `OccupancyMap`, `DepthImage::robot_mask` | [perception](docs/perception.md) |
|
|
91
|
+
| Training data | `datagen`, `npy::export`, `lerobot::export` | [training data](docs/training-data.md) |
|
|
92
|
+
|
|
93
|
+
IK finds configurations; a plan finds the path to one, or to a pose, solving IK from the start itself. Solve IK first only to choose among its solutions. Every call takes many problems, each naming its world: batch them rather than calling once per problem, since each GPU call has a fixed cost.
|
|
94
|
+
|
|
95
|
+
### Qualify the robot
|
|
96
|
+
|
|
97
|
+
A robot that loads may still have spheres unfit to plan with. Before planning with a new robot or a hand-tuned collision model ([qualifying a robot](docs/robots.md#qualifying-a-robot)):
|
|
98
|
+
|
|
99
|
+
1. `robot.qualify()`: each link's `overhang` past its geometry, the `uncovered` share of its surface and its `thickness`, and each hand's `tips`. Fingers whose overhang or tip is a large part of the smallest object you will grasp will collide with what it stands on; give them a tighter tolerance (`RobotOptions::sphere_tolerances`) or a finer `SphereOptions::voxel_size`.
|
|
100
|
+
2. `world.always_touching(&robot)` with the table the robot stands on: links that touch it in every configuration need a touch, or every plan's start collides.
|
|
101
|
+
3. Fingers that are part of the palm's link, as the SO-101's fixed jaw: name them in `RobotOptions::finger_links`, or grasps where they meet the object fail.
|
|
102
|
+
4. `device.contacts(&worlds, &item_world, &q, 0.0)` at the default configuration, the SRDF's named states and a few others: each should collide with nothing.
|
|
103
|
+
|
|
104
|
+
### Read results
|
|
105
|
+
|
|
106
|
+
- `ik.best(i)`, `plans.best(i)` and `solved()` return only valid answers; `IkResult::success` and `PlanResult::valid` are per seed, problem-major (`problem * seeds + seed`).
|
|
107
|
+
- A plan is B-spline control points, not a trajectory: time it with `Trajectory::try_new` (`Trajectory::moving` from a moving start), sample it with `at` or `sample`, and `check` it before it runs on hardware. Never send control points to a controller.
|
|
108
|
+
- Plans do not depend on the batch or the device; with a `time_budget`, they depend on the machine's speed. IK starts every problem from the same seeds in any batch, but a GPU rounds differently from the CPU, and differently on either side of where it switches how many invocations share a configuration (4,096 configurations, more for robots with many spheres), so solutions there can differ slightly, and so can plans to poses, which end where IK solves them.
|
|
109
|
+
|
|
110
|
+
### Diagnose a failed request, in order
|
|
111
|
+
|
|
112
|
+
Check these before raising budgets:
|
|
113
|
+
|
|
114
|
+
1. **The input.** An `Error::Input` names what was refused and the value given: shapes, joint order, unit quaternions, joint limits (starts and goals must be within them), frames.
|
|
115
|
+
2. **The start.** `PlanFailure::StartCollides { contacts }` names each link and obstacle, or pair of links. A link touching the table in every configuration needs a touch (`always_touching`); a start inside a held object's spheres needs the object attached.
|
|
116
|
+
3. **The goal.** `PlanFailure::GoalCollides`, and for a pose goal `GoalUnreachable`, which more `PlanOptions::goal_ik` iterations may fix; for IK, `ik::failures` tells `Unreachable` from `Collides { contacts }`.
|
|
117
|
+
4. **The search.** `NoPath`: more `rrt.max_iterations`, or the way is blocked. `NoSpline`: more `control_points`, or a slower start motion. `OutOfTime`: a longer `time_budget`.
|
|
118
|
+
5. **Tasks.** A `TaskFailure` prints where and why every candidate stopped; each `CandidateStop` has the step, the reason and the contacts, whose obstacles are numbered as in the task's world; in a place, `world.obstacles.len()` is the object where it is set down. `grasps` says why it found none. Grasps near a support need `GraspOptions::tip` and `floor`, which keeps the open fingers over it too; from above, they keep at least `overlap` of the fingers (1 cm) in the object, so an object whose top is less than that over the floor has none.
|
|
119
|
+
|
|
120
|
+
### Footguns
|
|
121
|
+
|
|
122
|
+
- glam's quaternion order is x, y, z, w; a w-first source passed straight through turns the hand.
|
|
123
|
+
- `PlanOptions::seeds` is 0: no trajectory optimization unless asked for.
|
|
124
|
+
- A world's touches name links: worlds where a held object may touch something are uploaded to the device of the robot holding it.
|
|
125
|
+
- `holding` takes the device without the object: one already holding a `Held` in that hand is an `Error::Input`.
|
|
126
|
+
- `grasps` returns poses in the object's frame; with a `floor`, pass the obstacle as it stands in the world.
|
|
127
|
+
- `Robot::lower` and `upper` are ranges; continuous joints turn without limit and wrap.
|
|
128
|
+
- `Follower::tick` with `None` trusts the trajectory; on hardware, pass the measured state.
|
|
129
|
+
- `FollowStatus::Stopping { contacts }` numbers obstacles as in the worlds of the tick the stop began at, on every tick of the stop: map them through those worlds, not the current ones.
|
|
130
|
+
- A `Follower` on a tool path (`follow`) needs `FollowerOptions::replan` off: a replan leaves the path for a free-space motion to its end. Off, a collision ahead stops it on the path and a robot past `tolerance` is held where it is, both `Blocked` at rest ([live motion](docs/planning.md#live-motion)).
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
What changed in each release, for people upgrading, in the format of
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
|
|
5
|
+
[Cargo's semver](https://doc.rust-lang.org/cargo/reference/semver.html): before 1.0, a new minor
|
|
6
|
+
version (0.9 → 0.10) may break the interface, and a patch (0.9.0 → 0.9.1) breaks nothing.
|
|
7
|
+
[Contributing](CONTRIBUTING.md#releases) says how releases are made.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Breaking:** `plan` takes the hands' poses as a goal: `PlanProblem::goal` is a `Goal`, a
|
|
14
|
+
configuration (`Goal::Joints`) or a pose or `None` per hand (`Goal::Poses`), and
|
|
15
|
+
`PlanProblem::new` takes either, a `Pose` for a one-handed robot. `plan` solves IK for every
|
|
16
|
+
pose goal of the call in one batch, seeded from each start and keeping its held joints, what it
|
|
17
|
+
holds and its constraints, and plans to the collision-free solution nearest the start up to
|
|
18
|
+
whole turns. Poses no seed reaches collision-free are solved again with four times the seeds, up
|
|
19
|
+
to sixteen times, while a `time_budget` lasts; those still unreached fail as
|
|
20
|
+
`PlanFailure::GoalUnreachable`, or `GoalCollides` where IK reaches them only in collision.
|
|
21
|
+
`PlanOptions::goal_ik` sets that IK. `PlanProblem::new` takes a configuration as a `Vec<f32>`,
|
|
22
|
+
`&[f32]`, `&Vec<f32>`, `[f32; N]` or `&[f32; N]`; convert any other form with `to_vec()`. Code
|
|
23
|
+
reading a goal as a slice reads `goal.joints()`, a struct literal wraps a configuration in
|
|
24
|
+
`Goal::Joints`, and code that writes out every field of `PlanOptions` needs the new one. The benchmark's ik+plan mode plans to the
|
|
25
|
+
goal pose this way. In Python, `PlanProblem` takes a `Pose`, or a `Pose` or `None` per hand, as
|
|
26
|
+
its goal, beside `PlanOptions.goal_ik` and `PlanFailure.GoalUnreachable`.
|
|
27
|
+
- Python bindings, in `python/`: the `batchplan` package, built with maturin
|
|
28
|
+
([Python](docs/python.md)). Robots, assembled ones among them, obstacles and worlds, devices, IK
|
|
29
|
+
(`solve_ik`, `ik_failures`), planning with hand constraints (`plan`), timing (`Trajectory`),
|
|
30
|
+
tool paths (`follow`), whole tasks (`pick`, `place`, `grasps`, `place_poses`) and held objects,
|
|
31
|
+
with NumPy arrays in and out, `batchplan::Error`'s kinds as exception classes, and the
|
|
32
|
+
interpreter released while each call works. Values are frozen and changed with
|
|
33
|
+
`replace(**changes)`, as Rust's struct update syntax does. Install it from the repository with
|
|
34
|
+
`pip install "git+https://github.com/natsuku-lab/batchplan#subdirectory=python"`, which needs a
|
|
35
|
+
Rust toolchain.
|
|
36
|
+
- **Breaking:** `PlanProblem::holding`, `IkProblem::holding` and `ToolPath::holding`: an object a
|
|
37
|
+
hand holds (a `Held`, as `TaskPlan::held` gives it), per problem. One `plan`, `solve_ik` or
|
|
38
|
+
`follow` call takes problems holding nothing, different objects, or one object by different
|
|
39
|
+
grasps, and gives each what a device holding its object alone gives: it groups them by what
|
|
40
|
+
they hold and runs a batch per group, in parallel, on the device's GPU or threads. Holding per
|
|
41
|
+
problem took a device and a call per hold. Pass the device without the object; one already
|
|
42
|
+
holding a `Held` in that hand is an `Error::Input`. Code that writes out every field of
|
|
43
|
+
`IkProblem`, `PlanProblem` or `ToolPath` needs the new one (`holding: None`).
|
|
44
|
+
- `SphereOptions` implements `PartialEq`.
|
|
45
|
+
- **Breaking:** `GraspOptions::overlap`, 1 cm by default: the least the fingers reach into the
|
|
46
|
+
object in a grasp from above over a `floor`. An object flatter than `tip` and the floor's
|
|
47
|
+
clearance is now held from above with the fingertips at the floor and the frame over its top,
|
|
48
|
+
as long as its top is at least `overlap` over the floor. `grasps` left out every grasp from
|
|
49
|
+
above of such an object: an 8.8 x 4.8 x 1.85 cm package got only grasps from the sides, which
|
|
50
|
+
put the hand into the table. Every grasp from above over a floor now keeps at least `overlap`
|
|
51
|
+
of the fingers in the object, where it kept the frame inside it, so with `tip` under 1 cm (the
|
|
52
|
+
default 0 among them) some it returned are left out. Code that writes out every field of
|
|
53
|
+
`GraspOptions` needs the new one.
|
|
54
|
+
- **Breaking:** `FollowerOptions::replan`, on by default. Off, a `Follower` keeps its
|
|
55
|
+
trajectory's path, as a motion along a tool path must: where the rest of the motion collides,
|
|
56
|
+
it stops on the path, and where the robot is measured past `tolerance`, it holds it there,
|
|
57
|
+
`Blocked`. In either case it used to plan a free-space motion to the goal, off the path. Code
|
|
58
|
+
that writes out every field of `FollowerOptions` needs the new one.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- Batched IK spends less time around the solve: a problem's seeds share its targets instead
|
|
63
|
+
of each carrying a copy, large batches are seeded on the device's threads, buffers go to the
|
|
64
|
+
GPU without an extra copy, and a robot without collision spheres skips the collision push,
|
|
65
|
+
which is zero for it. The same results, bit for bit. In RoboKit's IK benchmark on an RTX 4090
|
|
66
|
+
(a Panda, collision off, 64 seeds of 30 steps), 1,000 targets take 4.5 ms where they took
|
|
67
|
+
10.8, 2,000 take 9.1 ms where 23.2, and one takes 0.9 ms where 1.26
|
|
68
|
+
([Benchmarks](docs/benchmarks.md#against-robokit)).
|
|
69
|
+
- **Breaking:** `IkOptions::collision_step` must be finite: an infinite or NaN one is an input
|
|
70
|
+
error. NaN and negative infinity used to mean no push, and positive infinity solved nothing
|
|
71
|
+
(the CPU left every seed where it started).
|
|
72
|
+
- IK on a GPU takes 10-28% less time: the kernels are built without the counter naga adds to
|
|
73
|
+
every loop to rule out infinite ones, which theirs cannot be (the crate's one `unsafe`). One
|
|
74
|
+
target's 64 seeds of 30 steps take 1.01 ms where they took 1.27 on an M4 Pro for a Panda
|
|
75
|
+
without collision spheres (2.83 ms where 3.33 with them), and 0.53 ms where 0.74 on an RTX
|
|
76
|
+
4090 (about 1.2 ms where 1.33). A GPU's results round differently for it, as they do from
|
|
77
|
+
driver to driver ([Benchmarks](docs/benchmarks.md#how-the-speed-came)).
|
|
78
|
+
- Trajectories are sooner: time-optimal timing uses the full velocity and acceleration limits,
|
|
79
|
+
and where its smoothed time map overshoots a limit, slows the profile there instead of
|
|
80
|
+
stretching the whole motion for it. Planned Panda motions take 6.6% less time on average, the
|
|
81
|
+
hand's straight moves 3.0%, replans from a moving start 5.7%, and the 2,600 benchmark
|
|
82
|
+
problems' motions 5.0%, within the same limits (`Trajectory::check`); more joins are worth
|
|
83
|
+
blending. Timing costs less than it did (a Ryzen thread): a path from rest 9% less, and one
|
|
84
|
+
from a moving start 59% less (475 µs where 1,172), timed once with its start at the full
|
|
85
|
+
limits, its candidate time maps turned down at a quarter of the checking density, and each
|
|
86
|
+
map measured over the joints in contiguous rows. A moving start's timing and a stop's are
|
|
87
|
+
also held to their limits either side of where they cross the path's knots, between the
|
|
88
|
+
samples checked, where jerk could pass its limit by up to 12%, and in a stop acceleration
|
|
89
|
+
by up to 20% and jerk by 19%: of 960 stops asked early in planned Panda motions 52 are
|
|
90
|
+
refused where 40 were. Replans from a moving start take 11% less time one at a time on a
|
|
91
|
+
Panda and 70% less on an SO-101; a batch of 96 Panda replans takes 65 ms where it took 61
|
|
92
|
+
(within the runs' spread), since a moving start no timing fits costs more to turn down
|
|
93
|
+
(2.0 ms where 1.6). A spline that cannot follow the start motion is then tried again,
|
|
94
|
+
stopping at every waypoint, with less of that timing, which takes a further quarter off a
|
|
95
|
+
Panda's replans one at a time and a sixth off the SO-101's (an M4 Pro)
|
|
96
|
+
([Benchmarks](docs/benchmarks.md#timing-up-to-the-limits)).
|
|
97
|
+
- **Breaking:** `IkOptions::default()` is 64 seeds of 30 steps of at most 1 rad, where it was 32
|
|
98
|
+
seeds of 60 steps of at most 0.3 rad: at 0.3 rad, about half the seeds ran out of steps before
|
|
99
|
+
converging. The same problems solve. Results carry 64 seeds per problem, and `TaskOptions`,
|
|
100
|
+
`DemoOptions`, `PickPlaceOptions` and `PlanOptions::goal_ik` inherit the new IK; pass the old
|
|
101
|
+
values to keep the old results.
|
|
102
|
+
- **Breaking:** An IK step that would move no joint by more than 1e-6 rad is not taken, on the
|
|
103
|
+
CPU and the GPU alike: a seed that has come to rest stays exactly there, and CPU threads skip
|
|
104
|
+
its remaining steps, which makes their IK about a fifth faster. Seeds can differ from before
|
|
105
|
+
by a few microradians. Together with the new defaults, the 2,600 benchmark problems plan to
|
|
106
|
+
pose goals 1.8 times as fast in batches on a Radeon 8060S (6,225 against 3,489 per second, and
|
|
107
|
+
in 5 ms instead of 9 for one) and 1.18 times as fast on 32 CPU threads (3,344 against 2,836);
|
|
108
|
+
IK for 512 tabletop targets takes 71 ms instead of 84 on those threads.
|
|
109
|
+
- `solve_ik` on a GPU checks the solved configurations' clearances in the same submission, from
|
|
110
|
+
the configurations already on the GPU, and reads everything back at once: one wait per 65,536
|
|
111
|
+
seeds instead of three, about 0.4 ms less per call.
|
|
112
|
+
- **Breaking:** `Robot::load`, `Robot::from_urdf` and `Robot::assemble` refuse a collision
|
|
113
|
+
model whose spheres have a non-finite center or radius or a negative one, or whose
|
|
114
|
+
self-collision buffer for a link is not finite or would shrink one of its spheres below nothing,
|
|
115
|
+
naming the link and the value (`Error::Load`; `Error::Input` from `from_urdf`). Spheres fitted to
|
|
116
|
+
geometry that is not finite, or so large it overflows, are refused alike. Such models loaded
|
|
117
|
+
before, and a sphere smaller than nothing checked as farther from what it touched than it was.
|
|
118
|
+
- **Breaking:** `Held` is `batchplan::Held`, no longer also `batchplan::task::Held`.
|
|
119
|
+
- A device keeps the spheres it fits to held objects, the latest 64, and shares them with the
|
|
120
|
+
devices `with_robot` makes from it. `pick`, `place` and every call holding a cylinder or a
|
|
121
|
+
capsule fitted it again, which takes milliseconds; a box or a ball is covered in microseconds.
|
|
122
|
+
- **Breaking:** `FollowStatus::Stopping { contacts }` and `FollowStatus::Collides { at, contacts }`
|
|
123
|
+
name what is in the way, as `Device::contacts` lists it: what the rest of the motion collided
|
|
124
|
+
with where it was first found to, numbered as in the worlds of the tick the stop began at and
|
|
125
|
+
repeated on every tick of the stop while it is clear, and what the robot collides with at
|
|
126
|
+
`at`. A graze a GPU device's check finds just outside what its CPU threads list names the
|
|
127
|
+
nearest within 0.1 mm. They said only that something collided. `Stopping` was a unit variant.
|
|
128
|
+
- **Breaking:** `grasps` keeps the open fingers above `GraspOptions::floor` too, `opening` apart
|
|
129
|
+
and reaching `tip` past the frame, where it kept only the frame and the point between the
|
|
130
|
+
fingertips. A grasp tilted about a bar over a support, as a drawer's handle, swung one open
|
|
131
|
+
finger into it: of a bar whose top is 3.7 cm over a plate, every one of the 12 grasps tilted
|
|
132
|
+
from above at 16 turns put a Panda's finger into the plate. They are now held as far out as
|
|
133
|
+
keeps the finger over the floor, and left out where that holds less than `overlap`: the 4 left
|
|
134
|
+
all pick it. Grasps from the side that close upright on an object standing on the table, their
|
|
135
|
+
lower finger below the table's top, are left out too, so an object only those fit, such as a
|
|
136
|
+
box too wide across its top to grasp from above, gets an `Error::Input` where it got them. An
|
|
137
|
+
infinite `opening` is an `Error::Input`.
|
|
138
|
+
|
|
139
|
+
### Fixed
|
|
140
|
+
|
|
141
|
+
- A plan from a moving start is optimized (`PlanOptions::seeds`) unless its path is within 2% of
|
|
142
|
+
the straight line from the start: the line was measured from the path's first control point,
|
|
143
|
+
which lies behind a moving start, so paths that bent by half as much again were left
|
|
144
|
+
unoptimized.
|
|
145
|
+
- `Trajectory::stop` stops a motion from more of the states it passes through, and sooner. It
|
|
146
|
+
refused where a joint ran within 0.6% of its velocity limit, since a stop's velocity was held
|
|
147
|
+
to the margin under the limit even where it starts, and often while a motion still gathered
|
|
148
|
+
speed, since braking at once breaks the jerk limit there, or stopped only by creeping to the
|
|
149
|
+
path's end. It now also tries carrying on for one span with the motion's jerk, half of it or
|
|
150
|
+
none before braking, and keeps the stop that ends soonest along the path. Trajectories timed
|
|
151
|
+
up to the limits pass through such states more often. Through the first fifth of 48 planned
|
|
152
|
+
Panda motions, 52 of 960 stops are refused where 153 were and one takes longer than going on
|
|
153
|
+
where 66 did; of 19 states at a velocity limit, 9 stop where none did. A quarter of the way
|
|
154
|
+
along 438 motions, stops last 0.633 s where they lasted 0.646, and take 0.62 ms to find where
|
|
155
|
+
they took 0.73 (`examples/timing.rs`, which now times them, on an M4 Pro). A `Follower` that
|
|
156
|
+
must stop where none is found keeps to its trajectory, which comes to rest at the path's end.
|
|
157
|
+
- Optimized seeds (`PlanOptions::seeds`) are validated as searched splines are checked: at samples no
|
|
158
|
+
farther apart than `shortcut.resolution`, keeping the margin from the world searched splines keep
|
|
159
|
+
away from their ends (2 mm, or `shortcut.clearance` if less). Validation kept no margin and
|
|
160
|
+
sampled each span `validate_substeps` times however far it moved, so optimization, which pulls
|
|
161
|
+
paths taut against obstacles, returned valid paths that collided between samples, as on picks
|
|
162
|
+
under a table. Their lengths, which `best` compares, are measured at those samples too.
|
|
163
|
+
- `plan` refuses a start or a joint goal that is not finite. A continuous joint's range has no
|
|
164
|
+
limits, so a start at infinity there passed the limit check and planned a path of infinite
|
|
165
|
+
control points marked valid.
|
|
166
|
+
- `Trajectory::at` and `Trajectory::sample` keep each position between the control points it
|
|
167
|
+
blends, so a path that runs along a joint limit never samples past it. Rounding left samples a
|
|
168
|
+
unit or two in the last place past (up to 4.8e-7 rad), which limit checks with no tolerance
|
|
169
|
+
refuse.
|
|
170
|
+
- `pick` builds each holding device where it moves with it again, so building one overlaps the
|
|
171
|
+
other grasps' moves. 0.10.0 built them all first, which cost picks on a GPU device about 1.5%:
|
|
172
|
+
64 picks of the tasks example (`cargo run --release --example tasks -- 64`) took 185.5 ms on
|
|
173
|
+
an M4 Pro and take 183.2 ms now, against 181.5 ms in 0.9.0 (medians of seven alternated runs).
|
|
174
|
+
The rest of that gap is in grasping and moving in, whose code did not change.
|
|
175
|
+
- `grasps` refuses an object whose size is negative or not finite, or whose rotation is not a
|
|
176
|
+
unit quaternion, naming it, as uploading a world does.
|
|
177
|
+
|
|
178
|
+
## [0.10.0] - 2026-11-07
|
|
179
|
+
|
|
180
|
+
### Changed
|
|
181
|
+
|
|
182
|
+
- **Breaking:** `Robot::attach` refuses a shape whose size is negative or not finite, or whose
|
|
183
|
+
rotation is not a unit quaternion to within 1e-3, as uploading a world does, naming the shape.
|
|
184
|
+
0.9.0 attached such shapes with spheres that did not cover them. Normalize a rotation read
|
|
185
|
+
with few digits, such as (0, 0, 0.71, 0.71), before attaching it.
|
|
186
|
+
- The model's `source` also gives the largest of each link's reach over its own tolerance.
|
|
187
|
+
- `Robot::attach` fits a single shape in its own frame and places its spheres by the shape's
|
|
188
|
+
pose, so where it sits on its link no longer changes them, and `pick`, `place` and attaching
|
|
189
|
+
`Held::attachment` give a held object the same spheres.
|
|
190
|
+
- `pick` and `place` fit each held object once and place its spheres by each grasp, where they
|
|
191
|
+
attached it at each grasp and fitted it again: picks of cylinders and capsules spend less of
|
|
192
|
+
their time attaching.
|
|
193
|
+
|
|
194
|
+
### Fixed
|
|
195
|
+
|
|
196
|
+
- `place` refuses a held object whose size is not finite, as an input error naming its task.
|
|
197
|
+
- A pick's contacts where it lifts the object name obstacles as the task's world numbers them:
|
|
198
|
+
every obstacle after the picked one came back one index too low.
|
|
199
|
+
- Where the budget reaches every tolerance but cannot hold each link within its own, what it has
|
|
200
|
+
left holds the spheres tighter. It used to go unspent, with spheres reaching up to twice their
|
|
201
|
+
link's tolerance. A stand of two boxes held to 2 cm (`tests/robot.rs`) reached 36.6 mm with 379
|
|
202
|
+
of 512 spheres, and now 24.1 mm with 502; the Panda at 1,200 spheres reached 9.5 mm against
|
|
203
|
+
5 mm, and now 5.8 mm. The search for the least growth makes such fits two to three times as
|
|
204
|
+
long: the Panda's at 1,200 spheres takes 0.20 s, where it took 0.10 s, and the stand's at 512
|
|
205
|
+
0.13 s, where it took 0.05 s.
|
|
206
|
+
- Covers are refined until they reach every surface sample, where they stopped after 8 rounds,
|
|
207
|
+
so large boxes keep to their tolerance where the budget allows: the stand reached 25.7 mm at
|
|
208
|
+
1,024 spheres, and now 20.0 mm.
|
|
209
|
+
|
|
210
|
+
## [0.9.0] - 2026-11-06
|
|
211
|
+
|
|
212
|
+
### Changed
|
|
213
|
+
|
|
214
|
+
- Fitted spheres reach less far past thin parts. Each link's spheres reach at most twice its
|
|
215
|
+
tolerance, scaled by what the budget allows, and less as the budget grows. Where the budget
|
|
216
|
+
affords it, each closed link is held within its own tolerance. Parts thinner than 0.1 mm and
|
|
217
|
+
vertices whose averaged normal leads out of the geometry may reach a little farther: at 2,048
|
|
218
|
+
spheres the Panda's base reaches 5.18 mm against 5 mm. The model's `source` names the scale.
|
|
219
|
+
Fitted models change, and saved models load as before.
|
|
220
|
+
- Spheres held that tight leave slivers of surface about a millimeter deep outside them:
|
|
221
|
+
`LinkQualification::uncovered` reaches about 2% of a link, where it was a fraction of a
|
|
222
|
+
percent.
|
|
223
|
+
- `Robot::qualify` measures on samples the fit did not draw.
|
|
224
|
+
|
|
225
|
+
## [0.8.0] - 2026-11-06
|
|
226
|
+
|
|
227
|
+
### Added
|
|
228
|
+
|
|
229
|
+
- `Robot::relock` holds joints locked at load at new values, and `Device::with_robot` takes the
|
|
230
|
+
relocked robot without compiling kernels or uploading worlds again.
|
|
231
|
+
- `FollowerOptions::lead`: time after the planning budget for a replan to reach the robot.
|
|
232
|
+
|
|
233
|
+
### Changed
|
|
234
|
+
|
|
235
|
+
- **Breaking:** `FollowStatus::Replanned` carries the new trajectory: `Replanned { at, trajectory }`.
|
|
236
|
+
- **Breaking:** `FollowerOptions` gains `lead`, and `CollisionModel` gains `locked_joints`, the
|
|
237
|
+
joints locked while fitting. A model fitted with locked joints must be loaded with them locked
|
|
238
|
+
at the same values, and `relock` refuses to move them. Models saved before 0.8.0 load as before.
|
|
239
|
+
|
|
240
|
+
### Fixed
|
|
241
|
+
|
|
242
|
+
- `Device::evaluate` refuses configurations that are not finite, and `Robot::load` lock values
|
|
243
|
+
that are not finite.
|
|
244
|
+
- GPU picks at small batches are as fast as in 0.6.1 again: IK that does not push away from
|
|
245
|
+
obstacles no longer splits each configuration's work across GPU invocations, and robots with
|
|
246
|
+
many spheres keep the split past 4,096 configurations.
|
|
247
|
+
|
|
248
|
+
Earlier releases have no notes beyond their commits.
|
|
249
|
+
|
|
250
|
+
[Unreleased]: https://github.com/natsuku-lab/batchplan/compare/v0.10.0...HEAD
|
|
251
|
+
[0.10.0]: https://github.com/natsuku-lab/batchplan/compare/v0.9.0...v0.10.0
|
|
252
|
+
[0.9.0]: https://github.com/natsuku-lab/batchplan/compare/v0.8.0...v0.9.0
|
|
253
|
+
[0.8.0]: https://github.com/natsuku-lab/batchplan/compare/v0.7.0...v0.8.0
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. Issues and pull requests are welcome at
|
|
4
|
+
[natsuku-lab/batchplan](https://github.com/natsuku-lab/batchplan).
|
|
5
|
+
|
|
6
|
+
## Building and testing
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
cargo build --release --all-targets --features lerobot,usd
|
|
10
|
+
BATCHPLAN_REQUIRE_GPU=1 cargo test --release --features lerobot,usd # fails rather than skips the GPU tests without an adapter
|
|
11
|
+
cargo test --release --no-default-features # the CPU-only build
|
|
12
|
+
cargo clippy --release --all-targets --features lerobot,usd # zero warnings; CI denies them
|
|
13
|
+
cargo doc --no-deps --features lerobot,usd # zero warnings
|
|
14
|
+
(cd demos && cargo clippy --release --all-targets) # the Rerun demos, a crate of their own
|
|
15
|
+
(cd python && BATCHPLAN_REQUIRE_GPU=1 uv run pytest) # the Python bindings, built and tested through the package
|
|
16
|
+
scripts/check.sh # every lint CI runs, on every crate: run it before pushing
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
CI runs the same on Linux, with the GPU kernels on Mesa's llvmpipe, and builds on macOS, Windows
|
|
20
|
+
and aarch64 Linux. [Verification](docs/verification.md) says what each test pins down.
|
|
21
|
+
|
|
22
|
+
## Style
|
|
23
|
+
|
|
24
|
+
The code follows [Google's Rust style guide](https://google.github.io/styleguide/rustguide.html).
|
|
25
|
+
Format with stable rustfmt, then check the guide's options that only nightly rustfmt has:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cargo fmt
|
|
29
|
+
rustup toolchain install nightly --profile minimal -c rustfmt
|
|
30
|
+
cargo +nightly fmt --check -- --config imports_granularity=Module,hex_literal_case=Lower,format_macro_matchers=true
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Clippy checks what it can of the guide (`[lints]` in `Cargo.toml`, and `src/lib.rs` for the
|
|
34
|
+
library). Where the guide leaves a choice to the codebase:
|
|
35
|
+
|
|
36
|
+
- **Imports.** No glob imports, except `use super::*` in test modules and the rayon and
|
|
37
|
+
fearless_simd preludes. Call free functions through their module (`world::box_distance`,
|
|
38
|
+
`datagen::handover`, `batchplan::plan`), and import types by name, unless the name would clash
|
|
39
|
+
or comes from wgpu or rerun.
|
|
40
|
+
- **Modules.** A module with children only declares and re-exports them.
|
|
41
|
+
- **API.** Document every public item; mark pure functions `#[must_use]`; name a constructor that
|
|
42
|
+
returns a `Result` `try_new`; return named fields rather than tuples; spell names out (the
|
|
43
|
+
field's own terms, such as `dof` and `q`, excepted). Results and enums that may grow are
|
|
44
|
+
`#[non_exhaustive]`; options and problems keep struct literals, so struct update syntax works.
|
|
45
|
+
- **Implementation.** `expect` says why it cannot fail, `for` loops replace sequential
|
|
46
|
+
`for_each`, iterator chains are named before a `for` loop, and assertions say why they hold.
|
|
47
|
+
|
|
48
|
+
## Changes
|
|
49
|
+
|
|
50
|
+
- **A new test must fail on the unfixed code.** Plant the bug and watch the test fail.
|
|
51
|
+
- **The CPU and the GPU implement the same math.** A change to one (`src/cpu.rs`, the kernels in
|
|
52
|
+
`src/*.wgsl` and `gpu.rs`) lands in the other in the same change; the parity tests catch drift.
|
|
53
|
+
- **Performance claims need an A/B comparison on one machine.** Build the previous commit in a
|
|
54
|
+
`git worktree` with its own `CARGO_TARGET_DIR`, and alternate runs with yours after checking the
|
|
55
|
+
machine is idle. One run proves nothing.
|
|
56
|
+
- **Numbers in the docs come from runs you can name.** [Benchmarks](docs/benchmarks.md) says how
|
|
57
|
+
each was measured; rerun what a change moves and update every page that quotes it.
|
|
58
|
+
|
|
59
|
+
## Review
|
|
60
|
+
|
|
61
|
+
Before pushing, run `scripts/check.sh`, then have the reviewer agent read the change. In
|
|
62
|
+
[Claude Code](https://claude.com/claude-code), ask for the `reviewer` agent on your branch
|
|
63
|
+
([`.claude/agents/reviewer.md`](.claude/agents/reviewer.md)). It checks the change against the
|
|
64
|
+
rules in [CLAUDE.md](CLAUDE.md): correctness, the CPU/GPU twin and the other invariants,
|
|
65
|
+
deep-module design, the style guide, tests that fail on the unfixed code, measured performance and
|
|
66
|
+
docs that match the code. It reports each defect with the input that shows it. Fix what it finds,
|
|
67
|
+
or say in the pull request why a finding does not apply.
|
|
68
|
+
|
|
69
|
+
[CLAUDE.md](CLAUDE.md) holds the design rules, invariants and working rules, for people and coding
|
|
70
|
+
agents alike; [architecture.md](docs/architecture.md) says how the code fits together.
|
|
71
|
+
|
|
72
|
+
## Releases
|
|
73
|
+
|
|
74
|
+
Merging is not releasing. Changes collect on `main` and ship together, so a version stands for a
|
|
75
|
+
batch of changes rather than for each one.
|
|
76
|
+
|
|
77
|
+
- **The changelog.** A change a user would notice adds a line to `## [Unreleased]` in
|
|
78
|
+
[CHANGELOG.md](CHANGELOG.md), under Keep a Changelog's headings (Added, Changed, Removed,
|
|
79
|
+
Fixed). Mark with **Breaking:** whatever
|
|
80
|
+
breaks code written against the last release.
|
|
81
|
+
- **Before a release.** A project that needs a merged change before it ships pins its commit,
|
|
82
|
+
`batchplan = { git = "https://github.com/natsuku-lab/batchplan", rev = "<commit>" }`, and goes
|
|
83
|
+
back to a version when the release is out. crates.io takes no crate with a git dependency, so
|
|
84
|
+
this is for applications and unpublished crates. To try the changes from crates.io, cut a
|
|
85
|
+
pre-release: `0.10.0-beta.1`, then `0.10.0-beta.2`. Cargo picks a pre-release only for a
|
|
86
|
+
requirement that names one, so users of `0.9` never get it. One beta may break the last, and a
|
|
87
|
+
requirement of one takes the next, so a project trying a beta pins it exactly
|
|
88
|
+
(`batchplan = "=0.10.0-beta.1"`).
|
|
89
|
+
- **Patch or minor.** `cargo semver-checks --release-type minor`
|
|
90
|
+
(`cargo install cargo-semver-checks --locked`) fails on a change to the interface that breaks
|
|
91
|
+
the latest release on crates.io. It calls such a change major, which before 1.0 means the next
|
|
92
|
+
minor version. A call that now behaves differently can break callers too, which only a reader
|
|
93
|
+
sees. A batch that breaks nothing is a patch, even one that adds to the interface. Breaking
|
|
94
|
+
changes wait on `main` for the next minor version.
|
|
95
|
+
- **When.** A patch whenever a fix should reach crates.io users. A minor release at most every
|
|
96
|
+
two weeks, once a beta of it has run in the projects that use batchplan,
|
|
97
|
+
[batchplan-ros](https://github.com/natsuku-lab/batchplan-ros) among them. A patch for a release
|
|
98
|
+
that `main` has moved past with breaking changes goes on a branch from that release's last tag
|
|
99
|
+
(`0.9.x` from `v0.9.0`). Changes reach the branch by pull request, so CI runs on them, and their
|
|
100
|
+
changelog lines go to `main` too.
|
|
101
|
+
- **Cutting one.**
|
|
102
|
+
1. Set `version` in `Cargo.toml` and `python/Cargo.toml` (the Python package's). The next build
|
|
103
|
+
of each crate updates `Cargo.lock`, `demos/Cargo.lock` and `python/Cargo.lock`, and
|
|
104
|
+
`scripts/check.sh` builds all three.
|
|
105
|
+
2. For a release, not a beta, move the lines under `## [Unreleased]` to a new
|
|
106
|
+
`## [0.10.0] - date`, and point the compare links at the end of the file at it.
|
|
107
|
+
3. Commit it as `batchplan 0.10.0`, to `main` or by pull request to a release's branch, and
|
|
108
|
+
wait for CI.
|
|
109
|
+
4. `git tag -a v0.10.0 -m "batchplan 0.10.0" && git push origin v0.10.0`. The release workflow
|
|
110
|
+
checks that the tag names the version and that `cargo semver-checks` passes, then publishes to
|
|
111
|
+
crates.io.
|
|
112
|
+
5. After a release, move batchplan-ros to it.
|
|
113
|
+
- **1.0** waits for an interface we mean to keep. Two-digit minor versions until then are usual
|
|
114
|
+
for Rust crates: nalgebra, glam and rapier are past 0.30. One decision comes first: how options
|
|
115
|
+
and problems grow. Today a new field breaks callers' struct literals, and after 1.0 each one would
|
|
116
|
+
take a major version.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
Contributions are dual-licensed under MIT or Apache-2.0, as the project is, unless you say
|
|
121
|
+
otherwise.
|