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.
- qcoder/__init__.py +3 -0
- qcoder/__main__.py +6 -0
- qcoder/cli.py +116 -0
- qcoder/core/__init__.py +1 -0
- qcoder/core/context.py +16 -0
- qcoder/core/qasm2/__init__.py +1 -0
- qcoder/core/qasm2/adjoint_eligibility.py +128 -0
- qcoder/core/qasm2/mirror_build.py +234 -0
- qcoder/core/run_config.py +84 -0
- qcoder/core/schema.py +26 -0
- qcoder/engines/feature_extraction/adapters/__init__.py +1 -0
- qcoder/engines/feature_extraction/adapters/qiskit_intake.py +46 -0
- qcoder/engines/feature_extraction/extractor.py +43 -0
- qcoder/engines/feature_extraction/features/compute_v0.py +157 -0
- qcoder/engines/feature_extraction/features/schema_v0.py +84 -0
- qcoder/engines/feature_extraction/ir.py +41 -0
- qcoder/engines/feature_extraction/labeling.py +68 -0
- qcoder/engines/feature_extraction/parsers/__init__.py +21 -0
- qcoder/engines/feature_extraction/qasm2_regex_parser.py +184 -0
- qcoder/engines/feature_extraction/reps/cut_profile.py +106 -0
- qcoder/engines/feature_extraction/reps/depth.py +47 -0
- qcoder/engines/feature_extraction/reps/entangling_layers.py +57 -0
- qcoder/engines/feature_extraction/reps/gate_set_stats.py +82 -0
- qcoder/engines/feature_extraction/reps/interaction_graph.py +30 -0
- qcoder/engines/feature_extraction/reps/interaction_graph_metrics.py +113 -0
- qcoder/engines/feature_extraction/reps/spans.py +89 -0
- qcoder/engines/prediction_model/__init__.py +16 -0
- qcoder/engines/prediction_model/artifact.py +85 -0
- qcoder/engines/prediction_model/engine.py +209 -0
- qcoder/engines/prediction_model/models.py +62 -0
- qcoder/engines/prediction_model/policy.py +45 -0
- qcoder/engines/prediction_model/schema_alignment.py +41 -0
- qcoder/engines/quantumness/__init__.py +8 -0
- qcoder/engines/quantumness/scorer.py +254 -0
- qcoder/pipelines/analyze.py +131 -0
- qcoder/pipelines/batch.py +56 -0
- qcoder/tools/analyze.py +88 -0
- qcoder/tools/analyze_shot_scaling.py +239 -0
- qcoder/tools/batch.py +39 -0
- qcoder/tools/generate_corpus.py +491 -0
- qcoder/tools/harness.py +15 -0
- qcoder/tools/inspect_corpus_features.py +273 -0
- qcoder/tools/join_runs_features.py +252 -0
- qcoder/tools/mirror.py +15 -0
- qcoder/tools/predict_baseline.py +347 -0
- qcoder/tools/qr_dll_bootstrap.py +31 -0
- qcoder/tools/runner.py +15 -0
- qcoder/tools/runners/__init__.py +1 -0
- qcoder/tools/runners/quantum_rings/__init__.py +1 -0
- qcoder/tools/runners/quantum_rings/v12/__init__.py +1 -0
- qcoder/tools/runners/quantum_rings/v12/harness.py +1350 -0
- qcoder/tools/runners/quantum_rings/v12/mirror.py +459 -0
- qcoder/tools/runners/quantum_rings/v12/runner.py +549 -0
- qcoder/tools/train_baseline_models.py +619 -0
- qcoder/tools/validate_baseline.py +307 -0
- qcoder-0.1.0a0.dist-info/METADATA +86 -0
- qcoder-0.1.0a0.dist-info/RECORD +62 -0
- qcoder-0.1.0a0.dist-info/WHEEL +5 -0
- qcoder-0.1.0a0.dist-info/entry_points.txt +2 -0
- qcoder-0.1.0a0.dist-info/licenses/LICENSE +201 -0
- qcoder-0.1.0a0.dist-info/licenses/NOTICE +11 -0
- 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
|
+
|