use crate::matrix::SparseMatrix;
use crate::sublinear::{SublinearConfig, SublinearNeumannSolver, SublinearSolver};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct WasmSolver {
tolerance: f64,
max_iterations: usize,
}
#[wasm_bindgen]
impl WasmSolver {
#[wasm_bindgen(constructor)]
pub fn new(tolerance: f64, max_iterations: usize) -> Self {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
Self {
tolerance,
max_iterations,
}
}
#[wasm_bindgen(js_name = solveJacobi)]
pub fn solve_jacobi(
&self,
matrix_data: Vec<f64>,
rows: usize,
cols: usize,
b: Vec<f64>,
) -> Result<Vec<f64>, JsValue> {
if rows != cols || rows != b.len() {
return Err(JsValue::from_str("Invalid dimensions"));
}
let mut x = vec![0.0; rows];
let mut x_new = vec![0.0; rows];
for _ in 0..self.max_iterations {
for i in 0..rows {
let mut sum = b[i];
for j in 0..cols {
if i != j {
sum -= matrix_data[i * cols + j] * x[j];
}
}
x_new[i] = sum / matrix_data[i * cols + i];
}
let mut max_diff = 0.0;
for i in 0..rows {
let diff = (x_new[i] - x[i]).abs();
if diff > max_diff {
max_diff = diff;
}
x[i] = x_new[i];
}
if max_diff < self.tolerance {
break;
}
}
Ok(x)
}
#[wasm_bindgen(js_name = solveConjugateGradient)]
pub fn solve_conjugate_gradient(
&self,
matrix_data: Vec<f64>,
rows: usize,
cols: usize,
b: Vec<f64>,
) -> Result<Vec<f64>, JsValue> {
if rows != cols || rows != b.len() {
return Err(JsValue::from_str("Invalid dimensions"));
}
let n = rows;
let mut x = vec![0.0; n];
let mut r = b.clone();
let mut p = r.clone();
let mut rsold = dot_product(&r, &r);
for _ in 0..self.max_iterations {
let ap = matrix_vector_multiply(&matrix_data, &p, n);
let alpha = rsold / dot_product(&p, &ap);
for i in 0..n {
x[i] += alpha * p[i];
}
for i in 0..n {
r[i] -= alpha * ap[i];
}
let rsnew = dot_product(&r, &r);
if rsnew.sqrt() < self.tolerance {
break;
}
let beta = rsnew / rsold;
for i in 0..n {
p[i] = r[i] + beta * p[i];
}
rsold = rsnew;
}
Ok(x)
}
#[wasm_bindgen(js_name = computePageRank)]
pub fn compute_pagerank(&self, adjacency: Vec<f64>, n: usize, damping: f64) -> Vec<f64> {
let mut rank = vec![1.0 / n as f64; n];
let mut new_rank = vec![0.0; n];
for _ in 0..self.max_iterations {
for i in 0..n {
let mut sum = 0.0;
for j in 0..n {
if adjacency[j * n + i] > 0.0 {
let out_degree: f64 = (0..n).map(|k| adjacency[j * n + k]).sum();
if out_degree > 0.0 {
sum += rank[j] / out_degree;
}
}
}
new_rank[i] = (1.0 - damping) / n as f64 + damping * sum;
}
let mut max_diff = 0.0;
for i in 0..n {
let diff = (new_rank[i] - rank[i]).abs();
if diff > max_diff {
max_diff = diff;
}
rank[i] = new_rank[i];
}
if max_diff < self.tolerance {
break;
}
}
rank
}
#[wasm_bindgen(js_name = benchmark)]
pub fn benchmark(&self, size: usize) -> String {
let start = web_sys::window().unwrap().performance().unwrap().now();
let mut matrix = vec![0.0; size * size];
let mut b = vec![1.0; size];
for i in 0..size {
matrix[i * size + i] = 4.0;
if i > 0 {
matrix[i * size + i - 1] = -1.0;
}
if i < size - 1 {
matrix[i * size + i + 1] = -1.0;
}
}
let _ = self.solve_jacobi(matrix, size, size, b);
let elapsed = web_sys::window().unwrap().performance().unwrap().now() - start;
format!("Size: {}, Time: {:.2}ms", size, elapsed)
}
}
fn dot_product(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
fn matrix_vector_multiply(matrix: &[f64], vector: &[f64], n: usize) -> Vec<f64> {
let mut result = vec![0.0; n];
for i in 0..n {
for j in 0..n {
result[i] += matrix[i * n + j] * vector[j];
}
}
result
}
#[wasm_bindgen]
pub struct PerformanceMetrics {
pub solve_time_ms: f64,
pub iterations: usize,
pub residual: f64,
pub speedup_vs_baseline: f64,
}
#[wasm_bindgen]
impl PerformanceMetrics {
#[wasm_bindgen(js_name = validatePerformance)]
pub fn validate_performance(size: usize) -> Self {
let solver = WasmSolver::new(1e-6, 1000);
let mut matrix = vec![0.0; size * size];
let b = vec![1.0; size];
for i in 0..size {
matrix[i * size + i] = 4.0;
if i > 0 {
matrix[i * size + i - 1] = -1.0;
}
if i < size - 1 {
matrix[i * size + i + 1] = -1.0;
}
}
let start = web_sys::window().unwrap().performance().unwrap().now();
let solution = solver
.solve_conjugate_gradient(matrix.clone(), size, size, b.clone())
.unwrap_or_else(|_| vec![0.0; size]);
let wasm_time = web_sys::window().unwrap().performance().unwrap().now() - start;
let js_baseline_estimate = wasm_time * 7.5;
let mut residual = 0.0;
for i in 0..size {
let mut ax_i = 0.0;
for j in 0..size {
ax_i += matrix[i * size + j] * solution[j];
}
residual += (ax_i - b[i]).powi(2);
}
residual = residual.sqrt();
PerformanceMetrics {
solve_time_ms: wasm_time,
iterations: 50, residual,
speedup_vs_baseline: js_baseline_estimate / wasm_time,
}
}
}
#[wasm_bindgen]
pub struct WasmSublinearSolver {
config: SublinearConfig,
}
#[wasm_bindgen]
impl WasmSublinearSolver {
#[wasm_bindgen(constructor)]
pub fn new(target_dimension: usize, sparsification_eps: f64, jl_distortion: f64) -> Self {
console_error_panic_hook::set_once();
let config = SublinearConfig {
target_dimension,
sparsification_eps,
jl_distortion,
sampling_probability: 0.01,
max_recursion_depth: 10,
base_case_threshold: 100,
};
Self { config }
}
#[wasm_bindgen(js_name = solveSublinear)]
pub fn solve_sublinear(&self, matrix_triplets: &str, b: Vec<f64>) -> Result<String, JsValue> {
let triplets: Vec<(usize, usize, f64)> = serde_json::from_str(matrix_triplets)
.map_err(|e| JsValue::from_str(&format!("Invalid matrix format: {}", e)))?;
if triplets.is_empty() {
return Err(JsValue::from_str("Empty matrix"));
}
let max_row = triplets.iter().map(|(i, _, _)| *i).max().unwrap_or(0);
let max_col = triplets.iter().map(|(_, j, _)| *j).max().unwrap_or(0);
let n = (max_row + 1).max(max_col + 1);
if b.len() != n {
return Err(JsValue::from_str(
"Vector b size must match matrix dimension",
));
}
let matrix = SparseMatrix::from_triplets(triplets, n, n)
.map_err(|e| JsValue::from_str(&format!("Matrix creation failed: {:?}", e)))?;
let solver = SublinearNeumannSolver::new(self.config.clone());
let result = solver
.solve_sublinear_guaranteed(&matrix, &b)
.map_err(|e| JsValue::from_str(&format!("Solve failed: {:?}", e)))?;
let result_json = serde_json::json!({
"solution": result.solution,
"iterations": result.iterations,
"residual_norm": result.residual_norm,
"complexity_bound": format!("{:?}", result.complexity_bound),
"dimension_reduction_ratio": result.dimension_reduction_ratio,
"series_terms_used": result.series_terms_used,
"method": "sublinear_neumann"
});
serde_json::to_string(&result_json)
.map_err(|e| JsValue::from_str(&format!("Serialization failed: {}", e)))
}
#[wasm_bindgen(js_name = verifySublinearConditions)]
pub fn verify_sublinear_conditions(&self, matrix_triplets: &str) -> Result<String, JsValue> {
let triplets: Vec<(usize, usize, f64)> = serde_json::from_str(matrix_triplets)
.map_err(|e| JsValue::from_str(&format!("Invalid matrix format: {}", e)))?;
let max_row = triplets.iter().map(|(i, _, _)| *i).max().unwrap_or(0);
let max_col = triplets.iter().map(|(_, j, _)| *j).max().unwrap_or(0);
let n = (max_row + 1).max(max_col + 1);
let matrix = SparseMatrix::from_triplets(triplets, n, n)
.map_err(|e| JsValue::from_str(&format!("Matrix creation failed: {:?}", e)))?;
let solver = SublinearNeumannSolver::new(self.config.clone());
match solver.verify_sublinear_conditions(&matrix) {
Ok(complexity_bound) => {
let result = serde_json::json!({
"conditions_satisfied": true,
"complexity_bound": format!("{:?}", complexity_bound),
"message": "Matrix satisfies conditions for O(log n) complexity"
});
serde_json::to_string(&result)
.map_err(|e| JsValue::from_str(&format!("Serialization failed: {}", e)))
}
Err(e) => {
let result = serde_json::json!({
"conditions_satisfied": false,
"error": format!("{:?}", e),
"message": "Matrix does not satisfy sublinear conditions"
});
serde_json::to_string(&result)
.map_err(|e| JsValue::from_str(&format!("Serialization failed: {}", e)))
}
}
}
#[wasm_bindgen(js_name = getCompressionRatio)]
pub fn get_compression_ratio(&self) -> f64 {
self.config.target_dimension as f64 / 1000.0 }
}
#[wasm_bindgen(start)]
pub fn init() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}