Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,182 @@
//! Chain complex implementation
use super::Homology;
use crate::{Error, Result};
use nalgebra::DMatrix;
/// A chain complex for computing homology
///
/// A chain complex is a sequence of abelian groups (vector spaces) connected
/// by boundary maps: ... -> C_{n+1} -d_{n+1}-> C_n -d_n-> C_{n-1} -> ...
///
/// The key property is d_n ∘ d_{n+1} = 0 (boundary of boundary is zero).
#[derive(Debug, Clone)]
pub struct ChainComplex {
/// Boundary maps d_n: C_n -> C_{n-1}
boundary_maps: Vec<DMatrix<f64>>,
}
impl ChainComplex {
/// Create a new chain complex from boundary maps
pub fn new(boundary_maps: Vec<DMatrix<f64>>) -> Self {
Self { boundary_maps }
}
/// Create a chain complex from dimensions and explicit maps
pub fn from_dimensions(dimensions: &[usize]) -> Self {
let mut maps = Vec::new();
for i in 1..dimensions.len() {
maps.push(DMatrix::zeros(dimensions[i - 1], dimensions[i]));
}
Self::new(maps)
}
/// Get the number of chain groups
pub fn length(&self) -> usize {
self.boundary_maps.len() + 1
}
/// Get the n-th boundary map
pub fn boundary(&self, n: usize) -> Option<&DMatrix<f64>> {
self.boundary_maps.get(n)
}
/// Set the n-th boundary map
pub fn set_boundary(&mut self, n: usize, map: DMatrix<f64>) -> Result<()> {
if n >= self.boundary_maps.len() {
return Err(Error::InvalidTopology(format!(
"Boundary index {} out of range",
n
)));
}
self.boundary_maps[n] = map;
Ok(())
}
/// Check the chain complex property: d ∘ d = 0
pub fn verify(&self, epsilon: f64) -> Result<bool> {
for i in 0..self.boundary_maps.len().saturating_sub(1) {
let d_i = &self.boundary_maps[i];
let d_i1 = &self.boundary_maps[i + 1];
// Check dimensions are compatible
if d_i.ncols() != d_i1.nrows() {
return Ok(false);
}
// Check d_i ∘ d_{i+1} = 0
let composition = d_i * d_i1;
if composition.norm() > epsilon {
return Ok(false);
}
}
Ok(true)
}
/// Compute the n-th homology group H_n = ker(d_n) / im(d_{n+1})
pub fn homology(&self, n: usize) -> Result<Homology> {
// Get the relevant boundary maps
let d_n = self.boundary_maps.get(n);
let d_n1 = if n + 1 < self.boundary_maps.len() {
Some(&self.boundary_maps[n + 1])
} else {
None
};
// Compute kernel of d_n
let kernel_dim = if let Some(d) = d_n {
compute_kernel_dimension(d)
} else {
// If no outgoing boundary, kernel is everything
if n > 0 && n - 1 < self.boundary_maps.len() {
self.boundary_maps[n - 1].ncols()
} else {
0
}
};
// Compute image of d_{n+1}
let image_dim = if let Some(d) = d_n1 {
compute_image_dimension(d)
} else {
0
};
// Homology dimension = dim(ker) - dim(im)
let homology_dim = kernel_dim.saturating_sub(image_dim);
Ok(Homology::new(n, homology_dim))
}
/// Compute all homology groups
pub fn all_homology(&self) -> Result<Vec<Homology>> {
let mut result = Vec::new();
for n in 0..self.length() {
result.push(self.homology(n)?);
}
Ok(result)
}
/// Get the Betti numbers
pub fn betti_numbers(&self) -> Result<Vec<usize>> {
let homology = self.all_homology()?;
Ok(homology.iter().map(|h| h.dimension()).collect())
}
}
/// Compute the dimension of the kernel of a matrix
fn compute_kernel_dimension(matrix: &DMatrix<f64>) -> usize {
// Use SVD to compute rank, kernel dimension = ncols - rank
let svd = matrix.clone().svd(false, false);
let singular_values = svd.singular_values;
let threshold = 1e-10;
let rank = singular_values.iter().filter(|&&s| s > threshold).count();
matrix.ncols().saturating_sub(rank)
}
/// Compute the dimension of the image of a matrix
fn compute_image_dimension(matrix: &DMatrix<f64>) -> usize {
// Image dimension = rank
let svd = matrix.clone().svd(false, false);
let singular_values = svd.singular_values;
let threshold = 1e-10;
singular_values.iter().filter(|&&s| s > threshold).count()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chain_complex_creation() {
let d0 = DMatrix::from_row_slice(2, 3, &[1.0, -1.0, 0.0, 0.0, 1.0, -1.0]);
let complex = ChainComplex::new(vec![d0]);
assert_eq!(complex.length(), 2);
}
#[test]
fn test_kernel_dimension() {
// Identity matrix has trivial kernel
let identity = DMatrix::identity(3, 3);
assert_eq!(compute_kernel_dimension(&identity), 0);
// Zero matrix has full kernel
let zero = DMatrix::zeros(2, 3);
assert_eq!(compute_kernel_dimension(&zero), 3);
}
#[test]
fn test_image_dimension() {
// Identity matrix has full image
let identity = DMatrix::identity(3, 3);
assert_eq!(compute_image_dimension(&identity), 3);
// Zero matrix has trivial image
let zero = DMatrix::zeros(2, 3);
assert_eq!(compute_image_dimension(&zero), 0);
}
}

