# 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, cache: DashMap, // Keyed on graph version hash } impl CoherenceSolverAdapter { /// Convert SheafGraph to CsrMatrix for solver input pub fn sheaf_to_csr(graph: &SheafGraph) -> CsrMatrix { 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 = 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 { 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, } 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, features: &[Vec], epsilon: f64, ) -> Result>, 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 = 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, features: &[Vec], ) -> Vec> { 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, } 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, 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 { 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, Json(request): Json, ) -> Result, 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 CsrMatrix { pub fn to_csc(&self) -> CscMatrix { ... } pub fn transpose(&self) -> CsrMatrix { ... } } ``` ### 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), } ``` ### 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 { backend: B, config: SolverConfig, } impl SolverEngine for SublinearSolver { type Input = SparseSystem; type Output = SolverResult; type Error = SolverError; fn solve(&self, input: &Self::Input) -> Result { // Implementation using self.backend for matrix operations todo!() } } ``` ### 5.2 Runtime DI via Arc ```rust /// Application state with DI pub struct AppState { pub solver: Arc>, pub router: Arc, pub session_repo: Arc, pub event_bus: broadcast::Sender, } ``` --- ## 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, features: &[Vec]) -> Vec> { 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 { 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, ttl: Duration, max_entries: usize, } impl SolverCache { pub fn get_or_compute( &self, key: u64, compute: impl FnOnce() -> Result, ) -> Result { 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) } } ```