[Pather / Tool] more unification work and fixes
This commit is contained in:
parent
8712398990
commit
f9611933ac
19 changed files with 1022 additions and 137 deletions
|
|
@ -32,7 +32,7 @@ from __future__ import annotations
|
|||
from collections.abc import Iterable, Mapping, Sequence
|
||||
from dataclasses import dataclass, replace
|
||||
from itertools import combinations
|
||||
from math import cos, isclose as math_isclose, sin
|
||||
from math import cos, sin
|
||||
from typing import Any, Literal
|
||||
|
||||
import numpy
|
||||
|
|
@ -50,6 +50,7 @@ from ..tools import (
|
|||
SOffer,
|
||||
StraightOffer,
|
||||
Tool,
|
||||
UOffer,
|
||||
)
|
||||
from ..error import (
|
||||
MinimumStatus,
|
||||
|
|
@ -57,7 +58,9 @@ from ..error import (
|
|||
RouteFailureDetails,
|
||||
RouteFailurePolicy,
|
||||
RouteOperation,
|
||||
ToolContractError,
|
||||
)
|
||||
from .._tolerances import scalar_close
|
||||
from ..utils import ell
|
||||
from . import bounds as planner_bounds
|
||||
from .interface import (
|
||||
|
|
@ -84,7 +87,7 @@ def validate_strategy(strategy: RouteTieBreakStrategy | str) -> RouteTieBreakStr
|
|||
|
||||
def is_close(a: float, b: float) -> bool:
|
||||
"""Compare route-solver scalars with the planner tolerance."""
|
||||
return math_isclose(float(a), float(b), rel_tol=1e-5, abs_tol=1e-8)
|
||||
return scalar_close(a, b)
|
||||
|
||||
|
||||
def clean_parameter(value: float) -> float:
|
||||
|
|
@ -225,8 +228,6 @@ class SelectedPrimitive:
|
|||
"""Finite additive planning cost reported by the offer."""
|
||||
role: Literal['main', 'adapter'] = 'main'
|
||||
"""Whether this step satisfies route geometry or adapts ptype."""
|
||||
route_kind: PrimitiveKind | None = None
|
||||
"""Primitive kind used when querying the Tool for this step."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -318,7 +319,7 @@ class Solver:
|
|||
|
||||
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.eval_cache: dict[tuple[int, float, str | None, str | None, str, str], SelectedPrimitive] = {}
|
||||
self.offer_cache: dict[
|
||||
tuple[PrimitiveKind, str | None, str | None, tuple[tuple[str, Any], ...]],
|
||||
tuple[PrimitiveOffer, ...],
|
||||
|
|
@ -333,9 +334,9 @@ class Solver:
|
|||
for step in steps:
|
||||
if step.role == 'adapter':
|
||||
continue
|
||||
if step.route_kind == 'bend':
|
||||
if step.offer.kind == 'bend':
|
||||
count += 1
|
||||
elif step.route_kind in ('s', 'u'):
|
||||
elif step.offer.kind in ('s', 'u'):
|
||||
count += 2
|
||||
return count
|
||||
|
||||
|
|
@ -371,7 +372,6 @@ class Solver:
|
|||
tuple((
|
||||
offer_key(step.offer),
|
||||
step.role,
|
||||
step.route_kind,
|
||||
round(float(step.parameter), 9),
|
||||
endpoint_key(step.out_port),
|
||||
) for step in candidate.steps),
|
||||
|
|
@ -448,16 +448,54 @@ class Solver:
|
|||
kwargs = dict(self.request.tool_options)
|
||||
if extra:
|
||||
kwargs.update(extra)
|
||||
|
||||
def query_tool() -> tuple[PrimitiveOffer, ...]:
|
||||
expected_offer_type = {
|
||||
'straight': StraightOffer,
|
||||
'bend': BendOffer,
|
||||
's': SOffer,
|
||||
'u': UOffer,
|
||||
}[kind]
|
||||
offers = self.request.tool.primitive_offers(
|
||||
kind,
|
||||
in_ptype=in_ptype,
|
||||
out_ptype=out_ptype,
|
||||
**kwargs,
|
||||
)
|
||||
if not isinstance(offers, tuple):
|
||||
raise ToolContractError(
|
||||
f'Tool.primitive_offers({kind!r}) must return a tuple, '
|
||||
f'got {type(offers).__name__}'
|
||||
)
|
||||
for index, offer in enumerate(offers):
|
||||
if not isinstance(offer, PrimitiveOffer):
|
||||
raise ToolContractError(
|
||||
f'Tool.primitive_offers({kind!r}) item {index} must be a PrimitiveOffer, '
|
||||
f'got {type(offer).__name__}'
|
||||
)
|
||||
if offer.kind != kind:
|
||||
raise ToolContractError(
|
||||
f'Tool.primitive_offers({kind!r}) item {index} returned '
|
||||
f'{offer.kind!r} offer'
|
||||
)
|
||||
if not isinstance(offer, expected_offer_type):
|
||||
raise ToolContractError(
|
||||
f'Tool.primitive_offers({kind!r}) item {index} must be '
|
||||
f'{expected_offer_type.__name__}, got {type(offer).__name__}'
|
||||
)
|
||||
return offers
|
||||
|
||||
extra_items = tuple(sorted((extra or {}).items()))
|
||||
try:
|
||||
cache_key = (kind, in_ptype, out_ptype, tuple(sorted(kwargs.items())))
|
||||
cache_key = (kind, in_ptype, out_ptype, extra_items)
|
||||
hash(cache_key)
|
||||
except TypeError:
|
||||
return self.request.tool.primitive_offers(kind, in_ptype=in_ptype, out_ptype=out_ptype, **kwargs)
|
||||
return query_tool()
|
||||
|
||||
cached = self.offer_cache.get(cache_key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
offers = self.request.tool.primitive_offers(kind, in_ptype=in_ptype, out_ptype=out_ptype, **kwargs)
|
||||
offers = query_tool()
|
||||
self.offer_cache[cache_key] = offers
|
||||
return offers
|
||||
|
||||
|
|
@ -469,7 +507,6 @@ class Solver:
|
|||
*,
|
||||
out_ptype: str | None,
|
||||
role: Literal['main', 'adapter'],
|
||||
route_kind: PrimitiveKind | None,
|
||||
route_name: str | None = None,
|
||||
) -> SelectedPrimitive:
|
||||
"""
|
||||
|
|
@ -481,7 +518,7 @@ class Solver:
|
|||
"""
|
||||
route_name = self.request.route_name if route_name is None else route_name
|
||||
selected = offer.canonicalize_parameter(clean_parameter(parameter))
|
||||
key = (id(offer), round(float(selected), 12), in_ptype, out_ptype, role, route_name, route_kind)
|
||||
key = (id(offer), round(float(selected), 12), in_ptype, out_ptype, role, route_name)
|
||||
cached = self.eval_cache.get(key)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
|
@ -491,28 +528,37 @@ class Solver:
|
|||
if isinstance(offer, SOffer) and is_close(selected, 0):
|
||||
raise BuildError('zero-jog S primitive candidates are not allowed')
|
||||
out_port = offer.endpoint_at(selected)
|
||||
if not isinstance(out_port, Port):
|
||||
raise ToolContractError(
|
||||
f'{route_name} primitive endpoint_at() must return a Port, '
|
||||
f'got {type(out_port).__name__}'
|
||||
)
|
||||
if not ptypes_compatible(out_port.ptype, offer.out_ptype):
|
||||
raise RoutePlanningError(
|
||||
raise ToolContractError(
|
||||
f'{route_name} primitive endpoint ptype does not match declared offer out_ptype',
|
||||
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',
|
||||
policy=RouteFailurePolicy.FATAL,
|
||||
)
|
||||
cost = float(offer.cost_at(selected))
|
||||
try:
|
||||
if type(offer).cost_at is PrimitiveOffer.cost_at:
|
||||
cost = float(PrimitiveOffer._cost_for_endpoint(offer, selected, out_port))
|
||||
else:
|
||||
cost = float(offer.cost_at(selected))
|
||||
except (TypeError, ValueError, OverflowError) as err:
|
||||
raise ToolContractError(f'{route_name} primitive returned a non-numeric cost') from err
|
||||
if not numpy.isfinite(cost):
|
||||
raise BuildError(f'{route_name} primitive returned non-finite cost')
|
||||
raise ToolContractError(f'{route_name} primitive returned non-finite cost')
|
||||
if cost < 0:
|
||||
raise BuildError(f'{route_name} primitive returned negative cost')
|
||||
raise ToolContractError(f'{route_name} primitive returned negative cost')
|
||||
primitive = SelectedPrimitive(
|
||||
offer,
|
||||
selected,
|
||||
out_port,
|
||||
cost,
|
||||
role=role,
|
||||
route_kind=route_kind,
|
||||
)
|
||||
self.eval_cache[key] = primitive
|
||||
return primitive
|
||||
|
|
@ -532,7 +578,7 @@ class Solver:
|
|||
for step in steps:
|
||||
out_port = step.out_port
|
||||
if out_port.rotation is None:
|
||||
raise BuildError('Primitive endpoints must have rotation')
|
||||
raise ToolContractError('Primitive endpoints must have rotation')
|
||||
angle_cos = cos(angle)
|
||||
angle_sin = sin(angle)
|
||||
x += angle_cos * float(out_port.x) - angle_sin * float(out_port.y)
|
||||
|
|
@ -573,7 +619,6 @@ class Solver:
|
|||
current_ptype,
|
||||
out_ptype=None,
|
||||
role='adapter',
|
||||
route_kind=kind,
|
||||
route_name=f'{kind} adapter',
|
||||
)
|
||||
except BuildError as err:
|
||||
|
|
@ -606,7 +651,6 @@ class Solver:
|
|||
current_ptype,
|
||||
out_ptype=None,
|
||||
role='main',
|
||||
route_kind='straight',
|
||||
route_name='trace',
|
||||
)
|
||||
except BuildError as err:
|
||||
|
|
@ -642,7 +686,6 @@ class Solver:
|
|||
current_ptype,
|
||||
out_ptype=None,
|
||||
role='main',
|
||||
route_kind='bend',
|
||||
route_name='trace',
|
||||
)
|
||||
except BuildError as err:
|
||||
|
|
@ -678,7 +721,6 @@ class Solver:
|
|||
current_ptype,
|
||||
out_ptype=None,
|
||||
role='main',
|
||||
route_kind=kind,
|
||||
route_name=route_name,
|
||||
)
|
||||
except BuildError as err:
|
||||
|
|
@ -814,7 +856,6 @@ class Solver:
|
|||
current_ptype,
|
||||
out_ptype=None,
|
||||
role=step.role,
|
||||
route_kind=step.route_kind,
|
||||
)
|
||||
selected.append(selected_step)
|
||||
current_ptype = selected_step.out_port.ptype
|
||||
|
|
@ -1183,7 +1224,7 @@ class RoutingPlanner:
|
|||
out_port.rotate_around((0, 0), pi + port_rot)
|
||||
out_port.translate(current.offset)
|
||||
render_steps.append(RenderStep(
|
||||
selected.offer.opcode,
|
||||
selected.offer.kind,
|
||||
leg.tool,
|
||||
current.copy(),
|
||||
out_port.copy(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue