File size: 8,061 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
"""
Syntax Checker - Basic sanity checks for quantum circuits.
Validates gate names, qubit indices, and parameter counts.
"""

from typing import Any

from ...core.exceptions import (
    QubitIndexError,
    CircuitParseError,
    ValidationError,
)


# Valid gate definitions: name -> (num_qubits, num_params)
VALID_GATES: dict[str, tuple[int, int]] = {
    # Single qubit gates (no params)
    "id": (1, 0), "i": (1, 0),
    "x": (1, 0), "y": (1, 0), "z": (1, 0),
    "h": (1, 0),
    "s": (1, 0), "sdg": (1, 0),
    "t": (1, 0), "tdg": (1, 0),
    "sx": (1, 0), "sxdg": (1, 0),
    
    # Single qubit gates (1 param)
    "rx": (1, 1), "ry": (1, 1), "rz": (1, 1),
    "p": (1, 1), "u1": (1, 1),
    
    # Single qubit gates (2 params)
    "u2": (1, 2),
    
    # Single qubit gates (3 params)
    "u": (1, 3), "u3": (1, 3),
    
    # Two qubit gates (no params)
    "cx": (2, 0), "cnot": (2, 0),
    "cy": (2, 0), "cz": (2, 0),
    "swap": (2, 0), "iswap": (2, 0),
    "ch": (2, 0),
    
    # Two qubit gates (1 param)
    "cp": (2, 1), "crx": (2, 1), "cry": (2, 1), "crz": (2, 1),
    "rxx": (2, 1), "ryy": (2, 1), "rzz": (2, 1),
    
    # Three qubit gates
    "ccx": (3, 0), "toffoli": (3, 0),
    "cswap": (3, 0), "fredkin": (3, 0),
    
    # Special operations
    "measure": (1, 0),
    "reset": (1, 0),
    "barrier": (-1, 0),  # -1 means any number of qubits
}


def check_syntax(circuit_data: dict[str, Any]) -> dict[str, Any]:
    """
    Perform comprehensive syntax validation on a circuit.
    
    Args:
        circuit_data: Circuit dictionary with gates and metadata
    
    Returns:
        Validation result dictionary
    """
    errors: list[str] = []
    warnings: list[str] = []
    
    num_qubits = circuit_data.get("num_qubits", 0)
    num_classical_bits = circuit_data.get("num_classical_bits", 0)
    gates = circuit_data.get("gates", [])
    
    # Check basic circuit structure
    if num_qubits <= 0:
        errors.append("Circuit must have at least 1 qubit")
    
    if not gates:
        warnings.append("Circuit has no gates")
    
    # Validate each gate
    for idx, gate in enumerate(gates):
        gate_errors = _validate_gate(gate, num_qubits, num_classical_bits, idx)
        errors.extend(gate_errors)
    
    # Check for potential issues
    circuit_warnings = _check_circuit_warnings(gates, num_qubits)
    warnings.extend(circuit_warnings)
    
    is_valid = len(errors) == 0
    
    return {
        "valid": is_valid,
        "errors": errors,
        "warnings": warnings,
        "gates_checked": len(gates),
        "summary": "Circuit syntax is valid" if is_valid else f"Found {len(errors)} syntax error(s)",
    }


