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

clustering coefficient update #1909

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

Conversation

wyatt-joyner-pometry
Copy link
Contributor

What changes were proposed in this pull request?

Refactor global and local clustering coefficient. Add two variants of batch local clustering coefficient.

Why are the changes needed?

It's currently extremely inefficient to run LCC on a group of nodes. The batch versions should do a better job of parallelizing the process and reducing overhead.

Does this PR introduce any user-facing change? If yes is this documented?

'clustering_coefficient' is renamed to 'global_clustering_coefficient'. All of the clustering coefficient variants have been moved to a submodule of 'metrics' called 'clustering_coefficient'. The new batch implementations have corresponding docstrings.

How was this patch tested?

The two methods were tested for parity against the existing implementation in Rust and Python.

Are there any further changes required?

Currently working on an approximate version that uses HyperLogLog.

Copy link
Collaborator

@ljeub-pometry ljeub-pometry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • the path-based algorithm has room for optimisation
  • we need a benchmark to decide whether it is worth keeping the set-based algorithm at all
  • the filtering of nodes for the batch versions is unnecessarily inefficient (no need for creating subgraph views)
  • python wrappers should raise proper errors instead of panicking

Comment on lines +49 to +54
if all_src_nodes == false {
(nodes, src_nodes) = filter_nodes(graph, &v);
g = graph.subgraph(nodes);
} else {
g = graph.subgraph(graph.nodes());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filter thing is rather inefficient? Most efficient is probably a Vec<bool> for the src_nodes?

Comment on lines +69 to +75
.filter_map(|nb| match g.has_edge(nb[0].id(), nb[1].id()) {
true => Some(1),
false => match g.has_edge(nb[1].id(), nb[0].id()) {
true => Some(1),
false => None,
},
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should use the internal ids, not global ids (much more efficient as this version incurs unnecessary hash map lookups)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also the option of considering nodes in degree order which eliminates the triple-counting of triangles and reduces the number of existence checks by quite a lot. We can then simply use atomic accumulators to keep track of the number of triangles at each node.

Comment on lines +48 to +53
if all_src_nodes == false {
(nodes, src_nodes) = filter_nodes(graph, &v);
g = graph.subgraph(nodes);
} else {
g = graph.subgraph(graph.nodes());
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same problem as the other version, filter should be more efficient

@@ -0,0 +1,40 @@
use crate::{core::entities::nodes::node_ref::AsNodeRef, db::api::view::*};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the filter_nodes bit is necessary

@@ -76,6 +77,7 @@ mod triangle_count_tests {
prelude::NO_PROPS,
test_storage,
};
use tracing::info;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used?

///
/// # Returns
/// the local clustering coefficient of node v in g.
pub fn local_clustering_coefficient_batch_path<G: StaticGraphViewOps, V: AsNodeRef>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need some benchmarks, is this actually much slower than the set-based version?

) -> AlgorithmResult<DynamicGraph, f64, OrderedFloat<f64>> {
local_clustering_coefficient_batch_intersection_rs(
&graph.graph,
process_node_param(v).unwrap(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to return a PyResult!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively, we can push all of this to the pyo3 layer if we add an struct/enum which implements FromPyObject instead of the process_node_param function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants