microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/add-link-to-qsharp-application

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/pip/tests/qre/test_estimation_table.py

370lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4import pytest
5
6cirq = pytest.importorskip("cirq")
7
8import pandas as pd
9
10from qsharp.qre import (
11 PSSPC,
12 LatticeSurgery,
13 estimate,
14)
15from qsharp.qre.application import QSharpApplication
16from qsharp.qre.models import SurfaceCode, GateBased
17from qsharp.qre._estimation import (
18 EstimationTable,
19 EstimationTableEntry,
20)
21from qsharp.qre._instruction import InstructionSource
22from qsharp.qre.instruction_ids import LATTICE_SURGERY
23from qsharp.qre.property_keys import DISTANCE, NUM_TS_PER_ROTATION
24
25from .conftest import ExampleFactory
26
27
28def _make_entry(qubits, runtime, error, properties=None):
29 """Helper to create an EstimationTableEntry with a dummy InstructionSource."""
30 return EstimationTableEntry(
31 qubits=qubits,
32 runtime=runtime,
33 error=error,
34 source=InstructionSource(),
35 properties=properties or {},
36 )
37
38
39def test_estimation_table_default_columns():
40 """Test that a new EstimationTable has the three default columns."""
41 table = EstimationTable()
42 table.append(_make_entry(100, 5000, 0.01))
43
44 frame = table.as_frame()
45 assert list(frame.columns) == ["qubits", "runtime", "error"]
46 assert frame["qubits"][0] == 100
47 assert frame["runtime"][0] == pd.Timedelta(5000, unit="ns")
48 assert frame["error"][0] == 0.01
49
50
51def test_estimation_table_multiple_rows():
52 """Test as_frame with multiple entries."""
53 table = EstimationTable()
54 table.append(_make_entry(100, 5000, 0.01))
55 table.append(_make_entry(200, 10000, 0.02))
56 table.append(_make_entry(300, 15000, 0.03))
57
58 frame = table.as_frame()
59 assert len(frame) == 3
60 assert list(frame["qubits"]) == [100, 200, 300]
61 assert list(frame["error"]) == [0.01, 0.02, 0.03]
62
63
64def test_estimation_table_empty():
65 """Test as_frame with no entries produces an empty DataFrame."""
66 table = EstimationTable()
67 frame = table.as_frame()
68 assert len(frame) == 0
69
70
71def test_estimation_table_add_column():
72 """Test adding a column to the table."""
73 VAL = 0
74
75 table = EstimationTable()
76 table.append(_make_entry(100, 5000, 0.01, properties={VAL: 42}))
77 table.append(_make_entry(200, 10000, 0.02, properties={VAL: 84}))
78
79 table.add_column("val", lambda e: e.properties[VAL])
80
81 frame = table.as_frame()
82 assert list(frame.columns) == ["qubits", "runtime", "error", "val"]
83 assert list(frame["val"]) == [42, 84]
84
85
86def test_estimation_table_add_column_with_formatter():
87 """Test adding a column with a formatter."""
88 NS = 0
89
90 table = EstimationTable()
91 table.append(_make_entry(100, 5000, 0.01, properties={NS: 1000}))
92
93 table.add_column(
94 "duration",
95 lambda e: e.properties[NS],
96 formatter=lambda x: pd.Timedelta(x, unit="ns"),
97 )
98
99 frame = table.as_frame()
100 assert frame["duration"][0] == pd.Timedelta(1000, unit="ns")
101
102
103def test_estimation_table_add_multiple_columns():
104 """Test adding multiple columns preserves order."""
105 A = 0
106 B = 1
107 C = 2
108
109 table = EstimationTable()
110 table.append(_make_entry(100, 5000, 0.01, properties={A: 1, B: 2, C: 3}))
111
112 table.add_column("a", lambda e: e.properties[A])
113 table.add_column("b", lambda e: e.properties[B])
114 table.add_column("c", lambda e: e.properties[C])
115
116 frame = table.as_frame()
117 assert list(frame.columns) == ["qubits", "runtime", "error", "a", "b", "c"]
118 assert frame["a"][0] == 1
119 assert frame["b"][0] == 2
120 assert frame["c"][0] == 3
121
122
123def test_estimation_table_insert_column_at_beginning():
124 """Test inserting a column at index 0."""
125 NAME = 0
126
127 table = EstimationTable()
128 table.append(_make_entry(100, 5000, 0.01, properties={NAME: "test"}))
129
130 table.insert_column(0, "name", lambda e: e.properties[NAME])
131
132 frame = table.as_frame()
133 assert list(frame.columns) == ["name", "qubits", "runtime", "error"]
134 assert frame["name"][0] == "test"
135
136
137def test_estimation_table_insert_column_in_middle():
138 """Test inserting a column between existing default columns."""
139 EXTRA = 0
140
141 table = EstimationTable()
142 table.append(_make_entry(100, 5000, 0.01, properties={EXTRA: 99}))
143
144 # Insert between qubits and runtime (index 1)
145 table.insert_column(1, "extra", lambda e: e.properties[EXTRA])
146
147 frame = table.as_frame()
148 assert list(frame.columns) == ["qubits", "extra", "runtime", "error"]
149 assert frame["extra"][0] == 99
150
151
152def test_estimation_table_insert_column_at_end():
153 """Test inserting a column at the end (same effect as add_column)."""
154 LAST = 0
155
156 table = EstimationTable()
157 table.append(_make_entry(100, 5000, 0.01, properties={LAST: True}))
158
159 # 3 default columns, inserting at index 3 = end
160 table.insert_column(3, "last", lambda e: e.properties[LAST])
161
162 frame = table.as_frame()
163 assert list(frame.columns) == ["qubits", "runtime", "error", "last"]
164 assert frame["last"][0]
165
166
167def test_estimation_table_insert_column_with_formatter():
168 """Test inserting a column with a formatter."""
169 NS = 0
170
171 table = EstimationTable()
172 table.append(_make_entry(100, 5000, 0.01, properties={NS: 2000}))
173
174 table.insert_column(
175 0,
176 "custom_time",
177 lambda e: e.properties[NS],
178 formatter=lambda x: pd.Timedelta(x, unit="ns"),
179 )
180
181 frame = table.as_frame()
182 assert frame["custom_time"][0] == pd.Timedelta(2000, unit="ns")
183 assert list(frame.columns)[0] == "custom_time"
184
185
186def test_estimation_table_insert_and_add_columns():
187 """Test combining insert_column and add_column."""
188 A = 0
189 B = 0
190
191 table = EstimationTable()
192 table.append(_make_entry(100, 5000, 0.01, properties={A: 1, B: 2}))
193
194 table.add_column("b", lambda e: e.properties[B])
195 table.insert_column(0, "a", lambda e: e.properties[A])
196
197 frame = table.as_frame()
198 assert list(frame.columns) == ["a", "qubits", "runtime", "error", "b"]
199
200
201def test_estimation_table_factory_summary_no_factories():
202 """Test factory summary column when entries have no factories."""
203 table = EstimationTable()
204 table.append(_make_entry(100, 5000, 0.01))
205
206 table.add_factory_summary_column()
207
208 frame = table.as_frame()
209 assert "factories" in frame.columns
210 assert frame["factories"][0] == "None"
211
212
213def test_estimation_table_factory_summary_with_estimation():
214 """Test factory summary column with real estimation results."""
215 code = """
216 {
217 use (a, b, c) = (Qubit(), Qubit(), Qubit());
218 T(a);
219 CCNOT(a, b, c);
220 Rz(1.2345, a);
221 }
222 """
223 app = QSharpApplication(code)
224 arch = GateBased(gate_time=50, measurement_time=100)
225 results = estimate(
226 app,
227 arch,
228 SurfaceCode.q() * ExampleFactory.q(),
229 PSSPC.q() * LatticeSurgery.q(),
230 max_error=0.5,
231 )
232
233 assert len(results) >= 1
234
235 results.add_factory_summary_column()
236 frame = results.as_frame()
237
238 assert "factories" in frame.columns
239 # Each result should mention T in the factory summary
240 for val in frame["factories"]:
241 assert "T" in val
242
243
244def test_estimation_table_add_column_from_source():
245 """Test adding a column that accesses the InstructionSource (like distance)."""
246 code = """
247 {
248 use (a, b, c) = (Qubit(), Qubit(), Qubit());
249 T(a);
250 CCNOT(a, b, c);
251 Rz(1.2345, a);
252 }
253 """
254 app = QSharpApplication(code)
255 arch = GateBased(gate_time=50, measurement_time=100)
256 results = estimate(
257 app,
258 arch,
259 SurfaceCode.q() * ExampleFactory.q(),
260 PSSPC.q() * LatticeSurgery.q(),
261 max_error=0.5,
262 )
263
264 assert len(results) >= 1
265
266 results.add_column(
267 "compute_distance",
268 lambda entry: entry.source[LATTICE_SURGERY].instruction[DISTANCE],
269 )
270
271 frame = results.as_frame()
272 assert "compute_distance" in frame.columns
273 for d in frame["compute_distance"]:
274 assert isinstance(d, int)
275 assert d >= 3
276
277
278def test_estimation_table_add_column_from_properties():
279 """Test adding columns that access trace properties from estimation."""
280 code = """
281 {
282 use (a, b, c) = (Qubit(), Qubit(), Qubit());
283 T(a);
284 CCNOT(a, b, c);
285 Rz(1.2345, a);
286 }
287 """
288 app = QSharpApplication(code)
289 arch = GateBased(gate_time=50, measurement_time=100)
290 results = estimate(
291 app,
292 arch,
293 SurfaceCode.q() * ExampleFactory.q(),
294 PSSPC.q() * LatticeSurgery.q(),
295 max_error=0.5,
296 )
297
298 assert len(results) >= 1
299
300 results.add_column(
301 "num_ts_per_rotation",
302 lambda entry: entry.properties[NUM_TS_PER_ROTATION],
303 )
304
305 frame = results.as_frame()
306 assert "num_ts_per_rotation" in frame.columns
307 for val in frame["num_ts_per_rotation"]:
308 assert isinstance(val, int)
309 assert val >= 1
310
311
312def test_estimation_table_insert_column_before_defaults():
313 """Test inserting a name column before all default columns, similar to the factoring notebook."""
314 code = """
315 {
316 use (a, b, c) = (Qubit(), Qubit(), Qubit());
317 T(a);
318 CCNOT(a, b, c);
319 Rz(1.2345, a);
320 }
321 """
322 app = QSharpApplication(code)
323 arch = GateBased(gate_time=50, measurement_time=100)
324 results = estimate(
325 app,
326 arch,
327 SurfaceCode.q() * ExampleFactory.q(),
328 PSSPC.q() * LatticeSurgery.q(),
329 max_error=0.5,
330 name="test_experiment",
331 )
332
333 assert len(results) >= 1
334
335 # Add a factory summary at the end
336 results.add_factory_summary_column()
337
338 frame = results.as_frame()
339 assert frame.columns[0] == "name"
340 assert frame.columns[-1] == "factories"
341 # Default columns should still be in order
342 assert list(frame.columns[1:4]) == ["qubits", "runtime", "error"]
343
344
345def test_estimation_table_as_frame_sortable():
346 """Test that the DataFrame from as_frame can be sorted, as done in the factoring tests."""
347 table = EstimationTable()
348 table.append(_make_entry(300, 15000, 0.03))
349 table.append(_make_entry(100, 5000, 0.01))
350 table.append(_make_entry(200, 10000, 0.02))
351
352 frame = table.as_frame()
353 sorted_frame = frame.sort_values(by=["qubits", "runtime"]).reset_index(drop=True)
354
355 assert list(sorted_frame["qubits"]) == [100, 200, 300]
356 assert list(sorted_frame["error"]) == [0.01, 0.02, 0.03]
357
358
359def test_estimation_table_computed_column():
360 """Test adding a column that computes a derived value from the entry."""
361 table = EstimationTable()
362 table.append(_make_entry(100, 5_000_000, 0.01))
363 table.append(_make_entry(200, 10_000_000, 0.02))
364
365 # Compute qubits * error as a derived metric
366 table.add_column("qubit_error_product", lambda e: e.qubits * e.error)
367
368 frame = table.as_frame()
369 assert frame["qubit_error_product"][0] == pytest.approx(1.0)
370 assert frame["qubit_error_product"][1] == pytest.approx(4.0)
371