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

            Line data    Source code
       1              : #include "cfd/boundary/boundary_conditions.h"
       2              : #include "cfd/core/cfd_status.h"
       3              : #include "cfd/core/grid.h"
       4              : #include "cfd/core/indexing.h"
       5              : #include "cfd/core/memory.h"
       6              : #include "cfd/solvers/navier_stokes_solver.h"
       7              : #include "cfd/solvers/poisson_solver.h"
       8              : #include "cfd/solvers/energy_solver.h"
       9              : #include "../../energy/energy_solver_internal.h"
      10              : 
      11              : #include "../boundary_copy_utils.h"
      12              : 
      13              : #include <math.h>
      14              : #include <omp.h>
      15              : #include <stdio.h>
      16              : #include <string.h>
      17              : 
      18              : 
      19              : #ifndef M_PI
      20              : #define M_PI 3.14159265358979323846
      21              : #endif
      22              : 
      23              : // Physical limits
      24              : #define MAX_VELOCITY 100.0
      25              : 
      26          123 : cfd_status_t solve_projection_method_omp(flow_field* field, const grid* grid,
      27              :                                          const ns_solver_params_t* params) {
      28          123 :     if (!field || !grid || !params) {
      29              :         return CFD_ERROR_INVALID;
      30              :     }
      31          123 :     if (field->nx < 3 || field->ny < 3 || (field->nz > 1 && field->nz < 3)) {
      32              :         return CFD_ERROR_INVALID;
      33              :     }
      34              : 
      35          123 :     size_t nx = field->nx;
      36          123 :     size_t ny = field->ny;
      37          123 :     size_t nz = field->nz;
      38              : 
      39              :     /* Reject non-uniform z-spacing */
      40          123 :     if (nz > 1 && grid->dz) {
      41            7 :         for (size_t kk = 1; kk < nz - 1; kk++) {
      42            6 :             if (fabs(grid->dz[kk] - grid->dz[0]) > 1e-14) {
      43              :                 return CFD_ERROR_INVALID;
      44              :             }
      45              :         }
      46              :     }
      47              : 
      48          123 :     size_t plane = nx * ny;
      49          123 :     size_t total = plane * nz;
      50              : 
      51              :     /* Branch-free 3D constants */
      52          123 :     size_t stride_z = (nz > 1) ? plane : 0;
      53          123 :     size_t k_start  = (nz > 1) ? 1 : 0;
      54          123 :     size_t k_end    = (nz > 1) ? (nz - 1) : 1;
      55              : 
      56          123 :     double dx = grid->dx[0];
      57          123 :     double dy = grid->dy[0];
      58          123 :     double dz = (nz > 1 && grid->dz) ? grid->dz[0] : 0.0;
      59          123 :     double dt = params->dt;
      60          123 :     double nu = params->mu;
      61          123 :     double inv_2dz = (nz > 1 && grid->dz) ? 1.0 / (2.0 * dz) : 0.0;
      62            1 :     double inv_dz2 = (nz > 1 && grid->dz) ? 1.0 / (dz * dz) : 0.0;
      63              : 
      64          123 :     double* u_star = (double*)cfd_calloc(total, sizeof(double));
      65          123 :     double* v_star = (double*)cfd_calloc(total, sizeof(double));
      66          123 :     double* w_star = (double*)cfd_calloc(total, sizeof(double));
      67          123 :     double* p_new = (double*)cfd_calloc(total, sizeof(double));
      68          123 :     double* p_temp = (double*)cfd_calloc(total, sizeof(double));
      69          123 :     double* rhs = (double*)cfd_calloc(total, sizeof(double));
      70          123 :     int needs_T_ws = (params->alpha > 0.0 || params->beta != 0.0);
      71          123 :     double* T_energy_ws = needs_T_ws
      72            1 :         ? (double*)cfd_calloc(total, sizeof(double)) : NULL;
      73              : 
      74          123 :     if (!u_star || !v_star || !w_star || !p_new || !p_temp || !rhs ||
      75          123 :         (needs_T_ws && !T_energy_ws)) {
      76            0 :         cfd_free(u_star);
      77            0 :         cfd_free(v_star);
      78            0 :         cfd_free(w_star);
      79            0 :         cfd_free(p_new);
      80            0 :         cfd_free(p_temp);
      81            0 :         cfd_free(rhs);
      82            0 :         cfd_free(T_energy_ws);
      83            0 :         return CFD_ERROR_NOMEM;
      84              :     }
      85              : 
      86          123 :     memcpy(u_star, field->u, total * sizeof(double));
      87          123 :     memcpy(v_star, field->v, total * sizeof(double));
      88          123 :     memcpy(w_star, field->w, total * sizeof(double));
      89          123 :     memcpy(p_new, field->p, total * sizeof(double));
      90              : 
      91          265 :     for (int iter = 0; iter < params->max_iter; iter++) {
      92              :         /* STEP 1: Predictor — compute u_star, v_star, w_star without pressure */
      93          289 :         for (size_t kk = k_start; kk < k_end; kk++) {
      94          147 :             int j;
      95          147 : #pragma omp parallel for schedule(static)
      96              :             for (j = 1; j < (int)ny - 1; j++) {
      97              :                 for (int i = 1; i < (int)nx - 1; i++) {
      98              :                     size_t idx = kk * stride_z + IDX_2D(i, j, nx);
      99              : 
     100              :                     double u = field->u[idx];
     101              :                     double v = field->v[idx];
     102              :                     double w = field->w[idx];
     103              : 
     104              :                     double du_dx = (field->u[idx + 1] - field->u[idx - 1]) / (2.0 * dx);
     105              :                     double du_dy = (field->u[idx + nx] - field->u[idx - nx]) / (2.0 * dy);
     106              :                     double du_dz = (field->u[idx + stride_z] - field->u[idx - stride_z]) * inv_2dz;
     107              :                     double dv_dx = (field->v[idx + 1] - field->v[idx - 1]) / (2.0 * dx);
     108              :                     double dv_dy = (field->v[idx + nx] - field->v[idx - nx]) / (2.0 * dy);
     109              :                     double dv_dz = (field->v[idx + stride_z] - field->v[idx - stride_z]) * inv_2dz;
     110              :                     double dw_dx = (field->w[idx + 1] - field->w[idx - 1]) / (2.0 * dx);
     111              :                     double dw_dy = (field->w[idx + nx] - field->w[idx - nx]) / (2.0 * dy);
     112              :                     double dw_dz = (field->w[idx + stride_z] - field->w[idx - stride_z]) * inv_2dz;
     113              : 
     114              :                     double conv_u = u * du_dx + v * du_dy + w * du_dz;
     115              :                     double conv_v = u * dv_dx + v * dv_dy + w * dv_dz;
     116              :                     double conv_w = u * dw_dx + v * dw_dy + w * dw_dz;
     117              : 
     118              :                     double d2u_dx2 = (field->u[idx + 1] - 2.0 * u + field->u[idx - 1]) / (dx * dx);
     119              :                     double d2u_dy2 = (field->u[idx + nx] - 2.0 * u + field->u[idx - nx]) / (dy * dy);
     120              :                     double d2u_dz2 = (field->u[idx + stride_z] - 2.0 * u + field->u[idx - stride_z]) * inv_dz2;
     121              :                     double d2v_dx2 = (field->v[idx + 1] - 2.0 * v + field->v[idx - 1]) / (dx * dx);
     122              :                     double d2v_dy2 = (field->v[idx + nx] - 2.0 * v + field->v[idx - nx]) / (dy * dy);
     123              :                     double d2v_dz2 = (field->v[idx + stride_z] - 2.0 * v + field->v[idx - stride_z]) * inv_dz2;
     124              :                     double d2w_dx2 = (field->w[idx + 1] - 2.0 * w + field->w[idx - 1]) / (dx * dx);
     125              :                     double d2w_dy2 = (field->w[idx + nx] - 2.0 * w + field->w[idx - nx]) / (dy * dy);
     126              :                     double d2w_dz2 = (field->w[idx + stride_z] - 2.0 * w + field->w[idx - stride_z]) * inv_dz2;
     127              : 
     128              :                     double visc_u = nu * (d2u_dx2 + d2u_dy2 + d2u_dz2);
     129              :                     double visc_v = nu * (d2v_dx2 + d2v_dy2 + d2v_dz2);
     130              :                     double visc_w = nu * (d2w_dx2 + d2w_dy2 + d2w_dz2);
     131              : 
     132              :                     double source_u = 0.0;
     133              :                     double source_v = 0.0;
     134              :                     double source_w = 0.0;
     135              :                     double z_coord = (nz > 1 && grid->z) ? grid->z[kk] : 0.0;
     136              :                     if (params->source_func) {
     137              :                         params->source_func(grid->x[i], grid->y[j], z_coord, iter * dt,
     138              :                                             params->source_context,
     139              :                                             &source_u, &source_v, &source_w);
     140              :                     } else if (params->source_amplitude_u > 0) {
     141              :                         source_u = params->source_amplitude_u * sin(M_PI * grid->y[j]) *
     142              :                                    exp(-params->source_decay_rate * iter * dt);
     143              :                         source_v = params->source_amplitude_v * sin(2.0 * M_PI * grid->x[i]) *
     144              :                                    exp(-params->source_decay_rate * iter * dt);
     145              :                     }
     146              : 
     147              :                     // Boussinesq buoyancy source (no-op when beta == 0)
     148              :                     energy_compute_buoyancy(field->T[idx], params,
     149              :                                             &source_u, &source_v, &source_w);
     150              : 
     151              :                     u_star[idx] = u + dt * (-conv_u + visc_u + source_u);
     152              :                     v_star[idx] = v + dt * (-conv_v + visc_v + source_v);
     153              :                     w_star[idx] = w + dt * (-conv_w + visc_w + source_w);
     154              : 
     155              :                     u_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, u_star[idx]));
     156              :                     v_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, v_star[idx]));
     157              :                     w_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, w_star[idx]));
     158              :                 }
     159              :             }
     160              :         }
     161              : 
     162              :         /* Copy boundary values from field to star arrays */
     163          142 :         copy_boundary_velocities_3d(u_star, v_star, w_star,
     164          142 :                                     field->u, field->v, field->w, nx, ny, nz);
     165              : 
     166              :         /* STEP 2: Pressure Poisson equation */
     167          142 :         double rho = field->rho[0] < 1e-10 ? 1.0 : field->rho[0];
     168              : 
     169          289 :         for (size_t kk = k_start; kk < k_end; kk++) {
     170          147 :             int j;
     171          147 : #pragma omp parallel for schedule(static)
     172              :             for (j = 1; j < (int)ny - 1; j++) {
     173              :                 for (int i = 1; i < (int)nx - 1; i++) {
     174              :                     size_t idx = kk * stride_z + IDX_2D(i, j, nx);
     175              :                     double du_star_dx = (u_star[idx + 1] - u_star[idx - 1]) / (2.0 * dx);
     176              :                     double dv_star_dy = (v_star[idx + nx] - v_star[idx - nx]) / (2.0 * dy);
     177              :                     double dw_star_dz = (w_star[idx + stride_z] - w_star[idx - stride_z]) * inv_2dz;
     178              :                     rhs[idx] = (rho / dt) * (du_star_dx + dv_star_dy + dw_star_dz);
     179              :                 }
     180              :             }
     181              :         }
     182              : 
     183              :         /* Use OMP CG for parallelized Poisson solve */
     184          142 :         int poisson_iters = poisson_solve_3d(p_new, p_temp, rhs, nx, ny, nz,
     185              :                                              dx, dy, dz, POISSON_SOLVER_CG_OMP);
     186              : 
     187          142 :         if (poisson_iters < 0) {
     188            0 :             cfd_free(u_star);
     189            0 :             cfd_free(v_star);
     190            0 :             cfd_free(w_star);
     191            0 :             cfd_free(p_new);
     192            0 :             cfd_free(p_temp);
     193            0 :             cfd_free(rhs);
     194            0 :             cfd_free(T_energy_ws);
     195            0 :             return CFD_ERROR_MAX_ITER;
     196              :         }
     197              : 
     198              :         /* STEP 3: Corrector — project velocities with pressure gradient */
     199          142 :         double dt_over_rho = dt / rho;
     200          289 :         for (size_t kk = k_start; kk < k_end; kk++) {
     201          147 :             int j;
     202          147 : #pragma omp parallel for schedule(static)
     203              :             for (j = 1; j < (int)ny - 1; j++) {
     204              :                 for (int i = 1; i < (int)nx - 1; i++) {
     205              :                     size_t idx = kk * stride_z + IDX_2D(i, j, nx);
     206              :                     double dp_dx = (p_new[idx + 1] - p_new[idx - 1]) / (2.0 * dx);
     207              :                     double dp_dy = (p_new[idx + nx] - p_new[idx - nx]) / (2.0 * dy);
     208              :                     double dp_dz = (p_new[idx + stride_z] - p_new[idx - stride_z]) * inv_2dz;
     209              : 
     210              :                     field->u[idx] = u_star[idx] - dt_over_rho * dp_dx;
     211              :                     field->v[idx] = v_star[idx] - dt_over_rho * dp_dy;
     212              :                     field->w[idx] = w_star[idx] - dt_over_rho * dp_dz;
     213              : 
     214              :                     field->u[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->u[idx]));
     215              :                     field->v[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->v[idx]));
     216              :                     field->w[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->w[idx]));
     217              :                 }
     218              :             }
     219              :         }
     220              : 
     221          142 :         memcpy(field->p, p_new, total * sizeof(double));
     222              : 
     223              :         /* Energy equation: advance temperature after velocity correction */
     224              :         {
     225          142 :             cfd_status_t energy_status = energy_step_explicit_omp_with_workspace(
     226              :                 field, grid, params, dt, iter * dt, T_energy_ws, total);
     227          142 :             if (energy_status != CFD_SUCCESS) {
     228            0 :                 cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     229            0 :                 cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     230            0 :                 cfd_free(T_energy_ws);
     231            0 :                 return energy_status;
     232              :             }
     233              :         }
     234              : 
     235              :         /* Apply configured thermal BCs to temperature field */
     236          142 :         cfd_status_t bc_status = energy_apply_thermal_bcs(field, params);
     237          142 :         if (bc_status != CFD_SUCCESS) {
     238            0 :             cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     239            0 :             cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     240            0 :             cfd_free(T_energy_ws);
     241            0 :             return bc_status;
     242              :         }
     243              : 
     244              :         /* Copy boundary velocity values from star arrays (which have caller's BCs) */
     245          142 :         copy_boundary_velocities_3d(field->u, field->v, field->w,
     246              :                                     u_star, v_star, w_star, nx, ny, nz);
     247              : 
     248              :         /* Check for NaN/Inf values (parallelized) */
     249          142 :         int has_nan = 0;
     250          142 :         ptrdiff_t total_int = (ptrdiff_t)total;
     251          142 :         ptrdiff_t ii;
     252          142 : #pragma omp parallel for reduction(| : has_nan) schedule(static)
     253              :         for (ii = 0; ii < total_int; ii++) {
     254              :             if (!isfinite(field->u[ii]) || !isfinite(field->v[ii]) ||
     255              :                 !isfinite(field->w[ii]) || !isfinite(field->p[ii])) {
     256              :                 has_nan = 1;
     257              :             }
     258              :         }
     259          142 :         if (has_nan) {
     260            0 :             cfd_free(u_star);
     261            0 :             cfd_free(v_star);
     262            0 :             cfd_free(w_star);
     263            0 :             cfd_free(p_new);
     264            0 :             cfd_free(p_temp);
     265            0 :             cfd_free(rhs);
     266            0 :             cfd_free(T_energy_ws);
     267            0 :             return CFD_ERROR_DIVERGED;
     268              :         }
     269              :     }
     270              : 
     271          123 :     cfd_free(u_star);
     272          123 :     cfd_free(v_star);
     273          123 :     cfd_free(w_star);
     274          123 :     cfd_free(p_new);
     275          123 :     cfd_free(p_temp);
     276          123 :     cfd_free(rhs);
     277          123 :     cfd_free(T_energy_ws);
     278          123 :     return CFD_SUCCESS;
     279              : }
        

Generated by: LCOV version 2.0-1