Classical Function compiler (#4522)

* classical function compiler

Co-authored-by: Bruno Schmitt <bruno.schmitt@epfl.ch>

* utils

* synthesis

* renaming

* int1

* import

* reno

* docstring

* doc

Co-authored-by: Bruno Schmitt <bruno.schmitt@epfl.ch>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Luciano Bello 2020-09-30 16:21:47 -04:00 committed by GitHub
parent e8418fb601
commit bbe1f1c973
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1042 additions and 1 deletions

View File

@ -230,3 +230,5 @@ from .parameter import Parameter
from .parametervector import ParameterVector
from .parameterexpression import ParameterExpression
from .equivalence import EquivalenceLibrary
from .classicalfunction.types import Int1, Int2
from .classicalfunction import classical_function

View File

@ -0,0 +1,117 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
=====================================
ClassicalFunction compiler (:mod:`qiskit.circuit.classicalfunction`)
=====================================
.. currentmodule:: qiskit.circuit.classicalfunction
Overview
========
The classical function compiler provides the necessary tools to map a classical
irreversible functions into quantum circuits. Below is a simple example of
how to synthesize a simple boolean function defined using Python into a
QuantumCircuit:
.. jupyter-execute::
from qiskit.circuit import classical_function, Int1
@classical_function
def grover_oracle(a: Int1, b: Int1, c: Int1, d: Int1) -> Int1:
return (not a and b and not c and d)
quantum_circuit = grover_oracle.synth()
quantum_circuit.draw()
Following Qiskit's little-endian bit ordering convention, the left-most bit (`a`) is the most
significant bit and the right-most bit (`d`) is the least significant bit. The resulting
Supplementary Information
=========================
.. container:: toggle
.. container:: header
**Tweedledum**
Tweedledum is a C++-17 header-only library that implements a large set of
reversible (and quantum) synthesis, optimization, and mapping algorithms.
The classical function compiler relies on it and its dependencies to both represent logic
networks and synthesize them into quantum circuits.
.. container:: toggle
.. container:: header
**ClassicalFunction data types**
At the moment, the only type supported by the classical_function compilers is
``qiskit.circuit.classicalfunction.types.Int1``. The classical function function
to parse *must* include type hints (just ``Int1`` for now). The resulting gate
will be a gate in the size of the sum of all the parameters and the return.
The type ``Int1`` means the classical function will only operate at bit level.
ClassicalFunction compiler API
===================
classical_function
------
Decorator for a classical function that returns a `ClassicalFunction` object.
ClassicalFunction
------
.. autosummary::
:toctree: ../stubs/
ClassicalFunction
Exceptions
----------
.. autosummary::
:toctree: ../stubs/
ClassicalFunctionCompilerTypeError
ClassicalFunctionParseError
ClassicalFunctionCompilerTypeError
"""
from .exceptions import (ClassicalFunctionParseError, ClassicalFunctionCompilerError,
ClassicalFunctionCompilerTypeError)
def classical_function(func):
"""
Parses and type checks the callable ``func`` to compile it into an ``ClassicalFunction``
that can be synthesised into a ``QuantumCircuit``.
Args:
func (callable): A callable (with type hints) to compile into an ``ClassicalFunction``.
Returns:
ClassicalFunction: An object that can synthesis into a QuantumCircuit (via ``synth()``
method).
"""
import inspect
from .classicalfunction import ClassicalFunction
source = inspect.getsource(func).strip()
return ClassicalFunction(source, name=func.__name__)

View File

@ -0,0 +1,144 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Node visitor as defined in https://docs.python.org/3/library/ast.html#ast.NodeVisitor
This module is used internally by ``qiskit.transpiler.classicalfunction.ClassicalFunction``.
"""
import ast
try:
from tweedledum import xag_network # pylint: disable=no-name-in-module
HAS_TWEEDLEDUM = True
except Exception: # pylint: disable=broad-except
HAS_TWEEDLEDUM = False
import _ast
from .exceptions import ClassicalFunctionParseError, ClassicalFunctionCompilerTypeError
class ClassicalFunctionVisitor(ast.NodeVisitor):
"""Node visitor as defined in https://docs.python.org/3/library/ast.html#ast.NodeVisitor"""
# pylint: disable=invalid-name
bitops = {_ast.BitAnd: 'create_and',
_ast.BitOr: 'create_or',
_ast.BitXor: 'create_xor',
_ast.And: 'create_and',
_ast.Or: 'create_or',
_ast.Not: 'create_not'
}
def __init__(self):
if not HAS_TWEEDLEDUM:
raise ImportError("To use the classicalfunction compiler, tweedledum "
"must be installed. To install tweedledum run "
'"pip install tweedledum".')
self.scopes = []
self.args = []
self._network = None
self.name = None
super().__init__()
def visit_Module(self, node):
"""The full snippet should contain a single function"""
if len(node.body) != 1 and not isinstance(node.body[0], ast.FunctionDef):
raise ClassicalFunctionParseError("just functions, sorry!")
self.name = node.body[0].name
self.visit(node.body[0])
def visit_FunctionDef(self, node):
"""The function definition should have type hints"""
if node.returns is None:
raise ClassicalFunctionParseError("return type is needed")
scope = {'return': (node.returns.id, None), node.returns.id: ('type', None)}
# Extend scope with the decorator's names
scope.update({decorator.id: ('decorator', None) for decorator in node.decorator_list})
self.scopes.append(scope)
self._network = xag_network()
self.extend_scope(node.args)
return super().generic_visit(node)
def visit_Return(self, node):
"""The return type should match the return type hint."""
_type, signal = self.visit(node.value)
if _type != self.scopes[-1]['return'][0]:
raise ClassicalFunctionParseError("return type error")
self._network.create_po(signal)
def visit_Assign(self, node):
"""When assign, the scope needs to be updated with the right type"""
type_value, signal_value = self.visit(node.value)
for target in node.targets:
self.scopes[-1][target.id] = (type_value, signal_value)
return (type_value, signal_value)
def bit_binop(self, op, values):
"""Uses ClassicalFunctionVisitor.bitops to extend self._network"""
bitop = ClassicalFunctionVisitor.bitops.get(type(op))
if not bitop:
raise ClassicalFunctionParseError("Unknown binop.op %s" % op)
binop = getattr(self._network, bitop)
left_type, left_signal = values[0]
if left_type != 'Int1':
raise ClassicalFunctionParseError("binop type error")
for right_type, right_signal in values[1:]:
if right_type != 'Int1':
raise ClassicalFunctionParseError("binop type error")
left_signal = binop(left_signal, right_signal)
return 'Int1', left_signal
def visit_BoolOp(self, node):
"""Handles ``and`` and ``or``.
node.left=Int1 and node.right=Int1 return Int1 """
return self.bit_binop(node.op, [self.visit(value) for value in node.values])
def visit_BinOp(self, node):
"""Handles ``&``, ``^``, and ``|``.
node.left=Int1 and node.right=Int1 return Int1 """
return self.bit_binop(node.op, [self.visit(node.left), self.visit(node.right)])
def visit_UnaryOp(self, node):
"""Handles ``~``. Cannot operate on Int1s. """
operand_type, operand_signal = self.visit(node.operand)
if operand_type != 'Int1':
raise ClassicalFunctionCompilerTypeError(
"UntaryOp.op %s only support operation on Int1s for now" % node.op)
bitop = ClassicalFunctionVisitor.bitops.get(type(node.op))
if not bitop:
raise ClassicalFunctionCompilerTypeError(
"UntaryOp.op %s does not operate with Int1 type " % node.op)
return 'Int1', getattr(self._network, bitop)(operand_signal)
def visit_Name(self, node):
"""Reduce variable names. """
if node.id not in self.scopes[-1]:
raise ClassicalFunctionParseError('out of scope: %s' % node.id)
return self.scopes[-1][node.id]
def generic_visit(self, node):
"""Catch all for the unhandled nodes."""
if isinstance(node, (_ast.arguments, _ast.arg, _ast.Load, _ast.BitAnd,
_ast.BitOr, _ast.BitXor, _ast.BoolOp, _ast.Or)):
return super().generic_visit(node)
raise ClassicalFunctionParseError("Unknown node: %s" % type(node))
def extend_scope(self, args_node: _ast.arguments) -> None:
"""Add the arguments to the scope"""
for arg in args_node.args:
if arg.annotation is None:
raise ClassicalFunctionParseError("argument type is needed")
self.args.append(arg.arg)
self.scopes[-1][arg.annotation.id] = ('type', None)
self.scopes[-1][arg.arg] = (arg.annotation.id, self._network.create_pi())

View File

@ -0,0 +1,130 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""ClassicalFunction class"""
import ast
try:
from tweedledum import synthesize_xag, simulate # pylint: disable=no-name-in-module
HAS_TWEEDLEDUM = True
except Exception: # pylint: disable=broad-except
HAS_TWEEDLEDUM = False
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.circuit.gate import Gate
from qiskit.exceptions import QiskitError
from .utils import tweedledum2qiskit
from .classical_function_visitor import ClassicalFunctionVisitor
class ClassicalFunction(Gate):
"""An ClassicalFunction object represents an classicalfunction function and
its logic network."""
def __init__(self, source, name=None):
"""Creates a ``ClassicalFunction`` from Python source code in ``source``. The code should
be a single function with types.
Args:
source (str): Python code with type hints.
name (str): Optional. Default: "*classicalfunction*". ClassicalFunction name.
Raises:
ImportError: If tweedledum is not installed.
QiskitError: If source is not a string.
"""
if not isinstance(source, str):
raise QiskitError('ClassicalFunction needs a source code as a string.')
if not HAS_TWEEDLEDUM:
raise ImportError("To use the classicalfunction compiler, tweedledum "
"must be installed. To install tweedledum run "
'"pip install tweedledum".')
self._ast = ast.parse(source)
self._network = None
self._scopes = None
self._args = None
super().__init__(name or '*classicalfunction*',
num_qubits=sum([qreg.size for qreg in self.qregs]),
params=[])
def compile(self):
"""Parses and creates the logical circuit"""
_classical_function_visitor = ClassicalFunctionVisitor()
_classical_function_visitor.visit(self._ast)
self._network = _classical_function_visitor._network
self._scopes = _classical_function_visitor.scopes
self._args = _classical_function_visitor.args
self.name = _classical_function_visitor.name
@property
def network(self):
"""Returns the logical network"""
if self._network is None:
self.compile()
return self._network
@property
def scopes(self):
"""Returns the scope dict"""
if self._scopes is None:
self.compile()
return self._scopes
@property
def args(self):
"""Returns the classicalfunction arguments"""
if self._args is None:
self.compile()
return self._args
@property
def types(self):
"""Dumps a list of scopes with their variables and types.
Returns:
list(dict): A list of scopes as dicts, where key is the variable name and
value is its type.
"""
ret = []
for scope in self.scopes:
ret.append({k: v[0] for k, v in scope.items()})
return ret
def simulate(self):
"""Runs ``tweedledum.simulate`` on the logic network."""
return simulate(self._network)
def synth(self, registerless=True) -> QuantumCircuit:
"""Synthesis the logic network into a ``QuantumCircuit``.
Args:
registerless (bool): Default ``True``. If ``False`` uses the parameter names to create
registers with those names. Otherwise, creates a circuit with a flat quantum register.
Returns:
QuantumCircuit: A circuit implementing the logic network.
"""
if registerless:
qregs = None
else:
qregs = self.qregs
return tweedledum2qiskit(synthesize_xag(self._network), name=self.name, qregs=qregs)
def _define(self):
"""The definition of the classicalfunction is its synthesis"""
self.definition = self.synth()
@property
def qregs(self):
"""The list of qregs used by the classicalfunction"""
qregs = [QuantumRegister(1, name=arg) for arg in self.args if self.types[0][arg] == 'Int1']
qregs.reverse()
if self.types[0]['return'] == 'Int1':
qregs.append(QuantumRegister(1, name='return'))
return qregs

View File

@ -0,0 +1,32 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Exceptions for ClassicalFunction compiler"""
from qiskit.exceptions import QiskitError
class ClassicalFunctionCompilerError(QiskitError):
"""ClassicalFunction compiler generic error."""
pass
class ClassicalFunctionParseError(ClassicalFunctionCompilerError):
"""ClassicalFunction compiler parse error.
The classicalfunction function fails at parsing time."""
pass
class ClassicalFunctionCompilerTypeError(ClassicalFunctionCompilerError):
"""ClassicalFunction compiler type error.
The classicalfunction function fails at type checking time."""
pass

View File

@ -0,0 +1,18 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""ClassicalFunction function types."""
from typing import NewType
Int1 = NewType('Int1', bool)
Int2 = NewType('Int2', int) # pylint: disable=invalid-name

View File

@ -0,0 +1,66 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Internal utils for ClassicalFunction Compiler"""
from qiskit import QuantumCircuit
from qiskit.circuit.library.standard_gates import ZGate, TGate, SGate, TdgGate, SdgGate, U1Gate, \
XGate, HGate, U3Gate
from qiskit.circuit.classicalfunction.exceptions import ClassicalFunctionCompilerError
def tweedledum2qiskit(tweedledum_circuit, name=None, qregs=None):
""" Converts a `Tweedledum <https://github.com/boschmitt/tweedledum>`_
circuit into a Qiskit circuit. A Tweedledum circuit is a
dictionary with the following shape:
{
"num_qubits": 2,
"gates": [{
"gate": "X",
"qubits": [1],
"control_qubits": [0],
"control_state": "1"
}]
Args:
tweedledum_circuit (dict): Tweedledum circuit.
name (str): Name for the resulting Qiskit circuit.
qregs (list(QuantumRegister)): Optional. List of QuantumRegisters on which the
circuit would operate. If not provided, it will create a flat register.
Returns:
QuantumCircuit: The Tweedledum circuit converted to a Qiskit circuit.
Raises:
ClassicalFunctionCompilerError: If there a gate in the Tweedledum circuit has no Qiskit
equivalent.
"""
gates = {'z': ZGate, 't': TGate, 's': SGate, 'tdg': TdgGate, 'sdg': SdgGate, 'u1': U1Gate,
'x': XGate, 'h': HGate, 'u3': U3Gate}
if qregs:
circuit = QuantumCircuit(*qregs, name=name)
else:
circuit = QuantumCircuit(tweedledum_circuit['num_qubits'], name=name)
for gate in tweedledum_circuit['gates']:
basegate = gates.get(gate['gate'].lower())
if basegate is None:
raise ClassicalFunctionCompilerError('The Tweedledum gate %s has no Qiskit equivalent'
% gate['gate'])
ctrl_qubits = gate.get('control_qubits', [])
trgt_qubits = gate.get('qubits', [])
if ctrl_qubits:
gate = basegate().control(len(ctrl_qubits), ctrl_state=gate.get('control_state'))
else:
gate = basegate()
circuit.append(gate, ctrl_qubits + trgt_qubits)
return circuit

View File

@ -0,0 +1,39 @@
---
features:
- |
This first version of a classical function compiler allows to "compile" typed python functions (operating only on
bits `Int1` at the moment) into quantum circuits.
.. jupyter-execute:
from qiskit.circuit import classical_function, Int1
@classical_function
def grover_oracle(a: Int1, b: Int1, c: Int1, d: Int1) -> Int1:
x = not a and b
y = d and not c
z = not x or y
return z
quantum_circuit = grover_oracle.synth()
quantum_circuit.draw()
The parameter ``registerless=False`` in :meth:`qiskit.circuit.classical_function.ClassicalFunction.synth` creates a
circuit with registers refering to the parameter names:
.. jupyter-execute:
quantum_circuit = grover_oracle.synth(registerless=False)
quantum_circuit.draw())
A decorated classical function can be used the same way as any other quantum gate when appending it to a circuit.
.. jupyter-execute:
circuit = QuantumCircuit(5)
circuit.append(grover_oracle, range(5))
quantum_circuit.draw())
The feature requires ``tweedledum``, a library for synthesizing quantum circuits, that can be installed via pip
with ``pip install tweedledum``.

