Move plug/place/interface to Pattern
Since Pattern has ports already, these should live in Pattern and get wrapped elsewhere. Builder becomes a context-holder (holding .library and .dead) and some code duplication goes away.
This commit is contained in:
		
							parent
							
								
									4bca0e2638
								
							
						
					
					
						commit
						d02ea400a0
					
				| @ -18,39 +18,44 @@ logger = logging.getLogger(__name__) | ||||
| 
 | ||||
| class Builder(PortList): | ||||
|     """ | ||||
|     TODO DOCUMENT Builder | ||||
|     A `Device` is a combination of a `Pattern` with a set of named `Port`s | ||||
|       which can be used to "snap" devices together to make complex layouts. | ||||
|       A `Builder` is a helper object used for snapping together multiple | ||||
|     lower-level patterns at their `Port`s. | ||||
| 
 | ||||
|     `Device`s can be as simple as one or two ports (e.g. an electrical pad | ||||
|     or wire), but can also be used to build and represent a large routed | ||||
|     layout (e.g. a logical block with multiple I/O connections or even a | ||||
|     full chip). | ||||
|       The `Builder` mostly just holds context, in the form of a `Library`, | ||||
|     in addition to its underlying pattern. This simplifies some calls | ||||
|     to `plug` and `place`, by making the library implicit. | ||||
| 
 | ||||
|     For convenience, ports can be read out using square brackets: | ||||
|     - `device['A'] == Port((0, 0), 0)` | ||||
|     - `device[['A', 'B']] == {'A': Port((0, 0), 0), 'B': Port((0, 0), pi)}` | ||||
|     `Builder` can also be `set_dead()`, at which point further calls to `plug()` | ||||
|     and `place()` are ignored (intended for debugging). | ||||
| 
 | ||||
|     Examples: Creating a Device | ||||
| 
 | ||||
|     Examples: Creating a Builder | ||||
|     =========================== | ||||
|     - `Device(pattern, ports={'A': port_a, 'C': port_c})` uses an existing | ||||
|         pattern and defines some ports. | ||||
|     - `Builder(library, ports={'A': port_a, 'C': port_c}, name='mypat')` makes | ||||
|         an empty pattern, adds the given ports, and places it into `library` | ||||
|         under the name `'mypat'`. | ||||
| 
 | ||||
|     - `Device(ports=None)` makes a new empty pattern with | ||||
|         default ports ('A' and 'B', in opposite directions, at (0, 0)). | ||||
|     - `Builder(library)` makes an empty pattern with no ports. The pattern | ||||
|         is not added into `library` and must later be added with e.g. | ||||
|         `library['mypat'] = builder.pattern` | ||||
| 
 | ||||
|     - `my_device.build('my_layout')` makes a new pattern and instantiates | ||||
|         `my_device` in it with offset (0, 0) as a base for further building. | ||||
|     - `Builder(library, pattern=pattern, name='mypat')` uses an existing | ||||
|         pattern (including its ports) and sets `library['mypat'] = pattern`. | ||||
| 
 | ||||
|     - `my_device.as_interface('my_component', port_map=['A', 'B'])` makes a new | ||||
|         (empty) pattern, copies over ports 'A' and 'B' from `my_device`, and | ||||
|         creates additional ports 'in_A' and 'in_B' facing in the opposite | ||||
|         directions. This can be used to build a device which can plug into | ||||
|         `my_device` (using the 'in_*' ports) but which does not itself include | ||||
|         `my_device` as a subcomponent. | ||||
|     - `Builder.interface(other_pat, port_map=['A', 'B'], library=library)` | ||||
|         makes a new (empty) pattern, copies over ports 'A' and 'B' from | ||||
|         `other_pat`, and creates additional ports 'in_A' and 'in_B' facing | ||||
|         in the opposite directions. This can be used to build a device which | ||||
|         can plug into `other_pat` (using the 'in_*' ports) but which does not | ||||
|         itself include `other_pat` as a subcomponent. | ||||
| 
 | ||||
|     Examples: Adding to a Device | ||||
|     ============================ | ||||
|     - `Builder.interface(other_builder, ...)` does the same thing as | ||||
|         `Builder.interface(other_builder.pattern, ...)` but also uses | ||||
|         `other_builder.library` as its library by default. | ||||
| 
 | ||||
| 
 | ||||
|     Examples: Adding to a pattern | ||||
|     ============================= | ||||
|     - `my_device.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})` | ||||
|         instantiates `subdevice` into `my_device`, plugging ports 'A' and 'B' | ||||
|         of `my_device` into ports 'C' and 'B' of `subdevice`. The connected ports | ||||
| @ -75,10 +80,9 @@ class Builder(PortList): | ||||
|     pattern: Pattern | ||||
|     """ Layout of this device """ | ||||
| 
 | ||||
|     library: ILibrary | None | ||||
|     library: ILibrary | ||||
|     """ | ||||
|     Library from which existing patterns should be referenced, and to which | ||||
|     new ones should be added | ||||
|     Library from which patterns should be referenced | ||||
|     """ | ||||
| 
 | ||||
|     _dead: bool | ||||
| @ -94,7 +98,7 @@ class Builder(PortList): | ||||
| 
 | ||||
|     def __init__( | ||||
|             self, | ||||
|             library: ILibrary | None = None, | ||||
|             library: ILibrary, | ||||
|             *, | ||||
|             pattern: Pattern | None = None, | ||||
|             ports: str | Mapping[str, Port] | None = None, | ||||
| @ -114,15 +118,11 @@ class Builder(PortList): | ||||
|             if self.pattern.ports: | ||||
|                 raise BuildError('Ports supplied for pattern with pre-existing ports!') | ||||
|             if isinstance(ports, str): | ||||
|                 if library is None: | ||||
|                     raise BuildError('Ports given as a string, but `library` was `None`!') | ||||
|                 ports = library.abstract(ports).ports | ||||
| 
 | ||||
|             self.pattern.ports.update(copy.deepcopy(dict(ports))) | ||||
| 
 | ||||
|         if name is not None: | ||||
|             if library is None: | ||||
|                 raise BuildError('Name was supplied, but no library was given!') | ||||
|             library[name] = self.pattern | ||||
| 
 | ||||
|     @classmethod | ||||
| @ -137,31 +137,15 @@ class Builder(PortList): | ||||
|             name: str | None = None, | ||||
|             ) -> 'Builder': | ||||
|         """ | ||||
|         Begin building a new device based on all or some of the ports in the | ||||
|           source device. Do not include the source device; instead use it | ||||
|           to define ports (the "interface") for the new device. | ||||
| 
 | ||||
