-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.py
More file actions
92 lines (74 loc) · 3.6 KB
/
Copy pathcli.py
File metadata and controls
92 lines (74 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""Command-line entry point for gdsnorm.
python cli.py a.gds [-o out_dir] [--grid 0.001] # hash manifest for one layout
python cli.py a.gds b.gds [--grid 0.001] # are two layouts canonically equal?
With one file it writes hashes.json and manifest.md. With two it prints whether they are canonically identical
and exits non-zero if they differ, which makes it usable as a CI gate.
"""
from __future__ import annotations
import argparse
import os
import sys
import load
import report
def _load(path):
if not os.path.exists(path):
raise SystemExit(f"gdsnorm: layout file not found: {path}")
return load.from_gds(path)
def run(source, out_dir=".", grid=1e-3):
"""Write the hash manifest (hashes.json + manifest.md) for one layout."""
lib = _load(source) if isinstance(source, str) else source
m = report.manifest(lib, grid)
os.makedirs(out_dir, exist_ok=True)
json_path = os.path.join(out_dir, "hashes.json")
md_path = os.path.join(out_dir, "manifest.md")
with open(json_path, "w", encoding="utf-8") as f:
f.write(report.to_json(m))
with open(md_path, "w", encoding="utf-8") as f:
f.write(report.to_markdown(m))
return {"json": json_path, "markdown": md_path, "library_hash": m["library_hash"]}
def compare(source_a, source_b, grid=1e-3):
"""Compare two layouts and return the verdict dict."""
a = _load(source_a) if isinstance(source_a, str) else source_a
b = _load(source_b) if isinstance(source_b, str) else source_b
return report.compare(a, b, grid)
def _validate():
import tempfile
with tempfile.TemporaryDirectory() as d:
gds = os.path.join(d, "demo.gds")
load._build_demo_library().write_gds(gds)
out = run(gds, os.path.join(d, "out"))
ok_json = os.path.exists(out["json"]) and os.path.getsize(out["json"]) > 0
ok_md = os.path.exists(out["markdown"]) and os.path.getsize(out["markdown"]) > 0
ok_hash = len(out["library_hash"]) == 64
import cellhash
ga, gb = os.path.join(d, "a.gds"), os.path.join(d, "b.gds")
cellhash._build("ab").write_gds(ga)
cellhash._build("ba").write_gds(gb)
ok_equal = compare(ga, gb)["equal"] is True
for label, cond in [("hashes.json written", ok_json), ("manifest.md written", ok_md),
("library hash", ok_hash), ("equivalent gds compare equal", ok_equal)]:
print(f"[{label}] {'PASS' if cond else 'FAIL'}")
print("RESULT:", "PASS" if all([ok_json, ok_md, ok_hash, ok_equal]) else "FAIL")
def main():
ap = argparse.ArgumentParser(description="gdsnorm - canonical layout hash + equivalence")
ap.add_argument("a", nargs="?", help="GDS layout file")
ap.add_argument("b", nargs="?", help="second GDS file to compare against")
ap.add_argument("-o", "--out", default=".", help="output directory")
ap.add_argument("--grid", type=float, default=1e-3, help="database grid in microns")
ap.add_argument("--validate", action="store_true")
args = ap.parse_args()
if args.validate:
_validate(); return
if not args.a:
ap.error("provide a GDS file (and optionally a second to compare), or --validate")
if args.b:
verdict = compare(args.a, args.b, args.grid)
if verdict["equal"]:
print(f"canonically identical ({verdict['library_hash_a']})")
sys.exit(0)
print(f"layouts differ: {verdict['reason']}")
sys.exit(1)
out = run(args.a, args.out, args.grid)
print(f"wrote {out['markdown']} and {out['json']} (library hash {out['library_hash'][:16]}...)")
if __name__ == "__main__":
main()