def test_memory_footprint_from_classic_import(): # use maxsum as is has a computation_memory function defined import pydcop.algorithms.amaxsum as maxsum_module from pydcop.computations_graph.factor_graph import ( VariableComputationNode as FGVariableComputationNode, ) v1 = Variable("v1", [1, 2]) comp_def = ComputationDef( FGVariableComputationNode(v1, []), AlgorithmDef.build_with_default_param("amaxsum"), ) comp = maxsum_module.MaxSumVariableComputation(comp_def=comp_def) # The variable has no neighbors : footprint is 0 assert comp.footprint() == 0
def test_current_local_cost_unary(self): x = Variable("x", list(range(5))) # x2 = Variable('x2', list(range(5))) # @AsNAryFunctionRelation(x, x2) # def phi(x1_): # return x1_ phi = UnaryFunctionRelation("phi", x, lambda x_: 1 if x_ in [0, 2, 3] else 0) computation = Mgm2Computation(x, [phi], comp_def=MagicMock()) computation.__value__ = 0 computation2 = Mgm2Computation(x, [phi], comp_def=MagicMock()) computation2.__value__ = 1 self.assertEqual(computation._current_local_cost(), 1) self.assertEqual(computation2._current_local_cost(), 0)
def test_must_host_one(self): d1 = VariableDomain('d1', '', [1, 2, 3, 5]) v1 = Variable('v1', d1) f1 = relation_from_str('f1', 'v1 * 0.5', [v1]) cv1 = VariableComputationNode(v1, ['f1']) cf1 = FactorComputationNode(f1) cg = ComputationsFactorGraph([cv1], [cf1]) hints = DistributionHints({'a1': ['v1']}, None) agents = [AgentDef('a1', capacity=100), AgentDef('a2', capacity=100)] agent_mapping = distribute(cg, agents, hints, computation_memory=lambda x: 10) self.assertIn('v1', agent_mapping.computations_hosted('a1')) self.assertTrue(is_all_hosted(cg, agent_mapping))
def test_cost_for_1var(self): domain = list(range(10)) x1 = Variable('x1', domain) @AsNAryFunctionRelation(x1) def cost(x1_): return x1_ * 2 f = FactorAlgo(cost, comp_def=MagicMock()) costs = f._costs_for_var(x1) # in the max-sum algorithm, for an unary factor the costs is simply # the result of the factor function self.assertEqual(costs[0], 0) self.assertEqual(costs[5], 10)
def test_no_neighbors(): x1 = Variable("x1", list(range(10))) cost_x1 = constraint_from_str('cost_x1', 'x1 *2 ', [x1]) computation = Mgm2Computation(x1, [cost_x1], mode='max', comp_def=MagicMock()) computation.value_selection = MagicMock() computation.finished = MagicMock() vals, cost = computation._compute_best_value() assert cost == 18 assert set(vals) == {9} computation.on_start() computation.value_selection.assert_called_once_with(9, 18) computation.finished.assert_called_once_with()
def peav_variables_for_resource( resource: Resource, events: Dict[EVT, Event], slots_count: int) -> Dict[Tuple[RESOURCE, EVT], Variable]: variables: Dict[Tuple[RESOURCE, EVT], Variable] = {} for event in events.values(): if resource.id in event.resources: name = f"v_{resource.id:02d}_{event.id:02d}" # The domain represents the start time (as slot) this event could start at. # Time slots start at 1, the value 0 represents a combination # (event, resource) that is not scheduled. domain = Domain( f"d_{name}", "time_slot", values=range(0, slots_count - event.length + 2), ) variables[resource.id, event.id] = Variable(name, domain) return variables
def test_eff_cost_A_unary(self): domain = list(range(3)) x1 = Variable('x1', domain) @AsNAryFunctionRelation(x1) def phi(x1_): return x1_ g = GdbaComputation(x1, [phi], comp_def=MagicMock()) c, _, _ = g.__constraints__[0] g.__value__ = 0 asgt = frozenset({'x1': 0}.items()) g.__constraints_modifiers__[c][asgt] = 5 self.assertEqual(g._eff_cost(c, 0), 5) self.assertEqual(g._eff_cost(c, 1), 1) self.assertEqual(g._eff_cost(c, 2), 2)
def test_deploy_computation_request(orchestrated_agent): orchestrated_agent.start() orchestrated_agent.add_computation = MagicMock() mgt = orchestrated_agent._mgt_computation v1 = Variable('v1', [1, 2, 3]) comp_node = VariableComputationNode(v1, []) comp_def = ComputationDef(comp_node, AlgoDef('dsa')) mgt.on_message('orchestrator', DeployMessage(comp_def), 0) # Check the computation is deployed, but not started, on the agent calls = orchestrated_agent.add_computation.mock_calls assert len(calls) == 1 _, args, _ = calls[0] computation = args[0] assert isinstance(computation, MessagePassingComputation) assert not computation.is_running
def test_unary_function_relation(self): x = Variable("x", list(range(5))) # x2 = Variable('x2', list(range(5))) # @AsNAryFunctionRelation(x, x2) # def phi(x1_): # return x1_ phi = UnaryFunctionRelation("phi", x, lambda x_: 1 if x_ in [0, 2, 3] else 0) computation = Mgm2Computation( ComputationDef( VariableComputationNode(x, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation.__value__ = 0 self.assertEqual(computation._compute_cost(**{"x": 0}), 1)
def test_build_computation_with_params(): v1 = Variable('v1', [0, 1, 2, 3, 4]) n1 = VariableComputationNode(v1, []) comp_def = ComputationDef( n1, AlgorithmDef.build_with_default_param('dsa', mode='max', params={ 'variant': 'C', 'stop_cycle': 10, 'probability': 0.5 })) c = DsaComputation(comp_def) assert c.mode == 'max' assert c.variant == 'C' assert c.stop_cycle == 10 assert c.probability == 0.5
def test_simple_fg(self): d1 = VariableDomain('d1', '', [1, 2, 3, 5]) v1 = Variable('v1', d1) f1 = relation_from_str('f1', 'v1 * 0.5', [v1]) cv1 = VariableComputationNode(v1, ['f1']) cf1 = FactorComputationNode(f1) cg = ComputationsFactorGraph([cv1], [cf1]) agents= [AgentDef('a1'), AgentDef('a2')] distribution = distribute(cg, agents) self.assertEqual(len(distribution.agents), 2) self.assertEqual(len(distribution.computations), 2) self.assertEqual(len(distribution.computations_hosted('a1')), 1) self.assertEqual(len(distribution.computations_hosted('a2')), 1) self.assertNotEqual(distribution.computations_hosted('a2'), distribution.computations_hosted('a1'))
def test_no_neighbors(): x1 = Variable("x1", list(range(10))) cost_x1 = constraint_from_str("cost_x1", "x1 *2 ", [x1]) computation = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [cost_x1]), AlgorithmDef.build_with_default_param("mgm2", mode="max"), )) computation.value_selection = MagicMock() computation.finished = MagicMock() vals, cost = computation._compute_best_value() assert cost == 18 assert set(vals) == {9} computation.on_start() computation.value_selection.assert_called_once_with(9, 18) computation.finished.assert_called_once_with()
def test_cost_for_1var(): domain = list(range(10)) x1 = Variable("x1", domain) @AsNAryFunctionRelation(x1) def cost(x1_): return x1_ * 2 comp_def = MagicMock() comp_def.algo.algo = "amaxsum" comp_def.algo.mode = "min" comp_def.node.factor = cost f = MaxSumFactorComputation(comp_def=comp_def) costs = factor_costs_for_var(cost, x1, f._costs, f.mode) # costs = f._costs_for_var(x1) # in the max-sum algorithm, for an unary factor the costs is simply # the result of the factor function assert costs[0] == 0 assert costs[5] == 10
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 test_communication_load(): v = Variable('v1', list(range(10))) var_node = VariableComputationNode(v, []) assert dsa.UNIT_SIZE + dsa.HEADER_SIZE == dsa.communication_load( var_node, 'f1')
def toy_pb(): # A toy problem with 5 variables and 5 constraints. # The objective here is to have a problem that is simple enough to be solved # manually and used in test, but that is representative enough to be meaningful. # For example, it includes a loop to make sure we have pseudo parents v_a = Variable("A", ["R", "B"]) v_b = Variable("B", ["R", "B"]) v_c = Variable("C", ["R", "B"]) v_d = Variable("D", ["R", "B"]) v_e = Variable("E", ["R", "B"]) c1 = constraint_from_str( "c1", "{('R', 'B'): 1, " " ('R', 'R'): 5, " " ('B', 'B'): 3, " " ('B', 'R'): 2 " "}[(A, B)]", [v_a, v_b], ) c2 = constraint_from_str( "c2", "{('R', 'B'): 2, " " ('R', 'R'): 8, " " ('B', 'B'): 5, " " ('B', 'R'): 3 " "}[(A, C)]", [v_a, v_c], ) c3 = constraint_from_str( "c3", "{('R', 'B'): 2, " " ('R', 'R'): 4, " " ('B', 'B'): 2, " " ('B', 'R'): 0 " "}[(A, D)]", [v_a, v_d], ) c4 = constraint_from_str( "c4", "{('R', 'B'): 0, " " ('R', 'R'): 10, " " ('B', 'B'): 2, " " ('B', 'R'): 1 " "}[(B, D)]", [v_b, v_d], ) c5 = constraint_from_str( "c5", "{('R', 'B'): 2, " " ('R', 'R'): 4, " " ('B', 'B'): 0, " " ('B', 'R'): 15 " "}[(D, E)]", [v_d, v_e], ) # build the pseudo-tree for this problem g = build_computation_graph(None, constraints=[c1, c2, c3, c4, c5], variables=[v_a, v_b, v_c, v_d, v_e]) return g
def single_variable_pb(): x1 = Variable("x1", ["R", "B"]) # build the pseudo-tree for this problem g = build_computation_graph(None, constraints=[], variables=[x1]) return g
def test_variable_memory_two_neighbor(self): d1 = VariableDomain("d1", "", [1, 2, 3, 5]) v1 = Variable("v1", d1) cv1 = VariableComputationNode(v1, ["f1", "f2"]) self.assertEqual(computation_memory(cv1), VARIABLE_UNIT_SIZE * 4 * 2)
def generate_mixed_problem(args): logger.debug('generate_mixed_problem %s ', args) variable_count = args.variable_count constraint_count = args.constraint_count density = args.density real_density = density domain_range = args.range arity = args.arity auto_agents = args.agents is None capacity = args.capacity agents_count = variable_count if auto_agents else args.agents nb_max_edges = constraint_count * min(arity, variable_count) edges_count = int(nb_max_edges * density) hard_count = int(args.hard_constraint * edges_count) logger.info( 'Generating random DCOP graph with %s variables, whose domain ' 'are [0;%s], %s edges, %s agents, %s hard ' 'constraints and %s soft constraints', variable_count, domain_range - 1, edges_count, agents_count, hard_count, constraint_count - hard_count) if arity > variable_count: raise ValueError( "The arity of a constraint must be at most the " "number of variable. Arity: {}, Nb variables: {}".format( arity, variable_count)) if hard_count < 0: raise ValueError( "The argument '-h' (or '--hard_count') must be " "between 0 and 1. Currently set to: {}".format(hard_count)) # Create sets for the bipartite graph if constraint_count <= 0: raise ValueError( "The argument '-c' (or '--constraint_count') must be " "strictly positive. Currently set to: {}".format(constraint_count)) if variable_count < 0: raise ValueError( "The argument '-v' (or '--variable_count') must be " "at least 1. Currently set to: {}".format(variable_count)) if arity <= 0: raise ValueError("The argument '-a' (or '--arity') must be " "at least 1. Currently set to: {}".format(arity)) d = VariableDomain('levels', 'level', range(domain_range)) variables = {} agents = {} constraints = {} if arity == 1: if constraint_count != variable_count: raise ValueError("For max arity 1 you need the same number of " "variables, constraints and edges. You asked " "for {} variables and {} constraints.".format( variable_count, constraint_count)) nodes = [i + 1 for i in range(variable_count)] constraints_list = [("c{}".format(i + 1), 'hard') if i < hard_count else ("c{}".format(i + 1), 'soft') for i in range(constraint_count)] variables = {} constraints = {} while len(nodes) != 0: n = nodes.pop() c = constraints_list.pop( random.randint(0, len(constraints_list) - 1)) w = choose_weight() hard = c[1] == 'hard' objective = find_objective([w], domain_range - 1, hard) if hard: expression = "float('inf') if " + str(w) + "*v" + str(n) +\ " != " + str(objective) + " else 0" else: expression = str(w) + "*v" + str(n) + " - " + str(objective) v = Variable("v" + str(n), d) variables["v" + str(n)] = v constraints[c[0]] = relation_from_str(c[0], expression, [v]) if auto_agents: a_name = 'a' + str(n) agents[a_name] = AgentDef(a_name, capacity) if not auto_agents: for i in range(agents_count): a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) elif arity == 2: edges_count = int(variable_count * (variable_count - 1) * density / 2) if constraint_count != edges_count: logger.warning("edges count is different of constraint count ({} " "!= {}) but for arity 2, constraints are the deges" "of the graph. We use the density ({}) to determine" " the number of edges".format( edges_count, constraint_count, density)) is_connected = False while not is_connected: graph = nx.gnp_random_graph(variable_count, density) is_connected = nx.is_connected(graph) # Compute nb of hard constraints regarding the true density real_density = nx.density(graph) hard_count = args.hard_constraint * real_density * args.variable_count\ * (args.variable_count + 1) / 2 for i, node in enumerate(graph.nodes_iter()): logger.debug('node %s - %s', node, i) name = 'v' + str(i) variables[name] = Variable(name, d) if auto_agents: a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) if not auto_agents: for i in range(agents_count): a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) constraints = {} for i, edge in enumerate(graph.edges_iter()): logger.debug('edge %s - %s', edge, i) name = 'c' + str(i) u, v = edge weights = [round(choose_weight(), 2), round(choose_weight(), 2)] # Create hard_constraints if i < hard_count: objective = round(find_objective(weights, domain_range, True), 2) expression = "0 if v{} != v{} else float(" \ "'inf')".format(u, v) else: max_val = (weights[0] + weights[1]) * domain_range expression = 'abs(v{} + v{} - {})'\ .format(u, v, round(random.uniform(0, max_val), 2)) constraints[name] = relation_from_str(name, expression, variables.values()) logger.debug(repr(constraints[name])) else: if edges_count < constraint_count and arity != 1: raise ValueError( "The number of edges must be greater or equal to the " "number of constraints. Otherwise you have unused " "constraints. Edges: {}, Constraints: {}".format( edges_count, constraint_count)) nodes = [i for i in list(range(variable_count))] constraints = [("c{}".format(i), "hard") if i < hard_count else ("c{}".format(i), "soft") for i in list(range(constraint_count))] # Randomly add edges edges = defaultdict(lambda: []) # final set of edges # Available edges at a given run available_edges = {n: constraints.copy() for n in nodes} # First, make sure each variable has one constraint for n in nodes: if constraints: node = n c = constraints.pop(random.randint(0, len(constraints) - 1)) else: node, c = choose_in_available_edges(available_edges, n) add_edge(node, c, available_edges, edges, arity) logger.debug('Add edge (%s, %s)', n, c) edges_count -= variable_count # Second, make sure each constraint is used for c in constraints: n = random.choice(nodes) add_edge(n, c, available_edges, edges, arity) edges_count -= 1 logger.debug('Add edge (%s, %s)', n, c) # Third, randomly add remaining constraints while edges_count != 0: n, c = choose_in_available_edges(available_edges) if (n, c) == (None, None): # If more edges than possible are asked, returns just the maximum # edges (regarding nodes number and constraints arity) logger.warning( '%s edges were not added because you asked for too' ' many edges regarding the number of constraints (' '%s) and their maximum arity (%s)', edges_count - len(edges), constraint_count, args.arity) break else: add_edge(n, c, available_edges, edges, arity) edges_count -= 1 # Now create a DCOP from the graph for i in nodes: name = 'v' + str(i) variables[name] = Variable(name, d) if auto_agents: a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) if not auto_agents: for i in range(agents_count): a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) constraints = {} for c, neighbors in edges.items(): logger.debug('Constraint %s, variables: %s', c, neighbors) name, is_hard = c[0], c[1] == 'hard' c_variables = [variables["v" + str(name)] for name in neighbors] addition_string = "" first = True weights = [] max_val = 0 for i in neighbors: # Add random weights in constraints m = round(choose_weight(), 2) weights.append(m) max_val += m * (domain_range - 1) if not first: addition_string += ' + ' else: first = False if m != 1: addition_string += str(m) + '*' addition_string += 'v' + str(i) + ' ' # To ensure that our hard constraints are achievable, we use # the value obtained for a set of random values (in the domain) # as the objective. objective = round(find_objective(weights, domain_range, is_hard), 2) if is_hard: expression = "0 if " + addition_string + " == " + \ str(objective) + " else float('inf')" else: const_function = "abs(" + addition_string if objective: expression = const_function + " - " + str(objective) + ")" else: expression = addition_string constraints[name] = relation_from_str(name, expression, c_variables) logger.debug(repr(constraints[name])) dcop = DCOP('mixed constraints problem', 'min', domains={'levels': d}, variables=variables, constraints=constraints, agents=agents) if args.output is not None: outputfile = args.output[0] if args.correct_density: outputfile = correct_density(outputfile, real_density) write_in_file(outputfile, dcop_yaml(dcop)) else: print(dcop_yaml(dcop))
class TestsConstraintViolation(unittest.TestCase): domain = list(range(2)) x1 = Variable('x1', domain) x2 = Variable('x2', domain) x3 = Variable('x3', domain) phi = UnaryFunctionRelation('phi', Variable('x1', domain), lambda x: x) phi_n_ary = NAryFunctionRelation( lambda x1_, x2_, x3_: 2 if x1_ == x2_ else (1 if x1_ == x3_ else 0), [x1, x2, x3]) def NZ_violation_unary(self): g = GdbaComputation(self.x1, [self.phi], comp_def=MagicMock()) g._neighbors_values['x2'] = 1 g._neighbors_values['x3'] = 2 c = g.__constraints__[0] self.assertEqual(g._is_violated(c, 0), False) self.assertEqual(g._is_violated(c, 1), True) self.assertEqual(g._is_violated(c, 2), True) def NZ_violation_n_ary(self): g = GdbaComputation(self.x1, [self.phi_n_ary], comp_def=MagicMock()) g._neighbors_values['x2'] = 1 g._neighbors_values['x3'] = 2 c = g.__constraints__[0] self.assertEqual(g._is_violated(c, 0), False) self.assertEqual(g._is_violated(c, 1), True) self.assertEqual(g._is_violated(c, 2), True) def NM_violation_unary(self): g = GdbaComputation(self.x1, [self.phi], comp_def=MagicMock()) g._neighbors_values['x2'] = 1 g._neighbors_values['x3'] = 2 g._violation_mode = 'NM' c = g.__constraints__[0] self.assertEqual(g._is_violated(c, 0), False) self.assertEqual(g._is_violated(c, 1), True) self.assertEqual(g._is_violated(c, 2), True) def NM_violation_n_ary(self): g = GdbaComputation(self.x1, [self.phi_n_ary], comp_def=MagicMock()) g._neighbors_values['x2'] = 1 g._neighbors_values['x3'] = 2 g._violation_mode = 'NM' c = g.__constraints__[0] self.assertEqual(g._is_violated(c, 0), False) self.assertEqual(g._is_violated(c, 1), True) self.assertEqual(g._is_violated(c, 2), True) def MX_violation_unary(self): g = GdbaComputation(self.x1, [self.phi], comp_def=MagicMock()) g._neighbors_values['x2'] = 1 g._neighbors_values['x3'] = 2 g._violation_mode = 'MX' c = g.__constraints__[0] self.assertEqual(g._is_violated(c, 0), False) self.assertEqual(g._is_violated(c, 1), False) self.assertEqual(g._is_violated(c, 2), True) def MX_violation_n_ary(self): g = GdbaComputation(self.x1, [self.phi_n_ary], comp_def=MagicMock()) g._neighbors_values['x2'] = 1 g._neighbors_values['x3'] = 2 g._violation_mode = 'MX' c = g.__constraints__[0] self.assertEqual(g._is_violated(c, 0), False) self.assertEqual(g._is_violated(c, 1), True) self.assertEqual(g._is_violated(c, 2), False)
def generate_small_world(args): logger.debug("generate small world problem %s ", args) # Erdős-Rényi graph aka binomial graph. graph = nx.barabasi_albert_graph(args.num, 2) # import matplotlib.pyplot as plt # plt.subplot(121) # nx.draw(graph) # default spring_layout # plt.show() domain = Domain("d", "d", range(args.domain)) variables = {} agents = {} for n in graph.nodes: v = Variable(var_name(n), domain) variables[v.name] = v logger.debug("Create var for node %s : %s", n, v) constraints = {} for i, (n1, n2) in enumerate(graph.edges): v1 = variables[var_name(n1)] v2 = variables[var_name(n2)] values = random_assignment_matrix([v1, v2], range(args.range)) c = NAryMatrixRelation([v1, v2], values, name=c_name(n1, n2)) logger.debug("Create constraints for edge (%s, %s) : %s", v1, v2, c) constraints[c.name] = c dcop = DCOP( "graph coloring", "min", domains={"d": domain}, variables=variables, agents={}, constraints=constraints, ) graph_module = import_module("pydcop.computations_graph.factor_graph") cg = graph_module.build_computation_graph(dcop) algo_module = load_algorithm_module("maxsum") footprints = {n.name: algo_module.computation_memory(n) for n in cg.nodes} f_vals = footprints.values() logger.info( "%s computations, footprint: \n sum: %s, avg: %s max: %s, " "min: %s", len(footprints), sum(f_vals), sum(f_vals) / len(footprints), max(f_vals), min(f_vals), ) default_hosting_cost = 2000 small_agents = [agt_name(i) for i in range(75)] small_capa, avg_capa, big_capa = 40, 200, 1000 avg_agents = [agt_name(i) for i in range(75, 95)] big_agents = [agt_name(i) for i in range(95, 100)] hosting_factor = 10 for a in small_agents: # communication costs with all other agents comm_costs = {other: 6 for other in small_agents if other != a} comm_costs.update({other: 8 for other in avg_agents}) comm_costs.update({other: 10 for other in big_agents}) # hosting cost for all computations hosting_costs = {} for n in cg.nodes: # hosting_costs[n.name] = hosting_factor * \ # abs(small_capa -footprints[n.name]) hosting_costs[n.name] = footprints[n.name] / small_capa agt = AgentDef( a, default_hosting_cost=default_hosting_cost, hosting_costs=hosting_costs, default_route=10, routes=comm_costs, capacity=small_capa, ) agents[agt.name] = agt logger.debug("Create small agt : %s", agt) for a in avg_agents: # communication costs with all other agents comm_costs = {other: 8 for other in small_agents} comm_costs.update({other: 2 for other in avg_agents if other != a}) comm_costs.update({other: 4 for other in big_agents}) # hosting cost for all computations hosting_costs = {} for n in cg.nodes: # hosting_costs[n.name] = hosting_factor * \ # abs(avg_capa - footprints[n.name]) hosting_costs[n.name] = footprints[n.name] / avg_capa agt = AgentDef( a, default_hosting_cost=default_hosting_cost, hosting_costs=hosting_costs, default_route=10, routes=comm_costs, capacity=avg_capa, ) agents[agt.name] = agt logger.debug("Create avg agt : %s", agt) for a in big_agents: # communication costs with all other agents comm_costs = {other: 10 for other in small_agents} comm_costs.update({other: 4 for other in avg_agents}) comm_costs.update({other: 1 for other in big_agents if other != a}) # hosting cost for all computations hosting_costs = {} for n in cg.nodes: hosting_costs[n.name] = footprints[n.name] / big_capa agt = AgentDef( a, default_hosting_cost=default_hosting_cost, hosting_costs=hosting_costs, default_route=10, routes=comm_costs, capacity=big_capa, ) agents[agt.name] = agt logger.debug("Create big agt : %s", agt) dcop = DCOP( "graph coloring", "min", domains={"d": domain}, variables=variables, agents=agents, constraints=constraints, ) if args.output: outputfile = args.output[0] write_in_file(outputfile, dcop_yaml(dcop)) else: print(dcop_yaml(dcop))
def test_offer_already_has_partner(self): x1 = Variable("x1", list(range(2))) x2 = Variable("x2", list(range(2))) x3 = Variable("x3", list(range(2))) @AsNAryFunctionRelation(x1, x2, x3) def phi(x1_, x2_, x3_): return x1_ + x2_ + x3_ # Receives a fake offer computation = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation.message_sender = DummySender() computation._state = "offer" computation._is_offerer = True computation.on_offer_msg("x2", Mgm2OfferMessage(), 1) self.assertEqual(computation._state, "offer") # self.assertEqual(computation._offers, []) # Received only fake offers computation2 = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation2.message_sender = DummySender() computation2._state = "offer" computation2.__nb_received_offers__ = 1 computation2.on_offer_msg("x2", Mgm2OfferMessage(), 1) self.assertEqual(computation2._state, "gain") self.assertEqual(computation2._offers, [("x2", Mgm2OfferMessage())]) # receives a real offer computation3 = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation3.message_sender = DummySender() computation3._state = "offer" computation3._is_offerer = True computation3.__cost__ = 15 computation3.on_offer_msg( "x2", Mgm2OfferMessage({(1, 1): 8}, is_offering=True), 1) self.assertEqual(computation3._state, "offer") self.assertEqual(2, len(computation3._offers)) self.assertEqual(computation3._potential_gain, 0) self.assertIsNone(computation3._potential_value) # Receives a real offer which is the last expected one computation4 = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation4.message_sender = DummySender() computation4._state = "offer" computation4._is_offerer = True computation4.__nb_received_offers__ = 1 computation4.on_offer_msg( "x2", Mgm2OfferMessage({(1, 1): 8}, is_offering=True), 1) self.assertEqual(len(computation4), 3) self.assertEqual(computation4._state, "answer?") self.assertEqual(computation4._potential_gain, 0) self.assertIsNone(computation4._potential_value)
def test_offer_has_no_partner_yet(self): x1 = Variable("x1", list(range(2))) x2 = Variable("x2", list(range(2))) x3 = Variable("x3", list(range(2))) @AsNAryFunctionRelation(x1, x2, x3) def phi(x1_, x2_, x3_): return x1_ + x2_ + x3_ # Receives a fake offer computation = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation.message_sender = DummySender() computation._state = "offer" computation.on_offer_msg("x2", Mgm2OfferMessage(), 1) self.assertEqual(computation._state, "offer") self.assertEqual(computation._offers, []) # Received only fake offers computation2 = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation2.message_sender = DummySender() computation2._state = "offer" computation2.__nb_received_offers__ = 1 computation2.on_offer_msg("x2", Mgm2OfferMessage(), 1) self.assertEqual(computation2._state, "gain") self.assertEqual(computation2._offers, []) # Receives a real offer (but still expects other OfferMessages) computation3 = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation3.message_sender = DummySender() computation3._state = "offer" computation3.on_offer_msg( "x2", Mgm2OfferMessage({(1, 1): 8}, is_offering=True), 1) self.assertEqual(computation3._state, "offer") self.assertEqual(computation3._offers, [("x2", {(1, 1): 8})]) # Receives a real offer and is the last expected OfferMessage computation4 = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation4.message_sender = DummySender() computation4._state = "offer" computation4._neighbors_values = {"x2": 0, "x3": 1} computation4.__value__ = 0 computation4.__cost__ = 1 computation4.__nb_received_offers__ = 1 computation4.on_offer_msg( "x2", Mgm2OfferMessage({(1, 1): 8}, is_offering=True), 1) self.assertEqual(computation4._offers, [("x2", {(1, 1): 8})]) self.assertEqual(computation4._state, "gain") self.assertEqual(computation4._potential_gain, 9) self.assertEqual(computation4._potential_value, 1)
from pydcop.algorithms import amaxsum as ms from pydcop.algorithms.amaxsum import communication_load, computation_memory from pydcop.algorithms.maxsum import VARIABLE_UNIT_SIZE from pydcop.computations_graph.factor_graph import ComputationsFactorGraph, \ VariableComputationNode, FactorComputationNode, FactorGraphLink from pydcop.dcop.objects import Variable, VariableDomain, AgentDef from pydcop.dcop.relations import relation_from_str from pydcop.distribution.ilp_fgdp import distribute, _build_alphaijk_binvars, \ _objective_function, _computation_memory_in_cg from pydcop.distribution.objects import ImpossibleDistributionException Agent = namedtuple('Agent', ['name']) d1 = VariableDomain('d1', '', [1, 2, 3, 5]) v1 = Variable('v1', d1) v2 = Variable('v2', d1) v3 = Variable('v3', d1) v4 = Variable('v4', d1) v5 = Variable('v5', d1) a1 = AgentDef('a1', capacity=200) a2 = AgentDef('a2', capacity=200) def is_all_hosted(cg, dist): if len(cg.nodes) == len(dist.computations): for c in cg.nodes: if c.name not in dist.computations: return False return True
def generate_graph_coloring(args): logger.debug('generate_graph_coloring %s ', args) node_count = args.node_count density = args.density color_count = args.color_count auto_agents = args.agents is None agents_count = node_count if auto_agents else args.agents capacity = args.capacity logger.info( 'Generating random graph coloring with %s variables, ' '%s colors, target density %s and %s agents', node_count, color_count, args.density, agents_count) # First a random graph is_connected = False if not args.allow_subgraph: while not is_connected: graph = nx.gnp_random_graph(args.node_count, density) is_connected = nx.is_connected(graph) else: graph = nx.gnp_random_graph(args.node_count, density) is_connected = nx.is_connected(graph) real_density = nx.density(graph) logger.info(nx.info(graph)) logger.info('Connected : %s', nx.is_connected(graph)) logger.info('Density %s', nx.density(graph)) # Now create a DCOP from the graph d = VariableDomain('colors', 'color', range(color_count)) variables = {} agents = {} for i, node in enumerate(graph.nodes_iter()): logger.debug('node %s - %s', node, i) name = 'v' + str(i) variables[name] = Variable(name, d) if auto_agents: a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) if not auto_agents: for i in range(agents_count): a_name = 'a' + str(i) agents[a_name] = AgentDef(a_name, capacity) constraints = {} for i, edge in enumerate(graph.edges_iter()): logger.debug('edge %s - %s', edge, i) name = 'c' + str(i) u, v = edge expression = '1000 if v{} == v{} else 0'.format(u, v) constraints[name] = relation_from_str(name, expression, variables.values()) logger.debug(repr(constraints[name])) dcop = DCOP('graph coloring', 'min', domains={'colors': d}, variables=variables, agents=agents, constraints=constraints) if args.output: outputfile = args.output[0] if args.correct_density: outputfile = correct_density(outputfile, real_density) write_in_file(outputfile, dcop_yaml(dcop)) else: print(dcop_yaml(dcop))
def dpop_nonbinaryrelation(): x0 = Variable('x0', list(range(10))) x1 = Variable('x1', list(range(10))) x2 = Variable('x2', list(range(10))) @relations.AsNAryFunctionRelation(x0) def x0_prefs(x): if x > 5: return 0 return 10 @relations.AsNAryFunctionRelation(x1) def x1_prefs(x): if 2 < x < 7: return 0 return 10 @relations.AsNAryFunctionRelation(x2) def x2_prefs(x): if x < 5: return 0 return 10 @relations.AsNAryFunctionRelation(x0, x1, x2) def three_ary_relation(x, y, z): return abs(10 - (x+y+z)) comm = InProcessCommunicationLayer() al0 = DpopAlgo(x0, mode='min') al1 = DpopAlgo(x1, mode='min') al2 = DpopAlgo(x2, mode='min') # unary relation for preferences al0.add_relation(x0_prefs) al1.add_relation(x1_prefs) al2.add_relation(x2_prefs) al0.add_child(x1) al1.add_child(x2) al1.set_parent(x0) al2.set_parent(x1) al2.add_relation(three_ary_relation) a0 = Agent('a0', comm) a1 = Agent('a1', comm) a2 = Agent('a2', comm) a0.add_computation(al0) a1.add_computation(al1) a2.add_computation(al2) results, _, _ = synchronous_single_run([a0, a1, a2]) if results == {'x0': 6, 'x1': 3, 'x2': 1} or \ results == {'x0': 7, 'x1': 3, 'x2': 0}: logging.info('SUCCESS !! ' + str(results)) return 0 else: logging.info('invalid result found, needs some debugging ...' + str( results)) return 1
def maxsum_smartlights_multiplecomputationagent_costvariable(): """ This sample use variable with integrated cost. * 3 lights l1, l2 & l3 each light can have a luminosity level in the 0-9 range The energy cost is a linear function of the luminosity level, with l1 more efficient than l2 and l3 * one scene action y1, the room luminosity level y1 = (l1 + l2 + l3 )/3 y1 domain is also between 0-9 * one rule : l3 must be off AND y1 Must be 5 """ # building the Factor Graph # Lights : l1 = VariableWithCostFunc('l1', list(range(10)), lambda x: x * 0.5) l2 = VariableWithCostFunc('l2', list(range(10)), lambda x: x) l3 = VariableWithCostFunc('l3', list(range(10)), lambda x: x) # Scene action y1 = Variable('y1', list(range(10))) @relations.AsNAryFunctionRelation(l1, l2, l3, y1) def scene_rel(l1, l2, l3, y1): if y1 == round(l1 / 3.0 + l2 / 3.0 + l3 / 3.0): return 0 return INFNT # Rule @relations.AsNAryFunctionRelation(l3, y1) def rule_rel(l3, y1): """ This rule means : target luminosity if 5, and x3 is off. :param x3: :param y1: :return: """ return 10 * (abs(y1 - 5) + l3) # Create computation for factors and variables # Light 1 algo_l1 = amaxsum.VariableAlgo(l1, [scene_rel.name]) # Light 2 algo_l2 = amaxsum.VariableAlgo(l2, [scene_rel.name]) # Light 3 algo_l3 = amaxsum.VariableAlgo(l3, [scene_rel.name, rule_rel.name]) # Scene algo_y1 = amaxsum.VariableAlgo(y1, [rule_rel.name, scene_rel.name]) algo_scene = amaxsum.FactorAlgo(scene_rel) # Rule algo_rule = amaxsum.FactorAlgo(rule_rel) # Distribution of the computation on the three physical light-bulb nodes. # We have 9 computations to distribute on 3 agents, mapping the 3 light # bulbs. comm = infrastructure.communication.InProcessCommunicationLayer() a1 = infrastructure.Agent('Bulb1', comm) a1.add_computation(algo_l1) a1.add_computation(algo_scene) a1.add_computation(algo_y1) a2 = infrastructure.Agent('Bulb2', comm) a2.add_computation(algo_l2) a3 = infrastructure.Agent('Bulb3', comm) a3.add_computation(algo_l3) a3.add_computation(algo_rule) dcop_agents = [a1, a2, a3] results, _, _ = infrastructure.synchronous_single_run(dcop_agents, 5) print(results) if results == {'l1': 9, 'y1': 5, 'l3': 0, 'l2': 5}: logging.info('SUCCESS !! ') return 0 else: logging.info('invalid result found, needs some debugging ...' + str(results)) return 1
def test_1_unary_constraint_means_no_neighbors(self): variable = Variable('a', [0, 1, 2, 3, 4]) c1 = UnaryFunctionRelation('c1', variable, lambda x: abs(x - 2)) computation = DsaComputation(variable, [c1], comp_def=MagicMock()) self.assertEqual(len(computation._neighbors), 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
def test_gain_all_received(self): x1 = Variable("x1", list(range(3))) x2 = Variable("x2", list(range(2))) x3 = Variable("x3", list(range(2))) @AsNAryFunctionRelation(x1, x2) def phi(x1_, x2_): if x1_ == x2_: return 1 return 0 @AsNAryFunctionRelation(x1, x3) def psi(x1_, x3_): if x1_ == x3_: return 8 return 0 computation = Mgm2Computation( ComputationDef( VariableComputationNode(x1, [phi, psi]), AlgorithmDef.build_with_default_param("mgm2"), )) computation.message_sender = DummySender() computation._neighbors_values = {"x2": 1, "x3": 0} # If potential gain is 0 computation.__value__ = 1 computation.__cost__ = 1 computation._potential_value = 0 computation._potential_gain = 0 computation._state = "gain" computation._neighbors_gains["x3"] = 2 computation.on_gain_msg("x2", Mgm2GainMessage(5), 1) self.assertEqual(computation.current_value, 1) self.assertEqual(computation._state, "value") self.assertEqual(computation.current_cost, 1) # If commited and has best gain computation._state = "gain" computation.__value__ = 1 computation.__cost__ = 1 computation._committed = True computation._partner = x3 computation._potential_gain = 10 computation._neighbors_gains["x3"] = 10 computation._potential_value = 0 computation.on_gain_msg("x2", Mgm2GainMessage(5), 1) self.assertEqual(computation.current_value, 1) self.assertEqual(computation.current_cost, 1) self.assertTrue(computation._can_move) self.assertEqual(computation._state, "go?") # If commited and has not best gain computation._state = "gain" computation.__value__ = 1 computation.__cost__ = 1 computation._committed = True computation._partner = x3 computation._potential_gain = 1 computation._neighbors_gains["x3"] = 1 computation._potential_value = 0 computation.on_gain_msg("x2", Mgm2GainMessage(5), 1) self.assertEqual(computation.current_value, 1) self.assertEqual(computation.current_cost, 1) self.assertFalse(computation._can_move) self.assertEqual(computation._state, "go?") self.test_clear_agent() # If not committed and has best gain not alone: no test as it could (in # the future) be randomly chosen # If not committed and not best gain computation._state = "gain" computation.__value__ = 1 computation.__cost__ = 1 computation._committed = False computation._partner = None computation._potential_gain = 2 computation._neighbors_gains["x3"] = 2 computation._potential_value = 0 computation.on_gain_msg("x2", Mgm2GainMessage(5), 1) self.assertEqual(computation.current_value, 1) self.assertEqual(computation.current_cost, 1) self.assertEqual(computation._state, "value") self.test_clear_agent()