|         The ports specified by `port_map` (default: all ports) are copied to | ||||
|           new device, and additional (input) ports are created facing in the | ||||
|           opposite directions. The specified `in_prefix` and `out_prefix` are | ||||
|           prepended to the port names to differentiate them. | ||||
| 
 | ||||
|         By default, the flipped ports are given an 'in_' prefix and unflipped | ||||
|           ports keep their original names, enabling intuitive construction of | ||||
|           a device that will "plug into" the current device; the 'in_*' ports | ||||
|           are used for plugging the devices together while the original port | ||||
|           names are used for building the new device. | ||||
| 
 | ||||
|         Another use-case could be to build the new device using the 'in_' | ||||
|           ports, creating a new device which could be used in place of the | ||||
|           current device. | ||||
|         Wrapper for `Pattern.interface()`, which returns a Builder instead. | ||||
| 
 | ||||
|         Args: | ||||
|             source: A collection of ports (e.g. Pattern, Builder, or dict) | ||||
|                 from which to create the interface. | ||||
|             library: Library from which existing patterns should be referenced,     TODO | ||||
|                 and to which new ones should be added. If not provided, | ||||
|                 the source's library will be used (if available). | ||||
|                 from which to create the interface. May be a pattern name if | ||||
|                 `library` is provided. | ||||
|             library: Library from which existing patterns should be referenced, | ||||
|                 and to which the new one should be added (if named). If not provided, | ||||
|                 `source.library` must exist and will be used. | ||||
|             in_prefix: Prepended to port names for newly-created ports with | ||||
|                 reversed directions compared to the current device. | ||||
|             out_prefix: Prepended to port names for ports which are directly | ||||
| @ -185,72 +169,16 @@ class Builder(PortList): | ||||
|         if library is None: | ||||
|             if hasattr(source, 'library') and isinstance(source.library, ILibrary): | ||||
|                 library = source.library | ||||
|             else: | ||||
|                 raise BuildError('No library was given, and `source.library` does not have one either.') | ||||
| 
 | ||||
|         if isinstance(source, str): | ||||
|             if library is None: | ||||
|                 raise BuildError('Source given as a string, but `library` was `None`!') | ||||
|             orig_ports = library.abstract(source).ports | ||||
|         elif isinstance(source, PortList): | ||||
|             orig_ports = source.ports | ||||
|         elif isinstance(source, dict): | ||||
|             orig_ports = source | ||||
|         else: | ||||
|             raise BuildError(f'Unable to get ports from {type(source)}: {source}') | ||||
|             source = library.abstract(source).ports | ||||
| 
 | ||||
|         if port_map: | ||||
|             if isinstance(port_map, dict): | ||||
|                 missing_inkeys = set(port_map.keys()) - set(orig_ports.keys()) | ||||
|                 mapped_ports = {port_map[k]: v for k, v in orig_ports.items() if k in port_map} | ||||
|             else: | ||||
|                 port_set = set(port_map) | ||||
|                 missing_inkeys = port_set - set(orig_ports.keys()) | ||||
|                 mapped_ports = {k: v for k, v in orig_ports.items() if k in port_set} | ||||
| 
 | ||||
|             if missing_inkeys: | ||||
|                 raise PortError(f'`port_map` keys not present in source: {missing_inkeys}') | ||||
|         else: | ||||
|             mapped_ports = orig_ports | ||||
| 
 | ||||
|         ports_in = {f'{in_prefix}{name}': port.deepcopy().rotate(pi) | ||||
|                     for name, port in mapped_ports.items()} | ||||
|         ports_out = {f'{out_prefix}{name}': port.deepcopy() | ||||
|                      for name, port in mapped_ports.items()} | ||||
| 
 | ||||
|         duplicates = set(ports_out.keys()) & set(ports_in.keys()) | ||||
|         if duplicates: | ||||
|             raise PortError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}') | ||||
| 
 | ||||
|         new = Builder(library=library, ports={**ports_in, **ports_out}, name=name) | ||||
|         pat = Pattern.interface(source, in_prefix=in_prefix, out_prefix=out_prefix, port_map=port_map) | ||||
|         new = Builder(library=library, pattern=pat, name=name) | ||||
|         return new | ||||
| 
 | ||||
| #    @overload | ||||
| #    def plug( | ||||
| #            self, | ||||
| #            other: Abstract | str, | ||||
| #            map_in: dict[str, str], | ||||
| #            map_out: dict[str, str | None] | None, | ||||
| #            *, | ||||
| #            mirrored: bool = False, | ||||
| #            inherit_name: bool, | ||||
| #            set_rotation: bool | None, | ||||
| #            append: bool, | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| # | ||||
| #    @overload | ||||
| #    def plug( | ||||
| #            self, | ||||
| #            other: Pattern, | ||||
| #            map_in: dict[str, str], | ||||
| #            map_out: dict[str, str | None] | None = None, | ||||
| #            *, | ||||
| #            mirrored: bool = False, | ||||
| #            inherit_name: bool = True, | ||||
| #            set_rotation: bool | None = None, | ||||
| #            append: bool = False, | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| 
 | ||||
