Ejemplo n.º 1
0
    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")
Ejemplo n.º 2
0
    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')
Ejemplo n.º 3
0
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
Ejemplo n.º 4
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
Ejemplo n.º 5
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
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
Ejemplo n.º 8
0
    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')
Ejemplo n.º 9
0
    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")