View File

@ -22,3 +22,4 @@ sphinx-tabs>=1.1.11
sphinx-autodoc-typehints
jupyter-sphinx
pygments>=2.4
tweedledum

View File

@ -109,6 +109,7 @@ setup(
'visualization': ['matplotlib>=2.1', 'ipywidgets>=7.3.0',
'pydot', "pillow>=4.2.1", "pylatexenc>=1.4",
"seaborn>=0.9.0", "pygments>=2.4"],
'classical-function-compiler': ['tweedledum'],
'full-featured-simulators': ['qiskit-aer>=0.1'],
'crosstalk-pass': ['z3-solver>=4.7'],
},

View File

@ -198,7 +198,7 @@ class TestGateEquivalenceEqual(QiskitTestCase):
exclude = {'ControlledGate', 'DiagonalGate', 'UCGate', 'MCGupDiag',
'MCU1Gate', 'UnitaryGate', 'HamiltonianGate', 'MCPhaseGate',
'UCPauliRotGate', 'SingleQubitUnitary', 'MCXGate',
'VariadicZeroParamGate'}
'VariadicZeroParamGate', 'ClassicalFunction'}
cls._gate_classes = []
for aclass in class_list:
if aclass.__name__ not in exclude:

View File

@ -0,0 +1,13 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""ClassicalFunction compiler tests."""

View File

@ -0,0 +1,38 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=invalid-name, missing-function-docstring, undefined-variable
"""These are bad examples and raise errors in in the classicalfunction compiler"""
from qiskit.circuit import Int1, Int2
def id_no_type_arg(a) -> Int1:
return a
def id_no_type_return(a: Int1):
return a
def id_bad_return(a: Int1) -> Int2:
return a
def out_of_scope(a: Int1) -> Int1:
return a & c
def bit_not(a: Int1) -> Int1:
# Bitwise not does not operate on booleans (aka, bits), but int
return ~a

View File

@ -0,0 +1,60 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=invalid-name, missing-function-docstring
"""These examples should be handle by the classicalfunction compiler"""
from qiskit.circuit import Int1
def identity(a: Int1) -> Int1:
return a
def bit_and(a: Int1, b: Int1) -> Int1:
return a & b
def bit_or(a: Int1, b: Int1) -> Int1:
return a | b
def bool_or(a: Int1, b: Int1) -> Int1:
return a or b
def bool_not(a: Int1) -> Int1:
return not a
def and_and(a: Int1, b: Int1, c: Int1) -> Int1:
return a and b and c
def multiple_binop(a: Int1, b: Int1) -> Int1:
return (a or b) | (b & a) and (a & b)
def id_assing(a: Int1) -> Int1:
b = a
return b
def example1(a: Int1, b: Int1) -> Int1:
c = a & b
d = b | a
return c ^ a | d
def grover_oracle(a: Int1, b: Int1, c: Int1, d: Int1) -> Int1:
return not a and b and not c and d

View File

@ -0,0 +1,37 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests ClassicalFunction as a gate."""
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit import QuantumCircuit
from qiskit.circuit.library.standard_gates import XGate
from . import examples
class TestOracleDecomposition(QiskitTestCase):
"""Tests ClassicalFunction.decomposition."""
def test_grover_oracle(self):
""" grover_oracle.decomposition"""
oracle = compile_classical_function(examples.grover_oracle)
quantum_circuit = QuantumCircuit(5)
quantum_circuit.append(oracle, [2, 1, 0, 3, 4])
expected = QuantumCircuit(5)
expected.append(XGate().control(4, ctrl_state='0101'), [2, 1, 0, 3, 4])
self.assertEqual(quantum_circuit.decompose(), expected)