|     def plug( | ||||
|             self, | ||||
|             other: Abstract | str | Pattern, | ||||
| @ -263,34 +191,18 @@ class Builder(PortList): | ||||
|             append: bool = False, | ||||
|             ) -> Self: | ||||
|         """ | ||||
|         Instantiate or append a pattern into the current device, connecting | ||||
|           the ports specified by `map_in` and renaming the unconnected | ||||
|           ports specified by `map_out`. | ||||
| 
 | ||||
|         Examples: | ||||
|         ========= | ||||
|         - `my_device.plug(lib, 'subdevice', {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})` | ||||
|             instantiates `lib['subdevice']` into `my_device`, plugging ports 'A' and 'B' | ||||
|             of `my_device` into ports 'C' and 'B' of `subdevice`. The connected ports | ||||
|             are removed and any unconnected ports from `subdevice` are added to | ||||
|             `my_device`. Port 'D' of `subdevice` (unconnected) is renamed to 'myport'. | ||||
| 
 | ||||
|         - `my_device.plug(lib, 'wire', {'myport': 'A'})` places port 'A' of `lib['wire']` | ||||
|             at 'myport' of `my_device`. | ||||
|             If `'wire'` has only two ports (e.g. 'A' and 'B'), no `map_out` argument is | ||||
|             provided, and the `inherit_name` argument is not explicitly set to `False`, | ||||
|             the unconnected port of `wire` is automatically renamed to 'myport'. This | ||||
|             allows easy extension of existing ports without changing their names or | ||||
|             having to provide `map_out` each time `plug` is called. | ||||
|         Wrapper around `Pattern.plug` which allows a string for `other`. | ||||
|         The `Builder`'s library is used to dereference the string (or `Abstract`, if | ||||
|         one is passed with `append=True`). | ||||
| 
 | ||||
|         Args: | ||||
|             other: An `Abstract` describing the device to be instatiated. | ||||
|             other: An `Abstract`, string, or `Pattern` describing the device to be instatiated. | ||||
|             map_in: dict of `{'self_port': 'other_port'}` mappings, specifying | ||||
|                 port connections between the two devices. | ||||
|             map_out: dict of `{'old_name': 'new_name'}` mappings, specifying | ||||
|                 new names for ports in `other`. | ||||
|             mirrored: Enables mirroring `other` across the x or y axes prior | ||||
|                 to connecting any ports. | ||||
|             mirrored: Enables mirroring `other` across the x axis prior to | ||||
|                 connecting any ports. | ||||
|             inherit_name: If `True`, and `map_in` specifies only a single port, | ||||
|                 and `map_out` is `None`, and `other` has only two ports total, | ||||
|                 then automatically renames the output port of `other` to the | ||||
| @ -303,6 +215,9 @@ class Builder(PortList): | ||||
|                 port with `rotation=None`), `set_rotation` must be provided | ||||
|                 to indicate how much `other` should be rotated. Otherwise, | ||||
|                 `set_rotation` must remain `None`. | ||||
|             append: If `True`, `other` is appended instead of being referenced. | ||||
|                 Note that this does not flatten  `other`, so its refs will still | ||||
|                 be refs (now inside `self`). | ||||
| 
 | ||||
|         Returns: | ||||
|             self | ||||
| @ -320,72 +235,21 @@ class Builder(PortList): | ||||
|             return self | ||||
| 
 | ||||
|         if isinstance(other, str): | ||||
|             if self.library is None: | ||||
|                 raise BuildError('No library available, but `other` was a string!') | ||||
|             other = self.library.abstract(other) | ||||
|         if append and isinstance(other, Abstract): | ||||
|             other = self.library[other.name] | ||||
| 
 | ||||
|         # If asked to inherit a name, check that all conditions are met | ||||
|         if (inherit_name | ||||
|                 and not map_out | ||||
|                 and len(map_in) == 1 | ||||
|                 and len(other.ports) == 2): | ||||
|             out_port_name = next(iter(set(other.ports.keys()) - set(map_in.values()))) | ||||
|             map_out = {out_port_name: next(iter(map_in.keys()))} | ||||
| 
 | ||||
|         if map_out is None: | ||||
|             map_out = {} | ||||
|         map_out = copy.deepcopy(map_out) | ||||
| 
 | ||||
|         self.check_ports(other.ports.keys(), map_in, map_out) | ||||
|         translation, rotation, pivot = self.find_transform( | ||||
|             other, | ||||
|             map_in, | ||||
|         self.pattern.plug( | ||||
|             other=other, | ||||
|             map_in=map_in, | ||||
|             map_out=map_out, | ||||
|             mirrored=mirrored, | ||||
|             inherit_name=inherit_name, | ||||
|             set_rotation=set_rotation, | ||||
|             append=append, | ||||
|             ) | ||||
| 
 | ||||
|         # get rid of plugged ports | ||||
|         for ki, vi in map_in.items(): | ||||
|             del self.ports[ki] | ||||
|             map_out[vi] = None | ||||
| 
 | ||||
|         if isinstance(other, Pattern): | ||||
|             assert append | ||||
| 
 | ||||
|         self.place(other, offset=translation, rotation=rotation, pivot=pivot, | ||||
|                    mirrored=mirrored, port_map=map_out, skip_port_check=True, append=append) | ||||
|         return self | ||||
| 
 | ||||
| #    @overload | ||||
| #    def place( | ||||
| #            self, | ||||
| #            other: Abstract | str, | ||||
| #            *, | ||||
| #            offset: ArrayLike, | ||||
| #            rotation: float, | ||||
| #            pivot: ArrayLike, | ||||
| #            mirrored: bool = False, | ||||
| #            port_map: dict[str, str | None] | None, | ||||
| #            skip_port_check: bool, | ||||
| #            append: bool, | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| # | ||||
| #    @overload | ||||
| #    def place( | ||||
| #            self, | ||||
| #            other: Pattern, | ||||
| #            *, | ||||
| #            offset: ArrayLike, | ||||
| #            rotation: float, | ||||
| #            pivot: ArrayLike, | ||||
| #            mirrored: bool = False, | ||||
| #            port_map: dict[str, str | None] | None, | ||||
| #            skip_port_check: bool, | ||||
| #            append: Literal[True], | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| 
 | ||||
|     def place( | ||||
|             self, | ||||
|             other: Abstract | str | Pattern, | ||||
| @ -440,52 +304,20 @@ class Builder(PortList): | ||||
|             return self | ||||
| 
 | ||||
|         if isinstance(other, str): | ||||
|             if self.library is None: | ||||
|                 raise BuildError('No library available, but `other` was a string!') | ||||
|             other = self.library.abstract(other) | ||||
|         if append and isinstance(other, Abstract): | ||||
|             other = self.library[other.name] | ||||
| 
 | ||||
|         if port_map is None: | ||||
|             port_map = {} | ||||
| 
 | ||||
|         if not skip_port_check: | ||||
|             self.check_ports(other.ports.keys(), map_in=None, map_out=port_map) | ||||
| 
 | ||||
|         ports = {} | ||||
|         for name, port in other.ports.items(): | ||||
|             new_name = port_map.get(name, name) | ||||
|             if new_name is None: | ||||
|                 continue | ||||
|             ports[new_name] = port | ||||
| 
 | ||||
|         for name, port in ports.items(): | ||||
|             p = port.deepcopy() | ||||
|             if mirrored: | ||||
|                 p.mirror() | ||||
|             p.rotate_around(pivot, rotation) | ||||
|             p.translate(offset) | ||||
|             self.ports[name] = p | ||||
| 
 | ||||
|         if append: | ||||
|             if isinstance(other, Pattern): | ||||
|                 other_pat = other | ||||
|             elif isinstance(other, Abstract): | ||||
|                 assert self.library is not None | ||||
|                 other_pat = self.library[other.name] | ||||
|             else: | ||||
|                 other_pat = self.library[name] | ||||
|             other_copy = other_pat.deepcopy() | ||||
|             other_copy.ports.clear() | ||||
|             if mirrored: | ||||
|                 other_copy.mirror() | ||||
|             other_copy.rotate_around(pivot, rotation) | ||||
|             other_copy.translate_elements(offset) | ||||
|             self.pattern.append(other_copy) | ||||
|         else: | ||||
|             assert not isinstance(other, Pattern) | ||||
|             ref = Ref(mirrored=mirrored) | ||||
|             ref.rotate_around(pivot, rotation) | ||||
|             ref.translate(offset) | ||||
|             self.pattern.refs[other.name].append(ref) | ||||
|         self.pattern.place( | ||||
|             other=other, | ||||
|             offset=offset, | ||||
|             rotation=rotation, | ||||
|             pivot=pivot, | ||||
|             mirrored=mirrored, | ||||
|             port_map=port_map, | ||||
|             skip_port_check=skip_port_check, | ||||
|             append=append, | ||||
|             ) | ||||
|         return self | ||||
| 
 | ||||
|     def translate(self, offset: ArrayLike) -> Self: | ||||
|  | ||||
| @ -149,14 +149,19 @@ class Pather(Builder): | ||||
|             cls, | ||||
|             builder: Builder, | ||||
|             *, | ||||
|             library: ILibrary | None = None, | ||||
|             tools: Tool | MutableMapping[str | None, Tool] | None = None, | ||||
|             ) -> 'Pather': | ||||
|         """TODO from_builder docs""" | ||||
|         library = library if library is not None else builder.library | ||||
|         if library is None: | ||||
|             raise BuildError('No library available for Pather!') | ||||
|         new = Pather(library=library, tools=tools, pattern=builder.pattern) | ||||
|         """ | ||||
|         Construct a `Pather` by adding tools to a `Builder`. | ||||
| 
 | ||||
