mirror of https://github.com/Qiskit/qiskit.git
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:
parent
e8418fb601
commit
bbe1f1c973
|
@ -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
|
||||
|
|
|
@ -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__)
|
|
@ -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())
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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``.
|
|
@ -22,3 +22,4 @@ sphinx-tabs>=1.1.11
|
|||
sphinx-autodoc-typehints
|
||||
jupyter-sphinx
|
||||
pygments>=2.4
|
||||
tweedledum
|
||||
|
|
1
setup.py
1
setup.py
|
@ -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'],
|
||||
},
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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."""
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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')
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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')
|
|
@ -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')
|
|
@ -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]
|
Loading…
Reference in New Issue