Internal Energy Balance

The equation of the internal energy is

Tt=A+Dx+Dy+Dz,

where A is the advective terms:

A1J¯Jhξ1u1δξ1Tξ11J¯Jhξ2u2δξ2Tξ21J¯Jhξ3u3δξ3Tξ3,

while Di is the diffusive term involving spatial differentiation in the i-th direction:

Di1PrRa1Jδξi(Jhξi1hξiδξiT)(no summation overi).

See the spatial discretization.

The temporal discretization for each Runge-Kutta iteration leads to

ΔT=αkΔt(Ak+Dk)+βkΔt(Ak1+Dk1),Tk+1=Tk+ΔT,

when all diffusive terms are treated explicitly, while

ΔT=αkΔtAk+βkΔtAk1+γkΔtDk,Tk+1=Tk+{1cγkΔt1PrRa1Jδξ3(Jhξ31hξ3δξ3)}1{1cγkΔt1PrRa1Jδξ2(Jhξ21hξ2δξ2)}1{1cγkΔt1PrRa1Jδξ1(Jhξ11hξ1δξ1)}1ΔT,

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

ΔT=αkΔt(Ak+Dk2+Dk3)+βkΔt(Ak1+Dk12+Dk13)+γkΔtDk1,Tk+1=Tk+{1cγkΔt1PrRa1Jδξ1(Jhξ11hξ1δξ1)}1ΔT.

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 Δ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    }