masque/masque/test/test_builder.py

129 lines
3.9 KiB
Python

from numpy.testing import assert_equal, assert_allclose
from numpy import pi
from ..builder import Builder
from ..library import Library
from ..pattern import Pattern
from ..ports import Port
def test_builder_init() -> None:
lib = Library()
b = Builder(lib, name="mypat")
assert b.pattern is lib["mypat"]
assert b.library is lib
def test_builder_place() -> None:
lib = Library()
child = Pattern()
child.ports["A"] = Port((0, 0), 0)
lib["child"] = child
b = Builder(lib)
b.place("child", offset=(10, 20), port_map={"A": "child_A"})
assert "child_A" in b.ports
assert_equal(b.ports["child_A"].offset, [10, 20])
assert "child" in b.pattern.refs
def test_builder_plug() -> None:
lib = Library()
wire = Pattern()
wire.ports["in"] = Port((0, 0), 0)
wire.ports["out"] = Port((10, 0), pi)
lib["wire"] = wire
b = Builder(lib)
b.ports["start"] = Port((100, 100), 0)
# Plug wire's "in" port into builder's "start" port
# Wire's "out" port should be renamed to "start" because thru=True (default) and wire has 2 ports
# builder start: (100, 100) rotation 0
# wire in: (0, 0) rotation 0
# wire out: (10, 0) rotation pi
# Plugging wire in (rot 0) to builder start (rot 0) means wire is rotated by pi (180 deg)
# so wire in is at (100, 100), wire out is at (100 - 10, 100) = (90, 100)
b.plug("wire", map_in={"start": "in"})
assert "start" in b.ports
assert_equal(b.ports["start"].offset, [90, 100])
assert_allclose(b.ports["start"].rotation, 0, atol=1e-10)
def test_builder_interface() -> None:
lib = Library()
source = Pattern()
source.ports["P1"] = Port((0, 0), 0)
lib["source"] = source
b = Builder.interface("source", library=lib, name="iface")
assert "in_P1" in b.ports
assert "P1" in b.ports
assert b.pattern is lib["iface"]
def test_builder_set_dead() -> None:
lib = Library()
lib["sub"] = Pattern()
b = Builder(lib)
b.set_dead()
b.place("sub")
assert not b.pattern.has_refs()
def test_builder_dead_ports() -> None:
lib = Library()
pat = Pattern()
pat.ports['A'] = Port((0, 0), 0)
b = Builder(lib, pattern=pat)
b.set_dead()
# Attempt to plug a device where ports don't line up
# A has rotation 0, C has rotation 0. plug() expects opposing rotations (pi difference).
other = Pattern(ports={'C': Port((10, 10), 0), 'D': Port((20, 20), 0)})
# This should NOT raise PortError because b is dead
b.plug(other, map_in={'A': 'C'}, map_out={'D': 'B'})
# Port A should be removed, and Port B (renamed from D) should be added
assert 'A' not in b.ports
assert 'B' in b.ports
# Verify geometry was not added
assert not b.pattern.has_refs()
assert not b.pattern.has_shapes()
def test_dead_plug_best_effort() -> None:
lib = Library()
pat = Pattern()
pat.ports['A'] = Port((0, 0), 0)
b = Builder(lib, pattern=pat)
b.set_dead()
# Device with multiple ports, none of which line up correctly
other = Pattern(ports={
'P1': Port((10, 10), 0), # Wrong rotation (0 instead of pi)
'P2': Port((20, 20), pi) # Correct rotation but wrong offset
})
# Try to plug. find_transform will fail.
# It should fall back to aligning the first pair ('A' and 'P1').
b.plug(other, map_in={'A': 'P1'}, map_out={'P2': 'B'})
assert 'A' not in b.ports
assert 'B' in b.ports
# Dummy transform aligns A (0,0) with P1 (10,10)
# A rotation 0, P1 rotation 0 -> rotation = (0 - 0 - pi) = -pi
# P2 (20,20) rotation pi:
# 1. Translate P2 so P1 is at origin: (20,20) - (10,10) = (10,10)
# 2. Rotate (10,10) by -pi: (-10,-10)
# 3. Translate by s_port.offset (0,0): (-10,-10)
assert_allclose(b.ports['B'].offset, [-10, -10], atol=1e-10)
# P2 rot pi + transform rot -pi = 0
assert_allclose(b.ports['B'].rotation, 0, atol=1e-10)