Skip to content

Commit c55ea2e

Browse files
authored
test: Add RNG integration tests (#346)
1 parent fee73b8 commit c55ea2e

1 file changed

Lines changed: 192 additions & 0 deletions

File tree

integration/test_rng.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""Test RNG capabilities."""
2+
3+
from typing import Callable, ContextManager
4+
5+
import numpy as np
6+
from pytket.backends.backendresult import BackendResult
7+
from pytket.circuit import Bit, Circuit
8+
9+
import qnexus as qnx
10+
from qnexus.models.references import (
11+
CircuitRef,
12+
ExecutionResultRef,
13+
)
14+
15+
16+
def get_rng_circuit(seed: int, n_rng: int, test_index: bool = False) -> Circuit:
17+
"""Creates a single qubit pytket circuit to test RNGs.
18+
19+
Note: see https://docs.quantinuum.com/systems/trainings/h2/getting_started/rng.html
20+
and https://docs.quantinuum.com/tket/api-docs/circuit_class.html#rng-operations
21+
"""
22+
circuit = Circuit(1)
23+
24+
rng_regs = []
25+
for i in range(n_rng):
26+
rng_regs.append(circuit.add_c_register(f"rng{i}", 32))
27+
28+
seed_reg = circuit.add_c_register("seed", 64)
29+
circuit.add_c_setreg(seed, seed_reg)
30+
circuit.set_rng_seed(seed_reg)
31+
32+
job_shot_reg = circuit.add_c_register("job_shot_num", 32)
33+
circuit.get_job_shot_num(job_shot_reg)
34+
35+
if test_index:
36+
# set rng index = job_shotnum * num_rng_calls
37+
index_reg = circuit.add_c_register("index", 32)
38+
circuit.add_clexpr_from_logicexp(job_shot_reg * n_rng, index_reg.to_list())
39+
circuit.set_rng_index(index_reg)
40+
41+
for rng_reg in rng_regs:
42+
circuit.get_rng_num(rng_reg)
43+
circuit.measure_all()
44+
45+
return circuit
46+
47+
48+
def ints_from_pytket_shots(shots: np.ndarray) -> list[int]:
49+
"""Convert pytket register shots to integers.
50+
51+
Args:
52+
shots: Array of shape (n_shots, n_bits) from result.get_shots(cbits=reg).
53+
Column i corresponds to reg[i], with reg[0] as the LSB.
54+
55+
Returns:
56+
List of integers, one per shot.
57+
"""
58+
powers = 1 << np.arange(shots.shape[1])
59+
return list(map(int, (shots @ powers).tolist()))
60+
61+
62+
def test_rng(
63+
test_case_name: str,
64+
create_circuit_in_project: Callable[
65+
[Circuit, str, str], ContextManager[CircuitRef]
66+
],
67+
) -> None:
68+
"""Test that we can run RNG circuits in H-Series machines."""
69+
local_project_name = f"project for {test_case_name}"
70+
backend_config = qnx.QuantinuumConfig(device_name="H2-1E")
71+
n_shots = 5
72+
n_rng = 3
73+
74+
rng_circuit_case1 = get_rng_circuit(42, n_rng, test_index=False)
75+
rng_circuit_case2 = get_rng_circuit(42, n_rng, test_index=True)
76+
rng_circuit_case3 = get_rng_circuit(24, n_rng, test_index=False)
77+
78+
# Pytket "rngX" registers to extract the RNG numbers from the results.
79+
rng_reg_names = []
80+
for rng in range(n_rng):
81+
rng_reg_names.append(f"rng{rng}")
82+
83+
with create_circuit_in_project(
84+
rng_circuit_case1,
85+
local_project_name,
86+
f"RNG circuit 1 for {test_case_name}",
87+
) as rng_circ_case1_ref:
88+
with create_circuit_in_project(
89+
rng_circuit_case2,
90+
local_project_name,
91+
f"RNG circuit 2 for {test_case_name}",
92+
) as rng_circ_case2_ref:
93+
with create_circuit_in_project(
94+
rng_circuit_case3,
95+
local_project_name,
96+
f"RNG circuit 3 for {test_case_name}",
97+
) as rng_circ_case3_ref:
98+
my_proj = qnx.projects.get(name=local_project_name)
99+
100+
execute_job_case1 = qnx.start_execute_job(
101+
programs=[rng_circ_case1_ref, rng_circ_case1_ref],
102+
name=f"Same seed no index RNG job for {test_case_name}",
103+
project=my_proj,
104+
backend_config=backend_config,
105+
n_shots=[n_shots, n_shots],
106+
)
107+
execute_job_case2 = qnx.start_execute_job(
108+
programs=[rng_circ_case2_ref],
109+
name=f"Same seed with index RNG job for {test_case_name}",
110+
project=my_proj,
111+
backend_config=backend_config,
112+
n_shots=[n_shots],
113+
)
114+
execute_job_case3 = qnx.start_execute_job(
115+
programs=[rng_circ_case3_ref],
116+
name=f"Different seed no index RNG job for {test_case_name}",
117+
project=my_proj,
118+
backend_config=backend_config,
119+
n_shots=[n_shots],
120+
)
121+
122+
# Case 1: Executing a circuit with the same seed and no index
123+
# multiple times should give the same RNG numbers in all shots.
124+
qnx.jobs.wait_for(execute_job_case1)
125+
results_same = qnx.jobs.results(execute_job_case1)
126+
rng1_A_result_ref = results_same[0]
127+
rng1_B_result_ref = results_same[1]
128+
assert isinstance(rng1_A_result_ref, ExecutionResultRef)
129+
assert isinstance(rng1_B_result_ref, ExecutionResultRef)
130+
131+
rng1_A_result = rng1_A_result_ref.download_result()
132+
rng1_B_result = rng1_B_result_ref.download_result()
133+
assert isinstance(rng1_A_result, BackendResult)
134+
assert isinstance(rng1_B_result, BackendResult)
135+
136+
for rng_reg_name in rng_reg_names:
137+
reg = [Bit(f"{rng_reg_name}", i) for i in range(32)]
138+
139+
rng1_A_numbers = ints_from_pytket_shots(
140+
rng1_A_result.get_shots(cbits=reg)
141+
)
142+
rng1_B_numbers = ints_from_pytket_shots(
143+
rng1_B_result.get_shots(cbits=reg)
144+
)
145+
146+
assert len(rng1_A_numbers) == len(rng1_B_numbers)
147+
assert rng1_A_numbers == rng1_B_numbers, (
148+
f"RNG numbers of {rng_reg_name} generated with the same seed should be equal."
149+
)
150+
151+
# Case 2: Executing the same circuit with the same seed and changing the
152+
# index should give different RNG numbers in all shots.
153+
qnx.jobs.wait_for(execute_job_case2)
154+
results_index = qnx.jobs.results(execute_job_case2)
155+
rng2_result_ref = results_index[0]
156+
assert isinstance(rng2_result_ref, ExecutionResultRef)
157+
158+
rng2_result = rng2_result_ref.download_result()
159+
assert isinstance(rng2_result, BackendResult)
160+
161+
all_shots_numbers = []
162+
for rng_reg_name in rng_reg_names:
163+
reg = [Bit(f"{rng_reg_name}", i) for i in range(32)]
164+
165+
all_shots_numbers.extend(
166+
ints_from_pytket_shots(rng2_result.get_shots(cbits=reg))
167+
)
168+
169+
assert len(set(all_shots_numbers)) == len(all_shots_numbers), (
170+
"All RNG numbers should be different across shots."
171+
)
172+
173+
# Case 3: Executing the circuit with a different seed should give
174+
# a different RNG number than the case 1 execution.
175+
qnx.jobs.wait_for(execute_job_case3)
176+
results_diff = qnx.jobs.results(execute_job_case3)
177+
rng3_result_ref = results_diff[0]
178+
assert isinstance(rng3_result_ref, ExecutionResultRef)
179+
180+
rng3_result = rng3_result_ref.download_result()
181+
assert isinstance(rng3_result, BackendResult)
182+
183+
for rng_reg_name in rng_reg_names:
184+
reg = [Bit(f"{rng_reg_name}", i) for i in range(32)]
185+
186+
rng3_numbers = ints_from_pytket_shots(
187+
rng3_result.get_shots(cbits=reg)
188+
)
189+
190+
assert rng1_A_numbers != rng3_numbers, (
191+
f"RNG numbers of {rng_reg_name} generated with a different seed should be different."
192+
)

0 commit comments

Comments
 (0)