LCOV - code coverage report
Current view: top level - solvers/navier_stokes/cpu - solver_projection.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 86.8 % 144 125
Test Date: 2026-06-23 13:41:07 Functions: 100.0 % 1 1

            Line data    Source code
       1              : /**
       2              :  * Projection Method NSSolver (Chorin's Method)
       3              :  *
       4              :  * The projection method solves the incompressible Navier-Stokes equations
       5              :  * by splitting the solution into two steps:
       6              :  *
       7              :  * 1. Predictor Step: Compute intermediate velocity u* ignoring pressure
       8              :  *    u* = u^n + dt * (-u·∇u + ν∇²u + f)
       9              :  *
      10              :  * 2. Pressure Projection: Solve Poisson equation for pressure
      11              :  *    ∇²p = (ρ/dt) * ∇·u*
      12              :  *
      13              :  * 3. Corrector Step: Project velocity to be divergence-free
      14              :  *    u^(n+1) = u* - (dt/ρ) * ∇p
      15              :  *
      16              :  * This ensures ∇·u^(n+1) = 0 (incompressibility constraint)
      17              :  */
      18              : 
      19              : #include "cfd/boundary/boundary_conditions.h"
      20              : #include "cfd/core/cfd_status.h"
      21              : #include "cfd/core/grid.h"
      22              : #include "cfd/core/indexing.h"
      23              : #include "cfd/core/memory.h"
      24              : #include "cfd/solvers/energy_solver.h"
      25              : #include "cfd/solvers/navier_stokes_solver.h"
      26              : #include "cfd/solvers/poisson_solver.h"
      27              : 
      28              : #include "../../energy/energy_solver_internal.h"
      29              : #include "../boundary_copy_utils.h"
      30              : 
      31              : #include <math.h>
      32              : #include <stdio.h>
      33              : #include <string.h>
      34              : 
      35              : #ifndef M_PI
      36              : #define M_PI 3.14159265358979323846
      37              : #endif
      38              : 
      39              : // Physical limits
      40              : #define MAX_VELOCITY 100.0
      41              : #define MAX_PRESSURE 1000.0
      42              : 
      43              : /**
      44              :  * Projection Method Solver
      45              :  */
      46         6085 : cfd_status_t solve_projection_method(flow_field* field, const grid* grid,
      47              :                                      const ns_solver_params_t* params) {
      48         6085 :     if (!field || !grid || !params) {
      49              :         return CFD_ERROR_INVALID;
      50              :     }
      51         6085 :     if (field->nx < 3 || field->ny < 3 || (field->nz > 1 && field->nz < 3)) {
      52              :         return CFD_ERROR_INVALID;
      53              :     }
      54              : 
      55         6085 :     size_t nx = field->nx;
      56         6085 :     size_t ny = field->ny;
      57         6085 :     size_t nz = field->nz;
      58              : 
      59              :     /* Reject non-uniform z-spacing (solver uses constant dz) */
      60         6085 :     if (nz > 1 && grid->dz) {
      61            7 :         for (size_t k = 1; k < nz - 1; k++) {
      62            6 :             if (fabs(grid->dz[k] - grid->dz[0]) > 1e-14) {
      63              :                 return CFD_ERROR_INVALID;
      64              :             }
      65              :         }
      66              :     }
      67              : 
      68         6085 :     size_t plane = nx * ny;
      69         6085 :     size_t total = plane * nz;
      70         6085 :     size_t bytes = total * sizeof(double);
      71              : 
      72              :     /* Grid spacing (uniform grid) */
      73         6085 :     double dx = grid->dx[0];
      74         6085 :     double dy = grid->dy[0];
      75         6085 :     double dz = (nz > 1 && grid->dz) ? grid->dz[0] : 0.0;
      76         6085 :     double dt = params->dt;
      77         6085 :     double nu = params->mu;
      78              : 
      79              :     /* Branch-free 3D constants */
      80         6085 :     size_t stride_z = (nz > 1) ? plane : 0;
      81         6085 :     size_t k_start  = (nz > 1) ? 1 : 0;
      82         6085 :     size_t k_end    = (nz > 1) ? (nz - 1) : 1;
      83         6085 :     double inv_2dz  = (nz > 1 && grid->dz) ? 1.0 / (2.0 * dz) : 0.0;
      84            1 :     double inv_dz2  = (nz > 1 && grid->dz) ? 1.0 / (dz * dz) : 0.0;
      85              : 
      86              :     /* Allocate temporary arrays */
      87         6085 :     double* u_star = (double*)cfd_calloc(total, sizeof(double));
      88         6085 :     double* v_star = (double*)cfd_calloc(total, sizeof(double));
      89         6085 :     double* w_star = (double*)cfd_calloc(total, sizeof(double));
      90         6085 :     double* p_new  = (double*)cfd_calloc(total, sizeof(double));
      91         6085 :     double* p_temp = (double*)cfd_calloc(total, sizeof(double));
      92         6085 :     double* rhs    = (double*)cfd_calloc(total, sizeof(double));
      93         6085 :     int needs_T_ws = (params->alpha > 0.0 || params->beta != 0.0);
      94         6085 :     double* T_energy_ws = needs_T_ws
      95         1881 :         ? (double*)cfd_calloc(total, sizeof(double)) : NULL;
      96              : 
      97         6085 :     if (!u_star || !v_star || !w_star || !p_new || !p_temp || !rhs ||
      98         6085 :         (needs_T_ws && !T_energy_ws)) {
      99            0 :         cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     100            0 :         cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     101            0 :         cfd_free(T_energy_ws);
     102            0 :         return CFD_ERROR_NOMEM;
     103              :     }
     104              : 
     105         6085 :     memcpy(u_star, field->u, bytes);
     106         6085 :     memcpy(v_star, field->v, bytes);
     107         6085 :     memcpy(w_star, field->w, bytes);
     108         6085 :     memcpy(p_new, field->p, bytes);
     109              : 
     110              :     /* Main iteration loop */
     111        12208 :     for (int iter = 0; iter < params->max_iter; iter++) {
     112              :         /* ============================================================
     113              :          * STEP 1: Predictor - Compute intermediate velocity u*
     114              :          * u* = u^n + dt * (-u·∇u + ν∇²u + f)
     115              :          * ============================================================ */
     116        12251 :         for (size_t k = k_start; k < k_end; k++) {
     117       153946 :             for (size_t j = 1; j < ny - 1; j++) {
     118      4652628 :                 for (size_t i = 1; i < nx - 1; i++) {
     119      4504810 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     120              : 
     121      4504810 :                     double u = field->u[idx];
     122      4504810 :                     double v = field->v[idx];
     123      4504810 :                     double w = field->w[idx];
     124              : 
     125              :                     /* First derivatives (central) */
     126      4504810 :                     double du_dx = (field->u[idx + 1] - field->u[idx - 1]) / (2.0 * dx);
     127      4504810 :                     double du_dy = (field->u[idx + nx] - field->u[idx - nx]) / (2.0 * dy);
     128      4504810 :                     double du_dz = (field->u[idx + stride_z] - field->u[idx - stride_z]) * inv_2dz;
     129              : 
     130      4504810 :                     double dv_dx = (field->v[idx + 1] - field->v[idx - 1]) / (2.0 * dx);
     131      4504810 :                     double dv_dy = (field->v[idx + nx] - field->v[idx - nx]) / (2.0 * dy);
     132      4504810 :                     double dv_dz = (field->v[idx + stride_z] - field->v[idx - stride_z]) * inv_2dz;
     133              : 
     134      4504810 :                     double dw_dx = (field->w[idx + 1] - field->w[idx - 1]) / (2.0 * dx);
     135      4504810 :                     double dw_dy = (field->w[idx + nx] - field->w[idx - nx]) / (2.0 * dy);
     136      4504810 :                     double dw_dz = (field->w[idx + stride_z] - field->w[idx - stride_z]) * inv_2dz;
     137              : 
     138              :                     /* Convective terms: u·∇φ */
     139      4504810 :                     double conv_u = u * du_dx + v * du_dy + w * du_dz;
     140      4504810 :                     double conv_v = u * dv_dx + v * dv_dy + w * dv_dz;
     141      4504810 :                     double conv_w = u * dw_dx + v * dw_dy + w * dw_dz;
     142              : 
     143              :                     /* Second derivatives (viscous) */
     144      4504810 :                     double d2u_dx2 = (field->u[idx + 1] - 2.0 * u + field->u[idx - 1]) / (dx * dx);
     145      4504810 :                     double d2u_dy2 = (field->u[idx + nx] - 2.0 * u + field->u[idx - nx]) / (dy * dy);
     146      4504810 :                     double d2u_dz2 = (field->u[idx + stride_z] - 2.0 * u +
     147              :                                       field->u[idx - stride_z]) * inv_dz2;
     148              : 
     149      4504810 :                     double d2v_dx2 = (field->v[idx + 1] - 2.0 * v + field->v[idx - 1]) / (dx * dx);
     150      4504810 :                     double d2v_dy2 = (field->v[idx + nx] - 2.0 * v + field->v[idx - nx]) / (dy * dy);
     151      4504810 :                     double d2v_dz2 = (field->v[idx + stride_z] - 2.0 * v +
     152              :                                       field->v[idx - stride_z]) * inv_dz2;
     153              : 
     154      4504810 :                     double d2w_dx2 = (field->w[idx + 1] - 2.0 * w + field->w[idx - 1]) / (dx * dx);
     155      4504810 :                     double d2w_dy2 = (field->w[idx + nx] - 2.0 * w + field->w[idx - nx]) / (dy * dy);
     156      4504810 :                     double d2w_dz2 = (field->w[idx + stride_z] - 2.0 * w +
     157              :                                       field->w[idx - stride_z]) * inv_dz2;
     158              : 
     159      4504810 :                     double visc_u = nu * (d2u_dx2 + d2u_dy2 + d2u_dz2);
     160      4504810 :                     double visc_v = nu * (d2v_dx2 + d2v_dy2 + d2v_dz2);
     161      4504810 :                     double visc_w = nu * (d2w_dx2 + d2w_dy2 + d2w_dz2);
     162              : 
     163              :                     /* Source terms */
     164      4504810 :                     double source_u = 0.0, source_v = 0.0, source_w = 0.0;
     165      4504810 :                     double x_coord = grid->x[i];
     166      4504810 :                     double y_coord = grid->y[j];
     167      4504810 :                     double z_coord = (nz > 1 && grid->z) ? grid->z[k] : 0.0;
     168      4504810 :                     compute_source_terms(x_coord, y_coord, z_coord, iter, dt, params,
     169              :                                          &source_u, &source_v, &source_w);
     170              : 
     171              :                     /* Boussinesq buoyancy source */
     172      4504810 :                     energy_compute_buoyancy(field->T[idx], params,
     173              :                                             &source_u, &source_v, &source_w);
     174              : 
     175              :                     /* Intermediate velocity (without pressure gradient) */
     176      4504810 :                     u_star[idx] = u + dt * (-conv_u + visc_u + source_u);
     177      4504810 :                     v_star[idx] = v + dt * (-conv_v + visc_v + source_v);
     178      4504810 :                     w_star[idx] = w + dt * (-conv_w + visc_w + source_w);
     179              : 
     180      4504810 :                     u_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, u_star[idx]));
     181      4504810 :                     v_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, v_star[idx]));
     182      4504810 :                     w_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, w_star[idx]));
     183              :                 }
     184              :             }
     185              :         }
     186              : 
     187              :         /* Copy boundary values from field to star arrays */
     188         6123 :         copy_boundary_velocities_3d(u_star, v_star, w_star,
     189         6123 :                                     field->u, field->v, field->w, nx, ny, nz);
     190              : 
     191              :         /* ============================================================
     192              :          * STEP 2: Solve Poisson equation for pressure
     193              :          * ∇²p = (ρ/dt) * ∇·u*
     194              :          * ============================================================ */
     195         6123 :         double rho = field->rho[0];
     196         6123 :         if (rho < 1e-10) {
     197            2 :             rho = 1.0;
     198              :         }
     199              : 
     200        12251 :         for (size_t k = k_start; k < k_end; k++) {
     201       153946 :             for (size_t j = 1; j < ny - 1; j++) {
     202      4652628 :                 for (size_t i = 1; i < nx - 1; i++) {
     203      4504810 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     204              : 
     205      4504810 :                     double du_star_dx = (u_star[idx + 1] - u_star[idx - 1]) / (2.0 * dx);
     206      4504810 :                     double dv_star_dy = (v_star[idx + nx] - v_star[idx - nx]) / (2.0 * dy);
     207      4504810 :                     double dw_star_dz = (w_star[idx + stride_z] -
     208      4504810 :                                          w_star[idx - stride_z]) * inv_2dz;
     209              : 
     210      4504810 :                     double divergence = du_star_dx + dv_star_dy + dw_star_dz;
     211      4504810 :                     rhs[idx] = (rho / dt) * divergence;
     212              :                 }
     213              :             }
     214              :         }
     215              : 
     216              :         /* Solve Poisson equation using library solver */
     217         6123 :         int poisson_iters = poisson_solve_3d(p_new, p_temp, rhs, nx, ny, nz, dx, dy, dz,
     218              :                                              POISSON_SOLVER_CG_SCALAR);
     219              : 
     220         6123 :         if (poisson_iters < 0) {
     221            0 :             cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     222            0 :             cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     223            0 :             return CFD_ERROR_MAX_ITER;
     224              :         }
     225              : 
     226              :         /* ============================================================
     227              :          * STEP 3: Corrector - Project velocity to be divergence-free
     228              :          * u^(n+1) = u* - (dt/ρ) * ∇p
     229              :          * ============================================================ */
     230         6123 :         double dt_over_rho = dt / rho;
     231              : 
     232        12251 :         for (size_t k = k_start; k < k_end; k++) {
     233       153946 :             for (size_t j = 1; j < ny - 1; j++) {
     234      4652628 :                 for (size_t i = 1; i < nx - 1; i++) {
     235      4504810 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     236              : 
     237      4504810 :                     double dp_dx = (p_new[idx + 1] - p_new[idx - 1]) / (2.0 * dx);
     238      4504810 :                     double dp_dy = (p_new[idx + nx] - p_new[idx - nx]) / (2.0 * dy);
     239      4504810 :                     double dp_dz = (p_new[idx + stride_z] - p_new[idx - stride_z]) * inv_2dz;
     240              : 
     241      4504810 :                     field->u[idx] = u_star[idx] - dt_over_rho * dp_dx;
     242      4504810 :                     field->v[idx] = v_star[idx] - dt_over_rho * dp_dy;
     243      4504810 :                     field->w[idx] = w_star[idx] - dt_over_rho * dp_dz;
     244              : 
     245      4504810 :                     field->u[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->u[idx]));
     246      4504810 :                     field->v[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->v[idx]));
     247      4504810 :                     field->w[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->w[idx]));
     248              :                 }
     249              :             }
     250              :         }
     251              : 
     252              :         /* Update pressure */
     253         6123 :         memcpy(field->p, p_new, bytes);
     254              : 
     255              :         /* Energy equation: advance temperature after velocity correction */
     256              :         {
     257         6123 :             cfd_status_t energy_status = energy_step_explicit_with_workspace(
     258              :                 field, grid, params, dt, iter * dt, T_energy_ws, total);
     259         6123 :             if (energy_status != CFD_SUCCESS) {
     260            0 :                 cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     261            0 :                 cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     262            0 :                 cfd_free(T_energy_ws);
     263            0 :                 return energy_status;
     264              :             }
     265              :         }
     266              : 
     267              :         /* Apply configured thermal BCs to temperature field */
     268         6123 :         cfd_status_t bc_status = energy_apply_thermal_bcs(field, params);
     269         6123 :         if (bc_status != CFD_SUCCESS) {
     270            0 :             cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     271            0 :             cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     272            0 :             cfd_free(T_energy_ws);
     273            0 :             return bc_status;
     274              :         }
     275              : 
     276              :         /* Restore caller-set boundary conditions */
     277         6123 :         copy_boundary_velocities_3d(field->u, field->v, field->w,
     278              :                                     u_star, v_star, w_star, nx, ny, nz);
     279              : 
     280              :         /* NaN/Inf check */
     281      5126805 :         for (size_t n = 0; n < total; n++) {
     282      5120682 :             if (!isfinite(field->u[n]) || !isfinite(field->v[n]) ||
     283      5120682 :                 !isfinite(field->w[n]) || !isfinite(field->p[n])) {
     284            0 :                 cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     285            0 :                 cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     286            0 :                 cfd_free(T_energy_ws);
     287            0 :                 return CFD_ERROR_DIVERGED;
     288              :             }
     289              :         }
     290              :     }
     291              : 
     292         6085 :     cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     293         6085 :     cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     294         6085 :     cfd_free(T_energy_ws);
     295              : 
     296         6085 :     return CFD_SUCCESS;
     297              : }
        

Generated by: LCOV version 2.0-1