- Notifications
You must be signed in to change notification settings - Fork71
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 resultTrue>>> for solution in problem.getSolutionIter():... sorted(solution.items()) in resultTrueTrueTrue>>> for solution in problem.getSolutions():... sorted(solution.items()) in resultTrueTrueTrue"""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 = forwardcheckdef 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]))