Skip to content

Commit

Permalink
Merge pull request #93 from ampolloreno/grover_example_and_docs
Browse files Browse the repository at this point in the history
Grover example and docs
  • Loading branch information
jotterbach committed Oct 19, 2017
2 parents 7cba626 + dd58be5 commit 3151333
Show file tree
Hide file tree
Showing 8 changed files with 300 additions and 18 deletions.
10 changes: 10 additions & 0 deletions docs/_static/mathjax_macros.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MathJax.Hub.Config({
TeX: {
Macros: {
ket: ["\\left| #1 \\right\\rangle",1],
bra: ["\\left\\langle #1 \\right|",1],
braket: ["\\left\\langle #1 | #2 \\right\\rangle",2]
}
}
});
MathJax.Ajax.loadComplete("[MathJax]/config/local/local.js");
5 changes: 5 additions & 0 deletions docs/_templates/layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% extends "!layout.html" %}
{% set script_files = script_files + ["_static/mathjax_macros.js"] %}
{% block extrahead %}
{{ super() }}
{% endblock %}
26 changes: 22 additions & 4 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@
# specify which docstring to use for autoclass
autoclass_content = 'init'

# for mathjaxs suppoort
mathjax_path = "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

Expand Down Expand Up @@ -153,7 +151,7 @@
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = []
html_static_path = ['_static']

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down Expand Up @@ -228,7 +226,7 @@
#'pointsize': '10pt',

# Additional stuff for the LaTeX preamble.
#'preamble': '',
'preamble': '',

# Latex figure (float) alignment
#'figure_align': 'htbp',
Expand Down Expand Up @@ -299,3 +297,23 @@
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False

# Additional stuff for the LaTeX preamble.
#####################################################
# add LaTeX macros
latex_elements['preamble'] = '\usepackage{amsmath}\n\usepackage{amssymb}\n'

with open('latex_macros.sty', "r") as f:

try:
imgmath_latex_preamble # check whether this is alread$
except NameError:
imgmath_latex_preamble = ""

for macro in f:
# used when building latex and pdf versions
latex_elements['preamble'] += macro + '\n'
# used when building html version
imgmath_latex_preamble += macro + '\n'

#####################################################

102 changes: 100 additions & 2 deletions docs/grover.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,107 @@ Grover's Search Algorithm and Amplitude Amplification
Overview
--------

Quantum search algorithm and more via amplitude amplification.
This module implements Grover's Search Algorithm, and the more general Amplitude Amplification
Algorithm. Grover's Algorithm solves the following problem:

Given a collection of basis states {:math:`\ket{y}_i`}, and a quantum circuit :math:`U_w` that
performs the following:

.. math::
U_w: \ket{x}\ket{q} \to \ket{x}\ket{q\oplus f(x)}
where :math:`f(x)=1` iff :math:`\ket{x}\in\{\ket{y}_i\}`, construct a quantum circuit that when
given the uniform superposition :math:`\ket{s} = \frac{1}{\sqrt{N}}\sum\limits^{N-1}_{i=0}\ket{x_i}`
as input, produces a state :math:`\ket{s'}` that, when measured, produces a state :math:`\{y_i\}`
with probability near one.

As an example, take :math:`U_w: \ket{x}\ket{q} \to \ket{x}\ket{q\oplus (x\cdot\vec{1})}`, where
\vec{1} is the vector of ones with the same dimension as \ket{x}. In this case, :math:`f(x)=1` iff
:math:`x=1`, and so starting with the state :math:`\ket{s}` we hope end up with a state
:math:`\ket{\psi}` such that :math:`\braket{\psi}{\vec{1}}\approx1`. In this example,
:math:`\{y_i\}=\{\vec{1}\}`.

Algorithm and Details
---------------------

Grover's Algorithm requires an oracle :math:`U_w`, that performs the mapping as described above,
with :math:`f:\{0,1\}^n\to\{0,1\}^n`, and :math:`\ket{q}` a single ancilla qubit. We see that if
we prepare the ancilla qubit :math:`\ket{q}` in the state :math:`\ket{-} =
\frac{1}{\sqrt{2}}(\ket{0} - \ket{1})` then :math:`U_w` takes on a particularly useful action on our
qubits:

.. math::
U_w: \ket{x}\ket{-}\to\frac{1}{\sqrt{2}}\ket{x}(\ket{0\oplus f(x)} - \ket{1\oplus f(x)})
If :math:`f(x)=0`, then the ancilla qubit is left unchanged, however if :math:`f(x)=1` we see that
the ancilla picks up a phase factor of :math:`-1`. Thus, when used in conjunction with the ancilla
qubit, we may write the action of the oracle circuit on the data qubits :math:`\ket{x}` as:

.. math::
U_w: \ket{x}\to(-1)^{f(x)}\ket{x}
The other gate of note in Grover's Algorithm is the Diffusion operator. This operator is
defined as:

.. math:: \mathcal{D} :=
\begin{bmatrix}
\frac{2}{N} - 1 & \frac{2}{N} & \dots & \frac{2}{N} \\
\frac{2}{N}\\
\vdots & & \ddots \\
\frac{2}{N} & & & \frac{2}{N} - 1
\end{bmatrix}

This operator takes on its name from its similarity to a discretized version of the diffusion
equation, which provided motivation for Grover [2]_. The diffusion equation is given by
:math:`\frac{\partial\rho(t)}{\partial t} = \nabla\cdot\nabla\rho(t)`, where :math:`\rho` is a
density diffusing through space. We can discretize this process, as is described in [2]_, by
considering :math:`N` vertices on a complete graph, each of which can diffuse to :math:`N-1` other
vertices in each time step. By considering this process, one arrives at an equation of the form
:math:`\psi(t + \Delta t) = \mathcal{D}'\psi` where :math:`\mathcal{D}'` has a form similar to
:math:`\mathcal{D}`. One might note that the diffusion equation is the same as the Schr\uodinger
equation, up to a missing :math:`i`, and in many ways it describes the diffusion of the probability
amplitude of a quantum state, but with slightly different properties. From this analogy one might be
led to explore how this diffusion process can be taken advantage of in a computational setting.

One property that :math:`\mathcal{D}` has is that it inverts the amplitudes of an input state about
their mean. Thus, one way of viewing Grover's Algorithm is as follows. First, we flip the amplitude
of the desired state(s) with :math:`U_w`, then invert the amplitudes about their mean, which will
result in the amplitude of the desired state being slightly larger than all the other
amplitudes. Iterating this process will eventually result in the desired state having a
significantly larger amplitude. As short example by analogy, consider the vector of all ones,
:math:`[1, 1, ..., 1]`. Suppose we want to apply a transformation that increases the value of the
second input, and supresses all other inputs. We can first flip the sign to yield :math:`[1, -1, 1,
..., 1]` Then, if there are a large number of entries we see that the mean will be rougly one. Thus
inverting the entries about the mean will yield, approximately, :math:`[-1, 3, -1, ..., -1]`. Thus
we see that this procedure, after one iteration, significantly increases the amplitude of the
desired index with respect to the other indices. See [2]_ for more.

Given these definitions we can now describe Grover's Algorithm:

Input:
:math:`n + 1` qubits

Algorithm:
#. Initialize them to the state :math:`\ket{s}\ket{-}`.

#. Apply the oracle :math:`U_w` to the qubits, yielding
:math:`\sum\limits^{N-1}_{0}(-1)^{f(x)}\ket{x}\ket{-}`, where :math:`N = 2^n`

#. Apply the n-fold Hadamard gate :math:`H^{\otimes n}` to :math:`\ket{x}`

#. Apply :math:`\mathcal{D}`

#. Apply :math:`H^{\otimes n}` to :math:`\ket{x}`

It can be shown [1]_ that if this process is iterated for :math:`\mathcal{O}(\sqrt{N})` iterations,
a measurement of :math:`\ket{x}` will result in one of :math:`\{y_i\}` with probability near one.

Source Code Docs
----------------

Here you can find documentation for the different submodules in amplification.

grove.amplification.amplification

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. automodule:: grove.amplification.amplification
Expand All @@ -26,3 +119,8 @@ grove.amplification.grover
:members:
:undoc-members:
:show-inheritance:

