Spaces:
Sleeping
Sleeping
File size: 9,954 Bytes
6ce350d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
"""
Circuit Parser - Converts QASM strings and other formats into the internal DAG representation.
"""
import re
from typing import Any
from .dag_representation import CircuitDAG
from .exceptions import CircuitParseError, InvalidQASMError
class CircuitParser:
"""
Parser for quantum circuit representations.
Supports OpenQASM 2.0/3.0 and JSON circuit dictionaries.
"""
# Standard gate patterns for QASM 2.0
QASM2_GATE_PATTERN = re.compile(
r'^(\w+)(?:\(([\d\.,\s\*\/\+\-\w]+)\))?\s+([\w\[\],\s]+);$'
)
# Qubit/register patterns
QREG_PATTERN = re.compile(r'^qreg\s+(\w+)\[(\d+)\];$')
CREG_PATTERN = re.compile(r'^creg\s+(\w+)\[(\d+)\];$')
QUBIT_REF_PATTERN = re.compile(r'(\w+)\[(\d+)\]')
# Single qubit gates (no parameters)
SINGLE_GATES = {'x', 'y', 'z', 'h', 's', 't', 'sdg', 'tdg', 'id', 'sx', 'sxdg'}
# Parameterized single qubit gates
PARAM_SINGLE_GATES = {'rx', 'ry', 'rz', 'p', 'u1', 'u2', 'u3', 'u'}
# Two qubit gates
TWO_QUBIT_GATES = {'cx', 'cz', 'cy', 'swap', 'iswap', 'cnot', 'cp', 'crx', 'cry', 'crz'}
# Three qubit gates
THREE_QUBIT_GATES = {'ccx', 'cswap', 'toffoli', 'fredkin'}
@classmethod
def parse_qasm(cls, qasm_string: str) -> CircuitDAG:
"""
Parse OpenQASM 2.0/3.0 string into CircuitDAG.
Args:
qasm_string: Valid QASM code
Returns:
CircuitDAG representation
"""
lines = qasm_string.strip().split('\n')
# Track quantum and classical registers
qregs: dict[str, tuple[int, int]] = {} # name -> (start_index, size)
cregs: dict[str, tuple[int, int]] = {}
total_qubits = 0
total_cbits = 0
# First pass: find registers
for line_num, line in enumerate(lines, 1):
line = line.strip()
if not line or line.startswith('//') or line.startswith('OPENQASM') or line.startswith('include'):
continue
qreg_match = cls.QREG_PATTERN.match(line)
if qreg_match:
name, size = qreg_match.groups()
qregs[name] = (total_qubits, int(size))
total_qubits += int(size)
continue
creg_match = cls.CREG_PATTERN.match(line)
if creg_match:
name, size = creg_match.groups()
cregs[name] = (total_cbits, int(size))
total_cbits += int(size)
continue
if total_qubits == 0:
raise InvalidQASMError("No quantum registers defined", qasm_string[:200])
# Create DAG
dag = CircuitDAG(total_qubits, total_cbits)
# Second pass: parse gates
for line_num, line in enumerate(lines, 1):
line = line.strip()
if not line or line.startswith('//') or line.startswith('OPENQASM') or line.startswith('include'):
continue
if cls.QREG_PATTERN.match(line) or cls.CREG_PATTERN.match(line):
continue
# Parse gate
try:
cls._parse_gate_line(line, dag, qregs, cregs, line_num)
except Exception as e:
raise CircuitParseError(str(e), line_num)
return dag
@classmethod
def _parse_gate_line(
cls,
line: str,
dag: CircuitDAG,
qregs: dict[str, tuple[int, int]],
cregs: dict[str, tuple[int, int]],
line_num: int
) -> None:
"""Parse a single gate line and add to DAG."""
# Handle barrier
if line.startswith('barrier'):
qubit_str = line[7:].strip().rstrip(';')
qubits = cls._resolve_qubits(qubit_str, qregs)
dag.add_gate('barrier', qubits)
return
# Handle measurement
if line.startswith('measure'):
parts = line[7:].strip().rstrip(';').split('->')
if len(parts) != 2:
raise CircuitParseError(f"Invalid measure syntax: {line}", line_num)
qubits = cls._resolve_qubits(parts[0].strip(), qregs)
cbits = cls._resolve_cbits(parts[1].strip(), cregs)
for q, c in zip(qubits, cbits):
dag.add_gate('measure', [q], classical_bits=[c])
return
# Handle reset
if line.startswith('reset'):
qubit_str = line[5:].strip().rstrip(';')
qubits = cls._resolve_qubits(qubit_str, qregs)
for q in qubits:
dag.add_gate('reset', [q])
return
# General gate pattern
gate_match = cls.QASM2_GATE_PATTERN.match(line)
if not gate_match:
raise CircuitParseError(f"Cannot parse line: {line}", line_num)
gate_name = gate_match.group(1).lower()
params_str = gate_match.group(2)
qubits_str = gate_match.group(3)
# Parse parameters
params: list[float] = []
if params_str:
try:
params = [cls._eval_param(p.strip()) for p in params_str.split(',')]
except Exception:
raise CircuitParseError(f"Invalid parameters: {params_str}", line_num)
# Parse qubits
qubits = cls._resolve_qubits(qubits_str, qregs)
# Validate gate
if gate_name in cls.SINGLE_GATES:
if len(qubits) != 1:
raise CircuitParseError(
f"Gate {gate_name} expects 1 qubit, got {len(qubits)}", line_num
)
elif gate_name in cls.PARAM_SINGLE_GATES:
if len(qubits) != 1:
raise CircuitParseError(
f"Gate {gate_name} expects 1 qubit, got {len(qubits)}", line_num
)
elif gate_name in cls.TWO_QUBIT_GATES:
if len(qubits) != 2:
raise CircuitParseError(
f"Gate {gate_name} expects 2 qubits, got {len(qubits)}", line_num
)
elif gate_name in cls.THREE_QUBIT_GATES:
if len(qubits) != 3:
raise CircuitParseError(
f"Gate {gate_name} expects 3 qubits, got {len(qubits)}", line_num
)
dag.add_gate(gate_name, qubits, params)
@classmethod
def _resolve_qubits(cls, qubit_str: str, qregs: dict[str, tuple[int, int]]) -> list[int]:
"""Resolve qubit references to absolute indices."""
qubits: list[int] = []
for match in cls.QUBIT_REF_PATTERN.finditer(qubit_str):
reg_name, idx = match.groups()
if reg_name not in qregs:
raise CircuitParseError(f"Unknown register: {reg_name}")
start_idx, size = qregs[reg_name]
qubit_idx = int(idx)
if qubit_idx >= size:
raise CircuitParseError(
f"Qubit index {qubit_idx} out of range for register {reg_name}[{size}]"
)
qubits.append(start_idx + qubit_idx)
return qubits
@classmethod
def _resolve_cbits(cls, cbit_str: str, cregs: dict[str, tuple[int, int]]) -> list[int]:
"""Resolve classical bit references to absolute indices."""
cbits: list[int] = []
for match in cls.QUBIT_REF_PATTERN.finditer(cbit_str):
reg_name, idx = match.groups()
if reg_name not in cregs:
raise CircuitParseError(f"Unknown classical register: {reg_name}")
start_idx, size = cregs[reg_name]
cbit_idx = int(idx)
if cbit_idx >= size:
raise CircuitParseError(
f"Classical bit index {cbit_idx} out of range for register {reg_name}[{size}]"
)
cbits.append(start_idx + cbit_idx)
return cbits
@classmethod
def _eval_param(cls, param_str: str) -> float:
"""Safely evaluate a parameter expression."""
import math
# Allow pi and basic math
safe_dict = {
'pi': math.pi,
'PI': math.pi,
'e': math.e,
'sqrt': math.sqrt,
'sin': math.sin,
'cos': math.cos,
'tan': math.tan,
'exp': math.exp,
}
# Clean the expression
param_str = param_str.replace('^', '**')
return float(eval(param_str, {"__builtins__": {}}, safe_dict))
@classmethod
def parse_json_circuit(cls, circuit_dict: dict[str, Any]) -> CircuitDAG:
"""
Parse a JSON circuit dictionary into CircuitDAG.
Expected format:
{
"num_qubits": 2,
"num_classical_bits": 2,
"gates": [
{"name": "h", "qubits": [0]},
{"name": "cx", "qubits": [0, 1]},
{"name": "measure", "qubits": [0], "classical_bits": [0]}
]
}
"""
num_qubits = circuit_dict.get('num_qubits', 0)
num_cbits = circuit_dict.get('num_classical_bits', 0)
if num_qubits == 0:
raise CircuitParseError("num_qubits must be specified and > 0")
dag = CircuitDAG(num_qubits, num_cbits)
for gate_dict in circuit_dict.get('gates', []):
name = gate_dict.get('name', '').lower()
qubits = gate_dict.get('qubits', [])
params = gate_dict.get('params', [])
classical_bits = gate_dict.get('classical_bits', [])
if not name:
raise CircuitParseError("Gate must have a 'name' field")
if not qubits:
raise CircuitParseError(f"Gate '{name}' must have 'qubits' field")
dag.add_gate(name, qubits, params, classical_bits)
return dag
|