def dijkstra_bidirectional(graph, s, t, cancel=False):
    graphr = graph.reverse(copy=True)
    distances = defaultdict(constant_factory(float("+inf")))
    distancesr = defaultdict(constant_factory(float("+inf")))

    queue = PriorityQueue()
    queue.insert(s, 0)

    queuer = PriorityQueue()
    queuer.insert(t, 0)

    distances[s] = 0
    distancesr[t] = 0

    mue = float("+inf")

    settled = {}

    num_settled = 0
    num_relaxed = 0

    settled_forward = []
    settled_backward = []
    while not queue.is_empty() and not queuer.is_empty():
        u, k = queue.extract_min()
        num_settled += 1
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                num_relaxed += 1
                distances[node] = distances[u] + edge_details['weight']
                mue = min(mue, distances[node] + distancesr[node])
                if queue.contains(node):
                    queue.decrease_key(node, distances[node])
                else:
                    queue.insert(node, distances[node])
        settled_forward.append(u)
        if u in settled:
            break
        settled[u] = True


        ur, kr = queuer.extract_min()
        num_settled += 1
        for node, edge_details in graphr[ur].items():
            if distancesr[ur] + edge_details['weight'] < distancesr[node]:
                num_relaxed += 1
                distancesr[node] = distancesr[ur] + edge_details['weight']
                mue = min(mue, distances[node] + distancesr[node])
                if queuer.contains(node):
                    queuer.decrease_key(node, distancesr[node])
                else:
                    queuer.insert(node, distancesr[node])
        settled_backward.append(ur)
        if ur in settled:
            break
        settled[ur] = True


    return mue, num_settled, num_relaxed, [settled_forward, settled_backward]
def dijkstra_cancel(graph, s, t):
    distances = defaultdict(constant_factory(float("+inf")))
    parents = {}

    queue = PriorityQueue()
    queue.insert(s, 0)
    distances[s] = 0
    
    settled = 0
    relaxed = 0

    settled_nodes = []

    while not queue.is_empty():
        u, k = queue.extract_min()
        settled += 1
        settled_nodes.append(u)
        if u == t:
            break
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                relaxed += 1
                distances[node] = distances[u] + edge_details['weight']
                parents[node] = u
                if queue.contains(node):
                    queue.decrease_key(node, distances[node])
                else:
                    queue.insert(node, distances[node])
    return distances[t], settled, relaxed, [settled_nodes]
def a_star(graph, s, t):
    xt, yt = graph.node[t]['x'], graph.node[t]['y']
    def pi(v):
        x, y = graph.node[v]['x'], graph.node[v]['y']
        return ((x - xt) **2 + (y - yt) ** 2) ** 0.5

    distances = defaultdict(constant_factory(float("+inf")))
    parents = {}

    queue = PriorityQueue()
    queue.insert(s, pi(s))
    distances[s] = 0
    
    settled = 0
    relaxed = 0

    settled_nodes = []

    while not queue.is_empty():
        u, k = queue.extract_min()
        settled += 1
        settled_nodes.append(u)
        if u == t:
            break
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                relaxed += 1
                distances[node] = distances[u] + edge_details['weight']
                parents[node] = u
                if queue.contains(node):
                    queue.decrease_key(node, distances[node] + pi(node))
                else:
                    queue.insert(node, distances[node] + pi(node))
    return distances[t], settled, relaxed, [settled_nodes]
def cheater(graph, s, t):
    g_reverse = graph.reverse(copy=True)
    _, pi = dijkstra(g_reverse, t)

    distances = defaultdict(constant_factory(float("+inf")))
    parents = {}

    queue = PriorityQueue()
    queue.insert(s, pi[s])
    distances[s] = 0
    
    settled = 0
    relaxed = 0

    while not queue.is_empty():
        u, r = queue.extract_min()
        settled += 1
        if u == t:
            break
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                relaxed += 1
                distances[node] = distances[u] + edge_details['weight']
                parents[node] = u
                if queue.contains(node):
                    queue.decrease_key(node, distances[node] + pi[node])
                else:
                    queue.insert(node, distances[node] + pi[node])
    return distances[t], settled, relaxed, [ parents.keys(), [] ]
def dijkstra(graph, s):
    distances = defaultdict(constant_factory(float("+inf")))
    parents = {}

    queue = PriorityQueue()
    queue.insert(s, 0)
    distances[s] = 0
    visited = 0
    while not queue.is_empty():
        u, k = queue.extract_min()
        visited += 1
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                distances[node] = distances[u] + edge_details['weight']
                parents[node] = u
                if queue.contains(node):
                    queue.decrease_key(node, distances[node])
                else:
                    queue.insert(node, distances[node])
    result = nx.DiGraph()
    for u, v, data in graph.edges(data=True):
        if v in parents and parents[v] == u:
            result.add_edge(u, v, **data)
    return result, distances
