Internal Energy Balance

The equation of the internal energy is

\[\pder{T}{t} = A + D_x + D_y + D_z,\]

where \(A\) is the advective terms:

\[A \equiv - \dtempadv{1} - \dtempadv{2} - \dtempadv{3},\]

while \(D_i\) is the diffusive term involving spatial differentiation in the \(i\)-th direction:

\[D_i \equiv \dtempdif{i} \,\, (\text{no summation over}\,i).\]

See the spatial discretization.

The temporal discretization for each Runge-Kutta iteration leads to

\[ \begin{align}\begin{aligned}\Delta T & = \alpha^k \Delta t \left( A^{k } + D^{k } \right) + \beta^k \Delta t \left( A^{k-1} + D^{k-1} \right),\\T^{k+1} & = T^k + \Delta T,\end{aligned}\end{align} \]

when all diffusive terms are treated explicitly, while

\[ \begin{align}\begin{aligned}\newcommand{\lap}[2]{ {#2} \gamma^k \Delta t \frac{1}{\sqrt{Pr} \sqrt{Ra}} \frac{1}{J} \dif{}{\gcs{#1}} \left( \frac{J}{\sfact{#1}} \frac{1}{\sfact{#1}} \dif{}{\gcs{#1}} \right) } \Delta T & = \alpha^k \Delta t A^{k } + \beta^k \Delta t A^{k-1} + \gamma^k \Delta t D^{k },\\T^{k+1} & = T^k + \left\{ 1 - \lap{3}{c} \right\}^{-1} \left\{ 1 - \lap{2}{c} \right\}^{-1} \left\{ 1 - \lap{1}{c} \right\}^{-1} \Delta T,\end{aligned}\end{align} \]

when all diffusive terms are treated implicitly.

Diffusive terms are sometimes partially treated implicitly (c.f., van der Poel et al., Comput. Fluids (116), 2015). When only the diffusive term in \(x\) direction is implicitly treated (the default configuration in this project), we have

\[ \begin{align}\begin{aligned}\Delta T & = \alpha^k \Delta t \left( A^{k } + D_2^{k } + D_3^{k } \right) + \beta^k \Delta t \left( A^{k-1} + D_2^{k-1} + D_3^{k-1} \right) + \gamma^k \Delta t D_1^{k },\\T^{k+1} & = T^k + \left\{ 1 - \lap{1}{c} \right\}^{-1} \Delta T.\end{aligned}\end{align} \]

First, the explicit and implicit terms are calculated and stored to the corresponding buffers:

src/fluid/predict/t.c
290int compute_rhs_t (
291    const domain_t * domain,
292    fluid_t * fluid
293) {
294  if (!laplacians.is_initialised) {
295    if (0 != init_laplacians(domain)) {
296      return 1;
297    }
298  }
299  const double * restrict ux = fluid->ux.data;
300  const double * restrict uy = fluid->uy.data;
301#if NDIMS == 3
302  const double * restrict uz = fluid->uz.data;
303#endif
304  const double * restrict  t = fluid-> t.data;
305  // buffer for explicit terms
306  double * restrict srca = fluid->srct.alpha.data;
307  // buffer for implicit terms
308  double * restrict srcg = fluid->srct.gamma.data;
309  const double diffusivity = fluid_compute_temperature_diffusivity(fluid);
310  // advective contributions, always explicit
311  advection_x(domain, t, ux, srca);
312  advection_y(domain, t, uy, srca);
313#if NDIMS == 3
314  advection_z(domain, t, uz, srca);
315#endif
316  // diffusive contributions, can be explicit or implicit
317  diffusion_x(domain, diffusivity, t, param_t_implicit_x ? srcg : srca);
318  diffusion_y(domain, diffusivity, t, param_t_implicit_y ? srcg : srca);
319#if NDIMS == 3
320  diffusion_z(domain, diffusivity, t, param_t_implicit_z ? srcg : srca);
321#endif
322  return 0;
323}

The buffers are used to compute \(\Delta T\):

src/fluid/predict/t.c
358    // Runge-Kutta coefficients, alpha, beta, gamma
359    const double coef_a = rkcoefs[rkstep].alpha;
360    const double coef_b = rkcoefs[rkstep].beta ;
361    const double coef_g = rkcoefs[rkstep].gamma;
362    // Runge-Kutta buffers, alpha, beta, gamma
363    const double * restrict srcta = fluid->srct.alpha.data;
364    const double * restrict srctb = fluid->srct.beta .data;
365    const double * restrict srctg = fluid->srct.gamma.data;
366    const int isize = domain->mysizes[0];
367    const int jsize = domain->mysizes[1];
368#if NDIMS == 3
369    const int ksize = domain->mysizes[2];
370#endif
371    double * restrict dtemp = linear_system.x1pncl;
372#if NDIMS == 2
373    const size_t nitems = isize * jsize;
374#else
375    const size_t nitems = isize * jsize * ksize;
376#endif
377    // compute T(new) - T(old)
378    for (size_t n = 0; n < nitems; n++) {
379      dtemp[n] =
380        + coef_a * dt * srcta[n]
381        + coef_b * dt * srctb[n]
382        + coef_g * dt * srctg[n];
383    }

When necessary, linear systems are solved to take care of the implicit treatments:

src/fluid/predict/t.c
390if (param_t_implicit_x) {
391  // prepare tri-diagonal coefficients
392  tdm_info_t * tdm_info = linear_system.tdm_x;
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 * lapx = laplacians.lapx;
402  for (int i = 0; i < size; i++) {
403    tdm_l[i] =    - prefactor * lapx[i].l;
404    tdm_c[i] = 1. - prefactor * lapx[i].c;
405    tdm_u[i] =    - prefactor * lapx[i].u;
406  }
407  // solve all
408  tdm.solve(tdm_info, linear_system.x1pncl);
409}
src/fluid/predict/t.c
411if (param_t_implicit_y) {
412  // make all y data accessible
413  sdecomp.transpose.execute(
414      linear_system.transposer_x1_to_y1,
415      linear_system.x1pncl,
416      linear_system.y1pncl
417  );
418  // prepare tri-diagonal coefficients
419  tdm_info_t * tdm_info = linear_system.tdm_y;
420  int size = 0;
421  double * restrict tdm_l = NULL;
422  double * restrict tdm_c = NULL;
423  double * restrict tdm_u = NULL;
424  tdm.get_size(tdm_info, &size);
425  tdm.get_l(tdm_info, &tdm_l);
426  tdm.get_c(tdm_info, &tdm_c);
427  tdm.get_u(tdm_info, &tdm_u);
428  const laplacian_t * lapy = &laplacians.lapy;
429  for (int j = 0; j < size; j++) {
430    tdm_l[j] =    - prefactor * (*lapy).l;
431    tdm_c[j] = 1. - prefactor * (*lapy).c;
432    tdm_u[j] =    - prefactor * (*lapy).u;
433  }
434  // solve all
435  tdm.solve(tdm_info, linear_system.y1pncl);
436  // recover original memory alignment
437  sdecomp.transpose.execute(
438      linear_system.transposer_y1_to_x1,
439      linear_system.y1pncl,
440      linear_system.x1pncl
441  );
442}
src/fluid/predict/t.c
445if (param_t_implicit_z) {
446  // make all z data accessible
447  sdecomp.transpose.execute(
448      linear_system.transposer_x1_to_z2,
449      linear_system.x1pncl,
450      linear_system.z2pncl
451  );
452  // prepare tri-diagonal coefficients
453  tdm_info_t * tdm_info = linear_system.tdm_z;
454  int size = 0;
455  double * restrict tdm_l = NULL;
456  double * restrict tdm_c = NULL;
457  double * restrict tdm_u = NULL;
458  tdm.get_size(tdm_info, &size);
459  tdm.get_l(tdm_info, &tdm_l);
460  tdm.get_c(tdm_info, &tdm_c);
461  tdm.get_u(tdm_info, &tdm_u);
462  const laplacian_t * lapz = &laplacians.lapz;
463  for (int k = 0; k < size; k++) {
464    tdm_l[k] =    - prefactor * (*lapz).l;
465    tdm_c[k] = 1. - prefactor * (*lapz).c;
466    tdm_u[k] =    - prefactor * (*lapz).u;
467  }
468  // solve all
469  tdm.solve(tdm_info, linear_system.z2pncl);
470  // recover original memory alignment
471  sdecomp.transpose.execute(
472      linear_system.transposer_z2_to_x1,
473      linear_system.z2pncl,
474      linear_system.x1pncl
475  );
476}

Finally the temperature field is updated:

src/fluid/predict/t.c
481    const int isize = domain->mysizes[0];
482    const int jsize = domain->mysizes[1];
483#if NDIMS == 3
484    const int ksize = domain->mysizes[2];
485#endif
486    const double * restrict dtemp = linear_system.x1pncl;
487    double * restrict t = fluid->t.data;
488    BEGIN
489#if NDIMS == 2
490      T(i, j) += dtemp[cnt];
491#else
492      T(i, j, k) += dtemp[cnt];
493#endif
494    END
495    if (0 != fluid_update_boundaries_t(domain, &fluid->t)) {
496      return 1;
497    }