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,459 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ qcoder.tools.mirror
4
+
5
+ Mirror runner:
6
+ - Builds mirror QASM (U then U†) using qcoder.core.qasm2.mirror_build
7
+ - Loads mirror circuit in QuantumRingsLib
8
+ - Runs with measurements
9
+ - Computes mirror "success probability": P(measuring all-zeros) from counts
10
+ - Records wall time + peak RSS (external sampler)
11
+ - Records SDK-native metrics best-effort:
12
+ - result.get_fidelity() (semantics TBD; logged for analysis)
13
+ - result.get_peakmemorysize(0) (signature requires an int)
14
+ - result.get_memory()
15
+
16
+ Why counts-based mirror?
17
+ - Venkat says get_amplitude is not a published API; do not use.
18
+ - Counts-based "return-to-zero probability" is a standard mirror-style metric.
19
+
20
+ NOTE:
21
+ - This is NOT guaranteed to correlate perfectly with forward-distribution fidelity,
22
+ but it is at least well-defined and doesn’t rely on unpublished amplitude APIs.
23
+ """
24
+
25
+ from qcoder.tools import qr_dll_bootstrap
26
+
27
+ qr_dll_bootstrap.bootstrap()
28
+
29
+ import argparse
30
+ import datetime as _dt
31
+ import hashlib
32
+ import importlib.metadata as _imd
33
+ import json
34
+ import os
35
+ import platform
36
+ import re
37
+ import subprocess
38
+ import sys
39
+ import time
40
+ from typing import Any, Dict, List, Optional, Tuple
41
+
42
+ import psutil
43
+
44
+ from QuantumRingsLib import QuantumRingsProvider, QuantumCircuit, job_monitor
45
+
46
+ from qcoder.core.qasm2.mirror_build import build_mirror_qasm, UnsupportedQasm
47
+ from qcoder.core.qasm2.adjoint_eligibility import classify_mirror_eligibility
48
+
49
+
50
+ # -----------------------------
51
+ # Helpers
52
+ # -----------------------------
53
+
54
+ def die(msg: str, code: int = 1) -> None:
55
+ print(f"[fatal] {msg}", file=sys.stderr, flush=True)
56
+ sys.exit(code)
57
+
58
+ def sha256_file(path: str) -> str:
59
+ h = hashlib.sha256()
60
+ with open(path, "rb") as f:
61
+ while True:
62
+ b = f.read(1 << 20)
63
+ if not b:
64
+ break
65
+ h.update(b)
66
+ return h.hexdigest()
67
+
68
+ def resolve_backend_id(provider: QuantumRingsProvider, backend_arg: str, precision: str) -> str:
69
+ """
70
+ Accept friendly names (Scarlet/Amber/Serin or CPU/GPU/Both) OR raw backend ids.
71
+ """
72
+ if not backend_arg:
73
+ return "scarlet_quantum_rings"
74
+
75
+ b = backend_arg.strip()
76
+ if "_" in b or b.endswith("_quantum_rings"):
77
+ return b
78
+
79
+ key = b.lower()
80
+ if key in ("scarlet", "cpu"):
81
+ candidates = ["scarlet_quantum_rings", "scarlet"]
82
+ elif key in ("amber", "gpu"):
83
+ candidates = ["amber_quantum_rings", "amber"]
84
+ elif key in ("serin", "both", "hybrid"):
85
+ candidates = ["serin_quantum_rings", "serin"]
86
+ else:
87
+ candidates = [b]
88
+
89
+ last_err = None
90
+ for cid in candidates:
91
+ try:
92
+ _ = provider.get_backend(cid, precision=precision)
93
+ return cid
94
+ except Exception as e:
95
+ last_err = e
96
+ die(f"Could not resolve backend '{backend_arg}'. Tried: {candidates}. Last error: {last_err}")
97
+ return b # unreachable
98
+
99
+ def default_json_out(qasm_path: str, backend_id: str, precision: str, threshold: int, shots: int, out_dir: str) -> str:
100
+ base = os.path.splitext(os.path.basename(qasm_path))[0]
101
+ fname = f"{base}__MIRROR__{backend_id}__{precision}__thr{threshold}__shots{shots}.json"
102
+ return os.path.join(out_dir, fname)
103
+
104
+ def try_nvidia_smi() -> Optional[Dict[str, Any]]:
105
+ try:
106
+ cmd = [
107
+ "nvidia-smi",
108
+ "--query-gpu=name,driver_version,memory.total",
109
+ "--format=csv,noheader,nounits",
110
+ ]
111
+ p = subprocess.run(cmd, capture_output=True, text=True, check=False)
112
+ if p.returncode != 0:
113
+ return None
114
+ line = (p.stdout.strip().splitlines() or [""])[0].strip()
115
+ if not line:
116
+ return None
117
+ parts = [x.strip() for x in line.split(",")]
118
+ if len(parts) >= 3:
119
+ return {
120
+ "gpu_name": parts[0],
121
+ "driver_version": parts[1],
122
+ "gpu_memory_total_mb": float(parts[2]) if parts[2].replace(".", "", 1).isdigit() else parts[2],
123
+ }
124
+ return {"raw": line}
125
+ except Exception:
126
+ return None
127
+
128
+ def start_peak_rss_sampler(interval_s: float = 0.05):
129
+ import threading
130
+ import time as _time
131
+ import os as _os
132
+
133
+ proc = psutil.Process(_os.getpid())
134
+ peak = {"rss": 0}
135
+ running = {"on": True}
136
+
137
+ def _loop():
138
+ while running["on"]:
139
+ try:
140
+ rss = proc.memory_info().rss
141
+ if rss > peak["rss"]:
142
+ peak["rss"] = rss
143
+ except Exception:
144
+ pass
145
+ _time.sleep(interval_s)
146
+
147
+ th = threading.Thread(target=_loop, daemon=True)
148
+ th.start()
149
+ return running, th, peak
150
+
151
+ def stop_peak_rss_sampler(running, th):
152
+ running["on"] = False
153
+ th.join(timeout=1.0)
154
+
155
+ def get_dist_version(names: List[str]) -> Optional[str]:
156
+ for n in names:
157
+ try:
158
+ return _imd.version(n)
159
+ except Exception:
160
+ pass
161
+ return None
162
+
163
+ def safe_call_noarg(result: Any, name: str):
164
+ if not hasattr(result, name):
165
+ return None, "missing"
166
+ try:
167
+ return getattr(result, name)(), None
168
+ except Exception as e:
169
+ return None, repr(e)
170
+
171
+ def safe_call_1arg(result: Any, name: str, arg0: int):
172
+ if not hasattr(result, name):
173
+ return None, "missing"
174
+ try:
175
+ return getattr(result, name)(arg0), None
176
+ except Exception as e:
177
+ return None, repr(e)
178
+
179
+ def jsonable_value(x: Any) -> Any:
180
+ if x is None:
181
+ return None
182
+ if isinstance(x, (str, int, float, bool)):
183
+ return x
184
+ return repr(x)
185
+
186
+ def normalize_counts_keys(counts: Dict[Any, Any]) -> Dict[str, int]:
187
+ out: Dict[str, int] = {}
188
+ for k, v in counts.items():
189
+ ks = str(k).replace(" ", "")
190
+ out[ks] = int(v)
191
+ return out
192
+
193
+ _MEASURE_LINE_RE = re.compile(r"^\s*measure\s+.*?;\s*$", flags=re.M | re.I)
194
+ _CREG_DECL_RE = re.compile(r"^\s*creg\s+.*?;\s*$", flags=re.M | re.I)
195
+
196
+ def strip_measures_and_cregs(qasm: str) -> str:
197
+ """
198
+ Make QASM unitary-ish by removing creg declarations and measure lines.
199
+ This is required for qc.inverse() to work reliably.
200
+ """
201
+ q = _MEASURE_LINE_RE.sub("", qasm)
202
+ q = _CREG_DECL_RE.sub("", q)
203
+ return q
204
+
205
+
206
+
207
+ # -----------------------------
208
+ # Main
209
+ # -----------------------------
210
+
211
+ def main() -> None:
212
+ ap = argparse.ArgumentParser(description="QR12 mirror runner: mirror QASM + counts-based return-to-zero probability.")
213
+ ap.add_argument("qasm", help="Path to original QASM2 file (U)")
214
+ ap.add_argument("--backend", default="Scarlet", help="Scarlet|Amber|Serin (or CPU/GPU/Both) OR backend id")
215
+ ap.add_argument("--precision", choices=["single", "double"], default="single")
216
+ ap.add_argument("--threshold", type=int, default=128)
217
+ ap.add_argument("--shots", type=int, default=1024,
218
+ help="Shots for mirror counts. (Unlike amplitude-based mirror, you usually want >1.)")
219
+ ap.add_argument("--json-out", default=None)
220
+ ap.add_argument("--out-dir", default="runs")
221
+ ap.add_argument("--save-mirror-qasm", default=None, help="Optional: save generated mirror QASM for debugging")
222
+ ap.add_argument("--topk", type=int, default=20, help="Store top-K outcomes in the JSON")
223
+ ap.add_argument(
224
+ "--mirror-mode",
225
+ choices=["qasm_counts", "sdk_inverse_fidelity"],
226
+ default="qasm_counts",
227
+ help="qasm_counts: build mirror via qcoder.core.qasm2.mirror_build + measurements + p_return_zero. "
228
+ "sdk_inverse_fidelity: build mirror via qc.inverse()+append and use result.get_fidelity()."
229
+ )
230
+
231
+ args = ap.parse_args()
232
+
233
+ print("===== QR12 Mirror Runner =====", flush=True)
234
+
235
+ os.makedirs(args.out_dir, exist_ok=True)
236
+
237
+ sdk_pkg = "quantumrings-cuda13x"
238
+ sdk_ver = get_dist_version([sdk_pkg, "quantumrings_cuda13x", "QuantumRingsLib"])
239
+ print(f"[env] sdk_pkg={sdk_pkg} sdk_version={sdk_ver} python={sys.version.split()[0]}", flush=True)
240
+
241
+ # Provider first (important for entitlement context)
242
+ provider = QuantumRingsProvider()
243
+ backend_id = resolve_backend_id(provider, args.backend, args.precision)
244
+ backend = provider.get_backend(backend_id, precision=args.precision)
245
+ print("[backend]", backend, flush=True)
246
+
247
+ # Read original QASM
248
+ with open(args.qasm, "r", encoding="utf-8", errors="replace") as f:
249
+ orig_text = f.read()
250
+ eligibility, eligibility_reason = classify_mirror_eligibility(orig_text)
251
+ if eligibility != "ok":
252
+ die(f"Mirror eligibility={eligibility}: {eligibility_reason}", code=2)
253
+
254
+ # ---- Build mirror circuit (two modes) ----
255
+ qc_mirror = None
256
+ n_qubits = None
257
+ mirror_qasm = None # only set in qasm_counts
258
+
259
+ if args.mirror_mode == "sdk_inverse_fidelity":
260
+ # Venkat-style: unitary circuit -> inverse -> append
261
+ unitary_qasm = strip_measures_and_cregs(orig_text)
262
+ try:
263
+ qc_u = QuantumCircuit.from_qasm_str(unitary_qasm)
264
+ except Exception as e:
265
+ die(f"Unitary QASM failed to load for sdk_inverse_fidelity: {e}", code=3)
266
+
267
+ try:
268
+ qc_inv = qc_u.inverse()
269
+ qc_u.append(qc_inv)
270
+ except Exception as e:
271
+ die(f"qc.inverse()/append failed in sdk_inverse_fidelity: {e}", code=4)
272
+
273
+ qc_mirror = qc_u
274
+ try:
275
+ n_qubits = int(qc_mirror.num_qubits)
276
+ except Exception:
277
+ n_qubits = None
278
+
279
+ # Venkat-like run kwargs (keep simple)
280
+ run_kwargs = dict(
281
+ shots=int(args.shots),
282
+ mode="async",
283
+ quiet=True,
284
+ performance="custom",
285
+ threshold=int(args.threshold),
286
+ )
287
+
288
+ else:
289
+ # Existing method: QASM mirror + measurements + counts-based p_return_zero
290
+ try:
291
+ mirror_qasm, n_qubits = build_mirror_qasm(orig_text, drop_barriers=True)
292
+ except UnsupportedQasm as e:
293
+ die(f"Unsupported QASM for mirror inversion: {e}", code=2)
294
+
295
+ if args.save_mirror_qasm:
296
+ os.makedirs(os.path.dirname(args.save_mirror_qasm) or ".", exist_ok=True)
297
+ with open(args.save_mirror_qasm, "w", encoding="utf-8") as f:
298
+ f.write(mirror_qasm)
299
+ print(f"[ok] wrote mirror QASM: {args.save_mirror_qasm}", flush=True)
300
+
301
+ try:
302
+ qc_mirror = QuantumCircuit.from_qasm_str(mirror_qasm)
303
+ except Exception as e:
304
+ die(f"Mirror QASM failed to load in SDK: {e}", code=3)
305
+
306
+ run_kwargs = dict(
307
+ shots=int(args.shots),
308
+ mode="sync",
309
+ generate_amplitude=False,
310
+ quiet=True,
311
+ performance="custom",
312
+ threshold=int(args.threshold),
313
+ transfer_to_cpu=True,
314
+ max_threads=5,
315
+ )
316
+
317
+
318
+ print(f"[run] backend_id={backend_id} precision={args.precision} thr={args.threshold} shots={args.shots}", flush=True)
319
+
320
+ running, th, peak = start_peak_rss_sampler(0.05)
321
+ t_submit = time.perf_counter()
322
+
323
+ job = backend.run(qc_mirror, **run_kwargs)
324
+ job_monitor(job, quiet=True)
325
+ result = job.result()
326
+
327
+ t_done = time.perf_counter()
328
+ stop_peak_rss_sampler(running, th)
329
+
330
+ run_wall_s = t_done - t_submit
331
+ peak_rss_bytes = int(peak["rss"])
332
+
333
+ # Mirror metrics
334
+ p_zero = None
335
+ shots_from_counts = 0
336
+ items = []
337
+ top_items = []
338
+
339
+ zero_key = None
340
+ try:
341
+ if n_qubits is not None:
342
+ zero_key = "0" * int(n_qubits)
343
+ except Exception:
344
+ zero_key = None
345
+
346
+ if args.mirror_mode == "qasm_counts":
347
+ counts_raw = result.get_counts()
348
+ if not isinstance(counts_raw, dict):
349
+ die(f"result.get_counts() returned unexpected type: {type(counts_raw)}")
350
+
351
+ counts = normalize_counts_keys(counts_raw)
352
+ shots_from_counts = sum(counts.values()) if counts else 0
353
+
354
+ if n_qubits is not None:
355
+ zero_key = "0" * int(n_qubits)
356
+ p_zero = (counts.get(zero_key, 0) / shots_from_counts) if shots_from_counts else None
357
+
358
+ items = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))
359
+ topk = max(1, int(args.topk))
360
+ top_items = items[:topk]
361
+ else:
362
+ # sdk_inverse_fidelity: no measurements, so no counts-based p_return_zero
363
+ pass
364
+
365
+
366
+ # SDK-native metrics (best-effort)
367
+ sdk_metrics: Dict[str, Any] = {"errors": {}}
368
+
369
+ gf, gf_err = safe_call_noarg(result, "get_fidelity")
370
+ if gf_err:
371
+ sdk_metrics["errors"]["get_fidelity"] = gf_err
372
+ sdk_metrics["get_fidelity"] = jsonable_value(gf)
373
+
374
+ # IMPORTANT: signature requires one int arg
375
+ gpm0, gpm0_err = safe_call_1arg(result, "get_peakmemorysize", 0)
376
+ if gpm0_err:
377
+ sdk_metrics["errors"]["get_peakmemorysize(0)"] = gpm0_err
378
+ sdk_metrics["get_peakmemorysize_0"] = jsonable_value(gpm0)
379
+
380
+ memx, memx_err = safe_call_noarg(result, "get_memory")
381
+ if memx_err:
382
+ sdk_metrics["errors"]["get_memory"] = memx_err
383
+ sdk_metrics["get_memory"] = jsonable_value(memx)
384
+
385
+ # Output path
386
+ if args.json_out:
387
+ out_path = args.json_out
388
+ else:
389
+ out_path = default_json_out(args.qasm, backend_id, args.precision, int(args.threshold), int(args.shots), args.out_dir)
390
+ os.makedirs(os.path.dirname(out_path) or ".", exist_ok=True)
391
+
392
+ gpu_info = try_nvidia_smi()
393
+
394
+ payload: Dict[str, Any] = {
395
+ "meta": {
396
+ "created_utc": _dt.datetime.now(_dt.timezone.utc).replace(microsecond=0).isoformat(),
397
+ "original_qasm_file": os.path.basename(args.qasm),
398
+ "original_qasm_path": os.path.abspath(args.qasm),
399
+ "original_qasm_sha256": sha256_file(args.qasm),
400
+ "n_qubits": int(n_qubits),
401
+ "backend": str(args.backend),
402
+ "backend_id": backend_id,
403
+ "backend_repr": str(backend),
404
+ "precision": args.precision,
405
+ "threshold": int(args.threshold),
406
+ "shots": int(args.shots),
407
+ "mode": args.mirror_mode,
408
+ "sdk_pkg": sdk_pkg,
409
+ "sdk_version": sdk_ver,
410
+ },
411
+ "sdk": {
412
+ **run_kwargs
413
+ },
414
+ "system": {
415
+ "hostname": platform.node(),
416
+ "platform": platform.platform(),
417
+ "python": sys.version.split()[0],
418
+ "cpu_count": os.cpu_count(),
419
+ "gpu": gpu_info,
420
+ },
421
+ "timing_s": {
422
+ "run_wall_s": float(run_wall_s),
423
+ },
424
+ "memory": {
425
+ "peak_rss_bytes": int(peak_rss_bytes),
426
+ "peak_rss_mb": float(peak_rss_bytes) / (1024.0 * 1024.0),
427
+ },
428
+ "sdk_metrics": sdk_metrics,
429
+ "mirror_metrics": {
430
+ "shots_from_counts": int(shots_from_counts),
431
+ "p_return_zero": None if p_zero is None else float(p_zero),
432
+ "return_zero_bitstring": zero_key,
433
+ "unique_outcomes": int(len(items)),
434
+ "top": [{"bitstring": b, "count": int(c)} for b, c in top_items],
435
+ },
436
+ }
437
+
438
+ with open(out_path, "w", encoding="utf-8") as f:
439
+ json.dump(payload, f, indent=2, sort_keys=True)
440
+
441
+ print(f"[ok] wrote JSON: {out_path}", flush=True)
442
+ print(f"[mirror] p_return_zero={p_zero} unique_outcomes={len(items)}", flush=True)
443
+ print(f"[timing] run_wall_s={run_wall_s:.3f} [mem] peak_rss_mb={payload['memory']['peak_rss_mb']:.1f}", flush=True)
444
+ print(f"[sdk] get_fidelity={sdk_metrics.get('get_fidelity')} get_peakmemorysize_0={sdk_metrics.get('get_peakmemorysize_0')}", flush=True)
445
+ if sdk_metrics.get("errors"):
446
+ print(f"[sdk] errors={sdk_metrics['errors']}", flush=True)
447
+ print("===== done =====", flush=True)
448
+
449
+
450
+ if __name__ == "__main__":
451
+ try:
452
+ main()
453
+ except SystemExit:
454
+ raise
455
+ except Exception as e:
456
+ import traceback
457
+ traceback.print_exc()
458
+ die(f"Unhandled exception: {e}")
459
+