Skip to content

Commit

Permalink
Merge 13aba30 into 0ec113b
Browse files Browse the repository at this point in the history
  • Loading branch information
SILIZ4 committed May 18, 2024
2 parents 0ec113b + 13aba30 commit 781ad34
Show file tree
Hide file tree
Showing 9 changed files with 403 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/source/api/random_graph_generator_functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Random Graph Generator Functions
rustworkx.directed_gnm_random_graph
rustworkx.undirected_gnm_random_graph
rustworkx.random_geometric_graph
rustworkx.undirected_hyperbolic_random_graph
rustworkx.barabasi_albert_graph
rustworkx.directed_barabasi_albert_graph
rustworkx.directed_random_bipartite_graph
Expand Down
11 changes: 11 additions & 0 deletions releasenotes/notes/hyperbolic-random-graph-d85c115930d8ac08.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
features:
- |
Adds new random graph generator function, :func:`.undirected_hyperbolic_random_graph`
to generate a random graph using the H² random graph model.
- |
Adds new function to the rustworkx-core module ``rustworkx_core::generators``
``hyperbolic_random_graph()`` that generates a H^2 hyperbolic random graph.
issues:
- |
Related to issue `#150 <https://github.com/Qiskit/rustworkx/issues/150>`.
1 change: 1 addition & 0 deletions rustworkx-core/src/generators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub use petersen_graph::petersen_graph;
pub use random_graph::barabasi_albert_graph;
pub use random_graph::gnm_random_graph;
pub use random_graph::gnp_random_graph;
pub use random_graph::hyperbolic_random_graph;
pub use random_graph::random_bipartite_graph;
pub use random_graph::random_geometric_graph;
pub use star_graph::star_graph;
278 changes: 276 additions & 2 deletions rustworkx-core/src/generators/random_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#![allow(clippy::float_cmp)]

use std::f64::consts::PI;
use std::hash::Hash;

use petgraph::data::{Build, Create};
Expand Down Expand Up @@ -619,14 +620,141 @@ where
Ok(graph)
}

/// Generate a hyperbolic random undirected graph.
///
/// The H² hyperbolic random graph model connects pairs of nodes with a probability
/// that decreases as their hyperbolic distance increases.
///
/// The number of nodes is inferred from the coordinates `radii` and `angles`. `radii`
/// and `angles` must have the same size and cannot be empty. If `beta` is `None`,
/// all pairs of nodes with a distance smaller than ``r`` are connected.
///
/// D. Krioukov et al. "Hyperbolic geometry of complex networks", Phys. Rev. E 82, pp 036106, 2010.
///
/// Arguments:
///
/// * `radii` - radial coordinates (nonnegative) of the nodes.
/// * `angles` - angular coordinates (between -pi and pi) of the nodes.
/// * `beta` - Sigmoid sharpness (nonnegative) of the connection probability.
/// * `r` - Distance at which the connection probability is 0.5 for the probabilistic model.
/// Threshold when ``beta`` is ``None``.
/// * `seed` - An optional seed to use for the random number generator.
/// * `default_node_weight` - A callable that will return the weight to use
/// for newly created nodes.
/// * `default_edge_weight` - A callable that will return the weight object
/// to use for newly created edges.
///
/// # Example
/// ```rust
/// use rustworkx_core::petgraph;
/// use rustworkx_core::generators::hyperbolic_random_graph;
///
/// let g: petgraph::graph::UnGraph<(), ()> = hyperbolic_random_graph(
/// &vec![0.4, 2., 3.],
/// &vec![0., 0., 0.],
/// None,
/// 2.,
/// None,
/// || {()},
/// || {()},
/// ).unwrap();
/// assert_eq!(g.node_count(), 3);
/// assert_eq!(g.edge_count(), 2);
/// ```
pub fn hyperbolic_random_graph<G, T, F, H, M>(
radii: &[f64],
angles: &[f64],
beta: Option<f64>,
r: f64,
seed: Option<u64>,
mut default_node_weight: F,
mut default_edge_weight: H,
) -> Result<G, InvalidInputError>
where
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable + GraphProp,
F: FnMut() -> T,
H: FnMut() -> M,
G::NodeId: Eq + Hash,
{
let num_nodes = radii.len();
if num_nodes == 0 || radii.len() != angles.len() {
return Err(InvalidInputError {});
}
if radii.iter().any(|x| x.is_infinite() || x.is_nan()) {
return Err(InvalidInputError {});
}
if angles.iter().any(|x| x.is_infinite() || x.is_nan()) {
return Err(InvalidInputError {});
}
if radii.iter().fold(0_f64, |a, &b| a.min(b)) < 0. {
return Err(InvalidInputError {});
}
if angles.iter().map(|x| x.abs()).fold(0_f64, |a, b| a.max(b)) > PI {
return Err(InvalidInputError {});
}
if beta.is_some_and(|b| b < 0. || b.is_nan()) {
return Err(InvalidInputError {});
}
if r < 0. || r.is_nan() {
return Err(InvalidInputError {});
}

let mut rng: Pcg64 = match seed {
Some(seed) => Pcg64::seed_from_u64(seed),
None => Pcg64::from_entropy(),
};
let mut graph = G::with_capacity(num_nodes, num_nodes);
if graph.is_directed() {
return Err(InvalidInputError {});
}

for _ in 0..num_nodes {
graph.add_node(default_node_weight());
}

let between = Uniform::new(0.0, 1.0);
for (v, (r1, theta1)) in radii
.iter()
.zip(angles.iter())
.enumerate()
.take(num_nodes - 1)
{
for (w, (r2, theta2)) in radii.iter().zip(angles.iter()).enumerate().skip(v + 1) {
let dist = hyperbolic_distance(r1, theta1, r2, theta2);
let is_edge = match beta {
Some(b) => {
let prob = 1. / ((b / 2. * (dist - r)).exp() + 1.);
let u: f64 = between.sample(&mut rng);
u < prob
}
None => dist < r,
};
if is_edge {
graph.add_edge(
graph.from_index(v),
graph.from_index(w),
default_edge_weight(),
);
}
}
}
Ok(graph)
}