View File

@ -0,0 +1,50 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests the classicalfunction parser."""
from qiskit.circuit.classicalfunction import ClassicalFunctionParseError
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.test import QiskitTestCase
from . import bad_examples as examples
class TestParseFail(QiskitTestCase):
"""Tests bad_examples with the classicalfunction parser."""
def assertExceptionMessage(self, context, message):
"""Asserts the message of an exception context"""
self.assertTrue(message in context.exception.args[0])
def test_id_bad_return(self):
"""Trying to parse examples.id_bad_return raises ClassicalFunctionParseError"""
with self.assertRaises(ClassicalFunctionParseError) as context:
compile_classical_function(examples.id_bad_return)
self.assertExceptionMessage(context, 'return type error')
def test_id_no_type_arg(self):
"""Trying to parse examples.id_no_type_arg raises ClassicalFunctionParseError"""
with self.assertRaises(ClassicalFunctionParseError) as context:
compile_classical_function(examples.id_no_type_arg)
self.assertExceptionMessage(context, 'argument type is needed')
def test_id_no_type_return(self):
"""Trying to parse examples.id_no_type_return raises ClassicalFunctionParseError"""
with self.assertRaises(ClassicalFunctionParseError) as context:
compile_classical_function(examples.id_no_type_return)
self.assertExceptionMessage(context, 'return type is needed')
def test_out_of_scope(self):
"""Trying to parse examples.out_of_scope raises ClassicalFunctionParseError"""
with self.assertRaises(ClassicalFunctionParseError) as context:
compile_classical_function(examples.out_of_scope)
self.assertExceptionMessage(context, 'out of scope: c')

View File

@ -0,0 +1,29 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests LogicNetwork.simulate method."""
from ddt import ddt, data
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.test import QiskitTestCase
from .utils import get_truthtable_from_function, example_list
@ddt
class TestSimulate(QiskitTestCase):
"""Tests LogicNetwork.simulate method"""
@data(*example_list())
def test_(self, a_callable):
"""Tests LogicSimulate.simulate() on all the examples"""
network = compile_classical_function(a_callable)
truth_table = network.simulate()
self.assertEqual(truth_table, get_truthtable_from_function(a_callable))

