step.motion.path.optimize¶
Fluent path-optimizer builder — optimize([...]).
A thin, fluent wrapper around the existing compiler + executor pipeline.
Where smooth_path() exposes optimization as boolean flags, optimize()
exposes it as a chain of explicit passes — Java-stream style:
optimize([drive_forward(50), turn_right(90), drive_forward(30)])
.cut_corners(5)
The builder is itself a Step: it compiles its raw steps through
PathCompiler and runs the result through PathExecutor, mirroring the
non-absolute, non-spline execution wiring of SmoothPath.
DecomposePass then MergePass are ALWAYS-ON — prepended to every
compiled pipeline before the user’s chained passes — because both are
behavior-preserving. Use seq() instead of optimize() if you want
neither.
A small compile-time state machine validates composition of the CHAINED
passes. Each pass may optionally declare the segment representation it
requires / produces and whether it is terminal (see
passes/contract.py for Representation and the defaults); passes
that don’t declare these (MergePass / CornerCutPass) default to
EITHER / SAME / non-terminal and keep working unchanged.
Exceptions¶
Raised when passes are composed in an invalid order. |
Classes¶
Fluent path optimizer — compiles motion steps through opt-in passes. |
Functions¶
|
Build a fluent path optimizer over a list of motion steps. |
Module Contents¶
- exception step.motion.path.optimize.PathBuildError¶
Bases:
ExceptionRaised when passes are composed in an invalid order.
- class step.motion.path.optimize.Optimizer(steps: list)¶
Bases:
step.StepFluent path optimizer — compiles motion steps through opt-in passes.
Built via the
optimize()factory. Chain.cut_corners(),.to_absolute(),.splinify(),.absolute_heading()or.apply(custom_pass)to append compiler passes; each returnsselfso calls compose Java-stream style. Execution compiles the raw steps throughPathCompilerand runs the result onPathExecutor(relative, non-spline path).Two passes are ALWAYS-ON and run before any user pass:
DecomposePass(splitsafter_cm + sensorlegs in time) thenMergePass(collapses adjacent same-type legs). Both are behavior-preserving RELATIVE→RELATIVE transforms, so they are prepended to the compiled pipeline without going through_add(they don’t affect the representation state the user’s passes are validated against). If you want neither, useseq()rather thanoptimize().- to_absolute() Optimizer¶
Drive as ABSOLUTE as possible — closed-loop on the localization filter.
Turns on absolute mode. On its own, each maximal run of known-endpoint
linear/turn/diagonallegs becomes ONEGotoWaypointsand each sensor-bounded single-axis leg becomes anAbsoluteHoldMove(cross-axis + heading held absolute, free axis until the sensor) — every leg regulating on the particle filter so accumulated odometry drift is corrected during the move. Chained with.splinify()the whole path instead becomes one absolute spline (a continuousSplineFollow).Reads localization only — never feeds it.
- absolute_heading() Optimizer¶
Pin every straight leg to one integrated heading (
AbsoluteHeadingPass).Integrates a single running heading through the path at compile time (start 0; turns/arcs add their signed angle) and stamps it onto each
linear/follow_lineleg, so every straight leg regulates against ONE coherent heading reference rather than re-zeroing on the previous leg’s drifted frame — drift-robust over long paths. The pass emits headings in the path-start frame; the executor adds the path-start world-heading offset H0 (captured once at the first motion segment) at runtime so they become absolute world targets.
- cut_corners(radius_cm: float, cut_until: bool = False) Optimizer¶
Round corners between straight legs with arcs (
CornerCutPass).Cuts two corner shapes, trimming
radius_cmfrom each straight leg:Turn corners (
linear+turn+linear, same-axis legs) become a circular arc — forward legs give a drive arc, strafe legs a lateral arc.Crab corners (
linear(Forward)+linear(Lateral), no turn) become a constant-headingcrab_arc: a holonomic base blends forward into strafe over a 90° quarter circle without rotating.
By default only corners between known fixed-distance legs are cut. Set
cut_until=Trueto also round a corner whose EXIT leg is a sensor-bounded.until()leg: the entry leg is trimmed, the arc rounds the corner, and the.until()leg then runs from the arc’s end until its condition fires (it is kept untrimmed — its endpoint is unknown, and the sensor still triggers at the same place). The ENTRY leg can never be a.until()leg: the fillet would have to start curving before the sensor fires, which can’t be anticipated.radius_cmis the cut distance trimmed from each (known) straight leg, in centimeters; converted to meters forCornerCutPasshere.
- splinify() Optimizer¶
Replace the whole relative run with one Catmull-Rom spline (terminal).
Collapses the linear/turn path into a single
splinesegment driven through its waypoints (SplinifyPass). Terminal — nothing may follow. RaisesValueErrorat compile time if the path can’t be splinified (side actions, arcs, conditions, deferred steps, or < 2 waypoints).
- time_optimal(max_speed_cmps: float = 100.0, accel_cmps2: float = 60.0, lateral_accel_cmps2: float = 50.0, sensor_carry_cmps: float = float('inf')) Optimizer¶
Carry speed through the WHOLE path — the global generalisation of the per-seam warm-start (
VelocityProfilePass).Runs a forward/backward sweep that stamps the fastest feasible entry/exit speed on every leg, so the robot only decelerates for REAL stops (turns, direction reversals, blocking side actions, the final leg) and corner/curvature limits — instead of braking to a halt at every segment seam.
sensor_carry_cmpsis the “on steroids” knob for the sensor-driven style: a leg that ends on a.until()SENSOR/time condition normally forces a full stop, but when the NEXT leg continues in the same direction the robot instead FLOWS THROUGH the sensor boundary rather than braking to zero — far cleaner, faster motion on a robot that stops at every sensor. It defaults toinf(the robot keeps its full cruise through the seam — it is already at cruise when the sensor fires, so braking would only waste time, and the same-direction flow adds no lateral overshoot). Lower it (cm/s) to throttle the flow-through where a sensor needs dense sampling; set 0 to brake at every sensor as before. Accuracy-preserving by construction.PATH-PRESERVING: changes only speed, never geometry, so the endpoint matches the un-profiled path. Most effective after
cut_corners()(it carries speed through the arcs the corner-cut introduced). Limits are in cm:max_speed_cmpscruise cap,accel_cmps2accel/decel,lateral_accel_cmps2the arc curvature cap (v <= sqrt(a_lat * R)).
- required_resources() frozenset[str]¶
Return the hardware resources this step requires exclusive access to.
For leaf steps (drive, motor, servo), return the resources this step directly uses. Composite steps override
collected_resourcesinstead to include children —required_resourcesstays empty for composites because they don’t touch hardware themselves.
- collected_resources() frozenset[str]¶
Return all resources this step and its children require.
Used by
validate_no_overlapfor static conflict detection at construction time. Leaf steps don’t need to override this — the default delegates torequired_resources. Composite steps override to union their children’s collected resources.
- step.motion.path.optimize.optimize(steps) Optimizer¶
Build a fluent path optimizer over a list of motion steps.
optimize()is the explicit-pass counterpart tosmooth_path(): it wraps the same compiler + executor pipeline, but instead of boolean flags you append optimization passes by chaining methods. Each chain method returns the builder, Java-stream style:optimize([drive_forward(50), turn_right(90), drive_forward(30)]) .cut_corners(5)
decomposethenmergeALWAYS run first (before any chained pass): both are behavior-preserving, sooptimize()always splitsafter_cm + sensorlegs and collapses adjacent same-type legs. If you want neither, useseq(steps)instead.A compile-time state machine validates the order of the CHAINED passes: a pass may declare the segment
Representationit requires/produces and whether it is terminal. Composing passes that disagree (e.g. an absolute-only pass on a relative stream, or any pass after a terminal one) raisesPathBuildErrorat build time.Available chained passes:
.cut_corners(radius_cm)— replacelinear+turn+linearcorners with a circular arc, trimmingradius_cmfrom each straight leg..to_absolute()— convert known-endpoint relative runs into one closed-loop navigate-to-pose move..absolute_heading()— pin every straight leg to one integrated heading..splinify()— collapse the relative run into one spline (terminal)..apply(pass)— append any customCompilerPass.
Use
.explain()to dump the IR node list after each pass (including the always-on decompose + merge) without executing anything.- Prerequisites:
calibrate_distance()if any segment uses distance-based mode.mark_heading_reference()if any segment uses heading hold.
- Parameters:
steps – A list of motion steps (or a single step / nested
seq()). Accepts the same step family assmooth_path.- Returns:
An
Optimizerstep. Chain passes onto it, then run it like any step.
Example:
from raccoon.step.motion import optimize, drive_forward, turn_right optimize( [ drive_forward(30), drive_forward(20), turn_right(90), drive_forward(40), ] ).cut_corners(5)