#[inline]
fn hyperbolic_distance(r1: &f64, theta1: &f64, r2: &f64, theta2: &f64) -> f64 {
(r1.cosh() * r2.cosh() - r1.sinh() * r2.sinh() * (theta1 - theta2).cos()).acosh()
}

#[cfg(test)]
mod tests {
use crate::generators::InvalidInputError;
use crate::generators::{
barabasi_albert_graph, gnm_random_graph, gnp_random_graph, path_graph,
random_bipartite_graph, random_geometric_graph,
barabasi_albert_graph, gnm_random_graph, gnp_random_graph, hyperbolic_random_graph,
path_graph, random_bipartite_graph, random_geometric_graph,
};
use crate::petgraph;
use std::f64::consts::PI;

// Test gnp_random_graph

Expand Down Expand Up @@ -916,4 +1044,150 @@ mod tests {
Err(e) => assert_eq!(e, InvalidInputError),
};
}

// Test hyperbolic_random_graph

#[test]
fn test_hyperbolic_random_graph_seeded() {
let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![3., 0.5, 0.5, 0.],
&vec![0., PI, 0., 0.],
Some(10000.),
0.75,
Some(10),
|| (),
|| (),
)
.unwrap();
assert_eq!(g.node_count(), 4);
assert_eq!(g.edge_count(), 2);
}

#[test]
fn test_hyperbolic_random_graph_empty() {
let g = hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![3., 0.5, 1.],
&vec![0., PI, 0.],
None,
1.,
None,
|| (),
|| (),
)
.unwrap();
assert_eq!(g.node_count(), 3);
assert_eq!(g.edge_count(), 0);
}

#[test]
fn test_hyperbolic_random_graph_bad_angle_error() {
match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![0., 0.],
&vec![0., 3.142],
None,
1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}

#[test]
fn test_hyperbolic_random_graph_neg_radii_error() {
match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![0., -1.],
&vec![0., 0.],
None,
1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}

#[test]
fn test_hyperbolic_random_graph_neg_r_error() {
match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![0.],
&vec![0.],
None,
-1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}

#[test]
fn test_hyperbolic_random_graph_neg_beta_error() {
match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![0.],
&vec![0.],
Some(-1.),
1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}

#[test]
fn test_hyperbolic_random_graph_len_coord_error() {
match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![1.],
&vec![1., 2.],
None,
1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}

#[test]
fn test_hyperbolic_random_graph_empty_error() {
match hyperbolic_random_graph::<petgraph::graph::UnGraph<(), ()>, _, _, _, _>(
&vec![],
&vec![],
None,
1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}

#[test]
fn test_hyperbolic_random_graph_directed_error() {
match hyperbolic_random_graph::<petgraph::graph::DiGraph<(), ()>, _, _, _, _>(
&vec![0.],
&vec![0.],
None,
1.,
None,
|| (),
|| (),
) {
Ok(_) => panic!("Returned a non-error"),
Err(e) => assert_eq!(e, InvalidInputError),
}
}
}
1 change: 1 addition & 0 deletions rustworkx/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ from .rustworkx import undirected_gnm_random_graph as undirected_gnm_random_grap
from .rustworkx import directed_gnp_random_graph as directed_gnp_random_graph
from .rustworkx import undirected_gnp_random_graph as undirected_gnp_random_graph
from .rustworkx import random_geometric_graph as random_geometric_graph
from .rustworkx import undirected_hyperbolic_random_graph as undirected_hyperbolic_random_graph
from .rustworkx import barabasi_albert_graph as barabasi_albert_graph
from .rustworkx import directed_barabasi_albert_graph as directed_barabasi_albert_graph
from .rustworkx import undirected_random_bipartite_graph as undirected_random_bipartite_graph
Expand Down
8 changes: 8 additions & 0 deletions rustworkx/rustworkx.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,14 @@ def random_geometric_graph(
p: float = ...,
seed: int | None = ...,
) -> PyGraph: ...
def undirected_hyperbolic_random_graph(
radii: list[float],
angles: list[float],
r: float,
beta: float | None,
/,
seed: int | None = ...,
) -> PyGraph: ...
def barabasi_albert_graph(
n: int,
m: int,
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ fn rustworkx(py: Python<'_>, m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(directed_gnm_random_graph))?;
m.add_wrapped(wrap_pyfunction!(undirected_gnm_random_graph))?;
m.add_wrapped(wrap_pyfunction!(random_geometric_graph))?;
m.add_wrapped(wrap_pyfunction!(undirected_hyperbolic_random_graph))?;
m.add_wrapped(wrap_pyfunction!(barabasi_albert_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_barabasi_albert_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_random_bipartite_graph))?;
Expand Down
Loading

0 comments on commit 781ad34

Please sign in to comment.