Make tweedledum a hard requirement (#6588)

* Make tweedledum a hard requirement

This commit switches the tweedledum requirement from being optional to a
hard requirement for installing qiskit-terra. We rely on tweedledum to
synthesize phase oracles which is commonly used functionality and several
issues have been opened. This use of tweedledum will likely continue to
grow so we should just list it as a requirement moving forward.

We originally made it optional because the functionality depending on
tweedledum was isolated to just the classical function compiler which
wasn't widely used. There were also packaging issues in the past where
the available precompiled binaries for tweedledum didn't support all of
our supported environments, but those have been resolved. There is still
an issue for arm64 macOS binaries but Qiskit doesn't have wide support
for that yet (although there is a job for terra).

At the same time this commit cleans up the optional requirements list
so that aer is no longer listed there and we add an 'all' extra so that
people can have a simple entypoint to install all the optional extras at
once.

Fixes #6333
Fixes Qiskit/qiskit#1253

* Remove unused imports

* Cleanup docstrings

* black setup

* Update requirements.txt

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

Co-authored-by: Luciano Bello <bel@zurich.ibm.com>
Co-authored-by: Bruno Schmitt <bruno.schmitt@epfl.ch>
This commit is contained in:
Matthew Treinish 2021-07-01 10:46:56 -04:00 committed by GitHub
parent 8c062c7772
commit 927fd0226b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 60 additions and 107 deletions

View File

@ -33,7 +33,7 @@ unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=retworkx, numpy
extension-pkg-whitelist=retworkx, numpy, tweedledum
[MESSAGES CONTROL]

View File

@ -12,14 +12,14 @@
"""A quantum oracle constructed from a logical expression or a string in the DIMACS format."""
from os.path import basename, isfile
from typing import Callable, Optional
from os.path import basename, isfile
from tweedledum import BitVec, BoolFunction
from tweedledum.synthesis import pkrm_synth
from qiskit.circuit import QuantumCircuit
from qiskit.exceptions import MissingOptionalLibraryError
from .classical_element import ClassicalElement
from .utils import HAS_TWEEDLEDUM
class BooleanExpression(ClassicalElement):
@ -31,17 +31,7 @@ class BooleanExpression(ClassicalElement):
expression (str): The logical expression string.
name (str): Optional. Instruction gate name. Otherwise part of
the expression is going to be used.
Raises:
MissingOptionalLibraryError: If tweedledum is not installed. Tweedledum is required.
"""
if not HAS_TWEEDLEDUM:
raise MissingOptionalLibraryError(
libname="tweedledum",
name="BooleanExpression compiler",
pip_install="pip install tweedledum",
)
from tweedledum import BoolFunction
self._tweedledum_bool_expression = BoolFunction.from_expression(expression)
@ -63,8 +53,6 @@ class BooleanExpression(ClassicalElement):
Returns:
bool: result of the evaluation.
"""
from tweedledum import BitVec
bits = []
for bit in bitstring:
bits.append(BitVec(1, bit))
@ -92,8 +80,7 @@ class BooleanExpression(ClassicalElement):
qregs = None # TODO: Probably from self._tweedledum_bool_expression._signature
if synthesizer is None:
from tweedledum.synthesis import pkrm_synth # pylint: disable=no-name-in-module
from .utils import tweedledum2qiskit
from .utils import tweedledum2qiskit # Avoid an import cycle
truth_table = self._tweedledum_bool_expression.truth_table(output_bit=0)
return tweedledum2qiskit(pkrm_synth(truth_table), name=self.name, qregs=qregs)
@ -113,16 +100,8 @@ class BooleanExpression(ClassicalElement):
BooleanExpression: A gate for the input string
Raises:
MissingOptionalLibraryError: If tweedledum is not installed. Tweedledum is required.
FileNotFoundError: If filename is not found.
"""
if not HAS_TWEEDLEDUM:
raise MissingOptionalLibraryError(
libname="tweedledum",
name="BooleanExpression compiler",
pip_install="pip install tweedledum",
)
from tweedledum import BoolFunction
expr_obj = cls.__new__(cls)
if not isfile(filename):

View File

@ -17,8 +17,8 @@ This module is used internally by ``qiskit.transpiler.classicalfunction.Classica
import ast
import _ast
from qiskit.exceptions import MissingOptionalLibraryError
from .utils import HAS_TWEEDLEDUM
from tweedledum.classical import LogicNetwork
from .exceptions import ClassicalFunctionParseError, ClassicalFunctionCompilerTypeError
@ -36,12 +36,6 @@ class ClassicalFunctionVisitor(ast.NodeVisitor):
}
def __init__(self):
if not HAS_TWEEDLEDUM:
raise MissingOptionalLibraryError(
libname="tweedledum",
name="classical function compiler",
pip_install="pip install tweedledum",
)
self.scopes = []
self.args = []
self._network = None
@ -57,14 +51,6 @@ class ClassicalFunctionVisitor(ast.NodeVisitor):
def visit_FunctionDef(self, node):
"""The function definition should have type hints"""
if HAS_TWEEDLEDUM:
from tweedledum.classical import LogicNetwork # pylint: disable=no-name-in-module
else:
raise MissingOptionalLibraryError(
libname="tweedledum",
name="classical function compiler",
pip_install="pip install tweedledum",
)
if node.returns is None:
raise ClassicalFunctionParseError("return type is needed")
scope = {"return": (node.returns.id, None), node.returns.id: ("type", None)}

View File

@ -15,11 +15,14 @@
import ast
from typing import Callable, Optional
from tweedledum.classical import simulate
from tweedledum.synthesis import pkrm_synth
from qiskit.circuit import QuantumCircuit, QuantumRegister
from qiskit.exceptions import MissingOptionalLibraryError, QiskitError
from qiskit.exceptions import QiskitError
from .classical_element import ClassicalElement
from .utils import HAS_TWEEDLEDUM
from .classical_function_visitor import ClassicalFunctionVisitor
from .utils import tweedledum2qiskit
class ClassicalFunction(ClassicalElement):
@ -35,17 +38,10 @@ class ClassicalFunction(ClassicalElement):
name (str): Optional. Default: "*classicalfunction*". ClassicalFunction name.
Raises:
MissingOptionalLibraryError: 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 MissingOptionalLibraryError(
libname="tweedledum",
name="classical function compiler",
pip_install="pip install tweedledum",
)
self._ast = ast.parse(source)
self._network = None
self._scopes = None
@ -111,8 +107,6 @@ class ClassicalFunction(ClassicalElement):
Returns:
bool: result of the evaluation.
"""
from tweedledum.classical import simulate # pylint: disable=no-name-in-module
return simulate(self._network, bitstring)
def simulate_all(self):
@ -133,8 +127,6 @@ class ClassicalFunction(ClassicalElement):
def truth_table(self):
"""Returns (and computes) the truth table"""
if self._truth_table is None:
from tweedledum.classical import simulate # pylint: disable=no-name-in-module
self._truth_table = simulate(self._network)
return self._truth_table
@ -161,9 +153,6 @@ class ClassicalFunction(ClassicalElement):
if synthesizer:
return synthesizer(self)
from .utils import tweedledum2qiskit
from tweedledum.synthesis import pkrm_synth # pylint: disable=no-name-in-module
return tweedledum2qiskit(pkrm_synth(self.truth_table[0]), name=self.name, qregs=qregs)
def _define(self):

View File

@ -12,13 +12,8 @@
"""Internal utils for Classical Function Compiler"""
try:
from tweedledum.ir import Qubit # pylint: disable=no-name-in-module
from tweedledum.passes import parity_decomp # pylint: disable=no-name-in-module
HAS_TWEEDLEDUM = True
except Exception: # pylint: disable=broad-except
HAS_TWEEDLEDUM = False
from tweedledum.ir import Qubit
from tweedledum.passes import parity_decomp
from qiskit.circuit import QuantumCircuit

View File

@ -0,0 +1,25 @@
---
upgrade:
- |
The `tweedledum <https://pypi.org/project/tweedledum/>`__ library which
was previously an optional dependency has been made a requirement. This
was done because of the wide use of the
:class:`~qiskit.circuit.library.PhaseOracle` (which depends on
having tweedledum installed) with several algorithms
from :mod:`qiskit.algorithms`.
- |
The optional extra ``full-featured-simulators`` which could previously used
to install ``qiskit-aer`` with something like
``pip install qiskit-terra[full-featured-simulators]`` has been removed
from the qiskit-terra package. If this was being used to install
``qiskit-aer`` with ``qiskit-terra`` instead you should rely on the
`qiskit <https://pypi.org/project/qiskit/>`__ metapackage or just install
qiskit-terra and qiskit-aer together with
``pip install qiskit-terra qiskit-aer``.
features:
- |
A new optional extra ``all`` has been added to the qiskit-terra package.
This enables installing all the optional requirements with a single
extra, for example: ``pip install 'qiskit-terra[all]'``, Previously, it
was necessary to list all the extras individually to install all the
optional dependencies simultaneously.

View File

@ -11,3 +11,4 @@ fastjsonschema>=2.10
python-constraint>=1.4
python-dateutil>=2.8.0
symengine>0.7 ; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64'
tweedledum>=1.1,<2.0

View File

@ -77,6 +77,23 @@ with open(README_PATH) as readme_file:
flags=re.S | re.M,
)
visualization_extras = [
"matplotlib>=2.1",
"ipywidgets>=7.3.0",
"pydot",
"pillow>=4.2.1",
"pylatexenc>=1.4",
"seaborn>=0.9.0",
"pygments>=2.4",
]
z3_requirements = [
"z3-solver>=4.7",
]
setup(
name="qiskit-terra",
version="0.18.0",
@ -109,19 +126,10 @@ setup(
include_package_data=True,
python_requires=">=3.6",
extras_require={
"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>=1.0,<2.0"],
"full-featured-simulators": ["qiskit-aer>=0.1"],
"crosstalk-pass": ["z3-solver>=4.7"],
"visualization": visualization_extras,
"bip-mapper": ["cplex", "docplex"],
"crosstalk-pass": z3_requirements,
"all": visualization_extras + z3_requirements,
},
project_urls={
"Bug Tracker": "https://github.com/Qiskit/qiskit-terra/issues",

View File

@ -11,12 +11,9 @@
# that they have been altered from the originals.
"""Tests ClassicalFunction as a gate."""
import unittest
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.circuit.classicalfunction.classicalfunction import HAS_TWEEDLEDUM
from qiskit import QuantumCircuit
from qiskit.circuit.library.standard_gates import XGate
@ -27,7 +24,6 @@ from . import examples
class TestOracleDecomposition(QiskitTestCase):
"""Tests ClassicalFunction.decomposition."""
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_grover_oracle(self):
"""grover_oracle.decomposition"""
oracle = compile_classical_function(examples.grover_oracle)

View File

@ -11,11 +11,8 @@
# that they have been altered from the originals.
"""Tests the classicalfunction parser."""
import unittest
from qiskit.circuit.classicalfunction import ClassicalFunctionParseError
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.circuit.classicalfunction.classicalfunction import HAS_TWEEDLEDUM
from qiskit.test import QiskitTestCase
from . import bad_examples as examples
@ -28,28 +25,24 @@ class TestParseFail(QiskitTestCase):
"""Asserts the message of an exception context"""
self.assertTrue(message in context.exception.args[0])
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
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")
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
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")
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
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")
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_out_of_scope(self):
"""Trying to parse examples.out_of_scope raises ClassicalFunctionParseError"""
with self.assertRaises(ClassicalFunctionParseError) as context:

View File

@ -11,11 +11,8 @@
# that they have been altered from the originals.
"""Tests LogicNetwork.simulate method."""
import unittest
from ddt import ddt, data
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.circuit.classicalfunction.classicalfunction import HAS_TWEEDLEDUM
from qiskit.test import QiskitTestCase
from .utils import get_truthtable_from_function, example_list
@ -25,7 +22,6 @@ class TestSimulate(QiskitTestCase):
"""Tests LogicNetwork.simulate method"""
@data(*example_list())
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_(self, a_callable):
"""Tests LogicSimulate.simulate() on all the examples"""
network = compile_classical_function(a_callable)

View File

@ -11,12 +11,9 @@
# that they have been altered from the originals.
"""Tests classicalfunction compiler synthesis."""
import unittest
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.circuit.classicalfunction.classicalfunction import HAS_TWEEDLEDUM
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.circuit.library.standard_gates import XGate
@ -27,7 +24,6 @@ from . import examples
class TestSynthesis(QiskitTestCase):
"""Tests ClassicalFunction.synth method."""
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_grover_oracle(self):
"""Synthesis of grover_oracle example"""
oracle = compile_classical_function(examples.grover_oracle)
@ -39,7 +35,6 @@ class TestSynthesis(QiskitTestCase):
self.assertEqual(quantum_circuit.name, "grover_oracle")
self.assertEqual(quantum_circuit, expected)
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_grover_oracle_arg_regs(self):
"""Synthesis of grover_oracle example with arg_regs"""
oracle = compile_classical_function(examples.grover_oracle)

View File

@ -11,12 +11,9 @@
# that they have been altered from the originals.
"""Tests classicalfunction compiler type checker."""
import unittest
from qiskit.test import QiskitTestCase
from qiskit.circuit.classicalfunction import ClassicalFunctionCompilerTypeError
from qiskit.circuit.classicalfunction import classical_function as compile_classical_function
from qiskit.circuit.classicalfunction.classicalfunction import HAS_TWEEDLEDUM
from . import examples, bad_examples
@ -24,21 +21,18 @@ from . import examples, bad_examples
class TestTypeCheck(QiskitTestCase):
"""Tests classicalfunction compiler type checker (good examples)."""
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
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"}])
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
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"}])
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_id_assign(self):
"""Tests examples.id_assing type checking"""
network = compile_classical_function(examples.id_assing)
@ -47,7 +41,6 @@ class TestTypeCheck(QiskitTestCase):
network.types, [{"Int1": "type", "a": "Int1", "b": "Int1", "return": "Int1"}]
)
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_bit_and(self):
"""Tests examples.bit_and type checking"""
network = compile_classical_function(examples.bit_and)
@ -56,7 +49,6 @@ class TestTypeCheck(QiskitTestCase):
network.types, [{"Int1": "type", "a": "Int1", "b": "Int1", "return": "Int1"}]
)
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_bit_or(self):
"""Tests examples.bit_or type checking"""
network = compile_classical_function(examples.bit_or)
@ -65,7 +57,6 @@ class TestTypeCheck(QiskitTestCase):
network.types, [{"Int1": "type", "a": "Int1", "b": "Int1", "return": "Int1"}]
)
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_bool_or(self):
"""Tests examples.bool_or type checking"""
network = compile_classical_function(examples.bool_or)
@ -82,7 +73,6 @@ class TestTypeCheckFail(QiskitTestCase):
"""Asserts the message of an exception context"""
self.assertTrue(message in context.exception.args[0])
@unittest.skipUnless(HAS_TWEEDLEDUM, "tweedledum not available")
def test_bit_not(self):
"""Int1wise not does not work on bit (aka bool)
~True # -2