def test_variable_add_value(): variable = base.Variable() domain = "abcdefghijklmn" for c in domain: variable.add_value(c) assert len(domain) == variable.domain_size() verify_domain_consistency(variable, domain)
def test_variable_domain_iteration(): domain = list(range(10)) variable = base.Variable(domain) assert len(domain) == variable.domain_size() domain_of_variable = [] for value in variable: domain_of_variable.append(value) assert domain == domain_of_variable
def test_variable_pruned_index_iteration(): domain = list(range(10)) variable = base.Variable(domain) variable.prune_at_index(5) assert len(domain) - 1 == variable.domain_size() domain_of_variable = [] for value in variable: domain_of_variable.append(value) assert [d for d in domain if d != 5] == domain_of_variable variable.prune_at_index(5, unprune=True) assert len(domain) == variable.domain_size()
def test_nested_iteration(): # should go through all value combinations domain = list(range(10)) variable = base.Variable(domain) value_combos = set() for v in variable: for vv in variable: value_combos.add((v, vv)) should_produce = set(itertools.product(domain, domain)) assert value_combos == should_produce
def solve(size, prop=propagator.ForwardCheck()): rows = list(range(size)) cols = [base.Variable(rows) for _ in range(size)] csp = base.CSP(cols) for i in range(len(cols)): for j in range(i + 1, len(cols)): # can't be on same row csp.add_constraint(base.DifferentConstraint(cols[i], cols[j])) # upward diagonal (have to use default variables to bind at definition time) csp.add_constraint(base.FunctionConstraint([cols[i], cols[j]], lambda a, b, i=i, j=j: a != b + (j - i))) # downward diagonal csp.add_constraint(base.FunctionConstraint([cols[i], cols[j]], lambda a, b, i=i, j=j: a != b - (j - i))) bt = search.BacktrackSearch(csp, prop=prop) return bt.search()
def csp(nodes: typing.List[Node]): def select_next_var(csp): min_domain = math.inf chosen_i = None vs = csp.variables() chosen = [] for i in range(len(vs)): v = vs[i] n = nodes[i] if v.is_assigned(): continue # break ties by choosing variable corresponding to node with highest degree if v.domain_size() < min_domain or ( v.domain_size() == min_domain and n.degree() > nodes[chosen_i].degree()): chosen_i = i min_domain = v.domain_size() chosen.append(chosen_i) # randomly choose from the top of the ones chosen if chosen_i is None: return None, None # choose from the best 3 chosen_i = random.choice(chosen[-3:]) return vs[chosen_i], chosen_i def select_next_val(csp: base.CSP, var_i): node = nodes[var_i] vars = csp.variables() # we only need to check our neighbouring colours + 1 neighbouring_colours = set(vars[n.id].assigned_value() for n in node.neighbours) c = 0 while c in neighbouring_colours: c += 1 yield c # can use greedy algorithm as the baseline (won't need more colours than it) max_colours, _, assignments = greedy(nodes) # TODO select timesouts based on number of nodes and connectedness (problem hardness) # how much time to spend on everything single_search_timeout = 2000 # how much time to spend on a single colour step down (increase to allow more restarts) colour_timeout = single_search_timeout * 5 # how much time to spend on trying to reduce additional colours (increase to be more ambitious) total_timeout = colour_timeout * 5 total_start = time.time() best_colours = max_colours # try to decrease the number of colours for num_colours in range(max_colours - 1, 0, -1): # create csp problem vars = [] for _ in nodes: vars.append(base.Variable(range(num_colours))) csp = base.CSP(vars) for n in nodes: for neighbour in n.neighbours: # avoid adding duplicate constraints by having all lower id != higher id if n.id < neighbour.id: csp.add_constraint( base.DifferentConstraint(vars[n.id], vars[neighbour.id])) # try to solve (assume last solution was the best if this time we can't solve for it) bt = search.BacktrackSearch(csp, select_next_variable=select_next_var) # use randomization and restarts to try to improve results # each search will have a timeout searcher = timeout.timeout(single_search_timeout)(bt.search) solution = None # in addition to total timeout start_time = time.time() while True: current_time = time.time() total_elapsed = current_time - total_start if total_elapsed > total_timeout: logger.info("Total timeout {} seconds".format(total_elapsed)) break elapsed = current_time - start_time if elapsed > colour_timeout: logger.info("Colour timeout {} seconds".format(elapsed)) break try: solution = searcher() logger.info("Search took {} seconds".format(time.time() - current_time)) if not solution: logger.info( "Could not exhaustively find a solution for {} colours" .format(num_colours)) break except TimeoutError as e: # retry continue except Exception as e: elapsed = time.time() - current_time if abs(elapsed - single_search_timeout) > 1: logger.info( "Exception after searching {} seconds".format(elapsed)) raise e continue if solution: break if solution: logger.info("Solution for {} colours found in {} seconds".format( num_colours, time.time() - start_time)) best_colours = num_colours assignments = solution else: break return best_colours, True, assignments
def test_variable_enumerated_iteration(): domain = list(range(9, 0, -1)) variable = base.Variable(domain) verify_domain_consistency(variable, domain)