407 lines
14 KiB
Rust
407 lines
14 KiB
Rust
// Copyright (c) 2024 RustyTorch++ Team
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
//! Comprehensive tests for GPU solvers using TDD methodology.
|
|
|
|
#[cfg(disabled)]
|
|
mod tests {
|
|
use super::super::*;
|
|
use crate::assembly::SparseMatrix;
|
|
use nalgebra::DVector;
|
|
|
|
/// Create a test symmetric positive definite matrix
|
|
fn create_spd_matrix(size: usize) -> SparseMatrix {
|
|
let mut matrix = SparseMatrix::new(size, size);
|
|
|
|
// Create a diagonally dominant SPD matrix
|
|
for i in 0..size {
|
|
matrix.add_entry(i, i, (size as f64) + 1.0).unwrap();
|
|
if i > 0 {
|
|
matrix.add_entry(i, i - 1, -1.0).unwrap();
|
|
matrix.add_entry(i - 1, i, -1.0).unwrap();
|
|
}
|
|
}
|
|
|
|
matrix.finalize().unwrap();
|
|
matrix
|
|
}
|
|
|
|
/// Create a test general matrix
|
|
fn create_general_matrix(size: usize) -> SparseMatrix {
|
|
let mut matrix = SparseMatrix::new(size, size);
|
|
|
|
// Create a non-symmetric matrix
|
|
for i in 0..size {
|
|
matrix.add_entry(i, i, (size as f64) + 1.0).unwrap();
|
|
if i > 0 {
|
|
matrix.add_entry(i, i - 1, -1.0).unwrap();
|
|
}
|
|
if i < size - 1 {
|
|
matrix.add_entry(i, i + 1, -2.0).unwrap();
|
|
}
|
|
}
|
|
|
|
matrix.finalize().unwrap();
|
|
matrix
|
|
}
|
|
|
|
/// Create a test right-hand side vector
|
|
fn create_rhs(size: usize) -> DVector<f64> {
|
|
DVector::from_fn(size, |i, _| (i as f64) + 1.0)
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_cholesky_solver_capabilities() {
|
|
let solver = GpuCholeskyDirect::new();
|
|
let caps = solver.capabilities();
|
|
|
|
assert!(caps.symmetric);
|
|
assert!(caps.positive_definite);
|
|
assert!(caps.gpu_acceleration);
|
|
assert!(caps.multiple_rhs);
|
|
assert!(caps.iterative_refinement);
|
|
assert!(caps.memory_efficiency >= 3);
|
|
assert!(caps.computational_efficiency >= 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_lu_solver_capabilities() {
|
|
let solver = GpuLuDirect::new();
|
|
let caps = solver.capabilities();
|
|
|
|
assert!(!caps.symmetric);
|
|
assert!(!caps.positive_definite);
|
|
assert!(caps.gpu_acceleration);
|
|
assert!(caps.multiple_rhs);
|
|
assert!(caps.iterative_refinement);
|
|
assert!(caps.memory_efficiency >= 2);
|
|
assert!(caps.computational_efficiency >= 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_ldlt_solver_capabilities() {
|
|
let solver = GpuLdltDirect::new();
|
|
let caps = solver.capabilities();
|
|
|
|
assert!(caps.symmetric);
|
|
assert!(!caps.positive_definite);
|
|
assert!(caps.gpu_acceleration);
|
|
assert!(caps.multiple_rhs);
|
|
assert!(caps.iterative_refinement);
|
|
assert!(caps.memory_efficiency >= 3);
|
|
assert!(caps.computational_efficiency >= 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_cg_solver_capabilities() {
|
|
let solver = GpuConjugateGradient::new();
|
|
let caps = solver.capabilities();
|
|
|
|
assert!(caps.symmetric);
|
|
assert!(caps.positive_definite);
|
|
assert!(caps.gpu_acceleration);
|
|
assert!(!caps.multiple_rhs); // CG typically handles single RHS
|
|
assert!(!caps.iterative_refinement); // CG is already iterative
|
|
assert!(caps.memory_efficiency >= 4);
|
|
assert!(caps.computational_efficiency >= 4);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_cholesky_solve_small_spd_matrix() {
|
|
let matrix = create_spd_matrix(3);
|
|
let rhs = create_rhs(3);
|
|
let options = SolverOptions::default();
|
|
|
|
let mut solver = GpuCholeskyDirect::new();
|
|
let result = solver.solve(&matrix, &rhs, &options);
|
|
|
|
match result {
|
|
Ok((solution, info)) => {
|
|
assert_eq!(solution.len(), 3);
|
|
assert!(info.converged);
|
|
assert_eq!(info.iterations, 1); // Direct solver
|
|
assert!(info.solve_time.as_secs_f64() >= 0.0);
|
|
assert!(info.memory_usage > 0);
|
|
|
|
// Verify solution quality: Ax = b
|
|
let computed_rhs = matrix.multiply_vector(&solution).unwrap();
|
|
let residual = (&computed_rhs - &rhs).norm();
|
|
assert!(residual < 1e-10, "Residual too large: {}", residual);
|
|
}
|
|
Err(_) => {
|
|
// GPU might not be available in test environment
|
|
// This is acceptable for CI environments
|
|
println!("GPU solver not available, test skipped");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_lu_solve_general_matrix() {
|
|
let matrix = create_general_matrix(3);
|
|
let rhs = create_rhs(3);
|
|
let options = SolverOptions::default();
|
|
|
|
let mut solver = GpuLuDirect::new();
|
|
let result = solver.solve(&matrix, &rhs, &options);
|
|
|
|
match result {
|
|
Ok((solution, info)) => {
|
|
assert_eq!(solution.len(), 3);
|
|
assert!(info.converged);
|
|
assert_eq!(info.iterations, 1); // Direct solver
|
|
assert!(info.solve_time.as_secs_f64() >= 0.0);
|
|
assert!(info.memory_usage > 0);
|
|
|
|
// Verify solution quality: Ax = b
|
|
let computed_rhs = matrix.multiply_vector(&solution).unwrap();
|
|
let residual = (&computed_rhs - &rhs).norm();
|
|
assert!(residual < 1e-10, "Residual too large: {}", residual);
|
|
}
|
|
Err(_) => {
|
|
// GPU might not be available in test environment
|
|
println!("GPU solver not available, test skipped");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_ldlt_solve_symmetric_indefinite() {
|
|
// Create a symmetric but not positive definite matrix
|
|
let mut matrix = SparseMatrix::new(3, 3);
|
|
matrix.add_entry(0, 0, 1.0).unwrap();
|
|
matrix.add_entry(0, 1, 2.0).unwrap();
|
|
matrix.add_entry(1, 0, 2.0).unwrap();
|
|
matrix.add_entry(1, 1, -1.0).unwrap(); // Negative diagonal element
|
|
matrix.add_entry(1, 2, 1.0).unwrap();
|
|
matrix.add_entry(2, 1, 1.0).unwrap();
|
|
matrix.add_entry(2, 2, 2.0).unwrap();
|
|
matrix.finalize().unwrap();
|
|
|
|
let rhs = create_rhs(3);
|
|
let options = SolverOptions::default();
|
|
|
|
let mut solver = GpuLdltDirect::new();
|
|
let result = solver.solve(&matrix, &rhs, &options);
|
|
|
|
match result {
|
|
Ok((solution, info)) => {
|
|
assert_eq!(solution.len(), 3);
|
|
assert!(info.converged);
|
|
assert_eq!(info.iterations, 1); // Direct solver
|
|
assert!(info.solve_time.as_secs_f64() >= 0.0);
|
|
assert!(info.memory_usage > 0);
|
|
|
|
// Verify solution quality: Ax = b
|
|
let computed_rhs = matrix.multiply_vector(&solution).unwrap();
|
|
let residual = (&computed_rhs - &rhs).norm();
|
|
assert!(residual < 1e-8, "Residual too large: {}", residual);
|
|
}
|
|
Err(_) => {
|
|
// GPU might not be available in test environment
|
|
println!("GPU solver not available, test skipped");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_gpu_cg_solve_spd_matrix() {
|
|
let matrix = create_spd_matrix(5);
|
|
let rhs = create_rhs(5);
|
|
let mut options = SolverOptions::default();
|
|
options.max_iterations = 100;
|
|
options.tolerance = 1e-8;
|
|
|
|
let mut solver = GpuConjugateGradient::new();
|
|
let result = solver.solve(&matrix, &rhs, &options);
|
|
|
|
match result {
|
|
Ok((solution, info)) => {
|
|
assert_eq!(solution.len(), 5);
|
|
assert!(info.solve_time.as_secs_f64() >= 0.0);
|
|
assert!(info.memory_usage > 0);
|
|
assert!(info.iterations <= options.max_iterations);
|
|
|
|
if info.converged {
|
|
// Verify solution quality: Ax = b
|
|
let computed_rhs = matrix.multiply_vector(&solution).unwrap();
|
|
let residual = (&computed_rhs - &rhs).norm();
|
|
assert!(residual < 1e-6, "Residual too large: {}", residual);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// GPU might not be available in test environment
|
|
println!("GPU solver not available, test skipped");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_dimension_mismatch_error() {
|
|
let matrix = create_spd_matrix(3);
|
|
let rhs = create_rhs(2); // Wrong size
|
|
let options = SolverOptions::default();
|
|
|
|
let mut solver = GpuCholeskyDirect::new();
|
|
let result = solver.solve(&matrix, &rhs, &options);
|
|
|
|
// Should fail due to dimension mismatch, regardless of GPU availability
|
|
match result {
|
|
Err(crate::error::FeaError::Solver(crate::error::SolverError::DimensionMismatch {
|
|
..
|
|
})) => {
|
|
// Expected error
|
|
}
|
|
Err(_) => {
|
|
// Might fail for other reasons if GPU not available
|
|
println!("GPU solver not available or other error occurred");
|
|
}
|
|
Ok(_) => {
|
|
panic!("Should have failed due to dimension mismatch");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_non_symmetric_matrix_error() {
|
|
let matrix = create_general_matrix(3);
|
|
let rhs = create_rhs(3);
|
|
let options = SolverOptions::default();
|
|
|
|
// Test that Cholesky solver rejects non-symmetric matrices
|
|
let mut chol_solver = GpuCholeskyDirect::new();
|
|
let chol_result = chol_solver.solve(&matrix, &rhs, &options);
|
|
|
|
match chol_result {
|
|
Err(crate::error::FeaError::Solver(crate::error::SolverError::MatrixNotSymmetric)) => {
|
|
// Expected error
|
|
}
|
|
Err(_) => {
|
|
// Might fail for other reasons if GPU not available
|
|
println!("GPU solver not available or other error occurred");
|
|
}
|
|
Ok(_) => {
|
|
panic!("Cholesky solver should reject non-symmetric matrices");
|
|
}
|
|
}
|
|
|
|
// Test that CG solver rejects non-symmetric matrices
|
|
let mut cg_solver = GpuConjugateGradient::new();
|
|
let cg_result = cg_solver.solve(&matrix, &rhs, &options);
|
|
|
|
match cg_result {
|
|
Err(crate::error::FeaError::Solver(crate::error::SolverError::MatrixNotSymmetric)) => {
|
|
// Expected error
|
|
}
|
|
Err(_) => {
|
|
// Might fail for other reasons if GPU not available
|
|
println!("GPU solver not available or other error occurred");
|
|
}
|
|
Ok(_) => {
|
|
panic!("CG solver should reject non-symmetric matrices");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_solver_name_consistency() {
|
|
let chol_solver = GpuCholeskyDirect::new();
|
|
assert_eq!(chol_solver.name(), "GPU cuSOLVER Cholesky Direct");
|
|
|
|
let lu_solver = GpuLuDirect::new();
|
|
assert_eq!(lu_solver.name(), "GPU cuSOLVER LU Direct");
|
|
|
|
let ldlt_solver = GpuLdltDirect::new();
|
|
assert_eq!(ldlt_solver.name(), "GPU cuSOLVER LDLT Direct");
|
|
|
|
let cg_solver = GpuConjugateGradient::new();
|
|
assert_eq!(cg_solver.name(), "GPU cuSPARSE Conjugate Gradient");
|
|
}
|
|
|
|
#[test]
|
|
fn test_solver_supports_gpu() {
|
|
let chol_solver = GpuCholeskyDirect::new();
|
|
let lu_solver = GpuLuDirect::new();
|
|
let ldlt_solver = GpuLdltDirect::new();
|
|
let cg_solver = GpuConjugateGradient::new();
|
|
|
|
// These should return true if GPU is available, false otherwise
|
|
// The exact value depends on the test environment
|
|
let _ = chol_solver.supports_gpu();
|
|
let _ = lu_solver.supports_gpu();
|
|
let _ = ldlt_solver.supports_gpu();
|
|
let _ = cg_solver.supports_gpu();
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_rhs_capability() {
|
|
let matrix = create_spd_matrix(3);
|
|
let mut rhs_matrix = nalgebra::DMatrix::zeros(3, 2);
|
|
rhs_matrix.set_column(0, &create_rhs(3));
|
|
rhs_matrix.set_column(1, &DVector::from_vec(vec![3.0, 2.0, 1.0]));
|
|
let options = SolverOptions::default();
|
|
|
|
let mut solver = GpuCholeskyDirect::new();
|
|
let result = solver.solve_multiple(&matrix, &rhs_matrix, &options);
|
|
|
|
match result {
|
|
Ok((solutions, info)) => {
|
|
assert_eq!(solutions.nrows(), 3);
|
|
assert_eq!(solutions.ncols(), 2);
|
|
assert!(info.converged);
|
|
assert!(info.solve_time.as_secs_f64() >= 0.0);
|
|
|
|
// Verify each solution
|
|
for col in 0..2 {
|
|
let solution = solutions.column(col);
|
|
let rhs = rhs_matrix.column(col);
|
|
let computed_rhs = matrix.multiply_vector(&solution.into_owned()).unwrap();
|
|
let residual = (&computed_rhs - &rhs.into_owned()).norm();
|
|
assert!(
|
|
residual < 1e-10,
|
|
"Residual too large for column {}: {}",
|
|
col,
|
|
residual
|
|
);
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// GPU might not be available in test environment
|
|
println!("GPU solver not available, test skipped");
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_iterative_refinement_option() {
|
|
let matrix = create_spd_matrix(3);
|
|
let rhs = create_rhs(3);
|
|
let mut options = SolverOptions::default();
|
|
options.iterative_refinement = true;
|
|
options.tolerance = 1e-12;
|
|
|
|
let mut solver = GpuCholeskyDirect::new();
|
|
let result = solver.solve(&matrix, &rhs, &options);
|
|
|
|
match result {
|
|
Ok((solution, info)) => {
|
|
assert_eq!(solution.len(), 3);
|
|
assert!(info.converged);
|
|
|
|
// With iterative refinement, the solution should be very accurate
|
|
let computed_rhs = matrix.multiply_vector(&solution).unwrap();
|
|
let residual = (&computed_rhs - &rhs).norm();
|
|
assert!(
|
|
residual < 1e-10,
|
|
"Residual too large with refinement: {}",
|
|
residual
|
|
);
|
|
}
|
|
Err(_) => {
|
|
// GPU might not be available in test environment
|
|
println!("GPU solver not available, test skipped");
|
|
}
|
|
}
|
|
}
|
|
}
|