Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
5.1k stars 2.34k forks source link

`StatevectorSampler` always samples the same outcomes when `seed` is an integer #13047

Open timmintam opened 1 month ago

timmintam commented 1 month ago

Environment

What is happening?

Suppose you have a StatevectorSampler with the seed set to an integer. Then if you run the same circuit multiple times, it will always sample the same outcomes.

The same issue probably affects StatevectorEstimator.

How can we reproduce the issue?

from qiskit.circuit import QuantumCircuit
from qiskit.primitives.statevector_sampler import StatevectorSampler

qc = QuantumCircuit(1)
qc.h(0)
qc.measure_all()

sampler = StatevectorSampler(seed=0)
job = sampler.run(pubs=[qc, qc], shots=5)
result = job.result()
print("1st run, 1st pub:", result[0].data.meas.get_bitstrings())
print("1st run, 2nd pub:", result[1].data.meas.get_bitstrings())

job = sampler.run(pubs=[qc], shots=5)
result = job.result()
print("2nd run, 1st pub:", result[0].data.meas.get_bitstrings())

Output:

1st run, 1st pub: ['1', '0', '0', '0', '1']
1st run, 2nd pub: ['1', '0', '0', '0', '1']
2nd run, 1st pub: ['1', '0', '0', '0', '1']

What should happen?

It should give different outcomes for each pub and for each call to the run method, even if the circuits are the same. If you set the seed to a numpy.random.Generator it has the expected behavior. For instance keeping the code snippet from above but only changing the seed as

from numpy.random import default_rng
...
sampler = StatevectorSampler(seed=default_rng(0))
...

then it will output:

1st run, 1st pub: ['1', '0', '0', '0', '1']
1st run, 2nd pub: ['1', '1', '1', '1', '1']
2nd run, 1st pub: ['1', '0', '1', '0', '1']

Any suggestions?

Currently the StatevectorSampler class stores the private attribute _seed as it is (either Generator, int or None). The run method iterates over the circuits. Inside this for loop, it fixes the seed of the statevector, which transforms the integer seed into a Generator when needed. It means the exact same Generator is instantiated each single time. Instead we could transform an integer seed into a Generator at the initiliazation of StatevectorSampler objects, such that the attribute _seed is either a Generator or None. For instance:

class StatevectorSampler(BaseSamplerV2):
...
    def __init__(self, *, default_shots: int = 1024, seed: np.random.Generator | int | None = None):
        ...
        self._seed = default_rng(seed) if isinstance(seed, int) else seed
ElePT commented 1 month ago

Good catch @timmintam, I think you are right and the generator should be set in the init, not every time users call run. And this issue extends to the StatevectorEstimator too.

t-imamichi commented 1 month ago

As for StatevectorEstimator, it is an intentional behavior to fix #12519. See #12862 for details.

t-imamichi commented 1 month ago

I recalled that I first implemented StatevectorSampler https://github.com/Qiskit/qiskit/pull/11566#event-11509453112 in the same way as proposed in this issue. https://github.com/Qiskit/qiskit/blob/e5f8cdbd9ebeaad2769144faf617d4fabcd52488/qiskit/primitives/statevector_sampler.py#L58-L67 But, I modified it in the current way to apply @chriseclectic's suggestion https://github.com/Qiskit/qiskit/pull/11566#pullrequestreview-1827508583 So, we need to discuss the int seed behavior with @chriseclectic.