Example #1
0
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]
Example #2
0
    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))
Example #3
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
Example #4
0
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]
Example #5
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 [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