def _validate_gate(
    gate: dict[str, Any],
    num_qubits: int,
    num_classical_bits: int,
    gate_idx: int
) -> list[str]:
    """Validate a single gate."""
    errors: list[str] = []
    
    name = gate.get("name", "").lower()
    qubits = gate.get("qubits", [])
    params = gate.get("params", [])
    classical_bits = gate.get("classical_bits", [])
    
    prefix = f"Gate {gate_idx} ({name})"
    
    # Check gate name
    if not name:
        errors.append(f"{prefix}: Missing gate name")
        return errors
    
    if name not in VALID_GATES:
        errors.append(f"{prefix}: Unknown gate '{name}'")
        return errors
    
    expected_qubits, expected_params = VALID_GATES[name]
    
    # Check qubit count
    if expected_qubits > 0 and len(qubits) != expected_qubits:
        errors.append(
            f"{prefix}: Expected {expected_qubits} qubit(s), got {len(qubits)}"
        )
    
    # Check qubit indices
    for q in qubits:
        if not isinstance(q, int):
            errors.append(f"{prefix}: Qubit index must be integer, got {type(q).__name__}")
        elif q < 0 or q >= num_qubits:
            errors.append(
                f"{prefix}: Qubit index {q} out of range (valid: 0-{num_qubits-1})"
            )
    
    # Check for duplicate qubits
    if len(qubits) != len(set(qubits)):
        errors.append(f"{prefix}: Duplicate qubit indices detected")
    
    # Check parameter count
    param_count = len([p for p in params if not (isinstance(p, str) and p.startswith("param:"))])
    if expected_params > 0 and param_count < expected_params:
        # Allow parameterized circuits with symbolic params
        if not any(isinstance(p, str) and p.startswith("param:") for p in params):
            errors.append(
                f"{prefix}: Expected {expected_params} parameter(s), got {param_count}"
            )
    
    # Check classical bits for measurements
    if name == "measure":
        if not classical_bits:
            errors.append(f"{prefix}: Measurement requires classical bit(s)")
        for c in classical_bits:
            if not isinstance(c, int):
                errors.append(f"{prefix}: Classical bit index must be integer")
            elif c < 0 or c >= num_classical_bits:
                errors.append(
                    f"{prefix}: Classical bit {c} out of range (valid: 0-{num_classical_bits-1})"
                )
    
    return errors


def _check_circuit_warnings(gates: list[dict[str, Any]], num_qubits: int) -> list[str]:
    """Check for potential issues in the circuit."""
    warnings: list[str] = []
    
    # Track which qubits have been used
    used_qubits: set[int] = set()
    measured_qubits: set[int] = set()
    gates_after_measure: dict[int, int] = {}
    
    for gate in gates:
        qubits = gate.get("qubits", [])
        name = gate.get("name", "").lower()
        
        for q in qubits:
            used_qubits.add(q)
            
            if q in measured_qubits and name not in ("measure", "reset", "barrier"):
                gates_after_measure[q] = gates_after_measure.get(q, 0) + 1
        
        if name == "measure":
            for q in qubits:
                measured_qubits.add(q)
    
    # Warn about unused qubits
    unused = set(range(num_qubits)) - used_qubits
    if unused:
        warnings.append(f"Unused qubits: {sorted(unused)}")
    
    # Warn about gates after measurement
    for q, count in gates_after_measure.items():
        warnings.append(
            f"Qubit {q} has {count} gate(s) after measurement (may collapse state)"
        )
    
    return warnings


def validate_qasm_syntax(qasm_string: str) -> dict[str, Any]:
    """
    Validate OpenQASM syntax without parsing into circuit.
    
    Args:
        qasm_string: QASM code string
    
    Returns:
        Validation result
    """
    errors: list[str] = []
    warnings: list[str] = []
    
    lines = qasm_string.strip().split('\n')
    has_openqasm = False
    has_include = False
    has_qreg = False
    
    for line_num, line in enumerate(lines, 1):
        line = line.strip()
        
        if not line or line.startswith('//'):
            continue
        
        if line.startswith('OPENQASM'):
            has_openqasm = True
            if '2.0' not in line and '3.0' not in line:
                warnings.append(f"Line {line_num}: Unusual OPENQASM version")
        
        elif line.startswith('include'):
            has_include = True
        
        elif line.startswith('qreg'):
            has_qreg = True
            if '[' not in line or ']' not in line:
                errors.append(f"Line {line_num}: Invalid qreg syntax")
        
        elif line.startswith('creg'):
            if '[' not in line or ']' not in line:
                errors.append(f"Line {line_num}: Invalid creg syntax")
        
        elif not line.endswith(';'):
            if not line.startswith('gate ') and not line.startswith('{') and line != '}':
                errors.append(f"Line {line_num}: Statement should end with semicolon")
    
    if not has_openqasm:
        warnings.append("Missing OPENQASM header")
    
    if not has_qreg:
        errors.append("No quantum register defined")
    
    return {
        "valid": len(errors) == 0,
        "errors": errors,
        "warnings": warnings,
        "lines_checked": len(lines),
    }