def distribute( computation_graph: ComputationsFactorGraph, agentsdef: Iterable[AgentDef], hints=None, computation_memory: Callable[[ComputationNode], float] = None, communication_load: Callable[[ComputationNode, str], float] = None, timeout=600, # Max 10 min ) -> Distribution: if computation_memory is None or communication_load is None: raise ImpossibleDistributionException( "oilp_secp_fgdp distribution requires " "computation_memory and link_communication functions" ) mapping = defaultdict(lambda: []) agents_capa = {a.name: a.capacity for a in agentsdef} variable_computations, factor_computations = [], [] for comp in computation_graph.nodes: if isinstance(comp, VariableComputationNode): variable_computations.append(comp.name) elif isinstance(comp, FactorComputationNode): factor_computations.append(comp.name) else: raise ImpossibleDistributionException( f"Error: {comp} is neither a factor nor a variable computation" ) # actuators variables and cost factor on the corresponding agent: for variable in variable_computations[:]: for agent in agentsdef: if agent.hosting_cost(variable) == 0: # Found an actuator variable, host it on the agent mapping[agent.name].append(variable) variable_computations.remove(variable) agents_capa[agent.name] -= computation_memory( computation_graph.computation(variable) ) # search for the cost factor, if any, and host it on the same agent. for factor in factor_computations[:]: if f"c_{variable}" == factor: mapping[agent.name].append(factor) factor_computations.remove(factor) agents_capa[agent.name] -= computation_memory( computation_graph.computation(factor) ) if agents_capa[agent.name] < 0: raise ImpossibleDistributionException( f"Not enough capacity on {agent} to hosts actuator {variable}: {agents_capa[agent.name]}" ) break logger.info(f"Actuator variables - agents: {dict(mapping)}") logger.info(f"Remaining capacity: {dict(agents_capa)}") return fg_secp_ilp( computation_graph, agentsdef, Distribution(mapping), computation_memory, communication_load, )
def secp_dist_objective_function(cg: ComputationsFactorGraph, communication_load, alphas, agents_names): # The objective function is the negated sum of the communication cost on # the links in the factor graph. return lpSum([ -communication_load(cg.computation(link.variable_node), link.factor_node) * alphas[((link.variable_node, link.factor_node), k)] for link in cg.links for k in agents_names ])
def distribution_cost( distribution: Distribution, computation_graph: ComputationsFactorGraph, agentsdef: Iterable[AgentDef], computation_memory: Callable[[ComputationNode], float], communication_load: Callable[[ComputationNode, str], float], ) -> float: """ Compute the cost of the distribution. Only takes communication costs into account (no hosting nor route costs). Parameters ---------- distribution computation_graph agentsdef computation_memory communication_load Returns ------- """ comm = 0 agt_names = [a.name for a in agentsdef] for l in computation_graph.links: # As we support hypergraph, we may have more than 2 ends to a link for c1, c2 in combinations(l.nodes, 2): if distribution.agent_for(c1) != distribution.agent_for(c2): edge_cost = communication_load( computation_graph.computation(c1), c2) logger.debug(f"edge cost between {c1} and {c2} : {edge_cost}") comm += edge_cost else: logger.debug( f"On same agent, no edge cost between {c1} and {c2}") # This distribution model only takes communication cost into account. # cost = RATIO_HOST_COMM * comm + (1-RATIO_HOST_COMM) * hosting return comm, comm, 0
def secp_computation_memory_in_cg(computation_name: str, cg: ComputationsFactorGraph, computation_memory): computation = cg.computation(computation_name) l = computation_memory(computation) return l
def distribute( computation_graph: ComputationsFactorGraph, agentsdef: Iterable[AgentDef], hints: DistributionHints = None, computation_memory: Callable[[ComputationNode], float] = None, communication_load: Callable[[ComputationNode, str], float] = None, timeout=None, # not used ) -> Distribution: if computation_memory is None: raise ImpossibleDistributionException("adhoc distribution requires " "computation_memory functions") # as we're dealing with a secp modelled as a factor graph, we have computations for # actuator and physical model variables, rules and physical model factors. mapping = defaultdict(lambda: []) agents_capa = {a.name: a.capacity for a in agentsdef} variable_computations = [] factor_computations = [] for comp in computation_graph.nodes: if isinstance(comp, VariableComputationNode): variable_computations.append(comp.name) elif isinstance(comp, FactorComputationNode): factor_computations.append(comp.name) else: raise ImpossibleDistributionException( f"Error: {comp} is neither a factor nor a variable computation" ) # First, put each actuator variable and cost factor on its agent for variable in variable_computations[:]: for agent in agentsdef: if agent.hosting_cost(variable) == 0: # Found an actuator variable, host it on the agent mapping[agent.name].append(variable) variable_computations.remove(variable) agents_capa[agent.name] -= computation_memory( computation_graph.computation(variable)) # search for the cost factor, if any, and host it on the same agent. for factor in factor_computations[:]: if f"c_{variable}" == factor: mapping[agent.name].append(factor) factor_computations.remove(factor) agents_capa[agent.name] -= computation_memory( computation_graph.computation(factor)) if agents_capa[agent.name] < 0: raise ImpossibleDistributionException( f"Not enough capacity on {agent} to hosts actuator {variable}: {agents_capa[agent.name]}" ) break logger.info(f"Actuator computations - agents: {dict(mapping)}") logger.info(f"Remaining capacity: {dict(agents_capa)}") # now find computations for physical models and variables variables. # * all remaining variables are model variables # * physical model factor computation names contain the name of the variable model_variables = variable_computations models = [] for model_var in model_variables: for fact in factor_computations: if f"c_{model_var}" == fact: models.append((model_var, fact)) factor_computations.remove(fact) # All remaining factor ar rule factors rule_factors = factor_computations logger.debug(f"Physical models: {models}") logger.debug(f"Rules: {rule_factors}") # Now place models for model_var, model_fac in models: footprint = computation_memory( computation_graph.computation(model_fac)) + computation_memory( computation_graph.computation(model_var)) neighbors = computation_graph.neighbors(model_fac) candidates = find_candidates(agents_capa, model_fac, footprint, mapping, neighbors) # Host the model on the first agent and decrease its remaining capacity selected = candidates[0][2] mapping[selected].append(model_var) mapping[selected].append(model_fac) agents_capa[selected] -= footprint logger.debug(f"All models hosted: {dict(mapping)}") logger.debug(f"Remaining capacity: {agents_capa}") # And rules at last: for rule_fac in rule_factors: footprint = computation_memory(computation_graph.computation(rule_fac)) neighbors = computation_graph.neighbors(rule_fac) candidates = find_candidates(agents_capa, rule_fac, footprint, mapping, neighbors) # Host the computation on the first agent and decrease its remaining capacity selected = candidates[0][2] mapping[selected].append(rule_fac) agents_capa[selected] -= footprint return Distribution({a: list(mapping[a]) for a in mapping})