rocket-engine 0.0.2b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rocket_engine/__init__.py +8 -0
- rocket_engine/__main__.py +12 -0
- rocket_engine/adapters/README.md +54 -0
- rocket_engine/adapters/__init__.py +7 -0
- rocket_engine/adapters/api.py +39 -0
- rocket_engine/adapters/base.py +46 -0
- rocket_engine/adapters/cace/__init__.py +163 -0
- rocket_engine/adapters/circulax/README.md +10 -0
- rocket_engine/adapters/circulax/__init__.py +112 -0
- rocket_engine/adapters/circulax/_driver.py +190 -0
- rocket_engine/adapters/circulax/adapter.py +538 -0
- rocket_engine/adapters/circulax/results.py +291 -0
- rocket_engine/adapters/cli/README.md +34 -0
- rocket_engine/adapters/cli/__init__.py +305 -0
- rocket_engine/adapters/connectivity/__init__.py +196 -0
- rocket_engine/adapters/connectivity/results.py +214 -0
- rocket_engine/adapters/cosim/README.md +9 -0
- rocket_engine/adapters/cosim/__init__.py +1390 -0
- rocket_engine/adapters/cosim/results.py +214 -0
- rocket_engine/adapters/devsim/__init__.py +171 -0
- rocket_engine/adapters/digital_flow/README.md +9 -0
- rocket_engine/adapters/digital_flow/__init__.py +1121 -0
- rocket_engine/adapters/digital_flow/results.py +227 -0
- rocket_engine/adapters/digital_flow/state.py +462 -0
- rocket_engine/adapters/drc/README.klayout.md +7 -0
- rocket_engine/adapters/drc/README.md +7 -0
- rocket_engine/adapters/drc/__init__.py +1 -0
- rocket_engine/adapters/drc/klayout.py +952 -0
- rocket_engine/adapters/drc/magic.py +287 -0
- rocket_engine/adapters/drc/results.py +184 -0
- rocket_engine/adapters/drc/results_kl.py +188 -0
- rocket_engine/adapters/elmer/__init__.py +163 -0
- rocket_engine/adapters/envelope.py +120 -0
- rocket_engine/adapters/equivalence/__init__.py +156 -0
- rocket_engine/adapters/errors.py +181 -0
- rocket_engine/adapters/extract_magic/README.md +15 -0
- rocket_engine/adapters/extract_magic/__init__.py +425 -0
- rocket_engine/adapters/extract_magic/results.py +209 -0
- rocket_engine/adapters/formal_sby/README.md +7 -0
- rocket_engine/adapters/formal_sby/__init__.py +773 -0
- rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
- rocket_engine/adapters/formal_sby/results.py +185 -0
- rocket_engine/adapters/gdsfactory/__init__.py +194 -0
- rocket_engine/adapters/gdsfactory/_driver.py +459 -0
- rocket_engine/adapters/gdsfactory/adapter.py +609 -0
- rocket_engine/adapters/gdsfactory/results.py +181 -0
- rocket_engine/adapters/hdl_sim/README.md +7 -0
- rocket_engine/adapters/hdl_sim/__init__.py +369 -0
- rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
- rocket_engine/adapters/hdl_sim/results.py +178 -0
- rocket_engine/adapters/hdl_sim/verilator.py +167 -0
- rocket_engine/adapters/http/README.md +15 -0
- rocket_engine/adapters/http/__init__.py +458 -0
- rocket_engine/adapters/http/events.py +63 -0
- rocket_engine/adapters/ipc/README.md +9 -0
- rocket_engine/adapters/ipc/__init__.py +111 -0
- rocket_engine/adapters/lvs_netgen/README.md +20 -0
- rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
- rocket_engine/adapters/lvs_netgen/results.py +168 -0
- rocket_engine/adapters/mcp/README.md +19 -0
- rocket_engine/adapters/mcp/__init__.py +85 -0
- rocket_engine/adapters/mcp/api-schema.json +6107 -0
- rocket_engine/adapters/mcp/pull.py +246 -0
- rocket_engine/adapters/mcp/server.py +794 -0
- rocket_engine/adapters/meep/__init__.py +167 -0
- rocket_engine/adapters/meow/README.md +7 -0
- rocket_engine/adapters/meow/__init__.py +190 -0
- rocket_engine/adapters/meow/_driver.py +227 -0
- rocket_engine/adapters/meow/_envelope.py +108 -0
- rocket_engine/adapters/meow/adapter.py +491 -0
- rocket_engine/adapters/meow/results.py +218 -0
- rocket_engine/adapters/mutation/__init__.py +192 -0
- rocket_engine/adapters/ngspice/README.md +28 -0
- rocket_engine/adapters/ngspice/__init__.py +627 -0
- rocket_engine/adapters/ngspice/results.py +215 -0
- rocket_engine/adapters/postlayout/README.md +28 -0
- rocket_engine/adapters/postlayout/__init__.py +17 -0
- rocket_engine/adapters/postlayout/adapter.py +1062 -0
- rocket_engine/adapters/postlayout/results.py +330 -0
- rocket_engine/adapters/postlayout/translate.py +71 -0
- rocket_engine/adapters/profile.py +220 -0
- rocket_engine/adapters/sax/README.md +23 -0
- rocket_engine/adapters/sax/__init__.py +516 -0
- rocket_engine/adapters/sax/_driver.py +148 -0
- rocket_engine/adapters/sax/results.py +291 -0
- rocket_engine/adapters/stdio_console.py +1151 -0
- rocket_engine/adapters/syntax/README.md +5 -0
- rocket_engine/adapters/syntax/__init__.py +366 -0
- rocket_engine/adapters/syntax/results.py +181 -0
- rocket_engine/adapters/synth_yosys/README.md +7 -0
- rocket_engine/adapters/synth_yosys/__init__.py +655 -0
- rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
- rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
- rocket_engine/adapters/synth_yosys/results.py +173 -0
- rocket_engine/adapters/timing_sta/README.md +9 -0
- rocket_engine/adapters/timing_sta/__init__.py +726 -0
- rocket_engine/adapters/view_sync/__init__.py +185 -0
- rocket_engine/adapters/view_sync/results.py +189 -0
- rocket_engine/adapters/vlsir/__init__.py +142 -0
- rocket_engine/adapters/xschem/__init__.py +440 -0
- rocket_engine/adapters/xschem/deck.py +266 -0
- rocket_engine/clients/__init__.py +5 -0
- rocket_engine/clients/runtime.py +255 -0
- rocket_engine/composition.py +2279 -0
- rocket_engine/execution/README.md +30 -0
- rocket_engine/execution/__init__.py +150 -0
- rocket_engine/execution/apig/__init__.py +1 -0
- rocket_engine/execution/apig/pool.py +463 -0
- rocket_engine/execution/apig/submit.py +124 -0
- rocket_engine/execution/block_runs_scope.py +479 -0
- rocket_engine/execution/coalesce.py +414 -0
- rocket_engine/execution/console/__init__.py +1 -0
- rocket_engine/execution/console/apply_session.py +318 -0
- rocket_engine/execution/console/lifecycle.py +265 -0
- rocket_engine/execution/dispatch.py +205 -0
- rocket_engine/execution/errors.py +31 -0
- rocket_engine/execution/ports.py +797 -0
- rocket_engine/execution/pyexec/__init__.py +1 -0
- rocket_engine/execution/pyexec/apply_context.py +547 -0
- rocket_engine/execution/pyexec/approvals.py +173 -0
- rocket_engine/execution/pyexec/runner.py +404 -0
- rocket_engine/execution/pyexec/support.py +464 -0
- rocket_engine/execution/python_exec.py +897 -0
- rocket_engine/execution/queue.py +85 -0
- rocket_engine/execution/replay_safe.py +601 -0
- rocket_engine/execution/routing/__init__.py +1 -0
- rocket_engine/execution/routing/calls.py +364 -0
- rocket_engine/execution/routing/namespace.py +785 -0
- rocket_engine/execution/routing/ports.py +102 -0
- rocket_engine/execution/routing/tools.py +346 -0
- rocket_engine/execution/runner.py +311 -0
- rocket_engine/execution/runtime.py +1149 -0
- rocket_engine/execution/service.py +279 -0
- rocket_engine/execution/worker.py +464 -0
- rocket_engine/interfaces/README.md +26 -0
- rocket_engine/interfaces/__init__.py +58 -0
- rocket_engine/interfaces/apig_assets.py +407 -0
- rocket_engine/interfaces/celllink.py +461 -0
- rocket_engine/interfaces/documents.py +92 -0
- rocket_engine/interfaces/envelopes.py +131 -0
- rocket_engine/interfaces/errors.py +16 -0
- rocket_engine/interfaces/layout.py +410 -0
- rocket_engine/interfaces/layout_schema.yaml +278 -0
- rocket_engine/interfaces/paths.py +223 -0
- rocket_engine/interfaces/pdks.py +550 -0
- rocket_engine/interfaces/ports.py +72 -0
- rocket_engine/interfaces/references.py +115 -0
- rocket_engine/interfaces/refs.py +85 -0
- rocket_engine/interfaces/runparams.py +304 -0
- rocket_engine/interfaces/setup.py +552 -0
- rocket_engine/interfaces/setup_catalogue.yaml +243 -0
- rocket_engine/interfaces/setup_probes.py +66 -0
- rocket_engine/interfaces/vocabulary.py +58 -0
- rocket_engine/interfaces/yaml_dump.py +31 -0
- rocket_engine/library/README.md +22 -0
- rocket_engine/library/__init__.py +13 -0
- rocket_engine/library/apig_catalog.py +371 -0
- rocket_engine/library/catalog.py +185 -0
- rocket_engine/library/errors.py +16 -0
- rocket_engine/mutate/__init__.py +776 -0
- rocket_engine/mutate/operators.py +700 -0
- rocket_engine/ops/__init__.py +1 -0
- rocket_engine/ops/deferred.py +279 -0
- rocket_engine/ops/editors.py +404 -0
- rocket_engine/ops/evidence.py +194 -0
- rocket_engine/ops/gates.py +262 -0
- rocket_engine/ops/ports.py +76 -0
- rocket_engine/ops/queries.py +417 -0
- rocket_engine/ops/runs.py +553 -0
- rocket_engine/ops/submit.py +614 -0
- rocket_engine/project/README.md +27 -0
- rocket_engine/project/__init__.py +6 -0
- rocket_engine/project/canvas/__init__.py +55 -0
- rocket_engine/project/canvas/compile.py +109 -0
- rocket_engine/project/canvas/integrate.py +48 -0
- rocket_engine/project/canvas/schema.py +241 -0
- rocket_engine/project/canvas/templates.py +46 -0
- rocket_engine/project/canvas/validate.py +603 -0
- rocket_engine/project/edit/__init__.py +5 -0
- rocket_engine/project/edit/cas.py +87 -0
- rocket_engine/project/edit/transactions.py +282 -0
- rocket_engine/project/errors.py +16 -0
- rocket_engine/project/frontend13.py +685 -0
- rocket_engine/project/geometry/__init__.py +23 -0
- rocket_engine/project/geometry/ops.py +451 -0
- rocket_engine/project/geometry/snap.py +103 -0
- rocket_engine/project/ip/__init__.py +1 -0
- rocket_engine/project/ip/blocks.py +195 -0
- rocket_engine/project/ip/entry.schema.json +78 -0
- rocket_engine/project/ip/lifecycle.py +402 -0
- rocket_engine/project/ip/verbs.py +123 -0
- rocket_engine/project/layers/__init__.py +6 -0
- rocket_engine/project/layers/bindings.py +115 -0
- rocket_engine/project/layers/catalog.py +228 -0
- rocket_engine/project/layers/presets.py +43 -0
- rocket_engine/project/ledger/__init__.py +1 -0
- rocket_engine/project/ledger/docs.py +167 -0
- rocket_engine/project/ledger/entities.py +176 -0
- rocket_engine/project/ledger/events.py +29 -0
- rocket_engine/project/ledger/runs.py +257 -0
- rocket_engine/project/ledger/shared.py +174 -0
- rocket_engine/project/ledger/snapshots.py +385 -0
- rocket_engine/project/ledger/wires.py +552 -0
- rocket_engine/project/process/__init__.py +14 -0
- rocket_engine/project/process/model.py +193 -0
- rocket_engine/project/process/recipes.py +121 -0
- rocket_engine/project/schematic/__init__.py +1 -0
- rocket_engine/project/schematic/hierarchy.py +312 -0
- rocket_engine/project/schematic/instances.py +393 -0
- rocket_engine/project/schematic/sheets.py +198 -0
- rocket_engine/project/service.py +2941 -0
- rocket_engine/project/symbols/__init__.py +1 -0
- rocket_engine/project/symbols/cells.py +324 -0
- rocket_engine/project/symbols/linkage.py +356 -0
- rocket_engine/project/symbols/symbols.py +352 -0
- rocket_engine/project/templates/__init__.py +303 -0
- rocket_engine/project/templates/errors.py +25 -0
- rocket_engine/project/testbench/__init__.py +49 -0
- rocket_engine/project/testbench/blocks.py +619 -0
- rocket_engine/project/testbench/templates.py +134 -0
- rocket_engine/stores/README.md +12 -0
- rocket_engine/stores/__init__.py +6 -0
- rocket_engine/stores/ecs.py +1508 -0
- rocket_engine/stores/layers.py +1023 -0
- rocket_engine/stores/settings.py +1188 -0
- rocket_engine/stores/wireplace.py +1324 -0
- rocket_engine/workflows/README.md +18 -0
- rocket_engine/workflows/__init__.py +53 -0
- rocket_engine/workflows/capability_plan.py +102 -0
- rocket_engine/workflows/completion.py +228 -0
- rocket_engine/workflows/connectgraph.py +734 -0
- rocket_engine/workflows/connectgraph_emit.py +336 -0
- rocket_engine/workflows/gates.py +144 -0
- rocket_engine/workflows/photonic_wire.py +76 -0
- rocket_engine/workflows/stages.py +324 -0
- rocket_engine/workflows/viewsync.py +399 -0
- rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
- rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
- rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
- rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
- rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
"""Meow EME solver adapter: run_em behind the ToolAdapter port.
|
|
2
|
+
|
|
3
|
+
Protocol, mirroring the SAX adapter: stage the sim driver plus a job
|
|
4
|
+
JSON into the workdir, run `python meow_driver.py meow_job.json`
|
|
5
|
+
under a timeout, then parse the JSON envelope on stdout. The verdict
|
|
6
|
+
always comes from parsed output: missed S-parameter or convergence
|
|
7
|
+
bounds are FAIL, a malformed device synthesizes an error violation
|
|
8
|
+
with file/line, and only a meow-less interpreter raises
|
|
9
|
+
missing_dependency.
|
|
10
|
+
|
|
11
|
+
Generalization law: the project device YAML is the input — widths,
|
|
12
|
+
wavelength, cell length, mode counts, and targets all ride the
|
|
13
|
+
spec. `run_eme_smoke` (the sibling smoke) qualifies the install;
|
|
14
|
+
this op generalizes it and must never hardcode the proof widths.
|
|
15
|
+
Every run solves twice (base then refined modes) so convergence
|
|
16
|
+
evidence ships with the verdict, not as prose. FDTD/FEM stay out:
|
|
17
|
+
proposed, not proven.
|
|
18
|
+
|
|
19
|
+
Tool resolution: an explicit job `tool_dir` containing a python
|
|
20
|
+
wins, else the engine interpreter (meow arrives via the pip
|
|
21
|
+
`photonic` group). Where the tools live on disk is B10 scope.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import math
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
from ruamel.yaml import YAML
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
from ...execution.errors import ExecutionError
|
|
34
|
+
except ImportError:
|
|
35
|
+
from rocket_engine.execution.errors import ExecutionError
|
|
36
|
+
try:
|
|
37
|
+
from ...execution.runner import tool_roots
|
|
38
|
+
except ImportError:
|
|
39
|
+
from rocket_engine.execution.runner import tool_roots
|
|
40
|
+
try:
|
|
41
|
+
from ...interfaces import ToolAdapter
|
|
42
|
+
except ImportError:
|
|
43
|
+
from rocket_engine.interfaces import ToolAdapter
|
|
44
|
+
try:
|
|
45
|
+
from ..errors import AdapterError
|
|
46
|
+
except ImportError:
|
|
47
|
+
from rocket_engine.adapters.errors import AdapterError
|
|
48
|
+
try:
|
|
49
|
+
from ._envelope import extract_envelope
|
|
50
|
+
except ImportError:
|
|
51
|
+
from rocket_engine.adapters.meow._envelope import extract_envelope
|
|
52
|
+
|
|
53
|
+
OPERATION = "run_em"
|
|
54
|
+
_TIMEOUT_S = 120
|
|
55
|
+
_DRIVER_NAME = "meow_driver.py"
|
|
56
|
+
_JOB_NAME = "meow_job.json"
|
|
57
|
+
_METRICS = (
|
|
58
|
+
"transmission",
|
|
59
|
+
"reflection",
|
|
60
|
+
"convergence_dT",
|
|
61
|
+
"convergence_dneff",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _is_number(value):
|
|
66
|
+
return (
|
|
67
|
+
isinstance(value, (int, float))
|
|
68
|
+
and not isinstance(value, bool)
|
|
69
|
+
and math.isfinite(value)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _is_int(value):
|
|
74
|
+
return isinstance(value, int) and not isinstance(value, bool)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _check_doc(doc, spec):
|
|
78
|
+
if not isinstance(doc, dict):
|
|
79
|
+
raise AdapterError(
|
|
80
|
+
"invalid_command", f"Device spec is not a mapping: {spec}."
|
|
81
|
+
)
|
|
82
|
+
for key in ("wl_um", "cell_length_um"):
|
|
83
|
+
if not _is_number(doc.get(key)) or not doc[key] > 0:
|
|
84
|
+
raise AdapterError(
|
|
85
|
+
"invalid_command",
|
|
86
|
+
f"Device spec needs a positive {key}: {spec}.",
|
|
87
|
+
)
|
|
88
|
+
widths = doc.get("widths_um")
|
|
89
|
+
if (
|
|
90
|
+
not isinstance(widths, list)
|
|
91
|
+
or not widths
|
|
92
|
+
or not all(_is_number(w) and w > 0 for w in widths)
|
|
93
|
+
):
|
|
94
|
+
raise AdapterError(
|
|
95
|
+
"invalid_command",
|
|
96
|
+
f"Device spec needs positive widths_um: {spec}.",
|
|
97
|
+
)
|
|
98
|
+
modes = doc.get("modes")
|
|
99
|
+
if not isinstance(modes, dict):
|
|
100
|
+
raise AdapterError(
|
|
101
|
+
"invalid_command", f"Device spec needs modes: {spec}."
|
|
102
|
+
)
|
|
103
|
+
base, refined = modes.get("base"), modes.get("refined")
|
|
104
|
+
if not _is_int(base) or base < 1 or not _is_int(refined):
|
|
105
|
+
raise AdapterError(
|
|
106
|
+
"invalid_command",
|
|
107
|
+
f"Device modes need integer base/refined counts: {spec}.",
|
|
108
|
+
)
|
|
109
|
+
if not refined > base:
|
|
110
|
+
raise AdapterError(
|
|
111
|
+
"invalid_command",
|
|
112
|
+
f"Refined modes must exceed base modes: {modes!r} in {spec}.",
|
|
113
|
+
)
|
|
114
|
+
targets = doc.get("targets", [])
|
|
115
|
+
if not isinstance(targets, list):
|
|
116
|
+
raise AdapterError(
|
|
117
|
+
"invalid_command", f"Device targets must be a list: {spec}."
|
|
118
|
+
)
|
|
119
|
+
for target in targets:
|
|
120
|
+
if not isinstance(target, dict) or target.get("metric") not in _METRICS:
|
|
121
|
+
raise AdapterError(
|
|
122
|
+
"invalid_command",
|
|
123
|
+
f"Bad target {target!r}: {spec} "
|
|
124
|
+
f"(metric one of {', '.join(_METRICS)}).",
|
|
125
|
+
)
|
|
126
|
+
bounds = {
|
|
127
|
+
key: target[key]
|
|
128
|
+
for key in ("min", "max")
|
|
129
|
+
if target.get(key) is not None
|
|
130
|
+
}
|
|
131
|
+
if not bounds or not all(_is_number(v) for v in bounds.values()):
|
|
132
|
+
raise AdapterError(
|
|
133
|
+
"invalid_command",
|
|
134
|
+
f"Target needs numeric min and/or max: {target!r}.",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _load_spec(spec):
|
|
139
|
+
try:
|
|
140
|
+
text = Path(spec).read_text(encoding="utf-8")
|
|
141
|
+
except OSError as exc:
|
|
142
|
+
raise AdapterError(
|
|
143
|
+
"invalid_reference", f"Device spec unreadable: {spec} ({exc})."
|
|
144
|
+
) from exc
|
|
145
|
+
try:
|
|
146
|
+
doc = YAML(typ="safe").load(text)
|
|
147
|
+
except Exception as exc:
|
|
148
|
+
mark = getattr(exc, "problem_mark", None) or getattr(
|
|
149
|
+
exc, "context_mark", None
|
|
150
|
+
)
|
|
151
|
+
where = (
|
|
152
|
+
f"{spec}:{mark.line + 1}:{mark.column + 1}"
|
|
153
|
+
if mark is not None
|
|
154
|
+
else spec
|
|
155
|
+
)
|
|
156
|
+
raise AdapterError(
|
|
157
|
+
"invalid_reference", f"Device spec is not YAML: {where} ({exc})."
|
|
158
|
+
) from exc
|
|
159
|
+
_check_doc(doc, spec)
|
|
160
|
+
return text, doc
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _target_line(spec_text):
|
|
164
|
+
for number, line in enumerate((spec_text or "").splitlines(), 1):
|
|
165
|
+
if "targets" in line:
|
|
166
|
+
return number
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class MeowAdapter(ToolAdapter):
|
|
171
|
+
"""run_em via meow: EME mode/field solving with convergence evidence.
|
|
172
|
+
|
|
173
|
+
Serves the run_em operation with deck.tool `meow` (the only
|
|
174
|
+
tool). Stages the sim driver plus a job JSON, runs it under a
|
|
175
|
+
timeout, and parses the JSON envelope: the fundamental
|
|
176
|
+
S-parameters at the base mode count plus the refined-basis
|
|
177
|
+
deltas (convergence evidence) against min/max bounds.
|
|
178
|
+
|
|
179
|
+
Job keys:
|
|
180
|
+
sources (required): Exactly one device spec YAML path.
|
|
181
|
+
timeout_s: Sim budget in seconds (default 120).
|
|
182
|
+
tool_dir: Directory holding the sim python; the engine
|
|
183
|
+
interpreter otherwise.
|
|
184
|
+
deck: Optional mapping; deck.tool must be meow here.
|
|
185
|
+
workdir: Run directory (default ".").
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
runner: Optional isolated runner; direct subprocess when None.
|
|
189
|
+
|
|
190
|
+
Caution: Missed S-parameter or convergence bounds are FAIL and
|
|
191
|
+
a malformed device synthesizes an error violation with
|
|
192
|
+
file/line — the verdict always comes from parsed output,
|
|
193
|
+
never the exit code.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
def __init__(self, runner=None):
|
|
197
|
+
self._runner = runner
|
|
198
|
+
|
|
199
|
+
def capabilities(self, target):
|
|
200
|
+
"""Report the served operation and tool name.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
target: Target the capabilities are reported for
|
|
204
|
+
(accepted, unused — one device per job).
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Capability mapping with operations and tool.
|
|
208
|
+
"""
|
|
209
|
+
return {"operations": [OPERATION], "tool": "meow"}
|
|
210
|
+
|
|
211
|
+
def preflight(self, job):
|
|
212
|
+
"""Check operation, timeout, spec shape, and sim python.
|
|
213
|
+
|
|
214
|
+
Cheap deterministic checks first, tool resolution last, the
|
|
215
|
+
runner probe closing it out.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
job: Solver job with operation run_em plus the device
|
|
219
|
+
spec source and timeout inputs.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Checked mapping with the resolved python, spec, spec
|
|
223
|
+
text, parsed doc, and timeout_s.
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
AdapterError: unsupported_operation for another
|
|
227
|
+
operation, invalid_command for a bad timeout or
|
|
228
|
+
device spec, invalid_reference unless exactly one
|
|
229
|
+
readable spec source is given, missing_dependency
|
|
230
|
+
for an unresolvable sim python.
|
|
231
|
+
"""
|
|
232
|
+
if job.get("operation") != OPERATION:
|
|
233
|
+
raise AdapterError(
|
|
234
|
+
"unsupported_operation",
|
|
235
|
+
f"Meow handles {OPERATION}, got {job.get('operation')}.",
|
|
236
|
+
)
|
|
237
|
+
timeout = job.get("timeout_s", _TIMEOUT_S)
|
|
238
|
+
if (
|
|
239
|
+
not isinstance(timeout, (int, float))
|
|
240
|
+
or isinstance(timeout, bool)
|
|
241
|
+
or not timeout > 0
|
|
242
|
+
):
|
|
243
|
+
raise AdapterError("invalid_command", f"Bad timeout_s: {timeout!r}.")
|
|
244
|
+
sources = job.get("sources") or []
|
|
245
|
+
missing = [s for s in sources if not Path(s).is_file()]
|
|
246
|
+
if len(sources) != 1 or missing:
|
|
247
|
+
raise AdapterError(
|
|
248
|
+
"invalid_reference",
|
|
249
|
+
f"run_em needs one device spec, got: {missing or sources}.",
|
|
250
|
+
)
|
|
251
|
+
spec = sources[0]
|
|
252
|
+
text, doc = _load_spec(spec)
|
|
253
|
+
checked = {
|
|
254
|
+
"python": self._resolve(job),
|
|
255
|
+
"spec": spec,
|
|
256
|
+
"text": text,
|
|
257
|
+
"doc": doc,
|
|
258
|
+
"timeout_s": timeout,
|
|
259
|
+
}
|
|
260
|
+
if self._runner is not None:
|
|
261
|
+
try:
|
|
262
|
+
self._runner.preflight()
|
|
263
|
+
except ExecutionError as exc:
|
|
264
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
265
|
+
return checked
|
|
266
|
+
|
|
267
|
+
def execute(self, job):
|
|
268
|
+
"""Stage the driver plus job JSON, then run the sim.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
job: Solver job with operation run_em plus the device
|
|
272
|
+
spec source and timeout inputs.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Native output with exit_code, stdout/stderr, spec,
|
|
276
|
+
spec_text, and the parsed targets.
|
|
277
|
+
|
|
278
|
+
Raises:
|
|
279
|
+
AdapterError: invalid_reference for a missing workdir,
|
|
280
|
+
runner_unavailable on a sim timeout, plus the
|
|
281
|
+
preflight codes (execute re-checks first).
|
|
282
|
+
"""
|
|
283
|
+
checked = self.preflight(job)
|
|
284
|
+
workdir = job.get("workdir", ".")
|
|
285
|
+
if not Path(workdir).is_dir():
|
|
286
|
+
raise AdapterError(
|
|
287
|
+
"invalid_reference", f"Workdir not found: {workdir}."
|
|
288
|
+
)
|
|
289
|
+
workdir_path = Path(workdir)
|
|
290
|
+
driver = Path(__file__).with_name("_driver.py").read_text(
|
|
291
|
+
encoding="utf-8"
|
|
292
|
+
)
|
|
293
|
+
(workdir_path / _DRIVER_NAME).write_text(driver, encoding="utf-8")
|
|
294
|
+
(workdir_path / _JOB_NAME).write_text(
|
|
295
|
+
json.dumps(
|
|
296
|
+
{
|
|
297
|
+
"spec": checked["spec"],
|
|
298
|
+
"text": checked["text"],
|
|
299
|
+
"doc": checked["doc"],
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
encoding="utf-8",
|
|
303
|
+
)
|
|
304
|
+
roots = tool_roots([checked["python"]])
|
|
305
|
+
proc = self._run(
|
|
306
|
+
[checked["python"], _DRIVER_NAME, _JOB_NAME],
|
|
307
|
+
workdir,
|
|
308
|
+
checked["timeout_s"],
|
|
309
|
+
roots,
|
|
310
|
+
)
|
|
311
|
+
return {
|
|
312
|
+
"exit_code": proc.returncode,
|
|
313
|
+
"stdout": proc.stdout,
|
|
314
|
+
"stderr": proc.stderr,
|
|
315
|
+
"spec": checked["spec"],
|
|
316
|
+
"spec_text": checked["text"],
|
|
317
|
+
"targets": list(checked["doc"].get("targets") or []),
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
def parse(self, native_output):
|
|
321
|
+
"""Parse the driver envelope into S-params and convergence.
|
|
322
|
+
|
|
323
|
+
Malformed devices synthesize one error violation with
|
|
324
|
+
file/line rather than parsing as clean; S-parameter and
|
|
325
|
+
convergence bound misses carry engine-computed margins.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
native_output: Native output with the driver stdout
|
|
329
|
+
envelope, spec, spec_text, and targets.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
{total, violations[], measurements[], modes,
|
|
333
|
+
convergence, meow_version, tidy3d_version} with
|
|
334
|
+
ratio values and margins.
|
|
335
|
+
|
|
336
|
+
Raises:
|
|
337
|
+
AdapterError: missing_dependency when the sim python
|
|
338
|
+
has no meow, runner_unavailable when the driver
|
|
339
|
+
printed no JSON envelope or no result.
|
|
340
|
+
"""
|
|
341
|
+
spec = native_output.get("spec", "")
|
|
342
|
+
envelope = extract_envelope(native_output.get("stdout", ""))
|
|
343
|
+
if envelope is None:
|
|
344
|
+
raise AdapterError(
|
|
345
|
+
"runner_unavailable",
|
|
346
|
+
"meow driver printed no JSON envelope.",
|
|
347
|
+
)
|
|
348
|
+
if not isinstance(envelope, dict) or not isinstance(
|
|
349
|
+
envelope.get("ok"), bool
|
|
350
|
+
):
|
|
351
|
+
raise AdapterError(
|
|
352
|
+
"runner_unavailable",
|
|
353
|
+
"meow driver printed no JSON envelope.",
|
|
354
|
+
)
|
|
355
|
+
if not envelope["ok"]:
|
|
356
|
+
error = envelope.get("error") or {}
|
|
357
|
+
code = error.get("code")
|
|
358
|
+
message = error.get("message", "meow run failed")
|
|
359
|
+
if code == "missing_dependency":
|
|
360
|
+
raise AdapterError("missing_dependency", message)
|
|
361
|
+
if code != "invalid_device":
|
|
362
|
+
raise AdapterError(
|
|
363
|
+
"runner_unavailable",
|
|
364
|
+
f"meow driver failed without a device error: {message}.",
|
|
365
|
+
)
|
|
366
|
+
violation = {
|
|
367
|
+
"rule": "device",
|
|
368
|
+
"severity": "error",
|
|
369
|
+
"message": message,
|
|
370
|
+
"file": spec,
|
|
371
|
+
}
|
|
372
|
+
if error.get("line") is not None:
|
|
373
|
+
violation["line"] = error["line"]
|
|
374
|
+
return {
|
|
375
|
+
"total": 1,
|
|
376
|
+
"violations": [violation],
|
|
377
|
+
"measurements": [],
|
|
378
|
+
"meow_version": envelope.get("meow_version"),
|
|
379
|
+
"tidy3d_version": envelope.get("tidy3d_version"),
|
|
380
|
+
}
|
|
381
|
+
result = envelope.get("result") or {}
|
|
382
|
+
base = result.get("base") or {}
|
|
383
|
+
convergence = result.get("convergence") or {}
|
|
384
|
+
values = {
|
|
385
|
+
"transmission": base.get("transmission"),
|
|
386
|
+
"reflection": base.get("reflection"),
|
|
387
|
+
"convergence_dT": convergence.get("dT"),
|
|
388
|
+
"convergence_dneff": convergence.get("dneff"),
|
|
389
|
+
}
|
|
390
|
+
if not all(_is_number(v) for v in values.values()):
|
|
391
|
+
raise AdapterError(
|
|
392
|
+
"runner_unavailable",
|
|
393
|
+
"meow driver envelope has no result.",
|
|
394
|
+
)
|
|
395
|
+
violations = []
|
|
396
|
+
measurements = []
|
|
397
|
+
for target in native_output.get("targets") or []:
|
|
398
|
+
metric = target["metric"]
|
|
399
|
+
actual = float(values[metric])
|
|
400
|
+
bounds = {
|
|
401
|
+
key: target[key]
|
|
402
|
+
for key in ("min", "max")
|
|
403
|
+
if target.get(key) is not None
|
|
404
|
+
}
|
|
405
|
+
margins = {}
|
|
406
|
+
if "min" in bounds:
|
|
407
|
+
margins["min"] = actual - bounds["min"]
|
|
408
|
+
if "max" in bounds:
|
|
409
|
+
margins["max"] = bounds["max"] - actual
|
|
410
|
+
failed = [
|
|
411
|
+
f"{kind} {bounds[kind]}"
|
|
412
|
+
for kind, margin in margins.items()
|
|
413
|
+
if margin < 0
|
|
414
|
+
]
|
|
415
|
+
status = "fail" if failed else "pass"
|
|
416
|
+
margin = min(margins.values())
|
|
417
|
+
measurements.append(
|
|
418
|
+
{
|
|
419
|
+
"name": metric,
|
|
420
|
+
"value": actual,
|
|
421
|
+
"unit": "",
|
|
422
|
+
"target": dict(bounds),
|
|
423
|
+
"margin": margin,
|
|
424
|
+
"status": status,
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
if failed:
|
|
428
|
+
rule = (
|
|
429
|
+
"convergence"
|
|
430
|
+
if metric.startswith("convergence")
|
|
431
|
+
else "s_param"
|
|
432
|
+
)
|
|
433
|
+
violation = {
|
|
434
|
+
"rule": rule,
|
|
435
|
+
"severity": "error",
|
|
436
|
+
"message": (
|
|
437
|
+
f"{metric} = {actual:.6g} misses "
|
|
438
|
+
f"{', '.join(failed)} "
|
|
439
|
+
f"(margin {margin:.6g})."
|
|
440
|
+
),
|
|
441
|
+
"file": spec,
|
|
442
|
+
}
|
|
443
|
+
line = _target_line(native_output.get("spec_text"))
|
|
444
|
+
if line is not None:
|
|
445
|
+
violation["line"] = line
|
|
446
|
+
violations.append(violation)
|
|
447
|
+
return {
|
|
448
|
+
"total": len(violations),
|
|
449
|
+
"violations": violations,
|
|
450
|
+
"measurements": measurements,
|
|
451
|
+
"modes": {
|
|
452
|
+
"base": base,
|
|
453
|
+
"refined": result.get("refined") or {},
|
|
454
|
+
},
|
|
455
|
+
"convergence": dict(convergence),
|
|
456
|
+
"meow_version": envelope.get("meow_version"),
|
|
457
|
+
"tidy3d_version": envelope.get("tidy3d_version"),
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
@staticmethod
|
|
461
|
+
def _resolve(job):
|
|
462
|
+
tool_dir = job.get("tool_dir")
|
|
463
|
+
if tool_dir:
|
|
464
|
+
for name in ("python3", "python"):
|
|
465
|
+
candidate = Path(tool_dir) / name
|
|
466
|
+
if candidate.is_file():
|
|
467
|
+
return str(candidate)
|
|
468
|
+
raise AdapterError(
|
|
469
|
+
"missing_dependency", f"python not found in {tool_dir}."
|
|
470
|
+
)
|
|
471
|
+
return sys.executable
|
|
472
|
+
|
|
473
|
+
def _run(self, argv, workdir, timeout_s, extra_binds=()):
|
|
474
|
+
if self._runner is not None:
|
|
475
|
+
try:
|
|
476
|
+
return self._runner.run(argv, workdir, timeout_s, extra_binds)
|
|
477
|
+
except ExecutionError as exc:
|
|
478
|
+
raise AdapterError(exc.code, str(exc)) from exc
|
|
479
|
+
try:
|
|
480
|
+
return subprocess.run(
|
|
481
|
+
argv,
|
|
482
|
+
capture_output=True,
|
|
483
|
+
text=True,
|
|
484
|
+
timeout=timeout_s,
|
|
485
|
+
cwd=workdir,
|
|
486
|
+
)
|
|
487
|
+
except subprocess.TimeoutExpired as exc:
|
|
488
|
+
raise AdapterError(
|
|
489
|
+
"runner_unavailable",
|
|
490
|
+
f"meow sim timed out after {timeout_s}s.",
|
|
491
|
+
) from exc
|