310 lines
10 KiB
Python
310 lines
10 KiB
Python
import pytest
|
|
from numpy import pi
|
|
from numpy.testing import assert_allclose
|
|
|
|
from masque.builder.tools import AutoTool
|
|
from masque.builder.pather import Pather
|
|
from masque.library import Library
|
|
from masque.pattern import Pattern
|
|
from masque.ports import Port
|
|
|
|
|
|
def _make_transition_straight(length: float, width: float = 2, ptype: str = "wire") -> Pattern:
|
|
pat = Pattern()
|
|
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
|
|
pat.ports["in"] = Port((0, 0), 0, ptype=ptype)
|
|
pat.ports["out"] = Port((length, 0), pi, ptype=ptype)
|
|
return pat
|
|
|
|
|
|
@pytest.fixture
|
|
def autotool_setup() -> tuple[Pather, AutoTool, Library]:
|
|
lib = Library()
|
|
|
|
bend_pat = Pattern()
|
|
bend_pat.ports["in"] = Port((0, 0), 0, ptype="wire")
|
|
bend_pat.ports["out"] = Port((2, -2), pi / 2, ptype="wire")
|
|
lib["bend"] = bend_pat
|
|
lib.abstract("bend")
|
|
|
|
via_pat = Pattern()
|
|
via_pat.ports["m1"] = Port((0, 0), 0, ptype="wire_m1")
|
|
via_pat.ports["m2"] = Port((1, 0), pi, ptype="wire_m2")
|
|
lib["via"] = via_pat
|
|
via_abs = lib.abstract("via")
|
|
|
|
tool_m1 = AutoTool(
|
|
straights=[
|
|
AutoTool.Straight(ptype="wire_m1", fn=lambda length: _make_transition_straight(length, ptype="wire_m1"), in_port_name="in", out_port_name="out")
|
|
],
|
|
bends=[],
|
|
sbends=[],
|
|
transitions={("wire_m2", "wire_m1"): AutoTool.Transition(via_abs, "m2", "m1")},
|
|
default_out_ptype="wire_m1",
|
|
)
|
|
|
|
p = Pather(lib, tools=tool_m1)
|
|
p.ports["start"] = Port((0, 0), pi, ptype="wire_m2")
|
|
|
|
return p, tool_m1, lib
|
|
|
|
def test_autotool_transition(autotool_setup: tuple[Pather, AutoTool, Library]) -> None:
|
|
p, _tool, _lib = autotool_setup
|
|
|
|
p.straight("start", 10)
|
|
|
|
# Via length is 1, so the remaining wire_m1 straight length is 9.
|
|
assert_allclose(p.ports["start"].offset, [10, 0], atol=1e-10)
|
|
assert p.ports["start"].ptype == "wire_m1"
|
|
|
|
def make_straight(length, width=2, ptype="wire"):
|
|
pat = Pattern()
|
|
pat.rect((1, 0), xmin=0, xmax=length, yctr=0, ly=width)
|
|
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
|
|
pat.ports["B"] = Port((length, 0), pi, ptype=ptype)
|
|
return pat
|
|
|
|
def make_bend(R, width=2, ptype="wire", clockwise=True):
|
|
pat = Pattern()
|
|
# Rectangular approximation of a 90 degree bend.
|
|
if clockwise:
|
|
pat.rect((1, 0), xmin=0, xmax=R, yctr=0, ly=width)
|
|
pat.rect((1, 0), xctr=R, lx=width, ymin=-R, ymax=0)
|
|
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
|
|
pat.ports["B"] = Port((R, -R), pi/2, ptype=ptype)
|
|
else:
|
|
pat.rect((1, 0), xmin=0, xmax=R, yctr=0, ly=width)
|
|
pat.rect((1, 0), xctr=R, lx=width, ymin=0, ymax=R)
|
|
pat.ports["A"] = Port((0, 0), 0, ptype=ptype)
|
|
pat.ports["B"] = Port((R, R), -pi/2, ptype=ptype)
|
|
return pat
|
|
|
|
@pytest.fixture
|
|
def multi_bend_tool():
|
|
lib = Library()
|
|
|
|
lib["b1"] = make_bend(2, ptype="wire")
|
|
b1_abs = lib.abstract("b1")
|
|
lib["b2"] = make_bend(5, ptype="wire")
|
|
b2_abs = lib.abstract("b2")
|
|
|
|
tool = AutoTool(
|
|
straights=[
|
|
AutoTool.Straight(ptype="wire", fn=make_straight, in_port_name="A", out_port_name="B", length_range=(0, 10)),
|
|
AutoTool.Straight(ptype="wire", fn=lambda l: make_straight(l, width=4), in_port_name="A", out_port_name="B", length_range=(10, 1e8))
|
|
],
|
|
bends=[
|
|
AutoTool.Bend(b1_abs, "A", "B", clockwise=True, mirror=True),
|
|
AutoTool.Bend(b2_abs, "A", "B", clockwise=True, mirror=True)
|
|
],
|
|
sbends=[],
|
|
transitions={},
|
|
default_out_ptype="wire"
|
|
)
|
|
return tool, lib
|
|
|
|
@pytest.fixture
|
|
def asymmetric_transition_tool() -> AutoTool:
|
|
lib = Library()
|
|
|
|
bend_pat = Pattern()
|
|
bend_pat.ports["in"] = Port((0, 0), 0, ptype="core")
|
|
bend_pat.ports["out"] = Port((2, -2), pi / 2, ptype="core")
|
|
lib["core_bend"] = bend_pat
|
|
|
|
trans_pat = Pattern()
|
|
trans_pat.ports["CORE"] = Port((0, 0), 0, ptype="core")
|
|
trans_pat.ports["MID"] = Port((3, 1), pi, ptype="mid")
|
|
lib["core_mid"] = trans_pat
|
|
|
|
return AutoTool(
|
|
straights=[
|
|
AutoTool.Straight(
|
|
ptype="core",
|
|
fn=lambda length: make_straight(length, ptype="core"),
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
length_range=(0, 3),
|
|
),
|
|
AutoTool.Straight(
|
|
ptype="mid",
|
|
fn=lambda length: make_straight(length, ptype="mid"),
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
length_range=(0, 1e8),
|
|
),
|
|
],
|
|
bends=[
|
|
AutoTool.Bend(lib.abstract("core_bend"), "in", "out", clockwise=True, mirror=True),
|
|
],
|
|
sbends=[],
|
|
transitions={
|
|
("mid", "core"): AutoTool.Transition(lib.abstract("core_mid"), "MID", "CORE"),
|
|
},
|
|
default_out_ptype="core",
|
|
).add_complementary_transitions()
|
|
|
|
def assert_trace_matches_plan(plan_port: Port, tree: Library, port_names: tuple[str, str] = ("A", "B")) -> None:
|
|
pat = tree.top_pattern()
|
|
out_port = pat[port_names[1]]
|
|
dxy, rot = pat[port_names[0]].measure_travel(out_port)
|
|
assert_allclose(dxy, plan_port.offset)
|
|
assert rot is not None
|
|
assert plan_port.rotation is not None
|
|
assert_allclose(rot, plan_port.rotation)
|
|
assert out_port.ptype == plan_port.ptype
|
|
|
|
def test_autotool_planL_selection(multi_bend_tool) -> None:
|
|
tool, _ = multi_bend_tool
|
|
|
|
p, data = tool.planL(True, 5)
|
|
assert data.straight.length_range == (0, 10)
|
|
assert data.straight_length == 3
|
|
assert data.bend.abstract.name == "b1"
|
|
assert_allclose(p.offset, [5, 2])
|
|
|
|
p, data = tool.planL(True, 15)
|
|
assert data.straight.length_range == (10, 1e8)
|
|
assert data.straight_length == 13
|
|
assert_allclose(p.offset, [15, 2])
|
|
|
|
|
|
@pytest.mark.parametrize("ccw", [False, True])
|
|
def test_autotool_traceL_matches_plan_with_post_bend_transition(ccw: bool) -> None:
|
|
lib = Library()
|
|
|
|
bend_pat = Pattern()
|
|
bend_pat.ports["A"] = Port((0, 0), 0, ptype="core")
|
|
bend_pat.ports["B"] = Port((2, -2), pi / 2, ptype="core")
|
|
lib["core_bend"] = bend_pat
|
|
|
|
trans_pat = Pattern()
|
|
trans_pat.ports["CORE"] = Port((0, 0), 0, ptype="core")
|
|
trans_pat.ports["EXT"] = Port((3, 1), pi, ptype="ext")
|
|
lib["out_trans"] = trans_pat
|
|
|
|
tool = AutoTool(
|
|
straights=[
|
|
AutoTool.Straight(
|
|
ptype="core",
|
|
fn=lambda length: make_straight(length, ptype="core"),
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
length_range=(0, 1e8),
|
|
),
|
|
],
|
|
bends=[
|
|
AutoTool.Bend(lib.abstract("core_bend"), "A", "B", clockwise=True, mirror=True),
|
|
],
|
|
sbends=[],
|
|
transitions={
|
|
("ext", "core"): AutoTool.Transition(lib.abstract("out_trans"), "EXT", "CORE"),
|
|
},
|
|
default_out_ptype="core",
|
|
)
|
|
|
|
plan_port, data = tool.planL(ccw, 10, out_ptype="ext")
|
|
|
|
assert data.out_transition is not None
|
|
|
|
tree = tool.traceL(ccw, 10, out_ptype="ext")
|
|
assert_trace_matches_plan(plan_port, tree)
|
|
|
|
|
|
def test_autotool_planU_consistency(multi_bend_tool) -> None:
|
|
tool, lib = multi_bend_tool
|
|
|
|
p, data = tool.planU(20, length=10)
|
|
assert data.ldata0.straight_length == 7
|
|
assert data.ldata0.bend.abstract.name == "b2"
|
|
assert data.l2_length == 13
|
|
assert data.ldata1.straight_length == 0
|
|
assert data.ldata1.bend.abstract.name == "b1"
|
|
|
|
def test_autotool_traceU_matches_plan_with_asymmetric_transition(asymmetric_transition_tool: AutoTool) -> None:
|
|
tool = asymmetric_transition_tool
|
|
|
|
plan_port, data = tool.planU(12, length=0, in_ptype="core")
|
|
|
|
assert data.ldata1.in_transition is not None
|
|
assert data.ldata1.b_transition is not None
|
|
|
|
tree = tool.traceU(12, length=0, in_ptype="core")
|
|
assert_trace_matches_plan(plan_port, tree)
|
|
|
|
def test_autotool_planS_double_L(multi_bend_tool) -> None:
|
|
tool, lib = multi_bend_tool
|
|
|
|
p, data = tool.planS(20, 10)
|
|
assert_allclose(p.offset, [20, 10])
|
|
assert_allclose(p.rotation, pi)
|
|
|
|
assert data.ldata0.straight_length == 16
|
|
assert data.ldata1.straight_length == 0
|
|
assert data.l2_length == 6
|
|
|
|
def test_autotool_traceS_double_l_matches_plan_with_asymmetric_transition(asymmetric_transition_tool: AutoTool) -> None:
|
|
tool = asymmetric_transition_tool
|
|
|
|
plan_port, data = tool.planS(4, 10, in_ptype="core")
|
|
|
|
assert isinstance(data, AutoTool.UData)
|
|
assert data.ldata1.in_transition is not None
|
|
assert data.ldata1.b_transition is not None
|
|
|
|
tree = tool.traceS(4, 10, in_ptype="core")
|
|
assert_trace_matches_plan(plan_port, tree)
|
|
|
|
def test_autotool_planS_pure_sbend_with_transition_dx() -> None:
|
|
lib = Library()
|
|
|
|
def make_straight(length: float) -> Pattern:
|
|
pat = Pattern()
|
|
pat.ports["A"] = Port((0, 0), 0, ptype="core")
|
|
pat.ports["B"] = Port((length, 0), pi, ptype="core")
|
|
return pat
|
|
|
|
def make_sbend(jog: float) -> Pattern:
|
|
pat = Pattern()
|
|
pat.ports["A"] = Port((0, 0), 0, ptype="core")
|
|
pat.ports["B"] = Port((10, jog), pi, ptype="core")
|
|
return pat
|
|
|
|
trans_pat = Pattern()
|
|
trans_pat.ports["EXT"] = Port((0, 0), 0, ptype="ext")
|
|
trans_pat.ports["CORE"] = Port((5, 0), pi, ptype="core")
|
|
lib["xin"] = trans_pat
|
|
|
|
tool = AutoTool(
|
|
straights=[
|
|
AutoTool.Straight(
|
|
ptype="core",
|
|
fn=make_straight,
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
length_range=(1, 1e8),
|
|
)
|
|
],
|
|
bends=[],
|
|
sbends=[
|
|
AutoTool.SBend(
|
|
ptype="core",
|
|
fn=make_sbend,
|
|
in_port_name="A",
|
|
out_port_name="B",
|
|
jog_range=(0, 1e8),
|
|
)
|
|
],
|
|
transitions={
|
|
("ext", "core"): AutoTool.Transition(lib.abstract("xin"), "EXT", "CORE"),
|
|
},
|
|
default_out_ptype="core",
|
|
)
|
|
|
|
p, data = tool.planS(15, 4, in_ptype="ext")
|
|
|
|
assert_allclose(p.offset, [15, 4])
|
|
assert_allclose(p.rotation, pi)
|
|
assert data.straight_length == 0
|
|
assert data.jog_remaining == 4
|
|
assert data.in_transition is not None
|