Skip to content

Commit e6e91f8

Browse files
authored
Merge pull request #41 from scaleway/fix-sampler
feat(sampler): fix sampling base conversion
2 parents 36ae78f + 501a10d commit e6e91f8

File tree

4 files changed

+96
-67
lines changed

4 files changed

+96
-67
lines changed

qiskit_scaleway/backends/base_job.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def submit(self, session_id: str) -> None:
8080

8181
options = self._config.copy()
8282
shots = options.pop("shots")
83-
memory = options.pop("memory")
83+
memory = options.pop("memory", False)
8484

8585
programs = map(lambda c: QuantumProgram.from_qiskit_circuit(c), self._circuits)
8686

qiskit_scaleway/primitives/sampler.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,26 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
from qiskit.primitives import BackendSamplerV2
14+
import numpy as np
15+
import re
16+
17+
from numpy.typing import NDArray
18+
19+
from qiskit.primitives.backend_sampler_v2 import (
20+
BackendSamplerV2,
21+
_MeasureInfo,
22+
_samples_to_packed_array,
23+
QiskitError,
24+
ResultMemory,
25+
)
26+
27+
from qiskit.primitives.containers import (
28+
BitArray,
29+
DataBin,
30+
SamplerPubResult,
31+
)
32+
33+
_NON_BINARY_CHARS = re.compile(r"[^01]")
1534

1635

1736
class Sampler(BackendSamplerV2):
@@ -37,3 +56,68 @@ def __init__(
3756
backend=backend,
3857
options=options,
3958
)
59+
60+
def _postprocess_pub(
61+
self,
62+
result_memory: list[ResultMemory],
63+
shots: int,
64+
shape: tuple[int, ...],
65+
meas_info: list[_MeasureInfo],
66+
max_num_bytes: int,
67+
circuit_metadata: dict,
68+
meas_level: int | None,
69+
) -> SamplerPubResult:
70+
"""Converts the memory data into a sampler pub result
71+
72+
For level 2 data, the memory data are stored in an array of bit arrays
73+
with the shape of the pub. For level 1 data, the data are stored in a
74+
complex numpy array.
75+
"""
76+
if meas_level == 2 or meas_level is None:
77+
arrays = {
78+
item.creg_name: np.zeros(
79+
shape + (shots, item.num_bytes), dtype=np.uint8
80+
)
81+
for item in meas_info
82+
}
83+
memory_array = _memory_array(result_memory, max_num_bytes)
84+
85+
for samples, index in zip(memory_array, np.ndindex(*shape)):
86+
for item in meas_info:
87+
ary = _samples_to_packed_array(samples, item.num_bits, item.start)
88+
arrays[item.creg_name][index] = ary
89+
90+
meas = {
91+
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits)
92+
for item in meas_info
93+
}
94+
elif meas_level == 1:
95+
raw = np.array(result_memory)
96+
cplx = raw[..., 0] + 1j * raw[..., 1]
97+
cplx = np.reshape(cplx, (*shape, *cplx.shape[1:]))
98+
meas = {item.creg_name: cplx for item in meas_info}
99+
else:
100+
raise QiskitError(f"Unsupported meas_level: {meas_level}")
101+
return SamplerPubResult(
102+
DataBin(**meas, shape=shape),
103+
metadata={"shots": shots, "circuit_metadata": circuit_metadata},
104+
)
105+
106+
107+
def _memory_array(results: list[list[str]], num_bytes: int) -> NDArray[np.uint8]:
108+
"""Converts the memory data into an array in an unpacked way."""
109+
lst = []
110+
# Heuristic: check only the first result format
111+
if len(results) > 0 and len(results[0]) > 0:
112+
base = 16 if _NON_BINARY_CHARS.search(results[0][0]) else 2
113+
114+
for memory in results:
115+
if num_bytes > 0:
116+
data = b"".join(int(i, base).to_bytes(num_bytes, "big") for i in memory)
117+
data = np.frombuffer(data, dtype=np.uint8).reshape(-1, num_bytes)
118+
else:
119+
# no measure in a circuit
120+
data = np.zeros((len(memory), num_bytes), dtype=np.uint8)
121+
lst.append(data)
122+
ary = np.asarray(lst)
123+
return np.unpackbits(ary, axis=-1, bitorder="big")

tests/test_aer_multiple_circuits.py

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,42 +12,15 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import os
15-
import numpy as np
1615
import random
1716

1817
from qiskit import QuantumCircuit
1918
from qiskit_scaleway import ScalewayProvider
2019

