Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- app.py +409 -0
- requirements.txt +5 -0
app.py
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Text-to-Quantum Circuit Generator
|
| 3 |
+
Convert natural language to OpenQASM quantum circuits
|
| 4 |
+
|
| 5 |
+
Eric Raymond | Purdue ECE 2026
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import gradio as gr
|
| 9 |
+
import re
|
| 10 |
+
from typing import Tuple, Optional
|
| 11 |
+
import matplotlib
|
| 12 |
+
matplotlib.use('Agg')
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
import io
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
| 17 |
+
# Qiskit imports
|
| 18 |
+
try:
|
| 19 |
+
from qiskit import QuantumCircuit
|
| 20 |
+
from qiskit.visualization import circuit_drawer
|
| 21 |
+
from qiskit_aer import AerSimulator
|
| 22 |
+
from qiskit.compiler import transpile
|
| 23 |
+
QISKIT_AVAILABLE = True
|
| 24 |
+
except ImportError:
|
| 25 |
+
QISKIT_AVAILABLE = False
|
| 26 |
+
|
| 27 |
+
TITLE = "Text-to-Quantum Circuit"
|
| 28 |
+
DESCRIPTION = """
|
| 29 |
+
## English to OpenQASM Generator
|
| 30 |
+
|
| 31 |
+
Convert natural language descriptions into executable quantum circuits.
|
| 32 |
+
|
| 33 |
+
### Supported Operations
|
| 34 |
+
- **Single-qubit gates:** H (Hadamard), X, Y, Z, S, T, Rx, Ry, Rz
|
| 35 |
+
- **Two-qubit gates:** CNOT/CX, CZ, SWAP
|
| 36 |
+
- **Three-qubit gates:** Toffoli/CCX, Fredkin/CSWAP
|
| 37 |
+
- **Measurement:** measure qubit(s)
|
| 38 |
+
|
| 39 |
+
### Example Phrases
|
| 40 |
+
- "Create a Bell state with 2 qubits"
|
| 41 |
+
- "Apply Hadamard to qubit 0, then CNOT from 0 to 1"
|
| 42 |
+
- "3 qubit GHZ state"
|
| 43 |
+
- "Quantum teleportation circuit"
|
| 44 |
+
|
| 45 |
+
Built by Eric Raymond | Purdue ECE
|
| 46 |
+
"""
|
| 47 |
+
|
| 48 |
+
# Common circuit templates
|
| 49 |
+
CIRCUIT_TEMPLATES = {
|
| 50 |
+
'bell': [
|
| 51 |
+
('h', [0]),
|
| 52 |
+
('cx', [0, 1]),
|
| 53 |
+
('measure_all', []),
|
| 54 |
+
],
|
| 55 |
+
'ghz3': [
|
| 56 |
+
('h', [0]),
|
| 57 |
+
('cx', [0, 1]),
|
| 58 |
+
('cx', [0, 2]),
|
| 59 |
+
('measure_all', []),
|
| 60 |
+
],
|
| 61 |
+
'teleportation': [
|
| 62 |
+
('h', [1]),
|
| 63 |
+
('cx', [1, 2]),
|
| 64 |
+
('cx', [0, 1]),
|
| 65 |
+
('h', [0]),
|
| 66 |
+
('measure', [0]),
|
| 67 |
+
('measure', [1]),
|
| 68 |
+
],
|
| 69 |
+
'grover2': [
|
| 70 |
+
('h', [0]), ('h', [1]),
|
| 71 |
+
('cz', [0, 1]),
|
| 72 |
+
('h', [0]), ('h', [1]),
|
| 73 |
+
('x', [0]), ('x', [1]),
|
| 74 |
+
('cz', [0, 1]),
|
| 75 |
+
('x', [0]), ('x', [1]),
|
| 76 |
+
('h', [0]), ('h', [1]),
|
| 77 |
+
('measure_all', []),
|
| 78 |
+
],
|
| 79 |
+
'qft2': [
|
| 80 |
+
('h', [0]),
|
| 81 |
+
('cp', [1, 0, np.pi/2]),
|
| 82 |
+
('h', [1]),
|
| 83 |
+
('swap', [0, 1]),
|
| 84 |
+
('measure_all', []),
|
| 85 |
+
],
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def detect_template(text: str) -> Optional[str]:
|
| 90 |
+
"""Detect if user is asking for a known circuit template."""
|
| 91 |
+
text_lower = text.lower()
|
| 92 |
+
|
| 93 |
+
if 'bell' in text_lower and ('state' in text_lower or 'pair' in text_lower):
|
| 94 |
+
return 'bell'
|
| 95 |
+
if 'ghz' in text_lower or ('entangle' in text_lower and '3' in text_lower):
|
| 96 |
+
return 'ghz3'
|
| 97 |
+
if 'teleport' in text_lower:
|
| 98 |
+
return 'teleportation'
|
| 99 |
+
if 'grover' in text_lower:
|
| 100 |
+
return 'grover2'
|
| 101 |
+
if 'qft' in text_lower or 'fourier' in text_lower:
|
| 102 |
+
return 'qft2'
|
| 103 |
+
|
| 104 |
+
return None
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def parse_qubit_count(text: str) -> int:
|
| 108 |
+
"""Extract number of qubits from text."""
|
| 109 |
+
match = re.search(r'(\d+)\s*qubits?', text.lower())
|
| 110 |
+
if match:
|
| 111 |
+
return int(match.group(1))
|
| 112 |
+
|
| 113 |
+
indices = re.findall(r'qubit\s*(\d+)', text.lower())
|
| 114 |
+
indices += re.findall(r'\bq(\d+)\b', text.lower())
|
| 115 |
+
indices += re.findall(r'\b(\d+)\s*(?:to|and|,)\s*(\d+)', text.lower())
|
| 116 |
+
|
| 117 |
+
flat_indices = []
|
| 118 |
+
for i in indices:
|
| 119 |
+
if isinstance(i, tuple):
|
| 120 |
+
flat_indices.extend(i)
|
| 121 |
+
else:
|
| 122 |
+
flat_indices.append(i)
|
| 123 |
+
|
| 124 |
+
if flat_indices:
|
| 125 |
+
return max(int(i) for i in flat_indices) + 1
|
| 126 |
+
|
| 127 |
+
return 2
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def nl_to_operations(text: str) -> Tuple[list, int, str]:
|
| 131 |
+
"""Convert natural language to list of gate operations."""
|
| 132 |
+
|
| 133 |
+
# Check templates first
|
| 134 |
+
template = detect_template(text)
|
| 135 |
+
if template:
|
| 136 |
+
ops = CIRCUIT_TEMPLATES[template]
|
| 137 |
+
n_qubits = max(max(op[1]) if op[1] else 0 for op in ops) + 1
|
| 138 |
+
return ops, n_qubits, f"Using template: {template}"
|
| 139 |
+
|
| 140 |
+
operations = []
|
| 141 |
+
n_qubits = parse_qubit_count(text)
|
| 142 |
+
text_lower = text.lower()
|
| 143 |
+
notes = []
|
| 144 |
+
|
| 145 |
+
# Superposition patterns
|
| 146 |
+
if 'superposition' in text_lower or 'hadamard all' in text_lower or 'h all' in text_lower:
|
| 147 |
+
for i in range(n_qubits):
|
| 148 |
+
operations.append(('h', [i]))
|
| 149 |
+
notes.append(f"Applied H to all {n_qubits} qubits")
|
| 150 |
+
|
| 151 |
+
# Parse gate patterns
|
| 152 |
+
patterns = [
|
| 153 |
+
# Hadamard
|
| 154 |
+
(r'(?:hadamard|h)\s+(?:on\s+)?(?:qubit\s+)?(\d+)', 'h', lambda m: [int(m.group(1))]),
|
| 155 |
+
(r'apply\s+(?:hadamard|h)\s+to\s+(?:qubit\s+)?(\d+)', 'h', lambda m: [int(m.group(1))]),
|
| 156 |
+
|
| 157 |
+
# Pauli gates
|
| 158 |
+
(r'(?:pauli[- ]?)?x\s+(?:on\s+)?(?:qubit\s+)?(\d+)', 'x', lambda m: [int(m.group(1))]),
|
| 159 |
+
(r'(?:pauli[- ]?)?y\s+(?:on\s+)?(?:qubit\s+)?(\d+)', 'y', lambda m: [int(m.group(1))]),
|
| 160 |
+
(r'(?:pauli[- ]?)?z\s+(?:on\s+)?(?:qubit\s+)?(\d+)', 'z', lambda m: [int(m.group(1))]),
|
| 161 |
+
(r'(?:not|flip)\s+(?:qubit\s+)?(\d+)', 'x', lambda m: [int(m.group(1))]),
|
| 162 |
+
|
| 163 |
+
# Phase gates
|
| 164 |
+
(r's\s+(?:gate\s+)?(?:on\s+)?(?:qubit\s+)?(\d+)', 's', lambda m: [int(m.group(1))]),
|
| 165 |
+
(r't\s+(?:gate\s+)?(?:on\s+)?(?:qubit\s+)?(\d+)', 't', lambda m: [int(m.group(1))]),
|
| 166 |
+
|
| 167 |
+
# CNOT
|
| 168 |
+
(r'(?:cnot|cx)\s+(?:from\s+)?(?:qubit\s+)?(\d+)\s+(?:to\s+)?(?:qubit\s+)?(\d+)', 'cx', lambda m: [int(m.group(1)), int(m.group(2))]),
|
| 169 |
+
(r'(?:cnot|cx)\s+(\d+)\s*[,→-]\s*(\d+)', 'cx', lambda m: [int(m.group(1)), int(m.group(2))]),
|
| 170 |
+
(r'controlled[- ]?(?:not|x)\s+(?:control\s+)?(\d+)\s+(?:target\s+)?(\d+)', 'cx', lambda m: [int(m.group(1)), int(m.group(2))]),
|
| 171 |
+
|
| 172 |
+
# CZ
|
| 173 |
+
(r'cz\s+(\d+)\s*[,→-]?\s*(\d+)', 'cz', lambda m: [int(m.group(1)), int(m.group(2))]),
|
| 174 |
+
(r'controlled[- ]?z\s+(\d+)\s+(\d+)', 'cz', lambda m: [int(m.group(1)), int(m.group(2))]),
|
| 175 |
+
|
| 176 |
+
# SWAP
|
| 177 |
+
(r'swap\s+(?:qubit\s+)?(\d+)\s+(?:and\s+)?(?:qubit\s+)?(\d+)', 'swap', lambda m: [int(m.group(1)), int(m.group(2))]),
|
| 178 |
+
|
| 179 |
+
# Toffoli
|
| 180 |
+
(r'(?:toffoli|ccx)\s+(\d+)\s+(\d+)\s+(\d+)', 'ccx', lambda m: [int(m.group(1)), int(m.group(2)), int(m.group(3))]),
|
| 181 |
+
|
| 182 |
+
# Measurement
|
| 183 |
+
(r'measure\s+(?:qubit\s+)?(\d+)', 'measure', lambda m: [int(m.group(1))]),
|
| 184 |
+
(r'measure\s+all', 'measure_all', lambda m: []),
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
+
for pattern, gate, extractor in patterns:
|
| 188 |
+
for match in re.finditer(pattern, text_lower):
|
| 189 |
+
args = extractor(match)
|
| 190 |
+
operations.append((gate, args))
|
| 191 |
+
if args:
|
| 192 |
+
n_qubits = max(n_qubits, max(args) + 1)
|
| 193 |
+
|
| 194 |
+
status = f"Parsed {len(operations)} operations for {n_qubits} qubits"
|
| 195 |
+
if notes:
|
| 196 |
+
status += " | " + ", ".join(notes)
|
| 197 |
+
|
| 198 |
+
return operations, n_qubits, status
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def operations_to_qasm(operations: list, n_qubits: int) -> str:
|
| 202 |
+
"""Convert operations list to OpenQASM 2.0 string."""
|
| 203 |
+
lines = [
|
| 204 |
+
'OPENQASM 2.0;',
|
| 205 |
+
'include "qelib1.inc";',
|
| 206 |
+
f'qreg q[{n_qubits}];',
|
| 207 |
+
f'creg c[{n_qubits}];',
|
| 208 |
+
'',
|
| 209 |
+
]
|
| 210 |
+
|
| 211 |
+
for gate, args in operations:
|
| 212 |
+
if gate == 'h':
|
| 213 |
+
lines.append(f'h q[{args[0]}];')
|
| 214 |
+
elif gate == 'x':
|
| 215 |
+
lines.append(f'x q[{args[0]}];')
|
| 216 |
+
elif gate == 'y':
|
| 217 |
+
lines.append(f'y q[{args[0]}];')
|
| 218 |
+
elif gate == 'z':
|
| 219 |
+
lines.append(f'z q[{args[0]}];')
|
| 220 |
+
elif gate == 's':
|
| 221 |
+
lines.append(f's q[{args[0]}];')
|
| 222 |
+
elif gate == 't':
|
| 223 |
+
lines.append(f't q[{args[0]}];')
|
| 224 |
+
elif gate == 'cx':
|
| 225 |
+
lines.append(f'cx q[{args[0]}], q[{args[1]}];')
|
| 226 |
+
elif gate == 'cz':
|
| 227 |
+
lines.append(f'cz q[{args[0]}], q[{args[1]}];')
|
| 228 |
+
elif gate == 'swap':
|
| 229 |
+
lines.append(f'swap q[{args[0]}], q[{args[1]}];')
|
| 230 |
+
elif gate == 'ccx':
|
| 231 |
+
lines.append(f'ccx q[{args[0]}], q[{args[1]}], q[{args[2]}];')
|
| 232 |
+
elif gate == 'cp':
|
| 233 |
+
lines.append(f'cp({args[2]}) q[{args[0]}], q[{args[1]}];')
|
| 234 |
+
elif gate == 'measure':
|
| 235 |
+
lines.append(f'measure q[{args[0]}] -> c[{args[0]}];')
|
| 236 |
+
elif gate == 'measure_all':
|
| 237 |
+
for i in range(n_qubits):
|
| 238 |
+
lines.append(f'measure q[{i}] -> c[{i}];')
|
| 239 |
+
|
| 240 |
+
return '\n'.join(lines)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def operations_to_circuit(operations: list, n_qubits: int) -> Optional[QuantumCircuit]:
|
| 244 |
+
"""Convert operations to Qiskit circuit."""
|
| 245 |
+
if not QISKIT_AVAILABLE:
|
| 246 |
+
return None
|
| 247 |
+
|
| 248 |
+
try:
|
| 249 |
+
qc = QuantumCircuit(n_qubits, n_qubits)
|
| 250 |
+
|
| 251 |
+
for gate, args in operations:
|
| 252 |
+
if gate == 'h':
|
| 253 |
+
qc.h(args[0])
|
| 254 |
+
elif gate == 'x':
|
| 255 |
+
qc.x(args[0])
|
| 256 |
+
elif gate == 'y':
|
| 257 |
+
qc.y(args[0])
|
| 258 |
+
elif gate == 'z':
|
| 259 |
+
qc.z(args[0])
|
| 260 |
+
elif gate == 's':
|
| 261 |
+
qc.s(args[0])
|
| 262 |
+
elif gate == 't':
|
| 263 |
+
qc.t(args[0])
|
| 264 |
+
elif gate == 'cx':
|
| 265 |
+
qc.cx(args[0], args[1])
|
| 266 |
+
elif gate == 'cz':
|
| 267 |
+
qc.cz(args[0], args[1])
|
| 268 |
+
elif gate == 'swap':
|
| 269 |
+
qc.swap(args[0], args[1])
|
| 270 |
+
elif gate == 'ccx':
|
| 271 |
+
qc.ccx(args[0], args[1], args[2])
|
| 272 |
+
elif gate == 'cp':
|
| 273 |
+
qc.cp(args[2], args[0], args[1])
|
| 274 |
+
elif gate == 'measure':
|
| 275 |
+
qc.measure(args[0], args[0])
|
| 276 |
+
elif gate == 'measure_all':
|
| 277 |
+
qc.measure_all()
|
| 278 |
+
|
| 279 |
+
return qc
|
| 280 |
+
except Exception as e:
|
| 281 |
+
print(f"Circuit error: {e}")
|
| 282 |
+
return None
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def simulate(qc: QuantumCircuit, shots: int = 1024) -> dict:
|
| 286 |
+
"""Run simulation."""
|
| 287 |
+
if not QISKIT_AVAILABLE or qc is None:
|
| 288 |
+
return {}
|
| 289 |
+
|
| 290 |
+
try:
|
| 291 |
+
# Ensure measurements
|
| 292 |
+
if qc.num_clbits == 0 or not any(i.operation.name == 'measure' for i in qc.data):
|
| 293 |
+
qc = qc.copy()
|
| 294 |
+
qc.measure_all()
|
| 295 |
+
|
| 296 |
+
sim = AerSimulator()
|
| 297 |
+
compiled = transpile(qc, sim)
|
| 298 |
+
result = sim.run(compiled, shots=shots).result()
|
| 299 |
+
return result.get_counts()
|
| 300 |
+
except Exception as e:
|
| 301 |
+
print(f"Sim error: {e}")
|
| 302 |
+
return {}
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def format_results(counts: dict) -> str:
|
| 306 |
+
"""Format results as markdown."""
|
| 307 |
+
if not counts:
|
| 308 |
+
return ""
|
| 309 |
+
|
| 310 |
+
total = sum(counts.values())
|
| 311 |
+
lines = ["\n### Simulation Results (1024 shots)\n"]
|
| 312 |
+
|
| 313 |
+
for state, count in sorted(counts.items(), key=lambda x: -x[1])[:8]:
|
| 314 |
+
prob = count / total * 100
|
| 315 |
+
bar = "█" * int(prob / 4)
|
| 316 |
+
lines.append(f"`|{state}>` {prob:5.1f}% {bar}")
|
| 317 |
+
|
| 318 |
+
return '\n'.join(lines)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def draw_circuit(qc: QuantumCircuit) -> Optional[str]:
|
| 322 |
+
"""Draw circuit to temp file."""
|
| 323 |
+
if not QISKIT_AVAILABLE or qc is None:
|
| 324 |
+
return None
|
| 325 |
+
|
| 326 |
+
try:
|
| 327 |
+
import tempfile
|
| 328 |
+
fig = circuit_drawer(qc, output='mpl', style='iqp')
|
| 329 |
+
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
| 330 |
+
fig.savefig(tmp.name, dpi=150, bbox_inches='tight', facecolor='white')
|
| 331 |
+
plt.close(fig)
|
| 332 |
+
return tmp.name
|
| 333 |
+
except Exception as e:
|
| 334 |
+
print(f"Draw error: {e}")
|
| 335 |
+
return None
|
| 336 |
+
|
| 337 |
+
|
| 338 |
+
def generate(description: str, run_sim: bool = True) -> Tuple[str, Optional[str], str]:
|
| 339 |
+
"""Main: NL -> QASM + Circuit + Simulation."""
|
| 340 |
+
|
| 341 |
+
if not description.strip():
|
| 342 |
+
return "", None, "Please enter a circuit description"
|
| 343 |
+
|
| 344 |
+
# Parse
|
| 345 |
+
operations, n_qubits, status = nl_to_operations(description)
|
| 346 |
+
|
| 347 |
+
if not operations:
|
| 348 |
+
return "", None, "Could not parse any quantum operations. Try: 'Hadamard 0, CNOT 0 1, measure all'"
|
| 349 |
+
|
| 350 |
+
# Generate QASM
|
| 351 |
+
qasm = operations_to_qasm(operations, n_qubits)
|
| 352 |
+
|
| 353 |
+
# Build circuit
|
| 354 |
+
qc = operations_to_circuit(operations, n_qubits)
|
| 355 |
+
|
| 356 |
+
# Draw
|
| 357 |
+
img_path = draw_circuit(qc)
|
| 358 |
+
|
| 359 |
+
# Simulate
|
| 360 |
+
results_md = ""
|
| 361 |
+
if run_sim and qc:
|
| 362 |
+
counts = simulate(qc)
|
| 363 |
+
results_md = format_results(counts)
|
| 364 |
+
|
| 365 |
+
output = f"### OpenQASM 2.0\n```qasm\n{qasm}\n```\n{results_md}"
|
| 366 |
+
|
| 367 |
+
return output, img_path, status
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
# Build UI
|
| 371 |
+
with gr.Blocks(title=TITLE, theme=gr.themes.Soft()) as demo:
|
| 372 |
+
gr.Markdown(f"# {TITLE}")
|
| 373 |
+
gr.Markdown(DESCRIPTION)
|
| 374 |
+
|
| 375 |
+
with gr.Row():
|
| 376 |
+
with gr.Column(scale=2):
|
| 377 |
+
input_text = gr.Textbox(
|
| 378 |
+
label="Describe your quantum circuit",
|
| 379 |
+
placeholder="Example: Create a Bell state with 2 qubits",
|
| 380 |
+
lines=3
|
| 381 |
+
)
|
| 382 |
+
|
| 383 |
+
with gr.Row():
|
| 384 |
+
sim_check = gr.Checkbox(label="Run simulation", value=True)
|
| 385 |
+
btn = gr.Button("Generate Circuit", variant="primary")
|
| 386 |
+
|
| 387 |
+
gr.Examples(
|
| 388 |
+
examples=[
|
| 389 |
+
["Create a Bell state with 2 qubits"],
|
| 390 |
+
["3 qubit GHZ state"],
|
| 391 |
+
["Hadamard 0, CNOT 0 1, measure all"],
|
| 392 |
+
["Quantum teleportation circuit"],
|
| 393 |
+
["Put 4 qubits in superposition, then measure all"],
|
| 394 |
+
["Grover's algorithm for 2 qubits"],
|
| 395 |
+
["Apply X to qubit 0, H to qubit 1, then CZ 0 1"],
|
| 396 |
+
],
|
| 397 |
+
inputs=input_text
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
with gr.Column(scale=3):
|
| 401 |
+
status = gr.Textbox(label="Status", lines=1)
|
| 402 |
+
circuit_img = gr.Image(label="Circuit Diagram")
|
| 403 |
+
output = gr.Markdown(label="Output")
|
| 404 |
+
|
| 405 |
+
btn.click(generate, [input_text, sim_check], [output, circuit_img, status])
|
| 406 |
+
input_text.submit(generate, [input_text, sim_check], [output, circuit_img, status])
|
| 407 |
+
|
| 408 |
+
if __name__ == "__main__":
|
| 409 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0
|
| 2 |
+
numpy
|
| 3 |
+
matplotlib
|
| 4 |
+
qiskit>=1.0
|
| 5 |
+
qiskit-aer
|