IndexError in _extract_bitstrings when measuring the same qubit twice on a QPU
Pre-Report Checklist
- [ ] I am running the latest versions of pyQuil and the Forest SDK
- [x] I checked to make sure that this bug has not already been reported
Issue Description
When running on a QPU, measuring the same qubit 2-or-more times results in an IndexError in pyquil.api._qpu._extract_bitstrings.
Measuring the same qubit multiple times is admittedly a slightly odd thing to do, since QPU programs are restricted to protoquil, i.e. all MEASURE instructions must come at the end of the program with no gates interleaved.
I only noticed this while staring at the implementation of _extract_bitstrings in order to port it to a lisp project that needs something similar.
The crux of the issue is that we compile a list of ro_sources for the program, and then use that in _extract_bitstrings to munge the results returned by the QPU into the expected shape. The docstring for _extract_bitstrings describes ro_sources like so:
:param ro_sources: Specification of the ro_sources, cf
:py:func:`pyquil.api._compiler._collect_classical_memory_write_locations`.
It is a list whose value ``(q, m)`` at index ``addr`` records that the ``m``-th
measurement of qubit ``q`` was measured into ``ro`` address ``addr``.
A value of `None` means nothing was measured into ``ro`` address ``addr``.
but then in the body of _extract_bitstrings we find
qubit, meas_idx = src
buf = buffers[f"q{qubit}"]
if buf.ndim == 1:
buf = buf.reshape((num_shots, 1))
bitstrings[:, col_idx] = buf[:, meas_idx]
so when buf.ndim == 1, buf is reshaped into (num_shots, 1), but if qubit was measured more than once, meas_idx might be > 0 resulting in IndexError.
For example, for the program given below, ro_sources will be [(0, 0), (0, 1)], but buffers["q0"] will be a 1-element array.
How to Reproduce
Run the following snipped on a QPU
Code Snippet
from pyquil import Program, get_qc
qc = get_qc('Aspen-4-2Q-G')
p = Program('''DECLARE ro BIT[2]
H 0
MEASURE 0 ro[0]
MEASURE 0 ro[1]
''')
exe = qc.compile(p, protoquil=True)
qc.run(exe)
Error Output
(venv) [ON QPU] [forest@8431-qmi ~]$ python repeated_ro.py
Traceback (most recent call last):
File "repeated_ro.py", line 12, in <module>
print(qc.run(exe))
File "/home/forest/.virtualenvs/venv/lib/python3.6/site-packages/pyquil/api/_error_reporting.py", line 251, in wrapper
val = func(*args, **kwargs)
File "/home/forest/.virtualenvs/venv/lib/python3.6/site-packages/pyquil/api/_quantum_computer.py", line 137, in run
return self.qam.run().wait().read_memory(region_name="ro")
File "/home/forest/.virtualenvs/venv/lib/python3.6/site-packages/pyquil/api/_error_reporting.py", line 251, in wrapper
val = func(*args, **kwargs)
File "/home/forest/.virtualenvs/venv/lib/python3.6/site-packages/pyquil/api/_qpu.py", line 228, in run
bitstrings = _extract_bitstrings(ro_sources, results)
File "/home/forest/.virtualenvs/venv/lib/python3.6/site-packages/pyquil/api/_qpu.py", line 71, in _extract_bitstrings
bitstrings[:, col_idx] = buf[:, meas_idx]
IndexError: index 1 is out of bounds for axis 1 with size 1
Environment Context
Operating System: QMI
Python Version (python -V): 3.6.8
Quilc Version (quilc --version): 1.16.1
QVM Version (qvm --version): 1.15.3
Python Environment Details (pip freeze or conda list):
antlr4-python3-runtime==4.7.2
attrs==19.3.0
backcall==0.1.0
bleach==3.1.0
certifi==2019.11.28
chardet==3.0.4
cycler==0.10.0
decorator==4.4.1
defusedxml==0.6.0
entrypoints==0.3
idna==2.8
immutables==0.6
importlib-metadata==1.5.0
ipykernel==5.1.4
ipython==7.11.1
ipython-genutils==0.2.0
jedi==0.16.0
Jinja2==2.11.0
json5==0.8.5
jsonschema==3.2.0
jupyter-client==5.3.4
jupyter-core==4.6.1
jupyterlab==1.2.6
jupyterlab-server==1.0.6
kiwisolver==1.1.0
MarkupSafe==1.1.1
matplotlib==3.1.2
mistune==0.8.4
msgpack==0.6.2
nbconvert==5.6.1
nbformat==5.0.4
networkx==2.4
notebook==6.0.3
numpy==1.18.1
pandas==1.0.0
pandocfilters==1.4.2
parso==0.6.0
pexpect==4.8.0
pickleshare==0.7.5
prometheus-client==0.7.1
prompt-toolkit==3.0.3
ptyprocess==0.6.0
Pygments==2.5.2
pyparsing==2.4.6
pyquil==2.17.0
pyrsistent==0.15.7
python-dateutil==2.8.1
python-rapidjson==0.9.1
pytz==2019.3
pyzmq==18.1.1
requests==2.22.0
rpcq==3.0.0
ruamel.yaml==0.16.7
ruamel.yaml.clib==0.2.0
scipy==1.4.1
seaborn==0.10.0
Send2Trash==1.5.0
six==1.14.0
terminado==0.8.3
testpath==0.4.4
tornado==6.0.3
tqdm==4.42.0
traitlets==4.3.3
urllib3==1.25.8
wcwidth==0.1.8
webencodings==0.5.1
zipp==2.1.0