Momentum Balance¶
The momentum balance is
where \(P_i\) and \(A_i\) are the pressure-gradient terms:
and the advective terms:
respectively.
\(D_{ij}\) is the diffusive term involving spatial differentiation in the \(j\)-th direction:
See the spatial discretization.
The temporal discretization for each Runge-Kutta iteration leads to
when all advective and diffusive terms are treated explicitly, while
when diffusive terms are treated implicitly.
Although we obtain a new velocity field, this does not satisfy the incompressibility in general, which necessitates the additional procedure below.
SMAC method¶
In addition to the incompressibility constraint, we need to somehow update the pressure field as well, which we do not have any equation such as:
To overcome these issues, we adopt the Simplified Marker And Cell (SMAC) method (Amsden and Harlow, J. Comput. Phys. (6), 1970), which is a two-step method.
Prediction Step¶
In the first step (prediction step), momentum equation is integrated in time, without taking care of the mass conservation, as discussed above. Basically the procedure is identical to how we handle the temperature field. First, the explicit and implicit terms are calculated and stored to the corresponding buffers:
363int compute_rhs_ux (
364 const domain_t * domain,
365 fluid_t * fluid
366) {
367 if (!laplacians.is_initialised) {
368 if (0 != init_laplacians(domain)) {
369 return 1;
370 }
371 }
372 const double * restrict ux = fluid->ux.data;
373 const double * restrict uy = fluid->uy.data;
374#if NDIMS == 3
375 const double * restrict uz = fluid->uz.data;
376#endif
377 const double * restrict p = fluid-> p.data;
378 const double * restrict t = fluid-> t.data;
379 // buffer for explicit terms
380 double * restrict srca = fluid->srcux.alpha.data;
381 // buffer for implicit terms
382 double * restrict srcg = fluid->srcux.gamma.data;
383 const double diffusivity = fluid_compute_momentum_diffusivity(fluid);
384 // advective contributions, always explicit
385 advection_x(domain, ux, srca);
386 advection_y(domain, ux, uy, srca);
387#if NDIMS == 3
388 advection_z(domain, ux, uz, srca);
389#endif
390 // pressure-gradient contribution, always implicit
391 pressure(domain, p, srcg);
392 // diffusive contributions, can be explicit or implicit
393 diffusion_x(domain, diffusivity, ux, param_m_implicit_x ? srcg : srca);
394 diffusion_y(domain, diffusivity, ux, param_m_implicit_y ? srcg : srca);
395#if NDIMS == 3
396 diffusion_z(domain, diffusivity, ux, param_m_implicit_z ? srcg : srca);
397#endif
398 // buoyancy contribution, always explicit
399 buoyancy(domain, t, srca);
400 return 0;
401}
325int compute_rhs_uy(
326 const domain_t * domain,
327 fluid_t * fluid
328) {
329 if (!laplacians.is_initialised) {
330 if (0 != init_laplacians(domain)) {
331 return 1;
332 }
333 }
334 const double * restrict ux = fluid->ux.data;
335 const double * restrict uy = fluid->uy.data;
336#if NDIMS == 3
337 const double * restrict uz = fluid->uz.data;
338#endif
339 const double * restrict p = fluid-> p.data;
340 // buffer for explicit terms
341 double * restrict srca = fluid->srcuy.alpha.data;
342 // buffer for implicit terms
343 double * restrict srcg = fluid->srcuy.gamma.data;
344 const double diffusivity = fluid_compute_momentum_diffusivity(fluid);
345 // advective contributions, always explicit
346 advection_x(domain, uy, ux, srca);
347 advection_y(domain, uy, srca);
348#if NDIMS == 3
349 advection_z(domain, uy, uz, srca);
350#endif
351 // pressure-gradient contribution, always implicit
352 pressure(domain, p, srcg);
353 // diffusive contributions, can be explicit or implicit
354 diffusion_x(domain, diffusivity, uy, param_m_implicit_x ? srcg : srca);
355 diffusion_y(domain, diffusivity, uy, param_m_implicit_y ? srcg : srca);
356#if NDIMS == 3
357 diffusion_z(domain, diffusivity, uy, param_m_implicit_z ? srcg : srca);
358#endif
359 return 0;
360}
255int compute_rhs_uz (
256 const domain_t * domain,
257 fluid_t * fluid
258) {
259 if (!laplacians.is_initialised) {
260 if (0 != init_laplacians(domain)) {
261 return 1;
262 }
263 }
264 const double * restrict ux = fluid->ux.data;
265 const double * restrict uy = fluid->uy.data;
266 const double * restrict uz = fluid->uz.data;
267 const double * restrict p = fluid-> p.data;
268 // buffer for explicit terms
269 double * restrict srca = fluid->srcuz.alpha.data;
270 // buffer for implicit terms
271 double * restrict srcg = fluid->srcuz.gamma.data;
272 const double diffusivity = fluid_compute_momentum_diffusivity(fluid);
273 // advective contributions, always explicit
274 advection_x(domain, uz, ux, srca);
275 advection_y(domain, uz, uy, srca);
276 advection_z(domain, uz, srca);
277 // pressure-gradient contribution, always implicit
278 pressure(domain, p, srcg);
279 // diffusive contributions, can be explicit or implicit
280 diffusion_x(domain, diffusivity, uz, param_m_implicit_x ? srcg : srca);
281 diffusion_y(domain, diffusivity, uz, param_m_implicit_y ? srcg : srca);
282 diffusion_z(domain, diffusivity, uz, param_m_implicit_z ? srcg : srca);
283 return 0;
284}
The stored values are used to compute \(\Delta u_i\):
436 const double coef_a = rkcoefs[rkstep].alpha;
437 const double coef_b = rkcoefs[rkstep].beta ;
438 const double coef_g = rkcoefs[rkstep].gamma;
439 const double * restrict srcuxa = fluid->srcux.alpha.data;
440 const double * restrict srcuxb = fluid->srcux.beta .data;
441 const double * restrict srcuxg = fluid->srcux.gamma.data;
442 const int isize = domain->mysizes[0];
443 const int jsize = domain->mysizes[1];
444#if NDIMS == 3
445 const int ksize = domain->mysizes[2];
446#endif
447 double * restrict dux = linear_system.x1pncl;
448#if NDIMS == 2
449 const size_t nitems = (isize - 1) * jsize;
450#else
451 const size_t nitems = (isize - 1) * jsize * ksize;
452#endif
453 for (size_t n = 0; n < nitems; n++) {
454 dux[n] =
455 + coef_a * dt * srcuxa[n]
456 + coef_b * dt * srcuxb[n]
457 + coef_g * dt * srcuxg[n];
458 }
395 const double coef_a = rkcoefs[rkstep].alpha;
396 const double coef_b = rkcoefs[rkstep].beta ;
397 const double coef_g = rkcoefs[rkstep].gamma;
398 const double * restrict srcuya = fluid->srcuy.alpha.data;
399 const double * restrict srcuyb = fluid->srcuy.beta .data;
400 const double * restrict srcuyg = fluid->srcuy.gamma.data;
401 const int isize = domain->mysizes[0];
402 const int jsize = domain->mysizes[1];
403#if NDIMS == 3
404 const int ksize = domain->mysizes[2];
405#endif
406 double * restrict duy = linear_system.x1pncl;
407#if NDIMS == 2
408 const size_t nitems = isize * jsize;
409#else
410 const size_t nitems = isize * jsize * ksize;
411#endif
412 for (size_t n = 0; n < nitems; n++) {
413 duy[n] =
414 + coef_a * dt * srcuya[n]
415 + coef_b * dt * srcuyb[n]
416 + coef_g * dt * srcuyg[n];
417 }
315const double coef_a = rkcoefs[rkstep].alpha;
316const double coef_b = rkcoefs[rkstep].beta ;
317const double coef_g = rkcoefs[rkstep].gamma;
318const double * restrict srcuza = fluid->srcuz.alpha.data;
319const double * restrict srcuzb = fluid->srcuz.beta .data;
320const double * restrict srcuzg = fluid->srcuz.gamma.data;
321const int isize = domain->mysizes[0];
322const int jsize = domain->mysizes[1];
323const int ksize = domain->mysizes[2];
324double * restrict duz = linear_system.x1pncl;
325const size_t nitems = isize * jsize * ksize;
326for (size_t n = 0; n < nitems; n++) {
327 duz[n] =
328 + coef_a * dt * srcuza[n]
329 + coef_b * dt * srcuzb[n]
330 + coef_g * dt * srcuzg[n];
331}
When necessary, linear systems are solved to take care of the implicit treatments:
465if (param_m_implicit_x) {
466 tdm_info_t * tdm_info = linear_system.tdm_x;
467 int size = 0;
468 double * restrict tdm_l = NULL;
469 double * restrict tdm_c = NULL;
470 double * restrict tdm_u = NULL;
471 tdm.get_size(tdm_info, &size);
472 tdm.get_l(tdm_info, &tdm_l);
473 tdm.get_c(tdm_info, &tdm_c);
474 tdm.get_u(tdm_info, &tdm_u);
475 const laplacian_t * lapx = laplacians.lapx;
476 for (int i = 0; i < size; i++) {
477 tdm_l[i] = - prefactor * lapx[i].l;
478 tdm_c[i] = 1. - prefactor * lapx[i].c;
479 tdm_u[i] = - prefactor * lapx[i].u;
480 }
481 tdm.solve(tdm_info, linear_system.x1pncl);
482}
484if (param_m_implicit_y) {
485 sdecomp.transpose.execute(
486 linear_system.transposer_x1_to_y1,
487 linear_system.x1pncl,
488 linear_system.y1pncl
489 );
490 tdm_info_t * tdm_info = linear_system.tdm_y;
491 int size = 0;
492 double * restrict tdm_l = NULL;
493 double * restrict tdm_c = NULL;
494 double * restrict tdm_u = NULL;
495 tdm.get_size(tdm_info, &size);
496 tdm.get_l(tdm_info, &tdm_l);
497 tdm.get_c(tdm_info, &tdm_c);
498 tdm.get_u(tdm_info, &tdm_u);
499 const laplacian_t * lapy = &laplacians.lapy;
500 for (int j = 0; j < size; j++) {
501 tdm_l[j] = - prefactor * (*lapy).l;
502 tdm_c[j] = 1. - prefactor * (*lapy).c;
503 tdm_u[j] = - prefactor * (*lapy).u;
504 }
505 tdm.solve(tdm_info, linear_system.y1pncl);
506 sdecomp.transpose.execute(
507 linear_system.transposer_y1_to_x1,
508 linear_system.y1pncl,
509 linear_system.x1pncl
510 );
511}
514if (param_m_implicit_z) {
515 sdecomp.transpose.execute(
516 linear_system.transposer_x1_to_z2,
517 linear_system.x1pncl,
518 linear_system.z2pncl
519 );
520 tdm_info_t * tdm_info = linear_system.tdm_z;
521 int size = 0;
522 double * restrict tdm_l = NULL;
523 double * restrict tdm_c = NULL;
524 double * restrict tdm_u = NULL;
525 tdm.get_size(tdm_info, &size);
526 tdm.get_l(tdm_info, &tdm_l);
527 tdm.get_c(tdm_info, &tdm_c);
528 tdm.get_u(tdm_info, &tdm_u);
529 const laplacian_t * lapz = &laplacians.lapz;
530 for (int k = 0; k < size; k++) {
531 tdm_l[k] = - prefactor * (*lapz).l;
532 tdm_c[k] = 1. - prefactor * (*lapz).c;
533 tdm_u[k] = - prefactor * (*lapz).u;
534 }
535 tdm.solve(tdm_info, linear_system.z2pncl);
536 sdecomp.transpose.execute(
537 linear_system.transposer_z2_to_x1,
538 linear_system.z2pncl,
539 linear_system.x1pncl
540 );
541}
424if (param_m_implicit_x) {
425 tdm_info_t * tdm_info = linear_system.tdm_x;
426 int size = 0;
427 double * restrict tdm_l = NULL;
428 double * restrict tdm_c = NULL;
429 double * restrict tdm_u = NULL;
430 tdm.get_size(tdm_info, &size);
431 tdm.get_l(tdm_info, &tdm_l);
432 tdm.get_c(tdm_info, &tdm_c);
433 tdm.get_u(tdm_info, &tdm_u);
434 const laplacian_t * lapx = laplacians.lapx;
435 for (int i = 0; i < size; i++) {
436 tdm_l[i] = - prefactor * lapx[i].l;
437 tdm_c[i] = 1. - prefactor * lapx[i].c;
438 tdm_u[i] = - prefactor * lapx[i].u;
439 }
440 tdm.solve(tdm_info, linear_system.x1pncl);
441}
443if (param_m_implicit_y) {
444 sdecomp.transpose.execute(
445 linear_system.transposer_x1_to_y1,
446 linear_system.x1pncl,
447 linear_system.y1pncl
448 );
449 tdm_info_t * tdm_info = linear_system.tdm_y;
450 int size = 0;
451 double * restrict tdm_l = NULL;
452 double * restrict tdm_c = NULL;
453 double * restrict tdm_u = NULL;
454 tdm.get_size(tdm_info, &size);
455 tdm.get_l(tdm_info, &tdm_l);
456 tdm.get_c(tdm_info, &tdm_c);
457 tdm.get_u(tdm_info, &tdm_u);
458 const laplacian_t * lapy = &laplacians.lapy;
459 for (int j = 0; j < size; j++) {
460 tdm_l[j] = - prefactor * (*lapy).l;
461 tdm_c[j] = 1. - prefactor * (*lapy).c;
462 tdm_u[j] = - prefactor * (*lapy).u;
463 }
464 tdm.solve(tdm_info, linear_system.y1pncl);
465 sdecomp.transpose.execute(
466 linear_system.transposer_y1_to_x1,
467 linear_system.y1pncl,
468 linear_system.x1pncl
469 );
470}
473if (param_m_implicit_z) {
474 sdecomp.transpose.execute(
475 linear_system.transposer_x1_to_z2,
476 linear_system.x1pncl,
477 linear_system.z2pncl
478 );
479 tdm_info_t * tdm_info = linear_system.tdm_z;
480 int size = 0;
481 double * restrict tdm_l = NULL;
482 double * restrict tdm_c = NULL;
483 double * restrict tdm_u = NULL;
484 tdm.get_size(tdm_info, &size);
485 tdm.get_l(tdm_info, &tdm_l);
486 tdm.get_c(tdm_info, &tdm_c);
487 tdm.get_u(tdm_info, &tdm_u);
488 const laplacian_t * lapz = &laplacians.lapz;
489 for (int k = 0; k < size; k++) {
490 tdm_l[k] = - prefactor * (*lapz).l;
491 tdm_c[k] = 1. - prefactor * (*lapz).c;
492 tdm_u[k] = - prefactor * (*lapz).u;
493 }
494 tdm.solve(tdm_info, linear_system.z2pncl);
495 sdecomp.transpose.execute(
496 linear_system.transposer_z2_to_x1,
497 linear_system.z2pncl,
498 linear_system.x1pncl
499 );
500}
338if (param_m_implicit_x) {
339 tdm_info_t * tdm_info = linear_system.tdm_x;
340 int size = 0;
341 double * restrict tdm_l = NULL;
342 double * restrict tdm_c = NULL;
343 double * restrict tdm_u = NULL;
344 tdm.get_size(tdm_info, &size);
345 tdm.get_l(tdm_info, &tdm_l);
346 tdm.get_c(tdm_info, &tdm_c);
347 tdm.get_u(tdm_info, &tdm_u);
348 const laplacian_t * lapx = laplacians.lapx;
349 for (int i = 0; i < size; i++) {
350 tdm_l[i] = - prefactor * lapx[i].l;
351 tdm_c[i] = 1. - prefactor * lapx[i].c;
352 tdm_u[i] = - prefactor * lapx[i].u;
353 }
354 tdm.solve(tdm_info, linear_system.x1pncl);
355}
357if (param_m_implicit_y) {
358 sdecomp.transpose.execute(
359 linear_system.transposer_x1_to_y1,
360 linear_system.x1pncl,
361 linear_system.y1pncl
362 );
363 tdm_info_t * tdm_info = linear_system.tdm_y;
364 int size = 0;
365 double * restrict tdm_l = NULL;
366 double * restrict tdm_c = NULL;
367 double * restrict tdm_u = NULL;
368 tdm.get_size(tdm_info, &size);
369 tdm.get_l(tdm_info, &tdm_l);
370 tdm.get_c(tdm_info, &tdm_c);
371 tdm.get_u(tdm_info, &tdm_u);
372 const laplacian_t * lapy = &laplacians.lapy;
373 for (int j = 0; j < size; j++) {
374 tdm_l[j] = - prefactor * (*lapy).l;
375 tdm_c[j] = 1. - prefactor * (*lapy).c;
376 tdm_u[j] = - prefactor * (*lapy).u;
377 }
378 tdm.solve(tdm_info, linear_system.y1pncl);
379 sdecomp.transpose.execute(
380 linear_system.transposer_y1_to_x1,
381 linear_system.y1pncl,
382 linear_system.x1pncl
383 );
384}
386if (param_m_implicit_z) {
387 sdecomp.transpose.execute(
388 linear_system.transposer_x1_to_z2,
389 linear_system.x1pncl,
390 linear_system.z2pncl
391 );
392 tdm_info_t * tdm_info = linear_system.tdm_z;
393 int size = 0;
394 double * restrict tdm_l = NULL;
395 double * restrict tdm_c = NULL;
396 double * restrict tdm_u = NULL;
397 tdm.get_size(tdm_info, &size);
398 tdm.get_l(tdm_info, &tdm_l);
399 tdm.get_c(tdm_info, &tdm_c);
400 tdm.get_u(tdm_info, &tdm_u);
401 const laplacian_t * lapz = &laplacians.lapz;
402 for (int k = 0; k < size; k++) {
403 tdm_l[k] = - prefactor * (*lapz).l;
404 tdm_c[k] = 1. - prefactor * (*lapz).c;
405 tdm_u[k] = - prefactor * (*lapz).u;
406 }
407 tdm.solve(tdm_info, linear_system.z2pncl);
408 sdecomp.transpose.execute(
409 linear_system.transposer_z2_to_x1,
410 linear_system.z2pncl,
411 linear_system.x1pncl
412 );
413}
Finally the velocity field is updated:
546 const int isize = domain->mysizes[0];
547 const int jsize = domain->mysizes[1];
548#if NDIMS == 3
549 const int ksize = domain->mysizes[2];
550#endif
551 const double * restrict dux = linear_system.x1pncl;
552 double * restrict ux = fluid->ux.data;
553 BEGIN
554#if NDIMS == 2
555 UX(i, j) += dux[cnt];
556#else
557 UX(i, j, k) += dux[cnt];
558#endif
559 END
560 if (0 != fluid_update_boundaries_ux(domain, &fluid->ux)) {
561 return 1;
562 }
505 const int isize = domain->mysizes[0];
506 const int jsize = domain->mysizes[1];
507#if NDIMS == 3
508 const int ksize = domain->mysizes[2];
509#endif
510 const double * restrict duy = linear_system.x1pncl;
511 double * restrict uy = fluid->uy.data;
512 BEGIN
513#if NDIMS == 2
514 UY(i, j) += duy[cnt];
515#else
516 UY(i, j, k) += duy[cnt];
517#endif
518 END
519 if (0 != fluid_update_boundaries_uy(domain, &fluid->uy)) {
520 return 1;
521 }
417const int isize = domain->mysizes[0];
418const int jsize = domain->mysizes[1];
419const int ksize = domain->mysizes[2];
420const double * restrict duz = linear_system.x1pncl;
421double * restrict uz = fluid->uz.data;
422BEGIN
423 UZ(i, j, k) += duz[cnt];
424END
425if (0 != fluid_update_boundaries_uz(domain, &fluid->uz)) {
426 return 1;
427}
Correction Step¶
The updated velocity field \(u_i^*\), which in general violates the incompressibility, is corrected in the second step (correction step). The idea is mathematically written as
where \(\psi\) is a scalar potential to be given. By taking the (discrete) divergence, we obtain
By requesting the (discrete) incompressibility constraint on the new velocity field (namely the left-hand-side term to be zero), we obtain
which is a Poisson equation with respect to \(\psi\).
The right-hand-side term is computed as follows in the code:
455static int assign_input (
456 const domain_t * domain,
457 const size_t rkstep,
458 const double dt,
459 const fluid_t * fluid,
460 double * restrict rhs
461) {
462 const int isize = domain->mysizes[0];
463 const int jsize = domain->mysizes[1];
464#if NDIMS == 3
465 const int ksize = domain->mysizes[2];
466#endif
467 const double * restrict hxxf = domain->hxxf;
468 const double hy = domain->hy;
469#if NDIMS == 3
470 const double hz = domain->hz;
471#endif
472 const double * restrict jdxf = domain->jdxf;
473 const double * restrict jdxc = domain->jdxc;
474 const double * restrict ux = fluid->ux.data;
475 const double * restrict uy = fluid->uy.data;
476#if NDIMS == 3
477 const double * restrict uz = fluid->uz.data;
478#endif
479 // normalise FFT beforehand
480#if NDIMS == 2
481 const double norm = 1. * domain->glsizes[1];
482#else
483 const double norm = 1. * domain->glsizes[1] * domain->glsizes[2];
484#endif
485 const double prefactor = 1. / (rkcoefs[rkstep].gamma * dt) / norm;
486 BEGIN
487 const double hx_xm = HXXF(i );
488 const double hx_xp = HXXF(i+1);
489 const double jd_xm = JDXF(i );
490 const double jd_x0 = JDXC(i );
491 const double jd_xp = JDXF(i+1);
492#if NDIMS == 2
493 const double ux_xm = UX(i , j );
494 const double ux_xp = UX(i+1, j );
495 const double uy_ym = UY(i , j );
496 const double uy_yp = UY(i , j+1);
497#else
498 const double ux_xm = UX(i , j , k );
499 const double ux_xp = UX(i+1, j , k );
500 const double uy_ym = UY(i , j , k );
501 const double uy_yp = UY(i , j+1, k );
502 const double uz_zm = UZ(i , j , k );
503 const double uz_zp = UZ(i , j , k+1);
504#endif
505 const double div = 1. / jd_x0 * (
506 - jd_xm / hx_xm * ux_xm + jd_xp / hx_xp * ux_xp
507 - jd_x0 / hy * uy_ym + jd_x0 / hy * uy_yp
508#if NDIMS == 3
509 - jd_x0 / hz * uz_zm + jd_x0 / hz * uz_zp
510#endif
511 );
512 rhs[cnt] = prefactor * div;
513 END
514 return 0;
515}
After solving the Poisson equation, the velocity field is corrected as follows in the code:
28int fluid_correct_velocity_ux (
29 const domain_t * domain,
30 const double prefactor,
31 fluid_t * fluid
32) {
33 const int isize = domain->mysizes[0];
34 const int jsize = domain->mysizes[1];
35#if NDIMS == 3
36 const int ksize = domain->mysizes[2];
37#endif
38 const double * restrict hxxf = domain->hxxf;
39 const double * restrict psi = fluid->psi.data;
40 double * restrict ux = fluid->ux.data;
41 BEGIN
42 const double hx = HXXF(i );
43#if NDIMS == 2
44 const double psi_xm = PSI(i-1, j );
45 const double psi_xp = PSI(i , j );
46 double * vel = &UX(i, j);
47#else
48 const double psi_xm = PSI(i-1, j , k );
49 const double psi_xp = PSI(i , j , k );
50 double * vel = &UX(i, j, k);
51#endif
52 *vel -= prefactor / hx * (
53 - psi_xm
54 + psi_xp
55 );
56 END
57 // update boundary and halo cells
58 if (0 != fluid_update_boundaries_ux(domain, &fluid->ux)) {
59 return 1;
60 }
61 return 0;
62}
27int fluid_correct_velocity_uy (
28 const domain_t * domain,
29 const double prefactor,
30 fluid_t * fluid
31) {
32 const int isize = domain->mysizes[0];
33 const int jsize = domain->mysizes[1];
34#if NDIMS == 3
35 const int ksize = domain->mysizes[2];
36#endif
37 const double hy = domain->hy;
38 const double * restrict psi = fluid->psi.data;
39 double * restrict uy = fluid->uy.data;
40 BEGIN
41#if NDIMS == 2
42 const double psi_ym = PSI(i , j-1);
43 const double psi_yp = PSI(i , j );
44 double * vel = &UY(i, j);
45#else
46 const double psi_ym = PSI(i , j-1, k );
47 const double psi_yp = PSI(i , j , k );
48 double * vel = &UY(i, j, k);
49#endif
50 *vel -= prefactor / hy * (
51 - psi_ym
52 + psi_yp
53 );
54 END
55 // update boundary and halo cells
56 if (0 != fluid_update_boundaries_uy(domain, &fluid->uy)) {
57 return 1;
58 }
59 return 0;
60}
10int fluid_correct_velocity_uz (
11 const domain_t * domain,
12 const double prefactor,
13 fluid_t * fluid
14) {
15 const int isize = domain->mysizes[0];
16 const int jsize = domain->mysizes[1];
17 const int ksize = domain->mysizes[2];
18 const double hz = domain->hz;
19 const double * restrict psi = fluid->psi.data;
20 double * restrict uz = fluid->uz.data;
21 for (int k = 1; k <= ksize; k++) {
22 for (int j = 1; j <= jsize; j++) {
23 for (int i = 1; i <= isize; i++) {
24 const double psi_zm = PSI(i , j , k-1);
25 const double psi_zp = PSI(i , j , k );
26 UZ(i, j, k) -= prefactor / hz * (
27 - psi_zm
28 + psi_zp
29 );
30 }
31 }
32 }
33 // update boundary and halo cells
34 if (0 != fluid_update_boundaries_uz(domain, &fluid->uz)) {
35 return 1;
36 }
37 return 0;
38}
Pressure and Scalar Potential¶
Finally we relate the pressure field with the scalar potential to close the system. Each Runge-Kutta step when all diffusive terms are treated explicitly is given by
Summing all three steps yield
Since the pressure field should be treated implicitly in time, we find a requirement:
or equivalently
Next, we consider cases where the diffusive terms are partially treated implicitly in time; for instance, the following relation holds when the diffusive terms are treated implicitly only in \(x\) direction:
Note that, in the second step, the left-hand side is \(u_i^*\) while it should be \(u_i^{k+1}\) but is unknown. By requesting the pressure-gradient term to be implicit in time and with some algebra, we obtain
or equivalently
Since the spatial-differential operators are interchangeable, we simplify the relation to
Similarly, when all diffusive terms are treated implicitly, we have
Updating pressure field using the scalar potential is implemented as follows:
166int fluid_update_pressure (
167 const domain_t * domain,
168 const size_t rkstep,
169 const double dt,
170 fluid_t * fluid
171) {
172 // explicit contribution, always present
173 explicit_contribution(domain, fluid);
174 const double prefactor =
175 0.5 * rkcoefs[rkstep].gamma * dt * fluid_compute_momentum_diffusivity(fluid);
176 // additional terms if diffusive terms in the direction is treated implicitly
177 if (param_m_implicit_x) {
178 implicit_x_contribution(domain, prefactor, fluid);
179 }
180 if (param_m_implicit_y) {
181 implicit_y_contribution(domain, prefactor, fluid);
182 }
183#if NDIMS == 3
184 if (param_m_implicit_z) {
185 implicit_z_contribution(domain, prefactor, fluid);
186 }
187#endif
188 // impose boundary conditions and communicate halo cells
189 if (0 != fluid_update_boundaries_p(domain, &fluid->p)) {
190 return 1;
191 }
192 return 0;
193}
The explicit contribution, which is always present, is given here:
31static int explicit_contribution (
32 const domain_t * domain,
33 const fluid_t * fluid
34) {
35 const int isize = domain->mysizes[0];
36 const int jsize = domain->mysizes[1];
37#if NDIMS == 3
38 const int ksize = domain->mysizes[2];
39#endif
40 const double * restrict psi = fluid->psi.data;
41 double * restrict p = fluid->p.data;
42 BEGIN
43#if NDIMS == 2
44 P(i, j) += PSI(i, j);
45#else
46 P(i, j, k) += PSI(i, j, k);
47#endif
48 END
49 return 0;
50}
The implicit contributions, which is needed when the Laplace operator in the direction is implicitly treated, are given here:
53static int implicit_x_contribution (
54 const domain_t * domain,
55 const double prefactor,
56 fluid_t * fluid
57) {
58 const int isize = domain->mysizes[0];
59 const int jsize = domain->mysizes[1];
60#if NDIMS == 3
61 const int ksize = domain->mysizes[2];
62#endif
63 const double * restrict hxxf = domain->hxxf;
64 const double * restrict jdxf = domain->jdxf;
65 const double * restrict jdxc = domain->jdxc;
66 const double * restrict psi = fluid->psi.data;
67 double * restrict p = fluid->p.data;
68 BEGIN
69 const double hx_xm = HXXF(i );
70 const double hx_xp = HXXF(i+1);
71 const double jd_xm = JDXF(i );
72 const double jd_x0 = JDXC(i );
73 const double jd_xp = JDXF(i+1);
74 const double l = 1. / jd_x0 * jd_xm / hx_xm / hx_xm;
75 const double u = 1. / jd_x0 * jd_xp / hx_xp / hx_xp;
76 const double c = - l - u;
77#if NDIMS == 2
78 const double psi_xm = PSI(i-1, j );
79 const double psi_x0 = PSI(i , j );
80 const double psi_xp = PSI(i+1, j );
81 double * pre = &P(i, j);
82#else
83 const double psi_xm = PSI(i-1, j , k );
84 const double psi_x0 = PSI(i , j , k );
85 const double psi_xp = PSI(i+1, j , k );
86 double * pre = &P(i, j, k);
87#endif
88 *pre -= prefactor * (
89 + l * psi_xm
90 + c * psi_x0
91 + u * psi_xp
92 );
93 END
94 return 0;
95}
98static int implicit_y_contribution (
99 const domain_t * domain,
100 const double prefactor,
101 fluid_t * fluid
102) {
103 const int isize = domain->mysizes[0];
104 const int jsize = domain->mysizes[1];
105#if NDIMS == 3
106 const int ksize = domain->mysizes[2];
107#endif
108 const double hy = domain->hy;
109 const double * restrict psi = fluid->psi.data;
110 double * restrict p = fluid->p.data;
111 BEGIN
112 const double l = 1. / hy / hy;
113 const double u = 1. / hy / hy;
114 const double c = - l - u;
115#if NDIMS == 2
116 const double psi_ym = PSI(i , j-1);
117 const double psi_y0 = PSI(i , j );
118 const double psi_yp = PSI(i , j+1);
119 double * pre = &P(i, j);
120#else
121 const double psi_ym = PSI(i , j-1, k );
122 const double psi_y0 = PSI(i , j , k );
123 const double psi_yp = PSI(i , j+1, k );
124 double * pre = &P(i, j, k);
125#endif
126 *pre -= prefactor * (
127 + l * psi_ym
128 + c * psi_y0
129 + u * psi_yp
130 );
131 END
132 return 0;
133}
137static int implicit_z_contribution (
138 const domain_t * domain,
139 const double prefactor,
140 fluid_t * fluid
141) {
142 const int isize = domain->mysizes[0];
143 const int jsize = domain->mysizes[1];
144 const int ksize = domain->mysizes[2];
145 const double hz = domain->hz;
146 const double * restrict psi = fluid->psi.data;
147 double * restrict p = fluid->p.data;
148 BEGIN
149 const double l = 1. / hz / hz;
150 const double u = 1. / hz / hz;
151 const double c = - l - u;
152 const double psi_zm = PSI(i , j , k-1);
153 const double psi_z0 = PSI(i , j , k );
154 const double psi_zp = PSI(i , j , k+1);
155 P(i, j, k) -= prefactor * (
156 + l * psi_zm
157 + c * psi_z0
158 + u * psi_zp
159 );
160 END
161 return 0;
162}