View File

@@ -0,0 +1,177 @@
//! Homology group implementation
use nalgebra::DVector;
/// A homology group H_n
///
/// Homology groups measure "holes" in topological spaces:
/// - H_0: connected components
/// - H_1: loops/tunnels
/// - H_2: voids/cavities
#[derive(Debug, Clone)]
pub struct Homology {
/// Degree n of the homology group
degree: usize,
/// Dimension of the homology group (Betti number)
dimension: usize,
/// Generators of the homology group (representative cycles)
generators: Vec<DVector<f64>>,
}
impl Homology {
/// Create a new homology group
pub fn new(degree: usize, dimension: usize) -> Self {
Self {
degree,
dimension,
generators: Vec::new(),
}
}
/// Create a homology group with generators
pub fn with_generators(degree: usize, generators: Vec<DVector<f64>>) -> Self {
let dimension = generators.len();
Self {
degree,
dimension,
generators,
}
}
/// Get the degree of the homology group
pub fn degree(&self) -> usize {
self.degree
}
/// Get the dimension (Betti number)
pub fn dimension(&self) -> usize {
self.dimension
}
/// Get the generators
pub fn generators(&self) -> &[DVector<f64>] {
&self.generators
}
/// Check if the homology group is trivial
pub fn is_trivial(&self) -> bool {
self.dimension == 0
}
/// Set the generators
pub fn set_generators(&mut self, generators: Vec<DVector<f64>>) {
self.dimension = generators.len();
self.generators = generators;
}
/// Add a generator
pub fn add_generator(&mut self, generator: DVector<f64>) {
self.generators.push(generator);
self.dimension += 1;
}
/// Check if a cycle is a boundary (homologous to zero)
pub fn is_boundary(&self, cycle: &DVector<f64>, epsilon: f64) -> bool {
// A cycle is a boundary if it's in the span of boundaries
// For now, check if it's close to zero
cycle.norm() < epsilon
}
/// Compute the homology class of a cycle
pub fn classify(&self, cycle: &DVector<f64>) -> HomologyClass {
if self.generators.is_empty() {
return HomologyClass::Zero;
}
// Project onto generator space
let mut coefficients = Vec::new();
for gen in &self.generators {
let coeff = cycle.dot(gen) / gen.dot(gen);
coefficients.push(coeff);
}
HomologyClass::NonTrivial(coefficients)
}
}
/// A homology class [α] in H_n
#[derive(Debug, Clone)]
pub enum HomologyClass {
/// The zero class
Zero,
/// Non-trivial class with coefficients in terms of generators
NonTrivial(Vec<f64>),
}
impl HomologyClass {
/// Check if this is the zero class
pub fn is_zero(&self) -> bool {
matches!(self, HomologyClass::Zero)
}
/// Get the coefficients if non-trivial
pub fn coefficients(&self) -> Option<&[f64]> {
match self {
HomologyClass::Zero => None,
HomologyClass::NonTrivial(c) => Some(c),
}
}
}
/// Relative homology H_n(X, A)
#[derive(Debug, Clone)]
pub struct RelativeHomology {
/// Degree
degree: usize,
/// Space X
space: Homology,
/// Subspace A
subspace: Homology,
/// Relative homology dimension
dimension: usize,
}
impl RelativeHomology {
/// Create new relative homology
pub fn new(degree: usize, space: Homology, subspace: Homology) -> Self {
// Long exact sequence: ... -> H_n(A) -> H_n(X) -> H_n(X,A) -> H_{n-1}(A) -> ...
let dimension = space.dimension().saturating_sub(subspace.dimension());
Self {
degree,
space,
subspace,
dimension,
}
}
/// Get the dimension
pub fn dimension(&self) -> usize {
self.dimension
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_homology_creation() {
let h1 = Homology::new(1, 2);
assert_eq!(h1.degree(), 1);
assert_eq!(h1.dimension(), 2);
assert!(!h1.is_trivial());
}
#[test]
fn test_trivial_homology() {
let h0 = Homology::new(0, 0);
assert!(h0.is_trivial());
}
#[test]
fn test_homology_class() {
let class = HomologyClass::NonTrivial(vec![1.0, 2.0]);
assert!(!class.is_zero());
assert_eq!(class.coefficients().unwrap(), &[1.0, 2.0]);
}
}

View File

@@ -0,0 +1,695 @@
//! Sheaf Cohomology Module for Prime-Radiant
//!
//! This module provides sheaf cohomology computations for detecting global obstructions
//! to local consistency in belief networks. Key capabilities:
//!
//! - **SheafGraph**: Directed graph with local sections on nodes
//! - **CohomologyEngine**: Computes cohomology groups H^i and obstruction classes
//! - **RestrictionMaps**: Linear maps between stalks encoding local compatibility
//! - **Obstruction Detection**: Identifies global inconsistencies from local data
//!
//! ## Mathematical Background
//!
//! A sheaf F on a graph G assigns vector spaces F(U) to open sets U and restriction
//! maps r_{UV}: F(V) -> F(U) for U ⊆ V satisfying:
//! - r_{UU} = id
//! - r_{UW} = r_{UV} ∘ r_{VW} for U ⊆ V ⊆ W
//!
//! The cohomology groups H^i(G, F) measure the failure of local sections to glue globally.
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Error types for cohomology operations
#[derive(Debug, Clone, PartialEq)]
pub enum CohomologyError {
/// Dimension mismatch between sections
DimensionMismatch { expected: usize, got: usize },
/// Invalid node index
InvalidNode(usize),
/// Invalid edge specification
InvalidEdge(usize, usize),
/// Singular matrix in computation
SingularMatrix,
/// Numerical error
NumericalError(String),
}
impl std::fmt::Display for CohomologyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DimensionMismatch { expected, got } => {
write!(f, "Dimension mismatch: expected {}, got {}", expected, got)
}
Self::InvalidNode(n) => write!(f, "Invalid node index: {}", n),
Self::InvalidEdge(i, j) => write!(f, "Invalid edge: ({}, {})", i, j),
Self::SingularMatrix => write!(f, "Singular matrix encountered"),
Self::NumericalError(msg) => write!(f, "Numerical error: {}", msg),
}
}
}
impl std::error::Error for CohomologyError {}
pub type Result<T> = std::result::Result<T, CohomologyError>;
/// A node in the sheaf graph with local section data
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SheafNode {
/// Node identifier
pub id: usize,
/// Node label
pub label: String,
/// Local section as a vector (stalk of the sheaf)
pub section: Vec<f64>,
/// Confidence weight for this node
pub weight: f64,
}
impl SheafNode {
pub fn new(id: usize, label: impl Into<String>, section: Vec<f64>) -> Self {
Self {
id,
label: label.into(),
section,
weight: 1.0,
}
}
pub fn with_weight(mut self, weight: f64) -> Self {
self.weight = weight;
self
}
pub fn dimension(&self) -> usize {
self.section.len()
}
}
/// An edge with restriction map
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SheafEdge {
/// Source node index
pub source: usize,
/// Target node index
pub target: usize,
/// Restriction map as a matrix (row-major, target_dim x source_dim)
/// Maps source section to target section
pub restriction_map: Vec<f64>,
/// Source dimension
pub source_dim: usize,
/// Target dimension
pub target_dim: usize,
}
impl SheafEdge {
/// Create an edge with identity restriction (sections must have same dimension)
pub fn identity(source: usize, target: usize, dim: usize) -> Self {
let mut restriction = vec![0.0; dim * dim];
for i in 0..dim {
restriction[i * dim + i] = 1.0;
}
Self {
source,
target,
restriction_map: restriction,
source_dim: dim,
target_dim: dim,
}
}
/// Create an edge with a custom restriction map
pub fn with_map(source: usize, target: usize, map: Vec<f64>, source_dim: usize, target_dim: usize) -> Self {
Self {
source,
target,
restriction_map: map,
source_dim,
target_dim,
}
}
/// Apply restriction map to a section
pub fn apply(&self, section: &[f64]) -> Result<Vec<f64>> {
if section.len() != self.source_dim {
return Err(CohomologyError::DimensionMismatch {
expected: self.source_dim,
got: section.len(),
});
}
let mut result = vec![0.0; self.target_dim];
for i in 0..self.target_dim {
for j in 0..self.source_dim {
result[i] += self.restriction_map[i * self.source_dim + j] * section[j];
}
}
Ok(result)
}
}
/// A sheaf on a graph
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SheafGraph {
/// Nodes with local sections
pub nodes: Vec<SheafNode>,
/// Edges with restriction maps
pub edges: Vec<SheafEdge>,
}
impl SheafGraph {
pub fn new() -> Self {
Self {
nodes: Vec::new(),
edges: Vec::new(),
}
}
pub fn add_node(&mut self, node: SheafNode) -> usize {
let id = self.nodes.len();
self.nodes.push(node);
id
}
pub fn add_edge(&mut self, edge: SheafEdge) -> Result<()> {
if edge.source >= self.nodes.len() {
return Err(CohomologyError::InvalidNode(edge.source));
}
if edge.target >= self.nodes.len() {
return Err(CohomologyError::InvalidNode(edge.target));
}
self.edges.push(edge);
Ok(())
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
}
impl Default for SheafGraph {
fn default() -> Self {
Self::new()
}
}
/// Result of cohomology computation
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CohomologyResult {
/// Dimension of H^0 (global sections)
pub h0_dim: usize,
/// Dimension of H^1 (first obstruction group)
pub h1_dim: usize,
/// Euler characteristic χ = dim(H^0) - dim(H^1)
pub euler_characteristic: i64,
/// Local consistency energy (sum of squared restriction errors)
pub consistency_energy: f64,
/// Obstruction cocycle (if any)
pub obstruction_cocycle: Option<Vec<f64>>,
/// Is the sheaf globally consistent?
pub is_consistent: bool,
}
/// Detected obstruction to global consistency
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Obstruction {
/// Edge where the obstruction is localized
pub edge_index: usize,
/// Source node
pub source_node: usize,
/// Target node
pub target_node: usize,
/// Obstruction vector (difference after restriction)
pub obstruction_vector: Vec<f64>,
/// Magnitude of the obstruction
pub magnitude: f64,
/// Description of the inconsistency
pub description: String,
}
/// Main cohomology computation engine
#[derive(Clone, Debug)]
pub struct CohomologyEngine {
/// Tolerance for numerical comparisons
pub tolerance: f64,
}
impl CohomologyEngine {
pub fn new() -> Self {
Self { tolerance: 1e-10 }
}
pub fn with_tolerance(tolerance: f64) -> Self {
Self { tolerance }
}
/// Compute cohomology groups of a sheaf graph
pub fn compute_cohomology(&self, graph: &SheafGraph) -> Result<CohomologyResult> {
if graph.nodes.is_empty() {
return Ok(CohomologyResult {
h0_dim: 0,
h1_dim: 0,
euler_characteristic: 0,
consistency_energy: 0.0,
obstruction_cocycle: None,
is_consistent: true,
});
}
// Compute the coboundary map d^0: C^0 -> C^1
// C^0 = direct sum of stalks at vertices
// C^1 = direct sum of stalks at edges (target stalks)
let c0_dim: usize = graph.nodes.iter().map(|n| n.dimension()).sum();
let c1_dim: usize = graph.edges.iter().map(|e| e.target_dim).sum();
// Build the coboundary matrix
let coboundary = self.build_coboundary_matrix(graph, c0_dim, c1_dim)?;
// Compute kernel dimension (H^0)
let kernel_dim = self.compute_kernel_dimension(&coboundary, c0_dim, c1_dim);
// Compute image dimension
let image_dim = self.compute_rank(&coboundary, c0_dim, c1_dim);
// H^1 = C^1 / Im(d^0) for this simplified case
let h1_dim = if c1_dim > image_dim { c1_dim - image_dim } else { 0 };
// Compute consistency energy
let (consistency_energy, obstruction) = self.compute_consistency_energy(graph)?;
let is_consistent = consistency_energy < self.tolerance;
Ok(CohomologyResult {
h0_dim: kernel_dim,
h1_dim,
euler_characteristic: kernel_dim as i64 - h1_dim as i64,
consistency_energy,
obstruction_cocycle: obstruction,
is_consistent,
})
}
/// Detect all obstructions to global consistency
pub fn detect_obstructions(&self, graph: &SheafGraph) -> Result<Vec<Obstruction>> {
let mut obstructions = Vec::new();
for (i, edge) in graph.edges.iter().enumerate() {
let source = &graph.nodes[edge.source];
let target = &graph.nodes[edge.target];
// Apply restriction map to source section
let restricted = edge.apply(&source.section)?;
// Compare with target section
let mut diff = Vec::with_capacity(edge.target_dim);
let mut magnitude_sq = 0.0;
for j in 0..edge.target_dim.min(target.section.len()) {
let d = restricted[j] - target.section[j];
diff.push(d);
magnitude_sq += d * d;
}
let magnitude = magnitude_sq.sqrt();
if magnitude > self.tolerance {
obstructions.push(Obstruction {
edge_index: i,
source_node: edge.source,
target_node: edge.target,
obstruction_vector: diff,
magnitude,
description: format!(
"Inconsistency between '{}' and '{}': magnitude {:.6}",
source.label, target.label, magnitude
),
});
}
}
// Sort by magnitude (largest first)
obstructions.sort_by(|a, b| b.magnitude.partial_cmp(&a.magnitude).unwrap_or(std::cmp::Ordering::Equal));
Ok(obstructions)
}
/// Compute the global section space (H^0)
pub fn compute_global_sections(&self, graph: &SheafGraph) -> Result<Vec<Vec<f64>>> {
if graph.nodes.is_empty() {
return Ok(Vec::new());
}
// For a simple connected graph, a global section must agree on all restrictions
// We find sections that minimize the total restriction error
let dim = graph.nodes.get(0).map(|n| n.dimension()).unwrap_or(0);
let mut global_sections = Vec::new();
// Start with the first node's section as a candidate
if let Some(first_node) = graph.nodes.first() {
let mut candidate = first_node.section.clone();
// Check if it's a valid global section
let mut is_global = true;
for edge in &graph.edges {
let restricted = edge.apply(&graph.nodes[edge.source].section)?;
let target = &graph.nodes[edge.target].section;
for j in 0..edge.target_dim.min(target.len()) {
if (restricted[j] - target[j]).abs() > self.tolerance {
is_global = false;
break;
}
}
if !is_global { break; }
}
if is_global {
global_sections.push(candidate);
}
}
// Try to find a global section by averaging (simple approach)
if global_sections.is_empty() && !graph.nodes.is_empty() {
let dim = graph.nodes[0].dimension();
let mut avg = vec![0.0; dim];
let mut total_weight = 0.0;
for node in &graph.nodes {
for j in 0..dim.min(node.section.len()) {
avg[j] += node.section[j] * node.weight;
}
total_weight += node.weight;
}
if total_weight > 0.0 {
for v in &mut avg {
*v /= total_weight;
}
global_sections.push(avg);
}
}
Ok(global_sections)
}
/// Repair local sections to achieve global consistency
pub fn repair_sections(&self, graph: &mut SheafGraph) -> Result<f64> {
// Iterative repair: adjust sections to minimize total restriction error
let mut total_adjustment = 0.0;
let max_iterations = 100;
let learning_rate = 0.5;
for _ in 0..max_iterations {
let mut iteration_adjustment = 0.0;
for edge in &graph.edges {
let source = &graph.nodes[edge.source];
let target = &graph.nodes[edge.target];
// Apply restriction
let restricted = edge.apply(&source.section)?;
// Compute gradient for target adjustment
let mut gradient = Vec::with_capacity(edge.target_dim);
for j in 0..edge.target_dim.min(target.section.len()) {
gradient.push(restricted[j] - target.section[j]);
}
// Apply adjustment (weighted by node weights)
let source_weight = source.weight;
let target_weight = target.weight;
let total_w = source_weight + target_weight;
if total_w > 0.0 {
// Adjust target
let target_node = &mut graph.nodes[edge.target];
for j in 0..gradient.len().min(target_node.section.len()) {
let adj = learning_rate * gradient[j] * source_weight / total_w;
target_node.section[j] += adj;
iteration_adjustment += adj.abs();
}
}
}
total_adjustment += iteration_adjustment;
if iteration_adjustment < self.tolerance {
break;
}
}
Ok(total_adjustment)
}
// Private helper methods
fn build_coboundary_matrix(&self, graph: &SheafGraph, c0_dim: usize, c1_dim: usize) -> Result<Vec<f64>> {
// Coboundary matrix d^0: C^0 -> C^1
// For edge e: u -> v, d^0 acts as: (d^0 s)(e) = r_e(s_u) - s_v
let mut matrix = vec![0.0; c1_dim * c0_dim];
let mut row_offset = 0;
let mut col_offsets: Vec<usize> = Vec::with_capacity(graph.nodes.len());
let mut current_offset = 0;
for node in &graph.nodes {
col_offsets.push(current_offset);
current_offset += node.dimension();
}
for edge in &graph.edges {
let source_offset = col_offsets[edge.source];
let target_offset = col_offsets[edge.target];
// Add restriction map contribution (positive)
for i in 0..edge.target_dim {
for j in 0..edge.source_dim {
let row = row_offset + i;
let col = source_offset + j;
if row < c1_dim && col < c0_dim {
matrix[row * c0_dim + col] = edge.restriction_map[i * edge.source_dim + j];
}
}
}
// Subtract identity on target (negative contribution)
for i in 0..edge.target_dim.min(graph.nodes[edge.target].dimension()) {
let row = row_offset + i;
let col = target_offset + i;
if row < c1_dim && col < c0_dim {
matrix[row * c0_dim + col] -= 1.0;
}
}
row_offset += edge.target_dim;
}
Ok(matrix)
}
fn compute_kernel_dimension(&self, matrix: &[f64], rows: usize, cols: usize) -> usize {
// Kernel dimension = cols - rank
let rank = self.compute_rank(matrix, rows, cols);
if cols > rank { cols - rank } else { 0 }
}
fn compute_rank(&self, matrix: &[f64], rows: usize, cols: usize) -> usize {
// Simple rank computation via Gaussian elimination
if rows == 0 || cols == 0 {
return 0;
}
let mut m = matrix.to_vec();
let mut rank = 0;
let mut pivot_col = 0;
for row in 0..rows {
if pivot_col >= cols {
break;
}
// Find pivot
let mut max_row = row;
let mut max_val = m[row * cols + pivot_col].abs();
for k in (row + 1)..rows {
let val = m[k * cols + pivot_col].abs();
if val > max_val {
max_val = val;
max_row = k;
}
}
if max_val < self.tolerance {
pivot_col += 1;
continue;
}
// Swap rows
if max_row != row {
for j in 0..cols {
m.swap(row * cols + j, max_row * cols + j);
}
}
// Eliminate
let pivot = m[row * cols + pivot_col];
for k in (row + 1)..rows {
let factor = m[k * cols + pivot_col] / pivot;
for j in pivot_col..cols {
m[k * cols + j] -= factor * m[row * cols + j];
}
}
rank += 1;
pivot_col += 1;
}
rank
}
fn compute_consistency_energy(&self, graph: &SheafGraph) -> Result<(f64, Option<Vec<f64>>)> {
let mut total_energy = 0.0;
let mut obstruction = Vec::new();
for edge in &graph.edges {
let source = &graph.nodes[edge.source];
let target = &graph.nodes[edge.target];
let restricted = edge.apply(&source.section)?;
for j in 0..edge.target_dim.min(target.section.len()) {
let diff = restricted[j] - target.section[j];
total_energy += diff * diff;
obstruction.push(diff);
}
}
let obs = if obstruction.iter().any(|&x| x.abs() > self.tolerance) {
Some(obstruction)
} else {
None
};
Ok((total_energy, obs))
}
}
impl Default for CohomologyEngine {
fn default() -> Self {
Self::new()
}
}
/// Builder for creating sheaf graphs from belief networks
#[derive(Clone, Debug)]
pub struct BeliefGraphBuilder {
dimension: usize,
}
impl BeliefGraphBuilder {
pub fn new(dimension: usize) -> Self {
Self { dimension }
}
/// Create a sheaf graph from belief nodes and edges
pub fn build_from_beliefs(
&self,
beliefs: &[(String, Vec<f64>)],
connections: &[(usize, usize)],
) -> Result<SheafGraph> {
let mut graph = SheafGraph::new();
for (i, (label, section)) in beliefs.iter().enumerate() {
let node = SheafNode::new(i, label.clone(), section.clone());
graph.add_node(node);
}
for &(source, target) in connections {
let source_dim = graph.nodes[source].dimension();
let target_dim = graph.nodes[target].dimension();
// Create identity restriction map (or projection if dimensions differ)
let min_dim = source_dim.min(target_dim);
let mut map = vec![0.0; target_dim * source_dim];
for i in 0..min_dim {
map[i * source_dim + i] = 1.0;
}
let edge = SheafEdge::with_map(source, target, map, source_dim, target_dim);
graph.add_edge(edge)?;
}
Ok(graph)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sheaf_graph_creation() {
let mut graph = SheafGraph::new();
let n1 = graph.add_node(SheafNode::new(0, "A", vec![1.0, 2.0]));
let n2 = graph.add_node(SheafNode::new(1, "B", vec![1.0, 2.0]));
let edge = SheafEdge::identity(n1, n2, 2);
graph.add_edge(edge).unwrap();
assert_eq!(graph.node_count(), 2);
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_cohomology_consistent() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![1.0, 2.0]));
graph.add_node(SheafNode::new(1, "B", vec![1.0, 2.0]));
let edge = SheafEdge::identity(0, 1, 2);
graph.add_edge(edge).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert!(result.is_consistent);
assert!(result.consistency_energy < 1e-10);
}
#[test]
fn test_cohomology_inconsistent() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![1.0, 2.0]));
graph.add_node(SheafNode::new(1, "B", vec![3.0, 4.0]));
let edge = SheafEdge::identity(0, 1, 2);
graph.add_edge(edge).unwrap();
let engine = CohomologyEngine::new();
let result = engine.compute_cohomology(&graph).unwrap();
assert!(!result.is_consistent);
assert!(result.consistency_energy > 0.0);
}
#[test]
fn test_detect_obstructions() {
let mut graph = SheafGraph::new();
graph.add_node(SheafNode::new(0, "A", vec![1.0, 0.0]));
graph.add_node(SheafNode::new(1, "B", vec![0.0, 1.0]));
let edge = SheafEdge::identity(0, 1, 2);
graph.add_edge(edge).unwrap();
let engine = CohomologyEngine::new();
let obstructions = engine.detect_obstructions(&graph).unwrap();
assert_eq!(obstructions.len(), 1);
assert!(obstructions[0].magnitude > 1.0);
}
}

