scgraph.spanning

  1from heapq import heappush, heappop
  2
  3
  4class SpanningTree:
  5    @staticmethod
  6    def makowskis_spanning_tree(graph: list[dict], node_id: int) -> dict:
  7        """
  8        Function:
  9
 10        - Calculate the spanning tree of a graph using Makowski's modified Dijkstra algorithm
 11            - Modifications allow for a sparse distance matrix to be used instead of a dense distance matrix
 12            - Improvements include only computing future potential nodes based on the open leaves for each branch
 13                - Open leaves are nodes that have not been visited yet but are adjacent to other visited nodes
 14            - This can dramatically reduce the memory and compute requirements of the algorithm
 15            - For particularly sparse graphs, this algorithm runs close to O(n log n) time
 16                - Where n is the number of nodes in the graph
 17            - For dense graphs, this algorithm runs closer to O(n^2) time (similar to the standard Dijkstra algorithm)
 18                - Where n is the number of nodes in the graph
 19
 20        Required Arguments:
 21
 22        - `graph`:
 23            - Type: list of dictionaries
 24            - See: https://connor-makowski.github.io/scgraph/scgraph/graph.html#Graph.validate_graph
 25        - `node_id`
 26            - Type: int
 27            - What: The id of the node from which to calculate the spanning tree
 28
 29        Returns:
 30
 31        - A dictionary with the following keys:
 32            - `node_id`: The id of the node from which the spanning tree was calculated
 33            - `predecessors`: A list of node ids referring to the predecessor of each node given the spanning tree
 34                - Note: For disconnected graphs, nodes that are not connected to the origin node will have a predecessor of None
 35            - `distance_matrix`: A list of distances from the origin node to each node in the graph
 36                - Note: For disconnected graphs, nodes that are not connected to the origin node will have a distance of float("inf")
 37
 38        """
 39        # Input Validation
 40        assert isinstance(node_id, int), "node_id must be an integer"
 41        assert 0 <= node_id < len(graph), "node_id must be a valid node id"
 42        # Variable Initialization
 43        distance_matrix = [float("inf")] * len(graph)
 44        distance_matrix[node_id] = 0
 45        open_leaves = []
 46        heappush(open_leaves, (0, node_id))
 47        predecessor = [-1] * len(graph)
 48
 49        while open_leaves:
 50            current_distance, current_id = heappop(open_leaves)
 51            for connected_id, connected_distance in graph[current_id].items():
 52                possible_distance = current_distance + connected_distance
 53                if possible_distance < distance_matrix[connected_id]:
 54                    distance_matrix[connected_id] = possible_distance
 55                    predecessor[connected_id] = current_id
 56                    heappush(open_leaves, (possible_distance, connected_id))
 57
 58        return {
 59            "node_id": node_id,
 60            "predecessors": predecessor,
 61            "distance_matrix": distance_matrix,
 62        }
 63
 64    @staticmethod
 65    def get_path(
 66        origin_id: int,
 67        destination_id: int,
 68        spanning_tree: dict,
 69        length_only: bool = False,
 70    ) -> dict:
 71        """
 72        Function:
 73
 74        - Get the path from the origin node to the destination node using the provided spanning tree
 75        - Return a list of node ids in the order they are visited as well as the length of the path
 76        - If the destination node is not reachable from the origin node, return None
 77
 78        Required Arguments:
 79
 80        - `origin_id`
 81            - Type: int
 82            - What: The id of the origin node from the graph dictionary to start the shortest path from
 83        - `destination_id`
 84            - Type: int
 85            - What: The id of the destination node from the graph dictionary to end the shortest path at
 86        - `spanning_tree`
 87            - Type: dict
 88            - What: The output from the makowskis_spanning_tree function
 89
 90        Optional Arguments:
 91
 92        - `length_only`
 93            - Type: bool
 94            - What: If True, only returns the length of the path
 95            - Default: False
 96
 97        Returns:
 98
 99        - A dictionary with the following keys:
100            - `path`: A list of node ids in the order they are visited from the origin node to the destination node
101            - `length`: The length of the path from the origin node to the destination node
102        """
103        if spanning_tree["node_id"] != origin_id:
104            raise Exception(
105                "The origin node must be the same as the spanning node for this function to work."
106            )
107        destination_distance = spanning_tree["distance_matrix"][destination_id]
108        if destination_distance == float("inf"):
109            raise Exception(
110                "Something went wrong, the origin and destination nodes are not connected."
111            )
112        if length_only:
113            return {"length": destination_distance}
114        current_id = destination_id
115        current_path = [destination_id]
116        while current_id != origin_id:
117            current_id = spanning_tree["predecessors"][current_id]
118            current_path.append(current_id)
119        current_path.reverse()
120        return {
121            "path": current_path,
122            "length": destination_distance,
123        }
class SpanningTree:
  5class SpanningTree:
  6    @staticmethod
  7    def makowskis_spanning_tree(graph: list[dict], node_id: int) -> dict:
  8        """
  9        Function:
 10
 11        - Calculate the spanning tree of a graph using Makowski's modified Dijkstra algorithm
 12            - Modifications allow for a sparse distance matrix to be used instead of a dense distance matrix
 13            - Improvements include only computing future potential nodes based on the open leaves for each branch
 14                - Open leaves are nodes that have not been visited yet but are adjacent to other visited nodes
 15            - This can dramatically reduce the memory and compute requirements of the algorithm
 16            - For particularly sparse graphs, this algorithm runs close to O(n log n) time
 17                - Where n is the number of nodes in the graph
 18            - For dense graphs, this algorithm runs closer to O(n^2) time (similar to the standard Dijkstra algorithm)
 19                - Where n is the number of nodes in the graph
 20
 21        Required Arguments:
 22
 23        - `graph`:
 24            - Type: list of dictionaries
 25            - See: https://connor-makowski.github.io/scgraph/scgraph/graph.html#Graph.validate_graph
 26        - `node_id`
 27            - Type: int
 28            - What: The id of the node from which to calculate the spanning tree
 29
 30        Returns:
 31
 32        - A dictionary with the following keys:
 33            - `node_id`: The id of the node from which the spanning tree was calculated
 34            - `predecessors`: A list of node ids referring to the predecessor of each node given the spanning tree
 35                - Note: For disconnected graphs, nodes that are not connected to the origin node will have a predecessor of None
 36            - `distance_matrix`: A list of distances from the origin node to each node in the graph
 37                - Note: For disconnected graphs, nodes that are not connected to the origin node will have a distance of float("inf")
 38
 39        """
 40        # Input Validation
 41        assert isinstance(node_id, int), "node_id must be an integer"
 42        assert 0 <= node_id < len(graph), "node_id must be a valid node id"
 43        # Variable Initialization
 44        distance_matrix = [float("inf")] * len(graph)
 45        distance_matrix[node_id] = 0
 46        open_leaves = []
 47        heappush(open_leaves, (0, node_id))
 48        predecessor = [-1] * len(graph)
 49
 50        while open_leaves:
 51            current_distance, current_id = heappop(open_leaves)
 52            for connected_id, connected_distance in graph[current_id].items():
 53                possible_distance = current_distance + connected_distance
 54                if possible_distance < distance_matrix[connected_id]:
 55                    distance_matrix[connected_id] = possible_distance
 56                    predecessor[connected_id] = current_id
 57                    heappush(open_leaves, (possible_distance, connected_id))
 58
 59        return {
 60            "node_id": node_id,
 61            "predecessors": predecessor,
 62            "distance_matrix": distance_matrix,
 63        }
 64
 65    @staticmethod
 66    def get_path(
 67        origin_id: int,
 68        destination_id: int,
 69        spanning_tree: dict,
 70        length_only: bool = False,
 71    ) -> dict:
 72        """
 73        Function:
 74
 75        - Get the path from the origin node to the destination node using the provided spanning tree
 76        - Return a list of node ids in the order they are visited as well as the length of the path
 77        - If the destination node is not reachable from the origin node, return None
 78
 79        Required Arguments:
 80
 81        - `origin_id`
 82            - Type: int
 83            - What: The id of the origin node from the graph dictionary to start the shortest path from
 84        - `destination_id`
 85            - Type: int
 86            - What: The id of the destination node from the graph dictionary to end the shortest path at
 87        - `spanning_tree`
 88            - Type: dict
 89            - What: The output from the makowskis_spanning_tree function
 90
 91        Optional Arguments:
 92
 93        - `length_only`
 94            - Type: bool
 95            - What: If True, only returns the length of the path
 96            - Default: False
 97
 98        Returns:
 99
100        - A dictionary with the following keys:
101            - `path`: A list of node ids in the order they are visited from the origin node to the destination node
102            - `length`: The length of the path from the origin node to the destination node
103        """
104        if spanning_tree["node_id"] != origin_id:
105            raise Exception(
106                "The origin node must be the same as the spanning node for this function to work."
107            )
108        destination_distance = spanning_tree["distance_matrix"][destination_id]
109        if destination_distance == float("inf"):
110            raise Exception(
111                "Something went wrong, the origin and destination nodes are not connected."
112            )
113        if length_only:
114            return {"length": destination_distance}
115        current_id = destination_id
116        current_path = [destination_id]
117        while current_id != origin_id:
118            current_id = spanning_tree["predecessors"][current_id]
119            current_path.append(current_id)
120        current_path.reverse()
121        return {
122            "path": current_path,
123            "length": destination_distance,
124        }
@staticmethod
def makowskis_spanning_tree(graph: list[dict], node_id: int) -> dict:
 6    @staticmethod
 7    def makowskis_spanning_tree(graph: list[dict], node_id: int) -> dict:
 8        """
 9        Function:
10
11        - Calculate the spanning tree of a graph using Makowski's modified Dijkstra algorithm
12            - Modifications allow for a sparse distance matrix to be used instead of a dense distance matrix
13            - Improvements include only computing future potential nodes based on the open leaves for each branch
14                - Open leaves are nodes that have not been visited yet but are adjacent to other visited nodes
15            - This can dramatically reduce the memory and compute requirements of the algorithm
16            - For particularly sparse graphs, this algorithm runs close to O(n log n) time
17                - Where n is the number of nodes in the graph
18            - For dense graphs, this algorithm runs closer to O(n^2) time (similar to the standard Dijkstra algorithm)
19                - Where n is the number of nodes in the graph
20
21        Required Arguments:
22
23        - `graph`:
24            - Type: list of dictionaries
25            - See: https://connor-makowski.github.io/scgraph/scgraph/graph.html#Graph.validate_graph
26        - `node_id`
27            - Type: int
28            - What: The id of the node from which to calculate the spanning tree
29
30        Returns:
31
32        - A dictionary with the following keys:
33            - `node_id`: The id of the node from which the spanning tree was calculated
34            - `predecessors`: A list of node ids referring to the predecessor of each node given the spanning tree
35                - Note: For disconnected graphs, nodes that are not connected to the origin node will have a predecessor of None
36            - `distance_matrix`: A list of distances from the origin node to each node in the graph
37                - Note: For disconnected graphs, nodes that are not connected to the origin node will have a distance of float("inf")
38
39        """
40        # Input Validation
41        assert isinstance(node_id, int), "node_id must be an integer"
42        assert 0 <= node_id < len(graph), "node_id must be a valid node id"
43        # Variable Initialization
44        distance_matrix = [float("inf")] * len(graph)
45        distance_matrix[node_id] = 0
46        open_leaves = []
47        heappush(open_leaves, (0, node_id))
48        predecessor = [-1] * len(graph)
49
50        while open_leaves:
51            current_distance, current_id = heappop(open_leaves)
52            for connected_id, connected_distance in graph[current_id].items():
53                possible_distance = current_distance + connected_distance
54                if possible_distance < distance_matrix[connected_id]:
55                    distance_matrix[connected_id] = possible_distance
56                    predecessor[connected_id] = current_id
57                    heappush(open_leaves, (possible_distance, connected_id))
58
59        return {
60            "node_id": node_id,
61            "predecessors": predecessor,
62            "distance_matrix": distance_matrix,
63        }

