Example #1
0
 def _increase_cost(self, constraint: NAryMatrixRelation):
     """
     Increase the cost(s) of a constraint according to the given
     increase_mode
     :param constraint: a constraint as NAryMatrixRelation
     :return:
     """
     asgt = self._neighbors_values.copy()
     asgt[self.name] = self.current_value
     self.logger.debug("%s increases cost for %s", self.name, constraint)
     if self._increase_mode == "E":
         self._increase_modifier(constraint, asgt)
     elif self._increase_mode == "R":
         for val in self.variable.domain:
             asgt[self.name] = val
             self._increase_modifier(constraint, asgt)
     elif self._increase_mode == "C":
         # Creates all the assignments for the constraints, with the
         # agent variable set to its current value
         asgts = generate_assignment_as_dict(list(self._neighbors))
         for ass in asgts:
             ass[self.name] = self.current_value
             self._increase_modifier(constraint, ass)
     elif self._increase_mode == "T":
         # Creates all the assignments for the constraints
         asgts = generate_assignment_as_dict(constraint.dimensions)
         for ass in asgts:
             self._increase_modifier(constraint, ass)
Example #2
0
    def _compute_offers_to_send(self) -> Dict[Tuple[float, float], float]:
        """
        Computes all the coordinated moves with the partner (if exists).
        It also set the attribute best_unilateral_move, which corresponds to
        the best eval the agent can achieve if it moves alone and the list of
        values to achieve this eval

        Returns
        -------
        offers:
            a dictionary which keys are couples (my_value, my_partner_value)
            and which values are the gain realized by the offerer thanks to this
            coordinated change.

        """
        partial_asgt = self._neighbors_values.copy()
        offers = dict()

        for limited_asgt in generate_assignment_as_dict(
            [self.variable, self._partner]):
            partial_asgt.update(limited_asgt)
            cost = self._compute_cost(**partial_asgt)
            if self.logger.isEnabledFor(logging.DEBUG):
                self.logger.debug(
                    f"looking for offer : {partial_asgt} - cost {cost}"
                    f" current {self.current_cost} {self._mode}")

            if (self.current_cost > cost
                    and self._mode == "min") or (self.current_cost < cost
                                                 and self._mode == "max"):
                offers[(limited_asgt[self.name],
                        limited_asgt[self._partner.name])] = (
                            self.current_cost - cost)
        return offers
Example #3
0
def _yaml_constraints(constraints: Iterable[RelationProtocol]):
    constraints_dict = {}
    for r in constraints:
        if hasattr(r, "expression"):

            constraints_dict[r.name] = {
                "type": "intention",
                "function": r.expression
            }
        else:
            # fallback to extensional constraint
            variables = [v.name for v in r.dimensions]
            values = defaultdict(lambda: [])

            for assignment in generate_assignment_as_dict(r.dimensions):
                val = r(**assignment)
                ass_str = " ".join([str(assignment[var]) for var in variables])
                values[val].append(ass_str)

            for val in values:
                values[val] = " | ".join(values[val])
            values = dict(values)
            constraints_dict[r.name] = {
                "type": "extensional",
                "variables": variables,
                "values": values,
            }

    return yaml.dump({"constraints": constraints_dict},
                     default_flow_style=False)
Example #4
0
def _valid_assignments(constraint: Constraint, infinity_value):
    """
    Return a list of all valid assignments for the Constraint
    """
    valid_assignments = []
    for assignment in generate_assignment_as_dict(constraint.dimensions[:]):
        if abs(constraint(**assignment)) != infinity_value:
            valid_assignments.append(assignment)
    return valid_assignments
Example #5
0
    def _valid_assignments(self):
        """
        Populates a cache with all valid assignments for the factor
        managed by the algorithm.

        :return: a list of all assignments returning a non-infinite value
        """
        # Fixme: extract as a function
        # FIXME: does not take into account min / max
        if self._valid_assignments_cache is None:
            self._valid_assignments_cache = []
            all_vars = self.factor.dimensions[:]
            for assignment in generate_assignment_as_dict(all_vars):
                if self.factor(**assignment) != INFINITY:
                    self._valid_assignments_cache.append(assignment)
        return self._valid_assignments_cache
Example #6
0
    def __init__(self, variable, constraints, variant='B', proba_hard=0.7,
                 proba_soft=0.7, mode='min', comp_def=None):
        """

        :param variable a variable object for which this computation is
        responsible
        :param constraints: the list of constraints involving this variable
        :param variant: the variant of the DSA algorithm : 'A' for DSA-A,
        etc.. possible values avec 'A', 'B' and 'C'
        :param proba_hard : the probability to change the value in case of
        the number of violated hard constraints can be decreased
        :param proba_soft : the probability to change the value in case the
        cost on hard constraints can't be improved, but the cost on soft
        constraints can
        :param mode: optimization mode, 'min' for minimization and 'max' for
        maximization. Defaults to 'min'.

        """
        super().__init__(variable, comp_def)

        self.proba_hard = proba_hard
        self.proba_soft = proba_soft
        self.variant = variant
        self.mode = mode
        # some constraints might be unary, and our variable can have several
        # constraints involving the same variable
        self._neighbors = set([v.name for c in constraints
                               for v in c.dimensions if v != variable])
        self._neighbors_values = {}
        self._postponed_messages = list()

        self.hard_constraints = list()
        self.soft_constraints = list()
        self._violated_hard_cons = list()
        self._curr_dcop_cost = None
        self.__optimum_dict__ = {}
        # We do not use pydcop.dcop.relations.find_optimum() to distinguish
        # hard and soft constraints
        for c in constraints:
            hard = False
            variables = [v for v in c.dimensions if v != self._variable]
            boundary = None
            for asgt in generate_assignment_as_dict(variables):
                rel = c.slice(filter_assignment_dict(asgt, c.dimensions))
                for val in self._variable.domain:
                    rel_val = rel(val)
                    if boundary is None:
                        boundary = rel_val
                    elif self.mode == 'max' and rel_val > boundary:
                        boundary = rel_val
                    elif self.mode == 'min' and rel_val < boundary:
                        boundary = rel_val
                    if rel_val == float("inf") or rel_val == -float("inf"):
                        hard = True
            self.__optimum_dict__[c.name] = boundary
            if hard:
                self.hard_constraints.append(c)
            else:
                self.soft_constraints.append(c)

        if not self.hard_constraints:
            global INFINITY
            INFINITY = float("inf")
Example #7
0
def factor_costs_for_var(factor: Constraint, variable: Variable, recv_costs,
                         mode: str):
    """
    Computes the marginals to be send by a factor to a variable


    The content of this message is a table d -> mincost where
    * d is a value of the domain of the variable v
    * mincost is the minimum value of f when the variable v take the
      value d

    :param variable: the variable we want to send the costs to
    :return: a mapping { value => cost}
    where value is all the values from the domain of 'variable'
    costs is the cost when 'variable'  == 'value'

    Parameters
    ----------
    factor: Constraint
        the factor that will send these cost to `variable`
    variable: Variable

    recv_costs: Dict
        a dict containing the costs received from other variables
    mode: str
        "min" or "max"

    Returns
    -------
    Dict:
        a dict that associates a cost to each value in the domain of `variable`

    """
    # TODO: support passing list of valid assignment as param
    costs = {}
    other_vars = factor.dimensions[:]
    other_vars.remove(variable)
    for d in variable.domain:
        # for each value d in the domain of v, calculate min cost (a)
        # where a is any assignment where v = d
        # cost (a) = f(a) + sum( costvar())
        # where costvar is the cost received from our other variables

        mode_opt = INFINITY if mode == "min" else -INFINITY
        optimal_value = mode_opt

        for assignment in generate_assignment_as_dict(other_vars):
            assignment[variable.name] = d
            f_val = factor(**assignment)
            if f_val == INFINITY:
                continue

            sum_cost = 0
            # sum of the costs from all other variables
            for another_var, var_value in assignment.items():
                if another_var == variable.name:
                    continue
                if another_var in recv_costs:
                    if var_value not in recv_costs[another_var]:
                        # If there is no cost for this value, it means it
                        #  is infinite (as infinite cost are not included
                        # in messages) and we can stop adding costs.
                        sum_cost = mode_opt
                        break
                    sum_cost += recv_costs[another_var][var_value]
                else:
                    # we have not received yet costs from variable v
                    pass

            current_val = f_val + sum_cost
            if (optimal_value > current_val
                    and mode == "min") or (optimal_value < current_val
                                           and mode == "max"):

                optimal_value = current_val

        if optimal_value != mode_opt:
            costs[d] = optimal_value

    return costs
Example #8
0
def factor_costs_for_var(factor: Constraint, variable: Variable, recv_costs,
                         mode: str):
    """
    Computes the marginals to be send by a factor to a variable


    The content of this message is a table d -> mincost where
    * d is a value of the domain of the variable v
    * mincost is the minimum value of f when the variable v take the
      value d

    Parameters
    ----------
    factor: Constraint
        the factor that will send these cost to `variable`
    variable: Variable

    recv_costs: Dict
        a dict containing the costs received from other variables
    mode: str
        "min" or "max"

    Returns
    -------
    Dict:
        a dict that associates a cost to each value in the domain of `variable`

    """
    # TODO: support passing list of valid assignment as param
    costs = {}
    other_vars = factor.dimensions[:]
    other_vars.remove(variable)
    for d in variable.domain:
        # for each value d in the domain of v, calculate min cost (a)
        # where a is any assignment where v = d
        # cost (a) = f(a) + sum( costvar())
        # where costvar is the cost received from our other variables

        optimal_value = float("inf") if mode == "min" else -float("inf")

        for assignment in generate_assignment_as_dict(other_vars):
            assignment[variable.name] = d
            f_val = factor(**assignment)

            sum_cost = 0
            # sum of the costs from all other variables
            for another_var, var_value in assignment.items():
                if another_var == variable.name:
                    continue
                if another_var in recv_costs:
                    if var_value not in recv_costs[another_var]:
                        continue
                    sum_cost += recv_costs[another_var][var_value]
                else:
                    # we have not received yet costs from variable v
                    pass

            current_val = f_val + sum_cost
            if (optimal_value > current_val
                    and mode == "min") or (optimal_value < current_val
                                           and mode == "max"):

                optimal_value = current_val

        costs[d] = optimal_value

    return costs