[Pather/Planner] Doc cleanup and more informative error enums
This commit is contained in:
parent
3a3cd854a5
commit
49713896c3
13 changed files with 370 additions and 95 deletions
|
|
@ -6,11 +6,19 @@ Tool primitive offers. `Pather` passes copied `RoutePortContext` snapshots here;
|
|||
the planner returns `PreparedRouteResult` records that describe pending
|
||||
mutations without applying them to the live Pattern.
|
||||
|
||||
Public routing modes and bounds are normalized by `bounds.py` before the solver
|
||||
sees them. This module plans one route intent at a time: it queries Tool offers,
|
||||
enumerates bounded primitive compositions, inserts ptype adapters, solves
|
||||
primitive parameters, ranks candidates, and commits only the selected offers
|
||||
into `RenderStep` payloads.
|
||||
Public routing modes and bounds are normalized by `bounds.py` into per-leg
|
||||
`SolverRequest` values. `Solver` performs the pure search: it queries Tool
|
||||
offers, enumerates bounded primitive compositions, inserts ptype adapters,
|
||||
solves primitive parameters, and returns the ranked `Candidate`. A `RouteLeg`
|
||||
then attaches the candidate to its copied source port and Tool. Preparation is
|
||||
the layer above selection: only chosen offers are committed into `RenderStep`
|
||||
payloads and returned in a `PreparedRouteResult` for Pather to apply.
|
||||
|
||||
These named stages are architectural boundaries even though most of their
|
||||
implementation is currently co-located in this module. File layout is an
|
||||
internal organization choice; the distinction between normalized request,
|
||||
pure selection, prepared result, and live application is the meaningful
|
||||
mutation boundary.
|
||||
|
||||
All search is performed in Tool-local route coordinates. The active input port
|
||||
is at the origin, travel is along +x, and positive jog is to the left. After a
|
||||
|
|
@ -43,7 +51,13 @@ from ..tools import (
|
|||
StraightOffer,
|
||||
Tool,
|
||||
)
|
||||
from ..error import RouteError, RouteFailureDetails, RouteOperation
|
||||
from ..error import (
|
||||
MinimumStatus,
|
||||
RouteError,
|
||||
RouteFailureDetails,
|
||||
RouteFailurePolicy,
|
||||
RouteOperation,
|
||||
)
|
||||
from ..utils import ell
|
||||
from . import bounds as planner_bounds
|
||||
from .interface import (
|
||||
|
|
@ -51,7 +65,7 @@ from .interface import (
|
|||
PreparedRouteResult,
|
||||
RoutePlanningError,
|
||||
RoutePortContext,
|
||||
route_error_is_fatal,
|
||||
route_failure_policy,
|
||||
)
|
||||
|
||||
RouteTieBreakStrategy = Literal['straight_first', 'turn_first']
|
||||
|
|
@ -149,7 +163,7 @@ def is_adapter_offer(offer: PrimitiveOffer) -> bool:
|
|||
|
||||
def raise_if_fatal(err: Exception) -> None:
|
||||
"""Propagate fatal planning errors while allowing normal candidate rejection."""
|
||||
if getattr(err, 'fatal', False):
|
||||
if route_failure_policy(err) is RouteFailurePolicy.FATAL:
|
||||
raise err
|
||||
|
||||
|
||||
|
|
@ -231,9 +245,9 @@ class Candidate:
|
|||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RouteRequest:
|
||||
class SolverRequest:
|
||||
"""
|
||||
Normalized solver input for one route leg.
|
||||
Normalized input for one `Solver` invocation and one route leg.
|
||||
|
||||
Public Pather calls are converted into this smaller shape before grammar
|
||||
enumeration. `length`, `jog`, and `out_ptype` become endpoint constraints;
|
||||
|
|
@ -302,7 +316,7 @@ class Solver:
|
|||
enumerated so fixed and adjustable offers share the same path.
|
||||
"""
|
||||
|
||||
def __init__(self, request: RouteRequest) -> None:
|
||||
def __init__(self, request: SolverRequest) -> None:
|
||||
self.request = request
|
||||
self.eval_cache: dict[tuple[int, float, str | None, str | None, str, str, PrimitiveKind | None], SelectedPrimitive] = {}
|
||||
self.offer_cache: dict[
|
||||
|
|
@ -396,7 +410,7 @@ class Solver:
|
|||
|
||||
if not candidates:
|
||||
for err in errors:
|
||||
if getattr(err, 'fatal', False):
|
||||
if route_failure_policy(err) is RouteFailurePolicy.FATAL:
|
||||
raise err
|
||||
if errors:
|
||||
last_error = errors[-1]
|
||||
|
|
@ -476,12 +490,12 @@ class Solver:
|
|||
if not ptypes_compatible(out_port.ptype, offer.out_ptype):
|
||||
raise RoutePlanningError(
|
||||
f'{route_name} primitive endpoint ptype does not match declared offer out_ptype',
|
||||
fatal=True,
|
||||
policy=RouteFailurePolicy.FATAL,
|
||||
)
|
||||
if out_ptype is not None and not ptypes_compatible(out_port.ptype, out_ptype):
|
||||
raise RoutePlanningError(
|
||||
'Requested out_ptype does not match primitive endpoint ptype',
|
||||
fatal=True,
|
||||
policy=RouteFailurePolicy.FATAL,
|
||||
)
|
||||
cost = float(offer.cost_at(selected))
|
||||
if not numpy.isfinite(cost):
|
||||
|
|
@ -993,7 +1007,7 @@ class RoutingPlanner:
|
|||
bands.append((4, 4))
|
||||
return tuple(bands)
|
||||
|
||||
def route_request(
|
||||
def solver_request(
|
||||
self,
|
||||
family: PrimitiveKind,
|
||||
context: RoutePortContext,
|
||||
|
|
@ -1005,9 +1019,9 @@ class RoutingPlanner:
|
|||
max_bends: int | None = None,
|
||||
strategy: RouteTieBreakStrategy | str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> RouteRequest:
|
||||
) -> SolverRequest:
|
||||
"""Build normalized solver input for one route leg."""
|
||||
return RouteRequest(
|
||||
return SolverRequest(
|
||||
family=family,
|
||||
tool=context.tool,
|
||||
in_ptype=context.port.ptype,
|
||||
|
|
@ -1021,7 +1035,7 @@ class RoutingPlanner:
|
|||
strategy=self.resolve_strategy(strategy),
|
||||
)
|
||||
|
||||
def solver_for_request(self, request: RouteRequest) -> Solver:
|
||||
def solver_for_request(self, request: SolverRequest) -> Solver:
|
||||
"""Construct the solver for a route request."""
|
||||
return Solver(request)
|
||||
|
||||
|
|
@ -1079,13 +1093,12 @@ class RoutingPlanner:
|
|||
resolved_length=float(length),
|
||||
resolved_jog=None if jog is None else float(jog),
|
||||
minimum_length=None,
|
||||
minimum_attempted=False,
|
||||
minimum_exhausted=False,
|
||||
minimum_status=MinimumStatus.NOT_EVALUATED,
|
||||
cause='Resolved route length must be finite and nonnegative',
|
||||
)
|
||||
raise RouteError(details, fatal=True)
|
||||
raise RouteError(details, policy=RouteFailurePolicy.FATAL)
|
||||
|
||||
request = self.route_request(
|
||||
request = self.solver_request(
|
||||
family=family,
|
||||
context=context,
|
||||
length=length,
|
||||
|
|
@ -1107,22 +1120,24 @@ class RoutingPlanner:
|
|||
|
||||
minimum_length: float | None = None
|
||||
minimum_cause: str | None = None
|
||||
minimum_exhausted = False
|
||||
minimum_status: MinimumStatus
|
||||
if length is None:
|
||||
minimum_cause = cause
|
||||
minimum_exhausted = True
|
||||
minimum_status = MinimumStatus.NO_ROUTE
|
||||
else:
|
||||
minimum_request = replace(request, length=None)
|
||||
try:
|
||||
minimum_candidate = self.solver_for_request(minimum_request).solve()
|
||||
minimum_length = minimum_candidate.public_length
|
||||
minimum_status = MinimumStatus.FOUND
|
||||
except NoLegalRouteError as minimum_err:
|
||||
minimum_cause = str(minimum_err)
|
||||
minimum_exhausted = True
|
||||
minimum_status = MinimumStatus.NO_ROUTE
|
||||
except Exception as minimum_err:
|
||||
if route_error_is_fatal(minimum_err):
|
||||
if route_failure_policy(minimum_err) is RouteFailurePolicy.FATAL:
|
||||
raise
|
||||
minimum_cause = str(minimum_err)
|
||||
minimum_status = MinimumStatus.FAILED
|
||||
|
||||
details = RouteFailureDetails(
|
||||
operation=operation,
|
||||
|
|
@ -1133,8 +1148,7 @@ class RoutingPlanner:
|
|||
resolved_length=None if length is None else float(length),
|
||||
resolved_jog=None if jog is None else float(jog),
|
||||
minimum_length=minimum_length,
|
||||
minimum_attempted=True,
|
||||
minimum_exhausted=minimum_exhausted,
|
||||
minimum_status=minimum_status,
|
||||
cause=cause,
|
||||
minimum_cause=minimum_cause,
|
||||
)
|
||||
|
|
@ -1619,7 +1633,7 @@ class RoutingPlanner:
|
|||
desired.rotation = port_dst.rotation - pi
|
||||
desired.ptype = out_ptype
|
||||
family, length, jog, ccw = self.trace_into_spec(context_src.port, desired)
|
||||
request = self.route_request(
|
||||
request = self.solver_request(
|
||||
family,
|
||||
context_src,
|
||||
length=length,
|
||||
|
|
@ -1638,7 +1652,7 @@ class RoutingPlanner:
|
|||
candidate = solver.solve(min_bends=min_bends, max_bends=max_bends)
|
||||
break
|
||||
except (BuildError, NotImplementedError) as err:
|
||||
if route_error_is_fatal(err):
|
||||
if route_failure_policy(err) is RouteFailurePolicy.FATAL:
|
||||
raise
|
||||
last_error = err
|
||||
if candidate is None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue