hchevva commited on
Commit
1c17b67
·
verified ·
1 Parent(s): 99fd41d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +69 -50
app.py CHANGED
@@ -1,6 +1,8 @@
 
1
  import os
2
  import tempfile
3
  import pathlib
 
4
  import gradio as gr
5
  import numpy as np
6
 
@@ -8,6 +10,8 @@ from quread.engine import QuantumStateVector
8
  from quread.exporters import to_openqasm2, to_qiskit, to_cirq
9
  from quread.llm_explain_openai import explain_with_gpt4o
10
  from quread.circuit_diagram import draw_circuit_svg
 
 
11
 
12
 
13
  # ---------- Helpers ----------
@@ -40,6 +44,10 @@ def _write_tmp(filename: str, content: str) -> str:
40
  return str(p)
41
 
42
 
 
 
 
 
43
  def dl_qasm(qc, n_qubits):
44
  return _write_tmp("circuit.qasm", to_openqasm2(qc.history, n_qubits=int(n_qubits)))
45
 
@@ -94,25 +102,31 @@ def measure_collapse(qc, shots):
94
  return last_counts, f"✅ Collapsed to |{res}⟩ and sampled shots."
95
 
96
 
97
- def explain_llm(qc, n_qubits, shots, hf_model_id):
 
 
 
 
 
 
 
 
 
 
98
  state_ket = qc.ket_notation(max_terms=6)
99
  probs_top = _probs_top(qc, int(n_qubits), k=6)
100
 
101
- cfg = ExplainConfig(
102
- model_id=hf_model_id.strip(),
103
- max_new_tokens=280,
104
- temperature=0.2,
105
- )
106
-
107
- return explain_circuit_with_hf(
108
  n_qubits=int(n_qubits),
109
  history=qc.history,
110
  state_ket=state_ket,
111
  probs_top=probs_top,
112
  shots=int(shots),
113
- cfg=cfg,
114
  )
115
 
 
 
 
116
  def _refresh_choices(n):
117
  opts = list(range(int(n)))
118
  return (
@@ -122,7 +136,7 @@ def _refresh_choices(n):
122
  )
123
 
124
 
125
- # ---------- Streamlit-like styling ----------
126
  CSS = """
127
  #title h1 { font-size: 42px !important; margin-bottom: 6px; }
128
  #subtitle { color: #6b7280; margin-top: 0px; }
@@ -147,33 +161,31 @@ CSS = """
147
 
148
  theme = gr.themes.Soft(radius_size="lg", text_size="md")
149
 
 
150
  with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio") as demo:
151
  qc_state = gr.State()
152
  last_counts_state = gr.State()
153
  selected_gate_state = gr.State()
 
 
154
 
155
  with gr.Row():
156
- # ----- Sidebar -----
157
  with gr.Column(scale=3, elem_classes=["sidebar"]):
158
  gr.Markdown("### Simulator Settings")
159
  n_qubits = gr.Slider(1, 10, value=2, step=1, label="Number of qubits")
160
- shots = gr.Slider(128, 8192, value=1024, step=128, label="Shots (for sampling)")
161
 
162
  gr.Markdown("---")
163
- gr.Markdown("### LLM Explanation (Hugging Face)")
164
- hf_model_id = gr.Textbox(
165
- value="optimum/t5-small",
166
- label="HF model repo id",
167
- )
168
- gr.Markdown("<div class='small-note'>Set secret in HF Space: <b>HF_TOKEN</b></div>")
169
 
170
  reset_btn = gr.Button("Reset Simulator", variant="secondary")
171
 
172
- # ----- Main content -----
173
  with gr.Column(scale=9):
174
- gr.Markdown("<div id='title'><h1>Quread.ai — State Vector Studio (Dev Tool Edition)</h1></div>")
175
- gr.Markdown("<div id='subtitle'>Build circuits, inspect the statevector, visualize the circuit, export to QASM/Qiskit/Cirq, and get grounded explanations.</div>")
176
-
177
  status = gr.Markdown("")
178
 
179
  with gr.Group(elem_classes=["card"]):
@@ -186,7 +198,7 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
186
  gate_S = gr.Button("S")
187
  gate_T = gr.Button("T")
188
 
189
- target = gr.Dropdown(choices=[0, 1], value=0, label="Target qubit for single-qubit gates")
190
 
191
  with gr.Row():
192
  apply_gate_btn = gr.Button("Apply Selected Gate", variant="primary")
@@ -199,30 +211,23 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
199
  cnot_target = gr.Dropdown(choices=[0, 1], value=1, label="Target")
200
  apply_cnot_btn = gr.Button("Apply CNOT")
201
 
202
- # Circuit diagram card (missing before)
203
  with gr.Group(elem_classes=["card"]):
204
  gr.Markdown("<div class='section-title'>Circuit Diagram</div>")
205
  circuit_html = gr.HTML()
206
 
207
- # Outputs
208
  with gr.Row():
209
  with gr.Column(scale=7):
210
  with gr.Group(elem_classes=["card"]):
211
- gr.Markdown("<div class='section-title'>Statevector (human-readable)</div>")
212
  ket_out = gr.Code(label="", language="python")
213
  gr.Markdown("<div class='section-title'>Top probabilities</div>")
214
- probs_out = gr.Dataframe(headers=["bitstring", "prob"], label="", interactive=False)
215
 
216
  with gr.Column(scale=5):
217
  with gr.Group(elem_classes=["card"]):
218
  gr.Markdown("<div class='section-title'>Measurement distribution</div>")
219
- counts_out = gr.Textbox(
220
- label="",
221
- lines=10,
222
- placeholder="Click 'Sample shots' or 'Measure + Collapse' to generate counts.",
223
- )
224
 
225
- # Export as download buttons (instead of copy/paste boxes)
226
  with gr.Group(elem_classes=["card"]):
227
  gr.Markdown("<div class='section-title'>Export</div>")
228
  qasm_dl = gr.DownloadButton("Download OpenQASM 2.0")
@@ -230,30 +235,39 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
230
  cirq_dl = gr.DownloadButton("Download Cirq code")
231
 
232
  with gr.Group(elem_classes=["card"]):
233
- gr.Markdown("<div class='section-title'>Explain (HF Inference)</div>")
234
- explain_btn = gr.Button("Explain (LLM)", variant="primary")
235
- llm_out = gr.Markdown("")
236
- gr.Markdown("<div class='small-note'>Tip: Sample shots first for better explanations.</div>")
 
 
 
237
 
238
  # init
239
  def _init_all(n):
240
  qc, last_counts, selected_gate = _new_sim(n)
241
  return qc, last_counts, selected_gate
242
 
243
- demo.load(fn=_init_all, inputs=[n_qubits], outputs=[qc_state, last_counts_state, selected_gate_state]).then(
 
 
 
 
244
  fn=update_views,
245
  inputs=[qc_state, last_counts_state, n_qubits],
246
  outputs=[circuit_html, ket_out, counts_out, probs_out],
247
  )
248
 
249
- # keep dropdowns synced with n_qubits
250
- n_qubits.change(fn=_refresh_choices, inputs=[n_qubits], outputs=[target, control, cnot_target]).then(
 
 
 
251
  fn=update_views,
252
  inputs=[qc_state, last_counts_state, n_qubits],
253
  outputs=[circuit_html, ket_out, counts_out, probs_out],
254
  )
255
 
256
- # reset
257
  reset_btn.click(
258
  fn=init_or_reset,
259
  inputs=[n_qubits],
@@ -264,7 +278,6 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
264
  outputs=[circuit_html, ket_out, counts_out, probs_out],
265
  )
266
 
267
- # gate select
268
  gate_H.click(fn=lambda: set_gate("H"), outputs=[selected_gate_state, status])
269
  gate_X.click(fn=lambda: set_gate("X"), outputs=[selected_gate_state, status])
270
  gate_Y.click(fn=lambda: set_gate("Y"), outputs=[selected_gate_state, status])
@@ -272,7 +285,6 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
272
  gate_S.click(fn=lambda: set_gate("S"), outputs=[selected_gate_state, status])
273
  gate_T.click(fn=lambda: set_gate("T"), outputs=[selected_gate_state, status])
274
 
275
- # apply gate
276
  apply_gate_btn.click(
277
  fn=apply_selected_gate,
278
  inputs=[qc_state, last_counts_state, selected_gate_state, target],
@@ -283,7 +295,6 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
283
  outputs=[circuit_html, ket_out, counts_out, probs_out],
284
  )
285
 
286
- # cnot
287
  apply_cnot_btn.click(
288
  fn=apply_cnot,
289
  inputs=[qc_state, last_counts_state, control, cnot_target],
@@ -294,7 +305,6 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
294
  outputs=[circuit_html, ket_out, counts_out, probs_out],
295
  )
296
 
297
- # sample
298
  sample_btn.click(
299
  fn=sample_shots,
300
  inputs=[qc_state, shots],
@@ -305,7 +315,6 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
305
  outputs=[circuit_html, ket_out, counts_out, probs_out],
306
  )
307
 
308
- # measure
309
  measure_btn.click(
310
  fn=measure_collapse,
311
  inputs=[qc_state, shots],
@@ -316,17 +325,27 @@ with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio")
316
  outputs=[circuit_html, ket_out, counts_out, probs_out],
317
  )
318
 
319
- # export downloads
320
  qasm_dl.click(fn=dl_qasm, inputs=[qc_state, n_qubits], outputs=[qasm_dl])
321
  qiskit_dl.click(fn=dl_qiskit, inputs=[qc_state, n_qubits], outputs=[qiskit_dl])
322
  cirq_dl.click(fn=dl_cirq, inputs=[qc_state, n_qubits], outputs=[cirq_dl])
323
 
324
- # explain
325
  explain_btn.click(
326
  fn=explain_llm,
327
- inputs=[qc_state, n_qubits, shots, hf_model_id],
328
- outputs=[llm_out],
329
  )
330
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  if __name__ == "__main__":
332
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
1
+ # app.py
2
  import os
3
  import tempfile
4
  import pathlib
5
+ import hashlib
6
  import gradio as gr
7
  import numpy as np
8
 
 
10
  from quread.exporters import to_openqasm2, to_qiskit, to_cirq
11
  from quread.llm_explain_openai import explain_with_gpt4o
12
  from quread.circuit_diagram import draw_circuit_svg
13
+ from quread.cost_guard import allow_request
14
+ from quread.export_pdf import md_to_pdf
15
 
16
 
17
  # ---------- Helpers ----------
 
44
  return str(p)
45
 
46
 
47
+ def _circuit_hash(history):
48
+ return hashlib.sha256(str(history).encode()).hexdigest()
49
+
50
+
51
  def dl_qasm(qc, n_qubits):
52
  return _write_tmp("circuit.qasm", to_openqasm2(qc.history, n_qubits=int(n_qubits)))
53
 
 
102
  return last_counts, f"✅ Collapsed to |{res}⟩ and sampled shots."
103
 
104
 
105
+ def explain_llm(qc, n_qubits, shots, last_hash):
106
+ # circuit-change gating
107
+ curr_hash = _circuit_hash(qc.history)
108
+ if curr_hash == last_hash:
109
+ return "ℹ️ Circuit unchanged. Explanation reused.", last_hash, ""
110
+
111
+ # cost guard
112
+ EST_TOKENS = 900
113
+ if not allow_request(EST_TOKENS):
114
+ return "🚫 Explanation disabled (daily token limit reached).", last_hash, ""
115
+
116
  state_ket = qc.ket_notation(max_terms=6)
117
  probs_top = _probs_top(qc, int(n_qubits), k=6)
118
 
119
+ explanation = explain_with_gpt4o(
 
 
 
 
 
 
120
  n_qubits=int(n_qubits),
121
  history=qc.history,
122
  state_ket=state_ket,
123
  probs_top=probs_top,
124
  shots=int(shots),
 
125
  )
126
 
127
+ return explanation, curr_hash, explanation
128
+
129
+
130
  def _refresh_choices(n):
