use amari_core::Multivector;
use std::collections::HashMap;
pub mod error;
pub mod tropical;
#[cfg(feature = "formal-verification")]
pub mod verified;
#[cfg(feature = "formal-verification")]
pub mod verified_contracts;
pub use error::{NetworkError, NetworkResult};
#[cfg(feature = "formal-verification")]
pub use verified::{NetworkProperty, NetworkSignature, VerifiedGeometricNetwork};
#[cfg(feature = "formal-verification")]
pub use verified_contracts::{
GeometricProperties, GraphTheoreticProperties, TropicalProperties,
VerifiedContractGeometricNetwork,
};
#[derive(Clone, Debug)]
pub struct GeometricNetwork<const P: usize, const Q: usize, const R: usize> {
nodes: Vec<Multivector<P, Q, R>>,
edges: Vec<GeometricEdge>,
node_metadata: HashMap<usize, NodeMetadata>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct GeometricEdge {
pub source: usize,
pub target: usize,
pub weight: f64,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeMetadata {
pub label: Option<String>,
pub properties: HashMap<String, f64>,
}
#[derive(Clone, Debug)]
pub struct Community<const P: usize, const Q: usize, const R: usize> {
pub nodes: Vec<usize>,
pub geometric_centroid: Multivector<P, Q, R>,
pub cohesion_score: f64,
}
#[derive(Clone, Debug)]
pub struct PropagationAnalysis {
pub coverage: Vec<usize>,
pub influence_scores: Vec<f64>,
pub convergence_time: usize,
}
impl NodeMetadata {
pub fn new() -> Self {
Self {
label: None,
properties: HashMap::new(),
}
}
pub fn with_label(label: impl Into<String>) -> Self {
Self {
label: Some(label.into()),
properties: HashMap::new(),
}
}
pub fn with_property(mut self, key: impl Into<String>, value: f64) -> Self {
self.properties.insert(key.into(), value);
self
}
}
impl Default for NodeMetadata {
fn default() -> Self {
Self::new()
}
}
impl<const P: usize, const Q: usize, const R: usize> GeometricNetwork<P, Q, R> {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
node_metadata: HashMap::new(),
}
}
pub fn with_capacity(num_nodes: usize, num_edges: usize) -> Self {
Self {
nodes: Vec::with_capacity(num_nodes),
edges: Vec::with_capacity(num_edges),
node_metadata: HashMap::with_capacity(num_nodes),
}
}
pub fn add_node(&mut self, position: Multivector<P, Q, R>) -> usize {
let index = self.nodes.len();
self.nodes.push(position);
index
}
pub fn add_node_with_metadata(
&mut self,
position: Multivector<P, Q, R>,
metadata: NodeMetadata,
) -> usize {
let index = self.add_node(position);
self.node_metadata.insert(index, metadata);
index
}
pub fn add_edge(&mut self, source: usize, target: usize, weight: f64) -> NetworkResult<()> {
if source >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(source));
}
if target >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(target));
}
self.edges.push(GeometricEdge {
source,
target,
weight,
});
Ok(())
}
pub fn add_undirected_edge(&mut self, a: usize, b: usize, weight: f64) -> NetworkResult<()> {
self.add_edge(a, b, weight)?;
self.add_edge(b, a, weight)?;
Ok(())
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
pub fn num_edges(&self) -> usize {
self.edges.len()
}
pub fn get_node(&self, index: usize) -> Option<&Multivector<P, Q, R>> {
self.nodes.get(index)
}
pub fn get_metadata(&self, index: usize) -> Option<&NodeMetadata> {
self.node_metadata.get(&index)
}
pub fn neighbors(&self, node: usize) -> Vec<usize> {
self.edges
.iter()
.filter(|edge| edge.source == node)
.map(|edge| edge.target)
.collect()
}
pub fn degree(&self, node: usize) -> usize {
self.edges.iter().filter(|edge| edge.source == node).count()
}
pub fn edges(&self) -> &[GeometricEdge] {
&self.edges
}
pub fn geometric_distance(&self, node1: usize, node2: usize) -> NetworkResult<f64> {
if node1 >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(node1));
}
if node2 >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(node2));
}
let diff = self.nodes[node1].clone() - self.nodes[node2].clone();
Ok(diff.norm())
}
pub fn compute_geometric_centrality(&self) -> NetworkResult<Vec<f64>> {
if self.nodes.is_empty() {
return Ok(Vec::new());
}
let mut centrality = vec![0.0; self.nodes.len()];
for (i, centrality_value) in centrality.iter_mut().enumerate().take(self.nodes.len()) {
let mut total_distance = 0.0;
let mut reachable_count = 0;
for j in 0..self.nodes.len() {
if i != j {
let distance = self.geometric_distance(i, j)?;
if distance > 0.0 {
total_distance += distance;
reachable_count += 1;
}
}
}
*centrality_value = if total_distance > 0.0 && reachable_count > 0 {
reachable_count as f64 / total_distance
} else {
0.0
};
}
Ok(centrality)
}
pub fn compute_betweenness_centrality(&self) -> NetworkResult<Vec<f64>> {
if self.nodes.is_empty() {
return Ok(Vec::new());
}
let mut betweenness = vec![0.0; self.nodes.len()];
let distances = self.compute_all_pairs_shortest_paths()?;
for s in 0..self.nodes.len() {
for t in 0..self.nodes.len() {
if s == t {
continue;
}
if distances[s][t].is_infinite() {
continue; }
for v in 0..self.nodes.len() {
if v == s || v == t {
continue;
}
if !distances[s][v].is_infinite() && !distances[v][t].is_infinite() {
let path_through_v = distances[s][v] + distances[v][t];
if (path_through_v - distances[s][t]).abs() < 1e-10 {
betweenness[v] += 1.0;
}
}
}
}
}
let normalization = ((self.nodes.len() - 1) * (self.nodes.len() - 2)) as f64;
if normalization > 0.0 {
for value in &mut betweenness {
*value /= normalization;
}
}
Ok(betweenness)
}
pub fn compute_all_pairs_shortest_paths(&self) -> NetworkResult<Vec<Vec<f64>>> {
let n = self.nodes.len();
let mut distances = vec![vec![f64::INFINITY; n]; n];
for (i, distance_row) in distances.iter_mut().enumerate().take(n) {
distance_row[i] = 0.0; }
for edge in &self.edges {
distances[edge.source][edge.target] = edge.weight;
}
for k in 0..n {
for i in 0..n {
for j in 0..n {
if distances[i][k] != f64::INFINITY && distances[k][j] != f64::INFINITY {
let new_distance = distances[i][k] + distances[k][j];
if new_distance < distances[i][j] {
distances[i][j] = new_distance;
}
}
}
}
}
Ok(distances)
}
pub fn shortest_path(
&self,
source: usize,
target: usize,
) -> NetworkResult<Option<(Vec<usize>, f64)>> {
if source >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(source));
}
if target >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(target));
}
if source == target {
return Ok(Some((vec![source], 0.0)));
}
let n = self.nodes.len();
let mut distances = vec![f64::INFINITY; n];
let mut previous = vec![None; n];
let mut visited = vec![false; n];
distances[source] = 0.0;
let mut adj_list: Vec<Vec<(usize, f64)>> = vec![Vec::new(); n];
for edge in &self.edges {
adj_list[edge.source].push((edge.target, edge.weight));
}
for _ in 0..n {
let mut current = None;
let mut min_distance = f64::INFINITY;
for v in 0..n {
if !visited[v] && distances[v] < min_distance {
min_distance = distances[v];
current = Some(v);
}
}
let current = match current {
Some(v) => v,
None => break, };
if current == target {
break; }
visited[current] = true;
for &(neighbor, weight) in &adj_list[current] {
if !visited[neighbor] {
let new_distance = distances[current] + weight;
if new_distance < distances[neighbor] {
distances[neighbor] = new_distance;
previous[neighbor] = Some(current);
}
}
}
}
if distances[target].is_infinite() {
return Ok(None);
}
let mut path = Vec::new();
let mut current = target;
while let Some(prev) = previous[current] {
path.push(current);
current = prev;
}
path.push(source);
path.reverse();
Ok(Some((path, distances[target])))
}
pub fn shortest_geometric_path(
&self,
source: usize,
target: usize,
) -> NetworkResult<Option<(Vec<usize>, f64)>> {
if source >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(source));
}
if target >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(target));
}
if source == target {
return Ok(Some((vec![source], 0.0)));
}
let n = self.nodes.len();
let mut distances = vec![f64::INFINITY; n];
let mut previous = vec![None; n];
let mut visited = vec![false; n];
distances[source] = 0.0;
for _ in 0..n {
let mut current = None;
let mut min_distance = f64::INFINITY;
for v in 0..n {
if !visited[v] && distances[v] < min_distance {
min_distance = distances[v];
current = Some(v);
}
}
let current = match current {
Some(v) => v,
None => break,
};
if current == target {
break;
}
visited[current] = true;
for edge in &self.edges {
if edge.source == current {
let neighbor = edge.target;
if !visited[neighbor] {
let geometric_distance = self.geometric_distance(current, neighbor)?;
let new_distance = distances[current] + geometric_distance;
if new_distance < distances[neighbor] {
distances[neighbor] = new_distance;
previous[neighbor] = Some(current);
}
}
}
}
}
if distances[target].is_infinite() {
return Ok(None);
}
let mut path = Vec::new();
let mut current = target;
while let Some(prev) = previous[current] {
path.push(current);
current = prev;
}
path.push(source);
path.reverse();
Ok(Some((path, distances[target])))
}
pub fn to_tropical_network(&self) -> NetworkResult<crate::tropical::TropicalNetwork> {
let n = self.nodes.len();
let mut weights = vec![vec![f64::INFINITY; n]; n];
for (i, weight_row) in weights.iter_mut().enumerate().take(n) {
weight_row[i] = 0.0;
}
for edge in &self.edges {
weights[edge.source][edge.target] = edge.weight;
}
crate::tropical::TropicalNetwork::from_weights(&weights)
}
pub fn find_communities(
&self,
num_communities: usize,
) -> NetworkResult<Vec<Community<P, Q, R>>> {
if self.nodes.is_empty() {
return Ok(Vec::new());
}
if num_communities == 0 {
return Err(NetworkError::invalid_param(
"num_communities",
0,
"greater than 0",
));
}
if num_communities >= self.nodes.len() {
let communities = self
.nodes
.iter()
.enumerate()
.map(|(i, node)| Community {
nodes: vec![i],
geometric_centroid: node.clone(),
cohesion_score: 1.0,
})
.collect();
return Ok(communities);
}
let mut centroids: Vec<Multivector<P, Q, R>> = Vec::with_capacity(num_communities);
centroids.push(self.nodes[0].clone());
for _ in 1..num_communities {
let mut max_distance = 0.0;
let mut farthest_node = 0;
for (i, node) in self.nodes.iter().enumerate() {
let mut min_distance_to_centroid = f64::INFINITY;
for centroid in ¢roids {
let diff = node.clone() - centroid.clone();
let distance = diff.norm();
if distance < min_distance_to_centroid {
min_distance_to_centroid = distance;
}
}
if min_distance_to_centroid > max_distance {
max_distance = min_distance_to_centroid;
farthest_node = i;
}
}
centroids.push(self.nodes[farthest_node].clone());
}
let mut assignments = vec![0; self.nodes.len()];
let max_iterations = 100;
for _ in 0..max_iterations {
let mut changed = false;
for (node_idx, node) in self.nodes.iter().enumerate() {
let mut best_cluster = 0;
let mut min_distance = f64::INFINITY;
for (cluster_idx, centroid) in centroids.iter().enumerate() {
let diff = node.clone() - centroid.clone();
let distance = diff.norm();
if distance < min_distance {
min_distance = distance;
best_cluster = cluster_idx;
}
}
if assignments[node_idx] != best_cluster {
assignments[node_idx] = best_cluster;
changed = true;
}
}
if !changed {
break;
}
for (cluster_idx, centroid) in centroids.iter_mut().enumerate().take(num_communities) {
let cluster_nodes: Vec<&Multivector<P, Q, R>> = self
.nodes
.iter()
.enumerate()
.filter(|(i, _)| assignments[*i] == cluster_idx)
.map(|(_, node)| node)
.collect();
if !cluster_nodes.is_empty() {
let mut sum = cluster_nodes[0].clone();
for node in cluster_nodes.iter().skip(1) {
sum = sum + (*node).clone();
}
*centroid = sum * (1.0 / cluster_nodes.len() as f64);
}
}
}
let mut communities = Vec::new();
for (cluster_idx, centroid) in centroids.iter().enumerate().take(num_communities) {
let cluster_nodes: Vec<usize> = assignments
.iter()
.enumerate()
.filter(|(_, &assignment)| assignment == cluster_idx)
.map(|(i, _)| i)
.collect();
if cluster_nodes.is_empty() {
continue;
}
let mut total_distance = 0.0;
let mut pair_count = 0;
for &i in &cluster_nodes {
for &j in &cluster_nodes {
if i < j {
let distance = self.geometric_distance(i, j)?;
total_distance += distance;
pair_count += 1;
}
}
}
let cohesion_score = if pair_count > 0 && total_distance > 0.0 {
pair_count as f64 / total_distance
} else {
1.0
};
communities.push(Community {
nodes: cluster_nodes,
geometric_centroid: centroid.clone(),
cohesion_score,
});
}
Ok(communities)
}
pub fn simulate_diffusion(
&self,
initial_sources: &[usize],
max_steps: usize,
diffusion_rate: f64,
) -> NetworkResult<PropagationAnalysis> {
if initial_sources.is_empty() {
return Err(NetworkError::insufficient_data(
"No initial sources provided",
));
}
for &source in initial_sources {
if source >= self.nodes.len() {
return Err(NetworkError::NodeIndexOutOfBounds(source));
}
}
if diffusion_rate <= 0.0 || diffusion_rate > 1.0 {
return Err(NetworkError::invalid_param(
"diffusion_rate",
diffusion_rate,
"between 0 and 1",
));
}
let n = self.nodes.len();
let mut information_levels = vec![0.0; n];
let mut coverage = Vec::new();
let mut influence_scores = vec![0.0; n];
for &source in initial_sources {
information_levels[source] = 1.0;
}
let convergence_threshold = 1e-6;
let mut converged_step = max_steps;
for step in 0..max_steps {
let current_coverage = information_levels
.iter()
.filter(|&&level| level > convergence_threshold)
.count();
coverage.push(current_coverage);
let previous_levels = information_levels.clone();
let mut new_levels = information_levels.clone();
for edge in &self.edges {
let source_level = information_levels[edge.source];
if source_level > convergence_threshold {
let similarity = self.compute_geometric_similarity(edge.source, edge.target)?;
let transfer_amount = source_level * diffusion_rate * similarity * edge.weight;
new_levels[edge.target] += transfer_amount;
influence_scores[edge.source] += transfer_amount;
}
}
for level in &mut new_levels {
*level *= 0.95; }
information_levels = new_levels;
let total_change: f64 = information_levels
.iter()
.zip(previous_levels.iter())
.map(|(new, old)| (new - old).abs())
.sum();
if total_change < convergence_threshold {
converged_step = step + 1;
break;
}
}
Ok(PropagationAnalysis {
coverage,
influence_scores,
convergence_time: converged_step,
})
}
fn compute_geometric_similarity(&self, node1: usize, node2: usize) -> NetworkResult<f64> {
let pos1 = &self.nodes[node1];
let pos2 = &self.nodes[node2];
let product = pos1.geometric_product(pos2);
let scalar_part = product.scalar_part();
let norm1 = pos1.norm();
let norm2 = pos2.norm();
if norm1 > 0.0 && norm2 > 0.0 {
Ok((scalar_part / (norm1 * norm2)).abs())
} else {
Ok(0.0)
}
}
pub fn spectral_clustering(&self, num_clusters: usize) -> NetworkResult<Vec<Vec<usize>>> {
use nalgebra::{DMatrix, SymmetricEigen};
if self.nodes.is_empty() {
return Ok(Vec::new());
}
if num_clusters == 0 {
return Err(NetworkError::invalid_param(
"num_clusters",
0,
"greater than 0",
));
}
let n = self.nodes.len();
if num_clusters >= n {
return Ok((0..n).map(|i| vec![i]).collect());
}
let mut adjacency = DMatrix::zeros(n, n);
for edge in &self.edges {
adjacency[(edge.source, edge.target)] = edge.weight;
adjacency[(edge.target, edge.source)] = edge.weight;
}
let mut degree = DMatrix::zeros(n, n);
for i in 0..n {
let node_degree: f64 = adjacency.row(i).sum();
if node_degree > 0.0 {
degree[(i, i)] = node_degree;
}
}
let mut normalized_laplacian = DMatrix::identity(n, n);
for i in 0..n {
for j in 0..n {
if i != j && degree[(i, i)] > 0.0 && degree[(j, j)] > 0.0 {
let value = adjacency[(i, j)] / (degree[(i, i)].sqrt() * degree[(j, j)].sqrt());
normalized_laplacian[(i, j)] = -value;
}
}
}
let eigen = SymmetricEigen::new(normalized_laplacian);
let eigenvalues = eigen.eigenvalues;
let eigenvectors = eigen.eigenvectors;
let mut clusters = vec![Vec::new(); num_clusters];
for i in 0..n {
let mut best_cluster = 0;
let mut max_value = eigenvectors[(i, 0)];
for k in 1..num_clusters.min(eigenvalues.len()) {
if eigenvectors[(i, k)].abs() > max_value.abs() {
max_value = eigenvectors[(i, k)];
best_cluster = k;
}
}
clusters[best_cluster].push(i);
}
clusters.retain(|cluster| !cluster.is_empty());
Ok(clusters)
}
}
impl<const P: usize, const Q: usize, const R: usize> Default for GeometricNetwork<P, Q, R> {
fn default() -> Self {
Self::new()
}
}