1#[cfg(test)]
6mod tests {
7 use super::super::functions::*;
8 use crate::fluid_sim_gpu::FlipParticle;
9 use crate::fluid_sim_gpu::FluidSimConfig;
10 use crate::fluid_sim_gpu::FluidSimulation;
11 use crate::fluid_sim_gpu::GpuBoundaryBox;
12 use crate::fluid_sim_gpu::GpuNeighborList;
13 use crate::fluid_sim_gpu::LbmCellType;
14 use crate::fluid_sim_gpu::LbmD2Q9;
15 use crate::fluid_sim_gpu::MacGrid;
16 use crate::fluid_sim_gpu::MultiGpuDomain;
17 use crate::fluid_sim_gpu::SphConfig;
18 use crate::fluid_sim_gpu::SphKernels;
19 use crate::fluid_sim_gpu::SphParticle;
20 #[test]
21 fn test_poly6_inside() {
22 let w = SphKernels::poly6(0.0, 0.1);
23 assert!(w > 0.0);
24 }
25 #[test]
26 fn test_poly6_outside() {
27 let w = SphKernels::poly6(0.2, 0.1);
28 assert!((w).abs() < 1e-15);
29 }
30 #[test]
31 fn test_spiky_grad_inside() {
32 let r_vec = [0.05, 0.0, 0.0];
33 let g = SphKernels::spiky_grad(r_vec, 0.05, 0.1);
34 assert!(g[0] != 0.0);
35 }
36 #[test]
37 fn test_viscosity_laplacian() {
38 let lap = SphKernels::viscosity_laplacian(0.05, 0.1);
39 assert!(lap > 0.0);
40 }
41 #[test]
42 fn test_sph_compute_density_single_particle() {
43 let mut particles = vec![SphParticle::new([0.0; 3], 1.0)];
44 let config = SphConfig::default();
45 sph_compute_density(&mut particles, &config);
46 assert!(particles[0].density > 0.0);
47 }
48 #[test]
49 fn test_sph_step_does_not_panic() {
50 let mut particles = vec![
51 SphParticle::new([0.0, 0.0, 0.0], 0.001),
52 SphParticle::new([0.05, 0.0, 0.0], 0.001),
53 ];
54 let config = SphConfig::default();
55 sph_step(&mut particles, &config);
56 }
57 #[test]
58 fn test_lbm_d2q9_new() {
59 let lbm = LbmD2Q9::new(8, 8, 1.0);
60 assert_eq!(lbm.nx, 8);
61 assert_eq!(lbm.ny, 8);
62 }
63 #[test]
64 fn test_lbm_density_initial() {
65 let lbm = LbmD2Q9::new(4, 4, 1.0);
66 let rho = lbm.density(2, 2);
67 assert!((rho - 1.0).abs() < 1e-10);
68 }
69 #[test]
70 fn test_lbm_velocity_initial_zero() {
71 let lbm = LbmD2Q9::new(4, 4, 1.0);
72 let [ux, uy] = lbm.velocity(2, 2);
73 assert!(ux.abs() < 1e-10);
74 assert!(uy.abs() < 1e-10);
75 }
76 #[test]
77 fn test_lbm_f_equilibrium_rest() {
78 let f_eq = LbmD2Q9::f_equilibrium(1.0, 0.0, 0.0, 0);
79 assert!((f_eq - D2Q9_W[0]).abs() < 1e-12);
80 }
81 #[test]
82 fn test_lbm_step_does_not_panic() {
83 let mut lbm = LbmD2Q9::new(8, 8, 0.6);
84 lbm.set_inlet_velocity(0.05, 0.0);
85 lbm.step();
86 }
87 #[test]
88 fn test_lbm_bounce_back_solid() {
89 let mut lbm = LbmD2Q9::new(4, 4, 1.0);
90 lbm.cell_type[0] = LbmCellType::Solid;
91 lbm.apply_bounce_back();
92 }
93 #[test]
94 fn test_mac_grid_new() {
95 let g = MacGrid::new(4, 4, 4, 0.1);
96 assert_eq!(g.nx, 4);
97 assert_eq!(g.u.len(), 5 * 4 * 4);
98 }
99 #[test]
100 fn test_mac_grid_set_get_u() {
101 let mut g = MacGrid::new(4, 4, 4, 0.1);
102 g.set_u(2, 1, 1, 3.125);
103 assert!((g.get_u(2, 1, 1) - 3.125).abs() < 1e-12);
104 }
105 #[test]
106 fn test_mac_grid_compute_divergence_zero() {
107 let mut g = MacGrid::new(4, 4, 4, 0.1);
108 g.flags = vec![1u8; 4 * 4 * 4];
109 g.compute_divergence();
110 for &d in &g.div {
111 assert!(d.abs() < 1e-12);
112 }
113 }
114 #[test]
115 fn test_jacobi_pressure_solve() {
116 let mut g = MacGrid::new(4, 4, 4, 0.1);
117 g.flags = vec![1u8; 4 * 4 * 4];
118 g.jacobi_pressure_solve(1000.0, 0.01, 5);
119 }
120 #[test]
121 fn test_compute_vorticity_zero_vel() {
122 let g = MacGrid::new(8, 8, 8, 0.1);
123 let vort = compute_vorticity(&g);
124 assert_eq!(vort.len(), 8 * 8 * 8);
125 for v in &vort {
126 assert!(v[0].abs() < 1e-12);
127 }
128 }
129 #[test]
130 fn test_surface_tension_csf() {
131 let nx = 4;
132 let ny = 4;
133 let nz = 4;
134 let phi = vec![-1.0f64; nx * ny * nz];
135 let forces = surface_tension_csf(&phi, nx, ny, nz, 0.1, 0.0728);
136 assert_eq!(forces.len(), nx * ny * nz);
137 }
138 #[test]
139 fn test_flip_particle_new() {
140 let p = FlipParticle::new([1.0, 2.0, 3.0]);
141 assert_eq!(p.position, [1.0, 2.0, 3.0]);
142 assert_eq!(p.velocity, [0.0; 3]);
143 }
144 #[test]
145 fn test_p2g_g2p_roundtrip() {
146 let mut particles = vec![FlipParticle::new([0.25, 0.25, 0.25])];
147 particles[0].velocity = [1.0, 0.0, 0.0];
148 let mut grid = MacGrid::new(4, 4, 4, 0.1);
149 p2g_transfer(&particles, &mut grid);
150 }
151 #[test]
152 fn test_fluid_simulation_new() {
153 let config = FluidSimConfig::default();
154 let sim = FluidSimulation::new(config);
155 assert_eq!(sim.particles.len(), 0);
156 assert!((sim.time).abs() < 1e-15);
157 }
158 #[test]
159 fn test_fluid_simulation_add_block() {
160 let config = FluidSimConfig::default();
161 let mut sim = FluidSimulation::new(config);
162 sim.add_fluid_block([0.1; 3], [0.9; 3], 8);
163 assert!(sim.particles.len() >= 8);
164 }
165 #[test]
166 fn test_fluid_simulation_step() {
167 let config = FluidSimConfig {
168 grid_size: [8, 8, 8],
169 pressure_iters: 5,
170 ..Default::default()
171 };
172 let mut sim = FluidSimulation::new(config);
173 sim.add_fluid_block([0.1; 3], [0.5; 3], 4);
174 sim.step();
175 assert_eq!(sim.step_count, 1);
176 assert!(sim.time > 0.0);
177 }
178 #[test]
179 fn test_fluid_kinetic_energy_increases_under_gravity() {
180 let config = FluidSimConfig {
181 grid_size: [8, 8, 8],
182 pressure_iters: 5,
183 ..Default::default()
184 };
185 let mut sim = FluidSimulation::new(config);
186 sim.add_fluid_block([0.4; 3], [0.6; 3], 4);
187 let ke0 = sim.kinetic_energy();
188 sim.step();
189 let ke1 = sim.kinetic_energy();
190 assert!(ke1 >= ke0);
191 }
192 #[test]
193 fn test_advect_scalar_uniform_field() {
194 let nx = 4;
195 let ny = 4;
196 let nz = 4;
197 let grid = MacGrid::new(nx, ny, nz, 0.1);
198 let phi = vec![1.0f64; nx * ny * nz];
199 let result = advect_scalar(&phi, &grid, 0.01, [0.0, -9.81, 0.0]);
200 assert_eq!(result.len(), nx * ny * nz);
201 for v in &result {
202 assert!((v - 1.0).abs() < 1e-10);
203 }
204 }
205 #[test]
206 fn test_sph_particle_gravity_descent() {
207 let mut p = vec![SphParticle::new([0.0, 1.0, 0.0], 0.001)];
208 let config = SphConfig {
209 gravity: [0.0, -9.81, 0.0],
210 ..Default::default()
211 };
212 let y0 = p[0].position[1];
213 for _ in 0..100 {
214 sph_step(&mut p, &config);
215 }
216 assert!(p[0].position[1] < y0);
217 }
218 #[test]
219 fn test_lbm_outlet_pressure() {
220 let mut lbm = LbmD2Q9::new(8, 8, 0.8);
221 lbm.set_outlet_pressure(1.0);
222 assert_eq!(lbm.cell_type[7], LbmCellType::Outlet);
223 }
224 #[test]
225 fn test_mac_grid_pressure_project_no_panic() {
226 let mut g = MacGrid::new(4, 4, 4, 0.1);
227 g.flags = vec![1u8; 64];
228 g.pressure_project(1000.0, 0.01);
229 }
230 #[test]
231 fn test_gpu_sph_density_summation_parallel() {
232 let mut particles = vec![
233 SphParticle::new([0.0, 0.0, 0.0], 0.01),
234 SphParticle::new([0.05, 0.0, 0.0], 0.01),
235 ];
236 let config = SphConfig::default();
237 gpu_sph_density_parallel(&mut particles, &config);
238 assert!(particles[0].density > 0.0);
239 assert!(particles[1].density > 0.0);
240 }
241 #[test]
242 fn test_gpu_pressure_jacobi_single_iter() {
243 let mut g = MacGrid::new(4, 4, 4, 0.1);
244 g.flags = vec![1u8; 64];
245 gpu_jacobi_pressure_solve(&mut g, 1000.0, 0.01, 3);
246 assert_eq!(g.p.len(), 64);
247 }
248 #[test]
249 fn test_gpu_neighbor_list_update() {
250 let positions = vec![[0.0f64, 0.0, 0.0], [0.05, 0.0, 0.0], [0.3, 0.0, 0.0]];
251 let nl = GpuNeighborList::build(&positions, 0.1, [0.5, 0.5, 0.5]);
252 let neighbors = nl.neighbors_of(0);
253 assert!(neighbors.contains(&1));
254 assert!(!neighbors.contains(&2));
255 }
256 #[test]
257 fn test_gpu_lbm_bgk_collision() {
258 let mut lbm = LbmD2Q9::new(8, 8, 0.8);
259 gpu_lbm_bgk_collide(&mut lbm);
260 let rho = lbm.density(4, 4);
261 assert!((rho - 1.0).abs() < 1e-9);
262 }
263 #[test]
264 fn test_morton_sort_particles() {
265 let mut particles = vec![
266 SphParticle::new([0.9, 0.9, 0.9], 1.0),
267 SphParticle::new([0.0, 0.0, 0.0], 1.0),
268 SphParticle::new([0.5, 0.5, 0.5], 1.0),
269 ];
270 morton_sort_particles(&mut particles, [1.0, 1.0, 1.0]);
271 assert!(particles[0].position[0] < 0.2);
272 }
273 #[test]
274 fn test_gpu_particle_integrate_euler() {
275 let mut particles = vec![SphParticle::new([0.0, 0.0, 0.0], 1.0)];
276 particles[0].force = [1.0, 0.0, 0.0];
277 particles[0].density = 1.0;
278 gpu_particle_integrate_euler(&mut particles, 0.01);
279 assert!(particles[0].velocity[0] > 0.0);
280 assert!(particles[0].position[0] > 0.0);
281 }
282 #[test]
283 fn test_gpu_particle_integrate_verlet() {
284 let mut particles = vec![SphParticle::new([0.0, 1.0, 0.0], 1.0)];
285 particles[0].force = [0.0, -9.81, 0.0];
286 particles[0].density = 1.0;
287 let dt = 0.01;
288 let y0 = particles[0].position[1];
289 gpu_particle_integrate_verlet(&mut particles, dt, dt);
290 assert!(particles[0].position[1] < y0);
291 }
292 #[test]
293 fn test_gpu_boundary_condition_box() {
294 let mut particles = vec![SphParticle::new([1.5, 0.5, 0.5], 1.0)];
295 particles[0].velocity = [1.0, 0.0, 0.0];
296 let bounds = GpuBoundaryBox {
297 min: [0.0; 3],
298 max: [1.0; 3],
299 restitution: 0.5,
300 };
301 gpu_apply_boundary_box(&mut particles, &bounds);
302 assert!(particles[0].position[0] <= 1.0);
303 assert!(particles[0].velocity[0] <= 0.0);
304 }
305 #[test]
306 fn test_multi_gpu_domain_decomp_2_devices() {
307 let positions: Vec<[f64; 3]> = (0..8).map(|i| [i as f64 * 0.1, 0.0, 0.0]).collect();
308 let domains = MultiGpuDomain::decompose_x(&positions, 2);
309 assert_eq!(domains.len(), 2);
310 let total: usize = domains.iter().map(|d| d.particle_indices.len()).sum();
311 assert_eq!(total, 8);
312 }
313 #[test]
314 fn test_gpu_reduce_energy() {
315 let particles = [
316 SphParticle::new([0.0; 3], 1.0),
317 SphParticle::new([0.0; 3], 1.0),
318 ];
319 let mut p0 = particles[0].clone();
320 let mut p1 = particles[1].clone();
321 p0.velocity = [2.0, 0.0, 0.0];
322 p1.velocity = [0.0, 1.0, 0.0];
323 let particles2 = vec![p0, p1];
324 let ke = gpu_reduce_kinetic_energy(&particles2, 0.5);
325 assert!((ke - 1.25).abs() < 1e-10);
326 }
327 #[test]
328 fn test_gpu_reduce_momentum() {
329 let mut p = SphParticle::new([0.0; 3], 1.0);
330 p.velocity = [3.0, 0.0, 0.0];
331 let particles = vec![p];
332 let mom = gpu_reduce_momentum(&particles, 2.0);
333 assert!((mom[0] - 6.0).abs() < 1e-10);
334 }
335 #[test]
336 fn test_gpu_advect_field() {
337 let nx = 4usize;
338 let ny = 4;
339 let field: Vec<f64> = (0..nx * ny).map(|i| i as f64).collect();
340 let vel = vec![[0.0f64; 2]; nx * ny];
341 let result = gpu_advect_2d(&field, &vel, nx, ny, 0.1, 0.01);
342 assert_eq!(result.len(), nx * ny);
343 }
344 #[test]
345 fn test_gpu_pressure_poisson_staggered() {
346 let mut p_grid = vec![0.0f64; 4 * 4];
347 let div = vec![0.0f64; 4 * 4];
348 gpu_pressure_poisson_jacobi_2d(&mut p_grid, &div, 4, 4, 0.1, 5);
349 for &v in &p_grid {
350 assert!(v.abs() < 1e-10);
351 }
352 }
353 #[test]
354 fn test_neighbor_list_cell_counts() {
355 let positions: Vec<[f64; 3]> = (0..4).map(|i| [i as f64 * 0.02, 0.0, 0.0]).collect();
356 let nl = GpuNeighborList::build(&positions, 0.1, [0.5, 0.5, 0.5]);
357 let n0 = nl.neighbors_of(0);
358 assert_eq!(n0.len(), 3);
359 }
360 #[test]
361 fn test_morton_code_ordering() {
362 let a = morton_encode_3d(0, 0, 0);
363 let b = morton_encode_3d(1, 0, 0);
364 let c = morton_encode_3d(0, 1, 0);
365 assert!(a < b);
366 assert!(a < c);
367 }
368 #[test]
369 fn test_gpu_sph_verlet_energy_conservation_approx() {
370 let mut particles = vec![SphParticle::new([0.0, 0.5, 0.0], 0.01)];
371 let config = SphConfig {
372 gravity: [0.0, 0.0, 0.0],
373 ..Default::default()
374 };
375 sph_compute_density(&mut particles, &config);
376 sph_compute_pressure(&mut particles, &config);
377 let v_before = particles[0].velocity;
378 gpu_particle_integrate_verlet(&mut particles, 0.001, 0.001);
379 let v_after = particles[0].velocity;
380 assert!((v_before[1] - v_after[1]).abs() < 1e-8);
381 }
382 #[test]
383 fn test_domain_decomp_particles_go_to_correct_half() {
384 let positions: Vec<[f64; 3]> = vec![
385 [0.1, 0.0, 0.0],
386 [0.2, 0.0, 0.0],
387 [0.6, 0.0, 0.0],
388 [0.8, 0.0, 0.0],
389 ];
390 let domains = MultiGpuDomain::decompose_x(&positions, 2);
391 assert_eq!(domains[0].particle_indices.len(), 2);
392 assert_eq!(domains[1].particle_indices.len(), 2);
393 }
394}