def generateTree(G):
    edges = []
    for line in nx.generate_edgelist(G, data=['weight']):
        vals = line.split(" ")
        if len(vals) == 3:
            edges.append((int(vals[0]), int(vals[1]), float(vals[2])))

    new_Graph = nx.Graph()

    for i in G.nodes():
        new_Graph.add_node(i)

    G_edges = edges

    while not nx.is_tree(new_Graph) and not is_valid_network(
            G, new_Graph):  #nx.number_connected_components(new_Graph) != 1:
        n = np.random.choice(range(len(G_edges)))
        edgeVal = G_edges[n]
        new_Graph.add_edge(edgeVal[0], edgeVal[1], weight=edgeVal[2])
        has_cycle = True
        try:
            nx.find_cycle(new_Graph)
        except:
            has_cycle = False

        if has_cycle == True:
            new_Graph.remove_edge(edgeVal[0], edgeVal[1])
        else:
            G_edges.remove(edgeVal)

    assert nx.is_tree(new_Graph)
    assert is_valid_network(G, new_Graph)

    return new_Graph
    def find_tree(n, T_copy, G):

        if n == 0:
            return T.copy()

        iters = 0

        # print(n, len(T_copy.nodes()))

        while not (is_valid_network(G, T_copy)):
            if iters >= 20:
                T_copy = T.copy()
                break
            T_copy = T.copy()
            # print(len(T_copy.nodes()), len(T.nodes()))
            to_remove = np.random.choice(T_copy.nodes(), n, replace=False)
            for x in to_remove:
                T_copy.remove_node(x)
            #count += 1
            iters += 1

        if not is_valid_network(G, T_copy):

            return find_tree(n - 1, T_copy, G)

        return T_copy
Beispiel #3
0
def random_prune(G, tree):
    min_tree = tree
    for k in range(1000):
        T = tree.copy()
        """list = range(25)
        random_range = random.choice(list)
        for j in range(70): #ramdom select leaves. if delete and valid, delete it
            temp = T.copy()
            leaves = [x for x in T.nodes() if T.degree(x) == 1 or T.degree(x) == 2]
            if len(leaves) == 0:
                break
            select = random.choice(leaves)
            temp.remove_node(select)
            if is_valid_network(G, temp):
                T = temp"""

        for j in range(30):
            temp = T.copy()
            for i in range(5):
                leaves = [x for x in T.nodes() if T.degree(x) == 1 or T.degree(x) == 2]
                if len(leaves) == 0:
                    break
                select = random.choice(leaves)
                temp = T.copy()
                temp.remove_node(select)
                if is_valid_network(G, temp):
                    T = temp
            if is_valid_network(G, temp) and average_pairwise_distance(temp) < average_pairwise_distance(T):
                T = temp

        if average_pairwise_distance(T) < average_pairwise_distance(min_tree):
            min_tree = T
            print(average_pairwise_distance(min_tree))
    return min_tree
def add_edge(G, val):
    """
    :param G: The original graph, used to check whether the new graph is a valid network.
    :param val: estimated cost of choosing each vertex.
    :return: possible best result
    """
    best_graph = nx.Graph()
    edge_list = []
    new_edge = ""
    node = val.index(max(val))
    new_edge += str(node)
    max_val = -float("inf")
    max_neighbour = 0
    for neighbour in G.neighbors(node):
        if val[neighbour] > max_val and neighbour != node:
            max_val = val[neighbour]
            max_neighbour = neighbour
    new_edge += " " + str(max_neighbour)
    new_edge += " " + str(G.get_edge_data(node, max_neighbour).get("weight"))
    edge_list.append(new_edge)
    new_graph = nx.parse_edgelist(edge_list,
                                  nodetype=int,
                                  data=(('weight', float), ))
    val[node], val[max_neighbour] = -float("inf"), -float("inf")
    if is_valid_network(G, new_graph):
        # if average_pairwise_distance_fast(new_graph) < best_so_far:
        #     best_so_far = average_pairwise_distance_fast(new_graph)
        best_graph = copy.deepcopy(new_graph)
        return best_graph

    while True:
        new_edge = ""
        max_val = -float("inf")
        max_neighbour = 0
        max_node = 0
        check = False
        for node in new_graph.nodes():
            for neighbour in G.neighbors(node):
                if val[neighbour] > max_val:
                    max_val = val[neighbour]
                    max_neighbour = neighbour
                    max_node = node
                    check = True
        if not check:
            break
        new_edge += str(max_node)
        new_edge += " " + str(max_neighbour)
        new_edge += " " + str(
            G.get_edge_data(max_node, max_neighbour).get("weight"))
        edge_list.append(new_edge)
        new_graph = nx.parse_edgelist(edge_list,
                                      nodetype=int,
                                      data=(('weight', float), ))
        val[max_node], val[max_neighbour] = -float("inf"), -float("inf")
        if is_valid_network(G, new_graph):
            best_graph = copy.deepcopy(new_graph)
            break
    return best_graph
