Source code for minilink.planning.spatial.track

"""Reference tracks: a centerline path with a constant-width corridor around it."""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING

from minilink.core.backends import array_module
from minilink.planning.spatial.collision import CollisionBody
from minilink.planning.spatial.paths import ReferencePath

if TYPE_CHECKING:
    from minilink.planning.spatial.state_fields import StateField


###############################################################################
# Public API
###############################################################################


[docs] @dataclass(frozen=True) class ReferenceTrack: """ Target path with a constant half-width corridor in workspace units. A track composes into a :class:`~minilink.planning.problems.PlanningProblem` through its state fields:: track = ReferenceTrack(from_waypoints(pts), half_width=1.0) X = bounds & track.corridor_field(body).as_constraint() cost = base + track.distance_field(body).as_cost(weight=5.0) Parameters ---------- path : ReferencePath Workspace centerline. half_width : float Corridor radius around the path (same units as ``path.distance``). """ path: ReferencePath half_width: float = 1.0 def __post_init__(self) -> None: require_positive_half_width(self.half_width)
[docs] def distance(self, p, t=0.0, params=None): """Distance from ``p`` to the centerline.""" return self.path.distance(p, t=t, params=params)
[docs] def corridor_margin(self, p, t=0.0, params=None): """Positive when ``p`` lies inside the corridor: ``half_width - distance``.""" xp = array_module(p) half_width = self.half_width path = self.path # m(p) = w − d(p), the room left to the corridor's edge margin = xp.asarray(half_width) - path.distance(p, t=t, params=params) return margin
[docs] def distance_field(self, body: CollisionBody) -> StateField: """The least distance from ``body``'s probes to the centerline, as a field.""" # Lazy import: the state fields load on first use, not with the track from minilink.planning.spatial.state_fields import PathDistanceField return PathDistanceField(self, body)
[docs] def corridor_field(self, body: CollisionBody) -> StateField: """The smallest corridor margin over ``body``'s probes, as a field.""" # Lazy import: the state fields load on first use, not with the track from minilink.planning.spatial.state_fields import CorridorMarginField return CorridorMarginField(self, body)
[docs] def plot(self, **kwargs): """Plot the centerline and corridor (lazy matplotlib import).""" # Lazy import: the graphics load on first use, not with the core from minilink.planning.spatial.plotting import plot_track return plot_track(self, **kwargs)
############################################################################### # Internal machinery ############################################################################### def require_positive_half_width(half_width): """Refuse a corridor of zero or negative half-width.""" if half_width <= 0.0: raise ValueError("half_width must be positive")