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,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .features.compute_v0 import FeatureVector, compute_features_v0
|
|
7
|
+
from .ir import CircuitIR
|
|
8
|
+
from .labeling import infer_function
|
|
9
|
+
from .parsers import parse_circuit_file
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class CircuitExample:
|
|
14
|
+
id: str | None
|
|
15
|
+
name: str | None
|
|
16
|
+
function_hint: str # small normalized label
|
|
17
|
+
function_source: str # "name" | "qasm" | "unknown"
|
|
18
|
+
qasm_path: str
|
|
19
|
+
ir: CircuitIR
|
|
20
|
+
global_features: FeatureVector
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def extract_example(
|
|
24
|
+
qasm_path: str,
|
|
25
|
+
*,
|
|
26
|
+
circuit_id: str | None = None,
|
|
27
|
+
circuit_name: str | None = None,
|
|
28
|
+
) -> CircuitExample:
|
|
29
|
+
p = Path(qasm_path)
|
|
30
|
+
ir = parse_circuit_file(str(p))
|
|
31
|
+
fv = compute_features_v0(ir)
|
|
32
|
+
|
|
33
|
+
function_hint, function_source = infer_function(circuit_name, ir)
|
|
34
|
+
|
|
35
|
+
return CircuitExample(
|
|
36
|
+
id=circuit_id,
|
|
37
|
+
name=circuit_name,
|
|
38
|
+
function_hint=function_hint,
|
|
39
|
+
function_source=function_source,
|
|
40
|
+
qasm_path=str(p),
|
|
41
|
+
ir=ir,
|
|
42
|
+
global_features=fv,
|
|
43
|
+
)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from ..ir import CircuitIR
|
|
6
|
+
from ..reps.cut_profile import compute_cut_profile_stats
|
|
7
|
+
from ..reps.depth import compute_depth_stats
|
|
8
|
+
from ..reps.entangling_layers import compute_entangling_layer_stats
|
|
9
|
+
from ..reps.gate_set_stats import compute_gate_set_stats
|
|
10
|
+
from ..reps.interaction_graph import build_interaction_graph
|
|
11
|
+
from ..reps.interaction_graph_metrics import compute_interaction_graph_metrics
|
|
12
|
+
from ..reps.spans import compute_span_stats
|
|
13
|
+
from .schema_v0 import SCHEMA_V0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class FeatureVector:
|
|
18
|
+
schema_version: str
|
|
19
|
+
feature_names: tuple[str, ...]
|
|
20
|
+
features: tuple[float, ...]
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> dict:
|
|
23
|
+
return {
|
|
24
|
+
"schema_version": self.schema_version,
|
|
25
|
+
"feature_names": list(self.feature_names),
|
|
26
|
+
"features": list(self.features),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def compute_features_v0(ir: CircuitIR) -> FeatureVector:
|
|
31
|
+
n_measure = 0
|
|
32
|
+
n_barrier = 0
|
|
33
|
+
n_reset = 0
|
|
34
|
+
n_gate = 0
|
|
35
|
+
n_1q = 0
|
|
36
|
+
n_2q = 0
|
|
37
|
+
n_3p = 0
|
|
38
|
+
n_param = 0
|
|
39
|
+
n_custom = 0
|
|
40
|
+
|
|
41
|
+
for op in ir.operations:
|
|
42
|
+
if op.is_measure:
|
|
43
|
+
n_measure += 1
|
|
44
|
+
continue
|
|
45
|
+
if op.is_barrier:
|
|
46
|
+
n_barrier += 1
|
|
47
|
+
continue
|
|
48
|
+
if op.is_reset:
|
|
49
|
+
n_reset += 1
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
if not op.qubits:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
n_gate += 1
|
|
56
|
+
if op.is_custom:
|
|
57
|
+
n_custom += 1
|
|
58
|
+
if op.params:
|
|
59
|
+
n_param += 1
|
|
60
|
+
|
|
61
|
+
ar = len(op.qubits)
|
|
62
|
+
if ar == 1:
|
|
63
|
+
n_1q += 1
|
|
64
|
+
elif ar == 2:
|
|
65
|
+
n_2q += 1
|
|
66
|
+
else:
|
|
67
|
+
n_3p += 1
|
|
68
|
+
|
|
69
|
+
depth = compute_depth_stats(ir)
|
|
70
|
+
ig = build_interaction_graph(ir)
|
|
71
|
+
spans = compute_span_stats(ir)
|
|
72
|
+
cuts = compute_cut_profile_stats(ig)
|
|
73
|
+
gate_stats = compute_gate_set_stats(ir)
|
|
74
|
+
ig_metrics = compute_interaction_graph_metrics(ig)
|
|
75
|
+
ent_layers = compute_entangling_layer_stats(ir)
|
|
76
|
+
|
|
77
|
+
n_edges = float(len(ig.edges))
|
|
78
|
+
denom = (ir.n_qubits * (ir.n_qubits - 1) / 2.0) if ir.n_qubits >= 2 else 1.0
|
|
79
|
+
edge_density = float(n_edges / denom) if denom > 0 else 0.0
|
|
80
|
+
|
|
81
|
+
values = {
|
|
82
|
+
"n_qubits": float(ir.n_qubits),
|
|
83
|
+
"n_cbits": float(ir.n_cbits),
|
|
84
|
+
"n_ops": float(ir.n_ops),
|
|
85
|
+
|
|
86
|
+
"n_gate_ops": float(n_gate),
|
|
87
|
+
"n_1q_gate_ops": float(n_1q),
|
|
88
|
+
"n_2q_gate_ops": float(n_2q),
|
|
89
|
+
"n_3p_gate_ops": float(n_3p),
|
|
90
|
+
"n_param_ops": float(n_param),
|
|
91
|
+
"n_custom_ops": float(n_custom),
|
|
92
|
+
|
|
93
|
+
"n_measure_ops": float(n_measure),
|
|
94
|
+
"n_barrier_ops": float(n_barrier),
|
|
95
|
+
"n_reset_ops": float(n_reset),
|
|
96
|
+
|
|
97
|
+
"estimated_depth": float(depth.estimated_depth),
|
|
98
|
+
"real_depth": float(depth.real_depth),
|
|
99
|
+
"avg_parallel_gates": float(depth.avg_parallel_gates),
|
|
100
|
+
"parallelism_factor": float(depth.parallelism_factor),
|
|
101
|
+
|
|
102
|
+
"ig_n_edges": float(n_edges),
|
|
103
|
+
"ig_edge_density": float(edge_density),
|
|
104
|
+
|
|
105
|
+
"span_avg": float(spans.avg_span),
|
|
106
|
+
"span_max": float(spans.max_span),
|
|
107
|
+
"span_std": float(spans.span_std),
|
|
108
|
+
"span_nearest_neighbor_ratio": float(spans.nearest_neighbor_ratio),
|
|
109
|
+
"span_long_range_ratio": float(spans.long_range_ratio),
|
|
110
|
+
"span_long_range_ratio_early": float(spans.long_range_ratio_early),
|
|
111
|
+
"span_long_range_ratio_late": float(spans.long_range_ratio_late),
|
|
112
|
+
"span_avg_early": float(spans.avg_span_early),
|
|
113
|
+
"span_avg_late": float(spans.avg_span_late),
|
|
114
|
+
|
|
115
|
+
# Phase 2: cut profile (natural qubit order only)
|
|
116
|
+
"cut_max": float(cuts.cut_max),
|
|
117
|
+
"cut_mean": float(cuts.cut_mean),
|
|
118
|
+
"cut_std": float(cuts.cut_std),
|
|
119
|
+
"cut_entropy": float(cuts.cut_entropy),
|
|
120
|
+
"n_active_cuts": float(cuts.n_active_cuts),
|
|
121
|
+
"max_span_in_order": float(cuts.max_span_in_order),
|
|
122
|
+
|
|
123
|
+
# Quantumness v1: gate set + angles + basis/diagonal
|
|
124
|
+
"n_basis_change_ops": float(gate_stats.n_basis_change_ops),
|
|
125
|
+
"basis_change_qubit_coverage": float(gate_stats.basis_change_qubit_coverage),
|
|
126
|
+
"n_diagonal_gate_ops": float(gate_stats.n_diagonal_gate_ops),
|
|
127
|
+
"diagonal_gate_fraction": float(gate_stats.diagonal_gate_fraction),
|
|
128
|
+
"n_t_like_ops": float(gate_stats.n_t_like_ops),
|
|
129
|
+
"n_distinct_angles": float(gate_stats.n_distinct_angles),
|
|
130
|
+
"angle_genericity_ratio": float(gate_stats.angle_genericity_ratio),
|
|
131
|
+
"is_certified_diagonal_only": float(gate_stats.is_certified_diagonal_only),
|
|
132
|
+
|
|
133
|
+
# Quantumness v1: IG degree/connectivity metrics (unweighted)
|
|
134
|
+
"ig_max_degree": float(ig_metrics.ig_max_degree),
|
|
135
|
+
"ig_avg_degree": float(ig_metrics.ig_avg_degree),
|
|
136
|
+
"ig_degree_std": float(ig_metrics.ig_degree_std),
|
|
137
|
+
"ig_degree_entropy": float(ig_metrics.ig_degree_entropy),
|
|
138
|
+
"ig_n_components": float(ig_metrics.ig_n_components),
|
|
139
|
+
"ig_largest_cc_frac": float(ig_metrics.ig_largest_cc_frac),
|
|
140
|
+
"ig_is_connected": float(ig_metrics.ig_is_connected),
|
|
141
|
+
"ig_pair_reuse_hhi": float(ig_metrics.ig_pair_reuse_hhi),
|
|
142
|
+
"ig_pair_reuse_top1_frac": float(ig_metrics.ig_pair_reuse_top1_frac),
|
|
143
|
+
|
|
144
|
+
# Quantumness v1: entangling depth/layers
|
|
145
|
+
"entangling_depth": float(ent_layers.entangling_depth),
|
|
146
|
+
"n_entangling_layers": float(ent_layers.n_entangling_layers),
|
|
147
|
+
"avg_2q_per_entangling_layer": float(ent_layers.avg_2q_per_entangling_layer),
|
|
148
|
+
"max_2q_per_entangling_layer": float(ent_layers.max_2q_per_entangling_layer),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
feats = tuple(float(values.get(name, 0.0)) for name in SCHEMA_V0.feature_names)
|
|
152
|
+
|
|
153
|
+
return FeatureVector(
|
|
154
|
+
schema_version=SCHEMA_V0.version,
|
|
155
|
+
feature_names=SCHEMA_V0.feature_names,
|
|
156
|
+
features=feats,
|
|
157
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from qcoder.core.schema import make_schema
|
|
4
|
+
|
|
5
|
+
# Schema 0.4.0: PURE circuit-derived features (no backend/precision/threshold).
|
|
6
|
+
FEATURE_NAMES_V0 = [
|
|
7
|
+
# basic sizes
|
|
8
|
+
"n_qubits",
|
|
9
|
+
"n_cbits",
|
|
10
|
+
"n_ops",
|
|
11
|
+
|
|
12
|
+
# gate arity counts (exclude measure/barrier/reset)
|
|
13
|
+
"n_gate_ops",
|
|
14
|
+
"n_1q_gate_ops",
|
|
15
|
+
"n_2q_gate_ops",
|
|
16
|
+
"n_3p_gate_ops", # 3+ qubit gate ops
|
|
17
|
+
"n_param_ops",
|
|
18
|
+
"n_custom_ops",
|
|
19
|
+
|
|
20
|
+
# control ops
|
|
21
|
+
"n_measure_ops",
|
|
22
|
+
"n_barrier_ops",
|
|
23
|
+
"n_reset_ops",
|
|
24
|
+
|
|
25
|
+
# depth proxies
|
|
26
|
+
"estimated_depth",
|
|
27
|
+
"real_depth",
|
|
28
|
+
"avg_parallel_gates",
|
|
29
|
+
"parallelism_factor",
|
|
30
|
+
|
|
31
|
+
# interaction graph
|
|
32
|
+
"ig_n_edges",
|
|
33
|
+
"ig_edge_density",
|
|
34
|
+
|
|
35
|
+
# span stats (2Q only)
|
|
36
|
+
"span_avg",
|
|
37
|
+
"span_max",
|
|
38
|
+
"span_std",
|
|
39
|
+
"span_nearest_neighbor_ratio",
|
|
40
|
+
"span_long_range_ratio",
|
|
41
|
+
|
|
42
|
+
# Phase 2: cut profile (natural qubit order only)
|
|
43
|
+
"cut_max",
|
|
44
|
+
"cut_mean",
|
|
45
|
+
"cut_std",
|
|
46
|
+
"cut_entropy",
|
|
47
|
+
"n_active_cuts",
|
|
48
|
+
"max_span_in_order",
|
|
49
|
+
|
|
50
|
+
# Quantumness v1: gate set + angles + basis/diagonal
|
|
51
|
+
"n_basis_change_ops",
|
|
52
|
+
"basis_change_qubit_coverage",
|
|
53
|
+
"n_diagonal_gate_ops",
|
|
54
|
+
"diagonal_gate_fraction",
|
|
55
|
+
"n_t_like_ops",
|
|
56
|
+
"n_distinct_angles",
|
|
57
|
+
"angle_genericity_ratio",
|
|
58
|
+
"is_certified_diagonal_only",
|
|
59
|
+
|
|
60
|
+
# Quantumness v1: IG degree/connectivity metrics (unweighted)
|
|
61
|
+
"ig_max_degree",
|
|
62
|
+
"ig_avg_degree",
|
|
63
|
+
"ig_degree_std",
|
|
64
|
+
"ig_degree_entropy",
|
|
65
|
+
"ig_n_components",
|
|
66
|
+
"ig_largest_cc_frac",
|
|
67
|
+
"ig_is_connected",
|
|
68
|
+
|
|
69
|
+
# Quantumness v1: entangling depth/layers
|
|
70
|
+
"entangling_depth",
|
|
71
|
+
"n_entangling_layers",
|
|
72
|
+
"avg_2q_per_entangling_layer",
|
|
73
|
+
"max_2q_per_entangling_layer",
|
|
74
|
+
|
|
75
|
+
# Phase 3: temporal span + weighted pair-reuse features (append-only)
|
|
76
|
+
"span_long_range_ratio_early",
|
|
77
|
+
"span_long_range_ratio_late",
|
|
78
|
+
"span_avg_early",
|
|
79
|
+
"span_avg_late",
|
|
80
|
+
"ig_pair_reuse_hhi",
|
|
81
|
+
"ig_pair_reuse_top1_frac",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
SCHEMA_V0 = make_schema(FEATURE_NAMES_V0)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Sequence
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class Operation:
|
|
9
|
+
name: str
|
|
10
|
+
qubits: tuple[int, ...]
|
|
11
|
+
params: tuple[str, ...]
|
|
12
|
+
line_index: int
|
|
13
|
+
op_index: int
|
|
14
|
+
is_measure: bool = False
|
|
15
|
+
is_barrier: bool = False
|
|
16
|
+
is_reset: bool = False
|
|
17
|
+
is_custom: bool = False
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def arity(self) -> int:
|
|
21
|
+
return len(self.qubits)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CircuitIR:
|
|
26
|
+
n_qubits: int
|
|
27
|
+
n_cbits: int
|
|
28
|
+
operations: tuple[Operation, ...]
|
|
29
|
+
qasm_format: str # "qasm2" | "qasm3" | "unknown"
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def source_format(self) -> str:
|
|
33
|
+
# Future-facing alias (QIR, QASM3, etc.)
|
|
34
|
+
return self.qasm_format
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def n_ops(self) -> int:
|
|
38
|
+
return len(self.operations)
|
|
39
|
+
|
|
40
|
+
def iter_ops(self) -> Sequence[Operation]:
|
|
41
|
+
return self.operations
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .ir import CircuitIR
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _name_hint(name: str) -> str | None:
|
|
7
|
+
s = name.strip().lower()
|
|
8
|
+
if not s:
|
|
9
|
+
return None
|
|
10
|
+
|
|
11
|
+
# Small, deterministic keyword map (expand later as needed)
|
|
12
|
+
if "qft" in s:
|
|
13
|
+
return "qft"
|
|
14
|
+
if "qaoa" in s:
|
|
15
|
+
return "qaoa"
|
|
16
|
+
if "grover" in s:
|
|
17
|
+
return "grover"
|
|
18
|
+
if "ucc" in s or "vqe" in s:
|
|
19
|
+
return "vqe_ucc"
|
|
20
|
+
if "adder" in s or "arith" in s or "mult" in s:
|
|
21
|
+
return "arithmetic"
|
|
22
|
+
if "qnn" in s or "ansatz" in s or "vqc" in s:
|
|
23
|
+
return "variational"
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def infer_function(circuit_name: str | None, ir: CircuitIR) -> tuple[str, str]:
|
|
28
|
+
"""
|
|
29
|
+
Returns: (function_hint, source) where source ∈ {"name", "qasm", "unknown"}.
|
|
30
|
+
|
|
31
|
+
Keep categories small/stable. This is metadata for models to optionally consume.
|
|
32
|
+
"""
|
|
33
|
+
if circuit_name:
|
|
34
|
+
h = _name_hint(circuit_name)
|
|
35
|
+
if h is not None:
|
|
36
|
+
return (h, "name")
|
|
37
|
+
|
|
38
|
+
# QASM-derived minimal heuristics (cheap + robust)
|
|
39
|
+
counts: dict[str, int] = {}
|
|
40
|
+
n_gate = 0
|
|
41
|
+
n_param = 0
|
|
42
|
+
|
|
43
|
+
for op in ir.operations:
|
|
44
|
+
if op.is_measure or op.is_barrier or op.is_reset:
|
|
45
|
+
continue
|
|
46
|
+
if not op.qubits:
|
|
47
|
+
continue
|
|
48
|
+
n_gate += 1
|
|
49
|
+
counts[op.name] = counts.get(op.name, 0) + 1
|
|
50
|
+
if op.params:
|
|
51
|
+
n_param += 1
|
|
52
|
+
|
|
53
|
+
ccx = counts.get("ccx", 0)
|
|
54
|
+
cp_like = counts.get("cp", 0) + counts.get("cu1", 0)
|
|
55
|
+
h = counts.get("h", 0)
|
|
56
|
+
|
|
57
|
+
if ccx > 0:
|
|
58
|
+
return ("arithmetic", "qasm")
|
|
59
|
+
|
|
60
|
+
# Very rough QFT signal: many controlled-phase-like ops + some H
|
|
61
|
+
if cp_like >= 3 and h >= 1 and cp_like >= int(0.25 * max(1, n_gate)):
|
|
62
|
+
return ("qft", "qasm")
|
|
63
|
+
|
|
64
|
+
# Variational-ish: lots of parameterized rotations
|
|
65
|
+
if n_gate >= 10 and n_param >= int(0.30 * n_gate):
|
|
66
|
+
return ("variational", "qasm")
|
|
67
|
+
|
|
68
|
+
return ("unknown", "unknown")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..ir import CircuitIR
|
|
4
|
+
from ..qasm2_regex_parser import parse_qasm2_file, parse_qasm2_text
|
|
5
|
+
|
|
6
|
+
__all__ = ["parse_circuit_file", "parse_qasm2_file", "parse_qasm2_text"]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def parse_circuit_file(path: str) -> CircuitIR:
|
|
10
|
+
"""
|
|
11
|
+
Parser routing point.
|
|
12
|
+
Today: OpenQASM2 (regex parser).
|
|
13
|
+
Future: OpenQASM3, QIR, other IR adapters.
|
|
14
|
+
"""
|
|
15
|
+
ir = parse_qasm2_file(path)
|
|
16
|
+
|
|
17
|
+
# For now: we accept qasm2 and unknown headers (still parseable),
|
|
18
|
+
# but explicitly reject qasm3 until implemented.
|
|
19
|
+
if ir.qasm_format == "qasm3":
|
|
20
|
+
raise NotImplementedError("OpenQASM 3 parsing is not implemented yet.")
|
|
21
|
+
return ir
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .ir import CircuitIR, Operation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_RE_QREG = re.compile(r"^\s*qreg\s+([A-Za-z_]\w*)\s*\[\s*(\d+)\s*\]\s*;\s*$")
|
|
11
|
+
_RE_CREG = re.compile(r"^\s*creg\s+([A-Za-z_]\w*)\s*\[\s*(\d+)\s*\]\s*;\s*$")
|
|
12
|
+
_RE_OPENQASM2 = re.compile(r"^\s*OPENQASM\s+2(\.\d+)?\s*;\s*$", re.IGNORECASE)
|
|
13
|
+
_RE_OPENQASM3 = re.compile(r"^\s*OPENQASM\s+3(\.\d+)?\s*;\s*$", re.IGNORECASE)
|
|
14
|
+
_RE_INCLUDE = re.compile(r'^\s*include\s+"[^"]+"\s*;\s*$', re.IGNORECASE)
|
|
15
|
+
|
|
16
|
+
# op forms: name(params?) q[i],q[j];
|
|
17
|
+
_RE_OP = re.compile(
|
|
18
|
+
r"^\s*([A-Za-z_]\w*)\s*(\([^)]*\))?\s+(.+?)\s*;\s*$"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# qubit ref: q[12]
|
|
22
|
+
_RE_QREF = re.compile(r"([A-Za-z_]\w*)\s*\[\s*(\d+)\s*\]")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class _Regs:
|
|
27
|
+
qreg_base: dict[str, int]
|
|
28
|
+
qreg_size: dict[str, int]
|
|
29
|
+
n_qubits: int
|
|
30
|
+
n_cbits: int
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _strip_inline_comment(line: str) -> str:
|
|
34
|
+
# QASM2 line comments start with //
|
|
35
|
+
idx = line.find("//")
|
|
36
|
+
return line[:idx] if idx >= 0 else line
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _detect_format(lines: list[str]) -> str:
|
|
40
|
+
for raw in lines:
|
|
41
|
+
s = raw.strip()
|
|
42
|
+
if not s or s.startswith("//"):
|
|
43
|
+
continue
|
|
44
|
+
if _RE_OPENQASM3.match(s):
|
|
45
|
+
return "qasm3"
|
|
46
|
+
if _RE_OPENQASM2.match(s):
|
|
47
|
+
return "qasm2"
|
|
48
|
+
break
|
|
49
|
+
return "unknown"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _build_regs(lines: list[str]) -> _Regs:
|
|
53
|
+
qreg_base: dict[str, int] = {}
|
|
54
|
+
qreg_size: dict[str, int] = {}
|
|
55
|
+
n_qubits = 0
|
|
56
|
+
n_cbits = 0
|
|
57
|
+
|
|
58
|
+
for raw in lines:
|
|
59
|
+
s = raw.strip()
|
|
60
|
+
if not s:
|
|
61
|
+
continue
|
|
62
|
+
|
|
63
|
+
m = _RE_QREG.match(s)
|
|
64
|
+
if m:
|
|
65
|
+
name = m.group(1)
|
|
66
|
+
size = int(m.group(2))
|
|
67
|
+
if name not in qreg_base:
|
|
68
|
+
qreg_base[name] = n_qubits
|
|
69
|
+
qreg_size[name] = size
|
|
70
|
+
n_qubits += size
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
m = _RE_CREG.match(s)
|
|
74
|
+
if m:
|
|
75
|
+
size = int(m.group(2))
|
|
76
|
+
n_cbits += size
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
return _Regs(qreg_base=qreg_base, qreg_size=qreg_size, n_qubits=n_qubits, n_cbits=n_cbits)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _flatten_qubits(arg_str: str, regs: _Regs) -> tuple[int, ...]:
|
|
83
|
+
# find all qref occurrences in the operand string
|
|
84
|
+
out: list[int] = []
|
|
85
|
+
for reg_name, idx_str in _RE_QREF.findall(arg_str):
|
|
86
|
+
if reg_name not in regs.qreg_base:
|
|
87
|
+
continue
|
|
88
|
+
idx = int(idx_str)
|
|
89
|
+
size = regs.qreg_size[reg_name]
|
|
90
|
+
if 0 <= idx < size:
|
|
91
|
+
out.append(regs.qreg_base[reg_name] + idx)
|
|
92
|
+
return tuple(out)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_qasm2_lines(lines: list[str]) -> CircuitIR:
|
|
96
|
+
fmt = _detect_format(lines)
|
|
97
|
+
regs = _build_regs(lines)
|
|
98
|
+
|
|
99
|
+
ops: list[Operation] = []
|
|
100
|
+
op_index = 0
|
|
101
|
+
|
|
102
|
+
for line_index, s in enumerate(lines):
|
|
103
|
+
if not s:
|
|
104
|
+
continue
|
|
105
|
+
if _RE_OPENQASM2.match(s) or _RE_OPENQASM3.match(s) or _RE_INCLUDE.match(s):
|
|
106
|
+
continue
|
|
107
|
+
if _RE_QREG.match(s) or _RE_CREG.match(s):
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
# recognize common non-gate statements
|
|
111
|
+
low = s.lower()
|
|
112
|
+
if low.startswith("barrier"):
|
|
113
|
+
qubits = _flatten_qubits(s, regs)
|
|
114
|
+
ops.append(Operation("barrier", qubits, (), line_index, op_index, is_barrier=True))
|
|
115
|
+
op_index += 1
|
|
116
|
+
continue
|
|
117
|
+
if low.startswith("reset"):
|
|
118
|
+
qubits = _flatten_qubits(s, regs)
|
|
119
|
+
ops.append(Operation("reset", qubits, (), line_index, op_index, is_reset=True))
|
|
120
|
+
op_index += 1
|
|
121
|
+
continue
|
|
122
|
+
if low.startswith("measure"):
|
|
123
|
+
qubits = _flatten_qubits(s, regs)
|
|
124
|
+
ops.append(Operation("measure", qubits, (), line_index, op_index, is_measure=True))
|
|
125
|
+
op_index += 1
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
m = _RE_OP.match(s)
|
|
129
|
+
if not m:
|
|
130
|
+
# unknown construct; count as a custom op with no qubits
|
|
131
|
+
ops.append(Operation("custom", (), (), line_index, op_index, is_custom=True))
|
|
132
|
+
op_index += 1
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
name = m.group(1)
|
|
136
|
+
params_raw = (m.group(2) or "").strip()
|
|
137
|
+
operands = (m.group(3) or "").strip()
|
|
138
|
+
|
|
139
|
+
params: tuple[str, ...] = ()
|
|
140
|
+
if params_raw.startswith("(") and params_raw.endswith(")"):
|
|
141
|
+
inside = params_raw[1:-1].strip()
|
|
142
|
+
if inside:
|
|
143
|
+
params = tuple(x.strip() for x in inside.split(",") if x.strip())
|
|
144
|
+
|
|
145
|
+
qubits = _flatten_qubits(operands, regs)
|
|
146
|
+
|
|
147
|
+
is_custom = False
|
|
148
|
+
if not name or (not qubits and name not in {"id"}):
|
|
149
|
+
is_custom = True
|
|
150
|
+
name = "custom"
|
|
151
|
+
|
|
152
|
+
ops.append(Operation(name=name.lower(), qubits=qubits, params=params, line_index=line_index, op_index=op_index, is_custom=is_custom))
|
|
153
|
+
op_index += 1
|
|
154
|
+
|
|
155
|
+
return CircuitIR(
|
|
156
|
+
n_qubits=regs.n_qubits,
|
|
157
|
+
n_cbits=regs.n_cbits,
|
|
158
|
+
operations=tuple(ops),
|
|
159
|
+
qasm_format=fmt,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def parse_qasm2_text(qasm_text: str, *, source_label: str | None = None) -> CircuitIR:
|
|
164
|
+
"""
|
|
165
|
+
Parse OpenQASM 2 source from a string. Same IR as parse_qasm2_file for identical content.
|
|
166
|
+
|
|
167
|
+
Does not raise for OpenQASM 3 headers; consumers that need rejection should use
|
|
168
|
+
parse_circuit_file() or check ir.qasm_format.
|
|
169
|
+
|
|
170
|
+
source_label: optional provenance string for future diagnostics (unused today).
|
|
171
|
+
"""
|
|
172
|
+
_ = source_label # reserved for error-context diagnostics
|
|
173
|
+
raw_lines = qasm_text.splitlines()
|
|
174
|
+
lines: list[str] = []
|
|
175
|
+
for raw in raw_lines:
|
|
176
|
+
stripped = _strip_inline_comment(raw).strip()
|
|
177
|
+
lines.append(stripped)
|
|
178
|
+
return _parse_qasm2_lines(lines)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def parse_qasm2_file(path: str) -> CircuitIR:
|
|
182
|
+
p = Path(path)
|
|
183
|
+
text = p.read_text(encoding="utf-8", errors="replace")
|
|
184
|
+
return parse_qasm2_text(text)
|