Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make python-constraint optional #7733

Merged
merged 9 commits into from
May 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions qiskit/transpiler/passes/layout/_csp_custom_solver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# 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.

"""A custom python-constraint solver used by the :class:`~.CSPLayout` pass"""
from time import time

from qiskit.utils import optionals as _optionals

# This isn't ideal usage because we will import constraint at import time
# but to ensure the CustomSolver class is defined we need to do this.
# If constraint is not installed this will not raise a missing library
# exception until CSPLayout is initialized
if _optionals.HAS_CONSTRAINT:
from constraint import RecursiveBacktrackingSolver

class CustomSolver(RecursiveBacktrackingSolver):
"""A wrap to RecursiveBacktrackingSolver to support ``call_limit``"""

def __init__(self, call_limit=None, time_limit=None):
self.call_limit = call_limit
self.time_limit = time_limit
self.call_current = None
self.time_start = None
self.time_current = None
super().__init__()

def limit_reached(self):
"""Checks if a limit is reached."""
if self.call_current is not None:
self.call_current += 1
if self.call_current > self.call_limit:
return True
if self.time_start is not None:
self.time_current = time() - self.time_start
if self.time_current > self.time_limit:
return True
return False

def getSolution(self, domains, constraints, vconstraints):
"""Wrap RecursiveBacktrackingSolver.getSolution to add the limits."""
if self.call_limit is not None:
self.call_current = 0
if self.time_limit is not None:
self.time_start = time()
return super().getSolution(domains, constraints, vconstraints)

