1use crate::base::{EdgeWeight, Graph, IndexType, Node};
14use std::collections::{HashMap, HashSet};
15use std::hash::Hash;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum MotifType {
20 Triangle,
22 Square,
24 Star3,
26 Clique4,
28 Path3,
30 BiFan,
32 FeedForwardLoop,
34 BiDirectional,
36}
37
38#[derive(Debug, Clone)]
40pub struct VF2Result<N: Node> {
41 pub is_match: bool,
43 pub mappings: Vec<HashMap<N, N>>,
45 pub states_explored: usize,
47}
48
49#[derive(Debug, Clone)]
51pub struct WLKernelResult {
52 pub kernel_matrix: Vec<Vec<f64>>,
54 pub feature_vectors: Vec<HashMap<String, usize>>,
56 pub iterations: usize,
58}
59
60#[derive(Debug, Clone)]
62pub struct GraphletDegreeVector {
63 pub orbit_counts: Vec<usize>,
65}
66
67#[derive(Debug, Clone)]
69pub struct GraphletDDResult<N: Node> {
70 pub node_gdvs: HashMap<N, GraphletDegreeVector>,
72 pub gdv_agreement: f64,
74}
75
76#[derive(Debug, Clone)]
78pub struct FrequentPattern<N: Node> {
79 pub nodes: Vec<N>,
81 pub edges: Vec<(usize, usize)>,
83 pub support: usize,
85}
86
87pub fn find_motifs<N, E, Ix>(graph: &Graph<N, E, Ix>, motiftype: MotifType) -> Vec<Vec<N>>
93where
94 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
95 E: EdgeWeight + Send + Sync,
96 Ix: IndexType + Send + Sync,
97{
98 match motiftype {
99 MotifType::Triangle => find_triangles(graph),
100 MotifType::Square => find_squares(graph),
101 MotifType::Star3 => find_star3s(graph),
102 MotifType::Clique4 => find_clique4s(graph),
103 MotifType::Path3 => find_path3s(graph),
104 MotifType::BiFan => find_bi_fans(graph),
105 MotifType::FeedForwardLoop => find_feed_forward_loops(graph),
106 MotifType::BiDirectional => find_bidirectional_motifs(graph),
107 }
108}
109
110pub fn count_motif_frequencies<N, E, Ix>(graph: &Graph<N, E, Ix>) -> HashMap<MotifType, usize>
112where
113 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
114 E: EdgeWeight + Send + Sync,
115 Ix: IndexType + Send + Sync,
116{
117 use scirs2_core::parallel_ops::*;
118
119 let motif_types = vec![
120 MotifType::Triangle,
121 MotifType::Square,
122 MotifType::Star3,
123 MotifType::Clique4,
124 MotifType::Path3,
125 MotifType::BiFan,
126 MotifType::FeedForwardLoop,
127 MotifType::BiDirectional,
128 ];
129
130 motif_types
131 .par_iter()
132 .map(|motif_type| {
133 let count = find_motifs(graph, *motif_type).len();
134 (*motif_type, count)
135 })
136 .collect()
137}
138
139pub fn count_3node_motifs<N, E, Ix>(graph: &Graph<N, E, Ix>) -> (usize, usize)
141where
142 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
143 E: EdgeWeight + Send + Sync,
144 Ix: IndexType + Send + Sync,
145{
146 let triangles = find_triangles(graph).len();
147 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
148
149 let mut open_triads = 0usize;
151 for node in &nodes {
152 let deg = graph.degree(node);
153 if deg >= 2 {
154 open_triads += deg * (deg - 1) / 2;
156 }
157 }
158 let open = open_triads.saturating_sub(3 * triangles);
160
161 (triangles, open)
162}
163
164pub fn count_4node_motifs<N, E, Ix>(graph: &Graph<N, E, Ix>) -> HashMap<&'static str, usize>
166where
167 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
168 E: EdgeWeight + Send + Sync,
169 Ix: IndexType + Send + Sync,
170{
171 let mut counts = HashMap::new();
172 counts.insert("path3", find_path3s(graph).len());
173 counts.insert("star3", find_star3s(graph).len());
174 counts.insert("square", find_squares(graph).len());
175 counts.insert("clique4", find_clique4s(graph).len());
176 counts
177}
178
179pub fn sample_motif_frequencies<N, E, Ix>(
181 graph: &Graph<N, E, Ix>,
182 sample_size: usize,
183 rng: &mut impl scirs2_core::random::Rng,
184) -> HashMap<MotifType, f64>
185where
186 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
187 E: EdgeWeight + Send + Sync,
188 Ix: IndexType + Send + Sync,
189{
190 use scirs2_core::random::seq::SliceRandom;
191
192 let all_nodes: Vec<_> = graph.nodes().into_iter().cloned().collect();
193 if all_nodes.len() <= sample_size {
194 return count_motif_frequencies(graph)
195 .into_iter()
196 .map(|(k, v)| (k, v as f64))
197 .collect();
198 }
199
200 let mut sampled_nodes = all_nodes.clone();
201 sampled_nodes.shuffle(rng);
202 sampled_nodes.truncate(sample_size);
203
204 let mut subgraph = crate::generators::create_graph::<N, E>();
205 for node in &sampled_nodes {
206 let _ = subgraph.add_node(node.clone());
207 }
208
209 for node1 in &sampled_nodes {
210 if let Ok(neighbors) = graph.neighbors(node1) {
211 for node2 in neighbors {
212 if sampled_nodes.contains(&node2) && node1 != &node2 {
213 if let Ok(weight) = graph.edge_weight(node1, &node2) {
214 let _ = subgraph.add_edge(node1.clone(), node2, weight);
215 }
216 }
217 }
218 }
219 }
220
221 let subgraph_counts = count_motif_frequencies(&subgraph);
222 let scaling_factor = (all_nodes.len() as f64) / (sample_size as f64);
223
224 subgraph_counts
225 .into_iter()
226 .map(|(motif_type, count)| (motif_type, count as f64 * scaling_factor))
227 .collect()
228}
229
230pub fn vf2_subgraph_isomorphism<N, E, Ix>(
244 pattern: &Graph<N, E, Ix>,
245 target: &Graph<N, E, Ix>,
246 max_matches: usize,
247) -> VF2Result<N>
248where
249 N: Node + Clone + Hash + Eq + std::fmt::Debug,
250 E: EdgeWeight,
251 Ix: IndexType,
252{
253 let p_nodes: Vec<N> = pattern.nodes().into_iter().cloned().collect();
254 let t_nodes: Vec<N> = target.nodes().into_iter().cloned().collect();
255
256 if p_nodes.is_empty() {
257 return VF2Result {
258 is_match: true,
259 mappings: vec![HashMap::new()],
260 states_explored: 0,
261 };
262 }
263
264 if p_nodes.len() > t_nodes.len() {
265 return VF2Result {
266 is_match: false,
267 mappings: vec![],
268 states_explored: 0,
269 };
270 }
271
272 let p_adj = build_adj_set(pattern, &p_nodes);
274 let t_adj = build_adj_set(target, &t_nodes);
275
276 let p_idx: HashMap<N, usize> = p_nodes
277 .iter()
278 .enumerate()
279 .map(|(i, n)| (n.clone(), i))
280 .collect();
281 let t_idx: HashMap<N, usize> = t_nodes
282 .iter()
283 .enumerate()
284 .map(|(i, n)| (n.clone(), i))
285 .collect();
286
287 let mut state = VF2State {
288 core_p: vec![None; p_nodes.len()],
289 core_t: vec![None; t_nodes.len()],
290 in_p: vec![0usize; p_nodes.len()],
291 out_p: vec![0usize; p_nodes.len()],
292 in_t: vec![0usize; t_nodes.len()],
293 out_t: vec![0usize; t_nodes.len()],
294 depth: 0,
295 };
296
297 let mut mappings = Vec::new();
298 let mut states_explored = 0;
299 let limit = if max_matches == 0 {
300 usize::MAX
301 } else {
302 max_matches
303 };
304
305 vf2_match(
306 &p_nodes,
307 &t_nodes,
308 &p_adj,
309 &t_adj,
310 &p_idx,
311 &t_idx,
312 &mut state,
313 &mut mappings,
314 &mut states_explored,
315 limit,
316 );
317
318 VF2Result {
319 is_match: !mappings.is_empty(),
320 mappings,
321 states_explored,
322 }
323}
324
325struct VF2State {
327 core_p: Vec<Option<usize>>, core_t: Vec<Option<usize>>, in_p: Vec<usize>,
330 out_p: Vec<usize>,
331 in_t: Vec<usize>,
332 out_t: Vec<usize>,
333 depth: usize,
334}
335
336fn build_adj_set<N, E, Ix>(graph: &Graph<N, E, Ix>, nodes: &[N]) -> Vec<HashSet<usize>>
337where
338 N: Node + Clone + Hash + Eq + std::fmt::Debug,
339 E: EdgeWeight,
340 Ix: IndexType,
341{
342 let idx_map: HashMap<N, usize> = nodes
343 .iter()
344 .enumerate()
345 .map(|(i, n)| (n.clone(), i))
346 .collect();
347 let mut adj = vec![HashSet::new(); nodes.len()];
348 for (i, node) in nodes.iter().enumerate() {
349 if let Ok(neighbors) = graph.neighbors(node) {
350 for neighbor in &neighbors {
351 if let Some(&j) = idx_map.get(neighbor) {
352 adj[i].insert(j);
353 }
354 }
355 }
356 }
357 adj
358}
359
360fn vf2_match<N: Node + Clone + Hash + Eq + std::fmt::Debug>(
361 p_nodes: &[N],
362 t_nodes: &[N],
363 p_adj: &[HashSet<usize>],
364 t_adj: &[HashSet<usize>],
365 _p_idx: &HashMap<N, usize>,
366 _t_idx: &HashMap<N, usize>,
367 state: &mut VF2State,
368 mappings: &mut Vec<HashMap<N, N>>,
369 states_explored: &mut usize,
370 limit: usize,
371) {
372 *states_explored += 1;
373
374 if mappings.len() >= limit {
375 return;
376 }
377
378 if state.depth == p_nodes.len() {
379 let mut mapping = HashMap::new();
381 for (pi, ti_opt) in state.core_p.iter().enumerate() {
382 if let Some(ti) = ti_opt {
383 mapping.insert(p_nodes[pi].clone(), t_nodes[*ti].clone());
384 }
385 }
386 mappings.push(mapping);
387 return;
388 }
389
390 let candidates = vf2_candidates(state, p_nodes.len(), t_nodes.len());
392
393 for (p, t) in candidates {
394 if vf2_feasible(p, t, p_adj, t_adj, state, p_nodes.len(), t_nodes.len()) {
395 state.core_p[p] = Some(t);
397 state.core_t[t] = Some(p);
398 let old_depth = state.depth;
399 state.depth += 1;
400
401 let mut in_p_changes = Vec::new();
403 let mut out_p_changes = Vec::new();
404 let mut in_t_changes = Vec::new();
405 let mut out_t_changes = Vec::new();
406
407 for &neighbor in &p_adj[p] {
408 if state.in_p[neighbor] == 0 && state.core_p[neighbor].is_none() {
409 state.in_p[neighbor] = state.depth;
410 in_p_changes.push(neighbor);
411 }
412 if state.out_p[neighbor] == 0 && state.core_p[neighbor].is_none() {
413 state.out_p[neighbor] = state.depth;
414 out_p_changes.push(neighbor);
415 }
416 }
417 for &neighbor in &t_adj[t] {
418 if state.in_t[neighbor] == 0 && state.core_t[neighbor].is_none() {
419 state.in_t[neighbor] = state.depth;
420 in_t_changes.push(neighbor);
421 }
422 if state.out_t[neighbor] == 0 && state.core_t[neighbor].is_none() {
423 state.out_t[neighbor] = state.depth;
424 out_t_changes.push(neighbor);
425 }
426 }
427
428 vf2_match(
429 p_nodes,
430 t_nodes,
431 p_adj,
432 t_adj,
433 _p_idx,
434 _t_idx,
435 state,
436 mappings,
437 states_explored,
438 limit,
439 );
440
441 state.core_p[p] = None;
443 state.core_t[t] = None;
444 state.depth = old_depth;
445
446 for idx in in_p_changes {
447 state.in_p[idx] = 0;
448 }
449 for idx in out_p_changes {
450 state.out_p[idx] = 0;
451 }
452 for idx in in_t_changes {
453 state.in_t[idx] = 0;
454 }
455 for idx in out_t_changes {
456 state.out_t[idx] = 0;
457 }
458
459 if mappings.len() >= limit {
460 return;
461 }
462 }
463 }
464}
465
466fn vf2_candidates(state: &VF2State, n_p: usize, n_t: usize) -> Vec<(usize, usize)> {
467 let p_node = (0..n_p).find(|&i| state.core_p[i].is_none());
469
470 if let Some(p) = p_node {
471 let candidates: Vec<(usize, usize)> = (0..n_t)
473 .filter(|&t| state.core_t[t].is_none())
474 .map(|t| (p, t))
475 .collect();
476 candidates
477 } else {
478 vec![]
479 }
480}
481
482fn vf2_feasible(
483 p: usize,
484 t: usize,
485 p_adj: &[HashSet<usize>],
486 t_adj: &[HashSet<usize>],
487 state: &VF2State,
488 _n_p: usize,
489 _n_t: usize,
490) -> bool {
491 for &p_neighbor in &p_adj[p] {
493 if let Some(t_mapped) = state.core_p[p_neighbor] {
494 if !t_adj[t].contains(&t_mapped) {
495 return false;
496 }
497 }
498 }
499
500 for &t_neighbor in &t_adj[t] {
502 if let Some(p_mapped) = state.core_t[t_neighbor] {
503 if !p_adj[p].contains(&p_mapped) {
504 return false;
505 }
506 }
507 }
508
509 let p_unmapped_neighbors = p_adj[p]
513 .iter()
514 .filter(|&&n| state.core_p[n].is_none())
515 .count();
516 let t_unmapped_neighbors = t_adj[t]
517 .iter()
518 .filter(|&&n| state.core_t[n].is_none())
519 .count();
520
521 p_unmapped_neighbors <= t_unmapped_neighbors
522}
523
524pub fn wl_subtree_kernel<N, E, Ix>(graphs: &[&Graph<N, E, Ix>], iterations: usize) -> WLKernelResult
538where
539 N: Node + Clone + Hash + Eq + std::fmt::Debug,
540 E: EdgeWeight,
541 Ix: IndexType,
542{
543 let num_graphs = graphs.len();
544
545 if num_graphs == 0 {
546 return WLKernelResult {
547 kernel_matrix: vec![],
548 feature_vectors: vec![],
549 iterations: 0,
550 };
551 }
552
553 let mut graph_labels: Vec<HashMap<N, String>> = graphs
555 .iter()
556 .map(|g| {
557 let mut labels = HashMap::new();
558 for node in g.nodes() {
559 labels.insert(node.clone(), format!("{}", g.degree(node)));
560 }
561 labels
562 })
563 .collect();
564
565 let mut all_features: Vec<HashMap<String, usize>> = vec![HashMap::new(); num_graphs];
566
567 for (gi, labels) in graph_labels.iter().enumerate() {
569 for label in labels.values() {
570 *all_features[gi].entry(label.clone()).or_insert(0) += 1;
571 }
572 }
573
574 for _iter in 0..iterations {
576 let mut new_graph_labels: Vec<HashMap<N, String>> = Vec::with_capacity(num_graphs);
577
578 for (gi, graph) in graphs.iter().enumerate() {
579 let mut new_labels = HashMap::new();
580
581 for node in graph.nodes() {
582 let own_label = graph_labels[gi].get(node).cloned().unwrap_or_default();
583
584 let mut neighbor_labels: Vec<String> = Vec::new();
586 if let Ok(neighbors) = graph.neighbors(node) {
587 for neighbor in &neighbors {
588 if let Some(label) = graph_labels[gi].get(neighbor) {
589 neighbor_labels.push(label.clone());
590 }
591 }
592 }
593 neighbor_labels.sort();
594
595 let new_label = format!("{own_label}|{}", neighbor_labels.join(","));
597 new_labels.insert(node.clone(), new_label.clone());
598
599 *all_features[gi].entry(new_label).or_insert(0) += 1;
600 }
601
602 new_graph_labels.push(new_labels);
603 }
604
605 graph_labels = new_graph_labels;
606 }
607
608 let mut kernel_matrix = vec![vec![0.0f64; num_graphs]; num_graphs];
610
611 for i in 0..num_graphs {
612 for j in i..num_graphs {
613 let mut dot = 0.0;
615 for (key, &count_i) in &all_features[i] {
616 if let Some(&count_j) = all_features[j].get(key) {
617 dot += (count_i as f64) * (count_j as f64);
618 }
619 }
620 kernel_matrix[i][j] = dot;
621 kernel_matrix[j][i] = dot;
622 }
623 }
624
625 WLKernelResult {
626 kernel_matrix,
627 feature_vectors: all_features,
628 iterations,
629 }
630}
631
632pub fn graphlet_degree_distribution<N, E, Ix>(graph: &Graph<N, E, Ix>) -> GraphletDDResult<N>
651where
652 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
653 E: EdgeWeight + Send + Sync,
654 Ix: IndexType + Send + Sync,
655{
656 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
657 let n = nodes.len();
658 let num_orbits = 10;
659
660 let node_to_idx: HashMap<N, usize> = nodes
661 .iter()
662 .enumerate()
663 .map(|(i, n)| (n.clone(), i))
664 .collect();
665
666 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
668 for (i, node) in nodes.iter().enumerate() {
669 if let Ok(neighbors) = graph.neighbors(node) {
670 for neighbor in &neighbors {
671 if let Some(&j) = node_to_idx.get(neighbor) {
672 adj[i].push(j);
673 }
674 }
675 }
676 }
677
678 let mut orbits = vec![vec![0usize; num_orbits]; n];
679
680 for i in 0..n {
682 orbits[i][0] = adj[i].len();
683 }
684
685 for i in 0..n {
687 for &j in &adj[i] {
688 for &k in &adj[j] {
689 if k != i && !adj[i].contains(&k) {
690 orbits[j][1] += 1; orbits[i][2] += 1; }
693 }
694 }
695 }
696 for i in 0..n {
698 orbits[i][1] /= 2;
699 }
700
701 for i in 0..n {
703 for (idx_j, &j) in adj[i].iter().enumerate() {
704 if j <= i {
705 continue;
706 }
707 for &k in adj[i].iter().skip(idx_j + 1) {
708 if k <= j {
709 continue;
710 }
711 if adj[j].contains(&k) {
712 orbits[i][3] += 1;
713 orbits[j][3] += 1;
714 orbits[k][3] += 1;
715 }
716 }
717 }
718 }
719
720 for i in 0..n {
722 let deg = adj[i].len();
723 if deg >= 3 {
724 let mut star_count = 0;
727 for j_idx in 0..adj[i].len() {
728 for k_idx in (j_idx + 1)..adj[i].len() {
729 for l_idx in (k_idx + 1)..adj[i].len() {
730 let j = adj[i][j_idx];
731 let k = adj[i][k_idx];
732 let l = adj[i][l_idx];
733 if !adj[j].contains(&k) && !adj[k].contains(&l) && !adj[j].contains(&l) {
734 star_count += 1;
735 orbits[j][7] += 1;
736 orbits[k][7] += 1;
737 orbits[l][7] += 1;
738 }
739 }
740 }
741 }
742 orbits[i][6] = star_count;
743 }
744 }
745
746 for i in 0..n {
748 for &j in &adj[i] {
749 for &k in &adj[j] {
750 if k == i {
751 continue;
752 }
753 for &l in &adj[k] {
754 if l == j || l == i {
755 continue;
756 }
757 if !adj[i].contains(&k) && !adj[i].contains(&l) && !adj[j].contains(&l) {
759 orbits[i][4] += 1; orbits[l][4] += 1; orbits[j][5] += 1; orbits[k][5] += 1; }
764 }
765 }
766 }
767 }
768 for i in 0..n {
770 orbits[i][4] /= 2;
771 orbits[i][5] /= 2;
772 }
773
774 let mut node_gdvs = HashMap::new();
776 for (i, node) in nodes.iter().enumerate() {
777 node_gdvs.insert(
778 node.clone(),
779 GraphletDegreeVector {
780 orbit_counts: orbits[i].clone(),
781 },
782 );
783 }
784
785 let mut total_agreement = 0.0;
787 let mut pair_count = 0;
788 for i in 0..n {
789 for j in (i + 1)..n {
790 let mut agree = 0.0;
791 for o in 0..num_orbits {
792 let a = orbits[i][o] as f64;
793 let b = orbits[j][o] as f64;
794 let max_val = a.max(b);
795 if max_val > 0.0 {
796 agree += 1.0 - (a - b).abs() / max_val;
797 } else {
798 agree += 1.0;
799 }
800 }
801 total_agreement += agree / num_orbits as f64;
802 pair_count += 1;
803 }
804 }
805
806 let gdv_agreement = if pair_count > 0 {
807 total_agreement / pair_count as f64
808 } else {
809 1.0
810 };
811
812 GraphletDDResult {
813 node_gdvs,
814 gdv_agreement,
815 }
816}
817
818pub fn frequent_subgraph_mining<N, E, Ix>(
832 graph: &Graph<N, E, Ix>,
833 min_support: usize,
834 max_size: usize,
835) -> Vec<FrequentPattern<N>>
836where
837 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
838 E: EdgeWeight + Send + Sync,
839 Ix: IndexType + Send + Sync,
840{
841 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
842 let n = nodes.len();
843
844 if n == 0 || max_size == 0 {
845 return vec![];
846 }
847
848 let node_to_idx: HashMap<N, usize> = nodes
849 .iter()
850 .enumerate()
851 .map(|(i, n)| (n.clone(), i))
852 .collect();
853
854 let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); n];
855 for (i, node) in nodes.iter().enumerate() {
856 if let Ok(neighbors) = graph.neighbors(node) {
857 for neighbor in &neighbors {
858 if let Some(&j) = node_to_idx.get(neighbor) {
859 adj[i].insert(j);
860 }
861 }
862 }
863 }
864
865 let mut patterns: Vec<FrequentPattern<N>> = Vec::new();
866
867 let mut edge_canonical: HashMap<(usize, usize), usize> = HashMap::new();
869 for i in 0..n {
870 for &j in &adj[i] {
871 if i < j {
872 let deg_i = adj[i].len();
873 let deg_j = adj[j].len();
874 let canonical = if deg_i <= deg_j {
875 (deg_i, deg_j)
876 } else {
877 (deg_j, deg_i)
878 };
879 *edge_canonical.entry(canonical).or_insert(0) += 1;
880 }
881 }
882 }
883
884 for (&(deg_a, deg_b), &count) in &edge_canonical {
885 if count >= min_support {
886 for i in 0..n {
888 for &j in &adj[i] {
889 if i < j {
890 let di = adj[i].len();
891 let dj = adj[j].len();
892 let c = if di <= dj { (di, dj) } else { (dj, di) };
893 if c == (deg_a, deg_b) {
894 patterns.push(FrequentPattern {
895 nodes: vec![nodes[i].clone(), nodes[j].clone()],
896 edges: vec![(0, 1)],
897 support: count,
898 });
899 break;
900 }
901 }
902 }
903 if !patterns.is_empty()
904 && patterns.last().map(|p| p.support == count).unwrap_or(false)
905 {
906 break;
907 }
908 }
909 }
910 }
911
912 if max_size >= 3 {
914 let triangles = find_triangles(graph);
915 if triangles.len() >= min_support {
916 if let Some(tri) = triangles.first() {
917 patterns.push(FrequentPattern {
918 nodes: tri.clone(),
919 edges: vec![(0, 1), (1, 2), (0, 2)],
920 support: triangles.len(),
921 });
922 }
923 }
924
925 let mut path2_count = 0;
927 let mut path2_example: Option<Vec<N>> = None;
928 for i in 0..n {
929 for &j in &adj[i] {
930 for &k in &adj[j] {
931 if k > i && !adj[i].contains(&k) {
932 path2_count += 1;
933 if path2_example.is_none() {
934 path2_example =
935 Some(vec![nodes[i].clone(), nodes[j].clone(), nodes[k].clone()]);
936 }
937 }
938 }
939 }
940 }
941 path2_count /= 2; if path2_count >= min_support {
944 if let Some(example) = path2_example {
945 patterns.push(FrequentPattern {
946 nodes: example,
947 edges: vec![(0, 1), (1, 2)],
948 support: path2_count,
949 });
950 }
951 }
952 }
953
954 if max_size >= 4 {
956 let squares = find_squares(graph);
957 if squares.len() >= min_support {
958 if let Some(sq) = squares.first() {
959 patterns.push(FrequentPattern {
960 nodes: sq.clone(),
961 edges: vec![(0, 1), (1, 2), (2, 3), (3, 0)],
962 support: squares.len(),
963 });
964 }
965 }
966 }
967
968 patterns.sort_by(|a, b| b.support.cmp(&a.support));
970 patterns
971}
972
973fn find_triangles<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
978where
979 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
980 E: EdgeWeight + Send + Sync,
981 Ix: IndexType + Send + Sync,
982{
983 use scirs2_core::parallel_ops::*;
984 use std::sync::Mutex;
985
986 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
987 let triangles = Mutex::new(Vec::new());
988
989 nodes.par_iter().enumerate().for_each(|(_i, node_i)| {
990 if let Ok(neighbors_i) = graph.neighbors(node_i) {
991 let neighbors_i: Vec<_> = neighbors_i;
992
993 for (j, node_j) in neighbors_i.iter().enumerate() {
994 for node_k in neighbors_i.iter().skip(j + 1) {
995 if graph.has_edge(node_j, node_k) {
996 if let Ok(mut triangles_guard) = triangles.lock() {
997 let mut triangle = vec![node_i.clone(), node_j.clone(), node_k.clone()];
998 triangle.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
999
1000 if !triangles_guard.iter().any(|t| t == &triangle) {
1001 triangles_guard.push(triangle);
1002 }
1003 }
1004 }
1005 }
1006 }
1007 }
1008 });
1009
1010 triangles.into_inner().unwrap_or_default()
1011}
1012
1013fn find_squares<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1014where
1015 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1016 E: EdgeWeight + Send + Sync,
1017 Ix: IndexType + Send + Sync,
1018{
1019 let mut squares = Vec::new();
1020 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1021
1022 for i in 0..nodes.len() {
1023 for j in i + 1..nodes.len() {
1024 if !graph.has_edge(&nodes[i], &nodes[j]) {
1025 continue;
1026 }
1027 for k in j + 1..nodes.len() {
1028 if !graph.has_edge(&nodes[j], &nodes[k]) {
1029 continue;
1030 }
1031 for l in k + 1..nodes.len() {
1032 if graph.has_edge(&nodes[k], &nodes[l])
1033 && graph.has_edge(&nodes[l], &nodes[i])
1034 && !graph.has_edge(&nodes[i], &nodes[k])
1035 && !graph.has_edge(&nodes[j], &nodes[l])
1036 {
1037 squares.push(vec![
1038 nodes[i].clone(),
1039 nodes[j].clone(),
1040 nodes[k].clone(),
1041 nodes[l].clone(),
1042 ]);
1043 }
1044 }
1045 }
1046 }
1047 }
1048
1049 squares
1050}
1051
1052fn find_star3s<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1053where
1054 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1055 E: EdgeWeight + Send + Sync,
1056 Ix: IndexType + Send + Sync,
1057{
1058 let mut stars = Vec::new();
1059 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1060
1061 for center in &nodes {
1062 if let Ok(neighbors) = graph.neighbors(center) {
1063 let neighbor_list: Vec<N> = neighbors;
1064
1065 if neighbor_list.len() >= 3 {
1066 for i in 0..neighbor_list.len() {
1067 for j in i + 1..neighbor_list.len() {
1068 for k in j + 1..neighbor_list.len() {
1069 if !graph.has_edge(&neighbor_list[i], &neighbor_list[j])
1070 && !graph.has_edge(&neighbor_list[j], &neighbor_list[k])
1071 && !graph.has_edge(&neighbor_list[i], &neighbor_list[k])
1072 {
1073 stars.push(vec![
1074 center.clone(),
1075 neighbor_list[i].clone(),
1076 neighbor_list[j].clone(),
1077 neighbor_list[k].clone(),
1078 ]);
1079 }
1080 }
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 stars
1088}
1089
1090fn find_clique4s<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1091where
1092 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1093 E: EdgeWeight + Send + Sync,
1094 Ix: IndexType + Send + Sync,
1095{
1096 let mut cliques = Vec::new();
1097 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1098
1099 for i in 0..nodes.len() {
1100 for j in i + 1..nodes.len() {
1101 if !graph.has_edge(&nodes[i], &nodes[j]) {
1102 continue;
1103 }
1104 for k in j + 1..nodes.len() {
1105 if !graph.has_edge(&nodes[i], &nodes[k]) || !graph.has_edge(&nodes[j], &nodes[k]) {
1106 continue;
1107 }
1108 for l in k + 1..nodes.len() {
1109 if graph.has_edge(&nodes[i], &nodes[l])
1110 && graph.has_edge(&nodes[j], &nodes[l])
1111 && graph.has_edge(&nodes[k], &nodes[l])
1112 {
1113 cliques.push(vec![
1114 nodes[i].clone(),
1115 nodes[j].clone(),
1116 nodes[k].clone(),
1117 nodes[l].clone(),
1118 ]);
1119 }
1120 }
1121 }
1122 }
1123 }
1124
1125 cliques
1126}
1127
1128fn find_path3s<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1129where
1130 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1131 E: EdgeWeight + Send + Sync,
1132 Ix: IndexType + Send + Sync,
1133{
1134 use scirs2_core::parallel_ops::*;
1135 use std::sync::Mutex;
1136
1137 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1138 let paths = Mutex::new(Vec::new());
1139
1140 nodes.par_iter().for_each(|start_node| {
1141 if let Ok(neighbors1) = graph.neighbors(start_node) {
1142 for middle1 in neighbors1 {
1143 if let Ok(neighbors2) = graph.neighbors(&middle1) {
1144 for middle2 in neighbors2 {
1145 if middle2 == *start_node {
1146 continue;
1147 }
1148
1149 if let Ok(neighbors3) = graph.neighbors(&middle2) {
1150 for end_node in neighbors3 {
1151 if end_node == middle1 || end_node == *start_node {
1152 continue;
1153 }
1154
1155 if !graph.has_edge(start_node, &middle2)
1156 && !graph.has_edge(start_node, &end_node)
1157 && !graph.has_edge(&middle1, &end_node)
1158 {
1159 let mut path = vec![
1160 start_node.clone(),
1161 middle1.clone(),
1162 middle2.clone(),
1163 end_node.clone(),
1164 ];
1165 path.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1166
1167 if let Ok(mut paths_guard) = paths.lock() {
1168 if !paths_guard.iter().any(|p| p == &path) {
1169 paths_guard.push(path);
1170 }
1171 }
1172 }
1173 }
1174 }
1175 }
1176 }
1177 }
1178 }
1179 });
1180
1181 paths.into_inner().unwrap_or_default()
1182}
1183
1184fn find_bi_fans<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1185where
1186 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1187 E: EdgeWeight + Send + Sync,
1188 Ix: IndexType + Send + Sync,
1189{
1190 use scirs2_core::parallel_ops::*;
1191 use std::sync::Mutex;
1192
1193 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1194 let bi_fans = Mutex::new(Vec::new());
1195
1196 nodes.par_iter().enumerate().for_each(|(i, node1)| {
1197 for node2 in nodes.iter().skip(i + 1) {
1198 if let (Ok(neighbors1), Ok(neighbors2)) =
1199 (graph.neighbors(node1), graph.neighbors(node2))
1200 {
1201 let neighbors1: HashSet<_> = neighbors1.into_iter().collect();
1202 let neighbors2: HashSet<_> = neighbors2.into_iter().collect();
1203
1204 let common: Vec<_> = neighbors1
1205 .intersection(&neighbors2)
1206 .filter(|&n| n != node1 && n != node2)
1207 .cloned()
1208 .collect();
1209
1210 if common.len() >= 2 {
1211 for (j, fan1) in common.iter().enumerate() {
1212 for fan2 in common.iter().skip(j + 1) {
1213 let mut bi_fan =
1214 vec![node1.clone(), node2.clone(), fan1.clone(), fan2.clone()];
1215 bi_fan.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1216
1217 if let Ok(mut bi_fans_guard) = bi_fans.lock() {
1218 if !bi_fans_guard.iter().any(|bf| bf == &bi_fan) {
1219 bi_fans_guard.push(bi_fan);
1220 }
1221 }
1222 }
1223 }
1224 }
1225 }
1226 }
1227 });
1228
1229 bi_fans.into_inner().unwrap_or_default()
1230}
1231
1232fn find_feed_forward_loops<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1233where
1234 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1235 E: EdgeWeight + Send + Sync,
1236 Ix: IndexType + Send + Sync,
1237{
1238 use scirs2_core::parallel_ops::*;
1239 use std::sync::Mutex;
1240
1241 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1242 let ffls = Mutex::new(Vec::new());
1243
1244 nodes.par_iter().for_each(|node_a| {
1245 if let Ok(out_neighbors_a) = graph.neighbors(node_a) {
1246 let out_neighbors_a: Vec<_> = out_neighbors_a;
1247
1248 for (i, node_b) in out_neighbors_a.iter().enumerate() {
1249 for node_c in out_neighbors_a.iter().skip(i + 1) {
1250 if graph.has_edge(node_b, node_c)
1251 && !graph.has_edge(node_b, node_a)
1252 && !graph.has_edge(node_c, node_a)
1253 && !graph.has_edge(node_c, node_b)
1254 {
1255 let mut ffl = vec![node_a.clone(), node_b.clone(), node_c.clone()];
1256 ffl.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1257
1258 if let Ok(mut ffls_guard) = ffls.lock() {
1259 if !ffls_guard.iter().any(|f| f == &ffl) {
1260 ffls_guard.push(ffl);
1261 }
1262 }
1263 }
1264 }
1265 }
1266 }
1267 });
1268
1269 ffls.into_inner().unwrap_or_default()
1270}
1271
1272fn find_bidirectional_motifs<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Vec<Vec<N>>
1273where
1274 N: Node + Clone + Hash + Eq + std::fmt::Debug + Send + Sync,
1275 E: EdgeWeight + Send + Sync,
1276 Ix: IndexType + Send + Sync,
1277{
1278 use scirs2_core::parallel_ops::*;
1279 use std::sync::Mutex;
1280
1281 let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
1282 let bidirectionals = Mutex::new(Vec::new());
1283
1284 nodes.par_iter().enumerate().for_each(|(i, node1)| {
1285 for node2 in nodes.iter().skip(i + 1) {
1286 if graph.has_edge(node1, node2) && graph.has_edge(node2, node1) {
1287 let mut motif = vec![node1.clone(), node2.clone()];
1288 motif.sort_by(|a, b| format!("{a:?}").cmp(&format!("{b:?}")));
1289
1290 if let Ok(mut bidirectionals_guard) = bidirectionals.lock() {
1291 if !bidirectionals_guard.iter().any(|m| m == &motif) {
1292 bidirectionals_guard.push(motif);
1293 }
1294 }
1295 }
1296 }
1297 });
1298
1299 bidirectionals.into_inner().unwrap_or_default()
1300}
1301
1302#[cfg(test)]
1307mod tests {
1308 use super::*;
1309 use crate::error::Result as GraphResult;
1310 use crate::generators::create_graph;
1311
1312 #[test]
1313 fn test_find_triangles() -> GraphResult<()> {
1314 let mut graph = create_graph::<&str, ()>();
1315 graph.add_edge("A", "B", ())?;
1316 graph.add_edge("B", "C", ())?;
1317 graph.add_edge("C", "A", ())?;
1318 graph.add_edge("A", "D", ())?;
1319
1320 let triangles = find_motifs(&graph, MotifType::Triangle);
1321 assert_eq!(triangles.len(), 1);
1322 let triangle = &triangles[0];
1323 assert_eq!(triangle.len(), 3);
1324 assert!(triangle.contains(&"A"));
1325 assert!(triangle.contains(&"B"));
1326 assert!(triangle.contains(&"C"));
1327 Ok(())
1328 }
1329
1330 #[test]
1331 fn test_find_squares() -> GraphResult<()> {
1332 let mut graph = create_graph::<&str, ()>();
1333 graph.add_edge("A", "B", ())?;
1334 graph.add_edge("B", "C", ())?;
1335 graph.add_edge("C", "D", ())?;
1336 graph.add_edge("D", "A", ())?;
1337
1338 let squares = find_motifs(&graph, MotifType::Square);
1339 assert_eq!(squares.len(), 1);
1340 assert_eq!(squares[0].len(), 4);
1341 Ok(())
1342 }
1343
1344 #[test]
1345 fn test_find_star3() -> GraphResult<()> {
1346 let mut graph = create_graph::<&str, ()>();
1347 graph.add_edge("A", "B", ())?;
1348 graph.add_edge("A", "C", ())?;
1349 graph.add_edge("A", "D", ())?;
1350
1351 let stars = find_motifs(&graph, MotifType::Star3);
1352 assert_eq!(stars.len(), 1);
1353 assert_eq!(stars[0].len(), 4);
1354 assert!(stars[0].contains(&"A"));
1355 Ok(())
1356 }
1357
1358 #[test]
1359 fn test_find_clique4() -> GraphResult<()> {
1360 let mut graph = create_graph::<&str, ()>();
1361 let nodes = ["A", "B", "C", "D"];
1362 for i in 0..nodes.len() {
1363 for j in i + 1..nodes.len() {
1364 graph.add_edge(nodes[i], nodes[j], ())?;
1365 }
1366 }
1367
1368 let cliques = find_motifs(&graph, MotifType::Clique4);
1369 assert_eq!(cliques.len(), 1);
1370 assert_eq!(cliques[0].len(), 4);
1371 Ok(())
1372 }
1373
1374 #[test]
1375 fn test_vf2_exact_match() -> GraphResult<()> {
1376 let mut pattern = create_graph::<i32, ()>();
1377 pattern.add_edge(0, 1, ())?;
1378 pattern.add_edge(1, 2, ())?;
1379
1380 let mut target = create_graph::<i32, ()>();
1381 target.add_edge(10, 11, ())?;
1382 target.add_edge(11, 12, ())?;
1383
1384 let result = vf2_subgraph_isomorphism(&pattern, &target, 0);
1385 assert!(result.is_match);
1386 assert!(!result.mappings.is_empty());
1387 Ok(())
1388 }
1389
1390 #[test]
1391 fn test_vf2_no_match() -> GraphResult<()> {
1392 let mut pattern = create_graph::<i32, ()>();
1393 pattern.add_edge(0, 1, ())?;
1394 pattern.add_edge(1, 2, ())?;
1395 pattern.add_edge(2, 0, ())?; let mut target = create_graph::<i32, ()>();
1398 target.add_edge(10, 11, ())?;
1399 target.add_edge(11, 12, ())?;
1400 let result = vf2_subgraph_isomorphism(&pattern, &target, 0);
1403 assert!(!result.is_match);
1404 Ok(())
1405 }
1406
1407 #[test]
1408 fn test_vf2_subgraph() -> GraphResult<()> {
1409 let mut pattern = create_graph::<i32, ()>();
1410 pattern.add_edge(0, 1, ())?;
1411 pattern.add_edge(1, 2, ())?;
1412
1413 let mut target = create_graph::<i32, ()>();
1414 target.add_edge(10, 11, ())?;
1415 target.add_edge(11, 12, ())?;
1416 target.add_edge(12, 13, ())?;
1417 target.add_edge(10, 13, ())?;
1418
1419 let result = vf2_subgraph_isomorphism(&pattern, &target, 0);
1420 assert!(result.is_match);
1421 assert!(!result.mappings.is_empty());
1423 Ok(())
1424 }
1425
1426 #[test]
1427 fn test_wl_kernel_identical() -> GraphResult<()> {
1428 let mut g1 = create_graph::<i32, ()>();
1429 g1.add_edge(0, 1, ())?;
1430 g1.add_edge(1, 2, ())?;
1431
1432 let mut g2 = create_graph::<i32, ()>();
1433 g2.add_edge(10, 11, ())?;
1434 g2.add_edge(11, 12, ())?;
1435
1436 let result = wl_subtree_kernel(&[&g1, &g2], 3);
1437 assert_eq!(result.kernel_matrix.len(), 2);
1438 assert!((result.kernel_matrix[0][0] - result.kernel_matrix[1][1]).abs() < 1e-6);
1440 assert!((result.kernel_matrix[0][1] - result.kernel_matrix[0][0]).abs() < 1e-6);
1441 Ok(())
1442 }
1443
1444 #[test]
1445 fn test_wl_kernel_different() -> GraphResult<()> {
1446 let mut g1 = create_graph::<i32, ()>();
1447 g1.add_edge(0, 1, ())?;
1448 g1.add_edge(1, 2, ())?;
1449
1450 let mut g2 = create_graph::<i32, ()>();
1451 g2.add_edge(0, 1, ())?;
1452 g2.add_edge(1, 2, ())?;
1453 g2.add_edge(2, 0, ())?; let result = wl_subtree_kernel(&[&g1, &g2], 2);
1456 assert!(result.kernel_matrix[0][0] > 0.0);
1458 assert!(result.kernel_matrix[1][1] > 0.0);
1459 Ok(())
1460 }
1461
1462 #[test]
1463 fn test_graphlet_degree_distribution() -> GraphResult<()> {
1464 let mut graph = create_graph::<i32, ()>();
1465 graph.add_edge(0, 1, ())?;
1466 graph.add_edge(1, 2, ())?;
1467 graph.add_edge(2, 0, ())?;
1468 graph.add_edge(2, 3, ())?;
1469
1470 let result = graphlet_degree_distribution(&graph);
1471 assert_eq!(result.node_gdvs.len(), 4);
1472
1473 let gdv_2 = &result.node_gdvs[&2];
1475 assert_eq!(gdv_2.orbit_counts[0], 3); let gdv_3 = &result.node_gdvs[&3];
1479 assert_eq!(gdv_3.orbit_counts[3], 0); assert!(result.gdv_agreement >= 0.0 && result.gdv_agreement <= 1.0);
1482 Ok(())
1483 }
1484
1485 #[test]
1486 fn test_count_3node_motifs() -> GraphResult<()> {
1487 let mut graph = create_graph::<i32, ()>();
1488 graph.add_edge(0, 1, ())?;
1489 graph.add_edge(1, 2, ())?;
1490 graph.add_edge(2, 0, ())?;
1491
1492 let (triangles, open) = count_3node_motifs(&graph);
1493 assert_eq!(triangles, 1);
1494 assert_eq!(open, 0); Ok(())
1496 }
1497
1498 #[test]
1499 fn test_count_4node_motifs() -> GraphResult<()> {
1500 let mut graph = create_graph::<i32, ()>();
1501 graph.add_edge(0, 1, ())?;
1502 graph.add_edge(1, 2, ())?;
1503 graph.add_edge(2, 3, ())?;
1504
1505 let counts = count_4node_motifs(&graph);
1506 assert!(counts.contains_key("path3"));
1507 assert_eq!(counts["path3"], 1);
1508 Ok(())
1509 }
1510
1511 #[test]
1512 fn test_frequent_subgraph_mining() -> GraphResult<()> {
1513 let mut graph = create_graph::<i32, ()>();
1514 graph.add_edge(0, 1, ())?;
1516 graph.add_edge(1, 2, ())?;
1517 graph.add_edge(2, 0, ())?;
1518 graph.add_edge(2, 3, ())?;
1519 graph.add_edge(3, 4, ())?;
1520 graph.add_edge(4, 2, ())?;
1521
1522 let patterns = frequent_subgraph_mining(&graph, 1, 3);
1523 assert!(!patterns.is_empty());
1524 Ok(())
1526 }
1527
1528 #[test]
1529 fn test_frequent_subgraph_empty() {
1530 let graph = create_graph::<i32, ()>();
1531 let patterns = frequent_subgraph_mining(&graph, 1, 3);
1532 assert!(patterns.is_empty());
1533 }
1534}