[pather / planner] pass planner options via plan_options, not strategy/bend_policy
This commit is contained in:
parent
91a5d63b57
commit
ce7463e57c
8 changed files with 280 additions and 115 deletions
|
|
@ -996,7 +996,13 @@ def test_autotool_strategy_orders_main_steps_across_adapters(jog: float) -> None
|
|||
for strategy in ('straight_first', 'turn_first'):
|
||||
pather = Pather(library, tools=tool, render='deferred')
|
||||
pather.ports['A'] = Port((0, 0), 0, ptype=primary)
|
||||
pather.jog('A', jog, length=route_length, out_ptype=primary, strategy=strategy)
|
||||
pather.jog(
|
||||
'A',
|
||||
jog,
|
||||
length=route_length,
|
||||
out_ptype=primary,
|
||||
plan_options={'strategy': strategy},
|
||||
)
|
||||
selected_kinds[strategy] = [step.kind for step in pather._paths['A']]
|
||||
|
||||
assert selected_kinds['straight_first'] == ['straight', 'straight', 'straight', 's']
|
||||
|
|
|
|||
|
|
@ -986,6 +986,16 @@ def test_routing_entry_points_have_no_catch_all_kwargs(route_type: type, name: s
|
|||
assert all(parameter.kind is not inspect.Parameter.VAR_KEYWORD for parameter in parameters)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('route_type', [Pather, PortPather])
|
||||
@pytest.mark.parametrize('name', ['trace', 'trace_to', 'straight', 'bend', 'ccw', 'cw', 'jog', 'uturn', 'trace_into'])
|
||||
def test_routing_entry_points_use_generic_plan_options(route_type: type, name: str) -> None:
|
||||
parameters = inspect.signature(getattr(route_type, name)).parameters
|
||||
|
||||
assert 'plan_options' in parameters
|
||||
assert 'strategy' not in parameters
|
||||
assert 'bend_policy' not in parameters
|
||||
|
||||
|
||||
def test_routing_typo_fails_before_tool_lookup() -> None:
|
||||
tool = RequestCountingTool()
|
||||
p = Pather(Library(), tools=tool, render='deferred')
|
||||
|
|
@ -1007,6 +1017,41 @@ def test_tool_options_reject_invalid_or_reserved_keys(tool_options: dict[Any, An
|
|||
assert tool.offer_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('plan_options', [7, {1: 'bad'}])
|
||||
def test_plan_options_reject_invalid_mapping_shape(plan_options: Any) -> None:
|
||||
tool = RequestCountingTool()
|
||||
p = Pather(Library(), tools=tool, render='deferred')
|
||||
|
||||
with pytest.raises(BuildError, match='plan_options'):
|
||||
p.trace('A', None, plan_options=plan_options)
|
||||
|
||||
assert tool.offer_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'plan_options',
|
||||
[
|
||||
{'bend_policy': 'flexible'},
|
||||
{'unknown': True},
|
||||
],
|
||||
)
|
||||
def test_default_planner_rejects_unsupported_plan_options_before_tool_lookup(
|
||||
plan_options: dict[str, Any],
|
||||
) -> None:
|
||||
tool = RequestCountingTool()
|
||||
p = Pather(
|
||||
Library(),
|
||||
ports={'A': Port((0, 0), rotation=0, ptype='wire')},
|
||||
tools=tool,
|
||||
render='deferred',
|
||||
)
|
||||
|
||||
with pytest.raises(BuildError, match='unsupported keys'):
|
||||
p.trace('A', None, length=1, plan_options=plan_options)
|
||||
|
||||
assert tool.offer_calls == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'operation',
|
||||
[
|
||||
|
|
|
|||
|
|
@ -93,6 +93,41 @@ def test_pather_accepts_and_reuses_planner_instance() -> None:
|
|||
assert planner.trace_to_calls == 2
|
||||
|
||||
|
||||
def test_pather_copies_and_forwards_plan_options_to_planner() -> None:
|
||||
class RecordingPlanner(RoutingPlanner):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.received_plan_options: Any = None
|
||||
|
||||
def plan_trace_to_route(
|
||||
self,
|
||||
*args: Any,
|
||||
plan_options: Any = None,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
self.received_plan_options = plan_options
|
||||
return super().plan_trace_to_route(
|
||||
*args,
|
||||
plan_options=plan_options,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
planner = RecordingPlanner()
|
||||
p = Pather(
|
||||
Library(),
|
||||
tools=PathTool(layer=(1, 0), width=1),
|
||||
render='deferred',
|
||||
planner=planner,
|
||||
)
|
||||
p.ports['A'] = Port((0, 0), rotation=0)
|
||||
supplied = {'strategy': 'turn_first'}
|
||||
|
||||
p.straight('A', 1, plan_options=supplied)
|
||||
|
||||
assert planner.received_plan_options == supplied
|
||||
assert planner.received_plan_options is not supplied
|
||||
|
||||
|
||||
def test_port_tool_policy_and_portpather_selection_follow_names() -> None:
|
||||
default_tool = PathTool(layer=(1, 0), width=1, ptype='wire')
|
||||
named_tool = PathTool(layer=(2, 0), width=1, ptype='wire')
|
||||
|
|
|
|||
|
|
@ -732,7 +732,7 @@ def test_pather_route_strategy_uses_planner_default() -> None:
|
|||
def test_pather_route_strategy_per_route_overrides_planner_default() -> None:
|
||||
pather, _tool = pather_with_strategy_tool(RoutingPlanner(strategy='turn_first'))
|
||||
|
||||
pather.jog('A', 4, length=10, strategy='straight_first')
|
||||
pather.jog('A', 4, length=10, plan_options={'strategy': 'straight_first'})
|
||||
|
||||
assert [step.data['kind'] for step in pather._paths['A']] == ['straight', 's']
|
||||
|
||||
|
|
@ -740,15 +740,21 @@ def test_pather_route_strategy_per_route_overrides_planner_default() -> None:
|
|||
def test_pather_route_strategy_per_route_can_request_turn_first() -> None:
|
||||
pather, _tool = pather_with_strategy_tool()
|
||||
|
||||
pather.jog('A', 4, length=10, strategy='turn_first')
|
||||
pather.jog('A', 4, length=10, plan_options={'strategy': 'turn_first'})
|
||||
|
||||
assert [step.data['kind'] for step in pather._paths['A']] == ['s', 'straight']
|
||||
|
||||
|
||||
def test_pather_route_strategy_is_not_forwarded_to_tool() -> None:
|
||||
def test_pather_plan_options_are_not_forwarded_to_tool() -> None:
|
||||
pather, tool = pather_with_strategy_tool()
|
||||
|
||||
pather.jog('A', 4, length=10, strategy='turn_first', tool_options={'marker': 'sentinel'})
|
||||
pather.jog(
|
||||
'A',
|
||||
4,
|
||||
length=10,
|
||||
plan_options={'strategy': 'turn_first'},
|
||||
tool_options={'marker': 'sentinel'},
|
||||
)
|
||||
|
||||
assert tool.seen_kwargs
|
||||
assert all('strategy' not in kwargs for kwargs in tool.seen_kwargs)
|
||||
|
|
@ -762,7 +768,7 @@ def test_pather_route_strategy_rejects_invalid_values() -> None:
|
|||
|
||||
pather, _tool = pather_with_strategy_tool()
|
||||
with pytest.raises(BuildError, match='Invalid route strategy'):
|
||||
pather.jog('A', 4, length=10, strategy='sideways')
|
||||
pather.jog('A', 4, length=10, plan_options={'strategy': 'sideways'})
|
||||
|
||||
|
||||
def test_solver_rejects_rotation_impossible_candidates_before_parameter_solving() -> None:
|
||||
|
|
|
|||
|
|
@ -131,7 +131,12 @@ def test_pather_trace_into_minimal_policy_accepts_required_topologies(dst: Port)
|
|||
pather.ports['src'] = Port((0, 0), rotation=0)
|
||||
pather.ports['dst'] = dst
|
||||
|
||||
pather.trace_into('src', 'dst', plug_destination=False, bend_policy='minimal')
|
||||
pather.trace_into(
|
||||
'src',
|
||||
'dst',
|
||||
plug_destination=False,
|
||||
plan_options={'bend_policy': 'minimal'},
|
||||
)
|
||||
|
||||
assert numpy.allclose(pather.ports['src'].offset, dst.offset)
|
||||
assert pather.ports['src'].rotation is not None
|
||||
|
|
@ -153,7 +158,7 @@ def test_pather_trace_into_bend_policy_changes_real_solver_fallback() -> None:
|
|||
flexible.at('src').trace_into(
|
||||
'dst',
|
||||
plug_destination=False,
|
||||
bend_policy='flexible',
|
||||
plan_options={'bend_policy': 'flexible'},
|
||||
)
|
||||
|
||||
assert_equal(flexible.ports['src'].offset, (2, 0))
|
||||
|
|
@ -415,7 +420,7 @@ def test_trace_into_reuses_solver_across_staged_bend_bands(
|
|||
out_ptype=None,
|
||||
plug_destination=True,
|
||||
thru=None,
|
||||
bend_policy='flexible',
|
||||
plan_options={'bend_policy': 'flexible'},
|
||||
)
|
||||
|
||||
assert planner.solver.attempts == attempts
|
||||
|
|
@ -434,7 +439,7 @@ def test_trace_into_staged_bend_budget_stops_on_fatal_error() -> None:
|
|||
out_ptype=None,
|
||||
plug_destination=True,
|
||||
thru=None,
|
||||
bend_policy='flexible',
|
||||
plan_options={'bend_policy': 'flexible'},
|
||||
)
|
||||
|
||||
assert planner.solver.attempts == [(0, 2)]
|
||||
|
|
@ -497,7 +502,7 @@ def test_trace_into_minimal_policy_uses_orientation_required_band(
|
|||
out_ptype=None,
|
||||
plug_destination=True,
|
||||
thru=None,
|
||||
bend_policy='minimal',
|
||||
plan_options={'bend_policy': 'minimal'},
|
||||
)
|
||||
|
||||
assert planner.solver.attempts == [required_band]
|
||||
|
|
@ -515,7 +520,7 @@ def test_trace_into_minimal_policy_rejects_fallback_without_mutation() -> None:
|
|||
pather.ports['dst'] = Port((-10, 0), rotation=pi, ptype='wire')
|
||||
|
||||
with pytest.raises(BuildError, match='try next budget'):
|
||||
pather.trace_into('src', 'dst', bend_policy='minimal')
|
||||
pather.trace_into('src', 'dst', plan_options={'bend_policy': 'minimal'})
|
||||
|
||||
assert planner.solver.attempts == [(0, 0)]
|
||||
assert set(pather.ports) == {'src', 'dst'}
|
||||
|
|
@ -547,7 +552,7 @@ def test_trace_into_bend_policy_planner_default_and_route_override() -> None:
|
|||
out_ptype=None,
|
||||
plug_destination=True,
|
||||
thru=None,
|
||||
bend_policy='flexible',
|
||||
plan_options={'bend_policy': 'flexible'},
|
||||
)
|
||||
assert flexible_planner.solver.attempts == [(0, 2), (4, 4)]
|
||||
|
||||
|
|
@ -555,3 +560,14 @@ def test_trace_into_bend_policy_planner_default_and_route_override() -> None:
|
|||
def test_trace_into_rejects_invalid_bend_policy() -> None:
|
||||
with pytest.raises(BuildError, match='Invalid trace_into bend policy'):
|
||||
RoutingPlanner(bend_policy='sideways') # type: ignore[arg-type]
|
||||
|
||||
pather = Pather(
|
||||
Library(),
|
||||
tools=PathTool(layer='M1', width=1, ptype='wire'),
|
||||
render='deferred',
|
||||
)
|
||||
pather.ports['src'] = Port((0, 0), rotation=0, ptype='wire')
|
||||
pather.ports['dst'] = Port((-10, 0), rotation=pi, ptype='wire')
|
||||
|
||||
with pytest.raises(BuildError, match='Invalid trace_into bend policy'):
|
||||
pather.trace_into('src', 'dst', plan_options={'bend_policy': 'sideways'})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue