def select_value(variable: Variable, costs: Dict, mode: str) -> Tuple[Any, float]: """ select the value for `variable` with the best cost / reward (depending on `mode`) Returns ------- a Tuple containing the selected value and the corresponding cost for this computation. """ # If we have received costs from all our factor, we can select a # value from our domain. d_costs = {d: variable.cost_for_val(d) for d in variable.domain} for d in variable.domain: for f_costs in costs.values(): if d not in f_costs: # As infinite costs are not included in messages, # if there is not cost for this value it means the costs # is infinite and we can stop adding other costs. d_costs[d] = INFINITY if mode == "min" else -INFINITY break d_costs[d] += f_costs[d] from operator import itemgetter if mode == "min": optimal_d = min(d_costs.items(), key=itemgetter(1)) else: optimal_d = max(d_costs.items(), key=itemgetter(1)) return optimal_d[0], optimal_d[1]
def __init__(self, variable: Variable, factor_names: List[str], msg_sender=None, comp_def: ComputationDef=None): """ :param variable: variable object :param factor_names: a list containing the names of the factors that depend on the variable managed by this algorithm :param msg_sender: the object that will be used to send messages to neighbors, it must have a post_msg(sender, target_name, name) method. """ super().__init__(variable, comp_def) self._msg_handlers['max_sum'] = self._on_cost_msg # self._v = variable.clone() # Add noise to the variable, on top of cost if needed if hasattr(variable, 'cost_for_val'): self._v = VariableNoisyCostFunc( variable.name, variable.domain, cost_func=lambda x: variable.cost_for_val(x), initial_value= variable.initial_value) else: self._v = VariableNoisyCostFunc( variable.name, variable.domain, cost_func=lambda x: 0, initial_value= variable.initial_value) self.var_with_cost = True # the currently selected value, will evolve when the algorithm is # still running. # if self._v.initial_value: # self.value_selection(self._v.initial_value, None) # # elif self.var_with_cost: # current_cost, current_value =\ # min(((self._v.cost_for_val(dv), dv) for dv in self._v.domain )) # self.value_selection(current_value, current_cost) # The list of factors (names) this variables is linked with self._factors = factor_names # The object used to send messages to factor self._msg_sender = msg_sender # costs : this dict is used to store, for each value of the domain, # the associated cost sent by each factor this variable is involved # with. { factor : {domain value : cost }} self._costs = {} self.logger = logging.getLogger('pydcop.maxsum.' + variable.name) self.cycle_logger = logging.getLogger('cycle') self._is_stable = False self._prev_messages = defaultdict(lambda: (None, 0))
def costs_for_factor(variable: Variable, factor: FactorName, factors: List[Constraint], costs: Dict) -> Dict[VarVal, Cost]: """ Produce the message that must be sent to factor f. The content if this message is a d -> cost table, where * d is a value from the domain * cost is the sum of the costs received from all other factors except f for this value d for the domain. Parameters ---------- variable: Variable the variable sending the message factor: str the name of the factor the message will be sent to factors: list of Constraints the constraints this variables depends on costs: dict the accumulated costs received by the variable from all factors Returns ------- Dict: a dict containing a cost for each value in the domain of the variable """ # If our variable has integrated costs, add them msg_costs = {d: variable.cost_for_val(d) for d in variable.domain} sum_cost = 0 for d in variable.domain: for f in factors: if f == factor or f not in costs: continue # [f for f in factors if f != factor and f in costs]: f_costs = costs[f] if d not in f_costs: continue c = f_costs[d] sum_cost += c msg_costs[d] += c # Experimentally, when we do not normalize costs the algorithm takes # more cycles to stabilize # return {d: c for d, c in msg_costs.items() } # Normalize costs with the average cost, to avoid exploding costs avg_cost = sum_cost / len(msg_costs) normalized_msg_costs = {d: c - avg_cost for d, c in msg_costs.items()} return normalized_msg_costs
def select_value(variable: Variable, costs: Dict[str, Dict], mode: str) -> Tuple[Any, float]: """ Select the value for `variable` with the best cost / reward (depending on `mode`) Parameters ---------- variable: Variable the variable for which we need to select a value costs: Dict a dict { factorname : { value : costs}} representing the cost messages received from factors mode: str min or max Returns ------- Tuple: a Tuple containing the selected value and the corresponding cost for this computation. """ # Select a value from the domain, based on the variable cost and # the costs received from neighbor factors d_costs = {d: variable.cost_for_val(d) for d in variable.domain} for d in variable.domain: for f_costs in costs.values(): d_costs[d] += f_costs[d] from operator import itemgetter # print(f" ### On selecting value for {variable.name} : {d_costs}") if mode == "min": optimal_d = min(d_costs.items(), key=itemgetter(1)) else: optimal_d = max(d_costs.items(), key=itemgetter(1)) return optimal_d[0], optimal_d[1]
def costs_for_factor(variable: Variable, factor: FactorName, factors: List[Constraint], costs: Dict) -> Dict[VarVal, Cost]: """ Produce the message that must be sent to factor f. The content if this message is a d -> cost table, where * d is a value from the domain * cost is the sum of the costs received from all other factors except f for this value d for the domain. Parameters ---------- variable: Variable the variable sending the message factor: str the name of the factor the message will be sent to factors: list of Constraints the constraints this variables depends on costs: dict the accumulated costs received by the variable from all factors Returns ------- Dict: a dict containing a cost for each value in the domain of the variable """ # If our variable has integrated costs, add them msg_costs = {d: variable.cost_for_val(d) for d in variable.domain} sum_cost = 0 for d in variable.domain: for f in [f for f in factors if f != factor and f in costs]: f_costs = costs[f] if d not in f_costs: msg_costs[d] = INFINITY break c = f_costs[d] sum_cost += c msg_costs[d] += c # Experimentally, when we do not normalize costs the algorithm takes # more cycles to stabilize # return {d: c for d, c in msg_costs.items() if c != INFINITY} # Normalize costs with the average cost, to avoid exploding costs avg_cost = sum_cost / len(msg_costs) normalized_msg_costs = { d: c - avg_cost for d, c in msg_costs.items() if c != INFINITY } msg_costs = normalized_msg_costs # FIXME: restore damping support # prev_costs, count = self._prev_messages[factor] # damped_costs = {} # if prev_costs is not None: # for d, c in msg_costs.items(): # damped_costs[d] = self.damping * prev_costs[d] + (1 - self.damping) * c # self.logger.warning("damping : replace %s with %s", msg_costs, damped_costs) # msg_costs = damped_costs return msg_costs