LCOV - code coverage report
Current view: top level - solvers/navier_stokes/omp - solver_explicit_euler_omp.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 74.7 % 75 56
Test Date: 2026-06-23 13:41:07 Functions: 100.0 % 1 1

            Line data    Source code
       1              : #include "cfd/core/cfd_status.h"
       2              : #include "cfd/core/grid.h"
       3              : #include "cfd/core/indexing.h"
       4              : #include "cfd/core/memory.h"
       5              : 
       6              : #include "cfd/solvers/navier_stokes_solver.h"
       7              : #include "cfd/solvers/energy_solver.h"
       8              : #include "../../energy/energy_solver_internal.h"
       9              : #include "../boundary_copy_utils.h"
      10              : #include <math.h>
      11              : #include <omp.h>
      12              : #include <stdio.h>
      13              : #include <string.h>
      14              : 
      15              : 
      16              : 
      17              : #ifndef M_PI
      18              : #define M_PI 3.14159265358979323846
      19              : #endif
      20              : 
      21              : // Physical stability limits (same as cpu solver)
      22              : #define MAX_DERIVATIVE_LIMIT        100.0
      23              : #define MAX_SECOND_DERIVATIVE_LIMIT 1000.0
      24              : #define MAX_VELOCITY_LIMIT          100.0
      25              : #define MAX_DIVERGENCE_LIMIT        10.0
      26              : #define DT_CONSERVATIVE_LIMIT       0.0001
      27              : #define UPDATE_LIMIT                1.0
      28              : #define PRESSURE_UPDATE_FACTOR      0.1
      29              : 
      30              : // Internal OpenMP explicit Euler implementation
      31          213 : cfd_status_t explicit_euler_omp_impl(flow_field* field, const grid* grid,
      32              :                                      const ns_solver_params_t* params) {
      33          213 :     if (field->nx < 3 || field->ny < 3 || (field->nz > 1 && field->nz < 3)) {
      34              :         return CFD_ERROR_INVALID;
      35              :     }
      36              : 
      37          213 :     size_t nz = field->nz;
      38          213 :     if (nz > 1 && grid->dz) {
      39           14 :         for (size_t kk = 1; kk < nz - 1; kk++) {
      40           12 :             if (fabs(grid->dz[kk] - grid->dz[0]) > 1e-14) {
      41              :                 return CFD_ERROR_INVALID;
      42              :             }
      43              :         }
      44              :     }
      45              : 
      46          213 :     size_t nx = field->nx;
      47          213 :     size_t ny = field->ny;
      48          213 :     size_t plane = nx * ny;
      49          213 :     size_t total = plane * nz;
      50              : 
      51          213 :     size_t stride_z = (nz > 1) ? plane : 0;
      52          213 :     size_t k_start  = (nz > 1) ? 1 : 0;
      53          213 :     size_t k_end    = (nz > 1) ? (nz - 1) : 1;
      54          213 :     double inv_2dz  = (nz > 1 && grid->dz) ? 1.0 / (2.0 * grid->dz[0]) : 0.0;
      55            2 :     double inv_dz2  = (nz > 1 && grid->dz) ? 1.0 / (grid->dz[0] * grid->dz[0]) : 0.0;
      56              : 
      57              :     // Allocate temporary arrays
      58          213 :     double* u_new = (double*)cfd_calloc(total, sizeof(double));
      59          213 :     double* v_new = (double*)cfd_calloc(total, sizeof(double));
      60          213 :     double* w_new = (double*)cfd_calloc(total, sizeof(double));
      61          213 :     double* p_new = (double*)cfd_calloc(total, sizeof(double));
      62          213 :     int needs_T_ws = (params->alpha > 0.0 || params->beta != 0.0);
      63          213 :     double* T_energy_ws = needs_T_ws
      64            1 :         ? (double*)cfd_calloc(total, sizeof(double)) : NULL;
      65              : 
      66          213 :     if (!u_new || !v_new || !w_new || !p_new ||
      67          213 :         (needs_T_ws && !T_energy_ws)) {
      68            0 :         cfd_free(u_new);
      69            0 :         cfd_free(v_new);
      70            0 :         cfd_free(w_new);
      71            0 :         cfd_free(p_new);
      72            0 :         cfd_free(T_energy_ws);
      73            0 :         return CFD_ERROR_NOMEM;
      74              :     }
      75              : 
      76              :     // Initialize with current values
      77          213 :     memcpy(u_new, field->u, total * sizeof(double));
      78          213 :     memcpy(v_new, field->v, total * sizeof(double));
      79          213 :     memcpy(w_new, field->w, total * sizeof(double));
      80          213 :     memcpy(p_new, field->p, total * sizeof(double));
      81              : 
      82          213 :     double conservative_dt = fmin(params->dt, DT_CONSERVATIVE_LIMIT);
      83              : 
      84          445 :     for (int iter = 0; iter < params->max_iter; iter++) {
      85          474 :         for (size_t kk = k_start; kk < k_end; kk++) {
      86          242 :             int j;
      87          242 : #pragma omp parallel for schedule(static)
      88              :             for (j = 1; j < (int)ny - 1; j++) {
      89              :                 for (int i = 1; i < (int)nx - 1; i++) {
      90              :                     size_t idx = (kk * stride_z) + IDX_2D(i, j, nx);
      91              : 
      92              :                     // Derivatives
      93              :                     double du_dx = (field->u[idx + 1] - field->u[idx - 1]) / (2.0 * grid->dx[i]);
      94              :                     double du_dy =
      95              :                         (field->u[idx + nx] - field->u[idx - nx]) / (2.0 * grid->dy[j]);
      96              :                     double dv_dx = (field->v[idx + 1] - field->v[idx - 1]) / (2.0 * grid->dx[i]);
      97              :                     double dv_dy =
      98              :                         (field->v[idx + nx] - field->v[idx - nx]) / (2.0 * grid->dy[j]);
      99              : 
     100              :                     double dp_dx = (field->p[idx + 1] - field->p[idx - 1]) / (2.0 * grid->dx[i]);
     101              :                     double dp_dy =
     102              :                         (field->p[idx + nx] - field->p[idx - nx]) / (2.0 * grid->dy[j]);
     103              : 
     104              :                     double d2u_dx2 = (field->u[idx + 1] - 2.0 * field->u[idx] + field->u[idx - 1]) /
     105              :                                      (grid->dx[i] * grid->dx[i]);
     106              :                     double d2u_dy2 =
     107              :                         (field->u[idx + nx] - 2.0 * field->u[idx] + field->u[idx - nx]) /
     108              :                         (grid->dy[j] * grid->dy[j]);
     109              :                     double d2v_dx2 = (field->v[idx + 1] - 2.0 * field->v[idx] + field->v[idx - 1]) /
     110              :                                      (grid->dx[i] * grid->dx[i]);
     111              :                     double d2v_dy2 =
     112              :                         (field->v[idx + nx] - 2.0 * field->v[idx] + field->v[idx - nx]) /
     113              :                         (grid->dy[j] * grid->dy[j]);
     114              : 
     115              :                     double du_dz = (field->u[idx + stride_z] - field->u[idx - stride_z]) * inv_2dz;
     116              :                     double dv_dz = (field->v[idx + stride_z] - field->v[idx - stride_z]) * inv_2dz;
     117              :                     double dw_dx = (field->w[idx + 1] - field->w[idx - 1]) / (2.0 * grid->dx[i]);
     118              :                     double dw_dy = (field->w[idx + nx] - field->w[idx - nx]) / (2.0 * grid->dy[j]);
     119              :                     double dw_dz = (field->w[idx + stride_z] - field->w[idx - stride_z]) * inv_2dz;
     120              :                     double dp_dz = (field->p[idx + stride_z] - field->p[idx - stride_z]) * inv_2dz;
     121              : 
     122              :                     double d2u_dz2 = (field->u[idx + stride_z] - 2.0 * field->u[idx] + field->u[idx - stride_z]) * inv_dz2;
     123              :                     double d2v_dz2 = (field->v[idx + stride_z] - 2.0 * field->v[idx] + field->v[idx - stride_z]) * inv_dz2;
     124              :                     double d2w_dx2 = (field->w[idx + 1] - 2.0 * field->w[idx] + field->w[idx - 1]) / (grid->dx[i] * grid->dx[i]);
     125              :                     double d2w_dy2 = (field->w[idx + nx] - 2.0 * field->w[idx] + field->w[idx - nx]) / (grid->dy[j] * grid->dy[j]);
     126              :                     double d2w_dz2 = (field->w[idx + stride_z] - 2.0 * field->w[idx] + field->w[idx - stride_z]) * inv_dz2;
     127              : 
     128              :                     if (field->rho[idx] <= 1e-10) {
     129              :                         continue;
     130              :                     }
     131              : 
     132              :                     double nu = params->mu / fmax(field->rho[idx], 1e-10);
     133              :                     nu = fmin(nu, 1.0);
     134              : 
     135              :                     // Source terms
     136              :                     double source_u = 0.0;
     137              :                     double source_v = 0.0;
     138              :                     double source_w = 0.0;
     139              :                     double z_coord = (nz > 1 && grid->z) ? grid->z[kk] : 0.0;
     140              :                     if (params->source_func) {
     141              :                         params->source_func(grid->x[i], grid->y[j], z_coord,
     142              :                                             iter * conservative_dt,
     143              :                                             params->source_context,
     144              :                                             &source_u, &source_v, &source_w);
     145              :                     } else {
     146              :                         source_u = params->source_amplitude_u * sin(M_PI * grid->y[j]) *
     147              :                                    exp(-params->source_decay_rate * iter * conservative_dt);
     148              :                         source_v = params->source_amplitude_v * sin(2.0 * M_PI * grid->x[i]) *
     149              :                                    exp(-params->source_decay_rate * iter * conservative_dt);
     150              :                     }
     151              : 
     152              :                     // Boussinesq buoyancy source (no-op when beta == 0)
     153              :                     energy_compute_buoyancy(field->T[idx], params,
     154              :                                             &source_u, &source_v, &source_w);
     155              : 
     156              :                     // Update u
     157              :                     double du = conservative_dt * (-field->u[idx] * du_dx - field->v[idx] * du_dy
     158              :                                                    - field->w[idx] * du_dz
     159              :                                                    - dp_dx / fmax(field->rho[idx], 1e-10)
     160              :                                                    + nu * (d2u_dx2 + d2u_dy2 + d2u_dz2) + source_u);
     161              : 
     162              :                     // Update v
     163              :                     double dv = conservative_dt * (-field->u[idx] * dv_dx - field->v[idx] * dv_dy
     164              :                                                    - field->w[idx] * dv_dz
     165              :                                                    - dp_dy / fmax(field->rho[idx], 1e-10)
     166              :                                                    + nu * (d2v_dx2 + d2v_dy2 + d2v_dz2) + source_v);
     167              : 
     168              :                     // Update w
     169              :                     double dw = conservative_dt * (-field->u[idx] * dw_dx - field->v[idx] * dw_dy
     170              :                                                    - field->w[idx] * dw_dz
     171              :                                                    - dp_dz / fmax(field->rho[idx], 1e-10)
     172              :                                                    + nu * (d2w_dx2 + d2w_dy2 + d2w_dz2) + source_w);
     173              : 
     174              :                     du = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, du));
     175              :                     dv = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, dv));
     176              :                     dw = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, dw));
     177              : 
     178              :                     u_new[idx] = field->u[idx] + du;
     179              :                     v_new[idx] = field->v[idx] + dv;
     180              : 
     181              :                     // Limit velocity
     182              :                     u_new[idx] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, u_new[idx]));
     183              :                     v_new[idx] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, v_new[idx]));
     184              :                     w_new[idx] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, field->w[idx] + dw));
     185              : 
     186              :                     // Pressure update
     187              :                     double divergence = du_dx + dv_dy + dw_dz;
     188              :                     divergence = fmax(-MAX_DIVERGENCE_LIMIT, fmin(MAX_DIVERGENCE_LIMIT, divergence));
     189              : 
     190              :                     double dp =
     191              :                         -PRESSURE_UPDATE_FACTOR * conservative_dt * field->rho[idx] * divergence;
     192              :                     dp = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, dp));
     193              :                     p_new[idx] = field->p[idx] + dp;
     194              :                 }
     195              :             }
     196              :         }
     197              : 
     198              :         // Copy back (implicit barrier at end of parallel for)
     199          232 :         memcpy(field->u, u_new, total * sizeof(double));
     200          232 :         memcpy(field->v, v_new, total * sizeof(double));
     201          232 :         memcpy(field->w, w_new, total * sizeof(double));
     202          232 :         memcpy(field->p, p_new, total * sizeof(double));
     203              : 
     204              :         // Energy equation: advance temperature using updated velocity
     205              :         {
     206          232 :             cfd_status_t energy_status = energy_step_explicit_omp_with_workspace(
     207              :                 field, grid, params, conservative_dt,
     208              :                 iter * conservative_dt, T_energy_ws, total);
     209          232 :             if (energy_status != CFD_SUCCESS) {
     210            0 :                 cfd_free(u_new); cfd_free(v_new); cfd_free(w_new);
     211            0 :                 cfd_free(p_new); cfd_free(T_energy_ws);
     212            0 :                 return energy_status;
     213              :             }
     214              :         }
     215              : 
     216              :         // Store caller-set boundary values before apply_boundary_conditions overwrites them,
     217              :         // then restore them afterward. Then apply configured thermal BCs.
     218          232 :         copy_boundary_velocities_3d(u_new, v_new, w_new,
     219          232 :                                     field->u, field->v, field->w, nx, ny, nz);
     220          232 :         apply_boundary_conditions(field, grid);
     221          232 :         copy_boundary_velocities_3d(field->u, field->v, field->w,
     222              :                                     u_new, v_new, w_new, nx, ny, nz);
     223          232 :         cfd_status_t bc_status = energy_apply_thermal_bcs(field, params);
     224          232 :         if (bc_status != CFD_SUCCESS) {
     225            0 :             cfd_free(u_new); cfd_free(v_new); cfd_free(w_new);
     226            0 :             cfd_free(p_new); cfd_free(T_energy_ws);
     227            0 :             return bc_status;
     228              :         }
     229              : 
     230              :         // Check for NaN/Inf values
     231          232 :         int has_nan = 0;
     232          232 :         ptrdiff_t total_int = (ptrdiff_t)total;
     233          232 :         ptrdiff_t ii;
     234          232 : #pragma omp parallel for reduction(| : has_nan) schedule(static)
     235              :         for (ii = 0; ii < total_int; ii++) {
     236              :             if (!isfinite(field->u[ii]) || !isfinite(field->v[ii]) ||
     237              :                 !isfinite(field->w[ii]) || !isfinite(field->p[ii])) {
     238              :                 has_nan = 1;
     239              :             }
     240              :         }
     241              : 
     242          232 :         if (has_nan) {
     243            0 :             cfd_free(u_new);
     244            0 :             cfd_free(v_new);
     245            0 :             cfd_free(w_new);
     246            0 :             cfd_free(p_new);
     247            0 :             cfd_free(T_energy_ws);
     248            0 :             cfd_set_error(CFD_ERROR_DIVERGED,
     249              :                           "NaN/Inf detected in explicit_euler_omp step");
     250            0 :             return CFD_ERROR_DIVERGED;
     251              :         }
     252              :     }
     253              : 
     254          213 :     cfd_free(u_new);
     255          213 :     cfd_free(v_new);
     256          213 :     cfd_free(w_new);
     257          213 :     cfd_free(p_new);
     258          213 :     cfd_free(T_energy_ws);
     259              : 
     260          213 :     return CFD_SUCCESS;
     261              : }
        

Generated by: LCOV version 2.0-1