LCOV - code coverage report
Current view: top level - solvers/navier_stokes/cpu - solver_rk2.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 89.1 % 183 163
Test Date: 2026-06-23 13:41:07 Functions: 100.0 % 2 2

            Line data    Source code
       1              : /**
       2              :  * @file solver_rk2.c
       3              :  * @brief RK2 (Heun's method) time integration for Navier-Stokes
       4              :  *
       5              :  * Second-order Runge-Kutta time stepping:
       6              :  *   k1 = RHS(Q^n)
       7              :  *   Q_pred = Q^n + dt * k1
       8              :  *   k2 = RHS(Q_pred)
       9              :  *   Q^{n+1} = Q^n + (dt/2) * (k1 + k2)
      10              :  *
      11              :  * Uses the same spatial discretisation (central differences) and physics
      12              :  * as the explicit Euler solver, but achieves O(dt^2) temporal accuracy
      13              :  * instead of O(dt).
      14              :  *
      15              :  * Branch-free 3D: when nz==1, stride_z=0 and inv_2dz/inv_dz2=0.0 cause all
      16              :  * z-terms to vanish, producing bit-identical results to the 2D code path.
      17              :  */
      18              : 
      19              : #include "cfd/core/cfd_status.h"
      20              : #include "cfd/core/grid.h"
      21              : #include "cfd/core/indexing.h"
      22              : #include "cfd/core/memory.h"
      23              : #include "cfd/solvers/energy_solver.h"
      24              : #include "cfd/solvers/navier_stokes_solver.h"
      25              : 
      26              : #include "../../energy/energy_solver_internal.h"
      27              : 
      28              : #include <math.h>
      29              : #include <string.h>
      30              : 
      31              : #ifndef M_PI
      32              : #define M_PI 3.14159265358979323846
      33              : #endif
      34              : 
      35              : /* Physical stability limits */
      36              : #define MAX_DERIVATIVE_LIMIT        100.0
      37              : #define MAX_SECOND_DERIVATIVE_LIMIT 1000.0
      38              : #define MAX_VELOCITY_LIMIT          100.0
      39              : #define MAX_DIVERGENCE_LIMIT        10.0
      40              : 
      41              : #define PRESSURE_UPDATE_FACTOR 0.1
      42              : 
      43              : /* ============================================================================
      44              :  * RHS EVALUATION
      45              :  * ============================================================================ */
      46              : 
      47              : /**
      48              :  * Compute the right-hand side of the semi-discrete Navier-Stokes equations.
      49              :  *
      50              :  * For each interior point, computes:
      51              :  *   du/dt = -u·∇u - (1/ρ)∂p/∂x + ν∇²u + source_u
      52              :  *   dv/dt = -u·∇v - (1/ρ)∂p/∂y + ν∇²v + source_v
      53              :  *   dw/dt = -u·∇w - (1/ρ)∂p/∂z + ν∇²w + source_w
      54              :  *   dp/dt = -0.1 * ρ * (∂u/∂x + ∂v/∂y + ∂w/∂z)
      55              :  *
      56              :  * Uses periodic stencil indices to avoid relying on ghost cells, which is
      57              :  * critical for preserving RK2 temporal order (ghost cell values may be stale
      58              :  * during intermediate RK stages).
      59              :  */
      60         8064 : static void compute_rhs(const double* u, const double* v, const double* w,
      61              :                          const double* p, const double* rho, const double* T,
      62              :                          double* rhs_u, double* rhs_v, double* rhs_w, double* rhs_p,
      63              :                          const grid* grid, const ns_solver_params_t* params,
      64              :                          size_t nx, size_t ny, size_t nz,
      65              :                          size_t stride_z, size_t k_start, size_t k_end,
      66              :                          double inv_2dz, double inv_dz2,
      67              :                          int iter, double dt) {
      68        16158 :     for (size_t k = k_start; k < k_end; k++) {
      69       759346 :         for (size_t j = 1; j < ny - 1; j++) {
      70     85990684 :             for (size_t i = 1; i < nx - 1; i++) {
      71     85239432 :                 size_t idx = k * stride_z + IDX_2D(i, j, nx);
      72              : 
      73              :                 /* Safety checks */
      74     85239432 :                 if (rho[idx] <= 1e-10) {
      75            0 :                     rhs_u[idx] = 0.0;
      76            0 :                     rhs_v[idx] = 0.0;
      77            0 :                     rhs_w[idx] = 0.0;
      78            0 :                     rhs_p[idx] = 0.0;
      79            0 :                     continue;
      80              :                 }
      81     85239432 :                 if (fabs(grid->dx[i]) < 1e-10 || fabs(grid->dy[j]) < 1e-10) {
      82            0 :                     rhs_u[idx] = 0.0;
      83            0 :                     rhs_v[idx] = 0.0;
      84            0 :                     rhs_w[idx] = 0.0;
      85            0 :                     rhs_p[idx] = 0.0;
      86            0 :                     continue;
      87              :                 }
      88              : 
      89              :                 /* Periodic stencil indices in x and y — avoids relying on ghost cells,
      90              :                  * which is critical for preserving RK2 temporal order. */
      91     85239432 :                 size_t il = (i > 1)      ? idx - 1  : k * stride_z + IDX_2D(nx - 2, j, nx);
      92     85239432 :                 size_t ir = (i < nx - 2) ? idx + 1  : k * stride_z + IDX_2D(1, j, nx);
      93     85239432 :                 size_t jd = (j > 1)      ? idx - nx : k * stride_z + IDX_2D(i, ny - 2, nx);
      94     85239432 :                 size_t ju = (j < ny - 2) ? idx + nx : k * stride_z + IDX_2D(i, 1, nx);
      95              : 
      96              :                 /* Periodic stencil indices in z.
      97              :                  * When nz==1: k=0, stride_z=0, so kd=ku=idx → z-terms vanish. */
      98    170478864 :                 size_t kd = (k > 1)      ? idx - stride_z
      99     85239432 :                                          : (nz - 2) * stride_z + IDX_2D(i, j, nx);
     100    170478864 :                 size_t ku = (k < nz - 2) ? idx + stride_z
     101     85239432 :                                          : 1 * stride_z + IDX_2D(i, j, nx);
     102              : 
     103              :                 /* First derivatives (central differences) */
     104     85239432 :                 double du_dx = (u[ir] - u[il]) / (2.0 * grid->dx[i]);
     105     85239432 :                 double du_dy = (u[ju] - u[jd]) / (2.0 * grid->dy[j]);
     106     85239432 :                 double du_dz = (u[ku] - u[kd]) * inv_2dz;
     107              : 
     108     85239432 :                 double dv_dx = (v[ir] - v[il]) / (2.0 * grid->dx[i]);
     109     85239432 :                 double dv_dy = (v[ju] - v[jd]) / (2.0 * grid->dy[j]);
     110     85239432 :                 double dv_dz = (v[ku] - v[kd]) * inv_2dz;
     111              : 
     112     85239432 :                 double dw_dx = (w[ir] - w[il]) / (2.0 * grid->dx[i]);
     113     85239432 :                 double dw_dy = (w[ju] - w[jd]) / (2.0 * grid->dy[j]);
     114     85239432 :                 double dw_dz = (w[ku] - w[kd]) * inv_2dz;
     115              : 
     116              :                 /* Pressure gradients */
     117     85239432 :                 double dp_dx = (p[ir] - p[il]) / (2.0 * grid->dx[i]);
     118     85239432 :                 double dp_dy = (p[ju] - p[jd]) / (2.0 * grid->dy[j]);
     119     85239432 :                 double dp_dz = (p[ku] - p[kd]) * inv_2dz;
     120              : 
     121              :                 /* Second derivatives (viscous terms) */
     122     85239432 :                 double d2u_dx2 = (u[ir] - 2.0 * u[idx] + u[il]) / (grid->dx[i] * grid->dx[i]);
     123     85239432 :                 double d2u_dy2 = (u[ju] - 2.0 * u[idx] + u[jd]) / (grid->dy[j] * grid->dy[j]);
     124     85239432 :                 double d2u_dz2 = (u[ku] - 2.0 * u[idx] + u[kd]) * inv_dz2;
     125              : 
     126     85239432 :                 double d2v_dx2 = (v[ir] - 2.0 * v[idx] + v[il]) / (grid->dx[i] * grid->dx[i]);
     127     85239432 :                 double d2v_dy2 = (v[ju] - 2.0 * v[idx] + v[jd]) / (grid->dy[j] * grid->dy[j]);
     128     85239432 :                 double d2v_dz2 = (v[ku] - 2.0 * v[idx] + v[kd]) * inv_dz2;
     129              : 
     130     85239432 :                 double d2w_dx2 = (w[ir] - 2.0 * w[idx] + w[il]) / (grid->dx[i] * grid->dx[i]);
     131     85239432 :                 double d2w_dy2 = (w[ju] - 2.0 * w[idx] + w[jd]) / (grid->dy[j] * grid->dy[j]);
     132     85239432 :                 double d2w_dz2 = (w[ku] - 2.0 * w[idx] + w[kd]) * inv_dz2;
     133              : 
     134              :                 /* Kinematic viscosity */
     135     85239432 :                 double nu = params->mu / fmax(rho[idx], 1e-10);
     136     85239432 :                 nu = fmin(nu, 1.0);
     137              : 
     138              :                 /* Clamp first derivatives */
     139     85239432 :                 du_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, du_dx));
     140     85239432 :                 du_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, du_dy));
     141     85239432 :                 du_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, du_dz));
     142     85239432 :                 dv_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dv_dx));
     143     85239432 :                 dv_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dv_dy));
     144     85239432 :                 dv_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dv_dz));
     145     85239432 :                 dw_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dw_dx));
     146     85239432 :                 dw_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dw_dy));
     147     85239432 :                 dw_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dw_dz));
     148     85239432 :                 dp_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dp_dx));
     149     85239432 :                 dp_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dp_dy));
     150     85239432 :                 dp_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dp_dz));
     151              : 
     152              :                 /* Clamp second derivatives */
     153     85239432 :                 d2u_dx2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2u_dx2));
     154     85239432 :                 d2u_dy2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2u_dy2));
     155     85239432 :                 d2u_dz2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2u_dz2));
     156     85239432 :                 d2v_dx2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2v_dx2));
     157     85239432 :                 d2v_dy2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2v_dy2));
     158     85239432 :                 d2v_dz2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2v_dz2));
     159     85239432 :                 d2w_dx2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2w_dx2));
     160     85239432 :                 d2w_dy2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2w_dy2));
     161     85239432 :                 d2w_dz2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2w_dz2));
     162              : 
     163              :                 /* Source terms */
     164     85239432 :                 double source_u = 0.0, source_v = 0.0, source_w = 0.0;
     165     85239432 :                 double z_coord = (nz > 1 && grid->z) ? grid->z[k] : 0.0;
     166     85239432 :                 compute_source_terms(grid->x[i], grid->y[j], z_coord, iter, dt,
     167              :                                      params, &source_u, &source_v, &source_w);
     168              : 
     169              :                 /* Boussinesq buoyancy source */
     170     85239432 :                 if (T) {
     171     85239432 :                     energy_compute_buoyancy(T[idx], params,
     172              :                                             &source_u, &source_v, &source_w);
     173              :                 }
     174              : 
     175              :                 /* RHS for u-momentum */
     176     85239432 :                 rhs_u[idx] = -u[idx] * du_dx - v[idx] * du_dy - w[idx] * du_dz
     177     85239432 :                              - dp_dx / rho[idx]
     178     85239432 :                              + nu * (d2u_dx2 + d2u_dy2 + d2u_dz2)
     179     85239432 :                              + source_u;
     180              : 
     181              :                 /* RHS for v-momentum */
     182     85239432 :                 rhs_v[idx] = -u[idx] * dv_dx - v[idx] * dv_dy - w[idx] * dv_dz
     183     85239432 :                              - dp_dy / rho[idx]
     184     85239432 :                              + nu * (d2v_dx2 + d2v_dy2 + d2v_dz2)
     185     85239432 :                              + source_v;
     186              : 
     187              :                 /* RHS for w-momentum */
     188     85239432 :                 rhs_w[idx] = -u[idx] * dw_dx - v[idx] * dw_dy - w[idx] * dw_dz
     189     85239432 :                              - dp_dz / rho[idx]
     190     85239432 :                              + nu * (d2w_dx2 + d2w_dy2 + d2w_dz2)
     191     85239432 :                              + source_w;
     192              : 
     193              :                 /* Simplified pressure RHS (divergence-based) */
     194     85239432 :                 double divergence = du_dx + dv_dy + dw_dz;
     195     85239432 :                 divergence = fmax(-MAX_DIVERGENCE_LIMIT,
     196              :                                   fmin(MAX_DIVERGENCE_LIMIT, divergence));
     197     85239432 :                 rhs_p[idx] = -PRESSURE_UPDATE_FACTOR * rho[idx] * divergence;
     198              :             }
     199              :         }
     200              :     }
     201         8064 : }
     202              : 
     203              : /* ============================================================================
     204              :  * RK2 SOLVER
     205              :  * ============================================================================ */
     206              : 
     207         3994 : cfd_status_t rk2_impl(flow_field* field, const grid* grid,
     208              :                        const ns_solver_params_t* params) {
     209         3994 :     if (field->nx < 3 || field->ny < 3 || (field->nz > 1 && field->nz < 3)) {
     210              :         return CFD_ERROR_INVALID;
     211              :     }
     212              : 
     213         3994 :     size_t nx = field->nx;
     214         3994 :     size_t ny = field->ny;
     215         3994 :     size_t nz = field->nz;
     216              : 
     217              :     /* Reject non-uniform z-spacing (solver uses constant inv_2dz/inv_dz2) */
     218         3994 :     if (nz > 1 && grid->dz) {
     219           21 :         for (size_t k = 1; k < nz - 1; k++) {
     220           18 :             if (fabs(grid->dz[k] - grid->dz[0]) > 1e-14) {
     221              :                 return CFD_ERROR_INVALID;
     222              :             }
     223              :         }
     224              :     }
     225              : 
     226         3994 :     size_t plane = nx * ny;
     227         3994 :     size_t total = plane * nz;
     228         3994 :     size_t bytes = total * sizeof(double);
     229              : 
     230              :     /* Branch-free 3D constants */
     231         3994 :     size_t stride_z = (nz > 1) ? plane : 0;
     232         3994 :     size_t k_start  = (nz > 1) ? 1 : 0;
     233         3994 :     size_t k_end    = (nz > 1) ? (nz - 1) : 1;
     234         3994 :     double inv_2dz  = (nz > 1 && grid->dz) ? 1.0 / (2.0 * grid->dz[0]) : 0.0;
     235            3 :     double inv_dz2  = (nz > 1 && grid->dz) ? 1.0 / (grid->dz[0] * grid->dz[0]) : 0.0;
     236              : 
     237              :     /* Allocate working arrays:
     238              :      *   k1_u/v/w/p : Stage 1 derivatives
     239              :      *   k2_u/v/w/p : Stage 2 derivatives
     240              :      *   u0/v0/w0/p0 : Saved state Q^n
     241              :      */
     242         3994 :     double* k1_u = (double*)cfd_calloc(total, sizeof(double));
     243         3994 :     double* k1_v = (double*)cfd_calloc(total, sizeof(double));
     244         3994 :     double* k1_w = (double*)cfd_calloc(total, sizeof(double));
     245         3994 :     double* k1_p = (double*)cfd_calloc(total, sizeof(double));
     246         3994 :     double* k2_u = (double*)cfd_calloc(total, sizeof(double));
     247         3994 :     double* k2_v = (double*)cfd_calloc(total, sizeof(double));
     248         3994 :     double* k2_w = (double*)cfd_calloc(total, sizeof(double));
     249         3994 :     double* k2_p = (double*)cfd_calloc(total, sizeof(double));
     250         3994 :     double* u0 = (double*)cfd_calloc(total, sizeof(double));
     251         3994 :     double* v0 = (double*)cfd_calloc(total, sizeof(double));
     252         3994 :     double* w0 = (double*)cfd_calloc(total, sizeof(double));
     253         3994 :     double* p0 = (double*)cfd_calloc(total, sizeof(double));
     254         3994 :     int needs_T_ws = (params->alpha > 0.0 || params->beta != 0.0);
     255         3994 :     double* T_energy_ws = needs_T_ws
     256            2 :         ? (double*)cfd_calloc(total, sizeof(double)) : NULL;
     257              : 
     258         3994 :     if (!k1_u || !k1_v || !k1_w || !k1_p ||
     259         3994 :         !k2_u || !k2_v || !k2_w || !k2_p ||
     260         3994 :         !u0 || !v0 || !w0 || !p0 ||
     261         3994 :         (needs_T_ws && !T_energy_ws)) {
     262            0 :         cfd_free(k1_u); cfd_free(k1_v); cfd_free(k1_w); cfd_free(k1_p);
     263            0 :         cfd_free(k2_u); cfd_free(k2_v); cfd_free(k2_w); cfd_free(k2_p);
     264            0 :         cfd_free(u0); cfd_free(v0); cfd_free(w0); cfd_free(p0);
     265            0 :         cfd_free(T_energy_ws);
     266            0 :         return CFD_ERROR_NOMEM;
     267              :     }
     268              : 
     269         3994 :     double dt = params->dt;
     270         3994 :     cfd_status_t status = CFD_SUCCESS;
     271              : 
     272         8026 :     for (int iter = 0; iter < params->max_iter; iter++) {
     273              :         /* Save Q^n */
     274         4032 :         memcpy(u0, field->u, bytes);
     275         4032 :         memcpy(v0, field->v, bytes);
     276         4032 :         memcpy(w0, field->w, bytes);
     277         4032 :         memcpy(p0, field->p, bytes);
     278              : 
     279              :         /* ---- Stage 1: k1 = RHS(Q^n) ---- */
     280         4032 :         memset(k1_u, 0, bytes);
     281         4032 :         memset(k1_v, 0, bytes);
     282         4032 :         memset(k1_w, 0, bytes);
     283         4032 :         memset(k1_p, 0, bytes);
     284              : 
     285         4032 :         compute_rhs(field->u, field->v, field->w, field->p, field->rho, field->T,
     286              :                      k1_u, k1_v, k1_w, k1_p,
     287              :                      grid, params, nx, ny, nz,
     288              :                      stride_z, k_start, k_end, inv_2dz, inv_dz2,
     289              :                      iter, dt);
     290              : 
     291              :         /* ---- Intermediate: field = Q^n + dt * k1 ---- */
     292     44146024 :         for (size_t n = 0; n < total; n++) {
     293     44141992 :             field->u[n] = u0[n] + dt * k1_u[n];
     294     44141992 :             field->v[n] = v0[n] + dt * k1_v[n];
     295     44141992 :             field->w[n] = w0[n] + dt * k1_w[n];
     296     44141992 :             field->p[n] = p0[n] + dt * k1_p[n];
     297              : 
     298     44141992 :             field->u[n] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->u[n]));
     299     44141992 :             field->v[n] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->v[n]));
     300     44141992 :             field->w[n] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->w[n]));
     301              :         }
     302              : 
     303              :         /* NOTE: Do NOT apply BCs between RK stages. The ghost cells carry
     304              :          * zero-derivative evolution (k1[ghost]=0), which is consistent with
     305              :          * the semi-discrete ODE system. Applying BCs here would modify the
     306              :          * intermediate state outside the ODE trajectory and reduce RK2 to
     307              :          * first-order temporal accuracy. */
     308              : 
     309              :         /* ---- Stage 2: k2 = RHS(Q_pred) ---- */
     310         4032 :         memset(k2_u, 0, bytes);
     311         4032 :         memset(k2_v, 0, bytes);
     312         4032 :         memset(k2_w, 0, bytes);
     313         4032 :         memset(k2_p, 0, bytes);
     314              : 
     315         4032 :         compute_rhs(field->u, field->v, field->w, field->p, field->rho, field->T,
     316              :                      k2_u, k2_v, k2_w, k2_p,
     317              :                      grid, params, nx, ny, nz,
     318              :                      stride_z, k_start, k_end, inv_2dz, inv_dz2,
     319              :                      iter, dt);
     320              : 
     321              :         /* ---- Final update: Q^{n+1} = Q^n + (dt/2)*(k1 + k2) ---- */
     322         4032 :         double half_dt = 0.5 * dt;
     323     44146024 :         for (size_t n = 0; n < total; n++) {
     324     44141992 :             field->u[n] = u0[n] + half_dt * (k1_u[n] + k2_u[n]);
     325     44141992 :             field->v[n] = v0[n] + half_dt * (k1_v[n] + k2_v[n]);
     326     44141992 :             field->w[n] = w0[n] + half_dt * (k1_w[n] + k2_w[n]);
     327     44141992 :             field->p[n] = p0[n] + half_dt * (k1_p[n] + k2_p[n]);
     328              : 
     329     44141992 :             field->u[n] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->u[n]));
     330     44141992 :             field->v[n] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->v[n]));
     331     44141992 :             field->w[n] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->w[n]));
     332              :         }
     333              : 
     334              :         /* Energy equation: advance temperature after RK2 velocity update */
     335              :         {
     336         4032 :             cfd_status_t energy_status = energy_step_explicit_with_workspace(
     337              :                 field, grid, params, dt, iter * dt, T_energy_ws, total);
     338         4032 :             if (energy_status != CFD_SUCCESS) {
     339            0 :                 status = energy_status;
     340            0 :                 goto cleanup;
     341              :             }
     342              :         }
     343              : 
     344              :         /* Apply BCs to final state only (after the full RK2 step).
     345              :          * This updates ghost cells for the next step's k1 evaluation.
     346              :          * Then apply configured thermal BCs (overwrites periodic T values). */
     347         4032 :         apply_boundary_conditions(field, grid);
     348         4032 :         status = energy_apply_thermal_bcs(field, params);
     349         4032 :         if (status != CFD_SUCCESS) {
     350            0 :             goto cleanup;
     351              :         }
     352              : 
     353              :         /* NaN / Inf check */
     354     44146024 :         for (size_t n = 0; n < total; n++) {
     355     44141992 :             if (!isfinite(field->u[n]) || !isfinite(field->v[n]) ||
     356     44141992 :                 !isfinite(field->w[n]) || !isfinite(field->p[n])) {
     357            0 :                 status = CFD_ERROR_DIVERGED;
     358            0 :                 goto cleanup;
     359              :             }
     360              :         }
     361              :     }
     362              : 
     363         3994 : cleanup:
     364         3994 :     cfd_free(k1_u); cfd_free(k1_v); cfd_free(k1_w); cfd_free(k1_p);
     365         3994 :     cfd_free(k2_u); cfd_free(k2_v); cfd_free(k2_w); cfd_free(k2_p);
     366         3994 :     cfd_free(u0); cfd_free(v0); cfd_free(w0); cfd_free(p0);
     367         3994 :     cfd_free(T_energy_ws);
     368              : 
     369         3994 :     return status;
     370              : }
        

Generated by: LCOV version 2.0-1