Spaces:
Running
Running
| """ | |
| 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'} | |
| 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 | |
| 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) | |
| 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 | |
| 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 | |
| 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)) | |
| 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 | |