Source code for mango.express.topology

from contextlib import contextmanager

import networkx as nx

from mango.agent.core import Agent, AgentAddress, State, TopologyService

AGENT_NODE_KEY = "node"
STATE_EDGE_KEY = "state"


def _flatten_list(list):
    return [x for xs in list for x in xs]


class AgentNode:
    def __init__(self, agents: list = None) -> None:
        self.agents: list[Agent] = [] if agents is None else agents

    def add(self, agent: Agent):
        self.agents.append(agent)


[docs] class Topology: def __init__(self, graph) -> None: self.graph: nx.Graph = graph for node in self.graph.nodes: self.graph.nodes[node][AGENT_NODE_KEY] = AgentNode() for edge in self.graph.edges: self.graph.edges[edge][STATE_EDGE_KEY] = State.NORMAL
[docs] def set_edge_state(self, node_id_from: int, node_id_to: int, state: State): self.graph.edges[node_id_from, node_id_to] = state
[docs] def add_node(self, *agents: Agent): id = len(self.graph) self.graph.add_node(id, node=AgentNode(agents)) return id
[docs] def add_edge(self, node_from, node_to, state: State = State.NORMAL): self.graph.add_edge(node_from, node_to, state=state)
@property def agents(self) -> list[Agent]: """Return all agents controlled by the topology after the neighborhood were injected. :return: the list of agents :rtype: list[Agent] """ return _flatten_list( [self.graph.nodes[node][AGENT_NODE_KEY].agents for node in self.graph.nodes] )
[docs] def inject(self): # 2nd pass, build the neighborhoods and add it to agents for node in self.graph.nodes: agent_node = self.graph.nodes[node][AGENT_NODE_KEY] state_to_neighbors: dict[State, list[AgentAddress]] = dict() for neighbor in self.graph.neighbors(node): state = self.graph.edges[node, neighbor][STATE_EDGE_KEY] neighbor_addresses = state_to_neighbors.setdefault(state, []) neighbor_addresses.extend( [ lambda: agent.addr for agent in self.graph.nodes[neighbor][AGENT_NODE_KEY].agents ] ) for agent in agent_node.agents: topology_service = agent.service_of_type( TopologyService, TopologyService() ) topology_service.state_to_neighbors = state_to_neighbors
[docs] def complete_topology(number_of_nodes: int) -> Topology: """ Create a fully-connected topology. """ graph = nx.complete_graph(number_of_nodes) return Topology(graph)
[docs] def custom_topology(graph: nx.Graph) -> Topology: """ Create an already created custom topology base on a nx Graph. """ return Topology(graph)
[docs] @contextmanager def create_topology(directed: bool = False): """Create a topology, which will automatically inject the neighbors to the participating agents. Example: .. code-block:: python agents = [TopAgent(), TopAgent(), TopAgent()] with create_topology() as topology: id_1 = topology.add_node(agents[0]) id_2 = topology.add_node(agents[1]) id_3 = topology.add_node(agents[2]) topology.add_edge(id_1, id_2) topology.add_edge(id_1, id_3) :param directed: _description_, defaults to False :type directed: bool, optional :yield: _description_ :rtype: _type_ """ topology = Topology(nx.DiGraph() if directed else nx.Graph()) try: yield topology finally: topology.inject()
[docs] def per_node(topology: Topology): """Assign agents per node of the already created topology. This method shall be used as iterator in a for in construct. The iterator will return nodes, which can be used to add (with node.add()) agents to the node. .. code-block:: python topology = complete_topology(3) for node in per_node(topology): node.add(TopAgent()) :param topology: the topology :type topology: Topology :yield: AgentNode :rtype: _type_ """ for node in topology.graph.nodes: agent_node = topology.graph.nodes[node][AGENT_NODE_KEY] yield agent_node topology.inject()