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

(WIP) Add Sampler V2 #11264

Closed
wants to merge 63 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
0e428a9
Add EstimatorV2
ikkoham Nov 6, 2023
5564603
Merge branch 'main' into primitives/estimator-v2
ikkoham Nov 22, 2023
14c3ff3
Merge branch 'main' into primitives/estimator-v2
ikkoham Nov 27, 2023
4698d74
Update qiskit/primitives/base/base_estimator.py
ikkoham Nov 27, 2023
a074c82
Merge branch 'main' into primitives/estimator-v2
ikkoham Nov 28, 2023
a3d7643
Use PositiveInt for shots
ikkoham Nov 28, 2023
c18b209
improve type hint
ikkoham Nov 28, 2023
cc27a64
Update qiskit/primitives/containers/data_bin.py
ikkoham Nov 28, 2023
9d59318
remove _run and make run abstractmethod
ikkoham Nov 29, 2023
556aec2
Apply suggestions from code review
ikkoham Nov 29, 2023
2325224
fix from review comments
ikkoham Nov 29, 2023
200ef36
move test/python/primitives/containers
ikkoham Nov 29, 2023
a679f17
Merge branch 'main' into primitives/estimator-v2
ikkoham Nov 29, 2023
55122f4
Apply suggestions from code review
ikkoham Nov 29, 2023
9ffda4e
update docs
ikkoham Nov 29, 2023
8cfc8ff
rename task to pubs
ikkoham Nov 30, 2023
18e7395
Pubs -> Pub
ikkoham Nov 30, 2023
cd5b70f
make pydantic optional
ikkoham Dec 1, 2023
f075b4e
Apply suggestions from code review
ikkoham Dec 1, 2023
90447be
Apply suggestions from code review
ikkoham Dec 1, 2023
18053bd
fix lint
ikkoham Dec 1, 2023
998547e
type hint
ikkoham Dec 1, 2023
c3fd89a
fix type hint for python 3.8
ikkoham Dec 1, 2023
2bc2655
improve BindingsArray
ikkoham Dec 1, 2023
dfcc996
fix docs warning
ikkoham Dec 1, 2023
ce01d8f
Remove `slots=True` in dataclass usage.
ihincks Dec 1, 2023
199aeaa
update from review comments
ikkoham Dec 2, 2023
388d2bf
Update qiskit/primitives/containers/estimator_pub.py
ikkoham Dec 2, 2023
7777dea
Update qiskit/primitives/base/base_estimator.py
ikkoham Dec 2, 2023
5d320e9
Merge branch 'main' into primitives/estimator-v2
ikkoham Dec 4, 2023
5307922
Apply suggestions from code review
ikkoham Dec 19, 2023
bcdf842
lint
ikkoham Dec 19, 2023
b6d9376
Merge branch 'main' into primitives/estimator-v2
ikkoham Dec 21, 2023
48bddad
Merge branch 'main' into primitives/estimator-v2
ikkoham Jan 4, 2024
3066fb1
Merge branch 'main' into primitives/estimator-v2
ikkoham Jan 9, 2024
bbe64b2
WIP
ikkoham Nov 6, 2023
24f3b76
fix
ikkoham Nov 10, 2023
caafc92
Iterable is not subscriptable < 3.9
ikkoham Nov 10, 2023
4cd3329
refactor dataclass
ikkoham Nov 10, 2023
61303d7
fix type hint
ikkoham Nov 13, 2023
70ba4ab
fix lint
ikkoham Nov 13, 2023
cf38b65
EstimatorTask.parameter_values is optional
ikkoham Nov 14, 2023
c45ff3e
refactor bindings_array
ikkoham Nov 16, 2023
f240ff7
qiskit.primitives.containers
ikkoham Nov 16, 2023
b53c010
Add BaseSamplerV2 and StatevectorSampler
t-imamichi Nov 16, 2023
1519522
refactor
t-imamichi Nov 16, 2023
66bfc7b
fix bit order
t-imamichi Nov 17, 2023
834e24c
rebase EstimatorV2, update BitArray, and update StatevectorSampler
t-imamichi Nov 20, 2023
cc983c7
allow consecutive final measurements on the same qubit
t-imamichi Nov 20, 2023
4333b89
add BackendSamplerV2 tentatively
t-imamichi Nov 21, 2023
8aa0007
refactor
t-imamichi Nov 22, 2023
1e49b35
add test of bitarray
t-imamichi Nov 29, 2023
bd77bb4
update StatevectorSampler
t-imamichi Nov 29, 2023
d124ead
update BackendSampler
t-imamichi Nov 30, 2023
c6eeb3c
rename task with pub
t-imamichi Dec 1, 2023
1c601c0
update
t-imamichi Dec 5, 2023
2d10450
add sampler v2 tests
t-imamichi Dec 8, 2023
9bb6ad5
add tests of options
t-imamichi Dec 12, 2023
39e2dbe
add tests of metadata and multiple cregs
t-imamichi Dec 12, 2023
5b17cad
refactor BackendSamplerV2
t-imamichi Dec 12, 2023
f3fb4ca
update to allow no measure
t-imamichi Dec 13, 2023
a079fd4
update BackendSampler
t-imamichi Dec 13, 2023
2d6d2b5
lint
t-imamichi Dec 14, 2023
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
22 changes: 17 additions & 5 deletions qiskit/primitives/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
Estimator
BackendEstimator

