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)
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)
def _compute_offers_to_send(self): """ 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 :return: 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.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
def test_generate_1var(self): x1 = Variable('x1', ['a', 'b', 'c']) ass = list(algorithms.generate_assignment_as_dict([x1])) self.assertEqual(len(ass), len(x1.domain)) self.assertIn({'x1': 'a'}, ass) self.assertIn({'x1': 'b'}, ass) self.assertIn({'x1': 'c'}, ass)
def from_func_relation(rel: RelationProtocol) -> 'NAryMatrixRelation': variables = rel.dimensions cost_matrix = NAryMatrixRelation(variables) # We also compute the min and max value of the constraint as it is to # be needed in gdba mini = None maxi = None for asgt in generate_assignment_as_dict(variables): value = rel(asgt) cost_matrix = cost_matrix.set_value_for_assignment(asgt, value) return cost_matrix
def _compute_boundary(self, constraints): constraints_list = list() optimum = 0 for c in constraints: 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 constraints_list.append(c) optimum += boundary return constraints_list, optimum
def find_optimum(rel: RelationProtocol, mode: str) -> float: """ Compute the optimum of the relation given the mode :param rel: :param mode: 'min' or 'max' :return: The optimum value of the relation as a float """ if mode != "min" and mode != "max": raise ValueError("mode must be 'min' or 'max'") variables = [v for v in rel.dimensions] optimum = None for asgt in generate_assignment_as_dict(variables): rel_val = rel(**filter_assignment_dict(asgt, rel.dimensions)) if optimum is None: optimum = rel_val elif mode == 'max' and rel_val > optimum: optimum = rel_val elif mode == 'min' and rel_val < optimum: optimum = rel_val return optimum
def join_utils(u1: Constraint, u2: Constraint) -> Constraint: """ Build a new relation by joining the two relations u1 and u2. The dimension of the new relation is the union of the dimensions of u1 and u2. As order is important for most operation, variables for u1 are listed first, followed by variables from u2 that where already used by u1 (in the order in which they appear in u2.dimension). For any complete assignment, the value of this new relation is the sum of the values from u1 and u2 for the subset of this assignment that apply to their respective dimension. For more details, see the definition of the join operator in Petcu Phd Thesis. :param u1: n-ary relation :param u2: n-ary relation :return: a new relation """ # dims = u1.dimensions[:] for d2 in u2.dimensions: if d2 not in dims: dims.append(d2) u_j = NAryMatrixRelation(dims, name='joined_utils') for ass in generate_assignment_as_dict(dims): # FIXME use dict for assignement # for Get AND sett value u1_ass = filter_assignment_dict(ass, u1.dimensions) u2_ass = filter_assignment_dict(ass, u2.dimensions) s = u1(**u1_ass) + u2(**u2_ass) u_j = u_j.set_value_for_assignment(ass, s) return u_j
def __init__(self, variable, constraints, variant='B', proba_hard=0.7, proba_soft=0.7, mode='min', logger=None, 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._msg_handlers['mixed_dsa_value'] = self._on_value_msg 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.logger = logger if logger is not None else \ logging.getLogger('pydcop.algo.mixed-dsa'+variable.name) 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")