multiple bugfixes for ordering

ordering
jan 1 month ago
parent 1b07a50e4a
commit 70a9c2a8ef

@ -1,12 +1,13 @@
""" """
Helper functions for file reading and writing Helper functions for file reading and writing
""" """
from typing import IO, Iterator from typing import IO, Iterator, Mapping
import re import re
import pathlib import pathlib
import logging import logging
import tempfile import tempfile
import shutil import shutil
from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from pprint import pformat from pprint import pformat
from itertools import chain from itertools import chain
@ -24,6 +25,7 @@ def preflight(
allow_dangling_refs: bool | None = None, allow_dangling_refs: bool | None = None,
allow_named_layers: bool = True, allow_named_layers: bool = True,
prune_empty_patterns: bool = False, prune_empty_patterns: bool = False,
wrap_repeated_shapes: bool = False,
) -> Library: ) -> Library:
""" """
Run a standard set of useful operations and checks, usually done immediately prior Run a standard set of useful operations and checks, usually done immediately prior
@ -38,37 +40,45 @@ def preflight(
allow_named_layers: If `False`, raises a `PatternError` if any layer is referred to by allow_named_layers: If `False`, raises a `PatternError` if any layer is referred to by
a string instead of a number (or tuple). a string instead of a number (or tuple).
prune_empty_patterns: Runs `Library.prune_empty()`, recursively deleting any empty patterns. prune_empty_patterns: Runs `Library.prune_empty()`, recursively deleting any empty patterns.
wrap_repeated_shapes: Runs `Library.wrap_repeated_shapes()`, turning repeated shapes into
repeated refs containing non-repeated shapes.
Returns: Returns:
`lib` or an equivalent sorted library `lib` or an equivalent sorted library
""" """
if sort: if sort:
lib = Library(dict(sorted( lib = Library(dict(sorted(
(nn: pp.sort()) for nn, pp in lib.items() (nn, pp.sort()) for nn, pp in lib.items()
))) )))
if not allow_dangling_refs: if not allow_dangling_refs:
refs = lib.referenced_patterns() refs = lib.referenced_patterns()
dangling = refs - set(lib.keys()) dangling = refs - set(lib.keys())
msg = 'Dangling refs in found: ' + pformat(dangling) if dangling:
if allow_dangling_refs is None: msg = 'Dangling refs in found: ' + pformat(dangling)
logger.warning(msg) if allow_dangling_refs is None:
else: logger.warning(msg)
raise LibraryError(msg) else:
raise LibraryError(msg)
if not allow_named_layers: if not allow_named_layers:
named_layers = defaultdict(set) named_layers: Mapping[str, set] = defaultdict(set)
for name, pat in lib.items(): for name, pat in lib.items():
for layer in chain(pat.shapes.keys(), pat.labels.keys()): for layer in chain(pat.shapes.keys(), pat.labels.keys()):
if isinstance(layer, str): if isinstance(layer, str):
named_layers[name].add(layer) named_layers[name].add(layer)
raise PatternError('Non-numeric layers found:' + pformat(named_layers)) named_layers = dict(named_layers)
if named_layers:
raise PatternError('Non-numeric layers found:' + pformat(named_layers))
if prune_empty_patterns: if prune_empty_patterns:
pruned = lib.prune_empty() pruned = lib.prune_empty()
logger.info(f'Preflight pruned {len(pruned)} empty patterns') logger.info(f'Preflight pruned {len(pruned)} empty patterns')
logger.debug('Pruned: ' + pformat(pruned)) logger.debug('Pruned: ' + pformat(pruned))
if wrap_repeated_shapes:
lib.wrap_repeated_shapes()
return lib return lib

@ -247,6 +247,9 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
return False return False
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
if type(self) is not type(other):
return False
self_nonempty_targets = [target for target, reflist in self.refs.items() if reflist] self_nonempty_targets = [target for target, reflist in self.refs.items() if reflist]
other_nonempty_targets = [target for target, reflist in self.refs.items() if reflist] other_nonempty_targets = [target for target, reflist in self.refs.items() if reflist]
self_tgtkeys = tuple(sorted((target is None, target) for target in self_nonempty_targets)) self_tgtkeys = tuple(sorted((target is None, target) for target in self_nonempty_targets))
@ -305,16 +308,16 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
Returns: Returns:
self self
""" """
self.refs = dict(sorted( self.refs = defaultdict(list, sorted(
(tgt, sorted(rrs)) for tgt, rrs in self.refs.items() (tgt, sorted(rrs)) for tgt, rrs in self.refs.items()
)) ))
self.labels = dict(sorted( self.labels = defaultdict(list, sorted(
(layer, sorted(lls)) for layer, lls in self.labels.items(), ((layer, sorted(lls)) for layer, lls in self.labels.items()),
key=lambda kk, vv: layer2key(ll), key=lambda tt: layer2key(tt[0]),
)) ))
self.shapes = dict(sorted( self.shapes = defaultdict(list, sorted(
(layer, sorted(sss)) for layer, sss in self.shapes.items(), ((layer, sorted(sss)) for layer, sss in self.shapes.items()),
key=lambda kk, vv: layer2key(ll), key=lambda tt: layer2key(tt[0]),
)) ))
self.ports = dict(sorted(self.ports.items())) self.ports = dict(sorted(self.ports.items()))
@ -1217,7 +1220,7 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable):
ports specified by `map_out`. ports specified by `map_out`.
Examples: Examples:
========= ======list, ===
- `my_pat.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})` - `my_pat.plug(subdevice, {'A': 'C', 'B': 'B'}, map_out={'D': 'myport'})`
instantiates `subdevice` into `my_pat`, plugging ports 'A' and 'B' instantiates `subdevice` into `my_pat`, plugging ports 'A' and 'B'
of `my_pat` into ports 'C' and 'B' of `subdevice`. The connected ports of `my_pat` into ports 'C' and 'B' of `subdevice`. The connected ports

