Skip to content

Add BIT* planner optimising a pluggable cost function - #1

Merged
H-tr merged 2 commits into
mainfrom
feat/bit-star-planner
Jul 10, 2026
Merged

H-tr merged 2 commits into
mainfrom
feat/bit-star-planner

Conversation

@H-tr

@H-tr H-tr commented Jul 10, 2026

Copy link
Copy Markdown
Owner

What

Adds BIT* (Batch Informed Trees) as a separate planner, following Gammell et al. 2015 and the classic two-queue ompl::geometric::BITstar (OMPL 1.4.2 — OMPL's current BITstar is really Advanced BIT*, which drops the vertex queue).

Unlike RRTConnectPlanner, it returns the cheapest path it found rather than the first feasible one.

The cost interface

CostFunction::state_cost in the new cost_function.hpp is the single stub to fill in. It takes one Config, and reads context() — the vector<Config> of (x, y, theta) dependencies fixed at planner setup:

std::vector<Config> cost_context = {{0.4, 0.1, 0.0}, {0.9, -0.2, 1.57}};
BITStarPlanner planner(&checker, bounds_min, bounds_max, cost_context);
auto path = planner.plan(start, goal, BITStarPlanner::PlanParams{});
const double cost = planner.solution_cost();

It returns 0.0 today, so BIT* currently minimises weighted-SE(2) path length — runnable and testable before the real cost lands.

Why there's a length term

A path is charged the line integral of the density 1 + state_cost. The 1 + is load-bearing:

  • it makes heuristic_cost (plain distance) an admissible lower bound, which is what BIT*'s pruning and best-first ordering rest on;
  • it keeps every edge cost positive, so rewiring cannot create a cycle;
  • it keeps the optimum well defined where state_cost is 0 — including today, with the stub.

A cost that breaks the non-negative contract is clamped in edge_cost rather than left to corrupt the search. This is not theoretical: a state_cost < -1 makes edge costs negative, lets a vertex be reparented under its own descendant, and spins update_descendants() forever. Reproduced, then fixed.

Verification

  • 23/23 Catch2 tests pass; 0 failures in 200 repeat runs of [bit_star].
  • With state_cost temporarily patched to a Gaussian penalty, the path detours around the costly region (cost 3.0 vs 14.5 straight through) — the cost plumbing genuinely steers the plan.
  • With the zero stub, edge_cost == distance bit-for-bit, and solution_cost() equals the summed edge cost of the returned path exactly.
  • 40 cluttered-scene trials: zero segment-level collisions, exact endpoints.
  • θ-wrapping optimal; start == goal and unreachable-goal cases terminate cleanly.

Also in here

  • Bug fix: pyproject.toml used cmake.minimum-version, removed in scikit-build-core 0.8 — pip install -e . was failing outright. (Separate commit.)
  • Python surface is additive: Planner.plan_bit_star(), bit_star_solution_cost(), and a cost_context kwarg. Existing plan() is unchanged.
  • Renamed run_planner.py → run_rrtc_planner.py (pure rename) and added run_bit_star.py, which sweeps the batch budget so you can watch the cost fall.

🤖 Generated with Claude Code

H-tr and others added 2 commits July 10, 2026 09:27
`cmake.minimum-version` was removed in scikit-build-core 0.8, so
`pip install -e .` failed outright with "Use cmake.version instead of
cmake.minimum-version". Switch to `cmake.version` and require a backend
version that understands it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BIT* (Batch Informed Trees), following Gammell et al. 2015 and the
classic two-queue ompl::geometric::BITstar (OMPL 1.4.2; OMPL's current
BITstar is really Advanced BIT*, which drops the vertex queue). Unlike
RRT-Connect it returns the cheapest path it found rather than the first
feasible one.

The cost model lives in its own header so it can be filled in
independently. CostFunction::state_cost is the single stub: it takes one
Config and reads the (x, y, theta) context vector fixed at planner
setup. It returns 0 today, which reduces BIT* to weighted-SE(2) shortest
path, so the planner is runnable and testable before the real cost
exists.

A path is charged the line integral of the density `1 + state_cost`, so
an edge costs the distance it covers plus whatever the cost adds along
it. Keeping the length term is what makes CostFunction::heuristic_cost
(plain distance) an admissible lower bound, keeps every edge cost
positive so rewiring cannot cycle, and keeps the optimum well defined
where the cost is zero. A cost that breaks the non-negative contract is
clamped in edge_cost rather than left to corrupt the search: a negative
state_cost would otherwise let a vertex be reparented under its own
descendant and spin update_descendants() forever.

Exposed to Python additively as Planner.plan_bit_star() plus a
cost_context kwarg; the existing plan() is unchanged. Renames the
RRT-Connect demo to run_rrtc_planner.py and adds run_bit_star.py, which
sweeps the batch budget to show the cost falling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@H-tr
H-tr merged commit 790904f into main Jul 10, 2026
1 check passed
@H-tr
H-tr deleted the feat/bit-star-planner branch July 10, 2026 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant