1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use alloc::{vec, vec::Vec};
use num_traits::AsPrimitive;
use super::{
NodeScorer,
motifs::{MotifCountOrdering, build_undirected_motif_context},
};
use crate::{impls::SortedIterator, traits::UndirectedMonopartiteMonoplexGraph};
pub(super) fn square_counts_with_ordering<G>(graph: &G, ordering: MotifCountOrdering) -> Vec<usize>
where
G: UndirectedMonopartiteMonoplexGraph,
{
let context = build_undirected_motif_context(graph, ordering);
let n = context.nodes.len();
if n == 0 {
return Vec::new();
}
let mut counts = vec![0usize; n];
let mut visited = vec![0usize; n];
for root_index in 0..n {
if !context.in_cover[root_index] {
continue;
}
let root_node = context.nodes[root_index];
let stamp = root_index + 1;
for via in graph.neighbors(root_node) {
let via_index = via.as_();
if via_index == root_index {
continue;
}
for candidate in graph.neighbors(via) {
let candidate_index = candidate.as_();
if candidate_index == root_index
|| candidate_index == via_index
|| !context.in_cover[candidate_index]
|| context.rank[candidate_index] >= context.rank[root_index]
|| visited[candidate_index] == stamp
{
continue;
}
visited[candidate_index] = stamp;
let candidate_node = context.nodes[candidate_index];
let mut cover_common = 0usize;
let mut common_nodes = Vec::new();
for common in
graph.neighbors(root_node).sorted_intersection(graph.neighbors(candidate_node))
{
let common_index = common.as_();
if common_index == root_index || common_index == candidate_index {
continue;
}
common_nodes.push(common_index);
if context.in_cover[common_index] {
cover_common += 1;
}
}
let common_count = common_nodes.len();
if common_count < 2 {
continue;
}
let outside_common = common_count - cover_common;
let root_contribution = outside_common * outside_common.saturating_sub(1) / 2
+ outside_common * cover_common;
counts[root_index] += root_contribution;
counts[candidate_index] += root_contribution;
let common_contribution = common_count - 1;
for common_index in common_nodes {
counts[common_index] += common_contribution;
}
}
}
}
counts
}
/// Square-count scorer.
///
/// The score of a node is the number of distinct 4-cycles incident to that
/// node. The ordering selects the paper's vertex-cover construction heuristic.
/// The paper does not show a clear globally best schema for square counting, so
/// the default is [`MotifCountOrdering::Natural`]. Chorded 4-cycles are counted
/// in all modes.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SquareCountScorer {
ordering: MotifCountOrdering,
}
impl SquareCountScorer {
/// Creates a square-count scorer with the selected cover-ordering
/// heuristic.
///
/// # Examples
/// ```
/// use geometric_traits::{
/// impls::{CSR2D, SortedVec, SymmetricCSR2D},
/// prelude::*,
/// traits::{
/// SquareMatrix, VocabularyBuilder,
/// algorithms::{
/// MotifCountOrdering, NodeScorer, SquareCountScorer, randomized_graphs::cycle_graph,
/// },
/// },
/// };
///
/// fn wrap_undi(matrix: SymmetricCSR2D<CSR2D<usize, usize, usize>>) -> UndiGraph<usize> {
/// let nodes: SortedVec<usize> = GenericVocabularyBuilder::default()
/// .expected_number_of_symbols(matrix.order())
/// .symbols((0..matrix.order()).enumerate())
/// .build()
/// .unwrap();
/// UndiGraph::from((nodes, matrix))
/// }
///
/// let graph = wrap_undi(cycle_graph(4));
/// let scorer = SquareCountScorer::new(MotifCountOrdering::Natural);
///
/// assert_eq!(scorer.score_nodes(&graph), vec![1, 1, 1, 1]);
/// ```
#[inline]
#[must_use]
pub const fn new(ordering: MotifCountOrdering) -> Self {
Self { ordering }
}
/// Returns the selected cover-ordering heuristic.
///
/// # Examples
/// ```
/// use geometric_traits::traits::algorithms::{MotifCountOrdering, SquareCountScorer};
///
/// let scorer = SquareCountScorer::new(MotifCountOrdering::Natural);
/// assert_eq!(scorer.ordering(), MotifCountOrdering::Natural);
/// ```
#[inline]
#[must_use]
pub const fn ordering(self) -> MotifCountOrdering {
self.ordering
}
}
impl Default for SquareCountScorer {
#[inline]
fn default() -> Self {
Self::new(MotifCountOrdering::Natural)
}
}
impl<G> NodeScorer<G> for SquareCountScorer
where
G: UndirectedMonopartiteMonoplexGraph,
{
type Score = usize;
fn score_nodes(&self, graph: &G) -> Vec<Self::Score> {
square_counts_with_ordering(graph, self.ordering)
}
}