Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change G(n,p) to accept exact 0 or 1 probabilities #174

Merged
merged 1 commit into from
Oct 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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