""" 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), }