Deminiko
Initial commit: QuantumArchitect-MCP quantum circuit MCP server with Gradio UI
6ce350d
"""
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),
}