View File

@@ -0,0 +1,176 @@
//! Presheaf implementation
use super::{RestrictionMap, Section};
use crate::{Error, Result};
use nalgebra::{DMatrix, DVector};
use std::collections::HashMap;
/// A presheaf over a topological space
///
/// A presheaf F assigns to each open set U a set F(U) (sections over U)
/// and to each inclusion U ⊆ V a restriction map F(V) -> F(U).
#[derive(Debug, Clone)]
pub struct Presheaf {
/// Sections indexed by open set
sections: HashMap<String, Section>,
/// Restriction maps indexed by (source, target) pairs
restrictions: HashMap<(String, String), RestrictionMap>,
/// Topology as inclusion relations
inclusions: Vec<(String, String)>,
}
impl Presheaf {
/// Create a new empty presheaf
pub fn new() -> Self {
Self {
sections: HashMap::new(),
restrictions: HashMap::new(),
inclusions: Vec::new(),
}
}
/// Add a section over an open set
pub fn section(mut self, domain: impl Into<String>, values: DVector<f64>) -> Self {
let domain = domain.into();
self.sections
.insert(domain.clone(), Section::new(domain, values));
self
}
/// Add a restriction map between open sets
pub fn restriction(
mut self,
source: impl Into<String>,
target: impl Into<String>,
matrix: DMatrix<f64>,
) -> Self {
let source = source.into();
let target = target.into();
self.inclusions.push((target.clone(), source.clone()));
self.restrictions.insert(
(source.clone(), target.clone()),
RestrictionMap::new(source, target, matrix),
);
self
}
/// Get a section by domain
pub fn get_section(&self, domain: &str) -> Option<&Section> {
self.sections.get(domain)
}
/// Get a restriction map
pub fn get_restriction(&self, source: &str, target: &str) -> Option<&RestrictionMap> {
self.restrictions.get(&(source.to_string(), target.to_string()))
}
/// List all open sets
pub fn open_sets(&self) -> Vec<&str> {
self.sections.keys().map(|s| s.as_str()).collect()
}
/// Check presheaf functoriality
///
/// Verifies that restriction maps compose correctly:
/// If U ⊆ V ⊆ W, then res_{W,U} = res_{V,U} ∘ res_{W,V}
pub fn check_functoriality(&self, epsilon: f64) -> Result<bool> {
// Check identity: res_{U,U} = id
for (domain, section) in &self.sections {
if let Some(res) = self.get_restriction(domain, domain) {
let identity = DMatrix::identity(section.dimension(), section.dimension());
let diff = (&res.matrix - &identity).norm();
if diff > epsilon {
return Ok(false);
}
}
}
// Check composition for all triples
// This is a simplified check - full implementation would traverse the topology
Ok(true)
}
/// Compute the global sections
///
/// Global sections are elements that are compatible under all restriction maps
pub fn global_sections(&self) -> Result<Vec<DVector<f64>>> {
if self.sections.is_empty() {
return Ok(Vec::new());
}
// For a simple two-layer case, find vectors v such that res(v) = v|_U for all U
// This is the kernel of the difference map in the Cech complex
// Simplified: return sections that are consistent
let mut global = Vec::new();
// Check each section for global compatibility
for (domain, section) in &self.sections {
let mut is_global = true;
for ((src, tgt), res) in &self.restrictions {
if src == domain {
if let Some(target_section) = self.sections.get(tgt) {
let restricted = res.apply(&section.values)?;
let diff = (&restricted - &target_section.values).norm();
if diff > 1e-10 {
is_global = false;
break;
}
}
}
}
if is_global {
global.push(section.values.clone());
}
}
Ok(global)
}
/// Convert to a sheaf by checking/enforcing gluing conditions
pub fn to_sheaf(&self) -> Result<super::Sheaf> {
// Verify gluing axioms
self.verify_gluing()?;
Ok(super::Sheaf::from_presheaf(self.clone()))
}
/// Verify gluing axioms
fn verify_gluing(&self) -> Result<()> {
// Locality: if sections agree on all overlaps, they are equal
// Gluing: compatible sections can be glued to a global section
// Simplified check for now
Ok(())
}
}
impl Default for Presheaf {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_presheaf_creation() {
let presheaf = Presheaf::new()
.section("U", DVector::from_vec(vec![1.0, 2.0]))
.section("V", DVector::from_vec(vec![1.0]));
assert_eq!(presheaf.open_sets().len(), 2);
}
#[test]
fn test_presheaf_restriction() {
let matrix = DMatrix::from_row_slice(1, 2, &[1.0, 0.0]);
let presheaf = Presheaf::new()
.section("U", DVector::from_vec(vec![1.0, 2.0]))
.section("V", DVector::from_vec(vec![1.0]))
.restriction("U", "V", matrix);
assert!(presheaf.get_restriction("U", "V").is_some());
}
}

