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 get_next_assignment( variable: Variable, current_value: Optional[VarVal], constraints: List[Constraint], current_path: Path, upper_bound: Cost, mode: str, ): """ Find the first next value in `variable`'s domain that respects `upper_bound`. Parameters ---------- variable: variable The variable for which we want to select a value current_value: value or `None` The value currently assigned to `variable` constraints: The set of constraints `variable` is involved in current_path: Path a path assigning a value for each variable before `variable` upper_bound: float current bound for the path mode: str "min" or "max" Returns ------- """ candidates = get_value_candidates(variable, current_value) found = None for candidate in candidates: # Check if assigning candidate value to the variable would cause the global # cost to exceed the upper-bound. candidate_cost = 0 if not current_path: return candidate, 0 for var, val, elt_cost in current_path: var_constraints = constraints_for_variable(constraints, var) # This only works for binary constraints, we could extend it to n-ary constraints ass_cost = assignment_cost({ var: val, variable.name: candidate }, var_constraints) candidate_cost += ass_cost if mode == "min" and (candidate_cost >= upper_bound or ass_cost + elt_cost >= upper_bound): break # Try next value in domain. else: found = candidate, candidate_cost # Check for next elt in path. if mode == "max" and candidate_cost > upper_bound: found = candidate, candidate_cost if found: return found return None
def compute_best_value(self) -> Tuple[Any, float]: arg_min, min_cost = None, float('inf') for value in self.variable.domain: self.current_cycle[self.variable.name] = value cost = assignment_cost(self.current_cycle, self.constraints) if cost < min_cost: min_cost, arg_min = cost, value return arg_min, min_cost
def _find_best_offer( self, all_offers: List[Tuple[str, Dict]]) -> Tuple[List, float]: """ Find the offer that maximize the global gain of both partners in the given offers and for the given partner. Parameters ---------- all_offers: list a list of couples (offerer_name, offer) where offer is a dictionary of offers {(partner_val, my_val): partner_gain} Mgm2OfferMessage Returns ------- list: list of best offers (i.ee with the best gain) best_gain: gain for the best offers. """ bests, best_gain = [], 0 for partner, offers in all_offers: partial_asgt = self._neighbors_values.copy() current_partner = self._neighbor_var(partner) # Filter out the constraints linking those two variables to avoid # counting their cost twice. shared = find_dependent_relations(current_partner, self._constraints) concerned = [rel for rel in self._constraints if rel not in shared] for (val_p, my_offer_val), partner_local_gain in offers.items(): partial_asgt.update({ partner: val_p, self.variable.name: my_offer_val }) # Then we evaluate the agent constraint's for the offer # and add the partner's local gain. cost = assignment_cost(partial_asgt, concerned) global_gain = self.current_cost - cost + partner_local_gain if (global_gain > best_gain and self._mode == "min") or (global_gain < best_gain and self._mode == "max"): bests = [(val_p, my_offer_val, partner)] best_gain = global_gain elif global_gain == best_gain: bests.append((val_p, my_offer_val, partner)) return bests, best_gain
def tick(self): if self.is_paused: return # Check if we have a value for all our neighbors if len(self.current_assignment) == len(self.neighbors): if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( "Full neighbors assignment on periodic action %s : %s ", self.cycle_count, self.current_assignment, ) assignment = self.current_assignment.copy() args_best, best_cost = self.find_best_values(assignment) # if self.current_value is not None: assignment[self.variable.name] = self.current_value current_cost = assignment_cost(assignment, self.constraints) delta = abs(current_cost - best_cost) if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( f"Current value {self.current_value}, cost {current_cost}, " f"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) else: n = len(self.neighbors) c = len(self.current_assignment) print(f" {self.name} Still waiting for neighbors values {n-c} out of {n} ") if self.logger.isEnabledFor(logging.DEBUG): self.logger.debug( f"Still waiting for neighbors values {n-c} out of {n} " ) # In order to be more resilient to message loss, we send our value even # if it did not change. self.post_to_all_neighbors(ADsaMessage(self.current_value))
def find_best_values( self, assignment: Dict[Any, float]) -> Tuple[List[Any], float]: """ Find the best values for our variable, given the current assignment. Find the values from the domain of our variable that yield the best cost (min or max depending of mode) given the assignment known for our neighbors. Parameters ---------- assignment: The current assignment Returns ------- List[Any] A list of values from the domain of our variable float The cost achieved with these values. """ assignment = assignment.copy() arg_best, best_cost = None, float("inf") if self.mode == "max": arg_best, best_cost = None, -float("inf") for value in self.variable.domain: assignment[self.variable.name] = value cost = assignment_cost(assignment, self.constraints) # Take into account variable cost, if any cost += self.variable.cost_for_val(value) if cost == best_cost: arg_best.append(value) elif (self.mode == "min" and cost < best_cost) or (self.mode == "max" and cost > best_cost): best_cost, arg_best = cost, [value] return arg_best, best_cost
def evaluate_cycle(self): self.logger.debug('Full neighbors assignment for cycle %s : %s ', self.cycle_count, self.current_cycle) arg_min, min_cost = self.compute_best_value() self.current_cycle[self.variable.name] = self.current_value current_cost = assignment_cost(self.current_cycle, self.constraints) self.logger.debug("Evaluate cycle %s: current cost %s - best cost %s", self.cycle_count, current_cost, min_cost) if current_cost - min_cost > 0 and 0.5 > random.random(): self.value_selection(arg_min) self.logger.debug("Select new value %s for better cost %s ", self.cycle_count, min_cost) else: self.logger.debug("Do not change value %s ", self.current_value) self.new_cycle() self.current_cycle, self.next_cycle = self.next_cycle, {} self.post_to_all_neighbors(DsaMessage(self.current_value))
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 _compute_cost(self, **kwargs): return assignment_cost(kwargs, self._constraints)