python-constraint2 2.0.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- constraint/__init__.py +37 -0
- constraint/constraints.c +29807 -0
- constraint/constraints.py +786 -0
- constraint/domain.c +9306 -0
- constraint/domain.py +102 -0
- constraint/problem.c +16329 -0
- constraint/problem.py +256 -0
- constraint/solvers.c +23963 -0
- constraint/solvers.py +592 -0
- python_constraint2-2.0.0.dist-info/LICENSE +23 -0
- python_constraint2-2.0.0.dist-info/METADATA +238 -0
- python_constraint2-2.0.0.dist-info/RECORD +22 -0
- python_constraint2-2.0.0.dist-info/WHEEL +4 -0
- tests/__init__.py +0 -0
- tests/setup_teardown.py +40 -0
- tests/test_compilation.py +17 -0
- tests/test_constraint.py +127 -0
- tests/test_doctests.py +10 -0
- tests/test_problem.py +27 -0
- tests/test_solvers.py +72 -0
- tests/test_some_not_in_set.py +99 -0
- tests/test_toml_file.py +72 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: python-constraint2
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: python-constraint is a module for efficiently solving CSPs (Constraint Solving Problems) over finite domains.
|
|
5
|
+
License: BSD-2-Clause
|
|
6
|
+
Keywords: CSP,constraint solving problems,problem solver,SMT,satisfiability modulo theory,SAT
|
|
7
|
+
Author: Gustavo Niemeyer
|
|
8
|
+
Author-email: gustavo@niemeyer.net
|
|
9
|
+
Maintainer: Floris-Jan Willemsen
|
|
10
|
+
Maintainer-email: fjwillemsen97@gmail.com
|
|
11
|
+
Requires-Python: >=3.9,<3.14
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Education
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
18
|
+
Classifier: Natural Language :: English
|
|
19
|
+
Classifier: Programming Language :: C
|
|
20
|
+
Classifier: Programming Language :: Cython
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Classifier: Topic :: Scientific/Engineering
|
|
28
|
+
Classifier: Topic :: Software Development
|
|
29
|
+
Project-URL: Documentation, http://python-constraint.github.io/python-constraint/
|
|
30
|
+
Project-URL: Repository, https://github.com/python-constraint/python-constraint
|
|
31
|
+
Description-Content-Type: text/x-rst
|
|
32
|
+
|
|
33
|
+
|License| |Build Status| |Docs| |Python Versions| |Downloads| |Status| |Code Coverage|
|
|
34
|
+
|
|
35
|
+
.. image:: https://github.com/python-constraint/python-constraint/raw/main/docs/assets/logo/N-Queens_problem_Python.svg
|
|
36
|
+
:align: center
|
|
37
|
+
:width: 50%
|
|
38
|
+
|
|
39
|
+
python-constraint
|
|
40
|
+
=================
|
|
41
|
+
|
|
42
|
+
| This software is now back to active development / maintainance status.
|
|
43
|
+
| IMPORTANT: the new version can be installed with `pip install python-constraint2`, as the original pip release will not be updated.
|
|
44
|
+
| For an overview of recent changes, visit the `Changelog <https://github.com/python-constraint/python-constraint/blob/main/CHANGELOG.md>`_.
|
|
45
|
+
| The complete documentation can be found `here <http://python-constraint.github.io/python-constraint/>`_.
|
|
46
|
+
|
|
47
|
+
.. contents::
|
|
48
|
+
:local:
|
|
49
|
+
:depth: 1
|
|
50
|
+
|
|
51
|
+
Introduction
|
|
52
|
+
------------
|
|
53
|
+
The :code:`python-constraint` module offers efficient solvers for `Constraint Satisfaction Problems (CSPs) <https://en.wikipedia.org/wiki/Constraint_satisfaction_problem>`_ over finite domains in an accessible Python package.
|
|
54
|
+
CSP is class of problems which may be represented in terms of variables (a, b, ...), domains (a in [1, 2, 3], ...), and constraints (a < b, ...).
|
|
55
|
+
|
|
56
|
+
Examples
|
|
57
|
+
--------
|
|
58
|
+
|
|
59
|
+
Basics
|
|
60
|
+
~~~~~~
|
|
61
|
+
|
|
62
|
+
This interactive Python session demonstrates basic operations:
|
|
63
|
+
|
|
64
|
+
.. code-block:: python
|
|
65
|
+
|
|
66
|
+
>>> from constraint import *
|
|
67
|
+
>>> problem = Problem()
|
|
68
|
+
>>> problem.addVariable("a", [1,2,3])
|
|
69
|
+
>>> problem.addVariable("b", [4,5,6])
|
|
70
|
+
>>> problem.getSolutions()
|
|
71
|
+
[{'a': 3, 'b': 6}, {'a': 3, 'b': 5}, {'a': 3, 'b': 4},
|
|
72
|
+
{'a': 2, 'b': 6}, {'a': 2, 'b': 5}, {'a': 2, 'b': 4},
|
|
73
|
+
{'a': 1, 'b': 6}, {'a': 1, 'b': 5}, {'a': 1, 'b': 4}]
|
|
74
|
+
|
|
75
|
+
>>> problem.addConstraint(lambda a, b: a*2 == b,
|
|
76
|
+
("a", "b"))
|
|
77
|
+
>>> problem.getSolutions()
|
|
78
|
+
[{'a': 3, 'b': 6}, {'a': 2, 'b': 4}]
|
|
79
|
+
|
|
80
|
+
>>> problem = Problem()
|
|
81
|
+
>>> problem.addVariables(["a", "b"], [1, 2, 3])
|
|
82
|
+
>>> problem.addConstraint(AllDifferentConstraint())
|
|
83
|
+
>>> problem.getSolutions()
|
|
84
|
+
[{'a': 3, 'b': 2}, {'a': 3, 'b': 1}, {'a': 2, 'b': 3},
|
|
85
|
+
{'a': 2, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 3}]
|
|
86
|
+
|
|
87
|
+
Rooks problem
|
|
88
|
+
~~~~~~~~~~~~~
|
|
89
|
+
|
|
90
|
+
The following example solves the classical Eight Rooks problem:
|
|
91
|
+
|
|
92
|
+
.. code-block:: python
|
|
93
|
+
|
|
94
|
+
>>> problem = Problem()
|
|
95
|
+
>>> numpieces = 8
|
|
96
|
+
>>> cols = range(numpieces)
|
|
97
|
+
>>> rows = range(numpieces)
|
|
98
|
+
>>> problem.addVariables(cols, rows)
|
|
99
|
+
>>> for col1 in cols:
|
|
100
|
+
... for col2 in cols:
|
|
101
|
+
... if col1 < col2:
|
|
102
|
+
... problem.addConstraint(lambda row1, row2: row1 != row2,
|
|
103
|
+
... (col1, col2))
|
|
104
|
+
>>> solutions = problem.getSolutions()
|
|
105
|
+
>>> solutions
|
|
106
|
+
>>> solutions
|
|
107
|
+
[{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1, 7: 0},
|
|
108
|
+
{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 0, 7: 1},
|
|
109
|
+
{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 2, 7: 0},
|
|
110
|
+
{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 0, 7: 2},
|
|
111
|
+
...
|
|
112
|
+
{0: 7, 1: 5, 2: 3, 3: 6, 4: 2, 5: 1, 6: 4, 7: 0},
|
|
113
|
+
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 0, 7: 4},
|
|
114
|
+
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 4, 7: 0},
|
|
115
|
+
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 2, 7: 0},
|
|
116
|
+
{0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 0, 7: 2},
|
|
117
|
+
...]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Magic squares
|
|
121
|
+
~~~~~~~~~~~~~
|
|
122
|
+
|
|
123
|
+
This example solves a 4x4 magic square:
|
|
124
|
+
|
|
125
|
+
.. code-block:: python
|
|
126
|
+
|
|
127
|
+
>>> problem = Problem()
|
|
128
|
+
>>> problem.addVariables(range(0, 16), range(1, 16 + 1))
|
|
129
|
+
>>> problem.addConstraint(AllDifferentConstraint(), range(0, 16))
|
|
130
|
+
>>> problem.addConstraint(ExactSumConstraint(34), [0, 5, 10, 15])
|
|
131
|
+
>>> problem.addConstraint(ExactSumConstraint(34), [3, 6, 9, 12])
|
|
132
|
+
>>> for row in range(4):
|
|
133
|
+
... problem.addConstraint(ExactSumConstraint(34),
|
|
134
|
+
[row * 4 + i for i in range(4)])
|
|
135
|
+
>>> for col in range(4):
|
|
136
|
+
... problem.addConstraint(ExactSumConstraint(34),
|
|
137
|
+
[col + 4 * i for i in range(4)])
|
|
138
|
+
>>> solutions = problem.getSolutions()
|
|
139
|
+
|
|
140
|
+
Features
|
|
141
|
+
--------
|
|
142
|
+
|
|
143
|
+
The following solvers are available:
|
|
144
|
+
|
|
145
|
+
- Backtracking solver
|
|
146
|
+
- Optimized backtracking solver
|
|
147
|
+
- Recursive backtracking solver
|
|
148
|
+
- Minimum conflicts solver
|
|
149
|
+
|
|
150
|
+
.. role:: python(code)
|
|
151
|
+
:language: python
|
|
152
|
+
|
|
153
|
+
Predefined constraint types currently available:
|
|
154
|
+
|
|
155
|
+
- :python:`FunctionConstraint`
|
|
156
|
+
- :python:`AllDifferentConstraint`
|
|
157
|
+
- :python:`AllEqualConstraint`
|
|
158
|
+
- :python:`MaxSumConstraint`
|
|
159
|
+
- :python:`ExactSumConstraint`
|
|
160
|
+
- :python:`MinSumConstraint`
|
|
161
|
+
- :python:`MaxProdConstraint`
|
|
162
|
+
- :python:`MinProdConstraint`
|
|
163
|
+
- :python:`InSetConstraint`
|
|
164
|
+
- :python:`NotInSetConstraint`
|
|
165
|
+
- :python:`SomeInSetConstraint`
|
|
166
|
+
- :python:`SomeNotInSetConstraint`
|
|
167
|
+
|
|
168
|
+
API documentation
|
|
169
|
+
-----------------
|
|
170
|
+
Documentation for the module is available at: http://python-constraint.github.io/python-constraint/.
|
|
171
|
+
It can be built locally by running :code:`make clean html` from the `docs` folder.
|
|
172
|
+
For viewing RST files locally, `restview <https://pypi.org/project/restview/>`_ is recommended.
|
|
173
|
+
|
|
174
|
+
Download and install
|
|
175
|
+
--------------------
|
|
176
|
+
|
|
177
|
+
.. code-block:: shell
|
|
178
|
+
|
|
179
|
+
$ pip install python-constraint2
|
|
180
|
+
|
|
181
|
+
Testing
|
|
182
|
+
-------
|
|
183
|
+
|
|
184
|
+
Run :code:`nox` (tests for all supported Python versions in own virtual environment).
|
|
185
|
+
|
|
186
|
+
To test against your local Python version: make sure you have the development dependencies installed.
|
|
187
|
+
Run :code:`pytest` (optionally add :code:`--no-cov` if you have the C-extensions enabled).
|
|
188
|
+
|
|
189
|
+
Contributing
|
|
190
|
+
------------
|
|
191
|
+
|
|
192
|
+
Feel free to contribute by `submitting pull requests <https://github.com/python-constraint/python-constraint/pulls>`_ or `opening issues <https://github.com/python-constraint/python-constraint/issues>`_.
|
|
193
|
+
Please refer to the `contribution guidelines <https://github.com/python-constraint/python-constraint/contribute>`_ before doing so.
|
|
194
|
+
|
|
195
|
+
Roadmap
|
|
196
|
+
-------
|
|
197
|
+
|
|
198
|
+
This GitHub organization and repository is a global effort to help to maintain :code:`python-constraint`, which was written by Gustavo Niemeyer and originaly located at https://labix.org/python-constraint.
|
|
199
|
+
For an overview of recent changes, visit the `Changelog <https://github.com/python-constraint/python-constraint/blob/main/CHANGELOG.md>`_.
|
|
200
|
+
|
|
201
|
+
Planned development:
|
|
202
|
+
|
|
203
|
+
- Add parallel-capable solver
|
|
204
|
+
- Add a string parser for constraints
|
|
205
|
+
- Versioned documentation
|
|
206
|
+
|
|
207
|
+
Contact
|
|
208
|
+
-------
|
|
209
|
+
- `Floris-Jan Willemsen <https://github.com/fjwillemsen>`_ <fjwillemsen97@gmail.com> (current maintainer)
|
|
210
|
+
- `Sébastien Celles <https://github.com/s-celles/>`_ <s.celles@gmail.com> (former maintainer)
|
|
211
|
+
- `Gustavo Niemeyer <https://github.com/niemeyer/>`_ <gustavo@niemeyer.net> (initial developer)
|
|
212
|
+
|
|
213
|
+
But it's probably better to `open an issue <https://github.com/python-constraint/python-constraint/issues>`_.
|
|
214
|
+
|
|
215
|
+
.. |License| image:: https://img.shields.io/pypi/l/python-constraint2
|
|
216
|
+
:alt: PyPI - License
|
|
217
|
+
|
|
218
|
+
.. |Build Status| image:: https://github.com/python-constraint/python-constraint/actions/workflows/build-test-python-package.yml/badge.svg
|
|
219
|
+
:target: https://github.com/python-constraint/python-constraint/actions/workflows/build-test-python-package.yml
|
|
220
|
+
:alt: Build Status
|
|
221
|
+
|
|
222
|
+
.. |Docs| image:: https://img.shields.io/github/actions/workflow/status/python-constraint/python-constraint/publish-documentation.yml?label=Docs
|
|
223
|
+
:target: http://python-constraint.github.io/python-constraint/
|
|
224
|
+
:alt: Documentation Status
|
|
225
|
+
|
|
226
|
+
.. |Python Versions| image:: https://img.shields.io/pypi/pyversions/python-constraint2
|
|
227
|
+
:alt: PyPI - Python Versions
|
|
228
|
+
|
|
229
|
+
.. |Downloads| image:: https://img.shields.io/pypi/dm/python-constraint2
|
|
230
|
+
:alt: PyPI - Downloads
|
|
231
|
+
|
|
232
|
+
.. |Status| image:: https://img.shields.io/pypi/status/python-constraint2
|
|
233
|
+
:alt: PyPI - Status
|
|
234
|
+
|
|
235
|
+
.. |Code Coverage| image:: https://coveralls.io/repos/github/python-constraint/python-constraint/badge.svg
|
|
236
|
+
:target: https://coveralls.io/github/python-constraint/python-constraint
|
|
237
|
+
:alt: Code Coverage
|
|
238
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
constraint/__init__.py,sha256=7on8Mi-QJJbw8EwG9dahObY7MgpOdTVkgky0Vs68kJA,1845
|
|
2
|
+
constraint/constraints.c,sha256=sLlLgqvL9ZFLe8-5DWPRj5qdbtJDEDUGQN_uPX89BkQ,1288172
|
|
3
|
+
constraint/constraints.py,sha256=rPdVdTTQflJVneGeTZ5q6WNYOU7mmmjx5boR7VC2uQs,31371
|
|
4
|
+
constraint/domain.c,sha256=j_4sp_6FzlDsn7UpF4oe3cgFiIOxAZSt5dST8jrgptU,359677
|
|
5
|
+
constraint/domain.py,sha256=XLzl619prCdB8vxSLAVQWQiFwEV2qRzyLqUJLqxmWrk,3031
|
|
6
|
+
constraint/problem.c,sha256=LP1YGVG46BA7ZJmpBlOmJ5Rr1tx0L-nxxUgfbmZJ2Xo,670534
|
|
7
|
+
constraint/problem.py,sha256=VnBMDLDSBmbqsHmzfh17TDraiXBM93IVA1wxz3XtOgs,9561
|
|
8
|
+
constraint/solvers.c,sha256=Ay7M4-l79f1ec8K6ZjXFaR4brwSLZuQfbg0UCjwWbz8,1020661
|
|
9
|
+
constraint/solvers.py,sha256=Wzt6THvpr3-LeuAZUWTl1cIundOwb5UFv0JJweMec20,23024
|
|
10
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
tests/setup_teardown.py,sha256=z4g_jdHrz4Lnd22MCkIE9HCpr-wipQKX7-5p46ZFnj4,1390
|
|
12
|
+
tests/test_compilation.py,sha256=En9Z9NcOxG9N0qIUCGwFib5Jq_uxdqg5_I79mm37Gj0,472
|
|
13
|
+
tests/test_constraint.py,sha256=mKhbaKr0kN77qXGHQPsnDrNs5lzEInpDzP9L84HwDaI,3426
|
|
14
|
+
tests/test_doctests.py,sha256=PUD7QeB-OufzwywquWS0NkVAU1zTUtkk_tcZikCtBzI,425
|
|
15
|
+
tests/test_problem.py,sha256=k01G64kzZ969mC6iQxW87oYisvDIPQiZi5Y8fJa02qw,806
|
|
16
|
+
tests/test_solvers.py,sha256=M28578HKaoRL5nBqBXSTktDCwNhqbN0ZdhcM6RyXZbA,2853
|
|
17
|
+
tests/test_some_not_in_set.py,sha256=xgSairFNF4T-CYdAWhIv3qoOixnhGjWUdY01GzIjwds,3496
|
|
18
|
+
tests/test_toml_file.py,sha256=JYpsOPWOY_-hIVPLw4J5kvLMf7qWxXN8c56JoNDhl3Y,2230
|
|
19
|
+
python_constraint2-2.0.0.dist-info/LICENSE,sha256=-29wB_JJKS4anSogQEG0yZqx5lPZc2m8iPJhKL-p45A,1358
|
|
20
|
+
python_constraint2-2.0.0.dist-info/METADATA,sha256=WwzCbJK7CQ3T8SMyNclOuNj5tlbub8DXd6qP9hhpguQ,9144
|
|
21
|
+
python_constraint2-2.0.0.dist-info/WHEEL,sha256=CAXnmTOdevtyHhGbx1uQxRlUnaXo4-LVkad_lFClKZs,98
|
|
22
|
+
python_constraint2-2.0.0.dist-info/RECORD,,
|
tests/__init__.py
ADDED
|
File without changes
|
tests/setup_teardown.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""General setup and teardown for the tests, and module-level tests."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def unuse_extensions():
|
|
7
|
+
"""Make sure C-extensions are not used by adding an underscore to .so files."""
|
|
8
|
+
dir = Path("./constraint")
|
|
9
|
+
assert dir.exists()
|
|
10
|
+
files = dir.glob("*.so")
|
|
11
|
+
for file in files:
|
|
12
|
+
if file.name[0] != "_":
|
|
13
|
+
# add a leading underscore
|
|
14
|
+
file.rename(f"{dir}/_{file.name}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def use_extensions():
|
|
18
|
+
"""Make sure C-extensions are used by removing the underscore to .so files."""
|
|
19
|
+
dir = Path("./constraint")
|
|
20
|
+
assert dir.exists()
|
|
21
|
+
files = dir.glob("_*.so")
|
|
22
|
+
for file in files:
|
|
23
|
+
if file.name[0] == "_":
|
|
24
|
+
# remove the leading underscore
|
|
25
|
+
file.rename(f"{dir}/{file.name[1:]}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
parser = argparse.ArgumentParser()
|
|
30
|
+
parser.add_argument("--enable_extensions", action="store_true")
|
|
31
|
+
parser.add_argument("--no-enable_extensions", dest="enable_extensions", action="store_false")
|
|
32
|
+
parser.set_defaults(enable_extensions=True)
|
|
33
|
+
args = parser.parse_args()
|
|
34
|
+
enable_extensions = args.enable_extensions
|
|
35
|
+
if enable_extensions is True:
|
|
36
|
+
use_extensions()
|
|
37
|
+
elif enable_extensions is False:
|
|
38
|
+
unuse_extensions()
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Invalid value for {enable_extensions=}")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_if_compiled():
|
|
5
|
+
"""Test whether C-extensions are succesfully ran, if enabled."""
|
|
6
|
+
from constraint import check_if_compiled
|
|
7
|
+
|
|
8
|
+
# check if the .so files are commented
|
|
9
|
+
dir = Path("./constraint")
|
|
10
|
+
assert dir.exists()
|
|
11
|
+
files = list(dir.glob("_*.so"))
|
|
12
|
+
|
|
13
|
+
# check if the code uses C-extensions
|
|
14
|
+
if len(files) > 0:
|
|
15
|
+
assert check_if_compiled() is False
|
|
16
|
+
else:
|
|
17
|
+
assert check_if_compiled()
|
tests/test_constraint.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import constraint
|
|
2
|
+
|
|
3
|
+
from examples.abc import abc
|
|
4
|
+
from examples.coins import coins
|
|
5
|
+
|
|
6
|
+
# from examples.crosswords import crosswords
|
|
7
|
+
from examples.einstein import einstein
|
|
8
|
+
from examples.queens import queens
|
|
9
|
+
from examples.rooks import rooks
|
|
10
|
+
from examples.studentdesks import studentdesks
|
|
11
|
+
|
|
12
|
+
# from examples.sudoku import sudoku
|
|
13
|
+
# from examples.wordmath import (seisseisdoze, sendmoremoney, twotwofour)
|
|
14
|
+
# from examples.xsum import xsum
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_abc():
|
|
18
|
+
solutions = abc.solve()
|
|
19
|
+
minvalue, minsolution = solutions
|
|
20
|
+
assert minvalue == 37
|
|
21
|
+
assert minsolution == {"a": 1, "c": 2, "b": 1}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_coins():
|
|
25
|
+
solutions = coins.solve()
|
|
26
|
+
assert len(solutions) == 2
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_einstein():
|
|
30
|
+
solutions = einstein.solve()
|
|
31
|
+
expected_solutions = [
|
|
32
|
+
{
|
|
33
|
+
"nationality2": "dane",
|
|
34
|
+
"nationality3": "brit",
|
|
35
|
+
"nationality1": "norwegian",
|
|
36
|
+
"nationality4": "german",
|
|
37
|
+
"nationality5": "swede",
|
|
38
|
+
"color1": "yellow",
|
|
39
|
+
"color3": "red",
|
|
40
|
+
"color2": "blue",
|
|
41
|
+
"color5": "white",
|
|
42
|
+
"color4": "green",
|
|
43
|
+
"drink4": "coffee",
|
|
44
|
+
"drink5": "beer",
|
|
45
|
+
"drink1": "water",
|
|
46
|
+
"drink2": "tea",
|
|
47
|
+
"drink3": "milk",
|
|
48
|
+
"smoke5": "bluemaster",
|
|
49
|
+
"smoke4": "prince",
|
|
50
|
+
"smoke3": "pallmall",
|
|
51
|
+
"smoke2": "blends",
|
|
52
|
+
"smoke1": "dunhill",
|
|
53
|
+
"pet5": "dogs",
|
|
54
|
+
"pet4": "fish",
|
|
55
|
+
"pet1": "cats",
|
|
56
|
+
"pet3": "birds",
|
|
57
|
+
"pet2": "horses",
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
assert solutions == expected_solutions
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_queens():
|
|
64
|
+
solutions, size = queens.solve()
|
|
65
|
+
assert size == 8
|
|
66
|
+
for solution in solutions:
|
|
67
|
+
queens.showSolution(solution, size)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_rooks():
|
|
71
|
+
size = 8
|
|
72
|
+
solutions = rooks.solve(size)
|
|
73
|
+
assert len(solutions) == rooks.factorial(size)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_studentdesks():
|
|
77
|
+
solutions = studentdesks.solve()
|
|
78
|
+
expected_solutions = {
|
|
79
|
+
1: "A",
|
|
80
|
+
2: "E",
|
|
81
|
+
3: "D",
|
|
82
|
+
4: "E",
|
|
83
|
+
5: "D",
|
|
84
|
+
6: "A",
|
|
85
|
+
7: "C",
|
|
86
|
+
8: "B",
|
|
87
|
+
9: "C",
|
|
88
|
+
10: "B",
|
|
89
|
+
11: "E",
|
|
90
|
+
12: "D",
|
|
91
|
+
13: "E",
|
|
92
|
+
14: "D",
|
|
93
|
+
15: "A",
|
|
94
|
+
16: "C",
|
|
95
|
+
17: "B",
|
|
96
|
+
18: "C",
|
|
97
|
+
19: "B",
|
|
98
|
+
20: "A",
|
|
99
|
+
}
|
|
100
|
+
assert solutions == expected_solutions
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_constraint_without_variables():
|
|
104
|
+
problem = constraint.Problem()
|
|
105
|
+
problem.addVariable("a", [1, 2, 3])
|
|
106
|
+
problem.addConstraint(lambda a: a * 2 == 6)
|
|
107
|
+
solutions = problem.getSolutions()
|
|
108
|
+
assert solutions == [{"a": 3}]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_multipliers():
|
|
112
|
+
"""Test the multiplier functionality in the constraints."""
|
|
113
|
+
from constraint import MaxSumConstraint, ExactSumConstraint, MinSumConstraint
|
|
114
|
+
|
|
115
|
+
problem = constraint.Problem()
|
|
116
|
+
problem.addVariable("x", [-1, 0, 1, 2])
|
|
117
|
+
problem.addVariable("y", [1, 2])
|
|
118
|
+
problem.addConstraint(MaxSumConstraint(4, [2, 1]), ["x", "y"])
|
|
119
|
+
problem.addConstraint(ExactSumConstraint(4, [1, 2]), ["x", "y"])
|
|
120
|
+
problem.addConstraint(MinSumConstraint(0, [0.5, 1]), ["x"])
|
|
121
|
+
|
|
122
|
+
possible_solutions = [{"y": 2, "x": 0}, {"y": 1, "x": 2}]
|
|
123
|
+
|
|
124
|
+
# get the solutions
|
|
125
|
+
solutions = problem.getSolutions()
|
|
126
|
+
for solution in solutions:
|
|
127
|
+
assert solution in possible_solutions
|
tests/test_doctests.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import doctest
|
|
2
|
+
import constraint.problem as problem
|
|
3
|
+
import constraint.domain as domain
|
|
4
|
+
import constraint.constraints as constraints
|
|
5
|
+
import constraint.solvers as solvers
|
|
6
|
+
|
|
7
|
+
assert doctest.testmod(problem)[0] == 0
|
|
8
|
+
assert doctest.testmod(domain)[0] == 0
|
|
9
|
+
assert doctest.testmod(constraints, extraglobs={'Problem': problem.Problem})[0] == 0
|
|
10
|
+
assert doctest.testmod(solvers, extraglobs={'Problem': problem.Problem})[0] == 0
|
tests/test_problem.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from constraint import Constraint, Domain, Problem
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_addVariable_support_domain_subclasses():
|
|
5
|
+
class MyCustomDomain(Domain):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
class MyConstraint(Constraint):
|
|
9
|
+
def __call__(self, variables, domains, assignments, forwardcheck=False):
|
|
10
|
+
assert isinstance(domains["x"], Domain)
|
|
11
|
+
assert isinstance(domains["y"], MyCustomDomain)
|
|
12
|
+
return True
|
|
13
|
+
|
|
14
|
+
problem = Problem()
|
|
15
|
+
problem.addVariable("x", [0, 1])
|
|
16
|
+
problem.addVariable("y", MyCustomDomain([0, 1]))
|
|
17
|
+
problem.addConstraint(MyConstraint())
|
|
18
|
+
solution = problem.getSolution()
|
|
19
|
+
|
|
20
|
+
possible_solutions = [
|
|
21
|
+
{"x": 0, "y": 0},
|
|
22
|
+
{"x": 0, "y": 1},
|
|
23
|
+
{"x": 1, "y": 0},
|
|
24
|
+
{"x": 1, "y": 1},
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
assert solution in possible_solutions
|
tests/test_solvers.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from constraint import Problem, MinConflictsSolver, BacktrackingSolver, OptimizedBacktrackingSolver, RecursiveBacktrackingSolver, MaxProdConstraint, MinProdConstraint, MinSumConstraint, FunctionConstraint
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_min_conflicts_solver():
|
|
5
|
+
problem = Problem(MinConflictsSolver())
|
|
6
|
+
problem.addVariable("x", [0, 1])
|
|
7
|
+
problem.addVariable("y", [0, 1])
|
|
8
|
+
|
|
9
|
+
possible_solutions = [
|
|
10
|
+
{"x": 0, "y": 0},
|
|
11
|
+
{"x": 0, "y": 1},
|
|
12
|
+
{"x": 1, "y": 0},
|
|
13
|
+
{"x": 1, "y": 1},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
# test if all solutions are eventually found by iteration and adding the last solutions as a constraint
|
|
17
|
+
for _ in possible_solutions:
|
|
18
|
+
solution = problem.getSolution()
|
|
19
|
+
assert solution in possible_solutions
|
|
20
|
+
problem.addConstraint(FunctionConstraint(lambda x, y: (lambda x, y, xs, ys: x != xs or y != ys)(x, y, solution['x'], solution['y'])))
|
|
21
|
+
|
|
22
|
+
def test_optimized_backtracking_solver():
|
|
23
|
+
# setup the solvers
|
|
24
|
+
problem_bt = Problem(BacktrackingSolver())
|
|
25
|
+
problem_opt = Problem(OptimizedBacktrackingSolver())
|
|
26
|
+
problem_opt_nfwd = Problem(OptimizedBacktrackingSolver(forwardcheck=False))
|
|
27
|
+
problems = [problem_bt, problem_opt, problem_opt_nfwd]
|
|
28
|
+
|
|
29
|
+
# define the problem for all solvers
|
|
30
|
+
for problem in problems:
|
|
31
|
+
problem.addVariable("x", [-1, 0, 1, 2])
|
|
32
|
+
problem.addVariable("y", [1, 2])
|
|
33
|
+
problem.addConstraint(MaxProdConstraint(2), ["x", "y"])
|
|
34
|
+
problem.addConstraint(MinProdConstraint(1), ["x", "y"])
|
|
35
|
+
problem.addConstraint(MinSumConstraint(0), ["x"])
|
|
36
|
+
|
|
37
|
+
# get the solutions
|
|
38
|
+
true_solutions = [(2, 1), (1, 2), (1, 1)]
|
|
39
|
+
order = ["x", "y"]
|
|
40
|
+
solution = problem_bt.getSolution()
|
|
41
|
+
solution_tuple = tuple(solution[key] for key in order)
|
|
42
|
+
|
|
43
|
+
# validate a single solution
|
|
44
|
+
solution_opt = problem_opt.getSolution()
|
|
45
|
+
assert tuple(solution_opt[key] for key in order) in true_solutions
|
|
46
|
+
|
|
47
|
+
# validate all solutions
|
|
48
|
+
def validate(solutions_list, solutions_dict, size):
|
|
49
|
+
assert size == len(true_solutions)
|
|
50
|
+
assert solution_tuple in solutions_list
|
|
51
|
+
assert solution_tuple in solutions_dict
|
|
52
|
+
assert all(sol in solutions_list for sol in true_solutions)
|
|
53
|
+
|
|
54
|
+
validate(*problem_opt.getSolutionsAsListDict(order=order))
|
|
55
|
+
validate(*problem_opt_nfwd.getSolutionsAsListDict(order=order))
|
|
56
|
+
|
|
57
|
+
def test_recursive_backtracking_solver():
|
|
58
|
+
problem = Problem(RecursiveBacktrackingSolver())
|
|
59
|
+
problem.addVariable("x", [0, 1])
|
|
60
|
+
problem.addVariable("y", [0, 1])
|
|
61
|
+
solution = problem.getSolution()
|
|
62
|
+
solutions = problem.getSolutions()
|
|
63
|
+
|
|
64
|
+
possible_solutions = [
|
|
65
|
+
{"x": 0, "y": 0},
|
|
66
|
+
{"x": 0, "y": 1},
|
|
67
|
+
{"x": 1, "y": 0},
|
|
68
|
+
{"x": 1, "y": 1},
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
assert solution in possible_solutions
|
|
72
|
+
assert all(sol in possible_solutions for sol in solutions)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from constraint import Domain, Variable, SomeNotInSetConstraint
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_empty_constraint():
|
|
5
|
+
constrainer = SomeNotInSetConstraint(set())
|
|
6
|
+
v1, v2 = variables = [Variable("v1"), Variable("v2")]
|
|
7
|
+
assignments = {v1: "a", v2: "b"}
|
|
8
|
+
|
|
9
|
+
assert constrainer(variables, {}, assignments)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_no_overlap():
|
|
13
|
+
constrainer = SomeNotInSetConstraint(set("zy"))
|
|
14
|
+
v1, v2 = variables = [Variable("v1"), Variable("v2")]
|
|
15
|
+
assignments = {v1: "a", v2: "b"}
|
|
16
|
+
|
|
17
|
+
assert constrainer(variables, {}, assignments)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_some_overlap():
|
|
21
|
+
constrainer = SomeNotInSetConstraint(set("b"))
|
|
22
|
+
v1, v2 = variables = [Variable("v1"), Variable("v2")]
|
|
23
|
+
assignments = {v1: "a", v2: "b"}
|
|
24
|
+
|
|
25
|
+
assert constrainer(variables, {}, assignments)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_too_much_overlap():
|
|
29
|
+
constrainer = SomeNotInSetConstraint(set("ab"))
|
|
30
|
+
v1, v2 = variables = [Variable("v1"), Variable("v2")]
|
|
31
|
+
assignments = {v1: "a", v2: "b"}
|
|
32
|
+
|
|
33
|
+
assert not constrainer(variables, {}, assignments)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_exact():
|
|
37
|
+
constrainer = SomeNotInSetConstraint(set("abc"), n=2, exact=True)
|
|
38
|
+
v1, v2, v3 = variables = [Variable("v1"), Variable("v2"), Variable("v3")]
|
|
39
|
+
|
|
40
|
+
assignments = {v1: "a", v2: "y", v3: "z"}
|
|
41
|
+
assert constrainer(variables, {}, assignments)
|
|
42
|
+
|
|
43
|
+
assignments = {v1: "a", v2: "y"}
|
|
44
|
+
assert constrainer(variables, {}, assignments)
|
|
45
|
+
|
|
46
|
+
assignments = {v1: "a", v2: "b", v3: "z"}
|
|
47
|
+
assert not constrainer(variables, {}, assignments)
|
|
48
|
+
|
|
49
|
+
assignments = {v1: "a", v2: "b"}
|
|
50
|
+
assert not constrainer(variables, {}, assignments)
|
|
51
|
+
|
|
52
|
+
assignments = {v1: "a", v2: "b", v3: "c"}
|
|
53
|
+
assert not constrainer(variables, {}, assignments)
|
|
54
|
+
|
|
55
|
+
assignments = {v1: "x", v2: "y", v3: "z"}
|
|
56
|
+
assert not constrainer(variables, {}, assignments)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_forwardcheck():
|
|
60
|
+
constrainer = SomeNotInSetConstraint(set("abc"), n=2)
|
|
61
|
+
v1, v2, v3 = variables = [Variable("v1"), Variable("v2"), Variable("v3")]
|
|
62
|
+
|
|
63
|
+
domains = {v1: Domain(["a"]), v2: Domain(["b", "y"]), v3: Domain(["c", "z"])}
|
|
64
|
+
assert constrainer(variables, domains, {v1: "a"})
|
|
65
|
+
assert ["a"] == list(domains[v1])
|
|
66
|
+
assert ["b", "y"] == list(domains[v2])
|
|
67
|
+
assert ["c", "z"] == list(domains[v3])
|
|
68
|
+
|
|
69
|
+
assert constrainer(variables, domains, {v1: "a"}, True)
|
|
70
|
+
assert ["a"] == list(domains[v1])
|
|
71
|
+
assert ["y"] == list(domains[v2])
|
|
72
|
+
assert ["z"] == list(domains[v3])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_forwardcheck_empty_domain():
|
|
76
|
+
constrainer = SomeNotInSetConstraint(set("abc"))
|
|
77
|
+
v1, v2 = variables = [Variable("v1"), Variable("v2")]
|
|
78
|
+
|
|
79
|
+
domains = {v1: Domain(["a"]), v2: Domain(["b"])}
|
|
80
|
+
assert constrainer(variables, domains, {v1: "a"})
|
|
81
|
+
assert not constrainer(variables, domains, {v1: "a"}, True)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_forwardcheck_exact():
|
|
85
|
+
constrainer = SomeNotInSetConstraint(set("abc"), n=2, exact=True)
|
|
86
|
+
v1, v2, v3 = variables = [Variable("v1"), Variable("v2"), Variable("v3")]
|
|
87
|
+
assignments = {v1: "a"}
|
|
88
|
+
|
|
89
|
+
domains = {v1: Domain(["a", "x"]), v2: Domain(["b", "y"]), v3: Domain(["c", "z"])}
|
|
90
|
+
assert constrainer(variables, domains, assignments)
|
|
91
|
+
assert constrainer(variables, domains, assignments, True)
|
|
92
|
+
assert "b" not in domains[v2]
|
|
93
|
+
assert "y" in domains[v2]
|
|
94
|
+
assert "c" not in domains[v3]
|
|
95
|
+
assert "z" in domains[v3]
|
|
96
|
+
|
|
97
|
+
domains = {v1: Domain(["a", "x"]), v2: Domain(["b", "y"]), v3: Domain(["c"])}
|
|
98
|
+
assert constrainer(variables, domains, assignments)
|
|
99
|
+
assert not constrainer(variables, domains, assignments, True)
|