diff --git a/Cargo.toml b/Cargo.toml index d502b89..61623c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gramag" -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "MIT" description = "Graph Magnitude Homology in Rust, with Python bindings" diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..95d367b --- /dev/null +++ b/TODO.md @@ -0,0 +1 @@ +- [ ] Benchmark phlite vs lophat - why is phlite so much slower? diff --git a/docs/requirements.txt b/docs/requirements.txt index 83a9430..52b14b7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,4 @@ sphinx sphinx-rtd-theme sphinxcontrib-napoleon -gramag==0.4.0 +gramag==0.4.1 diff --git a/docs/source/examples/distance_matrix.py b/docs/source/examples/distance_matrix.py new file mode 100644 index 0000000..5dd2c6c --- /dev/null +++ b/docs/source/examples/distance_matrix.py @@ -0,0 +1,26 @@ +from gramag import MagGraph, format_rank_table + +# Create your graph based on a square distance matrix +# use -1 to denote an infinite distance + +# Undirected cycle graph on N nodes +N = 7 +distance_matrix = [[min((j - i) % N, (i - j) % N) for j in range(N)] for i in range(N)] + +mg = MagGraph.from_distance_matrix(distance_matrix) +# Compute generators of all MC^{(s, t)}_{k, l} for l<=6 +mg.populate_paths(l_max=11) + +# Reports the ranks of MC^{(s, t)}_{k, l}, summed over (s, t) +rk_gens = mg.rank_generators() + +# For each l, in parallel across each (s, t), computes MH^{(s, t)}_{k, l} +# Adds up the rank for each k, l +rk_hom = mg.rank_homology() + +# Pretty print +print("Rank of MC:") +print(format_rank_table(rk_gens)) + +print("Rank of MH:") +print(format_rank_table(rk_hom)) diff --git a/docs/source/examples/distance_matrix_2.py b/docs/source/examples/distance_matrix_2.py new file mode 100644 index 0000000..965cf68 --- /dev/null +++ b/docs/source/examples/distance_matrix_2.py @@ -0,0 +1,32 @@ +from gramag import MagGraph, format_rank_table + +# Create your graph based on a square distance matrix +# use -1 to denote an infinite distance + +# We do a digraph made up of two paths 0 -> 1 -> 2 -> 4 and 0 -> 3 -> 4 + +distance_matrix = [ + [0, 1, 2, 1, 2], + [-1, 0, 1, -1, 2], + [-1, -1, 0, -1, 1], + [-1, -1, -1, 0, 1], + [-1, -1, -1, -1, 0], +] + +mg = MagGraph.from_distance_matrix(distance_matrix) +# Compute generators of all MC^{(s, t)}_{k, l} for l<=6 +mg.populate_paths(l_max=3) + +# Reports the ranks of MC^{(s, t)}_{k, l}, summed over (s, t) +rk_gens = mg.rank_generators() + +# For each l, in parallel across each (s, t), computes MH^{(s, t)}_{k, l} +# Adds up the rank for each k, l +rk_hom = mg.rank_homology() + +# Pretty print +print("Rank of MC:") +print(format_rank_table(rk_gens)) + +print("Rank of MH:") +print(format_rank_table(rk_hom)) diff --git a/src/bindings.rs b/src/bindings.rs index d833593..4bae228 100644 --- a/src/bindings.rs +++ b/src/bindings.rs @@ -1,5 +1,6 @@ use std::{borrow::Borrow, collections::HashMap, iter, sync::Arc}; +use dashmap::DashMap; use rayon::prelude::*; use lophat::{algorithms::SerialDecomposition, columns::VecColumn}; @@ -141,6 +142,55 @@ impl MagGraph { } } + /// Call this to compute the magnitude homology of a finite quasimetric space. + /// Simply pass in your distance matrix as the first parameter, using ``-1`` to denote an infinite distance. + /// You will get back a |MagGraph|_ (this is a bit of a hack) from which you can compute magnitude homology. + /// + /// :param distance_matrix: The distance matrix of your finite quasimetric space. + /// :type distance_matrix: list[list[int]] + /// :return: A |MagGraph|_ object representing the apce. + /// :rtype: MagGraph + /// + #[staticmethod] + fn from_distance_matrix(distance_matrix: Vec>) -> Self { + // Bit of a hack, we'll set up a line graph on the number of nodes + // and then input our own distance matrix in lieu of running dijkstra + + let n_nodes = distance_matrix.len(); + for row in &distance_matrix { + if row.len() != n_nodes { + panic!("Not given a square matrix"); + } + } + + let mut digraph = Graph::<(), ()>::new(); + for i in 0..n_nodes { + let new_index = digraph.add_node(()); + assert!(new_index.index() == i as usize) + } + + let new_distance_matrix = DashMap::new(); + for i in 0..n_nodes { + let mut row = HashMap::new(); + for j in 0..n_nodes { + if distance_matrix[i][j] != -1 { + let key = NodeIndex::from(j as u32); + row.insert(key, distance_matrix[i][j] as usize); + } + } + let key = NodeIndex::from(i as u32); + new_distance_matrix.insert(key, row); + } + + let distance_matrix = Arc::new(DistanceMatrix(new_distance_matrix)); + + MagGraph { + digraph, + distance_matrix, + path_container: None, + } + } + #[pyo3(signature=(*,k_max=None, l_max=None))] /// You must call this method before attempting to compute any homology. /// diff --git a/src/distances.rs b/src/distances.rs index 0f2f10a..28ba1f8 100644 --- a/src/distances.rs +++ b/src/distances.rs @@ -8,7 +8,7 @@ use rayon::prelude::*; use std::{collections::HashMap, ops::Add}; #[derive(Debug)] -pub struct DistanceMatrix(DashMap>); +pub struct DistanceMatrix(pub(crate) DashMap>); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Distance {