Description
Hi!
A few possible improvements (overall I got over 100x speed improvement over your vanilla code).
a) mapping all variable names to integers (internally). This depends, but for me it gave up to 10x speedup.
b) Changing a few things in the solvers. I copy an example of what I did. It's rather simple optimization of taking some of the repeated code out of the loops. Unless I missed it there, there are no dependencies there. (I checked the correctness of the code). This gives around 4x speedup
c) Switching from dicts to lists inside the solvers (havent done that yet). Seems its gonna be possible in a few places. At the moment dictionary copy method in "yield assignments.copy()" takes bulk of the time.
Hope it helps!
class OptimizedSolver(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, lst):
forwardcheck = self._forwardcheck
assignments = {}
queue = []
while True:
# Mix the Degree and Minimum Remaing Values (MRV) heuristics
for variable in lst:
if variable not in assignments:
# Found unassigned variable
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 getSolutions(self, domains, constraints, vconstraints):
lst = [
(-len(vconstraints[variable]), len(domains[variable]), variable)
for variable in domains
]
lst.sort()
return list(self.getSolutionIter(domains, constraints, vconstraints, [c for a,b,c in lst]))