Skip to content

Commit

Permalink
Add RevKit interface (#241)
Browse files Browse the repository at this point in the history
  • Loading branch information
msoeken authored and thomashaener committed Jul 21, 2018
1 parent 536e1fe commit e98e980
Show file tree
Hide file tree
Showing 13 changed files with 739 additions and 5 deletions.
10 changes: 6 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,28 @@ matrix:
addons:
apt:
sources: ['ubuntu-toolchain-r-test']
packages: ['gcc-4.9', 'g++-4.9']
packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7']
env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=2.7
- os: linux
python: "3.4"
addons:
apt:
sources: ['ubuntu-toolchain-r-test']
packages: ['gcc-4.9', 'g++-4.9']
packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7']
env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.4
- os: linux
python: "3.5"
addons:
apt:
sources: ['ubuntu-toolchain-r-test']
packages: ['gcc-4.9', 'g++-4.9']
packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7']
env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.5
- os: linux
python: "3.6"
addons:
apt:
sources: ['ubuntu-toolchain-r-test']
packages: ['gcc-4.9', 'g++-4.9']
packages: ['gcc-4.9', 'g++-4.9', 'gcc-7', 'g++-7']
env: CC=gcc-4.9 CXX=g++-4.9 PYTHON=3.6

install:
Expand All @@ -38,6 +38,8 @@ install:
- pip$PY install -r requirements.txt
- pip$PY install pytest-cov
- pip$PY install coveralls
- CC=g++-7 pip$PY install revkit
- if [ "${PYTHON:0:1}" = "3" ]; then pip$PY install dormouse; fi
- pip$PY install -e .

# command to run tests
Expand Down
34 changes: 34 additions & 0 deletions docs/projectq.libs.revkit.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
revkit
======

This library integrates `RevKit <https://msoeken.github.io/revkit.html>`_ into
ProjectQ to allow some automatic synthesis routines for reversible logic. The
library adds the following operations that can be used to construct quantum
circuits:

- :class:`~projectq.libs.revkit.ControlFunctionOracle`: Synthesizes a reversible circuit from Boolean control function
- :class:`~projectq.libs.revkit.PermutationOracle`: Synthesizes a reversible circuit for a permutation
- :class:`~projectq.libs.revkit.PhaseOracle`: Synthesizes phase circuit from an arbitrary Boolean function

RevKit can be installed from PyPi with `pip install revkit`.

.. note::

The RevKit Python module must be installed in order to use this ProjectQ library.

There exist precompiled binaries in PyPi, as well as a source distribution.
Note that a C++ compiler with C++17 support is required to build the RevKit
python module from source. Examples for compatible compilers are Clang
6.0, GCC 7.3, and GCC 8.1.

The integration of RevKit into ProjectQ and other quantum programming languages is described in the paper

* Mathias Soeken, Thomas Haener, and Martin Roetteler "Programming Quantum Computers Using Design Automation," in: Design Automation and Test in Europe (2018) [`arXiv:1803.01022 <https://arxiv.org/abs/1803.01022>`_]

Module contents
---------------

.. automodule:: projectq.libs.revkit
:members:
:special-members: __init__,__or__
:imported-members:
3 changes: 2 additions & 1 deletion docs/projectq.libs.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
libs
====

The library collection of ProjectQ which, for now, only consists of a tiny math library. Soon, more libraries will be added.
The library collection of ProjectQ which, for now, consists of a tiny math library and an interface library to RevKit. Soon, more libraries will be added.

Subpackages
-----------

.. toctree::

projectq.libs.math
projectq.libs.revkit

Module contents
---------------
Expand Down
25 changes: 25 additions & 0 deletions examples/hws4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from projectq.cengines import MainEngine
from projectq.ops import All, H, X, Measure
from projectq.meta import Compute, Uncompute
from projectq.libs.revkit import PhaseOracle

# phase function
def f(a, b, c, d):
return (a and b) ^ (c and d)

eng = MainEngine()
x1, x2, x3, x4 = qubits = eng.allocate_qureg(4)

with Compute(eng):
All(H) | qubits
X | x1
PhaseOracle(f) | qubits
Uncompute(eng)

PhaseOracle(f) | qubits
All(H) | qubits
All(Measure) | qubits

eng.flush()

print("Shift is {}".format(8 * int(x4) + 4 * int(x3) + 2 * int(x2) + int(x1)))
39 changes: 39 additions & 0 deletions examples/hws6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from projectq.cengines import MainEngine
from projectq.ops import All, H, X, CNOT, Measure
from projectq.meta import Compute, Uncompute, Dagger
from projectq.libs.revkit import PhaseOracle, PermutationOracle

import revkit

# phase function
def f(a, b, c, d, e, f):
return (a and b) ^ (c and d) ^ (e and f)

# permutation
pi = [0, 2, 3, 5, 7, 1, 4, 6]

eng = MainEngine()
qubits = eng.allocate_qureg(6)
x = qubits[::2] # qubits on odd lines
y = qubits[1::2] # qubits on even lines

# circuit
with Compute(eng):
All(H) | qubits
All(X) | [x[0], x[1]]
PermutationOracle(pi) | y
PhaseOracle(f) | qubits
Uncompute(eng)

with Compute(eng):
with Dagger(eng):
PermutationOracle(pi, synth = revkit.dbs) | x
PhaseOracle(f) | qubits
Uncompute(eng)

All(H) | qubits

All(Measure) | qubits

# measurement result
print("Shift is {}".format(sum(int(q) << i for i, q in enumerate(qubits))))
17 changes: 17 additions & 0 deletions projectq/libs/revkit/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2017 ProjectQ-Framework (www.projectq.ch)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from ._permutation import PermutationOracle
from ._control_function import ControlFunctionOracle
from ._phase import PhaseOracle
121 changes: 121 additions & 0 deletions projectq/libs/revkit/_control_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Copyright 2017 ProjectQ-Framework (www.projectq.ch)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from projectq.ops import BasicGate

from ._utils import _exec


class ControlFunctionOracle:
"""
Synthesizes a negation controlled by an arbitrary control function.
This creates a circuit for a NOT gate which is controlled by an arbitrary
Boolean control function. The control function is provided as integer
representation of the function's truth table in binary notation. For
example, for the majority-of-three function, which truth table 11101000,
the value for function can be, e.g., ``0b11101000``, ``0xe8``, or ``232``.
Example:
This example creates a circuit that causes to invert qubit ``d``,
the majority-of-three function evaluates to true for the control
qubits ``a``, ``b``, and ``c``.
.. code-block:: python
ControlFunctionOracle(0x8e) | ([a, b, c], d)
"""

def __init__(self, function, **kwargs):
"""
Initializes a control function oracle.
Args:
function (int): Function truth table.
Keyword Args:
synth: A RevKit synthesis command which creates a reversible
circuit based on a truth table and requires no additional
ancillae (e.g., ``revkit.esopbs``). Can also be a nullary
lambda that calls several RevKit commands.
**Default:** ``revkit.esopbs``
"""
if isinstance(function, int):
self.function = function
else:
try:
import dormouse
self.function = dormouse.to_truth_table(function)
except ImportError: # pragma: no cover
raise RuntimeError(
"The dormouse library needs to be installed in order to "
"automatically compile Python code into functions. Try "
"to install dormouse with 'pip install dormouse'."
)
self.kwargs = kwargs

self._check_function()

def __or__(self, qubits):
"""
Applies control function to qubits (and synthesizes circuit).
Args:
qubits (tuple<Qureg>): Qubits to which the control function is
being applied. The first `n` qubits are for
the controls, the last qubit is for the
target qubit.
"""
try:
import revkit
except ImportError: # pragma: no cover
raise RuntimeError(
"The RevKit Python library needs to be installed and in the "
"PYTHONPATH in order to call this function")

# convert qubits to tuple
qs = []
for item in BasicGate.make_tuple_of_qureg(qubits):
qs += item if isinstance(item, list) else [item]

# function truth table cannot be larger than number of control qubits
# allow
if 2**(2**(len(qs) - 1)) <= self.function:
raise AttributeError(
"Function truth table exceeds number of control qubits")

# create truth table from function integer
hex_length = max(2**(len(qs) - 1) // 4, 1)
revkit.tt(table="{0:#0{1}x}".format(self.function, hex_length))

# create reversible circuit from truth table
self.kwargs.get("synth", revkit.esopbs)()

# check whether circuit has correct signature
if revkit.ps(mct=True, silent=True)['qubits'] != len(qs):
raise RuntimeError("Generated circuit lines does not match "
"provided qubits")

# convert reversible circuit to ProjectQ code and execute it
_exec(revkit.write_projectq(log=True)["contents"], qs)

def _check_function(self):
"""
Checks whether function is valid.
"""
# function must be positive. We check in __or__ whether function is
# too large
if self.function < 0:
raise AttributeError("Function must be a postive integer")
75 changes: 75 additions & 0 deletions projectq/libs/revkit/_control_function_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright 2017 ProjectQ-Framework (www.projectq.ch)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for libs.revkit._control_function."""

import pytest

from projectq.types import Qubit
from projectq import MainEngine
from projectq.cengines import DummyEngine

from projectq.libs.revkit import ControlFunctionOracle


# run this test only if RevKit Python module can be loaded
revkit = pytest.importorskip('revkit')


def test_control_function_majority():
saving_backend = DummyEngine(save_commands=True)
main_engine = MainEngine(backend=saving_backend,
engine_list=[DummyEngine()])

qubit0 = main_engine.allocate_qubit()
qubit1 = main_engine.allocate_qubit()
qubit2 = main_engine.allocate_qubit()
qubit3 = main_engine.allocate_qubit()

ControlFunctionOracle(0xe8) | (qubit0, qubit1, qubit2, qubit3)

assert len(saving_backend.received_commands) == 7

def test_control_function_majority_from_python():
dormouse = pytest.importorskip('dormouse')

def maj(a, b, c):
return (a and b) or (a and c) or (b and c) # pragma: no cover

saving_backend = DummyEngine(save_commands=True)
main_engine = MainEngine(backend=saving_backend,
engine_list=[DummyEngine()])

qubit0 = main_engine.allocate_qubit()
qubit1 = main_engine.allocate_qubit()
qubit2 = main_engine.allocate_qubit()
qubit3 = main_engine.allocate_qubit()

ControlFunctionOracle(maj) | (qubit0, qubit1, qubit2, qubit3)


def test_control_function_invalid_function():
main_engine = MainEngine(backend=DummyEngine(),
engine_list=[DummyEngine()])

qureg = main_engine.allocate_qureg(3)

with pytest.raises(AttributeError):
ControlFunctionOracle(-42) | qureg

with pytest.raises(AttributeError):
ControlFunctionOracle(0x8e) | qureg

with pytest.raises(RuntimeError):
ControlFunctionOracle(0x8, synth=revkit.esopps) | qureg
Loading

0 comments on commit e98e980

Please sign in to comment.