Skip to content

Commit

Permalink
Make python-constraint optional
Browse files Browse the repository at this point in the history
Since Qiskit#7213 we no longer have been using the CSPLayout pass by default
in the preset passmanagers or transpile(). This is because it has been
superseded by the VF2Layout pass which is now used everywhere. While we
will keep the CSPLayout pass around for the forseeable future there is
no need to install python-constraint by default anymore since it's only
user is the CSPLayout pass, which isn't going to be commonly used
anymore now that it's not used in the default compilation path anymore.
This commit removes the python-constraint library from the requirements
list and makes it an optional dependency.

Fixes Qiskit#7726
  • Loading branch information
mtreinish committed May 25, 2022
1 parent be18775 commit cc33004
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 47 deletions.
95 changes: 53 additions & 42 deletions qiskit/transpiler/passes/layout/csp_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,51 +17,60 @@
"""
import random
from time import time
from constraint import Problem, RecursiveBacktrackingSolver, AllDifferentConstraint

from qiskit.transpiler.layout import Layout
from qiskit.transpiler.basepasses import AnalysisPass


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)


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
)


@_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 +111,8 @@ def run(self, dag):
qubits = dag.qubits
cxs = set()

from constraint import Problem, AllDifferentConstraint

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
6 changes: 6 additions & 0 deletions qiskit/utils/optionals.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,9 @@ def _nlopt_callback(available):
("pdftocairo", "-v"),
msg="This is part of the 'poppler' set of PDF utilities",
)

HAS_CONSTRAINT = _LazyImportTester(
"constraint",
name="python-constraint",
install="pip install python-constraint",
)
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 passs 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
6 changes: 2 additions & 4 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"]


setup(
Expand Down Expand Up @@ -89,7 +87,7 @@
# 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

0 comments on commit cc33004

Please sign in to comment.