use crate::graph::traits::{GraphBase, GraphQuery};
use crate::graph::Graph;
use crate::node::NodeIndex;
use nalgebra::{DMatrix, DVector};
pub struct AdjacencyMatrix {
matrix: DMatrix<f64>,
node_indices: Vec<NodeIndex>,
}
impl AdjacencyMatrix {
pub fn from_graph<T, E>(graph: &Graph<T, E>) -> Self
where
E: Clone + Into<f64>,
{
let n = graph.node_count();
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
let index_to_pos: std::collections::HashMap<usize, usize> = node_indices
.iter()
.enumerate()
.map(|(i, ni)| (ni.index(), i))
.collect();
let mut matrix = DMatrix::zeros(n, n);
for edge in graph.edges() {
if let Ok((src, tgt)) = graph.edge_endpoints(edge.index()) {
if let (Some(&i), Some(&j)) = (
index_to_pos.get(&src.index()),
index_to_pos.get(&tgt.index()),
) {
let weight: f64 = edge.data().clone().into();
matrix[(i, j)] = weight;
}
}
}
Self {
matrix,
node_indices,
}
}
pub fn as_matrix(&self) -> &DMatrix<f64> {
&self.matrix
}
pub fn node_indices(&self) -> &[NodeIndex] {
&self.node_indices
}
pub fn size(&self) -> usize {
self.matrix.nrows()
}
pub fn node_position(&self, node: NodeIndex) -> Option<usize> {
self.node_indices
.iter()
.position(|&ni| ni.index() == node.index())
}
pub fn node_from_position(&self, pos: usize) -> Option<NodeIndex> {
self.node_indices.get(pos).copied()
}
}
pub struct LaplacianMatrix {
matrix: DMatrix<f64>,
node_indices: Vec<NodeIndex>,
}
impl LaplacianMatrix {
pub fn from_graph<T>(graph: &Graph<T, f64>) -> Self {
let n = graph.node_count();
let node_indices: Vec<NodeIndex> = graph.nodes().map(|n| n.index()).collect();
let index_to_pos: std::collections::HashMap<usize, usize> = node_indices
.iter()
.enumerate()
.map(|(i, ni)| (ni.index(), i))
.collect();
let mut matrix = DMatrix::zeros(n, n);
for edge in graph.edges() {
if let Ok((src, tgt)) = graph.edge_endpoints(edge.index()) {
if let (Some(&i), Some(&j)) = (
index_to_pos.get(&src.index()),
index_to_pos.get(&tgt.index()),
) {
let weight = *edge.data();
matrix[(i, j)] = -weight;
matrix[(i, i)] += weight;
matrix[(j, j)] += weight;
}
}
}
Self {
matrix,
node_indices,
}
}
pub fn as_matrix(&self) -> &DMatrix<f64> {
&self.matrix
}
pub fn node_indices(&self) -> &[NodeIndex] {
&self.node_indices
}
}
pub fn fiedler_vector<T>(graph: &Graph<T, f64>) -> Option<DVector<f64>>
where
T: Clone,
{
let laplacian = LaplacianMatrix::from_graph(graph);
let matrix = laplacian.as_matrix();
let n = matrix.nrows();
if n < 2 {
return None;
}
let mut v = DVector::from_fn(n, |i, _| (i as f64 * 0.1).sin());
v.normalize_mut();
let sum: f64 = v.sum();
let ones = DVector::from_element(n, 1.0);
v -= &ones * (sum / n as f64);
v.normalize_mut();
for _ in 0..100 {
let mut w = matrix * &v;
let sum: f64 = w.sum();
w -= &ones * (sum / n as f64);
let norm = w.norm();
if norm < 1e-10 {
break;
}
v = w / norm;
}
Some(v)
}
pub fn spectral_radius<T>(graph: &Graph<T, f64>) -> f64
where
T: Clone,
{
let adjacency = AdjacencyMatrix::from_graph(graph);
let matrix = adjacency.as_matrix();
let n = matrix.nrows();
if n == 0 {
return 0.0;
}
let mut v = DVector::from_element(n, 1.0);
let mut eigenvalue = 0.0;
for _ in 0..100 {
let w = matrix * &v;
let new_eigenvalue = w.norm();
if (new_eigenvalue - eigenvalue).abs() < 1e-10 {
break;
}
eigenvalue = new_eigenvalue;
v = w / eigenvalue;
}
eigenvalue
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::builders::GraphBuilder;
#[test]
fn test_adjacency_matrix() {
let graph = GraphBuilder::directed()
.with_nodes(vec!["A", "B", "C"])
.with_edges(vec![(0, 1, 1.0), (1, 2, 2.0), (2, 0, 3.0)])
.build()
.unwrap();
let adj = AdjacencyMatrix::from_graph(&graph);
assert_eq!(adj.size(), 3);
assert_eq!(adj.matrix[(0, 1)], 1.0);
assert_eq!(adj.matrix[(1, 2)], 2.0);
assert_eq!(adj.matrix[(2, 0)], 3.0);
}
#[test]
fn test_laplacian_matrix() {
let graph = GraphBuilder::directed()
.with_nodes(vec!["A", "B", "C"])
.with_edges(vec![(0, 1, 1.0), (1, 2, 1.0)])
.build()
.unwrap();
let lap = LaplacianMatrix::from_graph(&graph);
assert_eq!(lap.matrix[(0, 0)], 1.0); assert_eq!(lap.matrix[(1, 1)], 2.0); assert_eq!(lap.matrix[(2, 2)], 1.0);
assert_eq!(lap.matrix[(0, 1)], -1.0);
assert_eq!(lap.matrix[(1, 2)], -1.0);
}
#[test]
fn test_spectral_radius() {
let graph = GraphBuilder::directed()
.with_nodes(vec!["A", "B", "C"])
.with_edges(vec![
(0, 1, 1.0),
(1, 0, 1.0), (1, 2, 1.0),
(2, 1, 1.0), (2, 0, 1.0),
(0, 2, 1.0), ])
.build()
.unwrap();
let radius = spectral_radius(&graph);
assert!((radius - 2.0).abs() < 0.5);
}
}