/** * WebGPU Sparse Linear Solver — Conjugate Gradient on GPU * * Solves Ax = b for sparse symmetric positive-definite matrices using * the Conjugate Gradient method entirely on the GPU. * * Architecture: * - CSR matrix and all CG vectors live in GPU storage buffers * - CSR-Vector SpMV for irregular TET10 row lengths (multi-thread per row) * - Fused p-update kernel (p = r + beta*p in one dispatch) * - Two-phase dot product reduction with async staging buffer readback * - Correct r = b - A*x₀ initialization for non-zero initial guesses * * Bind group layout (matches cg_kernels.wgsl): * group(0): CSR matrix [val, col_ind, row_ptr] — SpMV only * group(1): Vectors [vec_in (read), vec_out (rw)] * group(2): SolverArgs uniform * group(3): Reduction [partial_sums, scalar_result] — dot/reduce only * * @module gpu/SparseLinearSolver */ import type { WebGPUContext } from './WebGPUContext.ts'; /** Compressed Sparse Row matrix on the CPU side */ export interface CSRMatrix { /** Non-zero values */ val: Float32Array; /** Column indices for each non-zero */ col_ind: Uint32Array; /** Row pointer array (length = num_rows + 1) */ row_ptr: Uint32Array; /** Number of rows (and columns, for square matrices) */ num_rows: number; } /** Result from a CG solve */ export interface CGSolveResult { /** Solution vector x */ x: Float32Array; /** Number of iterations actually executed */ iterations: number; /** Final residual norm squared (r . r) */ residualNormSq: number; /** Whether the solver converged within tolerance */ converged: boolean; } /** Result from a CG solve that returns a live GPU buffer */ export interface DirectSolverResult { /** Solution vector x on the GPU */ xBuffer: GPUBuffer; /** Number of iterations actually executed */ iterations: number; /** Final residual norm squared (r . r) */ residualNormSq: number; /** Whether the solver converged within tolerance */ converged: boolean; } /** Options for the CG solver */ export interface CGSolveOptions { /** Maximum number of CG iterations (default: 1000) */ maxIterations?: number; /** Convergence tolerance on ||r||^2 (default: 1e-10) */ toleranceSq?: number; /** * Check convergence every N iterations (default: 50). * Lower = more GPU→CPU readbacks (slower per iteration, faster convergence detection). * Higher = fewer readbacks (faster per iteration, may overshoot). */ convergenceCheckInterval?: number; /** Progress callback: (iteration, residualNormSq) => void */ onProgress?: (iteration: number, residualNormSq: number) => void; } export declare class SparseLinearSolver { private context; private device; private shaderModule; private spmvPipeline; private spmvVectorPipeline; private saxpyPipeline; private dotPipeline; private finalReducePipeline; private vecCopyPipeline; private vecZeroPipeline; private pUpdatePipeline; private extractInvDiagPipeline; private applyPrecondPipeline; private divideScalarPipeline; private saxpyBufPipeline; private saxpyNegBufPipeline; private pUpdateBufPipeline; private initialized; constructor(context: WebGPUContext); /** Compile shaders and create all compute pipelines */ initialize(): Promise; /** * Solve Ax = b using Conjugate Gradient on the GPU. * * Algorithm (Hestenes-Stiefel): * r₀ = b - A·x₀ * p₀ = r₀ * for k = 0, 1, 2, ... * Ap = A·p * α = (r·r) / (p·Ap) * x = x + α·p * r = r - α·Ap * if ||r||² < tol: break * β = (r_new·r_new) / (r·r) * p = r + β·p ← fused kernel */ solveCG(A: CSRMatrix, b: Float32Array, xGuess: Float32Array, options?: CGSolveOptions): Promise; /** * solveCGDirect — Direct GPU-to-GPU Conjugate Gradient solve. * * Same as solveCG but avoids CPU readback of the solution vector. * Returns the live GPUBuffer containing the result. * * @warning Caller is responsible for destroying the returned xBuffer. */ solveCGDirect(A: CSRMatrix, b: Float32Array, x0: Float32Array, options?: { maxIterations?: number; toleranceSq?: number; xExtraUsage?: GPUBufferUsageFlags; convergenceCheckInterval?: number; /** When true (default), converge on the RELATIVE residual ‖r‖² < tol²·‖b‖² * (standard CG; matches the CPU PCG path). Set false for absolute ‖r‖² < tol². */ relativeTolerance?: boolean; }): Promise; /** SpMV: groups 0 (CSR), 1 (vecs), 2 (args) */ private dispatchSpmv; /** SAXPY: groups 1 (vecs), 2 (args) */ private dispatchSaxpy; /** Fused p = r + beta*p: groups 1 (vecs), 2 (args) */ private dispatchPUpdate; /** Vec copy: groups 1 (vecs), 2 (args) */ private dispatchVecCopy; /** Extract inverse diagonal (Jacobi M⁻¹): groups 0 (CSR), 1 (binding 1 = out), 2 (binding 0 = args) */ private dispatchExtractInvDiag; /** Apply preconditioner z = invDiag ∘ r: group1 {r, z}, group2 {args, invDiag} */ private dispatchApplyPrecond; /** Scalar divide out = num/(den+eps): group2 {b2=num, b3=den, b4=out} */ private dispatchDivide; /** SAXPY with scalar from buffer: vec_out = (±)s·vec_in + vec_out. group1 {in,out}, group2 {args, scalar@b2} */ private dispatchSaxpyBuf; /** p = vec_in + s·p with scalar from buffer: group1 {in, p}, group2 {args, scalar@b2} */ private dispatchPUpdateBuf; /** * Encode a dot product v1·v2 → targetScalar into an existing encoder (NO submit, NO readback). * Phase 1 writes per-workgroup partials; phase 2 reduces into targetScalar[0]. * argsVec must encode n in .n; argsReduce must encode numWgDot in .n. */ private encodeDot; /** * Full dot product: v1·v2 * Phase 1: dot_product kernel → partial_sums (per-workgroup) * Phase 2: final_reduce → scalar_result[0] * Readback: staging mapAsync → CPU f32 */ private dotProduct; private writeArgs; uploadStorage(data: Float32Array | Uint32Array, label: string, extraUsage?: GPUBufferUsageFlags): GPUBuffer; emptyVec(n: number, label: string, extraUsage?: GPUBufferUsageFlags): GPUBuffer; /** Map an already-COPY_DST-populated 4-byte staging buffer and read one f32. */ private readMappedScalar; readback(buf: GPUBuffer, n: number): Promise; cleanup(buffers: GPUBuffer[]): void; destroy(): void; } //# sourceMappingURL=SparseLinearSolver.d.ts.map