|         Args: | ||||
|             builder: Builder to turn into a Pather | ||||
|             tools: Tools for the `Pather` | ||||
| 
 | ||||
|         Returns: | ||||
|             A new Pather object, using `builder.library` and `builder.pattern`. | ||||
|         """ | ||||
|         new = Pather(library=builder.library, tools=tools, pattern=builder.pattern) | ||||
|         return new | ||||
| 
 | ||||
|     @classmethod | ||||
| @ -183,17 +188,11 @@ class Pather(Builder): | ||||
|         if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict): | ||||
|             tools = source.tools | ||||
| 
 | ||||
|         new = Pather.from_builder( | ||||
|             Builder.interface( | ||||
|                 source=source, | ||||
|                 library=library, | ||||
|                 in_prefix=in_prefix, | ||||
|                 out_prefix=out_prefix, | ||||
|                 port_map=port_map, | ||||
|                 name=name, | ||||
|                 ), | ||||
|             tools=tools, | ||||
|             ) | ||||
|         if isinstance(source, str): | ||||
|             source = library.abstract(source).ports | ||||
| 
 | ||||
|         pat = Pattern.interface(source, in_prefix=in_prefix, out_prefix=out_prefix, port_map=port_map) | ||||
|         new = Pather(library=library, pattern=pat, name=name, tools=tools) | ||||
|         return new | ||||
| 
 | ||||
|     def __repr__(self) -> str: | ||||
|  | ||||
| @ -9,7 +9,7 @@ from numpy.typing import ArrayLike | ||||
| 
 | ||||
| from ..pattern import Pattern | ||||
| from ..ref import Ref | ||||
| from ..library import ILibrary | ||||
| from ..library import ILibrary, Library | ||||
| from ..error import PortError, BuildError | ||||
| from ..ports import PortList, Port | ||||
| from ..abstract import Abstract | ||||
| @ -28,7 +28,7 @@ class RenderPather(PortList): | ||||
|     pattern: Pattern | ||||
|     """ Layout of this device """ | ||||
| 
 | ||||
|     library: ILibrary | None | ||||
|     library: ILibrary | ||||
|     """ Library from which patterns should be referenced """ | ||||
| 
 | ||||
|     _dead: bool | ||||
| @ -52,7 +52,7 @@ class RenderPather(PortList): | ||||
| 
 | ||||
|     def __init__( | ||||
|             self, | ||||
|             library: ILibrary | None = None, | ||||
|             library: ILibrary, | ||||
|             *, | ||||
|             pattern: Pattern | None = None, | ||||
|             ports: str | Mapping[str, Port] | None = None, | ||||
| @ -99,6 +99,7 @@ class RenderPather(PortList): | ||||
|             source: PortList | Mapping[str, Port] | str, | ||||
|             *, | ||||
|             library: ILibrary | None = None, | ||||
|             tools: Tool | MutableMapping[str | None, Tool] | None = None, | ||||
|             in_prefix: str = 'in_', | ||||
|             out_prefix: str = '', | ||||
|             port_map: dict[str, str] | Sequence[str] | None = None, | ||||
| @ -154,42 +155,17 @@ class RenderPather(PortList): | ||||
|         if library is None: | ||||
|             if hasattr(source, 'library') and isinstance(source.library, ILibrary): | ||||
|                 library = source.library | ||||
|             else: | ||||
|                 raise BuildError('No library provided (and not present in `source.library`') | ||||
| 
 | ||||
|         if tools is None and hasattr(source, 'tools') and isinstance(source.tools, dict): | ||||
|             tools = source.tools | ||||
| 
 | ||||
|         if isinstance(source, str): | ||||
|             if library is None: | ||||
|                 raise BuildError('Source given as a string, but `library` was `None`!') | ||||
|             orig_ports = library.abstract(source).ports | ||||
|         elif isinstance(source, PortList): | ||||
|             orig_ports = source.ports | ||||
|         elif isinstance(source, dict): | ||||
|             orig_ports = source | ||||
|         else: | ||||
|             raise BuildError(f'Unable to get ports from {type(source)}: {source}') | ||||
|             source = library.abstract(source).ports | ||||
| 
 | ||||
|         if port_map: | ||||
|             if isinstance(port_map, dict): | ||||
|                 missing_inkeys = set(port_map.keys()) - set(orig_ports.keys()) | ||||
|                 mapped_ports = {port_map[k]: v for k, v in orig_ports.items() if k in port_map} | ||||
|             else: | ||||
|                 port_set = set(port_map) | ||||
|                 missing_inkeys = port_set - set(orig_ports.keys()) | ||||
|                 mapped_ports = {k: v for k, v in orig_ports.items() if k in port_set} | ||||
| 
 | ||||
|             if missing_inkeys: | ||||
|                 raise PortError(f'`port_map` keys not present in source: {missing_inkeys}') | ||||
|         else: | ||||
|             mapped_ports = orig_ports | ||||
| 
 | ||||
|         ports_in = {f'{in_prefix}{pname}': port.deepcopy().rotate(pi) | ||||
|                     for pname, port in mapped_ports.items()} | ||||
|         ports_out = {f'{out_prefix}{pname}': port.deepcopy() | ||||
|                      for pname, port in mapped_ports.items()} | ||||
| 
 | ||||
|         duplicates = set(ports_out.keys()) & set(ports_in.keys()) | ||||
|         if duplicates: | ||||
|             raise PortError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}') | ||||
| 
 | ||||
|         new = RenderPather(library=library, ports={**ports_in, **ports_out}, name=name) | ||||
|         pat = Pattern.interface(source, in_prefix=in_prefix, out_prefix=out_prefix, port_map=port_map) | ||||
|         new = RenderPather(library=library, pattern=pat, name=name, tools=tools) | ||||
|         return new | ||||
| 
 | ||||
|     def plug( | ||||
| @ -201,44 +177,41 @@ class RenderPather(PortList): | ||||
|             mirrored: bool = False, | ||||
|             inherit_name: bool = True, | ||||
|             set_rotation: bool | None = None, | ||||
|             append: bool = False, | ||||
|             ) -> Self: | ||||
|         if self._dead: | ||||
|             logger.error('Skipping plug() since device is dead') | ||||
|             return self | ||||
| 
 | ||||
|         other_tgt: Pattern | Abstract | ||||
|         if isinstance(other, str): | ||||
|             if self.library is None: | ||||
|                 raise BuildError('No library available, but `other` was a string!') | ||||
|             other = self.library.abstract(other) | ||||
| 
 | ||||
|         # If asked to inherit a name, check that all conditions are met | ||||
|         if (inherit_name | ||||
|                 and not map_out | ||||
|                 and len(map_in) == 1 | ||||
|                 and len(other.ports) == 2): | ||||
|             out_port_name = next(iter(set(other.ports.keys()) - set(map_in.values()))) | ||||
|             map_out = {out_port_name: next(iter(map_in.keys()))} | ||||
| 
 | ||||
|         if map_out is None: | ||||
|             map_out = {} | ||||
|         map_out = copy.deepcopy(map_out) | ||||
| 
 | ||||
|         self.check_ports(other.ports.keys(), map_in, map_out) | ||||
|         translation, rotation, pivot = self.find_transform( | ||||
|             other, | ||||
|             map_in, | ||||
|             mirrored=mirrored, | ||||
|             set_rotation=set_rotation, | ||||
|             ) | ||||
|             other_tgt = self.library.abstract(other) | ||||
|         if append and isinstance(other, Abstract): | ||||
|             other_tgt = self.library[other.name] | ||||
| 
 | ||||
|         # get rid of plugged ports | ||||
|         for ki, vi in map_in.items(): | ||||
|             if ki in self.paths: | ||||
|                 self.paths[ki].append(RenderStep('P', None, self.ports[ki].copy(), self.ports[ki].copy(), None)) | ||||
|             del self.ports[ki] | ||||
|             map_out[vi] = None | ||||
|         self.place(other, offset=translation, rotation=rotation, pivot=pivot, | ||||
|                    mirrored=mirrored, port_map=map_out, skip_port_check=True) | ||||
|         for kk in map_in.keys(): | ||||
|             if kk in self.paths: | ||||
|                 self.paths[kk].append(RenderStep('P', None, self.ports[kk].copy(), self.ports[kk].copy(), None)) | ||||
| 
 | ||||
|         plugged = map_in.values() | ||||
|         for name, port in other_tgt.ports.items(): | ||||
|             if name in plugged: | ||||
|                 continue | ||||
|             new_name = map_out.get(name, name) if map_out is not None else name | ||||
|             if new_name is not None and new_name in self.paths: | ||||
|                 self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None)) | ||||
| 
 | ||||
|         self.pattern.plug( | ||||
|             other=other_tgt, | ||||
|             map_in=map_in, | ||||
|             map_out=map_out, | ||||
|             mirrored=mirrored, | ||||
|             inherit_name=inherit_name, | ||||
|             set_rotation=set_rotation, | ||||
|             append=append, | ||||
|             ) | ||||
| 
 | ||||
|         return self | ||||
| 
 | ||||
|     def place( | ||||
| @ -251,43 +224,34 @@ class RenderPather(PortList): | ||||
|             mirrored: bool = False, | ||||
|             port_map: dict[str, str | None] | None = None, | ||||
|             skip_port_check: bool = False, | ||||
|             append: bool = False, | ||||
|             ) -> Self: | ||||
|         if self._dead: | ||||
|             logger.error('Skipping place() since device is dead') | ||||
|             return self | ||||
| 
 | ||||
|         other_tgt: Pattern | Abstract | ||||
|         if isinstance(other, str): | ||||
|             if self.library is None: | ||||
|                 raise BuildError('No library available, but `other` was a string!') | ||||
|             other = self.library.abstract(other) | ||||
|             other_tgt = self.library.abstract(other) | ||||
|         if append and isinstance(other, Abstract): | ||||
|             other_tgt = self.library[other.name] | ||||
| 
 | ||||
|         if port_map is None: | ||||
|             port_map = {} | ||||
| 
 | ||||
|         if not skip_port_check: | ||||
|             self.check_ports(other.ports.keys(), map_in=None, map_out=port_map) | ||||
| 
 | ||||
|         ports = {} | ||||
|         for name, port in other.ports.items(): | ||||
|             new_name = port_map.get(name, name) | ||||
|             if new_name is None: | ||||
|                 continue | ||||
|             ports[new_name] = port | ||||
|             if new_name in self.paths: | ||||
|         for name, port in other_tgt.ports.items(): | ||||
|             new_name = port_map.get(name, name) if port_map is not None else name | ||||
|             if new_name is not None and new_name in self.paths: | ||||
|                 self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None)) | ||||
| 
 | ||||
|         for name, port in ports.items(): | ||||
|             p = port.deepcopy() | ||||
|             if mirrored: | ||||
|                 p.mirror() | ||||
|             p.rotate_around(pivot, rotation) | ||||
|             p.translate(offset) | ||||
|             self.ports[name] = p | ||||
|         self.pattern.place( | ||||
|             other=other_tgt, | ||||
|             offset=offset, | ||||
|             rotation=rotation, | ||||
|             pivot=pivot, | ||||
|             mirrored=mirrored, | ||||
|             port_map=port_map, | ||||
|             skip_port_check=skip_port_check, | ||||
|             append=append, | ||||
|             ) | ||||
| 
 | ||||
|         ref = Ref(mirrored=mirrored) | ||||
|         ref.rotate_around(pivot, rotation) | ||||
|         ref.translate(offset) | ||||
|         self.pattern.refs[other.name].append(ref) | ||||
|         return self | ||||
| 
 | ||||
|     def retool( | ||||
| @ -409,22 +373,32 @@ class RenderPather(PortList): | ||||
| 
 | ||||
|     def render( | ||||
|             self, | ||||
|             lib: ILibrary | None = None, | ||||
|             append: bool = True, | ||||
|             ) -> Self: | ||||
|         lib = lib if lib is not None else self.library | ||||
|         assert lib is not None | ||||
|         """ | ||||
|         Generate the geometry which has been planned out with `path`/`path_to`/etc. | ||||
| 
 | ||||