EstimatorV2
===========

.. autosummary::
:toctree: ../stubs/

StatevectorEstimator

Sampler
=======

Expand All @@ -50,13 +58,17 @@

EstimatorResult
SamplerResult
PrimitiveResult
PubResult
"""

from .base import BaseEstimator
from .base import BaseSampler
from .backend_estimator import BackendEstimator
from .estimator import Estimator
from .base.estimator_result import EstimatorResult
from .backend_sampler import BackendSampler
from .sampler import Sampler
from .base import BaseEstimator, BaseSampler
from .base.estimator_result import EstimatorResult
from .base.sampler_result import SamplerResult
from .containers import BindingsArray, EstimatorPub, ObservablesArray, PrimitiveResult, PubResult
from .estimator import Estimator
from .sampler import Sampler
from .statevector_estimator import Estimator as StatevectorEstimator
from .statevector_sampler import Sampler as StatevectorSampler
2 changes: 1 addition & 1 deletion qiskit/primitives/backend_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Sampler implementation for an artibtrary Backend object."""
"""Sampler implementation for an arbitrary Backend object."""

from __future__ import annotations

Expand Down
270 changes: 270 additions & 0 deletions qiskit/primitives/backend_sampler_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Sampler implementation for an arbitrary Backend object."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union

import numpy as np
from numpy.typing import NDArray
from pydantic import Field

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.providers.backend import BackendV1, BackendV2
from qiskit.result import Result
from qiskit.transpiler.passmanager import PassManager

from .backend_estimator import _run_circuits
from .base import BaseSamplerV2
from .containers import (
BasePrimitiveOptions,
BasePrimitiveOptionsLike,
BitArray,
PrimitiveResult,
PubResult,
SamplerPub,
SamplerPubLike,
make_data_bin,
)
from .containers.bit_array import _min_num_bytes
from .containers.dataclasses import mutable_dataclass
from .primitive_job import PrimitiveJob


@mutable_dataclass
class ExecutionOptions(BasePrimitiveOptions):
"""Options for execution."""

shots: int = 1 # TODO: discuss the default number of shots
seed: Optional[Union[int, np.random.Generator]] = None


@mutable_dataclass
class Options(BasePrimitiveOptions):
"""Options for the primitives.

Args:
execution: Execution time options. See :class:`ExecutionOptions` for all available options.
"""

execution: ExecutionOptions = Field(default_factory=ExecutionOptions)
transpilation: Dict[str, Any] = Field(default_factory=dict)


@dataclass
class _MeasureInfo:
creg_name: str
num_bits: int
num_bytes: int
start: int


class BackendSampler(BaseSamplerV2):
"""A :class:`~.BaseSampler` implementation that provides an interface for
leveraging the sampler interface from any backend.

This class provides a sampler interface from any backend and doesn't do
any measurement mitigation, it just computes the probability distribution
from the counts. It facilitates using backends that do not provide a
native :class:`~.BaseSampler` implementation in places that work with
:class:`~.BaseSampler`, such as algorithms in :mod:`qiskit.algorithms`
including :class:`~.qiskit.algorithms.minimum_eigensolvers.SamplingVQE`.
However, if you're using a provider that has a native implementation of
:class:`~.BaseSampler`, it is a better choice to leverage that native
implementation as it will likely include additional optimizations and be
a more efficient implementation. The generic nature of this class
precludes doing any provider- or backend-specific optimizations.
"""

_options_class = Options
options: Options

def __init__(
self,
*,
backend: BackendV1 | BackendV2,
options: Optional[BasePrimitiveOptionsLike] = None,
pass_manager: PassManager | None = None,
skip_transpilation: bool = False,
):
"""Initialize a new BackendSampler

Args:
backend: Required: the backend to run the sampler primitive on
options: Default options.
pass_manager: An optional pass manager to use for the internal compilation
skip_transpilation: If this is set to True the internal compilation
of the input circuits is skipped and the circuit objects
will be directly executed when this objected is called.
Raises:
ValueError: If backend is not provided
"""
if options is None:
options = Options()
elif not isinstance(options, Options):
options = Options(**options)
super().__init__(options=options)
self._backend = backend
self._circuits = []
self._parameters = []
self._transpile_options = Options()
self._pass_manager = pass_manager
self._transpiled_circuits: list[QuantumCircuit] = []
self._skip_transpilation = skip_transpilation

@property
def transpiled_circuits(self) -> list[QuantumCircuit]:
"""
Transpiled quantum circuits.
Returns:
List of the transpiled quantum circuit
"""
return self._transpiled_circuits

