From 91e0399ed64933699cd760b4a449fa0b6340b46d Mon Sep 17 00:00:00 2001 From: Matthew Butterick Date: Wed, 17 Oct 2018 11:51:26 -0700 Subject: [PATCH] again --- csp/port2/python-constraint-master/.gitignore | 60 + .../python-constraint-master/.travis.yml | 21 + csp/port2/python-constraint-master/LICENSE | 23 + csp/port2/python-constraint-master/README.rst | 159 ++ .../constraint/__init__.py | 1461 +++++++++++++++++ .../constraint/compat.py | 14 + .../constraint/version.py | 8 + .../examples/__init__.py | 0 .../examples/abc/__init__.py | 0 .../examples/abc/abc.py | 37 + .../examples/coins/__init__.py | 0 .../examples/coins/coins.py | 36 + .../examples/crosswords/__init__.py | 0 .../examples/crosswords/crosswords.py | 154 ++ .../examples/crosswords/large.mask | 27 + .../examples/crosswords/medium.mask | 19 + .../examples/crosswords/python.mask | 8 + .../examples/crosswords/small.mask | 8 + .../examples/einstein/__init__.py | 0 .../examples/einstein/einstein.py | 209 +++ .../examples/queens/__init__.py | 0 .../examples/queens/queens.py | 54 + .../examples/rooks/__init__.py | 0 .../examples/rooks/rooks.py | 57 + .../examples/studentdesks/__init__.py | 0 .../examples/studentdesks/studentdesks.py | 48 + .../examples/sudoku/__init__.py | 0 .../examples/sudoku/sudoku.py | 71 + .../examples/wordmath/__init__.py | 0 .../examples/wordmath/seisseisdoze.py | 39 + .../examples/wordmath/sendmoremoney.py | 42 + .../examples/wordmath/twotwofour.py | 37 + .../examples/xsum/__init__.py | 0 .../examples/xsum/xsum.py | 48 + csp/port2/python-constraint-master/setup.cfg | 9 + csp/port2/python-constraint-master/setup.py | 123 ++ .../tests/test_constraint.py | 91 + .../tests/test_solvers.py | 17 + .../tests/test_some_not_in_set.py | 102 ++ 39 files changed, 2982 insertions(+) create mode 100755 csp/port2/python-constraint-master/.gitignore create mode 100755 csp/port2/python-constraint-master/.travis.yml create mode 100755 csp/port2/python-constraint-master/LICENSE create mode 100755 csp/port2/python-constraint-master/README.rst create mode 100755 csp/port2/python-constraint-master/constraint/__init__.py create mode 100755 csp/port2/python-constraint-master/constraint/compat.py create mode 100755 csp/port2/python-constraint-master/constraint/version.py create mode 100755 csp/port2/python-constraint-master/examples/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/abc/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/abc/abc.py create mode 100755 csp/port2/python-constraint-master/examples/coins/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/coins/coins.py create mode 100755 csp/port2/python-constraint-master/examples/crosswords/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/crosswords/crosswords.py create mode 100755 csp/port2/python-constraint-master/examples/crosswords/large.mask create mode 100755 csp/port2/python-constraint-master/examples/crosswords/medium.mask create mode 100755 csp/port2/python-constraint-master/examples/crosswords/python.mask create mode 100755 csp/port2/python-constraint-master/examples/crosswords/small.mask create mode 100755 csp/port2/python-constraint-master/examples/einstein/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/einstein/einstein.py create mode 100755 csp/port2/python-constraint-master/examples/queens/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/queens/queens.py create mode 100755 csp/port2/python-constraint-master/examples/rooks/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/rooks/rooks.py create mode 100755 csp/port2/python-constraint-master/examples/studentdesks/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/studentdesks/studentdesks.py create mode 100755 csp/port2/python-constraint-master/examples/sudoku/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/sudoku/sudoku.py create mode 100755 csp/port2/python-constraint-master/examples/wordmath/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/wordmath/seisseisdoze.py create mode 100755 csp/port2/python-constraint-master/examples/wordmath/sendmoremoney.py create mode 100755 csp/port2/python-constraint-master/examples/wordmath/twotwofour.py create mode 100755 csp/port2/python-constraint-master/examples/xsum/__init__.py create mode 100755 csp/port2/python-constraint-master/examples/xsum/xsum.py create mode 100755 csp/port2/python-constraint-master/setup.cfg create mode 100755 csp/port2/python-constraint-master/setup.py create mode 100755 csp/port2/python-constraint-master/tests/test_constraint.py create mode 100755 csp/port2/python-constraint-master/tests/test_solvers.py create mode 100755 csp/port2/python-constraint-master/tests/test_some_not_in_set.py diff --git a/csp/port2/python-constraint-master/.gitignore b/csp/port2/python-constraint-master/.gitignore new file mode 100755 index 00000000..b102207f --- /dev/null +++ b/csp/port2/python-constraint-master/.gitignore @@ -0,0 +1,60 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# PyCharm / Intellij +.idea/ diff --git a/csp/port2/python-constraint-master/.travis.yml b/csp/port2/python-constraint-master/.travis.yml new file mode 100755 index 00000000..2bf2b8a3 --- /dev/null +++ b/csp/port2/python-constraint-master/.travis.yml @@ -0,0 +1,21 @@ +language: python +python: + - "2.7" + - "3.3" + - "3.4" + - "3.5" + - "3.6" + +# command to install dependencies +install: + - "pip install -qq flake8" + - "pip install coveralls --quiet" + - "pip install ." + +# command to run tests +script: + - nosetests -s -v --with-coverage --cover-package=constraint + - flake8 --ignore E501 constraint examples tests + +after_success: + - coveralls diff --git a/csp/port2/python-constraint-master/LICENSE b/csp/port2/python-constraint-master/LICENSE new file mode 100755 index 00000000..1551a23a --- /dev/null +++ b/csp/port2/python-constraint-master/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2005-2014 - Gustavo Niemeyer + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/csp/port2/python-constraint-master/README.rst b/csp/port2/python-constraint-master/README.rst new file mode 100755 index 00000000..564e46a5 --- /dev/null +++ b/csp/port2/python-constraint-master/README.rst @@ -0,0 +1,159 @@ +|Build Status| |Code Health| |Code Coverage| + +python-constraint +================= + +Introduction +------------ +The Python constraint module offers solvers for `Constraint Satisfaction Problems (CSPs) `_ over finite domains in simple and pure Python. 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, ...). + +Examples +-------- + +Basics +~~~~~~ + +This interactive Python session demonstrates the module basic operation: + +.. code-block:: python + + >>> from constraint import * + >>> problem = Problem() + >>> problem.addVariable("a", [1,2,3]) + >>> problem.addVariable("b", [4,5,6]) + >>> problem.getSolutions() + [{'a': 3, 'b': 6}, {'a': 3, 'b': 5}, {'a': 3, 'b': 4}, + {'a': 2, 'b': 6}, {'a': 2, 'b': 5}, {'a': 2, 'b': 4}, + {'a': 1, 'b': 6}, {'a': 1, 'b': 5}, {'a': 1, 'b': 4}] + + >>> problem.addConstraint(lambda a, b: a*2 == b, + ("a", "b")) + >>> problem.getSolutions() + [{'a': 3, 'b': 6}, {'a': 2, 'b': 4}] + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(AllDifferentConstraint()) + >>> problem.getSolutions() + [{'a': 3, 'b': 2}, {'a': 3, 'b': 1}, {'a': 2, 'b': 3}, + {'a': 2, 'b': 1}, {'a': 1, 'b': 2}, {'a': 1, 'b': 3}] + +Rooks problem +~~~~~~~~~~~~~ + +The following example solves the classical Eight Rooks problem: + +.. code-block:: python + + >>> problem = Problem() + >>> numpieces = 8 + >>> cols = range(numpieces) + >>> rows = range(numpieces) + >>> problem.addVariables(cols, rows) + >>> for col1 in cols: + ... for col2 in cols: + ... if col1 < col2: + ... problem.addConstraint(lambda row1, row2: row1 != row2, + ... (col1, col2)) + >>> solutions = problem.getSolutions() + >>> solutions + >>> solutions + [{0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 1, 7: 0}, + {0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 2, 6: 0, 7: 1}, + {0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 2, 7: 0}, + {0: 7, 1: 6, 2: 5, 3: 4, 4: 3, 5: 1, 6: 0, 7: 2}, + ... + {0: 7, 1: 5, 2: 3, 3: 6, 4: 2, 5: 1, 6: 4, 7: 0}, + {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 0, 7: 4}, + {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 2, 6: 4, 7: 0}, + {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 2, 7: 0}, + {0: 7, 1: 5, 2: 3, 3: 6, 4: 1, 5: 4, 6: 0, 7: 2}, + ...] + + +Magic squares +~~~~~~~~~~~~~ + +This example solves a 4x4 magic square: + +.. code-block:: python + + >>> problem = Problem() + >>> problem.addVariables(range(0, 16), range(1, 16 + 1)) + >>> problem.addConstraint(AllDifferentConstraint(), range(0, 16)) + >>> problem.addConstraint(ExactSumConstraint(34), [0, 5, 10, 15]) + >>> problem.addConstraint(ExactSumConstraint(34), [3, 6, 9, 12]) + >>> for row in range(4): + ... problem.addConstraint(ExactSumConstraint(34), + [row * 4 + i for i in range(4)]) + >>> for col in range(4): + ... problem.addConstraint(ExactSumConstraint(34), + [col + 4 * i for i in range(4)]) + >>> solutions = problem.getSolutions() + +Features +-------- + +The following solvers are available: + +- Backtracking solver +- Recursive backtracking solver +- Minimum conflicts solver + + +.. role:: python(code) + :language: python + +Predefined constraint types currently available: + +- :python:`FunctionConstraint` +- :python:`AllDifferentConstraint` +- :python:`AllEqualConstraint` +- :python:`ExactSumConstraint` +- :python:`MaxSumConstraint` +- :python:`MinSumConstraint` +- :python:`InSetConstraint` +- :python:`NotInSetConstraint` +- :python:`SomeInSetConstraint` +- :python:`SomeNotInSetConstraint` + +API documentation +----------------- +Documentation for the module is available at: http://labix.org/doc/constraint/ + +Download and install +-------------------- + +.. code-block:: shell + + $ pip install python-constraint + +Roadmap +------- + +This GitHub organization and repository is a global effort to help to +maintain python-constraint which was written by Gustavo Niemeyer +and originaly located at https://labix.org/python-constraint + +- Create some unit tests - DONE +- Enable continuous integration - DONE +- Port to Python 3 (Python 2 being also supported) - DONE +- Respect Style Guide for Python Code (PEP8) - DONE +- Improve code coverage writting more unit tests - ToDo +- Move doc to Sphinx or MkDocs - https://readthedocs.org/ - ToDo + +Contact +------- +- `Gustavo Niemeyer `_ +- `Sébastien Celles `_ + +But it's probably better to `open an issue `_. + + +.. |Build Status| image:: https://travis-ci.org/python-constraint/python-constraint.svg?branch=master + :target: https://travis-ci.org/python-constraint/python-constraint +.. |Code Health| image:: https://landscape.io/github/python-constraint/python-constraint/master/landscape.svg?style=flat + :target: https://landscape.io/github/python-constraint/python-constraint/master + :alt: Code Health +.. |Code Coverage| image:: https://coveralls.io/repos/github/python-constraint/python-constraint/badge.svg + :target: https://coveralls.io/github/python-constraint/python-constraint diff --git a/csp/port2/python-constraint-master/constraint/__init__.py b/csp/port2/python-constraint-master/constraint/__init__.py new file mode 100755 index 00000000..7932aa33 --- /dev/null +++ b/csp/port2/python-constraint-master/constraint/__init__.py @@ -0,0 +1,1461 @@ +#!/usr/bin/python +# +# Copyright (c) 2005-2014 - Gustavo Niemeyer +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" +@var Unassigned: Helper object instance representing unassigned values + +@sort: Problem, Variable, Domain +@group Solvers: Solver, + BacktrackingSolver, + RecursiveBacktrackingSolver, + MinConflictsSolver +@group Constraints: Constraint, + FunctionConstraint, + AllDifferentConstraint, + AllEqualConstraint, + MaxSumConstraint, + ExactSumConstraint, + MinSumConstraint, + InSetConstraint, + NotInSetConstraint, + SomeInSetConstraint, + SomeNotInSetConstraint +""" + +from __future__ import absolute_import, division, print_function + + +from .version import (__author__, __copyright__, __credits__, __license__, # noqa + __version__, __email__, __status__, __url__) # noqa + +import random +import copy +from .compat import xrange + +__all__ = ["Problem", "Variable", "Domain", "Unassigned", + "Solver", "BacktrackingSolver", "RecursiveBacktrackingSolver", + "MinConflictsSolver", "Constraint", "FunctionConstraint", + "AllDifferentConstraint", "AllEqualConstraint", "MaxSumConstraint", + "ExactSumConstraint", "MinSumConstraint", "InSetConstraint", + "NotInSetConstraint", "SomeInSetConstraint", + "SomeNotInSetConstraint"] + + +class Problem(object): + """ + Class used to define a problem and retrieve solutions + """ + + def __init__(self, solver=None): + """ + @param solver: Problem solver used to find solutions + (default is L{BacktrackingSolver}) + @type solver: instance of a L{Solver} subclass + """ + self._solver = solver or BacktrackingSolver() + self._constraints = [] + self._variables = {} + + def reset(self): + """ + Reset the current problem definition + + Example: + + >>> problem = Problem() + >>> problem.addVariable("a", [1, 2]) + >>> problem.reset() + >>> problem.getSolution() + >>> + """ + del self._constraints[:] + self._variables.clear() + + def setSolver(self, solver): + """ + Change the problem solver currently in use + + Example: + + >>> solver = BacktrackingSolver() + >>> problem = Problem(solver) + >>> problem.getSolver() is solver + True + + @param solver: New problem solver + @type solver: instance of a C{Solver} subclass + """ + self._solver = solver + + def getSolver(self): + """ + Obtain the problem solver currently in use + + Example: + + >>> solver = BacktrackingSolver() + >>> problem = Problem(solver) + >>> problem.getSolver() is solver + True + + @return: Solver currently in use + @rtype: instance of a L{Solver} subclass + """ + return self._solver + + def addVariable(self, variable, domain): + """ + Add a variable to the problem + + Example: + + >>> problem = Problem() + >>> problem.addVariable("a", [1, 2]) + >>> problem.getSolution() in ({'a': 1}, {'a': 2}) + True + + @param variable: Object representing a problem variable + @type variable: hashable object + @param domain: Set of items defining the possible values that + the given variable may assume + @type domain: list, tuple, or instance of C{Domain} + """ + if variable in self._variables: + msg = "Tried to insert duplicated variable %s" % repr(variable) + raise ValueError(msg) + if hasattr(domain, '__getitem__'): + domain = Domain(domain) + elif isinstance(domain, Domain): + domain = copy.copy(domain) + else: + msg = "Domains must be instances of subclasses of the Domain class" + raise TypeError(msg) + if not domain: + raise ValueError("Domain is empty") + self._variables[variable] = domain + + def addVariables(self, variables, domain): + """ + Add one or more variables to the problem + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> solutions = problem.getSolutions() + >>> len(solutions) + 9 + >>> {'a': 3, 'b': 1} in solutions + True + + @param variables: Any object containing a sequence of objects + represeting problem variables + @type variables: sequence of hashable objects + @param domain: Set of items defining the possible values that + the given variables may assume + @type domain: list, tuple, or instance of C{Domain} + """ + for variable in variables: + self.addVariable(variable, domain) + + def addConstraint(self, constraint, variables=None): + """ + Add a constraint to the problem + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b == a+1, ["a", "b"]) + >>> solutions = problem.getSolutions() + >>> + + @param constraint: Constraint to be included in the problem + @type constraint: instance a L{Constraint} subclass or a + function to be wrapped by L{FunctionConstraint} + @param variables: Variables affected by the constraint (default to + all variables). Depending on the constraint type + the order may be important. + @type variables: set or sequence of variables + """ + if not isinstance(constraint, Constraint): + if callable(constraint): + constraint = FunctionConstraint(constraint) + else: + msg = "Constraints must be instances of subclasses "\ + "of the Constraint class" + raise ValueError(msg) + self._constraints.append((constraint, variables)) + + def getSolution(self): + """ + Find and return a solution to the problem + + Example: + + >>> problem = Problem() + >>> problem.getSolution() is None + True + >>> problem.addVariables(["a"], [42]) + >>> problem.getSolution() + {'a': 42} + + @return: Solution for the problem + @rtype: dictionary mapping variables to values + """ + domains, constraints, vconstraints = self._getArgs() + if not domains: + return None + return self._solver.getSolution(domains, constraints, vconstraints) + + def getSolutions(self): + """ + Find and return all solutions to the problem + + Example: + + >>> problem = Problem() + >>> problem.getSolutions() == [] + True + >>> problem.addVariables(["a"], [42]) + >>> problem.getSolutions() + [{'a': 42}] + + @return: All solutions for the problem + @rtype: list of dictionaries mapping variables to values + """ + domains, constraints, vconstraints = self._getArgs() + if not domains: + return [] + return self._solver.getSolutions(domains, constraints, vconstraints) + + def getSolutionIter(self): + """ + Return an iterator to the solutions of the problem + + Example: + + >>> problem = Problem() + >>> list(problem.getSolutionIter()) == [] + True + >>> problem.addVariables(["a"], [42]) + >>> iter = problem.getSolutionIter() + >>> next(iter) + {'a': 42} + >>> next(iter) + Traceback (most recent call last): + File "", line 1, in ? + StopIteration + """ + domains, constraints, vconstraints = self._getArgs() + if not domains: + return iter(()) + return self._solver.getSolutionIter(domains, constraints, + vconstraints) + + def _getArgs(self): + domains = self._variables.copy() + allvariables = domains.keys() + constraints = [] + for constraint, variables in self._constraints: + if not variables: + variables = list(allvariables) + constraints.append((constraint, variables)) + vconstraints = {} + for variable in domains: + vconstraints[variable] = [] + for constraint, variables in constraints: + for variable in variables: + vconstraints[variable].append((constraint, variables)) + for constraint, variables in constraints[:]: + constraint.preProcess(variables, domains, + constraints, vconstraints) + for domain in domains.values(): + domain.resetState() + if not domain: + return None, None, None + # doArc8(getArcs(domains, constraints), domains, {}) + return domains, constraints, vconstraints + +# ---------------------------------------------------------------------- +# Solvers +# ---------------------------------------------------------------------- + + +def getArcs(domains, constraints): + """ + Return a dictionary mapping pairs (arcs) of constrained variables + + @attention: Currently unused. + """ + arcs = {} + for x in constraints: + constraint, variables = x + if len(variables) == 2: + variable1, variable2 = variables + arcs.setdefault(variable1, {})\ + .setdefault(variable2, [])\ + .append(x) + arcs.setdefault(variable2, {})\ + .setdefault(variable1, [])\ + .append(x) + return arcs + + +def doArc8(arcs, domains, assignments): + """ + Perform the ARC-8 arc checking algorithm and prune domains + + @attention: Currently unused. + """ + check = dict.fromkeys(domains, True) + while check: + variable, _ = check.popitem() + if variable not in arcs or variable in assignments: + continue + domain = domains[variable] + arcsvariable = arcs[variable] + for othervariable in arcsvariable: + arcconstraints = arcsvariable[othervariable] + if othervariable in assignments: + otherdomain = [assignments[othervariable]] + else: + otherdomain = domains[othervariable] + if domain: + # changed = False + for value in domain[:]: + assignments[variable] = value + if otherdomain: + for othervalue in otherdomain: + assignments[othervariable] = othervalue + for constraint, variables in arcconstraints: + if not constraint(variables, domains, + assignments, True): + break + else: + # All constraints passed. Value is safe. + break + else: + # All othervalues failed. Kill value. + domain.hideValue(value) + # changed = True + del assignments[othervariable] + del assignments[variable] + # if changed: + # check.update(dict.fromkeys(arcsvariable)) + if not domain: + return False + return True + + +class Solver(object): + """ + Abstract base class for solvers + + @sort: getSolution, getSolutions, getSolutionIter + """ + + def getSolution(self, domains, constraints, vconstraints): + """ + Return one solution for the given problem + + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + msg = "%s is an abstract class" % self.__class__.__name__ + raise NotImplementedError(msg) + + def getSolutions(self, domains, constraints, vconstraints): + """ + Return all solutions for the given problem + + @param domains: Dictionary mapping variables to domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + msg = "%s provides only a single solution" % self.__class__.__name__ + raise NotImplementedError(msg) + + def getSolutionIter(self, domains, constraints, vconstraints): + """ + Return an iterator for the solutions of the given problem + + @param domains: Dictionary mapping variables to domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + msg = "%s doesn't provide iteration" % self.__class__.__name__ + raise NotImplementedError(msg) + + +class BacktrackingSolver(Solver): + """ + Problem solver with backtracking capabilities + + Examples: + + >>> result = [[('a', 1), ('b', 2)], + ... [('a', 1), ('b', 3)], + ... [('a', 2), ('b', 3)]] + + >>> problem = Problem(BacktrackingSolver()) + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + + >>> solution = problem.getSolution() + >>> sorted(solution.items()) in result + True + + >>> for solution in problem.getSolutionIter(): + ... sorted(solution.items()) in result + True + True + True + + >>> for solution in problem.getSolutions(): + ... sorted(solution.items()) in result + True + True + True + """ + + def __init__(self, forwardcheck=True): + """ + @param forwardcheck: If false forward checking will not be requested + to constraints while looking for solutions + (default is true) + @type forwardcheck: bool + """ + self._forwardcheck = forwardcheck + + def getSolutionIter(self, domains, constraints, vconstraints): + forwardcheck = self._forwardcheck + assignments = {} + + queue = [] + + while True: + + # Mix the Degree and Minimum Remaing Values (MRV) heuristics + lst = [(-len(vconstraints[variable]), + len(domains[variable]), variable) for variable in domains] + lst.sort() + for item in lst: + if item[-1] not in assignments: + # Found unassigned variable + variable = item[-1] + values = domains[variable][:] + if forwardcheck: + pushdomains = [domains[x] for x in domains + if x not in assignments and x != variable] + else: + pushdomains = None + break + else: + # No unassigned variables. We've got a solution. Go back + # to last variable, if there's one. + yield assignments.copy() + if not queue: + return + variable, values, pushdomains = queue.pop() + if pushdomains: + for domain in pushdomains: + domain.popState() + + while True: + # We have a variable. Do we have any values left? + if not values: + # No. Go back to last variable, if there's one. + del assignments[variable] + while queue: + variable, values, pushdomains = queue.pop() + if pushdomains: + for domain in pushdomains: + domain.popState() + if values: + break + del assignments[variable] + else: + return + + # Got a value. Check it. + assignments[variable] = values.pop() + + if pushdomains: + for domain in pushdomains: + domain.pushState() + + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments, + pushdomains): + # Value is not good. + break + else: + break + + if pushdomains: + for domain in pushdomains: + domain.popState() + + # Push state before looking for next variable. + queue.append((variable, values, pushdomains)) + + raise RuntimeError("Can't happen") + + def getSolution(self, domains, constraints, vconstraints): + iter = self.getSolutionIter(domains, constraints, vconstraints) + try: + return next(iter) + except StopIteration: + return None + + def getSolutions(self, domains, constraints, vconstraints): + return list(self.getSolutionIter(domains, constraints, vconstraints)) + + +class RecursiveBacktrackingSolver(Solver): + """ + Recursive problem solver with backtracking capabilities + + Examples: + + >>> result = [[('a', 1), ('b', 2)], + ... [('a', 1), ('b', 3)], + ... [('a', 2), ('b', 3)]] + + >>> problem = Problem(RecursiveBacktrackingSolver()) + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + + >>> solution = problem.getSolution() + >>> sorted(solution.items()) in result + True + + >>> for solution in problem.getSolutions(): + ... sorted(solution.items()) in result + True + True + True + + >>> problem.getSolutionIter() + Traceback (most recent call last): + ... + NotImplementedError: RecursiveBacktrackingSolver doesn't provide iteration + """ + + def __init__(self, forwardcheck=True): + """ + @param forwardcheck: If false forward checking will not be requested + to constraints while looking for solutions + (default is true) + @type forwardcheck: bool + """ + self._forwardcheck = forwardcheck + + def recursiveBacktracking(self, solutions, domains, vconstraints, + assignments, single): + + # Mix the Degree and Minimum Remaing Values (MRV) heuristics + lst = [(-len(vconstraints[variable]), + len(domains[variable]), variable) for variable in domains] + lst.sort() + for item in lst: + if item[-1] not in assignments: + # Found an unassigned variable. Let's go. + break + else: + # No unassigned variables. We've got a solution. + solutions.append(assignments.copy()) + return solutions + + variable = item[-1] + assignments[variable] = None + + forwardcheck = self._forwardcheck + if forwardcheck: + pushdomains = [domains[x] for x in domains if x not in assignments] + else: + pushdomains = None + + for value in domains[variable]: + assignments[variable] = value + if pushdomains: + for domain in pushdomains: + domain.pushState() + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments, + pushdomains): + # Value is not good. + break + else: + # Value is good. Recurse and get next variable. + self.recursiveBacktracking(solutions, domains, vconstraints, + assignments, single) + if solutions and single: + return solutions + if pushdomains: + for domain in pushdomains: + domain.popState() + del assignments[variable] + return solutions + + def getSolution(self, domains, constraints, vconstraints): + solutions = self.recursiveBacktracking([], domains, vconstraints, + {}, True) + return solutions and solutions[0] or None + + def getSolutions(self, domains, constraints, vconstraints): + return self.recursiveBacktracking([], domains, vconstraints, + {}, False) + + +class MinConflictsSolver(Solver): + """ + Problem solver based on the minimum conflicts theory + + Examples: + + >>> result = [[('a', 1), ('b', 2)], + ... [('a', 1), ('b', 3)], + ... [('a', 2), ('b', 3)]] + + >>> problem = Problem(MinConflictsSolver()) + >>> problem.addVariables(["a", "b"], [1, 2, 3]) + >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + + >>> solution = problem.getSolution() + >>> sorted(solution.items()) in result + True + + >>> problem.getSolutions() + Traceback (most recent call last): + ... + NotImplementedError: MinConflictsSolver provides only a single solution + + >>> problem.getSolutionIter() + Traceback (most recent call last): + ... + NotImplementedError: MinConflictsSolver doesn't provide iteration + """ + + def __init__(self, steps=1000): + """ + @param steps: Maximum number of steps to perform before giving up + when looking for a solution (default is 1000) + @type steps: int + """ + self._steps = steps + + def getSolution(self, domains, constraints, vconstraints): + assignments = {} + # Initial assignment + for variable in domains: + assignments[variable] = random.choice(domains[variable]) + for _ in xrange(self._steps): + conflicted = False + lst = list(domains.keys()) + random.shuffle(lst) + for variable in lst: + # Check if variable is not in conflict + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments): + break + else: + continue + # Variable has conflicts. Find values with less conflicts. + mincount = len(vconstraints[variable]) + minvalues = [] + for value in domains[variable]: + assignments[variable] = value + count = 0 + for constraint, variables in vconstraints[variable]: + if not constraint(variables, domains, assignments): + count += 1 + if count == mincount: + minvalues.append(value) + elif count < mincount: + mincount = count + del minvalues[:] + minvalues.append(value) + # Pick a random one from these values. + assignments[variable] = random.choice(minvalues) + conflicted = True + if not conflicted: + return assignments + return None + +# ---------------------------------------------------------------------- +# Variables +# ---------------------------------------------------------------------- + + +class Variable(object): + """ + Helper class for variable definition + + Using this class is optional, since any hashable object, + including plain strings and integers, may be used as variables. + """ + + def __init__(self, name): + """ + @param name: Generic variable name for problem-specific purposes + @type name: string + """ + self.name = name + + def __repr__(self): + return self.name + + +Unassigned = Variable("Unassigned") + +# ---------------------------------------------------------------------- +# Domains +# ---------------------------------------------------------------------- + + +class Domain(list): + """ + Class used to control possible values for variables + + When list or tuples are used as domains, they are automatically + converted to an instance of that class. + """ + + def __init__(self, set): + """ + @param set: Set of values that the given variables may assume + @type set: set of objects comparable by equality + """ + list.__init__(self, set) + self._hidden = [] + self._states = [] + + def resetState(self): + """ + Reset to the original domain state, including all possible values + """ + self.extend(self._hidden) + del self._hidden[:] + del self._states[:] + + def pushState(self): + """ + Save current domain state + + Variables hidden after that call are restored when that state + is popped from the stack. + """ + self._states.append(len(self)) + + def popState(self): + """ + Restore domain state from the top of the stack + + Variables hidden since the last popped state are then available + again. + """ + diff = self._states.pop() - len(self) + if diff: + self.extend(self._hidden[-diff:]) + del self._hidden[-diff:] + + def hideValue(self, value): + """ + Hide the given value from the domain + + After that call the given value won't be seen as a possible value + on that domain anymore. The hidden value will be restored when the + previous saved state is popped. + + @param value: Object currently available in the domain + """ + list.remove(self, value) + self._hidden.append(value) + +# ---------------------------------------------------------------------- +# Constraints +# ---------------------------------------------------------------------- + + +class Constraint(object): + """ + Abstract base class for constraints + """ + + def __call__(self, variables, domains, assignments, forwardcheck=False): + """ + Perform the constraint checking + + If the forwardcheck parameter is not false, besides telling if + the constraint is currently broken or not, the constraint + implementation may choose to hide values from the domains of + unassigned variables to prevent them from being used, and thus + prune the search space. + + @param variables: Variables affected by that constraint, in the + same order provided by the user + @type variables: sequence + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param assignments: Dictionary mapping assigned variables to their + current assumed value + @type assignments: dict + @param forwardcheck: Boolean value stating whether forward checking + should be performed or not + @return: Boolean value stating if this constraint is currently + broken or not + @rtype: bool + """ + return True + + def preProcess(self, variables, domains, constraints, vconstraints): + """ + Preprocess variable domains + + This method is called before starting to look for solutions, + and is used to prune domains with specific constraint logic + when possible. For instance, any constraints with a single + variable may be applied on all possible values and removed, + since they may act on individual values even without further + knowledge about other assignments. + + @param variables: Variables affected by that constraint, in the + same order provided by the user + @type variables: sequence + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param constraints: List of pairs of (constraint, variables) + @type constraints: list + @param vconstraints: Dictionary mapping variables to a list of + constraints affecting the given variables. + @type vconstraints: dict + """ + if len(variables) == 1: + variable = variables[0] + domain = domains[variable] + for value in domain[:]: + if not self(variables, domains, {variable: value}): + domain.remove(value) + constraints.remove((self, variables)) + vconstraints[variable].remove((self, variables)) + + def forwardCheck(self, variables, domains, assignments, + _unassigned=Unassigned): + """ + Helper method for generic forward checking + + Currently, this method acts only when there's a single + unassigned variable. + + @param variables: Variables affected by that constraint, in the + same order provided by the user + @type variables: sequence + @param domains: Dictionary mapping variables to their domains + @type domains: dict + @param assignments: Dictionary mapping assigned variables to their + current assumed value + @type assignments: dict + @return: Boolean value stating if this constraint is currently + broken or not + @rtype: bool + """ + unassignedvariable = _unassigned + for variable in variables: + if variable not in assignments: + if unassignedvariable is _unassigned: + unassignedvariable = variable + else: + break + else: + if unassignedvariable is not _unassigned: + # Remove from the unassigned variable domain's all + # values which break our variable's constraints. + domain = domains[unassignedvariable] + if domain: + for value in domain[:]: + assignments[unassignedvariable] = value + if not self(variables, domains, assignments): + domain.hideValue(value) + del assignments[unassignedvariable] + if not domain: + return False + return True + + +class FunctionConstraint(Constraint): + """ + Constraint which wraps a function defining the constraint logic + + Examples: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> def func(a, b): + ... return b > a + >>> problem.addConstraint(func, ["a", "b"]) + >>> problem.getSolution() + {'a': 1, 'b': 2} + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> def func(a, b): + ... return b > a + >>> problem.addConstraint(FunctionConstraint(func), ["a", "b"]) + >>> problem.getSolution() + {'a': 1, 'b': 2} + """ + + def __init__(self, func, assigned=True): + """ + @param func: Function wrapped and queried for constraint logic + @type func: callable object + @param assigned: Whether the function may receive unassigned + variables or not + @type assigned: bool + """ + self._func = func + self._assigned = assigned + + def __call__(self, variables, domains, assignments, forwardcheck=False, + _unassigned=Unassigned): + parms = [assignments.get(x, _unassigned) for x in variables] + missing = parms.count(_unassigned) + if missing: + return ((self._assigned or self._func(*parms)) and + (not forwardcheck or missing != 1 or + self.forwardCheck(variables, domains, assignments))) + return self._func(*parms) + + +class AllDifferentConstraint(Constraint): + """ + Constraint enforcing that values of all given variables are different + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(AllDifferentConstraint()) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """ + + def __call__(self, variables, domains, assignments, forwardcheck=False, + _unassigned=Unassigned): + seen = {} + for variable in variables: + value = assignments.get(variable, _unassigned) + if value is not _unassigned: + if value in seen: + return False + seen[value] = True + if forwardcheck: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in seen: + if value in domain: + domain.hideValue(value) + if not domain: + return False + return True + + +class AllEqualConstraint(Constraint): + """ + Constraint enforcing that values of all given variables are equal + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(AllEqualConstraint()) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)], [('a', 2), ('b', 2)]] + """ + + def __call__(self, variables, domains, assignments, forwardcheck=False, + _unassigned=Unassigned): + singlevalue = _unassigned + for variable in variables: + value = assignments.get(variable, _unassigned) + if singlevalue is _unassigned: + singlevalue = value + elif value is not _unassigned and value != singlevalue: + return False + if forwardcheck and singlevalue is not _unassigned: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + if singlevalue not in domain: + return False + for value in domain[:]: + if value != singlevalue: + domain.hideValue(value) + return True + + +class MaxSumConstraint(Constraint): + """ + Constraint enforcing that values of given variables sum up to + a given amount + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(MaxSumConstraint(3)) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)], [('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """ + + def __init__(self, maxsum, multipliers=None): + """ + @param maxsum: Value to be considered as the maximum sum + @type maxsum: number + @param multipliers: If given, variable values will be multiplied by + the given factors before being summed to be checked + @type multipliers: sequence of numbers + """ + self._maxsum = maxsum + self._multipliers = multipliers + + def preProcess(self, variables, domains, constraints, vconstraints): + Constraint.preProcess(self, variables, domains, + constraints, vconstraints) + multipliers = self._multipliers + maxsum = self._maxsum + if multipliers: + for variable, multiplier in zip(variables, multipliers): + domain = domains[variable] + for value in domain[:]: + if value * multiplier > maxsum: + domain.remove(value) + else: + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value > maxsum: + domain.remove(value) + + def __call__(self, variables, domains, assignments, forwardcheck=False): + multipliers = self._multipliers + maxsum = self._maxsum + sum = 0 + if multipliers: + for variable, multiplier in zip(variables, multipliers): + if variable in assignments: + sum += assignments[variable] * multiplier + if type(sum) is float: + sum = round(sum, 10) + if sum > maxsum: + return False + if forwardcheck: + for variable, multiplier in zip(variables, multipliers): + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum + value * multiplier > maxsum: + domain.hideValue(value) + if not domain: + return False + else: + for variable in variables: + if variable in assignments: + sum += assignments[variable] + if type(sum) is float: + sum = round(sum, 10) + if sum > maxsum: + return False + if forwardcheck: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum + value > maxsum: + domain.hideValue(value) + if not domain: + return False + return True + + +class ExactSumConstraint(Constraint): + """ + Constraint enforcing that values of given variables sum exactly + to a given amount + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(ExactSumConstraint(3)) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """ + + def __init__(self, exactsum, multipliers=None): + """ + @param exactsum: Value to be considered as the exact sum + @type exactsum: number + @param multipliers: If given, variable values will be multiplied by + the given factors before being summed to be checked + @type multipliers: sequence of numbers + """ + self._exactsum = exactsum + self._multipliers = multipliers + + def preProcess(self, variables, domains, constraints, vconstraints): + Constraint.preProcess(self, variables, domains, + constraints, vconstraints) + multipliers = self._multipliers + exactsum = self._exactsum + if multipliers: + for variable, multiplier in zip(variables, multipliers): + domain = domains[variable] + for value in domain[:]: + if value * multiplier > exactsum: + domain.remove(value) + else: + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value > exactsum: + domain.remove(value) + + def __call__(self, variables, domains, assignments, forwardcheck=False): + multipliers = self._multipliers + exactsum = self._exactsum + sum = 0 + missing = False + if multipliers: + for variable, multiplier in zip(variables, multipliers): + if variable in assignments: + sum += assignments[variable] * multiplier + else: + missing = True + if type(sum) is float: + sum = round(sum, 10) + if sum > exactsum: + return False + if forwardcheck and missing: + for variable, multiplier in zip(variables, multipliers): + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum + value * multiplier > exactsum: + domain.hideValue(value) + if not domain: + return False + else: + for variable in variables: + if variable in assignments: + sum += assignments[variable] + else: + missing = True + if type(sum) is float: + sum = round(sum, 10) + if sum > exactsum: + return False + if forwardcheck and missing: + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if sum + value > exactsum: + domain.hideValue(value) + if not domain: + return False + if missing: + return sum <= exactsum + else: + return sum == exactsum + + +class MinSumConstraint(Constraint): + """ + Constraint enforcing that values of given variables sum at least + to a given amount + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(MinSumConstraint(3)) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)], [('a', 2), ('b', 2)]] + """ + + def __init__(self, minsum, multipliers=None): + """ + @param minsum: Value to be considered as the minimum sum + @type minsum: number + @param multipliers: If given, variable values will be multiplied by + the given factors before being summed to be checked + @type multipliers: sequence of numbers + """ + self._minsum = minsum + self._multipliers = multipliers + + def __call__(self, variables, domains, assignments, forwardcheck=False): + for variable in variables: + if variable not in assignments: + return True + else: + multipliers = self._multipliers + minsum = self._minsum + sum = 0 + if multipliers: + for variable, multiplier in zip(variables, multipliers): + sum += assignments[variable] * multiplier + else: + for variable in variables: + sum += assignments[variable] + if type(sum) is float: + sum = round(sum, 10) + return sum >= minsum + + +class InSetConstraint(Constraint): + """ + Constraint enforcing that values of given variables are present in + the given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(InSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)]] + """ + + def __init__(self, set): + """ + @param set: Set of allowed values + @type set: set + """ + self._set = set + + def __call__(self, variables, domains, assignments, forwardcheck=False): + # preProcess() will remove it. + raise RuntimeError("Can't happen") + + def preProcess(self, variables, domains, constraints, vconstraints): + set = self._set + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value not in set: + domain.remove(value) + vconstraints[variable].remove((self, variables)) + constraints.remove((self, variables)) + + +class NotInSetConstraint(Constraint): + """ + Constraint enforcing that values of given variables are not present in + the given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(NotInSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 2), ('b', 2)]] + """ + + def __init__(self, set): + """ + @param set: Set of disallowed values + @type set: set + """ + self._set = set + + def __call__(self, variables, domains, assignments, forwardcheck=False): + # preProcess() will remove it. + raise RuntimeError("Can't happen") + + def preProcess(self, variables, domains, constraints, vconstraints): + set = self._set + for variable in variables: + domain = domains[variable] + for value in domain[:]: + if value in set: + domain.remove(value) + vconstraints[variable].remove((self, variables)) + constraints.remove((self, variables)) + + +class SomeInSetConstraint(Constraint): + """ + Constraint enforcing that at least some of the values of given + variables must be present in a given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(SomeInSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 1)], [('a', 1), ('b', 2)], [('a', 2), ('b', 1)]] + """ + + def __init__(self, set, n=1, exact=False): + """ + @param set: Set of values to be checked + @type set: set + @param n: Minimum number of assigned values that should be present + in set (default is 1) + @type n: int + @param exact: Whether the number of assigned values which are + present in set must be exactly C{n} + @type exact: bool + """ + self._set = set + self._n = n + self._exact = exact + + def __call__(self, variables, domains, assignments, forwardcheck=False): + set = self._set + missing = 0 + found = 0 + for variable in variables: + if variable in assignments: + found += assignments[variable] in set + else: + missing += 1 + if missing: + if self._exact: + if not (found <= self._n <= missing + found): + return False + else: + if self._n > missing + found: + return False + if forwardcheck and self._n - found == missing: + # All unassigned variables must be assigned to + # values in the set. + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if value not in set: + domain.hideValue(value) + if not domain: + return False + else: + if self._exact: + if found != self._n: + return False + else: + if found < self._n: + return False + return True + + +class SomeNotInSetConstraint(Constraint): + """ + Constraint enforcing that at least some of the values of given + variables must not be present in a given set + + Example: + + >>> problem = Problem() + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addConstraint(SomeNotInSetConstraint([1])) + >>> sorted(sorted(x.items()) for x in problem.getSolutions()) + [[('a', 1), ('b', 2)], [('a', 2), ('b', 1)], [('a', 2), ('b', 2)]] + """ + + def __init__(self, set, n=1, exact=False): + """ + @param set: Set of values to be checked + @type set: set + @param n: Minimum number of assigned values that should not be present + in set (default is 1) + @type n: int + @param exact: Whether the number of assigned values which are + not present in set must be exactly C{n} + @type exact: bool + """ + self._set = set + self._n = n + self._exact = exact + + def __call__(self, variables, domains, assignments, forwardcheck=False): + set = self._set + missing = 0 + found = 0 + for variable in variables: + if variable in assignments: + found += assignments[variable] not in set + else: + missing += 1 + if missing: + if self._exact: + if not (found <= self._n <= missing + found): + return False + else: + if self._n > missing + found: + return False + if forwardcheck and self._n - found == missing: + # All unassigned variables must be assigned to + # values not in the set. + for variable in variables: + if variable not in assignments: + domain = domains[variable] + for value in domain[:]: + if value in set: + domain.hideValue(value) + if not domain: + return False + else: + if self._exact: + if found != self._n: + return False + else: + if found < self._n: + return False + return True + + +if __name__ == "__main__": + import doctest + doctest.testmod() diff --git a/csp/port2/python-constraint-master/constraint/compat.py b/csp/port2/python-constraint-master/constraint/compat.py new file mode 100755 index 00000000..ef31a009 --- /dev/null +++ b/csp/port2/python-constraint-master/constraint/compat.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +PY2 = sys.version_info[0] == 2 +PY3 = (sys.version_info[0] >= 3) + +if PY3: + string_types = str + xrange = range +else: + string_types = basestring # noqa + xrange = xrange diff --git a/csp/port2/python-constraint-master/constraint/version.py b/csp/port2/python-constraint-master/constraint/version.py new file mode 100755 index 00000000..af97862e --- /dev/null +++ b/csp/port2/python-constraint-master/constraint/version.py @@ -0,0 +1,8 @@ +__author__ = "Gustavo Niemeyer" +__copyright__ = "Copyright (c) 2005-2014 - Gustavo Niemeyer " +__credits__ = ["Sebastien Celles"] +__license__ = "" +__version__ = "1.3.1" +__email__ = "gustavo@niemeyer.net" +__status__ = "Development" +__url__ = 'https://github.com/python-constraint/python-constraint' diff --git a/csp/port2/python-constraint-master/examples/__init__.py b/csp/port2/python-constraint-master/examples/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/abc/__init__.py b/csp/port2/python-constraint-master/examples/abc/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/abc/abc.py b/csp/port2/python-constraint-master/examples/abc/abc.py new file mode 100755 index 00000000..27b72906 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/abc/abc.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# +# What's the minimum value for: +# +# ABC +# ------- +# A+B+C +# +# From http://www.umassd.edu/mathcontest/abc.cfm +# +from constraint import Problem + + +def solve(): + problem = Problem() + problem.addVariables("abc", range(1, 10)) + problem.getSolutions() + minvalue = 999 / (9 * 3) + minsolution = {} + for solution in problem.getSolutions(): + a = solution["a"] + b = solution["b"] + c = solution["c"] + value = (a * 100 + b * 10 + c) / (a + b + c) + if value < minvalue: + minsolution = solution + return minvalue, minsolution + + +def main(): + minvalue, minsolution = solve() + print(minvalue) + print(minsolution) + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/coins/__init__.py b/csp/port2/python-constraint-master/examples/coins/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/coins/coins.py b/csp/port2/python-constraint-master/examples/coins/coins.py new file mode 100755 index 00000000..98c2b62d --- /dev/null +++ b/csp/port2/python-constraint-master/examples/coins/coins.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# +# 100 coins must sum to $5.00 +# +# That's kind of a country-specific problem, since depending on the +# country there are different values for coins. Here is presented +# the solution for a given set. +# +from constraint import Problem, ExactSumConstraint +import sys + + +def solve(): + problem = Problem() + total = 5.00 + variables = ("0.01", "0.05", "0.10", "0.50", "1.00") + values = [float(x) for x in variables] + for variable, value in zip(variables, values): + problem.addVariable(variable, range(int(total / value))) + problem.addConstraint(ExactSumConstraint(total, values), variables) + problem.addConstraint(ExactSumConstraint(100)) + solutions = problem.getSolutionIter() + return solutions, variables + + +def main(): + solutions, variables = solve() + for i, solution in enumerate(solutions): + sys.stdout.write("%03d -> " % (i + 1)) + for variable in variables: + sys.stdout.write("%s:%d " % (variable, solution[variable])) + sys.stdout.write("\n") + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/crosswords/__init__.py b/csp/port2/python-constraint-master/examples/crosswords/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/crosswords/crosswords.py b/csp/port2/python-constraint-master/examples/crosswords/crosswords.py new file mode 100755 index 00000000..df0fce61 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/crosswords/crosswords.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +from constraint import Problem, AllDifferentConstraint +import random +import sys + +MINLEN = 3 + + +def main(puzzle, lines): + puzzle = puzzle.rstrip().splitlines() + while puzzle and not puzzle[0]: + del puzzle[0] + + # Extract horizontal words + horizontal = [] + word = [] + predefined = {} + for row in range(len(puzzle)): + for col in range(len(puzzle[row])): + char = puzzle[row][col] + if not char.isspace(): + word.append((row, col)) + if char != "#": + predefined[row, col] = char + elif word: + if len(word) > MINLEN: + horizontal.append(word[:]) + del word[:] + if word: + if len(word) > MINLEN: + horizontal.append(word[:]) + del word[:] + + # Extract vertical words + vertical = [] + validcol = True + col = 0 + while validcol: + validcol = False + for row in range(len(puzzle)): + if col >= len(puzzle[row]): + if word: + if len(word) > MINLEN: + vertical.append(word[:]) + del word[:] + else: + validcol = True + char = puzzle[row][col] + if not char.isspace(): + word.append((row, col)) + if char != "#": + predefined[row, col] = char + elif word: + if len(word) > MINLEN: + vertical.append(word[:]) + del word[:] + if word: + if len(word) > MINLEN: + vertical.append(word[:]) + del word[:] + col += 1 + + # hnames = ["h%d" % i for i in range(len(horizontal))] + # vnames = ["v%d" % i for i in range(len(vertical))] + + # problem = Problem(MinConflictsSolver()) + problem = Problem() + + for hi, hword in enumerate(horizontal): + for vi, vword in enumerate(vertical): + for hchar in hword: + if hchar in vword: + hci = hword.index(hchar) + vci = vword.index(hchar) + problem.addConstraint(lambda hw, vw, hci=hci, vci=vci: + hw[hci] == vw[vci], + ("h%d" % hi, "v%d" % vi)) + + for char, letter in predefined.items(): + for hi, hword in enumerate(horizontal): + if char in hword: + hci = hword.index(char) + problem.addConstraint(lambda hw, hci=hci, letter=letter: + hw[hci] == letter, ("h%d" % hi,)) + for vi, vword in enumerate(vertical): + if char in vword: + vci = vword.index(char) + problem.addConstraint(lambda vw, vci=vci, letter=letter: + vw[vci] == letter, ("v%d" % vi,)) + + wordsbylen = {} + for hword in horizontal: + wordsbylen[len(hword)] = [] + for vword in vertical: + wordsbylen[len(vword)] = [] + + for line in lines: + line = line.strip() + ll = len(line) + if ll in wordsbylen: + wordsbylen[ll].append(line.upper()) + + for hi, hword in enumerate(horizontal): + words = wordsbylen[len(hword)] + random.shuffle(words) + problem.addVariable("h%d" % hi, words) + for vi, vword in enumerate(vertical): + words = wordsbylen[len(vword)] + random.shuffle(words) + problem.addVariable("v%d" % vi, words) + + problem.addConstraint(AllDifferentConstraint()) + + solution = problem.getSolution() + if not solution: + print("No solution found!") + + maxcol = 0 + maxrow = 0 + for hword in horizontal: + for row, col in hword: + if row > maxrow: + maxrow = row + if col > maxcol: + maxcol = col + for vword in vertical: + for row, col in vword: + if row > maxrow: + maxrow = row + if col > maxcol: + maxcol = col + + matrix = [] + for row in range(maxrow + 1): + matrix.append([" "] * (maxcol + 1)) + + for variable in solution: + if variable[0] == "v": + word = vertical[int(variable[1:])] + else: + word = horizontal[int(variable[1:])] + for (row, col), char in zip(word, solution[variable]): + matrix[row][col] = char + + for row in range(maxrow + 1): + for col in range(maxcol + 1): + sys.stdout.write(matrix[row][col]) + sys.stdout.write("\n") + + +if __name__ == "__main__": + if len(sys.argv) != 3: + sys.exit("Usage: crosswords.py ") + main(open(sys.argv[1]).read(), open(sys.argv[2])) diff --git a/csp/port2/python-constraint-master/examples/crosswords/large.mask b/csp/port2/python-constraint-master/examples/crosswords/large.mask new file mode 100755 index 00000000..ba5364c8 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/crosswords/large.mask @@ -0,0 +1,27 @@ + +# ######## # +# # # # # +######## # # +# # # # # +# # ######## +# # # # # # +######## # # +# # # # # # + # # # +######## # # + # # # # # + # ######## + # # # # # + # # ######## + # # # # # # + # # ######## + # # # # +######## # # + # # # # # # + # # # # # # + ######## # # + # # # # + # ######## + # # # # +######## # # + diff --git a/csp/port2/python-constraint-master/examples/crosswords/medium.mask b/csp/port2/python-constraint-master/examples/crosswords/medium.mask new file mode 100755 index 00000000..3332a097 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/crosswords/medium.mask @@ -0,0 +1,19 @@ + + # +######### +# # # +# # ###### +# # # +# # # # +# # # # +######## # +# # # + # # # + ######### + # # # + ######### + # # # + # # +####### + # + diff --git a/csp/port2/python-constraint-master/examples/crosswords/python.mask b/csp/port2/python-constraint-master/examples/crosswords/python.mask new file mode 100755 index 00000000..fe5a5767 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/crosswords/python.mask @@ -0,0 +1,8 @@ + P + Y +####T#### + # H # + # O # +####N # + # # +######### diff --git a/csp/port2/python-constraint-master/examples/crosswords/small.mask b/csp/port2/python-constraint-master/examples/crosswords/small.mask new file mode 100755 index 00000000..0e43ff78 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/crosswords/small.mask @@ -0,0 +1,8 @@ + # + # +######### + # # + # # # # +##### # # + # # # +######### diff --git a/csp/port2/python-constraint-master/examples/einstein/__init__.py b/csp/port2/python-constraint-master/examples/einstein/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/einstein/einstein.py b/csp/port2/python-constraint-master/examples/einstein/einstein.py new file mode 100755 index 00000000..2ce6e45b --- /dev/null +++ b/csp/port2/python-constraint-master/examples/einstein/einstein.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +# +# ALBERT EINSTEIN'S RIDDLE +# +# ARE YOU IN THE TOP 2% OF INTELLIGENT PEOPLE IN THE WORLD? +# SOLVE THE RIDDLE AND FIND OUT. +# +# There are no tricks, just pure logic, so good luck and don't give up. +# +# 1. In a street there are five houses, painted five different colours. +# 2. In each house lives a person of different nationality +# 3. These five homeowners each drink a different kind of beverage, smoke +# different brand of cigar and keep a different pet. +# +# THE QUESTION: WHO OWNS THE FISH? +# +# HINTS +# +# 1. The Brit lives in a red house. +# 2. The Swede keeps dogs as pets. +# 3. The Dane drinks tea. +# 4. The Green house is on the left of the White house. +# 5. The owner of the Green house drinks coffee. +# 6. The person who smokes Pall Mall rears birds. +# 7. The owner of the Yellow house smokes Dunhill. +# 8. The man living in the centre house drinks milk. +# 9. The Norwegian lives in the first house. +# 10. The man who smokes Blends lives next to the one who keeps cats. +# 11. The man who keeps horses lives next to the man who smokes Dunhill. +# 12. The man who smokes Blue Master drinks beer. +# 13. The German smokes Prince. +# 14. The Norwegian lives next to the blue house. +# 15. The man who smokes Blends has a neighbour who drinks water. +# +# ALBERT EINSTEIN WROTE THIS RIDDLE EARLY DURING THE 19th CENTURY. HE +# SAID THAT 98% OF THE WORLD POPULATION WOULD NOT BE ABLE TO SOLVE IT. + +from constraint import Problem, AllDifferentConstraint + +# Check http://www.csc.fi/oppaat/f95/python/talot.py + + +def solve(): + problem = Problem() + for i in range(1, 6): + problem.addVariable("color%d" % i, + ["red", "white", "green", "yellow", "blue"]) + problem.addVariable("nationality%d" % i, + ["brit", "swede", "dane", "norwegian", "german"]) + problem.addVariable("drink%d" % i, + ["tea", "coffee", "milk", "beer", "water"]) + problem.addVariable("smoke%d" % i, + ["pallmall", "dunhill", "blends", + "bluemaster", "prince"]) + problem.addVariable("pet%d" % i, + ["dogs", "birds", "cats", "horses", "fish"]) + + problem.addConstraint(AllDifferentConstraint(), + ["color%d" % i for i in range(1, 6)]) + problem.addConstraint(AllDifferentConstraint(), + ["nationality%d" % i for i in range(1, 6)]) + problem.addConstraint(AllDifferentConstraint(), + ["drink%d" % i for i in range(1, 6)]) + problem.addConstraint(AllDifferentConstraint(), + ["smoke%d" % i for i in range(1, 6)]) + problem.addConstraint(AllDifferentConstraint(), + ["pet%d" % i for i in range(1, 6)]) + + for i in range(1, 6): + + # Hint 1 + problem.addConstraint(lambda nationality, color: + nationality != "brit" or color == "red", + ("nationality%d" % i, "color%d" % i)) + + # Hint 2 + problem.addConstraint(lambda nationality, pet: + nationality != "swede" or pet == "dogs", + ("nationality%d" % i, "pet%d" % i)) + + # Hint 3 + problem.addConstraint(lambda nationality, drink: + nationality != "dane" or drink == "tea", + ("nationality%d" % i, "drink%d" % i)) + + # Hint 4 + if i < 5: + problem.addConstraint(lambda colora, colorb: + colora != "green" or colorb == "white", + ("color%d" % i, "color%d" % (i + 1))) + else: + problem.addConstraint(lambda color: color != "green", + ("color%d" % i,)) + + # Hint 5 + problem.addConstraint(lambda color, drink: + color != "green" or drink == "coffee", + ("color%d" % i, "drink%d" % i)) + + # Hint 6 + problem.addConstraint(lambda smoke, pet: + smoke != "pallmall" or pet == "birds", + ("smoke%d" % i, "pet%d" % i)) + + # Hint 7 + problem.addConstraint(lambda color, smoke: + color != "yellow" or smoke == "dunhill", + ("color%d" % i, "smoke%d" % i)) + + # Hint 8 + if i == 3: + problem.addConstraint(lambda drink: drink == "milk", + ("drink%d" % i,)) + + # Hint 9 + if i == 1: + problem.addConstraint(lambda nationality: + nationality == "norwegian", + ("nationality%d" % i,)) + + # Hint 10 + if 1 < i < 5: + problem.addConstraint(lambda smoke, peta, petb: + smoke != "blends" or peta == "cats" or + petb == "cats", + ("smoke%d" % i, "pet%d" % (i - 1), + "pet%d" % (i + 1))) + else: + problem.addConstraint(lambda smoke, pet: + smoke != "blends" or pet == "cats", + ("smoke%d" % i, + "pet%d" % (i == 1 and 2 or 4))) + + # Hint 11 + if 1 < i < 5: + problem.addConstraint(lambda pet, smokea, smokeb: + pet != "horses" or smokea == "dunhill" or + smokeb == "dunhill", + ("pet%d" % i, "smoke%d" % (i - 1), + "smoke%d" % (i + 1))) + else: + problem.addConstraint(lambda pet, smoke: + pet != "horses" or smoke == "dunhill", + ("pet%d" % i, + "smoke%d" % (i == 1 and 2 or 4))) + + # Hint 12 + problem.addConstraint(lambda smoke, drink: + smoke != "bluemaster" or drink == "beer", + ("smoke%d" % i, "drink%d" % i)) + + # Hint 13 + problem.addConstraint(lambda nationality, smoke: + nationality != "german" or smoke == "prince", + ("nationality%d" % i, "smoke%d" % i)) + + # Hint 14 + if 1 < i < 5: + problem.addConstraint(lambda nationality, colora, colorb: + nationality != "norwegian" or + colora == "blue" or colorb == "blue", + ("nationality%d" % i, "color%d" % (i - 1), + "color%d" % (i + 1))) + else: + problem.addConstraint(lambda nationality, color: + nationality != "norwegian" or + color == "blue", + ("nationality%d" % i, + "color%d" % (i == 1 and 2 or 4))) + + # Hint 15 + if 1 < i < 5: + problem.addConstraint(lambda smoke, drinka, drinkb: + smoke != "blends" or + drinka == "water" or drinkb == "water", + ("smoke%d" % i, "drink%d" % (i - 1), + "drink%d" % (i + 1))) + else: + problem.addConstraint(lambda smoke, drink: + smoke != "blends" or drink == "water", + ("smoke%d" % i, + "drink%d" % (i == 1 and 2 or 4))) + + solutions = problem.getSolutions() + return solutions + + +def showSolution(solution): + for i in range(1, 6): + print("House %d" % i) + print("--------") + print("Nationality: %s" % solution["nationality%d" % i]) + print("Color: %s" % solution["color%d" % i]) + print("Drink: %s" % solution["drink%d" % i]) + print("Smoke: %s" % solution["smoke%d" % i]) + print("Pet: %s" % solution["pet%d" % i]) + print("") + + +def main(): + solutions = solve() + print("Found %d solution(s)!" % len(solutions)) + print("") + for solution in solutions: + showSolution(solution) + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/queens/__init__.py b/csp/port2/python-constraint-master/examples/queens/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/queens/queens.py b/csp/port2/python-constraint-master/examples/queens/queens.py new file mode 100755 index 00000000..88aa5651 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/queens/queens.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +# +# http://mathworld.wolfram.com/QueensProblem.html +# +from constraint import Problem +import sys + + +def solve(): + problem = Problem() + size = 8 + cols = range(size) + rows = range(size) + problem.addVariables(cols, rows) + for col1 in cols: + for col2 in cols: + if col1 < col2: + problem.addConstraint(lambda row1, row2, col1=col1, col2=col2: + abs(row1 - row2) != abs(col1 - col2) and + row1 != row2, (col1, col2)) + solutions = problem.getSolutions() + return solutions, size + + +def showSolution(solution, size): + sys.stdout.write(" %s \n" % ("-" * ((size * 4) - 1))) + for i in range(size): + sys.stdout.write(" |") + for j in range(size): + if solution[j] == i: + sys.stdout.write(" %d |" % j) + else: + sys.stdout.write(" |") + sys.stdout.write("\n") + if i != size - 1: + sys.stdout.write(" |%s|\n" % ("-" * ((size * 4) - 1))) + sys.stdout.write(" %s \n" % ("-" * ((size * 4) - 1))) + + +def main(show=False): + solutions, size = solve() + print("Found %d solution(s)!" % len(solutions)) + if show: + for solution in solutions: + showSolution(solution, size) + + +if __name__ == "__main__": + show = False + if len(sys.argv) == 2 and sys.argv[1] == "-s": + show = True + elif len(sys.argv) != 1: + sys.exit("Usage: queens.py [-s]") + main(show) diff --git a/csp/port2/python-constraint-master/examples/rooks/__init__.py b/csp/port2/python-constraint-master/examples/rooks/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/rooks/rooks.py b/csp/port2/python-constraint-master/examples/rooks/rooks.py new file mode 100755 index 00000000..a7979019 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/rooks/rooks.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# +# http://mathworld.wolfram.com/RooksProblem.html +# +from constraint import Problem +import sys + + +def factorial(x): + return x == 1 or factorial(x - 1) * x + + +def solve(size): + problem = Problem() + cols = range(size) + rows = range(size) + problem.addVariables(cols, rows) + for col1 in cols: + for col2 in cols: + if col1 < col2: + problem.addConstraint(lambda row1, row2: row1 != row2, + (col1, col2)) + solutions = problem.getSolutions() + return solutions + + +def main(show=False): + size = 8 + solutions = solve(size) + print("Found %d solution(s)!" % len(solutions)) + if show: + for solution in solutions: + showSolution(solution, size) + + +def showSolution(solution, size): + sys.stdout.write(" %s \n" % ("-" * ((size * 4) - 1))) + for i in range(size): + sys.stdout.write(" |") + for j in range(size): + if solution[j] == i: + sys.stdout.write(" %d |" % j) + else: + sys.stdout.write(" |") + sys.stdout.write("\n") + if i != size - 1: + sys.stdout.write(" |%s|\n" % ("-" * ((size * 4) - 1))) + sys.stdout.write(" %s \n" % ("-" * ((size * 4) - 1))) + + +if __name__ == "__main__": + show = False + if len(sys.argv) == 2 and sys.argv[1] == "-s": + show = True + elif len(sys.argv) != 1: + sys.exit("Usage: rooks.py [-s]") + main(show) diff --git a/csp/port2/python-constraint-master/examples/studentdesks/__init__.py b/csp/port2/python-constraint-master/examples/studentdesks/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/studentdesks/studentdesks.py b/csp/port2/python-constraint-master/examples/studentdesks/studentdesks.py new file mode 100755 index 00000000..a2978ec9 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/studentdesks/studentdesks.py @@ -0,0 +1,48 @@ +#!/usr/bin/python +# +# http://home.chello.no/~dudley/ +# +from constraint import Problem, AllDifferentConstraint, SomeInSetConstraint +import sys + +STUDENTDESKS = [[0, 1, 0, 0, 0, 0], + [0, 2, 3, 4, 5, 6], + [0, 7, 8, 9, 10, 0], + [0, 11, 12, 13, 14, 0], + [15, 16, 17, 18, 19, 0], + [0, 0, 0, 0, 20, 0]] + + +def solve(): + problem = Problem() + problem.addVariables(range(1, 21), ["A", "B", "C", "D", "E"]) + problem.addConstraint(SomeInSetConstraint(["A"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["B"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["C"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["D"], 4, True)) + problem.addConstraint(SomeInSetConstraint(["E"], 4, True)) + for row in range(len(STUDENTDESKS) - 1): + for col in range(len(STUDENTDESKS[row]) - 1): + lst = [STUDENTDESKS[row][col], STUDENTDESKS[row][col + 1], + STUDENTDESKS[row + 1][col], STUDENTDESKS[row + 1][col + 1]] + lst = [x for x in lst if x] + problem.addConstraint(AllDifferentConstraint(), lst) + solutions = problem.getSolution() + return solutions + + +def main(): + solutions = solve() + showSolution(solutions) + + +def showSolution(solution): + for row in range(len(STUDENTDESKS)): + for col in range(len(STUDENTDESKS[row])): + id = STUDENTDESKS[row][col] + sys.stdout.write(" %s" % (id and solution[id] or " ")) + sys.stdout.write("\n") + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/sudoku/__init__.py b/csp/port2/python-constraint-master/examples/sudoku/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/sudoku/sudoku.py b/csp/port2/python-constraint-master/examples/sudoku/sudoku.py new file mode 100755 index 00000000..820c76dd --- /dev/null +++ b/csp/port2/python-constraint-master/examples/sudoku/sudoku.py @@ -0,0 +1,71 @@ +# +# Sudoku puzzle solver by by Luigi Poderico (www.poderico.it). +# +import sys +from constraint import Problem, AllDifferentConstraint + + +def solve(): + problem = Problem() + + # Define the variables: 9 rows of 9 variables rangin in 1...9 + for i in range(1, 10): + problem.addVariables(range(i * 10 + 1, i * 10 + 10), range(1, 10)) + + # Each row has different values + for i in range(1, 10): + problem.addConstraint(AllDifferentConstraint(), range(i * 10 + 1, i * 10 + 10)) + + # Each colum has different values + for i in range(1, 10): + problem.addConstraint(AllDifferentConstraint(), range(10 + i, 100 + i, 10)) + + # Each 3x3 box has different values + problem.addConstraint(AllDifferentConstraint(), [11, 12, 13, 21, 22, 23, 31, 32, 33]) + problem.addConstraint(AllDifferentConstraint(), [41, 42, 43, 51, 52, 53, 61, 62, 63]) + problem.addConstraint(AllDifferentConstraint(), [71, 72, 73, 81, 82, 83, 91, 92, 93]) + + problem.addConstraint(AllDifferentConstraint(), [14, 15, 16, 24, 25, 26, 34, 35, 36]) + problem.addConstraint(AllDifferentConstraint(), [44, 45, 46, 54, 55, 56, 64, 65, 66]) + problem.addConstraint(AllDifferentConstraint(), [74, 75, 76, 84, 85, 86, 94, 95, 96]) + + problem.addConstraint(AllDifferentConstraint(), [17, 18, 19, 27, 28, 29, 37, 38, 39]) + problem.addConstraint(AllDifferentConstraint(), [47, 48, 49, 57, 58, 59, 67, 68, 69]) + problem.addConstraint(AllDifferentConstraint(), [77, 78, 79, 87, 88, 89, 97, 98, 99]) + + # Some value is given. + initValue = [[0, 9, 0, 7, 0, 0, 8, 6, 0], + [0, 3, 1, 0, 0, 5, 0, 2, 0], + [8, 0, 6, 0, 0, 0, 0, 0, 0], + [0, 0, 7, 0, 5, 0, 0, 0, 6], + [0, 0, 0, 3, 0, 7, 0, 0, 0], + [5, 0, 0, 0, 1, 0, 7, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 0, 9], + [0, 2, 0, 6, 0, 0, 0, 5, 0], + [0, 5, 4, 0, 0, 8, 0, 7, 0]] + + for i in range(1, 10): + for j in range(1, 10): + if initValue[i - 1][j - 1] != 0: + problem.addConstraint(lambda var, val=initValue[i - 1][j - 1]: + var == val, (i * 10 + j,)) + + # Get the solutions. + solutions = problem.getSolutions() + return solutions + + +def main(): + solutions = solve() + # Print the solutions + for solution in solutions: + for i in range(1, 10): + for j in range(1, 10): + index = i * 10 + j + sys.stdout.write("%s " % solution[index]) + print("") + print("") + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/wordmath/__init__.py b/csp/port2/python-constraint-master/examples/wordmath/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/wordmath/seisseisdoze.py b/csp/port2/python-constraint-master/examples/wordmath/seisseisdoze.py new file mode 100755 index 00000000..22776db0 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/wordmath/seisseisdoze.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# +# Assign equal values to equal letters, and different values to +# different letters, in a way that satisfies the following sum: +# +# SEIS +# + SEIS +# ------ +# DOZE +# +from constraint import Problem, AllDifferentConstraint + + +def solve(): + problem = Problem() + problem.addVariables("seidoz", range(10)) + problem.addConstraint(lambda s, e: (2 * s) % 10 == e, "se") + problem.addConstraint(lambda i, s, z, e: ((10 * 2 * i) + (2 * s)) % 100 == + z * 10 + e, "isze") + problem.addConstraint(lambda s, e, i, d, o, z: + 2 * (s * 1000 + e * 100 + i * 10 + s) == + d * 1000 + o * 100 + z * 10 + e, "seidoz") + problem.addConstraint(lambda s: s != 0, "s") + problem.addConstraint(lambda d: d != 0, "d") + problem.addConstraint(AllDifferentConstraint()) + solutions = problem.getSolutions() + return solutions + + +def main(): + solutions = solve() + print("SEIS+SEIS=DOZE") + for s in solutions: + print("%(s)d%(e)d%(i)d%(s)s+%(s)d%(e)d%(i)d%(s)d=" + "%(d)d%(o)d%(z)d%(e)d") % s + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/wordmath/sendmoremoney.py b/csp/port2/python-constraint-master/examples/wordmath/sendmoremoney.py new file mode 100755 index 00000000..9e9578ec --- /dev/null +++ b/csp/port2/python-constraint-master/examples/wordmath/sendmoremoney.py @@ -0,0 +1,42 @@ +#!/usr/bin/python +# +# Assign equal values to equal letters, and different values to +# different letters, in a way that satisfies the following sum: +# +# SEND +# + MORE +# ------ +# MONEY +# +from constraint import Problem, NotInSetConstraint, AllDifferentConstraint + + +def solve(): + problem = Problem() + problem.addVariables("sendmory", range(10)) + problem.addConstraint(lambda d, e, y: (d + e) % 10 == y, "dey") + problem.addConstraint(lambda n, d, r, e, y: (n * 10 + d + r * 10 + e) % 100 == + e * 10 + y, "ndrey") + problem.addConstraint(lambda e, n, d, o, r, y: + (e * 100 + n * 10 + d + o * 100 + r * 10 + e) % 1000 == + n * 100 + e * 10 + y, "endory") + problem.addConstraint(lambda s, e, n, d, m, o, r, y: + 1000 * s + 100 * e + 10 * n + d + 1000 * m + 100 * o + 10 * r + e == + 10000 * m + 1000 * o + 100 * n + 10 * e + y, "sendmory") + problem.addConstraint(NotInSetConstraint([0]), "sm") + problem.addConstraint(AllDifferentConstraint()) + solutions = problem.getSolutions() + return solutions + + +def main(): + solutions = solve() + print("SEND+MORE=MONEY") + for s in solutions: + print("%(s)d%(e)d%(n)d%(d)d+" + "%(m)d%(o)d%(r)d%(e)d=" + "%(m)d%(o)d%(n)d%(e)d%(y)d" % s) + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/wordmath/twotwofour.py b/csp/port2/python-constraint-master/examples/wordmath/twotwofour.py new file mode 100755 index 00000000..33e4aabb --- /dev/null +++ b/csp/port2/python-constraint-master/examples/wordmath/twotwofour.py @@ -0,0 +1,37 @@ +#!/usr/bin/python +# +# Assign equal values to equal letters, and different values to +# different letters, in a way that satisfies the following sum: +# +# TWO +# + TWO +# ----- +# FOUR +# +from constraint import Problem, AllDifferentConstraint, NotInSetConstraint + + +def solve(): + problem = Problem() + problem.addVariables("twofur", range(10)) + problem.addConstraint(lambda o, r: (2 * o) % 10 == r, "or") + problem.addConstraint(lambda w, o, u, + r: ((10 * 2 * w) + (2 * o)) % 100 == u * 10 + r, "wour") + problem.addConstraint(lambda t, w, o, f, u, r: + 2 * (t * 100 + w * 10 + o) == + f * 1000 + o * 100 + u * 10 + r, "twofur") + problem.addConstraint(NotInSetConstraint([0]), "ft") + problem.addConstraint(AllDifferentConstraint()) + solutions = problem.getSolutions() + return solutions + + +def main(): + solutions = solve() + print("TWO+TWO=FOUR") + for s in solutions: + print("%(t)d%(w)d%(o)d+%(t)d%(w)d%(o)d=%(f)d%(o)d%(u)d%(r)d" % s) + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/examples/xsum/__init__.py b/csp/port2/python-constraint-master/examples/xsum/__init__.py new file mode 100755 index 00000000..e69de29b diff --git a/csp/port2/python-constraint-master/examples/xsum/xsum.py b/csp/port2/python-constraint-master/examples/xsum/xsum.py new file mode 100755 index 00000000..987438f3 --- /dev/null +++ b/csp/port2/python-constraint-master/examples/xsum/xsum.py @@ -0,0 +1,48 @@ +#!/usr/bin/python +# +# Reorganize the following numbers in a way that each line of +# 5 numbers sum to 27. +# +# 1 6 +# 2 7 +# 3 +# 8 4 +# 9 5 +# +from constraint import Problem, AllDifferentConstraint + + +def solve(): + problem = Problem() + problem.addVariables("abcdxefgh", range(1, 10)) + problem.addConstraint(lambda a, b, c, d, x: + a < b < c < d and a + b + c + d + x == 27, "abcdx") + problem.addConstraint(lambda e, f, g, h, x: + e < f < g < h and e + f + g + h + x == 27, "efghx") + problem.addConstraint(AllDifferentConstraint()) + solutions = problem.getSolutions() + return solutions + + +def main(): + solutions = solve() + print("Found %d solutions!" % len(solutions)) + showSolutions(solutions) + + +def showSolutions(solutions): + for solution in solutions: + print(""" %d %d + %d %d + %d + %d %d + %d %d +""" % (solution["a"], solution["e"], + solution["b"], solution["f"], + solution["x"], + solution["g"], solution["c"], + solution["h"], solution["d"])) + + +if __name__ == "__main__": + main() diff --git a/csp/port2/python-constraint-master/setup.cfg b/csp/port2/python-constraint-master/setup.cfg new file mode 100755 index 00000000..5bf04527 --- /dev/null +++ b/csp/port2/python-constraint-master/setup.cfg @@ -0,0 +1,9 @@ +[bdist_wheel] +universal = 1 + +[bdist_rpm] +doc_files = README.rst +use_bzip2 = 1 + +[sdist] +formats = bztar diff --git a/csp/port2/python-constraint-master/setup.py b/csp/port2/python-constraint-master/setup.py new file mode 100755 index 00000000..4a597e55 --- /dev/null +++ b/csp/port2/python-constraint-master/setup.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from setuptools import setup, find_packages # Always prefer setuptools over distutils +from codecs import open # To use a consistent encoding +from os import path +import io + +NAME = 'python-constraint' +filename = "%s/version.py" % 'constraint' +with open(filename) as f: + exec(f.read()) + +here = path.abspath(path.dirname(__file__)) + +def readme(): + filename = path.join(here, 'README.rst') + with io.open(filename, 'rt', encoding='UTF-8') as f: + return f.read() + +setup( + name=NAME, + + # Versions should comply with PEP440. For a discussion on single-sourcing + # the version across setup.py and the project code, see + # https://packaging.python.org/en/latest/development.html#single-sourcing-the-version + #version='0.0.1', + version=__version__, + + description="python-constraint is a module implementing support "\ + "for handling CSPs (Constraint Solving Problems) over finite domain", + + long_description=readme(), + + # The project's main homepage. + url=__url__, + + # Author details + author=__author__, + author_email=__email__, + + # Choose your license + license=__license__, + + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + 'Development Status :: 3 - Alpha', + + # Indicate who your project is intended for + 'Environment :: Console', + #'Topic :: Software Development :: Build Tools', + 'Intended Audience :: Science/Research', + 'Operating System :: OS Independent', + + # Specify the Python versions you support here. In particular, ensure + # that you indicate whether you support Python 2, Python 3 or both. + 'Programming Language :: Cython', + + 'Programming Language :: Python', + #'Programming Language :: Python :: 2', + #'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + #'Programming Language :: Python :: 3', + #'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + + 'Topic :: Scientific/Engineering', + + # Pick your license as you wish (should match "license" above) + 'License :: OSI Approved :: BSD License', + + ], + + # What does your project relate to? + keywords='csp constraint solving problems problem solver', + + # You can just specify the packages manually here if your project is + # simple. Or you can use find_packages(). + packages=find_packages(exclude=['contrib', 'docs', 'tests*']), + + # List run-time dependencies here. These will be installed by pip when your + # project is installed. For an analysis of "install_requires" vs pip's + # requirements files see: + # https://packaging.python.org/en/latest/technical.html#install-requires-vs-requirements-files + install_requires=[], + + # List additional groups of dependencies here (e.g. development dependencies). + # You can install these using the following syntax, for example: + # $ pip install -e .[dev,test] + extras_require = { + 'dev': ['check-manifest', 'nose'], + 'test': ['coverage', 'nose'], + }, + + # If there are data files included in your packages that need to be + # installed, specify them here. If using Python 2.6 or less, then these + # have to be included in MANIFEST.in as well. + #package_data={ + # 'sample': ['logging.conf'], + #}, + + # Although 'package_data' is the preferred approach, in some case you may + # need to place data files outside of your packages. + # see http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files + # In this case, 'data_file' will be installed into '/my_data' + #data_files=[('my_data', ['data/data_file'])], + + # To provide executable scripts, use entry points in preference to the + # "scripts" keyword. Entry points provide cross-platform support and allow + # pip to create the appropriate form of executable for the target platform. + #entry_points={ + # 'console_scripts': [ + # 'sample=sample:main', + # ], + #}, +) diff --git a/csp/port2/python-constraint-master/tests/test_constraint.py b/csp/port2/python-constraint-master/tests/test_constraint.py new file mode 100755 index 00000000..e644bf4b --- /dev/null +++ b/csp/port2/python-constraint-master/tests/test_constraint.py @@ -0,0 +1,91 @@ +import constraint + +from examples.abc import abc +from examples.coins import coins +# from examples.crosswords import crosswords +from examples.einstein import einstein +from examples.queens import queens +from examples.rooks import rooks +from examples.studentdesks import studentdesks +# from examples.sudoku import sudoku +# from examples.wordmath import (seisseisdoze, sendmoremoney, twotwofour) +# from examples.xsum import xsum + +import constraint.compat as compat + + +def test_abc(): + solutions = abc.solve() + minvalue, minsolution = solutions + assert minvalue == 37 + assert minsolution == {'a': 1, 'c': 2, 'b': 1} + + +def test_coins(): + solutions = coins.solve() + assert len(solutions) == 2 + + +def test_einstein(): + solutions = einstein.solve() + expected_solutions = [ + { + 'nationality2': 'dane', + 'nationality3': 'brit', + 'nationality1': 'norwegian', + 'nationality4': 'german', + 'nationality5': 'swede', + 'color1': 'yellow', + 'color3': 'red', + 'color2': 'blue', + 'color5': 'white', + 'color4': 'green', + 'drink4': 'coffee', + 'drink5': 'beer', + 'drink1': 'water', + 'drink2': 'tea', + 'drink3': 'milk', + 'smoke5': 'bluemaster', + 'smoke4': 'prince', + 'smoke3': 'pallmall', + 'smoke2': 'blends', + 'smoke1': 'dunhill', + 'pet5': 'dogs', + 'pet4': 'fish', + 'pet1': 'cats', + 'pet3': 'birds', + 'pet2': 'horses' + } + ] + assert solutions == expected_solutions + + +def test_queens(): + solutions, size = queens.solve() + assert size == 8 + for solution in solutions: + queens.showSolution(solution, size) + + +def test_rooks(): + size = 8 + solutions = rooks.solve(size) + assert len(solutions) == rooks.factorial(size) + + +def test_studentdesks(): + solutions = studentdesks.solve() + expected_solutions = {1: 'A', 2: 'E', 3: 'D', 4: 'E', 5: 'D', 6: 'A', 7: 'C', 8: 'B', 9: 'C', 10: 'B', 11: 'E', 12: 'D', 13: 'E', 14: 'D', 15: 'A', 16: 'C', 17: 'B', 18: 'C', 19: 'B', 20: 'A'} + assert solutions == expected_solutions + + +def test_constraint_without_variables(): + problem = constraint.Problem() + problem.addVariable("a", [1, 2, 3]) + problem.addConstraint(lambda a: a * 2 == 6) + solutions = problem.getSolutions() + assert solutions == [{'a': 3}] + + +def test_version(): + assert isinstance(constraint.__version__, compat.string_types) diff --git a/csp/port2/python-constraint-master/tests/test_solvers.py b/csp/port2/python-constraint-master/tests/test_solvers.py new file mode 100755 index 00000000..1a24d382 --- /dev/null +++ b/csp/port2/python-constraint-master/tests/test_solvers.py @@ -0,0 +1,17 @@ +from constraint import Problem, MinConflictsSolver + + +def test_min_conflicts_solver(): + problem = Problem(MinConflictsSolver()) + problem.addVariable("x", [0, 1]) + problem.addVariable("y", [0, 1]) + solution = problem.getSolution() + + possible_solutions = [ + {'x': 0, 'y': 0}, + {'x': 0, 'y': 1}, + {'x': 1, 'y': 0}, + {'x': 1, 'y': 1} + ] + + assert solution in possible_solutions diff --git a/csp/port2/python-constraint-master/tests/test_some_not_in_set.py b/csp/port2/python-constraint-master/tests/test_some_not_in_set.py new file mode 100755 index 00000000..31ac4fc4 --- /dev/null +++ b/csp/port2/python-constraint-master/tests/test_some_not_in_set.py @@ -0,0 +1,102 @@ +from constraint import Domain, Variable, SomeNotInSetConstraint + + +def test_empty_constraint(): + constrainer = SomeNotInSetConstraint(set()) + v1, v2 = variables = [Variable('v1'), Variable('v2')] + assignments = {v1: 'a', v2: 'b'} + + assert constrainer(variables, {}, assignments) + + +def test_no_overlap(): + constrainer = SomeNotInSetConstraint(set('zy')) + v1, v2 = variables = [Variable('v1'), Variable('v2')] + assignments = {v1: 'a', v2: 'b'} + + assert constrainer(variables, {}, assignments) + + +def test_some_overlap(): + constrainer = SomeNotInSetConstraint(set('b')) + v1, v2 = variables = [Variable('v1'), Variable('v2')] + assignments = {v1: 'a', v2: 'b'} + + assert constrainer(variables, {}, assignments) + + +def test_too_much_overlap(): + constrainer = SomeNotInSetConstraint(set('ab')) + v1, v2 = variables = [Variable('v1'), Variable('v2')] + assignments = {v1: 'a', v2: 'b'} + + assert not constrainer(variables, {}, assignments) + + +def test_exact(): + constrainer = SomeNotInSetConstraint(set('abc'), n=2, exact=True) + v1, v2, v3 = variables = [Variable('v1'), Variable('v2'), Variable('v3')] + + assignments = {v1: 'a', v2: 'y', v3: 'z'} + assert constrainer(variables, {}, assignments) + + assignments = {v1: 'a', v2: 'y'} + assert constrainer(variables, {}, assignments) + + assignments = {v1: 'a', v2: 'b', v3: 'z'} + assert not constrainer(variables, {}, assignments) + + assignments = {v1: 'a', v2: 'b'} + assert not constrainer(variables, {}, assignments) + + assignments = {v1: 'a', v2: 'b', v3: 'c'} + assert not constrainer(variables, {}, assignments) + + assignments = {v1: 'x', v2: 'y', v3: 'z'} + assert not constrainer(variables, {}, assignments) + + +def test_forwardcheck(): + constrainer = SomeNotInSetConstraint(set('abc'), n=2) + v1, v2, v3 = variables = [Variable('v1'), Variable('v2'), Variable('v3')] + + domains = {v1: Domain(['a']), v2: Domain(['b', 'y']), + v3: Domain(['c', 'z'])} + assert constrainer(variables, domains, {v1: 'a'}) + assert ['a'] == list(domains[v1]) + assert ['b', 'y'] == list(domains[v2]) + assert ['c', 'z'] == list(domains[v3]) + + assert constrainer(variables, domains, {v1: 'a'}, True) + assert ['a'] == list(domains[v1]) + assert ['y'] == list(domains[v2]) + assert ['z'] == list(domains[v3]) + + +def test_forwardcheck_empty_domain(): + constrainer = SomeNotInSetConstraint(set('abc')) + v1, v2 = variables = [Variable('v1'), Variable('v2')] + + domains = {v1: Domain(['a']), v2: Domain(['b'])} + assert constrainer(variables, domains, {v1: 'a'}) + assert not constrainer(variables, domains, {v1: 'a'}, True) + + +def test_forwardcheck_exact(): + constrainer = SomeNotInSetConstraint(set('abc'), n=2, exact=True) + v1, v2, v3 = variables = [Variable('v1'), Variable('v2'), Variable('v3')] + assignments = {v1: 'a'} + + domains = {v1: Domain(['a', 'x']), v2: Domain(['b', 'y']), + v3: Domain(['c', 'z'])} + assert constrainer(variables, domains, assignments) + assert constrainer(variables, domains, assignments, True) + assert 'b' not in domains[v2] + assert 'y' in domains[v2] + assert 'c' not in domains[v3] + assert 'z' in domains[v3] + + domains = {v1: Domain(['a', 'x']), v2: Domain(['b', 'y']), + v3: Domain(['c'])} + assert constrainer(variables, domains, assignments) + assert not constrainer(variables, domains, assignments, True)