1

I want to generate random DAGs, where all node connections be only single direction.

I have tried following solution How can I create random single source random acyclic directed graphs with negative edge weights in python, which is able to generate random DAGs but node connections can be two way.

alper
  • 2,919
  • 9
  • 53
  • 102

2 Answers2

1

This should do the trick:

import networkx as nx
import random

def random_dag(nodes, edges):
    G = nx.DiGraph()
    for i in range(nodes):
        G.add_node(i)
    while edges > 0:
        a = random.randint(0, nodes-1)
        b = a
        while b == a:
            b = random.randint(0, nodes-1)
        G.add_edge(a, b)
        if nx.is_directed_acyclic_graph(G):
            edges -= 1
        else:
            G.remove_edge(a, b)
    return G

G = random_dag(5, 4)
nx.draw(G, with_labels=True)
Norhther
  • 545
  • 3
  • 15
  • 35
1

What you want is a directed graph without cycles.

My favorite Python package for directed graphs is networkx.

Here is a example of random directed DAG generation:

import networkx as nx
import matplotlib.pyplot as plt
import random

def generate_random_dag(num_nodes, num_edges):
    G = nx.DiGraph()
    while G.number_of_edges() < num_edges:
        G.add_edge(*random.sample(range(num_nodes), 2))
        if not nx.is_directed_acyclic_graph(G):
            G.remove_edges_from(list(nx.find_cycle(G, orientation='original')))
    return G

G = generate_random_dag(10, 15)

nx.draw(G, with_labels=True)
plt.show() 

Note the use of the class DiGraph (Di for directed), instead of Graph or other options.

Giving you this output:

Directed graph output

Following-up here with the question from comments: in essence, can we enforce a specific node (like #0) to be the initial node?

Here is the modified code to do that (for node 0 in this example but number can easily be changed to any other node):

def generate_random_dag(num_nodes, num_edges):
    G = nx.DiGraph()
    while G.number_of_edges() < num_edges:
        start_node, end_node = random.sample(range(num_nodes), 2)
        # ensure that node 0 is always a source (initial node)
        if end_node == 0:  
            start_node, end_node = end_node, start_node
        G.add_edge(start_node, end_node)
        if not nx.is_directed_acyclic_graph(G):
            G.remove_edge(start_node, end_node)
    return G

G = generate_random_dag(10, 15)
nx.draw(G, with_labels=True)
plt.show()

which gives you the following output graph (note that node 0 does not have any incoming edges, as expected):

Output DAG with node 0 as initial node

Following-up with the second question from the comments: can we enforce a specific node (like #0) to be the initial node AND make sure that the graph has a unique source node?

Here is the modified code to do that:

def generate_random_dag(num_nodes, num_edges):
    G = nx.DiGraph()
    
    for node in range(1, num_nodes):
        G.add_edge(0, node)

    while G.number_of_edges() < num_edges:
        start_node, end_node = random.sample(range(num_nodes), 2)
        # only node 0 as source, no other sources
        if end_node == 0 or start_node == 0:  
            continue
        G.add_edge(start_node, end_node)
        if not nx.is_directed_acyclic_graph(G):
            G.remove_edge(start_node, end_node)
    return G

G = generate_random_dag(10, 15)
nx.draw(G, with_labels=True)
plt.show()

And the resulting graph output:

Resulting graph with node 0 as UNIQUE source

Marc
  • 2,183
  • 2
  • 11
  • 16
  • Is it possible to always set `node 0` as sink (initial) node, where it does not receive any input? – alper Jul 09 '23 at 17:17
  • no, node 0 is not always a sink, intermediate or source node. the graph (from code above) is constructed by randomly adding edges between node pairs. The role of any node (as a source, sink, or intermediate node) is not explicitly defined and entirely depends on the outcome of this random process. – Marc Jul 09 '23 at 17:18
  • to answer your second question, yes, we definitely could force a specific role for node 0. I need to change the code accordingly, coming back to you shortly.. – Marc Jul 09 '23 at 17:20
  • Just a clarification first: you need node 0 as sink or source? My understanding of the definition of sink is a node with no outgoing edges, which would be the "final" one and source -a node with no incoming edges, which could be called "initial". which one do you need for node 0? – Marc Jul 09 '23 at 17:23
  • I wanted node-0 as an initial node, a node with no incoming edges. – alper Jul 09 '23 at 17:25
  • If possible only a single node (like if there is 10 nodes number 9) could be sink node. – alper Jul 09 '23 at 17:27
  • I have added a proposed solution at the end of the initial answer. Pls let me know if this is what you were looking for – Marc Jul 09 '23 at 17:37
  • Yes, its perfect! If possible, could only a single node (like if there is 10 nodes number 9) be a sink node? I can try to figure out from your code. – alper Jul 09 '23 at 17:40
  • sure, let me see how to adjust it accordingly.. – Marc Jul 09 '23 at 17:42
  • 1
    here you go. I have added this new variant at the end of the answer. let me know if this is what you wanted – Marc Jul 09 '23 at 17:56