def test_remove_contact_node(): graph = ContactGraph() identifier1 = ContactIdentifier(from_node='a', to_node='b', from_time=0.0, to_time=10.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier1) identifier2 = ContactIdentifier(from_node='b', to_node='c', from_time=10.0, to_time=20.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier2) assert len(graph.graph) == 2 identifier_false = ('a', 'b', 0.0, 10.0, 1000.0, 0.0) # Check if exception is thrown if invalid identifier is used with pytest.raises(ValueError): graph.remove_contact_node(identifier_false) assert len(graph.graph) == 2 graph.remove_contact_node(identifier2) # Assert that node was removed properly and both the successor and # predecessor lists are empty assert len(graph.graph) == 1 assert not graph.graph[identifier1][0] assert not graph.graph[identifier1][1]
def generate_neighbors(old_neigbors): new_neighbors = list() for neighbor in old_neigbors: tp = list() for hops in neighbor[3]: tp.append( ContactIdentifier( from_node=hops[0], to_node=hops[1], from_time=hops[2], to_time=hops[3], datarate=hops[4], delay=hops[5])) route = Route( transmission_plan=tp, edt=neighbor[1], capacity=1000, to_time=10000, hops=neighbor[2], next_hop=ContactIdentifier(*neighbor[0])) new_neighbor = Neighbor( contact=route.next_hop, node_id=route.next_hop.to_node, route=route) new_neighbors.append(new_neighbor) return new_neighbors
def test_reinitialize(): graph = ContactGraph() identifier1 = ContactIdentifier(from_node='a', to_node='b', from_time=0.0, to_time=10.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier1) identifier2 = ContactIdentifier(from_node='b', to_node='c', from_time=10.0, to_time=20.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier2) assert len(graph.graph) == 2 graph.reinitialize() # Assert that all contacts related to 'b' were removed and the graph is # empty assert len(graph.graph) == 0
def test_add_contact_node(): graph = ContactGraph() identifier = ('a', 'b', 0.0, 10.0, 1000.0, 0.0) # Check if exception is thrown if invalid identifier is used with pytest.raises(ValueError): graph.add_contact_node(identifier) identifier1 = ContactIdentifier(from_node='a', to_node='b', from_time=0.0, to_time=10.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier1) assert len(graph.graph) == 1 identifier2 = ContactIdentifier(from_node='b', to_node='c', from_time=10.0, to_time=20.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier2) identifier3 = ContactIdentifier(from_node='d', to_node='c', from_time=10.0, to_time=20.0, datarate=1000.0, delay=0.0) graph.add_contact_node(identifier3) # Check that the added nodes are properly connected # (predecessors and successors) assert len(graph.graph) == 3 assert identifier2 in graph.graph[identifier1][0] assert len(graph.graph[identifier1][0]) == 1 assert len(graph.graph[identifier1][1]) == 0 assert identifier1 in graph.graph[identifier2][1] assert len(graph.graph[identifier2][1]) == 1 assert len(graph.graph[identifier2][0]) == 0 assert len(graph.graph[identifier3][0]) == 0 assert len(graph.graph[identifier3][1]) == 0
def test_create_from_contact_plan(): plan = ContactPlan(1000, 0) plan.add_contact('a', 'b', 0.0, 10.0, 1000.0, 0.0, bidirectional=False) plan.add_contact('b', 'c', 0.0, 30.0, 1000.0, 0.0, bidirectional=False) plan.add_contact('a', 'c', 20.0, 30.0, 1000.0, 0.0, bidirectional=False) # Check if exception is thrown if invalid identifier is used with pytest.raises(ValueError): graph = ContactGraph([]) graph = ContactGraph(plan) assert len(graph.graph) == 6 id1 = ContactIdentifier('a', 'b', 0.0, 10.0, 1000.0, 0.0) id2 = ContactIdentifier('b', 'c', 0.0, 30.0, 1000.0, 0.0) id3 = ContactIdentifier('a', 'c', 20.0, 30.0, 1000.0, 0.0) assert id2 in graph.graph[id1][0] assert id1 in graph.graph[id2][1] assert len(graph.graph[id1][0]) == 2 assert len(graph.graph[id1][1]) == 1 assert len(graph.graph[id2][0]) == 1 assert len(graph.graph[id2][1]) == 2 assert len(graph.graph[id3][0]) == 1 assert len(graph.graph[id3][1]) == 1 # Verify number of root node vertices assert len(graph.graph[('a', 'a', 0, math.inf, math.inf, 0)][0]) == 2 assert len(graph.graph[('b', 'b', 0, math.inf, math.inf, 0)][0]) == 1 assert len(graph.graph[('c', 'c', 0, math.inf, math.inf, 0)][0]) == 0 # Verify that vertice list of contact nodes contains vertices to correct # terminal nodes assert ('a', 'a', 0, math.inf, math.inf, 0) in graph.graph[id1][1] assert ('b', 'b', 0, math.inf, math.inf, 0) in graph.graph[id1][0] assert ('b', 'b', 0, math.inf, math.inf, 0) in graph.graph[id2][1] assert ('c', 'c', 0, math.inf, math.inf, 0) in graph.graph[id2][0] assert ('a', 'a', 0, math.inf, math.inf, 0) in graph.graph[id3][1] assert ('c', 'c', 0, math.inf, math.inf, 0) in graph.graph[id3][0]
def create_route(old_route): plan = list() for contact in old_route[0]: plan.append( ContactIdentifier( from_node=contact[0], to_node=contact[1], from_time=contact[2], to_time=contact[3], datarate=contact[4], delay=contact[5])) new_route = Route( transmission_plan=plan, edt=old_route[1][0], capacity=old_route[1][1], to_time=old_route[1][2], hops=len(old_route[0]), next_hop=plan[0]) return new_route
def load_route_list(contact_graph, source_node, destination_node, current_time): """Generate a list of all feasible routes. Routes are calculated from the source node to the destination node. Args: contact_graph (dict): The topology information in the form of a contact graph. source_node (string): The identifier of the source node (where the routing decision is performed) destination_node (type): The identifier of the destination node. current_time (int): Time in the simulation when the calculation is performed (in ms). Raises: ValueError: If no matching limit contact can be found due to mismatching to_times. Returns: list: A list of all feasible routes in the form of ``(route, route_characteristics)`` """ # Initialize route list route_list = list() # Start by setting the anchorContact to empty anchor_contact = None # Create an empty list to house all suppressed contacts suppressed_contact_list = list() # Generate root_contact terminal node definition root_contact = ContactIdentifier( from_node=source_node, to_node=source_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Generate bundle's destination terminal node definition destination_contact = ContactIdentifier( from_node=destination_node, to_node=destination_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Main loop (which is left when there are no more routes to be considered # between the source node and the bundle's destination) while True: # Determine the shortest route (transmission plan) to the bundle's # destination in the contact graph representation of the contact plan transmission_plan, distance = dijkstra.get_best_route( root_contact, destination_contact, contact_graph, cgr_utils.cgr_neighbor_function, current_time, suppressed_contacts=suppressed_contact_list, hashes=contact_graph.hashes) # End the route finding process if no route is found. This means no # more routes are available in the contact graph if transmission_plan is None: break # Remove unnecessary root and terminal nodes from the transmission # plan transmission_plan = transmission_plan[1:-1] # Generate route characteristics and add this information to the route edt, cap, to_time = cgr_utils.cgr_get_route_characteristics( transmission_plan, distance) # Extract the first hop of the found route first_contact = transmission_plan[0] # If the anchor contact is set already, but not to recently found first # hop, then iterate over the contact graph (i.e. contact plan) and # initialize (and reset the suppress flag for all contact except the # ones where the source node is the source) if anchor_contact is not None and anchor_contact is not first_contact: # Reset the suppress flag for all contacts except the ones # where the source node is the source for contact in contact_graph.graph.keys(): if (contact[0] != source_node and contact in suppressed_contact_list): suppressed_contact_list.remove(contact) # Supress the anchor node for future iterations of the loop suppressed_contact_list.append(anchor_contact) anchor_contact = None continue # Assign the found route as option for the bundle's destination to the # route list route = Route( transmission_plan=transmission_plan, next_hop=transmission_plan[0].to_node, edt=edt, capacity=cap, to_time=to_time, hops=len(transmission_plan)) # Add route to route list route_list.append(route) # If the end time of the overall route is the end time of the first # contact of that route (i.e. the first hop), regard to the first # contact (hop) as the limiting contact (the contact that ends first # and thus renders the entire route invalid) if to_time == first_contact.to_time: limit_contact = first_contact # Otherwise select the first contact as anchor contact and iterate over # all remaining hops to determine the limit contact else: anchor_contact = first_contact limit_contact = None for contact in transmission_plan: if contact.to_time == to_time: limit_contact = contact break # Raise error if we couldn't find a contact matching to the calculated # to_time if limit_contact is None: raise ValueError("The calculated to_time of the route does not" + " match any contacts to_time!") # Supress the limit contact as we have already found the best route for # that limiting contact and worse routes with that limiting contact # should not be considered (realised by adding the contact to the # suppressed list) suppressed_contact_list.append(limit_contact) # Return the route list with the updated routes for the bundle's # destination node return route_list
def find_critical_bundle_neighbors(contact_graph, source_node, destination_node, excluded_nodes, current_time): """Determine all feasible neighbor nodes for forwarding a critical bundle. Args: contact_graph (dict): Topology information as contact graph. source_node (string): Node where this routing operation is performed. destination_node (string): Destination node of the packet. excluded_nodes (type): List of nodes that should not be considered for forwarding. current_time (int): Time in the simulation that the calculation is performed (in ms). Returns: list: A list of all feasible neighbor nodes in the form `(neighbor, distance, hop count, route)` """ # Initialize route list neighbor_list = list() # Generate root_contact terminal node definition root_contact = ContactIdentifier( from_node=source_node, to_node=source_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Generate bundle's destination terminal node definition destination_contact = ContactIdentifier( from_node=destination_node, to_node=destination_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Initialize the suppressed contacts list suppressed_contacts = [] for neighbor in contact_graph.graph[root_contact][0]: # Ignore neighbors that are in the excluded nodes list if neighbor in excluded_nodes: continue # Determine all neigbors that are ignored for this evaluation run blocked_neigbors = copy.deepcopy(contact_graph \ .graph[root_contact][0]) blocked_neigbors.remove(neighbor) suppressed_contacts = blocked_neigbors # Determine the shortest route to the bundle's destination in the # contact graph representation of the contact plan transmission_plan, distance = dijkstra.get_best_route( root_contact, destination_contact, contact_graph, cgr_utils.cgr_neighbor_function, current_time, hashes=contact_graph.hashes, suppressed_contacts=suppressed_contacts) # End the route finding process if no route is found. This means no # more routes are available in the contact graph if transmission_plan is None: continue # Remove unnecessary root and terminal nodes from the route transmission_plan = transmission_plan[1:-1] # Generate route characteristics and add this information to the route edt, cap, to_time = cgr_utils.cgr_get_route_characteristics( transmission_plan, distance) # Assign the found route as option for the bundle's destination to the # route list route = Route( transmission_plan=transmission_plan, next_hop=transmission_plan[0].to_node, edt=edt, capacity=cap, to_time=to_time, hops=len(transmission_plan)) # Assign the found route as option for the bundle's destination to the # route list neighbor_list.append((route.transmission_plan[0], route)) del blocked_neigbors return neighbor_list
def generate_next_route(contact_graph, source_node, destination_node, excluded_contacts, current_time, avg_rdt): """Generate an additional (next best) route to destination. Args: contact_graph (ContactGraph): A ContactGraph object for topology information. source_node (string): The node where the routing operation takes place destination_node (string): The destinatio node that routes should be provided for. excluded_contacts (list): A list of contacts that should not be considered during the route finding procedure. Used to mimic the removal of nodes within the graph in a more memory-efficient way. current_time (int): The simulated time when the route calculation is performed (in ms). avg_rdt (recordclass): A recordclass object holding the relevant values for determining and updating the dijkstra lookahead window. Raises: ValueError: If no matching limit contact can be found due to mismatching to_times. Returns: tuple: Provides a list of new routes and a list of excluded nodes. The excluded nodes were generated while finding the routes. They should not be considered in later searches! """ # Generate root_contact terminal node definition root_contact = ContactIdentifier( from_node=source_node, to_node=source_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Generate bundle's destination terminal node definition destination_contact = ContactIdentifier( from_node=destination_node, to_node=destination_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) if avg_rdt.mean >= 0: lookahead_time = int(current_time + avg_rdt.mean * 1.2) else: lookahead_time = 8000 # Determine the shortest route to the bundle's destination in the # contact graph representation of the contact plan with a lookahead # window limited to the 120% of the previously observed mean delivery # times. transmission_plan, distance = dijkstra.get_best_route( root_contact, destination_contact, contact_graph, cgr_utils.cgr_neighbor_function, current_time, hashes=contact_graph.hashes, suppressed_contacts=excluded_contacts, lookahead_time=lookahead_time) # If no route has been found in the lookahead window, fall back to the # version without the window. if transmission_plan is None: # Determine the shortest route to the bundle's destination in the # contact graph representation of the contact plan without any # lookahead window. transmission_plan, distance = dijkstra.get_best_route( root_contact, destination_contact, contact_graph, cgr_utils.cgr_neighbor_function, current_time, hashes=contact_graph.hashes, suppressed_contacts=excluded_contacts) if transmission_plan is None: # End the route finding process if no route is found. This means no # more routes are available in the contact graph return None, None avg_rdt.window_miss += 1 avg_rdt.count += 1 avg_rdt.mean += (distance - current_time) / avg_rdt.count else: avg_rdt.window_hit += 1 avg_rdt.count += 1 avg_rdt.mean += (distance - current_time) / avg_rdt.count # Remove unnecessary root and terminal nodes from the route transmission_plan = transmission_plan[1:-1] # Generate route characteristics and add this information to the route edt, cap, to_time = cgr_utils.cgr_get_route_characteristics( transmission_plan, distance) # Assign the found route as option for the bundle's destination to the # route list route = Route( transmission_plan=transmission_plan, next_hop=transmission_plan[0].to_node, edt=edt, capacity=cap, to_time=to_time, hops=len(transmission_plan)) # If the end time of the overall route is the end time of the first # contact of that route (i.e. the first hop), regard to the first # contact (hop) as the limiting contact (the contact that ends first # and thus renders the entire route invalid) limit_contact = None for contact in route.transmission_plan: if contact.to_time == route.to_time: limit_contact = contact new_excluded_contact = limit_contact break # Raise error if we couldn't find a contact matching to the calculated # to_time if limit_contact is None: raise ValueError("The calculated to_time of the route does not" + " match any contacts to_time!") # Supress the limit contact as we have already found the best route for # that limiting contact and worse routes with that limiting contact # should not be considered (realised by setting the suppressed flag in # the working area of the contact) # Return the route list with the updated routes for the bundle's # destination node return route, new_excluded_contact
def load_route_list(contact_graph, source_node, destination_node, current_time): """Generate a list of all feasible routes. Routes are calculated from the source node to the destination node. Args: contact_graph (dict): The topology information in the form of a contact graph. source_node (string): The identifier of the source node (where the routing decision is performed) destination_node (sting): The identifier of the destination node. current_time (int): Time in the simulation when the calculation is performed (in ms). Raises: ValueError: If no matching limit contact can be found due to mismatching to_times. Returns: list: A list of all feasible routes in the form of (route, route_characteristics) """ # Initialize route list route_list = list() # Generate root_contact terminal node definition root_contact = ContactIdentifier( from_node=source_node, to_node=source_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Generate bundle's destination terminal node definition destination_contact = ContactIdentifier( from_node=destination_node, to_node=destination_node, from_time=0, to_time=math.inf, datarate=math.inf, delay=0) # Initialize the list of excluded contacts suppressed_contacts = [] # Main loop (which is left when there are no more routes to be considered # between the source node and the bundle's destination) while True: # Determine the shortest route to the bundle's destination in the # contact graph representation of the contact plan transmission_plan, distance = dijkstra.get_best_route( root_contact, destination_contact, contact_graph, cgr_utils.cgr_neighbor_function, current_time, hashes=contact_graph.hashes, suppressed_contacts=suppressed_contacts) # End the route finding process if no route is found. This means no # more routes are available in the contact graph if transmission_plan is None: break # Remove unnecessary root and terminal nodes from the route transmission_plan = transmission_plan[1:-1] # Generate route characteristics and add this information to the route edt, cap, to_time = cgr_utils.cgr_get_route_characteristics( transmission_plan, distance) # Assign the found route as option for the bundle's destination to the # route list route = Route( transmission_plan=transmission_plan, next_hop=transmission_plan[0].to_node, edt=edt, capacity=cap, to_time=to_time, hops=len(transmission_plan)) route_list.append(route) limit_contact = None for contact in route.transmission_plan: if contact.to_time == route.to_time: limit_contact = contact break # Raise error if we couldn't find a contact matching to the calculated # to_time if limit_contact is None: raise ValueError("The calculated to_time of the route does not" + " match any contacts to_time!") # Remove the limit contact as we have already found the best route for # that limiting contact and worse routes with that limiting contact # should not be considered. As the temp graph is not used beyond # this iterative search, no side effects occur. suppressed_contacts.append(limit_contact) # Return the route list with the updated routes for the bundle's # destination node return route_list