coda-node 0.1.0__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.
- coda_node/__init__.py +18 -0
- coda_node/errors.py +44 -0
- coda_node/py.typed +0 -0
- coda_node/server/__init__.py +9 -0
- coda_node/server/app.py +191 -0
- coda_node/server/auth.py +192 -0
- coda_node/server/cli.py +360 -0
- coda_node/server/config.py +300 -0
- coda_node/server/consumer.py +625 -0
- coda_node/server/daemon.py +261 -0
- coda_node/server/executor.py +200 -0
- coda_node/server/heartbeat.py +113 -0
- coda_node/server/ir.py +182 -0
- coda_node/server/qasm.py +377 -0
- coda_node/server/webhook.py +161 -0
- coda_node/vpn/__init__.py +49 -0
- coda_node/vpn/guard.py +424 -0
- coda_node/vpn/service.py +593 -0
- coda_node-0.1.0.dist-info/METADATA +348 -0
- coda_node-0.1.0.dist-info/RECORD +23 -0
- coda_node-0.1.0.dist-info/WHEEL +4 -0
- coda_node-0.1.0.dist-info/entry_points.txt +3 -0
- coda_node-0.1.0.dist-info/licenses/LICENSE +21 -0
coda_node/server/ir.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Native-gate intermediate representation (IR) for quantum circuits.
|
|
2
|
+
|
|
3
|
+
Jobs received from the Coda cloud carry a JSON-serialized circuit in
|
|
4
|
+
this IR format. The IR is target-aware: each hardware target (e.g.
|
|
5
|
+
``cz``, ``cnot``, ``pcz``) defines a legal gate set, and
|
|
6
|
+
the validators reject programs that use gates outside that set or
|
|
7
|
+
reference qubits beyond the device's capacity.
|
|
8
|
+
|
|
9
|
+
Schema (version ``"1.0"``)::
|
|
10
|
+
|
|
11
|
+
{
|
|
12
|
+
"version": "1.0",
|
|
13
|
+
"target": "cz",
|
|
14
|
+
"num_qubits": 5,
|
|
15
|
+
"gates": [{"gate": "rx", "qubits": [0], "params": [1.57]}],
|
|
16
|
+
"measurements": [0, 1],
|
|
17
|
+
"metadata": {
|
|
18
|
+
"source_hash": "abc123",
|
|
19
|
+
"compiled_at": "2026-01-01T00:00:00Z"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from enum import StrEnum
|
|
27
|
+
from typing import Literal
|
|
28
|
+
|
|
29
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"GATE_SPECS",
|
|
33
|
+
"LEGAL_GATES",
|
|
34
|
+
"GateOp",
|
|
35
|
+
"IRMetadata",
|
|
36
|
+
"NativeGate",
|
|
37
|
+
"NativeGateIR",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class NativeGate(StrEnum):
|
|
42
|
+
"""Supported native gate identifiers across all hardware targets."""
|
|
43
|
+
|
|
44
|
+
RX = "rx"
|
|
45
|
+
RY = "ry"
|
|
46
|
+
RZ = "rz"
|
|
47
|
+
CZ = "cz"
|
|
48
|
+
ISWAP = "iswap"
|
|
49
|
+
CP = "cp"
|
|
50
|
+
ID = "id"
|
|
51
|
+
X90 = "x90"
|
|
52
|
+
Y_MINUS_90 = "y_minus_90"
|
|
53
|
+
VIRTUAL_Z = "virtual_z"
|
|
54
|
+
CNOT = "cnot"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
GATE_SPECS: dict[str, dict[str, int]] = {
|
|
58
|
+
"rx": {"qubits": 1, "params": 1},
|
|
59
|
+
"ry": {"qubits": 1, "params": 1},
|
|
60
|
+
"rz": {"qubits": 1, "params": 1},
|
|
61
|
+
"cz": {"qubits": 2, "params": 0},
|
|
62
|
+
"iswap": {"qubits": 2, "params": 0},
|
|
63
|
+
"cp": {"qubits": 2, "params": 1},
|
|
64
|
+
"id": {"qubits": 1, "params": 1},
|
|
65
|
+
"x90": {"qubits": 1, "params": 0},
|
|
66
|
+
"y_minus_90": {"qubits": 1, "params": 0},
|
|
67
|
+
"virtual_z": {"qubits": 1, "params": 1},
|
|
68
|
+
"cnot": {"qubits": 2, "params": 0},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
LEGAL_GATES: dict[str, set[str]] = {
|
|
72
|
+
"cz": {"rx", "ry", "rz", "cz", "id"},
|
|
73
|
+
"iswap": {"rx", "ry", "rz", "iswap", "id"},
|
|
74
|
+
"pcz": {"rx", "ry", "rz", "cp", "id"},
|
|
75
|
+
"cnot": {
|
|
76
|
+
"rx",
|
|
77
|
+
"ry",
|
|
78
|
+
"rz",
|
|
79
|
+
"x90",
|
|
80
|
+
"y_minus_90",
|
|
81
|
+
"virtual_z",
|
|
82
|
+
"cnot",
|
|
83
|
+
"id",
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class GateOp(BaseModel):
|
|
89
|
+
"""A single gate operation: gate name, target qubits, and parameters."""
|
|
90
|
+
|
|
91
|
+
gate: NativeGate
|
|
92
|
+
qubits: list[int]
|
|
93
|
+
params: list[float] = Field(default_factory=list)
|
|
94
|
+
|
|
95
|
+
@model_validator(mode="after")
|
|
96
|
+
def validate_gate_shape(self) -> GateOp:
|
|
97
|
+
"""Ensure qubit and parameter counts match the gate specification."""
|
|
98
|
+
gate = self.gate.value
|
|
99
|
+
if gate in GATE_SPECS:
|
|
100
|
+
expected_qubits = GATE_SPECS[gate]["qubits"]
|
|
101
|
+
if len(self.qubits) != expected_qubits:
|
|
102
|
+
raise ValueError(
|
|
103
|
+
f"Gate {gate} requires {expected_qubits} qubit(s), got {len(self.qubits)}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
expected_params = GATE_SPECS[gate]["params"]
|
|
107
|
+
if len(self.params) != expected_params:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"Gate {gate} requires {expected_params} param(s), got {len(self.params)}"
|
|
110
|
+
)
|
|
111
|
+
return self
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class IRMetadata(BaseModel):
|
|
115
|
+
"""Provenance metadata attached to a compiled circuit."""
|
|
116
|
+
|
|
117
|
+
source_hash: str
|
|
118
|
+
compiled_at: str
|
|
119
|
+
compiler_version: str = "0.1.0"
|
|
120
|
+
optimization_level: int = Field(ge=0, le=3, default=2)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class NativeGateIR(BaseModel):
|
|
124
|
+
"""A fully validated native-gate circuit ready for execution.
|
|
125
|
+
|
|
126
|
+
Validation enforces that every gate is legal for the declared target,
|
|
127
|
+
qubit indices are within ``[0, num_qubits)``, and each gate has the
|
|
128
|
+
correct number of qubit operands and parameters.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
version: Literal["1.0"] = "1.0"
|
|
132
|
+
target: str
|
|
133
|
+
num_qubits: int = Field(ge=1, le=50)
|
|
134
|
+
gates: list[GateOp]
|
|
135
|
+
measurements: list[int]
|
|
136
|
+
metadata: IRMetadata
|
|
137
|
+
|
|
138
|
+
@field_validator("target")
|
|
139
|
+
@classmethod
|
|
140
|
+
def validate_target(cls, value: str) -> str:
|
|
141
|
+
"""Reject targets not present in :data:`LEGAL_GATES`."""
|
|
142
|
+
if value not in LEGAL_GATES:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"Unknown target '{value}'. Valid: {list(LEGAL_GATES.keys())}"
|
|
145
|
+
)
|
|
146
|
+
return value
|
|
147
|
+
|
|
148
|
+
@model_validator(mode="after")
|
|
149
|
+
def validate_consistency(self) -> NativeGateIR:
|
|
150
|
+
"""Validate gate legality and qubit indices against the target."""
|
|
151
|
+
legal = LEGAL_GATES[self.target]
|
|
152
|
+
for index, op in enumerate(self.gates):
|
|
153
|
+
if op.gate.value not in legal:
|
|
154
|
+
raise ValueError(
|
|
155
|
+
f"Gate '{op.gate.value}' not legal for target '{self.target}' at index {index}"
|
|
156
|
+
)
|
|
157
|
+
for qubit in op.qubits:
|
|
158
|
+
if qubit < 0 or qubit >= self.num_qubits:
|
|
159
|
+
raise ValueError(
|
|
160
|
+
f"Qubit {qubit} out of range [0, {self.num_qubits}) at gate index {index}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
for qubit in self.measurements:
|
|
164
|
+
if qubit < 0 or qubit >= self.num_qubits:
|
|
165
|
+
raise ValueError(
|
|
166
|
+
f"Measurement qubit {qubit} out of range [0, {self.num_qubits})"
|
|
167
|
+
)
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_json(cls, json_str: str) -> NativeGateIR:
|
|
172
|
+
"""Deserialize and validate a JSON-encoded IR program.
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
pydantic.ValidationError: If the JSON is malformed or the
|
|
176
|
+
circuit violates gate-set or qubit-range constraints.
|
|
177
|
+
"""
|
|
178
|
+
return cls.model_validate_json(json_str)
|
|
179
|
+
|
|
180
|
+
def to_json(self) -> str:
|
|
181
|
+
"""Serialize the IR to a pretty-printed JSON string."""
|
|
182
|
+
return self.model_dump_json(indent=2)
|
coda_node/server/qasm.py
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"""OpenQASM 3.0 <-> NativeGateIR round-trip helpers.
|
|
2
|
+
|
|
3
|
+
These helpers support a deliberately small OpenQASM subset so tests can
|
|
4
|
+
round-trip circuits through text and keep validating that ``NativeGateIR``
|
|
5
|
+
remains a valid, stable IR representation going forward. They are not meant
|
|
6
|
+
to define a broader OpenQASM compatibility surface.
|
|
7
|
+
|
|
8
|
+
Supports the ``cz`` and ``cnot`` IR targets for the OpenQASM round-trip subset.
|
|
9
|
+
Only the QASM subset needed by these targets is handled; symbolic parameter
|
|
10
|
+
expressions (for example ``pi/2``) are not supported, so callers must use
|
|
11
|
+
numeric literals.
|
|
12
|
+
|
|
13
|
+
Known limitation: the ``id`` gate carries a duration parameter in the IR
|
|
14
|
+
(nanoseconds) that has no OpenQASM counterpart. ``ir_to_openqasm`` drops
|
|
15
|
+
the duration, and ``openqasm_to_ir`` fills it with ``0.0``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import math
|
|
21
|
+
import re
|
|
22
|
+
from datetime import UTC, datetime
|
|
23
|
+
|
|
24
|
+
from coda_node.server.ir import GateOp, IRMetadata, NativeGate, NativeGateIR
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class QASMConversionError(Exception):
|
|
28
|
+
"""Raised when an OpenQASM program cannot be converted."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_HALF_PI = math.pi / 2
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Regex patterns for the supported QASM subset
|
|
35
|
+
# TODO: Replace regex parsing with a proper AST parser if the supported QASM
|
|
36
|
+
# subset grows (e.g. multi-register circuits, barrier instructions, or new gates).
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
_HEADER_RE = re.compile(r"OPENQASM\s+[\d.]+\s*;")
|
|
40
|
+
_INCLUDE_RE = re.compile(r'include\s+"[^"]+"\s*;')
|
|
41
|
+
_QUBIT_DECL_RE = re.compile(r"qubit\[(\d+)]\s+\w+\s*;")
|
|
42
|
+
_BIT_DECL_RE = re.compile(r"bit\[(\d+)]\s+\w+\s*;")
|
|
43
|
+
_MEASURE_RE = re.compile(r"\w+\[(\d+)]\s*=\s*measure\s+\w+\[(\d+)]\s*;")
|
|
44
|
+
_GATE_RE = re.compile(
|
|
45
|
+
r"(\w+)"
|
|
46
|
+
r"(?:\(([^)]*)\))?"
|
|
47
|
+
r"\s+"
|
|
48
|
+
r"((?:\w+\[\d+])(?:\s*,\s*\w+\[\d+])*)"
|
|
49
|
+
r"\s*;"
|
|
50
|
+
)
|
|
51
|
+
_QUBIT_REF_RE = re.compile(r"\w+\[(\d+)]")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _format_float(value: float) -> str:
|
|
55
|
+
"""Format a numeric parameter for stable QASM output.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
value: Numeric value to serialize.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
String form with enough precision for round-trip comparisons.
|
|
62
|
+
"""
|
|
63
|
+
return f"{value:.15g}"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# OpenQASM → NativeGateIR
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def openqasm_to_ir(
|
|
72
|
+
qasm: str,
|
|
73
|
+
*,
|
|
74
|
+
target: str,
|
|
75
|
+
metadata: IRMetadata | None = None,
|
|
76
|
+
) -> NativeGateIR:
|
|
77
|
+
"""Parse supported OpenQASM text into ``NativeGateIR``.
|
|
78
|
+
|
|
79
|
+
This parser exists primarily to support round-trip validation of the IR.
|
|
80
|
+
It accepts only the subset of OpenQASM needed to confirm that the current
|
|
81
|
+
``NativeGateIR`` encoding can be serialized and reconstructed reliably.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
qasm: OpenQASM 3.0 source text.
|
|
85
|
+
target: Native gate target to validate against.
|
|
86
|
+
metadata: Optional metadata to attach to the returned IR. When omitted,
|
|
87
|
+
placeholder metadata is generated.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Parsed native-gate IR object.
|
|
91
|
+
|
|
92
|
+
Raises:
|
|
93
|
+
QASMConversionError: If the QASM text uses unsupported syntax, gates,
|
|
94
|
+
or targets, or if the resulting IR fails validation.
|
|
95
|
+
"""
|
|
96
|
+
num_qubits: int | None = None
|
|
97
|
+
gates: list[GateOp] = []
|
|
98
|
+
measurements: list[int] = []
|
|
99
|
+
|
|
100
|
+
for raw in qasm.strip().splitlines():
|
|
101
|
+
line = raw.strip()
|
|
102
|
+
if (
|
|
103
|
+
not line
|
|
104
|
+
or line.startswith("//")
|
|
105
|
+
or _HEADER_RE.match(line)
|
|
106
|
+
or _INCLUDE_RE.match(line)
|
|
107
|
+
):
|
|
108
|
+
continue
|
|
109
|
+
if _BIT_DECL_RE.match(line):
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
m_q = _QUBIT_DECL_RE.match(line)
|
|
113
|
+
if m_q:
|
|
114
|
+
if num_qubits is not None:
|
|
115
|
+
raise QASMConversionError(
|
|
116
|
+
"Multiple qubit register declarations are not supported"
|
|
117
|
+
)
|
|
118
|
+
num_qubits = int(m_q.group(1))
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
m_m = _MEASURE_RE.match(line)
|
|
122
|
+
if m_m:
|
|
123
|
+
measurements.append(int(m_m.group(2)))
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
m_g = _GATE_RE.match(line)
|
|
127
|
+
if m_g:
|
|
128
|
+
gate_name = m_g.group(1)
|
|
129
|
+
params = (
|
|
130
|
+
[float(p.strip()) for p in m_g.group(2).split(",")]
|
|
131
|
+
if m_g.group(2)
|
|
132
|
+
else []
|
|
133
|
+
)
|
|
134
|
+
qubits = [int(q) for q in _QUBIT_REF_RE.findall(m_g.group(3))]
|
|
135
|
+
gates.append(_openqasm_gate_to_ir(gate_name, params, qubits, target))
|
|
136
|
+
continue
|
|
137
|
+
|
|
138
|
+
raise QASMConversionError(f"Unsupported QASM line: {line!r}")
|
|
139
|
+
|
|
140
|
+
if num_qubits is None:
|
|
141
|
+
raise QASMConversionError("No qubit register declaration found")
|
|
142
|
+
|
|
143
|
+
if metadata is None:
|
|
144
|
+
metadata = IRMetadata(
|
|
145
|
+
source_hash="sha256:from-qasm",
|
|
146
|
+
compiled_at=datetime.now(UTC).isoformat(),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
return NativeGateIR(
|
|
151
|
+
target=target,
|
|
152
|
+
num_qubits=num_qubits,
|
|
153
|
+
gates=gates,
|
|
154
|
+
measurements=measurements,
|
|
155
|
+
metadata=metadata,
|
|
156
|
+
)
|
|
157
|
+
except Exception as exc:
|
|
158
|
+
raise QASMConversionError(str(exc)) from exc
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# ---------------------------------------------------------------------------
|
|
162
|
+
# NativeGateIR → OpenQASM
|
|
163
|
+
# ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def ir_to_openqasm(ir: NativeGateIR) -> str:
|
|
167
|
+
"""Serialize ``NativeGateIR`` into the supported OpenQASM subset.
|
|
168
|
+
|
|
169
|
+
The output is intentionally limited to the subset understood by
|
|
170
|
+
:func:`openqasm_to_ir` so tests can round-trip circuits through QASM and
|
|
171
|
+
keep treating ``NativeGateIR`` as the durable representation. The only
|
|
172
|
+
known lossy case is the ``id`` gate duration.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
ir: Native-gate IR to serialize.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
OpenQASM 3.0 source text.
|
|
179
|
+
|
|
180
|
+
Raises:
|
|
181
|
+
QASMConversionError: If the IR contains a gate that has no supported
|
|
182
|
+
mapping for the selected target.
|
|
183
|
+
"""
|
|
184
|
+
lines: list[str] = [
|
|
185
|
+
"OPENQASM 3.0;",
|
|
186
|
+
'include "stdgates.inc";',
|
|
187
|
+
f"qubit[{ir.num_qubits}] q;",
|
|
188
|
+
]
|
|
189
|
+
if ir.measurements:
|
|
190
|
+
lines.append(f"bit[{len(ir.measurements)}] c;")
|
|
191
|
+
|
|
192
|
+
for op in ir.gates:
|
|
193
|
+
lines.append(_ir_gate_to_openqasm(op, ir.target))
|
|
194
|
+
|
|
195
|
+
for i, qubit in enumerate(ir.measurements):
|
|
196
|
+
lines.append(f"c[{i}] = measure q[{qubit}];")
|
|
197
|
+
|
|
198
|
+
return "\n".join(lines) + "\n"
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
# Internal gate mapping helpers
|
|
203
|
+
# ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _openqasm_gate_to_ir(
|
|
207
|
+
name: str, params: list[float], qubits: list[int], target: str
|
|
208
|
+
) -> GateOp:
|
|
209
|
+
"""Map a parsed OpenQASM gate into a target-specific IR operation.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
name: Parsed OpenQASM gate name.
|
|
213
|
+
params: Parsed numeric gate parameters.
|
|
214
|
+
qubits: Parsed qubit indices referenced by the gate.
|
|
215
|
+
target: Native gate target that determines the mapping rules.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Corresponding native-gate operation.
|
|
219
|
+
|
|
220
|
+
Raises:
|
|
221
|
+
QASMConversionError: If the target is unsupported.
|
|
222
|
+
"""
|
|
223
|
+
if target == "cz":
|
|
224
|
+
return _openqasm_gate_to_ir_cz(name, params, qubits)
|
|
225
|
+
if target == "cnot":
|
|
226
|
+
return _openqasm_gate_to_ir_cnot(name, params, qubits)
|
|
227
|
+
raise QASMConversionError(f"Unsupported target: {target}")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _openqasm_gate_to_ir_cz(
|
|
231
|
+
name: str, params: list[float], qubits: list[int]
|
|
232
|
+
) -> GateOp:
|
|
233
|
+
"""Map an OpenQASM gate into the ``cz`` gate set.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
name: Parsed OpenQASM gate name.
|
|
237
|
+
params: Parsed numeric gate parameters.
|
|
238
|
+
qubits: Parsed qubit indices referenced by the gate.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Corresponding native-gate operation.
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
QASMConversionError: If the gate is not representable for this target.
|
|
245
|
+
"""
|
|
246
|
+
_map: dict[str, tuple[NativeGate, bool]] = {
|
|
247
|
+
"rx": (NativeGate.RX, True),
|
|
248
|
+
"ry": (NativeGate.RY, True),
|
|
249
|
+
"rz": (NativeGate.RZ, True),
|
|
250
|
+
"cz": (NativeGate.CZ, False),
|
|
251
|
+
"id": (NativeGate.ID, False),
|
|
252
|
+
}
|
|
253
|
+
if name not in _map:
|
|
254
|
+
raise QASMConversionError(f"Gate '{name}' not supported for cz")
|
|
255
|
+
ir_gate, has_params = _map[name]
|
|
256
|
+
if ir_gate == NativeGate.ID:
|
|
257
|
+
return GateOp(gate=ir_gate, qubits=qubits, params=[0.0])
|
|
258
|
+
return GateOp(gate=ir_gate, qubits=qubits, params=params if has_params else [])
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _openqasm_gate_to_ir_cnot(
|
|
262
|
+
name: str, params: list[float], qubits: list[int]
|
|
263
|
+
) -> GateOp:
|
|
264
|
+
"""Map an OpenQASM gate into the ``cnot`` gate set.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
name: Parsed QASM gate name.
|
|
268
|
+
params: Parsed numeric gate parameters.
|
|
269
|
+
qubits: Parsed qubit indices referenced by the gate.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
Corresponding native-gate operation.
|
|
273
|
+
|
|
274
|
+
Raises:
|
|
275
|
+
QASMConversionError: If the gate is not representable for this target.
|
|
276
|
+
"""
|
|
277
|
+
_simple: dict[str, tuple[NativeGate, bool]] = {
|
|
278
|
+
"sx": (NativeGate.X90, False),
|
|
279
|
+
"rz": (NativeGate.VIRTUAL_Z, True),
|
|
280
|
+
"cx": (NativeGate.CNOT, False),
|
|
281
|
+
"id": (NativeGate.ID, False),
|
|
282
|
+
}
|
|
283
|
+
if name in _simple:
|
|
284
|
+
ir_gate, has_params = _simple[name]
|
|
285
|
+
if ir_gate == NativeGate.ID:
|
|
286
|
+
return GateOp(gate=ir_gate, qubits=qubits, params=[0.0])
|
|
287
|
+
return GateOp(gate=ir_gate, qubits=qubits, params=params if has_params else [])
|
|
288
|
+
|
|
289
|
+
if name == "ry" and len(params) == 1:
|
|
290
|
+
if math.isclose(params[0], -_HALF_PI, rel_tol=1e-12, abs_tol=1e-12):
|
|
291
|
+
return GateOp(gate=NativeGate.Y_MINUS_90, qubits=qubits, params=[])
|
|
292
|
+
raise QASMConversionError(
|
|
293
|
+
f"ry({params[0]}) not representable in cnot "
|
|
294
|
+
f"(only ry(-π/2) maps to y_minus_90)"
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
raise QASMConversionError(f"Gate '{name}' not supported for cnot")
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def _ir_gate_to_openqasm(op: GateOp, target: str) -> str:
|
|
301
|
+
"""Map an IR operation into target-specific OpenQASM text.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
op: IR gate operation to serialize.
|
|
305
|
+
target: Native gate target that determines the mapping rules.
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Single OpenQASM instruction line.
|
|
309
|
+
|
|
310
|
+
Raises:
|
|
311
|
+
QASMConversionError: If the target is unsupported.
|
|
312
|
+
"""
|
|
313
|
+
name = op.gate.value
|
|
314
|
+
qubits_str = ", ".join(f"q[{q}]" for q in op.qubits)
|
|
315
|
+
|
|
316
|
+
if target == "cz":
|
|
317
|
+
return _ir_gate_to_openqasm_cz(name, op.params, qubits_str)
|
|
318
|
+
if target == "cnot":
|
|
319
|
+
return _ir_gate_to_openqasm_cnot(name, op.params, qubits_str)
|
|
320
|
+
raise QASMConversionError(f"Unsupported target: {target}")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _ir_gate_to_openqasm_cz(name: str, params: list[float], qubits_str: str) -> str:
|
|
324
|
+
"""Serialize a ``cz`` IR gate into OpenQASM text.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
name: Native gate name.
|
|
328
|
+
params: Gate parameters to emit.
|
|
329
|
+
qubits_str: Preformatted OpenQASM qubit operand list.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Single OpenQASM instruction line.
|
|
333
|
+
|
|
334
|
+
Raises:
|
|
335
|
+
QASMConversionError: If the gate has no supported OpenQASM mapping.
|
|
336
|
+
"""
|
|
337
|
+
_parameterized = {"rx", "ry", "rz"}
|
|
338
|
+
_parameterless = {"cz", "id"}
|
|
339
|
+
if name in _parameterized:
|
|
340
|
+
ps = ", ".join(_format_float(p) for p in params)
|
|
341
|
+
return f"{name}({ps}) {qubits_str};"
|
|
342
|
+
if name == "virtual_z":
|
|
343
|
+
if not params:
|
|
344
|
+
raise QASMConversionError(
|
|
345
|
+
"IR gate 'virtual_z' requires exactly one parameter"
|
|
346
|
+
)
|
|
347
|
+
return f"rz({_format_float(params[0])}) {qubits_str};"
|
|
348
|
+
if name in _parameterless:
|
|
349
|
+
return f"{name} {qubits_str};"
|
|
350
|
+
raise QASMConversionError(f"IR gate '{name}' has no OpenQASM mapping for cz")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _ir_gate_to_openqasm_cnot(name: str, params: list[float], qubits_str: str) -> str:
|
|
354
|
+
"""Serialize a ``cnot`` IR gate into OpenQASM text.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
name: Native gate name.
|
|
358
|
+
params: Gate parameters to emit.
|
|
359
|
+
qubits_str: Preformatted OpenQASM qubit operand list.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
Single OpenQASM instruction line.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
QASMConversionError: If the gate has no supported OpenQASM mapping.
|
|
366
|
+
"""
|
|
367
|
+
if name == "x90":
|
|
368
|
+
return f"sx {qubits_str};"
|
|
369
|
+
if name == "y_minus_90":
|
|
370
|
+
return f"ry({_format_float(-_HALF_PI)}) {qubits_str};"
|
|
371
|
+
if name == "virtual_z":
|
|
372
|
+
return f"rz({_format_float(params[0])}) {qubits_str};"
|
|
373
|
+
if name == "cnot":
|
|
374
|
+
return f"cx {qubits_str};"
|
|
375
|
+
if name == "id":
|
|
376
|
+
return f"id {qubits_str};"
|
|
377
|
+
raise QASMConversionError(f"IR gate '{name}' has no OpenQASM mapping for cnot")
|