def makeAllOutputFiles():
    for file in os.listdir("inputs"):
        if file.endswith(".in"):
            print(os.path.join("inputs", file))  #input file
            input_path = os.path.join("inputs", file)
            G = read_input_file(input_path)
            T = solve(G)
            assert is_valid_network(G, T)

            #use brute force for small graphs
            if file.startswith("small") and len(T) > 2:
                print("Trying brute forcing on SMALL file: " +
                      os.path.join("inputs", file))  #input file
                BRUTE_TREE = maes_second_dumbass_brute_force(G)
                if average_pairwise_distance_fast(
                        BRUTE_TREE) <= average_pairwise_distance_fast(T):
                    print("Small brute-force alg WINS.")
                    T = BRUTE_TREE
                else:
                    print("Solver alg WINS.")
                    #nothing happens

            #print("Average pairwise distance: {}".format(average_pairwise_distance_fast(T)))
            outname = os.path.splitext(file)[0] + '.out'
            output_path = os.path.join("outputs", outname)
            print(output_path + "\n")
            write_output_file(T, output_path)
            assert validate_file(output_path) == True
Beispiel #6
0
    def generate_random_old_way(self, s=1, d=0, high_degree=False):
        """
        Returns a random valid starting state for local search.
        s: number of nodes to form T on 
        d: minimum degree of each node on s
        """

        nodes = list(self.graph.nodes)
        edges = list(self.graph.edges)
        self.network = nx.Graph()

        a = sorted(nodes, key=lambda n: len(self.graph.edges(n)), reverse=True)

        if high_degree:
            self.network.add_node(a[0])
        else:
            self.network.add_node(nodes[0])

        self.network = nx.minimum_spanning_tree(self.network)
        # print("Graph Size:", len(nodes))
        s = random.randint(len(nodes) // 2, len(nodes))
        # print("Subset Size:", s)
        while not is_valid_network(self.graph, self.network):
            # print("Looking")
            self.network.clear()
            T_nodes = random.sample(nodes, s)
            self.network.add_nodes_from(T_nodes)
            self.network.add_weighted_edges_from(self.relevant_edges(T_nodes))
            self.network = nx.minimum_spanning_tree(self.network)
            s += 1
 def remove(copy, G_copy, k):
     nonlocal min_dist
     nonlocal minT
     has_valid = False
     count = 0
     for c in itertools.combinations(G_copy.edges(), k):
         temp = copy.copy()
         if count >= 10000:
             return solve(G)
         count += 1
         for e in c:
             u, v = e[0], e[1]
             if copy.degree(u) == 1:
                 copy.remove_node(u)
             elif copy.degree(v) == 1:
                 copy.remove_node(v)
             else:
                 copy.remove_edge(u, v)
         if is_valid_network(G, copy):
             has_valid = True
             if average_pairwise_distance_fast(copy) < min_dist:
                 min_dist = average_pairwise_distance_fast(copy)
                 minT = copy.copy()
         elif nx.is_dominating_set(G, copy.nodes()) and nx.is_connected(copy):
             has_valid = True
         copy = temp
     if not has_valid:
         return
     remove(copy, G_copy, k+1)
Beispiel #8
0
def trim_tree(G, T):
    """
	Trims tree T as solution for graph G.
	Valid solutions for the problem are trees whose vertices are dominating sets.
	"""
    leaves = [l for l in T.nodes() if T.degree()[l] == 1]
    subTreeNodes = list(T.nodes())
    cost = average_pairwise_distance_fast(T)
    while len(leaves) > 0:
        l, w = leaves[0], 100
        for v in leaves:
            for u in T.adj[v]:
                if T.adj[v][u]['weight'] <= w:
                    l, w = v, T.adj[v][u]['weight']
        leaves.remove(l)
        toCheck = T.adj[l]
        subTreeNodes.remove(l)
        Tn = T.subgraph(subTreeNodes)
        if len(subTreeNodes) > 0 and is_valid_network(G, Tn):
            costn = average_pairwise_distance_fast(Tn)
            if costn < cost:
                leaves += [v for v in toCheck if Tn.degree[v] == 1]
                T = Tn
                cost = costn
            else:
                subTreeNodes.append(l)
        else:
            subTreeNodes.append(l)
    return T
Beispiel #9
0
    def helper2(G):
        T = nx.minimum_spanning_tree(G)
        curr_lowest = average_pairwise_distance(T)
        curr_lowest_tree = T

        S = min_weighted_dominating_set(T)

        newG = nx.subgraph(T, S)

        ncc = nx.number_connected_components(newG)
        ccs = list(connected_components(newG))

        for i in range(len(ccs) - 1):
            curr_node = ccs[i].pop()
            ccs[i].add(curr_node)
            next_node = ccs[i + 1].pop()
            ccs[i + 1].add(next_node)
            path = nx.dijkstra_path(G, curr_node, next_node)

            for n in path:
                if (n not in list(newG.nodes)):
                    S.add(n)

            newG = nx.subgraph(G, S)
            newT = nx.minimum_spanning_tree(newG)
            if (is_valid_network(G, newT)):
                apd = average_pairwise_distance(newT)
                if (apd < curr_lowest):
                    curr_lowest = apd
                    curr_lowest_tree = newT

        return curr_lowest_tree
Beispiel #10
0
def solveFile(fileName: str, log = False) -> bool:
    """
    Solve a graph saved in ./inputs/{fileName}.in and output it in output folder. 
    Return if solve file succeed. 
    """

    try:
        G = read_input_file("./inputs/%s.in" % fileName)
        T = solver.ABC(G, N_EMPLOYED, N_ONLOOKER, N_ITERATIONS, FIRE_LIMIT, TERMINATION_LIMIT, log = log)
        assert(is_valid_network(G, T))

        if os.path.exists("./outputs/%s.out" % fileName):
            oldT = read_output_file("./outputs/%s.out" % fileName, G)
            if len(T) == 1 and len(oldT) != 1:
                write_output_file(T, "./outputs/%s.out" % fileName)
                return True
            if len(oldT) == 1 or average_pairwise_distance(oldT) <= average_pairwise_distance(T):
                # do nothing
                print("File %s is skipped because old tree is better. " % fileName)
                return True

        write_output_file(T, "./outputs/%s.out" % fileName)
        return True

    except KeyboardInterrupt:
        raise KeyboardInterrupt
    except:
        # stdout
        print("ERROR: An error occured when processing on %s: %s" % (fileName, sys.exc_info()[0]))
        return False
def improve(G, temp, val):
    """
    :param G: The original graph
    :param temp: Best graph we get so far. It's already a valid network, but in some cases add a new vertex can
    reduce the cost
    :param val: Heurisitics
    :return: new Best graph
    """
    best_so_far = average_pairwise_distance_fast(temp)
    new_graph = copy.deepcopy(temp)
    while True:
        min_val = float("inf")
        min_neighbour = 0
        min_node = 0
        check = False
        for node in temp.nodes():
            for neighbour in G.neighbors(node):
                if val[neighbour] < min_val:
                    min_val = val[neighbour]
                    min_neighbour = neighbour
                    min_node = node
        min_weight = G.get_edge_data(min_node, min_neighbour).get("weight")
        new_graph.add_edge(min_node, min_neighbour, weight=min_weight)
        val[min_node], val[min_neighbour] = -1, -1
        if is_valid_network(G, new_graph):
            if average_pairwise_distance_fast(new_graph) < best_so_far:
                best_so_far = average_pairwise_distance_fast(new_graph)
                check = True
        if not check:
            return temp
    return new_graph
Beispiel #12
0
 def _start(self):
     """
     Starting state for T.
     """
     # self.network = nx.minimum_spanning_tree(self.graph)
     # self.generate_random_old_way(high_degree=True)
     self.generate_random_new_way()
     assert is_valid_network(self.graph, self.network)
Beispiel #13
0
def solve(G):
    """
    Args:
        G: networkx.Graph

    Returns:
        T: networkx.Graph
    """
    if G.number_of_nodes() <= 1:
        return G
    if G.number_of_nodes() == 2:
        G.remove_node(0)
        return G

    apd = []
    T = naive_solve(G)
    if is_valid_network(G, T):
        apd.append(T)
    else:
        print('naive_solve not connected')

    T = greedy_mds(G)
    if is_valid_network(G, T):
        apd.append(T)
    else:
        print('greedy_mds not valid network')

    T = idea1(G, 'prune')
    if is_valid_network(G, T):
        apd.append(T)
    else:
        print('idea1 not valid network')

    T = idea2(G)
    if is_valid_network(G, T):
        apd.append(T)
    else:
        print('idea2 not valid network')

    T = idea3(G)
    if is_valid_network(G, T):
        apd.append(T)
    else:
        print('idea3 not valid network')

    return min(apd, key=average_pairwise_distance)
def random_weight(G, id):
    T = nx.Graph()
    nodes = list(G.nodes)
    v = np.random.choice(nodes, 1)[0]
    T.add_node(v)
    all_neighbors = set()
    count = 0
    flag = True
    while not is_valid_network(G, T):
        #print(count)
        if flag:
            neighbors = []
            for node in T:
                for w in G.neighbors(v):
                    if w not in T:
                        neighbors.append((node, w, 100 - G[v][w]['weight']))
            for n in neighbors:
                all_neighbors.add(n)

        total = sum([weight for u, w, weight in all_neighbors])
        dist = [weight / total for u, w, weight in all_neighbors]
        neighbor_edges = [(u, w, weight) for u, w, weight in all_neighbors]

        #print(dist)
        next_edge = neighbor_edges[np.random.choice(range(len(neighbor_edges)),
                                                    p=dist)]
        T.add_edge(next_edge[0], next_edge[1], weight=next_edge[2])

        if nx.is_tree(T):
            v = next_edge[1]
            flag = True
        else:
            T.remove_edge(next_edge[0], next_edge[1])
            all_neighbors.remove(next_edge)
            flag = False

        count += 1
        if T.number_of_nodes() == G.number_of_nodes():
            print('no')
            break

    score = average_pairwise_distance_fast(T)

    T_copy = T.copy()
    for v in T:
        for w in G.neighbors(v):
            if w not in G:
                T_copy.add_edge(v, w, G.get_edge_data(v, w)['weight'])
                new_score = average_pairwise_distance_fast(T_copy)
                if new_score > score:
                    T_copy.remove_edge(v, w)
                else:
                    score = new_score
                    print('good')

    #print(score - best_scores[id])

    update_best_graph(T, id, 'random weight')
Beispiel #15
0
def algo2(G):
    """
    add notes to previous algorithm.
    """
    T = algo1(G)
    newT = addNodes(G, T, set(list(G.nodes)) - set(list(T.nodes)))
    newT = buildTree(G, list(newT.nodes))
    assert is_valid_network(G, newT)
    return newT
Beispiel #16
0
def algo3(G):
    """
    remove notes from previous algotirm.
    """
    T = algo1(G.copy())
    newT = removeNodes(G.copy(), T)
    newT = buildTree(G.copy(), list(newT.nodes))
    assert is_valid_network(G, newT)
    return newT
Beispiel #17
0
def algo7(G):
    """
    build tree in different way.
    """
    T = algo6(G.copy())
    allNodes = set(list(G.nodes))
    for _ in range(1):
        T = addNodes(G, T, allNodes - set(list(T.nodes)))
        T = build(G, list(T.nodes))
        T = removeNodes(G, T)
        T = build(G, list(T.nodes))
    count = 3
    while not is_valid_network(G, T) and count:
        T = addNodes(G, T, allNodes - set(list(T.nodes)))
        T = removeNodes(G, T)
        count -= 1
    if not is_valid_network(G, T):
        return algo6(G.copy())
    return T
Beispiel #18
0
def algo1(G):
    """
    find the mdcs and connect them as components.
    Then find the min cost to connect components.
    """
    domin = mwd(G)
    components = getComponents(G, domin)
    nodes = connectComponents(G, components)
    T = buildTree(G, nodes)
    assert is_valid_network(G, T)
    return T
Beispiel #19
0
def advanced_prunning(G, T):
    nodes = list(G.nodes)
    for i in nodes:
        temp = copy.deepcopy(T)
        temp2 = copy.deepcopy(temp)
        temp.remove_node(i)
        if (is_valid_network(G, temp)):
            T.remove_node(i)
        else:
            temp = copy.deepcopy(temp2)
    return T
Beispiel #20
0
def algo5(G):
    """
    remove and node from mst.
    """
    T = algo0(G.copy())
    allNodes = set(list(G.nodes))
    for _ in range(2):
        T = addNodes(G, T, allNodes - set(list(T.nodes)))
        T = removeNodes(G, T)
    assert is_valid_network(G, T)
    return T
Beispiel #21
0
def algo4(G):
    """
    add and remove notes from tree.
    """
    T = algo2(G)
    allNodes = set(list(G.nodes))
    for _ in range(3):
        newT = addNodes(G, T, allNodes - set(list(T.nodes)))
        newT = removeNodes(G.copy(), newT)
    assert is_valid_network(G, newT)
    return newT
Beispiel #22
0
def heuristic_avg_cost(params):
    """
	Loss function of average cost over training graphs.
	"""
    total_cost, count = 0, 0
    for input in os.listdir('training/'):
        G = read_input_file('training/' + input)
        T = dijkstra_two_solve(G, [p1, p2, p3, p4])
        assert is_valid_network(G, T)
        total_cost += average_pairwise_distance_fast(T)
        count += 1
    return total_cost / count
def solve(G):
    """
    Args:
        G: networkx.Graph

    Returns:
        solution tree
    """

    T1 = solver.solve(G)
    T2 = solver1.solve(G)
    T3 = solver2.solve(G)

    if is_valid_network(G, T1) != True:
        return min([T2, T3], key=lambda x: average_pairwise_distance(x))
    elif is_valid_network(G, T2) != True:
        return min([T1, T3], key=lambda x: average_pairwise_distance(x))
    elif is_valid_network(G, T3) != True:
        return min([T1, T2], key=lambda x: average_pairwise_distance(x))
    else:
        return min([T1, T2, T3], key=lambda x: average_pairwise_distance(x))
Beispiel #24
0
def prune_leaf(G, T):
    for _ in range(25):
        leaves = [x for x in T.nodes() if T.degree(x) == 1]
        for i in leaves:
            temp = T.copy()
            temp.remove_node(i)
            try:
                if is_valid_network(G, temp) and average_pairwise_distance(temp) < average_pairwise_distance(T):
                    T = temp
            except:
                continue
    deg_two = [x for x in T.nodes() if T.degree(x) == 2]
    for i in deg_two:
        temp = T.copy()
        temp.remove_node(i)
        try:
            if is_valid_network(G, temp) and average_pairwise_distance(temp) < average_pairwise_distance(T):
                T = temp
        except:
            continue
    return T
Beispiel #25
0
def generate_new_solution(G, T):
    """With half probability decide to add or remove a vertex. If add a vertex, choose a vertex at random and add one of its
    neighbors that is not already within the graph. If remove vertex, choose a leaf at random, remove from tree, but only do
    so if the resulting graph is still a dominating set. Return the resulting solution tree."""
    global adds, remove_v, switch_e
    roll = np.random.random()
    if (roll < 0.33):  # add a vertex
        tree_vertices = list(T.nodes)
        rand_vertex = np.random.choice(tree_vertices)
        rand_neighbors = list(G.adj[rand_vertex])
        rand_neighbors = [
            rn for rn in rand_neighbors if rn not in tree_vertices
        ]  # ensure candidate neighbors not already inside the tree
        if not rand_neighbors:  # empty list
            return generate_new_solution(G, T)
        else:
            adds += 1
            chosen = np.random.choice(rand_neighbors)
            T.add_node(chosen)
            T.add_edge(chosen,
                       rand_vertex,
                       weight=G.get_edge_data(chosen, rand_vertex)['weight'])
            return T

    elif (roll < 0.67):  # remove a leaf
        tree_vertices = list(T.nodes)
        T_leaves = [l for l in tree_vertices if T.degree(l) == 1]
        np.random.shuffle(T_leaves)
        for leaf in T_leaves:
            T_prime = T.copy()
            T_prime.remove_node(leaf)
            if is_valid_network(G, T_prime):
                remove_v += 1
                return T_prime
        return generate_new_solution(G, T)

    else:  # switch an edge
        switch_e += 1
        tree_edges = list(T.edges)
        rand_index = np.random.choice(len(tree_edges))
        rand_edge = tree_edges[rand_index]
        T.remove_edge(rand_edge[0], rand_edge[1])
        cc = list(nx.connected_components(T))
        # randomly adding back in an edge to reconnect the two CCs
        for vertex_a in cc[0]:
            for vertex_b in cc[1]:
                if G.has_edge(vertex_a, vertex_b):
                    T.add_edge(vertex_a,
                               vertex_b,
                               weight=G.get_edge_data(vertex_a,
                                                      vertex_b)['weight'])
                    return T
Beispiel #26
0
def algo6(G):
    """
    remove and add nodes from previous tree.
    """
    T = algo5(G.copy())
    T = removeNodes(G, T)
    count = 3
    allNode = set(list(G.nodes))
    while not is_valid_network(G, T) and count:
        T = addNodes(G, T, allNode - set(list(T.nodes)))
        T = removeNodes(G, T)
        count -= 1
    return T
def update_best_graph(G, id, method):
    def write_best_graph():
        filename = id.split('.')[0]
        write_output_file(G, OUTPUT_PATH + filename + '.out')
        print(method + ' ' + id)

    if not is_valid_network(inputs[id], G):
        print("ERROR: " + id + ' ' + method + ' ')
        #print(nx.is_tree(inputs[id]))

    if G.number_of_nodes() == 1:  # put this in finished files
        if id not in finished_files:
            write_best_graph()
            best_scores[id] = 0
            finished_files.add(id)
            best_methods[id] = method
    else:
        current_score = average_pairwise_distance_fast(G)
        if current_score < best_scores[id] and is_valid_network(inputs[id], G):
            write_best_graph()
            best_scores[id] = current_score
            best_methods[id] = method
Beispiel #28
0
def makeAllOutputFiles():
    for file in os.listdir("inputs"):
        if file.endswith(".in"):
            print(os.path.join("inputs", file))  #input file
            input_path = os.path.join("inputs", file)
            G = read_input_file(input_path)
            try:
                T = solve(G)
            except:
                print("ERRORED OUT. CONTINUE ANYWAY")
                T = G
            assert is_valid_network(G, T)

            #randomization optimization
            if len(T) > 2:
                print("Trying randomization to find better result..")
                try:
                    betterT = maes_randomization_alg(
                        G, T, 100)  #50 iterations of randomness
                except:
                    print("ERRORED OUT. CONTINUE ANYWAY")
                    betterT = G
                assert is_valid_network(G, betterT)

                if average_pairwise_distance_fast(
                        betterT) < average_pairwise_distance_fast(T):
                    print("BETTER TREE FOUND.")
                    T = betterT
                else:
                    print("No improvements.")
                    #nothing happens

            #print("Average pairwise distance: {}".format(average_pairwise_distance_fast(T)))
            outname = os.path.splitext(file)[0] + '.out'
            output_path = os.path.join("outputs", outname)
            print(output_path + "\n")
            write_output_file(T, output_path)
            assert validate_file(output_path) == True
Beispiel #29
0
    def generate_random_new_way(self):
        og = min_weighted_dominating_set(self.graph, "weight")
        start_node = og.pop()
        holder = set()
        while og:
            curr_node = og.pop()
            holder.update(dijkstra_path(self.graph, start_node, curr_node))

        if holder and is_valid_network(self.graph,
                                       self.graph.subgraph(holder)):
            self.network = nx.minimum_spanning_tree(
                self.graph.subgraph(holder))
        else:
            self.generate_random_old_way(high_degree=True)
def random_edges(G, id):
    T = nx.Graph()
    edges = list(np.random.permutation(G.edges))
    for edge in edges:
        T.add_edge(edge[0],
                   edge[1],
                   weight=G.get_edge_data(edge[0], edge[1])['weight'])
        if not nx.is_tree(T):
            T.remove_edge(edge[0], edge[1])
        if is_valid_network(G, T):
            score = average_pairwise_distance_fast(T)
            print(score - best_scores[id])
            update_best_graph(T, id, 'random edges')
            break