use super::edge::{EdgeId, SheafEdge};
use super::node::{NodeId, SheafNode};
use chrono::{DateTime, Utc};
use dashmap::DashMap;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use uuid::Uuid;
pub type Namespace = String;
pub type ScopeId = String;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CoherenceFingerprint {
pub structure_hash: u64,
pub state_hash: u64,
pub generation: u64,
}
impl CoherenceFingerprint {
pub fn new(structure_hash: u64, state_hash: u64, generation: u64) -> Self {
Self {
structure_hash,
state_hash,
generation,
}
}
pub fn combined(&self) -> u64 {
self.structure_hash
.wrapping_mul(31)
.wrapping_add(self.state_hash)
.wrapping_mul(31)
.wrapping_add(self.generation)
}
pub fn has_changed(&self, other: &Self) -> bool {
self.combined() != other.combined()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoherenceEnergy {
pub total_energy: f32,
pub edge_energies: HashMap<EdgeId, f32>,
pub scope_energies: HashMap<ScopeId, f32>,
pub edge_count: usize,
pub computed_at: DateTime<Utc>,
pub fingerprint: CoherenceFingerprint,
}
impl CoherenceEnergy {
pub fn empty() -> Self {
Self {
total_energy: 0.0,
edge_energies: HashMap::new(),
scope_energies: HashMap::new(),
edge_count: 0,
computed_at: Utc::now(),
fingerprint: CoherenceFingerprint::new(0, 0, 0),
}
}
pub fn scope_energy(&self, scope: &str) -> f32 {
self.scope_energies.get(scope).copied().unwrap_or(0.0)
}
pub fn top_edges(&self, n: usize) -> Vec<(EdgeId, f32)> {
let mut edges: Vec<_> = self.edge_energies.iter().map(|(&k, &v)| (k, v)).collect();
edges.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
edges.truncate(n);
edges
}
pub fn is_coherent(&self, threshold: f32) -> bool {
self.total_energy <= threshold
}
pub fn incoherent_edges(&self, threshold: f32) -> Vec<(EdgeId, f32)> {
self.edge_energies
.iter()
.filter(|(_, &e)| e > threshold)
.map(|(&k, &v)| (k, v))
.collect()
}
}
#[derive(Debug)]
pub struct IncrementalCoherence {
residual_norms: DashMap<EdgeId, f32>,
scope_summaries: DashMap<ScopeId, f32>,
fingerprint: RwLock<CoherenceFingerprint>,
dirty_edges: DashMap<EdgeId, ()>,
}
impl IncrementalCoherence {
pub fn new() -> Self {
Self {
residual_norms: DashMap::new(),
scope_summaries: DashMap::new(),
fingerprint: RwLock::new(CoherenceFingerprint::new(0, 0, 0)),
dirty_edges: DashMap::new(),
}
}
pub fn mark_dirty(&self, edge_id: EdgeId) {
self.dirty_edges.insert(edge_id, ());
}
pub fn mark_node_dirty(&self, graph: &SheafGraph, node_id: NodeId) {
for edge_id in graph.edges_incident_to(node_id) {
self.dirty_edges.insert(edge_id, ());
}
}
pub fn update_residual(&self, edge_id: EdgeId, norm_sq: f32) {
self.residual_norms.insert(edge_id, norm_sq);
self.dirty_edges.remove(&edge_id);
}
pub fn get_residual(&self, edge_id: &EdgeId) -> Option<f32> {
if self.dirty_edges.contains_key(edge_id) {
None
} else {
self.residual_norms.get(edge_id).map(|r| *r)
}
}
pub fn has_dirty_edges(&self) -> bool {
!self.dirty_edges.is_empty()
}
pub fn dirty_count(&self) -> usize {
self.dirty_edges.len()
}
pub fn clear_dirty(&self) {
self.dirty_edges.clear();
}
pub fn update_fingerprint(&self, fingerprint: CoherenceFingerprint) {
*self.fingerprint.write() = fingerprint;
}
pub fn fingerprint(&self) -> CoherenceFingerprint {
*self.fingerprint.read()
}
}
impl Default for IncrementalCoherence {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Default)]
struct AdjacencyIndex {
node_edges: DashMap<NodeId, HashSet<EdgeId>>,
}
impl AdjacencyIndex {
fn new() -> Self {
Self::default()
}
fn add_edge(&self, edge: &SheafEdge) {
self.node_edges
.entry(edge.source)
.or_insert_with(HashSet::new)
.insert(edge.id);
self.node_edges
.entry(edge.target)
.or_insert_with(HashSet::new)
.insert(edge.id);
}
fn remove_edge(&self, edge: &SheafEdge) {
if let Some(mut edges) = self.node_edges.get_mut(&edge.source) {
edges.remove(&edge.id);
}
if let Some(mut edges) = self.node_edges.get_mut(&edge.target) {
edges.remove(&edge.id);
}
}
fn edges_for_node(&self, node_id: NodeId) -> Vec<EdgeId> {
self.node_edges
.get(&node_id)
.map(|edges| edges.iter().copied().collect())
.unwrap_or_default()
}
fn remove_node(&self, node_id: NodeId) {
self.node_edges.remove(&node_id);
}
}
pub struct SheafGraph {
nodes: Arc<DashMap<NodeId, SheafNode>>,
edges: Arc<DashMap<EdgeId, SheafEdge>>,
adjacency: AdjacencyIndex,
namespaces: DashMap<Namespace, HashSet<NodeId>>,
generation: AtomicU64,
incremental: IncrementalCoherence,
default_namespace: String,
}
impl SheafGraph {
pub fn new() -> Self {
Self {
nodes: Arc::new(DashMap::new()),
edges: Arc::new(DashMap::new()),
adjacency: AdjacencyIndex::new(),
namespaces: DashMap::new(),
generation: AtomicU64::new(0),
incremental: IncrementalCoherence::new(),
default_namespace: "default".to_string(),
}
}
pub fn with_namespace(namespace: impl Into<String>) -> Self {
Self {
default_namespace: namespace.into(),
..Self::new()
}
}
pub fn add_node(&self, node: SheafNode) -> NodeId {
let id = node.id;
let namespace = node
.metadata
.namespace
.clone()
.unwrap_or_else(|| self.default_namespace.clone());
self.namespaces
.entry(namespace)
.or_insert_with(HashSet::new)
.insert(id);
self.nodes.insert(id, node);
self.increment_generation();
id
}
pub fn get_node(&self, id: NodeId) -> Option<SheafNode> {
self.nodes.get(&id).map(|n| n.clone())
}
#[inline]
pub fn get_node_ref(
&self,
id: NodeId,
) -> Option<dashmap::mapref::one::Ref<'_, NodeId, SheafNode>> {
self.nodes.get(&id)
}
#[inline]
pub fn with_node<R>(&self, id: NodeId, f: impl FnOnce(&SheafNode) -> R) -> Option<R> {
self.nodes.get(&id).map(|n| f(&n))
}
pub fn node_state(&self, id: NodeId) -> Option<Vec<f32>> {
self.nodes.get(&id).map(|n| n.state.as_slice().to_vec())
}
pub fn update_node_state(&self, id: NodeId, new_state: &[f32]) -> bool {
if let Some(mut node) = self.nodes.get_mut(&id) {
node.update_state_from_slice(new_state);
self.incremental.mark_node_dirty(self, id);
self.increment_generation();
true
} else {
false
}
}
pub fn remove_node(&self, id: NodeId) -> Option<SheafNode> {
let incident_edges = self.edges_incident_to(id);
for edge_id in incident_edges {
self.remove_edge(edge_id);
}
if let Some((_, node)) = self.nodes.remove(&id) {
let namespace = node
.metadata
.namespace
.clone()
.unwrap_or_else(|| self.default_namespace.clone());
if let Some(mut ns_nodes) = self.namespaces.get_mut(&namespace) {
ns_nodes.remove(&id);
}
self.adjacency.remove_node(id);
self.increment_generation();
Some(node)
} else {
None
}
}
pub fn has_node(&self, id: NodeId) -> bool {
self.nodes.contains_key(&id)
}
pub fn node_count(&self) -> usize {
self.nodes.len()
}
pub fn node_ids(&self) -> Vec<NodeId> {
self.nodes.iter().map(|r| *r.key()).collect()
}
pub fn nodes_in_namespace(&self, namespace: &str) -> Vec<NodeId> {
self.namespaces
.get(namespace)
.map(|ns| ns.iter().copied().collect())
.unwrap_or_default()
}
pub fn add_edge(&self, edge: SheafEdge) -> Result<EdgeId, &'static str> {
if !self.has_node(edge.source) {
return Err("Source node does not exist");
}
if !self.has_node(edge.target) {
return Err("Target node does not exist");
}
let id = edge.id;
self.adjacency.add_edge(&edge);
self.edges.insert(id, edge);
self.incremental.mark_dirty(id);
self.increment_generation();
Ok(id)
}
pub fn get_edge(&self, id: EdgeId) -> Option<SheafEdge> {
self.edges.get(&id).map(|e| e.clone())
}
pub fn remove_edge(&self, id: EdgeId) -> Option<SheafEdge> {
if let Some((_, edge)) = self.edges.remove(&id) {
self.adjacency.remove_edge(&edge);
self.incremental.residual_norms.remove(&id);
self.increment_generation();
Some(edge)
} else {
None
}
}
pub fn update_edge_weight(&self, id: EdgeId, weight: f32) -> bool {
if let Some(mut edge) = self.edges.get_mut(&id) {
edge.set_weight(weight);
self.incremental.mark_dirty(id);
self.increment_generation();
true
} else {
false
}
}
pub fn has_edge(&self, id: EdgeId) -> bool {
self.edges.contains_key(&id)
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn edge_ids(&self) -> Vec<EdgeId> {
self.edges.iter().map(|r| *r.key()).collect()
}
pub fn edges_incident_to(&self, node_id: NodeId) -> Vec<EdgeId> {
self.adjacency.edges_for_node(node_id)
}
pub fn compute_energy(&self) -> CoherenceEnergy {
let fingerprint = self.compute_fingerprint();
#[cfg(feature = "parallel")]
let edge_energies: HashMap<EdgeId, f32> = {
use rayon::prelude::*;
self.edges
.iter()
.par_bridge()
.filter_map(|entry| {
let edge = entry.value();
let source_state = self.nodes.get(&edge.source)?;
let target_state = self.nodes.get(&edge.target)?;
let energy = edge.weighted_residual_energy(
source_state.state.as_slice(),
target_state.state.as_slice(),
);
Some((*entry.key(), energy))
})
.collect()
};
#[cfg(not(feature = "parallel"))]
let edge_energies: HashMap<EdgeId, f32> = self
.edges
.iter()
.filter_map(|entry| {
let edge = entry.value();
let source_state = self.nodes.get(&edge.source)?;
let target_state = self.nodes.get(&edge.target)?;
let energy = edge.weighted_residual_energy(
source_state.state.as_slice(),
target_state.state.as_slice(),
);
Some((*entry.key(), energy))
})
.collect();
let total_energy: f32 = edge_energies.values().sum();
let scope_energies = self.aggregate_by_scope(&edge_energies);
for (&id, &energy) in &edge_energies {
self.incremental.update_residual(id, energy);
}
self.incremental.update_fingerprint(fingerprint);
CoherenceEnergy {
total_energy,
edge_energies,
scope_energies,
edge_count: self.edges.len(),
computed_at: Utc::now(),
fingerprint,
}
}
pub fn compute_energy_incremental(&self) -> CoherenceEnergy {
if !self.incremental.has_dirty_edges() {
let mut edge_energies = HashMap::new();
for entry in self.incremental.residual_norms.iter() {
edge_energies.insert(*entry.key(), *entry.value());
}
let total_energy: f32 = edge_energies.values().sum();
let scope_energies = self.aggregate_by_scope(&edge_energies);
return CoherenceEnergy {
total_energy,
edge_energies,
scope_energies,
edge_count: self.edges.len(),
computed_at: Utc::now(),
fingerprint: self.incremental.fingerprint(),
};
}
let dirty_ids: Vec<EdgeId> = self
.incremental
.dirty_edges
.iter()
.map(|r| *r.key())
.collect();
for edge_id in dirty_ids {
if let Some(edge) = self.edges.get(&edge_id) {
if let (Some(source), Some(target)) =
(self.nodes.get(&edge.source), self.nodes.get(&edge.target))
{
let energy = edge
.weighted_residual_energy(source.state.as_slice(), target.state.as_slice());
self.incremental.update_residual(edge_id, energy);
}
}
}
let mut edge_energies = HashMap::new();
for entry in self.incremental.residual_norms.iter() {
edge_energies.insert(*entry.key(), *entry.value());
}
let total_energy: f32 = edge_energies.values().sum();
let scope_energies = self.aggregate_by_scope(&edge_energies);
let fingerprint = self.compute_fingerprint();
self.incremental.update_fingerprint(fingerprint);
CoherenceEnergy {
total_energy,
edge_energies,
scope_energies,
edge_count: self.edges.len(),
computed_at: Utc::now(),
fingerprint,
}
}
pub fn compute_local_energy(&self, node_id: NodeId) -> f32 {
let incident_edges = self.edges_incident_to(node_id);
let mut total = 0.0;
for edge_id in incident_edges {
if let Some(edge) = self.edges.get(&edge_id) {
if let (Some(source), Some(target)) =
(self.nodes.get(&edge.source), self.nodes.get(&edge.target))
{
total += edge
.weighted_residual_energy(source.state.as_slice(), target.state.as_slice());
}
}
}
total
}
fn aggregate_by_scope(&self, edge_energies: &HashMap<EdgeId, f32>) -> HashMap<ScopeId, f32> {
let mut scope_energies: HashMap<ScopeId, f32> = HashMap::new();
for (&edge_id, &energy) in edge_energies {
if let Some(edge) = self.edges.get(&edge_id) {
let scope = edge
.namespace
.clone()
.unwrap_or_else(|| self.default_namespace.clone());
*scope_energies.entry(scope).or_insert(0.0) += energy;
}
}
scope_energies
}
pub fn compute_fingerprint(&self) -> CoherenceFingerprint {
use std::hash::{Hash, Hasher};
let mut structure_hasher = std::collections::hash_map::DefaultHasher::new();
let mut state_hasher = std::collections::hash_map::DefaultHasher::new();
let mut node_ids: Vec<_> = self.nodes.iter().map(|r| *r.key()).collect();
node_ids.sort();
for id in &node_ids {
id.hash(&mut structure_hasher);
}
let mut edge_ids: Vec<_> = self.edges.iter().map(|r| *r.key()).collect();
edge_ids.sort();
for id in &edge_ids {
id.hash(&mut structure_hasher);
if let Some(edge) = self.edges.get(id) {
edge.source.hash(&mut structure_hasher);
edge.target.hash(&mut structure_hasher);
}
}
for id in &node_ids {
if let Some(node) = self.nodes.get(id) {
state_hasher.write_u64(node.state.content_hash());
state_hasher.write_u64(node.version);
}
}
CoherenceFingerprint {
structure_hash: structure_hasher.finish(),
state_hash: state_hasher.finish(),
generation: self.generation.load(Ordering::SeqCst),
}
}
pub fn has_changed_since(&self, fingerprint: &CoherenceFingerprint) -> bool {
self.generation.load(Ordering::SeqCst) != fingerprint.generation
|| self.compute_fingerprint().has_changed(fingerprint)
}
pub fn generation(&self) -> u64 {
self.generation.load(Ordering::SeqCst)
}
fn increment_generation(&self) {
self.generation.fetch_add(1, Ordering::SeqCst);
}
pub fn stats(&self) -> GraphStats {
let node_count = self.nodes.len();
let edge_count = self.edges.len();
let mut total_degree = 0usize;
let mut max_degree = 0usize;
for entry in self.adjacency.node_edges.iter() {
let degree = entry.value().len();
total_degree += degree;
max_degree = max_degree.max(degree);
}
let avg_degree = if node_count > 0 {
total_degree as f64 / node_count as f64
} else {
0.0
};
GraphStats {
node_count,
edge_count,
namespace_count: self.namespaces.len(),
avg_degree,
max_degree,
dirty_edges: self.incremental.dirty_count(),
generation: self.generation(),
}
}
}
impl Default for SheafGraph {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphStats {
pub node_count: usize,
pub edge_count: usize,
pub namespace_count: usize,
pub avg_degree: f64,
pub max_degree: usize,
pub dirty_edges: usize,
pub generation: u64,
}
pub struct SheafGraphBuilder {
graph: SheafGraph,
}
impl SheafGraphBuilder {
pub fn new() -> Self {
Self {
graph: SheafGraph::new(),
}
}
pub fn default_namespace(mut self, namespace: impl Into<String>) -> Self {
self.graph.default_namespace = namespace.into();
self
}
pub fn node(self, node: SheafNode) -> Self {
self.graph.add_node(node);
self
}
pub fn nodes(self, nodes: impl IntoIterator<Item = SheafNode>) -> Self {
for node in nodes {
self.graph.add_node(node);
}
self
}
pub fn edge(self, edge: SheafEdge) -> Self {
self.graph.add_edge(edge).expect("Failed to add edge");
self
}
pub fn edges(self, edges: impl IntoIterator<Item = SheafEdge>) -> Self {
for edge in edges {
self.graph.add_edge(edge).expect("Failed to add edge");
}
self
}
pub fn build(self) -> SheafGraph {
self.graph
}
}
impl Default for SheafGraphBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::substrate::edge::SheafEdgeBuilder;
use crate::substrate::node::{SheafNodeBuilder, StateVector};
use crate::substrate::restriction::RestrictionMap;
fn make_test_graph() -> SheafGraph {
let graph = SheafGraph::new();
let node1 = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 0.0, 0.0])
.namespace("test")
.build();
let node2 = SheafNodeBuilder::new()
.state_from_slice(&[0.0, 1.0, 0.0])
.namespace("test")
.build();
let node3 = SheafNodeBuilder::new()
.state_from_slice(&[0.0, 0.0, 1.0])
.namespace("test")
.build();
let id1 = graph.add_node(node1);
let id2 = graph.add_node(node2);
let id3 = graph.add_node(node3);
let edge12 = SheafEdgeBuilder::new(id1, id2)
.identity_restrictions(3)
.namespace("test")
.build();
let edge23 = SheafEdgeBuilder::new(id2, id3)
.identity_restrictions(3)
.namespace("test")
.build();
let edge31 = SheafEdgeBuilder::new(id3, id1)
.identity_restrictions(3)
.namespace("test")
.build();
graph.add_edge(edge12).unwrap();
graph.add_edge(edge23).unwrap();
graph.add_edge(edge31).unwrap();
graph
}
#[test]
fn test_graph_creation() {
let graph = SheafGraph::new();
assert_eq!(graph.node_count(), 0);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_add_node() {
let graph = SheafGraph::new();
let node = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 2.0, 3.0])
.build();
let id = graph.add_node(node);
assert!(graph.has_node(id));
assert_eq!(graph.node_count(), 1);
}
#[test]
fn test_add_edge() {
let graph = SheafGraph::new();
let node1 = SheafNodeBuilder::new().state_from_slice(&[1.0]).build();
let node2 = SheafNodeBuilder::new().state_from_slice(&[2.0]).build();
let id1 = graph.add_node(node1);
let id2 = graph.add_node(node2);
let edge = SheafEdgeBuilder::new(id1, id2)
.identity_restrictions(1)
.build();
let edge_id = graph.add_edge(edge).unwrap();
assert!(graph.has_edge(edge_id));
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_edge_without_nodes_fails() {
let graph = SheafGraph::new();
let fake_id = Uuid::new_v4();
let edge = SheafEdgeBuilder::new(fake_id, fake_id)
.identity_restrictions(1)
.build();
let result = graph.add_edge(edge);
assert!(result.is_err());
}
#[test]
fn test_remove_node() {
let graph = make_test_graph();
let node_ids = graph.node_ids();
let removed = graph.remove_node(node_ids[0]);
assert!(removed.is_some());
assert!(!graph.has_node(node_ids[0]));
assert_eq!(graph.node_count(), 2);
assert!(graph.edge_count() < 3);
}
#[test]
fn test_update_node_state() {
let graph = SheafGraph::new();
let node = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 2.0])
.build();
let id = graph.add_node(node);
assert!(graph.update_node_state(id, &[3.0, 4.0]));
let state = graph.node_state(id).unwrap();
assert_eq!(state, vec![3.0, 4.0]);
}
#[test]
fn test_compute_energy() {
let graph = make_test_graph();
let energy = graph.compute_energy();
assert!(energy.total_energy > 0.0);
assert_eq!(energy.edge_count, 3);
assert_eq!(energy.edge_energies.len(), 3);
}
#[test]
fn test_coherent_graph() {
let graph = SheafGraph::new();
let node1 = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 1.0, 1.0])
.build();
let node2 = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 1.0, 1.0])
.build();
let id1 = graph.add_node(node1);
let id2 = graph.add_node(node2);
let edge = SheafEdgeBuilder::new(id1, id2)
.identity_restrictions(3)
.build();
graph.add_edge(edge).unwrap();
let energy = graph.compute_energy();
assert!(energy.total_energy < 1e-10);
assert!(energy.is_coherent(0.01));
}
#[test]
fn test_incremental_energy() {
let graph = make_test_graph();
let energy1 = graph.compute_energy();
let energy2 = graph.compute_energy_incremental();
assert!((energy1.total_energy - energy2.total_energy).abs() < 1e-10);
let node_ids = graph.node_ids();
graph.update_node_state(node_ids[0], &[0.5, 0.5, 0.0]);
assert!(graph.incremental.has_dirty_edges());
let energy3 = graph.compute_energy_incremental();
let energy4 = graph.compute_energy_incremental();
assert!((energy3.total_energy - energy4.total_energy).abs() < 1e-10);
assert!(energy3.edge_energies.len() == energy1.edge_energies.len());
}
#[test]
fn test_local_energy() {
let graph = make_test_graph();
let node_ids = graph.node_ids();
let local_energy = graph.compute_local_energy(node_ids[0]);
assert!(local_energy > 0.0);
let total = graph.compute_energy().total_energy;
assert!(local_energy <= total);
}
#[test]
fn test_fingerprint() {
let graph = make_test_graph();
let fp1 = graph.compute_fingerprint();
let fp2 = graph.compute_fingerprint();
assert_eq!(fp1.combined(), fp2.combined());
let node_ids = graph.node_ids();
graph.update_node_state(node_ids[0], &[2.0, 0.0, 0.0]);
let fp3 = graph.compute_fingerprint();
assert!(fp1.has_changed(&fp3));
}
#[test]
fn test_edges_incident_to() {
let graph = make_test_graph();
let node_ids = graph.node_ids();
let edges = graph.edges_incident_to(node_ids[0]);
assert_eq!(edges.len(), 2);
}
#[test]
fn test_namespaces() {
let graph = SheafGraph::new();
let node1 = SheafNodeBuilder::new()
.state_from_slice(&[1.0])
.namespace("ns1")
.build();
let node2 = SheafNodeBuilder::new()
.state_from_slice(&[2.0])
.namespace("ns2")
.build();
graph.add_node(node1);
graph.add_node(node2);
assert_eq!(graph.nodes_in_namespace("ns1").len(), 1);
assert_eq!(graph.nodes_in_namespace("ns2").len(), 1);
}
#[test]
fn test_builder() {
let node1 = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 2.0])
.build();
let node2 = SheafNodeBuilder::new()
.state_from_slice(&[1.0, 2.0])
.build();
let id1 = node1.id;
let id2 = node2.id;
let edge = SheafEdgeBuilder::new(id1, id2)
.identity_restrictions(2)
.build();
let graph = SheafGraphBuilder::new()
.default_namespace("test")
.node(node1)
.node(node2)
.edge(edge)
.build();
assert_eq!(graph.node_count(), 2);
assert_eq!(graph.edge_count(), 1);
}
#[test]
fn test_graph_stats() {
let graph = make_test_graph();
let stats = graph.stats();
assert_eq!(stats.node_count, 3);
assert_eq!(stats.edge_count, 3);
assert!((stats.avg_degree - 2.0).abs() < 0.01); assert_eq!(stats.max_degree, 2);
}
#[test]
fn test_scope_energies() {
let graph = SheafGraph::new();
let node1 = SheafNodeBuilder::new()
.state_from_slice(&[1.0])
.namespace("scope_a")
.build();
let node2 = SheafNodeBuilder::new()
.state_from_slice(&[2.0])
.namespace("scope_a")
.build();
let node3 = SheafNodeBuilder::new()
.state_from_slice(&[3.0])
.namespace("scope_b")
.build();
let id1 = graph.add_node(node1);
let id2 = graph.add_node(node2);
let id3 = graph.add_node(node3);
let edge1 = SheafEdgeBuilder::new(id1, id2)
.identity_restrictions(1)
.namespace("scope_a")
.build();
let edge2 = SheafEdgeBuilder::new(id2, id3)
.identity_restrictions(1)
.namespace("scope_b")
.build();
graph.add_edge(edge1).unwrap();
graph.add_edge(edge2).unwrap();
let energy = graph.compute_energy();
assert!(energy.scope_energies.contains_key("scope_a"));
assert!(energy.scope_energies.contains_key("scope_b"));
}
}