def test_create_several_binvariables_from_several_lists(self): variables = create_binary_variables("m_", (["x1", "x2"], ["a1", "a2", "a3"])) self.assertEqual(len(variables), 6) self.assertIn(("x1", "a2"), variables) self.assertTrue(isinstance(variables[("x2", "a3")], BinaryVariable)) self.assertEqual(variables[("x2", "a3")].name, "m_x2_a3")
def test_create_several_binvariables_from_several_lists(self): vars = create_binary_variables('m_', (['x1', 'x2'], ['a1', 'a2', 'a3'])) self.assertEqual(len(vars), 6) self.assertIn(('x1', 'a2'), vars) self.assertTrue(isinstance(vars[('x2', 'a3')], BinaryVariable)) self.assertEqual(vars[('x2', 'a3')].name, 'm_x2_a3')
def test_create_comm_constraint_for_agent_single_var(): # Say 1 candidate computation c1 could be hosted on a1 # c1 depends on c3 and c4, c3 is not a candidate (ie is fixed). c4 is a # candidate computation but cannot be h repair_info = (['a1', 'a2'], {'c3': 'a2'}, {'c4': ['a2', 'a3']}) # cost is uniform, no matter the computations and the agents def comm(com1, comp2, agt2): return 10 # binary vars for the candidate var that could be hosted on a1 bin_vars = create_binary_variables('x_', (['c1'], ['a1'])) # and bin vars for all the candidiate neighbors of these candidate _, _, mobile = repair_info for vm, ams in mobile.items(): bin_vm = create_binary_variables('x_', ([vm], ams)) bin_vars.update(bin_vm) comm_c = create_agent_comp_comm_constraint('a1', 'c1', repair_info, comm, bin_vars) assert set(v.name for v in comm_c.dimensions) == \ {'x_c1_a1', 'x_c4_a2', 'x_c4_a3'} # if a1 hosts c1, the comm host with c3 and c4 will always be 20 assert comm_c(x_c1_a1=1, x_c4_a2=1, x_c4_a3=0) == 20 assert comm_c(x_c1_a1=1, x_c4_a2=0, x_c4_a3=1) == 20 # and if ot does not host it, it must obvouisly be zero assert comm_c(x_c1_a1=0, x_c4_a2=0, x_c4_a3=1) == 0 assert comm_c(x_c1_a1=0, x_c4_a2=1, x_c4_a3=0) == 0
def test_create_hosted_constraint_for_computation(): # say we have a computation c1 that can be hosted on agents a1, a2 and a3 bin_vars = create_binary_variables('v_', (['c1'], ['a1', 'a2', 'a3'])) constraint = create_computation_hosted_constraint('c1', bin_vars) # check the constraints depends on one variable for each agent assert set(v.name for v in constraint.dimensions) == \ {'v_c1_a1', 'v_c1_a2', 'v_c1_a3'} # Check some return value for the constraint assert constraint(v_c1_a1=1, v_c1_a2=0, v_c1_a3=0) == 0 assert constraint(v_c1_a1=1, v_c1_a2=0, v_c1_a3=1) != 0 assert constraint(v_c1_a1=1, v_c1_a2=1, v_c1_a3=1) != 0
def test_create_hosting_constraint_for_agent(): # Say agent a1 could host the candidate computations [c1, c2, c3] candidates = ['c1', 'c2', 'c3'] def hosting_cost_a1(comp_name): costs = {'c1': 10, 'c2': 0, 'c3': 100} return costs[comp_name] bin_vars = create_binary_variables('x_', (candidates, ['a1'])) cost_c = create_agent_hosting_constraint('a1', hosting_cost_a1, bin_vars) assert set(v.name for v in cost_c.dimensions) == \ {'x_c1_a1', 'x_c2_a1', 'x_c3_a1', } assert cost_c(x_c1_a1=1, x_c2_a1=0, x_c3_a1=0) == 10 assert cost_c(x_c1_a1=1, x_c2_a1=1, x_c3_a1=0) == 10 assert cost_c(x_c1_a1=1, x_c2_a1=1, x_c3_a1=1) == 110 assert cost_c(x_c1_a1=0, x_c2_a1=1, x_c3_a1=1) == 100
def test_create_capacity_constraint_for_agent(): # Say our agent a1 has a remaining capacity of 50 and could hosts # candidates computations from [c1, c2, c3, c4], which all have a # footprint of 25 candidates = ['c1', 'c2', 'c3', 'c4'] def footprint(comp_name): return 25 bin_vars = create_binary_variables('x_', (candidates, ['a1'])) capa_c = create_agent_capacity_constraint('a1', 50, footprint, bin_vars) assert set(v.name for v in capa_c.dimensions) == \ {'x_c1_a1', 'x_c2_a1', 'x_c3_a1', 'x_c4_a1'} assert capa_c(x_c1_a1=1, x_c2_a1=0, x_c3_a1=0, x_c4_a1=0) == 0 assert capa_c(x_c1_a1=1, x_c2_a1=1, x_c3_a1=0, x_c4_a1=0) == 0 assert capa_c(x_c1_a1=1, x_c2_a1=1, x_c3_a1=1, x_c4_a1=0) != 0 assert capa_c(x_c1_a1=1, x_c2_a1=1, x_c3_a1=1, x_c4_a1=1) != 0
def setup_repair(self, repair_info): self.logger.info('Setup repair %s', repair_info) # create computation for the reparation dcop # The reparation dcop uses a dcop algorithm where computations maps to # variable (in order to have another dcop distribution problem) and use # binary variable for each candidate computation. # This agent will host one variable-computation for each # binary variable x_i^m indicating if the candidate computation x_i # is hosted on this agent a_m. Notice that by construction, # the agent already have a replica for all the candidates x_i. # The reparation dcop includes several constraints and variables: # Variables # * one binary variable for each orphaned computation # Constraints # * hosted constraints : one for each candidate computation # * capacity constraint : one for this agent # * hosting costs constraint : one for this agent # * communication constraint # # For reparation, we use a dcop algorithm where computations maps to # variables of the dcop. On this agent, we host the computations # corresponding to the variables representing the orphaned computation # that could be hosted on this agent (aka candidate computation). # Here, we use MGM own_name = self.name # `orphaned_binvars` is a map that contains binary variables for # orphaned computations. # Notice that it only contains variables for computations # that this agents knows of, i.e. computations that could be hosted # here (aka candidate computations) or that depends on computations # that could be hosted here. # There is one binary variable x_i^m for each pair (x_i, a_m), # where x_i is an orphaned computation and a_m is an agent that could # host x_i (i.e. has a replica of x_i). orphaned_binvars = {} # type: Dict[Tuple, BinaryVariable] # One binary variable x_i^m for each candidate computation x_i that # could be hosted on this agent a_m. Computation for these variables # will be hosted in this agent. This is a subset of orphaned_binvars. candidate_binvars = {} # type: Dict[Tuple, BinaryVariable] # Agent that will host the computation for each binary var. # it is a dict { bin var name : agent_name } # agt_hosting_binvar = {} # type: Dict[str, str] # `hosted_cs` contains hard constraints ensuring that all candidate # computations are hosted: hosted_cs = {} # type: Dict[str, Constraint] for candidate_comp, candidate_info in repair_info.items(): try: # This computation is not hosted any more, if we had it in # discovery, forget about it but do not publish this # information, this agent is not responsible for updatings # other's discovery services. self.discovery.unregister_computation(candidate_comp, publish=False) except UnknownComputation: pass agts, _, neighbors = candidate_info # One binary variable for each candidate agent for computation # candidate_comp: v_binvar = create_binary_variables( 'B', ([candidate_comp], candidate_info[0])) orphaned_binvars.update(v_binvar) # the variable representing if the computation will be hosted on # this agent: candidate_binvars[(candidate_comp, own_name)] = \ v_binvar[(candidate_comp, own_name)] # the 'hosted' hard constraint for this candidate variable: hosted_cs[candidate_comp] =\ create_computation_hosted_constraint(candidate_comp, v_binvar) self.logger.debug('Hosted hard constraint for computation %s : %r', candidate_comp, hosted_cs[candidate_comp]) # One binary variable for each pair (x_j, a_n) where x_j is an # orphaned neighbors of candidate_comp and a_n is an agent that # could host a_n: for neighbor in neighbors: v_binvar = create_binary_variables( 'B', ([neighbor], neighbors[neighbor])) orphaned_binvars.update(v_binvar) self.logger.debug('Binary variable for reparation %s ', orphaned_binvars) # Agent that will host the computation for each binary var. # it is a dict { bin var name : agent_name } agt_hosting_binvar = { v.name: a for (_, a), v in orphaned_binvars.items() } self.logger.debug( 'Agents hosting the computations for these binary ' 'variables : %s ', agt_hosting_binvar) # The capacity (hard) constraint for this agent. This ensures that the # capacity of the current agent will not be overflown by hosting too # many candidate computations. This constraints depends on the binary # variables for the candidate computations. remaining_capacity = self.agent_def.capacity - \ sum(c.footprint() for c in self.computations()) self.logger.debug('Remaining capacity on agent %s : %s', self.name, remaining_capacity) def footprint_func(c_name: str): # We have a replica for these computation, we known its footprint. return self.replication_comp.hosted_replicas[c_name][1] capacity_c = create_agent_capacity_constraint(own_name, remaining_capacity, footprint_func, candidate_binvars) self.logger.debug('Capacity constraint for agt %s : %r', self.name, capacity_c) # Hosting costs constraint for this agent. This soft constraint is # used to minimize the hosting costs on this agent ; it depends on # the binary variables for the candidate computations. hosting_c = create_agent_hosting_constraint( own_name, self.agent_def.hosting_cost, candidate_binvars) self.logger.debug('Hosting cost constraint for agt %s : %r', self.name, hosting_c) # The communication constraint. This soft constraints is used to # minimize the communication cost on this agent. As communication # cost depends on where computation on both side of an edge are # hosted, it also depends on the binary variables for orphaned # computations that could not be hosted here. def comm_func(candidate_comp: str, neighbor_comp: str, agt: str): # returns the communication cost between the computation # candidate_name hosted on the current agent and it's neighbor # computation neigh_comp hosted on agt. route_cost = self.agent_def.route(agt) comp_def = self.replication_comp.replicas[candidate_comp] algo = comp_def.algo.algo algo_module = import_module('pydcop.algorithms.{}'.format(algo)) communication_load = algo_module.communication_load msg_load = 0 for l in comp_def.node.neighbors: if l == neighbor_comp: msg_load += communication_load(comp_def.node, neighbor_comp) com_load = msg_load * route_cost return com_load # Now that we have the variables and constraints, we can create # computation instances for each of the variable this agent is # responsible for, i.e. the binary variables x_i^m that correspond to # the candidate variable x_i (and a_m is the current agent) self._repair_computations.clear() algo_def = AlgoDef(repair_algo.algo_name(), cycle_stop=10, threshold=0.2) for (comp, agt), candidate_var in candidate_binvars.items(): self.logger.debug( 'Building computation for binary variable %s (' 'variable %s on %s)', candidate_var, comp, agt) comm_c = create_agent_comp_comm_constraint(agt, comp, repair_info[comp], comm_func, orphaned_binvars) self.logger.debug( 'Communication constraint for computation %s ' 'on agt %s : %r', comp, self.name, comm_c) constraints = [comm_c, hosting_c, capacity_c, hosted_cs[comp]] # constraints.extend(hosted_cs.values()) self.logger.debug('Got %s Constraints for var %s : %s ', len(constraints), candidate_var, constraints) node = chg.VariableComputationNode(candidate_var, constraints) comp_def = ComputationDef(node, algo_def) computation = repair_algo.build_computation(comp_def) self.logger.debug('Computation for %s : %r ', candidate_var, computation) # add the computation on this agents and register the neighbors self.add_computation(computation) self._repair_computations[computation.name] = \ RepairComputationRegistration(computation, 'ready', comp) for neighbor_comp in node.neighbors: neighbor_agt = agt_hosting_binvar[neighbor_comp] try: self.discovery.register_computation(neighbor_comp, neighbor_agt, publish=False) except UnknownAgent: # If we don't know this agent yet, we must perform a lookup # and only register the computation once found. # Note the use of partial, to force the capture of # neighbor_comp. def _agt_lookup_done(comp, evt, evt_agt, _): if evt == 'agent_added': self.discovery.register_computation(comp, evt_agt, publish=False) self.discovery.subscribe_agent(neighbor_agt, partial( _agt_lookup_done, neighbor_comp), one_shot=True) self.logger.info( 'Repair setup done one %s, %s computations created, ' 'inform orchestrator', self.name, len(candidate_binvars)) return candidate_binvars
def test_create_several_binvariables_from_list(self): vars = create_binary_variables('x_', ['a1', 'a2', 'a3']) self.assertIn('x_a1', vars) self.assertTrue(isinstance(vars['x_a2'], BinaryVariable)) self.assertEqual(vars['x_a3'].name, 'x_a3')
def test_create_several_binvariables_from_list(self): variables = create_binary_variables("x_", ["a1", "a2", "a3"]) self.assertIn("x_a1", variables) self.assertTrue(isinstance(variables["x_a2"], BinaryVariable)) self.assertEqual(variables["x_a3"].name, "x_a3")