View File

@ -0,0 +1,54 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests classicalfunction compiler synthesis."""
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import XGate
from . import examples
class TestSynthesis(QiskitTestCase):
"""Tests ClassicalFunction.synth method."""
def test_grover_oracle(self):
"""Synthesis of grover_oracle example"""
oracle = compile_classical_function(examples.grover_oracle)
quantum_circuit = oracle.synth()
expected = QuantumCircuit(5)
expected.append(XGate().control(4, ctrl_state='0101'), [0, 1, 2, 3, 4])
self.assertEqual(quantum_circuit.name, 'grover_oracle')
self.assertEqual(quantum_circuit, expected)
def test_grover_oracle_arg_regs(self):
"""Synthesis of grover_oracle example with arg_regs"""
oracle = compile_classical_function(examples.grover_oracle)
quantum_circuit = oracle.synth(registerless=False)
qr_a = QuantumRegister(1, 'a')
qr_b = QuantumRegister(1, 'b')
qr_c = QuantumRegister(1, 'c')
qr_d = QuantumRegister(1, 'd')
qr_return = QuantumRegister(1, 'return')
expected = QuantumCircuit(qr_d, qr_c, qr_b, qr_a, qr_return)
expected.append(XGate().control(4, ctrl_state='0101'),
[qr_d[0], qr_c[0], qr_b[0], qr_a[0], qr_return[0]])
self.assertEqual(quantum_circuit.name, 'grover_oracle')
self.assertEqual(quantum_circuit, expected)

View File

@ -0,0 +1,72 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests LogicNetwork.Tweedledum2Qiskit converter."""
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction.utils import tweedledum2qiskit
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import XGate
class TestTweedledum2Qiskit(QiskitTestCase):
"""Tests qiskit.transpiler.classicalfunction.utils.tweedledum2qiskit function."""
def test_x(self):
"""Single uncontrolled X"""
tweedledum_circuit = {'num_qubits': 1, 'gates': [{'gate': 'X', 'qubits': [0]}]}
circuit = tweedledum2qiskit(tweedledum_circuit)
expected = QuantumCircuit(1)
expected.x(0)
self.assertEqual(circuit, expected)
def test_cx_0_1(self):
"""CX(0, 1)"""
tweedledum_circuit = {'num_qubits': 2, 'gates': [{'gate': 'X',
'qubits': [1],
'control_qubits': [0],
'control_state': '1'}]}
circuit = tweedledum2qiskit(tweedledum_circuit)
expected = QuantumCircuit(2)
expected.append(XGate().control(1, ctrl_state='1'), [0, 1])
self.assertEqual(circuit, expected)
def test_cx_1_0(self):
"""CX(1, 0)"""
tweedledum_circuit = {'num_qubits': 2, 'gates': [{'gate': 'X',
'qubits': [0],
'control_qubits': [1],
'control_state': '1'}]}
circuit = tweedledum2qiskit(tweedledum_circuit)
expected = QuantumCircuit(2)
expected.append(XGate().control(1, ctrl_state='1'), [1, 0])
self.assertEqual(expected, circuit)
def test_cx_qreg(self):
"""CX(0, 1) with qregs parameter"""
qr = QuantumRegister(2, 'qr')
tweedledum_circuit = {'num_qubits': 2, 'gates': [{'gate': 'X',
'qubits': [0],
'control_qubits': [1],
'control_state': '1'}]}
circuit = tweedledum2qiskit(tweedledum_circuit, qregs=[qr])
expected = QuantumCircuit(qr)
expected.append(XGate().control(1, ctrl_state='1'), [qr[1], qr[0]])
self.assertEqual(expected, circuit)

