1pub const D2Q9_Q: usize = 9;
14
15pub const D2Q9_WEIGHTS: [f64; D2Q9_Q] = [
19 4.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 9.0, 1.0 / 36.0, 1.0 / 36.0, 1.0 / 36.0, 1.0 / 36.0, ];
29
30pub const D2Q9_DIRS: [[i32; 2]; D2Q9_Q] = [
32 [0, 0], [1, 0], [-1, 0], [0, 1], [0, -1], [1, 1], [-1, 1], [-1, -1], [1, -1], ];
42
43pub const D2Q9_OPPOSITE: [usize; D2Q9_Q] = [0, 2, 1, 4, 3, 7, 8, 5, 6];
47
48#[derive(Debug, Clone, PartialEq)]
52pub struct D2Q9Weights {
53 pub weights: [f64; 9],
55 pub dirs: [[i32; 2]; 9],
57}
58
59impl D2Q9Weights {
60 pub fn standard() -> Self {
62 Self {
63 weights: D2Q9_WEIGHTS,
64 dirs: D2Q9_DIRS,
65 }
66 }
67
68 pub fn cs2() -> f64 {
70 1.0 / 3.0
71 }
72}
73
74impl Default for D2Q9Weights {
75 fn default() -> Self {
76 Self::standard()
77 }
78}
79
80#[derive(Debug, Clone, PartialEq)]
84pub struct LbmCell {
85 pub f_in: [f64; 9],
87 pub f_out: [f64; 9],
89}
90
91impl LbmCell {
92 pub fn new_equilibrium(rho: f64) -> Self {
94 let mut f = [0.0f64; 9];
95 for (i, w) in D2Q9_WEIGHTS.iter().enumerate() {
96 f[i] = w * rho;
97 }
98 Self { f_in: f, f_out: f }
99 }
100
101 pub fn density(&self) -> f64 {
103 self.f_in.iter().sum()
104 }
105
106 pub fn velocity(&self) -> [f64; 2] {
108 let rho = self.density();
109 if rho < 1e-15 {
110 return [0.0, 0.0];
111 }
112 let mut ux = 0.0f64;
113 let mut uy = 0.0f64;
114 for (i, &fi) in self.f_in.iter().enumerate() {
115 ux += fi * D2Q9_DIRS[i][0] as f64;
116 uy += fi * D2Q9_DIRS[i][1] as f64;
117 }
118 [ux / rho, uy / rho]
119 }
120}
121
122impl Default for LbmCell {
123 fn default() -> Self {
124 Self::new_equilibrium(1.0)
125 }
126}
127
128pub fn bgk_equilibrium(rho: f64, ux: f64, uy: f64, w: f64, cx: f64, cy: f64) -> f64 {
139 let cs2 = 1.0 / 3.0;
140 let cu = cx * ux + cy * uy;
141 let u2 = ux * ux + uy * uy;
142 w * rho * (1.0 + cu / cs2 + cu * cu / (2.0 * cs2 * cs2) - u2 / (2.0 * cs2))
143}
144
145pub fn relaxation_from_viscosity(nu: f64, cs2: f64) -> f64 {
149 1.0 / (nu / cs2 + 0.5)
150}
151
152#[derive(Debug, Clone)]
156pub struct GpuLbmGrid {
157 pub nx: usize,
159 pub ny: usize,
161 pub cells: Vec<LbmCell>,
163}
164
165impl GpuLbmGrid {
166 pub fn new(nx: usize, ny: usize, rho: f64) -> Self {
171 let cells = vec![LbmCell::new_equilibrium(rho); nx * ny];
172 Self { nx, ny, cells }
173 }
174
175 #[inline]
177 pub fn idx(&self, x: usize, y: usize) -> usize {
178 y * self.nx + x
179 }
180
181 pub fn collision_bgk(&mut self, omega: f64) {
184 for cell in &mut self.cells {
185 let rho = cell.density();
186 let [ux, uy] = cell.velocity();
187 for i in 0..9 {
188 let cx = D2Q9_DIRS[i][0] as f64;
189 let cy = D2Q9_DIRS[i][1] as f64;
190 let feq = bgk_equilibrium(rho, ux, uy, D2Q9_WEIGHTS[i], cx, cy);
191 cell.f_out[i] = cell.f_in[i] - omega * (cell.f_in[i] - feq);
192 }
193 }
194 }
195
196 pub fn stream_periodic(&mut self) {
201 let nx = self.nx;
202 let ny = self.ny;
203 let snapshot: Vec<[f64; 9]> = self.cells.iter().map(|c| c.f_out).collect();
205
206 for y in 0..ny {
207 for x in 0..nx {
208 let src_idx = y * nx + x;
209 for i in 0..9 {
210 let cx = D2Q9_DIRS[i][0];
211 let cy = D2Q9_DIRS[i][1];
212 let dest_x = ((x as i64 + cx as i64).rem_euclid(nx as i64)) as usize;
213 let dest_y = ((y as i64 + cy as i64).rem_euclid(ny as i64)) as usize;
214 let dest_idx = dest_y * nx + dest_x;
215 self.cells[dest_idx].f_in[i] = snapshot[src_idx][i];
216 }
217 }
218 }
219 }
220
221 pub fn step(&mut self, omega: f64) {
223 self.collision_bgk(omega);
224 self.stream_periodic();
225 }
226}
227
228#[derive(Debug, Clone, PartialEq)]
232pub enum LbmBoundary {
233 Periodic,
235 NoSlip,
237 ZouHe {
239 rho: f64,
241 ux: f64,
243 uy: f64,
245 },
246}
247
248#[derive(Debug, Clone)]
253pub struct GpuLbmDispatch {
254 pub grid: GpuLbmGrid,
256 pub boundary: Vec<LbmBoundary>,
258}
259
260impl GpuLbmDispatch {
261 pub fn new(nx: usize, ny: usize, rho: f64) -> Self {
264 let grid = GpuLbmGrid::new(nx, ny, rho);
265 let boundary = vec![LbmBoundary::Periodic; nx * ny];
266 Self { grid, boundary }
267 }
268
269 pub fn set_boundary(&mut self, x: usize, y: usize, bc: LbmBoundary) {
271 let idx = self.grid.idx(x, y);
272 self.boundary[idx] = bc;
273 }
274
275 pub fn dispatch_step(&mut self, omega: f64) {
278 self.apply_boundaries();
279 self.grid.step(omega);
280 }
281
282 fn apply_boundaries(&mut self) {
284 let nx = self.grid.nx;
285 let ny = self.grid.ny;
286 for y in 0..ny {
287 for x in 0..nx {
288 let idx = y * nx + x;
289 match &self.boundary[idx] {
290 LbmBoundary::Periodic => {}
291 LbmBoundary::NoSlip => {
292 let cell = &mut self.grid.cells[idx];
294 let old = cell.f_in;
295 for i in 0..9 {
296 cell.f_in[i] = old[D2Q9_OPPOSITE[i]];
297 }
298 }
299 LbmBoundary::ZouHe { rho, ux, uy } => {
300 let rho = *rho;
301 let ux = *ux;
302 let uy = *uy;
303 let cell = &mut self.grid.cells[idx];
305 for i in 0..9 {
306 let cx = D2Q9_DIRS[i][0] as f64;
307 let cy = D2Q9_DIRS[i][1] as f64;
308 cell.f_in[i] = bgk_equilibrium(rho, ux, uy, D2Q9_WEIGHTS[i], cx, cy);
309 }
310 }
311 }
312 }
313 }
314 }
315}
316
317pub struct LbmDiagnostics;
321
322impl LbmDiagnostics {
323 pub fn compute_vorticity(grid: &GpuLbmGrid) -> Vec<f64> {
328 let nx = grid.nx;
329 let ny = grid.ny;
330 let mut vort = vec![0.0f64; nx * ny];
331
332 for y in 0..ny {
333 for x in 0..nx {
334 let xp = (x + 1) % nx;
335 let xm = (x + nx - 1) % nx;
336 let yp = (y + 1) % ny;
337 let ym = (y + ny - 1) % ny;
338
339 let uy_xp = grid.cells[grid.idx(xp, y)].velocity()[1];
340 let uy_xm = grid.cells[grid.idx(xm, y)].velocity()[1];
341 let ux_yp = grid.cells[grid.idx(x, yp)].velocity()[0];
342 let ux_ym = grid.cells[grid.idx(x, ym)].velocity()[0];
343
344 let duy_dx = (uy_xp - uy_xm) / 2.0;
345 let dux_dy = (ux_yp - ux_ym) / 2.0;
346 vort[grid.idx(x, y)] = duy_dx - dux_dy;
347 }
348 }
349 vort
350 }
351
352 pub fn compute_divergence(grid: &GpuLbmGrid) -> Vec<f64> {
355 let nx = grid.nx;
356 let ny = grid.ny;
357 let mut div = vec![0.0f64; nx * ny];
358
359 for y in 0..ny {
360 for x in 0..nx {
361 let xp = (x + 1) % nx;
362 let xm = (x + nx - 1) % nx;
363 let yp = (y + 1) % ny;
364 let ym = (y + ny - 1) % ny;
365
366 let ux_xp = grid.cells[grid.idx(xp, y)].velocity()[0];
367 let ux_xm = grid.cells[grid.idx(xm, y)].velocity()[0];
368 let uy_yp = grid.cells[grid.idx(x, yp)].velocity()[1];
369 let uy_ym = grid.cells[grid.idx(x, ym)].velocity()[1];
370
371 let dux_dx = (ux_xp - ux_xm) / 2.0;
372 let duy_dy = (uy_yp - uy_ym) / 2.0;
373 div[grid.idx(x, y)] = dux_dx + duy_dy;
374 }
375 }
376 div
377 }
378
379 pub fn max_velocity(grid: &GpuLbmGrid) -> f64 {
381 grid.cells
382 .iter()
383 .map(|c| {
384 let [ux, uy] = c.velocity();
385 (ux * ux + uy * uy).sqrt()
386 })
387 .fold(0.0f64, f64::max)
388 }
389}
390
391#[cfg(test)]
394mod tests {
395 use super::*;
396
397 const EPS: f64 = 1e-12;
398
399 #[test]
402 fn test_d2q9_weights_sum_to_one() {
403 let sum: f64 = D2Q9_WEIGHTS.iter().sum();
404 assert!((sum - 1.0).abs() < EPS, "weights sum = {sum}");
405 }
406
407 #[test]
408 fn test_d2q9_weights_standard_construction() {
409 let lw = D2Q9Weights::standard();
410 assert_eq!(lw.weights, D2Q9_WEIGHTS);
411 assert_eq!(lw.dirs, D2Q9_DIRS);
412 }
413
414 #[test]
415 fn test_d2q9_default_equals_standard() {
416 let a = D2Q9Weights::default();
417 let b = D2Q9Weights::standard();
418 assert_eq!(a, b);
419 }
420
421 #[test]
422 fn test_d2q9_cs2() {
423 assert!((D2Q9Weights::cs2() - 1.0 / 3.0).abs() < EPS);
424 }
425
426 #[test]
427 fn test_d2q9_dirs_rest_is_zero() {
428 assert_eq!(D2Q9_DIRS[0], [0, 0]);
429 }
430
431 #[test]
432 fn test_d2q9_opposite_involution() {
433 for i in 0..9 {
434 assert_eq!(D2Q9_OPPOSITE[D2Q9_OPPOSITE[i]], i);
435 }
436 }
437
438 #[test]
439 fn test_d2q9_opposite_directions_are_negatives() {
440 for i in 0..9 {
441 let j = D2Q9_OPPOSITE[i];
442 assert_eq!(D2Q9_DIRS[j][0], -D2Q9_DIRS[i][0]);
443 assert_eq!(D2Q9_DIRS[j][1], -D2Q9_DIRS[i][1]);
444 }
445 }
446
447 #[test]
450 fn test_bgk_equilibrium_rest_state() {
451 let rho = 1.0;
453 for i in 0..9 {
454 let cx = D2Q9_DIRS[i][0] as f64;
455 let cy = D2Q9_DIRS[i][1] as f64;
456 let feq = bgk_equilibrium(rho, 0.0, 0.0, D2Q9_WEIGHTS[i], cx, cy);
457 assert!(
458 (feq - D2Q9_WEIGHTS[i]).abs() < EPS,
459 "i={i}: feq={feq}, w={}",
460 D2Q9_WEIGHTS[i]
461 );
462 }
463 }
464
465 #[test]
466 fn test_bgk_equilibrium_sum_is_rho() {
467 let rho = 1.5;
468 let ux = 0.1;
469 let uy = -0.05;
470 let sum: f64 = (0..9)
471 .map(|i| {
472 bgk_equilibrium(
473 rho,
474 ux,
475 uy,
476 D2Q9_WEIGHTS[i],
477 D2Q9_DIRS[i][0] as f64,
478 D2Q9_DIRS[i][1] as f64,
479 )
480 })
481 .sum();
482 assert!((sum - rho).abs() < 1e-10, "sum={sum}, rho={rho}");
483 }
484
485 #[test]
486 fn test_bgk_equilibrium_momentum_x_is_rho_ux() {
487 let rho = 1.2;
488 let ux = 0.08;
489 let uy = 0.0;
490 let mom_x: f64 = (0..9)
491 .map(|i| {
492 bgk_equilibrium(
493 rho,
494 ux,
495 uy,
496 D2Q9_WEIGHTS[i],
497 D2Q9_DIRS[i][0] as f64,
498 D2Q9_DIRS[i][1] as f64,
499 ) * D2Q9_DIRS[i][0] as f64
500 })
501 .sum();
502 assert!((mom_x - rho * ux).abs() < 1e-10, "mom_x={mom_x}");
503 }
504
505 #[test]
506 fn test_bgk_equilibrium_momentum_y_is_rho_uy() {
507 let rho = 1.0;
508 let ux = 0.0;
509 let uy = 0.05;
510 let mom_y: f64 = (0..9)
511 .map(|i| {
512 bgk_equilibrium(
513 rho,
514 ux,
515 uy,
516 D2Q9_WEIGHTS[i],
517 D2Q9_DIRS[i][0] as f64,
518 D2Q9_DIRS[i][1] as f64,
519 ) * D2Q9_DIRS[i][1] as f64
520 })
521 .sum();
522 assert!((mom_y - rho * uy).abs() < 1e-10, "mom_y={mom_y}");
523 }
524
525 #[test]
526 fn test_bgk_equilibrium_positive_for_small_u() {
527 for i in 0..9 {
528 let val = bgk_equilibrium(
529 1.0,
530 0.05,
531 0.05,
532 D2Q9_WEIGHTS[i],
533 D2Q9_DIRS[i][0] as f64,
534 D2Q9_DIRS[i][1] as f64,
535 );
536 assert!(val >= 0.0, "i={i}: feq={val}");
537 }
538 }
539
540 #[test]
543 fn test_relaxation_from_viscosity_nu0() {
544 let omega = relaxation_from_viscosity(0.0, 1.0 / 3.0);
546 assert!((omega - 2.0).abs() < EPS);
547 }
548
549 #[test]
550 fn test_relaxation_from_viscosity_typical() {
551 let cs2 = 1.0 / 3.0;
552 let nu = 1.0 / 6.0;
553 let omega = relaxation_from_viscosity(nu, cs2);
555 assert!((omega - 1.0).abs() < EPS);
556 }
557
558 #[test]
559 fn test_relaxation_from_viscosity_range() {
560 let cs2 = 1.0 / 3.0;
561 for nu_times_10 in 1..20usize {
562 let nu = nu_times_10 as f64 * 0.01;
563 let omega = relaxation_from_viscosity(nu, cs2);
564 assert!(omega > 0.0 && omega < 2.0, "nu={nu}, omega={omega}");
566 }
567 }
568
569 #[test]
572 fn test_lbm_cell_density_equilibrium() {
573 let rho = 1.3;
574 let cell = LbmCell::new_equilibrium(rho);
575 assert!((cell.density() - rho).abs() < EPS);
576 }
577
578 #[test]
579 fn test_lbm_cell_velocity_rest() {
580 let cell = LbmCell::new_equilibrium(1.0);
581 let [ux, uy] = cell.velocity();
582 assert!(ux.abs() < EPS);
583 assert!(uy.abs() < EPS);
584 }
585
586 #[test]
587 fn test_lbm_cell_default_density_one() {
588 let cell = LbmCell::default();
589 assert!((cell.density() - 1.0).abs() < EPS);
590 }
591
592 #[test]
593 fn test_lbm_cell_velocity_zero_density() {
594 let cell = LbmCell {
595 f_in: [0.0; 9],
596 f_out: [0.0; 9],
597 };
598 let [ux, uy] = cell.velocity();
599 assert_eq!([ux, uy], [0.0, 0.0]);
600 }
601
602 #[test]
605 fn test_gpu_lbm_grid_creation() {
606 let grid = GpuLbmGrid::new(8, 8, 1.0);
607 assert_eq!(grid.nx, 8);
608 assert_eq!(grid.ny, 8);
609 assert_eq!(grid.cells.len(), 64);
610 }
611
612 #[test]
613 fn test_gpu_lbm_grid_idx() {
614 let grid = GpuLbmGrid::new(4, 4, 1.0);
615 assert_eq!(grid.idx(0, 0), 0);
616 assert_eq!(grid.idx(3, 3), 15);
617 assert_eq!(grid.idx(1, 2), 9);
618 }
619
620 #[test]
621 fn test_collision_bgk_conserves_mass() {
622 let mut grid = GpuLbmGrid::new(4, 4, 1.0);
623 let mass_before: f64 = grid.cells.iter().map(|c| c.density()).sum();
624 grid.collision_bgk(1.0);
625 let mass_after: f64 = grid.cells.iter().map(|c| c.f_out.iter().sum::<f64>()).sum();
626 assert!((mass_before - mass_after).abs() < 1e-10);
627 }
628
629 #[test]
630 fn test_collision_bgk_equilibrium_state_unchanged() {
631 let mut grid = GpuLbmGrid::new(4, 4, 1.0);
633 let omega = 1.0;
634 grid.collision_bgk(omega);
635 for cell in &grid.cells {
636 for i in 0..9 {
637 assert!((cell.f_in[i] - cell.f_out[i]).abs() < EPS);
638 }
639 }
640 }
641
642 #[test]
643 fn test_stream_periodic_mass_conservation() {
644 let mut grid = GpuLbmGrid::new(6, 6, 1.0);
645 let mass_before: f64 = grid.cells.iter().map(|c| c.density()).sum();
646 for cell in &mut grid.cells {
648 cell.f_out = cell.f_in;
649 }
650 grid.stream_periodic();
651 let mass_after: f64 = grid.cells.iter().map(|c| c.density()).sum();
652 assert!((mass_before - mass_after).abs() < 1e-10);
653 }
654
655 #[test]
656 fn test_step_mass_conservation() {
657 let mut grid = GpuLbmGrid::new(8, 8, 1.0);
658 let mass_before: f64 = grid.cells.iter().map(|c| c.density()).sum();
659 grid.step(1.0);
660 let mass_after: f64 = grid.cells.iter().map(|c| c.density()).sum();
661 assert!((mass_before - mass_after).abs() < 1e-9);
662 }
663
664 #[test]
665 fn test_step_multiple_iterations() {
666 let mut grid = GpuLbmGrid::new(4, 4, 1.0);
667 let mass_before: f64 = grid.cells.iter().map(|c| c.density()).sum();
668 for _ in 0..10 {
669 grid.step(1.0);
670 }
671 let mass_after: f64 = grid.cells.iter().map(|c| c.density()).sum();
672 assert!((mass_before - mass_after).abs() < 1e-8);
673 }
674
675 #[test]
678 fn test_gpu_lbm_dispatch_creation() {
679 let dispatch = GpuLbmDispatch::new(4, 4, 1.0);
680 assert_eq!(dispatch.boundary.len(), 16);
681 assert!(
682 dispatch
683 .boundary
684 .iter()
685 .all(|b| *b == LbmBoundary::Periodic)
686 );
687 }
688
689 #[test]
690 fn test_dispatch_set_boundary() {
691 let mut d = GpuLbmDispatch::new(4, 4, 1.0);
692 d.set_boundary(1, 2, LbmBoundary::NoSlip);
693 assert_eq!(d.boundary[d.grid.idx(1, 2)], LbmBoundary::NoSlip);
694 }
695
696 #[test]
697 fn test_dispatch_zou_he_boundary() {
698 let mut d = GpuLbmDispatch::new(4, 4, 1.0);
699 d.set_boundary(
700 0,
701 0,
702 LbmBoundary::ZouHe {
703 rho: 1.05,
704 ux: 0.1,
705 uy: 0.0,
706 },
707 );
708 d.dispatch_step(1.0);
709 let total_mass: f64 = d.grid.cells.iter().map(|c| c.density()).sum();
713 assert!(total_mass.is_finite());
714 }
715
716 #[test]
717 fn test_dispatch_step_mass_finite() {
718 let mut d = GpuLbmDispatch::new(6, 6, 1.0);
719 d.set_boundary(0, 0, LbmBoundary::NoSlip);
720 d.dispatch_step(1.0);
721 for cell in &d.grid.cells {
722 assert!(cell.density().is_finite());
723 }
724 }
725
726 #[test]
729 fn test_vorticity_uniform_flow_is_zero() {
730 let grid = GpuLbmGrid::new(6, 6, 1.0);
732 let vort = LbmDiagnostics::compute_vorticity(&grid);
733 for v in &vort {
734 assert!(v.abs() < EPS, "vorticity={v}");
735 }
736 }
737
738 #[test]
739 fn test_vorticity_field_length() {
740 let grid = GpuLbmGrid::new(5, 7, 1.0);
741 let vort = LbmDiagnostics::compute_vorticity(&grid);
742 assert_eq!(vort.len(), 35);
743 }
744
745 #[test]
746 fn test_divergence_uniform_flow_is_zero() {
747 let grid = GpuLbmGrid::new(6, 6, 1.0);
748 let div = LbmDiagnostics::compute_divergence(&grid);
749 for d in &div {
750 assert!(d.abs() < EPS, "div={d}");
751 }
752 }
753
754 #[test]
755 fn test_divergence_field_length() {
756 let grid = GpuLbmGrid::new(3, 4, 1.0);
757 let div = LbmDiagnostics::compute_divergence(&grid);
758 assert_eq!(div.len(), 12);
759 }
760
761 #[test]
762 fn test_max_velocity_rest_state() {
763 let grid = GpuLbmGrid::new(4, 4, 1.0);
764 let mv = LbmDiagnostics::max_velocity(&grid);
765 assert!(mv.abs() < EPS, "max_vel={mv}");
766 }
767
768 #[test]
769 fn test_max_velocity_after_step() {
770 let mut grid = GpuLbmGrid::new(4, 4, 1.0);
771 grid.cells[0].f_in[1] += 0.1;
773 grid.step(1.0);
774 let mv = LbmDiagnostics::max_velocity(&grid);
775 assert!(mv.is_finite());
776 }
777
778 #[test]
781 fn test_macroscopic_recovery_rho() {
782 let mut cell = LbmCell {
784 f_in: [0.0; 9],
785 f_out: [0.0; 9],
786 };
787 cell.f_in[0] = 0.5;
788 cell.f_in[1] = 0.25;
789 cell.f_in[2] = 0.25;
790 assert!((cell.density() - 1.0).abs() < EPS);
791 }
792
793 #[test]
794 fn test_macroscopic_recovery_ux() {
795 let rho = 1.0;
797 let ux = 0.1;
798 let uy = 0.0;
799 let mut cell = LbmCell {
800 f_in: [0.0; 9],
801 f_out: [0.0; 9],
802 };
803 for i in 0..9 {
804 let cx = D2Q9_DIRS[i][0] as f64;
805 let cy = D2Q9_DIRS[i][1] as f64;
806 cell.f_in[i] = bgk_equilibrium(rho, ux, uy, D2Q9_WEIGHTS[i], cx, cy);
807 }
808 let [vx, vy] = cell.velocity();
809 assert!((vx - ux).abs() < 1e-10, "vx={vx}");
810 assert!(vy.abs() < 1e-10, "vy={vy}");
811 }
812
813 #[test]
814 fn test_macroscopic_recovery_uy() {
815 let rho = 1.0;
816 let ux = 0.0;
817 let uy = -0.07;
818 let mut cell = LbmCell {
819 f_in: [0.0; 9],
820 f_out: [0.0; 9],
821 };
822 for i in 0..9 {
823 let cx = D2Q9_DIRS[i][0] as f64;
824 let cy = D2Q9_DIRS[i][1] as f64;
825 cell.f_in[i] = bgk_equilibrium(rho, ux, uy, D2Q9_WEIGHTS[i], cx, cy);
826 }
827 let [vx, vy] = cell.velocity();
828 assert!(vx.abs() < 1e-10, "vx={vx}");
829 assert!((vy - uy).abs() < 1e-10, "vy={vy}");
830 }
831
832 #[test]
835 fn test_stream_periodic_translation() {
836 let nx = 4;
839 let ny = 1;
840 let mut grid = GpuLbmGrid::new(nx, ny, 1.0);
841
842 for cell in &mut grid.cells {
844 cell.f_in = [0.0; 9];
845 cell.f_out = [0.0; 9];
846 }
847 grid.cells[0].f_in[1] = 1.0; for cell in &mut grid.cells {
850 cell.f_out = cell.f_in;
851 }
852 grid.stream_periodic();
853
854 assert!((grid.cells[1].f_in[1] - 1.0).abs() < EPS);
856 assert!(grid.cells[0].f_in[1].abs() < EPS);
857 }
858
859 #[test]
860 fn test_stream_periodic_wrap() {
861 let nx = 4;
863 let ny = 1;
864 let mut grid = GpuLbmGrid::new(nx, ny, 1.0);
865 for cell in &mut grid.cells {
866 cell.f_in = [0.0; 9];
867 cell.f_out = [0.0; 9];
868 }
869 grid.cells[nx - 1].f_in[1] = 1.0;
870 for cell in &mut grid.cells {
871 cell.f_out = cell.f_in;
872 }
873 grid.stream_periodic();
874 assert!((grid.cells[0].f_in[1] - 1.0).abs() < EPS);
875 }
876}