@ -135,7 +135,7 @@ class Port(PositionableImpl, Rotatable, PivotableImpl, Copyable, Mirrorable):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) == type(other) type(self) is type(other)
and self.ptype == other.ptype and self.ptype == other.ptype
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and self.rotation == other.rotation and self.rotation == other.rotation

@ -280,7 +280,7 @@ class Grid(Repetition):
return (f'<Grid {self.a_count}x{self.b_count} ({self.a_vector}{bv})>') return (f'<Grid {self.a_count}x{self.b_count} ({self.a_vector}{bv})>')
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
if type(other) != type(self): if type(other) is not type(self):
return False return False
if self.a_count != other.a_count or self.b_count != other.b_count: if self.a_count != other.a_count or self.b_count != other.b_count:
return False return False
@ -295,7 +295,7 @@ class Grid(Repetition):
return True return True
def __le__(self, other: Repetition) -> bool: def __le__(self, other: Repetition) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) return repr(type(self)) < repr(type(other))
other = cast(Grid, other) other = cast(Grid, other)
if self.a_count != other.a_count: if self.a_count != other.a_count:
@ -353,12 +353,12 @@ class Arbitrary(Repetition):
return (f'<Arbitrary {len(self.displacements)}pts >') return (f'<Arbitrary {len(self.displacements)}pts >')
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
if not type(other) != type(self): if not type(other) is not type(self):
return False return False
return numpy.array_equal(self.displacements, other.displacements) return numpy.array_equal(self.displacements, other.displacements)
def __le__(self, other: Repetition) -> bool: def __le__(self, other: Repetition) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) return repr(type(self)) < repr(type(other))
other = cast(Arbitrary, other) other = cast(Arbitrary, other)
if self.displacements.size != other.displacements.size: if self.displacements.size != other.displacements.size:

@ -191,7 +191,7 @@ class Arc(Shape):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) != type(other) type(self) is type(other)
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and numpy.array_equal(self.radii, other.radii) and numpy.array_equal(self.radii, other.radii)
and numpy.array_equal(self.angles, other.angles) and numpy.array_equal(self.angles, other.angles)
@ -202,8 +202,10 @@ class Arc(Shape):
) )
def __lt__(self, other: Shape) -> bool: def __lt__(self, other: Shape) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) if repr(type(self)) != repr(type(other)):
return repr(type(self)) < repr(type(other))
return id(type(self)) < id(type(other))
other = cast(Arc, other) other = cast(Arc, other)
if self.width != other.width: if self.width != other.width:
return self.width < other.width return self.width < other.width

