sublinear 0.3.3

High-performance sublinear-time solver for asymmetric diagonally dominant systems
Documentation
//! WebAssembly bindings for the sublinear-time solver.
//!
//! This module provides high-performance WASM exports for browser and Node.js environments.

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 {
    /// Create a new WASM solver instance.
    #[wasm_bindgen(constructor)]
    pub fn new(tolerance: f64, max_iterations: usize) -> Self {
        // Set panic hook for better debugging
        #[cfg(feature = "console_error_panic_hook")]
        console_error_panic_hook::set_once();

        Self {
            tolerance,
            max_iterations,
        }
    }

    /// Solve a linear system Ax = b using the Jacobi method.
    #[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"));
        }

        // Simple Jacobi iteration for demonstration
        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];
            }

            // Check convergence
            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)
    }

    /// Solve using conjugate gradient method (for symmetric positive definite matrices).
    #[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 {
            // Ap = A * p
            let ap = matrix_vector_multiply(&matrix_data, &p, n);
            let alpha = rsold / dot_product(&p, &ap);

            // x = x + alpha * p
            for i in 0..n {
                x[i] += alpha * p[i];
            }

            // r = r - alpha * Ap
            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;

            // p = r + beta * p
            for i in 0..n {
                p[i] = r[i] + beta * p[i];
            }

            rsold = rsnew;
        }

        Ok(x)
    }

    /// Compute PageRank for a graph.
    #[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;
            }

            // Check convergence
            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
    }

    /// Benchmark the solver performance.
    #[wasm_bindgen(js_name = benchmark)]
    pub fn benchmark(&self, size: usize) -> String {
        let start = web_sys::window().unwrap().performance().unwrap().now();

        // Generate test matrix (diagonally dominant)
        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)
    }
}

// Helper functions
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
}

/// Performance metrics for validation
#[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 {
    /// Validate that WASM provides performance improvements
    #[wasm_bindgen(js_name = validatePerformance)]
    pub fn validate_performance(size: usize) -> Self {
        let solver = WasmSolver::new(1e-6, 1000);

        // Generate test problem
        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;

        // Estimate JavaScript baseline (typically 5-10x slower)
        let js_baseline_estimate = wasm_time * 7.5;

        // Calculate residual
        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, // Approximate
            residual,
            speedup_vs_baseline: js_baseline_estimate / wasm_time,
        }
    }
}

/// Sublinear solver with O(log n) complexity for WASM
#[wasm_bindgen]
pub struct WasmSublinearSolver {
    config: SublinearConfig,
}

#[wasm_bindgen]
impl WasmSublinearSolver {
    /// Create new sublinear solver
    #[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 }
    }

    /// Solve system with guaranteed O(log n) complexity
    #[wasm_bindgen(js_name = solveSublinear)]
    pub fn solve_sublinear(&self, matrix_triplets: &str, b: Vec<f64>) -> Result<String, JsValue> {
        // Parse matrix triplets from JSON
        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"));
        }

        // Determine matrix dimensions
        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",
            ));
        }

        // Create sparse matrix
        let matrix = SparseMatrix::from_triplets(triplets, n, n)
            .map_err(|e| JsValue::from_str(&format!("Matrix creation failed: {:?}", e)))?;

        // Create sublinear solver
        let solver = SublinearNeumannSolver::new(self.config.clone());

        // Solve with sublinear complexity
        let result = solver
            .solve_sublinear_guaranteed(&matrix, &b)
            .map_err(|e| JsValue::from_str(&format!("Solve failed: {:?}", e)))?;

        // Serialize result
        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)))
    }

    /// Verify that sublinear conditions are met
    #[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)))
            }
        }
    }

    /// Get compression ratio achieved by dimension reduction
    #[wasm_bindgen(js_name = getCompressionRatio)]
    pub fn get_compression_ratio(&self) -> f64 {
        self.config.target_dimension as f64 / 1000.0 // Assume typical input size
    }
}

/// Initialize the WASM module
#[wasm_bindgen(start)]
pub fn init() {
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();
}