Repository navigation
Releases: personalrobotics/sscbirrt
Release list
v3.9.0
planner.validator(constraint_tsrs=...) returns the validator a
CBiRRT.plan() call applies, so a consumer can validate another motion under
the plan's own rules using only public API (#236).
Added
CBiRRT.validator(constraint_tsrs=None, *, backend=None): the validatorplan(..., constraint_tsrs=...)applies.- It lowers the constraint exactly as
plandoes, with the same space, checker, policy and backend selection. - Its reports equal
result.validationandresult.trajectory.validation, on either backend (#236). - It lets personalrobotics/ssrobot#132 validate a runtime command under the plan's own rules with public API only.
- It lowers the constraint exactly as
Changed
ValidationReport.backendis recorded but no longer part of equality. Two reports for the same subject under the
same policy compare equal whichever implementation answered.
v3.8.0
Motion validation is public, and it is the planner's own check (#230).
sscbirrt.Validator answers for a state, a motion, a path or a timed
trajectory under an explicit policy. The planner returns a path or
trajectory only after that same validator accepts it. Seeded paths differ
from 3.7 because edge subdivision now tolerates rounding.
Added
-
Public motion validation (#230).
sscbirrt.Validatorvalidates a state, one motion or a whole path, and
returns aValidationReport: valid under the reportedValidationPolicy, or the first invalid state with its
segment, fraction, failure kind, contacts (native MuJoCo scene) and violated path-constraint member. It is the
planner's own check:Validator.for_problem(problem, config), in Python and natively (sscbirrt::Validator), with
the native core used when every component lowers.- Motions are raw-linear: the straight line in raw joint values, as runtimes execute it. A continuous joint is never
wrapped, so a half turn or more is validated as the long raw line. PlanResult.validationis the returned path's report. A planner returns a path only after validating it whole;
a path that fails is not returned (failure_reason"Returned path failed final validation", native status
invalid_result). Only checkers that are not invariant to full turns of a continuous joint can fail it.sscbirrt._native.__version__, and aStateValidator::contactshook that witnesses use.
- Motions are raw-linear: the straight line in raw joint values, as runtimes execute it. A continuous joint is never
-
Timed trajectories are validated whole (#230).
Validator.validate_trajectorychecks a trajectory's whole law:- pieces along its source path (
Trajectory.path) at the path's own states within their span; - other pieces at speed-bounded time steps;
- position limits exactly, at every ramp's turning point.
Its witness gives the piece and
time_s. A trajectory that breaks its own invariants raisesMalformedTrajectory.
The smoother accepts a shortcut only if every piece the splice leaves passes this rule, andTrajectory.validation
is the returned trajectory's report. A trajectory that failed would not be returned ("Returned trajectory failed
final validation"). - pieces along its source path (
-
sscbirrt.mujoco.validator(model, data, arm, ...): the validatorplanwith the same arguments applies. -
Evidence for #230.
tools/validation_artifact.pywritestests/reference/validation_evidence.json, and a test holds a fresh run's
verdicts to it. On a small native MuJoCo world it records, for both backends:- an accepted and a rejected path, with the rejection's witness;
- a trajectory along its path (an exact reparameterization), and a trace-changing shortcut through the wall;
- the reports from planning next to the public calls;
- repeated and reordered queries.
tools/geodude_132_evidence.pyreruns the reduced Geodude edge of personalrobotics/ssrobot#132 inside ssrobot and
records the result intests/reference/geodude_132_evidence.json. Its timed trajectory crosses the left gripper
for 4 ms inside a shortcut.- The 3.7 rule and this validator both accept it under the 0.05 policy, and planning and the public call agree.
- The miss is the policy's, which #222 owns.
-
Two modes of motion check (#230).
- Prefix:
MotionValidator.validateanswers how far from the start a motion is valid. Tree growth keeps that
prefix, so it checks in order. - Whole: the new optional
validate_whole(native: a virtual with a default throughvalidate) answers whether
the whole motion is valid. It returns the same configurations on a yes, and checks the same states in van der
Corput order, so it stops sooner on a no. Path shortcuts and trajectory shortcut acceptance use it. - Reports:
Validatoranswers whole first. On a failure it refines to the earliest invalid state. - Verdicts and planner outputs are unchanged; only the order of the checks differs.
- Prefix:
Changed
- Edge subdivision tolerates rounding (#230). An edge of length
dis checked atmax(1, ceil(d / resolution - 1e-9))intervals, in the planner and the validator alike. Without the tolerance, a path piece exactly one resolution
long, recomputed from its stored nodes, could split in two, so the final check sampled a state the planner never
checked (one door path in 20 failed at such a midpoint). It also drops the redundant third sample that a step of
exactlystep_sizetook. Search state checks fall 4–14% on the benchmark problems (door about 1%), with every
problem still succeeding 100%. Seeded paths differ from 3.7; the reference artifact's cases and statuses do not.
v3.7.1
Fixed
- The
ImportErrorraised bysscbirrt.backends.ssikwhen ssik is missing named the old range>=7,<8. It now
names the[ssik]extra, which pins the supported range (>=8.2,<9since 3.7.0), with no range of its own (#226).
v3.7.0
Every ssik family runs natively (#220). Pose goals on the JACO2, the Panda and
the iiwa now plan in C++, 14–19x faster than on the Python backend in a
no-obstacle probe. Requires ssik 8.2.
Changed
-
Every SSIK family runs natively (#220). The native backend plans pose regions (TSRs and TSR chains) with SSIK
for every family ssik solves natively (ssik.cpp.NATIVE_FAMILIES), 6R and 7R alike:- UR arms;
- general 6R arms such as ADA's Kinova JACO2;
- the 7R SRS and spherical-shoulder arms such as the KUKA iiwa and the Franka Panda.
Only UR arms ran natively before; the others fell back to Python for every pose goal. The adapter is ssik's own
ssik::Solver(ssik 8.2, personalrobotics/ssik#707), which ssik verifies against its Python, so sscbirrt drops its
family allowlist. -
Requires
ssik>=8.2,<9(was>=7.0,<8). ssik 8.2 also caches the general-6R preparation, so a runtime-built
JACO2's first query no longer takes about 30 s once the cache is warm (personalrobotics/ssik#708).
v3.6.0
Paths end on the nearest winding of the start or goal member they reached
(#212). On the pick demo the wrist no longer spins an extra turn on almost
half the seeds. The median path falls from 7.21 to 5.48 rad, and the median
timed trajectory (#214) from 3.61 to 2.91 s. Pick's median solve rises from
0.035 to 0.047 s; transport and door are unchanged (100 seeds each,
benchmarks/endpoint_windings/).
Changed
-
A path ends on the nearest winding of the start or goal member it
reached (#212). When a role is a set (not finite), the member the trees
reached can be a full turn of a bounded joint away from another member
with the same pose. On the pick demo that spun the wrist an extra turn on
most seeds.After smoothing, the planner proposes the endpoint's windings that unwind
a turn (a joint more than π from the path's other end), and the set's
containsdecides membership. A candidate is used if a validated
route makes the path shorter. The route unwinds the path: it keeps the
path's route and makes the turn early, so the end of the path is
physically unchanged. The pass is deterministic and is in both backends.CBiRRTConfig.shorten_endpoints(defaultTrue; C++
PlannerConfig::shorten_endpoints) turns it off.goal_indexand the
sources are unchanged.
Migrating from 3.5.0: paths to and from sets can end on a different winding
of the same member, so seeded paths change. shorten_endpoints=False
restores 3.5.0's paths.
v3.5.0
Timed trajectories for execution (#214). Given joint velocity and
acceleration limits, sscbirrt now also returns a trajectory along the planned
path. It carries velocity through corners and cuts them where the planning
problem allows. On the pick demo the median duration falls from 6.23 s (a
stop at every corner) to 3.61 s. PR #211's blends retimed by TOPP-RA reach
4.47 s, and they fail validation on 8% of seeds.
Added
-
trajectory=TrajectoryOptions(...)onCBiRRT.solve,CBiRRT.plan
(withreturn_details=True) andsscbirrt.mujoco.plansets
result.trajectory.CBiRRT.smooth_trajectory(problem, path, options)
times a path you already have.result.pathis unchanged. -
sscbirrt.Trajectory: the exact source law. It provides:evaluate, which givesq,qdotandqddot;- the boundary states
startandgoal; - the limits used,
joint_namesand the validationresolution; seed,backendand why itstopped;- a validation record on every piece (
"planned"or"shortcut").
sample(dt_ns)gives a runtime command. It is a discretization, which the
consumer validates under its own interpolation. -
sscbirrt.TrajectoryOptions: the limits (required; a scalar or one per
joint) and named stopping defaults:- stop once the last 40 attempts shortened the trajectory by less than 0.5%;
- at most 400 attempts;
- no time budget.
The defaults come from a stopping-rule study
(tools/trajectory_benchmark.py trace/stopping). Every rule except
time_budgetdepends only on the trajectory, so a seed gives the same
trajectory on any machine.abort_fnalso stops smoothing. -
Native trajectory smoother (
sscbirrt::trajectoryin the C++ core).
It mirrors the Python reference operation by operation, and only the
random stream differs. Parity tests hold the ramps, synchronization and
corner merging to 1e-12.backend="auto"uses it when the problem lowers. -
tools/trajectory_benchmark.py: #214's adoption benchmark and gates, and
offline replay of stopping rules.tools/blend_baseline.pyis a frozen copy
of PR #211's blends, for the TOPP-RA baseline only. Results are in
benchmarks/trajectory/.
v3.4.0
Defaults set from measurement. Collision checking is finer by default, and
root collection keeps every IK candidate of a draw, which a 240-setting
parameter study found 1.7–1.9× faster on the median, with shorter or equal
paths (#186). On the pick demo, the median solve is now 0.05 s.
Migrating from 3.3.0:
- Code that relied on the old defaults plans different, denser paths.
Collision is checked every 0.05 rad instead of everystep_size, and up to
every IK candidate of a draw becomes a root instead of 3. - To get the old behaviour, set
edge_resolution=Noneandmax_per_draw=3. max_per_drawnow acceptsNone. C++:PlannerConfig::max_per_drawis
std::optional<int>, andedge_resolutiondefaults to 0.05.
Changed
-
max_per_drawdefaults toNone, keeping every candidate of a draw
(#186). A parameter study chose this. It is inbenchmarks/ablation/,
run withtools/planning_benchmark.py sweep/analyze, with a decision
rule fixed before the data. Stages:- screening: 240 settings of
num_tree_roots, the sample probabilities,
max_per_drawandstep_sizeon pick, 20 seeds each; - confirmation: the shortlist on 100 fresh seeds;
- generalization: transport and the door.
Only
max_per_drawbeat today's defaults. On fresh seeds, pick's median
solve is ×0.54 [0.46, 0.65] and its p90 ×0.58 [0.32, 0.92], with paths 12%
shorter. Under the library's own defaults, pick is ×0.60 and transport
×0.53. Since #200, keeping all of a pose's IK solutions is cheap, and they
give a tree roots on every branch. The other parameters were not better
than today's values within the CI, so they stay. A sample probability of
0 is the one setting that breaks the planner (72% success). The door
keeps its explicit 400 roots: for a fixed grasp, fewer roots made its p90
2.3–2.5× worse.max_per_drawtakesNoneor an int (C++:
std::optional<int>). A draw is now visited in random order whenever it
offers more candidates than can be kept, including when the cap comes
fromnum_tree_roots(#168). - screening: 240 settings of
-
The door demo reaches the handle first, then opens the door (#198).
It used to plan the opening first and then reach its start. That start is
one IK solution, which can lie on a joint winding HOME cannot reach. The
demo failed at seed 1 that way, and at seed 0 once candidate order
changed. Reaching any grasp first, then opening from there, succeeded on
seeds 0–9. -
edge_resolutiondefaults to 0.05 rad, independent ofstep_size(#204).
It is the spacing of collision checks along an edge, a joint-space distance.
It used to default tostep_size(0.1 rad). On a UR5e with gripper, a point
can move up to ~1.2 m per radian, so the old default let it move up to
12 cm between checks and step over thin obstacles. 0.05 rad bounds that to
~6 cm. Every shipped demo and reference case already set 0.05, so they are
unchanged. A problem that relied on the default gets finer checks, is
slower, and plans different paths. Its returned paths are also denser: the
planner stores each checked configuration, so there is a waypoint every
0.05 rad.edge_resolution=Nonestill meansstep_size. C++:
PlannerConfig::edge_resolutiondefaults to 0.05.
v3.3.0
Root collection now follows CBiRRT, and it is much cheaper. Over 100 seeds
of the pick demo, every run succeeds (it was 97%), and the median solve drops
from 0.68 to 0.07 s (p90 from 1.69 to 0.18 s). Two changes, each measured
against the one before it with tools/planning_benchmark.py:
- The start and goal sets keep growing during the search (#196).
- IK windings of one physical configuration share a collision verdict
(#200). This gives the same paths, faster.
Migrating from 3.2.0:
- The same seed gives a different path.
goal_biasandstart_biasare deprecated, and their meaning changed.
Prefergoal_sample_probabilityandstart_sample_probability; the old
names map to them, with a warning, until 4.0.- C++ consumers: the
PlannerConfigfields are renamed the same way.
The start and goal sets keep growing during the search, as in CBiRRT (#196).
On each tree's turn a weighted coin, the paper's P_sample, either draws new
members of that tree's own start or goal set and adds them as roots, or takes
an ordinary turn toward a random configuration. Until now, roots were
collected once, before the search, and frozen. Measured over 100 seeds per
problem, with both versions interleaved on one machine
(tools/planning_benchmark.py):
- every run that used to time out now succeeds: pick goes from 97% to 100%
of seeds, and the TSR-chain crank from 93% to 100%; - the slow tail shrinks: pick's p90 goes from 1.69 to 0.84 s (worst case
from a 30 s timeout to 1.75 s); the crank's p90 from 11.0 to 5.4 s (worst
case from a 120 s timeout to 12.6 s); transport's p90 from 0.45 to 0.32 s; - medians hold or improve, for example pick from 0.68 to 0.54 s, measured
as root collection plus search; - the door, whose up-front roots already connect it in one iteration, is
unchanged.
Changed
-
Behaviour. The same seed now gives a different path, in both backends.
Every reference-artifact case keeps its outcome and passes validation;
native parity holds. -
Parameters. New
start_sample_probabilityandgoal_sample_probability
(default 0.1) replacestart_biasandgoal_bias. The meaning changed: the
old biases steered a turn's extension toward a member of the other tree's
set. The new probabilities add roots to the tree's own set. A finite set
never tosses the coin, because its members are all roots already. The old
names still work until 4.0, mapped to the new ones, with a
DeprecationWarningthat says the meaning changed. -
Faster root collection, same result (#200). SSIK returns every winding
of every IK branch: 320 solutions per pick pose, but only about 12
physically distinct arm poses. Root collection now collision-checks each
physical configuration once per draw instead of every winding. Pick's root
collection goes from 0.41 s to 0.03 s, so its median solve drops from 0.53
to 0.07 s (p90 from 0.80 to 0.18 s). Transport's median drops from 0.14 to
0.05 s. All 700 benchmark runs give identical paths. This needs two
declarations, never inferred:- the IK solver's
revolute_joints(SSIK: every joint); - the collision checker's
full_turn_invariant(the MuJoCo checkers, when
every planned joint is a hinge).
stats["reused_verdicts"]counts the shared verdicts. C++:Sample::key,
IKSolver::revolute_joints(),StateValidator::full_turn_invariant()and
SolveStats::reused_verdictsare new. - the IK solver's
-
New stat.
PlanResult.stats["search_roots"]counts the roots added
during the search, on both backends. -
New tool.
tools/planning_benchmark.pymeasures planning time and work
on realistic problems.benchmarks/holds the v3.2.0 baseline and this
change's measurement. -
C++ consumers.
PlannerConfig::goal_biasandstart_biasare renamed
togoal_sample_probabilityandstart_sample_probability, with the new
meaning.Tree::add_rootis new.SolveStats::search_rootsis new.
v3.2.0
TSR chains plan on the native backend (#184). sscbirrt's TSRs now come from
sstsr's own C++ core (sstsr 3.3), which carries chains, so a chain goal,
start or path constraint no longer falls back to Python. The door demo plans
its chain about 15-20 times faster than on Python.
Migrating from 3.1.0: nothing to change in Python. Install pulls
sstsr>=3.3. C++ consumers of the installed package need sstsr's CMake
package findable (tsr.get_cmake_dir(), from a built wheel; an editable
install has none).
Changed
sstsr>=3.3,<4is required, to build and to run. The nativeTSRis
sstsr's C++, and sscbirrt's own C++ port of the TSR rules is removed.- The native backend lowers
TSRChainregions. Exact paths match the Python
bit-for-bit. Elsewhere a chain's distance is sstsr's bounded inverse, an
upper bound in both backends, held to the same properties rather than to
the same numbers. A chain whose later link has a non-identityT0_wis
refused by sstsr 3.3 itself (personalrobotics/sstsr#169). - The door demo plans only its chain and drops the single-TSR comparison.
Its README GIF is re-rendered from the native plan. - The reference artifact gains
ur5e_mujoco_tsr_chain_crank: a UR5e turning
a crank, with start, goal and path constraint one chain. It runs natively
underauto.
Removed
- C++ consumers: the RPY helpers in
sscbirrt/transform.hpp
(rot_to_rpy,xyzrpy_to_transand their kin), which only the TSR port used,
and theirsscbirrt._nativebindings.sscbirrt/sscbirrt.hppno longer
includes the TSR headers; includesscbirrt/tsr/tsr_set.hpp, which needs
sstsr_cpp(found byfind_package(sscbirrt)).
v3.1.0
sscbirrt becomes effortless to start with. pip install "sscbirrt[demo]"
then sscbirrt-demo renders MuJoCo demos with no Menagerie clone: the robot
models come from the new sscbirrt-assets wheel. sscbirrt.mujoco plans for
an arm in a MuJoCo world in one call:
plan(model, data, Arm(model, joints, site, mjcf=...), goal=...). Errors
name what exists and how to fix it. Names are consistent across the API, and
the README opens with something you can run.
Migrating from 3.0.0: nothing breaks. Renamed names keep working with a
DeprecationWarning until 4.0:
ik_solver→ik;tsr_samples/max_ik_per_pose→sample_draws/max_per_draw;angular_joints→continuous_joints;seeds→explicit_samples;plan_native→sscbirrt.mujoco.plan.
Two behaviours tighten:
- A MuJoCo model with free bodies now needs explicit
joint_names. - A malformed
startorgoalraises aValueError. Seeded TSR plans on
arms whose IK enumerates many solutions per pose (SSIK on the UR5e) take
different, equally valid paths, because roots are now a random subset of
each draw (#168).
Added
sscbirrt-demo(pip install "sscbirrt[demo]"): rendered MuJoCo demos on
sscbirrt.mujoco, with no Menagerie clone and no system ffmpeg (#163-#165).pick: a goal set of every side grasp of three cans among obstacles.transport: a can carried upright over a box by a path constraint.door: a door opened along its handle's arc by a TSR chain.- The README shows them as GIFs, regenerated by
tools/readme_gifs.py.
sscbirrt-assets, a separate data wheel inassets/that packages the
MuJoCo Menagerie UR5e and Robotiq 2F-85 models (pinned upstream commit,
SHA-256 manifest, upstream BSD licenses kept), released by its own
assets-v*tag pipeline. It is the first step toward
pip install "sscbirrt[demo]"running the demos without a Menagerie
clone (#162).sscbirrt.mujoco, the one-call MuJoCo API (#175).Arm(model, joints, ee_site, mjcf=...)describes the arm once and
builds analytical IK from its MJCF. The frames that relate SSIK to the
world are computed from the model, so an arm attached anywhere, with a
name prefix, just works, and they are checked.plan(model, data, arm, goal=..., start=..., constraint=..., holding=...)
always returns aPlanResult.startdefaults to where the arm is now.start,goalandconstrainteach take a configuration,
configurations, a TSR, TSRs, or any set.holding="can"takes the grasp from the current poses.
Changed
- Errors name what exists: a missing site or joint lists the model's sites
or joints, and the unlimited-joint error names the joint and both fixes.
MuJoCoRobotModel(joint_limits=(lower, upper))gives finite planning
limits. Install hints match pyproject.NativeUnsupportedis exported
fromsscbirrt(#174). CBiRRTConfig's set tolerances are documented as applying to the sets
plan(...)builds (#177).
Deprecated
- Consistent names; the old ones keep working, with a
DeprecationWarning,
until 4.0 (#176).CBiRRT(ik_solver=)is nowik=.ikis optional: planning between
configurations never uses it, and TSRs without it raise a clear error.CBiRRTConfig.tsr_samples/max_ik_per_poseare nowsample_draws/
max_per_draw, the names the native core already used. The budgets
apply to any sampleable set, not only TSRs.CBiRRTConfig.angular_jointsandJointSpace(angular_joints=)are now
continuous_joints. Every revolute joint is angular; the flag means
unlimited.sscbirrt.seedsis nowexplicit_samples, becauseseedmeans an RNG
seed everywhere else.
sscbirrt.backends.native_mujoco.plan_native: usesscbirrt.mujoco.plan.
It keeps working, with aDeprecationWarning, until 4.0 (#175).
Removed
- The UR5e examples (
ur5e_mujoco.py,ur5e_transport.py,
tsr_union_demo.py), superseded bysscbirrt-demoonsscbirrt.mujoco.
Their scene, which the reference artifact and the UR5e tests plan in, moved
unchanged tosscbirrt.testing.ur5e; the artifact is bit-for-bit
identical.multi_config_demo.pyis nowplanar_arm.py -e 4, and
planar_arm.pyusessscbirrt.testing's arm and IK. Theexamplesextra
no longer installs mediapy (#166).
Fixed
-
Roots kept from one sampling draw are now a random subset of its
candidates, asmax_ik_per_pose's "for diversity" promised, on both
backends. Previously the firstmax_ik_per_posecandidates were kept,
in the sampler's order. An IK solver that enumerates branches and joint
windings (SSIK on the UR5e returns 256 solutions per pose) lists them in
a fixed order, so every draw contributed near-duplicates from one
corner of the solution set. Draws at or under the cap consume no
randomness: the planar reference cases are bit-for-bit unchanged, and
the three UR5e cases have new paths that pass every validation (#168). -
dataclasses.replace(config, membership_tolerance=x)kept the old value,
because the deprecatedtsr_tolerancewas a stored field mirroring it.
Deprecated names are no longer fields (#176). -
The native backend's "All N start/goal configuration(s)" count matches
Python's; it counted each rejected explicit candidate twice (#170). -
plan(start=[0.1, 0.2]): a flat list is one configuration on every
backend, and a malformedstart/goalraises aValueErrornaming the
argument and the expected shape. It used to crash under the default
backend withTypeError: iteration over a 0-d array(#171). -
plan_nativechecks its arguments before doing any work. Pose regions
withoutikraise aValueErrornamingik, instead of leaking_NoIK
or claiming there are no pose regions. A wrong site or joint is reported
before the scene is built. A missing native scene says thatfallback
cannot cover it (#172). -
MuJoCoRobotModel,MuJoCoCollisionCheckerandMuJoCoIKSolverno
longer take every joint by default when the model has free or ball joints,
which silently gave the wrong DOF; they ask for the arm's joints and list
them (#173).
Documentation
- README images and links use relative paths, so they render on every
branch, in pull requests and in local previews. The release build pins
them to the release tag in the PyPI description (tools/pypi_readme.py),
so PyPI shows each release's own images; before this, relative images
were broken on PyPI. - The README starts with installing and a runnable MuJoCo plan
(sscbirrt.mujocowith the packaged UR5e), then the concepts. The
PlanResulttable listsbackend,backend_reasons,provenanceand
stats. The MuJoCo snippets use real site names, and the tsr hand models
are introduced for grasp regions.tests/test_readme.pyruns the snippets
(#178). - The README starts with installing and a runnable MuJoCo plan
(sscbirrt.mujocowith the packaged UR5e), then the concepts. The
PlanResulttable listsbackend,backend_reasons,provenanceand
stats. The MuJoCo snippets use real site names, and the tsr hand models
are introduced for grasp regions.tests/test_readme.pyruns the snippets
(#178).