@property
def backend(self) -> BackendV1 | BackendV2:
"""
Returns:
The backend which this sampler object based on
"""
return self._backend

@property
def transpile_options(self) -> Dict[str, Any]:
"""Return the transpiler options for transpiling the circuits."""
return self.options.transpilation

def set_transpile_options(self, **fields):
"""Set the transpiler options for transpiler.
Args:
**fields: The fields to update the options.
Returns:
self.
"""
self._options.transpilation.update(**fields)

def _transpile(self, circuits: List[QuantumCircuit]) -> None:
if self._skip_transpilation:
ret = circuits
elif self._pass_manager:
ret = self._pass_manager.run(circuits)
else:
from qiskit.compiler import transpile

ret = transpile(
circuits,
self.backend,
**self.options.transpilation,
)
self._transpiled_circuits = ret if isinstance(ret, list) else [ret]

def run(self, pubs: Iterable[SamplerPubLike]) -> PrimitiveJob[PrimitiveResult[PubResult]]:
job = PrimitiveJob(self._run, pubs)
job.submit()
return job

def _run(self, pubs: Iterable[SamplerPubLike]) -> PrimitiveResult[PubResult]:
coerced_pubs = [SamplerPub.coerce(pub) for pub in pubs]
for pub in coerced_pubs:
pub.validate()

shots = self.options.execution.shots

self._transpile([pub.circuit for pub in coerced_pubs])

results = []
for pub, circuit in zip(coerced_pubs, self._transpiled_circuits):
meas_info, max_num_bytes = _analyze_circuit(pub.circuit)
bound_circuits = pub.parameter_values.bind_all(circuit)
arrays = {
item.creg_name: np.zeros(
bound_circuits.shape + (shots, item.num_bytes), dtype=np.uint8
)
for item in meas_info
}
flatten_circuits = np.ravel(bound_circuits).tolist()
result_memory, _ = _run_circuits(
flatten_circuits, self._backend, memory=True, **self.options.execution.__dict__
)
memory_list = _prepare_memory(result_memory, max_num_bytes)

for samples, index in zip(memory_list, np.ndindex(*bound_circuits.shape)):
for item in meas_info:
ary = _samples_to_packed_array(samples, item.num_bits, item.start)
arrays[item.creg_name][index] = ary

data_bin_cls = make_data_bin(
[(item.creg_name, BitArray) for item in meas_info],
shape=bound_circuits.shape,
)
meas = {
item.creg_name: BitArray(arrays[item.creg_name], item.num_bits)
for item in meas_info
}
data_bin = data_bin_cls(**meas)
results.append(PubResult(data_bin, metadata={"shots": shots}))
return PrimitiveResult(results)


def _analyze_circuit(circuit: QuantumCircuit) -> Tuple[List[_MeasureInfo], int]:
meas_info = []
start = 0
for creg in circuit.cregs:
name = creg.name
num_bits = creg.size
meas_info.append(
_MeasureInfo(
creg_name=name,
num_bits=num_bits,
num_bytes=_min_num_bytes(num_bits),
start=start,
)
)
start += num_bits
return meas_info, _min_num_bytes(start)


def _prepare_memory(results: List[Result], num_bytes: int) -> NDArray[np.uint8]:
lst = []
for res in results:
for exp in res.results:
if hasattr(exp.data, "memory"):
data = b"".join(int(i, 16).to_bytes(num_bytes, "big") for i in exp.data.memory)
data = np.frombuffer(data, dtype=np.uint8).reshape(-1, num_bytes)
else:
# no measure in a circuit
data = np.zeros((exp.shots, num_bytes), dtype=np.uint8)
lst.append(data)
ary = np.array(lst, copy=False)
return np.unpackbits(ary, axis=-1, bitorder="big")


def _samples_to_packed_array(
samples: NDArray[np.uint8], num_bits: int, start: int
) -> NDArray[np.uint8]:
# samples of `Backend.run(memory=True)` will be the order of
# clbit_last, ..., clbit_1, clbit_0
# place samples in the order of clbit_start+num_bits-1, ..., clbit_start+1, clbit_start
if start == 0:
ary = samples[:, -start - num_bits :]
else:
ary = samples[:, -start - num_bits : -start]
# pad 0 in the left to align the number to be mod 8
# since np.packbits(bitorder='big') pads 0 to the right.
pad_size = -num_bits % 8
ary = np.pad(ary, ((0, 0), (pad_size, 0)), constant_values=0)
# pack bits in big endian order
ary = np.packbits(ary, axis=-1, bitorder="big")
return ary
4 changes: 2 additions & 2 deletions qiskit/primitives/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
Abstract base classes for primitives module.
"""

from .base_estimator import BaseEstimator
from .base_sampler import BaseSampler
from .base_estimator import BaseEstimator, BaseEstimatorV2
from .base_sampler import BaseSampler, BaseSamplerV2
from .estimator_result import EstimatorResult
from .sampler_result import SamplerResult
Loading