Q-Route / src /q_route /generator.py
jay2219's picture
v1.0.0
183b2d3
Raw
History Blame Contribute Delete
11.8 kB
"""
Data Generation Engine for Q-Route.
Generates random quantum circuits, defines physical hardware topologies,
and uses Qiskit's compiler oracle (with pure Python fallback) to generate ground-truth routed circuits.
"""
import random
import re
from typing import List, Tuple, Dict, Any, Optional
try:
import qiskit
from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap
from qiskit.compiler import transpile
HAS_QISKIT = True
except (ImportError, Exception):
HAS_QISKIT = False
QuantumCircuit = None
CouplingMap = None
transpile = None
try:
from qiskit.qasm2 import dumps as qasm2_dumps
except (ImportError, Exception):
qasm2_dumps = None
from q_route.prompt import create_training_example
class PurePythonCircuit:
"""Lightweight pure Python quantum circuit representation when Qiskit native DLL is unavailable."""
def __init__(self, num_qubits: int):
self.num_qubits = num_qubits
self.instructions: List[Tuple[str, List[int], List[float]]] = []
def h(self, q: int):
self.instructions.append(("h", [q], []))
def t(self, q: int):
self.instructions.append(("t", [q], []))
def s(self, q: int):
self.instructions.append(("s", [q], []))
def x(self, q: int):
self.instructions.append(("x", [q], []))
def y(self, q: int):
self.instructions.append(("y", [q], []))
def rz(self, angle: float, q: int):
self.instructions.append(("rz", [q], [angle]))
def cx(self, q1: int, q2: int):
self.instructions.append(("cx", [q1, q2], []))
def swap(self, q1: int, q2: int):
self.instructions.append(("swap", [q1, q2], []))
def to_qasm(self) -> str:
lines = [
"OPENQASM 2.0;",
'include "qelib1.inc";',
f"qreg q[{self.num_qubits}];",
f"creg c[{self.num_qubits}];",
]
for op, qubits, params in self.instructions:
if op == "cx":
lines.append(f"cx q[{qubits[0]}], q[{qubits[1]}];")
elif op == "swap":
lines.append(f"swap q[{qubits[0]}], q[{qubits[1]}];")
elif op == "rz":
lines.append(f"rz({params[0]}) q[{qubits[0]}];")
else:
lines.append(f"{op} q[{qubits[0]}];")
lines.append("measure q -> c;")
return "\n".join(lines)
def circuit_to_qasm(circuit: Any) -> str:
"""Export a QuantumCircuit or PurePythonCircuit to OpenQASM 2.0 string."""
if isinstance(circuit, PurePythonCircuit):
return circuit.to_qasm()
if qasm2_dumps is not None:
try:
return qasm2_dumps(circuit)
except Exception:
pass
if hasattr(circuit, "qasm"):
return circuit.qasm()
raise RuntimeError("Unable to export QuantumCircuit to QASM 2.0 format.")
def generate_topology(topology_type: str, num_qubits: int) -> Tuple[List[Tuple[int, int]], str, int]:
"""
Generate undirected coupling map edges for a given topology type and qubit count.
Returns (edges, topology_name, actual_num_qubits).
"""
edges: List[Tuple[int, int]] = []
if topology_type == "line":
name = f"Linear-{num_qubits}"
for i in range(num_qubits - 1):
edges.append((i, i + 1))
edges.append((i + 1, i))
elif topology_type == "ring":
name = f"Ring-{num_qubits}"
for i in range(num_qubits - 1):
edges.append((i, i + 1))
edges.append((i + 1, i))
if num_qubits > 2:
edges.append((num_qubits - 1, 0))
edges.append((0, num_qubits - 1))
elif topology_type == "star":
name = f"Star-{num_qubits}"
center = 0
for i in range(1, num_qubits):
edges.append((center, i))
edges.append((i, center))
elif topology_type == "grid":
cols = max(2, int(num_qubits**0.5))
rows = max(2, num_qubits // cols)
num_qubits = rows * cols
name = f"Grid-{rows}x{cols}"
for r in range(rows):
for c in range(cols):
node = r * cols + c
if c + 1 < cols:
right = r * cols + (c + 1)
edges.append((node, right))
edges.append((right, node))
if r + 1 < rows:
down = (r + 1) * cols + c
edges.append((node, down))
edges.append((down, node))
elif topology_type == "heavy_hex":
name = f"HeavyHex-{num_qubits}"
for i in range(num_qubits - 1):
edges.append((i, i + 1))
edges.append((i + 1, i))
for i in range(0, num_qubits - 2, 4):
if i + 2 < num_qubits:
edges.append((i, i + 2))
edges.append((i + 2, i))
else:
raise ValueError(f"Unknown topology type: {topology_type}")
unique_edges = sorted(list(set(edges)))
return unique_edges, name, num_qubits
def generate_random_circuit(
num_qubits: int, depth: int, shallow: bool = False, seed: Optional[int] = None
) -> Any:
"""
Generate a random abstract quantum circuit.
When shallow=True, produces 3 to 8 total gates with 1-2 SWAPs for curriculum learning.
"""
if seed is not None:
random.seed(seed)
qc = QuantumCircuit(num_qubits) if HAS_QISKIT else PurePythonCircuit(num_qubits)
single_qubit_gates = ['h', 't', 'rz', 'x', 'y', 's']
if shallow:
# Curriculum Shallow Mode: 3 to 8 total gates with 1 to 2 2-qubit CX gates
num_1q = random.randint(2, 5)
num_2q = random.randint(1, 2)
for _ in range(num_1q):
q = random.randint(0, num_qubits - 1)
gate = random.choice(single_qubit_gates)
if gate == 'h':
qc.h(q)
elif gate == 't':
qc.t(q)
elif gate == 'rz':
qc.rz(3.14159 / 4, q)
elif gate == 'x':
qc.x(q)
elif gate == 'y':
qc.y(q)
elif gate == 's':
qc.s(q)
for _ in range(num_2q):
if num_qubits >= 2:
q1, q2 = random.sample(range(num_qubits), 2)
qc.cx(q1, q2)
else:
for layer in range(depth):
for q in range(num_qubits):
if random.random() < 0.6:
gate = random.choice(single_qubit_gates)
if gate == 'h':
qc.h(q)
elif gate == 't':
qc.t(q)
elif gate == 'rz':
angle = 3.14159 / 4
qc.rz(angle, q)
elif gate == 'x':
qc.x(q)
elif gate == 'y':
qc.y(q)
elif gate == 's':
qc.s(q)
num_cx = random.randint(1, max(1, num_qubits // 2))
for _ in range(num_cx):
q1, q2 = random.sample(range(num_qubits), 2)
qc.cx(q1, q2)
return qc
def pure_python_route_circuit(
num_qubits: int, abstract_qasm: str, edges: List[Tuple[int, int]]
) -> str:
"""
100% Mathematically Exact Pure Python Graph Shortest-Path Routing Algorithm.
Guarantees 100.0% Topology Edge Pass@1 and zero invalid physical gate connections.
"""
allowed_edges = set(edges)
adj: Dict[int, List[int]] = {}
for u, v in edges:
adj.setdefault(u, []).append(v)
def find_shortest_path(start: int, target: int) -> List[int]:
if start == target:
return [start]
queue = [[start]]
visited = {start}
while queue:
path = queue.pop(0)
node = path[-1]
for neighbor in adj.get(node, []):
if neighbor == target:
return path + [neighbor]
if neighbor not in visited:
visited.add(neighbor)
queue.append(path + [neighbor])
return [start, target]
lines = abstract_qasm.strip().splitlines()
routed_lines = []
qubit_indices = [int(idx) for idx in re.findall(r"q\[(\d+)\]", abstract_qasm)]
max_qubit = max(qubit_indices) + 1 if qubit_indices else num_qubits
total_q = max(num_qubits, max_qubit)
phys_to_virt = list(range(total_q))
virt_to_phys = list(range(total_q))
for line in lines:
match_cx = re.search(r"cx\s+q\[(\d+)\]\s*,\s*q\[(\d+)\]\s*;", line)
match_1q = re.search(r"^([a-z]+(?:\([^\)]+\))?)\s+q\[(\d+)\]\s*;", line)
if match_cx:
v_ctrl, v_targ = int(match_cx.group(1)), int(match_cx.group(2))
p_ctrl = virt_to_phys[v_ctrl]
p_targ = virt_to_phys[v_targ]
if (p_ctrl, p_targ) not in allowed_edges:
path = find_shortest_path(p_ctrl, p_targ)
for idx in range(len(path) - 2):
p_from = virt_to_phys[v_ctrl]
p_to = path[idx + 1]
if p_from != p_to:
routed_lines.append(f"swap q[{p_from}], q[{p_to}];")
v_at_from = phys_to_virt[p_from]
v_at_to = phys_to_virt[p_to]
phys_to_virt[p_from] = v_at_to
phys_to_virt[p_to] = v_at_from
virt_to_phys[v_at_from] = p_to
virt_to_phys[v_at_to] = p_from
curr_p_ctrl = virt_to_phys[v_ctrl]
curr_p_targ = virt_to_phys[v_targ]
routed_lines.append(f"cx q[{curr_p_ctrl}], q[{curr_p_targ}];")
elif match_1q:
gate_name = match_1q.group(1)
v_qubit = int(match_1q.group(2))
p_qubit = virt_to_phys[v_qubit]
routed_lines.append(f"{gate_name} q[{p_qubit}];")
else:
routed_lines.append(line)
return "\n".join(routed_lines)
def generate_circuit_pair(
num_qubits: int,
depth: int,
topology_type: str,
shallow: bool = False,
seed: Optional[int] = None,
format_type: str = "chatml",
) -> Optional[Dict[str, Any]]:
"""
Generate a single (Abstract Circuit, Hardware-Routed Circuit) dataset pair.
"""
try:
edges, topo_name, actual_num_qubits = generate_topology(topology_type, num_qubits)
abstract_qc = generate_random_circuit(actual_num_qubits, depth, shallow=shallow, seed=seed)
abstract_qasm = circuit_to_qasm(abstract_qc)
if HAS_QISKIT and CouplingMap is not None and transpile is not None:
try:
c_map = CouplingMap(edges)
routed_qc = transpile(
abstract_qc,
coupling_map=c_map,
optimization_level=2,
seed_transpiler=seed if seed is not None else 42,
)
target_qasm = circuit_to_qasm(routed_qc)
except Exception:
target_qasm = pure_python_route_circuit(actual_num_qubits, abstract_qasm, edges)
else:
target_qasm = pure_python_route_circuit(actual_num_qubits, abstract_qasm, edges)
return create_training_example(
num_qubits=actual_num_qubits,
coupling_map=edges,
abstract_qasm=abstract_qasm,
target_qasm=target_qasm,
topology_name=topo_name,
format_type=format_type,
)
except Exception as e:
return None