//! GPU-accelerated SIMPLE solver example //! //! This example demonstrates how to use the GPU-accelerated SIMPLE solver //! for solving the lid-driven cavity problem, a classic CFD benchmark. use rtx_cfd::solvers::incompressible::*; use rtx_cfd::*; #[tokio::main] async fn main() -> CfdResult<()> { println!("GPU-accelerated CFD Example"); println!("==========================="); // Create configuration let config = CfdConfig::new() .with_density(1.0) .with_viscosity(0.01) .with_reference_velocity(1.0) .with_reference_length(1.0) .with_device_id(0); println!("Configuration:"); println!(" Grid: {}x{}", config.nx, config.ny); println!(" Domain: {:.1}x{:.1}", config.lx, config.ly); println!( " Reynolds number: {:.0}", estimate_reynolds_number(&config) ); // Create SIMPLE solver parameters let params = SimpleParameters::new() .with_pressure_relaxation(0.3) // Under-relaxation for pressure .with_velocity_relaxation(0.7) // Under-relaxation for velocity .with_max_iterations(1000) // Maximum iterations .with_tolerance(1e-6) // Convergence tolerance .with_time_step(config.dt); // Try to create GPU solver, fall back to CPU if GPU not available match create_solver(config.clone(), params).await { Ok(mut solver) => { println!("Successfully created GPU solver"); run_lid_driven_cavity_simulation(solver, config).await?; } Err(e) => { println!("GPU solver creation failed: {}", e); println!("GPU features may not be available on this system"); run_cpu_fallback_demo(config).await?; } } Ok(()) } /// Create GPU solver or return error #[cfg(feature = "cuda")] async fn create_solver( config: CfdConfig, params: SimpleParameters, ) -> CfdResult { use rtx_cfd::solvers::incompressible::SimpleGpuSolver; SimpleGpuSolver::new(config, params) } #[cfg(not(feature = "cuda"))] async fn create_solver( _config: CfdConfig, _params: SimpleParameters, ) -> CfdResult { Err(CfdError::not_implemented( "CUDA not available - compile with --features cuda", )) } /// Run lid-driven cavity simulation with GPU solver #[cfg(feature = "cuda")] async fn run_lid_driven_cavity_simulation( mut solver: rtx_cfd::solvers::incompressible::SimpleGpuSolver, config: CfdConfig, ) -> CfdResult<()> { println!("\nRunning lid-driven cavity simulation..."); // Create flow field let dx = config.lx / (config.nx - 1) as f64; let dy = config.ly / (config.ny - 1) as f64; let mut flow_field = FlowField::new(config.nx, config.ny, dx, dy)?; // Set up lid-driven cavity initial conditions setup_lid_driven_cavity(&mut flow_field, 1.0)?; // Create boundary conditions (simplified) let boundary_conditions = BoundaryConditions::new(); println!("Initial conditions set up"); println!("Solving with GPU SIMPLE algorithm..."); // Solve let start_time = std::time::Instant::now(); let result = solver.solve(&mut flow_field, &boundary_conditions).await?; let total_time = start_time.elapsed(); // Display results println!("\nSolution completed!"); println!(" Converged: {}", result.solver_result.converged); println!(" Iterations: {}", result.solver_result.iterations); println!( " Final residual: {:.2e}", result.solver_result.final_residual ); println!(" Solve time: {:?}", result.solver_result.solve_time); println!(" Total time: {:?}", total_time); // Analyze results analyze_flow_field(&flow_field)?; // Optional: Save results save_results(&flow_field, "gpu_lid_cavity_results.dat")?; println!("\nResults saved to gpu_lid_cavity_results.dat"); Ok(()) } #[cfg(not(feature = "cuda"))] async fn run_lid_driven_cavity_simulation( _solver: rtx_cfd::solvers::incompressible::SimpleSolver, _config: CfdConfig, ) -> CfdResult<()> { unreachable!("This function should not be called without CUDA") } /// Run CPU fallback demonstration async fn run_cpu_fallback_demo(config: CfdConfig) -> CfdResult<()> { println!("\nRunning CPU fallback demonstration..."); let params = SimpleParameters::new() .with_max_iterations(100) // Fewer iterations for demo .with_tolerance(1e-4); let mut cpu_solver = SimpleSolver::new(config.clone(), params)?; let dx = config.lx / (config.nx - 1) as f64; let dy = config.ly / (config.ny - 1) as f64; let mut flow_field = FlowField::new(config.nx, config.ny, dx, dy)?; setup_lid_driven_cavity(&mut flow_field, 1.0)?; let boundary_conditions = BoundaryConditions::new(); println!("Solving with CPU SIMPLE algorithm (limited iterations)..."); let start_time = std::time::Instant::now(); let result = cpu_solver .solve(&mut flow_field, &boundary_conditions) .await?; let total_time = start_time.elapsed(); println!("\nCPU solution completed!"); println!(" Converged: {}", result.solver_result.converged); println!(" Iterations: {}", result.solver_result.iterations); println!( " Final residual: {:.2e}", result.solver_result.final_residual ); println!(" Solve time: {:?}", result.solver_result.solve_time); println!(" Total time: {:?}", total_time); Ok(()) } /// Set up lid-driven cavity initial and boundary conditions fn setup_lid_driven_cavity(flow_field: &mut FlowField, lid_velocity: f64) -> CfdResult<()> { let nx = flow_field.nx; let ny = flow_field.ny; // Initialize all velocities to zero except top wall for j in 0..ny { for i in 0..nx { let u = if j == ny - 1 { lid_velocity } else { 0.0 }; flow_field.set_velocity(i, j, u, 0.0)?; flow_field.set_pressure(i, j, 0.0)?; } } Ok(()) } /// Analyze flow field results fn analyze_flow_field(flow_field: &FlowField) -> CfdResult<()> { let nx = flow_field.nx; let ny = flow_field.ny; let mut max_u = 0.0_f64; let mut max_v = 0.0_f64; let mut max_p = 0.0_f64; let mut min_p = 0.0_f64; // Find extrema for j in 0..ny { for i in 0..nx { let (u, v) = flow_field.get_velocity_at(i, j)?; let p = flow_field.get_pressure_at(i, j)?; max_u = max_u.max(u.abs()); max_v = max_v.max(v.abs()); max_p = max_p.max(p); min_p = min_p.min(p); } } println!("\nFlow field analysis:"); println!(" Max |u|: {:.4}", max_u); println!(" Max |v|: {:.4}", max_v); println!(" Pressure range: [{:.4}, {:.4}]", min_p, max_p); // Check for reasonable values if max_u > 10.0 || max_v > 10.0 { println!(" Warning: Very large velocities detected"); } if max_u < 0.01 { println!(" Warning: Very small velocities - solution may not be converged"); } // Compute velocity at geometric center let center_i = nx / 2; let center_j = ny / 2; let (u_center, v_center) = flow_field.get_velocity_at(center_i, center_j)?; println!(" Velocity at center: u={:.4}, v={:.4}", u_center, v_center); Ok(()) } /// Save results to file fn save_results(flow_field: &FlowField, filename: &str) -> CfdResult<()> { use std::fs::File; use std::io::Write; let mut file = File::create(filename) .map_err(|e| CfdError::invalid_parameter(&format!("Failed to create file: {}", e)))?; // Write header writeln!(file, "# Lid-driven cavity results")?; writeln!(file, "# nx={}, ny={}", flow_field.nx, flow_field.ny)?; writeln!(file, "# Format: i j x y u v p")?; // Write data let nx = flow_field.nx; let ny = flow_field.ny; for j in 0..ny { for i in 0..nx { let x = i as f64 * flow_field.dx; let y = j as f64 * flow_field.dy; let (u, v) = flow_field.get_velocity_at(i, j)?; let p = flow_field.get_pressure_at(i, j)?; writeln!( file, "{} {} {:.6} {:.6} {:.6} {:.6} {:.6}", i, j, x, y, u, v, p )?; } } Ok(()) } /// Estimate Reynolds number based on configuration fn estimate_reynolds_number(config: &CfdConfig) -> f64 { let characteristic_length = config.lx; // Use domain length let characteristic_velocity = 1.0; // Lid velocity let kinematic_viscosity = config.viscosity / config.density; characteristic_velocity * characteristic_length / kinematic_viscosity }