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.
Files changed (241) hide show
  1. rocket_engine/__init__.py +8 -0
  2. rocket_engine/__main__.py +12 -0
  3. rocket_engine/adapters/README.md +54 -0
  4. rocket_engine/adapters/__init__.py +7 -0
  5. rocket_engine/adapters/api.py +39 -0
  6. rocket_engine/adapters/base.py +46 -0
  7. rocket_engine/adapters/cace/__init__.py +163 -0
  8. rocket_engine/adapters/circulax/README.md +10 -0
  9. rocket_engine/adapters/circulax/__init__.py +112 -0
  10. rocket_engine/adapters/circulax/_driver.py +190 -0
  11. rocket_engine/adapters/circulax/adapter.py +538 -0
  12. rocket_engine/adapters/circulax/results.py +291 -0
  13. rocket_engine/adapters/cli/README.md +34 -0
  14. rocket_engine/adapters/cli/__init__.py +305 -0
  15. rocket_engine/adapters/connectivity/__init__.py +196 -0
  16. rocket_engine/adapters/connectivity/results.py +214 -0
  17. rocket_engine/adapters/cosim/README.md +9 -0
  18. rocket_engine/adapters/cosim/__init__.py +1390 -0
  19. rocket_engine/adapters/cosim/results.py +214 -0
  20. rocket_engine/adapters/devsim/__init__.py +171 -0
  21. rocket_engine/adapters/digital_flow/README.md +9 -0
  22. rocket_engine/adapters/digital_flow/__init__.py +1121 -0
  23. rocket_engine/adapters/digital_flow/results.py +227 -0
  24. rocket_engine/adapters/digital_flow/state.py +462 -0
  25. rocket_engine/adapters/drc/README.klayout.md +7 -0
  26. rocket_engine/adapters/drc/README.md +7 -0
  27. rocket_engine/adapters/drc/__init__.py +1 -0
  28. rocket_engine/adapters/drc/klayout.py +952 -0
  29. rocket_engine/adapters/drc/magic.py +287 -0
  30. rocket_engine/adapters/drc/results.py +184 -0
  31. rocket_engine/adapters/drc/results_kl.py +188 -0
  32. rocket_engine/adapters/elmer/__init__.py +163 -0
  33. rocket_engine/adapters/envelope.py +120 -0
  34. rocket_engine/adapters/equivalence/__init__.py +156 -0
  35. rocket_engine/adapters/errors.py +181 -0
  36. rocket_engine/adapters/extract_magic/README.md +15 -0
  37. rocket_engine/adapters/extract_magic/__init__.py +425 -0
  38. rocket_engine/adapters/extract_magic/results.py +209 -0
  39. rocket_engine/adapters/formal_sby/README.md +7 -0
  40. rocket_engine/adapters/formal_sby/__init__.py +773 -0
  41. rocket_engine/adapters/formal_sby/formal_edge.py +195 -0
  42. rocket_engine/adapters/formal_sby/results.py +185 -0
  43. rocket_engine/adapters/gdsfactory/__init__.py +194 -0
  44. rocket_engine/adapters/gdsfactory/_driver.py +459 -0
  45. rocket_engine/adapters/gdsfactory/adapter.py +609 -0
  46. rocket_engine/adapters/gdsfactory/results.py +181 -0
  47. rocket_engine/adapters/hdl_sim/README.md +7 -0
  48. rocket_engine/adapters/hdl_sim/__init__.py +369 -0
  49. rocket_engine/adapters/hdl_sim/ghdl.py +155 -0
  50. rocket_engine/adapters/hdl_sim/results.py +178 -0
  51. rocket_engine/adapters/hdl_sim/verilator.py +167 -0
  52. rocket_engine/adapters/http/README.md +15 -0
  53. rocket_engine/adapters/http/__init__.py +458 -0
  54. rocket_engine/adapters/http/events.py +63 -0
  55. rocket_engine/adapters/ipc/README.md +9 -0
  56. rocket_engine/adapters/ipc/__init__.py +111 -0
  57. rocket_engine/adapters/lvs_netgen/README.md +20 -0
  58. rocket_engine/adapters/lvs_netgen/__init__.py +889 -0
  59. rocket_engine/adapters/lvs_netgen/results.py +168 -0
  60. rocket_engine/adapters/mcp/README.md +19 -0
  61. rocket_engine/adapters/mcp/__init__.py +85 -0
  62. rocket_engine/adapters/mcp/api-schema.json +6107 -0
  63. rocket_engine/adapters/mcp/pull.py +246 -0
  64. rocket_engine/adapters/mcp/server.py +794 -0
  65. rocket_engine/adapters/meep/__init__.py +167 -0
  66. rocket_engine/adapters/meow/README.md +7 -0
  67. rocket_engine/adapters/meow/__init__.py +190 -0
  68. rocket_engine/adapters/meow/_driver.py +227 -0
  69. rocket_engine/adapters/meow/_envelope.py +108 -0
  70. rocket_engine/adapters/meow/adapter.py +491 -0
  71. rocket_engine/adapters/meow/results.py +218 -0
  72. rocket_engine/adapters/mutation/__init__.py +192 -0
  73. rocket_engine/adapters/ngspice/README.md +28 -0
  74. rocket_engine/adapters/ngspice/__init__.py +627 -0
  75. rocket_engine/adapters/ngspice/results.py +215 -0
  76. rocket_engine/adapters/postlayout/README.md +28 -0
  77. rocket_engine/adapters/postlayout/__init__.py +17 -0
  78. rocket_engine/adapters/postlayout/adapter.py +1062 -0
  79. rocket_engine/adapters/postlayout/results.py +330 -0
  80. rocket_engine/adapters/postlayout/translate.py +71 -0
  81. rocket_engine/adapters/profile.py +220 -0
  82. rocket_engine/adapters/sax/README.md +23 -0
  83. rocket_engine/adapters/sax/__init__.py +516 -0
  84. rocket_engine/adapters/sax/_driver.py +148 -0
  85. rocket_engine/adapters/sax/results.py +291 -0
  86. rocket_engine/adapters/stdio_console.py +1151 -0
  87. rocket_engine/adapters/syntax/README.md +5 -0
  88. rocket_engine/adapters/syntax/__init__.py +366 -0
  89. rocket_engine/adapters/syntax/results.py +181 -0
  90. rocket_engine/adapters/synth_yosys/README.md +7 -0
  91. rocket_engine/adapters/synth_yosys/__init__.py +655 -0
  92. rocket_engine/adapters/synth_yosys/amaranth.py +120 -0
  93. rocket_engine/adapters/synth_yosys/ghdl.py +128 -0
  94. rocket_engine/adapters/synth_yosys/results.py +173 -0
  95. rocket_engine/adapters/timing_sta/README.md +9 -0
  96. rocket_engine/adapters/timing_sta/__init__.py +726 -0
  97. rocket_engine/adapters/view_sync/__init__.py +185 -0
  98. rocket_engine/adapters/view_sync/results.py +189 -0
  99. rocket_engine/adapters/vlsir/__init__.py +142 -0
  100. rocket_engine/adapters/xschem/__init__.py +440 -0
  101. rocket_engine/adapters/xschem/deck.py +266 -0
  102. rocket_engine/clients/__init__.py +5 -0
  103. rocket_engine/clients/runtime.py +255 -0
  104. rocket_engine/composition.py +2279 -0
  105. rocket_engine/execution/README.md +30 -0
  106. rocket_engine/execution/__init__.py +150 -0
  107. rocket_engine/execution/apig/__init__.py +1 -0
  108. rocket_engine/execution/apig/pool.py +463 -0
  109. rocket_engine/execution/apig/submit.py +124 -0
  110. rocket_engine/execution/block_runs_scope.py +479 -0
  111. rocket_engine/execution/coalesce.py +414 -0
  112. rocket_engine/execution/console/__init__.py +1 -0
  113. rocket_engine/execution/console/apply_session.py +318 -0
  114. rocket_engine/execution/console/lifecycle.py +265 -0
  115. rocket_engine/execution/dispatch.py +205 -0
  116. rocket_engine/execution/errors.py +31 -0
  117. rocket_engine/execution/ports.py +797 -0
  118. rocket_engine/execution/pyexec/__init__.py +1 -0
  119. rocket_engine/execution/pyexec/apply_context.py +547 -0
  120. rocket_engine/execution/pyexec/approvals.py +173 -0
  121. rocket_engine/execution/pyexec/runner.py +404 -0
  122. rocket_engine/execution/pyexec/support.py +464 -0
  123. rocket_engine/execution/python_exec.py +897 -0
  124. rocket_engine/execution/queue.py +85 -0
  125. rocket_engine/execution/replay_safe.py +601 -0
  126. rocket_engine/execution/routing/__init__.py +1 -0
  127. rocket_engine/execution/routing/calls.py +364 -0
  128. rocket_engine/execution/routing/namespace.py +785 -0
  129. rocket_engine/execution/routing/ports.py +102 -0
  130. rocket_engine/execution/routing/tools.py +346 -0
  131. rocket_engine/execution/runner.py +311 -0
  132. rocket_engine/execution/runtime.py +1149 -0
  133. rocket_engine/execution/service.py +279 -0
  134. rocket_engine/execution/worker.py +464 -0
  135. rocket_engine/interfaces/README.md +26 -0
  136. rocket_engine/interfaces/__init__.py +58 -0
  137. rocket_engine/interfaces/apig_assets.py +407 -0
  138. rocket_engine/interfaces/celllink.py +461 -0
  139. rocket_engine/interfaces/documents.py +92 -0
  140. rocket_engine/interfaces/envelopes.py +131 -0
  141. rocket_engine/interfaces/errors.py +16 -0
  142. rocket_engine/interfaces/layout.py +410 -0
  143. rocket_engine/interfaces/layout_schema.yaml +278 -0
  144. rocket_engine/interfaces/paths.py +223 -0
  145. rocket_engine/interfaces/pdks.py +550 -0
  146. rocket_engine/interfaces/ports.py +72 -0
  147. rocket_engine/interfaces/references.py +115 -0
  148. rocket_engine/interfaces/refs.py +85 -0
  149. rocket_engine/interfaces/runparams.py +304 -0
  150. rocket_engine/interfaces/setup.py +552 -0
  151. rocket_engine/interfaces/setup_catalogue.yaml +243 -0
  152. rocket_engine/interfaces/setup_probes.py +66 -0
  153. rocket_engine/interfaces/vocabulary.py +58 -0
  154. rocket_engine/interfaces/yaml_dump.py +31 -0
  155. rocket_engine/library/README.md +22 -0
  156. rocket_engine/library/__init__.py +13 -0
  157. rocket_engine/library/apig_catalog.py +371 -0
  158. rocket_engine/library/catalog.py +185 -0
  159. rocket_engine/library/errors.py +16 -0
  160. rocket_engine/mutate/__init__.py +776 -0
  161. rocket_engine/mutate/operators.py +700 -0
  162. rocket_engine/ops/__init__.py +1 -0
  163. rocket_engine/ops/deferred.py +279 -0
  164. rocket_engine/ops/editors.py +404 -0
  165. rocket_engine/ops/evidence.py +194 -0
  166. rocket_engine/ops/gates.py +262 -0
  167. rocket_engine/ops/ports.py +76 -0
  168. rocket_engine/ops/queries.py +417 -0
  169. rocket_engine/ops/runs.py +553 -0
  170. rocket_engine/ops/submit.py +614 -0
  171. rocket_engine/project/README.md +27 -0
  172. rocket_engine/project/__init__.py +6 -0
  173. rocket_engine/project/canvas/__init__.py +55 -0
  174. rocket_engine/project/canvas/compile.py +109 -0
  175. rocket_engine/project/canvas/integrate.py +48 -0
  176. rocket_engine/project/canvas/schema.py +241 -0
  177. rocket_engine/project/canvas/templates.py +46 -0
  178. rocket_engine/project/canvas/validate.py +603 -0
  179. rocket_engine/project/edit/__init__.py +5 -0
  180. rocket_engine/project/edit/cas.py +87 -0
  181. rocket_engine/project/edit/transactions.py +282 -0
  182. rocket_engine/project/errors.py +16 -0
  183. rocket_engine/project/frontend13.py +685 -0
  184. rocket_engine/project/geometry/__init__.py +23 -0
  185. rocket_engine/project/geometry/ops.py +451 -0
  186. rocket_engine/project/geometry/snap.py +103 -0
  187. rocket_engine/project/ip/__init__.py +1 -0
  188. rocket_engine/project/ip/blocks.py +195 -0
  189. rocket_engine/project/ip/entry.schema.json +78 -0
  190. rocket_engine/project/ip/lifecycle.py +402 -0
  191. rocket_engine/project/ip/verbs.py +123 -0
  192. rocket_engine/project/layers/__init__.py +6 -0
  193. rocket_engine/project/layers/bindings.py +115 -0
  194. rocket_engine/project/layers/catalog.py +228 -0
  195. rocket_engine/project/layers/presets.py +43 -0
  196. rocket_engine/project/ledger/__init__.py +1 -0
  197. rocket_engine/project/ledger/docs.py +167 -0
  198. rocket_engine/project/ledger/entities.py +176 -0
  199. rocket_engine/project/ledger/events.py +29 -0
  200. rocket_engine/project/ledger/runs.py +257 -0
  201. rocket_engine/project/ledger/shared.py +174 -0
  202. rocket_engine/project/ledger/snapshots.py +385 -0
  203. rocket_engine/project/ledger/wires.py +552 -0
  204. rocket_engine/project/process/__init__.py +14 -0
  205. rocket_engine/project/process/model.py +193 -0
  206. rocket_engine/project/process/recipes.py +121 -0
  207. rocket_engine/project/schematic/__init__.py +1 -0
  208. rocket_engine/project/schematic/hierarchy.py +312 -0
  209. rocket_engine/project/schematic/instances.py +393 -0
  210. rocket_engine/project/schematic/sheets.py +198 -0
  211. rocket_engine/project/service.py +2941 -0
  212. rocket_engine/project/symbols/__init__.py +1 -0
  213. rocket_engine/project/symbols/cells.py +324 -0
  214. rocket_engine/project/symbols/linkage.py +356 -0
  215. rocket_engine/project/symbols/symbols.py +352 -0
  216. rocket_engine/project/templates/__init__.py +303 -0
  217. rocket_engine/project/templates/errors.py +25 -0
  218. rocket_engine/project/testbench/__init__.py +49 -0
  219. rocket_engine/project/testbench/blocks.py +619 -0
  220. rocket_engine/project/testbench/templates.py +134 -0
  221. rocket_engine/stores/README.md +12 -0
  222. rocket_engine/stores/__init__.py +6 -0
  223. rocket_engine/stores/ecs.py +1508 -0
  224. rocket_engine/stores/layers.py +1023 -0
  225. rocket_engine/stores/settings.py +1188 -0
  226. rocket_engine/stores/wireplace.py +1324 -0
  227. rocket_engine/workflows/README.md +18 -0
  228. rocket_engine/workflows/__init__.py +53 -0
  229. rocket_engine/workflows/capability_plan.py +102 -0
  230. rocket_engine/workflows/completion.py +228 -0
  231. rocket_engine/workflows/connectgraph.py +734 -0
  232. rocket_engine/workflows/connectgraph_emit.py +336 -0
  233. rocket_engine/workflows/gates.py +144 -0
  234. rocket_engine/workflows/photonic_wire.py +76 -0
  235. rocket_engine/workflows/stages.py +324 -0
  236. rocket_engine/workflows/viewsync.py +399 -0
  237. rocket_engine-0.0.2b1.dist-info/METADATA +200 -0
  238. rocket_engine-0.0.2b1.dist-info/RECORD +241 -0
  239. rocket_engine-0.0.2b1.dist-info/WHEEL +4 -0
  240. rocket_engine-0.0.2b1.dist-info/entry_points.txt +2 -0
  241. rocket_engine-0.0.2b1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,330 @@
