-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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 Statevector-based SamplerV2 #11566
Conversation
cf81d5e
to
378bbf1
Compare
Pull Request Test Coverage Report for Build 7571175775Warning: This coverage report may be inaccurate.We've detected an issue with your CI configuration that might affect the accuracy of this pull request's coverage report.
💛 - Coveralls |
b30fd58
to
8a2425d
Compare
The file names and class names are tentative. |
I'm wondering |
8a2425d
to
b23b4e0
Compare
One or more of the the following people are requested to review this:
|
Co-authored-by: Ian Hincks <ian.hincks@gmail.com>
b23b4e0
to
e5f8cdb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, couple questions and suggestions. The only major change I would say is to change the seed behavior to give deterministic output to every run call with the same input pubs.
qreg_indices: list[int] | ||
|
||
|
||
class Sampler(BaseSamplerV2): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to call it Sampler
or StatevectorSampler
? In 11227 the reference estimator is called StatevectorEstimator
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I slightly prefer StatevectorSampler
, but more importantly, they should have the same convention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple implementation of :class:`BaseSamplerV2` with Statevector. | ||
""" | ||
|
||
_DEFAULT_SHOTS: int = 512 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_DEFAULT_SHOTS: int = 512 |
I would replace this with init kwarg
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggested not putting it as a kwarg to discourage users from assuming that constructors are the "right" place to set shots. If it is placed in the constructor, consider calling it "default_shots". IMO, its fine right here as a class atribute, but if you both prefer it in the constructor, please go ahead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is the original Ian's suggestion #11566 (comment), JFYI.
I made default_shots
kwarg.
if isinstance(self._seed, np.random.Generator): | ||
self._rng = self._seed | ||
else: | ||
self._rng = np.random.default_rng(self._seed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can seed a default_rng
with another generator so this can always be np.random.default_rng(seed)
even when seed is a generator. Eg
rng1 = np.random.default_rng(10)
rng2 = np.random.default_rng(rng1)
rng2.integers(10, size=10)
and this is the same as using a fixed seed for rng2. Hence you can just have
if isinstance(self._seed, np.random.Generator): | |
self._rng = self._seed | |
else: | |
self._rng = np.random.default_rng(self._seed) | |
self._rng = np.random.default_rng(self._seed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be deterministic, we don't need _rng
and I put _seed
directly to Statevector
.
final_state = Statevector(bound_circuit_to_instruction(bound_circuit)) | ||
final_state.seed(self._rng) | ||
if qargs: | ||
samples = final_state.sample_memory(shots=pub.shots, qargs=qargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO For the future: we should add a sample
method to Statevector (or make a helper function here) that directly samples in the BitArray format rather than constructing bit strings and converting them which is going to be more inefficient.
self.assertEqual(len(result), 1) | ||
self._assert_allclose(result[0].data.meas, np.array({1: self._shots})) | ||
|
||
def test_circuit_with_multiple_cregs(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be good to also add a test where there are aliased registers, which can happen if you compose a circuit with conditionals into another circuit with a flat register. Eg for a bell teleportation like one
q = QuantumRegister(3, 'q')
c1 = ClassicalRegister(1, 'c1')
c2 = ClassicalRegister(1, 'c2')
qc = QuantumCircuit(q, c1, c2)
qc.h(2)
qc.cx(2, 1)
qc.cx(0, 1)
qc.h(0)
qc.measure(0, c1)
qc.measure(1, c2)
qc.z(2).c_if(c1, 1)
qc.x(2).c_if(c2, 1)
qc.draw()
qc2 = QuantumCircuit(5, 5)
qc2.compose(qc, [0, 2, 3], [2, 4], inplace=True)
qc2.cregs
qc2 will have cregs
[ClassicalRegister(5, 'c'),
ClassicalRegister(1, 'c4'),
ClassicalRegister(1, 'c5')]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that register alias renamed register names ('c1' -> 'c4', 'c2' -> 'c5' in your example).
Is it possible to keep the register names?
Here is a obvious case.
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
q = QuantumRegister(3, 'q')
c1 = ClassicalRegister(1, 'c123456')
c2 = ClassicalRegister(1, 'ccccccc')
qc = QuantumCircuit(q, c1, c2)
qc.h(2)
qc.cx(2, 1)
qc.cx(0, 1)
qc.h(0)
qc.measure(0, c1)
qc.measure(1, c2)
qc.z(2).c_if(c1, 1)
qc.x(2).c_if(c2, 1)
print(qc.cregs)
# [ClassicalRegister(1, 'c123456'), ClassicalRegister(1, 'ccccccc')]
qc2 = QuantumCircuit(5, 5)
qc2.compose(qc, [0, 2, 3], [2, 4], inplace=True)
print(qc2.cregs)
# [ClassicalRegister(5, 'c'), ClassicalRegister(1, 'c0'), ClassicalRegister(1, 'c1')]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyways, I added aliased registers to databin though they are renamed.
qreg_indices: list[int] | ||
|
||
|
||
class Sampler(BaseSamplerV2): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I slightly prefer StatevectorSampler
, but more importantly, they should have the same convention.
Simple implementation of :class:`BaseSamplerV2` with Statevector. | ||
""" | ||
|
||
_DEFAULT_SHOTS: int = 512 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggested not putting it as a kwarg to discourage users from assuming that constructors are the "right" place to set shots. If it is placed in the constructor, consider calling it "default_shots". IMO, its fine right here as a class atribute, but if you both prefer it in the constructor, please go ahead.
081a63c
to
328cf06
Compare
Co-authored-by: Ian Hincks <ian.hincks@gmail.com> Co-authored-by: Christopher J. Wood <cjwood@us.ibm.com>
328cf06
to
077714c
Compare
I think BitArray cannot support |
creg = loc.registers[0] | ||
qbit = circuit.find_bit(item.qubits[0]).index | ||
if cbit in active_cbits and qbit in active_qubits: | ||
mapping[creg] = qbit | ||
for creg in loc.registers: | ||
mapping[creg] = qbit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: This change supports aliased classical registers.
meas1 = result1[0].data.meas | ||
result2 = sampler.run([bell], shots=self._shots).result() | ||
meas2 = result2[0].data.meas | ||
self._assert_allclose(meas1, meas2, rtol=0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: Let rtol=0
to check two results are exactly same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
Edit: I saw your comment about shots=0 after reading through the files and approving. I agree we should eliminate the possibility that shots=0 in the SamplerPub.
Thanks. I updated this PR to raise ValueError with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR tracks |
Yes, in fact I think #11529 could have been technically sufficient, but it's nice to have a reference implementation here, too. |
* Add Statevector-based BaseSamplerV2 implementation Co-authored-by: Ian Hincks <ian.hincks@gmail.com> * Apply suggestions from code reivew Co-authored-by: Ian Hincks <ian.hincks@gmail.com> Co-authored-by: Christopher J. Wood <cjwood@us.ibm.com> * Apply suggestions from code review * shots should be positive * add tests of zero shots --------- Co-authored-by: Ian Hincks <ian.hincks@gmail.com> Co-authored-by: Christopher J. Wood <cjwood@us.ibm.com>
Summary
Add Statevector-based Sampler V2 based on BaseSamplerV2 #11529.
This uses ongoing PRs #11543 #11552 and #11529 and we need to wait for them to be merged.This PR will be rebased once they are merged.
previous version #11264
Details and comments