.. [1] Nielsen, M.A. and Chuang, I.L. Quantum computation and quantum information. Cambridge
University Press, 2000. Chapter 6.
.. [2] Lov K. Grover: “A fast quantum mechanical algorithm for database search”, 1996;
[http://arxiv.org/abs/quant-ph/9605043 arXiv:quant-ph/9605043].
8 changes: 8 additions & 0 deletions docs/latex_macros.sty
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
\newcommand{\sket}[1]{\left|\left. #1 \right\rangle\!\right\rangle}
\newcommand{\sbra}[1]{\left\langle\!\left\langle #1 \right.\right|}
\newcommand{\sbraket}[2]{\left\langle\!\left\langle #1 | #2 \right\rangle\!\right\rangle}
\newcommand{\ket}[1]{\left| #1 \right\rangle}
\newcommand{\bra}[1]{\left\langle #1 \right|}
\newcommand{\braket}[2]{\left\langle #1 | #2 \right\rangle}
\renewcommand{\vec}[1]{\text{vec}\left(#1\right)}
\newcommand{\tr}[1]{\text{Tr}\left(#1\right)}
143 changes: 143 additions & 0 deletions examples/GroversAlgorithm.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This Notebook is a brief sketch of how to use Grover's algorithm.\n",
"We start by declaring all necessary imports."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from pyquil.api import SyncConnection\n",
"from itertools import product\n",
"from mock import patch\n",
"\n",
"from grove.amplification.grover import Grover"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Grover's algorithm can be used to amplify the probability of an oracle-selected bitstring in an input superposition state."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"target_bitstring = '010'\n",
"bit = (\"0\", \"1\")\n",
"bitstring_map = {}\n",
"target_bitstring_phase = -1\n",
"nontarget_bitstring_phase = 1\n",
"\n",
"# We construct the bitmap for the oracle\n",
"for bitstring in product(bit, repeat=3):\n",
" bitstring = \"\".join(bitstring)\n",
" if bitstring == target_bitstring:\n",
" bitstring_map[bitstring] = target_bitstring_phase\n",
" else:\n",
" bitstring_map[bitstring] = nontarget_bitstring_phase"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We'll make sure the bitstring_map is as we expect."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"target_bitstring_phase = -1\n",
"nontarget_bitstring_phase = 1\n",
"for k,v in bitstring_map.items():\n",
" if k == target_bitstring:\n",
" assert v == target_bitstring_phase, \"The target bitstring has the wrong phase.\"\n",
" else:\n",
" assert v == nontarget_bitstring_phase, \"A nontarget bistring has the wrong phase.\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To use Grover's algorithm on quantum hardware we need to define a connection to a QVM or QPU. However we don't have a real connection in this notebook, so we just mock out the response. If you intend to run this notebook on a QVM or QPU, ensure to replace `cxn` with a pyQuil connection object."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"with patch(\"pyquil.api.SyncConnection\") as cxn:\n",
" cxn.run_and_measure.return_value = [[int(bit) for bit in target_bitstring]]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now let's run Grover's algorithm. We instantiate the Grover object and then call its `find_bitstring` method.\n",
"Finally we assert its correctness by checking with the bitstring we used to generate the object."
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
"grover = Grover()\n",
"found_bistring = grover.find_bitstring(cxn, bitstring_map)\n",
"assert found_bistring == target_bitstring, \"Found bitstring is not the expected bitstring\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If the assertion succeeded we know we found the correct bitstring."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.13"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
14 changes: 6 additions & 8 deletions grove/amplification/grover.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,16 @@ def find_bitstring(self, cxn, bitstring_map):
strings, an then use Grover's Algorithm to pick out the desired bitstring.
:param JobConnection cxn: the connection to the Rigetti cloud to run pyQuil programs.
:param bitstring_map: a mapping from bitstrings to the phases that the
oracle should impart on them. If the oracle should "look" for a bitstring, it should have a
``-1``, otherwise it should have a ``1``.
:param bitstring_map: a mapping from bitstrings to the phases that the oracle should impart
on them. If the oracle should "look" for a bitstring, it should have a ``-1``, otherwise
it should have a ``1``.
:type bitstring_map: Dict[String, Int]
:return: Returns the mask of the bitstring map or raises an Exception if the mask cannot be
found.
:return: Returns the bitstring resulting from measurement after Grover's Algorithm.
:rtype: str
"""
self._init_attr(bitstring_map)
sampled_bitstring = np.array(cxn.run_and_measure(self.grover_circuit, self.qubits),
dtype=int)
return sampled_bitstring
sampled_bitstring = cxn.run_and_measure(self.grover_circuit, self.qubits)[0]
return ''.join([str(b) for b in sampled_bitstring])

@staticmethod
def oracle_grover(oracle, qubits, num_iter=None):
Expand Down
10 changes: 6 additions & 4 deletions grove/tests/amplification/test_grover.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,14 @@ def test_find_bistring():
bitstring_map = {"0": 1, "1": -1}
builder = Grover()
with patch("pyquil.api.SyncConnection") as qvm:
expected_bitstring = np.asarray([0, 1], dtype=int)
qvm.run_and_measure.return_value = expected_bitstring
bitstring = builder.find_bitstring(qvm, bitstring_map)
expected_bitstring = [0, 1]
qvm.run_and_measure.return_value = [expected_bitstring, ]
returned_bitstring = builder.find_bitstring(qvm, bitstring_map)
prog = builder.grover_circuit
# Make sure it only defines the one ORACLE gate.
assert len(prog.defined_gates) == 1
# Make sure that it produces the oracle we expect.
assert (prog.defined_gates[0].matrix == np.array([[1, 0], [0, -1]])).all()
assert (bitstring == expected_bitstring).all()
expected_bitstring = "".join([str(bit) for bit in expected_bitstring])
returned_bitstring = "".join([str(bit) for bit in returned_bitstring])
assert expected_bitstring == returned_bitstring

0 comments on commit 3151333

Please sign in to comment.