def recursiveBacktracking(self, solutions, domains, vconstraints, assignments, single):
"""Like ``constraint.RecursiveBacktrackingSolver.recursiveBacktracking`` but
limited in the amount of calls by ``self.call_limit``"""
if self.limit_reached():
return None
return super().recursiveBacktracking(
solutions, domains, vconstraints, assignments, single
)
46 changes: 5 additions & 41 deletions qiskit/transpiler/passes/layout/csp_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,52 +16,13 @@
found, no ``property_set['layout']`` is set.
"""
import random
from time import time
from constraint import Problem, RecursiveBacktrackingSolver, AllDifferentConstraint

from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass
from qiskit.utils import optionals as _optionals


class CustomSolver(RecursiveBacktrackingSolver):
"""A wrap to RecursiveBacktrackingSolver to support ``call_limit``"""

def __init__(self, call_limit=None, time_limit=None):
self.call_limit = call_limit
self.time_limit = time_limit
self.call_current = None
self.time_start = None
self.time_current = None
super().__init__()

def limit_reached(self):
"""Checks if a limit is reached."""
if self.call_current is not None:
self.call_current += 1
if self.call_current > self.call_limit:
return True
if self.time_start is not None:
self.time_current = time() - self.time_start
if self.time_current > self.time_limit:
return True
return False

def getSolution(self, domains, constraints, vconstraints):
"""Wrap RecursiveBacktrackingSolver.getSolution to add the limits."""
if self.call_limit is not None:
self.call_current = 0
if self.time_limit is not None:
self.time_start = time()
return super().getSolution(domains, constraints, vconstraints)

def recursiveBacktracking(self, solutions, domains, vconstraints, assignments, single):
"""Like ``constraint.RecursiveBacktrackingSolver.recursiveBacktracking`` but
limited in the amount of calls by ``self.call_limit``"""
if self.limit_reached():
return None
return super().recursiveBacktracking(solutions, domains, vconstraints, assignments, single)


@_optionals.HAS_CONSTRAINT.require_in_instance
class CSPLayout(AnalysisPass):
"""If possible, chooses a Layout as a CSP, using backtracking."""

Expand Down Expand Up @@ -102,6 +63,9 @@ def run(self, dag):
qubits = dag.qubits
cxs = set()

from constraint import Problem, AllDifferentConstraint, RecursiveBacktrackingSolver
from qiskit.transpiler.passes.layout._csp_custom_solver import CustomSolver

for gate in dag.two_qubit_ops():
cxs.add((qubits.index(gate.qargs[0]), qubits.index(gate.qargs[1])))
edges = set(self.coupling_map.get_edges())
Expand Down
11 changes: 10 additions & 1 deletion qiskit/utils/optionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
.. list-table::
:widths: 25 75

* - .. py:data:: HAS_CONSTRAINT
- `python-constraint <https://github.com/python-constraint/python-constraint>__ is a
constraint satisfaction problem solver, used in the :class:`~.CSPLayout` transpiler pass.

* - .. py:data:: HAS_CPLEX
- The `IBM CPLEX Optimizer <https://www.ibm.com/analytics/cplex-optimizer>`__ is a
high-performance mathematical programming solver for linear, mixed-integer and quadratic
Expand Down Expand Up @@ -142,7 +146,6 @@
- `Z3 <https://github.com/Z3Prover/z3>`__ is a theorem prover, used in the
:class:`.CrosstalkAdaptiveSchedule` and :class:`.HoareOptimizer` transpiler passes.


External Command-Line Tools
---------------------------

Expand Down Expand Up @@ -207,6 +210,12 @@
install="pip install qiskit-ignis",
)

HAS_CONSTRAINT = _LazyImportTester(
"constraint",
name="python-constraint",
install="pip install python-constraint",
)

HAS_CPLEX = _LazyImportTester(
"cplex",
install="pip install 'qiskit-terra[bip-mapper]'",
Expand Down
12 changes: 12 additions & 0 deletions releasenotes/notes/constraint-optional-b6a2b2ee21211ccd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
upgrade:
- |
The ``python-constraint`` dependency, which is used solely by the
:class:`~.CSPLayout` transpiler pass, is no longer in the requirements list
for the Qiskit Terra package. This is because the :class:`~.CSPLayout` pass
is no longer used by default in any of the preset pass managers for
:func:`~.transpile`. While the pass is still available, if you're using it
you will need to manually install ``python-contraint`` or when you
install ``qiskit-terra`` you can use the ``csp-layout`` extra, for example::

pip install "qiskit-terra[csp-layout]"
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
setuptools-rust
coverage>=4.4.0
hypothesis>=4.24.3
python-constraint>=1.4
ipython<7.22.0
ipykernel<5.5.2
ipywidgets>=7.3.0
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ psutil>=5
scipy>=1.5
sympy>=1.3
dill>=0.3
python-constraint>=1.4
python-dateutil>=2.8.0
stevedore>=3.0.0
symengine>=0.9 ; platform_machine == 'x86_64' or platform_machine == 'aarch64' or platform_machine == 'ppc64le' or platform_machine == 'amd64' or platform_machine == 'arm64'
Expand Down
8 changes: 3 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@
)


csplayout_requirements = [
"python-constraint>=1.4",
]
visualization_extras = [
"matplotlib>=3.3",
"ipywidgets>=7.3.0",
Expand All @@ -49,6 +46,7 @@
"z3-solver>=4.7",
]
bip_requirements = ["cplex", "docplex"]
csp_requirements = ["python-constraint>=1.4"]
mtreinish marked this conversation as resolved.
Show resolved Hide resolved


setup(
Expand Down Expand Up @@ -85,11 +83,11 @@
"visualization": visualization_extras,
"bip-mapper": bip_requirements,
"crosstalk-pass": z3_requirements,
"csp-layout-pass": csplayout_requirements,
"csp-layout-pass": csp_requirements,
# Note: 'all' does not include 'bip-mapper' because cplex is too fiddly and too little
# supported on various Python versions and OSes compared to Terra. You have to ask for it
# explicitly.
"all": visualization_extras + z3_requirements + csplayout_requirements,
"all": visualization_extras + z3_requirements + csp_requirements,
},
project_urls={
"Bug Tracker": "https://github.com/Qiskit/qiskit-terra/issues",
Expand Down