@ -72,7 +72,7 @@ class Circle(Shape):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) != type(other) type(self) is type(other)
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and self.radius == other.radius and self.radius == other.radius
and self.repetition == other.repetition and self.repetition == other.repetition
@ -80,8 +80,10 @@ class Circle(Shape):
) )
def __lt__(self, other: Shape) -> bool: def __lt__(self, other: Shape) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) if repr(type(self)) != repr(type(other)):
return repr(type(self)) < repr(type(other))
return id(type(self)) < id(type(other))
other = cast(Circle, other) other = cast(Circle, other)
if not self.radius == other.radius: if not self.radius == other.radius:
return self.radius < other.radius return self.radius < other.radius

@ -121,7 +121,7 @@ class Ellipse(Shape):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) != type(other) type(self) is type(other)
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and numpy.array_equal(self.radii, other.radii) and numpy.array_equal(self.radii, other.radii)
and self.rotation == other.rotation and self.rotation == other.rotation
@ -130,8 +130,10 @@ class Ellipse(Shape):
) )
def __lt__(self, other: Shape) -> bool: def __lt__(self, other: Shape) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) if repr(type(self)) != repr(type(other)):
return repr(type(self)) < repr(type(other))
return id(type(self)) < id(type(other))
other = cast(Ellipse, other) other = cast(Ellipse, other)
if not numpy.array_equal(self.radii, other.radii): if not numpy.array_equal(self.radii, other.radii):
return tuple(self.radii) < tuple(other.radii) return tuple(self.radii) < tuple(other.radii)

@ -209,7 +209,7 @@ class Path(Shape):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) != type(other) type(self) is type(other)
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and numpy.array_equal(self.vertices, other.vertices) and numpy.array_equal(self.vertices, other.vertices)
and self.width == other.width and self.width == other.width
@ -220,8 +220,10 @@ class Path(Shape):
) )
def __lt__(self, other: Shape) -> bool: def __lt__(self, other: Shape) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) if repr(type(self)) != repr(type(other)):
return repr(type(self)) < repr(type(other))
return id(type(self)) < id(type(other))
other = cast(Path, other) other = cast(Path, other)
if self.width != other.width: if self.width != other.width:
return self.width < other.width return self.width < other.width

@ -117,7 +117,7 @@ class Polygon(Shape):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) != type(other) type(self) is type(other)
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and numpy.array_equal(self.vertices, other.vertices) and numpy.array_equal(self.vertices, other.vertices)
and self.repetition == other.repetition and self.repetition == other.repetition
@ -125,8 +125,10 @@ class Polygon(Shape):
) )
def __lt__(self, other: Shape) -> bool: def __lt__(self, other: Shape) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) if repr(type(self)) != repr(type(other)):
return repr(type(self)) < repr(type(other))
return id(type(self)) < id(type(other))
other = cast(Polygon, other) other = cast(Polygon, other)
if not numpy.array_equal(self.vertices, other.vertices): if not numpy.array_equal(self.vertices, other.vertices):
return tuple(tuple(xy) for xy in self.vertices) < tuple(tuple(xy) for xy in other.vertices) return tuple(tuple(xy) for xy in self.vertices) < tuple(tuple(xy) for xy in other.vertices)

@ -100,7 +100,7 @@ class Text(RotatableImpl, Shape):
def __eq__(self, other: Any) -> bool: def __eq__(self, other: Any) -> bool:
return ( return (
type(self) != type(other) type(self) is type(other)
and numpy.array_equal(self.offset, other.offset) and numpy.array_equal(self.offset, other.offset)
and self.string == other.string and self.string == other.string
and self.height == other.height and self.height == other.height
@ -111,8 +111,10 @@ class Text(RotatableImpl, Shape):
) )
def __lt__(self, other: Shape) -> bool: def __lt__(self, other: Shape) -> bool:
if type(self) != type(other): if type(self) is not type(other):
return repr(type(self)) < repr(type(other)) if repr(type(self)) != repr(type(other)):
return repr(type(self)) < repr(type(other))
return id(type(self)) < id(type(other))
other = cast(Text, other) other = cast(Text, other)
if not self.height == other.height: if not self.height == other.height:
return self.height < other.height return self.height < other.height

Loading…
Cancel
Save