Compare commits

...

4 Commits

3 changed files with 155 additions and 4 deletions

View File

@ -1,2 +1,3 @@
from .devices import Port, Device from .devices import Port, Device
from .utils import ell from .utils import ell
from .tools import Tool

View File

@ -15,6 +15,8 @@ from ..subpattern import SubPattern
from ..traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable from ..traits import PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable
from ..utils import AutoSlots, rotation_matrix_2d from ..utils import AutoSlots, rotation_matrix_2d
from ..error import DeviceError from ..error import DeviceError
from .tools import Tool
from .utils import ell
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -157,7 +159,7 @@ class Device(Copyable, Mirrorable):
renamed to 'gnd' so that further routing can use this signal or net name renamed to 'gnd' so that further routing can use this signal or net name
rather than the port name on the original `pad` device. rather than the port name on the original `pad` device.
""" """
__slots__ = ('pattern', 'ports', '_dead') __slots__ = ('pattern', 'ports', 'tools', '_dead')
pattern: Pattern pattern: Pattern
""" Layout of this device """ """ Layout of this device """
@ -165,6 +167,12 @@ class Device(Copyable, Mirrorable):
ports: Dict[str, Port] ports: Dict[str, Port]
""" Uniquely-named ports which can be used to snap to other Device instances""" """ Uniquely-named ports which can be used to snap to other Device instances"""
tools: Dict[Optional[str], Tool]
"""
Tool objects are used to dynamically generate new single-use Devices
(e.g wires or waveguides) to be plugged into this device.
"""
_dead: bool _dead: bool
""" If True, plug()/place() are skipped (for debugging)""" """ If True, plug()/place() are skipped (for debugging)"""
@ -173,6 +181,7 @@ class Device(Copyable, Mirrorable):
pattern: Optional[Pattern] = None, pattern: Optional[Pattern] = None,
ports: Optional[Dict[str, Port]] = None, ports: Optional[Dict[str, Port]] = None,
*, *,
tools: Union[None, Tool, Dict[Optional[str], Tool]] = None,
name: Optional[str] = None, name: Optional[str] = None,
) -> None: ) -> None:
""" """
@ -198,6 +207,13 @@ class Device(Copyable, Mirrorable):
else: else:
self.ports = copy.deepcopy(ports) self.ports = copy.deepcopy(ports)
if tools is None:
self.tools = {}
elif isinstance(tools, Tool):
self.tools = {None: tools}
else:
self.tools = tools
self._dead = False self._dead = False
@overload @overload
@ -205,7 +221,7 @@ class Device(Copyable, Mirrorable):
pass pass
@overload @overload
def __getitem__(self, key: Union[List[str], Tuple[str], KeysView[str], ValuesView[str]]) -> Dict[str, Port]: def __getitem__(self, key: Union[List[str], Tuple[str, ...], KeysView[str], ValuesView[str]]) -> Dict[str, Port]:
pass pass
def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]: def __getitem__(self, key: Union[str, Iterable[str]]) -> Union[Port, Dict[str, Port]]:
@ -333,7 +349,7 @@ class Device(Copyable, Mirrorable):
""" """
pat = Pattern(name) pat = Pattern(name)
pat.addsp(self.pattern) pat.addsp(self.pattern)
new = Device(pat, ports=self.ports) new = Device(pat, ports=self.ports, tools=self.tools)
return new return new
def as_interface( def as_interface(
@ -408,7 +424,7 @@ class Device(Copyable, Mirrorable):
if duplicates: if duplicates:
raise DeviceError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}') raise DeviceError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}')
new = Device(name=name, ports={**ports_in, **ports_out}) new = Device(name=name, ports={**ports_in, **ports_out}, tools=self.tools)
return new return new
def plug( def plug(
@ -752,6 +768,118 @@ class Device(Copyable, Mirrorable):
s += ']>' s += ']>'
return s return s
def retool(
self: D,
tool: Tool,
keys: Union[Optional[str], Sequence[Optional[str]]] = None,
) -> D:
if keys is None or isinstance(keys, str):
self.tools[keys] = tool
else:
for key in keys:
self.tools[key] = tool
return self
def path(
self: D,
portspec: str,
ccw: Optional[bool],
length: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
**kwargs,
) -> D:
if self._dead:
logger.error('Skipping path() since device is dead')
return self
tool = self.tools.get(portspec, self.tools[None])
in_ptype = self.ports[portspec].ptype
dev = tool.path(ccw, length, in_ptype=in_ptype, port_names=tool_port_names, **kwargs)
return self.plug(dev, {portspec: tool_port_names[0]})
def path_to(
self: D,
portspec: str,
ccw: Optional[bool],
position: float,
*,
tool_port_names: Sequence[str] = ('A', 'B'),
**kwargs,
) -> D:
if self._dead:
logger.error('Skipping path_to() since device is dead')
return self
port = self.ports[portspec]
x, y = port.offset
if port.rotation is None:
raise DeviceError(f'Port {portspec} has no rotation and cannot be used for path_to()')
if not numpy.isclose(port.rotation % (pi / 2), 0):
raise DeviceError('path_to was asked to route from non-manhattan port')
is_horizontal = numpy.isclose(port.rotation % pi, 0)
if is_horizontal:
if numpy.sign(numpy.cos(port.rotation)) == numpy.sign(position - x):
raise DeviceError(f'path_to routing to behind source port: x={x:g} to {position:g}')
length = numpy.abs(position - x)
else:
if numpy.sign(numpy.sin(port.rotation)) == numpy.sign(position - y):
raise DeviceError(f'path_to routing to behind source port: y={y:g} to {position:g}')
length = numpy.abs(position - y)
return self.path(portspec, ccw, length, tool_port_names=tool_port_names, **kwargs)
def busL(
self: D,
portspec: Union[str, Sequence[str]],
ccw: Optional[bool],
*,
spacing: Optional[Union[float, ArrayLike]] = None,
set_rotation: Optional[float] = None,
tool_port_names: Sequence[str] = ('A', 'B'),
container_name: str = '_busL',
force_container: bool = False,
**kwargs,
) -> D:
if self._dead:
logger.error('Skipping busL() since device is dead')
return self
bound_types = set()
if 'bound_type' in kwargs:
bound_types.add(kwargs['bound_type'])
bound = kwargs['bound']
for bt in ('emin', 'emax', 'pmin', 'pmax', 'min_past_furthest'):
if bt in kwargs:
bound_types.add(bt)
bound = kwargs[bt]
if not bound_types:
raise DeviceError('No bound type specified for busL')
elif len(bound_types) > 1:
raise DeviceError(f'Too many bound types specified for busL: {bound_types}')
bound_type = tuple(bound_types)[0]
if isinstance(portspec, str):
portspec = [portspec]
ports = self[tuple(portspec)]
extensions = ell(ports, ccw, spacing=spacing, bound=bound, bound_type=bound_type, set_rotation=set_rotation)
if len(ports) == 1 and not force_container:
# Not a bus, so having a container just adds noise to the layout
port_name = tuple(portspec)[0]
return self.path(port_name, ccw, extensions[port_name], tool_port_names=tool_port_names)
else:
dev = Device(name='', ports=ports, tools=self.tools).as_interface(container_name)
for name, length in extensions.items():
dev.path(name, ccw, length, tool_port_names=tool_port_names)
return self.plug(dev, {sp: 'in_' + sp for sp in ports.keys()}) # TODO safe to use 'in_'?
# TODO def path_join() and def bus_join()?
def rotate_offsets_around( def rotate_offsets_around(
offsets: NDArray[numpy.float64], offsets: NDArray[numpy.float64],

22
masque/builder/tools.py Normal file
View File

@ -0,0 +1,22 @@
"""
Tools are objects which dynamically generate simple single-use devices (e.g. wires or waveguides)
"""
from typing import TYPE_CHECKING, Optional, Sequence
if TYPE_CHECKING:
from .devices import Device
class Tool:
def path(
self,
ccw: Optional[bool],
length: float,
*,
in_ptype: Optional[str] = None,
out_ptype: Optional[str] = None,
port_names: Sequence[str] = ('A', 'B'),
**kwargs,
) -> 'Device':
raise NotImplementedError(f'path() not implemented for {type(self)}')