|         Args: | ||||
|             append: If `True`, the rendered geometry will be directly appended to | ||||
|                 `self.pattern`. Note that it will not be flattened, so if only one | ||||
|                 layer of hierarchy is eliminated. | ||||
| 
 | ||||
|         Returns: | ||||
|             self | ||||
|         """ | ||||
|         lib = self.library | ||||
|         tool_port_names = ('A', 'B') | ||||
|         bb = Builder(lib) | ||||
|         pat = Pattern() | ||||
| 
 | ||||
|         def render_batch(lib: ILibrary, portspec: str, batch: list[RenderStep], append: bool) -> None: | ||||
|         def render_batch(portspec: str, batch: list[RenderStep], append: bool) -> None: | ||||
|             assert batch[0].tool is not None | ||||
|             name = lib << batch[0].tool.render(batch, port_names=tool_port_names) | ||||
|             bb.ports[portspec] = batch[0].start_port.copy() | ||||
|             bb.plug(name, {portspec: tool_port_names[0]}, append=append) | ||||
|             pat.ports[portspec] = batch[0].start_port.copy() | ||||
|             if append: | ||||
|                 del lib[name] | ||||
|                 pat.plug(lib[name], {portspec: tool_port_names[0]}, append=append) | ||||
|                 del lib[name]       # NOTE if the rendered pattern has refs, those are now in `pat` but not flattened | ||||
|             else: | ||||
|                 pat.plug(lib.abstract(name), {portspec: tool_port_names[0]}, append=append) | ||||
| 
 | ||||
|         for portspec, steps in self.paths.items(): | ||||
|             batch: list[RenderStep] = [] | ||||
| @ -434,7 +408,7 @@ class RenderPather(PortList): | ||||
| 
 | ||||
|                 # If we can't continue a batch, render it | ||||
|                 if batch and (not appendable_op or not same_tool): | ||||
|                     render_batch(lib, portspec, batch, append) | ||||
|                     render_batch(portspec, batch, append) | ||||
|                     batch = [] | ||||
| 
 | ||||
|                 # batch is emptied already if we couldn't continue it | ||||
| @ -442,16 +416,16 @@ class RenderPather(PortList): | ||||
|                     batch.append(step) | ||||
| 
 | ||||
|                 # Opcodes which break the batch go below this line | ||||
|                 if not appendable_op and portspec in bb.ports: | ||||
|                     del bb.ports[portspec] | ||||
|                 if not appendable_op and portspec in pat.ports: | ||||
|                     del pat.ports[portspec] | ||||
| 
 | ||||
|             #If the last batch didn't end yet | ||||
|             if batch: | ||||
|                 render_batch(lib, portspec, batch, append) | ||||
|                 render_batch(portspec, batch, append) | ||||
| 
 | ||||
|         self.paths.clear() | ||||
|         bb.ports.clear() | ||||
|         self.pattern.append(bb.pattern) | ||||
|         pat.ports.clear() | ||||
|         self.pattern.append(pat) | ||||
| 
 | ||||
|         return self | ||||
| 
 | ||||
|  | ||||
| @ -14,10 +14,11 @@ from numpy.typing import NDArray, ArrayLike | ||||
| # .visualize imports matplotlib and matplotlib.collections | ||||
| 
 | ||||
| from .ref import Ref | ||||
| from .abstract import Abstract | ||||
| from .shapes import Shape, Polygon, Path, DEFAULT_POLY_NUM_VERTICES | ||||
| from .label import Label | ||||
| from .utils import rotation_matrix_2d, annotations_t, layer_t | ||||
| from .error import PatternError | ||||
| from .error import PatternError, PortError | ||||
| from .traits import AnnotatableImpl, Scalable, Mirrorable, Rotatable, Positionable, Repeatable, Bounded | ||||
| from .ports import Port, PortList | ||||
| 
 | ||||
| @ -860,6 +861,346 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): | ||||
|             pyplot.ylabel('y') | ||||
|             pyplot.show() | ||||
| 
 | ||||
| #    @overload | ||||
| #    def place( | ||||
| #            self, | ||||
| #            other: Pattern, | ||||
| #            *, | ||||
| #            offset: ArrayLike, | ||||
| #            rotation: float, | ||||
| #            pivot: ArrayLike, | ||||
| #            mirrored: bool, | ||||
| #            port_map: dict[str, str | None] | None, | ||||
| #            skip_port_check: bool, | ||||
| #            append: bool, | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| # | ||||
| #    @overload | ||||
| #    def place( | ||||
| #            self, | ||||
| #            other: Abstract, | ||||
| #            *, | ||||
| #            offset: ArrayLike, | ||||
| #            rotation: float, | ||||
| #            pivot: ArrayLike, | ||||
| #            mirrored: bool, | ||||
| #            port_map: dict[str, str | None] | None, | ||||
| #            skip_port_check: bool, | ||||
| #            append: Literal[False], | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| 
 | ||||
|     def place( | ||||
|             self, | ||||
|             other: Abstract | Pattern, | ||||
|             *, | ||||
|             offset: ArrayLike = (0, 0), | ||||
|             rotation: float = 0, | ||||
|             pivot: ArrayLike = (0, 0), | ||||
|             mirrored: bool = False, | ||||
|             port_map: dict[str, str | None] | None = None, | ||||
|             skip_port_check: bool = False, | ||||
|             append: bool = False, | ||||
|             ) -> Self: | ||||
|         """ | ||||
|         Instantiate or append the pattern `other` into the current pattern, adding its | ||||
|           ports to those of the current pattern (but not connecting/removing any ports). | ||||
| 
 | ||||
