def depth_first_search(network: Network, node_id: int = None) -> bool: """ Returns bool indicating graph connectivity (path between all nodes). This is an iterative DFS. :param network: object representing a graph (network) :param int node_id: identifies node where search will start (None by default) :return: bool indicating graph connectivity :rtype: bool """ nodes_encountered: Set[Optional[int]] = set() if not node_id: node_id = network.nodes()[0] stack = [node_id] while stack: node = stack.pop() if node not in nodes_encountered: nodes_encountered.add(node) stack.extend(network.network_dict[node].get_adjacents()) if len(nodes_encountered) != len(network.nodes()): return False else: return True
def test_mark_node_inactive(): node1 = Node(1, adjacency_dict={2: {'weight': 3, 'status': True}}) node2 = Node(2, adjacency_dict={1: {'weight': 3, 'status': True}}) test_net = Network({1: node1, 2: node2}) assert len(test_net.network_dict) is len(test_net.nodes()) assert node2.node_id in test_net.network_dict[node1.node_id].adjacency_dict assert node1.node_id in test_net.network_dict[node2.node_id].adjacency_dict # test marking existing active node as inactive test_net.mark_node_inactive(1) assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is not len(test_net.nodes()) assert not node1.status assert node2.status assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert node2.node_id in test_net.network_dict[node1.node_id].adjacency_dict assert node1.node_id in test_net.network_dict[node2.node_id].adjacency_dict # test already inactive node test_net.mark_node_inactive(1) assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is not len(test_net.nodes()) assert not node1.status assert node2.status assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert node2.node_id in test_net.network_dict[node1.node_id].adjacency_dict assert node1.node_id in test_net.network_dict[node2.node_id].adjacency_dict # test node that doesn't exist test_net.mark_node_inactive(3) assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is not len(test_net.nodes())
def test_nodes(): node1 = Node(1, adjacency_dict={2: {'weight': 3, 'status': True}}) node2 = Node(2, adjacency_dict={1: {'weight': 3, 'status': True}}) test_net = Network({1: node1, 2: node2}) # test graph with 2 active nodes assert len(test_net.nodes()) is 2 assert len(test_net.nodes()) is len(test_net.network_dict) assert node1.node_id in test_net.nodes() assert node2.node_id in test_net.nodes() # test edge deactivation test_net.mark_edge_inactive(node1.node_id, node2.node_id) assert len(test_net.nodes()) is 2 assert len(test_net.nodes()) is len(test_net.network_dict) assert node1.node_id in test_net.nodes() assert node2.node_id in test_net.nodes() # deactivate 1 node and test test_net.mark_node_inactive(node1.node_id) assert len(test_net.nodes()) is 1 assert len(test_net.nodes()) is not len(test_net.network_dict) assert len(test_net.network_dict) is 2 assert node1.node_id not in test_net.nodes() assert node2.node_id in test_net.nodes() # deactivate last node test_net.mark_node_inactive(node2.node_id) assert not test_net.nodes() assert len(test_net.nodes()) is not len(test_net.network_dict) assert len(test_net.network_dict) is 2 assert node1.node_id not in test_net.nodes() assert node2.node_id not in test_net.nodes()
def test_add_node(): node1 = Node(1) test_net = Network() # add node to empty graph assert len(test_net.nodes()) is 0 test_net.add_node(node1) assert len(test_net.nodes()) is 1 assert node1.node_id in test_net.nodes() # attempt to add node that already exists test_net.add_node(node1) assert len(test_net.nodes()) is 1 assert node1.node_id in test_net.nodes()
def test_remove_node(): node1 = Node(1) node2 = Node(2) test_net = Network({1: node1, 2: node2}) # remove an existing node assert len(test_net.nodes()) is 2 test_net.remove_node(1) assert len(test_net.nodes()) is 1 assert node2.node_id in test_net.network_dict # remove a node that doesn't exist test_net.remove_node(3) assert len(test_net.nodes()) is 1 assert node2.node_id in test_net.network_dict
def generate_organs(graph: Network, n: int) -> List[Organ]: """ Harvests a random number of organs from n patients. Not all organs are harvested to represent organs that are not suitable for donation (health condition, etc Generates n patients to add to wait list with random combinations of organ needed, blood type, priority, and location :param Network graph: network where organs can be generated :param int n: number of of bodies to harvest organs from """ # list of currently active nodes nodes = graph.nodes() organs: List[Organ] = list() # number of patients to harvest from for _ in range(n): # number of possible organs to harvest location_id = random.choice(nodes) blood_type = BloodType(BloodTypeLetter.random_blood_type(), BloodTypePolarity.random_blood_polarity()) for organ_type in OrganType: # determines if organ is suitable for harvest if random.randrange(4) != 0: organs.append( Organ(organ_type=organ_type, blood_type=blood_type, location=location_id)) return organs
def is_connected(network: Network, nodes_encountered: Set[Optional[int]] = None, source: int = None) -> bool: """ Returns bool indicating graph connectivity (path between all nodes). This is a recursive DFS. :param network: object representing a graph (network) :param Set[int] nodes_encountered: set of node_id's encountered (None by default) :param int source: node_id of start of search (None by default) :return: bool indicating graph connectivity :rtype: bool """ if not nodes_encountered: nodes_encountered = set() nodes = network.nodes() if not source: # chose a vertex from network as start point source = nodes[0] nodes_encountered.add(source) if len(nodes_encountered) != len(nodes): for node in network.network_dict[source].get_adjacents(): if node not in nodes_encountered: if ConnectivityChecker.is_connected( network, nodes_encountered, node): return True else: return True return False
def mark_network_inactive(network: Network) -> None: """ Marks all nodes in a given network inactive (this is the starting point for the subnetwork) :param Network network: graph that is the foundation for subnetworks """ for node in network.nodes(): network.mark_node_inactive(node, feedback=False)
def breadth_first_search(network: Network, node_id: int = None) -> bool: """ Returns bool indicating graph connectivity (path between all nodes). This is an iterative BFS. :param network: object representing a graph (network) :param int node_id: identifies node where search will start (None by default) :return: bool indicating graph connectivity :rtype: bool """ # mark all the nodes as not visited (value is None) visited = dict.fromkeys(network.nodes()) nodes_encountered = set() queue = [] if not node_id: node_id = network.nodes()[0] # mark the source node as visited and enqueue it queue.append(node_id) visited[node_id] = True nodes_encountered.add(node_id) while queue: # dequeue a node from queue and add to nodes_encountered node_id = queue.pop(0) nodes_encountered.add(node_id) # all adjacents of current node are checked, if the node hasn't been # enqueued previously, node is enqueued and added to nodes_encountered for node in network.network_dict[node_id].get_adjacents(): if not visited[node]: queue.append(node) visited[node] = True nodes_encountered.add(node) # if size of nodes_encountered equals size of nodes(), return True if len(nodes_encountered) == len(network.nodes()): return True else: return False
def test_mark_edge_inactive(): node1 = Node(1, adjacency_dict={2: {'weight': 3, 'status': True}}) node2 = Node(2, adjacency_dict={1: {'weight': 3, 'status': True}}) test_net = Network({1: node1, 2: node2}) # test existing nodes with shared, active edge test_net.mark_edge_inactive(node1.node_id, node2.node_id) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert not node1.get_adjacents() assert not node2.get_adjacents() assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is len(test_net.nodes()) # test existing nodes with shared, inactive edge test_net.mark_edge_inactive(node1.node_id, node2.node_id) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert not node1.get_adjacents() assert not node2.get_adjacents() assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is len(test_net.nodes()) # test existing nodes with shared, inactive edge test_net.add_node(Node(3)) test_net.mark_edge_inactive(node1.node_id, 3) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert not node1.get_adjacents() assert not node2.get_adjacents() assert len(test_net.network_dict) is 3 assert len(test_net.network_dict) is len(test_net.nodes()) # test with nonexistent node test_net.add_node(Node(3)) test_net.mark_edge_inactive(node1.node_id, 4) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert not node1.get_adjacents() assert not node2.get_adjacents() assert len(test_net.network_dict) is 3 assert len(test_net.network_dict) is len(test_net.nodes())
def dijkstra(graph: Network, source: int) -> dijkstra_structure: """ This function finds the shortest path to all connected nodes from a given source node. The return are two dicts: 1) the weight from source to the key 2) the node preceding the key This function is intended to be used by the initializer when creating and Dijkstra instance. The output will be stored as attributes of the object. These structures can be referenced later to determine the shortest path (and cost) to a given destination :param graph: network which will be used for path finding :param source: source node (where all paths will start) :return: weight: dict represented as {node_id: <weight>, ...} :return: previous: dict represented as {node_id: <previous_node_id>, ...} """ unvisited = graph.nodes() weight = dict.fromkeys(graph.nodes(), float('inf')) previous: Dict[int, Union[int, None]] = dict.fromkeys(graph.nodes(), None) weight[source] = 0 # while there are nodes which haven't been visited while unvisited: # picks the unvisited node with the shortest weight to visit next current_node = Dijkstra.minimum_unvisited_distance( unvisited, weight) unvisited.remove(current_node) # looks at all of the current node's adjacents for adjacent in graph.network_dict[current_node].get_adjacents(): alt_weight = weight[current_node] + \ graph.network_dict[current_node].adjacency_dict[adjacent]['weight'] # if this adjacent has a lower weight than currently recorded weight, replace it if alt_weight < weight[adjacent]: weight[adjacent] = alt_weight previous[adjacent] = current_node return weight, previous
def convert_to_networkx(network: Network, is_regional_weight: bool = None) -> nx.Graph: """ Converts a Network object to a NetworkX graph :param Network network: :param bool is_regional_weight: indicates whether weight metric should be regional weight or distance :return: NetworkX Graph """ nx_graph = nx.Graph() nodes = network.nodes() for node in nodes: nx_graph.add_node(node) GraphConverter.add_edges(network, node, nx_graph, is_regional_weight) return nx_graph
def generate_patients(graph: Network, n: int) -> List[Patient]: """ Generates n patients to add to wait list with random combinations of organ needed, blood type, priority, and location :param Network graph: network for patients to be allocated to :param int n: number of patients to generate """ # list of currently active nodes nodes = graph.nodes() patients: List[Patient] = list() for x in range(n): patients.append( Patient(patient_name="generated patient #" + str(x + 1), illness="N/A", organ_needed=OrganType.random_organ_type(), blood_type=BloodType( BloodTypeLetter.random_blood_type(), BloodTypePolarity.random_blood_polarity()), priority=random.randrange(100 + n), location=random.choice(nodes))) return patients
def convert_to_attribute_nx(network: Network, is_regional_weight: bool = None, state_dict=None, region_dict=None) -> nx.Graph: """ Converts a Network object to a NetworkX graph :param Network network: :param bool is_regional_weight: indicates whether weight metric should be regional weight or distance :param dict state_dict: quantifies how many hospitals share the state :param dict region_dict: quantifies how many hospitals share the region :return: NetworkX Graph """ nx_graph = nx.Graph() nodes = network.nodes() for node_id in nodes: node = network.network_dict[node_id] label = node.label city = node.city state = node.state region = node.region state = 'Washington D.C.' if state == 'US' else state nx_graph.add_node(node_id, hospital_name=label, city=city, state=state, region=region, state_count=state_dict[state], region_count=region_dict[region]) GraphConverter.add_edges(network, node_id, nx_graph, is_regional_weight) return nx_graph
def test_mark_node_active(): node1 = Node(1, adjacency_dict={2: { 'weight': 3, 'status': True }}, status=False) node2 = Node(2, adjacency_dict={1: { 'weight': 3, 'status': True }}, status=False) test_net = Network({1: node1, 2: node2}) assert not test_net.nodes() # test existing inactive node test_net.mark_node_active(1) assert node1.node_id in test_net.nodes() assert len(test_net.nodes()) is 1 assert not node1.adjacency_dict[node2.node_id]['status'] assert node1.status assert len(test_net.network_dict) is 2 # test existing active node test_net.mark_node_active(1) assert node1.node_id in test_net.nodes() assert len(test_net.nodes()) is 1 assert not node1.adjacency_dict[node2.node_id]['status'] assert node1.status assert len(test_net.network_dict) is 2 # test nonexistent node test_net.mark_node_active(3) assert node1.node_id in test_net.nodes() assert len(test_net.nodes()) is 1 assert not node1.adjacency_dict[node2.node_id]['status'] assert node1.status assert len(test_net.network_dict) is 2
'status': True } }) net = Network({ node1.node_id: node1, node2.node_id: node2, node3.node_id: node3, node4.node_id: node4, node5.node_id: node5 }) print(net) # iterates through all nodes in network_1, finds and prints shortest path and weight # manually calls each destination individually for node in network_1.nodes(): path, weight = dijkstra.shortest_path(node) print('The shortest path between Node %s#%d%s and Node %s#%d%s is:' '\n\t%-8s%s%s%s' '\n\t%-8s%s%d%s\n' % (ANSI_CYAN, dijkstra.source, ANSI_RESET, ANSI_CYAN, node, ANSI_RESET, 'Path: ', ANSI_YELLOW, path, ANSI_RESET, 'Weight: ', ANSI_YELLOW, weight, ANSI_RESET)) # shortest_paths contains all shortest paths from the source node # all paths stored in a single structure shortest_paths = dijkstra.all_shortest_paths() path, weight = shortest_paths[7] print(f'Shortest path from #{dijkstra.source} to #7:' f'\n\tPath: {path}' f'\n\tWeight: {weight}')
def test_mark_edge_active(): node1 = Node(1, adjacency_dict={2: {'weight': 3, 'status': False}}) node2 = Node(2, adjacency_dict={1: {'weight': 3, 'status': False}}) test_net = Network({1: node1, 2: node2}) # test existing, active nodes with shared, inactive edge test_net.mark_edge_active(node1.node_id, node2.node_id) assert node1.adjacency_dict[node2.node_id]['status'] assert node2.adjacency_dict[node1.node_id]['status'] assert len(node1.adjacency_dict) is 1 assert len(node2.adjacency_dict) is 1 assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is len(test_net.nodes()) # test existing, active nodes with shared, active edge test_net.mark_edge_active(node1.node_id, node2.node_id) assert node1.adjacency_dict[node2.node_id]['status'] assert node2.adjacency_dict[node1.node_id]['status'] assert len(node1.adjacency_dict) is 1 assert len(node2.adjacency_dict) is 1 assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is len(test_net.nodes()) # test existing, inactive node with shared, inactive edge test_net.mark_node_inactive(node1.node_id) test_net.mark_edge_active(node1.node_id, node2.node_id) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert len(node1.adjacency_dict) is 1 assert len(node2.adjacency_dict) is 1 assert len(test_net.network_dict) is 2 assert len(test_net.network_dict) is not len(test_net.nodes()) assert not node1.status assert node2.status # test existing nodes without shared edge test_net.add_node(Node(3)) test_net.mark_edge_active(node2.node_id, 3) test_net.mark_node_inactive(node1.node_id) test_net.mark_edge_active(node1.node_id, node2.node_id) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert len(node1.adjacency_dict) is 1 assert len(node2.adjacency_dict) is 1 assert len(test_net.network_dict) is 3 assert len(test_net.network_dict) is not len(test_net.nodes()) assert not node1.status assert node2.status # test node that doesn't exist test_net.mark_edge_active(node2.node_id, 4) test_net.add_node(Node(3)) test_net.mark_edge_active(node2.node_id, 3) test_net.mark_node_inactive(node1.node_id) test_net.mark_edge_active(node1.node_id, node2.node_id) assert not node1.adjacency_dict[node2.node_id]['status'] assert not node2.adjacency_dict[node1.node_id]['status'] assert len(node1.adjacency_dict) is 1 assert len(node2.adjacency_dict) is 1 assert len(test_net.network_dict) is 3 assert len(test_net.network_dict) is not len(test_net.nodes()) assert not node1.status assert node2.status