def a_star_bidirectional(graph, s, t):
    """
    Bidirectional A* that stops iff one search has found the target
    """
    graphr = graph.reverse(copy=True)
    xt, yt = graph.node[t]['x'], graph.node[t]['y']
    def pi(v):
        x, y = graph.node[v]['x'], graph.node[v]['y']
        return ((x - xt) **2 + (y - yt) ** 2) ** 0.5

    xs, ys = graph.node[s]['x'], graph.node[s]['y']
    def pir(v):
        x, y = graph.node[v]['x'], graph.node[v]['y']
        return ((x - xs) **2 + (y - ys) ** 2) ** 0.5

    pf = lambda v : (pi(v) - pir(v))/2.0
    pr = lambda v : (pir(v) - pi(v))/2.0

    pfs = pf(s)
    pft = pf(t)

    distances = defaultdict(constant_factory(float("+inf")))
    distancesr = defaultdict(constant_factory(float("+inf")))

    queue = PriorityQueue()
    queue.insert(s, pi(s))

    queuer = PriorityQueue()
    queuer.insert(t, pir(t))

    distances[s] = 0
    distancesr[t] = 0

    mue = float("+inf")


    num_settled = 0
    num_relaxed = 0

    settled_nodes = []
    settled_nodes_r = []

    while not queue.is_empty() and not queuer.is_empty():
        u, k = queue.extract_min()
        num_settled += 1
        settled_nodes.append(u)
        if u == t:
            mue = distances[t]
            break
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                num_relaxed += 1
                distances[node] = distances[u] + edge_details['weight']
                mue = min(mue, distances[node] + distancesr[node])
                if queue.contains(node):
                    queue.decrease_key(node, distances[node] + pf(node))
                else:
                    queue.insert(node, distances[node] + pf(node))

        ur, kr = queuer.extract_min()
        num_settled += 1
        settled_nodes_r.append(ur)
        if ur == s:
            mue = distancesr[s]
            break
        for node, edge_details in graphr[ur].items():
            if distancesr[ur] + edge_details['weight'] < distancesr[node]:
                num_relaxed += 1
                distancesr[node] = distancesr[ur] + edge_details['weight']
                mue = min(mue, distances[node] + distancesr[node])
                if queuer.contains(node):
                    queuer.decrease_key(node, distancesr[node] + pr(node))
                else:
                    queuer.insert(node, distancesr[node] + pr(node))

        if k + kr > mue + pfs - pft:
            break
    return mue, num_settled, num_relaxed, [settled_nodes, settled_nodes_r]
def a_star_bidirectional_onesided(graph, s, t):
    """
    Bidirectional A* that stops iff one search has found the target
    """
    graphr = graph.reverse(copy=True)
    xt, yt = graph.node[t]['x'], graph.node[t]['y']
    def pi(v):
        x, y = graph.node[v]['x'], graph.node[v]['y']
        return ((x - xt) **2 + (y - yt) ** 2) ** 0.5

    xs, ys = graph.node[s]['x'], graph.node[s]['y']
    def pir(v):
        x, y = graph.node[v]['x'], graph.node[v]['y']
        return ((x - xs) **2 + (y - ys) ** 2) ** 0.5

    distances = defaultdict(constant_factory(float("+inf")))
    distancesr = defaultdict(constant_factory(float("+inf")))

    queue = PriorityQueue()
    queue.insert(s, pi(s))

    queuer = PriorityQueue()
    queuer.insert(t, pir(t))

    distances[s] = 0
    distancesr[t] = 0

    mue = float("+inf")

    settled = {}

    num_settled = 0
    num_relaxed = 0

    while not queue.is_empty() and not queuer.is_empty():
        u, k = queue.extract_min()
        num_settled += 1
        if mue < k:
            break
        for node, edge_details in graph[u].items():
            if distances[u] + edge_details['weight'] < distances[node]:
                num_relaxed += 1
                distances[node] = distances[u] + edge_details['weight']
                mue = min(mue, distances[node] + distancesr[node])
                if queue.contains(node):
                    queue.decrease_key(node, distances[node] + pi(node))
                else:
                    queue.insert(node, distances[node] + pi(node))

        ur, kr = queuer.extract_min()
        num_settled += 1
        if mue < kr:
            break
        for node, edge_details in graphr[ur].items():
            if distancesr[ur] + edge_details['weight'] < distancesr[node]:
                num_relaxed += 1
                distancesr[node] = distancesr[ur] + edge_details['weight']
                mue = min(mue, distances[node] + distancesr[node])
                if queuer.contains(node):
                    queuer.decrease_key(node, distancesr[node] + pir(node))
                else:
                    queuer.insert(node, distancesr[node] + pir(node))
    return mue, num_settled, num_relaxed