def lp_model(cg: ComputationGraph, agentsdef: Iterable[AgentDef], footprint: Callable[[str], float], capacity: Callable[[str], float], route: Callable[[str, str], float], msg_load: Callable[[str, str], float], hosting_cost: Callable[[str, str], float]): comp_names = [n.name for n in cg.nodes] agt_names = [a.name for a in agentsdef] pb = LpProblem('ilp_compref', LpMinimize) # One binary variable xij for each (variable, agent) couple xs = LpVariable.dict('x', (comp_names, agt_names), cat=LpBinary) # One binary variable for computations c1 and c2, and agent a1 and a2 betas = {} count = 0 for a1, a2 in combinations(agt_names, 2): # Only create variables for couple c1, c2 if there is an edge in the # graph between these two computations. for l in cg.links: # As we support hypergraph, we may have more than 2 ends to a link for c1, c2 in combinations(l.nodes, 2): count += 2 b = LpVariable('b_{}_{}_{}_{}'.format(c1, a1, c2, a2), cat=LpBinary) betas[(c1, a1, c2, a2)] = b pb += b <= xs[(c1, a1)] pb += b <= xs[(c2, a2)] pb += b >= xs[(c2, a2)] + xs[(c1, a1)] - 1 b = LpVariable('b_{}_{}_{}_{}'.format(c1, a2, c2, a1), cat=LpBinary) betas[(c1, a2, c2, a1)] = b pb += b <= xs[(c2, a1)] pb += b <= xs[(c1, a2)] pb += b >= xs[(c1, a2)] + xs[(c2, a1)] - 1 # Set objective: communication + hosting_cost pb += _objective(xs, betas, route, msg_load, hosting_cost), \ 'Communication costs and prefs' # Adding constraints: # Constraints: Memory capacity for all agents. for a in agt_names: pb += lpSum([footprint(i) * xs[i, a] for i in comp_names])\ <= capacity(a), \ 'Agent {} capacity'.format(a) # Constraints: all computations must be hosted. for c in comp_names: pb += lpSum([xs[c, a] for a in agt_names]) == 1, \ 'Computation {} hosted'.format(c) # solve using GLPK status = pb.solve(solver=GLPK_CMD(keepFiles=1, msg=False, options=['--pcost'])) if status != LpStatusOptimal: raise ImpossibleDistributionException("No possible optimal" " distribution ") logger.debug('GLPK cost : %s', value(pb.objective)) # print('BETAS:') # for c1, a1, c2, a2 in betas: # print(' ', c1, a1, c2, a2, value(betas[(c1, a1, c2, a2)])) # # print('XS:') # for c, a in xs: # print(' ', c, a, value(xs[(c, a)])) mapping = {} for k in agt_names: agt_computations = [i for i, ka in xs if ka == k and value(xs[(i, ka)]) == 1] # print(k, ' -> ', agt_computations) mapping[k] = agt_computations return mapping
def distribute_factors( agents: Dict[str, AgentDef], cg: ComputationGraph, footprints: Dict[str, float], mapping: Dict[str, List[str]], msg_load: Callable[[str, str], float], ) -> Dict[str, List[str]]: """ Optimal distribution of factors on agents. Parameters ---------- cg: computations graph agents: dict a dict {agent_name : AgentDef} containing all available agents Returns ------- a dict { agent_name: list of factor names} """ pb = LpProblem("ilp_factors", LpMinimize) # build the inverse mapping var -> agt inverse_mapping = {} # type: Dict[str, str] for a in mapping: inverse_mapping[mapping[a][0]] = a # One binary variable xij for each (variable, agent) couple factor_names = [n.name for n in cg.nodes if isinstance(n, FactorComputationNode)] xs = LpVariable.dict("x", (factor_names, agents), cat=LpBinary) logger.debug("Binary variables for factor distribution : %s", xs) # Hard constraints: respect agent's capacity for a in agents: # Footprint of the variable this agent is already hosting: v_footprint = footprints[mapping[a][0]] pb += ( lpSum([footprints[fn] * xs[fn, a] for fn in factor_names]) <= (agents[a].capacity - v_footprint), "Agent {} capacity".format(a), ) # Hard constraints: all computations must be hosted. for c in factor_names: pb += lpSum([xs[c, a] for a in agents]) == 1, "Factor {} hosted".format(c) # 1st objective : minimize communication costs: comm = LpAffineExpression() for (fn, an_f) in xs: for vn in cg.neighbors(fn): an_v = inverse_mapping[vn] # agt hosting neighbor var vn comm += agents[an_f].route(an_v) * msg_load(vn, fn) * xs[(fn, an_f)] # 2st objective : minimize hosting costs hosting = lpSum([agents[a].hosting_cost(c) * xs[(c, a)] for c, a in xs]) # agregate the two objectives using RATIO_HOST_COMM pb += lpSum([RATIO_HOST_COMM * comm, (1 - RATIO_HOST_COMM) * hosting]) # solve using GLPK and convert to mapping { agt_name : [factors names]} status = pb.solve(solver=GLPK_CMD(keepFiles=1, msg=False, options=["--pcost"])) if status != LpStatusOptimal: raise ImpossibleDistributionException( "No possible optimal distribution for factors" ) logger.debug("GLPK cost : %s", value(pb.objective)) mapping = {} # type: Dict[str, List[str]] for k in agents: agt_computations = [i for i, ka in xs if ka == k and value(xs[(i, ka)]) == 1] # print(k, ' -> ', agt_computations) mapping[k] = agt_computations logger.debug("Factors distribution : %s ", mapping) return mapping