qcoder 0.1.0a0__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 (62) hide show
  1. qcoder/__init__.py +3 -0
  2. qcoder/__main__.py +6 -0
  3. qcoder/cli.py +116 -0
  4. qcoder/core/__init__.py +1 -0
  5. qcoder/core/context.py +16 -0
  6. qcoder/core/qasm2/__init__.py +1 -0
  7. qcoder/core/qasm2/adjoint_eligibility.py +128 -0
  8. qcoder/core/qasm2/mirror_build.py +234 -0
  9. qcoder/core/run_config.py +84 -0
  10. qcoder/core/schema.py +26 -0
  11. qcoder/engines/feature_extraction/adapters/__init__.py +1 -0
  12. qcoder/engines/feature_extraction/adapters/qiskit_intake.py +46 -0
  13. qcoder/engines/feature_extraction/extractor.py +43 -0
  14. qcoder/engines/feature_extraction/features/compute_v0.py +157 -0
  15. qcoder/engines/feature_extraction/features/schema_v0.py +84 -0
  16. qcoder/engines/feature_extraction/ir.py +41 -0
  17. qcoder/engines/feature_extraction/labeling.py +68 -0
  18. qcoder/engines/feature_extraction/parsers/__init__.py +21 -0
  19. qcoder/engines/feature_extraction/qasm2_regex_parser.py +184 -0
  20. qcoder/engines/feature_extraction/reps/cut_profile.py +106 -0
  21. qcoder/engines/feature_extraction/reps/depth.py +47 -0
  22. qcoder/engines/feature_extraction/reps/entangling_layers.py +57 -0
  23. qcoder/engines/feature_extraction/reps/gate_set_stats.py +82 -0
  24. qcoder/engines/feature_extraction/reps/interaction_graph.py +30 -0
  25. qcoder/engines/feature_extraction/reps/interaction_graph_metrics.py +113 -0
  26. qcoder/engines/feature_extraction/reps/spans.py +89 -0
  27. qcoder/engines/prediction_model/__init__.py +16 -0
  28. qcoder/engines/prediction_model/artifact.py +85 -0
  29. qcoder/engines/prediction_model/engine.py +209 -0
  30. qcoder/engines/prediction_model/models.py +62 -0
  31. qcoder/engines/prediction_model/policy.py +45 -0
  32. qcoder/engines/prediction_model/schema_alignment.py +41 -0
  33. qcoder/engines/quantumness/__init__.py +8 -0
  34. qcoder/engines/quantumness/scorer.py +254 -0
  35. qcoder/pipelines/analyze.py +131 -0
  36. qcoder/pipelines/batch.py +56 -0
  37. qcoder/tools/analyze.py +88 -0
  38. qcoder/tools/analyze_shot_scaling.py +239 -0
  39. qcoder/tools/batch.py +39 -0
  40. qcoder/tools/generate_corpus.py +491 -0
  41. qcoder/tools/harness.py +15 -0
  42. qcoder/tools/inspect_corpus_features.py +273 -0
  43. qcoder/tools/join_runs_features.py +252 -0
  44. qcoder/tools/mirror.py +15 -0
  45. qcoder/tools/predict_baseline.py +347 -0
  46. qcoder/tools/qr_dll_bootstrap.py +31 -0
  47. qcoder/tools/runner.py +15 -0
  48. qcoder/tools/runners/__init__.py +1 -0
  49. qcoder/tools/runners/quantum_rings/__init__.py +1 -0
  50. qcoder/tools/runners/quantum_rings/v12/__init__.py +1 -0
  51. qcoder/tools/runners/quantum_rings/v12/harness.py +1350 -0
  52. qcoder/tools/runners/quantum_rings/v12/mirror.py +459 -0
  53. qcoder/tools/runners/quantum_rings/v12/runner.py +549 -0
  54. qcoder/tools/train_baseline_models.py +619 -0
  55. qcoder/tools/validate_baseline.py +307 -0
  56. qcoder-0.1.0a0.dist-info/METADATA +86 -0
  57. qcoder-0.1.0a0.dist-info/RECORD +62 -0
  58. qcoder-0.1.0a0.dist-info/WHEEL +5 -0
  59. qcoder-0.1.0a0.dist-info/entry_points.txt +2 -0
  60. qcoder-0.1.0a0.dist-info/licenses/LICENSE +201 -0
  61. qcoder-0.1.0a0.dist-info/licenses/NOTICE +11 -0
  62. qcoder-0.1.0a0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1350 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ qcoder.tools.harness