|         Mirroring is applied before rotation; translation (`offset`) is applied last. | ||||
| 
 | ||||
|         Examples: | ||||
|         ========= | ||||
|         - `my_pat.place(pad_pat, offset=(10, 10), rotation=pi / 2, port_map={'A': 'gnd'})` | ||||
|             instantiates `pad` at the specified (x, y) offset and with the specified | ||||
|             rotation, adding its ports to those of `my_pat`. Port 'A' of `pad` is | ||||
|             renamed to 'gnd' so that further routing can use this signal or net name | ||||
|             rather than the port name on the original `pad_pat` pattern. | ||||
| 
 | ||||
|         Args: | ||||
|             other: An `Abstract` or `Pattern` describing the device to be instatiated. | ||||
|             offset: Offset at which to place the instance. Default (0, 0). | ||||
|             rotation: Rotation applied to the instance before placement. Default 0. | ||||
|             pivot: Rotation is applied around this pivot point (default (0, 0)). | ||||
|                 Rotation is applied prior to translation (`offset`). | ||||
|             mirrored: Whether theinstance should be mirrored across the x axis. | ||||
|                 Mirroring is applied before translation and rotation. | ||||
|             port_map: dict of `{'old_name': 'new_name'}` mappings, specifying | ||||
|                 new names for ports in the instantiated pattern. New names can be | ||||
|                 `None`, which will delete those ports. | ||||
|             skip_port_check: Can be used to skip the internal call to `check_ports`, | ||||
|                 in case it has already been performed elsewhere. | ||||
|             append: If `True`, `other` is appended instead of being referenced. | ||||
|                 Note that this does not flatten  `other`, so its refs will still | ||||
|                 be refs (now inside `self`). | ||||
| 
 | ||||
