Skip to content

Commit

Permalink
Add information about QuantumComputer (#719)
Browse files Browse the repository at this point in the history
* Add information about QuantumComputer

* Add info about QPU, QVM, and both generally
  • Loading branch information
lcapelluto authored Dec 3, 2018
1 parent a190164 commit db2b9cc
Showing 1 changed file with 288 additions and 34 deletions.
322 changes: 288 additions & 34 deletions docs/source/qvm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,41 @@
The Quantum Computer
====================

PyQuil is used to build Quil (Quantum Instruction Language) programs and execute them on simulated or real quantum devices. Quil is an opinionated
quantum instruction language: its basic belief is that in the near term quantum computers will
operate as coprocessors, working in concert with traditional CPUs. This means that Quil is designed to execute on
a Quantum Abstract Machine (QAM) that has a shared classical/quantum architecture at its core.

A QAM must, therefore, implement certain abstract methods to manipulate classical and quantum states, such as loading
programs, writing to shared classical memory, and executing programs.

The program execution itself is sent from pyQuil to quantum computer endpoints, which will be one of two options:

- A Rigetti Quantum Virtual Machine (QVM)
- A Rigetti Quantum Processing Unit (QPU)

Within pyQuil, there is a :py:class:`~pyquil.api.QVM` object and a :py:class:`~pyquil.api.QPU` object which use
the exposed APIs of the QVM and QPU servers, respectively.

On this page, we'll learn a bit about the :ref:`QVM <qvm_use>` and :ref:`QPU <qpu>`. Then we will
show you how to use them from pyQuil with a :ref:`quantum_computer`.

For information on constructing quantum programs, please refer back to :ref:`basics`.

.. _qvm_use:

The Quantum Virtual Machine (QVM)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The Rigetti Quantum Virtual Machine is an implementation of the Quantum Abstract Machine from
*A Practical Quantum Instruction Set Architecture*. [1]_ It is implemented in ANSI Common LISP and
executes programs specified in the Quantum Instruction Language (Quil).

Quil is an opinionated quantum instruction language: its basic belief is that in the near term quantum computers will
operate as coprocessors, working in concert with traditional CPUs. This means that Quil is designed to execute on
a Quantum Abstract Machine that has a shared classical/quantum architecture at its core.
executes programs specified in Quil.

The QVM is a wavefunction simulation of unitary evolution with classical control flow
and shared quantum classical memory.

.. _qvm_use:

Using the QVM
-------------
After :ref:`downloading the SDK <sdkinstall>`, the QVM is available on your local machine. You can initialize a local
The QVM is part of the Forest SDK, and it's available for you to use on your local machine.
After :ref:`downloading and installing the SDK <sdkinstall>`, you can initialize a local
QVM server by typing ``qvm -S`` into your terminal. You should see the following message.

.. code:: python
Expand All @@ -36,43 +52,242 @@ QVM server by typing ``qvm -S`` into your terminal. You should see the following
[2018-11-06 18:18:18] Starting server on port 5000.
For a detailed description of how to use the ``qvm`` from the command line, see :ref:`The QVM manual page <qvm_man>`.
By default, the server is started on port 5000 on your local machine. Consequently, the endpoint which
the pyQuil :py:class:`~pyquil.api.QVM` will default to for the QVM address is ``"http://127.0.0.1:5000"``. When you
run your program, a pyQuil client will send a Quil program to the QVM server and wait for a response back.

Once the QVM is serving requests, we can run the following pyQuil program to get a ``QuantumComputer`` object which
will use the QVM.
It's also possible to use the QVM from the command line. You can write a Quil program in its own file:

.. code:: python
# example.quil
DECLARE ro BIT[1]
RX(pi/2) 0
CZ 0 1
and then execute it with the QVM directly from the command line:

.. code:: python
$ qvm -e < example.quil
[2018-11-30 11:13:58] Reading program.
[2018-11-30 11:13:58] Allocating memory for QVM of 2 qubits.
[2018-11-30 11:13:58] Allocation completed in 4 ms.
[2018-11-30 11:13:58] Loading quantum program.
[2018-11-30 11:13:58] Executing quantum program.
[2018-11-30 11:13:58] Execution completed in 6 ms.
[2018-11-30 11:13:58] Printing 2-qubit state.
[2018-11-30 11:13:58] Amplitudes:
[2018-11-30 11:13:58] |00>: 0.0, P= 0.0%
[2018-11-30 11:13:58] |01>: 0.0-1.0i, P=100.0%
[2018-11-30 11:13:58] |10>: 0.0, P= 0.0%
[2018-11-30 11:13:58] |11>: 0.0, P= 0.0%
[2018-11-30 11:13:58] Classical memory (low -> high indexes):
[2018-11-30 11:13:58] ro: 1 0
For a detailed description of how to use the ``qvm`` from the command line, see :ref:`The QVM manual page <qvm_man>` or
type ``man qvm`` in your terminal.

We also offer a Wavefunction Simulator (formerly a part of the :py:class:`~pyquil.api.QVM` object),
which allows users to contruct and inspect wavefunctions of quantum programs. Learn more
about the Wavefunction Simulator :ref:`here <wavefunction_simulator>`.

.. _qpu:

The Quantum Processing Unit (QPU)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To access a QPU endpoint, you will have to `sign up <https://www.rigetti.com/>`_ for Quantum Cloud Services (QCS).
Documentation for getting started with your Quantum Machine Image (QMI) is found
`here <https://www.rigetti.com/qcs/docs/intro-to-qcs>`_. Using QCS, you will ``ssh`` into your QMI, and reserve a
QPU lattice for a particular time block.

When your reservation begins, you will be authorized to access the QPU. A configuration file will be automatically
populated for you with the proper QPU endpoint for your reservation. Both your QMI and the QPU are located on premises,
giving you low latency access to the QPU server. That server accepts jobs in the form of ``BinaryExecutableRequest``s,
which is precisely what you get back when you compile your program in pyQuil and target the QPU (more on this soon).
This request contains all the information necessary to run your program on the control rack which sends and receives
waveforms from the QPU, so that you can receive classified readout results (``0``s and ``1``s).
For information on available lattices, you can check out your dashboard at https://qcs.rigetti.com/dashboard after you've
been invited to QCS.
.. _quantum_computer:
The ``QuantumComputer``
~~~~~~~~~~~~~~~~~~~~~~~

The :py:class:`~pyquil.api.QuantumComputer` abstraction offered by pyQuil provides an easy access point to the most
critical objects used in pyQuil for building and executing your quantum programs.
We will cover the main methods and attributes on this page.
The `QuantumComputer API Reference <apidocs/quantum_computer.html>`_ provides a reference for all of its methods and
options.

At a high level, the :py:class:`~pyquil.api.QuantumComputer` wraps around our favorite quantum computing tools:

- **A quantum abstract machine** ``.qam`` : this is our general purpose quantum computing device,
which implements the required abstract methods described :ref:`above <qvm>`. It is implemented as a
:py:class:`~pyquil.api.QVM` or :py:class:`~pyquil.api.QPU` object in pyQuil.
- **A compiler** ``.compiler`` : this determines how we manipulate the Quil input to something more efficient when possible,
and then into a form which our QAM can accept as input.
- **A device** ``.device`` : this specifies the topology and Instruction Set Architecture (ISA) of
the targeted device by listing the supported 1Q and 2Q gates.

When you instantiate a :py:class:`~pyquil.api.QuantumComputer` instance, these subcomponents will be compatible with
each other. So, if you get a ``QPU`` implementation for the ``.qam``, you will have a ``QPUCompiler`` for the
``.compiler``, and your ``.device`` will match the device used by the ``.compiler.``

The :py:class:`~pyquil.api.QuantumComputer` instance makes methods available which are built on the above objects. If
you need more fine grained controls for your work, you might try exploring what is offered by these objects.

For more information on each of the above, check out the following pages:

- `Compiler API Reference <apidocs/compilers.html>`_
- :ref:`Quil Compiler docs <compiler>`
- `Device API Reference <apidocs/devices.html>`_
- :ref:`new_topology`
- `Quantum abstract machine (QAM) API Reference <apidocs/qam.html>`_
- `The Quil Whitepaper <https://arxiv.org/abs/1608.03355>`_ which describes the QAM

Instantiation
-------------

A decent amount of information needs to be provided to initialize the ``compiler``, ``device``, and ``qam`` attributes,
much of which is already in your :ref:`config files <_advanced_usage>` (or provided reasonable defaults when running locally).
Typically, you will want a :py:class:`~pyquil.api.QuantumComputer` which either:

- pertains to a real, available QPU device
- is a QVM but mimics the topology of a QPU
- is some generic QVM

All of this can be accomplished with :py:func:`~pyquil.api.get_qc`.

.. code:: python
def get_qc(name: str, *, as_qvm: bool = None, noisy: bool = None,
connection: ForestConnection = None) -> QuantumComputer:
.. code:: python
from pyquil import get_qc
# Get a QPU
qc = get_qc(QPU_LATTICE_NAME) # QPU_LATTICE_NAME is just a string naming the device
# Get a QVM with the same topology as the QPU lattice
qc = get_qc(QPU_LATTICE_NAME, as_qvm=True)
# or, equivalently
qc = get_qc(f"{QPU_LATTICE_NAME}-qvm")
# A fully connected QVM
number_of_qubits = 10
qc = get_qc(f"{number_of_qubits}q-qvm")
For now, you will have to join QCS to get ``QPU_LATTICE_NAME`` by running the
``qcs lattices`` command from your QMI. Access to the QPU is only possible from a QMI, during a booked reservation.
If this sounds unfamiliar, check out our `documentation for QCS <https://www.rigetti.com/qcs/docs/intro-to-qcs>`_
and `join the waitlist <https://www.rigetti.com/>`_.

For more information about creating and adding your own noise models, check out :ref:`noise`.

.. note::
When connecting to a QVM locally (such as with ``get_qc(..., as_qvm=True)``) you'll have to set up the QVM
in :ref:`server mode <server>`.

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:

.. code:: python
from pyquil import get_qc, Program
from pyquil.gates import *
qc = get_qc('9q-square-qvm')
qc = get_qc('9q-square-qvm') # not general to any number of qubits, 9q-square-qvm is special
One executes quantum programs on the QVM using the ``.run(...)`` method, intended to closely mirror how one will
execute programs on a real QPU. We also offer a Wavefunction Simulator (formerly a part of the ``QVM`` object),
which allows users to contruct and inspect wavefunctions of quantum programs. Learn more
about the Wavefunction Simulator :ref:`here <wavefunction_simulator>`. For information on constructing quantum
programs, please refer back to :ref:`basics`.
qubits = qc.qubits() # this information comes from qc.device
p = Program()
# ... build program, potentially making use of the qubits list
compiled_program = qc.compile(p) # this makes multiple calls to qc.compiler
results = qc.run(compiled_program) # this makes multiple calls to qc.qam
.. note::

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 ``.run(...)`` method takes in a compiled program. You are responsible for compiling your program before running it.
Remember to also start up a ``quilc`` compiler server, too, with ``quilc -S``.
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``:

.. code:: python
from pyquil import Program, get_qc
from pyquil.gates import X, MEASURE
qc = get_qc("8q-qvm")
p = Program()
ro = p.declare('ro', 'BIT', 1)
p += X(0)
p += MEASURE(0, ro[0])
p += MEASURE(1, ro[1])
p.wrap_in_numshots_loop(5)
executable = qc.compile(p)
results = qc.run(executable)
print(results)
bitstrings = qc.run(executable) # .run takes in a compiled program, unlike .run_and_measure
print(bitstrings)
The results returned are a *list of lists of integers*. In the above case, that's
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 All @@ -86,23 +301,62 @@ we use as our readout register. We see that the result of this program is that t
the state of qubit 0, which should be ``1`` after an :math:`X`-gate. See :ref:`declaring_memory` and :ref:`measurement`
for more details about declaring and accessing classical memory regions.

.. [1] https://arxiv.org/abs/1608.03355
.. tip:: Get the results for qubit 0 with ``numpy.array(bitstrings)[:,0]``.

.. _new_topology:

Providing Your Own Device Topology
----------------------------------

It is simple to provide your own device topology as long as you can give your qubits each a number,
and specify which edges exist. Here is an example, using the topology of our 16Q chip (two octagons connected by a square):

.. code:: python
import networkx as nx
from pyquil.device import NxDevice, gates_in_isa
from pyquil.noise import decoherence_noise_with_asymmetric_ro
qubits = [0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17] # qubits are numbered by octagon
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 0), # first octagon
(1, 16), (2, 15), # connections across the square
(10, 11), (11, 12), (13, 14), (14, 15), (15, 16), (16, 17), (10, 17)] # second octagon
# Build the NX graph
topo = nx.from_edgelist(edges)
# You would uncomment the next line if you have disconnected qubits
# topo.add_nodes_from(qubits)
device = NxDevice(topo)
device.noise_model = decoherence_noise_with_asymmetric_ro(gates_in_isa(device.get_isa())) # Optional
Now that you have your device, you could set ``qc.device`` and ``qc.compiler.device`` to point to your new device,
or use it to make new objects.