131
  opts = list(range(int(n)))
132
  return (
 
136
  )
137
 
138
 
139
+ # ---------- Styling ----------
140
  CSS = """
141
  #title h1 { font-size: 42px !important; margin-bottom: 6px; }
142
  #subtitle { color: #6b7280; margin-top: 0px; }
 
161
 
162
  theme = gr.themes.Soft(radius_size="lg", text_size="md")
163
 
164
+
165
  with gr.Blocks(theme=theme, css=CSS, title="Quread.ai — State Vector Studio") as demo:
166
  qc_state = gr.State()
167
  last_counts_state = gr.State()
168
  selected_gate_state = gr.State()
169
+ explanation_md = gr.State("")
170
+ last_explained_hash = gr.State("")
171
 
172
  with gr.Row():
173
+ # Sidebar
174
  with gr.Column(scale=3, elem_classes=["sidebar"]):
175
  gr.Markdown("### Simulator Settings")
176
  n_qubits = gr.Slider(1, 10, value=2, step=1, label="Number of qubits")
177
+ shots = gr.Slider(128, 8192, value=1024, step=128, label="Shots")
178
 
179
  gr.Markdown("---")
180
+ gr.Markdown("### Explanation (GPT-4o)")
181
+ gr.Markdown("<div class='small-note'>Uses GPT-4o with cost guards.</div>")
 
 
 
 
182
 
183
  reset_btn = gr.Button("Reset Simulator", variant="secondary")
184
 
185
+ # Main
186
  with gr.Column(scale=9):
187
+ gr.Markdown("<div id='title'><h1>Quread.ai — State Vector Studio</h1></div>")
188
+ gr.Markdown("<div id='subtitle'>Build circuits, visualize, export, and get teaching-ready explanations.</div>")
 
189
  status = gr.Markdown("")
190
 
191
  with gr.Group(elem_classes=["card"]):
 
198
  gate_S = gr.Button("S")
199
  gate_T = gr.Button("T")
200
 
201
+ target = gr.Dropdown(choices=[0, 1], value=0, label="Target qubit")
202
 
203
  with gr.Row():
204
  apply_gate_btn = gr.Button("Apply Selected Gate", variant="primary")
 
211
  cnot_target = gr.Dropdown(choices=[0, 1], value=1, label="Target")
212
  apply_cnot_btn = gr.Button("Apply CNOT")
213
 
 
214
  with gr.Group(elem_classes=["card"]):
215
  gr.Markdown("<div class='section-title'>Circuit Diagram</div>")
216
  circuit_html = gr.HTML()
217
 
 
218
  with gr.Row():
219
  with gr.Column(scale=7):
220
  with gr.Group(elem_classes=["card"]):
221
+ gr.Markdown("<div class='section-title'>Statevector</div>")
222
  ket_out = gr.Code(label="", language="python")
223
  gr.Markdown("<div class='section-title'>Top probabilities</div>")
224
+ probs_out = gr.Dataframe(headers=["bitstring", "prob"], interactive=False)
225
 
226
  with gr.Column(scale=5):
227
  with gr.Group(elem_classes=["card"]):
228
  gr.Markdown("<div class='section-title'>Measurement distribution</div>")
229
+ counts_out = gr.Textbox(lines=10)
 
 
 
 
230
 
 
231
  with gr.Group(elem_classes=["card"]):
232
  gr.Markdown("<div class='section-title'>Export</div>")
233
  qasm_dl = gr.DownloadButton("Download OpenQASM 2.0")
 
235
  cirq_dl = gr.DownloadButton("Download Cirq code")
236
 
237
  with gr.Group(elem_classes=["card"]):
238
+ gr.Markdown("<div class='section-title'>Explain (GPT-4o)</div>")
239
+ explain_btn = gr.Button("Explain", variant="primary")
240
+ llm_out = gr.Markdown()
241
+ with gr.Row():
242
+ dl_md = gr.DownloadButton("Download Explanation (Markdown)")
243
+ dl_pdf = gr.DownloadButton("Download Explanation (PDF)")
244
+ gr.Markdown("<div class='small-note'>Tip: sample shots first.</div>")
245
 
246
  # init
247
  def _init_all(n):
248
  qc, last_counts, selected_gate = _new_sim(n)
249
  return qc, last_counts, selected_gate
250
 
251
+ demo.load(
252
+ fn=_init_all,
253
+ inputs=[n_qubits],
254
+ outputs=[qc_state, last_counts_state, selected_gate_state],
255
+ ).then(
256
  fn=update_views,
257
  inputs=[qc_state, last_counts_state, n_qubits],
258
  outputs=[circuit_html, ket_out, counts_out, probs_out],
259
  )
260
 
261
+ n_qubits.change(
262
+ fn=_refresh_choices,
263
+ inputs=[n_qubits],
264
+ outputs=[target, control, cnot_target],
265
+ ).then(
266
  fn=update_views,
267
  inputs=[qc_state, last_counts_state, n_qubits],
268
  outputs=[circuit_html, ket_out, counts_out, probs_out],
269
  )
270
 
 
271
  reset_btn.click(
272
  fn=init_or_reset,
273
  inputs=[n_qubits],
 
278
  outputs=[circuit_html, ket_out, counts_out, probs_out],
279
  )
280
 
 
281
  gate_H.click(fn=lambda: set_gate("H"), outputs=[selected_gate_state, status])
282
  gate_X.click(fn=lambda: set_gate("X"), outputs=[selected_gate_state, status])
283
  gate_Y.click(fn=lambda: set_gate("Y"), outputs=[selected_gate_state, status])
 
285
  gate_S.click(fn=lambda: set_gate("S"), outputs=[selected_gate_state, status])
286
  gate_T.click(fn=lambda: set_gate("T"), outputs=[selected_gate_state, status])
287
 
 
288
  apply_gate_btn.click(
289
  fn=apply_selected_gate,
290
  inputs=[qc_state, last_counts_state, selected_gate_state, target],
 
295
  outputs=[circuit_html, ket_out, counts_out, probs_out],
296
  )
297
 
 
298
  apply_cnot_btn.click(
299
  fn=apply_cnot,
300
  inputs=[qc_state, last_counts_state, control, cnot_target],
 
305
  outputs=[circuit_html, ket_out, counts_out, probs_out],
306
  )
307
 
 
308
  sample_btn.click(
309
  fn=sample_shots,
310
  inputs=[qc_state, shots],
 
315
  outputs=[circuit_html, ket_out, counts_out, probs_out],
316
  )
317
 
 
318
  measure_btn.click(
319
  fn=measure_collapse,
320
  inputs=[qc_state, shots],
 
325
  outputs=[circuit_html, ket_out, counts_out, probs_out],
326
  )
327
 
 
328
  qasm_dl.click(fn=dl_qasm, inputs=[qc_state, n_qubits], outputs=[qasm_dl])
329
  qiskit_dl.click(fn=dl_qiskit, inputs=[qc_state, n_qubits], outputs=[qiskit_dl])
330
  cirq_dl.click(fn=dl_cirq, inputs=[qc_state, n_qubits], outputs=[cirq_dl])
331
 
 
332
  explain_btn.click(
333
  fn=explain_llm,
334
+ inputs=[qc_state, n_qubits, shots, last_explained_hash],
335
+ outputs=[llm_out, last_explained_hash, explanation_md],
336
  )
337
 
338
+ def dl_explain_md(md_text):
339
+ return _write_tmp("explanation.md", md_text)
340
+
341
+ def dl_explain_pdf(md_text):
342
+ path = pathlib.Path(tempfile.gettempdir()) / "explanation.pdf"
343
+ md_to_pdf(md_text, str(path))
344
+ return str(path)
345
+
346
+ dl_md.click(fn=dl_explain_md, inputs=[explanation_md], outputs=[dl_md])
347
+ dl_pdf.click(fn=dl_explain_pdf, inputs=[explanation_md], outputs=[dl_pdf])
348
+
349
+
350
  if __name__ == "__main__":
351
  demo.launch(server_name="0.0.0.0", server_port=7860)