microsoft/qdk

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
fedimser/is-re

Branches

Tags

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

Clone

HTTPS

Download ZIP

source/pip/tests/test_adaptive_gpu_noise.py

437lines · modecode

1# Copyright (c) Microsoft Corporation.
2# Licensed under the MIT License.
3
4"""Per-opcode tests for the adaptive GPU bytecode interpreter.
5
6Each test targets one (or a small family of) bytecode instruction(s) by
7supplying hand-written Adaptive Profile QIR that exercises the instruction
8and encodes the expected result into a measurement outcome.
9
10Tests are ordered to match the opcode definitions in ``_adaptive_opcodes.py``
11so that coverage can be verified by reading both files side by side.
12
13Requires QDK_GPU_TESTS env var and a GPU adapter.
14"""
15
16import os
17import sys
18from collections import Counter
19import pytest
20from typing import Optional, List
21import qsharp.openqasm
22
23# Skip the whole module when GPU tests aren't requested.
24if not os.environ.get("QDK_GPU_TESTS"):
25 pytest.skip("Skipping GPU tests (QDK_GPU_TESTS not set)", allow_module_level=True)
26
27SKIP_REASON = "GPU is not available"
28GPU_AVAILABLE = False
29
30try:
31 from qsharp._native import try_create_gpu_adapter
32
33 gpu_info = try_create_gpu_adapter()
34 print(f"*** USING GPU: {gpu_info}", file=sys.stderr)
35 GPU_AVAILABLE = True
36except OSError as e:
37 SKIP_REASON = str(e)
38
39from qsharp._simulation import run_qir, GpuSimulator, NoiseConfig, Result
40
41# ---------------------------------------------------------------------------
42# Helpers
43# ---------------------------------------------------------------------------
44
45# Deterministic programs need a single shot but we run multiple shots
46# to verify that multiple shots yield the same result.
47SHOTS = 100
48
49
50def map_result_list_to_str(results: List[Result]):
51 results_str = ""
52 for r in results:
53 match r:
54 case Result.Zero:
55 results_str += "0"
56 case Result.One:
57 results_str += "1"
58 case Result.Loss:
59 results_str += "L"
60 return results_str
61
62
63def get_histogram(
64 qir_fragment: str,
65 *,
66 extra_decls: str = "",
67 num_qubits: int = 1,
68 num_results: int = 1,
69 noise: Optional[NoiseConfig] = None,
70 record: Optional[List[int]] = None,
71 shots=SHOTS,
72):
73 qir = format_qir(
74 qir_fragment,
75 extra_decls=extra_decls,
76 num_qubits=num_qubits,
77 num_results=num_results,
78 record=record,
79 )
80 results = map(
81 map_result_list_to_str, run_qir(qir, shots, noise, seed=42, type="gpu")
82 )
83 return Counter(results)
84
85
86def check_result(
87 qir_fragment: str,
88 expected: str,
89 *,
90 extra_decls: str = "",
91 num_qubits: int = 1,
92 num_results: int = 1,
93 noise: Optional[NoiseConfig] = None,
94 record: Optional[List[int]] = None,
95):
96 """Assert every shot produces *expected*."""
97 counts = get_histogram(
98 qir_fragment,
99 extra_decls=extra_decls,
100 num_qubits=num_qubits,
101 num_results=num_results,
102 noise=noise,
103 record=record,
104 )
105
106 assert counts == {
107 expected: SHOTS
108 }, f"Expected all {SHOTS} shots to be '{expected}', got {counts}"
109
110
111_DECLS = """\
112declare void @__quantum__qis__x__body(%Qubit*)
113declare void @__quantum__qis__h__body(%Qubit*)
114declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*)
115declare void @__quantum__qis__mz__body(%Qubit*, %Result*) #1
116declare void @__quantum__qis__reset__body(%Qubit*)
117declare void @__quantum__qis__cx__body(%Qubit*, %Qubit*)
118declare void @__quantum__qis__z__body(%Qubit*)
119declare void @__quantum__qis__s__body(%Qubit*)
120declare void @__quantum__qis__t__body(%Qubit*)
121declare void @__quantum__qis__cz__body(%Qubit*, %Qubit*)
122declare void @__quantum__qis__rz__body(double, %Qubit*)
123declare i1 @__quantum__qis__read_result__body(%Result*)
124declare void @__quantum__rt__tuple_record_output(i64, i8*)
125declare void @__quantum__rt__result_record_output(%Result*, i8*)
126declare void @__quantum__rt__initialize(i8*)
127"""
128
129
130def format_qir(
131 body: str,
132 *,
133 extra_decls: str = "",
134 num_qubits: int = 1,
135 num_results: int = 1,
136 record=None,
137):
138 if record is None:
139 record = range(num_results)
140 output_recording = (
141 f" call void @__quantum__rt__tuple_record_output(i64 {len(record)}, i8* null)"
142 )
143 for result_id in record:
144 output_recording += f"\n call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 {result_id} to %Result*), i8* null)"
145
146 return f"""\
147%Result = type opaque
148%Qubit = type opaque
149
150define i64 @ENTRYPOINT__main() #0 {{
151{body}
152{output_recording}
153 ret i64 0
154}}
155
156{_DECLS}
157{extra_decls}
158attributes #0 = {{ "entry_point" "qir_profiles"="adaptive_profile" "required_num_qubits"="{num_qubits}" "required_num_results"="{num_results}" }}
159attributes #1 = {{ "irreversible" }}
160"""
161
162
163# The purpose of this test is to inject noise in an identity gate, and assert its behavior.
164# Since QIS does not specify an identity gate, we use CNOT and inject noise in the target qubit.
165I_QIR = """
166entry:
167 call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*))
168 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
169"""
170
171H_I_H_QIR = """
172entry:
173 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
174 call void @__quantum__qis__cx__body(%Qubit* inttoptr (i64 1 to %Qubit*), %Qubit* inttoptr (i64 0 to %Qubit*))
175 call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*))
176 call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*))
177"""
178
179
180@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
181def test_no_noise_on_i_yields_0():
182 check_result(I_QIR, "0", num_qubits=2)
183
184
185@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
186def test_x_noise_on_i_yields_1():
187 noise = NoiseConfig()
188 noise.cx.ix = 1.0
189 check_result(I_QIR, "1", num_qubits=2, noise=noise)
190
191
192@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
193def test_y_noise_on_i_yields_1():
194 noise = NoiseConfig()
195 noise.cx.iy = 1.0
196 check_result(I_QIR, "1", num_qubits=2, noise=noise)
197
198
199@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
200def test_z_noise_on_i_yields_0():
201 noise = NoiseConfig()
202 noise.cx.iz = 1.0
203 check_result(I_QIR, "0", num_qubits=2, noise=noise)
204
205
206@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
207def test_x_noise_on_h_i_h_yields_0():
208 noise = NoiseConfig()
209 noise.cx.ix = 1.0
210 check_result(H_I_H_QIR, "0", num_qubits=2, noise=noise)
211
212
213@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
214def test_y_noise_on_h_i_h_yields_1():
215 noise = NoiseConfig()
216 noise.cx.iy = 1.0
217 check_result(H_I_H_QIR, "1", num_qubits=2, noise=noise)
218
219
220@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
221def test_z_noise_on_h_i_h_yields_1():
222 noise = NoiseConfig()
223 noise.cx.iz = 1.0
224 check_result(H_I_H_QIR, "1", num_qubits=2, noise=noise)
225
226
227@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
228def test_probabilistic_x_noise():
229 noise = NoiseConfig()
230 noise.cx.ix = 0.5
231 counts = get_histogram(I_QIR, shots=1000, noise=noise)
232
233 assert counts["0"] > 400, f"Expected ~500 '0' results, got {counts['0']}"
234 assert counts["1"] > 400, f"Expected ~500 '1' results, got {counts['1']}"
235
236
237QASM_WITH_CORRELATED_NOISE = """
238OPENQASM 3.0;
239include "stdgates.inc";
240
241@qdk.qir.noise_intrinsic
242gate test_noise_intrinsic q0, q1, q2 {}
243
244qubit[3] qs;
245x qs[1];
246test_noise_intrinsic qs[0], qs[1], qs[2];
247bit[3] res = measure qs;
248"""
249
250QIR_WITH_CORRELATED_NOISE = qsharp.openqasm.compile(
251 QASM_WITH_CORRELATED_NOISE,
252 output_semantics=qsharp.openqasm.OutputSemantics.OpenQasm,
253 target_profile=qsharp.TargetProfile.Adaptive_RIF,
254)
255
256
257@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
258def test_noise_intrinsics_noiseless():
259 output = run_qir(QIR_WITH_CORRELATED_NOISE, shots=1, noise=None, type="gpu")
260 assert output == [[Result.Zero, Result.One, Result.Zero]]
261
262
263@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
264def test_noise_intrinsics_noisy():
265 noise = NoiseConfig()
266 table = noise.intrinsic("test_noise_intrinsic", 3)
267 table.yyy = 1.0
268 output = run_qir(QIR_WITH_CORRELATED_NOISE, shots=1, noise=noise, type="gpu")
269 assert output == [[Result.One, Result.Zero, Result.One]]
270
271
272@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
273def test_noise_intrinsics_load_csv_dir():
274 noise = NoiseConfig()
275 noise.load_csv_dir("./csv_dir_test")
276 output = run_qir(QIR_WITH_CORRELATED_NOISE, shots=1, noise=noise, type="gpu")
277 assert output == [[Result.One, Result.Zero, Result.One]]
278
279
280@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
281def test_noise_intrinsics_gpu_sim_class():
282 sim = GpuSimulator()
283 sim.load_noise_tables("./csv_dir_test")
284 sim.set_program(QIR_WITH_CORRELATED_NOISE)
285 output = sim.run_shots(shots=1)["shot_results"]
286 assert output == ["101"]
287
288
289NOISE_INTRINSICS_WITH_REGISTERS_QIR = r"""
290%Result = type opaque
291%Qubit = type opaque
292
293@0 = internal constant [4 x i8] c"0_a\00"
294@1 = internal constant [6 x i8] c"1_a0r\00"
295@2 = internal constant [6 x i8] c"2_a1r\00"
296@3 = internal constant [6 x i8] c"3_a2r\00"
297
298define i64 @ENTRYPOINT__main() #0 {
299block_0:
300 %q1 = inttoptr i64 0 to %Qubit*
301 %q2 = inttoptr i64 1 to %Qubit*
302 %q3 = inttoptr i64 2 to %Qubit*
303 call void @__quantum__rt__initialize(i8* null)
304 call void @__quantum__qis__x__body(%Qubit* %q2)
305 call void @test_noise_intrinsic(%Qubit* %q1, %Qubit* %q2, %Qubit* %q3)
306 call void @__quantum__qis__m__body(%Qubit* %q1, %Result* inttoptr (i64 0 to %Result*))
307 call void @__quantum__qis__m__body(%Qubit* %q2, %Result* inttoptr (i64 1 to %Result*))
308 call void @__quantum__qis__m__body(%Qubit* %q3, %Result* inttoptr (i64 2 to %Result*))
309 call void @__quantum__rt__array_record_output(i64 3, i8* getelementptr inbounds ([4 x i8], [4 x i8]* @0, i64 0, i64 0))
310 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @1, i64 0, i64 0))
311 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 1 to %Result*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @2, i64 0, i64 0))
312 call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 2 to %Result*), i8* getelementptr inbounds ([6 x i8], [6 x i8]* @3, i64 0, i64 0))
313 ret i64 0
314}
315
316declare void @__quantum__rt__initialize(i8*)
317declare void @__quantum__qis__x__body(%Qubit*)
318declare void @test_noise_intrinsic(%Qubit*, %Qubit*, %Qubit*) #2
319declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1
320declare void @__quantum__rt__array_record_output(i64, i8*)
321declare void @__quantum__rt__result_record_output(%Result*, i8*)
322
323attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="3" "required_num_results"="3" }
324attributes #1 = { "irreversible" }
325attributes #2 = { "qdk_noise" }
326
327!llvm.module.flags = !{!0, !1, !2, !3, !4, !5}
328
329!0 = !{i32 1, !"qir_major_version", i32 1}
330!1 = !{i32 7, !"qir_minor_version", i32 0}
331!2 = !{i32 1, !"dynamic_qubit_management", i1 false}
332!3 = !{i32 1, !"dynamic_result_management", i1 false}
333!4 = !{i32 5, !"int_computations", !{!"i64"}}
334!5 = !{i32 5, !"float_computations", !{!"double"}}
335"""
336
337
338@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
339def test_noise_intrinsics_with_registers_noisy():
340 noise = NoiseConfig()
341 table = noise.intrinsic("test_noise_intrinsic", 3)
342 table.yyy = 1.0
343 output = run_qir(
344 NOISE_INTRINSICS_WITH_REGISTERS_QIR, shots=1, noise=noise, type="gpu"
345 )
346 assert output == [[Result.One, Result.Zero, Result.One]]
347
348
349# --- Tests for varied qubit counts (1, 2, 5) ---
350
351QASM_NOISE_1Q = """
352OPENQASM 3.0;
353include "stdgates.inc";
354
355@qdk.qir.noise_intrinsic
356gate noise_1q q0 {}
357
358qubit q;
359noise_1q q;
360bit res = measure q;
361"""
362
363QIR_NOISE_1Q = qsharp.openqasm.compile(
364 QASM_NOISE_1Q,
365 output_semantics=qsharp.openqasm.OutputSemantics.OpenQasm,
366 target_profile=qsharp.TargetProfile.Adaptive_RIF,
367)
368
369
370@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
371def test_noise_intrinsic_1q_x_flip():
372 noise = NoiseConfig()
373 table = noise.intrinsic("noise_1q", 1)
374 table.x = 1.0
375 output = run_qir(QIR_NOISE_1Q, shots=1, noise=noise, type="gpu")
376 assert output == [[Result.One]]
377
378
379QASM_NOISE_2Q = """
380OPENQASM 3.0;
381include "stdgates.inc";
382
383@qdk.qir.noise_intrinsic
384gate noise_2q q0, q1 {}
385
386qubit[2] qs;
387x qs[0];
388noise_2q qs[0], qs[1];
389bit[2] res = measure qs;
390"""
391
392QIR_NOISE_2Q = qsharp.openqasm.compile(
393 QASM_NOISE_2Q,
394 output_semantics=qsharp.openqasm.OutputSemantics.OpenQasm,
395 target_profile=qsharp.TargetProfile.Adaptive_RIF,
396)
397
398
399@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
400def test_noise_intrinsic_2q_xx_flip():
401 noise = NoiseConfig()
402 table = noise.intrinsic("noise_2q", 2)
403 table.xx = 1.0
404 # qs[0] was |1>, qs[1] was |0> -> XX flips both -> qs[0]=|0>, qs[1]=|1>
405 output = run_qir(QIR_NOISE_2Q, shots=1, noise=noise, type="gpu")
406 assert output == [[Result.Zero, Result.One]]
407
408
409QASM_NOISE_5Q = """
410OPENQASM 3.0;
411include "stdgates.inc";
412
413@qdk.qir.noise_intrinsic
414gate noise_5q q0, q1, q2, q3, q4 {}
415
416qubit[5] qs;
417x qs[1];
418x qs[3];
419noise_5q qs[0], qs[1], qs[2], qs[3], qs[4];
420bit[5] res = measure qs;
421"""
422
423QIR_NOISE_5Q = qsharp.openqasm.compile(
424 QASM_NOISE_5Q,
425 output_semantics=qsharp.openqasm.OutputSemantics.OpenQasm,
426 target_profile=qsharp.TargetProfile.Adaptive_RIF,
427)
428
429
430@pytest.mark.skipif(not GPU_AVAILABLE, reason=SKIP_REASON)
431def test_noise_intrinsic_5q_xxxxx_flip():
432 noise = NoiseConfig()
433 table = noise.intrinsic("noise_5q", 5)
434 table.xxxxx = 1.0
435 # Initial: |01010> -> XXXXX flips all -> |10101>
436 output = run_qir(QIR_NOISE_5Q, shots=1, noise=noise, type="gpu")
437 assert output == [[Result.One, Result.Zero, Result.One, Result.Zero, Result.One]]
438