""" Connectivity Validator - Checks if 2-qubit gates are adjacent on specific hardware topologies. Crucial for real hardware execution. """ from typing import Any import json import os # Default hardware topologies (coupling maps) DEFAULT_TOPOLOGIES: dict[str, dict[str, Any]] = { "ibm_brisbane": { "name": "IBM Brisbane", "num_qubits": 127, "native_gates": ["cx", "id", "rz", "sx", "x"], "coupling_map": [ [0, 1], [1, 0], [1, 2], [2, 1], [2, 3], [3, 2], [3, 4], [4, 3], [4, 5], [5, 4], [5, 6], [6, 5], [6, 7], [7, 6], [7, 8], [8, 7], [8, 9], [9, 8], # Heavy-hex lattice structure (simplified for first 20 qubits) [0, 14], [14, 0], [4, 15], [15, 4], [8, 16], [16, 8], [12, 17], [17, 12], [1, 18], [18, 1], [5, 19], [19, 5], ], "description": "127-qubit Eagle processor", }, "ibm_sherbrooke": { "name": "IBM Sherbrooke", "num_qubits": 127, "native_gates": ["cx", "id", "rz", "sx", "x"], "coupling_map": [ [0, 1], [1, 0], [1, 2], [2, 1], [2, 3], [3, 2], [3, 4], [4, 3], [4, 5], [5, 4], [5, 6], [6, 5], ], "description": "127-qubit Eagle processor", }, "rigetti_aspen": { "name": "Rigetti Aspen-M", "num_qubits": 80, "native_gates": ["cz", "rx", "rz"], "coupling_map": [ # Octagonal lattice (simplified) [0, 1], [1, 0], [1, 2], [2, 1], [2, 3], [3, 2], [3, 4], [4, 3], [4, 5], [5, 4], [5, 6], [6, 5], [6, 7], [7, 6], [7, 0], [0, 7], # Ring [0, 8], [8, 0], [2, 9], [9, 2], [4, 10], [10, 4], [6, 11], [11, 6], ], "description": "80-qubit Aspen processor", }, "google_sycamore": { "name": "Google Sycamore", "num_qubits": 53, "native_gates": ["fsim", "phxz", "syc"], "coupling_map": [ # 2D grid with nearest-neighbor coupling [0, 1], [1, 0], [1, 2], [2, 1], [2, 3], [3, 2], [3, 4], [4, 3], [4, 5], [5, 4], [5, 6], [6, 5], [0, 7], [7, 0], [1, 8], [8, 1], [2, 9], [9, 2], [3, 10], [10, 3], [4, 11], [11, 4], [5, 12], [12, 5], ], "description": "53-qubit Sycamore processor", }, "ionq_harmony": { "name": "IonQ Harmony", "num_qubits": 11, "native_gates": ["gpi", "gpi2", "ms"], "coupling_map": "all_to_all", # Ion traps have full connectivity "description": "11-qubit ion trap processor (full connectivity)", }, "quantinuum_h1": { "name": "Quantinuum H1", "num_qubits": 20, "native_gates": ["rz", "ry", "zz"], "coupling_map": "all_to_all", "description": "20-qubit ion trap processor (full connectivity)", }, "linear_5": { "name": "Linear 5-qubit", "num_qubits": 5, "native_gates": ["cx", "id", "rz", "sx", "x"], "coupling_map": [ [0, 1], [1, 0], [1, 2], [2, 1], [2, 3], [3, 2], [3, 4], [4, 3] ], "description": "5-qubit linear chain topology", }, } def validate_connectivity( circuit_data: dict[str, Any], hardware_name: str | None = None, custom_coupling_map: list[list[int]] | None = None ) -> dict[str, Any]: """ Validate that all 2-qubit gates respect hardware connectivity. Args: circuit_data: Circuit dictionary hardware_name: Name of hardware profile to use custom_coupling_map: Custom coupling map as list of [control, target] pairs Returns: Validation result with specific error locations """ errors: list[dict[str, Any]] = [] warnings: list[str] = [] # Get coupling map if custom_coupling_map: coupling_map = set(tuple(pair) for pair in custom_coupling_map) hardware_info = {"name": "Custom", "num_qubits": max(max(p) for p in custom_coupling_map) + 1} elif hardware_name: if hardware_name.lower() not in {k.lower() for k in DEFAULT_TOPOLOGIES}: available = list(DEFAULT_TOPOLOGIES.keys()) return { "valid": False, "errors": [{"message": f"Unknown hardware: {hardware_name}", "available": available}], "warnings": [], } # Case-insensitive lookup hardware_key = next(k for k in DEFAULT_TOPOLOGIES if k.lower() == hardware_name.lower()) hardware_info = DEFAULT_TOPOLOGIES[hardware_key] if hardware_info.get("coupling_map") == "all_to_all": # Full connectivity - all pairs valid coupling_map = None else: coupling_map = set(tuple(pair) for pair in hardware_info["coupling_map"]) else: # No hardware specified - check for obvious issues only coupling_map = None hardware_info = {"name": "No specific hardware", "num_qubits": 1000} gates = circuit_data.get("gates", []) num_qubits = circuit_data.get("num_qubits", 0) # Check qubit count against hardware if num_qubits > hardware_info.get("num_qubits", 1000): errors.append({ "gate_idx": -1, "message": f"Circuit uses {num_qubits} qubits but {hardware_info['name']} has only {hardware_info['num_qubits']}", "suggestion": "Reduce circuit size or use different hardware" }) # Check each 2-qubit gate swap_suggestions: list[dict[str, Any]] = [] for idx, gate in enumerate(gates): name = gate.get("name", "").lower() qubits = gate.get("qubits", []) if len(qubits) == 2: q0, q1 = qubits if coupling_map is not None: # Check if this edge exists if (q0, q1) not in coupling_map and (q1, q0) not in coupling_map: errors.append({ "gate_idx": idx, "gate": name, "qubits": [q0, q1], "message": f"Qubits {q0} and {q1} are not connected on {hardware_info['name']}", "suggestion": f"Insert SWAP gates to route this connection" }) # Try to find a path path = _find_qubit_path(q0, q1, coupling_map, num_qubits) if path: swap_suggestions.append({ "original_gate_idx": idx, "path": path, "swaps_needed": len(path) - 2 }) elif len(qubits) == 3: # Three-qubit gates need all pairs connected or decomposition q0, q1, q2 = qubits if coupling_map is not None: missing_pairs = [] for pair in [(q0, q1), (q1, q2), (q0, q2)]: if pair not in coupling_map and (pair[1], pair[0]) not in coupling_map: missing_pairs.append(pair) if missing_pairs: warnings.append( f"Gate {idx} ({name}): 3-qubit gate may need decomposition. " f"Missing connections: {missing_pairs}" ) is_valid = len(errors) == 0 result: dict[str, Any] = { "valid": is_valid, "hardware": hardware_info.get("name", "Unknown"), "hardware_qubits": hardware_info.get("num_qubits", 0), "circuit_qubits": num_qubits, "errors": errors, "warnings": warnings, "connectivity_type": "all_to_all" if coupling_map is None else "restricted", } if swap_suggestions: result["swap_suggestions"] = swap_suggestions if is_valid: result["summary"] = f"Circuit is compatible with {hardware_info['name']}" else: result["summary"] = f"Found {len(errors)} connectivity violation(s)" return result def _find_qubit_path( start: int, end: int, coupling_map: set[tuple[int, int]], max_qubits: int ) -> list[int] | None: """Find shortest path between two qubits using BFS.""" if not coupling_map: return None # Build adjacency list adj: dict[int, list[int]] = {} for q0, q1 in coupling_map: if q0 not in adj: adj[q0] = [] adj[q0].append(q1) # BFS visited = {start} queue = [(start, [start])] while queue: current, path = queue.pop(0) if current == end: return path for neighbor in adj.get(current, []): if neighbor not in visited: visited.add(neighbor) queue.append((neighbor, path + [neighbor])) return None def get_available_hardware() -> list[dict[str, Any]]: """Get list of available hardware profiles.""" return [ { "id": key, "name": info["name"], "num_qubits": info["num_qubits"], "native_gates": info["native_gates"], "connectivity": "all_to_all" if info.get("coupling_map") == "all_to_all" else "restricted", "description": info.get("description", ""), } for key, info in DEFAULT_TOPOLOGIES.items() ] def check_native_gates( circuit_data: dict[str, Any], hardware_name: str ) -> dict[str, Any]: """ Check if circuit uses only native gates for the specified hardware. Args: circuit_data: Circuit dictionary hardware_name: Hardware profile name Returns: Validation result with non-native gates listed """ hardware_key = next( (k for k in DEFAULT_TOPOLOGIES if k.lower() == hardware_name.lower()), None ) if not hardware_key: return { "valid": False, "error": f"Unknown hardware: {hardware_name}", "available": list(DEFAULT_TOPOLOGIES.keys()), } hardware_info = DEFAULT_TOPOLOGIES[hardware_key] native_gates = set(g.lower() for g in hardware_info["native_gates"]) # Common gate translations gate_aliases = { "cnot": "cx", "toffoli": "ccx", "fredkin": "cswap", } non_native: list[dict[str, Any]] = [] gates = circuit_data.get("gates", []) for idx, gate in enumerate(gates): name = gate.get("name", "").lower() canonical_name = gate_aliases.get(name, name) if name in ("barrier", "measure", "reset"): continue if canonical_name not in native_gates: non_native.append({ "gate_idx": idx, "gate": name, "qubits": gate.get("qubits", []), }) return { "valid": len(non_native) == 0, "hardware": hardware_info["name"], "native_gates": list(native_gates), "non_native_gates": non_native, "summary": f"Found {len(non_native)} non-native gate(s)" if non_native else "All gates are native", "suggestion": "Use transpilation to decompose non-native gates" if non_native else None, }