use scirs2_core::ndarray::Array2;
use crate::error::{GraphError, Result};
#[derive(Debug, Clone)]
pub struct AlignmentConfig {
pub alpha: f64,
pub max_iter: usize,
pub tolerance: f64,
pub greedy_candidates: usize,
pub local_search_depth: usize,
}
impl Default for AlignmentConfig {
fn default() -> Self {
Self {
alpha: 0.6,
max_iter: 100,
tolerance: 1e-8,
greedy_candidates: 5,
local_search_depth: 50,
}
}
}
#[derive(Debug, Clone)]
pub struct AlignmentResult {
pub mapping: Vec<(usize, usize)>,
pub score: f64,
pub edge_conservation: f64,
pub converged: bool,
pub iterations: usize,
}
#[derive(Debug, Clone)]
pub struct SimilarityMatrix {
data: Array2<f64>,
n1: usize,
n2: usize,
}
impl SimilarityMatrix {
pub fn new(n1: usize, n2: usize) -> Result<Self> {
if n1 == 0 || n2 == 0 {
return Err(GraphError::InvalidParameter {
param: "dimensions".to_string(),
value: format!("({}, {})", n1, n2),
expected: "both dimensions > 0".to_string(),
context: "SimilarityMatrix::new".to_string(),
});
}
let val = 1.0 / (n1 as f64 * n2 as f64);
let data = Array2::from_elem((n1, n2), val);
Ok(Self { data, n1, n2 })
}
pub fn from_prior(prior: Array2<f64>) -> Result<Self> {
let shape = prior.shape();
let n1 = shape[0];
let n2 = shape[1];
if n1 == 0 || n2 == 0 {
return Err(GraphError::InvalidParameter {
param: "prior dimensions".to_string(),
value: format!("({}, {})", n1, n2),
expected: "both dimensions > 0".to_string(),
context: "SimilarityMatrix::from_prior".to_string(),
});
}
let mut sm = Self {
data: prior,
n1,
n2,
};
sm.normalize();
Ok(sm)
}
pub fn get(&self, i: usize, j: usize) -> f64 {
if i < self.n1 && j < self.n2 {
self.data[[i, j]]
} else {
0.0
}
}
pub fn set(&mut self, i: usize, j: usize, value: f64) {
if i < self.n1 && j < self.n2 {
self.data[[i, j]] = value;
}
}
pub fn normalize(&mut self) {
let sum: f64 = self.data.iter().sum();
if sum.abs() < f64::EPSILON {
let val = 1.0 / (self.n1 as f64 * self.n2 as f64);
self.data.fill(val);
} else {
self.data /= sum;
}
}
pub fn as_array(&self) -> &Array2<f64> {
&self.data
}
pub fn n1(&self) -> usize {
self.n1
}
pub fn n2(&self) -> usize {
self.n2
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_alignment_config_default() {
let cfg = AlignmentConfig::default();
assert!((cfg.alpha - 0.6).abs() < f64::EPSILON);
assert_eq!(cfg.max_iter, 100);
assert!((cfg.tolerance - 1e-8).abs() < f64::EPSILON);
assert_eq!(cfg.greedy_candidates, 5);
assert_eq!(cfg.local_search_depth, 50);
}
#[test]
fn test_similarity_matrix_new() {
let sm = SimilarityMatrix::new(3, 4).expect("should create matrix");
let expected = 1.0 / 12.0;
for i in 0..3 {
for j in 0..4 {
assert!((sm.get(i, j) - expected).abs() < f64::EPSILON);
}
}
}
#[test]
fn test_similarity_matrix_zero_dim() {
assert!(SimilarityMatrix::new(0, 5).is_err());
assert!(SimilarityMatrix::new(5, 0).is_err());
}
#[test]
fn test_similarity_matrix_from_prior() {
let prior = array![[1.0, 2.0], [3.0, 4.0]];
let sm = SimilarityMatrix::from_prior(prior).expect("should create from prior");
let sum: f64 = sm.as_array().iter().sum();
assert!((sum - 1.0).abs() < 1e-12);
}
#[test]
fn test_similarity_matrix_set_get() {
let mut sm = SimilarityMatrix::new(2, 2).expect("should create matrix");
sm.set(0, 1, 0.99);
assert!((sm.get(0, 1) - 0.99).abs() < f64::EPSILON);
assert!((sm.get(10, 10)).abs() < f64::EPSILON);
}
#[test]
fn test_similarity_matrix_normalize_zero() {
let prior = Array2::zeros((3, 3));
let sm = SimilarityMatrix::from_prior(prior).expect("should create from zero prior");
let expected = 1.0 / 9.0;
assert!((sm.get(0, 0) - expected).abs() < 1e-12);
}
}