Files
wifi-densepose/docs/research/sublinear-time-solver/ddd/integration-patterns.md
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

659 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sublinear-Time Solver: DDD Integration Patterns
**Version**: 1.0
**Date**: 2026-02-20
**Status**: Proposed
---
## 1. Anti-Corruption Layers
Anti-Corruption Layers (ACLs) translate between the Solver Core bounded context and each consuming bounded context, preventing domain model leakage.
### 1.1 Solver-to-Coherence ACL
Translates between Prime Radiant's sheaf graph types and the solver's sparse matrix types.
```rust
/// ACL: Coherence Engine ←→ Solver Core
pub struct CoherenceSolverAdapter {
solver: Arc<dyn SparseLaplacianSolver>,
cache: DashMap<u64, SolverResult>, // Keyed on graph version hash
}
impl CoherenceSolverAdapter {
/// Convert SheafGraph to CsrMatrix for solver input
pub fn sheaf_to_csr(graph: &SheafGraph) -> CsrMatrix<f32> {
let n = graph.node_count();
let mut row_ptrs = Vec::with_capacity(n + 1);
let mut col_indices = Vec::new();
let mut values = Vec::new();
row_ptrs.push(0u32);
for node_id in 0..n {
let edges = graph.edges_from(node_id);
let degree: f32 = edges.iter().map(|e| e.weight).sum();
// Laplacian: L = D - A
// Add diagonal (degree)
col_indices.push(node_id as u32);
values.push(degree);
// Add off-diagonal (-weight)
for edge in &edges {
col_indices.push(edge.target as u32);
values.push(-edge.weight);
}
row_ptrs.push(col_indices.len() as u32);
}
CsrMatrix { values: values.into(), col_indices: col_indices.into(), row_ptrs, rows: n, cols: n }
}
/// Convert solver result back to coherence energy
pub fn solution_to_energy(
solution: &SolverResult,
graph: &SheafGraph,
) -> CoherenceEnergy {
// Residual vector r = L*x represents per-edge contradiction
let residual_norm = solution.convergence.final_residual;
// Energy = sum of squared edge residuals
let energy = residual_norm * residual_norm;
// Per-node energy distribution
let node_energies: Vec<f64> = solution.solution.iter()
.map(|&x| (x as f64) * (x as f64))
.collect();
CoherenceEnergy {
global_energy: energy,
node_energies,
solver_algorithm: solution.algorithm_used,
solver_iterations: solution.iterations,
accuracy_bound: solution.error_bounds.relative_error,
}
}
/// Cached solve: reuse result if graph hasn't changed
pub async fn solve_coherence(
&self,
graph: &SheafGraph,
signal: &[f32],
) -> Result<CoherenceEnergy, SolverError> {
let graph_hash = graph.content_hash();
if let Some(cached) = self.cache.get(&graph_hash) {
return Ok(Self::solution_to_energy(&cached, graph));
}
let csr = Self::sheaf_to_csr(graph);
let system = SparseSystem::new(csr, signal.to_vec());
let result = self.solver.solve(&system)?;
self.cache.insert(graph_hash, result.clone());
Ok(Self::solution_to_energy(&result, graph))
}
}
```
### 1.2 Solver-to-GNN ACL
Translates between GNN message passing and sparse system solves.
```rust
/// ACL: GNN ←→ Solver Core
pub struct GnnSolverAdapter {
solver: Arc<dyn SolverEngine>,
}
impl GnnSolverAdapter {
/// Sublinear message aggregation using sparse solver
/// Replaces: O(n × avg_degree) per layer
/// With: O(nnz × log(1/ε)) per layer
pub fn sublinear_aggregate(
&self,
adjacency: &CsrMatrix<f32>,
features: &[Vec<f32>],
epsilon: f64,
) -> Result<Vec<Vec<f32>>, SolverError> {
let n = adjacency.rows;
let feature_dim = features[0].len();
let mut aggregated = vec![vec![0.0f32; feature_dim]; n];
// Solve A·X_col = F_col for each feature dimension
// Using batch solver amortization
for d in 0..feature_dim {
let rhs: Vec<f32> = features.iter().map(|f| f[d]).collect();
let system = SparseSystem::new(adjacency.clone(), rhs);
let result = self.solver.solve_with_budget(
&system,
ComputeBudget::for_lane(ComputeLane::Heavy),
)?;
for i in 0..n {
aggregated[i][d] = result.solution[i];
}
}
Ok(aggregated)
}
}
/// GNN aggregation strategy using solver
pub struct SublinearAggregation {
adapter: GnnSolverAdapter,
epsilon: f64,
}
impl AggregationStrategy for SublinearAggregation {
fn aggregate(
&self,
adjacency: &CsrMatrix<f32>,
features: &[Vec<f32>],
) -> Vec<Vec<f32>> {
self.adapter.sublinear_aggregate(adjacency, features, self.epsilon)
.unwrap_or_else(|_| {
// Fallback to mean aggregation
MeanAggregation.aggregate(adjacency, features)
})
}
}
```
### 1.3 Solver-to-Graph ACL
Translates between ruvector-graph's property graph model and solver's sparse adjacency.
```rust
/// ACL: Graph Analytics ←→ Solver Core
pub struct GraphSolverAdapter {
push_solver: Arc<dyn SublinearPageRank>,
}
impl GraphSolverAdapter {
/// Convert PropertyGraph to SparseAdjacency for solver
pub fn property_graph_to_adjacency(graph: &PropertyGraph) -> SparseAdjacency {
let n = graph.node_count();
let edges: Vec<(usize, usize, f32)> = graph.edges()
.map(|e| (e.source, e.target, e.weight.unwrap_or(1.0)))
.collect();
SparseAdjacency {
adj: CsrMatrix::from_edges(&edges, n),
directed: graph.is_directed(),
weighted: graph.is_weighted(),
}
}
/// Solver-accelerated PageRank using Forward Push
/// Replaces: O(n × m × iterations) power iteration
/// With: O(1/ε) Forward Push
pub fn fast_pagerank(
&self,
graph: &PropertyGraph,
source: usize,
alpha: f64,
epsilon: f64,
) -> Result<Vec<(usize, f64)>, SolverError> {
let adj = Self::property_graph_to_adjacency(graph);
let problem = GraphProblem {
id: ProblemId::new(),
graph: adj,
query: GraphQuery::SingleSource { source },
parameters: PushParameters { alpha, epsilon, max_iterations: 1_000_000 },
};
let result = self.push_solver.solve(&problem)?;
// Convert solver output to ranked node list
let mut ranked: Vec<(usize, f64)> = result.solution.iter()
.enumerate()
.map(|(i, &score)| (i, score as f64))
.filter(|(_, score)| *score > epsilon)
.collect();
ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
Ok(ranked)
}
}
```
### 1.4 Platform ACL (WASM / NAPI / REST / MCP)
Serialization boundary between domain types and platform representations.
```rust
/// WASM ACL
#[wasm_bindgen]
pub struct JsSolverConfig {
inner: SolverConfig,
}
#[wasm_bindgen]
impl JsSolverConfig {
#[wasm_bindgen(constructor)]
pub fn new(js_config: JsValue) -> Result<JsSolverConfig, JsValue> {
let config: SolverConfig = serde_wasm_bindgen::from_value(js_config)
.map_err(|e| JsValue::from_str(&e.to_string()))?;
Ok(JsSolverConfig { inner: config })
}
}
/// REST ACL
pub async fn solve_handler(
State(state): State<AppState>,
Json(request): Json<SolverRequest>,
) -> Result<Json<SolverResponse>, AppError> {
// Translate REST types to domain types
let system = SparseSystem::from_request(&request)?;
let budget = ComputeBudget::from_request(&request);
// Execute domain logic
let result = state.orchestrator.solve(system).await?;
// Translate domain result to REST response
Ok(Json(SolverResponse::from_result(&result)))
}
/// MCP ACL
pub fn solver_tool_schema() -> McpTool {
McpTool {
name: "solve_sublinear".to_string(),
description: "Solve sparse linear system using sublinear algorithms".to_string(),
input_schema: json!({
"type": "object",
"required": ["matrix_rows", "matrix_cols", "values", "col_indices", "row_ptrs", "rhs"],
"properties": {
"matrix_rows": { "type": "integer", "minimum": 1 },
"matrix_cols": { "type": "integer", "minimum": 1 },
"values": { "type": "array", "items": { "type": "number" } },
"col_indices": { "type": "array", "items": { "type": "integer" } },
"row_ptrs": { "type": "array", "items": { "type": "integer" } },
"rhs": { "type": "array", "items": { "type": "number" } },
"tolerance": { "type": "number", "default": 1e-6 },
"max_iterations": { "type": "integer", "default": 1000 },
"algorithm": { "type": "string", "enum": ["auto", "neumann", "cg", "true"] },
}
}),
}
}
```
---
## 2. Shared Kernel
Types shared between Solver Core and other bounded contexts.
### 2.1 Sparse Matrix Types
Shared between Solver Core and Min-Cut Context:
```rust
// crates/ruvector-solver/src/shared/sparse.rs
// Also used by ruvector-mincut
pub use crate::domain::values::CsrMatrix;
pub use crate::domain::values::SparsityProfile;
/// Conversion between CsrMatrix and CscMatrix (Compressed Sparse Column)
impl<T: Copy> CsrMatrix<T> {
pub fn to_csc(&self) -> CscMatrix<T> { ... }
pub fn transpose(&self) -> CsrMatrix<T> { ... }
}
```
### 2.2 Error Types
Shared across all solver-related contexts:
```rust
// crates/ruvector-solver/src/shared/errors.rs
#[derive(Debug, thiserror::Error)]
pub enum SolverError {
#[error("solver did not converge: {iterations} iterations, best residual {best_residual}")]
NonConvergence { iterations: usize, best_residual: f64, budget: ComputeBudget },
#[error("numerical instability in {source}: {detail}")]
NumericalInstability { source: &'static str, detail: String },
#[error("compute budget exhausted: {progress:.1}% complete")]
BudgetExhausted { budget: ComputeBudget, progress: f64 },
#[error("invalid input: {0}")]
InvalidInput(#[from] ValidationError),
#[error("precision loss: expected ε={expected_eps}, achieved ε={achieved_eps}")]
PrecisionLoss { expected_eps: f64, achieved_eps: f64 },
#[error("all algorithms failed")]
AllAlgorithmsFailed,
#[error("backend error: {0}")]
BackendError(#[from] Box<dyn std::error::Error + Send + Sync>),
}
```
### 2.3 Compute Budget
Shared between Solver and Coherence Gate's compute ladder:
```rust
// Used by both ruvector-solver and cognitum-gate-tilezero
pub use crate::domain::entities::ComputeBudget;
pub use crate::domain::entities::ComputeLane;
```
---
## 3. Published Language
### 3.1 Solver Protocol (JSON Schema)
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ruvector.io/schemas/solver/v1",
"title": "RuVector Sublinear Solver Protocol v1",
"definitions": {
"SolverRequest": {
"type": "object",
"required": ["system"],
"properties": {
"system": { "$ref": "#/definitions/SparseSystem" },
"config": { "$ref": "#/definitions/SolverConfig" },
"budget": { "$ref": "#/definitions/ComputeBudget" }
}
},
"SparseSystem": {
"type": "object",
"required": ["rows", "cols", "values", "col_indices", "row_ptrs", "rhs"],
"properties": {
"rows": { "type": "integer", "minimum": 1, "maximum": 10000000 },
"cols": { "type": "integer", "minimum": 1, "maximum": 10000000 },
"values": { "type": "array", "items": { "type": "number" } },
"col_indices": { "type": "array", "items": { "type": "integer", "minimum": 0 } },
"row_ptrs": { "type": "array", "items": { "type": "integer", "minimum": 0 } },
"rhs": { "type": "array", "items": { "type": "number" } }
}
},
"SolverResult": {
"type": "object",
"properties": {
"solution": { "type": "array", "items": { "type": "number" } },
"algorithm_used": { "type": "string" },
"iterations": { "type": "integer" },
"residual_norm": { "type": "number" },
"wall_time_us": { "type": "integer" },
"converged": { "type": "boolean" },
"error_bounds": {
"type": "object",
"properties": {
"absolute_error": { "type": "number" },
"relative_error": { "type": "number" }
}
}
}
}
}
}
```
---
## 4. Event-Driven Integration
### 4.1 Event Flow Architecture
```
SolverOrchestrator
emits SolverEvent
┌──────┴──────┐
│ broadcast │
│ ::Sender │
└──────┬──────┘
┌─────┼─────┬──────────┬──────────┐
▼ ▼ ▼ ▼ ▼
Coherence Metrics Stream Audit SONA
Engine Collector API Trail Learning
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
Update Prometheus Server- Witness Update
energy counters Sent chain routing
Events entry weights
```
### 4.2 Coherence Gate as Solver Governor
```
Solve Request
┌────────────────┐
│ Complexity Est.│ "How expensive will this be?"
└───────┬────────┘
┌────────────────┐
│ Gate Decision │ Permit / Defer / Deny
└───┬────┬───┬───┘
│ │ │
Permit Defer Deny
│ │ │
▼ ▼ ▼
Execute Wait Reject
solver for with
human witness
approval
```
### 4.3 SONA Feedback Loop
```
[Solve Request] → [Route] → [Execute] → [Record Result]
▲ │
│ ▼
[Update Routing] [SONA micro-LoRA update]
[Weights] │
▲ │
└─── EWC-protected ──────┘
weight update
```
---
## 5. Dependency Injection
### 5.1 Generic Type Parameters
```rust
/// Solver generic over numeric backend
pub struct SublinearSolver<B: NumericBackend = NalgebraBackend> {
backend: B,
config: SolverConfig,
}
impl<B: NumericBackend> SolverEngine for SublinearSolver<B> {
type Input = SparseSystem;
type Output = SolverResult;
type Error = SolverError;
fn solve(&self, input: &Self::Input) -> Result<Self::Output, Self::Error> {
// Implementation using self.backend for matrix operations
todo!()
}
}
```
### 5.2 Runtime DI via Arc<dyn Trait>
```rust
/// Application state with DI
pub struct AppState {
pub solver: Arc<dyn SolverEngine<Input = SparseSystem, Output = SolverResult, Error = SolverError>>,
pub router: Arc<AlgorithmRouter>,
pub session_repo: Arc<dyn SolverSessionRepository>,
pub event_bus: broadcast::Sender<SolverEvent>,
}
```
---
## 6. Integration with Existing Patterns
### 6.1 Core-Binding-Surface Compliance
```
ruvector-solver → Core (pure Rust algorithms)
ruvector-solver-wasm → Binding (wasm-bindgen)
ruvector-solver-node → Binding (NAPI-RS)
@ruvector/solver (npm) → Surface (TypeScript API)
```
### 6.2 Event Sourcing Alignment
SolverEvent matches Prime Radiant's DomainEvent contract:
- `#[serde(tag = "type")]` — Discriminated union in JSON
- Deterministic replay via event log
- Content-addressable via SHAKE-256 hash
- Tamper-detectable in witness chain
### 6.3 Compute Ladder Integration
Solver maps to cognitum-gate-tilezero compute lanes:
| Lane | Solver Use Case | Budget |
|------|----------------|--------|
| Reflex | Cached result lookup | <1ms, 1MB |
| Retrieval | Small solve (n<1K) or Push query | ~10ms, 16MB |
| Heavy | Full CG/Neumann/BMSSP solve | ~100ms, 256MB |
| Deliberate | TRUE with preprocessing, streaming | Unbounded |
---
## 7. Migration Patterns
### 7.1 Strangler Fig for Coherence Engine
Gradual replacement of dense Laplacian computation:
```rust
impl CoherenceComputer {
pub fn compute_energy(&self, graph: &SheafGraph) -> CoherenceEnergy {
let density = graph.edge_density();
#[cfg(feature = "sublinear-coherence")]
if density < 0.05 {
// New: Sublinear path for sparse graphs
if let Ok(energy) = self.solver_adapter.solve_coherence(graph, &signal) {
return energy;
}
// Fallthrough to dense on solver failure
}
// Existing: Dense path (unchanged)
self.dense_laplacian_energy(graph)
}
}
```
Phase 1: Feature flag (opt-in, default off)
Phase 2: Default on for sparse graphs (density < 5%)
Phase 3: Default on for all graphs after benchmark validation
Phase 4: Remove dense path (breaking change in major version)
### 7.2 Branch by Abstraction for GNN
```rust
pub enum AggregationStrategy {
Mean,
Max,
Sum,
Attention,
#[cfg(feature = "sublinear-gnn")]
Sublinear { epsilon: f64 },
}
impl GnnLayer {
pub fn aggregate(&self, adj: &CsrMatrix<f32>, features: &[Vec<f32>]) -> Vec<Vec<f32>> {
match self.strategy {
AggregationStrategy::Mean => mean_aggregate(adj, features),
AggregationStrategy::Max => max_aggregate(adj, features),
AggregationStrategy::Sum => sum_aggregate(adj, features),
AggregationStrategy::Attention => attention_aggregate(adj, features),
#[cfg(feature = "sublinear-gnn")]
AggregationStrategy::Sublinear { epsilon } => {
SublinearAggregation::new(epsilon).aggregate(adj, features)
}
}
}
}
```
---
## 8. Cross-Cutting Concerns
### 8.1 Observability
```rust
use tracing::{instrument, info, warn};
impl SolverOrchestrator {
#[instrument(skip(self, system), fields(n = system.matrix.rows, nnz = system.matrix.nnz()))]
pub async fn solve(&self, system: SparseSystem) -> Result<SolverResult, SolverError> {
let algorithm = self.router.select(&system.profile());
info!(algorithm = ?algorithm, "routing decision");
let start = Instant::now();
let result = self.execute(algorithm, &system).await;
let elapsed = start.elapsed();
match &result {
Ok(r) => info!(iterations = r.iterations, residual = r.residual_norm, elapsed_us = elapsed.as_micros() as u64, "solve completed"),
Err(e) => warn!(error = %e, "solve failed"),
}
result
}
}
```
### 8.2 Caching
```rust
pub struct SolverCache {
results: DashMap<u64, (SolverResult, Instant)>,
ttl: Duration,
max_entries: usize,
}
impl SolverCache {
pub fn get_or_compute(
&self,
key: u64,
compute: impl FnOnce() -> Result<SolverResult, SolverError>,
) -> Result<SolverResult, SolverError> {
if let Some(entry) = self.results.get(&key) {
if entry.1.elapsed() < self.ttl {
return Ok(entry.0.clone());
}
}
let result = compute()?;
self.results.insert(key, (result.clone(), Instant::now()));
// Evict if over capacity
if self.results.len() > self.max_entries {
self.evict_oldest();
}
Ok(result)
}
}
```