Function:

  • Calculate the spanning tree of a graph using Makowski's modified Dijkstra algorithm
    • Modifications allow for a sparse distance matrix to be used instead of a dense distance matrix
    • Improvements include only computing future potential nodes based on the open leaves for each branch
      • Open leaves are nodes that have not been visited yet but are adjacent to other visited nodes
    • This can dramatically reduce the memory and compute requirements of the algorithm
    • For particularly sparse graphs, this algorithm runs close to O(n log n) time
      • Where n is the number of nodes in the graph
    • For dense graphs, this algorithm runs closer to O(n^2) time (similar to the standard Dijkstra algorithm)
      • Where n is the number of nodes in the graph

Required Arguments:

Returns:

  • A dictionary with the following keys:
    • node_id: The id of the node from which the spanning tree was calculated
    • predecessors: A list of node ids referring to the predecessor of each node given the spanning tree
      • Note: For disconnected graphs, nodes that are not connected to the origin node will have a predecessor of None
    • distance_matrix: A list of distances from the origin node to each node in the graph
      • Note: For disconnected graphs, nodes that are not connected to the origin node will have a distance of float("inf")
@staticmethod
def get_path( origin_id: int, destination_id: int, spanning_tree: dict, length_only: bool = False) -> dict:
 65    @staticmethod
 66    def get_path(
 67        origin_id: int,
 68        destination_id: int,
 69        spanning_tree: dict,
 70        length_only: bool = False,
 71    ) -> dict:
 72        """
 73        Function:
 74
 75        - Get the path from the origin node to the destination node using the provided spanning tree
 76        - Return a list of node ids in the order they are visited as well as the length of the path
 77        - If the destination node is not reachable from the origin node, return None
 78
 79        Required Arguments:
 80
 81        - `origin_id`
 82            - Type: int
 83            - What: The id of the origin node from the graph dictionary to start the shortest path from
 84        - `destination_id`
 85            - Type: int
 86            - What: The id of the destination node from the graph dictionary to end the shortest path at
 87        - `spanning_tree`
 88            - Type: dict
 89            - What: The output from the makowskis_spanning_tree function
 90
 91        Optional Arguments:
 92
 93        - `length_only`
 94            - Type: bool
 95            - What: If True, only returns the length of the path
 96            - Default: False
 97
 98        Returns:
 99
100        - A dictionary with the following keys:
101            - `path`: A list of node ids in the order they are visited from the origin node to the destination node
102            - `length`: The length of the path from the origin node to the destination node
103        """
104        if spanning_tree["node_id"] != origin_id:
105            raise Exception(
106                "The origin node must be the same as the spanning node for this function to work."
107            )
108        destination_distance = spanning_tree["distance_matrix"][destination_id]
109        if destination_distance == float("inf"):
110            raise Exception(
111                "Something went wrong, the origin and destination nodes are not connected."
112            )
113        if length_only:
114            return {"length": destination_distance}
115        current_id = destination_id
116        current_path = [destination_id]
117        while current_id != origin_id:
118            current_id = spanning_tree["predecessors"][current_id]
119            current_path.append(current_id)
120        current_path.reverse()
121        return {
122            "path": current_path,
123            "length": destination_distance,
124        }

Function:

  • Get the path from the origin node to the destination node using the provided spanning tree
  • Return a list of node ids in the order they are visited as well as the length of the path
  • If the destination node is not reachable from the origin node, return None

Required Arguments:

  • origin_id
    • Type: int
    • What: The id of the origin node from the graph dictionary to start the shortest path from
  • destination_id
    • Type: int
    • What: The id of the destination node from the graph dictionary to end the shortest path at
  • spanning_tree
    • Type: dict
    • What: The output from the makowskis_spanning_tree function

Optional Arguments:

  • length_only
    • Type: bool
    • What: If True, only returns the length of the path
    • Default: False

Returns:

  • A dictionary with the following keys:
    • path: A list of node ids in the order they are visited from the origin node to the destination node
    • length: The length of the path from the origin node to the destination node