Simulating the QPU using the QVM
--------------------------------

The QVM is a powerful tool for testing quantum programs before executing them on the QPU.
The :py:class:`~pyquil.api.QAM` methods are intended to be used in the same way, whether a QVM or QPU is being targeted.
Everywhere on this page,
you can swap out the type of the QAM (QVM <=> QPU) and you will still
get reasonable results back. As long as the topology of the devices are the same, programs compiled and ran on the QVM
will be able to run on the QPU and visa-versa. Since :py:class:`~pyquil.api.QuantumComputer` is built on the ``QAM``
abstract class, its methods will also work for both QAM implementations.

This makes the QVM a powerful tool for testing quantum programs before executing them on the QPU.

.. code:: python
qc = get_qc("QuantumComputerName")
qc = get_qc("QuantumComputerName-qvm")
qpu = get_qc(QPU_LATTICE_NAME)
qvm = get_qc(QPU_LATTICE_NAME, as_qvm=True)
By simply providing ``-qvm`` in the device name, all programs executed on this QVM will, have the same topology as
the named QPU. To learn how to add noise models to your virtual ``QuantumComputer`` instance, check out
By simply providing ``as_qvm=True``, we get a QVM which will have the same topology as
the named QPU. It's a good idea to run your programs against the QVM before booking QPU time to iron out
bugs. To learn more about how to add noise models to your virtual ``QuantumComputer`` instance, check out
:ref:`noise`.

The Quantum Processing Unit
~~~~~~~~~~~~~~~~~~~~~~~~~~~
In the next section, we will see how to use the Wavefunction Simulator aspect of the Rigetti QVM to inspect the full
wavefunction set up by a Quil program.

.. [1] https://arxiv.org/abs/1608.03355
*Coming soon*: Detailed information about how to use :py:func:`~pyquil.get_qc` to target a QPU.

0 comments on commit db2b9cc

Please sign in to comment.