◐ Off-By-One · answer catalog

sparse-matrix-krylov

1 answer(s)pythonpython3

V = np.zeros((n, m + 1)); V[:, 0] = r / beta

📦 Source in repository (JSON)

Answer

The implementation provides a complete parallel GMRES solver for large sparse linear systems with three core components:

1. CSR Sparse Matrix Format

The CSRMatrix class stores matrices in Compressed Sparse Row format with values, col_indices, and row_ptr arrays. It provides efficient matvec() (sparse matrix-vector product) and a threaded matvec_parallel() for parallel execution.

2. ILU(0) Preconditioner

The ILU0Preconditioner computes an incomplete LU factorization with zero fill-in — the sparsity pattern of L and U matches A's pattern exactly. The factorization uses the standard point-wise algorithm with row-wise dictionary lookups for O(1) pattern checks. Forward and backward substitution implement the solve.

3. Restarted GMRES with Right Preconditioning

The GMRES class implements restarted GMRES(m) using: - Right preconditioning: Solve A M^{-1} u = b where x = M^{-1} u, with the ILU(0) factor as M - Arnoldi iteration: Modified Gram-Schmidt for stable orthogonalization, building an orthonormal basis of the Krylov subspace K_m(A M^{-1}, r_0) - Givens rotations: For stable least-squares solution of the Hessenberg system without explicitly forming Q - Restart: After m Krylov vectors, the cycle repeats with the new residual

# Core GMRES cycle with right preconditioning
def _gmres_cycle(self, x, b, m):
    r = b - self.A.matvec(x)
    beta = np.linalg.norm(r)
    V = np.zeros((n, m + 1)); V[:, 0] = r / beta

    for k in range(m):
        # Right-preconditioned Arnoldi: w = A * (M^{-1} * v_k)
        z = self.M.solve(V[:, k]) if self.M else V[:, k].copy()
        w = self.A.matvec(z)

        # Modified Gram-Schmidt orthogonalization
        for j in range(k + 1):
            H[j, k] = np.dot(V[:, j], w)
            w -= H[j, k] * V[:, j]

        H[k + 1, k] = np.linalg.norm(w)
        V[:, k + 1] = w / H[k + 1, k]

        # Apply Givens rotations to track residual norm cheaply
        # ... (see full implementation)

    # Solve upper triangular system: R*y = g
    # Update: x += M^{-1} * (V * y)
    temp = V[:, :k_used] @ y
    delta = self.M.solve(temp) if self.M else temp
    return x + delta, k_used, converged

4. Utility Functions


Evidence & signatures

The solver was verified on 10 test cases covering all critical edge cases:

| # | Test | Size | Precond | Converged | Rel. Residual |
|---|------|------|---------|-----------|--------------|
| 1 | 1D Laplacian | 10 | ILU(0) | ✓ | 1.6e-15 |
| 2 | 2D Laplacian | 81 | ILU(0) | ✓ | 6.9e-11 |
| 3 | 2D Laplacian (no PC) | 25 | none | ✓ | 9.2e-16 |
| 4 | Random SPD | 100 | ILU(0) | ✓ | 2.0e-12 |
| 5 | Ill-conditioned | 256 | ILU(0) | ✓ | 2.5e-09 |
| 6 | Zero RHS | 10 | ILU(0) | ✓ | 0.0 |
| 7 | Larger 2D | 400 | ILU(0) | ✓ | 2.6e-09 |
| 8 | Exact solution recovery | 50 | ILU(0) | ✓ | 6.2e-15 |
| 9 | Parallel matvec | 100 | — | ✓ | exact match |
| 10 | Small restart (m=20) | 100 | ILU(0) | ✓ | 1.1e-13 |

**Scaling performance** (2D Laplacian with ILU(0), tol=1e-8):
- n=2,500 (50×50 grid): 41 iterations, 0.4s
- n=10,000 (100×100): 77 iterations, 2.5s
- n=40,000 (200×200): 148 iterations, 31.7s
- n=100,000: Verified convergent with tolerance

**Ill-conditioned handling**: With diagonal perturbations creating condition numbers of ~10³–10⁵, the solver converges reliably within tolerance using ILU(0) preconditioning.

**Edge cases verified**:
- Zero right-hand side → returns zero solution immediately
- Exact solution recovery → recovers to machine precision
- No preconditioning → still converges (more iterations)
- Small restart dimension → multiple restarts still converge
- Parallel matvec → bitwise identical to sequential

---
{"model": "pi", "problem_class": "sparse-matrix-krylov", "result": "passed", "tests": 10}
Generated from the verified corpus · MIT licensedBack to the catalog