4
+
5
+ QCoder harness: Adaptive Threshold (2× ladder) + Benchmark + Aggregate Report
6
+
7
+ Pipeline per (circuit, backend, precision):
8
+ 1) Mirror sweep on threshold ladder using qcoder.tools.mirror until target metric is met (or exhausted)
9
+ - Optional: runner timing probe at each tested threshold (shots_probe) with JSON immediately deleted
10
+ 2) Select threshold = first meeting target (or fallback)
11
+ 3) (Optional) Verification mirror at selected threshold using counts-based mirror (qasm_counts) to report p_return_zero
12
+ 4) Benchmark original circuit with qcoder.tools.runner at selected threshold:
13
+ - shots_state (default 1) : proxy for state/setup time
14
+ - shots_hist (default 10000): histogram runtime (JSON kept per policy)
15
+ 5) Write ONE aggregate report JSON for the whole harness run
16
+ 6) Retention policy:
17
+ - Mirror JSONs: keep bracketing + selected (default)
18
+ - Runner JSONs: keep hist-only (default: keep shots_hist JSON, delete shots_state JSON)
19
+ - Probe JSONs: always deleted (metrics stored in report only)
20
+
21
+ Notes:
22
+ - Harness invokes your scripts via subprocess; it does NOT import QuantumRingsLib.
23
+ - This avoids DLL path issues; qr_dll_bootstrap runs inside each script.
24
+
25
+ New capabilities:
26
+ - --mirror-mode auto|qasm_counts|sdk_inverse_fidelity
27
+ auto => sdk_inverse_fidelity when --mirror-metric=sdk_get_fidelity else qasm_counts
28
+ - Timeouts per subprocess call (mirror/runner/probe)
29
+ - Optional verification mirror (--mirror-verify) to report both methods while using sdk_inverse_fidelity for selection
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import csv
36
+ import datetime as _dt
37
+ import glob
38
+ import hashlib
39
+ import json
40
+ import os
41
+ import re
42
+ import subprocess
43
+ import sys
44
+ from dataclasses import dataclass
45
+ from typing import Any, Dict, List, Optional, Tuple
46
+
47
+
48
+ # -----------------------------
49
+ # Utility
50
+ # -----------------------------
51
+
52
+ def die(msg: str, code: int = 1) -> None:
53
+ print(f"[fatal] {msg}", file=sys.stderr, flush=True)
54
+ sys.exit(code)
55
+
56
+ def utc_now_iso() -> str:
57
+ return _dt.datetime.now(_dt.timezone.utc).replace(microsecond=0).isoformat()
58
+
59
+ def ensure_dir(p: str) -> None:
60
+ os.makedirs(p, exist_ok=True)
61
+
62
+ def sha256_file(path: str, chunk_size: int = 1 << 20) -> str:
63
+ h = hashlib.sha256()
64
+ with open(path, "rb") as f:
65
+ while True:
66
+ b = f.read(chunk_size)
67
+ if not b:
68
+ break
69
+ h.update(b)
70
+ return h.hexdigest()
71
+
72
+ def read_text(path: str) -> str:
73
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
74
+ return f.read()
75
+
76
+ def safe_unlink(path: str) -> None:
77
+ try:
78
+ os.remove(path)
79
+ except FileNotFoundError:
80
+ return
81
+ except Exception as e:
82
+ print(f"[warn] could not delete {path}: {e}", flush=True)
83
+
84
+ def run_subprocess(cmd: List[str], quiet: bool, timeout_s: Optional[float]) -> Tuple[int, str]:
85
+ """
86
+ Run command; return (returncode, combined stdout+stderr).
87
+ Timeout returns rc=124 and includes a [timeout] line in output.
88
+ """
89
+ if not quiet:
90
+ print("[cmd]", " ".join(cmd), flush=True)
91
+
92
+ t = None
93
+ if timeout_s is not None and float(timeout_s) > 0:
94
+ t = float(timeout_s)
95
+
96
+ try:
97
+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=t)
98
+ out = (p.stdout or "") + (p.stderr or "")
99
+ if not quiet and out.strip():
100
+ print(out.rstrip(), flush=True)
101
+ return p.returncode, out
102
+ except subprocess.TimeoutExpired as e:
103
+ def _to_text(x: Any) -> str:
104
+ if x is None:
105
+ return ""
106
+ if isinstance(x, bytes):
107
+ return x.decode("utf-8", errors="replace")
108
+ return str(x)
109
+
110
+ out = ""
111
+ out += _to_text(getattr(e, "stdout", None))
112
+ out += _to_text(getattr(e, "stderr", None))
113
+ out += f"\n[timeout] exceeded {t}s\n"
114
+ if not quiet:
115
+ print(out.rstrip(), flush=True)
116
+ return 124, out
117
+
118
+ def parse_json(path: str) -> Dict[str, Any]:
119
+ with open(path, "r", encoding="utf-8") as f:
120
+ return json.load(f)
121
+
122
+ def to_float(x) -> Optional[float]:
123
+ try:
124
+ if x is None:
125
+ return None
126
+ return float(x)
127
+ except Exception:
128
+ return None
129
+
130
+ def to_int(x) -> Optional[int]:
131
+ try:
132
+ if x is None:
133
+ return None
134
+ return int(x)
135
+ except Exception:
136
+ return None
137
+
138
+
139
+ def _truncate_error_message(out: str, max_len: int = 600) -> Optional[str]:
140
+ """Compact subprocess output into a bounded error message."""
141
+ text = (out or "").strip()
142
+ if not text:
143
+ return None
144
+ lines = [ln.strip() for ln in text.replace("\r", "\n").splitlines() if ln.strip()]
145
+ if not lines:
146
+ return None
147
+ msg = " | ".join(lines[-6:])
148
+ if len(msg) > max_len:
149
+ msg = "..." + msg[-(max_len - 3):]
150
+ return msg
151
+
152
+
153
+ CANONICAL_STATUSES = {
154
+ "ok",
155
+ "non_unitary",
156
+ "unsupported_gate",
157
+ "backend_not_found",
158
+ "parse_error",
159
+ "runner_error",
160
+ "timeout",
161
+ }
162
+
163
+
164
+ def classify_attempt_status(note: str, returncode: Optional[int], error_message: Optional[str]) -> Tuple[str, str, str]:
165
+ """
166
+ Map subprocess outcome to canonical status/error labels.
167
+ Returns (status, error_code, error_detail).
168
+ """
169
+ note_s = (note or "").strip()
170
+ msg = (error_message or "").strip()
171
+ msg_l = msg.lower()
172
+
173
+ if (returncode == 0) and note_s == "ok":
174
+ return "ok", "", ""
175
+ if returncode == 124 or "timeout" in note_s:
176
+ return "timeout", "timeout", msg or note_s or "timeout"
177
+ if "could not resolve backend" in msg_l:
178
+ return "backend_not_found", "backend_not_found", msg or note_s
179
+ if "mirror eligibility=non_unitary" in msg_l:
180
+ return "non_unitary", "non_unitary", msg or note_s
181
+ if "mirror eligibility=parse_error" in msg_l or "unrecognized statement" in msg_l:
182
+ return "parse_error", "parse_error", msg or note_s
183
+ if "unsupported gate for mirror" in msg_l or "unsupported include for mirror" in msg_l:
184
+ return "unsupported_gate", "unsupported_gate", msg or note_s
185
+
186
+ # Distinguish structured missing-json cases; otherwise generic runner_error.
187
+ if note_s in {"mirror_missing_json", "runner_missing_json"}:
188
+ return "runner_error", note_s, msg or note_s
189
+ return "runner_error", "runner_error", msg or note_s
190
+
191
+
192
+ # -----------------------------
193
+ # Quick QASM2 features (cheap heuristics)
194
+ # -----------------------------
195
+
196
+ _QREG_DECL_RE = re.compile(r'^\s*qreg\s+([A-Za-z_]\w*)\s*\[(\d+)\]\s*;', re.M)
197
+ _CREG_DECL_RE = re.compile(r'^\s*creg\s+([A-Za-z_]\w*)\s*\[(\d+)\]\s*;', re.M)
198
+ _MEASURE_RE = re.compile(r'^\s*measure\s+', re.I)
199
+
200
+ _TWOQ_MNEMONICS = ("cx", "cz", "swap", "rzz", "rxx", "ryy", "rzx", "crx", "cry", "crz", "cp", "cu1", "cu3", "ch")
201
+
202
+ def qasm2_features(text: str) -> Dict[str, Any]:
203
+ # strip // comments and blank lines for counting
204
+ lines = []
205
+ for raw in text.splitlines():
206
+ s = raw.strip()
207
+ if not s or s.startswith("//"):
208
+ continue
209
+ lines.append(s)
210
+
211
+ qregs = [(m.group(1), int(m.group(2))) for m in _QREG_DECL_RE.finditer(text)]
212
+ cregs = [(m.group(1), int(m.group(2))) for m in _CREG_DECL_RE.finditer(text)]
213
+ n_qubits = sum(n for _, n in qregs) if qregs else None
214
+ n_clbits = sum(n for _, n in cregs) if cregs else 0
215
+
216
+ n_measure = 0
217
+ n_barrier = 0
218
+ n_decl = 0
219
+ n_ops = 0
220
+ n_2q = 0
221
+ n_other = 0
222
+
223
+ for s in lines:
224
+ sl = s.lower()
225
+ if sl.startswith("openqasm") or sl.startswith("include"):
226
+ continue
227
+ if sl.startswith("qreg") or sl.startswith("creg"):
228
+ n_decl += 1
229
+ continue
230
+ if sl.startswith("barrier"):
231
+ n_barrier += 1
232
+ continue
233
+ if _MEASURE_RE.match(s):
234
+ n_measure += 1
235
+ continue
236
+ if s.endswith(";"):
237
+ n_ops += 1
238
+ first = s.split(None, 1)[0].strip().lower()
239
+ if first in _TWOQ_MNEMONICS:
240
+ n_2q += 1
241
+ else:
242
+ n_other += 1
243
+
244
+ return {
245
+ "qasm_n_qubits_decl": n_qubits,
246
+ "qasm_n_clbits_decl": n_clbits,
247
+ "qasm_ops_total_approx": n_ops,
248
+ "qasm_ops_2q_approx": n_2q,
249
+ "qasm_ops_other": n_other,
250
+ "qasm_measures": n_measure,
251
+ "qasm_barriers": n_barrier,
252
+ "qasm_decls": n_decl,
253
+ "qasm_nonempty_lines": len(lines),
254
+ }
255
+
256
+
257
+ # -----------------------------
258
+ # Data classes
259
+ # -----------------------------
260
+
261
+ @dataclass
262
+ class MirrorRun:
263
+ threshold: int
264
+ json_path: str
265
+ metric_value: Optional[float] # the metric used for selection
266
+ p_return_zero: Optional[float] # from mirror_metrics.p_return_zero
267
+ sdk_get_fidelity: Optional[float] # from sdk_metrics.get_fidelity
268
+ sdk_peakmem0: Optional[float] # from sdk_metrics.get_peakmemorysize_0 (numeric-ish)
269
+ run_wall_s: Optional[float]
270
+ peak_rss_mb: Optional[float]
271
+ returncode: int
272
+ note: str
273
+ error_message: Optional[str] = None
274
+
275
+ @dataclass
276
+ class RunnerRun:
277
+ threshold: int
278
+ shots: int
279
+ json_path: str
280
+ run_wall_s: Optional[float]
281
+ peak_rss_mb: Optional[float]
282
+ unique_outcomes: Optional[int]
283
+ tail_mass: Optional[float]
284
+ sdk_get_fidelity: Optional[float]
285
+ sdk_peakmem0: Optional[float]
286
+ returncode: int
287
+ note: str
288
+ error_message: Optional[str] = None
289
+
290
+
291
+ # -----------------------------
292
+ # RunRecord (JSONL) schema
293
+ # -----------------------------
294
+
295
+ RUN_RECORD_SCHEMA = "qcoder.run_record.v0"
296
+ FIDELITY_NOT_SET_DEFAULTED = "FIDELITY_NOT_SET_DEFAULTED"
297
+
298
+
299
+ def build_run_record(
300
+ *,
301
+ content_hash: str,
302
+ features_ref: str,
303
+ fidelity_metric_source: str,
304
+ created_utc: str,
305
+ qasm_file: str,
306
+ qasm_path: str,
307
+ backend: str,
308
+ backend_id: str,
309
+ precision: str,
310
+ env_id: str,
311
+ status: str,
312
+ selected_threshold: Optional[int],
313
+ run_kind: str = "job_summary",
314
+ threshold: Optional[int] = None,
315
+ shots: Optional[int] = None,
316
+ runtime_wall_s: Optional[float] = None,
317
+ fidelity: Optional[float] = None,
318
+ fidelity_metric: Optional[str] = None,
319
+ returncode: Optional[int] = None,
320
+ error_type: Optional[str] = None,
321
+ error_message: Optional[str] = None,
322
+ error_code: Optional[str] = None,
323
+ error_detail: Optional[str] = None,
324
+ peak_rss_mb: Optional[float] = None,
325
+ mirror_wall_s: Optional[float] = None,
326
+ probe_wall_s: Optional[float] = None,
327
+ runner_wall_s: Optional[float] = None,
328
+ shots_mirror: Optional[int] = None,
329
+ shots_runner: Optional[int] = None,
330
+ ) -> Dict[str, Any]:
331
+ """Build a RunRecord dict for JSONL logging. features_ref is relative to out_dir (dataset root)."""
332
+ warnings: List[str] = []
333
+ if fidelity_metric_source == "default":
334
+ warnings.append(FIDELITY_NOT_SET_DEFAULTED)
335
+ return {
336
+ "schema": RUN_RECORD_SCHEMA,
337
+ "content_hash": content_hash,
338
+ "features_ref": features_ref,
339
+ "fidelity_metric_source": fidelity_metric_source,
340
+ "warnings": warnings,
341
+ "created_utc": created_utc,
342
+ "qasm_file": qasm_file,
343
+ "qasm_path": qasm_path,
344
+ "backend": backend,
345
+ "backend_id": backend_id,
346
+ "precision": precision,
347
+ "env_id": env_id,
348
+ "run_kind": run_kind,
349
+ "status": status,
350
+ "selected_threshold": selected_threshold,
351
+ "threshold": threshold,
352
+ "shots": shots,
353
+ "runtime_wall_s": runtime_wall_s,
354
+ "fidelity": fidelity,
355
+ "fidelity_metric": fidelity_metric,
356
+ "returncode": returncode,
357
+ "peak_rss_mb": peak_rss_mb,
358
+ "mirror_wall_s": mirror_wall_s,
359
+ "probe_wall_s": probe_wall_s,
360
+ "runner_wall_s": runner_wall_s,
361
+ "shots_mirror": shots_mirror,
362
+ "shots_runner": shots_runner,
363
+ "error_code": error_code,
364
+ "error_detail": error_detail,
365
+ # Legacy aliases kept for backward compatibility
366
+ "error_type": error_type,
367
+ "error_message": error_message,
368
+ }
369
+
370
+
371
+ # -----------------------------
372
+ # Extract metrics from per-run JSONs
373
+ # -----------------------------
374
+
375
+ def _extract_sdk_metrics(payload: Dict[str, Any]) -> Tuple[Optional[float], Optional[float]]:
376
+ sm = payload.get("sdk_metrics", {}) if isinstance(payload.get("sdk_metrics", {}), dict) else {}
377
+ gf = to_float(sm.get("get_fidelity"))
378
+
379
+ # We standardize on get_peakmemorysize_0; fallback to other keys if needed
380
+ pm = sm.get("get_peakmemorysize_0")
381
+ if pm is None:
382
+ pm = sm.get("get_peakmemorysize")
383
+ pmf = to_float(pm)
384
+
385
+ return gf, pmf
386
+
387
+ def extract_mirror_metrics(
388
+ payload: Dict[str, Any],
389
+ mirror_metric: str
390
+ ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float], Optional[float], Optional[float]]:
391
+ """
392
+ Returns:
393
+ metric_value (used for selection),
394
+ p_return_zero,
395
+ sdk_get_fidelity,
396
+ sdk_peakmem0,
397
+ run_wall_s,
398
+ peak_rss_mb
399
+ """
400
+ p0 = None
401
+ try:
402
+ mm = payload.get("mirror_metrics", {})
403
+ if isinstance(mm, dict):
404
+ p0 = to_float(mm.get("p_return_zero"))
405
+ except Exception:
406
+ p0 = None
407
+
408
+ gf, pm0 = _extract_sdk_metrics(payload)
409
+
410
+ t = to_float(payload.get("timing_s", {}).get("run_wall_s")) if isinstance(payload.get("timing_s", {}), dict) else None
411
+ mem = to_float(payload.get("memory", {}).get("peak_rss_mb")) if isinstance(payload.get("memory", {}), dict) else None
412
+
413
+ if mirror_metric == "p_return_zero":
414
+ mv = p0
415
+ elif mirror_metric == "sdk_get_fidelity":
416
+ mv = gf
417
+ else:
418
+ mv = None
419
+
420
+ return mv, p0, gf, pm0, t, mem
421
+
422
+ def extract_runner_metrics(payload: Dict[str, Any]) -> Tuple[Optional[float], Optional[float], Optional[int], Optional[float], Optional[float], Optional[float]]:
423
+ """
424
+ Returns:
425
+ run_wall_s, peak_rss_mb, unique_outcomes, tail_mass, sdk_get_fidelity, sdk_peakmem0
426
+ """
427
+ t = to_float(payload.get("timing_s", {}).get("run_wall_s")) if isinstance(payload.get("timing_s", {}), dict) else None
428
+ mem = to_float(payload.get("memory", {}).get("peak_rss_mb")) if isinstance(payload.get("memory", {}), dict) else None
429
+
430
+ u = None
431
+ tail = None
432
+ try:
433
+ h = payload.get("hist", {})
434
+ if isinstance(h, dict):
435
+ u = to_int(h.get("unique_outcomes"))
436
+ tail = to_float(h.get("tail_mass"))
437
+ except Exception:
438
+ pass
439
+
440
+ gf, pm0 = _extract_sdk_metrics(payload)
441
+ return t, mem, u, tail, gf, pm0
442
+
443
+
444
+ # -----------------------------
445
+ # Invocations
446
+ # -----------------------------
447
+
448
+ _OK_JSON_RE = re.compile(r"\[ok\]\s+wrote JSON:\s*(.+)$", re.M)
449
+
450
+ def run_mirror_once(
451
+ qasm_path: str,
452
+ backend: str,
453
+ precision: str,
454
+ threshold: int,
455
+ shots: int,
456
+ out_dir: str,
457
+ quiet: bool,
458
+ mirror_metric: str,
459
+ mirror_mode: Optional[str],
460
+ timeout_s: Optional[float],
461
+ ) -> MirrorRun:
462
+
463
+ ensure_dir(out_dir)
464
+
465
+ cmd = [
466
+ sys.executable, "-m", "qcoder.tools.mirror",
467
+ qasm_path,
468
+ "--backend", backend,
469
+ "--precision", precision,
470
+ "--threshold", str(int(threshold)),
471
+ "--shots", str(int(shots)),
472
+ "--out-dir", out_dir,
473
+ ]
474
+ if mirror_mode:
475
+ cmd += ["--mirror-mode", mirror_mode]
476
+
477
+ rc, out = run_subprocess(cmd, quiet=quiet, timeout_s=timeout_s)
478
+
479
+ json_path = ""
480
+ m = _OK_JSON_RE.search(out)
481
+ if m:
482
+ json_path = m.group(1).strip()
483
+
484
+ if rc == 124:
485
+ return MirrorRun(threshold, json_path, None, None, None, None, None, None, rc, "mirror_timeout", _truncate_error_message(out))
486
+
487
+ if rc != 0:
488
+ return MirrorRun(threshold, json_path, None, None, None, None, None, None, rc, "mirror_failed", _truncate_error_message(out))
489
+
490
+ if not json_path or not os.path.exists(json_path):
491
+ return MirrorRun(threshold, json_path, None, None, None, None, None, None, rc, "mirror_missing_json", _truncate_error_message(out))
492
+
493
+ payload = parse_json(json_path)
494
+ mv, p0, gf, pm0, t, mem = extract_mirror_metrics(payload, mirror_metric=mirror_metric)
495
+ return MirrorRun(threshold, json_path, mv, p0, gf, pm0, t, mem, rc, "ok")
496
+
497
+ def run_runner_once(
498
+ qasm_path: str,
499
+ backend: str,
500
+ precision: str,
501
+ threshold: int,
502
+ shots: int,
503
+ topk: int,
504
+ out_dir: str,
505
+ quiet: bool,
506
+ timeout_s: Optional[float],
507
+ ) -> RunnerRun:
508
+
509
+ ensure_dir(out_dir)
510
+
511
+ cmd = [
512
+ sys.executable, "-m", "qcoder.tools.runner",
513
+ qasm_path,
514
+ "--backend", backend,
515
+ "--precision", precision,
516
+ "--threshold", str(int(threshold)),
517
+ "--shots", str(int(shots)),
518
+ "--topk", str(int(topk)),
519
+ "--out-dir", out_dir,
520
+ ]
521
+ rc, out = run_subprocess(cmd, quiet=quiet, timeout_s=timeout_s)
522
+
523
+ json_path = ""
524
+ m = _OK_JSON_RE.search(out)
525
+ if m:
526
+ json_path = m.group(1).strip()
527
+
528
+ if rc == 124:
529
+ return RunnerRun(threshold, shots, json_path, None, None, None, None, None, None, rc, "runner_timeout", _truncate_error_message(out))
530
+
531
+ if rc != 0:
532
+ return RunnerRun(threshold, shots, json_path, None, None, None, None, None, None, rc, "runner_failed", _truncate_error_message(out))
533
+
534
+ if not json_path or not os.path.exists(json_path):
535
+ return RunnerRun(threshold, shots, json_path, None, None, None, None, None, None, rc, "runner_missing_json", _truncate_error_message(out))
536
+
537
+ payload = parse_json(json_path)
538
+ t, mem, u, tail, gf, pm0 = extract_runner_metrics(payload)
539
+ return RunnerRun(threshold, shots, json_path, t, mem, u, tail, gf, pm0, rc, "ok")
540
+
541
+
542
+ # -----------------------------
543
+ # Policies / helpers
544
+ # -----------------------------
545
+
546
+ def build_threshold_ladder(start: int, max_thr: int) -> List[int]:
547
+ x = max(1, int(start))
548
+ out: List[int] = []
549
+ while x <= int(max_thr):
550
+ out.append(x)
551
+ x *= 2
552
+ return out
553
+
554
+ def _forward_thresholds_from_mirror(
555
+ mirror_runs: List[MirrorRun],
556
+ selected_thr: Optional[int],
557
+ ladder: List[int],
558
+ ) -> List[int]:
559
+ """
560
+ Build forward_thresholds: thresholds from mirror_runs that returned successfully and have
561
+ a usable non-null metric_value. Preserves ladder order and deduplicates.
562
+ If none, fall back to [selected_thr] so report/CSV remain defined.
563
+ selected_thr is always included so the primary summary pair exists.
564
+ """
565
+ out = list(dict.fromkeys(
566
+ mr.threshold for mr in mirror_runs
567
+ if mr.returncode == 0 and mr.metric_value is not None
568
+ ))
569
+ if not out:
570
+ return [selected_thr] if selected_thr is not None else []
571
+ # Ensure selected_thr is included for job_summary/report/CSV primary pair.
572
+ if selected_thr is not None and selected_thr not in out:
573
+ out = out + [selected_thr]
574
+ out.sort(key=lambda t: ladder.index(t) if t in ladder else 999)
575
+ return out
576
+
577
+
578
+ def mirror_keep_set(mirror_runs: List[MirrorRun], selected_thr: Optional[int], keep_policy: str) -> set:
579
+ """
580
+ keep_policy: all | bracketing+selected | selected-only | none
581
+ """
582
+ if keep_policy == "all":
583
+ return {mr.json_path for mr in mirror_runs if mr.json_path}
584
+ if keep_policy == "none":
585
+ return set()
586
+ if selected_thr is None:
587
+ return {mr.json_path for mr in mirror_runs if mr.json_path}
588
+ if keep_policy == "selected-only":
589
+ return {mr.json_path for mr in mirror_runs if mr.json_path and mr.threshold == selected_thr}
590
+
591
+ keep = set()
592
+ sel_idx = None
593
+ for i, mr in enumerate(mirror_runs):
594
+ if mr.threshold == selected_thr:
595
+ sel_idx = i
596
+ break
597
+ if sel_idx is not None:
598
+ if mirror_runs[sel_idx].json_path:
599
+ keep.add(mirror_runs[sel_idx].json_path)
600
+ if sel_idx - 1 >= 0 and mirror_runs[sel_idx - 1].json_path:
601
+ keep.add(mirror_runs[sel_idx - 1].json_path)
602
+ return keep
603
+
604
+ def fmt_opt(x: Optional[float], digits: int = 3) -> str:
605
+ if x is None:
606
+ return "?"
607
+ return f"{x:.{digits}f}"
608
+
609
+ def fmt_int(x: Optional[int]) -> str:
610
+ return "?" if x is None else str(int(x))
611
+
612
+
613
+ # -----------------------------
614
+ # Main
615
+ # -----------------------------
616
+
617
+ def main() -> None:
618
+ ap = argparse.ArgumentParser(description="QCoder harness: mirror threshold selection + shots=1 and shots=hist benchmarks.")
619
+
620
+ ap.add_argument("--mirror-metric", choices=["p_return_zero", "sdk_get_fidelity"], default="p_return_zero",
621
+ help="Which metric from the mirror run to use for threshold selection.")
622
+
623
+ ap.add_argument(
624
+ "--mirror-mode",
625
+ choices=["auto", "qasm_counts", "sdk_inverse_fidelity"],
626
+ default="auto",
627
+ help="auto => sdk_inverse_fidelity when mirror-metric=sdk_get_fidelity else qasm_counts."
628
+ )
629
+
630
+ ap.add_argument("--mirror-verify", action="store_true",
631
+ help="After selecting threshold, run a counts-based mirror (qasm_counts) at selected threshold to report p_return_zero.")
632
+ ap.add_argument("--mirror-verify-shots", type=int, default=1000,
633
+ help="Shots for verification counts-based mirror (only used when --mirror-verify).")
634
+
635
+ ap.add_argument("--target-fidelity", dest="target", type=float, default=0.99,
636
+ help="Target mirror metric to meet/exceed (default 0.99).")
637
+ ap.add_argument("--threshold-start", type=int, default=2, help="Start of 2× threshold ladder.")
638
+ ap.add_argument("--threshold-max", type=int, default=256, help="Max threshold in ladder.")
639
+ ap.add_argument("--stop-when", choices=["first_cross", "exhaust"], default="first_cross",
640
+ help="Stop scanning ladder at first threshold meeting target, or always exhaust ladder.")
641
+
642
+ ap.add_argument("--circuits-folder", default="circuits", help="Folder containing QASM files.")
643
+ ap.add_argument("--circuit-glob", default="*.qasm", help="Glob pattern inside circuits-folder.")
644
+ ap.add_argument("--include", nargs="*", default=None, help="Optional allowlist (filenames).")
645
+ ap.add_argument("--exclude", nargs="*", default=None, help="Optional blocklist (filenames).")
646
+ ap.add_argument("--max-circuits", type=int, default=0, help="Optional cap (0=no cap).")
647
+ ap.add_argument("--sort-circuits", choices=["size", "name"], default="size",
648
+ help="Sort circuits by file size (ascending) or by name.")
649
+
650
+ ap.add_argument("--backends", nargs="+", default=["Scarlet"], help="Backends: e.g. Scarlet Amber Serin")
651
+ ap.add_argument("--precisions", nargs="+", default=["single"], choices=["single", "double"], help="Precisions.")
652
+
653
+ ap.add_argument("--shots-mirror", type=int, default=200,
654
+ help="Shots for mirror metric. If mirror-mode is sdk_inverse_fidelity, shots can be small (even 1).")
655
+
656
+ ap.add_argument("--shots-probe", type=int, default=0,
657
+ help="Runner shots for per-threshold timing probe during mirror sweep. Set 0 to disable.")
658
+
659
+ ap.add_argument("--shots-state", type=int, default=1, help="Runner shots for state/setup timing proxy (default 1).")
660
+ ap.add_argument("--shots-hist", type=int, default=10000, help="Runner shots for histogram timing (default 10000).")
661
+
662
+ ap.add_argument("--topk", type=int, default=50, help="Top-K bitstrings stored by runner JSON.")
663
+ ap.add_argument("--out-dir", default="runs", help="Output directory for per-run JSONs.")
664
+ ap.add_argument("--report-json", default="runs/harness_report.json", help="Aggregate report JSON path.")
665
+ ap.add_argument("--run-record", default=None, metavar="PATH",
666
+ help="If set, append one RunRecord JSON object per line (JSONL) to PATH. Path-driven; no default.")
667
+ ap.add_argument("--subdir", default=None, help="Optional subdirectory under --out-dir (e.g. exp_harness).")
668
+
669
+ ap.add_argument("--keep-mirror", choices=["bracketing+selected", "all", "selected-only", "none"],
670
+ default="bracketing+selected", help="Which mirror JSONs to retain on disk.")
671
+ ap.add_argument("--keep-runner", choices=["hist-only", "all", "none"], default="hist-only",
672
+ help="Which runner JSONs to retain (hist-only keeps only shots-hist JSON).")
673
+
674
+ ap.add_argument("--dataset-csv", default="runs/harness_dataset.csv", help="Dataset CSV path.")
675
+ ap.add_argument("--append-csv", action="store_true", help="Append to CSV if it exists.")
676
+ ap.add_argument("--quiet-cmd", action="store_true", help="Suppress subprocess stdout/stderr (use one-line summaries).")
677
+
678
+ ap.add_argument("--timeout-mirror-s", type=int, default=1800, help="Timeout per mirror rung (0 disables).")
679
+ ap.add_argument("--timeout-runner-s", type=int, default=1800, help="Timeout per runner call (0 disables).")
680
+ ap.add_argument("--timeout-probe-s", type=int, default=600, help="Timeout per probe runner call (0 disables).")
681
+
682
+ args = ap.parse_args()
683
+ args.mirror_metric_explicit = "--mirror-metric" in sys.argv
684
+
685
+ # Resolve mirror mode for the main selection sweep
686
+ if args.mirror_mode == "auto":
687
+ mirror_mode_sel = "sdk_inverse_fidelity" if args.mirror_metric == "sdk_get_fidelity" else "qasm_counts"
688
+ else:
689
+ mirror_mode_sel = args.mirror_mode
690
+
691
+ # Subdir behavior: put outputs into runs/<subdir>/ but preserve explicit custom paths elsewhere
692
+ if args.subdir:
693
+ base_out_dir = args.out_dir # typically "runs"
694
+ exp_out_dir = os.path.join(base_out_dir, args.subdir)
695
+
696
+ base_abs = os.path.abspath(base_out_dir)
697
+ rep_abs = os.path.abspath(args.report_json)
698
+ csv_abs = os.path.abspath(args.dataset_csv)
699
+
700
+ if rep_abs.startswith(base_abs + os.sep):
701
+ args.report_json = os.path.join(exp_out_dir, os.path.basename(args.report_json))
702
+
703
+ if csv_abs.startswith(base_abs + os.sep):
704
+ args.dataset_csv = os.path.join(exp_out_dir, os.path.basename(args.dataset_csv))
705
+
706
+ args.out_dir = exp_out_dir
707
+
708
+ target = float(args.target)
709
+ if not (0.0 < target <= 1.0):
710
+ die("--target-fidelity must be in (0,1].")
711
+
712
+ ladder = build_threshold_ladder(args.threshold_start, args.threshold_max)
713
+ if not ladder:
714
+ die("Empty ladder. Check --threshold-start/--threshold-max.")
715
+
716
+ ensure_dir(args.out_dir)
717
+ ensure_dir(os.path.dirname(args.report_json) or ".")
718
+ ensure_dir(os.path.dirname(args.dataset_csv) or ".")
719
+ run_record_path: Optional[str] = (args.run_record or "").strip() or None
720
+ if run_record_path:
721
+ ensure_dir(os.path.dirname(os.path.abspath(run_record_path)) or ".")
722
+ features_dir = os.path.join(args.out_dir, "features")
723
+
724
+ # Gather circuits
725
+ pattern = os.path.join(args.circuits_folder, args.circuit_glob)
726
+ candidates = glob.glob(pattern)
727
+ if not candidates:
728
+ die(f"No circuits found: {pattern}")
729
+
730
+ include = set(args.include or [])
731
+ exclude = set(args.exclude or [])
732
+ circuits: List[str] = []
733
+ for p in candidates:
734
+ base = os.path.basename(p)
735
+ if include and base not in include:
736
+ continue
737
+ if exclude and base in exclude:
738
+ continue
739
+ circuits.append(p)
740
+
741
+ if args.sort_circuits == "size":
742
+ circuits.sort(key=lambda p: (os.path.getsize(p), os.path.basename(p)))
743
+ else:
744
+ circuits.sort(key=lambda p: os.path.basename(p))
745
+
746
+ if args.max_circuits and args.max_circuits > 0:
747
+ circuits = circuits[: int(args.max_circuits)]
748
+
749
+ total_jobs = len(circuits) * len(args.backends) * len(args.precisions)
750
+ job_idx = 0
751
+
752
+ print("===== QCoder harness =====", flush=True)
753
+ print(f"[config] mirror_metric={args.mirror_metric} mirror_mode={args.mirror_mode} resolved_mode={mirror_mode_sel}", flush=True)
754
+ print(f"[config] target={target} ladder={ladder} stop_when={args.stop_when}", flush=True)
755
+ print(f"[config] circuits={len(circuits)} backends={args.backends} precisions={args.precisions}", flush=True)
756
+ print(f"[config] shots_mirror={args.shots_mirror} shots_probe={args.shots_probe} shots_state={args.shots_state} shots_hist={args.shots_hist}", flush=True)
757
+ print(f"[config] mirror_verify={args.mirror_verify} mirror_verify_shots={args.mirror_verify_shots}", flush=True)
758
+ print(f"[config] timeouts: mirror={args.timeout_mirror_s}s runner={args.timeout_runner_s}s probe={args.timeout_probe_s}s", flush=True)
759
+ print(f"[config] keep_mirror={args.keep_mirror} keep_runner={args.keep_runner} out_dir={args.out_dir}", flush=True)
760
+
761
+ report: Dict[str, Any] = {
762
+ "meta": {
763
+ "schema_version": "qr12_harness_1.1",
764
+ "created_utc": utc_now_iso(),
765
+ "mirror_metric": args.mirror_metric,
766
+ "mirror_mode": args.mirror_mode,
767
+ "mirror_mode_resolved": mirror_mode_sel,
768
+ "mirror_verify": bool(args.mirror_verify),
769
+ "mirror_verify_shots": int(args.mirror_verify_shots),
770
+ "target": target,
771
+ "threshold_ladder": ladder,
772
+ "stop_when": args.stop_when,
773
+ "circuits_folder": os.path.abspath(args.circuits_folder),
774
+ "circuit_glob": args.circuit_glob,
775
+ "sorted_by": args.sort_circuits,
776
+ "backends": args.backends,
777
+ "precisions": args.precisions,
778
+ "shots_mirror": int(args.shots_mirror),
779
+ "shots_probe": int(args.shots_probe),
780
+ "shots_state": int(args.shots_state),
781
+ "shots_hist": int(args.shots_hist),
782
+ "topk": int(args.topk),
783
+ "keep_mirror": args.keep_mirror,
784
+ "keep_runner": args.keep_runner,
785
+ "out_dir": os.path.abspath(args.out_dir),
786
+ "timeouts_s": {
787
+ "mirror": int(args.timeout_mirror_s),
788
+ "runner": int(args.timeout_runner_s),
789
+ "probe": int(args.timeout_probe_s),
790
+ },
791
+ "python": sys.version.split()[0],
792
+ },
793
+ "runs": []
794
+ }
795
+
796
+ # NOTE: CSV schema unchanged (to avoid churn). Verification results go into report JSON.
797
+ csv_fields = [
798
+ "created_utc",
799
+ "qasm_file",
800
+ "qasm_path",
801
+ "qasm_sha256",
802
+ "qasm_bytes",
803
+ "backend",
804
+ "backend_id",
805
+ "precision",
806
+ "env_id",
807
+ "mirror_metric",
808
+ "target",
809
+ "selected_threshold",
810
+ "mirror_metric_selected",
811
+ "mirror_p_return_zero_selected",
812
+ "mirror_sdk_get_fidelity_selected",
813
+ "mirror_sdk_peakmem0_selected",
814
+ "mirror_run_wall_s_selected",
815
+ "mirror_peak_rss_mb_selected",
816
+ "probe_shots",
817
+ "probe_run_wall_s_selected",
818
+ "probe_peak_rss_mb_selected",
819
+ "runner_t_state_s",
820
+ "runner_t_hist_s",
821
+ "runner_shots_state",
822
+ "runner_shots_hist",
823
+ "runner_per_shot_s_est",
824
+ "runner_hist_unique_outcomes",
825
+ "runner_hist_tail_mass",
826
+ "runner_state_sdk_get_fidelity",
827
+ "runner_state_sdk_peakmem0",
828
+ "runner_hist_sdk_get_fidelity",
829
+ "runner_hist_sdk_peakmem0",
830
+ "runner_state_json",
831
+ "runner_hist_json",
832
+ "status",
833
+ "error_code",
834
+ "error_detail",
835
+ "mirror_wall_s",
836
+ "probe_wall_s",
837
+ "runner_wall_s",
838
+ "shots_mirror",
839
+ "shots_runner",
840
+ "peak_rss_mb",
841
+ "note",
842
+ # heuristics
843
+ "qasm_n_qubits_decl",
844
+ "qasm_n_clbits_decl",
845
+ "qasm_ops_total_approx",
846
+ "qasm_ops_2q_approx",
847
+ "qasm_ops_other",
848
+ "qasm_measures",
849
+ "qasm_barriers",
850
+ "qasm_decls",
851
+ "qasm_nonempty_lines",
852
+ ]
853
+
854
+ write_header = not (args.append_csv and os.path.exists(args.dataset_csv))
855
+ csv_mode = "a" if args.append_csv else "w"
856
+ f_csv = open(args.dataset_csv, csv_mode, newline="", encoding="utf-8")
857
+ writer = csv.DictWriter(f_csv, fieldnames=csv_fields)
858
+ if write_header:
859
+ writer.writeheader()
860
+
861
+ f_run_record = open(run_record_path, "a", encoding="utf-8") if run_record_path else None
862
+ try:
863
+ for qasm_path in circuits:
864
+ qasm_abs = os.path.abspath(qasm_path)
865
+ qasm_file = os.path.basename(qasm_path)
866
+ qasm_hash = sha256_file(qasm_path)
867
+ qasm_bytes = os.path.getsize(qasm_path)
868
+
869
+ feats: Dict[str, Any] = {}
870
+ try:
871
+ feats = qasm2_features(read_text(qasm_path))
872
+ except Exception as e:
873
+ print(f"[warn] feature extraction failed for {qasm_file}: {e}", flush=True)
874
+
875
+ features_ref_rel = ""
876
+ if run_record_path:
877
+ ensure_dir(features_dir)
878
+ features_path = os.path.join(features_dir, qasm_hash + ".json")
879
+ with open(features_path, "w", encoding="utf-8") as f:
880
+ json.dump(feats, f, sort_keys=True)
881
+ # Store path relative to out_dir (dataset root) for portability
882
+ features_ref_rel = os.path.join("features", qasm_hash + ".json")
883
+
884
+ for backend in args.backends:
885
+ for precision in args.precisions:
886
+ job_idx += 1
887
+
888
+ mirror_runs: List[MirrorRun] = []
889
+ probe_runs: List[Optional[RunnerRun]] = []
890
+ selected_thr: Optional[int] = None
891
+
892
+ # 1) Mirror sweep
893
+ for thr in ladder:
894
+ fidelity_metric_source = "explicit" if args.mirror_metric_explicit else "default"
895
+ mr = run_mirror_once(
896
+ qasm_path=qasm_path,
897
+ backend=backend,
898
+ precision=precision,
899
+ threshold=int(thr),
900
+ shots=int(args.shots_mirror),
901
+ out_dir=args.out_dir,
902
+ quiet=bool(args.quiet_cmd),
903
+ mirror_metric=args.mirror_metric,
904
+ mirror_mode=mirror_mode_sel,
905
+ timeout_s=args.timeout_mirror_s,
906
+ )
907
+ mirror_runs.append(mr)
908
+ if f_run_record and features_ref_rel:
909
+ mr_status, mr_error_code, mr_error_detail = classify_attempt_status(
910
+ mr.note, mr.returncode, mr.error_message
911
+ )
912
+ rec = build_run_record(
913
+ content_hash=qasm_hash,
914
+ features_ref=features_ref_rel,
915
+ fidelity_metric_source=fidelity_metric_source,
916
+ created_utc=utc_now_iso(),
917
+ qasm_file=qasm_file,
918
+ qasm_path=qasm_abs,
919
+ backend=backend,
920
+ backend_id=backend,
921
+ precision=precision,
922
+ env_id="",
923
+ run_kind="mirror_threshold_attempt",
924
+ status=mr_status,
925
+ selected_threshold=int(thr),
926
+ threshold=int(thr),
927
+ runtime_wall_s=mr.run_wall_s,
928
+ fidelity=mr.metric_value,
929
+ fidelity_metric=args.mirror_metric,
930
+ returncode=mr.returncode,
931
+ peak_rss_mb=mr.peak_rss_mb,
932
+ mirror_wall_s=mr.run_wall_s,
933
+ shots_mirror=int(args.shots_mirror),
934
+ error_code=mr_error_code,
935
+ error_detail=mr_error_detail,
936
+ error_type=mr_error_code,
937
+ error_message=mr.error_message,
938
+ )
939
+ f_run_record.write(json.dumps(rec, sort_keys=True) + "\n")
940
+ f_run_record.flush()
941
+
942
+ probe_rr: Optional[RunnerRun] = None
943
+ if int(args.shots_probe) > 0 and mr.returncode == 0:
944
+ probe_rr = run_runner_once(
945
+ qasm_path=qasm_path,
946
+ backend=backend,
947
+ precision=precision,
948
+ threshold=int(thr),
949
+ shots=int(args.shots_probe),
950
+ topk=int(args.topk),
951
+ out_dir=args.out_dir,
952
+ quiet=bool(args.quiet_cmd),
953
+ timeout_s=args.timeout_probe_s,
954
+ )
955
+ # Probe JSON always deleted (metrics kept in report only)
956
+ if probe_rr.json_path and os.path.exists(probe_rr.json_path):
957
+ safe_unlink(probe_rr.json_path)
958
+ probe_rr.json_path = ""
959
+ probe_runs.append(probe_rr)
960
+
961
+ if mr.returncode != 0:
962
+ continue
963
+
964
+ if mr.metric_value is not None and mr.metric_value >= target:
965
+ selected_thr = int(thr)
966
+ if args.stop_when == "first_cross":
967
+ break
968
+
969
+ legacy_status = "ok"
970
+ note = ""
971
+
972
+ # 2) Select threshold fallback rules
973
+ if selected_thr is None:
974
+ good = [mr for mr in mirror_runs if mr.returncode == 0 and mr.metric_value is not None]
975
+ if good:
976
+ selected_thr = int(good[-1].threshold)
977
+ legacy_status = "no_threshold_met"
978
+ note = f"mirror_never_met_target;fallback_thr={selected_thr}"
979
+ else:
980
+ selected_thr = int(ladder[-1])
981
+ legacy_status = "mirror_failed"
982
+ note = f"no_successful_mirror;fallback_thr={selected_thr}"
983
+
984
+ sel_mr = next((mr for mr in mirror_runs if mr.threshold == selected_thr), None)
985
+
986
+ sel_probe: Optional[RunnerRun] = None
987
+ for mr, pr in zip(mirror_runs, probe_runs):
988
+ if mr.threshold == selected_thr:
989
+ sel_probe = pr
990
+ break
991
+
992
+ # Mirror retention policy for sweep runs
993
+ keep_mirror_paths = mirror_keep_set(mirror_runs, selected_thr, args.keep_mirror)
994
+ for mr in mirror_runs:
995
+ if mr.json_path and os.path.exists(mr.json_path) and (mr.json_path not in keep_mirror_paths):
996
+ safe_unlink(mr.json_path)
997
+
998
+ # 3) Optional verification mirror at selected threshold (counts-based)
999
+ verify_mr: Optional[MirrorRun] = None
1000
+ if args.mirror_verify:
1001
+ verify_mr = run_mirror_once(
1002
+ qasm_path=qasm_path,
1003
+ backend=backend,
1004
+ precision=precision,
1005
+ threshold=int(selected_thr),
1006
+ shots=int(args.mirror_verify_shots),
1007
+ out_dir=args.out_dir,
1008
+ quiet=bool(args.quiet_cmd),
1009
+ mirror_metric="p_return_zero",
1010
+ mirror_mode="qasm_counts",
1011
+ timeout_s=args.timeout_mirror_s,
1012
+ )
1013
+ # Respect keep_mirror=none by deleting verification json too
1014
+ if args.keep_mirror == "none" and verify_mr.json_path and os.path.exists(verify_mr.json_path):
1015
+ safe_unlink(verify_mr.json_path)
1016
+ verify_mr.json_path = ""
1017
+
1018
+ # 4) Benchmark forward circuit: run state+hist at every threshold with successful mirror (rc=0, metric_value set).
1019
+ # Build/setup timing comes from the 1-shot state forward row (shots_state), not from probe_wall_s.
1020
+ tested_forward_thresholds = _forward_thresholds_from_mirror(mirror_runs, selected_thr, ladder)
1021
+ forward_runs_by_threshold: Dict[int, Tuple[RunnerRun, RunnerRun]] = {}
1022
+ for thr in tested_forward_thresholds:
1023
+ rr_s = run_runner_once(
1024
+ qasm_path=qasm_path,
1025
+ backend=backend,
1026
+ precision=precision,
1027
+ threshold=int(thr),
1028
+ shots=int(args.shots_state),
1029
+ topk=int(args.topk),
1030
+ out_dir=args.out_dir,
1031
+ quiet=bool(args.quiet_cmd),
1032
+ timeout_s=args.timeout_runner_s,
1033
+ )
1034
+ rr_h = run_runner_once(
1035
+ qasm_path=qasm_path,
1036
+ backend=backend,
1037
+ precision=precision,
1038
+ threshold=int(thr),
1039
+ shots=int(args.shots_hist),
1040
+ topk=int(args.topk),
1041
+ out_dir=args.out_dir,
1042
+ quiet=bool(args.quiet_cmd),
1043
+ timeout_s=args.timeout_runner_s,
1044
+ )
1045
+ forward_runs_by_threshold[thr] = (rr_s, rr_h)
1046
+ if f_run_record and features_ref_rel:
1047
+ fidelity_metric_source = "explicit" if args.mirror_metric_explicit else "default"
1048
+ for rr in (rr_s, rr_h):
1049
+ rr_status, rr_error_code, rr_error_detail = classify_attempt_status(
1050
+ rr.note, rr.returncode, rr.error_message
1051
+ )
1052
+ rec = build_run_record(
1053
+ content_hash=qasm_hash,
1054
+ features_ref=features_ref_rel,
1055
+ fidelity_metric_source=fidelity_metric_source,
1056
+ created_utc=utc_now_iso(),
1057
+ qasm_file=qasm_file,
1058
+ qasm_path=qasm_abs,
1059
+ backend=backend,
1060
+ backend_id=backend,
1061
+ precision=precision,
1062
+ env_id="",
1063
+ run_kind="forward_runner_execution",
1064
+ status=rr_status,
1065
+ selected_threshold=int(selected_thr),
1066
+ threshold=rr.threshold,
1067
+ shots=rr.shots,
1068
+ runtime_wall_s=rr.run_wall_s,
1069
+ fidelity=rr.sdk_get_fidelity,
1070
+ fidelity_metric="sdk_get_fidelity",
1071
+ returncode=rr.returncode,
1072
+ peak_rss_mb=rr.peak_rss_mb,
1073
+ runner_wall_s=rr.run_wall_s,
1074
+ shots_runner=rr.shots,
1075
+ error_code=rr_error_code,
1076
+ error_detail=rr_error_detail,
1077
+ error_type=rr_error_code,
1078
+ error_message=rr.error_message,
1079
+ )
1080
+ f_run_record.write(json.dumps(rec, sort_keys=True) + "\n")
1081
+ f_run_record.flush()
1082
+
1083
+ # Primary pair for report/CSV/job_summary: selected threshold only (build/setup = 1-shot state row).
1084
+ rr_state, rr_hist = forward_runs_by_threshold[selected_thr]
1085
+
1086
+ if rr_state.returncode != 0 or rr_hist.returncode != 0:
1087
+ if legacy_status == "ok":
1088
+ legacy_status = "runner_failed"
1089
+ note = (note + ";" if note else "") + f"runner_rc_state={rr_state.returncode},runner_rc_hist={rr_hist.returncode}"
1090
+
1091
+ # Runner retention policy: apply to all forward runs (all tested thresholds).
1092
+ for _thr, (r_s, r_h) in forward_runs_by_threshold.items():
1093
+ if args.keep_runner == "none":
1094
+ if r_s.json_path and os.path.exists(r_s.json_path):
1095
+ safe_unlink(r_s.json_path)
1096
+ if r_h.json_path and os.path.exists(r_h.json_path):
1097
+ safe_unlink(r_h.json_path)
1098
+ elif args.keep_runner == "hist-only":
1099
+ if r_s.json_path and os.path.exists(r_s.json_path):
1100
+ safe_unlink(r_s.json_path)
1101
+
1102
+ per_shot_est = None
1103
+ if (
1104
+ rr_state.run_wall_s is not None
1105
+ and rr_hist.run_wall_s is not None
1106
+ and int(args.shots_hist) > int(args.shots_state)
1107
+ ):
1108
+ denom = float(int(args.shots_hist) - int(args.shots_state))
1109
+ per_shot_est = (float(rr_hist.run_wall_s) - float(rr_state.run_wall_s)) / denom
1110
+
1111
+ # Canonical summary status/error mapping for output records/CSV.
1112
+ status = "ok"
1113
+ error_code = ""
1114
+ error_detail = ""
1115
+ if legacy_status == "mirror_failed":
1116
+ mr_fail = next((x for x in mirror_runs if x.returncode != 0), None)
1117
+ if mr_fail is not None:
1118
+ status, error_code, error_detail = classify_attempt_status(
1119
+ mr_fail.note, mr_fail.returncode, mr_fail.error_message
1120
+ )
1121
+ else:
1122
+ status, error_code, error_detail = "runner_error", "runner_error", note
1123
+ elif legacy_status == "runner_failed":
1124
+ rr_fail = rr_state if rr_state.returncode != 0 else rr_hist
1125
+ status, error_code, error_detail = classify_attempt_status(
1126
+ rr_fail.note, rr_fail.returncode, rr_fail.error_message
1127
+ )
1128
+ elif legacy_status == "no_threshold_met":
1129
+ status = "ok"
1130
+ error_code = "threshold_target_not_met"
1131
+ error_detail = note
1132
+
1133
+ entry: Dict[str, Any] = {
1134
+ "created_utc": utc_now_iso(),
1135
+ "qasm": {
1136
+ "file": qasm_file,
1137
+ "path": qasm_abs,
1138
+ "sha256": qasm_hash,
1139
+ "bytes": int(qasm_bytes),
1140
+ "features": feats,
1141
+ },
1142
+ "backend": backend,
1143
+ "precision": precision,
1144
+ "selection": {
1145
+ "mirror_metric": args.mirror_metric,
1146
+ "mirror_mode_resolved": mirror_mode_sel,
1147
+ "target": target,
1148
+ "selected_threshold": int(selected_thr),
1149
+ "selected_mirror_metric_value": None if sel_mr is None else sel_mr.metric_value,
1150
+ "stop_when": args.stop_when,
1151
+ },
1152
+ "mirror_sweep": [
1153
+ {
1154
+ "threshold": mr.threshold,
1155
+ "metric_value": mr.metric_value,
1156
+ "p_return_zero": mr.p_return_zero,
1157
+ "sdk_get_fidelity": mr.sdk_get_fidelity,
1158
+ "sdk_peakmem0": mr.sdk_peakmem0,
1159
+ "run_wall_s": mr.run_wall_s,
1160
+ "peak_rss_mb": mr.peak_rss_mb,
1161
+ "returncode": mr.returncode,
1162
+ "note": mr.note,
1163
+ "json_path": mr.json_path if (mr.json_path in keep_mirror_paths) else "",
1164
+ "probe": None if pr is None else {
1165
+ "shots": pr.shots,
1166
+ "run_wall_s": pr.run_wall_s,
1167
+ "peak_rss_mb": pr.peak_rss_mb,
1168
+ "returncode": pr.returncode,
1169
+ "note": pr.note,
1170
+ },
1171
+ }
1172
+ for mr, pr in zip(mirror_runs, probe_runs)
1173
+ ],
1174
+ "mirror_verify": None if verify_mr is None else {
1175
+ "threshold": verify_mr.threshold,
1176
+ "p_return_zero": verify_mr.p_return_zero,
1177
+ "sdk_get_fidelity": verify_mr.sdk_get_fidelity,
1178
+ "sdk_peakmem0": verify_mr.sdk_peakmem0,
1179
+ "run_wall_s": verify_mr.run_wall_s,
1180
+ "peak_rss_mb": verify_mr.peak_rss_mb,
1181
+ "returncode": verify_mr.returncode,
1182
+ "note": verify_mr.note,
1183
+ "json_path": verify_mr.json_path,
1184
+ },
1185
+ "bench": {
1186
+ # Build/setup timing: state_run.run_wall_s (1-shot forward row), not probe_wall_s.
1187
+ "shots_state": int(args.shots_state),
1188
+ "shots_hist": int(args.shots_hist),
1189
+ "state_run": {
1190
+ "threshold": rr_state.threshold,
1191
+ "shots": rr_state.shots,
1192
+ "run_wall_s": rr_state.run_wall_s,
1193
+ "peak_rss_mb": rr_state.peak_rss_mb,
1194
+ "sdk_get_fidelity": rr_state.sdk_get_fidelity,
1195
+ "sdk_peakmem0": rr_state.sdk_peakmem0,
1196
+ "returncode": rr_state.returncode,
1197
+ "note": rr_state.note,
1198
+ "json_path": rr_state.json_path,
1199
+ },
1200
+ "hist_run": {
1201
+ "threshold": rr_hist.threshold,
1202
+ "shots": rr_hist.shots,
1203
+ "run_wall_s": rr_hist.run_wall_s,
1204
+ "peak_rss_mb": rr_hist.peak_rss_mb,
1205
+ "unique_outcomes": rr_hist.unique_outcomes,
1206
+ "tail_mass": rr_hist.tail_mass,
1207
+ "sdk_get_fidelity": rr_hist.sdk_get_fidelity,
1208
+ "sdk_peakmem0": rr_hist.sdk_peakmem0,
1209
+ "returncode": rr_hist.returncode,
1210
+ "note": rr_hist.note,
1211
+ "json_path": rr_hist.json_path,
1212
+ },
1213
+ "derived": {
1214
+ "per_shot_s_est": per_shot_est,
1215
+ },
1216
+ },
1217
+ "status": status,
1218
+ "legacy_status": legacy_status,
1219
+ "error_code": error_code,
1220
+ "error_detail": error_detail,
1221
+ "note": note,
1222
+ }
1223
+ report["runs"].append(entry)
1224
+
1225
+ if f_run_record and features_ref_rel:
1226
+ fidelity_metric_source = "explicit" if args.mirror_metric_explicit else "default"
1227
+ rec = build_run_record(
1228
+ content_hash=qasm_hash,
1229
+ features_ref=features_ref_rel,
1230
+ fidelity_metric_source=fidelity_metric_source,
1231
+ created_utc=entry["created_utc"],
1232
+ qasm_file=qasm_file,
1233
+ qasm_path=qasm_abs,
1234
+ backend=backend,
1235
+ backend_id=backend,
1236
+ precision=precision,
1237
+ env_id="",
1238
+ run_kind="job_summary",
1239
+ status=status,
1240
+ selected_threshold=selected_thr,
1241
+ returncode=None,
1242
+ peak_rss_mb=rr_hist.peak_rss_mb,
1243
+ mirror_wall_s=None if sel_mr is None else sel_mr.run_wall_s,
1244
+ probe_wall_s=None if sel_probe is None else sel_probe.run_wall_s, # optional probe run; build = 1-shot state row
1245
+ runner_wall_s=rr_hist.run_wall_s,
1246
+ shots_mirror=int(args.shots_mirror),
1247
+ shots_runner=int(args.shots_hist),
1248
+ error_code=error_code,
1249
+ error_detail=error_detail,
1250
+ error_type=error_code,
1251
+ error_message=error_detail,
1252
+ )
1253
+ f_run_record.write(json.dumps(rec, sort_keys=True) + "\n")
1254
+ f_run_record.flush()
1255
+
1256
+ mv = None if sel_mr is None else sel_mr.metric_value
1257
+ t_m = None if sel_mr is None else sel_mr.run_wall_s
1258
+ n_q = feats.get("qasm_n_qubits_decl", None)
1259
+
1260
+ verify_p0 = None if verify_mr is None else verify_mr.p_return_zero
1261
+ verify_str = "" if verify_mr is None else f" verify_p0={fmt_opt(verify_p0,6)}"
1262
+
1263
+ print(
1264
+ f"[summary {job_idx:03d}/{total_jobs:03d}] "
1265
+ f"{qasm_file} n={fmt_int(n_q)} "
1266
+ f"backend={backend} prec={precision} thr={selected_thr} "
1267
+ f"{args.mirror_metric}={fmt_opt(mv,8)} mirror_wall_s={fmt_opt(t_m,3)}s "
1268
+ f"probe_wall_s={fmt_opt(rr_state.run_wall_s,3)}s "
1269
+ f"runner_wall_s={fmt_opt(rr_hist.run_wall_s,3)}s "
1270
+ f"mem={fmt_opt(rr_hist.peak_rss_mb,1)}MB "
1271
+ f"status={status}{verify_str}",
1272
+ flush=True
1273
+ )
1274
+
1275
+ # CSV row (legacy + canonical fields)
1276
+ row = {k: "" for k in csv_fields}
1277
+ row.update({
1278
+ "created_utc": entry["created_utc"],
1279
+ "qasm_file": qasm_file,
1280
+ "qasm_path": qasm_abs,
1281
+ "qasm_sha256": qasm_hash,
1282
+ "qasm_bytes": qasm_bytes,
1283
+ "backend": backend,
1284
+ "backend_id": backend,
1285
+ "precision": precision,
1286
+ "env_id": "",
1287
+ "mirror_metric": args.mirror_metric,
1288
+ "target": target,
1289
+ "selected_threshold": selected_thr,
1290
+ "mirror_metric_selected": "" if mv is None else mv,
1291
+ "mirror_p_return_zero_selected": "" if (sel_mr is None or sel_mr.p_return_zero is None) else sel_mr.p_return_zero,
1292
+ "mirror_sdk_get_fidelity_selected": "" if (sel_mr is None or sel_mr.sdk_get_fidelity is None) else sel_mr.sdk_get_fidelity,
1293
+ "mirror_sdk_peakmem0_selected": "" if (sel_mr is None or sel_mr.sdk_peakmem0 is None) else sel_mr.sdk_peakmem0,
1294
+ "mirror_run_wall_s_selected": "" if t_m is None else t_m,
1295
+ "mirror_peak_rss_mb_selected": "" if (sel_mr is None or sel_mr.peak_rss_mb is None) else sel_mr.peak_rss_mb,
1296
+ "probe_shots": int(args.shots_probe) if int(args.shots_probe) > 0 else "",
1297
+ "probe_run_wall_s_selected": "" if (sel_probe is None or sel_probe.run_wall_s is None) else sel_probe.run_wall_s,
1298
+ "probe_peak_rss_mb_selected": "" if (sel_probe is None or sel_probe.peak_rss_mb is None) else sel_probe.peak_rss_mb,
1299
+ "runner_t_state_s": "" if rr_state.run_wall_s is None else rr_state.run_wall_s,
1300
+ "runner_t_hist_s": "" if rr_hist.run_wall_s is None else rr_hist.run_wall_s,
1301
+ "runner_shots_state": int(args.shots_state),
1302
+ "runner_shots_hist": int(args.shots_hist),
1303
+ "runner_per_shot_s_est": "" if per_shot_est is None else per_shot_est,
1304
+ "runner_hist_unique_outcomes": "" if rr_hist.unique_outcomes is None else rr_hist.unique_outcomes,
1305
+ "runner_hist_tail_mass": "" if rr_hist.tail_mass is None else rr_hist.tail_mass,
1306
+ "runner_state_sdk_get_fidelity": "" if rr_state.sdk_get_fidelity is None else rr_state.sdk_get_fidelity,
1307
+ "runner_state_sdk_peakmem0": "" if rr_state.sdk_peakmem0 is None else rr_state.sdk_peakmem0,
1308
+ "runner_hist_sdk_get_fidelity": "" if rr_hist.sdk_get_fidelity is None else rr_hist.sdk_get_fidelity,
1309
+ "runner_hist_sdk_peakmem0": "" if rr_hist.sdk_peakmem0 is None else rr_hist.sdk_peakmem0,
1310
+ "runner_state_json": rr_state.json_path,
1311
+ "runner_hist_json": rr_hist.json_path,
1312
+ "status": status,
1313
+ "error_code": error_code,
1314
+ "error_detail": error_detail,
1315
+ "mirror_wall_s": "" if t_m is None else t_m,
1316
+ "probe_wall_s": "" if (sel_probe is None or sel_probe.run_wall_s is None) else sel_probe.run_wall_s,
1317
+ "runner_wall_s": "" if rr_hist.run_wall_s is None else rr_hist.run_wall_s,
1318
+ "shots_mirror": int(args.shots_mirror),
1319
+ "shots_runner": int(args.shots_hist),
1320
+ "peak_rss_mb": "" if rr_hist.peak_rss_mb is None else rr_hist.peak_rss_mb,
1321
+ "note": note,
1322
+ })
1323
+ row.update(feats)
1324
+ writer.writerow(row)
1325
+
1326
+ finally:
1327
+ f_csv.close()
1328
+ if f_run_record is not None:
1329
+ f_run_record.close()
1330
+
1331
+ with open(args.report_json, "w", encoding="utf-8") as f_out:
1332
+ json.dump(report, f_out, indent=2, sort_keys=True)
1333
+
1334
+ print("===== done =====", flush=True)
1335
+ print(f"[ok] wrote report: {args.report_json}", flush=True)
1336
+ print(f"[ok] wrote dataset: {args.dataset_csv}", flush=True)
1337
+ if run_record_path:
1338
+ print(f"[ok] appended run records: {run_record_path}", flush=True)
1339
+
1340
+
1341
+ if __name__ == "__main__":
1342
+ try:
1343
+ main()
1344
+ except SystemExit:
1345
+ raise
1346
+ except Exception as e:
1347
+ import traceback
1348
+ traceback.print_exc()
1349
+ die(f"Unhandled exception: {e}")
1350
+