Skip to content

Commit

Permalink
Make available weighted_closeness for python users
Browse files Browse the repository at this point in the history
Signed-off-by: FedericoBruzzone <[email protected]>
  • Loading branch information
FedericoBruzzone committed Feb 12, 2025
1 parent 718a922 commit b364913
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ retworkx/*pyd
**/*.so
retworkx-core/Cargo.lock
**/.DS_Store
venv/
requirements.txt
52 changes: 52 additions & 0 deletions rustworkx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,58 @@ def closeness_centrality(graph, wf_improved=True):
raise TypeError("Invalid input type %s for graph" % type(graph))


@_rustworkx_dispatch
def newman_weighted_closeness_centrality(graph, weight_fn, wf_improved=True, default_weight=0.0):
r"""Compute the weighted closeness centrality of each node in the graph.
The weighted closeness centrality is an extension of the standard closeness
centrality measure where edge weights represent connection strength rather
than distance. To properly compute shortest paths, weights are inverted
so that stronger connections correspond to shorter effective distances.
The algorithm follows the method described by Newman (2001) in analyzing
weighted graphs.[Newman]
The edges originally represent connection strength between nodes.
The idea is that if two nodes have a strong connection, the computed
distance between them should be small (shorter), and vice versa.
Note that this assume that the graph is modelling a measure of
connection strength (e.g. trust, collaboration, or similarity).
If the graph is not modelling a measure of connection strength,
the function `weight_fn` should invert the weights before calling this
function, if not it is considered as a logical error.
In the case of a graphs with more than one connected component there is
an alternative improved formula that calculates the closeness centrality
as "a ratio of the fraction of actors in the group who are reachable, to
the average distance".[WF]
You can enable this by setting `wf_improved` to `true`.
:param PyGraph graph: The input graph. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param weight_fn: An optional input callable that will be passed the edge's
payload object and is expected to return a `float` weight for that edge.
If this is not specified ``default_weight`` will be used as the weight
for every edge in ``graph``
:param bool wf_improved: This is optional; the default is True. If True,
scale by the fraction of nodes reachable.
:param float default_weight: If ``weight_fn`` is not set the default weight
value to use for the weight of all edges
:returns: A dictionary mapping each node index to its closeness centrality.
:rtype: CentralityMapping
.. [Newman]: Newman, M. E. J. (2001). Scientific collaboration networks.
II. Shortest paths, weighted networks, and centrality.
Physical Review E, 64(1), 016132.
.. [WF]: Wasserman, S., & Faust, K. (1994). Social Network Analysis:
Methods and Applications (Structural Analysis in the Social Sciences).
Cambridge: Cambridge University Press.
<https://doi.org/10.1017/CBO9780511815478>
"""
raise TypeError("Invalid input type %s for graph" % type(graph))


@_rustworkx_dispatch
def degree_centrality(graph):
r"""Compute the degree centrality of each node in a graph object.
Expand Down
130 changes: 130 additions & 0 deletions src/centrality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,136 @@ pub fn digraph_closeness_centrality(
}
}

/// Compute the weighted closeness centrality of each node in the graph.
///
/// The weighted closeness centrality is an extension of the standard closeness
/// centrality measure where edge weights represent connection strength rather
/// than distance. To properly compute shortest paths, weights are inverted
/// so that stronger connections correspond to shorter effective distances.
/// The algorithm follows the method described by Newman (2001) in analyzing
/// weighted graphs.[Newman]
///
/// The edges originally represent connection strength between nodes.
/// The idea is that if two nodes have a strong connection, the computed
/// distance between them should be small (shorter), and vice versa.
/// Note that this assume that the graph is modelling a measure of
/// connection strength (e.g. trust, collaboration, or similarity).
/// If the graph is not modelling a measure of connection strength,
/// the function `weight_fn` should invert the weights before calling this
/// function, if not it is considered as a logical error.
///
/// In the case of a graphs with more than one connected component there is
/// an alternative improved formula that calculates the closeness centrality
/// as "a ratio of the fraction of actors in the group who are reachable, to
/// the average distance".[WF]
/// You can enable this by setting `wf_improved` to `true`.
///
/// :param PyGraph graph: The input graph. Can either be a
/// :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
/// :param weight_fn: An optional input callable that will be passed the edge's
/// payload object and is expected to return a `float` weight for that edge.
/// If this is not specified ``default_weight`` will be used as the weight
/// for every edge in ``graph``
/// :param bool wf_improved: This is optional; the default is True. If True,
/// scale by the fraction of nodes reachable.
/// :param float default_weight: If ``weight_fn`` is not set the default weight
/// value to use for the weight of all edges
///
/// :returns: A dictionary mapping each node index to its closeness centrality.
/// :rtype: CentralityMapping
#[pyfunction(signature = (graph, weight_fn, wf_improved=true, default_weight = 0.0))]
pub fn graph_newman_weighted_closeness_centrality(
py: Python,
graph: &graph::PyGraph,
weight_fn: PyObject,
wf_improved: bool,
default_weight: f64,
) -> PyResult<CentralityMapping> {
let mut edge_weights = vec![default_weight; graph.graph.edge_bound()];
let cost_fn = CostFn::from(weight_fn);
for edge in graph.graph.edge_indices() {
edge_weights[edge.index()] = cost_fn.call(py, graph.graph.edge_weight(edge).unwrap())?;
}

let closeness =
centrality::newman_weighted_closeness_centrality(&graph.graph, wf_improved, |e| {
edge_weights[e.id().index()]
});

Ok(CentralityMapping {
centralities: closeness
.into_iter()
.enumerate()
.filter_map(|(i, v)| v.map(|x| (i, x)))
.collect(),
})
}

/// Compute the weighted closeness centrality of each node in the graph.
///
/// The weighted closeness centrality is an extension of the standard closeness
/// centrality measure where edge weights represent connection strength rather
/// than distance. To properly compute shortest paths, weights are inverted
/// so that stronger connections correspond to shorter effective distances.
/// The algorithm follows the method described by Newman (2001) in analyzing
/// weighted graphs.[Newman]
///
/// The edges originally represent connection strength between nodes.
/// The idea is that if two nodes have a strong connection, the computed
/// distance between them should be small (shorter), and vice versa.
/// Note that this assume that the graph is modelling a measure of
/// connection strength (e.g. trust, collaboration, or similarity).
/// If the graph is not modelling a measure of connection strength,
/// the function `weight_fn` should invert the weights before calling this
/// function, if not it is considered as a logical error.
///
/// In the case of a graphs with more than one connected component there is
/// an alternative improved formula that calculates the closeness centrality
/// as "a ratio of the fraction of actors in the group who are reachable, to
/// the average distance".[WF]
/// You can enable this by setting `wf_improved` to `true`.
///
/// :param PyDiGraph graph: The input graph. Can either be a
/// :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
/// :param weight_fn: An optional input callable that will be passed the edge's
/// payload object and is expected to return a `float` weight for that edge.
/// If this is not specified ``default_weight`` will be used as the weight
/// for every edge in ``graph``
/// :param bool wf_improved: This is optional; the default is True. If True,
/// scale by the fraction of nodes reachable.
/// :param float default_weight: If ``weight_fn`` is not set the default weight
/// value to use for the weight of all edges
///
/// :returns: A dictionary mapping each node index to its closeness centrality.
/// :rtype: CentralityMapping
#[pyfunction(signature = (graph, weight_fn, wf_improved=true, default_weight = 0.0))]
pub fn digraph_newman_weighted_closeness_centrality(
py: Python,
graph: &digraph::PyDiGraph,
weight_fn: PyObject,
wf_improved: bool,
default_weight: f64,
) -> PyResult<CentralityMapping> {
let mut edge_weights = vec![default_weight; graph.graph.edge_bound()];
let cost_fn = CostFn::from(weight_fn);
for edge in graph.graph.edge_indices() {
edge_weights[edge.index()] = cost_fn.call(py, graph.graph.edge_weight(edge).unwrap())?;
}

let closeness =
centrality::newman_weighted_closeness_centrality(&graph.graph, wf_improved, |e| {
edge_weights[e.id().index()]
});

Ok(CentralityMapping {
centralities: closeness
.into_iter()
.enumerate()
.filter_map(|(i, v)| v.map(|x| (i, x)))
.collect(),
})
}

/// Compute the edge betweenness centrality of all edges in a :class:`~PyGraph`.
///
/// Edge betweenness centrality of an edge :math:`e` is the sum of the
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,8 @@ fn rustworkx(py: Python<'_>, m: &Bound<PyModule>) -> PyResult<()> {
m.add_wrapped(wrap_pyfunction!(digraph_betweenness_centrality))?;
m.add_wrapped(wrap_pyfunction!(graph_closeness_centrality))?;
m.add_wrapped(wrap_pyfunction!(digraph_closeness_centrality))?;
m.add_wrapped(wrap_pyfunction!(graph_newman_weighted_closeness_centrality))?;
m.add_wrapped(wrap_pyfunction!(digraph_newman_weighted_closeness_centrality))?;
m.add_wrapped(wrap_pyfunction!(graph_edge_betweenness_centrality))?;
m.add_wrapped(wrap_pyfunction!(digraph_edge_betweenness_centrality))?;
m.add_wrapped(wrap_pyfunction!(graph_eigenvector_centrality))?;
Expand Down

0 comments on commit b364913

Please sign in to comment.