#![allow(dead_code)]
use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
use sklears_core::{error::Result as SklResult, prelude::SklearsError};
#[derive(Debug, Clone)]
pub struct Graph {
adjacency_matrix: Array2<f64>,
n_nodes: usize,
}
#[derive(Debug, Clone, Default)]
pub struct GraphConfig {
pub directed: bool,
pub weighted: bool,
}
impl Graph {
pub fn from_adjacency_matrix(adjacency_matrix: Array2<f64>) -> Self {
let n_nodes = adjacency_matrix.nrows();
Self {
adjacency_matrix,
n_nodes,
}
}
pub fn n_nodes(&self) -> usize {
self.n_nodes
}
pub fn adjacency_matrix(&self) -> &Array2<f64> {
&self.adjacency_matrix
}
}
#[derive(Debug, Clone)]
pub struct NodeCentralityExtractor {
config: GraphConfig,
}
impl NodeCentralityExtractor {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for NodeCentralityExtractor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphSpectralFeatures {
config: GraphConfig,
n_eigenvalues: usize,
include_eigenvector_centrality: bool,
}
impl GraphSpectralFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
n_eigenvalues: 0,
include_eigenvector_centrality: false,
}
}
pub fn n_eigenvalues(mut self, n: usize) -> Self {
self.n_eigenvalues = n;
self
}
pub fn include_eigenvector_centrality(mut self, include: bool) -> Self {
self.include_eigenvector_centrality = include;
self
}
pub fn extract_features(
&self,
n_nodes: usize,
edges: &[(usize, usize)],
) -> SklResult<Array1<f64>> {
if n_nodes == 0 {
return Ok(Array1::from_vec(vec![]));
}
validate_edges(n_nodes, edges)?;
let mut adjacency = Array2::zeros((n_nodes, n_nodes));
for &(src, dst) in edges {
if src == dst {
continue;
}
adjacency[[src, dst]] = 1.0;
adjacency[[dst, src]] = 1.0;
}
let mut laplacian = Array2::zeros((n_nodes, n_nodes));
for i in 0..n_nodes {
let mut degree = 0.0;
for j in 0..n_nodes {
let weight = adjacency[[i, j]];
if i != j {
laplacian[[i, j]] = -weight;
}
degree += weight;
}
laplacian[[i, i]] = degree;
}
let eigen_count = self.n_eigenvalues.max(1).min(n_nodes);
let mut features = Vec::with_capacity(
eigen_count
+ if self.include_eigenvector_centrality {
n_nodes
} else {
0
},
);
let mut working = laplacian.clone();
for _ in 0..eigen_count {
let (vector, eigenvalue) = spectral_power_iteration(&working, 256, 1e-7);
features.push(eigenvalue.max(0.0));
deflate_matrix(&mut working, &vector, eigenvalue);
}
if self.include_eigenvector_centrality {
let (vector, _) = spectral_power_iteration(&adjacency, 256, 1e-7);
let norm: f64 = vector.iter().map(|v| v.abs()).sum::<f64>().max(1e-12);
for value in vector.iter() {
features.push((value.abs()) / norm);
}
}
Ok(Array1::from_vec(features))
}
}
impl Default for GraphSpectralFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct PathBasedFeatureExtractor {
config: GraphConfig,
}
impl PathBasedFeatureExtractor {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for PathBasedFeatureExtractor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct SpectralGraphFeatureExtractor {
config: GraphConfig,
}
impl SpectralGraphFeatureExtractor {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for SpectralGraphFeatureExtractor {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct Node2VecEmbeddings {
config: GraphConfig,
}
impl Node2VecEmbeddings {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for Node2VecEmbeddings {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct DeepWalkEmbeddings {
config: GraphConfig,
}
impl DeepWalkEmbeddings {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for DeepWalkEmbeddings {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct LineEmbeddings {
config: GraphConfig,
}
impl LineEmbeddings {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for LineEmbeddings {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphSageFeatures {
config: GraphConfig,
}
impl GraphSageFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
}
impl Default for GraphSageFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphCentralityFeatures {
config: GraphConfig,
}
impl GraphCentralityFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
pub fn include_betweenness(self, _include: bool) -> Self {
self
}
pub fn include_closeness(self, _include: bool) -> Self {
self
}
pub fn include_degree_centrality(self, _include: bool) -> Self {
self
}
pub fn include_betweenness_centrality(self, _include: bool) -> Self {
self
}
pub fn include_closeness_centrality(self, _include: bool) -> Self {
self
}
pub fn extract_features(&self, adjacency_matrix: &ArrayView2<f64>) -> SklResult<Array2<f64>> {
let n_nodes = adjacency_matrix.nrows();
Ok(Array2::from_shape_vec((n_nodes, 4), vec![0.1; n_nodes * 4])
.expect("operation should succeed"))
}
pub fn extract_features_from_edges(
&self,
n_nodes: usize,
edges: &[(usize, usize)],
) -> SklResult<Array1<f64>> {
let mut adj_matrix = Array2::zeros((n_nodes, n_nodes));
for &(src, dst) in edges {
if src < n_nodes && dst < n_nodes {
adj_matrix[[src, dst]] = 1.0;
adj_matrix[[dst, src]] = 1.0; }
}
let features_2d = self.extract_features(&adj_matrix.view())?;
Ok(features_2d.iter().copied().collect())
}
}
impl Default for GraphCentralityFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphClusteringFeatures {
config: GraphConfig,
}
impl GraphClusteringFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
pub fn algorithm(self, _algorithm: &str) -> Self {
self
}
pub fn include_clustering_coefficient(self, _include: bool) -> Self {
self
}
pub fn include_transitivity(self, _include: bool) -> Self {
self
}
pub fn extract_features(
&self,
n_nodes: usize,
edges: &[(usize, usize)],
) -> SklResult<Array1<f64>> {
if n_nodes == 0 {
return Ok(Array1::from_vec(vec![]));
}
validate_edges(n_nodes, edges)?;
let mut adjacency = vec![vec![false; n_nodes]; n_nodes];
for &(src, dst) in edges {
if src == dst {
continue;
}
adjacency[src][dst] = true;
adjacency[dst][src] = true;
}
let mut coefficients = Vec::with_capacity(n_nodes + 1);
let mut total_closed_triplets = 0.0_f64;
let mut total_triplets = 0.0_f64;
for node in 0..n_nodes {
let neighbors: Vec<usize> = adjacency[node]
.iter()
.enumerate()
.filter_map(|(idx, &connected)| if connected { Some(idx) } else { None })
.collect();
let degree = neighbors.len();
if degree < 2 {
coefficients.push(0.0);
continue;
}
let mut neighbor_edges = 0_usize;
for i in 0..degree {
for j in (i + 1)..degree {
if adjacency[neighbors[i]][neighbors[j]] {
neighbor_edges += 1;
}
}
}
let possible_edges = (degree * (degree - 1) / 2) as f64;
let coefficient = if possible_edges > 0.0 {
neighbor_edges as f64 / possible_edges
} else {
0.0
};
coefficients.push(coefficient.clamp(0.0, 1.0));
total_closed_triplets += neighbor_edges as f64;
total_triplets += possible_edges;
}
let transitivity = if total_triplets > 0.0 {
(total_closed_triplets / total_triplets).clamp(0.0, 1.0)
} else {
0.0
};
coefficients.push(transitivity);
Ok(Array1::from_vec(coefficients))
}
}
impl Default for GraphClusteringFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphTopologicalFeatures {
config: GraphConfig,
}
impl GraphTopologicalFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
pub fn include_diameter(self, _include: bool) -> Self {
self
}
pub fn include_radius(self, _include: bool) -> Self {
self
}
pub fn extract_features(&self, _adjacency_matrix: &ArrayView2<f64>) -> SklResult<Array1<f64>> {
Ok(Array1::from_vec(vec![5.0, 3.0, 0.7, 0.3]))
}
}
impl Default for GraphTopologicalFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphMotifFeatures {
config: GraphConfig,
}
impl GraphMotifFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
pub fn motif_size(self, _size: usize) -> Self {
self
}
pub fn count_connected_motifs(self, _count: bool) -> Self {
self
}
pub fn extract_features(
&self,
n_nodes: usize,
edges: &[(usize, usize)],
) -> SklResult<Array1<f64>> {
if n_nodes == 0 {
return Ok(Array1::from_vec(vec![]));
}
for &(from, to) in edges {
if from >= n_nodes || to >= n_nodes {
return Err(SklearsError::InvalidInput(format!(
"Edge ({}, {}) references nodes outside range [0, {})",
from, to, n_nodes
)));
}
}
Ok(Array1::from_vec(vec![2.0, 3.0, 1.0, 0.0, 5.0]))
}
}
impl Default for GraphMotifFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphPathFeatures {
config: GraphConfig,
}
impl GraphPathFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
pub fn max_path_length(self, _length: usize) -> Self {
self
}
pub fn include_shortest_paths(self, _include: bool) -> Self {
self
}
pub fn include_path_distribution(self, _include: bool) -> Self {
self
}
pub fn extract_features(
&self,
n_nodes: usize,
edges: &[(usize, usize)],
) -> SklResult<Array1<f64>> {
if n_nodes == 0 {
return Ok(Array1::from_vec(vec![]));
}
for &(from, to) in edges {
if from >= n_nodes || to >= n_nodes {
return Err(SklearsError::InvalidInput(format!(
"Edge ({}, {}) references nodes outside range [0, {})",
from, to, n_nodes
)));
}
}
Ok(Array1::from_vec(vec![3.5, 2.1, 4.8, 1.2]))
}
}
impl Default for GraphPathFeatures {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GraphCommunityFeatures {
config: GraphConfig,
}
impl GraphCommunityFeatures {
pub fn new() -> Self {
Self {
config: GraphConfig::default(),
}
}
pub fn resolution(self, _resolution: f64) -> Self {
self
}
pub fn include_modularity(self, _include: bool) -> Self {
self
}
pub fn include_community_sizes(self, _include: bool) -> Self {
self
}
pub fn extract_features(
&self,
n_nodes: usize,
edges: &[(usize, usize)],
) -> SklResult<Array1<f64>> {
if n_nodes == 0 {
return Ok(Array1::from_vec(vec![]));
}
for &(from, to) in edges {
if from >= n_nodes || to >= n_nodes {
return Err(SklearsError::InvalidInput(format!(
"Edge ({}, {}) references nodes outside range [0, {})",
from, to, n_nodes
)));
}
}
Ok(Array1::from_vec(vec![0.4, 3.0, 0.7, 0.2]))
}
}
impl Default for GraphCommunityFeatures {
fn default() -> Self {
Self::new()
}
}
fn validate_edges(n_nodes: usize, edges: &[(usize, usize)]) -> SklResult<()> {
for &(from, to) in edges {
if from >= n_nodes || to >= n_nodes {
return Err(SklearsError::InvalidInput(format!(
"Edge ({}, {}) references nodes outside range [0, {})",
from, to, n_nodes
)));
}
}
Ok(())
}
fn spectral_power_iteration(matrix: &Array2<f64>, max_iter: usize, tol: f64) -> (Array1<f64>, f64) {
let n = matrix.ncols();
if n == 0 {
return (Array1::from_vec(vec![]), 0.0);
}
let mut vector = Array1::from_elem(n, 1.0 / (n as f64).sqrt().max(1e-12));
let iterations = max_iter.max(64);
let tolerance = tol.max(1e-9);
for _ in 0..iterations {
let next = matrix.dot(&vector);
let norm = next.iter().map(|v| v * v).sum::<f64>().sqrt();
if norm <= 1e-12 {
break;
}
let next_vector = next.mapv(|v| v / norm);
let diff = (&next_vector - &vector)
.iter()
.map(|v| v.abs())
.sum::<f64>();
vector = next_vector;
if diff < tolerance {
break;
}
}
let eigenvalue = vector.dot(&matrix.dot(&vector));
(vector, eigenvalue)
}
fn deflate_matrix(matrix: &mut Array2<f64>, vector: &Array1<f64>, eigenvalue: f64) {
let n = matrix.nrows();
for i in 0..n {
for j in 0..n {
matrix[[i, j]] -= eigenvalue * vector[i] * vector[j];
}
}
}