View File

@ -0,0 +1,79 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests classicalfunction compiler type checker."""
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction import ClassicalFunctionCompilerTypeError
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from . import examples, bad_examples
class TestTypeCheck(QiskitTestCase):
"""Tests classicalfunction compiler type checker (good examples)."""
def test_id(self):
"""Tests examples.identity type checking"""
network = compile_classical_function(examples.identity)
self.assertEqual(network.args, ['a'])
self.assertEqual(network.types, [{'Int1': 'type', 'a': 'Int1', 'return': 'Int1'}])
def test_bool_not(self):
"""Tests examples.bool_not type checking"""
network = compile_classical_function(examples.bool_not)
self.assertEqual(network.args, ['a'])
self.assertEqual(network.types, [{'Int1': 'type', 'a': 'Int1', 'return': 'Int1'}])
def test_id_assign(self):
"""Tests examples.id_assing type checking"""
network = compile_classical_function(examples.id_assing)
self.assertEqual(network.args, ['a'])
self.assertEqual(network.types, [{'Int1': 'type', 'a': 'Int1',
'b': 'Int1', 'return': 'Int1'}])
def test_bit_and(self):
"""Tests examples.bit_and type checking"""
network = compile_classical_function(examples.bit_and)
self.assertEqual(network.args, ['a', 'b'])
self.assertEqual(network.types, [{'Int1': 'type', 'a': 'Int1',
'b': 'Int1', 'return': 'Int1'}])
def test_bit_or(self):
"""Tests examples.bit_or type checking"""
network = compile_classical_function(examples.bit_or)
self.assertEqual(network.args, ['a', 'b'])
self.assertEqual(network.types, [{'Int1': 'type', 'a': 'Int1',
'b': 'Int1', 'return': 'Int1'}])
def test_bool_or(self):
"""Tests examples.bool_or type checking"""
network = compile_classical_function(examples.bool_or)
self.assertEqual(network.args, ['a', 'b'])
self.assertEqual(network.types, [{'Int1': 'type', 'a': 'Int1',
'b': 'Int1', 'return': 'Int1'}])
class TestTypeCheckFail(QiskitTestCase):
"""Tests classicalfunction compiler type checker (bad examples)."""
def assertExceptionMessage(self, context, message):
"""Asserts the message of an exception context"""
self.assertTrue(message in context.exception.args[0])
def test_bit_not(self):
"""Int1wise not does not work on bit (aka bool)
~True # -2
~False # -1
"""
with self.assertRaises(ClassicalFunctionCompilerTypeError) as context:
compile_classical_function(bad_examples.bit_not)
self.assertExceptionMessage(context, 'does not operate with Int1 type')

View File

@ -0,0 +1,25 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Tests .utils.get_truthtable_from_function function"""
from qiskit.test import QiskitTestCase
from .utils import get_truthtable_from_function
from .examples import grover_oracle
class TestGetTruthtableFromFunction(QiskitTestCase):
"""Tests .utils.get_truthtable_from_function function"""
def test_grover_oracle(self):
"""Tests get_truthtable_from_function with examples.grover_oracle"""
truth_table = get_truthtable_from_function(grover_oracle)
self.assertEqual(truth_table, '0000010000000000')

View File

@ -0,0 +1,34 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""Testing utilities for classicalfunction compiler."""
from inspect import getfullargspec, isfunction
from . import examples
def get_truthtable_from_function(function):
"""Runs an classicalfunction function (on python) and generates a truthtable from it."""
amount_bit_input = len(getfullargspec(function).args)
result = ""
for decimal in range(2 ** amount_bit_input):
entry = bin(decimal)[2:].rjust(amount_bit_input, '0')
result += str(int(function(*[i == '1' for i in entry[::-1]])))
return result[::-1]
def example_list():
"""Creates a list with all the examples in examples.py"""
callables = [getattr(examples, example_name) for example_name in dir(examples)
if example_name[0] != '_']
return [func for func in callables
if isfunction(func) and 'examples.py' in func.__code__.co_filename]