feat: vendor midstream and sublinear-time-solver libraries (#109)

Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.
This commit is contained in:
rUv
2026-03-02 23:34:05 -05:00
committed by GitHub
parent 14902e6b4e
commit 407b46b206
1600 changed files with 1852646 additions and 0 deletions
+325
View File
@@ -0,0 +1,325 @@
//! Adjacency list representation for graphs
//!
//! Optimized for push algorithms with efficient neighbor iteration
//! and degree lookups.
use super::{Graph, NodeId, Weight, CompressedSparseRow};
use std::collections::HashMap;
/// Adjacency list representation of a graph
#[derive(Debug, Clone)]
pub struct AdjacencyList {
/// Number of nodes in the graph
num_nodes: usize,
/// Adjacency lists for each node
adjacency: Vec<Vec<(NodeId, Weight)>>,
/// Reverse adjacency lists (for backward operations)
reverse_adjacency: Vec<Vec<(NodeId, Weight)>>,
/// Cached degrees for each node
degrees: Vec<Weight>,
/// Cached reverse degrees for each node
reverse_degrees: Vec<Weight>,
}
impl AdjacencyList {
/// Create a new adjacency list with given number of nodes
pub fn new(num_nodes: usize) -> Self {
Self {
num_nodes,
adjacency: vec![Vec::new(); num_nodes],
reverse_adjacency: vec![Vec::new(); num_nodes],
degrees: vec![0.0; num_nodes],
reverse_degrees: vec![0.0; num_nodes],
}
}
/// Add an edge to the graph
pub fn add_edge(&mut self, from: NodeId, to: NodeId, weight: Weight) {
if from < self.num_nodes && to < self.num_nodes {
self.adjacency[from].push((to, weight));
self.reverse_adjacency[to].push((from, weight));
self.degrees[from] += weight;
self.reverse_degrees[to] += weight;
}
}
/// Create from a sparse matrix representation
pub fn from_csr(csr: &CompressedSparseRow) -> Self {
let mut graph = Self::new(csr.nrows);
for row in 0..csr.nrows {
for (col, value) in csr.row(row) {
if col < csr.ncols {
graph.adjacency[row].push((col, value));
graph.reverse_adjacency[col].push((row, value));
graph.degrees[row] += value;
graph.reverse_degrees[col] += value;
}
}
}
graph
}
/// Convert to compressed sparse row format
pub fn to_csr(&self) -> CompressedSparseRow {
let mut row_ptr = vec![0; self.num_nodes + 1];
let mut col_indices = Vec::new();
let mut values = Vec::new();
for (row, neighbors) in self.adjacency.iter().enumerate() {
row_ptr[row + 1] = row_ptr[row] + neighbors.len();
for &(col, weight) in neighbors {
col_indices.push(col);
values.push(weight);
}
}
CompressedSparseRow {
nrows: self.num_nodes,
ncols: self.num_nodes,
row_ptr,
col_indices,
values,
}
}
/// Get reverse neighbors (for backward push)
pub fn reverse_neighbors(&self, node: NodeId) -> &[(NodeId, Weight)] {
if node < self.num_nodes {
&self.reverse_adjacency[node]
} else {
&[]
}
}
/// Get the reverse degree of a node
pub fn reverse_degree(&self, node: NodeId) -> Weight {
if node < self.num_nodes {
self.reverse_degrees[node]
} else {
0.0
}
}
/// Normalize the graph to make it a proper transition matrix
pub fn normalize(&mut self) {
for node in 0..self.num_nodes {
let degree = self.degrees[node];
if degree > 0.0 {
for (_, weight) in &mut self.adjacency[node] {
*weight /= degree;
}
}
}
// Recompute degrees after normalization
self.degrees.fill(1.0);
// Also normalize reverse adjacency
for node in 0..self.num_nodes {
let reverse_degree = self.reverse_degrees[node];
if reverse_degree > 0.0 {
for (_, weight) in &mut self.reverse_adjacency[node] {
*weight /= reverse_degree;
}
}
}
self.reverse_degrees.fill(1.0);
}
/// Get the sparsity of the graph (fraction of possible edges that exist)
pub fn sparsity(&self) -> f64 {
let total_edges: usize = self.adjacency.iter().map(|adj| adj.len()).sum();
let max_edges = self.num_nodes * self.num_nodes;
1.0 - (total_edges as f64 / max_edges as f64)
}
/// Check if the graph is strongly connected (simplified check)
pub fn is_strongly_connected(&self) -> bool {
// Simple check: every node has at least one outgoing and incoming edge
for node in 0..self.num_nodes {
if self.adjacency[node].is_empty() || self.reverse_adjacency[node].is_empty() {
return false;
}
}
true
}
}
impl Graph for AdjacencyList {
fn num_nodes(&self) -> usize {
self.num_nodes
}
fn num_edges(&self) -> usize {
self.adjacency.iter().map(|adj| adj.len()).sum()
}
fn neighbors(&self, node: NodeId) -> Vec<(NodeId, Weight)> {
if node < self.num_nodes {
self.adjacency[node].clone()
} else {
Vec::new()
}
}
fn degree(&self, node: NodeId) -> Weight {
if node < self.num_nodes {
self.degrees[node]
} else {
0.0
}
}
fn has_edge(&self, from: NodeId, to: NodeId) -> bool {
if from < self.num_nodes {
self.adjacency[from].iter().any(|&(neighbor, _)| neighbor == to)
} else {
false
}
}
fn edge_weight(&self, from: NodeId, to: NodeId) -> Option<Weight> {
if from < self.num_nodes {
self.adjacency[from]
.iter()
.find(|&&(neighbor, _)| neighbor == to)
.map(|&(_, weight)| weight)
} else {
None
}
}
}
/// Specialized graph structure optimized for push algorithms
#[derive(Debug, Clone)]
pub struct PushGraph {
/// Forward adjacency representation
pub adjacency: CompressedSparseRow,
/// Reverse adjacency for backward push
pub reverse_adjacency: CompressedSparseRow,
/// Node degrees (row sums)
pub degrees: Vec<f64>,
/// Reverse degrees (column sums)
pub reverse_degrees: Vec<f64>,
}
impl PushGraph {
/// Create a push graph from a sparse matrix
pub fn from_matrix(matrix: &CompressedSparseRow) -> Self {
let adjacency = matrix.clone();
let reverse_adjacency = matrix.transpose();
let degrees = adjacency.row_sums();
let reverse_degrees = reverse_adjacency.row_sums();
Self {
adjacency,
reverse_adjacency,
degrees,
reverse_degrees,
}
}
/// Create a push graph from edge list
pub fn from_edges(num_nodes: usize, edges: &[(usize, usize, f64)]) -> Self {
let mut adjacency_list = AdjacencyList::new(num_nodes);
for &(from, to, weight) in edges {
if from < num_nodes && to < num_nodes {
adjacency_list.add_edge(from, to, weight);
}
}
let matrix = adjacency_list.to_csr();
Self::from_matrix(&matrix)
}
/// Get the number of nodes
pub fn num_nodes(&self) -> usize {
self.adjacency.nrows
}
/// Get the number of edges
pub fn num_edges(&self) -> usize {
self.adjacency.nnz()
}
/// Get forward neighbors with weights
pub fn forward_neighbors(&self, node: usize) -> impl Iterator<Item = (usize, f64)> + '_ {
self.adjacency.row(node)
}
/// Get backward neighbors with weights
pub fn backward_neighbors(&self, node: usize) -> impl Iterator<Item = (usize, f64)> + '_ {
self.reverse_adjacency.row(node)
}
/// Get the out-degree of a node
pub fn out_degree(&self, node: usize) -> f64 {
if node < self.degrees.len() {
self.degrees[node]
} else {
0.0
}
}
/// Get the in-degree of a node
pub fn in_degree(&self, node: usize) -> f64 {
if node < self.reverse_degrees.len() {
self.reverse_degrees[node]
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adjacency_list_creation() {
let mut graph = AdjacencyList::new(3);
graph.add_edge(0, 1, 0.5);
graph.add_edge(1, 2, 1.0);
graph.add_edge(2, 0, 0.3);
assert_eq!(graph.num_nodes(), 3);
assert_eq!(graph.num_edges(), 3);
assert_eq!(graph.degree(0), 0.5);
assert_eq!(graph.degree(1), 1.0);
assert_eq!(graph.degree(2), 0.3);
}
#[test]
fn test_csr_conversion() {
let mut graph = AdjacencyList::new(2);
graph.add_edge(0, 1, 1.0);
graph.add_edge(1, 0, 0.5);
let csr = graph.to_csr();
assert_eq!(csr.nrows, 2);
assert_eq!(csr.ncols, 2);
assert_eq!(csr.nnz(), 2);
let reconstructed = AdjacencyList::from_csr(&csr);
assert_eq!(reconstructed.degree(0), 1.0);
assert_eq!(reconstructed.degree(1), 0.5);
}
#[test]
fn test_push_graph() {
let mut csr = CompressedSparseRow::new(3, 3);
csr.row_ptr = vec![0, 1, 2, 3];
csr.col_indices = vec![1, 2, 0];
csr.values = vec![1.0, 0.5, 0.8];
let push_graph = PushGraph::from_matrix(&csr);
assert_eq!(push_graph.num_nodes(), 3);
assert_eq!(push_graph.out_degree(0), 1.0);
assert_eq!(push_graph.in_degree(0), 0.8);
}
}
+288
View File
@@ -0,0 +1,288 @@
//! Graph representations and algorithms for the sublinear time solver
//!
//! This module provides efficient graph data structures optimized for
//! push-based algorithms and random walk computations.
pub mod adjacency;
use std::collections::HashMap;
use bit_set::BitSet;
/// A node identifier in the graph
pub type NodeId = usize;
/// Weight type for edges
pub type Weight = f64;
/// Graph trait for different implementations
pub trait Graph {
/// Number of nodes in the graph
fn num_nodes(&self) -> usize;
/// Number of edges in the graph
fn num_edges(&self) -> usize;
/// Get neighbors of a node with their weights
fn neighbors(&self, node: NodeId) -> Vec<(NodeId, Weight)>;
/// Get the degree of a node (sum of edge weights)
fn degree(&self, node: NodeId) -> Weight;
/// Check if an edge exists between two nodes
fn has_edge(&self, from: NodeId, to: NodeId) -> bool;
/// Get the weight of an edge if it exists
fn edge_weight(&self, from: NodeId, to: NodeId) -> Option<Weight>;
}
/// Sparse matrix representation using Compressed Sparse Row format
#[derive(Debug, Clone)]
pub struct CompressedSparseRow {
/// Number of rows in the matrix
pub nrows: usize,
/// Number of columns in the matrix
pub ncols: usize,
/// Row pointers (length = nrows + 1)
pub row_ptr: Vec<usize>,
/// Column indices of non-zero elements
pub col_indices: Vec<usize>,
/// Values of non-zero elements
pub values: Vec<f64>,
}
impl CompressedSparseRow {
/// Create a new empty CSR matrix
pub fn new(nrows: usize, ncols: usize) -> Self {
Self {
nrows,
ncols,
row_ptr: vec![0; nrows + 1],
col_indices: Vec::new(),
values: Vec::new(),
}
}
/// Get the number of non-zero elements
pub fn nnz(&self) -> usize {
self.values.len()
}
/// Get neighbors of a row with their values
pub fn row(&self, row: usize) -> impl Iterator<Item = (usize, f64)> + '_ {
let start = self.row_ptr[row];
let end = self.row_ptr[row + 1];
(start..end).map(move |idx| {
(self.col_indices[idx], self.values[idx])
})
}
/// Compute row sums (degrees for graphs)
pub fn row_sums(&self) -> Vec<f64> {
let mut sums = vec![0.0; self.nrows];
for row in 0..self.nrows {
for (_, value) in self.row(row) {
sums[row] += value;
}
}
sums
}
/// Transpose the matrix
pub fn transpose(&self) -> Self {
let mut col_counts = vec![0; self.ncols];
// Count elements per column
for &col in &self.col_indices {
col_counts[col] += 1;
}
// Build row pointers for transposed matrix
let mut new_row_ptr = vec![0; self.ncols + 1];
for i in 0..self.ncols {
new_row_ptr[i + 1] = new_row_ptr[i] + col_counts[i];
}
let mut new_col_indices = vec![0; self.nnz()];
let mut new_values = vec![0.0; self.nnz()];
let mut col_positions = new_row_ptr.clone();
// Fill transposed matrix
for row in 0..self.nrows {
for (col, value) in self.row(row) {
let pos = col_positions[col];
new_col_indices[pos] = row;
new_values[pos] = value;
col_positions[col] += 1;
}
}
Self {
nrows: self.ncols,
ncols: self.nrows,
row_ptr: new_row_ptr,
col_indices: new_col_indices,
values: new_values,
}
}
}
/// Work queue for push algorithms with priority-based processing
#[derive(Debug)]
pub struct WorkQueue {
/// Binary heap for priority queue
heap: std::collections::BinaryHeap<WorkItem>,
/// Bit set to track which nodes are in the queue
in_queue: BitSet,
/// Threshold for adding items to queue
threshold: f64,
}
#[derive(Debug, PartialEq, PartialOrd)]
struct WorkItem {
/// Priority value (higher = more important)
priority: OrderedFloat,
/// Node identifier
node_id: usize,
}
/// Wrapper for f64 to enable ordering
#[derive(Debug, PartialEq, PartialOrd)]
struct OrderedFloat(f64);
impl Eq for OrderedFloat {}
impl Ord for OrderedFloat {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other).unwrap_or(std::cmp::Ordering::Equal)
}
}
impl WorkQueue {
/// Create a new work queue with given threshold
pub fn new(threshold: f64) -> Self {
Self {
heap: std::collections::BinaryHeap::new(),
in_queue: BitSet::new(),
threshold,
}
}
/// Push a node if it meets the threshold
pub fn push_if_threshold(&mut self, node: usize, residual: f64, degree: f64) {
let priority = if degree > 0.0 { residual / degree } else { residual };
if priority >= self.threshold && !self.in_queue.contains(node) {
self.heap.push(WorkItem {
priority: OrderedFloat(priority),
node_id: node,
});
self.in_queue.insert(node);
}
}
/// Pop the highest priority item
pub fn pop(&mut self) -> Option<(usize, f64)> {
if let Some(item) = self.heap.pop() {
self.in_queue.remove(item.node_id);
Some((item.node_id, item.priority.0))
} else {
None
}
}
/// Check if queue is empty
pub fn is_empty(&self) -> bool {
self.heap.is_empty()
}
/// Get current queue size
pub fn len(&self) -> usize {
self.heap.len()
}
/// Adaptively adjust threshold based on queue size
pub fn adaptive_threshold(&mut self, max_queue_size: usize, min_queue_size: usize) {
let current_size = self.len();
if current_size > max_queue_size {
self.threshold *= 1.1; // Increase threshold to reduce queue size
} else if current_size < min_queue_size && self.threshold > 1e-12 {
self.threshold *= 0.9; // Decrease threshold to increase queue size
}
}
}
/// Tracker for visited nodes during push operations
#[derive(Debug)]
pub struct VisitedTracker {
/// Bit set for fast membership testing
visited: BitSet,
/// Order in which nodes were visited
visit_order: Vec<usize>,
/// Timestamps for each node
timestamps: Vec<u32>,
/// Current timestamp
current_time: u32,
}
impl VisitedTracker {
/// Create a new visited tracker for n nodes
pub fn new(n: usize) -> Self {
Self {
visited: BitSet::new(),
visit_order: Vec::new(),
timestamps: vec![0; n],
current_time: 1,
}
}
/// Mark a node as visited, returns true if newly visited
pub fn mark_visited(&mut self, node: usize) -> bool {
if !self.visited.contains(node) {
self.visited.insert(node);
self.visit_order.push(node);
if node < self.timestamps.len() {
self.timestamps[node] = self.current_time;
}
true
} else {
false
}
}
/// Check if a node has been visited
pub fn is_visited(&self, node: usize) -> bool {
self.visited.contains(node)
}
/// Get all visited nodes in order
pub fn visited_nodes(&self) -> &[usize] {
&self.visit_order
}
/// Reset for a new query (incremental timestamp)
pub fn reset_for_new_query(&mut self) {
self.current_time += 1;
self.visit_order.clear();
// Full reset if timestamp overflow
if self.current_time == u32::MAX {
self.full_reset();
}
}
/// Complete reset of all tracking data
fn full_reset(&mut self) {
self.visited.clear();
self.visit_order.clear();
self.timestamps.fill(0);
self.current_time = 1;
}
/// Get number of visited nodes
pub fn num_visited(&self) -> usize {
self.visit_order.len()
}
}
pub use adjacency::AdjacencyList;