Skip to content

Commit

Permalink
Add Zachary's Karate Club (Qiskit#1280)
Browse files Browse the repository at this point in the history
* Karate club draft

* Add Zachary's data

* Consider weight for Karate Club

* Add tests

* Add karate_club_graph signature

* Fix clippy

* Update rustworkx-core/src/generators/karate_club.rs

Co-authored-by: Alexander Ivrii <[email protected]>

* Add labels and documentation

* Test node labels

* Add simple Rust docstring

* Add release notes

* Black changes

* Update test to be independent

* Apply suggestions from code review

Co-authored-by: Matthew Treinish <[email protected]>

* Format and u8

* Ignore ruff for that file

* Use adjacency list

* Add some documentation

* Update tests/graph/test_karate.py

---------

Co-authored-by: Alexander Ivrii <[email protected]>
Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
3 people authored and Gohlub committed Nov 17, 2024
1 parent bb2ee0c commit dcfb9e0
Show file tree
Hide file tree
Showing 6 changed files with 599 additions and 0 deletions.
14 changes: 14 additions & 0 deletions releasenotes/notes/karate-club-35708b3838689a0b.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
features:
- |
Added a new function, :func:`~rustworkx.generators.karate_club_graph` that
returns Zachary's Karate Club graph, commonly found in social network examples.
.. jupyter-execute::
import rustworkx.generators
from rustworkx.visualization import mpl_draw
graph = rustworkx.generators.karate_club_graph()
layout = rustworkx.circular_layout(graph)
mpl_draw(graph, pos=layout)
126 changes: 126 additions & 0 deletions rustworkx-core/src/generators/karate_club.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

use std::hash::Hash;

use petgraph::data::{Build, Create};
use petgraph::visit::{Data, NodeIndexable};

/// Generates Zachary's Karate Club graph.
///
/// Zachary's Karate Club graph is a well-known social network that represents
/// the relations between 34 members of a karate club.
/// Arguments:
///
/// * `default_node_weight` - A callable that will receive a boolean, indicating
/// if a node is part of Mr Hi's faction (True) or the Officer's faction (false).
/// It shoudl return the node weight according to the desired type.
/// * `default_edge_weight` - A callable that will receive the integer representing
/// the strenght of the relation between two nodes. It should return the edge
/// weight according to the desired type.
///
pub fn karate_club_graph<G, T, F, H, M>(mut default_node_weight: F, mut default_edge_weight: H) -> G
where
G: Build + Create + Data<NodeWeight = T, EdgeWeight = M> + NodeIndexable,
F: FnMut(bool) -> T,
H: FnMut(usize) -> M,
G::NodeId: Eq + Hash,
{
const N: usize = 34;
const M: usize = 78;
let mr_hi_members: [u8; 17] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 16, 17, 19, 21];
let membership: std::collections::HashSet<u8> = mr_hi_members.into_iter().collect();

let adjacency_list: Vec<Vec<(usize, usize)>> = vec![
vec![],
vec![(0, 4)],
vec![(0, 5), (1, 6)],
vec![(0, 3), (1, 3), (2, 3)],
vec![(0, 3)],
vec![(0, 3)],
vec![(0, 3), (4, 2), (5, 5)],
vec![(0, 2), (1, 4), (2, 4), (3, 3)],
vec![(0, 2), (2, 5)],
vec![(2, 1)],
vec![(0, 2), (4, 3), (5, 3)],
vec![(0, 3)],
vec![(0, 1), (3, 3)],
vec![(0, 3), (1, 5), (2, 3), (3, 3)],
vec![],
vec![],
vec![(5, 3), (6, 3)],
vec![(0, 2), (1, 1)],
vec![],
vec![(0, 2), (1, 2)],
vec![],
vec![(0, 2), (1, 2)],
vec![],
vec![],
vec![],
vec![(23, 5), (24, 2)],
vec![],
vec![(2, 2), (23, 4), (24, 3)],
vec![(2, 2)],
vec![(23, 3), (26, 4)],
vec![(1, 2), (8, 3)],
vec![(0, 2), (24, 2), (25, 7), (28, 2)],
vec![
(2, 2),
(8, 3),
(14, 3),
(15, 3),
(18, 1),
(20, 3),
(22, 2),
(23, 5),
(29, 4),
(30, 3),
(31, 4),
],
vec![
(8, 4),
(9, 2),
(13, 3),
(14, 2),
(15, 4),
(18, 2),
(19, 1),
(20, 1),
(23, 4),
(26, 2),
(27, 4),
(28, 2),
(29, 2),
(30, 3),
(31, 4),
(32, 5),
(22, 3),
],
];

let mut graph = G::with_capacity(N, M);

let mut node_indices = Vec::with_capacity(N);
for (row, neighbors) in adjacency_list.into_iter().enumerate() {
let node_id = graph.add_node(default_node_weight(membership.contains(&(row as u8))));
node_indices.push(node_id);

for (neighbor, weight) in neighbors.into_iter() {
graph.add_edge(
node_indices[neighbor],
node_indices[row],
default_edge_weight(weight),
);
}
}
graph
}
2 changes: 2 additions & 0 deletions rustworkx-core/src/generators/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ mod grid_graph;
mod heavy_hex_graph;
mod heavy_square_graph;
mod hexagonal_lattice_graph;
mod karate_club;
mod lollipop_graph;
mod path_graph;
mod petersen_graph;
Expand Down Expand Up @@ -55,6 +56,7 @@ pub use grid_graph::grid_graph;
pub use heavy_hex_graph::heavy_hex_graph;
pub use heavy_square_graph::heavy_square_graph;
pub use hexagonal_lattice_graph::{hexagonal_lattice_graph, hexagonal_lattice_graph_weighted};
pub use karate_club::karate_club_graph;
pub use lollipop_graph::lollipop_graph;
pub use path_graph::path_graph;
pub use petersen_graph::petersen_graph;
Expand Down
1 change: 1 addition & 0 deletions rustworkx/generators/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,4 @@ def directed_complete_graph(
multigraph: bool = ...,
) -> PyDiGraph: ...
def dorogovtsev_goltsev_mendes_graph(n: int) -> PyGraph: ...
def karate_club_graph(multigraph: bool = ...) -> PyGraph: ...
54 changes: 54 additions & 0 deletions src/generators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,59 @@ pub fn dorogovtsev_goltsev_mendes_graph(py: Python, n: usize) -> PyResult<graph:
})
}

/// Generates Zachary's Karate Club graph.
///
/// Zachary's Karate Club graph is a well-known social network that represents
/// the relations between 34 members of a karate club, as described in the
/// paper by Zachary [1]_.
///
/// The graph is undirected and contains 34 nodes (members) and 78 edges
/// (interactions). Each node represents a club member, and each edge represents
/// a relationship between two members.
///
/// :param bool multigraph: When set to ``False`` the output
/// :class:`~rustworkx.PyGraph` object will not be not be a multigraph and
/// won't allow parallel edges to be added. Instead
/// calls which would create a parallel edge will update the existing edge.
///
/// :returns: The undirected graph representing the Karate club graph.
/// Each node is labeled according to its assigned faction.
///
/// :rtype: PyGraph
///
/// .. jupyter-execute::
///
/// import rustworkx.generators
/// from rustworkx.visualization import mpl_draw
///
/// graph = rustworkx.generators.karate_club_graph()
/// layout = rustworkx.circular_layout(graph)
/// mpl_draw(graph, pos=layout)
///
/// .. [1] Zachary, Wayne W.
/// "An information flow model for conflict and fission in small groups"
/// Journal of Anthropological Research, 33, 452-473.
/// https://doi.org/10.1086/jar.33.4.3629752
#[pyfunction]
#[pyo3(
signature=(multigraph=true),
)]
pub fn karate_club_graph(py: Python, multigraph: bool) -> PyResult<graph::PyGraph> {
let default_node_fn = |w: bool| match w {
true => "Mr. Hi".to_object(py),
false => "Officer".to_object(py),
};
let default_edge_fn = |w: usize| (w as f64).to_object(py);
let graph: StablePyGraph<Undirected> =
core_generators::karate_club_graph(default_node_fn, default_edge_fn);
Ok(graph::PyGraph {
graph,
node_removed: false,
multigraph,
attrs: py.None(),
})
}

#[pymodule]
pub fn generators(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(cycle_graph))?;
Expand Down Expand Up @@ -1744,5 +1797,6 @@ pub fn generators(_py: Python, m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(complete_graph))?;
m.add_wrapped(wrap_pyfunction!(directed_complete_graph))?;
m.add_wrapped(wrap_pyfunction!(dorogovtsev_goltsev_mendes_graph))?;
m.add_wrapped(wrap_pyfunction!(karate_club_graph))?;
Ok(())
}
Loading

0 comments on commit dcfb9e0

Please sign in to comment.