Skip to content

Commit

Permalink
Change G(n,p) to accept exact 0 or 1 probabilities
Browse files Browse the repository at this point in the history
When p=0, we'll have an empty graph with n nodes and zero edges.
When p=1, we'll have a complete graph with n n(n-1) edges
for directed graphs and n(n-1)/2 edges for undirected graphs.
The time complexity stays the same. Let m be the max number of edges,
then run time is O(n+p*m), which reduces to O(n) when p=0 and, when
p=1 becomes O(n+n(n-1)) = O(n^2).
  • Loading branch information
MoAllabbad committed Oct 18, 2020
1 parent 4d66ef4 commit 829c0ff
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 58 deletions.
157 changes: 99 additions & 58 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1428,12 +1428,19 @@ fn digraph_astar_shortest_path(
/// Return a :math:`G_{np}` directed random graph, also known as an
/// Erdős-Rényi graph or a binomial graph.
///
/// The :math:`G_{n,p}` graph algorithm chooses each of the
/// :math:`n (n - 1)` possible edges with probability :math:`p`.
/// This algorithm [1]_ runs in :math:`O(n + m)` time, where :math:`m` is the
/// expected number of edges, which equals :math:`p n (n - 1)/2`.
///
/// Based on the implementation of the networkx function
/// For number of nodes :math:`n` and probability :math:`p`, the :math:`G_{n,p}`
/// graph algorithm creates :math:`n` nodes, and for all the :math:`n (n - 1)` possible edges,
/// each edge is created independently with probability :math:`p`.
/// In general, for any probability :math:`p`, the expected number of edges returned
/// is :math:`m = p n (n - 1)`. If :math:`p = 0` or :math:`p = 1`, the returned
/// graph is not random and will always be an empty or a complete graph respectively.
/// An empty graph has zero edges and a complete directed graph has :math:`n (n - 1)` edges.
/// The run time is :math:`O(n + m)` where :math:`m` is the expected number of edges mentioned above.
/// When :math:`p = 0`, run time always reduces to :math:`O(n)`, as the lower bound.
/// When :math:`p = 1`, run time always goes to :math:`O(n + n (n - 1))`, as the upper bound.
/// For other probabilities, this algorithm [1]_ runs in :math:`O(n + m)` time.
///
/// For :math:`0 < p < 1`, the algorithm is based on the implementation of the networkx function
/// ``fast_gnp_random_graph`` [2]_
///
/// :param int num_nodes: The number of nodes to create in the graph
Expand Down Expand Up @@ -1466,37 +1473,52 @@ pub fn directed_gnp_random_graph(
for x in 0..num_nodes {
inner_graph.add_node(x.to_object(py));
}
if probability <= 0.0 || probability >= 1.0 {
if probability < 0.0 || probability > 1.0 {
return Err(PyValueError::new_err(
"Probability out of range, must be 0 < p < 1",
"Probability out of range, must be 0 <= p <= 1",
));
}
let mut v: isize = 0;
let mut w: isize = -1;
let lp: f64 = (1.0 - probability).ln();

while v < num_nodes {
let random: f64 = rng.gen_range(0.0, 1.0);
let lr: f64 = (1.0 - random).ln();
let ratio: isize = (lr / lp) as isize;
w = w + 1 + ratio;
// avoid self loops
if v == w {
w += 1;
}
while v < num_nodes && num_nodes <= w {
w -= v;
v += 1;
// avoid self loops
if v == w {
w -= v;
v += 1;
if probability > 0.0 {
if probability == 1.0 {
for u in 0..num_nodes {
for v in 0..num_nodes {
if u != v {
// exclude self-loops
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
inner_graph.add_edge(u_index, v_index, py.None());
}
}
}
} else {
let mut v: isize = 0;
let mut w: isize = -1;
let lp: f64 = (1.0 - probability).ln();

while v < num_nodes {
let random: f64 = rng.gen_range(0.0, 1.0);
let lr: f64 = (1.0 - random).ln();
let ratio: isize = (lr / lp) as isize;
w = w + 1 + ratio;
// avoid self loops
if v == w {
w += 1;
}
while v < num_nodes && num_nodes <= w {
w -= v;
v += 1;
// avoid self loops
if v == w {
w -= v;
v += 1;
}
}
if v < num_nodes {
let v_index = NodeIndex::new(v as usize);
let w_index = NodeIndex::new(w as usize);
inner_graph.add_edge(v_index, w_index, py.None());
}
}
}
if v < num_nodes {
let v_index = NodeIndex::new(v as usize);
let w_index = NodeIndex::new(w as usize);
inner_graph.add_edge(v_index, w_index, py.None());
}
}

Expand All @@ -1512,12 +1534,19 @@ pub fn directed_gnp_random_graph(
/// Return a :math:`G_{np}` random undirected graph, also known as an
/// Erdős-Rényi graph or a binomial graph.
///
/// The :math:`G_{n,p}` graph algorithm chooses each of the
/// :math:`n (n - 1)/2` possible edges with probability :math:`p`.
/// This algorithm [1]_ runs in :math:`O(n + m)` time, where :math:`m` is the
/// expected number of edges, which equals :math:`p n (n - 1)/2`.
///
/// Based on the implementation of the networkx function
/// For number of nodes :math:`n` and probability :math:`p`, the :math:`G_{n,p}`
/// graph algorithm creates :math:`n` nodes, and for all the :math:`n (n - 1)/2` possible edges,
/// each edge is created independently with probability :math:`p`.
/// In general, for any probability :math:`p`, the expected number of edges returned
/// is :math:`m = p n (n - 1)/2`. If :math:`p = 0` or :math:`p = 1`, the returned
/// graph is not random and will always be an empty or a complete graph respectively.
/// An empty graph has zero edges and a complete undirected graph has :math:`n (n - 1)/2` edges.
/// The run time is :math:`O(n + m)` where :math:`m` is the expected number of edges mentioned above.
/// When :math:`p = 0`, run time always reduces to :math:`O(n)`, as the lower bound.
/// When :math:`p = 1`, run time always goes to :math:`O(n + n (n - 1)/2)`, as the upper bound.
/// For other probabilities, this algorithm [1]_ runs in :math:`O(n + m)` time.
///
/// For :math:`0 < p < 1`, the algorithm is based on the implementation of the networkx function
/// ``fast_gnp_random_graph`` [2]_
///
/// :param int num_nodes: The number of nodes to create in the graph
Expand Down Expand Up @@ -1550,28 +1579,40 @@ pub fn undirected_gnp_random_graph(
for x in 0..num_nodes {
inner_graph.add_node(x.to_object(py));
}
if probability <= 0.0 || probability >= 1.0 {
if probability < 0.0 || probability > 1.0 {
return Err(PyValueError::new_err(
"Probability out of range, must be 0 < p < 1",
"Probability out of range, must be 0 <= p <= 1",
));
}
let mut v: isize = 1;
let mut w: isize = -1;
let lp: f64 = (1.0 - probability).ln();

while v < num_nodes {
let random: f64 = rng.gen_range(0.0, 1.0);
let lr = (1.0 - random).ln();
let ratio: isize = (lr / lp) as isize;
w = w + 1 + ratio;
while w >= v && v < num_nodes {
w -= v;
v += 1;
}
if v < num_nodes {
let v_index = NodeIndex::new(v as usize);
let w_index = NodeIndex::new(w as usize);
inner_graph.add_edge(v_index, w_index, py.None());
if probability > 0.0 {
if probability == 1.0 {
for u in 0..num_nodes {
for v in u + 1..num_nodes {
let u_index = NodeIndex::new(u as usize);
let v_index = NodeIndex::new(v as usize);
inner_graph.add_edge(u_index, v_index, py.None());
}
}
} else {
let mut v: isize = 1;
let mut w: isize = -1;
let lp: f64 = (1.0 - probability).ln();

while v < num_nodes {
let random: f64 = rng.gen_range(0.0, 1.0);
let lr = (1.0 - random).ln();
let ratio: isize = (lr / lp) as isize;
w = w + 1 + ratio;
while w >= v && v < num_nodes {
w -= v;
v += 1;
}
if v < num_nodes {
let v_index = NodeIndex::new(v as usize);
let w_index = NodeIndex::new(w as usize);
inner_graph.add_edge(v_index, w_index, py.None());
}
}
}
}

Expand Down
20 changes: 20 additions & 0 deletions tests/test_random.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ def test_random_gnp_directed(self):
self.assertEqual(len(graph), 20)
self.assertEqual(len(graph.edges()), 104)

def test_random_gnp_directed_empty_graph(self):
graph = retworkx.directed_gnp_random_graph(20, 0)
self.assertEqual(len(graph), 20)
self.assertEqual(len(graph.edges()), 0)

def test_random_gnp_directed_complete_graph(self):
graph = retworkx.directed_gnp_random_graph(20, 1)
self.assertEqual(len(graph), 20)
self.assertEqual(len(graph.edges()), 20 * (20 - 1))

def test_random_gnp_directed_invalid_num_nodes(self):
with self.assertRaises(ValueError):
retworkx.directed_gnp_random_graph(-23, .5)
Expand All @@ -35,6 +45,16 @@ def test_random_gnp_undirected(self):
self.assertEqual(len(graph), 20)
self.assertEqual(len(graph.edges()), 105)

def test_random_gnp_undirected_empty_graph(self):
graph = retworkx.undirected_gnp_random_graph(20, 0)
self.assertEqual(len(graph), 20)
self.assertEqual(len(graph.edges()), 0)

def test_random_gnp_undirected_complete_graph(self):
graph = retworkx.undirected_gnp_random_graph(20, 1)
self.assertEqual(len(graph), 20)
self.assertEqual(len(graph.edges()), 20 * (20 - 1) / 2)

def test_random_gnp_undirected_invalid_num_nodes(self):
with self.assertRaises(ValueError):
retworkx.undirected_gnp_random_graph(-23, .5)
Expand Down

0 comments on commit 829c0ff

Please sign in to comment.