Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add seed list feature #197

Merged
merged 4 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ unreleased
* Update qiskit-ibm-runtime version to 0.13.0.
* Update qiskit-aer version to 0.13.0.
* Introduce dependency on qiskit-algorithms.
* Add possibility to submit list of seeds to backend in batch submission

0.45.0 (October 2023)
---------------------
Expand Down
9 changes: 5 additions & 4 deletions pytket/extensions/qiskit/backends/aer.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ def process_circuits(
circuits: Sequence[Circuit],
n_shots: Union[None, int, Sequence[Optional[int]]] = None,
valid_check: bool = True,
**kwargs: KwargTypes,
**kwargs: Union[int, float, str, None, Sequence[Optional[int]]],
) -> List[ResultHandle]:
circuits = list(circuits)
n_shots_list = Backend._get_n_shots_as_list(
Expand All @@ -258,11 +258,13 @@ def process_circuits(
circuits = noisy_circuits

handle_list: List[Optional[ResultHandle]] = [None] * len(circuits)
circuit_batches, batch_order = _batch_circuits(circuits, n_shots_list)
circuit_batches, batch_order = _batch_circuits(
circuits, n_shots_list, kwargs.get("seed")
)

replace_implicit_swaps = self.supports_state or self.supports_unitary

for (n_shots, batch), indices in zip(circuit_batches, batch_order):
for (n_shots, seed, batch), indices in zip(circuit_batches, batch_order):
qcs = []
for tkc in batch:
qc = tk_to_qiskit(tkc, replace_implicit_swaps)
Expand All @@ -275,7 +277,6 @@ def process_circuits(
if self._needs_transpile:
qcs = transpile(qcs, self._qiskit_backend)

seed = cast(Optional[int], kwargs.get("seed"))
job = self._qiskit_backend.run(
qcs,
shots=n_shots,
Expand Down
8 changes: 5 additions & 3 deletions pytket/extensions/qiskit/backends/ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ def process_circuits(
circuits: Sequence[Circuit],
n_shots: Union[None, int, Sequence[Optional[int]]] = None,
valid_check: bool = True,
**kwargs: KwargTypes,
**kwargs: Union[int, float, str, None, Sequence[Optional[int]]],
) -> List[ResultHandle]:
"""
See :py:meth:`pytket.backends.Backend.process_circuits`.
Expand All @@ -464,13 +464,15 @@ def process_circuits(
)

handle_list: List[Optional[ResultHandle]] = [None] * len(circuits)
circuit_batches, batch_order = _batch_circuits(circuits, n_shots_list)
circuit_batches, batch_order = _batch_circuits(
circuits, n_shots_list, kwargs.get("seed")
)

postprocess = kwargs.get("postprocess", False)
simplify_initial = kwargs.get("simplify_initial", False)

batch_id = 0 # identify batches for debug purposes only
for (n_shots, batch), indices in zip(circuit_batches, batch_order):
for (n_shots, _, batch), indices in zip(circuit_batches, batch_order):
for chunk in itertools.zip_longest(
*([iter(zip(batch, indices))] * self._max_per_job)
):
Expand Down
62 changes: 47 additions & 15 deletions pytket/extensions/qiskit/backends/ibm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"""

import itertools
from typing import Collection, Optional, Sequence, Tuple, List, TYPE_CHECKING
from typing import Collection, Optional, Sequence, Tuple, List, TYPE_CHECKING, Union

import numpy as np

Expand All @@ -39,8 +39,10 @@


def _batch_circuits(
circuits: Sequence["Circuit"], n_shots: Sequence[Optional[int]]
) -> Tuple[List[Tuple[Optional[int], List["Circuit"]]], List[List[int]]]:
circuits: Sequence["Circuit"],
n_shots: Sequence[Optional[int]],
seed: Union[int, float, str, None, Sequence[Optional[int]]],
) -> Tuple[List[Tuple[Optional[int], Optional[int], List["Circuit"]]], List[List[int]]]:
"""
Groups circuits into sets of circuits with the same number of shots.

Expand All @@ -50,17 +52,47 @@ def _batch_circuits(
:type circuits: Sequence[Circuit]
:param n_shots: Number of shots for each circuit.
:type n_shots: Sequence[int]
:param seed: RNG Seed for each circuit.
:type seed: Union[int, None, Sequence[Optional[int]]]
"""
# take care of None entries
n_shots_int = list(map(lambda x: x if x is not None else -1, n_shots))

order: Collection[int] = np.argsort(n_shots_int)
batches: List[Tuple[Optional[int], List["Circuit"]]] = [
(n, [circuits[i] for i in indices])
for n, indices in itertools.groupby(order, key=lambda i: n_shots[i])
]
batch_order: List[List[int]] = [
list(indices)
for n, indices in itertools.groupby(order, key=lambda i: n_shots[i])
]

n_seeds: list[Optional[int]] = []
if type(seed) == list:
n_seeds = seed
elif type(seed) == int:
n_seeds = [seed for _ in range(len(circuits))]
elif seed == None:
n_seeds = [None for _ in range(len(circuits))]
else:
raise ValueError(
f"""unknown seed type, type should be None,
int, or list[int], type found {type(seed)}"""
)

assert len(n_seeds) == len(n_shots)
assert len(n_seeds) == len(circuits)

batches: List[Tuple[Optional[int], Optional[int], List["Circuit"]]] = []
batch_order: List[List[int]] = []

if all(seed == n_seeds[0] for seed in n_seeds):
# take care of None entries
n_shots_int = list(map(lambda x: x if x is not None else -1, n_shots))

order: Collection[int] = np.argsort(n_shots_int)

batches = [
(n, n_seeds[0], [circuits[i] for i in indices])
for n, indices in itertools.groupby(order, key=lambda i: n_shots[i])
]
batch_order = [
list(indices)
for n, indices in itertools.groupby(order, key=lambda i: n_shots[i])
]
else:

for i in range(len(circuits)):
batches.append((n_shots[i], n_seeds[i], [circuits[i]]))
batch_order.append([i])

return batches, batch_order
10 changes: 6 additions & 4 deletions pytket/extensions/qiskit/backends/ibmq_emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ def process_circuits(
circuits: Sequence[Circuit],
n_shots: Union[None, int, Sequence[Optional[int]]] = None,
valid_check: bool = True,
**kwargs: KwargTypes,
**kwargs: Union[int, float, str, None, Sequence[Optional[int]]],
) -> List[ResultHandle]:
"""
See :py:meth:`pytket.backends.Backend.process_circuits`.
Expand All @@ -139,10 +139,12 @@ def process_circuits(
)

handle_list: List[Optional[ResultHandle]] = [None] * len(circuits)
circuit_batches, batch_order = _batch_circuits(circuits, n_shots_list)
circuit_batches, batch_order = _batch_circuits(
circuits, n_shots_list, kwargs.get("seed")
)

batch_id = 0 # identify batches for debug purposes only
for (n_shots, batch), indices in zip(circuit_batches, batch_order):
for (n_shots, seed, batch), indices in zip(circuit_batches, batch_order):
for chunk in itertools.zip_longest(
*([iter(zip(batch, indices))] * self._ibmq._max_per_job)
):
Expand Down Expand Up @@ -173,7 +175,7 @@ def process_circuits(
options.resilience_level = 0
options.execution.shots = n_shots
options.simulator.noise_model = self._noise_model
options.seed_simulator = kwargs.get("seed")
options.seed_simulator = seed
sampler = Sampler(session=self._session, options=options)
job = sampler.run(circuits=qcs)
job_id = job.job_id()
Expand Down
58 changes: 58 additions & 0 deletions tests/backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,64 @@ def test_nshots_batching(perth_backend: IBMQBackend) -> None:
backend._MACHINE_DEBUG = False


@pytest.mark.skipif(skip_remote_tests, reason=REASON)
def test_nshots_nseeds_batching(perth_backend: IBMQBackend) -> None:
backend = perth_backend
backend._MACHINE_DEBUG = True
try:
c1 = Circuit(2, 2).H(0).CX(0, 1).measure_all()
c2 = Circuit(2, 2).Rx(0.5, 0).CX(0, 1).measure_all()
c3 = Circuit(2, 2).H(1).CX(0, 1).measure_all()
c4 = Circuit(2, 2).Rx(0.5, 0).CX(0, 1).CX(1, 0).measure_all()
cs = [c1, c2, c3, c4]
n_shots = [10, 12, 10, 13]
n_seeds = [10, 10, 10, 10]
cs = backend.get_compiled_circuits(cs)
handles = backend.process_circuits(cs, n_shots=n_shots, seed=n_seeds)

from pytket.extensions.qiskit.backends.ibm import _DEBUG_HANDLE_PREFIX

assert all(
cast(str, hand[0]) == _DEBUG_HANDLE_PREFIX + suffix
for hand, suffix in zip(
handles,
[f"{(10, 0)}", f"{(12, 1)}", f"{(10, 0)}", f"{(13, 2)}"],
)
)
finally:
# ensure shared backend is reset for other tests
backend._MACHINE_DEBUG = False


@pytest.mark.skipif(skip_remote_tests, reason=REASON)
def test_nshots_nseeds_batching_ii(perth_backend: IBMQBackend) -> None:
backend = perth_backend
backend._MACHINE_DEBUG = True
try:
c1 = Circuit(2, 2).H(0).CX(0, 1).measure_all()
c2 = Circuit(2, 2).Rx(0.5, 0).CX(0, 1).measure_all()
c3 = Circuit(2, 2).H(1).CX(0, 1).measure_all()
c4 = Circuit(2, 2).Rx(0.5, 0).CX(0, 1).CX(1, 0).measure_all()
cs = [c1, c2, c3, c4]
n_shots = [10, 12, 10, 13]
n_seeds = [10, 11, 12, 13]
cs = backend.get_compiled_circuits(cs)
handles = backend.process_circuits(cs, n_shots=n_shots, seed=n_seeds)

from pytket.extensions.qiskit.backends.ibm import _DEBUG_HANDLE_PREFIX

assert all(
cast(str, hand[0]) == _DEBUG_HANDLE_PREFIX + suffix
for hand, suffix in zip(
handles,
[f"{(10, 0)}", f"{(12, 1)}", f"{(10, 2)}", f"{(13, 3)}"],
)
)
finally:
# ensure shared backend is reset for other tests
backend._MACHINE_DEBUG = False


@pytest.mark.flaky(reruns=3, reruns_delay=10)
@pytest.mark.skipif(skip_remote_tests, reason=REASON)
def test_nshots(perth_emulator_backend: IBMQEmulatorBackend) -> None:
Expand Down