hchevva commited on
Commit
d473aef
·
verified ·
1 Parent(s): d5182a9

Upload 10 files

Browse files
tests/__pycache__/test_app_flows.cpython-314.pyc ADDED
Binary file (14.5 kB). View file
 
tests/__pycache__/test_exporters.cpython-314.pyc ADDED
Binary file (2.43 kB). View file
 
tests/__pycache__/test_heatmap.cpython-314.pyc ADDED
Binary file (1.89 kB). View file
 
tests/__pycache__/test_metrics.cpython-314.pyc ADDED
Binary file (5.67 kB). View file
 
tests/__pycache__/test_noise_model.cpython-314.pyc ADDED
Binary file (2.01 kB). View file
 
tests/test_app_flows.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pathlib
3
+ import sys
4
+ import types
5
+ import unittest
6
+ from unittest.mock import patch
7
+
8
+ import numpy as np
9
+
10
+
11
+ def _install_gradio_stub() -> None:
12
+ if "gradio" in sys.modules:
13
+ return
14
+
15
+ module = types.ModuleType("gradio")
16
+
17
+ class _Event:
18
+ def then(self, *args, **kwargs):
19
+ return self
20
+
21
+ class _Component:
22
+ def __init__(self, *args, **kwargs):
23
+ self.args = args
24
+ self.kwargs = kwargs
25
+
26
+ def click(self, *args, **kwargs):
27
+ return _Event()
28
+
29
+ def change(self, *args, **kwargs):
30
+ return _Event()
31
+
32
+ class _Context:
33
+ def __init__(self, *args, **kwargs):
34
+ self.args = args
35
+ self.kwargs = kwargs
36
+
37
+ def __enter__(self):
38
+ return self
39
+
40
+ def __exit__(self, exc_type, exc, tb):
41
+ return False
42
+
43
+ class _Blocks(_Context):
44
+ def load(self, *args, **kwargs):
45
+ return _Event()
46
+
47
+ def launch(self, *args, **kwargs):
48
+ return None
49
+
50
+ class _Themes:
51
+ class Soft:
52
+ def __init__(self, *args, **kwargs):
53
+ self.args = args
54
+ self.kwargs = kwargs
55
+
56
+ module.themes = _Themes()
57
+ module.Blocks = _Blocks
58
+ module.Row = _Context
59
+ module.Column = _Context
60
+ module.Group = _Context
61
+ module.Tabs = _Context
62
+ module.Tab = _Context
63
+ module.Accordion = _Context
64
+
65
+ module.State = _Component
66
+ module.Markdown = _Component
67
+ module.Slider = _Component
68
+ module.Button = _Component
69
+ module.Dropdown = _Component
70
+ module.Checkbox = _Component
71
+ module.HTML = _Component
72
+ module.Code = _Component
73
+ module.Dataframe = _Component
74
+ module.Textbox = _Component
75
+ module.DownloadButton = _Component
76
+ module.Plot = _Component
77
+
78
+ sys.modules["gradio"] = module
79
+
80
+
81
+ def _install_openai_stub() -> None:
82
+ if "openai" in sys.modules:
83
+ return
84
+
85
+ module = types.ModuleType("openai")
86
+
87
+ class _Completions:
88
+ def create(self, *args, **kwargs):
89
+ raise RuntimeError("OpenAI call is stubbed in tests.")
90
+
91
+ class _Chat:
92
+ def __init__(self):
93
+ self.completions = _Completions()
94
+
95
+ class OpenAI:
96
+ def __init__(self, *args, **kwargs):
97
+ self.chat = _Chat()
98
+
99
+ module.OpenAI = OpenAI
100
+ sys.modules["openai"] = module
101
+
102
+
103
+ def _install_export_pdf_stub() -> None:
104
+ if "quread.export_pdf" in sys.modules:
105
+ return
106
+
107
+ module = types.ModuleType("quread.export_pdf")
108
+
109
+ def md_to_pdf(markdown_text: str, output_path: str):
110
+ pathlib.Path(output_path).write_text(markdown_text or "", encoding="utf-8")
111
+
112
+ module.md_to_pdf = md_to_pdf
113
+ sys.modules["quread.export_pdf"] = module
114
+
115
+
116
+ _install_gradio_stub()
117
+ _install_openai_stub()
118
+ _install_export_pdf_stub()
119
+
120
+ import app
121
+ from quread.engine import QuantumStateVector
122
+
123
+
124
+ class AppFlowsTest(unittest.TestCase):
125
+ def test_qubit_count_change_reinitializes_simulator(self):
126
+ qc, last_counts, selected_gate, _target, _control, _cnot_target, status = app._on_qubit_count_change(3)
127
+
128
+ self.assertEqual(qc.n_qubits, 3)
129
+ self.assertEqual(qc.history, [])
130
+ self.assertIsNone(last_counts)
131
+ self.assertEqual(selected_gate, "H")
132
+ self.assertIn("Reinitialized simulator with 3 qubits", status)
133
+
134
+ def test_write_tmp_generates_unique_paths(self):
135
+ p1 = app._write_tmp("circuit.qasm", "OPENQASM 2.0;")
136
+ p2 = app._write_tmp("circuit.qasm", "OPENQASM 2.0;")
137
+ try:
138
+ self.assertNotEqual(p1, p2)
139
+ self.assertTrue(pathlib.Path(p1).exists())
140
+ self.assertTrue(pathlib.Path(p2).exists())
141
+ self.assertEqual(pathlib.Path(p1).read_text(encoding="utf-8"), "OPENQASM 2.0;")
142
+ self.assertEqual(pathlib.Path(p2).read_text(encoding="utf-8"), "OPENQASM 2.0;")
143
+ finally:
144
+ for path in (p1, p2):
145
+ try:
146
+ os.remove(path)
147
+ except FileNotFoundError:
148
+ pass
149
+
150
+ def test_explain_reuse_preserves_previous_markdown(self):
151
+ qc = QuantumStateVector(2)
152
+ last_hash = app._circuit_hash(qc.history)
153
+
154
+ shown, returned_hash, stored_md = app.explain_llm(
155
+ qc=qc,
156
+ n_qubits=2,
157
+ shots=1024,
158
+ last_hash=last_hash,
159
+ previous_explanation="previous explanation",
160
+ )
161
+
162
+ self.assertEqual(returned_hash, last_hash)
163
+ self.assertEqual(stored_md, "previous explanation")
164
+ self.assertIn("Reusing previous explanation", shown)
165
+
166
+ def test_explain_failure_preserves_previous_markdown(self):
167
+ qc = QuantumStateVector(2)
168
+ qc.apply_single("H", target=0)
169
+
170
+ with patch.object(app, "explain_with_gpt4o", side_effect=RuntimeError("boom")):
171
+ shown, returned_hash, stored_md = app.explain_llm(
172
+ qc=qc,
173
+ n_qubits=2,
174
+ shots=1024,
175
+ last_hash="",
176
+ previous_explanation="previous explanation",
177
+ )
178
+
179
+ self.assertEqual(returned_hash, "")
180
+ self.assertEqual(stored_md, "previous explanation")
181
+ self.assertIn("Explanation request failed", shown)
182
+ self.assertIn("Showing previous explanation", shown)
183
+
184
+ def test_hotspot_rows_sorted_descending(self):
185
+ metrics = {
186
+ "composite_risk": np.array([0.22, 0.91, 0.45], dtype=float),
187
+ "hotspot_level": np.array([0, 2, 1], dtype=float),
188
+ "activity_count": np.array([1.0, 4.0, 2.0], dtype=float),
189
+ "gate_error": np.array([0.01, 0.04, 0.02], dtype=float),
190
+ "readout_error": np.array([0.02, 0.05, 0.03], dtype=float),
191
+ "state_fidelity": np.array([0.98, 0.82, 0.91], dtype=float),
192
+ "process_fidelity": np.array([0.97, 0.79, 0.9], dtype=float),
193
+ "coherence_health": np.array([0.8, 0.5, 0.7], dtype=float),
194
+ "decoherence_risk": np.array([0.2, 0.6, 0.3], dtype=float),
195
+ "fidelity": np.array([0.99, 0.95, 0.97], dtype=float),
196
+ }
197
+ rows = app._hotspot_rows(metrics, n_qubits=3, top_k=2)
198
+ self.assertEqual(len(rows), 2)
199
+ self.assertEqual(rows[0][0], 1)
200
+ self.assertEqual(rows[0][1], "critical")
201
+ self.assertGreaterEqual(rows[0][2], rows[1][2])
202
+
203
+ def test_ideal_vs_noisy_plot_returns_figure(self):
204
+ qc = QuantumStateVector(2)
205
+ qc.apply_single("H", target=0)
206
+ fig = app._ideal_vs_noisy_plot(
207
+ qc=qc,
208
+ shots=64,
209
+ calibration_text='{"qubits":{"0":{"readout_error":0.1},"1":{"readout_error":0.1}}}',
210
+ readout_scale=1.0,
211
+ depolarizing_prob=0.1,
212
+ )
213
+ self.assertTrue(hasattr(fig, "axes"))
214
+ self.assertGreaterEqual(len(fig.axes), 1)
215
+
216
+
217
+ if __name__ == "__main__":
218
+ unittest.main()
tests/test_exporters.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ from quread.engine import QuantumStateVector
4
+ from quread.exporters import to_cirq, to_openqasm2, to_qiskit
5
+
6
+
7
+ class ExportersTest(unittest.TestCase):
8
+ def test_all_palette_gates_export_cleanly(self):
9
+ qc = QuantumStateVector(2)
10
+ gates = ["H", "T†", "S†", "√X", "√Z", "RX(π)", "RY(π/2)", "RZ(π/2)", "I†", "X", "Y", "Z", "S", "T"]
11
+ for gate in gates:
12
+ qc.apply_single(gate, target=0)
13
+ qc.apply_cnot(0, 1)
14
+
15
+ qasm = to_openqasm2(qc.history, 2)
16
+ qiskit_src = to_qiskit(qc.history, 2)
17
+ cirq_src = to_cirq(qc.history, 2)
18
+
19
+ self.assertIn("tdg q[0];", qasm)
20
+ self.assertIn("sdg q[0];", qasm)
21
+ self.assertIn("rx(1.5707963267949) q[0];", qasm)
22
+ self.assertNotIn("π", qasm)
23
+
24
+ self.assertIn("qc.tdg(0)", qiskit_src)
25
+ self.assertIn("qc.sdg(0)", qiskit_src)
26
+ self.assertIn("qc.sx(0)", qiskit_src)
27
+ self.assertIn("qc.rx(3.14159265358979, 0)", qiskit_src)
28
+ self.assertNotIn("qc.rx(π)", qiskit_src)
29
+
30
+ self.assertIn("(cirq.T**-1).on(q[0])", cirq_src)
31
+ self.assertIn("(cirq.S**-1).on(q[0])", cirq_src)
32
+ self.assertIn("(cirq.X**0.5).on(q[0])", cirq_src)
33
+
34
+ compile(qiskit_src, "<qiskit_export>", "exec")
35
+ compile(cirq_src, "<cirq_export>", "exec")
36
+
37
+
38
+ if __name__ == "__main__":
39
+ unittest.main()
tests/test_heatmap.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ import matplotlib
4
+
5
+ matplotlib.use("Agg")
6
+
7
+ from quread.heatmap import HeatmapConfig, make_activity_heatmap
8
+
9
+
10
+ class HeatmapTest(unittest.TestCase):
11
+ def test_malformed_rows_are_skipped_without_crashing(self):
12
+ csv_text = "\n".join(
13
+ [
14
+ "step,gate,target,control,theta",
15
+ "0,H,0,,",
16
+ "1,CNOT,1,0,",
17
+ "2,H,not_an_int,,",
18
+ "3,,1,,",
19
+ ]
20
+ )
21
+
22
+ fig = make_activity_heatmap(csv_text, n_qubits=2, cfg=HeatmapConfig(rows=2, cols=2))
23
+ ax = fig.axes[0]
24
+ grid = ax.images[0].get_array()
25
+ labels = [t.get_text() for t in ax.texts]
26
+
27
+ self.assertEqual(float(grid[0, 0]), 2.0) # q0: H + CNOT(control)
28
+ self.assertEqual(float(grid[0, 1]), 1.0) # q1: CNOT(target)
29
+ self.assertIn("Skipped 2 malformed CSV row(s)", labels)
30
+
31
+
32
+ if __name__ == "__main__":
33
+ unittest.main()
tests/test_metrics.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ from quread.metrics import (
4
+ compute_metrics_from_csv,
5
+ to_metrics_csv,
6
+ MetricWeights,
7
+ MetricThresholds,
8
+ clamp_thresholds,
9
+ )
10
+
11
+
12
+ class MetricsPipelineTest(unittest.TestCase):
13
+ def test_compute_metrics_and_export_csv(self):
14
+ csv_text = "\n".join(
15
+ [
16
+ "step,gate,target,control,theta",
17
+ "0,H,0,,",
18
+ "1,CNOT,1,0,",
19
+ "2,X,1,,",
20
+ ]
21
+ )
22
+ calibration = (
23
+ '{"qubits":{"0":{"gate_error":0.01,"readout_error":0.02,"t1_us":90,"t2_us":70,"fidelity":0.992},'
24
+ '"1":{"gate_error":0.03,"readout_error":0.04,"t1_us":55,"t2_us":45,"fidelity":0.981}}}'
25
+ )
26
+
27
+ metrics, meta = compute_metrics_from_csv(csv_text, 2, calibration_json=calibration)
28
+
29
+ self.assertEqual(meta["skipped_rows"], 0)
30
+ self.assertIn("fidelity_backend", meta)
31
+ self.assertEqual(metrics["activity_count"].shape[0], 2)
32
+ self.assertGreaterEqual(float(metrics["activity_norm"][0]), 0.0)
33
+ self.assertLessEqual(float(metrics["activity_norm"][0]), 1.0)
34
+ self.assertGreaterEqual(float(metrics["composite_risk"][1]), 0.0)
35
+ self.assertLessEqual(float(metrics["composite_risk"][1]), 1.0)
36
+ self.assertGreaterEqual(float(metrics["state_fidelity"][0]), 0.0)
37
+ self.assertLessEqual(float(metrics["state_fidelity"][0]), 1.0)
38
+ self.assertGreaterEqual(float(metrics["process_fidelity"][0]), 0.0)
39
+ self.assertLessEqual(float(metrics["process_fidelity"][0]), 1.0)
40
+
41
+ csv_out = to_metrics_csv(metrics)
42
+ self.assertIn("qubit,activity_count,activity_norm,gate_error", csv_out)
43
+ self.assertIn("coherence_health", csv_out.splitlines()[0])
44
+ self.assertIn("state_fidelity", csv_out.splitlines()[0])
45
+ self.assertIn("process_fidelity", csv_out.splitlines()[0])
46
+ self.assertIn("\n0,", csv_out)
47
+ self.assertIn("\n1,", csv_out)
48
+
49
+ def test_invalid_calibration_json_falls_back_to_defaults(self):
50
+ csv_text = "step,gate,target,control,theta\n0,H,0,,\n"
51
+ metrics, meta = compute_metrics_from_csv(csv_text, 1, calibration_json="{bad json")
52
+
53
+ self.assertIn("using defaults", str(meta.get("calibration_note", "")).lower())
54
+ self.assertEqual(metrics["activity_count"].shape[0], 1)
55
+
56
+ def test_threshold_clamping_orders_warning_and_critical(self):
57
+ clamped = clamp_thresholds(MetricThresholds(warning=0.8, critical=0.5))
58
+ self.assertEqual(clamped.warning, 0.8)
59
+ self.assertEqual(clamped.critical, 0.8)
60
+
61
+ def test_custom_weights_affect_composite_risk(self):
62
+ csv_text = "\n".join(
63
+ [
64
+ "step,gate,target,control,theta",
65
+ "0,H,0,,",
66
+ "1,H,0,,",
67
+ "2,H,0,,",
68
+ ]
69
+ )
70
+ calibration = (
71
+ '{"qubits":{"0":{"gate_error":0.01,"readout_error":0.01,"t1_us":120,"t2_us":100,"fidelity":0.995},'
72
+ '"1":{"gate_error":0.02,"readout_error":0.02,"t1_us":120,"t2_us":100,"fidelity":0.995}}}'
73
+ )
74
+ # Heavy activity weighting should make q0 risk noticeably higher.
75
+ metrics, _ = compute_metrics_from_csv(
76
+ csv_text,
77
+ 2,
78
+ calibration_json=calibration,
79
+ weights=MetricWeights(activity=0.9, gate_error=0.025, readout_error=0.025, decoherence=0.025, fidelity=0.025),
80
+ thresholds=MetricThresholds(warning=0.2, critical=0.6),
81
+ )
82
+ self.assertGreater(float(metrics["composite_risk"][0]), float(metrics["composite_risk"][1]))
83
+ self.assertEqual(int(metrics["hotspot_level"][0]), 2)
84
+
85
+
86
+ if __name__ == "__main__":
87
+ unittest.main()
tests/test_noise_model.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+
3
+ import numpy as np
4
+
5
+ from quread.noise_model import sample_noisy_counts
6
+
7
+
8
+ class NoiseModelTest(unittest.TestCase):
9
+ def test_readout_flip_probability_one_flips_all_bits(self):
10
+ state = np.zeros((4,), dtype=complex)
11
+ state[0] = 1.0 + 0j # |00>
12
+ calibration = '{"qubits":{"0":{"readout_error":1.0},"1":{"readout_error":1.0}}}'
13
+
14
+ counts = sample_noisy_counts(
15
+ state,
16
+ n_qubits=2,
17
+ shots=32,
18
+ calibration_json=calibration,
19
+ readout_scale=1.0,
20
+ depolarizing_prob=0.0,
21
+ seed=7,
22
+ )
23
+ self.assertEqual(counts, {"11": 32})
24
+
25
+ def test_depolarizing_only_creates_multiple_outcomes(self):
26
+ state = np.zeros((4,), dtype=complex)
27
+ state[0] = 1.0 + 0j # |00>
28
+
29
+ counts = sample_noisy_counts(
30
+ state,
31
+ n_qubits=2,
32
+ shots=128,
33
+ calibration_json="",
34
+ readout_scale=0.0,
35
+ depolarizing_prob=0.4,
36
+ seed=123,
37
+ )
38
+ self.assertGreater(len(counts), 1)
39
+ self.assertEqual(sum(counts.values()), 128)
40
+
41
+
42
+ if __name__ == "__main__":
43
+ unittest.main()