1use std::f32::consts::PI;
12
13#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct GpuSphParticle {
18 pub pos: [f32; 3],
20 pub vel: [f32; 3],
22 pub density: f32,
24 pub pressure: f32,
26 pub mass: f32,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct GpuSphParams {
33 pub kernel_radius: f32,
35 pub eos_k: f32,
37 pub eos_gamma: f32,
39 pub viscosity: f32,
41 pub n_particles: usize,
43}
44
45#[derive(Debug, Clone)]
47pub struct GpuSphBuffer {
48 pub positions: Vec<[f32; 3]>,
50 pub velocities: Vec<[f32; 3]>,
52 pub densities: Vec<f32>,
54 pub pressures: Vec<f32>,
56}
57
58impl GpuSphBuffer {
59 pub fn new(n: usize) -> Self {
61 Self {
62 positions: vec![[0.0; 3]; n],
63 velocities: vec![[0.0; 3]; n],
64 densities: vec![0.0; n],
65 pressures: vec![0.0; n],
66 }
67 }
68
69 pub fn len(&self) -> usize {
71 self.positions.len()
72 }
73
74 pub fn is_empty(&self) -> bool {
76 self.positions.is_empty()
77 }
78}
79
80pub fn sph_kernel_gpu(r: f32, h: f32) -> f32 {
87 if h <= 0.0 || r >= h {
88 return 0.0;
89 }
90 let q = r / h;
91 let alpha = 21.0 / (2.0 * PI * h * h * h);
94 let t = 1.0 - 0.5 * q;
95 let t2 = t * t;
96 let t4 = t2 * t2;
97 alpha * t4 * (2.0 * q + 1.0)
98}
99
100pub fn sph_kernel_grad_gpu(r: f32, h: f32) -> f32 {
104 if h <= 0.0 || r >= h || r < 1e-12 {
105 return 0.0;
106 }
107 let q = r / h;
108 let alpha = 21.0 / (2.0 * PI * h * h * h);
109 let t = 1.0 - 0.5 * q;
110 let t2 = t * t;
111 let t3 = t2 * t;
112 alpha * t3 * (-5.0 * q) / h
120}
121
122pub fn compute_density_gpu(buf: &GpuSphBuffer, params: &GpuSphParams) -> Vec<f32> {
128 let n = buf.positions.len();
129 let h = params.kernel_radius;
130 let mass = 1.0_f32;
132 let mut densities = vec![0.0_f32; n];
133 for (rho, &pi) in densities.iter_mut().zip(buf.positions.iter()) {
134 let mut acc = 0.0_f32;
135 for j in 0..n {
136 let pj = buf.positions[j];
137 let dx = pi[0] - pj[0];
138 let dy = pi[1] - pj[1];
139 let dz = pi[2] - pj[2];
140 let r = (dx * dx + dy * dy + dz * dz).sqrt();
141 acc += mass * sph_kernel_gpu(r, h);
142 }
143 *rho = acc;
144 }
145 densities
146}
147
148pub fn compute_pressure_gpu(densities: &[f32], params: &GpuSphParams) -> Vec<f32> {
154 let rho0 = 1000.0_f32;
155 densities
156 .iter()
157 .map(|&rho| {
158 let ratio = rho / rho0;
159 params.eos_k * (ratio.powf(params.eos_gamma) - 1.0)
160 })
161 .collect()
162}
163
164pub fn compute_pressure_force_gpu(
171 buf: &GpuSphBuffer,
172 pressures: &[f32],
173 params: &GpuSphParams,
174) -> Vec<[f32; 3]> {
175 let n = buf.positions.len();
176 let h = params.kernel_radius;
177 let mass = 1.0_f32;
178 let mut forces = vec![[0.0_f32; 3]; n];
179
180 for (i, (force, &pi)) in forces.iter_mut().zip(buf.positions.iter()).enumerate() {
181 let rho_i = buf.densities[i].max(1e-6);
182 let p_i = pressures[i];
183 let mut fx = 0.0_f32;
184 let mut fy = 0.0_f32;
185 let mut fz = 0.0_f32;
186
187 for (j, &pj) in buf.positions.iter().enumerate() {
188 if i == j {
189 continue;
190 }
191 let dx = pi[0] - pj[0];
192 let dy = pi[1] - pj[1];
193 let dz = pi[2] - pj[2];
194 let r = (dx * dx + dy * dy + dz * dz).sqrt();
195 if r < 1e-12 {
196 continue;
197 }
198 let rho_j = buf.densities[j].max(1e-6);
199 let p_j = pressures[j];
200 let grad_mag = sph_kernel_grad_gpu(r, h);
201 let coeff = -mass * (p_i / (rho_i * rho_i) + p_j / (rho_j * rho_j)) * grad_mag / r;
202 fx += coeff * dx;
203 fy += coeff * dy;
204 fz += coeff * dz;
205 }
206 *force = [fx, fy, fz];
207 }
208 forces
209}
210
211pub fn compute_viscosity_force_gpu(buf: &GpuSphBuffer, params: &GpuSphParams) -> Vec<[f32; 3]> {
215 let n = buf.positions.len();
216 let h = params.kernel_radius;
217 let mu = params.viscosity;
218 let mass = 1.0_f32;
219 let mut forces = vec![[0.0_f32; 3]; n];
220
221 for (i, (force, (&pi, &vi))) in forces
222 .iter_mut()
223 .zip(buf.positions.iter().zip(buf.velocities.iter()))
224 .enumerate()
225 {
226 let rho_i = buf.densities[i].max(1e-6);
227 let mut fx = 0.0_f32;
228 let mut fy = 0.0_f32;
229 let mut fz = 0.0_f32;
230
231 for (j, (&pj, &vj)) in buf.positions.iter().zip(buf.velocities.iter()).enumerate() {
232 if i == j {
233 continue;
234 }
235 let dx = pi[0] - pj[0];
236 let dy = pi[1] - pj[1];
237 let dz = pi[2] - pj[2];
238 let r = (dx * dx + dy * dy + dz * dz).sqrt();
239 if r < 1e-12 {
240 continue;
241 }
242 let rho_j = buf.densities[j].max(1e-6);
243 let lap = sph_kernel_grad_gpu(r, h); let dvx = vj[0] - vi[0];
245 let dvy = vj[1] - vi[1];
246 let dvz = vj[2] - vi[2];
247 let coeff = mu * mass / rho_j * lap / r;
248 fx += coeff * dvx;
249 fy += coeff * dvy;
250 fz += coeff * dvz;
251 }
252 *force = [
253 force[0] + fx / rho_i,
254 force[1] + fy / rho_i,
255 force[2] + fz / rho_i,
256 ];
257 }
258 forces
259}
260
261pub fn integrate_sph_gpu(buf: &mut GpuSphBuffer, forces: &[[f32; 3]], dt: f32) {
268 for (vel, (pos, &f)) in buf
269 .velocities
270 .iter_mut()
271 .zip(buf.positions.iter_mut().zip(forces.iter()))
272 {
273 vel[0] += f[0] * dt;
274 vel[1] += f[1] * dt;
275 vel[2] += f[2] * dt;
276 pos[0] += vel[0] * dt;
277 pos[1] += vel[1] * dt;
278 pos[2] += vel[2] * dt;
279 }
280}
281
282pub fn gpu_sph_step(buf: &mut GpuSphBuffer, params: &GpuSphParams, dt: f32) {
288 let densities = compute_density_gpu(buf, params);
289 let pressures = compute_pressure_gpu(&densities, params);
290 buf.densities = densities;
291 buf.pressures = pressures.clone();
292 let pf = compute_pressure_force_gpu(buf, &pressures, params);
293 let vf = compute_viscosity_force_gpu(buf, params);
294 let n = buf.positions.len();
295 let mut total_forces = vec![[0.0_f32; 3]; n];
296 for i in 0..n {
297 total_forces[i][0] = pf[i][0] + vf[i][0];
298 total_forces[i][1] = pf[i][1] + vf[i][1];
299 total_forces[i][2] = pf[i][2] + vf[i][2];
300 }
301 integrate_sph_gpu(buf, &total_forces, dt);
302}
303
304#[cfg(test)]
307mod tests {
308 use super::*;
309
310 fn make_buf(n: usize) -> GpuSphBuffer {
311 let mut buf = GpuSphBuffer::new(n);
312 for i in 0..n {
313 buf.positions[i] = [i as f32 * 0.1, 0.0, 0.0];
314 buf.velocities[i] = [0.0; 3];
315 buf.densities[i] = 1000.0;
316 buf.pressures[i] = 0.0;
317 }
318 buf
319 }
320
321 fn default_params(n: usize) -> GpuSphParams {
322 GpuSphParams {
323 kernel_radius: 0.5,
324 eos_k: 1.0,
325 eos_gamma: 7.0,
326 viscosity: 0.01,
327 n_particles: n,
328 }
329 }
330
331 #[test]
332 fn test_gpu_sph_particle_fields() {
333 let p = GpuSphParticle {
334 pos: [1.0, 2.0, 3.0],
335 vel: [0.1, 0.2, 0.3],
336 density: 1000.0,
337 pressure: 101325.0,
338 mass: 0.001,
339 };
340 assert_eq!(p.pos[0], 1.0);
341 assert_eq!(p.mass, 0.001);
342 }
343
344 #[test]
345 fn test_gpu_sph_params_fields() {
346 let params = default_params(10);
347 assert_eq!(params.n_particles, 10);
348 assert!(params.kernel_radius > 0.0);
349 }
350
351 #[test]
352 fn test_gpu_sph_buffer_new() {
353 let buf = GpuSphBuffer::new(5);
354 assert_eq!(buf.len(), 5);
355 assert!(!buf.is_empty());
356 }
357
358 #[test]
359 fn test_gpu_sph_buffer_empty() {
360 let buf = GpuSphBuffer::new(0);
361 assert!(buf.is_empty());
362 }
363
364 #[test]
365 fn test_sph_kernel_gpu_zero_at_boundary() {
366 let w = sph_kernel_gpu(0.5, 0.5);
367 assert_eq!(w, 0.0);
368 }
369
370 #[test]
371 fn test_sph_kernel_gpu_zero_beyond() {
372 let w = sph_kernel_gpu(1.0, 0.5);
373 assert_eq!(w, 0.0);
374 }
375
376 #[test]
377 fn test_sph_kernel_gpu_positive_inside() {
378 let w = sph_kernel_gpu(0.1, 0.5);
379 assert!(w > 0.0);
380 }
381
382 #[test]
383 fn test_sph_kernel_gpu_peak_at_zero() {
384 let w0 = sph_kernel_gpu(0.0, 0.5);
385 let w1 = sph_kernel_gpu(0.2, 0.5);
386 assert!(w0 > w1);
387 }
388
389 #[test]
390 fn test_sph_kernel_gpu_zero_h() {
391 assert_eq!(sph_kernel_gpu(0.1, 0.0), 0.0);
392 }
393
394 #[test]
395 fn test_sph_kernel_grad_gpu_zero_h() {
396 assert_eq!(sph_kernel_grad_gpu(0.1, 0.0), 0.0);
397 }
398
399 #[test]
400 fn test_sph_kernel_grad_gpu_zero_r() {
401 assert_eq!(sph_kernel_grad_gpu(0.0, 0.5), 0.0);
402 }
403
404 #[test]
405 fn test_sph_kernel_grad_gpu_negative_inside() {
406 let g = sph_kernel_grad_gpu(0.2, 0.5);
408 assert!(g < 0.0);
409 }
410
411 #[test]
412 fn test_sph_kernel_grad_gpu_zero_at_boundary() {
413 let g = sph_kernel_grad_gpu(0.5, 0.5);
414 assert_eq!(g, 0.0);
415 }
416
417 #[test]
418 fn test_compute_density_gpu_nonzero() {
419 let buf = make_buf(4);
420 let params = default_params(4);
421 let densities = compute_density_gpu(&buf, ¶ms);
422 assert_eq!(densities.len(), 4);
423 assert!(densities.iter().any(|&d| d > 0.0));
425 }
426
427 #[test]
428 fn test_compute_density_gpu_length() {
429 let buf = make_buf(6);
430 let params = default_params(6);
431 let d = compute_density_gpu(&buf, ¶ms);
432 assert_eq!(d.len(), 6);
433 }
434
435 #[test]
436 fn test_compute_density_gpu_empty() {
437 let buf = GpuSphBuffer::new(0);
438 let params = default_params(0);
439 let d = compute_density_gpu(&buf, ¶ms);
440 assert!(d.is_empty());
441 }
442
443 #[test]
444 fn test_compute_pressure_gpu_positive() {
445 let params = default_params(3);
446 let densities = vec![1100.0_f32, 1000.0_f32, 900.0_f32];
447 let pressures = compute_pressure_gpu(&densities, ¶ms);
448 assert_eq!(pressures.len(), 3);
449 assert!(pressures[0] > pressures[1]);
451 }
452
453 #[test]
454 fn test_compute_pressure_gpu_rest_density() {
455 let params = default_params(1);
456 let d = vec![1000.0_f32]; let p = compute_pressure_gpu(&d, ¶ms);
458 assert!(p[0].abs() < 1e-3);
460 }
461
462 #[test]
463 fn test_compute_pressure_force_gpu_length() {
464 let buf = make_buf(4);
465 let params = default_params(4);
466 let pressures = vec![100.0_f32; 4];
467 let forces = compute_pressure_force_gpu(&buf, &pressures, ¶ms);
468 assert_eq!(forces.len(), 4);
469 }
470
471 #[test]
472 fn test_compute_viscosity_force_gpu_length() {
473 let buf = make_buf(4);
474 let params = default_params(4);
475 let forces = compute_viscosity_force_gpu(&buf, ¶ms);
476 assert_eq!(forces.len(), 4);
477 }
478
479 #[test]
480 fn test_integrate_sph_gpu_position_update() {
481 let mut buf = GpuSphBuffer::new(2);
482 buf.velocities[0] = [1.0, 0.0, 0.0];
483 buf.velocities[1] = [0.0, 1.0, 0.0];
484 let forces = vec![[0.0_f32; 3]; 2];
485 integrate_sph_gpu(&mut buf, &forces, 0.1);
486 assert!((buf.positions[0][0] - 0.1).abs() < 1e-5);
487 assert!((buf.positions[1][1] - 0.1).abs() < 1e-5);
488 }
489
490 #[test]
491 fn test_integrate_sph_gpu_velocity_update() {
492 let mut buf = GpuSphBuffer::new(1);
493 let forces = vec![[2.0_f32, 0.0, 0.0]];
494 integrate_sph_gpu(&mut buf, &forces, 0.5);
495 assert!((buf.velocities[0][0] - 1.0).abs() < 1e-5);
496 }
497
498 #[test]
499 fn test_gpu_sph_step_runs() {
500 let mut buf = make_buf(5);
501 let params = default_params(5);
502 gpu_sph_step(&mut buf, ¶ms, 0.001);
503 assert!(buf.densities.iter().any(|&d| d >= 0.0));
505 }
506
507 #[test]
508 fn test_gpu_sph_step_no_nan() {
509 let mut buf = make_buf(5);
510 let params = default_params(5);
511 gpu_sph_step(&mut buf, ¶ms, 0.001);
512 for i in 0..5 {
513 assert!(!buf.positions[i][0].is_nan());
514 assert!(!buf.densities[i].is_nan());
515 }
516 }
517
518 #[test]
519 fn test_gpu_sph_step_pressure_set() {
520 let mut buf = make_buf(3);
521 let params = default_params(3);
522 gpu_sph_step(&mut buf, ¶ms, 0.001);
523 assert_eq!(buf.pressures.len(), 3);
524 }
525
526 #[test]
527 fn test_sph_kernel_symmetry() {
528 let h = 0.5;
529 let w1 = sph_kernel_gpu(0.1, h);
530 let w2 = sph_kernel_gpu(0.1, h);
531 assert!((w1 - w2).abs() < 1e-8);
532 }
533
534 #[test]
535 fn test_sph_kernel_decreasing() {
536 let h = 1.0;
537 let mut prev = sph_kernel_gpu(0.0, h);
538 for i in 1..10 {
539 let r = i as f32 * 0.09;
540 let w = sph_kernel_gpu(r, h);
541 assert!(w <= prev + 1e-6);
542 prev = w;
543 }
544 }
545
546 #[test]
547 fn test_compute_density_uniform_grid() {
548 let n = 4;
550 let buf = GpuSphBuffer::new(n);
551 let params = default_params(n);
553 let d = compute_density_gpu(&buf, ¶ms);
554 for i in 1..n {
556 assert!((d[i] - d[0]).abs() < 1e-5);
557 }
558 let _ = buf.positions[0]; }
560
561 #[test]
562 fn test_pressure_force_zero_pressure() {
563 let buf = make_buf(3);
564 let params = default_params(3);
565 let pressures = vec![0.0_f32; 3];
566 let forces = compute_pressure_force_gpu(&buf, &pressures, ¶ms);
567 for f in &forces {
568 assert!(f[0].abs() < 1e-6 && f[1].abs() < 1e-6 && f[2].abs() < 1e-6);
569 }
570 }
571
572 #[test]
573 fn test_viscosity_force_same_velocity() {
574 let mut buf = make_buf(3);
576 for i in 0..3 {
577 buf.velocities[i] = [1.0, 0.5, 0.0];
578 }
579 let params = default_params(3);
580 let forces = compute_viscosity_force_gpu(&buf, ¶ms);
581 for f in &forces {
582 assert!(f[0].abs() < 1e-4 && f[1].abs() < 1e-4 && f[2].abs() < 1e-4);
583 }
584 }
585
586 #[test]
587 fn test_buf_clone() {
588 let buf = make_buf(3);
589 let buf2 = buf.clone();
590 assert_eq!(buf2.len(), 3);
591 }
592
593 #[test]
594 fn test_params_copy() {
595 let p = default_params(5);
596 let p2 = p;
597 assert_eq!(p2.n_particles, 5);
598 }
599
600 #[test]
601 fn test_integrate_multiple_steps() {
602 let mut buf = GpuSphBuffer::new(1);
603 buf.velocities[0] = [1.0, 0.0, 0.0];
604 let forces = vec![[0.0_f32; 3]];
605 for _ in 0..10 {
606 integrate_sph_gpu(&mut buf, &forces, 0.1);
607 }
608 assert!((buf.positions[0][0] - 1.0).abs() < 1e-4);
609 }
610}