1
+ """Postlayout v1 normalizer: v0 parse output to a ResultBundle.
2
+
3
+ Maps the ``run_post_layout`` parse shape (``measurements[]`` with
4
+ pre/post ``comparison[]`` rows, ``waveforms[]`` with scale plus
5
+ samples, and ``sim_fail`` violations) onto typed v1 datasets,
6
+ views, measurements, and findings. Pure function of the parsed
7
+ output: no I/O, no tool calls, deterministic ids derived from the
8
+ run id. Long waveforms are stride-downsampled to the inline cap;
9
+ canonical units live on ``measurements[]``.
10
+ """
11
+
12
+ TASK = "run_post_layout"
13
+
14
+ _DIAG_CAP = 240
15
+ _INLINE_CAP = 1024
16
+
17
+ _SEVERITY = {"error": "error", "warning": "warning"}
18
+
19
+
20
+ def _cap(text):
21
+ """Cap one diagnostic string at the bundle prose limit.
22
+
23
+ Args:
24
+ text: Raw diagnostic text, possibly unbounded tool prose.
25
+
26
+ Returns:
27
+ The text unchanged when short, else truncated with an
28
+ ellipsis marker.
29
+ """
30
+ text = text if isinstance(text, str) else str(text)
31
+ if len(text) <= _DIAG_CAP:
32
+ return text
33
+ return text[: _DIAG_CAP - 3] + "..."
34
+
35
+
36
+ def _quantity(value, unit):
37
+ """Build one v1 Quantity, mapping unitless to dimensionless.
38
+
39
+ Args:
40
+ value: Numeric value.
41
+ unit: Unit string, possibly empty for unitless measures.
42
+
43
+ Returns:
44
+ Quantity mapping with an explicit unit.
45
+ """
46
+ return {"value": float(value), "unit": unit if unit else "1"}
47
+
48
+
49
+ def _limit(item):
50
+ """Pick the binding target bound for one measurement.
51
+
52
+ Args:
53
+ item: v0 measurement with a ``target`` min/max mapping.
54
+
55
+ Returns:
56
+ The bound closest to the measured value, or None when the
57
+ target is empty.
58
+ """
59
+ target = item.get("target") or {}
60
+ scored = []
61
+ if target.get("min") is not None:
62
+ scored.append((float(item["value"]) - float(target["min"]),
63
+ float(target["min"])))
64
+ if target.get("max") is not None:
65
+ scored.append((float(target["max"]) - float(item["value"]),
66
+ float(target["max"])))
67
+ if not scored:
68
+ return None
69
+ return min(scored)[1]
70
+
71
+
72
+ def _downsample(values):
73
+ """Stride-downsample one sample list to the inline cap.
74
+
75
+ Args:
76
+ values: Sample values in stored order.
77
+
78
+ Returns:
79
+ (samples, downsampled) pair with at most _INLINE_CAP
80
+ entries, stride-picked deterministically.
81
+ """
82
+ values = list(values)
83
+ if len(values) <= _INLINE_CAP:
84
+ return values, False
85
+ step = -(-len(values) // _INLINE_CAP)
86
+ return values[::step], True
87
+
88
+
89
+ def _provenance(project_id, run_id, tools, provenance):
90
+ """Build ResultProvenance, defaulting to a placeholder closure.
91
+
92
+ Args:
93
+ project_id: Project the run belongs to.
94
+ run_id: Run the bundle describes.
95
+ tools: Tool ingredient mappings (id, version, digest?).
96
+ provenance: Full provenance mapping when the caller has
97
+ one; a deterministic placeholder otherwise.
98
+
99
+ Returns:
100
+ Provenance mapping valid against the v1 schema.
101
+ """
102
+ if provenance is not None:
103
+ return provenance
104
+ ref = {"scope": "project", "id": project_id, "revision": "r1"}
105
+ return {
106
+ "snapshot": dict(ref),
107
+ "input_lock": dict(ref),
108
+ "closure_hash": "0" * 64,
109
+ "tools": [
110
+ {
111
+ "id": item["id"],
112
+ "version": item.get("version", ""),
113
+ "digest": item.get("digest", "0" * 64),
114
+ }
115
+ for item in tools
116
+ ],
117
+ "parents": [],
118
+ }
119
+
120
+
121
+ def normalize(parsed, *, run_id, project_id="project", target=None,
122
+ tools=(), provenance=None):
123
+ """Normalize one postlayout parse output to a v1 ResultBundle.
124
+
125
+ Measurement rows become a table dataset plus a bound table
126
+ view; comparison rows become a second table when present;
127
+ every waveform becomes a transient series dataset with an
128
+ explicit time axis plus a bound trace view; violations become
129
+ findings with capped messages.
130
+
131
+ Args:
132
+ parsed: v0 parse mapping with ``measurements[]``,
133
+ ``comparison[]``, ``waveforms[]``, and
134
+ ``violations[]``.
135
+ run_id: Run the bundle describes (anchors all ids).
136
+ project_id: Project the run belongs to.
137
+ target: v1 target mapping; project kind by default.
138
+ tools: Tool ingredient mappings for provenance.
139
+ provenance: Full provenance mapping when the caller has
140
+ one; a deterministic placeholder otherwise.
141
+
142
+ Returns:
143
+ Schema-valid ResultBundle mapping.
144
+ """
145
+ measurements_in = parsed.get("measurements") or []
146
+ comparison_in = parsed.get("comparison") or []
147
+ waveforms_in = parsed.get("waveforms") or []
148
+ violations = parsed.get("violations") or []
149
+ table_id = f"{run_id}:dataset:measurements"
150
+ datasets = [
151
+ {
152
+ "id": table_id,
153
+ "kind": "table",
154
+ "row_count": len(measurements_in),
155
+ "columns": [
156
+ {
157
+ "id": f"{table_id}:name",
158
+ "name": "name",
159
+ "dtype": "utf8",
160
+ "unit": "1",
161
+ "data": [item["name"] for item in measurements_in],
162
+ },
163
+ {
164
+ "id": f"{table_id}:value",
165
+ "name": "value",
166
+ "dtype": "float64",
167
+ "unit": "1",
168
+ "data": [float(item["value"]) for item in measurements_in],
169
+ },
170
+ {
171
+ "id": f"{table_id}:margin",
172
+ "name": "margin",
173
+ "dtype": "float64",
174
+ "unit": "1",
175
+ "data": [float(item["margin"]) for item in measurements_in],
176
+ },
177
+ {
178
+ "id": f"{table_id}:status",
179
+ "name": "status",
180
+ "dtype": "utf8",
181
+ "unit": "1",
182
+ "data": [item["status"] for item in measurements_in],
183
+ },
184
+ ],
185
+ "conditions": [],
186
+ }
187
+ ]
188
+ views = [
189
+ {
190
+ "id": f"{run_id}:view:measurements",
191
+ "kind": "table",
192
+ "dataset_id": table_id,
193
+ "name": "measurements",
194
+ }
195
+ ]
196
+ if comparison_in:
197
+ cmp_id = f"{run_id}:dataset:comparison"
198
+ datasets.append(
199
+ {
200
+ "id": cmp_id,
201
+ "kind": "table",
202
+ "row_count": len(comparison_in),
203
+ "columns": [
204
+ {
205
+ "id": f"{cmp_id}:name",
206
+ "name": "name",
207
+ "dtype": "utf8",
208
+ "unit": "1",
209
+ "data": [row["name"] for row in comparison_in],
210
+ },
211
+ {
212
+ "id": f"{cmp_id}:delta",
213
+ "name": "delta",
214
+ "dtype": "float64",
215
+ "unit": "1",
216
+ "data": [float(row["delta"]) for row in comparison_in],
217
+ },
218
+ {
219
+ "id": f"{cmp_id}:status",
220
+ "name": "status",
221
+ "dtype": "utf8",
222
+ "unit": "1",
223
+ "data": [row["status"] for row in comparison_in],
224
+ },
225
+ ],
226
+ "conditions": [],
227
+ }
228
+ )
229
+ views.append(
230
+ {
231
+ "id": f"{run_id}:view:comparison",
232
+ "kind": "table",
233
+ "dataset_id": cmp_id,
234
+ "name": "pre-post comparison",
235
+ }
236
+ )
237
+ for index, wave in enumerate(waveforms_in):
238
+ samples, _ = _downsample(wave.get("samples") or [])
239
+ scale, _ = _downsample(wave.get("scale") or [])
240
+ ds_id = f"{run_id}:dataset:waveform:{index}"
241
+ axis_id = f"{ds_id}:time"
242
+ sig_id = f"{ds_id}:signal"
243
+ datasets.append(
244
+ {
245
+ "id": ds_id,
246
+ "kind": "series",
247
+ "analysis": "tran",
248
+ "axes": [
249
+ {
250
+ "id": axis_id,
251
+ "name": "time",
252
+ "quantity": "time",
253
+ "unit": "s",
254
+ "kind": "continuous",
255
+ "scale": "linear",
256
+ "values": [float(v) for v in scale],
257
+ }
258
+ ],
259
+ "signals": [
260
+ {
261
+ "id": sig_id,
262
+ "name": wave.get("name", f"waveform_{index}"),
263
+ "quantity": "voltage",
264
+ "unit": "V",
265
+ "values": {
266
+ "storage": "inline",
267
+ "dtype": "float64",
268
+ "shape": [len(samples)],
269
+ "data": [float(v) for v in samples],
270
+ },
271
+ }
272
+ ],
273
+ "conditions": [],
274
+ }
275
+ )
276
+ views.append(
277
+ {
278
+ "id": f"{run_id}:view:waveform:{index}",
279
+ "kind": "traces",
280
+ "dataset_id": ds_id,
281
+ "name": wave.get("name", f"waveform_{index}"),
282
+ "x_axis": axis_id,
283
+ "traces": [
284
+ {"signal": sig_id, "transform": "identity",
285
+ "axis": "left"}
286
+ ],
287
+ }
288
+ )
289
+ measurements = []
290
+ for item in measurements_in:
291
+ entry = {
292
+ "name": item["name"],
293
+ "value": _quantity(item["value"], item.get("unit", "")),
294
+ "margin": _quantity(item["margin"], item.get("unit", "")),
295
+ "status": "PASS" if item["status"] == "pass" else "FAIL",
296
+ }
297
+ bound = _limit(item)
298
+ if bound is not None:
299
+ entry["limit"] = _quantity(bound, item.get("unit", ""))
300
+ measurements.append(entry)
301
+ findings = [
302
+ {
303
+ "id": f"{run_id}:finding:{index}",
304
+ "severity": _SEVERITY.get(v.get("severity"), "info"),
305
+ "code": v.get("rule", "sim_fail"),
306
+ "message": _cap(v.get("message", "")),
307
+ }
308
+ for index, v in enumerate(violations)
309
+ ]
310
+ converged = not any(v.get("rule") == "sim_fail" for v in violations)
311
+ return {
312
+ "schema": "rocketeda/results/v1",
313
+ "ref": {"scope": "project", "id": run_id, "revision": "r1"},
314
+ "run_id": run_id,
315
+ "task": TASK,
316
+ "target": dict(target)
317
+ if target is not None
318
+ else {"project_id": project_id, "kind": "project"},
319
+ "state": "complete",
320
+ "freshness": "current",
321
+ "convergence": "converged" if converged else "not_converged",
322
+ "provenance": _provenance(project_id, run_id, tools, provenance),
323
+ "datasets": datasets,
324
+ "views": views,
325
+ "measurements": measurements,
326
+ "findings": findings,
327
+ "artifacts": [],
328
+ "logs": [],
329
+ "gaps": [],
330
+ }
@@ -0,0 +1,71 @@
1
+ """KLT to vendor translation for the postlayout path."""
2
+
3
+ import re
4
+
5
+ from ..errors import AdapterError
6
+
7
+ _KLT_RE = re.compile(r"klt", re.IGNORECASE)
8
+ _DEVICE_RE = re.compile(r"^([XMRCL]\S*)\s+(\S+)\s+(\S+)\s+(\S+)(.*)$",
9
+ re.M)
10
+
11
+ _VENDOR_MAP = {
12
+ "sky130_klt_nmos": "sky130_fd_pr__nfet_01v8",
13
+ "sky130_klt_pmos": "sky130_fd_pr__pfet_01v8",
14
+ "klt_nmos": "nfet_01v8",
15
+ "klt_pmos": "pfet_01v8",
16
+ "klt_res": "resistor",
17
+ "klt_cap": "capacitor",
18
+ }
19
+
20
+
21
+ def is_klt_netlist(text):
22
+ """Report whether a netlist carries klt generic devices.
23
+
24
+ Args:
25
+ text: Netlist text under check.
26
+
27
+ Returns:
28
+ True when klt markers appear, else False.
29
+ """
30
+ return bool(_KLT_RE.search(text or ""))
31
+
32
+
33
+ def translate_klt_netlist(text, vendor="sky130"):
34
+ """Translate klt generic devices to vendor models.
35
+
36
+ Args:
37
+ text: Extracted netlist text with klt device classes.
38
+ vendor: Vendor family the models translate to.
39
+
40
+ Returns:
41
+ (translated, mapping) with the translated netlist plus the
42
+ device rename rows.
43
+
44
+ Raises:
45
+ AdapterError: unsupported_operation when a klt device has
46
+ no vendor mapping — loud refusal naming the gap, never
47
+ a silent sim_fail.
48
+ """
49
+ mapping = []
50
+ out_lines = []
51
+ for line in (text or "").splitlines():
52
+ mapped = line
53
+ lowered = line.lower()
54
+ if "klt" in lowered:
55
+ hit = None
56
+ for klt, vend in _VENDOR_MAP.items():
57
+ if klt in lowered:
58
+ hit = (klt, vend)
59
+ break
60
+ if hit is None:
61
+ raise AdapterError(
62
+ "unsupported_operation",
63
+ f"KLT device has no {vendor} vendor mapping: "
64
+ f"{line.strip()[:160]} (POSTLAYOUT-REAL gap).",
65
+ )
66
+ klt, vend = hit
67
+ pattern = re.compile(re.escape(klt), re.IGNORECASE)
68
+ mapped = pattern.sub(vend, line)
69
+ mapping.append({"from": klt, "to": vend, "vendor": vendor})
70
+ out_lines.append(mapped)
71
+ return "\n".join(out_lines) + ("\n" if out_lines else ""), mapping
@@ -0,0 +1,220 @@
1
+ """PDK profile records: data-driven simulator resolution (PDK-PROFILE).
2
+
3
+ Adapters read PDK profile records (data), never PDK special-cases
4
+ (code): per-PDK simulator needs (model relpaths, corner names,
5
+ spiceinit lines) live in profile data under docs/pdk/<lane>/,
6
+ with a builtin copy here for contexts where the docs tree is not
7
+ shipped. New PDKs arrive as profile entries, never adapter edits.
8
+
9
+ Shape follows the PDK-PROFILE direction lock: {gdsfactory package
10
+ pin, KLayout tech, decks, models}. The `sim` section carries what
11
+ a SPICE adapter needs: `model_rel` (default deck relpath under
12
+ the registered store entry), `corners` (valid corner names), and
13
+ `spiceinit_extra` (PDK-only .spiceinit lines beyond the base).
14
+ """
15
+
16
+ from pathlib import Path
17
+
18
+ from ruamel.yaml import YAML
19
+
20
+ from ..interfaces.paths import slugify
21
+ from .errors import AdapterError
22
+
23
+ PROFILE_SCHEMA = "rocketeda/pdk-profile/v0.1"
24
+
25
+ _BASE_SPICEINIT = (
26
+ "set ngbehavior=hsa ; HSPICE-ish compatibility for PDK model libs",
27
+ "set ng_nomodcheck ; do not check model parameters",
28
+ "option noinit ; do not dump operating point data into the log",
29
+ "option klu ; KLU direct linear solver",
30
+ )
31
+
32
+ #: Builtin sim record, mirroring docs/pdk/sky130a/profile.yaml
33
+ #: (the authority). Used only when the docs tree is not beside
34
+ #: the package (installed wheels ship code, not docs/).
35
+ _BUILTIN_SIM = {
36
+ "sky130a": {
37
+ "model_rel": "libs.tech/combined/sky130.lib.spice",
38
+ "corners": ["tt", "ss", "ff", "sf", "fs"],
39
+ "spiceinit_extra": [
40
+ "set skywaterpdk ; skip some checks for faster lib loading",
41
+ ],
42
+ },
43
+ }
44
+
45
+
46
+ def _checkout_root():
47
+ """Engine checkout root, or None outside a source tree.
48
+
49
+ Returns:
50
+ Checkout root when this file sits inside one (a
51
+ pyproject marker beside src/), else None.
52
+ """
53
+ root = Path(__file__).resolve().parents[2]
54
+ if (root / "pyproject.toml").is_file():
55
+ return root
56
+ return None
57
+
58
+
59
+ def lane_profile_path(pdk_id):
60
+ """Profile file for one PDK lane, when the docs tree is present.
61
+
62
+ Args:
63
+ pdk_id: Canonical PDK display id (either spelling resolves).
64
+
65
+ Returns:
66
+ Profile path, or None when the id is missing, when no
67
+ checkout docs tree is beside the package, or when the lane
68
+ file is absent.
69
+ """
70
+ if not pdk_id or not isinstance(pdk_id, str):
71
+ return None
72
+ root = _checkout_root()
73
+ if root is None:
74
+ return None
75
+ candidate = root / "docs" / "pdk" / slugify(pdk_id) / "profile.yaml"
76
+ if candidate.is_file():
77
+ return candidate
78
+ return None
79
+
80
+
81
+ def load_profile_record(path):
82
+ """Load and validate one lane profile file.
83
+
84
+ Args:
85
+ path: Profile YAML path.
86
+
87
+ Returns:
88
+ Validated profile mapping.
89
+
90
+ Raises:
91
+ AdapterError: invalid_profile for an unreadable file or a
92
+ record failing schema validation.
93
+ """
94
+ try:
95
+ data = YAML(typ="safe").load(Path(path).read_text())
96
+ except OSError as exc:
97
+ raise AdapterError(
98
+ "invalid_profile", f"Cannot read PDK profile {path}: {exc}."
99
+ ) from exc
100
+ return validate_record(data, source=str(path))
101
+
102
+
103
+ def validate_record(record, source="record"):
104
+ """Validate one profile mapping against the profile schema.
105
+
106
+ Args:
107
+ record: Profile mapping under check.
108
+ source: Where the record came from (for messages).
109
+
110
+ Returns:
111
+ The same mapping, validated.
112
+
113
+ Raises:
114
+ AdapterError: invalid_profile for a non-mapping record, a
115
+ wrong schema version, or a missing id.
116
+ """
117
+ if not isinstance(record, dict):
118
+ raise AdapterError(
119
+ "invalid_profile", f"PDK profile must be a mapping: {source}."
120
+ )
121
+ if record.get("schema") != PROFILE_SCHEMA:
122
+ raise AdapterError(
123
+ "invalid_profile",
124
+ f"PDK profile {source} must carry schema {PROFILE_SCHEMA}.",
125
+ )
126
+ if not record.get("id") or not isinstance(record.get("id"), str):
127
+ raise AdapterError(
128
+ "invalid_profile", f"PDK profile {source} misses id."
129
+ )
130
+ return record
131
+
132
+
133
+ def sim_for(pdk_id, record=None):
134
+ """Sim record for one PDK: lane file first, builtin next.
135
+
136
+ Args:
137
+ pdk_id: Canonical PDK display id (either spelling resolves).
138
+ record: Optional already-loaded profile mapping, winning
139
+ over the lane file and the builtin.
140
+
141
+ Returns:
142
+ Sim mapping (model_rel, corners, spiceinit_extra), or None
143
+ when the PDK declares no sim profile.
144
+ """
145
+ if isinstance(record, dict):
146
+ sim = record.get("sim")
147
+ if isinstance(sim, dict):
148
+ return sim
149
+ if not pdk_id or not isinstance(pdk_id, str):
150
+ return None
151
+ path = lane_profile_path(pdk_id)
152
+ if path is not None:
153
+ try:
154
+ sim = load_profile_record(path).get("sim")
155
+ except AdapterError:
156
+ sim = None
157
+ if isinstance(sim, dict):
158
+ return sim
159
+ return _BUILTIN_SIM.get(slugify(pdk_id))
160
+
161
+
162
+ def default_model_rel(pdk_id, record=None):
163
+ """Default store model relpath for one PDK, if profiled.
164
+
165
+ Args:
166
+ pdk_id: Canonical PDK display id (either spelling resolves).
167
+ record: Optional already-loaded profile mapping.
168
+
169
+ Returns:
170
+ Default model relpath, or None when the PDK declares no
171
+ sim profile (callers fail loudly, never guess a relpath).
172
+ """
173
+ sim = sim_for(pdk_id, record=record)
174
+ if not isinstance(sim, dict):
175
+ return None
176
+ rel = sim.get("model_rel")
177
+ return rel if isinstance(rel, str) and rel else None
178
+
179
+
180
+ def base_spiceinit():
181
+ """PDK-neutral .spiceinit lines every SPICE run stages.
182
+
183
+ Returns:
184
+ Base .spiceinit lines (no PDK-only flags; those ride the
185
+ profile `spiceinit_extra`).
186
+ """
187
+ return list(_BASE_SPICEINIT)
188
+
189
+
190
+ def spiceinit_for(pdk_id, record=None):
191
+ """Full .spiceinit lines for one PDK: base plus profile extras.
192
+
193
+ Args:
194
+ pdk_id: Canonical PDK display id (either spelling resolves).
195
+ record: Optional already-loaded profile mapping.
196
+
197
+ Returns:
198
+ .spiceinit lines; base only when the PDK declares no sim
199
+ profile.
200
+ """
201
+ lines = base_spiceinit()
202
+ sim = sim_for(pdk_id, record=record)
203
+ if isinstance(sim, dict):
204
+ for line in sim.get("spiceinit_extra") or []:
205
+ if isinstance(line, str) and line and line not in lines:
206
+ lines.append(line)
207
+ return lines
208
+
209
+
210
+ def render_spiceinit(lines):
211
+ """Render .spiceinit text from staged lines.
212
+
213
+ Args:
214
+ lines: .spiceinit lines (base plus profile extras).
215
+
216
+ Returns:
217
+ .spiceinit file text with a profile-driven header.
218
+ """
219
+ head = "* rocket_engine ngspice init (profile-driven: see docs/pdk/)."
220
+ return head + "\n" + "\n".join(lines) + "\n"
@@ -0,0 +1,23 @@
1
+ # adapters/sax — SAX photonic sim solver
2
+
3
+ `run_optical` behind the `ToolAdapter` port: preflight refuses the
4
+ wrong operation, bad timeouts, missing/extra sources, unparseable
5
+ specs, and unresolvable interpreters; execute stages the sim driver
6
+ plus a job JSON into the workdir and runs
7
+ `python sax_driver.py sax_job.json`; parse normalizes the JSON
8
+ envelope into `{total, violations[], measurements[], spectra[],
9
+ sax_version}` with file/line locations where meaningful.
10
+
11
+ Verified against sax 0.18.2 on MZI/ring fixtures
12
+ (`tests/adapters/fixtures/sax/`). The balanced MZI reproduces its
13
+ documented transmission exactly (cross 1.0, bar 0.0); the mistuned
14
+ MZI misses its target with an engine-computed margin; a malformed
15
+ circuit synthesizes an error violation naming the offender with
16
+ file + line rather than going clean. A sax-less interpreter
17
+ raises `missing_dependency`, never a fake verdict.
18
+
19
+ Tool resolution is the engine interpreter (sax arrives via the pip
20
+ `photonic` group) plus an explicit job `tool_dir` holding an
21
+ alternate python; where the tools live is B10 scope. Registered in
22
+ the Engine as `run_optical` → `sax` per F8, so `deck.tool: sax`
23
+ selects it and a missing hint falls back to it as the default.