21-
22-
def _random_qiskit_circuit(size: int) -> QuantumCircuit:
23-
num_qubits = size
24-
num_gate = size
25-
26-
qc = QuantumCircuit(num_qubits)
27-
28-
for _ in range(num_gate):
29-
random_gate = np.random.choice(["unitary", "cx", "cy", "cz"])
30-
31-
if random_gate == "cx" or random_gate == "cy" or random_gate == "cz":
32-
control_qubit = np.random.randint(0, num_qubits)
33-
target_qubit = np.random.randint(0, num_qubits)
34-
35-
while target_qubit == control_qubit:
36-
target_qubit = np.random.randint(0, num_qubits)
37-
38-
getattr(qc, random_gate)(control_qubit, target_qubit)
39-
else:
40-
for q in range(num_qubits):
41-
random_gate = np.random.choice(["h", "x", "y", "z"])
42-
getattr(qc, random_gate)(q)
43-
44-
qc.measure_all()
45-
46-
return qc
20+
from qio.utils.circuit import random_square_qiskit_circuit
4721

4822

4923
def test_aer_multiple_circuits():
50-
5124
provider = ScalewayProvider(
5225
project_id=os.environ["QISKIT_SCALEWAY_PROJECT_ID"],
5326
secret_key=os.environ["QISKIT_SCALEWAY_SECRET_KEY"],
@@ -69,10 +42,10 @@ def test_aer_multiple_circuits():
6942
assert session_id is not None
7043

7144
try:
72-
qc1 = _random_qiskit_circuit(20)
73-
qc2 = _random_qiskit_circuit(15)
74-
qc3 = _random_qiskit_circuit(21)
75-
qc4 = _random_qiskit_circuit(17)
45+
qc1 = random_square_qiskit_circuit(20)
46+
qc2 = random_square_qiskit_circuit(15)
47+
qc3 = random_square_qiskit_circuit(21)
48+
qc4 = random_square_qiskit_circuit(17)
7649

7750
run_result = backend.run(
7851
[qc1, qc2, qc3, qc4],
@@ -126,7 +99,6 @@ def _simple_one_state_circuit():
12699

127100

128101
def test_aer_with_noise_model():
129-
130102
provider = ScalewayProvider(
131103
project_id=os.environ["QISKIT_SCALEWAY_PROJECT_ID"],
132104
secret_key=os.environ["QISKIT_SCALEWAY_SECRET_KEY"],

tests/test_aqt_multiple_circuits.py

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,38 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import os
15-
import numpy as np
1615
import random
1716

18-
from qiskit import QuantumCircuit
1917
from qiskit_scaleway import ScalewayProvider
2018

21-
22-
def _random_qiskit_circuit(size: int) -> QuantumCircuit:
23-
num_qubits = size
24-
num_gate = size
25-
26-
qc = QuantumCircuit(num_qubits)
27-
28-
for _ in range(num_gate):
29-
random_gate = np.random.choice(["unitary", "cx", "cy", "cz"])
30-
31-
if random_gate == "cx" or random_gate == "cy" or random_gate == "cz":
32-
control_qubit = np.random.randint(0, num_qubits)
33-
target_qubit = np.random.randint(0, num_qubits)
34-
35-
while target_qubit == control_qubit:
36-
target_qubit = np.random.randint(0, num_qubits)
37-
38-
getattr(qc, random_gate)(control_qubit, target_qubit)
39-
else:
40-
for q in range(num_qubits):
41-
random_gate = np.random.choice(["h", "x", "y", "z"])
42-
getattr(qc, random_gate)(q)
43-
44-
qc.measure_all()
45-
46-
return qc
19+
from qio.utils.circuit import random_square_qiskit_circuit
4720

4821

4922
def test_aqt_multiple_circuits():
@@ -66,10 +39,10 @@ def test_aqt_multiple_circuits():
6639
assert session_id is not None
6740

6841
try:
69-
qc1 = _random_qiskit_circuit(10)
70-
qc2 = _random_qiskit_circuit(12)
71-
qc3 = _random_qiskit_circuit(9)
72-
qc4 = _random_qiskit_circuit(10)
42+
qc1 = random_square_qiskit_circuit(10)
43+
qc2 = random_square_qiskit_circuit(12)
44+
qc3 = random_square_qiskit_circuit(9)
45+
qc4 = random_square_qiskit_circuit(10)
7346

7447
run_result = backend.run(
7548
[qc1, qc2, qc3, qc4],

0 commit comments

Comments
 (0)