import sys
from collections.abc import Iterator
import gdspy
from semi_cr.assets import get_sample_gds_path
[docs]
def is_gate_polygon(polygon: gdspy.Polygon) -> bool:
return polygon.properties.get(1) is not None
[docs]
class GDSGate:
name: str
polygon: list[tuple[float, float]]
def __init__(self, name: str, polygon: list[tuple[float, float]]):
self.name = name
self.polygon = polygon
def __repr__(self):
return f"<Gate: {self.name}, {len(self.polygon)} vertices>"
[docs]
def sort_gate_names(names: list[str]) -> list[str]:
"""Sort gate names: measurement plungers, numbered plungers, barriers, sensors.
Examples:
>>> sort_gate_names(['B2', 'P1', 'PN', 'B1', 'P2', 'PE', 'S1'])
['PN', 'PE', 'P1', 'P2', 'B1', 'B2', 'S1']
>>> sort_gate_names(['P10', 'P2', 'P1'])
['P1', 'P2', 'P10']
"""
MEASUREMENT_PLUNGERS = ["PN", "PE", "PS", "PW"] # Fixed order
def sort_key(name: str) -> tuple:
# Measurement plungers first (group 0)
if name in MEASUREMENT_PLUNGERS:
return (0, MEASUREMENT_PLUNGERS.index(name), 0)
# Numbered plungers (group 1)
if name.startswith("P") and name[1:].isdigit():
return (1, int(name[1:]), 0)
# Barriers (group 2)
if name.startswith("B") and name[1:].isdigit():
return (2, int(name[1:]), 0)
# Sensors (group 3)
if name.startswith("S") and name[1:].isdigit():
return (3, int(name[1:]), 0)
# Unknown gates last (group 4), alphabetically
return (4, 0, name)
return sorted(names, key=sort_key)
[docs]
class GDSFile:
"""Loads a GDS2 file, and can output the polygons in a neutral format
TODO: Improve validation (and write test cases)
- gdspy.Polygon may include more than one actual polygon, enforce that properly (and write a test case)
- We do not allow non-unique gate names.
"""
_gdsii: gdspy.GdsLibrary
def __init__(self, path: str | None = None):
self.path = path or get_sample_gds_path()
self._gdsii = gdspy.GdsLibrary(infile=self.path)
[docs]
def get_gates(self) -> Iterator[GDSGate]:
for poly in filter(is_gate_polygon, self._gdsii.cells["main_fo"].polygons):
name = poly.properties[1]
assert len(poly.polygons) == 1
coords = poly.polygons[0]
points = []
for x in coords:
points.append(tuple(x))
yield GDSGate(name, points)
[docs]
def get_gate_names(self) -> list[str]:
"""Return gate names in domain-appropriate order.
Order: measurement plungers (PN, PE, PS, PW) -> numbered plungers -> barriers -> sensors
"""
names = [gate.name for gate in self.get_gates()]
return sort_gate_names(names)
[docs]
def get_bbox(self) -> tuple[tuple[float, float], tuple[float, float]]:
xmin, ymin = sys.float_info.max, sys.float_info.max
xmax, ymax = sys.float_info.min, sys.float_info.min
for gate in self.get_gates():
for x, y in gate.polygon:
xmin = min(xmin, x)
xmax = max(xmax, x)
ymin = min(ymin, y)
ymax = max(ymax, y)
return ((xmin, ymin), (xmax, ymax))
if __name__ == "__main__":
g = GDSFile()
for gate in g.get_gates():
print(gate)