mirror of
https://github.com/ruvnet/RuView
synced 2026-07-20 17:03:24 +00:00
407b46b206
Add ruvnet/midstream (AIMDS real-time inference) and ruvnet/sublinear-time-solver (sublinear optimization algorithms) as vendored dependencies under vendor/.
333 lines
9.6 KiB
Rust
333 lines
9.6 KiB
Rust
// WASM bindings for the fast sublinear-time solver
|
|
#![cfg(feature = "wasm")]
|
|
|
|
use wasm_bindgen::prelude::*;
|
|
use serde::{Deserialize, Serialize};
|
|
use crate::solver::{SolverOptions, ConvergenceMode};
|
|
use crate::fast_solver::{FastCSRMatrix, FastConjugateGradient};
|
|
use crate::utils::Precision;
|
|
|
|
#[wasm_bindgen]
|
|
extern "C" {
|
|
#[wasm_bindgen(js_namespace = console)]
|
|
fn log(s: &str);
|
|
}
|
|
|
|
macro_rules! console_log {
|
|
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn init_panic_hook() {
|
|
#[cfg(feature = "console_error_panic_hook")]
|
|
console_error_panic_hook::set_once();
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct MatrixData {
|
|
pub values: Vec<f64>,
|
|
pub col_indices: Vec<u32>,
|
|
pub row_ptr: Vec<u32>,
|
|
pub rows: usize,
|
|
pub cols: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct SolverResult {
|
|
pub solution: Vec<f64>,
|
|
pub iterations: usize,
|
|
pub residual: f64,
|
|
pub converged: bool,
|
|
pub compute_time_ms: f64,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub struct WasmSolver {
|
|
tolerance: f64,
|
|
max_iterations: usize,
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
impl WasmSolver {
|
|
#[wasm_bindgen(constructor)]
|
|
pub fn new() -> Self {
|
|
init_panic_hook();
|
|
WasmSolver {
|
|
tolerance: 1e-6,
|
|
max_iterations: 1000,
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn set_tolerance(&mut self, tolerance: f64) {
|
|
self.tolerance = tolerance;
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn set_max_iterations(&mut self, max_iterations: usize) {
|
|
self.max_iterations = max_iterations;
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn solve_csr(&self, matrix_json: &str, vector_json: &str) -> Result<String, JsValue> {
|
|
let start = js_sys::Date::now();
|
|
|
|
// Parse input
|
|
let matrix_data: MatrixData = serde_json::from_str(matrix_json)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to parse matrix: {}", e)))?;
|
|
|
|
let vector: Vec<f64> = serde_json::from_str(vector_json)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to parse vector: {}", e)))?;
|
|
|
|
// Create FastCSRMatrix
|
|
let matrix = FastCSRMatrix {
|
|
values: matrix_data.values,
|
|
col_indices: matrix_data.col_indices,
|
|
row_ptr: matrix_data.row_ptr,
|
|
rows: matrix_data.rows,
|
|
cols: matrix_data.cols,
|
|
};
|
|
|
|
// Check matrix dimensions
|
|
if matrix.rows != vector.len() {
|
|
return Err(JsValue::from_str(&format!(
|
|
"Dimension mismatch: matrix has {} rows but vector has {} elements",
|
|
matrix.rows,
|
|
vector.len()
|
|
)));
|
|
}
|
|
|
|
// Create solver
|
|
let mut solver = FastConjugateGradient::new(&matrix, &vector);
|
|
|
|
// Solve
|
|
let solution = solver.solve_with_tolerance(self.tolerance, self.max_iterations);
|
|
|
|
let compute_time_ms = js_sys::Date::now() - start;
|
|
|
|
// Compute residual
|
|
let mut residual_vec = vec![0.0; matrix.rows];
|
|
matrix.multiply_vector(&solution, &mut residual_vec);
|
|
|
|
let mut residual = 0.0;
|
|
for i in 0..matrix.rows {
|
|
let diff = residual_vec[i] - vector[i];
|
|
residual += diff * diff;
|
|
}
|
|
residual = residual.sqrt();
|
|
|
|
let result = SolverResult {
|
|
solution,
|
|
iterations: solver.iterations,
|
|
residual,
|
|
converged: residual < self.tolerance,
|
|
compute_time_ms,
|
|
};
|
|
|
|
serde_json::to_string(&result)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e)))
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn solve_dense(&self, matrix_json: &str, vector_json: &str) -> Result<String, JsValue> {
|
|
let start = js_sys::Date::now();
|
|
|
|
// Parse dense matrix
|
|
let matrix_data: Vec<Vec<f64>> = serde_json::from_str(matrix_json)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to parse matrix: {}", e)))?;
|
|
|
|
let vector: Vec<f64> = serde_json::from_str(vector_json)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to parse vector: {}", e)))?;
|
|
|
|
let n = matrix_data.len();
|
|
if n == 0 {
|
|
return Err(JsValue::from_str("Empty matrix"));
|
|
}
|
|
|
|
// Convert dense to CSR
|
|
let mut values = Vec::new();
|
|
let mut col_indices = Vec::new();
|
|
let mut row_ptr = vec![0];
|
|
|
|
for row in &matrix_data {
|
|
if row.len() != n {
|
|
return Err(JsValue::from_str("Matrix must be square"));
|
|
}
|
|
for (j, &val) in row.iter().enumerate() {
|
|
if val.abs() > 1e-10 {
|
|
values.push(val);
|
|
col_indices.push(j as u32);
|
|
}
|
|
}
|
|
row_ptr.push(values.len() as u32);
|
|
}
|
|
|
|
let matrix = FastCSRMatrix {
|
|
values,
|
|
col_indices,
|
|
row_ptr,
|
|
rows: n,
|
|
cols: n,
|
|
};
|
|
|
|
// Create solver
|
|
let mut solver = FastConjugateGradient::new(&matrix, &vector);
|
|
|
|
// Solve
|
|
let solution = solver.solve_with_tolerance(self.tolerance, self.max_iterations);
|
|
|
|
let compute_time_ms = js_sys::Date::now() - start;
|
|
|
|
// Compute residual
|
|
let mut residual_vec = vec![0.0; matrix.rows];
|
|
matrix.multiply_vector(&solution, &mut residual_vec);
|
|
|
|
let mut residual = 0.0;
|
|
for i in 0..matrix.rows {
|
|
let diff = residual_vec[i] - vector[i];
|
|
residual += diff * diff;
|
|
}
|
|
residual = residual.sqrt();
|
|
|
|
let result = SolverResult {
|
|
solution,
|
|
iterations: solver.iterations,
|
|
residual,
|
|
converged: residual < self.tolerance,
|
|
compute_time_ms,
|
|
};
|
|
|
|
serde_json::to_string(&result)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e)))
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn solve_neumann(&self, matrix_json: &str, vector_json: &str) -> Result<String, JsValue> {
|
|
let start = js_sys::Date::now();
|
|
|
|
// Parse input
|
|
let matrix_data: MatrixData = serde_json::from_str(matrix_json)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to parse matrix: {}", e)))?;
|
|
|
|
let vector: Vec<f64> = serde_json::from_str(vector_json)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to parse vector: {}", e)))?;
|
|
|
|
// Create FastCSRMatrix
|
|
let matrix = FastCSRMatrix {
|
|
values: matrix_data.values,
|
|
col_indices: matrix_data.col_indices,
|
|
row_ptr: matrix_data.row_ptr,
|
|
rows: matrix_data.rows,
|
|
cols: matrix_data.cols,
|
|
};
|
|
|
|
// Use Neumann series solver
|
|
let options = SolverOptions {
|
|
tolerance: self.tolerance,
|
|
max_iterations: self.max_iterations,
|
|
convergence_mode: ConvergenceMode::Residual,
|
|
enable_caching: true,
|
|
cache_size: 100,
|
|
enable_simd: true,
|
|
enable_parallel: false,
|
|
thread_count: 1,
|
|
};
|
|
|
|
// Solve using Neumann series
|
|
let mut solution = vector.clone();
|
|
let mut residual = vec![0.0; matrix.rows];
|
|
let mut iterations = 0;
|
|
|
|
for iter in 0..self.max_iterations {
|
|
// Compute residual: r = b - Ax
|
|
matrix.multiply_vector(&solution, &mut residual);
|
|
for i in 0..matrix.rows {
|
|
residual[i] = vector[i] - residual[i];
|
|
}
|
|
|
|
// Check convergence
|
|
let residual_norm: f64 = residual.iter().map(|x| x * x).sum::<f64>().sqrt();
|
|
if residual_norm < self.tolerance {
|
|
iterations = iter + 1;
|
|
break;
|
|
}
|
|
|
|
// Update solution: x = x + r
|
|
for i in 0..matrix.rows {
|
|
solution[i] += residual[i];
|
|
}
|
|
|
|
iterations = iter + 1;
|
|
}
|
|
|
|
let compute_time_ms = js_sys::Date::now() - start;
|
|
|
|
// Final residual computation
|
|
matrix.multiply_vector(&solution, &mut residual);
|
|
let mut final_residual = 0.0;
|
|
for i in 0..matrix.rows {
|
|
let diff = residual[i] - vector[i];
|
|
final_residual += diff * diff;
|
|
}
|
|
final_residual = final_residual.sqrt();
|
|
|
|
let result = SolverResult {
|
|
solution,
|
|
iterations,
|
|
residual: final_residual,
|
|
converged: final_residual < self.tolerance,
|
|
compute_time_ms,
|
|
};
|
|
|
|
serde_json::to_string(&result)
|
|
.map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e)))
|
|
}
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn version() -> String {
|
|
env!("CARGO_PKG_VERSION").to_string()
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn create_test_matrix(n: usize) -> String {
|
|
// Create a diagonally dominant test matrix
|
|
let mut values = Vec::new();
|
|
let mut col_indices = Vec::new();
|
|
let mut row_ptr = vec![0];
|
|
|
|
for i in 0..n {
|
|
// Add off-diagonal elements
|
|
if i > 0 {
|
|
values.push(-1.0);
|
|
col_indices.push((i - 1) as u32);
|
|
}
|
|
|
|
// Add diagonal element (make it dominant)
|
|
values.push(4.0);
|
|
col_indices.push(i as u32);
|
|
|
|
if i < n - 1 {
|
|
values.push(-1.0);
|
|
col_indices.push((i + 1) as u32);
|
|
}
|
|
|
|
row_ptr.push(values.len() as u32);
|
|
}
|
|
|
|
let matrix_data = MatrixData {
|
|
values,
|
|
col_indices,
|
|
row_ptr,
|
|
rows: n,
|
|
cols: n,
|
|
};
|
|
|
|
serde_json::to_string(&matrix_data).unwrap_or_else(|_| "{}".to_string())
|
|
}
|
|
|
|
#[wasm_bindgen]
|
|
pub fn create_test_vector(n: usize) -> String {
|
|
let vector: Vec<f64> = (0..n).map(|i| (i + 1) as f64).collect();
|
|
serde_json::to_string(&vector).unwrap_or_else(|_| "[]".to_string())
|
|
} |