|         Returns: | ||||
|             self | ||||
| 
 | ||||
|         Raises: | ||||
|             `PortError` if any ports specified in `map_in` or `map_out` do not | ||||
|                 exist in `self.ports` or `other.ports`. | ||||
|             `PortError` if there are any duplicate names after `map_in` and `map_out` | ||||
|                 are applied. | ||||
|         """ | ||||
|         if port_map is None: | ||||
|             port_map = {} | ||||
| 
 | ||||
|         if not skip_port_check: | ||||
|             self.check_ports(other.ports.keys(), map_in=None, map_out=port_map) | ||||
| 
 | ||||
|         ports = {} | ||||
|         for name, port in other.ports.items(): | ||||
|             new_name = port_map.get(name, name) | ||||
|             if new_name is None: | ||||
|                 continue | ||||
|             ports[new_name] = port | ||||
| 
 | ||||
|         for name, port in ports.items(): | ||||
|             p = port.deepcopy() | ||||
|             if mirrored: | ||||
|                 p.mirror() | ||||
|             p.rotate_around(pivot, rotation) | ||||
|             p.translate(offset) | ||||
|             self.ports[name] = p | ||||
| 
 | ||||
|         if append: | ||||
|             if isinstance(other, Abstract): | ||||
|                 raise PatternError('Must provide a full `Pattern` (not an `Abstract`) when appending!') | ||||
|             other_copy = other.deepcopy() | ||||
|             other_copy.ports.clear() | ||||
|             if mirrored: | ||||
|                 other_copy.mirror() | ||||
|             other_copy.rotate_around(pivot, rotation) | ||||
|             other_copy.translate_elements(offset) | ||||
|             self.append(other_copy) | ||||
|         else: | ||||
|             assert not isinstance(other, Pattern) | ||||
|             ref = Ref(mirrored=mirrored) | ||||
|             ref.rotate_around(pivot, rotation) | ||||
|             ref.translate(offset) | ||||
|             self.refs[other.name].append(ref) | ||||
|         return self | ||||
| 
 | ||||
| #    @overload | ||||
| #    def plug( | ||||
| #            self, | ||||
| #            other: Abstract, | ||||
| #            map_in: dict[str, str], | ||||
| #            map_out: dict[str, str | None] | None, | ||||
| #            *, | ||||
| #            mirrored: bool, | ||||
| #            inherit_name: bool, | ||||
| #            set_rotation: bool | None, | ||||
| #            append: Literal[False], | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| # | ||||
| #    @overload | ||||
| #    def plug( | ||||
| #            self, | ||||
| #            other: Pattern, | ||||
| #            map_in: dict[str, str], | ||||
| #            map_out: dict[str, str | None] | None, | ||||
| #            *, | ||||
| #            mirrored: bool, | ||||
| #            inherit_name: bool, | ||||
| #            set_rotation: bool | None, | ||||
| #            append: bool, | ||||
| #            ) -> Self: | ||||
| #        pass | ||||
| 
 | ||||
|     def plug( | ||||
|             self, | ||||
|             other: Abstract | Pattern, | ||||
|             map_in: dict[str, str], | ||||
|             map_out: dict[str, str | None] | None = None, | ||||
|             *, | ||||
|             mirrored: bool = False, | ||||
|             inherit_name: bool = True, | ||||
|             set_rotation: bool | None = None, | ||||
|             append: bool = False, | ||||
|             ) -> Self: | ||||
|         """ | ||||
|         Instantiate or append a pattern into the current pattern, connecting | ||||
|           the ports specified by `map_in` and renaming the unconnected | ||||
|           ports specified by `map_out`. | ||||
| 
 | ||||
|         Examples: | ||||
|         ========= | ||||
|         - `my_pat.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})` | ||||
|             instantiates `subdevice` into `my_pat`, plugging ports 'A' and 'B' | ||||
|             of `my_pat` into ports 'C' and 'B' of `subdevice`. The connected ports | ||||
|             are removed and any unconnected ports from `subdevice` are added to | ||||
|             `my_pat`. Port 'D' of `subdevice` (unconnected) is renamed to 'myport'. | ||||
| 
 | ||||
|         - `my_pat.plug(wire, {'myport': 'A'})` places port 'A' of `wire` at 'myport' | ||||
|             of `my_pat`. | ||||
|             If `wire` has only two ports (e.g. 'A' and 'B'), no `map_out` argument is | ||||
|             provided, and the `inherit_name` argument is not explicitly set to `False`, | ||||
|             the unconnected port of `wire` is automatically renamed to 'myport'. This | ||||
|             allows easy extension of existing ports without changing their names or | ||||
|             having to provide `map_out` each time `plug` is called. | ||||
| 
 | ||||
|         Args: | ||||
|             other: A `Pattern` or `Abstract` describing the subdevice to be instatiated. | ||||
|             map_in: dict of `{'self_port': 'other_port'}` mappings, specifying | ||||
|                 port connections between the current pattern and the subdevice. | ||||
|             map_out: dict of `{'old_name': 'new_name'}` mappings, specifying | ||||
|                 new names for ports in `other`. | ||||
|             mirrored: Enables mirroring `other` across the x axis prior to connecting | ||||
|                 any ports. | ||||
|             inherit_name: If `True`, and `map_in` specifies only a single port, | ||||
|                 and `map_out` is `None`, and `other` has only two ports total, | ||||
|                 then automatically renames the output port of `other` to the | ||||
|                 name of the port from `self` that appears in `map_in`. This | ||||
|                 makes it easy to extend a pattern with simple 2-port devices | ||||
|                 (e.g. wires) without providing `map_out` each time `plug` is | ||||
|                 called. See "Examples" above for more info. Default `True`. | ||||
|             set_rotation: If the necessary rotation cannot be determined from | ||||
|                 the ports being connected (i.e. all pairs have at least one | ||||
|                 port with `rotation=None`), `set_rotation` must be provided | ||||
|                 to indicate how much `other` should be rotated. Otherwise, | ||||
|                 `set_rotation` must remain `None`. | ||||
|             append: If `True`, `other` is appended instead of being referenced. | ||||
|                 Note that this does not flatten  `other`, so its refs will still | ||||
|                 be refs (now inside `self`). | ||||
| 
 | ||||
