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/test_adaptive_cpu_quantum_ops.py

442lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4"""End-to-end tests for the adaptive CPU bytecode interpreter pipeline.
5
6Tests run Adaptive Profile QIR through the full pipeline:
7Python AdaptiveProfilePass → Rust receiver → CPU interpreter → results.
8
9This is a CPU counterpart to ``test_adaptive_gpu_quantum_ops.py``.
10
11For smaller tests covering the full Adaptive Profile instruction set,
12see ``test_adaptive_cpu_bytecode.py``.
13"""
14
15from collections import Counter
16import pytest
17from qsharp._simulation import run_qir, Result
18from typing import Literal
19
20SIM_TYPES = ["cpu", "clifford"]
21
22
23# ---------------------------------------------------------------------------
24# Helpers
25# ---------------------------------------------------------------------------
26
27
28def map_result_list_to_str(results):
29 s = ""
30 if isinstance(results, (list, tuple)):
31 for r in results:
32 s += map_result_list_to_str(r)
33 else:
34 match results:
35 case Result.Zero:
36 s += "0"
37 case Result.One:
38 s += "1"
39 case Result.Loss:
40 s += "L"
41 return s
42
43
44def _run(
45 qir: str,
46 shots: int,
47 seed: int = 42,
48 sim_type: Literal["clifford", "cpu"] = "cpu",
49):
50 """Run *qir* on the given simulator and return shot results as a list of strings."""
51 results = run_qir(qir, shots, seed=seed, type=sim_type)
52 return [map_result_list_to_str(r) for r in results]
53
54
55# ---------------------------------------------------------------------------
56# QIR source
57# ---------------------------------------------------------------------------
58
59# Example 1: Measure-and-correct (H → MResetZ → read_result → branch → X)
60MEASURE_AND_CORRECT_QIR = """\
61%Result = type opaque
62%Qubit = type opaque
63
64define void @ENTRYPOINT__main() #0 {
65entry:
66 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
67 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
68 %r = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
69 br i1 %r, label %then, label %end
70
71then:
72 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
73 br label %end
74
75end:
76 call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
77 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
78 ret void
79}
80
81declare void @__quantum__qis__h__body(%Qubit*)
82declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
83declare i1 @__quantum__qis__read_result__body(%Result*)
84declare void @__quantum__qis__x__body(%Qubit*)
85declare void @__quantum__rt__tuple_record_output(i64, i8*)
86declare void @__quantum__rt__result_record_output(%Result*, i8*)
87
88attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
89"""
90
91# Example 3: Conditionally terminating loop
92CONDITIONAL_LOOP_QIR = """\
93%Result = type opaque
94%Qubit = type opaque
95
96define void @ENTRYPOINT__main() #0 {
97entry:
98 br label %loop
99
100loop:
101 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
102 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
103 %r = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
104 br i1 %r, label %done, label %loop
105
106done:
107 call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
108 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
109 ret void
110}
111
112declare void @__quantum__qis__h__body(%Qubit*)
113declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
114declare i1 @__quantum__qis__read_result__body(%Result*)
115declare void @__quantum__rt__tuple_record_output(i64, i8*)
116declare void @__quantum__rt__result_record_output(%Result*, i8*)
117
118attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" }
119"""
120
121# Example 2: Loop with phi node — GHZ state preparation
122LOOP_WITH_PHI_QIR = """\
123%Result = type opaque
124%Qubit = type opaque
125
126define void @ENTRYPOINT__main() #0 {
127entry:
128 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
129 br label %loop
130
131loop:
132 %i = phi i64 [ 1, %entry ], [ %next_i, %loop ]
133 %qi = inttoptr i64 %i to %Qubit*
134 call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* %qi)
135 %next_i = add i64 %i, 1
136 %cond = icmp sle i64 %next_i, 4
137 br i1 %cond, label %loop, label %measure
138
139measure:
140 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
141 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
142 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
143 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 3 to %Qubit*), %Result* inttoptr (i64 3 to %Result*))
144 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 4 to %Result*))
145 call void @__quantum__rt__tuple_record_output(i64 5, i8* null)
146 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* null)
147 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* null)
148 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null)
149 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* null)
150 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 4 to %Result*), i8* null)
151 ret void
152}
153
154declare void @__quantum__qis__h__body(%Qubit*)
155declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
156declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
157declare void @__quantum__rt__tuple_record_output(i64, i8*)
158declare void @__quantum__rt__result_record_output(%Result*, i8*)
159
160attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="5" "required_num_results"="5" }
161"""
162
163# Example 4: Classical boolean computation
164BOOLEAN_COMPUTATION_QIR = """\
165%Result = type opaque
166%Qubit = type opaque
167
168define void @ENTRYPOINT__main() #0 {
169entry:
170 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
171 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*))
172 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
173 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
174 %r0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
175 %r1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
176 %both = and i1 %r0, %r1
177 br i1 %both, label %then, label %else
178
179then:
180 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 0 to %Qubit*))
181 br label %end
182
183else:
184 br label %end
185
186end:
187 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
188 call void @__quantum__rt__tuple_record_output(i64 1, i8* null)
189 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* null)
190 ret void
191}
192
193declare void @__quantum__qis__h__body(%Qubit*)
194declare void @__quantum__qis__x__body(%Qubit*)
195declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
196declare i1 @__quantum__qis__read_result__body(%Result*)
197declare void @__quantum__rt__tuple_record_output(i64, i8*)
198declare void @__quantum__rt__result_record_output(%Result*, i8*)
199
200attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="3" }
201"""
202
203# Example 5: Teleport chain
204TELEPORT_CHAIN_QIR = """\
205%Result = type opaque
206%Qubit = type opaque
207
208@0 = internal constant [5 x i8] c"0_t0\\00"
209@1 = internal constant [5 x i8] c"0_t1\\00"
210
211define void @TeleportChain() #0 {
212entry:
213 call void @__quantum__rt__initialize(i8* null)
214 br label %body
215body:
216 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
217 call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Qubit* inttoptr (i64 1 to %Qubit*))
218 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 2 to %Qubit*))
219 call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Qubit* inttoptr (i64 4 to %Qubit*))
220 call void @__quantum__qis__cnot__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 2 to %Qubit*))
221 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 1 to %Qubit*))
222 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
223 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 1 to %Qubit*))
224 %0 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 0 to %Result*))
225 br i1 %0, label %then__1, label %continue__1
226then__1:
227 call void @__quantum__qis__z__body(%Qubit* inttoptr (i64 4 to %Qubit*))
228 br label %continue__1
229continue__1:
230 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 2 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
231 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 2 to %Qubit*))
232 %1 = call i1 @__quantum__qis__read_result__body(%Result* inttoptr (i64 1 to %Result*))
233 br i1 %1, label %then__2, label %continue__2
234then__2:
235 call void @__quantum__qis__x__body(%Qubit* inttoptr (i64 4 to %Qubit*))
236 br label %continue__2
237continue__2:
238 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 2 to %Result*))
239 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 0 to %Qubit*))
240 call void @__quantum__qis__mz__body(%Qubit* inttoptr (i64 4 to %Qubit*), %Result* inttoptr (i64 3 to %Result*))
241 call void @__quantum__qis__reset__body(%Qubit* inttoptr (i64 4 to %Qubit*))
242 br label %exit
243exit:
244 call void @__quantum__rt__tuple_record_output(i64 2, i8* null)
245 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @0, i32 0, i32 0))
246 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 3 to %Result*), i8* getelementptr inbounds ([5 x i8], [5 x i8]* @1, i32 0, i32 0))
247 ret void
248}
249
250declare void @__quantum__qis__cnot__body(%Qubit*, %Qubit*)
251declare void @__quantum__qis__h__body(%Qubit*)
252declare void @__quantum__qis__x__body(%Qubit*)
253declare void @__quantum__qis__z__body(%Qubit*)
254declare void @__quantum__qis__reset__body(%Qubit*)
255declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
256declare void @__quantum__rt__initialize(i8*)
257declare i1 @__quantum__qis__read_result__body(%Result*)
258declare void @__quantum__rt__result_record_output(%Result*, i8*)
259declare void @__quantum__rt__tuple_record_output(i64, i8*)
260
261attributes #0 = { "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="5" "required_num_results"="4" }
262attributes #1 = { "irreversible" }
263"""
264
265
266# ---------------------------------------------------------------------------
267# Tests
268# ---------------------------------------------------------------------------
269
270
271@pytest.mark.parametrize("sim_type", SIM_TYPES)
272def test_measure_and_correct_histogram(sim_type):
273 """Example 1: H → MResetZ → read_result → conditional X.
274
275 Run 10000 shots and verify ~50/50 split of "0" and "1" outcomes.
276 """
277 results = _run(MEASURE_AND_CORRECT_QIR, shots=10000, seed=42, sim_type=sim_type)
278 assert len(results) == 10000
279
280 counts = Counter(results)
281 count_0 = counts.get("0", 0)
282 count_1 = counts.get("1", 0)
283
284 assert count_0 > 4000, f"Expected ~5000 '0' results, got {count_0}"
285 assert count_1 > 4000, f"Expected ~5000 '1' results, got {count_1}"
286 assert count_0 + count_1 == 10000, "All shots should produce a result"
287
288
289@pytest.mark.parametrize("sim_type", SIM_TYPES)
290def test_conditional_loop_all_results_are_one(sim_type):
291 """Example 3: The loop exits only when measurement yields 1.
292
293 Every shot's recorded result should be "1".
294 """
295 shots = 5000
296 results = _run(CONDITIONAL_LOOP_QIR, shots=shots, seed=99, sim_type=sim_type)
297 assert len(results) == shots
298
299 counts = Counter(results)
300 assert (
301 counts.get("1", 0) == shots
302 ), f"Expected all {shots} shots to produce '1', got counts: {counts}"
303
304
305# ---------------------------------------------------------------------------
306# Tests — Example 2: Loop with phi (GHZ state)
307# ---------------------------------------------------------------------------
308
309
310@pytest.mark.parametrize("sim_type", SIM_TYPES)
311def test_loop_with_phi_ghz_histogram(sim_type):
312 """Example 2: H → loop CNOT(q0, q_i) for i=1..4 → measure all.
313
314 Creates (|00000⟩ + |11111⟩)/√2. All 5 measurements must agree.
315 """
316 results = _run(LOOP_WITH_PHI_QIR, shots=10000, seed=42, sim_type=sim_type)
317 assert len(results) == 10000
318
319 counts = Counter(results)
320 assert set(counts.keys()) <= {
321 "00000",
322 "11111",
323 }, f"Unexpected outcomes in GHZ state: {counts}"
324
325 count_00000 = counts.get("00000", 0)
326 count_11111 = counts.get("11111", 0)
327
328 assert count_00000 > 4000, f"Expected ~5000 '00000' results, got {count_00000}"
329 assert count_11111 > 4000, f"Expected ~5000 '11111' results, got {count_11111}"
330 assert count_00000 + count_11111 == 10000, "All shots should produce a result"
331
332
333# ---------------------------------------------------------------------------
334# Tests — Example 4: Boolean computation (AND gate)
335# ---------------------------------------------------------------------------
336
337
338@pytest.mark.parametrize("sim_type", SIM_TYPES)
339def test_boolean_computation_histogram(sim_type):
340 """Example 4: H(q0), H(q1) → MResetZ both → AND results → conditional X.
341
342 r2=1 only when both r0=1 AND r1=1 (~25% of shots).
343 """
344 results = _run(BOOLEAN_COMPUTATION_QIR, shots=10000, seed=42, sim_type=sim_type)
345 assert len(results) == 10000
346
347 counts = Counter(results)
348 count_0 = counts.get("0", 0)
349 count_1 = counts.get("1", 0)
350
351 assert 1500 < count_1 < 3500, f"Expected ~2500 '1' results (~25%), got {count_1}"
352 assert 6500 < count_0 < 8500, f"Expected ~7500 '0' results (~75%), got {count_0}"
353 assert count_0 + count_1 == 10000, "All shots should produce a result"
354
355
356# ---------------------------------------------------------------------------
357# Tests — Example 5: Teleport chain
358# ---------------------------------------------------------------------------
359
360
361@pytest.mark.parametrize("sim_type", SIM_TYPES)
362def test_teleport_chain_histogram(sim_type):
363 """Example 5: Teleport chain with 2 Bell pairs and measure-and-correct.
364
365 Final measurements of q0 and q4 should be correlated:
366 both "0" or both "1", near 50/50.
367 """
368 results = _run(TELEPORT_CHAIN_QIR, shots=10000, seed=42, sim_type=sim_type)
369 assert len(results) == 10000
370
371 counts = Counter(results)
372 assert set(counts.keys()) <= {
373 "00",
374 "11",
375 }, f"Unexpected outcomes in teleport chain: {counts}"
376
377 count_00 = counts.get("00", 0)
378 count_11 = counts.get("11", 0)
379
380 assert count_00 > 4000, f"Expected ~5000 '00' results, got {count_00}"
381 assert count_11 > 4000, f"Expected ~5000 '11' results, got {count_11}"
382 assert count_00 + count_11 == 10000, "All shots should produce a result"
383
384
385DYNAMIC_ROTATION_ANGLE_QIR = r"""
386%Result = type opaque
387%Qubit = type opaque
388
389@0 = internal constant [4 x i8] c"0_r\00"
390
391define i64 @ENTRYPOINT__main() #0 {
392block_0:
393 call void @__quantum__rt__initialize(i8* null)
394 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
395 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
396 %var_1 = call i1 @__quantum__rt__read_result(%Result* inttoptr (i64 0 to %Result*))
397 %var_2 = icmp eq i1 %var_1, false
398 br i1 %var_2, label %block_1, label %block_2
399block_1:
400 br label %block_3
401block_2:
402 br label %block_3
403block_3:
404 %var_3 = phi double [0.5, %block_1], [1.0, %block_2]
405 call void @__quantum__qis__rx__body(double %var_3, %Qubit* inttoptr (i64 1 to %Qubit*))
406 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Result* inttoptr (i64 1 to %Result*))
407 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0))
408 ret i64 0
409}
410
411declare void @__quantum__rt__initialize(i8*)
412declare void @__quantum__qis__h__body(%Qubit*)
413declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1
414declare i1 @__quantum__rt__read_result(%Result*)
415declare void @__quantum__qis__rx__body(double, %Qubit*)
416declare void @__quantum__rt__result_record_output(%Result*, i8*)
417
418attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="2" "required_num_results"="2" }
419attributes #1 = { "irreversible" }
420
421!llvm.module.flags = !{!0, !1, !2, !3, !4, !5}
422
423!0 = !{i32 1, !"qir_major_version", i32 1}
424!1 = !{i32 7, !"qir_minor_version", i32 0}
425!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
426!3 = !{i32 1, !"dynamic_result_management", i1 false}
427!4 = !{i32 5, !"int_computations", !{!"i64"}}
428!5 = !{i32 5, !"float_computations", !{!"double"}}
429"""
430
431
432def test_dynamic_rotation_angle():
433 results = _run(DYNAMIC_ROTATION_ANGLE_QIR, shots=10_000, seed=42, sim_type="cpu")
434 assert len(results) == 10_000
435
436 counts = Counter(results)
437 count_0 = counts.get("0", 0)
438 count_1 = counts.get("1", 0)
439
440 assert count_1 > 1400, f"Expected ~15% '1' results, got {count_1}"
441 assert count_0 > 8400, f"Expected ~85% '0' results, got {count_0}"
442 assert count_0 + count_1 == 10_000, "All shots should produce a result"
443