Skip to content

Commit

Permalink
Update: Remove QuantumComputer.run_and_measure()
Browse files Browse the repository at this point in the history
  • Loading branch information
ameyer-rigetti committed Jun 4, 2021
1 parent 8668f29 commit 6346f57
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 195 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ Changelog
- Dropped `gates_in_isa` and refactored as an internal function for preparing a list of `pyquil.Gate`'s that the user may use to initialize a `NoiseModel` based on the underlying `CompilerISA`.

- `get_qc` raises `ValueError` when the user passes a QCS quantum processor name and `noisy=True`.

- `QuantumComputer.run_and_measure()` has been removed. Instead, add explicit `MEASURE` instructions to programs and use
`QuantumComputer.compile()` along with `QuantumComputer.run()` to compile and execute.

### Bugfixes

Expand Down
1 change: 0 additions & 1 deletion docs/source/apidocs/quantum_computer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ Quantum Computer
~QuantumComputer.run
~QuantumComputer.calibrate
~QuantumComputer.experiment
~QuantumComputer.run_and_measure
~QuantumComputer.run_symmetrized_readout
~QuantumComputer.qubits
~QuantumComputer.qubit_topology
Expand Down
46 changes: 5 additions & 41 deletions docs/source/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -176,40 +176,7 @@ using ``ro`` to store measured readout results. Check out :ref:`parametric_compi
Measurement
~~~~~~~~~~~

There are several ways you can handle measurements in your program. We will start with the simplest method -- letting
the ``QuantumComputer`` abstraction do it for us.

.. code:: python
from pyquil import Program, get_qc
from pyquil.gates import H, CNOT
# Get our QuantumComputer instance, with a Quantum Virutal Machine (QVM) backend
qc = get_qc("8q-qvm")
# Construct a simple Bell State
p = Program(H(0), CNOT(0, 1))
results = qc.run_and_measure(p, trials=10)
print(results)
.. parsed-literal::
{0: array([1, 1, 0, 1, 0, 0, 1, 1, 0, 1]),
1: array([1, 1, 0, 1, 0, 0, 1, 1, 0, 1]),
2: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
3: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
4: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
5: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
6: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
7: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])}
The method ``.run_and_measure`` will handle declaring memory for readout results, adding ``MEASURE`` instructions for each
qubit in the QVM, telling the QVM how many trials to run, running and returning the measurement results.

You might sometimes want finer grained control. In this case, we're probably only interested in the results on
qubits 0 and 1, but ``.run_and_measure`` returns the results for all eight qubits in the QVM. We can change our program
to be more particular about what we want.
We can use ``MEASURE`` instructions to measure particular qubits in a program:

.. code:: python
Expand Down Expand Up @@ -237,7 +204,7 @@ use the compiler to :ref:`re-index <rewiring>` your qubits):
.. note::

The QPU can only handle ``MEASURE`` final programs. You can't operate gates after measurements.
The QPU can only handle ``MEASURE`` instructions as final instructions. You can't operate gates after measurements.

Specifying the number of trials
-------------------------------
Expand All @@ -246,14 +213,11 @@ Quantum computing is inherently probabilistic. We often have to repeat the same
results we need. Sometimes we expect the results to all be the same, such as when we apply no gates, or only an ``X``
gate. When we prepare a superposition state, we expect probabilistic outcomes, such as a 50% probability measuring 0 or 1.

The number of `shots` (also called `trials`) is the number of times to execute a program at once.
The number of shots (also called "trials") is the number of times to execute a program at once.
This determines the length of the results that are returned.

As we saw above, the ``.run_and_measure`` method of the ``QuantumComputer`` object can handle multiple executions of a program.
If you would like more explicit control for representing multi-shot execution, another way to do this is
with ``.wrap_in_numshots_loop``. This puts the number of shots to be run in the representation of the program itself,
as opposed to in the arguments list of the execution method itself. Below, we specify that our program should
be executed 1000 times.
If you would like to perform multi-shot execution, you can use ``.wrap_in_numshots_loop``. Below, we specify that our
program should be executed 1000 times:

.. code:: python
Expand Down
2 changes: 1 addition & 1 deletion docs/source/exercises.rst
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,4 @@ Inspecting the results, we see that no matter what Picard does, Q will always wi
.. [1] https://link.aps.org/doi/10.1103/PhysRevLett.82.1052
.. [2] https://arxiv.org/abs/quant-ph/0611234
.. [3] See more: :ref:`basics`
.. [4] More about measurements and ``run_and_measure``: :ref:`measurement`
.. [4] More about measurements: :ref:`measurement`
50 changes: 5 additions & 45 deletions docs/source/qvm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,8 @@ For more information about creating and adding your own noise models, check out
Methods
-------

Now that you have your ``qc``, there's a lot you can do with it. Most users will want to use ``compile``, ``run`` or
``run_and_measure``, and ``qubits`` very regularly. The general flow of use would look like this:
Now that you have your ``qc``, there's a lot you can do with it. Most users will want to use ``compile``, ``run``, and
``qubits`` very regularly. The general flow of use would look like this:

.. code:: python
Expand All @@ -295,48 +295,11 @@ Now that you have your ``qc``, there's a lot you can do with it. Most users will
In addition to a running QVM server, you will need a running ``quilc`` server to compile your program. Setting
up both of these is very easy, as explained :ref:`here <server>`.


The ``.run_and_measure(...)`` method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This is the most high level way to run your program. With this method, you are **not** responsible for compiling your program
before running it, nor do you have to specify any ``MEASURE`` instructions; all qubits will get measured.

.. code:: python
from pyquil import Program, get_qc
from pyquil.gates import X
qc = get_qc("8q-qvm")
p = Program(X(0))
results = qc.run_and_measure(p, trials=5)
print(results)
``trials`` specifies how many times to run this program. Let's see our results:

.. parsed-literal::
{0: array([1, 1, 1, 1, 1]),
1: array([0, 0, 0, 0, 0]),
2: array([0, 0, 0, 0, 0]),
3: array([0, 0, 0, 0, 0]),
4: array([0, 0, 0, 0, 0]),
5: array([0, 0, 0, 0, 0]),
6: array([0, 0, 0, 0, 0]),
7: array([0, 0, 0, 0, 0])}
The return value is a dictionary from qubit index to results for all trials.
Every qubit in the lattice is measured for you, and as expected, qubit 0 has been flipped to the excited state
for each trial.

The ``.run(...)`` method
^^^^^^^^^^^^^^^^^^^^^^^^

The lower-level ``.run(...)`` method gives you more control over how you want to build and compile your program than
``.run_and_measure`` does. **You are responsible for compiling your program before running it.**
The above program would be written in this way to execute with ``run``:
When using the ``.run(...)`` method, **you are responsible for compiling your program before running it.**
For example:

.. code:: python
Expand All @@ -353,12 +316,9 @@ The above program would be written in this way to execute with ``run``:
p.wrap_in_numshots_loop(5)
executable = qc.compile(p)
bitstrings = qc.run(executable) # .run takes in a compiled program, unlike .run_and_measure
bitstrings = qc.run(executable) # .run takes in a compiled program
print(bitstrings)
By specifying ``MEASURE`` ourselves, we will only get the results that we are interested in. To be completely equivalent
to the previous example, we would have to measure all eight qubits.

The results returned is a *list of lists of integers*. In the above case, that's

.. parsed-literal::
Expand Down
43 changes: 28 additions & 15 deletions docs/source/start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,12 @@ Import a few things from pyQuil:
from pyquil import Program, get_qc
from pyquil.gates import *
from pyquil.quilbase import Declare
The :py:class:`~pyquil.quil.Program` object allows us to build up a Quil program. :py:func:`~pyquil.get_qc` connects us to a
The :py:class:`~pyquil.Program` object allows us to build up a Quil program. :py:func:`~pyquil.get_qc` connects us to a
:py:class:`~pyquil.api.QuantumComputer` object, which specifies what our program should run on (see: :ref:`qvm`). We've also imported all (``*``)
gates from the ``pyquil.gates`` module, which allows us to add operations to our program (:ref:`basics`).
gates from the ``pyquil.gates`` module, which allows us to add operations to our program (:ref:`basics`). :py:class:`~pyquil.quilbase.Declare`
allows us to declare classical memory regions.

.. note::

Expand All @@ -281,21 +283,34 @@ gates from the ``pyquil.gates`` module, which allows us to add operations to our
.. code:: python
from pyquil import get_qc, Program
from pyquil.gates import CNOT, Z
from pyquil.gates import CNOT, Z, MEASURE
from pyquil.api import local_forest_runtime
from pyquil.quilbase import Declare
prog = Program(Z(0), CNOT(0, 1))
prog = Program(
Declare("ro", "BIT", 2),
Z(0),
CNOT(0, 1),
MEASURE(0, ("ro", 0)),
MEASURE(1, ("ro", 1)),
).wrap_in_numshots_loop(10)
with local_forest_runtime():
qvm = get_qc('9q-square-qvm')
results = qvm.run_and_measure(prog, trials=10)
results = qvm.run(qvm.compile(prog)))
Next, let's construct our Bell State.

.. code:: python
# construct a Bell State program
p = Program(H(0), CNOT(0, 1))
p = Program(
Declare("ro", "BIT", 2),
H(0),
CNOT(0, 1),
MEASURE(0, ("ro", 0)),
MEASURE(1, ("ro", 1)),
).wrap_in_numshots_loop(10)
We've accomplished this by driving qubit 0 into a superposition state (that's what the "H" gate does), and then creating
an entangled state between qubits 0 and 1 (that's what the "CNOT" gate does). Finally, we'll want to run our program:
Expand All @@ -304,22 +319,20 @@ an entangled state between qubits 0 and 1 (that's what the "CNOT" gate does). Fi
# run the program on a QVM
qc = get_qc('9q-square-qvm')
result = qc.run_and_measure(p, trials=10)
result = qc.run(qc.compile(p))
print(result[0])
print(result[1])
Compare the two arrays of measurement results. The results will be correlated between the qubits and random from shot
to shot.

The ``qc`` is a simulated quantum computer. By specifying we want to ``.run_and_measure``, we've told our QVM to run
the program specified above, collapse the state with a measurement, and return the results to us. ``trials`` refers to
the number of times we run the whole program.
The ``qc`` is a simulated quantum computer. We've told our QVM to run the program specified above ten times and return
the results to us.

The call to ``run_and_measure`` will make a request to the two servers we
started up in the previous section: first, to the ``quilc`` server
instance to compile the Quil program into native Quil, and then to the ``qvm`` server
instance to simulate and return measurement results of the program 10 times. If you open up the terminal windows where your servers
are running, you should see output printed to the console regarding the requests you just made.
The calls to ``compile`` and ``run`` will make a request to the two servers we started up in the previous section:
first, to the ``quilc`` server instance to compile the Quil program into native Quil, and then to the ``qvm`` server
instance to simulate and return measurement results of the program 10 times. If you open up the terminal windows where
your servers are running, you should see output printed to the console regarding the requests you just made.


In the following sections, we'll cover gates, program construction & execution, and go into detail about our Quantum
Expand Down
54 changes: 1 addition & 53 deletions pyquil/api/_quantum_computer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from contextlib import contextmanager
from math import pi, log
from typing import (
Dict,
Tuple,
Iterator,
Mapping,
Expand Down Expand Up @@ -63,8 +62,7 @@
NxQuantumProcessor,
get_qcs_quantum_processor,
)
from pyquil.quil import Program, validate_supported_quil
from pyquil.quilatom import qubit_index
from pyquil.quil import Program


class QuantumComputer:
Expand Down Expand Up @@ -375,56 +373,6 @@ def run_symmetrized_readout(

return _consolidate_symmetrization_outputs(results, flip_arrays)

@_record_call
def run_and_measure(self, program: Program, trials: int) -> Dict[int, np.ndarray]:
"""
Run the provided state preparation program and measure all qubits.
The returned data is a dictionary keyed by qubit index because qubits for a given
QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary
into a 2d numpy array of bitstrings, consider::
bitstrings = qc.run_and_measure(...)
bitstring_array = np.vstack([bitstrings[q] for q in qc.qubits()]).T
bitstring_array.shape # (trials, len(qc.qubits()))
.. note::
If the target :py:class:`QuantumComputer` is a noiseless :py:class:`QVM` then
only the qubits explicitly used in the program will be measured. Otherwise all
qubits will be measured. In some circumstances this can exhaust the memory
available to the simulator, and this may be manifested by the QVM failing to
respond or timeout.
.. note::
In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates
noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``.
For faster noise-free simulation, consider
:py:class:`WavefunctionSimulator.run_and_measure`.
:param program: The state preparation program to run and then measure.
:param trials: The number of times to run the program.
:return: A dictionary keyed by qubit index where the corresponding value is a 1D array of
measured bits.
"""
program = program.copy()
validate_supported_quil(program)
ro = program.declare("ro", "BIT", len(self.qubits()))
measure_used = isinstance(self.qam, QVM) and self.qam.noise_model is None
qubits_to_measure = set(map(qubit_index, program.get_qubits()) if measure_used else self.qubits())
for i, q in enumerate(qubits_to_measure):
program.inst(MEASURE(q, ro[i]))
program.wrap_in_numshots_loop(trials)
executable = self.compile(program)
bitstring_array = self.run(executable=executable)
bitstring_dict = {}
for i, q in enumerate(qubits_to_measure):
bitstring_dict[q] = bitstring_array[:, i]
for q in set(self.qubits()) - set(qubits_to_measure):
bitstring_dict[q] = np.zeros(trials)
return bitstring_dict

@_record_call
def compile(
self,
Expand Down
Loading

0 comments on commit 6346f57

Please sign in to comment.