|         Returns: | ||||
|             self | ||||
| 
 | ||||
|         Raises: | ||||
|             `PortError` if any ports specified in `map_in` or `map_out` do not | ||||
|                 exist in `self.ports` or `other_names`. | ||||
|             `PortError` if there are any duplicate names after `map_in` and `map_out` | ||||
|                 are applied. | ||||
|             `PortError` if the specified port mapping is not achieveable (the ports | ||||
|                 do not line up) | ||||
|         """ | ||||
|         # If asked to inherit a name, check that all conditions are met | ||||
|         if (inherit_name | ||||
|                 and not map_out | ||||
|                 and len(map_in) == 1 | ||||
|                 and len(other.ports) == 2): | ||||
|             out_port_name = next(iter(set(other.ports.keys()) - set(map_in.values()))) | ||||
|             map_out = {out_port_name: next(iter(map_in.keys()))} | ||||
| 
 | ||||
|         if map_out is None: | ||||
|             map_out = {} | ||||
|         map_out = copy.deepcopy(map_out) | ||||
| 
 | ||||
|         self.check_ports(other.ports.keys(), map_in, map_out) | ||||
|         translation, rotation, pivot = self.find_transform( | ||||
|             other, | ||||
|             map_in, | ||||
|             mirrored=mirrored, | ||||
|             set_rotation=set_rotation, | ||||
|             ) | ||||
| 
 | ||||
|         # get rid of plugged ports | ||||
|         for ki, vi in map_in.items(): | ||||
|             del self.ports[ki] | ||||
|             map_out[vi] = None | ||||
| 
 | ||||
|         if isinstance(other, Pattern): | ||||
|             assert append | ||||
| 
 | ||||
|         self.place( | ||||
|             other, | ||||
|             offset=translation, | ||||
|             rotation=rotation, | ||||
|             pivot=pivot, | ||||
|             mirrored=mirrored, | ||||
|             port_map=map_out, | ||||
|             skip_port_check=True, | ||||
|             append=append, | ||||
|             ) | ||||
|         return self | ||||
| 
 | ||||
|     @classmethod | ||||
|     def interface( | ||||
|             cls, | ||||
|             source: PortList | Mapping[str, Port], | ||||
|             *, | ||||
|             in_prefix: str = 'in_', | ||||
|             out_prefix: str = '', | ||||
|             port_map: dict[str, str] | Sequence[str] | None = None, | ||||
|             ) -> 'Pattern': | ||||
|         """ | ||||
|         Generate an empty pattern with ports based on all or some of the ports | ||||
|           in the `source`. Do not include the source device istelf; instead use | ||||
|           it to define ports (the "interface") for the new device. | ||||
| 
 | ||||
|         The ports specified by `port_map` (default: all ports) are copied to | ||||
|           new device, and additional (input) ports are created facing in the | ||||
|           opposite directions. The specified `in_prefix` and `out_prefix` are | ||||
|           prepended to the port names to differentiate them. | ||||
| 
 | ||||
|         By default, the flipped ports are given an 'in_' prefix and unflipped | ||||
|           ports keep their original names, enabling intuitive construction of | ||||
|           a device that will "plug into" the current device; the 'in_*' ports | ||||
|           are used for plugging the devices together while the original port | ||||
|           names are used for building the new device. | ||||
| 
 | ||||
|         Another use-case could be to build the new device using the 'in_' | ||||
|           ports, creating a new device which could be used in place of the | ||||
|           current device. | ||||
| 
 | ||||
|         Args: | ||||
|             source: A collection of ports (e.g. Pattern, Builder, or dict) | ||||
|                 from which to create the interface. | ||||
|             in_prefix: Prepended to port names for newly-created ports with | ||||
|                 reversed directions compared to the current device. | ||||
|             out_prefix: Prepended to port names for ports which are directly | ||||
|                 copied from the current device. | ||||
|             port_map: Specification for ports to copy into the new device: | ||||
|                 - If `None`, all ports are copied. | ||||
|                 - If a sequence, only the listed ports are copied | ||||
|                 - If a mapping, the listed ports (keys) are copied and | ||||
|                     renamed (to the values). | ||||
| 
 | ||||
|         Returns: | ||||
|             The new empty pattern, with 2x as many ports as listed in port_map. | ||||
| 
 | ||||
|         Raises: | ||||
|             `PortError` if `port_map` contains port names not present in the | ||||
|                 current device. | ||||
|             `PortError` if applying the prefixes results in duplicate port | ||||
|                 names. | ||||
|         """ | ||||
|         if isinstance(source, PortList): | ||||
|             orig_ports = source.ports | ||||
|         elif isinstance(source, dict): | ||||
|             orig_ports = source | ||||
|         else: | ||||
|             raise PatternError(f'Unable to get ports from {type(source)}: {source}') | ||||
| 
 | ||||
|         if port_map: | ||||
|             if isinstance(port_map, dict): | ||||
|                 missing_inkeys = set(port_map.keys()) - set(orig_ports.keys()) | ||||
|                 mapped_ports = {port_map[k]: v for k, v in orig_ports.items() if k in port_map} | ||||
|             else: | ||||
|                 port_set = set(port_map) | ||||
|                 missing_inkeys = port_set - set(orig_ports.keys()) | ||||
|                 mapped_ports = {k: v for k, v in orig_ports.items() if k in port_set} | ||||
| 
 | ||||
|             if missing_inkeys: | ||||
|                 raise PortError(f'`port_map` keys not present in source: {missing_inkeys}') | ||||
|         else: | ||||
|             mapped_ports = orig_ports | ||||
| 
 | ||||
|         ports_in = {f'{in_prefix}{name}': port.deepcopy().rotate(pi) | ||||
|                     for name, port in mapped_ports.items()} | ||||
|         ports_out = {f'{out_prefix}{name}': port.deepcopy() | ||||
|                      for name, port in mapped_ports.items()} | ||||
| 
 | ||||
|         duplicates = set(ports_out.keys()) & set(ports_in.keys()) | ||||
|         if duplicates: | ||||
|             raise PortError(f'Duplicate keys after prefixing, try a different prefix: {duplicates}') | ||||
| 
 | ||||
|         new = Pattern(ports={**ports_in, **ports_out}) | ||||
|         return new | ||||
| 
 | ||||
| 
 | ||||
| TT = TypeVar('TT') | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user