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