def test_find_optimal_single_unary_constraint(): v1 = Variable('a', [0, 1, 2, 3, 4]) c1 = UnaryFunctionRelation('c1', v1, lambda x: abs(x - 2)) vals, cost = find_optimal(v1, {}, [c1], "min") assert vals == [2] assert cost == 0
def on_new_cycle(self, messages, cycle_id) -> Optional[List]: assignment = {self.variable.name: self.current_value} for sender, (message, t) in messages.items(): assignment[sender] = message.value self.logger.debug( f"Full neighbors assignment for cycle {self.cycle_count} : {assignment}" ) current_cost = assignment_cost(assignment, self.constraints) # Compute best local cost, based on current neighbors values: arg_min, min_cost = find_optimal(self.variable, assignment, self.constraints, self.mode) self.logger.debug( f"Evaluate cycle {self.cycle_count}: current cost {current_cost} - best cost {min_cost}" ) if current_cost - min_cost > 0 and 0.5 > random.random(): self.value_selection(arg_min[0]) self.logger.debug( f"Select new value {arg_min} for better cost {min_cost} ") else: self.logger.debug(f"Do not change value {self.current_value}") self.post_to_all_neighbors(DsaMessage(self.current_value))
def test_find_optimal_2_unary_constraints(): variable = Variable('a', [0, 1, 2, 3, 4]) c1 = UnaryFunctionRelation('c1', variable, lambda x: abs(x - 3)) c2 = UnaryFunctionRelation('c1', variable, lambda x: abs(x - 1) * 2) val, sum_costs = find_optimal(variable, {}, [c1, c2], "min") assert val == [1] assert sum_costs == 2
def test_find_optimal_one_binary_constraint(): v1 = Variable('v1', [0, 1, 2, 3, 4]) v2 = Variable('v2', [0, 1, 2, 3, 4]) @AsNAryFunctionRelation(v1, v2) def c1(v1_, v2_): return abs(v1_ - v2_) val, sum_costs = find_optimal(v1, {"v2": 1}, [c1], "min") assert val == [1] assert sum_costs == 0
def test_find_optimal_several_best_values(): # With this constraints definition, the value 0 and 3 returns the same # optimal cost of 0, find_best_values must return both values. v1 = Variable('v1', [0, 1, 2, 3, 4]) v2 = Variable('v2', [0, 1, 2, 3, 4]) @AsNAryFunctionRelation(v1, v2) def c1(v1_, v2_): return abs((v2_ - v1_) % 3) val, sum_costs = find_optimal(v1, {'v2': 3}, [c1], "min") assert val == [0, 3] assert sum_costs == 0
def evaluate_cycle(self): if len(self.current_cycle) == len(self.neighbors): self.logger.debug( "Full neighbors assignment for cycle %s : %s ", self.cycle_count, self.current_cycle, ) self.current_cycle[self.variable.name] = self.current_value assignment = self.current_cycle.copy() args_best, best_cost = find_optimal(self.variable, assignment, self.constraints, self.mode) current_cost = assignment_cost(self.current_cycle, self.constraints) delta = abs(current_cost - best_cost) self.logger.debug( f"Current cost {current_cost}, best cost {best_cost} " f"delta {delta}") if self.variant == "A": self.variant_a(delta, best_cost, args_best) elif self.variant == "B": self.variant_b(delta, best_cost, args_best) elif self.variant == "C": self.variant_c(delta, best_cost, args_best) self.new_cycle() self.current_cycle, self.next_cycle = self.next_cycle, {} # Check if this was the last cycle if self.stop_cycle and self.cycle_count >= self.stop_cycle: self.finished() self.stop() return self.post_to_all_neighbors(DsaMessage(self.current_value))
def value_phase(self, sender, value): if sender not in self._ancestors: raise ComputationException( f"Received at {self.name} value from {sender}, " f"which is not an ancestor: {self._ancestors}") # Init phase: select a value and send down the tree # as value messages are sent by parent and pseudo-parents, # they might arrive in several cycles and must be accumulated before we # can select our value. self._parents_values[sender] = value if len(self._parents_values) == len(self._ancestors): # Select our own value greedily. # For this, we only take into account the constraints with our ancestrors ancestors_constraints = [] for c in self._constraints: for v in c.scope_names: if v in self._ancestors: ancestors_constraints.append(c) break values, cost = find_optimal(self.variable, self._parents_values, ancestors_constraints, self._mode) self.value_selection(values[0]) self._upper_bound = cost # Send our value to our children. if not self.is_leaf: for child in self._descendants: self.post_msg(child, ValueMessage(self.current_value)) else: # At leafs, we initiate cost message (sent up) as we don't have to wait # for costs from our children. for ancestor in self._ancestors: self.post_msg(ancestor, CostMessage(cost))