rtx-cfd legacy GPU: kernel outputs were device-buffer CLONES (every write lost) — the buffers are borrowed mutably now; the Poisson kernels keep boundary Dirichlet values; tests: the advection pulse marched 50 steps, the Jacobi budget 20k, the reduction reference in f64
CI / Python Bindings (maturin) (ubuntu-latest) (push) Blocked by required conditions
CI / Build (macos-latest) (push) Waiting to run
CI / Test (macos-latest) (push) Blocked by required conditions
CI / Test (ubuntu-latest) (push) Blocked by required conditions
CI / Python Bindings (maturin) (macos-latest) (push) Blocked by required conditions
CI / WASM Build + Size Check (push) Blocked by required conditions
CI / Distributed Training Tests (push) Blocked by required conditions
CI / CI Success (push) Blocked by required conditions
CI / Build CPU-Only (Explicit) (push) Failing after 5s
Documentation / Build User Guide (push) Successful in 5s
Documentation / Build API Documentation (push) Failing after 5s
CI / Format Check (push) Failing after 10s
CI / Build (ubuntu-latest) (push) Failing after 1m22s
CI / Clippy Check (push) Failing after 1m38s
Performance Benchmarks / Run Benchmarks (push) Successful in 2m50s

This commit is contained in:
Omar Sobh
2026-09-16 07:28:42 -05:00
parent f3f6ac21fb
commit 0de750bad1
8 changed files with 58 additions and 62 deletions
@@ -25,9 +25,9 @@ extern "C" __global__ void poisson_jacobi_2d(
int idx = j * nx + i; int idx = j * nx + i;
// Boundary conditions (Dirichlet - zero on boundaries for now) // Boundary cells keep the Dirichlet values they hold.
if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1) { if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1) {
phi_new[idx] = 0.0f; phi_new[idx] = phi[idx];
return; return;
} }
@@ -66,7 +66,6 @@ extern "C" __global__ void poisson_gauss_seidel_2d(
// Boundary conditions // Boundary conditions
if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1) { if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1) {
phi[idx] = 0.0f;
return; return;
} }
@@ -105,7 +104,6 @@ extern "C" __global__ void poisson_sor_2d(
// Boundary conditions // Boundary conditions
if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1) { if (i == 0 || i == nx - 1 || j == 0 || j == ny - 1) {
phi[idx] = 0.0f;
return; return;
} }
@@ -147,7 +147,7 @@ impl PisoGpuSolver {
fn copy_to_gpu(&self, flow_field: &FlowField) -> CfdResult<()> { fn copy_to_gpu(&self, flow_field: &FlowField) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// Convert nalgebra matrices to flat vectors // Convert nalgebra matrices to flat vectors
@@ -171,7 +171,7 @@ impl PisoGpuSolver {
fn copy_from_gpu(&self, flow_field: &mut FlowField) -> CfdResult<()> { fn copy_from_gpu(&self, flow_field: &mut FlowField) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// Copy from GPU // Copy from GPU
@@ -271,7 +271,7 @@ impl PisoGpuSolver {
let (nx, ny) = { let (nx, ny) = {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
(buffers.nx, buffers.ny) (buffers.nx, buffers.ny)
}; };
@@ -313,7 +313,7 @@ impl PisoGpuSolver {
let residual_norm = { let residual_norm = {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
self.matrix_ops_kernel.vector_norm(&buffers.mass_source)? self.matrix_ops_kernel.vector_norm(&buffers.mass_source)?
}; };
@@ -464,8 +464,8 @@ impl PisoGpuSolver {
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&buffers.pressure_correction) .arg(&buffers.pressure_correction)
.arg(&mut buffers.u_correction.clone()) .arg(&mut buffers.u_correction)
.arg(&mut buffers.v_correction.clone()) .arg(&mut buffers.v_correction)
.arg(&correction_factor) .arg(&correction_factor)
.arg(&dx) .arg(&dx)
.arg(&dy) .arg(&dy)
@@ -136,7 +136,7 @@ impl SimpleGpuSolver {
fn copy_to_gpu(&self, flow_field: &FlowField) -> CfdResult<()> { fn copy_to_gpu(&self, flow_field: &FlowField) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// Convert nalgebra matrices to flat vectors // Convert nalgebra matrices to flat vectors
@@ -163,7 +163,7 @@ impl SimpleGpuSolver {
fn copy_from_gpu(&self, flow_field: &mut FlowField) -> CfdResult<()> { fn copy_from_gpu(&self, flow_field: &mut FlowField) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// Copy from GPU // Copy from GPU
@@ -300,7 +300,7 @@ impl SimpleGpuSolver {
async fn gpu_pressure_update(&self, pressure_relaxation: f32) -> CfdResult<()> { async fn gpu_pressure_update(&self, pressure_relaxation: f32) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// p = p + α_p * p' (pressure update with relaxation) // p = p + α_p * p' (pressure update with relaxation)
@@ -256,7 +256,7 @@ impl D2Q9GpuSolver {
fn gpu_collision_step(&self) -> CfdResult<()> { fn gpu_collision_step(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// First compute equilibrium distributions // First compute equilibrium distributions
@@ -284,7 +284,7 @@ impl D2Q9GpuSolver {
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f.clone()) .arg(&mut buffers.f)
.arg(&buffers.f_eq) .arg(&buffers.f_eq)
.arg(&(self.omega as f32)) .arg(&(self.omega as f32))
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
@@ -303,7 +303,7 @@ impl D2Q9GpuSolver {
pub fn gpu_streaming_step(&self) -> CfdResult<()> { pub fn gpu_streaming_step(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -327,7 +327,7 @@ impl D2Q9GpuSolver {
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f_temp.clone()) .arg(&mut buffers.f_temp)
.arg(&buffers.f) .arg(&buffers.f)
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
.arg(&(buffers.ny as i32)) .arg(&(buffers.ny as i32))
@@ -347,7 +347,7 @@ impl D2Q9GpuSolver {
fn gpu_compute_macroscopic(&self) -> CfdResult<()> { fn gpu_compute_macroscopic(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -371,9 +371,9 @@ impl D2Q9GpuSolver {
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.density.clone()) .arg(&mut buffers.density)
.arg(&mut buffers.velocity_x.clone()) .arg(&mut buffers.velocity_x)
.arg(&mut buffers.velocity_y.clone()) .arg(&mut buffers.velocity_y)
.arg(&buffers.f) .arg(&buffers.f)
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
.arg(&(buffers.ny as i32)) .arg(&(buffers.ny as i32))
@@ -394,7 +394,7 @@ impl D2Q9GpuSolver {
fn gpu_compute_equilibrium(&self) -> CfdResult<()> { fn gpu_compute_equilibrium(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -418,7 +418,7 @@ impl D2Q9GpuSolver {
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f_eq.clone()) .arg(&mut buffers.f_eq)
.arg(&buffers.density) .arg(&buffers.density)
.arg(&buffers.velocity_x) .arg(&buffers.velocity_x)
.arg(&buffers.velocity_y) .arg(&buffers.velocity_y)
@@ -438,7 +438,7 @@ impl D2Q9GpuSolver {
pub fn gpu_apply_bounce_back_boundaries(&self) -> CfdResult<()> { pub fn gpu_apply_bounce_back_boundaries(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -462,7 +462,7 @@ impl D2Q9GpuSolver {
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f.clone()) .arg(&mut buffers.f)
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
.arg(&(buffers.ny as i32)) .arg(&(buffers.ny as i32))
.launch(config) .launch(config)
@@ -492,7 +492,7 @@ impl D2Q9GpuSolver {
) -> CfdResult<()> { ) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -516,10 +516,10 @@ impl D2Q9GpuSolver {
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f.clone()) .arg(&mut buffers.f)
.arg(&mut buffers.density.clone()) .arg(&mut buffers.density)
.arg(&mut buffers.velocity_x.clone()) .arg(&mut buffers.velocity_x)
.arg(&mut buffers.velocity_y.clone()) .arg(&mut buffers.velocity_y)
.arg(&(density as f32)) .arg(&(density as f32))
.arg(&(velocity.x as f32)) .arg(&(velocity.x as f32))
.arg(&(velocity.y as f32)) .arg(&(velocity.y as f32))
@@ -539,7 +539,7 @@ impl D2Q9GpuSolver {
pub fn get_macroscopic_at(&self, i: usize, j: usize) -> CfdResult<(f64, Vector2<f64>)> { pub fn get_macroscopic_at(&self, i: usize, j: usize) -> CfdResult<(f64, Vector2<f64>)> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let idx = j * buffers.nx + i; let idx = j * buffers.nx + i;
@@ -585,7 +585,7 @@ impl D2Q9GpuSolver {
pub fn update_flow_field(&self, flow_field: &mut FlowField) -> CfdResult<()> { pub fn update_flow_field(&self, flow_field: &mut FlowField) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let (nx, ny, _, _) = flow_field.grid_info(); let (nx, ny, _, _) = flow_field.grid_info();
@@ -365,7 +365,7 @@ __global__ void d3q19_bounce_back_boundaries(
pub fn gpu_collision_step(&self) -> CfdResult<()> { pub fn gpu_collision_step(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let omega = 1.0 / self.params.tau; let omega = 1.0 / self.params.tau;
@@ -399,7 +399,7 @@ __global__ void d3q19_bounce_back_boundaries(
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f.clone()) .arg(&mut buffers.f)
.arg(&buffers.f_eq) .arg(&buffers.f_eq)
.arg(&(omega as f32)) .arg(&(omega as f32))
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
@@ -419,7 +419,7 @@ __global__ void d3q19_bounce_back_boundaries(
pub fn gpu_streaming_step(&self) -> CfdResult<()> { pub fn gpu_streaming_step(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -444,7 +444,7 @@ __global__ void d3q19_bounce_back_boundaries(
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f_temp.clone()) .arg(&mut buffers.f_temp)
.arg(&buffers.f) .arg(&buffers.f)
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
.arg(&(buffers.ny as i32)) .arg(&(buffers.ny as i32))
@@ -465,7 +465,7 @@ __global__ void d3q19_bounce_back_boundaries(
fn gpu_extract_macroscopic_variables(&self) -> CfdResult<()> { fn gpu_extract_macroscopic_variables(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -495,10 +495,10 @@ __global__ void d3q19_bounce_back_boundaries(
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.density.clone()) .arg(&mut buffers.density)
.arg(&mut buffers.velocity_x.clone()) .arg(&mut buffers.velocity_x)
.arg(&mut buffers.velocity_y.clone()) .arg(&mut buffers.velocity_y)
.arg(&mut buffers.velocity_z.clone()) .arg(&mut buffers.velocity_z)
.arg(&buffers.f) .arg(&buffers.f)
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
.arg(&(buffers.ny as i32)) .arg(&(buffers.ny as i32))
@@ -520,7 +520,7 @@ __global__ void d3q19_bounce_back_boundaries(
fn gpu_compute_equilibrium(&self) -> CfdResult<()> { fn gpu_compute_equilibrium(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -545,7 +545,7 @@ __global__ void d3q19_bounce_back_boundaries(
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f_eq.clone()) .arg(&mut buffers.f_eq)
.arg(&buffers.density) .arg(&buffers.density)
.arg(&buffers.velocity_x) .arg(&buffers.velocity_x)
.arg(&buffers.velocity_y) .arg(&buffers.velocity_y)
@@ -567,7 +567,7 @@ __global__ void d3q19_bounce_back_boundaries(
pub fn gpu_apply_bounce_back_boundaries(&self) -> CfdResult<()> { pub fn gpu_apply_bounce_back_boundaries(&self) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -591,7 +591,7 @@ __global__ void d3q19_bounce_back_boundaries(
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f.clone()) .arg(&mut buffers.f)
.arg(&(buffers.nx as i32)) .arg(&(buffers.nx as i32))
.arg(&(buffers.ny as i32)) .arg(&(buffers.ny as i32))
.arg(&(buffers.nz as i32)) .arg(&(buffers.nz as i32))
@@ -621,7 +621,7 @@ __global__ void d3q19_bounce_back_boundaries(
) -> CfdResult<()> { ) -> CfdResult<()> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let module = self let module = self
@@ -648,7 +648,7 @@ __global__ void d3q19_bounce_back_boundaries(
self.kernel_manager self.kernel_manager
.stream() .stream()
.launch_builder(&func) .launch_builder(&func)
.arg(&mut buffers.f.clone()) .arg(&mut buffers.f)
.arg(&(density as f32)) .arg(&(density as f32))
.arg(&(velocity.x as f32)) .arg(&(velocity.x as f32))
.arg(&(velocity.y as f32)) .arg(&(velocity.y as f32))
@@ -675,7 +675,7 @@ __global__ void d3q19_bounce_back_boundaries(
) -> CfdResult<MacroscopicVariables3D> { ) -> CfdResult<MacroscopicVariables3D> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
// Extract macroscopic variables first // Extract macroscopic variables first
@@ -726,7 +726,7 @@ __global__ void d3q19_bounce_back_boundaries(
pub fn gpu_total_mass(&self) -> CfdResult<f64> { pub fn gpu_total_mass(&self) -> CfdResult<f64> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
self.gpu_extract_macroscopic_variables()?; self.gpu_extract_macroscopic_variables()?;
@@ -739,7 +739,7 @@ __global__ void d3q19_bounce_back_boundaries(
pub fn gpu_kinetic_energy(&self) -> CfdResult<f64> { pub fn gpu_kinetic_energy(&self) -> CfdResult<f64> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
self.gpu_extract_macroscopic_variables()?; self.gpu_extract_macroscopic_variables()?;
@@ -429,7 +429,7 @@ impl KEpsilonGpuModel {
pub fn get_eddy_viscosity(&self) -> CfdResult<Vec<f32>> { pub fn get_eddy_viscosity(&self) -> CfdResult<Vec<f32>> {
let buffers = self let buffers = self
.gpu_buffers .gpu_buffers
.as_ref() .as_mut()
.ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?; .ok_or_else(|| CfdError::gpu_error("GPU buffers not initialized"))?;
let n = buffers.nx * buffers.ny * buffers.nz; let n = buffers.nx * buffers.ny * buffers.nz;
@@ -37,7 +37,7 @@ mod gpu_reduction_tests {
// Create test data // Create test data
let test_data: Vec<f32> = (1..=100).map(|i| i as f32).collect(); let test_data: Vec<f32> = (1..=100).map(|i| i as f32).collect();
let expected_sum = test_data.iter().sum::<f32>(); let expected_sum = test_data.iter().map(|&v| v as f64).sum::<f64>() as f32; // f64 reference: a sequential f32 sum of a million terms carries ~1e-3 of rounding
// Copy to GPU // Copy to GPU
let gpu_data = manager.copy_to_device(&test_data)?; let gpu_data = manager.copy_to_device(&test_data)?;
@@ -100,7 +100,7 @@ mod gpu_reduction_tests {
// Create large test array // Create large test array
let n = 1_000_000; let n = 1_000_000;
let test_data: Vec<f32> = (0..n).map(|i| (i % 100) as f32).collect(); let test_data: Vec<f32> = (0..n).map(|i| (i % 100) as f32).collect();
let expected_sum = test_data.iter().sum::<f32>(); let expected_sum = test_data.iter().map(|&v| v as f64).sum::<f64>() as f32; // f64 reference: a sequential f32 sum of a million terms carries ~1e-3 of rounding
// Copy to GPU // Copy to GPU
let gpu_data = manager.copy_to_device(&test_data)?; let gpu_data = manager.copy_to_device(&test_data)?;
@@ -46,17 +46,15 @@ mod cuda_tests {
d_phi = kernel_manager.copy_to_device(&phi)?; d_phi = kernel_manager.copy_to_device(&phi)?;
// Run advection kernel // Run advection kernel
advection_kernel.apply( // CFL = v dt / dx ≈ 0.1: march 50 steps so the pulse moves several cells.
&d_phi, for _ in 0..50 {
&mut d_phi_new, advection_kernel.apply(&d_phi, &mut d_phi_new, velocity as f32, dt as f32, dx as f32)?;
velocity as f32, std::mem::swap(&mut d_phi, &mut d_phi_new);
dt as f32, }
dx as f32,
)?;
// Copy result back // Copy result back
let mut result = vec![0.0f32; nx]; let mut result = vec![0.0f32; nx];
result = kernel_manager.copy_from_device(&d_phi_new)?; result = kernel_manager.copy_from_device(&d_phi)?;
// Verify that the pulse has moved (mass conservation) // Verify that the pulse has moved (mass conservation)
let initial_mass: f32 = phi.iter().sum(); let initial_mass: f32 = phi.iter().sum();
@@ -206,7 +204,7 @@ mod cuda_tests {
d_source = kernel_manager.copy_to_device(&source)?; d_source = kernel_manager.copy_to_device(&source)?;
// Solve Poisson equation // Solve Poisson equation
let max_iterations = 1000; let max_iterations = 20_000; // Jacobi needs O(n²) sweeps on this grid
let tolerance = 1e-6; let tolerance = 1e-6;
let iterations = poisson_kernel.solve_2d( let iterations = poisson_kernel.solve_2d(
&mut d_phi, &mut d_phi,