1use super::models::SGSModel as SGSModelTrait;
7use crate::error::IntegrateResult;
8use scirs2_core::ndarray::{Array2, Array3, Array4};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum SGSModel {
13 Smagorinsky,
15 DynamicSmagorinsky,
17 WALE,
19 Vreman,
21}
22
23#[derive(Debug, Clone)]
25pub struct LESolver {
26 pub nx: usize,
28 pub ny: usize,
29 pub nz: usize,
30 pub dx: f64,
32 pub dy: f64,
33 pub dz: f64,
34 pub sgs_model: SGSModel,
36 pub filter_ratio: f64,
38 pub cs: f64,
40}
41
42#[derive(Debug, Clone)]
44pub struct FluidState3D {
45 pub velocity: Vec<Array3<f64>>,
47 pub pressure: Array3<f64>,
49 pub dx: f64,
51 pub dy: f64,
52 pub dz: f64,
53}
54
55impl LESolver {
56 pub fn new(
58 nx: usize,
59 ny: usize,
60 nz: usize,
61 dx: f64,
62 dy: f64,
63 dz: f64,
64 sgs_model: SGSModel,
65 ) -> Self {
66 Self {
67 nx,
68 ny,
69 nz,
70 dx,
71 dy,
72 dz,
73 sgs_model,
74 filter_ratio: 2.0,
75 cs: 0.1, }
77 }
78
79 pub fn solve_3d(
81 &self,
82 initial_state: FluidState3D,
83 final_time: f64,
84 n_steps: usize,
85 ) -> IntegrateResult<Vec<FluidState3D>> {
86 let dt = final_time / n_steps as f64;
87 let mut state = initial_state;
88 let mut results = Vec::with_capacity(n_steps + 1);
89 results.push(state.clone());
90
91 for _step in 0..n_steps {
92 let sgs_stress = self.compute_sgs_stress(&state)?;
94
95 state = self.update_velocity_3d(&state, &sgs_stress, dt)?;
97
98 state = Self::apply_boundary_conditions_3d(state)?;
100
101 results.push(state.clone());
103 }
104
105 Ok(results)
106 }
107
108 pub fn compute_sgs_stress(&self, state: &FluidState3D) -> IntegrateResult<Array4<f64>> {
110 let mut sgs_stress = Array4::zeros((3, 3, self.nx, self.ny));
111
112 match self.sgs_model {
113 SGSModel::Smagorinsky => {
114 self.compute_smagorinsky_stress(&mut sgs_stress, state)?;
115 }
116 SGSModel::DynamicSmagorinsky => {
117 self.compute_dynamic_smagorinsky_stress(&mut sgs_stress, state)?;
118 }
119 SGSModel::WALE => {
120 self.compute_wale_stress(&mut sgs_stress, state)?;
121 }
122 SGSModel::Vreman => {
123 self.compute_vreman_stress(&mut sgs_stress, state)?;
124 }
125 }
126
127 Ok(sgs_stress)
128 }
129
130 fn compute_smagorinsky_stress(
132 &self,
133 sgs_stress: &mut Array4<f64>,
134 state: &FluidState3D,
135 ) -> IntegrateResult<()> {
136 let delta = (self.dx * self.dy * self.dz).powf(1.0 / 3.0); let strain_rate = self.compute_strain_rate_tensor_3d(state)?;
140
141 for i in 0..self.nx {
142 for j in 0..self.ny {
143 for k in 0..self.nz {
144 let mut s_mag: f64 = 0.0;
146 for ii in 0..3 {
147 for jj in 0..3 {
148 s_mag +=
149 strain_rate[[ii, jj, i, j, k]] * strain_rate[[ii, jj, i, j, k]];
150 }
151 }
152 s_mag = (2.0 * s_mag).sqrt();
153
154 let nu_sgs = (self.cs * delta).powi(2) * s_mag;
156
157 for ii in 0..3 {
159 for jj in 0..3 {
160 sgs_stress[[ii, jj, i, j]] =
161 -2.0 * nu_sgs * strain_rate[[ii, jj, i, j, k]];
162 }
163 }
164 }
165 }
166 }
167
168 Ok(())
169 }
170
171 fn compute_dynamic_smagorinsky_stress(
173 &self,
174 sgs_stress: &mut Array4<f64>,
175 state: &FluidState3D,
176 ) -> IntegrateResult<()> {
177 let test_filter_ratio = 2.0;
179 let delta = (self.dx * self.dy * self.dz).powf(1.0 / 3.0);
180 let delta_test = test_filter_ratio * delta;
181
182 let filtered_velocity = self.apply_test_filter_3d(&state.velocity)?;
184
185 let leonard_stress = self.compute_leonard_stress_3d(state, &filtered_velocity)?;
187
188 let filtered_state = FluidState3D {
190 velocity: filtered_velocity,
191 pressure: state.pressure.clone(),
192 dx: state.dx,
193 dy: state.dy,
194 dz: state.dz,
195 };
196 let filtered_strain = self.compute_strain_rate_tensor_3d(&filtered_state)?;
197
198 let strain_rate = self.compute_strain_rate_tensor_3d(state)?;
200
201 for i in 0..self.nx {
202 for j in 0..self.ny {
203 for k in 0..self.nz {
204 let cs_dynamic = self.compute_dynamic_coefficient(
206 &leonard_stress,
207 &strain_rate,
208 &filtered_strain,
209 i,
210 j,
211 k,
212 delta,
213 delta_test,
214 )?;
215
216 let mut s_mag: f64 = 0.0;
218 for ii in 0..3 {
219 for jj in 0..3 {
220 s_mag +=
221 strain_rate[[ii, jj, i, j, k]] * strain_rate[[ii, jj, i, j, k]];
222 }
223 }
224 s_mag = (2.0 * s_mag).sqrt();
225
226 let nu_sgs = (cs_dynamic * delta).powi(2) * s_mag;
228
229 for ii in 0..3 {
231 for jj in 0..3 {
232 sgs_stress[[ii, jj, i, j]] =
233 -2.0 * nu_sgs * strain_rate[[ii, jj, i, j, k]];
234 }
235 }
236 }
237 }
238 }
239
240 Ok(())
241 }
242
243 fn compute_wale_stress(
245 &self,
246 sgs_stress: &mut Array4<f64>,
247 state: &FluidState3D,
248 ) -> IntegrateResult<()> {
249 let delta = (self.dx * self.dy * self.dz).powf(1.0 / 3.0);
250 let cw = 0.5; let grad_u = self.compute_velocity_gradient_3d(state)?;
254
255 for i in 0..self.nx {
256 for j in 0..self.ny {
257 for k in 0..self.nz {
258 let mut s_d = Array2::zeros((3, 3)); let mut omega = Array2::zeros((3, 3)); for ii in 0..3 {
263 for jj in 0..3 {
264 let grad_ij = grad_u[[ii, jj, i, j, k]];
265 let grad_ji = grad_u[[jj, ii, i, j, k]];
266
267 omega[[ii, jj]] = 0.5 * (grad_ij - grad_ji);
268 s_d[[ii, jj]] = 0.5 * (grad_ij + grad_ji);
269 }
270 }
271
272 let trace = (s_d[[0, 0]] + s_d[[1, 1]] + s_d[[2, 2]]) / 3.0;
274 for ii in 0..3 {
275 s_d[[ii, ii]] -= trace;
276 }
277
278 let mut s_d_mag_sq: f64 = 0.0;
280 let mut omega_mag_sq: f64 = 0.0;
281
282 for ii in 0..3 {
283 for jj in 0..3 {
284 s_d_mag_sq += s_d[[ii, jj]] * s_d[[ii, jj]];
285 omega_mag_sq += omega[[ii, jj]] * omega[[ii, jj]];
286 }
287 }
288
289 let numerator = s_d_mag_sq.powf(1.5);
291 let denominator = (s_d_mag_sq.powf(2.5) + omega_mag_sq.powf(1.25)).max(1e-12);
292 let nu_sgs = (cw * delta).powi(2) * numerator / denominator;
293
294 for ii in 0..3 {
296 for jj in 0..3 {
297 sgs_stress[[ii, jj, i, j]] = -2.0 * nu_sgs * s_d[[ii, jj]];
298 }
299 }
300 }
301 }
302 }
303
304 Ok(())
305 }
306
307 fn compute_vreman_stress(
309 &self,
310 sgs_stress: &mut Array4<f64>,
311 state: &FluidState3D,
312 ) -> IntegrateResult<()> {
313 let delta = (self.dx * self.dy * self.dz).powf(1.0 / 3.0);
314 let cv: f64 = 0.07; let grad_u = self.compute_velocity_gradient_3d(state)?;
318
319 for i in 0..self.nx {
320 for j in 0..self.ny {
321 for k in 0..self.nz {
322 let mut alpha = Array2::zeros((3, 3));
324 let mut beta: Array2<f64> = Array2::zeros((3, 3));
325
326 for ii in 0..3 {
327 for jj in 0..3 {
328 alpha[[ii, jj]] = grad_u[[ii, jj, i, j, k]];
329
330 for kk in 0..3 {
331 beta[[ii, jj]] +=
332 grad_u[[ii, kk, i, j, k]] * grad_u[[jj, kk, i, j, k]];
333 }
334 }
335 }
336
337 let alpha_norm_sq = alpha.iter().map(|&x| x * x).sum::<f64>();
339
340 let b_beta = beta[[0, 0]] * beta[[1, 1]]
341 + beta[[1, 1]] * beta[[2, 2]]
342 + beta[[0, 0]] * beta[[2, 2]]
343 - beta[[0, 1]].powi(2)
344 - beta[[1, 2]].powi(2)
345 - beta[[0, 2]].powi(2);
346
347 let nu_sgs = if alpha_norm_sq > 1e-12 {
349 cv.powi(2) * delta.powi(2) * (b_beta / alpha_norm_sq).sqrt()
350 } else {
351 0.0
352 };
353
354 for ii in 0..3 {
356 for jj in 0..3 {
357 let strain =
358 0.5 * (grad_u[[ii, jj, i, j, k]] + grad_u[[jj, ii, i, j, k]]);
359 sgs_stress[[ii, jj, i, j]] = -2.0 * nu_sgs * strain;
360 }
361 }
362 }
363 }
364 }
365
366 Ok(())
367 }
368
369 fn compute_strain_rate_tensor_3d(
371 &self,
372 state: &FluidState3D,
373 ) -> IntegrateResult<scirs2_core::ndarray::Array5<f64>> {
374 let mut strain_rate =
375 scirs2_core::ndarray::Array5::zeros((3, 3, self.nx, self.ny, self.nz));
376
377 for i in 1..(self.nx - 1) {
379 for j in 1..(self.ny - 1) {
380 for k in 1..(self.nz - 1) {
381 let dudx = (state.velocity[0][[i + 1, j, k]]
383 - state.velocity[0][[i - 1, j, k]])
384 / (2.0 * self.dx);
385 let dudy = (state.velocity[0][[i, j + 1, k]]
386 - state.velocity[0][[i, j - 1, k]])
387 / (2.0 * self.dy);
388 let dudz = (state.velocity[0][[i, j, k + 1]]
389 - state.velocity[0][[i, j, k - 1]])
390 / (2.0 * self.dz);
391
392 let dvdx = (state.velocity[1][[i + 1, j, k]]
393 - state.velocity[1][[i - 1, j, k]])
394 / (2.0 * self.dx);
395 let dvdy = (state.velocity[1][[i, j + 1, k]]
396 - state.velocity[1][[i, j - 1, k]])
397 / (2.0 * self.dy);
398 let dvdz = (state.velocity[1][[i, j, k + 1]]
399 - state.velocity[1][[i, j, k - 1]])
400 / (2.0 * self.dz);
401
402 let dwdx = (state.velocity[2][[i + 1, j, k]]
403 - state.velocity[2][[i - 1, j, k]])
404 / (2.0 * self.dx);
405 let dwdy = (state.velocity[2][[i, j + 1, k]]
406 - state.velocity[2][[i, j - 1, k]])
407 / (2.0 * self.dy);
408 let dwdz = (state.velocity[2][[i, j, k + 1]]
409 - state.velocity[2][[i, j, k - 1]])
410 / (2.0 * self.dz);
411
412 strain_rate[[0, 0, i, j, k]] = dudx;
414 strain_rate[[1, 1, i, j, k]] = dvdy;
415 strain_rate[[2, 2, i, j, k]] = dwdz;
416
417 strain_rate[[0, 1, i, j, k]] = 0.5 * (dudy + dvdx);
418 strain_rate[[1, 0, i, j, k]] = strain_rate[[0, 1, i, j, k]];
419
420 strain_rate[[0, 2, i, j, k]] = 0.5 * (dudz + dwdx);
421 strain_rate[[2, 0, i, j, k]] = strain_rate[[0, 2, i, j, k]];
422
423 strain_rate[[1, 2, i, j, k]] = 0.5 * (dvdz + dwdy);
424 strain_rate[[2, 1, i, j, k]] = strain_rate[[1, 2, i, j, k]];
425 }
426 }
427 }
428
429 Ok(strain_rate)
430 }
431
432 fn compute_velocity_gradient_3d(
434 &self,
435 state: &FluidState3D,
436 ) -> IntegrateResult<scirs2_core::ndarray::Array5<f64>> {
437 let mut grad_u = scirs2_core::ndarray::Array5::zeros((3, 3, self.nx, self.ny, self.nz));
438
439 for i in 1..(self.nx - 1) {
441 for j in 1..(self.ny - 1) {
442 for k in 1..(self.nz - 1) {
443 grad_u[[0, 0, i, j, k]] = (state.velocity[0][[i + 1, j, k]]
445 - state.velocity[0][[i - 1, j, k]])
446 / (2.0 * self.dx);
447 grad_u[[0, 1, i, j, k]] = (state.velocity[0][[i, j + 1, k]]
448 - state.velocity[0][[i, j - 1, k]])
449 / (2.0 * self.dy);
450 grad_u[[0, 2, i, j, k]] = (state.velocity[0][[i, j, k + 1]]
451 - state.velocity[0][[i, j, k - 1]])
452 / (2.0 * self.dz);
453
454 grad_u[[1, 0, i, j, k]] = (state.velocity[1][[i + 1, j, k]]
455 - state.velocity[1][[i - 1, j, k]])
456 / (2.0 * self.dx);
457 grad_u[[1, 1, i, j, k]] = (state.velocity[1][[i, j + 1, k]]
458 - state.velocity[1][[i, j - 1, k]])
459 / (2.0 * self.dy);
460 grad_u[[1, 2, i, j, k]] = (state.velocity[1][[i, j, k + 1]]
461 - state.velocity[1][[i, j, k - 1]])
462 / (2.0 * self.dz);
463
464 grad_u[[2, 0, i, j, k]] = (state.velocity[2][[i + 1, j, k]]
465 - state.velocity[2][[i - 1, j, k]])
466 / (2.0 * self.dx);
467 grad_u[[2, 1, i, j, k]] = (state.velocity[2][[i, j + 1, k]]
468 - state.velocity[2][[i, j - 1, k]])
469 / (2.0 * self.dy);
470 grad_u[[2, 2, i, j, k]] = (state.velocity[2][[i, j, k + 1]]
471 - state.velocity[2][[i, j, k - 1]])
472 / (2.0 * self.dz);
473 }
474 }
475 }
476
477 Ok(grad_u)
478 }
479
480 fn apply_test_filter_3d(&self, velocity: &[Array3<f64>]) -> IntegrateResult<Vec<Array3<f64>>> {
482 let mut filtered = vec![Array3::zeros((self.nx, self.ny, self.nz)); 3];
483
484 let filter_width = 2;
486 let filter_weight = 1.0 / (filter_width * filter_width * filter_width) as f64;
487
488 for comp in 0..3 {
489 for i in filter_width..(self.nx - filter_width) {
490 for j in filter_width..(self.ny - filter_width) {
491 for k in filter_width..(self.nz - filter_width) {
492 let mut sum: f64 = 0.0;
493 for di in 0..filter_width {
494 for dj in 0..filter_width {
495 for dk in 0..filter_width {
496 sum += velocity[comp][[
497 i - filter_width / 2 + di,
498 j - filter_width / 2 + dj,
499 k - filter_width / 2 + dk,
500 ]];
501 }
502 }
503 }
504 filtered[comp][[i, j, k]] = sum * filter_weight;
505 }
506 }
507 }
508 }
509
510 Ok(filtered)
511 }
512
513 fn compute_leonard_stress_3d(
515 &self,
516 state: &FluidState3D,
517 filtered_velocity: &[Array3<f64>],
518 ) -> IntegrateResult<Array4<f64>> {
519 let mut leonard = Array4::zeros((3, 3, self.nx, self.ny));
520
521 for i in 0..self.nx {
523 for j in 0..self.ny {
524 for k in 0..self.nz {
525 for ii in 0..3 {
526 for jj in 0..3 {
527 let unfiltered_product =
528 state.velocity[ii][[i, j, k]] * state.velocity[jj][[i, j, k]];
529 let filtered_product =
530 filtered_velocity[ii][[i, j, k]] * filtered_velocity[jj][[i, j, k]];
531 leonard[[ii, jj, i, j]] = unfiltered_product - filtered_product;
532 }
533 }
534 }
535 }
536 }
537
538 Ok(leonard)
539 }
540
541 fn compute_dynamic_coefficient(
543 &self,
544 leonard: &Array4<f64>,
545 strain_rate: &scirs2_core::ndarray::Array5<f64>,
546 filtered_strain: &scirs2_core::ndarray::Array5<f64>,
547 i: usize,
548 j: usize,
549 k: usize,
550 delta: f64,
551 delta_test: f64,
552 ) -> IntegrateResult<f64> {
553 let mut numerator = 0.0;
556 let mut denominator = 0.0;
557
558 for ii in 0..3 {
559 for jj in 0..3 {
560 let l_ij = leonard[[ii, jj, i, j]];
561 let s_ij = strain_rate[[ii, jj, i, j, k]];
562 let s_test_ij = filtered_strain[[ii, jj, i, j, k]];
563
564 let m_ij = delta.powi(2) * s_ij.abs() * s_ij
565 - delta_test.powi(2) * s_test_ij.abs() * s_test_ij;
566
567 numerator += l_ij * m_ij;
568 denominator += m_ij * m_ij;
569 }
570 }
571
572 if denominator > 1e-12 {
573 Ok((numerator / denominator).max(0.0).min(0.5)) } else {
575 Ok(self.cs) }
577 }
578
579 fn update_velocity_3d(
581 &self,
582 state: &FluidState3D,
583 sgs_stress: &Array4<f64>,
584 dt: f64,
585 ) -> IntegrateResult<FluidState3D> {
586 let mut new_velocity = state.velocity.clone();
587
588 for comp in 0..3 {
590 for i in 1..self.nx - 1 {
591 for j in 1..self.ny - 1 {
592 for k in 1..self.nz - 1 {
593 let mut sgs_divergence = 0.0;
595 for jj in 0..3 {
596 if jj == 0 && i > 0 && i < self.nx - 1 {
597 sgs_divergence += (sgs_stress[[comp, jj, i + 1, j]]
598 - sgs_stress[[comp, jj, i - 1, j]])
599 / (2.0 * self.dx);
600 }
601 if jj == 1 && j > 0 && j < self.ny - 1 {
602 sgs_divergence += (sgs_stress[[comp, jj, i, j + 1]]
603 - sgs_stress[[comp, jj, i, j - 1]])
604 / (2.0 * self.dy);
605 }
606 }
607
608 new_velocity[comp][[i, j, k]] += dt * sgs_divergence;
609 }
610 }
611 }
612 }
613
614 Ok(FluidState3D {
615 velocity: new_velocity,
616 pressure: state.pressure.clone(),
617 dx: state.dx,
618 dy: state.dy,
619 dz: state.dz,
620 })
621 }
622
623 fn apply_boundary_conditions_3d(mut state: FluidState3D) -> IntegrateResult<FluidState3D> {
625 let (nx, ny, nz) = state.velocity[0].dim();
626
627 for comp in 0..3 {
629 for j in 0..ny {
631 for k in 0..nz {
632 state.velocity[comp][[0, j, k]] = 0.0;
633 state.velocity[comp][[nx - 1, j, k]] = 0.0;
634 }
635 }
636
637 for i in 0..nx {
639 for k in 0..nz {
640 state.velocity[comp][[i, 0, k]] = 0.0;
641 state.velocity[comp][[i, ny - 1, k]] = 0.0;
642 }
643 }
644
645 for i in 0..nx {
647 for j in 0..ny {
648 state.velocity[comp][[i, j, 0]] = 0.0;
649 state.velocity[comp][[i, j, nz - 1]] = 0.0;
650 }
651 }
652 }
653
654 Ok(state)
655 }
656}
657
658impl SGSModelTrait for LESolver {
659 fn compute_sgs_viscosity(
660 &self,
661 velocity: &[Array3<f64>],
662 filter_width: f64,
663 dx: f64,
664 dy: f64,
665 dz: f64,
666 ) -> IntegrateResult<Array3<f64>> {
667 let (nx, ny, nz) = velocity[0].dim();
668 let mut nu_sgs = Array3::zeros((nx, ny, nz));
669
670 let state = FluidState3D {
672 velocity: velocity.to_vec(),
673 pressure: Array3::zeros((nx, ny, nz)),
674 dx,
675 dy,
676 dz,
677 };
678
679 match self.sgs_model {
680 SGSModel::Smagorinsky => {
681 let strain_rate = self.compute_strain_rate_tensor_3d(&state)?;
682
683 for i in 0..nx {
684 for j in 0..ny {
685 for k in 0..nz {
686 let mut s_mag: f64 = 0.0;
687 for ii in 0..3 {
688 for jj in 0..3 {
689 s_mag += strain_rate[[ii, jj, i, j, k]]
690 * strain_rate[[ii, jj, i, j, k]];
691 }
692 }
693 s_mag = (2.0 * s_mag).sqrt();
694 nu_sgs[[i, j, k]] = (self.cs * filter_width).powi(2) * s_mag;
695 }
696 }
697 }
698 }
699 _ => {
700 nu_sgs.fill(0.1 * filter_width.powi(2));
702 }
703 }
704
705 Ok(nu_sgs)
706 }
707
708 fn compute_sgs_stress(
709 &self,
710 velocity: &[Array3<f64>],
711 filter_width: f64,
712 dx: f64,
713 dy: f64,
714 dz: f64,
715 ) -> IntegrateResult<Array3<[[f64; 3]; 3]>> {
716 let (nx, ny, nz) = velocity[0].dim();
717 let mut sgs_stress = Array3::from_elem((nx, ny, nz), [[0.0; 3]; 3]);
718
719 let state = FluidState3D {
720 velocity: velocity.to_vec(),
721 pressure: Array3::zeros((nx, ny, nz)),
722 dx,
723 dy,
724 dz,
725 };
726
727 let stress_tensor = self.compute_sgs_stress(&state)?;
728
729 for i in 0..nx {
730 for j in 0..ny {
731 for ii in 0..3 {
732 for jj in 0..3 {
733 sgs_stress[[i, j, 0]][ii][jj] = stress_tensor[[ii, jj, i, j]];
734 }
735 }
736 }
737 }
738
739 Ok(sgs_stress)
740 }
741}
742
743#[cfg(test)]
744mod tests {
745 use super::*;
746
747 #[test]
748 fn test_les_solver_creation() {
749 let solver = LESolver::new(8, 8, 8, 0.1, 0.1, 0.1, SGSModel::Smagorinsky);
750 assert_eq!(solver.nx, 8);
751 assert_eq!(solver.sgs_model, SGSModel::Smagorinsky);
752 assert_eq!(solver.cs, 0.1);
753 }
754
755 #[test]
756 fn test_fluid_state_3d_creation() {
757 let velocity = vec![
758 Array3::zeros((4, 4, 4)),
759 Array3::zeros((4, 4, 4)),
760 Array3::zeros((4, 4, 4)),
761 ];
762 let state = FluidState3D {
763 velocity,
764 pressure: Array3::zeros((4, 4, 4)),
765 dx: 0.1,
766 dy: 0.1,
767 dz: 0.1,
768 };
769
770 assert_eq!(state.velocity.len(), 3);
771 assert_eq!(state.velocity[0].dim(), (4, 4, 4));
772 }
773
774 #[test]
775 fn test_sgs_model_enum() {
776 assert_eq!(SGSModel::Smagorinsky, SGSModel::Smagorinsky);
777 assert_ne!(SGSModel::Smagorinsky, SGSModel::WALE);
778 }
779
780 #[test]
781 fn test_sgs_viscosity_computation() {
782 let solver = LESolver::new(4, 4, 4, 0.1, 0.1, 0.1, SGSModel::Smagorinsky);
783 let velocity = vec![
784 Array3::ones((4, 4, 4)),
785 Array3::zeros((4, 4, 4)),
786 Array3::zeros((4, 4, 4)),
787 ];
788
789 let nu_sgs = solver.compute_sgs_viscosity(&velocity, 0.2, 0.1, 0.1, 0.1);
790 assert!(nu_sgs.is_ok());
791 let viscosity = nu_sgs.unwrap();
792 assert_eq!(viscosity.dim(), (4, 4, 4));
793 }
794}