From dbff3f5abfd30fe8ed649012698f7288813e6e79 Mon Sep 17 00:00:00 2001 From: Michael Hassid Date: Tue, 4 Jun 2019 13:45:15 +0300 Subject: [PATCH 1/6] added the class, need to do documentation --- constraint/__init__.py | 84 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/constraint/__init__.py b/constraint/__init__.py index 274270f..73f0265 100755 --- a/constraint/__init__.py +++ b/constraint/__init__.py @@ -50,6 +50,7 @@ "BacktrackingSolver", "RecursiveBacktrackingSolver", "MinConflictsSolver", + "LeastConflictsSolver", "Constraint", "FunctionConstraint", "AllDifferentConstraint", @@ -718,6 +719,89 @@ def getSolution(self, domains, constraints, vconstraints): return None + + +class LeastConflictsSolver(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 = {} + best_assign = {} + best_conflicted = float('inf') + # Initial assignment + for variable in domains: + assignments[variable] = random.choice(domains[variable]) + for _ in xrange(self._steps): + conflicted = 0 + 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): + conflicted +=1 + if conflicted == 0: + return assignments + if best_conflicted > conflicted: + best_assign = assignments + best_conflicted = conflicted + # 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 best_assign + + # ---------------------------------------------------------------------- # Variables # ---------------------------------------------------------------------- From 085a04fb7545990924ff28dc2d51dfda82d37c14 Mon Sep 17 00:00:00 2001 From: Michael Hassid Date: Tue, 4 Jun 2019 15:37:48 +0300 Subject: [PATCH 2/6] added documentation and tests --- README.rst | 1 + constraint/__init__.py | 24 ++++++++++++----------- tests/test_solvers.py | 43 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index 564e46a..01b0a97 100644 --- a/README.rst +++ b/README.rst @@ -99,6 +99,7 @@ The following solvers are available: - Backtracking solver - Recursive backtracking solver - Minimum conflicts solver +- Least conflicts solver (returns a soltion with minimum conflicts). .. role:: python(code) diff --git a/constraint/__init__.py b/constraint/__init__.py index 73f0265..0aa61a7 100755 --- a/constraint/__init__.py +++ b/constraint/__init__.py @@ -723,17 +723,21 @@ def getSolution(self, domains, constraints, vconstraints): class LeastConflictsSolver(Solver): """ - Problem solver based on the minimum conflicts theory + Problem solver based on the minimum conflicts theory. + With this solver - you will always get an assignment - + the one with the minimum coflicts that the algorithm found. Examples: - >>> result = [[('a', 1), ('b', 2)], - ... [('a', 1), ('b', 3)], - ... [('a', 2), ('b', 3)]] + >>> result = [[('a', 1), ('b', 2), ('c', 1)], + ... [('a', 2), ('b', 1), ('c', 1)]] - >>> problem = Problem(MinConflictsSolver()) - >>> problem.addVariables(["a", "b"], [1, 2, 3]) - >>> problem.addConstraint(lambda a, b: b > a, ["a", "b"]) + >>> problem = Problem(LeastConflictsSolver()) + >>> problem.addVariables(["a", "b"], [1, 2]) + >>> problem.addVariable("c", [1]) + >>> problem.addConstraint(lambda a, b: b != a, ["a", "b"]) + >>> problem.addConstraint(lambda a, b: b != a, ["a", "c"]) + >>> problem.addConstraint(lambda a, b: b != a, ["b", "c"]) >>> solution = problem.getSolution() >>> sorted(solution.items()) in result @@ -742,12 +746,12 @@ class LeastConflictsSolver(Solver): >>> problem.getSolutions() Traceback (most recent call last): ... - NotImplementedError: MinConflictsSolver provides only a single solution + NotImplementedError: LeastConflictsSolver provides only a single solution >>> problem.getSolutionIter() Traceback (most recent call last): ... - NotImplementedError: MinConflictsSolver doesn't provide iteration + NotImplementedError: LeastConflictsSolver doesn't provide iteration """ def __init__(self, steps=1000): @@ -797,8 +801,6 @@ def getSolution(self, domains, constraints, vconstraints): # Pick a random one from these values. assignments[variable] = random.choice(minvalues) conflicted = True - if not conflicted: - return assignments return best_assign diff --git a/tests/test_solvers.py b/tests/test_solvers.py index 3fac3c9..a5096e8 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -1,5 +1,5 @@ -from constraint import Problem, MinConflictsSolver - +from constraint import Problem, MinConflictsSolver, LeastConflictsSolver +import itertools def test_min_conflicts_solver(): problem = Problem(MinConflictsSolver()) @@ -15,3 +15,42 @@ def test_min_conflicts_solver(): ] assert solution in possible_solutions + + +def test_least_conflicts_solver(): + problem = Problem(LeastConflictsSolver()) + problem.addVariable("x", [0,1,2]) + problem.addVariable("y", [1,2]) + problem.addVariable("z", [0,1]) + problem.addVariable("w", [2]) + for first, sec in itertools.combinations('xyzw', 2) : + problem.addConstraint(lambda a, b: b != a, [first, sec]) + + + solution = problem.getSolution() + + # possible_solutions = + # {"x": 0, "y": 0}, + # {"x": 0, "y": 1}, + # {"x": 1, "y": 0}, + # {"x": 1, "y": 1}, + # ] + + print(solution) + + problem = Problem(LeastConflictsSolver()) + + + result = [[('a', 1), ('b', 2), ('c', 1)], [('a', 2), ('b', 1), ('c', 1)]] + + problem.addVariables(["a", "b"], [1, 2]) + problem.addVariable("c", [1]) + problem.addConstraint(lambda a, b: b != a, ["a", "b"]) + problem.addConstraint(lambda a, b: b != a, ["a", "c"]) + problem.addConstraint(lambda a, b: b != a, ["b", "c"]) + + solution = problem.getSolution() + print(solution) + print(sorted(solution.items()) in result) + +test_least_conflicts_solver() \ No newline at end of file From 8ea91ea08b190d9e8cd24ee735d43c43b1296e9d Mon Sep 17 00:00:00 2001 From: Michael Hassid Date: Tue, 4 Jun 2019 15:40:52 +0300 Subject: [PATCH 3/6] small changes --- tests/test_solvers.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/tests/test_solvers.py b/tests/test_solvers.py index a5096e8..bb06ed1 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -18,25 +18,14 @@ def test_min_conflicts_solver(): def test_least_conflicts_solver(): - problem = Problem(LeastConflictsSolver()) - problem.addVariable("x", [0,1,2]) - problem.addVariable("y", [1,2]) - problem.addVariable("z", [0,1]) - problem.addVariable("w", [2]) - for first, sec in itertools.combinations('xyzw', 2) : - problem.addConstraint(lambda a, b: b != a, [first, sec]) - - - solution = problem.getSolution() - - # possible_solutions = - # {"x": 0, "y": 0}, - # {"x": 0, "y": 1}, - # {"x": 1, "y": 0}, - # {"x": 1, "y": 1}, - # ] - - print(solution) + # another test: + # problem = Problem(LeastConflictsSolver()) + # problem.addVariable("x", [0,1,2]) + # problem.addVariable("y", [1,2]) + # problem.addVariable("z", [0,1]) + # problem.addVariable("w", [2]) + # for first, sec in itertools.combinations('xyzw', 2) : + # problem.addConstraint(lambda a, b: b != a, [first, sec]) problem = Problem(LeastConflictsSolver()) @@ -50,7 +39,6 @@ def test_least_conflicts_solver(): problem.addConstraint(lambda a, b: b != a, ["b", "c"]) solution = problem.getSolution() - print(solution) - print(sorted(solution.items()) in result) + assert sorted(solution.items()) in result test_least_conflicts_solver() \ No newline at end of file From 5e1e81aa322b9593cf7dd40561828c8ac8ead5d3 Mon Sep 17 00:00:00 2001 From: Michael Hassid Date: Tue, 4 Jun 2019 15:43:33 +0300 Subject: [PATCH 4/6] small changes --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 01b0a97..5248c07 100644 --- a/README.rst +++ b/README.rst @@ -99,7 +99,7 @@ The following solvers are available: - Backtracking solver - Recursive backtracking solver - Minimum conflicts solver -- Least conflicts solver (returns a soltion with minimum conflicts). +- Least conflicts solver (returns a solution with minimum conflicts). .. role:: python(code) From 1e830bf116a92c4d385ce84803c7cf4fd1e10517 Mon Sep 17 00:00:00 2001 From: Michael Hassid Date: Tue, 11 Jun 2019 15:21:13 +0300 Subject: [PATCH 5/6] importanat fix --- constraint/__init__.py | 54 +++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/constraint/__init__.py b/constraint/__init__.py index 0aa61a7..7295160 100755 --- a/constraint/__init__.py +++ b/constraint/__init__.py @@ -719,8 +719,6 @@ def getSolution(self, domains, constraints, vconstraints): return None - - class LeastConflictsSolver(Solver): """ Problem solver based on the minimum conflicts theory. @@ -773,37 +771,39 @@ def getSolution(self, domains, constraints, vconstraints): conflicted = 0 lst = list(domains.keys()) random.shuffle(lst) + conflicted_var = None for variable in lst: # Check if variable is not in conflict for constraint, variables in vconstraints[variable]: if not constraint(variables, domains, assignments): - conflicted +=1 - if conflicted == 0: - return assignments - if best_conflicted > conflicted: - best_assign = assignments - best_conflicted = conflicted - # 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 + conflicted += 1 + # Variable has conflicts. Save it: + if conflicted > 0 and conflicted_var is None: + conflicted_var = variable + if conflicted == 0: + return assignments + if best_conflicted > conflicted: + best_assign = assignments + best_conflicted = conflicted + # Find values with less conflicts. + mincount = len(vconstraints[conflicted_var]) + minvalues = [] + for value in domains[conflicted_var]: + assignments[conflicted_var] = value + count = 0 + for constraint, variables in vconstraints[conflicted_var]: + 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[conflicted_var] = random.choice(minvalues) return best_assign - # ---------------------------------------------------------------------- # Variables # ---------------------------------------------------------------------- From 9ea13ca11119a85f9fb0435bee9b34b094c46fff Mon Sep 17 00:00:00 2001 From: Michael Hassid Date: Tue, 18 Jun 2019 15:33:14 +0300 Subject: [PATCH 6/6] update unitests --- constraint/__init__.py | 2 +- tests/test_solvers.py | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/constraint/__init__.py b/constraint/__init__.py index 7295160..c8d856b 100755 --- a/constraint/__init__.py +++ b/constraint/__init__.py @@ -803,7 +803,7 @@ def getSolution(self, domains, constraints, vconstraints): # Pick a random one from these values. assignments[conflicted_var] = random.choice(minvalues) return best_assign - + # ---------------------------------------------------------------------- # Variables # ---------------------------------------------------------------------- diff --git a/tests/test_solvers.py b/tests/test_solvers.py index bb06ed1..aa6ae6d 100644 --- a/tests/test_solvers.py +++ b/tests/test_solvers.py @@ -18,19 +18,13 @@ def test_min_conflicts_solver(): def test_least_conflicts_solver(): - # another test: - # problem = Problem(LeastConflictsSolver()) - # problem.addVariable("x", [0,1,2]) - # problem.addVariable("y", [1,2]) - # problem.addVariable("z", [0,1]) - # problem.addVariable("w", [2]) - # for first, sec in itertools.combinations('xyzw', 2) : - # problem.addConstraint(lambda a, b: b != a, [first, sec]) + # another test for LeastConflictsSolver problem = Problem(LeastConflictsSolver()) - result = [[('a', 1), ('b', 2), ('c', 1)], [('a', 2), ('b', 1), ('c', 1)]] + result = [[('a', 1), ('b', 2), ('c', 1)], [('a', 2), ('b', 1), ('c', 1)] + , [('a', 2), ('b', 2), ('c', 1)]] problem.addVariables(["a", "b"], [1, 2]) problem.addVariable("c", [1])