[Tool / SimpleTool / AutoTool] Documentation updates

This commit is contained in:
jan 2026-05-27 21:19:37 -07:00
commit e33a0f5ae1

View file

@ -1,9 +1,19 @@
""" """
Tools are objects which dynamically generate simple single-use devices (e.g. wires or waveguides) Tools are objects which dynamically generate simple single-use devices (e.g. wires or waveguides)
Concrete tools may implement native planning/rendering for `L`, `S`, or `U` routes. The `Tool` interface has two layers:
Any unimplemented planning method falls back to the corresponding `trace*()` method,
and `Pather` may further synthesize some routes from simpler primitives when needed. * `traceL`/`traceS`/`traceU` create concrete single-use geometry immediately.
* `planL`/`planS`/`planU` return an output `Port` plus tool-specific render
data, allowing `Pather(auto_render=False)` to defer geometry creation until
`Tool.render()` is called with a batch of `RenderStep`s.
Plans are expressed in local tool coordinates: the input port is at `(0, 0)`
with rotation `0`, `length` is measured along the input axis, and positive
`jog` is left of the direction of travel. Concrete tools may implement native
planning/rendering for L, S, and U routes; otherwise the base planning methods
fall back to the corresponding `trace*()` methods. `Pather` may also synthesize
some routes from simpler primitives when a tool does not provide a native route.
""" """
from typing import Literal, Any, Self, cast from typing import Literal, Any, Self, cast
from collections.abc import Sequence, Callable, Iterator from collections.abc import Sequence, Callable, Iterator
@ -25,8 +35,11 @@ from ..error import BuildError
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class RenderStep: class RenderStep:
""" """
Representation of a single saved operation, used by deferred `Pather` A single deferred routing operation.
instances and passed to `Tool.render()` when `Pather.render()` is called.
`Pather(auto_render=False)` stores these records while routing and later
passes batches of compatible steps to `Tool.render()` when `Pather.render()`
is called.
""" """
opcode: Literal['L', 'S', 'U', 'P'] opcode: Literal['L', 'S', 'U', 'P']
""" What operation is being performed. """ What operation is being performed.
@ -37,10 +50,13 @@ class RenderStep:
""" """
tool: 'Tool | None' tool: 'Tool | None'
""" The current tool. May be `None` if `opcode='P'` """ """ Tool that produced this step, or `None` for `opcode='P'`. """
start_port: Port start_port: Port
""" Input-side port before this step is rendered. """
end_port: Port end_port: Port
""" Output-side port after this step is rendered. """
data: Any data: Any
""" Arbitrary tool-specific data""" """ Arbitrary tool-specific data"""
@ -101,7 +117,9 @@ class RenderStep:
def measure_tool_plan(tree: ILibrary, port_names: tuple[str, str]) -> tuple[Port, Any]: def measure_tool_plan(tree: ILibrary, port_names: tuple[str, str]) -> tuple[Port, Any]:
""" """
Extracts a Port and returns the tree (as data) for tool planning fallbacks. Measure generated geometry for the base `Tool.plan*()` fallbacks.
Returns the calculated output port and the original tree as render data.
""" """
pat = tree.top_pattern() pat = tree.top_pattern()
in_p = pat[port_names[0]] in_p = pat[port_names[0]]
@ -113,6 +131,13 @@ def measure_tool_plan(tree: ILibrary, port_names: tuple[str, str]) -> tuple[Port
class Tool: class Tool:
""" """
Interface for path (e.g. wire or waveguide) generation. Interface for path (e.g. wire or waveguide) generation.
Subclasses may implement immediate `trace*()` methods, deferred
`plan*()`/`render()` methods, or both. The base `plan*()` implementations
call the matching `trace*()` method and measure the resulting ports, so a
simple immediate-rendering tool can implement only `traceL`, `traceS`, or
`traceU` as needed. Tools that support deferred rendering should return
compact, tool-specific data from `plan*()` and consume it in `render()`.
""" """
def traceL( def traceL(
self, self,
@ -172,7 +197,7 @@ class Tool:
""" """
Create a wire or waveguide that travels exactly `length` distance along the axis Create a wire or waveguide that travels exactly `length` distance along the axis
of its input port, and `jog` distance on the perpendicular axis. of its input port, and `jog` distance on the perpendicular axis.
`jog` is positive when moving left of the direction of travel (from input to ouput port). `jog` is positive when moving left of the direction of travel (from input to output port).
Used by `Pather`. Used by `Pather`.
@ -236,7 +261,7 @@ class Tool:
Returns: Returns:
The calculated output `Port` for the wire, assuming an input port at (0, 0) with rotation 0. The calculated output `Port` for the wire, assuming an input port at (0, 0) with rotation 0.
Any tool-specifc data, to be stored in `RenderStep.data`, for use during rendering. Any tool-specific data, to be stored in `RenderStep.data`, for use during rendering.
Raises: Raises:
BuildError if an impossible or unsupported geometry is requested. BuildError if an impossible or unsupported geometry is requested.
@ -284,7 +309,7 @@ class Tool:
Returns: Returns:
The calculated output `Port` for the wire, assuming an input port at (0, 0) with rotation 0. The calculated output `Port` for the wire, assuming an input port at (0, 0) with rotation 0.
Any tool-specifc data, to be stored in `RenderStep.data`, for use during rendering. Any tool-specific data, to be stored in `RenderStep.data`, for use during rendering.
Raises: Raises:
BuildError if an impossible or unsupported geometry is requested. BuildError if an impossible or unsupported geometry is requested.
@ -312,8 +337,9 @@ class Tool:
**kwargs, **kwargs,
) -> Library: ) -> Library:
""" """
Create a wire or waveguide that travels exactly `jog` distance along the axis Create a wire or waveguide whose output is displaced by `length` along
perpendicular to its input port (i.e. a U-bend). the input axis and `jog` along the perpendicular axis, while preserving
the input orientation (i.e. a U-bend or jogged U-turn).
Used by `Pather`. Tools may leave this unimplemented if they Used by `Pather`. Tools may leave this unimplemented if they
do not support a native U-bend primitive. do not support a native U-bend primitive.
@ -328,6 +354,7 @@ class Tool:
jog: The total offset from the input to output, along the perpendicular axis. jog: The total offset from the input to output, along the perpendicular axis.
A positive number implies a leftwards shift (i.e. counterclockwise bend A positive number implies a leftwards shift (i.e. counterclockwise bend
followed by a clockwise bend) followed by a clockwise bend)
length: The total offset from the input to output, along the input axis.
in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged. in_ptype: The `ptype` of the port into which this wire's input will be `plug`ged.
out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged. out_ptype: The `ptype` of the port into which this wire's output will be `plug`ged.
port_names: The output pattern will have its input port named `port_names[0]` and port_names: The output pattern will have its input port named `port_names[0]` and
@ -351,8 +378,9 @@ class Tool:
**kwargs, **kwargs,
) -> tuple[Port, Any]: ) -> tuple[Port, Any]:
""" """
Plan a wire or waveguide that travels exactly `jog` distance along the axis Plan a wire or waveguide whose output is displaced by optional `length`
perpendicular to its input port (i.e. a U-bend). along the input axis and `jog` along the perpendicular axis, while
preserving the input orientation (i.e. a U-bend or jogged U-turn).
Used by `Pather` when `auto_render=False`. This is an optional native-planning hook: tools may Used by `Pather` when `auto_render=False`. This is an optional native-planning hook: tools may
implement it when they can represent a U-turn directly, otherwise they may rely implement it when they can represent a U-turn directly, otherwise they may rely
@ -374,7 +402,7 @@ class Tool:
Returns: Returns:
The calculated output `Port` for the wire, assuming an input port at (0, 0) with rotation 0. The calculated output `Port` for the wire, assuming an input port at (0, 0) with rotation 0.
Any tool-specifc data, to be stored in `RenderStep.data`, for use during rendering. Any tool-specific data, to be stored in `RenderStep.data`, for use during rendering.
Raises: Raises:
BuildError if an impossible or unsupported geometry is requested. BuildError if an impossible or unsupported geometry is requested.
@ -404,6 +432,11 @@ class Tool:
Render the provided `batch` of `RenderStep`s into geometry, returning a tree Render the provided `batch` of `RenderStep`s into geometry, returning a tree
(a Library with a single topcell). (a Library with a single topcell).
The base implementation is intended for steps whose plan data came from
the base fallback planners, where `RenderStep.data` is already an
`ILibrary`. Subclasses with native `plan*()` data should generally
override this method.
Args: Args:
batch: A sequence of `RenderStep` objects containing the ports and data batch: A sequence of `RenderStep` objects containing the ports and data
provided by this tool's `planL`/`planS`/`planU` functions. provided by this tool's `planL`/`planS`/`planU` functions.
@ -464,15 +497,18 @@ abstract_tuple_t = tuple[Abstract, str, str]
@dataclass @dataclass
class SimpleTool(Tool, metaclass=ABCMeta): class SimpleTool(Tool, metaclass=ABCMeta):
""" """
A simple tool which relies on a single pre-rendered `bend` pattern, a function Minimal L-route tool built from one straight generator and one bend.
for generating straight paths, and a table of pre-rendered `transitions` for converting
from non-native ptypes. `SimpleTool` supports straight segments and single-bend L routes through
`planL`/`traceL`/`render`. It does not perform automatic port-type
transitions and does not provide native S or U routes. Use `AutoTool` when
routes need multiple candidate primitives, transitions, S-bends, or U-turns.
""" """
straight: tuple[Callable[[float], Pattern] | Callable[[float], Library], str, str] straight: tuple[Callable[[float], Pattern] | Callable[[float], Library], str, str]
""" `create_straight(length: float), in_port_name, out_port_name` """ """ `(create_straight, in_port_name, out_port_name)` for straight segments. """
bend: abstract_tuple_t # Assumed to be clockwise bend: abstract_tuple_t # Assumed to be clockwise
""" `clockwise_bend_abstract, in_port_name, out_port_name` """ """ `(clockwise_bend_abstract, in_port_name, out_port_name)` for L turns. """
default_out_ptype: str default_out_ptype: str
""" Default value for out_ptype """ """ Default value for out_ptype """
@ -482,7 +518,7 @@ class SimpleTool(Tool, metaclass=ABCMeta):
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class LData: class LData:
""" Data for planL """ """ Deferred render data returned by `planL()`. """
straight_length: float straight_length: float
straight_kwargs: dict[str, Any] straight_kwargs: dict[str, Any]
ccw: SupportsBool | None ccw: SupportsBool | None
@ -608,43 +644,114 @@ class SimpleTool(Tool, metaclass=ABCMeta):
@dataclass @dataclass
class AutoTool(Tool, metaclass=ABCMeta): class AutoTool(Tool, metaclass=ABCMeta):
""" """
A simple tool which relies on a single pre-rendered `bend` pattern, a function A routing tool assembled from reusable path primitives.
for generating straight paths, and a table of pre-rendered `transitions` for converting
from non-native ptypes. `AutoTool` chooses among prioritized straight generators, pre-rendered bends,
optional native S-bend generators, and pre-rendered transitions to satisfy the
`Tool` planning/rendering interface used by `Pather`.
Route selection is greedy in the order supplied by `straights`, `bends`, and
`sbends`. For each route, the planner subtracts any transition and bend
overhead from the requested distance, then uses the first candidate whose
remaining straight or jog length falls within that candidate's range.
`planL` uses one straight and, if `ccw` is not `None`, one bend. `planS`
first tries a straight plus a native S-bend, then a pure native S-bend, and
falls back to a two-L route when no native S-bend candidate fits. `planU`
is implemented as a two-L route.
Transition keys are `(external_ptype, internal_ptype)`. For example, a
transition keyed by `('m2wire', 'm1wire')` is used when the route is being
attached to an external `m2wire` port but the selected primitive is `m1wire`.
Call `add_complementary_transitions()` to automatically add reversed entries
for any missing opposite directions.
Straight and S-bend generator functions may return either a `Pattern` or a
single-top `Library`. Extra keyword arguments passed to `trace*()` or
`render()` are forwarded to those generators, along with any keyword
arguments captured during `plan*()`.
""" """
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class Straight: class Straight:
""" Description of a straight-path generator """ """
Description of a straight-path generator.
`fn(length, **kwargs)` must return a path whose `in_port_name` and
`out_port_name` ports are separated by `length` along the input axis.
The planner considers this generator only when the required length is in
`length_range`, with an inclusive lower bound and exclusive upper bound.
"""
ptype: str ptype: str
""" Port type produced by this straight segment. """
fn: Callable[[float], Pattern] | Callable[[float], Library] fn: Callable[[float], Pattern] | Callable[[float], Library]
""" Generator function called as `fn(length, **kwargs)`. """
in_port_name: str in_port_name: str
""" Name of the input port on the generated pattern. """
out_port_name: str out_port_name: str
""" Name of the output port on the generated pattern. """
length_range: tuple[float, float] = (0, numpy.inf) length_range: tuple[float, float] = (0, numpy.inf)
""" Valid generated lengths, as `(inclusive_min, exclusive_max)`. """
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class SBend: class SBend:
""" Description of an s-bend generator """ """
Description of a native S-bend generator.
`fn(jog, **kwargs)` is called with a non-negative jog magnitude and must
return a path whose output port faces back toward the input port. For a
negative requested jog, `AutoTool` mirrors the generated S-bend during
rendering.
"""
ptype: str ptype: str
""" Port type produced by this S-bend. """
fn: Callable[[float], Pattern] | Callable[[float], Library] fn: Callable[[float], Pattern] | Callable[[float], Library]
""" """
Generator function. `jog` (only argument) is assumed to be left (ccw) relative to travel Generator function called as `fn(abs(jog), **kwargs)`. The generated
and may be negative for a jog in the opposite direction. Won't be called if jog=0. geometry is assumed to jog left, i.e. counterclockwise relative to the
direction of travel. This function is not called when the residual jog is
zero.
""" """
in_port_name: str in_port_name: str
""" Name of the input port on the generated pattern. """
out_port_name: str out_port_name: str
""" Name of the output port on the generated pattern. """
jog_range: tuple[float, float] = (0, numpy.inf) jog_range: tuple[float, float] = (0, numpy.inf)
""" Valid residual jog magnitudes, as `(inclusive_min, exclusive_max)`. """
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class Bend: class Bend:
""" Description of a pre-rendered bend """ """
Description of a pre-rendered L-bend.
`abstract` must contain `in_port_name` and `out_port_name`. The
`clockwise` flag describes the in-to-out turn direction of that stored
bend. If `mirror` is true, `AutoTool` mirrors the stored bend to realize
the opposite turn direction; otherwise it plugs the bend from the
opposite port where possible.
"""
abstract: Abstract abstract: Abstract
""" Abstract for the reusable bend pattern. """
in_port_name: str in_port_name: str
""" Name of the bend input port. """
out_port_name: str out_port_name: str
""" Name of the bend output port. """
clockwise: bool = True # Is in-to-out clockwise? clockwise: bool = True # Is in-to-out clockwise?
""" Whether the stored bend turns clockwise from input to output. """
mirror: bool = True # Should we mirror to get the other rotation? mirror: bool = True # Should we mirror to get the other rotation?
""" Whether to mirror the stored bend to produce the opposite turn. """
@property @property
def in_port(self) -> Port: def in_port(self) -> Port:
@ -656,10 +763,22 @@ class AutoTool(Tool, metaclass=ABCMeta):
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class Transition: class Transition:
""" Description of a pre-rendered transition """ """
Description of a pre-rendered port-type transition.
`their_port_name` is the external side of the transition and
`our_port_name` is the side compatible with the selected internal
primitive. The transition table key should match that direction:
`(their_ptype, our_ptype)`.
"""
abstract: Abstract abstract: Abstract
""" Abstract for the reusable transition pattern. """
their_port_name: str their_port_name: str
""" Name of the external-side port. """
our_port_name: str our_port_name: str
""" Name of the internal primitive-side port. """
@property @property
def our_port(self) -> Port: def our_port(self) -> Port:
@ -674,7 +793,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class LPlan: class LPlan:
""" Template for an L-path configuration """ """ Candidate L-route configuration before final straight length is known. """
straight: 'AutoTool.Straight' straight: 'AutoTool.Straight'
bend: 'AutoTool.Bend | None' bend: 'AutoTool.Bend | None'
in_trans: 'AutoTool.Transition | None' in_trans: 'AutoTool.Transition | None'
@ -687,7 +806,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class LData: class LData:
""" Data for planL """ """ Deferred render data returned by `planL()`. """
straight_length: float straight_length: float
straight: 'AutoTool.Straight' straight: 'AutoTool.Straight'
straight_kwargs: dict[str, Any] straight_kwargs: dict[str, Any]
@ -758,7 +877,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class SData: class SData:
""" Data for planS """ """ Deferred render data for native-S routes returned by `planS()`. """
straight_length: float straight_length: float
straight: 'AutoTool.Straight' straight: 'AutoTool.Straight'
gen_kwargs: dict[str, Any] gen_kwargs: dict[str, Any]
@ -770,7 +889,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class UData: class UData:
""" Data for planU or planS (double-L) """ """ Deferred render data for `planU()` or double-L `planS()` routes. """
ldata0: 'AutoTool.LData' ldata0: 'AutoTool.LData'
ldata1: 'AutoTool.LData' ldata1: 'AutoTool.LData'
straight2: 'AutoTool.Straight' straight2: 'AutoTool.Straight'
@ -834,21 +953,27 @@ class AutoTool(Tool, metaclass=ABCMeta):
raise BuildError(f"Failed to find a valid double-L configuration for {length=}, {jog=}") raise BuildError(f"Failed to find a valid double-L configuration for {length=}, {jog=}")
straights: list[Straight] straights: list[Straight]
""" List of straight-generators to choose from, in order of priority """ """ Straight generators to choose from, in priority order. """
bends: list[Bend] bends: list[Bend]
""" List of bends to choose from, in order of priority """ """ L-bend primitives to choose from, in priority order. """
sbends: list[SBend] sbends: list[SBend]
""" List of S-bend generators to choose from, in order of priority """ """ Native S-bend generators to choose from, in priority order. """
transitions: dict[tuple[str, str], Transition] transitions: dict[tuple[str, str], Transition]
""" `{(external_ptype, internal_ptype): Transition, ...}` """ """ Mapping from `(external_ptype, internal_ptype)` to transition primitive. """
default_out_ptype: str default_out_ptype: str
""" Default value for out_ptype """ """ Output port type used when a zero-length route provides no primitive ptype. """
def add_complementary_transitions(self) -> Self: def add_complementary_transitions(self) -> Self:
"""
Add reversed transition entries for any missing opposite directions.
Existing explicit entries are preserved. The method mutates
`self.transitions` and returns `self` for fluent construction.
"""
for iioo in list(self.transitions.keys()): for iioo in list(self.transitions.keys()):
ooii = (iioo[1], iioo[0]) ooii = (iioo[1], iioo[0])
self.transitions.setdefault(ooii, self.transitions[iioo].reversed()) self.transitions.setdefault(ooii, self.transitions[iioo].reversed())
@ -928,7 +1053,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
straight_kwargs: dict[str, Any], straight_kwargs: dict[str, Any],
) -> ILibrary: ) -> ILibrary:
""" """
Render an L step into a preexisting tree Render an L step into an existing tree.
""" """
pat = tree.top_pattern() pat = tree.top_pattern()
if data.in_transition: if data.in_transition:
@ -1061,7 +1186,7 @@ class AutoTool(Tool, metaclass=ABCMeta):
gen_kwargs: dict[str, Any], gen_kwargs: dict[str, Any],
) -> ILibrary: ) -> ILibrary:
""" """
Render an L step into a preexisting tree Render a native-S step into an existing tree.
""" """
pat = tree.top_pattern() pat = tree.top_pattern()
if data.in_transition: if data.in_transition:
@ -1207,19 +1332,21 @@ class AutoTool(Tool, metaclass=ABCMeta):
@dataclass @dataclass
class PathTool(Tool, metaclass=ABCMeta): class PathTool(Tool, metaclass=ABCMeta):
""" """
A tool which draws `Path` geometry elements. Tool that renders routes directly as `Pattern.path()` geometry.
If `planL` / `render` are used, the `Path` elements can cover >2 vertices; `PathTool` supports L and S routes. Immediate `traceL()` and `traceS()`
with `path` only individual rectangles will be drawn. create one path element per route, while deferred `render()` combines a
compatible batch of L/S `RenderStep`s into one multi-vertex path. U routes
are left to `Pather` synthesis or to a different tool.
""" """
layer: layer_t layer: layer_t
""" Layer to draw on """ """ Layer to draw generated path geometry on. """
width: float width: float
""" `Path` width """ """ Width of generated path geometry. """
ptype: str = 'unk' ptype: str = 'unk'
""" ptype for any ports in patterns generated by this tool """ """ Port type for generated input and output ports. """
#@dataclass(frozen=True, slots=True) #@dataclass(frozen=True, slots=True)
#class LData: #class LData: