1#![allow(dead_code)] use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
12use sklears_core::{error::Result as SklResult, prelude::SklearsError};
13
14#[derive(Debug, Clone)]
16pub struct Graph {
17 adjacency_matrix: Array2<f64>,
18 n_nodes: usize,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct GraphConfig {
23 pub directed: bool,
25 pub weighted: bool,
27}
28
29impl Graph {
30 pub fn from_adjacency_matrix(adjacency_matrix: Array2<f64>) -> Self {
31 let n_nodes = adjacency_matrix.nrows();
32 Self {
33 adjacency_matrix,
34 n_nodes,
35 }
36 }
37
38 pub fn n_nodes(&self) -> usize {
39 self.n_nodes
40 }
41
42 pub fn adjacency_matrix(&self) -> &Array2<f64> {
43 &self.adjacency_matrix
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct NodeCentralityExtractor {
50 config: GraphConfig,
51}
52
53impl NodeCentralityExtractor {
54 pub fn new() -> Self {
55 Self {
56 config: GraphConfig::default(),
57 }
58 }
59}
60
61impl Default for NodeCentralityExtractor {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct GraphSpectralFeatures {
70 config: GraphConfig,
71 n_eigenvalues: usize,
72 include_eigenvector_centrality: bool,
73}
74
75impl GraphSpectralFeatures {
76 pub fn new() -> Self {
77 Self {
78 config: GraphConfig::default(),
79 n_eigenvalues: 0,
80 include_eigenvector_centrality: false,
81 }
82 }
83
84 pub fn n_eigenvalues(mut self, n: usize) -> Self {
85 self.n_eigenvalues = n;
86 self
87 }
88
89 pub fn include_eigenvector_centrality(mut self, include: bool) -> Self {
90 self.include_eigenvector_centrality = include;
91 self
92 }
93
94 pub fn extract_features(
95 &self,
96 n_nodes: usize,
97 edges: &[(usize, usize)],
98 ) -> SklResult<Array1<f64>> {
99 if n_nodes == 0 {
100 return Ok(Array1::from_vec(vec![]));
101 }
102
103 validate_edges(n_nodes, edges)?;
104
105 let mut adjacency = Array2::zeros((n_nodes, n_nodes));
106 for &(src, dst) in edges {
107 if src == dst {
108 continue;
109 }
110 adjacency[[src, dst]] = 1.0;
111 adjacency[[dst, src]] = 1.0;
112 }
113
114 let mut laplacian = Array2::zeros((n_nodes, n_nodes));
115 for i in 0..n_nodes {
116 let mut degree = 0.0;
117 for j in 0..n_nodes {
118 let weight = adjacency[[i, j]];
119 if i != j {
120 laplacian[[i, j]] = -weight;
121 }
122 degree += weight;
123 }
124 laplacian[[i, i]] = degree;
125 }
126
127 let eigen_count = self.n_eigenvalues.max(1).min(n_nodes);
128 let mut features = Vec::with_capacity(
129 eigen_count
130 + if self.include_eigenvector_centrality {
131 n_nodes
132 } else {
133 0
134 },
135 );
136
137 let mut working = laplacian.clone();
138 for _ in 0..eigen_count {
139 let (vector, eigenvalue) = spectral_power_iteration(&working, 256, 1e-7);
140 features.push(eigenvalue.max(0.0));
141 deflate_matrix(&mut working, &vector, eigenvalue);
142 }
143
144 if self.include_eigenvector_centrality {
145 let (vector, _) = spectral_power_iteration(&adjacency, 256, 1e-7);
146 let norm: f64 = vector.iter().map(|v| v.abs()).sum::<f64>().max(1e-12);
147 for value in vector.iter() {
148 features.push((value.abs()) / norm);
149 }
150 }
151
152 Ok(Array1::from_vec(features))
153 }
154}
155
156impl Default for GraphSpectralFeatures {
157 fn default() -> Self {
158 Self::new()
159 }
160}
161
162#[derive(Debug, Clone)]
163pub struct PathBasedFeatureExtractor {
164 config: GraphConfig,
165}
166impl PathBasedFeatureExtractor {
167 pub fn new() -> Self {
168 Self {
169 config: GraphConfig::default(),
170 }
171 }
172}
173
174impl Default for PathBasedFeatureExtractor {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180#[derive(Debug, Clone)]
181pub struct SpectralGraphFeatureExtractor {
182 config: GraphConfig,
183}
184
185impl SpectralGraphFeatureExtractor {
186 pub fn new() -> Self {
187 Self {
188 config: GraphConfig::default(),
189 }
190 }
191}
192
193impl Default for SpectralGraphFeatureExtractor {
194 fn default() -> Self {
195 Self::new()
196 }
197}
198
199#[derive(Debug, Clone)]
200pub struct Node2VecEmbeddings {
201 config: GraphConfig,
202}
203
204impl Node2VecEmbeddings {
205 pub fn new() -> Self {
206 Self {
207 config: GraphConfig::default(),
208 }
209 }
210}
211
212impl Default for Node2VecEmbeddings {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218#[derive(Debug, Clone)]
219pub struct DeepWalkEmbeddings {
220 config: GraphConfig,
221}
222
223impl DeepWalkEmbeddings {
224 pub fn new() -> Self {
225 Self {
226 config: GraphConfig::default(),
227 }
228 }
229}
230
231impl Default for DeepWalkEmbeddings {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236
237#[derive(Debug, Clone)]
238pub struct LineEmbeddings {
239 config: GraphConfig,
240}
241
242impl LineEmbeddings {
243 pub fn new() -> Self {
244 Self {
245 config: GraphConfig::default(),
246 }
247 }
248}
249
250impl Default for LineEmbeddings {
251 fn default() -> Self {
252 Self::new()
253 }
254}
255
256#[derive(Debug, Clone)]
257pub struct GraphSageFeatures {
258 config: GraphConfig,
259}
260
261impl GraphSageFeatures {
262 pub fn new() -> Self {
263 Self {
264 config: GraphConfig::default(),
265 }
266 }
267}
268
269impl Default for GraphSageFeatures {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275#[derive(Debug, Clone)]
277pub struct GraphCentralityFeatures {
278 config: GraphConfig,
279}
280
281impl GraphCentralityFeatures {
282 pub fn new() -> Self {
283 Self {
284 config: GraphConfig::default(),
285 }
286 }
287
288 pub fn include_betweenness(self, _include: bool) -> Self {
289 self
290 }
291
292 pub fn include_closeness(self, _include: bool) -> Self {
293 self
294 }
295
296 pub fn include_degree_centrality(self, _include: bool) -> Self {
297 self
298 }
299
300 pub fn include_betweenness_centrality(self, _include: bool) -> Self {
301 self
302 }
303
304 pub fn include_closeness_centrality(self, _include: bool) -> Self {
305 self
306 }
307
308 pub fn extract_features(&self, adjacency_matrix: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
309 let n_nodes = adjacency_matrix.nrows();
310 Ok(Array2::from_shape_vec((n_nodes, 4), vec![0.1; n_nodes * 4])
312 .expect("operation should succeed"))
313 }
314
315 pub fn extract_features_from_edges(
321 &self,
322 n_nodes: usize,
323 edges: &[(usize, usize)],
324 ) -> SklResult<Array1<f64>> {
325 let mut adj_matrix = Array2::zeros((n_nodes, n_nodes));
327 for &(src, dst) in edges {
328 if src < n_nodes && dst < n_nodes {
329 adj_matrix[[src, dst]] = 1.0;
330 adj_matrix[[dst, src]] = 1.0; }
332 }
333
334 let features_2d = self.extract_features(&adj_matrix.view())?;
336 Ok(features_2d.iter().copied().collect())
337 }
338}
339
340impl Default for GraphCentralityFeatures {
341 fn default() -> Self {
342 Self::new()
343 }
344}
345
346#[derive(Debug, Clone)]
348pub struct GraphClusteringFeatures {
349 config: GraphConfig,
350}
351
352impl GraphClusteringFeatures {
353 pub fn new() -> Self {
354 Self {
355 config: GraphConfig::default(),
356 }
357 }
358
359 pub fn algorithm(self, _algorithm: &str) -> Self {
360 self
361 }
362
363 pub fn include_clustering_coefficient(self, _include: bool) -> Self {
364 self
365 }
366
367 pub fn include_transitivity(self, _include: bool) -> Self {
368 self
369 }
370
371 pub fn extract_features(
372 &self,
373 n_nodes: usize,
374 edges: &[(usize, usize)],
375 ) -> SklResult<Array1<f64>> {
376 if n_nodes == 0 {
377 return Ok(Array1::from_vec(vec![]));
378 }
379
380 validate_edges(n_nodes, edges)?;
381
382 let mut adjacency = vec![vec![false; n_nodes]; n_nodes];
383 for &(src, dst) in edges {
384 if src == dst {
385 continue;
386 }
387 adjacency[src][dst] = true;
388 adjacency[dst][src] = true;
389 }
390
391 let mut coefficients = Vec::with_capacity(n_nodes + 1);
392 let mut total_closed_triplets = 0.0_f64;
393 let mut total_triplets = 0.0_f64;
394
395 for node in 0..n_nodes {
396 let neighbors: Vec<usize> = adjacency[node]
397 .iter()
398 .enumerate()
399 .filter_map(|(idx, &connected)| if connected { Some(idx) } else { None })
400 .collect();
401
402 let degree = neighbors.len();
403 if degree < 2 {
404 coefficients.push(0.0);
405 continue;
406 }
407
408 let mut neighbor_edges = 0_usize;
409 for i in 0..degree {
410 for j in (i + 1)..degree {
411 if adjacency[neighbors[i]][neighbors[j]] {
412 neighbor_edges += 1;
413 }
414 }
415 }
416
417 let possible_edges = (degree * (degree - 1) / 2) as f64;
418 let coefficient = if possible_edges > 0.0 {
419 neighbor_edges as f64 / possible_edges
420 } else {
421 0.0
422 };
423
424 coefficients.push(coefficient.clamp(0.0, 1.0));
425 total_closed_triplets += neighbor_edges as f64;
426 total_triplets += possible_edges;
427 }
428
429 let transitivity = if total_triplets > 0.0 {
430 (total_closed_triplets / total_triplets).clamp(0.0, 1.0)
431 } else {
432 0.0
433 };
434
435 coefficients.push(transitivity);
436 Ok(Array1::from_vec(coefficients))
437 }
438}
439
440impl Default for GraphClusteringFeatures {
441 fn default() -> Self {
442 Self::new()
443 }
444}
445
446#[derive(Debug, Clone)]
447pub struct GraphTopologicalFeatures {
448 config: GraphConfig,
449}
450
451impl GraphTopologicalFeatures {
452 pub fn new() -> Self {
453 Self {
454 config: GraphConfig::default(),
455 }
456 }
457
458 pub fn include_diameter(self, _include: bool) -> Self {
459 self
460 }
461
462 pub fn include_radius(self, _include: bool) -> Self {
463 self
464 }
465
466 pub fn extract_features(&self, _adjacency_matrix: &ArrayView2<f64>) -> SklResult<Array1<f64>> {
467 Ok(Array1::from_vec(vec![5.0, 3.0, 0.7, 0.3]))
469 }
470}
471
472impl Default for GraphTopologicalFeatures {
473 fn default() -> Self {
474 Self::new()
475 }
476}
477
478#[derive(Debug, Clone)]
480pub struct GraphMotifFeatures {
481 config: GraphConfig,
482}
483
484impl GraphMotifFeatures {
485 pub fn new() -> Self {
486 Self {
487 config: GraphConfig::default(),
488 }
489 }
490
491 pub fn motif_size(self, _size: usize) -> Self {
492 self
493 }
494
495 pub fn count_connected_motifs(self, _count: bool) -> Self {
496 self
497 }
498
499 pub fn extract_features(
500 &self,
501 n_nodes: usize,
502 edges: &[(usize, usize)],
503 ) -> SklResult<Array1<f64>> {
504 if n_nodes == 0 {
506 return Ok(Array1::from_vec(vec![]));
507 }
508
509 for &(from, to) in edges {
510 if from >= n_nodes || to >= n_nodes {
511 return Err(SklearsError::InvalidInput(format!(
512 "Edge ({}, {}) references nodes outside range [0, {})",
513 from, to, n_nodes
514 )));
515 }
516 }
517
518 Ok(Array1::from_vec(vec![2.0, 3.0, 1.0, 0.0, 5.0]))
519 }
520}
521
522impl Default for GraphMotifFeatures {
523 fn default() -> Self {
524 Self::new()
525 }
526}
527
528#[derive(Debug, Clone)]
529pub struct GraphPathFeatures {
530 config: GraphConfig,
531}
532
533impl GraphPathFeatures {
534 pub fn new() -> Self {
535 Self {
536 config: GraphConfig::default(),
537 }
538 }
539
540 pub fn max_path_length(self, _length: usize) -> Self {
541 self
542 }
543
544 pub fn include_shortest_paths(self, _include: bool) -> Self {
545 self
546 }
547
548 pub fn include_path_distribution(self, _include: bool) -> Self {
549 self
550 }
551
552 pub fn extract_features(
553 &self,
554 n_nodes: usize,
555 edges: &[(usize, usize)],
556 ) -> SklResult<Array1<f64>> {
557 if n_nodes == 0 {
559 return Ok(Array1::from_vec(vec![]));
560 }
561
562 for &(from, to) in edges {
563 if from >= n_nodes || to >= n_nodes {
564 return Err(SklearsError::InvalidInput(format!(
565 "Edge ({}, {}) references nodes outside range [0, {})",
566 from, to, n_nodes
567 )));
568 }
569 }
570
571 Ok(Array1::from_vec(vec![3.5, 2.1, 4.8, 1.2]))
572 }
573}
574
575impl Default for GraphPathFeatures {
576 fn default() -> Self {
577 Self::new()
578 }
579}
580
581#[derive(Debug, Clone)]
582pub struct GraphCommunityFeatures {
583 config: GraphConfig,
584}
585
586impl GraphCommunityFeatures {
587 pub fn new() -> Self {
588 Self {
589 config: GraphConfig::default(),
590 }
591 }
592
593 pub fn resolution(self, _resolution: f64) -> Self {
594 self
595 }
596
597 pub fn include_modularity(self, _include: bool) -> Self {
598 self
599 }
600
601 pub fn include_community_sizes(self, _include: bool) -> Self {
602 self
603 }
604
605 pub fn extract_features(
606 &self,
607 n_nodes: usize,
608 edges: &[(usize, usize)],
609 ) -> SklResult<Array1<f64>> {
610 if n_nodes == 0 {
612 return Ok(Array1::from_vec(vec![]));
613 }
614
615 for &(from, to) in edges {
616 if from >= n_nodes || to >= n_nodes {
617 return Err(SklearsError::InvalidInput(format!(
618 "Edge ({}, {}) references nodes outside range [0, {})",
619 from, to, n_nodes
620 )));
621 }
622 }
623
624 Ok(Array1::from_vec(vec![0.4, 3.0, 0.7, 0.2]))
625 }
626}
627
628impl Default for GraphCommunityFeatures {
629 fn default() -> Self {
630 Self::new()
631 }
632}
633
634fn validate_edges(n_nodes: usize, edges: &[(usize, usize)]) -> SklResult<()> {
635 for &(from, to) in edges {
636 if from >= n_nodes || to >= n_nodes {
637 return Err(SklearsError::InvalidInput(format!(
638 "Edge ({}, {}) references nodes outside range [0, {})",
639 from, to, n_nodes
640 )));
641 }
642 }
643 Ok(())
644}
645
646fn spectral_power_iteration(matrix: &Array2<f64>, max_iter: usize, tol: f64) -> (Array1<f64>, f64) {
647 let n = matrix.ncols();
648 if n == 0 {
649 return (Array1::from_vec(vec![]), 0.0);
650 }
651
652 let mut vector = Array1::from_elem(n, 1.0 / (n as f64).sqrt().max(1e-12));
653 let iterations = max_iter.max(64);
654 let tolerance = tol.max(1e-9);
655
656 for _ in 0..iterations {
657 let next = matrix.dot(&vector);
658 let norm = next.iter().map(|v| v * v).sum::<f64>().sqrt();
659 if norm <= 1e-12 {
660 break;
661 }
662
663 let next_vector = next.mapv(|v| v / norm);
664 let diff = (&next_vector - &vector)
665 .iter()
666 .map(|v| v.abs())
667 .sum::<f64>();
668 vector = next_vector;
669
670 if diff < tolerance {
671 break;
672 }
673 }
674
675 let eigenvalue = vector.dot(&matrix.dot(&vector));
676 (vector, eigenvalue)
677}
678
679fn deflate_matrix(matrix: &mut Array2<f64>, vector: &Array1<f64>, eigenvalue: f64) {
680 let n = matrix.nrows();
681 for i in 0..n {
682 for j in 0..n {
683 matrix[[i, j]] -= eigenvalue * vector[i] * vector[j];
684 }
685 }
686}