-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.py
More file actions
115 lines (94 loc) · 4.56 KB
/
Copy pathcli.py
File metadata and controls
115 lines (94 loc) · 4.56 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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
"""Command-line entry point: analyse a layout's density and write the report.
python cli.py layout.json [-o out_dir]
python cli.py layout.gds [-o out_dir]
A JSON layout holds the polygons per layer, the optional bounding box, the window grid, and the density
thresholds:
{"layers": [{"layer": [1, 0], "polygons": [[[0, 0], [5, 0], [5, 10], [0, 10]]]}],
"bbox": [0, 0, 10, 10], "nx": 8, "ny": 8,
"min_density": 0.2, "max_density": 0.9, "max_step": 0.4}
A gdsfactory Component can be analysed through the Python API: report.analyze(geom.from_component(c), ...).
"""
from __future__ import annotations
import argparse
import json
import os
import geom
import report
_PARAMS = ("nx", "ny", "window", "min_density", "max_density", "max_step")
def _load_json(source):
try:
with open(source, encoding="utf-8") as f:
data = json.load(f)
except json.JSONDecodeError as e:
raise SystemExit(f"denscheck: {source} is not valid JSON ({e})")
if not isinstance(data, dict) or "layers" not in data:
raise SystemExit("denscheck: the JSON layout must be an object with a 'layers' key")
polys_by_layer = {}
for entry in data["layers"]:
polys_by_layer[tuple(entry["layer"])] = entry["polygons"]
layout = geom.from_layers(polys_by_layer, data.get("bbox"))
params = {k: data[k] for k in _PARAMS if k in data}
return layout, params
def run(source, out_dir=".", **params):
"""Analyse a layout (JSON path, GDS path, or Component) and write report.json, report.md, and heatmaps."""
if isinstance(source, str):
if source.lower().endswith(".json"):
layout, file_params = _load_json(source)
file_params.update(params)
params = file_params
elif not os.path.exists(source):
raise SystemExit(f"denscheck: layout file not found: {source}")
else:
layout = geom.from_gds(source)
else:
layout = geom.from_component(source)
result = report.analyze(layout, **params)
os.makedirs(out_dir, exist_ok=True)
json_path = os.path.join(out_dir, "report.json")
md_path = os.path.join(out_dir, "report.md")
with open(json_path, "w", encoding="utf-8") as f:
f.write(report.to_json(result))
with open(md_path, "w", encoding="utf-8") as f:
f.write(report.to_markdown(result))
fig_params = {k: params[k] for k in ("nx", "ny", "window") if k in params}
figures = []
for rec in result["layers"]:
layer = tuple(rec["layer"])
fig_path = os.path.join(out_dir, f"density_{layer[0]}_{layer[1]}.png")
report.figure(layout, layer, fig_path, **fig_params)
figures.append(fig_path)
return {"json": json_path, "markdown": md_path, "figures": figures,
"n_violations": len(result["violations"])}
def _validate():
import tempfile
spec = {"layers": [{"layer": [1, 0], "polygons": [[[0, 0], [5, 0], [5, 10], [0, 10]]]}],
"bbox": [0, 0, 10, 10], "nx": 2, "ny": 1, "min_density": 0.2}
with tempfile.TemporaryDirectory() as d:
src = os.path.join(d, "layout.json")
with open(src, "w", encoding="utf-8") as f:
json.dump(spec, f)
out = run(src, os.path.join(d, "out"))
ok_md = os.path.exists(out["markdown"]) and os.path.getsize(out["markdown"]) > 0
ok_json = os.path.exists(out["json"]) and os.path.getsize(out["json"]) > 0
ok_fig = len(out["figures"]) == 1 and os.path.exists(out["figures"][0])
md = open(out["markdown"], encoding="utf-8").read()
ok_content = "density_below_min" in md and out["n_violations"] > 0
for label, cond in [("report.md written", ok_md), ("report.json written", ok_json),
("heatmap written", ok_fig), ("violation reported", ok_content)]:
print(f"[{label}] {'PASS' if cond else 'FAIL'}")
print("RESULT:", "PASS" if all([ok_md, ok_json, ok_fig, ok_content]) else "FAIL")
def main():
ap = argparse.ArgumentParser(description="denscheck - layout density analysis")
ap.add_argument("layout", nargs="?", help="JSON or GDS layout file")
ap.add_argument("-o", "--out", default=".", help="output directory")
ap.add_argument("--validate", action="store_true")
args = ap.parse_args()
if args.validate:
_validate(); return
if not args.layout:
ap.error("provide a JSON or GDS layout file, or --validate")
out = run(args.layout, args.out)
print(f"wrote {out['markdown']}, {out['json']}, and {len(out['figures'])} heatmap(s) "
f"({out['n_violations']} violation(s))")
if __name__ == "__main__":
main()