raayraay commited on
Commit
547a4a8
·
verified ·
1 Parent(s): 7d5a965

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. app.py +409 -0
  2. 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