[planner] make solver carry state across increasing bend counts
This commit is contained in:
parent
54c4cd9a4a
commit
f864ebbeab
2 changed files with 186 additions and 101 deletions
|
|
@ -7,7 +7,7 @@ from numpy.testing import assert_equal
|
|||
|
||||
from masque import Library, PathTool, Port, Pather
|
||||
from masque.builder.planner import PreparedRouteResult, RoutePlanningError, RoutePortContext, RoutingPlanner
|
||||
from masque.builder.planner.planner import Candidate, RouteLeg
|
||||
from masque.builder.planner.planner import Candidate, RouteRequest
|
||||
from masque.builder.tools import BendOffer, PrimitiveOffer, StraightOffer, Tool
|
||||
from masque.error import BuildError, PortError
|
||||
|
||||
|
|
@ -264,38 +264,37 @@ def test_pather_trace_into_rejects_reserved_route_kwargs(
|
|||
assert len(p._paths['A']) == 0
|
||||
|
||||
|
||||
class TraceIntoBudgetPlanner(RoutingPlanner):
|
||||
def __init__(self, successes: set[int], fatal_at: set[int] | None = None) -> None:
|
||||
class TraceIntoBudgetSolver:
|
||||
def __init__(self, successes: set[tuple[int, int]], fatal_at: set[tuple[int, int]] | None = None) -> None:
|
||||
self.successes = successes
|
||||
self.fatal_at = set() if fatal_at is None else fatal_at
|
||||
self.attempts: list[int | None] = []
|
||||
self.attempts: list[tuple[int, int]] = []
|
||||
|
||||
def plan_leg(
|
||||
def solve(
|
||||
self,
|
||||
family: Any,
|
||||
context: RoutePortContext,
|
||||
*,
|
||||
length: float | None = None,
|
||||
jog: float | None = None,
|
||||
ccw: Any = None,
|
||||
plug_into: str | None = None,
|
||||
constrain_jog: bool = False,
|
||||
min_bends: int = 0,
|
||||
max_bends: int | None = None,
|
||||
**kwargs: Any,
|
||||
) -> RouteLeg:
|
||||
_ = family, length, jog, ccw, constrain_jog, kwargs
|
||||
self.attempts.append(max_bends)
|
||||
if max_bends in self.fatal_at:
|
||||
) -> Candidate:
|
||||
assert max_bends is not None
|
||||
band = (min_bends, max_bends)
|
||||
self.attempts.append(band)
|
||||
if band in self.fatal_at:
|
||||
raise RoutePlanningError('fatal', fatal=True)
|
||||
if max_bends not in self.successes:
|
||||
if band not in self.successes:
|
||||
raise BuildError('try next budget')
|
||||
return RouteLeg(
|
||||
portspec=context.portspec,
|
||||
start_port=context.port.copy(),
|
||||
tool=context.tool,
|
||||
candidate=Candidate((), context.port.copy(), 0.0, 0, 0.0),
|
||||
plug_into=plug_into,
|
||||
)
|
||||
return Candidate((), Port((0, 0), rotation=0, ptype='wire'), 0.0, 0, 0.0)
|
||||
|
||||
|
||||
class TraceIntoBudgetPlanner(RoutingPlanner):
|
||||
def __init__(self, successes: set[tuple[int, int]], fatal_at: set[tuple[int, int]] | None = None) -> None:
|
||||
self.solver = TraceIntoBudgetSolver(successes, fatal_at=fatal_at)
|
||||
self.solver_requests = 0
|
||||
|
||||
def solver_for_request(self, request: RouteRequest) -> Any:
|
||||
_ = request
|
||||
self.solver_requests += 1
|
||||
return self.solver
|
||||
|
||||
def prepared_result_from_legs(
|
||||
self,
|
||||
|
|
@ -310,30 +309,43 @@ class TraceIntoBudgetPlanner(RoutingPlanner):
|
|||
@pytest.mark.parametrize(
|
||||
('dst', 'successes', 'attempts'),
|
||||
[
|
||||
(Port((-10, 0), rotation=pi, ptype='wire'), {2}, [2]),
|
||||
(Port((-10, 0), rotation=pi, ptype='wire'), {4}, [2, 4]),
|
||||
(Port((-10, -10), rotation=3 * pi / 2, ptype='wire'), {1}, [1]),
|
||||
(Port((-10, -10), rotation=3 * pi / 2, ptype='wire'), {3}, [1, 3]),
|
||||
(Port((-10, 0), rotation=pi, ptype='wire'), {(0, 2)}, [(0, 2)]),
|
||||
(Port((-10, 0), rotation=pi, ptype='wire'), {(4, 4)}, [(0, 2), (4, 4)]),
|
||||
(Port((-10, -10), rotation=3 * pi / 2, ptype='wire'), {(1, 1)}, [(1, 1)]),
|
||||
(Port((-10, -10), rotation=3 * pi / 2, ptype='wire'), {(3, 3)}, [(1, 1), (3, 3)]),
|
||||
],
|
||||
)
|
||||
def test_trace_into_uses_staged_bend_budgets(
|
||||
def test_trace_into_reuses_solver_across_staged_bend_bands(
|
||||
dst: Port,
|
||||
successes: set[int],
|
||||
attempts: list[int],
|
||||
successes: set[tuple[int, int]],
|
||||
attempts: list[tuple[int, int]],
|
||||
) -> None:
|
||||
planner = TraceIntoBudgetPlanner(successes)
|
||||
context = RoutePortContext('src', Port((0, 0), rotation=0, ptype='wire'), PathTool(layer='M1', width=1, ptype='wire'))
|
||||
|
||||
planner.plan_trace_into(context, 'dst', dst, out_ptype=None, plug_destination=True, thru=None)
|
||||
|
||||
assert planner.attempts == attempts
|
||||
assert planner.solver.attempts == attempts
|
||||
assert planner.solver_requests == 1
|
||||
|
||||
|
||||
def test_trace_into_staged_bend_budget_stops_on_fatal_error() -> None:
|
||||
planner = TraceIntoBudgetPlanner({4}, fatal_at={2})
|
||||
planner = TraceIntoBudgetPlanner({(4, 4)}, fatal_at={(0, 2)})
|
||||
context = RoutePortContext('src', Port((0, 0), rotation=0, ptype='wire'), PathTool(layer='M1', width=1, ptype='wire'))
|
||||
|
||||
with pytest.raises(RoutePlanningError, match='fatal'):
|
||||
planner.plan_trace_into(context, 'dst', Port((-10, 0), rotation=pi, ptype='wire'), out_ptype=None, plug_destination=True, thru=None)
|
||||
|
||||
assert planner.attempts == [2]
|
||||
assert planner.solver.attempts == [(0, 2)]
|
||||
assert planner.solver_requests == 1
|
||||
|
||||
|
||||
def test_trace_into_bend_bands_respect_max_bends() -> None:
|
||||
class OneBendPlanner(RoutingPlanner):
|
||||
TRACE_INTO_MAX_BENDS = 1
|
||||
|
||||
planner = OneBendPlanner()
|
||||
|
||||
assert planner.trace_into_bend_bands('straight') == ((0, 0),)
|
||||
assert planner.trace_into_bend_bands('s') == ((0, 0),)
|
||||
assert planner.trace_into_bend_bands('bend') == ((1, 1),)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue