Пример #1
0
    def global_metrics(self, current_status, t):

        if t is None:
            t = perf_counter()

        # Current global cost
        agent_values = self._agent_cycle_values[self._current_cycle]

        assignment = {k: agent_values[k][0] for k in agent_values
                      if agent_values[k]}
        # only keep dcop variable to compute the solution cost
        # it might other contain variables used for reparation
        dcop_assignment = filter_assignment_dict(
            assignment, self._dcop.variables.values())
        try:
            violation, cost = self._dcop.solution_cost(dcop_assignment,
                                                       self.infinity)
        except ValueError as ve:
            var_names = set(self._dcop.variables)
            ass_names = set(assignment)
            self.logger.debug(
                'Cannot compute cost for cycle %s, incomplete assignment: '
                'missing var %s in  %s', self._current_cycle,
                (var_names - ass_names), assignment)
            self.logger.debug(ve)
            cost, violation = None, None

        # msg stats and activity ratio
        msg_count, msg_size = 0, 0
        agt_cycles = []
        for agt in self._agt_cycle_metrics[self._current_cycle]:
            agt_metrics = self._agt_cycle_metrics[self._current_cycle][agt]
            try:
                msg_count += sum( agt_metrics['count_ext_msg'][v]
                                  for v in agt_metrics['count_ext_msg'])
                msg_size += sum(agt_metrics['size_ext_msg'][v]
                                for v in agt_metrics['size_ext_msg'])
                agt_cycles += [agt_metrics['cycles'][v]
                                    for v in agt_metrics['cycles']]
            except KeyError:
                self.logger.warning(
                    'Incomplete metrics for computation %s : %s ',
                    agt, agt_metrics)
        max_cycle = max(agt_cycles, default=0)

        total_time = t - self.start_time if self.start_time is not None else 0

        global_metrics = {
            'status': current_status,
            'assignment': assignment,
            'cost':  cost,
            'violation':  violation,
            'time': total_time,
            'msg_count': msg_count,
            'msg_size': msg_size,
            'cycle': max_cycle,
            'agt_metrics': self._agt_cycle_metrics[self._current_cycle]
        }

        return global_metrics
Пример #2
0
    def _compute_best_value(self):
        """
        Compute the best evaluation the variable can have wrt the current
        values of neighbors.

        :return: (list of values the variable that lead to the best
        evaluation, best evaluation)

        """
        reduced_cs = []
        concerned_vars = set()

        for c in self.utilities:
            asgt = filter_assignment_dict(self._neighbors_values, c.dimensions)
            reduced_cs.append(c.slice(asgt))
            concerned_vars.update(c.dimensions)
        var_val, rel_val = find_arg_optimal(
            self.variable,
            lambda x: functools.reduce(operator.add,
                                       [f(x) for f in reduced_cs]),
            self._mode,
        )
        # Add the cost for each variable value if any
        for var in concerned_vars:
            if var.name == self.name:
                rel_val += var.cost_for_val(self.current_value)
            else:
                rel_val += var.cost_for_val(self._neighbors_values[var.name])

        return var_val, rel_val
Пример #3
0
    def _handle_ok_message(self, variable_name, recv_msg):

        self._neighbors_values[variable_name] = recv_msg.value
        self.logger.info('%s received variable value %s from %s',
                         self.variable.name, recv_msg, variable_name)
        # if we have a value for all neighbors, compute our best value for
        # conflict reduction
        if len(self._neighbors_values) == len(self._neighbors):
            self.logger.info('%s received OK values from all neighbors : %s',
                             self.name, self._neighbors_values)
            # Replace all variables except its own variable with the values
            # received from neighbors
            reduced_cs = []
            for c in self.constraints:
                asgt = filter_assignment_dict(self._neighbors_values,
                                              c.dimensions)
                reduced_cs.append(c.slice(asgt))

            self.__cost__, _ = self.compute_eval_value(self.current_value,
                                                       reduced_cs)
            # Compute and send best improvement to neighbors
            self.improve(reduced_cs)

            self._go_to_wait_improve_mode()
        else:
            # Still waiting for other neighbors
            self.logger.info(
                '%s waiting for OK values from other neighbors (got %s) but '
                'neighbors are %s', self.name, self._neighbors_values,
                self.neighbors)
Пример #4
0
 def exists_violated_soft_constraint(self) -> bool:
     """
     Tells if there is a violated soft constraint regarding the current
     assignment
     :return: a boolean
     """
     for c in self.soft_constraints:
         asgt = self._neighbors_values.copy()
         asgt[self.name] = self.current_value
         const = c(filter_assignment_dict(asgt, c.dimensions))
         if const != self.__optimum_dict__[c.name]:
             return True
     return False
Пример #5
0
 def exists_violated_constraint(self) -> bool:
     """
     Tells if there is a violated soft constraint regarding the current
     assignment
     :return: a boolean
     """
     assignment = self.current_assignment.copy()
     assignment[self.variable.name] = self.current_value
     for c in self.constraints:
         const = c(**filter_assignment_dict(assignment, c.dimensions))
         if const != self.best_constraints_costs[c.name]:
             return True
     return False
Пример #6
0
    def _compute_dcop_cost(self, assignment, soft_cons=None, hard_cons=None) \
            -> Tuple[float, List[RelationProtocol]]:
        """
        Compute the cost for the given assignment, and the list of violated
        hard constraints. The cost computed does not include the infinite
        costs of the violated constraints.
        :param assignment: A full assignement for the relation
        :param soft_cons: a list of soft constraints. Default to the
        computation's soft constraints
        :param hard_cons: a list of hard constraints. Default to the
        computation's hard constraints
        :return: a couple: dcop_cost, list of violated constraints
        """
        softs = self.soft_constraints if soft_cons is None else soft_cons
        hards = self.hard_constraints if hard_cons is None else hard_cons
        # Cost for constraints:
        cost = functools.reduce(operator.add, [f(**filter_assignment_dict(
            assignment, f.dimensions)) for f in softs], 0)
        # Cost for variable, if any:
        concerned_vars = set(v for c in softs for v in c.dimensions)
        concerned_vars.update(v for c in hards for v in c.dimensions)
        for v in concerned_vars:
            if hasattr(v, 'cost_for_val'):
                cost += v.cost_for_val(assignment[v.name])

        hard_violated = list()
        for f in hards:
            c_cost = f(**filter_assignment_dict(assignment, f.dimensions))
            if c_cost == INFINITY:
                hard_violated.append(f)
                # We do not set the cost to infinity yet, so that we can
                # favorize solutions with the lowest cost (without counting
                # the violated hard constraints)
            else:
                cost += c_cost

        return cost, hard_violated
Пример #7
0
def solution_cost(relations, variables, assignment, infinity):
    """
    Return the cost of the solution given by the assignment.

    Raises a Value error if the assignment is not a full assignment with a
    value for all variables

    :param relations: a list a relations giving costs
    :param variables: a list a variables objects, only used if there are
    variable with integrated costs
    :param assignment: a dict var_name => value
    :param infinity: A float representing the value used to represent the
    infinity
    """
    cost_hard, cost_soft = 0, 0
    if len(variables) != len(assignment):
        raise ValueError('Cannot compute solution cost : incomplete '
                         'assignment, missing values for vars {}'.format(
                             set(variables) - set(assignment)))

    for r in relations:
        # values = filter_assignment_dict(assignment, r.dimensions)
        # values = {k: v for k, v in values.items() if v is not None}
        # # If we do not yet have a value for all variables, we ignore this
        # # relation in the cost.
        # if len(values) != len(r.dimensions):
        #     raise ValueError('Incomplete assignment')
        # else:
        try:
            r_cost = r(**filter_assignment_dict(assignment, r.dimensions))
        except NameError as ne:
            raise ValueError('Cannot compute solution cost : incomplete '
                             'assignment ' + str(ne))
        # logging.debug('Cost for relation %s : %s ', r.name, r_cost)
        if r_cost != infinity:
            cost_soft += r_cost
        else:
            cost_hard += 1

    for v in variables:

        if v.name in assignment and \
                assignment[v.name] is not None:
            cost_for_val = v.cost_for_val(assignment[v.name])
            if cost_for_val != infinity:
                cost_soft += cost_for_val
            else:
                cost_hard += 1
    return cost_hard, cost_soft
Пример #8
0
    def _is_violated(self, rel: Tuple[NAryMatrixRelation, float, float],
                     val) -> bool:
        """
        Determine if a constraint is violated according to the chosen violation
        mode.
        :param rel: A tuple (NAryMatrixRelation, min_val of the matrix,
        max_val of the matrix)
        :param val: the value of the agent variable to evaluate the violation
        :return: True (resp. False) if the constraint is (rep. not) violated
        """
        m, min_val, max_val = rel
        # Keep only the assignment of variables present in the constraint
        global_asgt = self._neighbors_values.copy()
        global_asgt[self.name] = val
        tmp_assignment = filter_assignment_dict(global_asgt, m.dimensions)

        if self._violation_mode == "NZ":
            return m.get_value_for_assignment(tmp_assignment) != 0
        elif self._violation_mode == "NM":
            return m.get_value_for_assignment(tmp_assignment) != min_val
        else:  # self._violation_mode == 'MX'
            return m.get_value_for_assignment(tmp_assignment) == max_val
Пример #9
0
    def _eff_cost(self, rel: NAryMatrixRelation, val) -> float:
        """
        Compute the effective cost of a constraint with combining its base
        cost with the associated modifier value (i.e. the weight of the
        constraint at the current step)
        :param rel: a constraint given as NAryMatrixRelation.
        :param val: the value of the agent's variablefor which to compute the
        _eff_cost
        :return: the effective cost of the constraint for the current
        assignment.
        """
        # Keep only the variables present in the relation rel
        global_asgt = self._neighbors_values.copy()
        global_asgt[self.name] = val
        asgt = filter_assignment_dict(global_asgt, rel.dimensions)

        c = rel.get_value_for_assignment(asgt)
        modifier = self._get_modifier_for_assignment(rel, asgt)
        if self._modifier_mode == "A":
            c += modifier
        else:  # modifier_mode == 'M'
            c *= modifier

        return c
Пример #10
0
    def _handle_value_message(self, variable_name, recv_msg):
        """
        Processes a received Value message to determine what is the best gain
        the variable can achieve

        :param variable_name: name of the sender
        :param recv_msg: A MgmValueMessage

        """
        self._neighbors_values[variable_name] = recv_msg.value
        # if we have a value for all neighbors, compute the best value for
        # conflict reduction
        if len(self._neighbors_values) == len(self._neighbors):
            if self.logger.isEnabledFor(logging.DEBUG):
                self.logger.debug(
                    f"Received values from all neighbors : {self._neighbors_values}"
                )
            # Compute the current_cost on the first step (initialization) of
            # the algorithm
            if self.current_cost is None:
                reduced_cs = []
                concerned_vars = set()
                cost = 0
                for c in self.utilities:
                    asgt = filter_assignment_dict(self._neighbors_values,
                                                  c.dimensions)
                    reduced_cs.append(c.slice(asgt))
                    cost = functools.reduce(
                        operator.add,
                        [f(self.current_value) for f in reduced_cs])
                    # Cost for variable, if any:
                    concerned_vars.update(c.dimensions)

                for v in concerned_vars:
                    if v.name == self.name:
                        cost += v.cost_for_val(self.current_value)
                    else:
                        cost += v.cost_for_val(self._neighbors_values[v.name])

                self.value_selection(self.current_value, cost)

            new_values, val_cost = self._compute_best_value()
            self._gain = self.current_cost - val_cost
            if ((self._mode == "min") &
                (self._gain > 0)) or ((self._mode == "max") &
                                      (self._gain < 0)):
                self._new_value = random.choice(new_values)
            else:
                self._new_value = self.current_value

            if self.logger.isEnabledFor(logging.INFO):
                self.logger.info(
                    f"Best local value for {self.name}: {self._new_value}"
                    f" {self._gain} (neighbors: {self._neighbors_values})")

            self._send_gain()

            self._wait_for_gains()
        else:
            # Still waiting for other neighbors
            if self.logger.isEnabledFor(logging.DEBUG):
                waited = [
                    n for n in self._neighbors
                    if n not in self._neighbors_values
                ]
                if self.logger.isEnabledFor(logging.DEBUG):
                    self.logger.debug(
                        f"Waiting for values from other neighbors: {waited}")
Пример #11
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")