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
qcoder/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ __all__ = []
2
+ __version__ = "0.1.0a0"
3
+ file = __file__
qcoder/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from qcoder.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
qcoder/cli.py ADDED
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+
7
+ from qcoder.pipelines.analyze import analyze_qasm
8
+ from qcoder.tools.batch import analyze_qasm_dir_to_jsonl
9
+
10
+
11
+ def _cmd_analyze(argv: list[str]) -> int:
12
+ p = argparse.ArgumentParser(prog="qcoder analyze", add_help=True)
13
+ p.add_argument("qasm", help="Path to a .qasm file")
14
+
15
+ # Circuit identity / metadata
16
+ p.add_argument("--id", dest="circuit_id", default=None, help="Optional circuit id")
17
+ p.add_argument("--name", dest="circuit_name", default=None, help="Optional circuit name")
18
+
19
+ # Run config (conditioning vars for predictors; not used by feature extraction)
20
+ p.add_argument(
21
+ "--processor",
22
+ "--backend",
23
+ dest="processor",
24
+ default="CPU",
25
+ help='Processor/backend label (aliases: Scarlet/Amber, CPU/GPU)',
26
+ )
27
+ p.add_argument("--precision", default="single", help="Precision: single|double (aliases: fp32/fp64)")
28
+ p.add_argument("--threshold", type=float, default=None, help="Optional threshold/bond-dim conditioning value")
29
+ p.add_argument("--mirror-artifacts-dir", default=None, metavar="DIR", help="If set, write mirror QASM to DIR and add adjoint_supported/adjoint_reason/mirror_qasm_ref to output")
30
+
31
+ p.add_argument("--json", action="store_true", help="Emit machine-readable JSON")
32
+ args = p.parse_args(argv)
33
+
34
+ report = analyze_qasm(
35
+ args.qasm,
36
+ circuit_id=args.circuit_id,
37
+ circuit_name=args.circuit_name,
38
+ processor=args.processor,
39
+ precision=args.precision,
40
+ threshold=args.threshold,
41
+ mirror_artifacts_dir=args.mirror_artifacts_dir or None,
42
+ )
43
+
44
+ if args.json:
45
+ print(json.dumps(report.to_json_dict(), indent=2, sort_keys=True))
46
+ return 0
47
+
48
+ ex = report.example
49
+ rc = report.run_config
50
+ print(f"file: {ex.qasm_path}")
51
+ print(f"format: {ex.ir.source_format}")
52
+ if ex.name:
53
+ print(f"name: {ex.name}")
54
+ print(f"function_hint: {ex.function_hint} ({ex.function_source})")
55
+ print(f"processor: {rc.processor} backend: {rc.backend} precision: {rc.precision} threshold: {rc.threshold}")
56
+ print(f"n_qubits: {ex.ir.n_qubits}")
57
+ print(f"n_ops: {ex.ir.n_ops}")
58
+ fv = ex.global_features
59
+ print(f"schema: {fv.schema_version}")
60
+ print(f"n_features: {len(fv.features)}")
61
+ return 0
62
+
63
+
64
+ def _cmd_batch(argv: list[str]) -> int:
65
+ p = argparse.ArgumentParser(prog="qcoder batch", add_help=True)
66
+ p.add_argument("circuits_dir", help="Directory containing QASM files")
67
+ p.add_argument("--out", required=True, help="Output JSONL path")
68
+ p.set_defaults(recursive=True)
69
+ p.add_argument("--recursive", dest="recursive", action="store_true", help="Discover files recursively (default)")
70
+ p.add_argument("--non-recursive", dest="recursive", action="store_false", help="Only discover top-level files")
71
+ p.add_argument("--pattern", default="*.qasm", help="Glob pattern for files (default: *.qasm)")
72
+ p.add_argument("--skip-errors", action="store_true", help="Continue on error, emit error records (default: fail-fast)")
73
+ p.add_argument("--processor", default=None, help="Processor/backend label for run_config")
74
+ p.add_argument("--backend", default=None, help="Backend label (CPU/GPU, etc.)")
75
+ p.add_argument("--precision", default=None, help="Precision: single|double|fp32|fp64")
76
+ p.add_argument("--threshold", type=float, default=None, help="Optional threshold for run_config")
77
+ p.add_argument("--mirror-artifacts-dir", default=None, metavar="DIR", help="If set, write mirror QASM to DIR and add adjoint_supported/adjoint_reason/mirror_qasm_ref to each record")
78
+ args = p.parse_args(argv)
79
+
80
+ n = analyze_qasm_dir_to_jsonl(
81
+ args.circuits_dir,
82
+ args.out,
83
+ processor=args.processor,
84
+ backend=args.backend,
85
+ precision=args.precision,
86
+ threshold=args.threshold,
87
+ recursive=args.recursive,
88
+ pattern=args.pattern,
89
+ fail_fast=not args.skip_errors,
90
+ mirror_artifacts_dir=args.mirror_artifacts_dir or None,
91
+ )
92
+ print(f"Wrote {n} records to {args.out}", file=sys.stderr)
93
+ return 0
94
+
95
+
96
+ def main(argv: list[str] | None = None) -> int:
97
+ if argv is None:
98
+ argv = sys.argv[1:]
99
+
100
+ parser = argparse.ArgumentParser(prog="qcoder")
101
+ sub = parser.add_subparsers(dest="cmd", required=True)
102
+ sub.add_parser("analyze", help="Analyze a QASM file (feature extraction + metadata + run config)")
103
+ sub.add_parser("batch", help="Batch extract directory to JSONL")
104
+ ns, rest = parser.parse_known_args(argv)
105
+
106
+ if ns.cmd == "analyze":
107
+ return _cmd_analyze(rest)
108
+ if ns.cmd == "batch":
109
+ return _cmd_batch(rest)
110
+
111
+ parser.error(f"Unknown command: {ns.cmd}")
112
+ return 2
113
+
114
+
115
+ if __name__ == "__main__":
116
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ # Core shared types and utilities
qcoder/core/context.py ADDED
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ # Backwards-compatible shim.
4
+ # Prefer: from qcoder.core.run_config import RunConfig
5
+ from qcoder.core.run_config import (
6
+ CPU_ALIASES,
7
+ GPU_ALIASES,
8
+ SINGLE_ALIASES,
9
+ DOUBLE_ALIASES,
10
+ RunConfig,
11
+ normalize_backend,
12
+ normalize_precision,
13
+ )
14
+
15
+ # Older code referred to Context; keep it as an alias.
16
+ Context = RunConfig
@@ -0,0 +1 @@
1
+ # QASM2 lightweight utilities (e.g. mirror build)
@@ -0,0 +1,128 @@
1
+ """
2
+ Lightweight adjoint/mirror eligibility for OpenQASM 2.
3
+
4
+ Detects unitary eligibility (no measure/reset; conservative) and attempts
5
+ to generate mirror QASM via existing inversion utilities. Does not modify
6
+ the 48-feature vector; for metadata only.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from dataclasses import dataclass
13
+ from typing import Optional
14
+
15
+ from .mirror_build import UnsupportedQasm, build_mirror_qasm
16
+
17
+ _RE_MEASURE = re.compile(r"^\s*measure\s+", re.I)
18
+ _RE_RESET = re.compile(r"^\s*reset\s+", re.I)
19
+ _RE_IF = re.compile(r"^\s*if\s*\(", re.I)
20
+ _RE_OPENQASM = re.compile(r"^\s*openqasm\s+", re.I)
21
+ _RE_INCLUDE = re.compile(r'^\s*include\s+"[^"]+"\s*;\s*$', re.I)
22
+ _RE_QREG = re.compile(r"^\s*qreg\s+[A-Za-z_]\w*\s*\[\s*\d+\s*\]\s*;\s*$", re.I)
23
+ _RE_CREG = re.compile(r"^\s*creg\s+[A-Za-z_]\w*\s*\[\s*\d+\s*\]\s*;\s*$", re.I)
24
+ _RE_BARRIER = re.compile(r"^\s*barrier\s+", re.I)
25
+ _RE_OP = re.compile(r"^\s*[A-Za-z_]\w*\s*(\([^)]*\))?\s+.+;\s*$")
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class AdjointEligibility:
30
+ adjoint_supported: bool
31
+ adjoint_reason: str
32
+ mirror_qasm: Optional[str] = None
33
+
34
+ def to_metadata_dict(self, *, include_mirror_qasm: bool = False) -> dict:
35
+ out = {
36
+ "adjoint_supported": self.adjoint_supported,
37
+ "adjoint_reason": self.adjoint_reason,
38
+ }
39
+ if include_mirror_qasm and self.mirror_qasm is not None:
40
+ out["mirror_qasm"] = self.mirror_qasm
41
+ return out
42
+
43
+
44
+ def classify_mirror_eligibility(qasm_text: str) -> tuple[str, str]:
45
+ """
46
+ Return (classification, reason) where classification is one of:
47
+ - ok
48
+ - non_unitary
49
+ - parse_error
50
+ Policy:
51
+ - allow terminal measurement block
52
+ - non_unitary on reset
53
+ - non_unitary on measurements before terminal block
54
+ - non_unitary on classical conditional execution (if (...))
55
+ """
56
+ saw_terminal_measure = False
57
+
58
+ for line in qasm_text.splitlines():
59
+ s = line.strip()
60
+ if not s or s.startswith("//"):
61
+ continue
62
+ # Remove trailing inline comments for simple statement checks.
63
+ if "//" in s:
64
+ s = s.split("//", 1)[0].strip()
65
+ if not s:
66
+ continue
67
+
68
+ if _RE_RESET.match(s):
69
+ return "non_unitary", "circuit contains reset (not unitary)"
70
+ if _RE_IF.match(s):
71
+ return "non_unitary", "circuit contains classical conditional execution"
72
+ if _RE_MEASURE.match(s):
73
+ saw_terminal_measure = True
74
+ continue
75
+
76
+ # Non-measurement statement after measure => mid-circuit measurement.
77
+ if saw_terminal_measure:
78
+ return "non_unitary", "circuit has measurement before terminal measurement block"
79
+
80
+ if (
81
+ _RE_OPENQASM.match(s)
82
+ or _RE_INCLUDE.match(s)
83
+ or _RE_QREG.match(s)
84
+ or _RE_CREG.match(s)
85
+ or _RE_BARRIER.match(s)
86
+ or _RE_OP.match(s)
87
+ ):
88
+ continue
89
+
90
+ return "parse_error", f"unrecognized statement: {s[:80]}"
91
+
92
+ return "ok", ""
93
+
94
+
95
+ def check_adjoint_eligibility(
96
+ qasm_text: str,
97
+ *,
98
+ drop_barriers: bool = True,
99
+ include_mirror_qasm: bool = True,
100
+ ) -> AdjointEligibility:
101
+ """
102
+ Check whether the circuit is eligible for mirror/adjoint (unitary) and
103
+ attempt to generate mirror QASM.
104
+
105
+ Returns AdjointEligibility with adjoint_supported, adjoint_reason, and
106
+ optionally mirror_qasm text. Unitary eligibility is conservative:
107
+ presence of measure or reset lines makes the circuit non-unitary.
108
+ """
109
+ cls, reason = classify_mirror_eligibility(qasm_text)
110
+ if cls != "ok":
111
+ return AdjointEligibility(
112
+ adjoint_supported=False,
113
+ adjoint_reason=reason or cls,
114
+ mirror_qasm=None,
115
+ )
116
+ try:
117
+ mirror_qasm, _ = build_mirror_qasm(qasm_text, drop_barriers=drop_barriers)
118
+ return AdjointEligibility(
119
+ adjoint_supported=True,
120
+ adjoint_reason="",
121
+ mirror_qasm=mirror_qasm if include_mirror_qasm else None,
122
+ )
123
+ except UnsupportedQasm as e:
124
+ return AdjointEligibility(
125
+ adjoint_supported=False,
126
+ adjoint_reason=str(e),
127
+ mirror_qasm=None,
128
+ )
@@ -0,0 +1,234 @@
1
+ """
2
+ Build mirror QASM (U then U†) from OpenQASM 2 source for counts-based mirror runs.
3
+
4
+ Tolerates standard OpenQASM 2 include "qelib1.inc"; and preserves it in output.
5
+ Raises UnsupportedQasm for other include statements or gates that cannot be
6
+ inverted in this minimal implementation (no gate definitions, no opaque).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import List, Optional, Tuple
13
+
14
+
15
+ class UnsupportedQasm(Exception):
16
+ """Raised when the QASM cannot be mirrored (e.g. include, unsupported gate)."""
17
+ pass
18
+
19
+
20
+ # Gate name -> (adjoint_param_negate_mask, self_adjoint)
21
+ # u1(t)† = u1(-t); u2(p,l)† = u2(-l,-p); u3(t,p,l)† = u3(-t,-l,-p)
22
+ # cx, cz, swap, id, x, y, z, h are self-adjoint; s<->sdg, t<->tdg
23
+ _ADJOINT_U1 = (True,) # one param negate
24
+ _ADJOINT_U2 = (True, True) # swap and negate both
25
+ _ADJOINT_U3 = (True, True, True) # negate all three
26
+ _SELF_ADJOINT = ()
27
+ _GATE_ADJOINT: dict[str, tuple] = {
28
+ "u1": _ADJOINT_U1,
29
+ "u2": _ADJOINT_U2,
30
+ "u3": _ADJOINT_U3,
31
+ "u": (True, True, True), # U(theta,phi,lambda) same as u3
32
+ "cu3": _ADJOINT_U3,
33
+ "p": _ADJOINT_U1,
34
+ "cp": _ADJOINT_U1,
35
+ "cu1": _ADJOINT_U1,
36
+ "rx": _ADJOINT_U1,
37
+ "ry": _ADJOINT_U1,
38
+ "rz": _ADJOINT_U1,
39
+ "rxx": _ADJOINT_U1,
40
+ "rzz": _ADJOINT_U1,
41
+ "crx": _ADJOINT_U1,
42
+ "cry": _ADJOINT_U1,
43
+ "crz": _ADJOINT_U1,
44
+ "cx": _SELF_ADJOINT,
45
+ "ch": _SELF_ADJOINT,
46
+ "cy": _SELF_ADJOINT,
47
+ "cz": _SELF_ADJOINT,
48
+ "swap": _SELF_ADJOINT,
49
+ "cswap": _SELF_ADJOINT,
50
+ "ccx": _SELF_ADJOINT,
51
+ "rccx": _SELF_ADJOINT,
52
+ "id": _SELF_ADJOINT,
53
+ "x": _SELF_ADJOINT,
54
+ "y": _SELF_ADJOINT,
55
+ "z": _SELF_ADJOINT,
56
+ "h": _SELF_ADJOINT,
57
+ "s": ("sdg",), # S† = Sdg (name swap, no params)
58
+ "sdg": ("s",),
59
+ "t": ("tdg",),
60
+ "tdg": ("t",),
61
+ "sx": ("sxdg",),
62
+ "sxdg": ("sx",),
63
+ }
64
+
65
+ _RE_INCLUDE = re.compile(r'^\s*include\s+"([^"]+)"\s*;\s*$', re.I)
66
+ _RE_OPENQASM = re.compile(r'^\s*OPENQASM\s+', re.I)
67
+ _RE_QREG = re.compile(r'^\s*qreg\s+([A-Za-z_]\w*)\s*\[\s*(\d+)\s*\]\s*;\s*$')
68
+ _RE_CREG = re.compile(r'^\s*creg\s+', re.I)
69
+ _RE_BARRIER = re.compile(r'^\s*barrier\s+', re.I)
70
+ _RE_MEASURE = re.compile(r'^\s*measure\s+', re.I)
71
+ # gate line: name ( params )? qubit_operands ;
72
+ _RE_OP = re.compile(r'^\s*([A-Za-z_]\w*)\s*(\([^)]*\))?\s+(.+?)\s*;\s*$')
73
+
74
+
75
+ def _parse_float(s: str) -> float:
76
+ s = s.strip()
77
+ try:
78
+ return float(s)
79
+ except ValueError:
80
+ return 0.0
81
+
82
+
83
+ def _negate_param(p: str) -> str:
84
+ s = p.strip()
85
+
86
+ def _strip_outer_parens(x: str) -> str:
87
+ x = x.strip()
88
+ while x.startswith("(") and x.endswith(")"):
89
+ depth = 0
90
+ ok = True
91
+ for i, ch in enumerate(x):
92
+ if ch == "(":
93
+ depth += 1
94
+ elif ch == ")":
95
+ depth -= 1
96
+ if depth == 0 and i != len(x) - 1:
97
+ ok = False
98
+ break
99
+ if not ok or depth != 0:
100
+ break
101
+ x = x[1:-1].strip()
102
+ return x
103
+
104
+ def _is_zero_literal(x: str) -> bool:
105
+ try:
106
+ return float(x.strip()) == 0.0
107
+ except Exception:
108
+ return False
109
+
110
+ try:
111
+ v = -float(s)
112
+ return "0" if v == 0.0 else str(v)
113
+ except ValueError:
114
+ core = _strip_outer_parens(s)
115
+ # Simplify double negation: -(-x) -> x
116
+ if core.startswith("-"):
117
+ pos = _strip_outer_parens(core[1:])
118
+ return "0" if _is_zero_literal(pos) else pos
119
+ # Keep symbolic form explicit for non-numeric params.
120
+ return f"-({core})"
121
+
122
+
123
+ def _emit_adjoint_gate(name: str, params: List[str], operands: str) -> str:
124
+ key = name.lower()
125
+ adj = _GATE_ADJOINT.get(key)
126
+ if adj is None:
127
+ raise UnsupportedQasm(f"Unsupported gate for mirror: {name}")
128
+ if adj == _SELF_ADJOINT:
129
+ param_str = f"({', '.join(params)})" if params else ""
130
+ return f"{name}{param_str} {operands};"
131
+ if isinstance(adj, tuple) and len(adj) == 1 and isinstance(adj[0], str):
132
+ # name swap only: s->sdg, sdg->s, t->tdg, tdg->t
133
+ adj_name = adj[0]
134
+ param_str = f"({', '.join(params)})" if params else ""
135
+ return f"{adj_name}{param_str} {operands};"
136
+ # negate params: adj is (bool,) for each param to negate
137
+ out_params: List[str] = []
138
+ for i, p in enumerate(params):
139
+ if i < len(adj) and adj[i]:
140
+ out_params.append(_negate_param(p))
141
+ else:
142
+ out_params.append(p)
143
+ if key == "u2":
144
+ # u2(phi, lambda)† = u2(-lambda, -phi): swap and negate
145
+ if len(out_params) >= 2:
146
+ out_params[0], out_params[1] = out_params[1], out_params[0]
147
+ if key == "cu3":
148
+ # cu3(theta, phi, lambda)† = cu3(-theta, -lambda, -phi)
149
+ if len(out_params) >= 3:
150
+ out_params[1], out_params[2] = out_params[2], out_params[1]
151
+ param_str = f"({', '.join(out_params)})" if out_params else ""
152
+ return f"{name}{param_str} {operands};"
153
+
154
+
155
+ def build_mirror_qasm(orig_text: str, drop_barriers: bool = True) -> Tuple[str, Optional[int]]:
156
+ """
157
+ Build mirror circuit QASM (U then U†) from OpenQASM 2 source.
158
+
159
+ Returns (mirror_qasm_string, n_qubits). Drops measure and optionally barrier.
160
+ Raises UnsupportedQasm if non-standard includes or unsupported gates are present.
161
+ """
162
+ lines = orig_text.splitlines()
163
+ header_lines: List[str] = []
164
+ qregs: List[Tuple[str, int]] = []
165
+ gate_lines: List[Tuple[str, List[str], str]] = [] # (name, params, operands)
166
+
167
+ for raw in lines:
168
+ s = raw.strip()
169
+ if not s or s.startswith("//"):
170
+ continue
171
+ m = _RE_INCLUDE.match(s)
172
+ if m:
173
+ inc = m.group(1).strip().lower()
174
+ if inc != "qelib1.inc":
175
+ raise UnsupportedQasm(f'Unsupported include for mirror: "{m.group(1).strip()}"')
176
+ header_lines.append(s)
177
+ continue
178
+ if _RE_OPENQASM.match(s):
179
+ header_lines.append(s)
180
+ continue
181
+ if _RE_CREG.match(s):
182
+ continue
183
+ if _RE_MEASURE.match(s):
184
+ continue
185
+ if _RE_BARRIER.match(s):
186
+ if not drop_barriers:
187
+ raise UnsupportedQasm("barrier in circuit (use drop_barriers=True)")
188
+ continue
189
+ m = _RE_QREG.match(s)
190
+ if m:
191
+ header_lines.append(s)
192
+ qregs.append((m.group(1), int(m.group(2))))
193
+ continue
194
+ m = _RE_OP.match(s)
195
+ if m:
196
+ name = m.group(1)
197
+ params_raw = (m.group(2) or "").strip()
198
+ operands = (m.group(3) or "").strip()
199
+ params: List[str] = []
200
+ if params_raw.startswith("(") and params_raw.endswith(")"):
201
+ inside = params_raw[1:-1].strip()
202
+ if inside:
203
+ params = [x.strip() for x in inside.split(",")]
204
+ gate_lines.append((name, params, operands))
205
+ continue
206
+ # unknown line (gate def, etc.)
207
+ raise UnsupportedQasm(f"Unsupported line for mirror: {s[:60]}")
208
+
209
+ if not qregs:
210
+ raise UnsupportedQasm("No qreg declaration found")
211
+ total_width = sum(sz for _, sz in qregs)
212
+
213
+ # Ensure OPENQASM 2.0 header
214
+ if not any(_RE_OPENQASM.match(h) for h in header_lines):
215
+ header_lines.insert(0, "OPENQASM 2.0;")
216
+
217
+ out: List[str] = []
218
+ out.extend(header_lines)
219
+ # U
220
+ for name, params, operands in gate_lines:
221
+ param_str = f"({', '.join(params)})" if params else ""
222
+ out.append(f"{name}{param_str} {operands};")
223
+ # U† (reversed, adjoint)
224
+ for name, params, operands in reversed(gate_lines):
225
+ out.append(_emit_adjoint_gate(name, params, operands))
226
+ # Final measurements for counts-based mirror mode.
227
+ out.append(f"creg c[{total_width}];")
228
+ cidx = 0
229
+ for qreg_name, qreg_size in qregs:
230
+ for i in range(int(qreg_size)):
231
+ out.append(f"measure {qreg_name}[{i}] -> c[{cidx}];")
232
+ cidx += 1
233
+
234
+ return "\n".join(out) + "\n", total_width
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ CPU_ALIASES = {"cpu", "scarlet"}
6
+ GPU_ALIASES = {"gpu", "amber"}
7
+
8
+ SINGLE_ALIASES = {"single", "fp32", "float32"}
9
+ DOUBLE_ALIASES = {"double", "fp64", "float64"}
10
+
11
+
12
+ def normalize_backend(x: str | None) -> str:
13
+ s = (x or "").strip().lower()
14
+ if s in CPU_ALIASES:
15
+ return "CPU"
16
+ if s in GPU_ALIASES:
17
+ return "GPU"
18
+ return "CPU"
19
+
20
+
21
+ def normalize_precision(x: str | None) -> str:
22
+ s = (x or "").strip().lower()
23
+ if s in SINGLE_ALIASES:
24
+ return "single"
25
+ if s in DOUBLE_ALIASES:
26
+ return "double"
27
+ return "single"
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class RunConfig:
32
+ """
33
+ Conditioning variables for runtime/fidelity predictors.
34
+
35
+ These are NOT circuit-derived features.
36
+ """
37
+ processor: str | None # raw label like "Amber", "Scarlet", "CPU", "GPU"
38
+ backend: str # normalized: "CPU" | "GPU"
39
+ precision: str # normalized: "single" | "double"
40
+ threshold: float | None = None # e.g. bond-dimension/threshold conditioning
41
+ prediction_artifact_path: str | None = None
42
+ prediction_allow_schema_mismatch: bool = False
43
+ prediction_fidelity_target: float | None = None
44
+ prediction_fidelity_metric: str | None = None
45
+
46
+ @staticmethod
47
+ def from_raw(
48
+ *,
49
+ processor: str | None = None,
50
+ backend: str | None = None,
51
+ precision: str | None = None,
52
+ threshold: float | None = None,
53
+ prediction_artifact_path: str | None = None,
54
+ prediction_allow_schema_mismatch: bool | None = None,
55
+ prediction_fidelity_target: float | None = None,
56
+ prediction_fidelity_metric: str | None = None,
57
+ ) -> "RunConfig":
58
+ # accept processor OR backend as the same input channel
59
+ raw = processor if processor is not None else backend
60
+ proc = (raw or "").strip()
61
+ proc = proc if proc else None
62
+
63
+ return RunConfig(
64
+ processor=proc,
65
+ backend=normalize_backend(raw or backend),
66
+ precision=normalize_precision(precision),
67
+ threshold=float(threshold) if threshold is not None else None,
68
+ prediction_artifact_path=prediction_artifact_path,
69
+ prediction_allow_schema_mismatch=prediction_allow_schema_mismatch or False,
70
+ prediction_fidelity_target=prediction_fidelity_target,
71
+ prediction_fidelity_metric=prediction_fidelity_metric,
72
+ )
73
+
74
+ def to_dict(self) -> dict:
75
+ return {
76
+ "processor": self.processor,
77
+ "backend": self.backend,
78
+ "precision": self.precision,
79
+ "threshold": self.threshold,
80
+ "prediction_artifact_path": self.prediction_artifact_path,
81
+ "prediction_allow_schema_mismatch": self.prediction_allow_schema_mismatch,
82
+ "prediction_fidelity_target": self.prediction_fidelity_target,
83
+ "prediction_fidelity_metric": self.prediction_fidelity_metric,
84
+ }
qcoder/core/schema.py ADDED
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Iterable
5
+
6
+
7
+ @dataclass(frozen=True)
8
+ class FeatureSchema:
9
+ version: str
10
+ feature_names: tuple[str, ...]
11
+ transforms: dict[str, str]
12
+
13
+ def index(self) -> dict[str, int]:
14
+ return {n: i for i, n in enumerate(self.feature_names)}
15
+
16
+
17
+ # Feature schema version (append-only updates).
18
+ SCHEMA_VERSION = "0.4.0"
19
+
20
+
21
+ def make_schema(names: Iterable[str], *, transforms: dict[str, str] | None = None) -> FeatureSchema:
22
+ return FeatureSchema(
23
+ version=SCHEMA_VERSION,
24
+ feature_names=tuple(names),
25
+ transforms=dict(transforms or {}),
26
+ )
@@ -0,0 +1 @@
1
+ """Optional format adapters (e.g. Qiskit). Imports here must remain lazy where noted."""
@@ -0,0 +1,46 @@
1
+ """
2
+ Optional Qiskit QuantumCircuit intake via OpenQASM 2 export.
3
+
4
+ Qiskit is imported only when functions in this module are called.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from ..features.compute_v0 import FeatureVector, compute_features_v0
12
+ from ..ir import CircuitIR
13
+ from ..qasm2_regex_parser import parse_qasm2_text
14
+
15
+
16
+ def _load_qiskit() -> tuple[type, Any]:
17
+ try:
18
+ from qiskit import QuantumCircuit
19
+ from qiskit.qasm2 import dumps
20
+ except ImportError as e:
21
+ raise ImportError(
22
+ "qcoder: Qiskit is not installed. "
23
+ "Install with: pip install qiskit or pip install 'qcoder[qiskit]' "
24
+ "(optional extra)."
25
+ ) from e
26
+ return QuantumCircuit, dumps
27
+
28
+
29
+ def circuit_ir_from_qiskit(qc: Any) -> CircuitIR:
30
+ """
31
+ Export ``qc`` with ``qiskit.qasm2.dumps`` and parse as OpenQASM 2 text.
32
+
33
+ Raises ImportError when Qiskit is not installed (only at call time).
34
+ Raises TypeError if ``qc`` is not a ``qiskit.QuantumCircuit``.
35
+ """
36
+ QuantumCircuit, dumps = _load_qiskit()
37
+ if not isinstance(qc, QuantumCircuit):
38
+ raise TypeError(f"expected qiskit.QuantumCircuit, got {type(qc).__name__}")
39
+ qasm_text = dumps(qc)
40
+ return parse_qasm2_text(qasm_text, source_label="qiskit.qasm2.dumps")
41
+
42
+
43
+ def extract_features_from_qiskit_circuit(qc: Any) -> FeatureVector:
44
+ """``circuit_ir_from_qiskit`` + ``compute_features_v0`` (same schema as file intake)."""
45
+ ir = circuit_ir_from_qiskit(qc)
46
+ return compute_features_v0(ir)