|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
r"""
|
|
===================
|
|
Overview of Sampler
|
|
===================
|
|
|
|
Sampler class calculates probabilities or quasi-probabilities of bitstrings from quantum circuits.
|
|
|
|
A sampler is initialized with an empty parameter set. The sampler is used to
|
|
create a :class:`~qiskit.providers.JobV1`, via the :meth:`qiskit.primitives.Sampler.run()`
|
|
method. This method is called with the following parameters
|
|
|
|
* quantum circuits (:math:`\psi_i(\theta)`): list of (parameterized) quantum circuits.
|
|
(a list of :class:`~qiskit.circuit.QuantumCircuit` objects)
|
|
|
|
* parameter values (:math:`\theta_k`): list of sets of parameter values
|
|
to be bound to the parameters of the quantum circuits.
|
|
(list of list of float)
|
|
|
|
The method returns a :class:`~qiskit.providers.JobV1` object, calling
|
|
:meth:`qiskit.providers.JobV1.result()` yields a :class:`~qiskit.primitives.SamplerResult`
|
|
object, which contains probabilities or quasi-probabilities of bitstrings,
|
|
plus optional metadata like error bars in the samples.
|
|
|
|
Here is an example of how sampler is used.
|
|
|
|
.. code-block:: python
|
|
|
|
from qiskit.primitives import Sampler
|
|
from qiskit import QuantumCircuit
|
|
from qiskit.circuit.library import RealAmplitudes
|
|
|
|
# a Bell circuit
|
|
bell = QuantumCircuit(2)
|
|
bell.h(0)
|
|
bell.cx(0, 1)
|
|
bell.measure_all()
|
|
|
|
# two parameterized circuits
|
|
pqc = RealAmplitudes(num_qubits=2, reps=2)
|
|
pqc.measure_all()
|
|
pqc2 = RealAmplitudes(num_qubits=2, reps=3)
|
|
pqc2.measure_all()
|
|
|
|
theta1 = [0, 1, 1, 2, 3, 5]
|
|
theta2 = [0, 1, 2, 3, 4, 5, 6, 7]
|
|
|
|
# initialization of the sampler
|
|
sampler = Sampler()
|
|
|
|
# Sampler runs a job on the Bell circuit
|
|
job = sampler.run(circuits=[bell], parameter_values=[[]], parameters=[[]])
|
|
job_result = job.result()
|
|
print([q.binary_probabilities() for q in job_result.quasi_dists])
|
|
|
|
# Sampler runs a job on the parameterized circuits
|
|
job2 = sampler.run(
|
|
circuits=[pqc, pqc2],
|
|
parameter_values=[theta1, theta2],
|
|
parameters=[pqc.parameters, pqc2.parameters])
|
|
job_result = job2.result()
|
|
print([q.binary_probabilities() for q in job_result.quasi_dists])
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import abstractmethod
|
|
from collections.abc import Sequence
|
|
from copy import copy
|
|
from typing import Generic, TypeVar
|
|
|
|
from qiskit.circuit import QuantumCircuit
|
|
from qiskit.circuit.parametertable import ParameterView
|
|
from qiskit.providers import JobV1 as Job
|
|
|
|
from .base_primitive import BasePrimitive
|
|
|
|
T = TypeVar("T", bound=Job)
|
|
|
|
|
|
class BaseSampler(BasePrimitive, Generic[T]):
|
|
"""Sampler base class
|
|
|
|
Base class of Sampler that calculates quasi-probabilities of bitstrings from quantum circuits.
|
|
"""
|
|
|
|
__hash__ = None
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
options: dict | None = None,
|
|
):
|
|
"""
|
|
Args:
|
|
options: Default options.
|
|
"""
|
|
self._circuits = []
|
|
self._parameters = []
|
|
super().__init__(options)
|
|
|
|
def run(
|
|
self,
|
|
circuits: QuantumCircuit | Sequence[QuantumCircuit],
|
|
parameter_values: Sequence[float] | Sequence[Sequence[float]] | None = None,
|
|
**run_options,
|
|
) -> T:
|
|
"""Run the job of the sampling of bitstrings.
|
|
|
|
Args:
|
|
circuits: One of more circuit objects.
|
|
parameter_values: Parameters to be bound to the circuit.
|
|
run_options: Backend runtime options used for circuit execution.
|
|
|
|
Returns:
|
|
The job object of the result of the sampler. The i-th result corresponds to
|
|
``circuits[i]`` evaluated with parameters bound as ``parameter_values[i]``.
|
|
|
|
Raises:
|
|
ValueError: Invalid arguments are given.
|
|
"""
|
|
|
|
circuits = self._validate_circuits(circuits)
|
|
parameter_values = self._validate_parameter_values(
|
|
parameter_values,
|
|
default=[()] * len(circuits),
|
|
)
|
|
|
|
|
|
self._cross_validate_circuits_parameter_values(circuits, parameter_values)
|
|
|
|
|
|
run_opts = copy(self.options)
|
|
run_opts.update_options(**run_options)
|
|
|
|
return self._run(
|
|
circuits,
|
|
parameter_values,
|
|
**run_opts.__dict__,
|
|
)
|
|
|
|
@abstractmethod
|
|
def _run(
|
|
self,
|
|
circuits: tuple[QuantumCircuit, ...],
|
|
parameter_values: tuple[tuple[float, ...], ...],
|
|
**run_options,
|
|
) -> T:
|
|
raise NotImplementedError("The subclass of BaseSampler must implment `_run` method.")
|
|
|
|
|
|
@classmethod
|
|
def _validate_circuits(
|
|
cls,
|
|
circuits: Sequence[QuantumCircuit] | QuantumCircuit,
|
|
) -> tuple[QuantumCircuit, ...]:
|
|
circuits = super()._validate_circuits(circuits)
|
|
for i, circuit in enumerate(circuits):
|
|
if circuit.num_clbits == 0:
|
|
raise ValueError(
|
|
f"The {i}-th circuit does not have any classical bit. "
|
|
"Sampler requires classical bits, plus measurements "
|
|
"on the desired qubits."
|
|
)
|
|
return circuits
|
|
|
|
@property
|
|
def circuits(self) -> tuple[QuantumCircuit, ...]:
|
|
"""Quantum circuits to be sampled.
|
|
|
|
Returns:
|
|
The quantum circuits to be sampled.
|
|
"""
|
|
return tuple(self._circuits)
|
|
|
|
@property
|
|
def parameters(self) -> tuple[ParameterView, ...]:
|
|
"""Parameters of quantum circuits.
|
|
|
|
Returns:
|
|
List of the parameters in each quantum circuit.
|
|
"""
|
|
return tuple(self._parameters)
|
|
|