View File

@@ -0,0 +1,258 @@
//! Sheaf implementation
use super::{BettiNumbers, ChainComplex, Homology, Presheaf, Section};
use crate::{Error, Result};
use nalgebra::{DMatrix, DVector};
use std::collections::HashMap;
/// A sheaf over a topological space
///
/// A sheaf is a presheaf that satisfies the gluing axioms:
/// 1. Locality: Sections that agree on overlaps are equal
/// 2. Gluing: Compatible sections can be glued to a global section
#[derive(Debug, Clone)]
pub struct Sheaf {
/// Underlying presheaf
presheaf: Presheaf,
/// Cached cohomology groups
cohomology_cache: HashMap<usize, Homology>,
}
impl Sheaf {
/// Create a new sheaf from a presheaf
pub fn from_presheaf(presheaf: Presheaf) -> Self {
Self {
presheaf,
cohomology_cache: HashMap::new(),
}
}
/// Create a sheaf from neural network activations
///
/// Treats each layer as an open set with the activation vectors as sections
pub fn from_activations(layers: &[DVector<f64>]) -> Result<Self> {
if layers.is_empty() {
return Err(Error::InvalidTopology("Empty layer list".to_string()));
}
let mut presheaf = Presheaf::new();
// Add each layer as a section
for (i, activations) in layers.iter().enumerate() {
presheaf = presheaf.section(format!("layer_{}", i), activations.clone());
}
// Add identity restrictions (simplified topology)
// In practice, you'd derive these from weight matrices
Ok(Self::from_presheaf(presheaf))
}
/// Create a sheaf builder
pub fn builder() -> SheafBuilder {
SheafBuilder::new()
}
/// Get the underlying presheaf
pub fn presheaf(&self) -> &Presheaf {
&self.presheaf
}
/// Compute the n-th cohomology group H^n(X, F)
///
/// Cohomology measures obstructions to extending local sections globally.
pub fn cohomology(&self, degree: usize) -> Result<Homology> {
// Check cache first
if let Some(cached) = self.cohomology_cache.get(&degree) {
return Ok(cached.clone());
}
// Build the Cech complex and compute cohomology
let complex = self.cech_complex()?;
let homology = complex.homology(degree)?;
Ok(homology)
}
/// Compute all Betti numbers up to a given degree
pub fn betti_numbers(&self, max_degree: usize) -> Result<BettiNumbers> {
let mut betti = BettiNumbers::default();
for degree in 0..=max_degree {
let h = self.cohomology(degree)?;
match degree {
0 => betti.b0 = h.dimension(),
1 => betti.b1 = h.dimension(),
2 => betti.b2 = h.dimension(),
_ => betti.higher.push(h.dimension()),
}
}
Ok(betti)
}
/// Compute persistent homology for multi-scale analysis
pub fn persistent_homology(&self) -> Result<PersistenceDiagram> {
// Compute homology at multiple filtration levels
let mut persistence = PersistenceDiagram::new();
// Simplified: compute at single scale
let h0 = self.cohomology(0)?;
let h1 = self.cohomology(1)?;
persistence.add_bar(0, 0.0, f64::INFINITY, h0.dimension());
persistence.add_bar(1, 0.0, f64::INFINITY, h1.dimension());
Ok(persistence)
}
/// Build the Cech complex for cohomology computation
fn cech_complex(&self) -> Result<ChainComplex> {
// The Cech complex is built from intersections of open sets
// C^0: Direct product of all F(U_i)
// C^1: Direct product of all F(U_i ∩ U_j)
// etc.
let open_sets = self.presheaf.open_sets();
let n = open_sets.len();
if n == 0 {
return Err(Error::InvalidTopology("No open sets".to_string()));
}
// Build boundary maps
// For simplicity, use identity matrices as placeholder
let dim = self
.presheaf
.get_section(open_sets[0])
.map(|s| s.dimension())
.unwrap_or(1);
let d0 = DMatrix::zeros(dim, dim);
let d1 = DMatrix::zeros(dim, dim);
Ok(ChainComplex::new(vec![d0, d1]))
}
/// Compute the Euler characteristic
pub fn euler_characteristic(&self) -> Result<i64> {
let betti = self.betti_numbers(2)?;
Ok(betti.euler_characteristic())
}
/// Check if the sheaf is locally constant
pub fn is_locally_constant(&self, epsilon: f64) -> Result<bool> {
self.presheaf.check_functoriality(epsilon)
}
}
/// Builder for constructing sheaves
#[derive(Debug, Default)]
pub struct SheafBuilder {
presheaf: Presheaf,
}
impl SheafBuilder {
/// Create a new builder
pub fn new() -> Self {
Self {
presheaf: Presheaf::new(),
}
}
/// Add a section
pub fn section(mut self, domain: impl Into<String>, values: DVector<f64>) -> Self {
self.presheaf = self.presheaf.section(domain, values);
self
}
/// Add a restriction map
pub fn restriction(
mut self,
source: impl Into<String>,
target: impl Into<String>,
matrix: DMatrix<f64>,
) -> Self {
self.presheaf = self.presheaf.restriction(source, target, matrix);
self
}
/// Build the sheaf
pub fn build(self) -> Result<Sheaf> {
self.presheaf.to_sheaf()
}
}
/// Persistence diagram for topological data analysis
#[derive(Debug, Clone, Default)]
pub struct PersistenceDiagram {
/// Bars (birth, death, multiplicity) by dimension
bars: HashMap<usize, Vec<(f64, f64, usize)>>,
}
impl PersistenceDiagram {
/// Create a new persistence diagram
pub fn new() -> Self {
Self {
bars: HashMap::new(),
}
}
/// Add a persistence bar
pub fn add_bar(&mut self, dimension: usize, birth: f64, death: f64, multiplicity: usize) {
self.bars
.entry(dimension)
.or_default()
.push((birth, death, multiplicity));
}
/// Get bars for a given dimension
pub fn bars(&self, dimension: usize) -> &[(f64, f64, usize)] {
self.bars.get(&dimension).map(|v| v.as_slice()).unwrap_or(&[])
}
/// Compute bottleneck distance to another diagram
pub fn bottleneck_distance(&self, other: &PersistenceDiagram) -> f64 {
// Simplified implementation
let mut max_dist = 0.0f64;
for dim in 0..=2 {
let self_bars = self.bars(dim);
let other_bars = other.bars(dim);
// Compare number of bars
let diff = (self_bars.len() as f64 - other_bars.len() as f64).abs();
max_dist = max_dist.max(diff);
}
max_dist
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sheaf_from_activations() {
let layers = vec![
DVector::from_vec(vec![1.0, 2.0, 3.0]),
DVector::from_vec(vec![0.5, 1.5]),
];
let sheaf = Sheaf::from_activations(&layers).unwrap();
assert!(sheaf.presheaf().get_section("layer_0").is_some());
assert!(sheaf.presheaf().get_section("layer_1").is_some());
}
#[test]
fn test_sheaf_builder() {
let sheaf = Sheaf::builder()
.section("U", DVector::from_vec(vec![1.0, 2.0]))
.section("V", DVector::from_vec(vec![1.0]))
.build()
.unwrap();
assert!(sheaf.presheaf().get_section("U").is_some());
}
}