mod application;
mod betweenness_centrality;
mod closeness_centrality;
mod degeneracy;
mod degree;
mod dsatur;
mod katz_centrality;
mod layered_label_propagation;
mod local_clustering;
mod motifs;
mod pagerank;
mod power_iteration_eigenvector_centrality;
mod square_clustering;
mod squares;
mod traversal;
mod triangles;
use alloc::vec::Vec;
pub use application::{NodeOrderApplicableGraph, PermutableVocabulary, apply_node_order_to_graph};
pub use betweenness_centrality::{BetweennessCentralityScorer, BetweennessCentralityScorerBuilder};
pub use closeness_centrality::{ClosenessCentralityScorer, ClosenessCentralityScorerBuilder};
pub use degeneracy::{CoreNumberScorer, DegeneracySorter};
pub use degree::{DegreeScorer, SecondOrderDegreeScorer};
pub use dsatur::DsaturSorter;
pub use katz_centrality::{KatzCentralityScorer, KatzCentralityScorerBuilder};
pub use layered_label_propagation::{
LAYERED_LABEL_PROPAGATION_DEFAULT_GAMMAS, LayeredLabelPropagationError,
LayeredLabelPropagationSorter,
};
pub use local_clustering::LocalClusteringCoefficientScorer;
pub use motifs::MotifCountOrdering;
use num_traits::{AsPrimitive, cast};
pub use pagerank::{PageRankScorer, PageRankScorerBuilder};
pub use power_iteration_eigenvector_centrality::{
PowerIterationEigenvectorCentralityScorer, PowerIterationEigenvectorCentralityScorerBuilder,
};
pub use square_clustering::SquareClusteringCoefficientScorer;
pub use squares::SquareCountScorer;
pub use traversal::{
BfsTraversalSorter, DfsTraversalSorter, TraversalNeighborOrder, TraversalSeedStrategy,
};
pub use triangles::TriangleCountScorer;
use crate::traits::{MonopartiteGraph, TotalOrd};
pub trait NodeSorter<G>
where
G: MonopartiteGraph,
{
#[must_use]
fn sort_nodes(&self, graph: &G) -> Vec<G::NodeId>;
}
pub trait NodeScorer<G>
where
G: MonopartiteGraph,
{
type Score;
#[must_use]
fn score_nodes(&self, graph: &G) -> Vec<Self::Score>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AscendingScoreSorter<S> {
scorer: S,
}
impl<S> AscendingScoreSorter<S> {
#[inline]
#[must_use]
pub const fn new(scorer: S) -> Self {
Self { scorer }
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DescendingScoreSorter<S> {
scorer: S,
}
impl<S> DescendingScoreSorter<S> {
#[inline]
#[must_use]
pub const fn new(scorer: S) -> Self {
Self { scorer }
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct DescendingLexicographicScoreSorter<Primary, Secondary> {
primary_scorer: Primary,
secondary_scorer: Secondary,
}
impl<Primary, Secondary> DescendingLexicographicScoreSorter<Primary, Secondary> {
#[inline]
#[must_use]
pub const fn new(primary_scorer: Primary, secondary_scorer: Secondary) -> Self {
Self { primary_scorer, secondary_scorer }
}
}
fn assert_score_count(actual: usize, expected: usize, wrong_length_message: &str) {
assert!(actual == expected, "{wrong_length_message}");
}
fn sort_nodes_by_scores<G, S, F>(graph: &G, scorer: &S, compare_scores: F) -> Vec<G::NodeId>
where
G: MonopartiteGraph,
S: NodeScorer<G>,
S::Score: TotalOrd,
F: Fn(&S::Score, &S::Score) -> core::cmp::Ordering,
{
const WRONG_LENGTH_MESSAGE: &str = "node scorer must return one score per node";
let node_scores = scorer.score_nodes(graph);
let mut nodes: Vec<G::NodeId> = graph.node_ids().collect();
assert_score_count(node_scores.len(), graph.number_of_nodes().as_(), WRONG_LENGTH_MESSAGE);
nodes.sort_unstable_by(|left, right| {
let left_index = (*left).as_();
let right_index = (*right).as_();
compare_scores(&node_scores[left_index], &node_scores[right_index])
.then_with(|| left_index.cmp(&right_index))
});
nodes
}
fn sort_nodes_by_score_pairs<G, Primary, Secondary>(
graph: &G,
primary: &Primary,
secondary: &Secondary,
) -> Vec<G::NodeId>
where
G: MonopartiteGraph,
Primary: NodeScorer<G>,
Secondary: NodeScorer<G>,
Primary::Score: TotalOrd,
Secondary::Score: TotalOrd,
{
const WRONG_PRIMARY_LENGTH_MESSAGE: &str = "primary node scorer must return one score per node";
const WRONG_SECONDARY_LENGTH_MESSAGE: &str =
"secondary node scorer must return one score per node";
let primary_scores = primary.score_nodes(graph);
let secondary_scores = secondary.score_nodes(graph);
let mut nodes: Vec<G::NodeId> = graph.node_ids().collect();
assert_score_count(
primary_scores.len(),
graph.number_of_nodes().as_(),
WRONG_PRIMARY_LENGTH_MESSAGE,
);
assert_score_count(
secondary_scores.len(),
graph.number_of_nodes().as_(),
WRONG_SECONDARY_LENGTH_MESSAGE,
);
nodes.sort_unstable_by(|left, right| {
let left_index = (*left).as_();
let right_index = (*right).as_();
primary_scores[right_index]
.total_cmp(&primary_scores[left_index])
.then_with(|| secondary_scores[right_index].total_cmp(&secondary_scores[left_index]))
.then_with(|| left_index.cmp(&right_index))
});
nodes
}
impl<G, S> NodeSorter<G> for AscendingScoreSorter<S>
where
G: MonopartiteGraph,
S: NodeScorer<G>,
S::Score: TotalOrd,
{
#[inline]
fn sort_nodes(&self, graph: &G) -> Vec<G::NodeId> {
sort_nodes_by_scores(graph, &self.scorer, TotalOrd::total_cmp)
}
}
impl<G, S> NodeSorter<G> for DescendingScoreSorter<S>
where
G: MonopartiteGraph,
S: NodeScorer<G>,
S::Score: TotalOrd,
{
#[inline]
fn sort_nodes(&self, graph: &G) -> Vec<G::NodeId> {
sort_nodes_by_scores(graph, &self.scorer, |left, right| right.total_cmp(left))
}
}
impl<G, Primary, Secondary> NodeSorter<G> for DescendingLexicographicScoreSorter<Primary, Secondary>
where
G: MonopartiteGraph,
Primary: NodeScorer<G>,
Secondary: NodeScorer<G>,
Primary::Score: TotalOrd,
Secondary::Score: TotalOrd,
{
#[inline]
fn sort_nodes(&self, graph: &G) -> Vec<G::NodeId> {
sort_nodes_by_score_pairs(graph, &self.primary_scorer, &self.secondary_scorer)
}
}
pub(super) const PAGERANK_SCORE_SCALE: f64 = 1.0e12;
pub(super) const KATZ_SCORE_SCALE: f64 = 1.0e12;
pub(super) const BETWEENNESS_SCORE_SCALE: f64 = 1.0e12;
pub(super) const CLOSENESS_SCORE_SCALE: f64 = 1.0e12;
pub(super) const LOCAL_CLUSTERING_SCORE_SCALE: f64 = 1.0e12;
pub(super) const SQUARE_CLUSTERING_SCORE_SCALE: f64 = 1.0e12;
pub(super) const POWER_ITERATION_EIGENVECTOR_SCORE_SCALE: f64 = 1.0e12;
#[inline]
pub(super) fn usize_to_f64(value: usize) -> f64 {
cast::<usize, f64>(value).expect("graph sizes and degrees must fit into f64 for PageRank")
}