LCOV - code coverage report
Current view: top level - solvers/navier_stokes/cpu - solver_projection.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 92.7 % 124 115
Test Date: 2026-03-04 10:22:18 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/navier_stokes_solver.h"
      25              : #include "cfd/solvers/poisson_solver.h"
      26              : 
      27              : #include "../boundary_copy_utils.h"
      28              : 
      29              : #include <math.h>
      30              : #include <stdio.h>
      31              : #include <string.h>
      32              : 
      33              : #ifndef M_PI
      34              : #define M_PI 3.14159265358979323846
      35              : #endif
      36              : 
      37              : // Physical limits
      38              : #define MAX_VELOCITY 100.0
      39              : #define MAX_PRESSURE 1000.0
      40              : 
      41              : /**
      42              :  * Projection Method Solver
      43              :  */
      44         4753 : cfd_status_t solve_projection_method(flow_field* field, const grid* grid,
      45              :                                      const ns_solver_params_t* params) {
      46         4753 :     if (!field || !grid || !params) {
      47              :         return CFD_ERROR_INVALID;
      48              :     }
      49         4753 :     if (field->nx < 3 || field->ny < 3 || (field->nz > 1 && field->nz < 3)) {
      50              :         return CFD_ERROR_INVALID;
      51              :     }
      52              : 
      53         4753 :     size_t nx = field->nx;
      54         4753 :     size_t ny = field->ny;
      55         4753 :     size_t nz = field->nz;
      56              : 
      57              :     /* Reject non-uniform z-spacing (solver uses constant dz) */
      58         4753 :     if (nz > 1 && grid->dz) {
      59         7857 :         for (size_t k = 1; k < nz - 1; k++) {
      60         7306 :             if (fabs(grid->dz[k] - grid->dz[0]) > 1e-14) {
      61              :                 return CFD_ERROR_INVALID;
      62              :             }
      63              :         }
      64              :     }
      65              : 
      66         4753 :     size_t plane = nx * ny;
      67         4753 :     size_t total = plane * nz;
      68         4753 :     size_t bytes = total * sizeof(double);
      69              : 
      70              :     /* Grid spacing (uniform grid) */
      71         4753 :     double dx = grid->dx[0];
      72         4753 :     double dy = grid->dy[0];
      73         4753 :     double dz = (nz > 1 && grid->dz) ? grid->dz[0] : 0.0;
      74         4753 :     double dt = params->dt;
      75         4753 :     double nu = params->mu;
      76              : 
      77              :     /* Branch-free 3D constants */
      78         4753 :     size_t stride_z = (nz > 1) ? plane : 0;
      79         4753 :     size_t k_start  = (nz > 1) ? 1 : 0;
      80         4753 :     size_t k_end    = (nz > 1) ? (nz - 1) : 1;
      81         4753 :     double inv_2dz  = (nz > 1 && grid->dz) ? 1.0 / (2.0 * dz) : 0.0;
      82          551 :     double inv_dz2  = (nz > 1 && grid->dz) ? 1.0 / (dz * dz) : 0.0;
      83              : 
      84              :     /* Allocate temporary arrays */
      85         4753 :     double* u_star = (double*)cfd_calloc(total, sizeof(double));
      86         4753 :     double* v_star = (double*)cfd_calloc(total, sizeof(double));
      87         4753 :     double* w_star = (double*)cfd_calloc(total, sizeof(double));
      88         4753 :     double* p_new  = (double*)cfd_calloc(total, sizeof(double));
      89         4753 :     double* p_temp = (double*)cfd_calloc(total, sizeof(double));
      90         4753 :     double* rhs    = (double*)cfd_calloc(total, sizeof(double));
      91              : 
      92         4753 :     if (!u_star || !v_star || !w_star || !p_new || !p_temp || !rhs) {
      93            0 :         cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
      94            0 :         cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
      95            0 :         return CFD_ERROR_NOMEM;
      96              :     }
      97              : 
      98         4753 :     memcpy(u_star, field->u, bytes);
      99         4753 :     memcpy(v_star, field->v, bytes);
     100         4753 :     memcpy(w_star, field->w, bytes);
     101         4753 :     memcpy(p_new, field->p, bytes);
     102              : 
     103              :     /* Main iteration loop */
     104         9506 :     for (int iter = 0; iter < params->max_iter; iter++) {
     105              :         /* ============================================================
     106              :          * STEP 1: Predictor - Compute intermediate velocity u*
     107              :          * u* = u^n + dt * (-u·∇u + ν∇²u + f)
     108              :          * ============================================================ */
     109        16261 :         for (size_t k = k_start; k < k_end; k++) {
     110       185550 :             for (size_t j = 1; j < ny - 1; j++) {
     111      3201508 :                 for (size_t i = 1; i < nx - 1; i++) {
     112      3027466 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     113              : 
     114      3027466 :                     double u = field->u[idx];
     115      3027466 :                     double v = field->v[idx];
     116      3027466 :                     double w = field->w[idx];
     117              : 
     118              :                     /* First derivatives (central) */
     119      3027466 :                     double du_dx = (field->u[idx + 1] - field->u[idx - 1]) / (2.0 * dx);
     120      3027466 :                     double du_dy = (field->u[idx + nx] - field->u[idx - nx]) / (2.0 * dy);
     121      3027466 :                     double du_dz = (field->u[idx + stride_z] - field->u[idx - stride_z]) * inv_2dz;
     122              : 
     123      3027466 :                     double dv_dx = (field->v[idx + 1] - field->v[idx - 1]) / (2.0 * dx);
     124      3027466 :                     double dv_dy = (field->v[idx + nx] - field->v[idx - nx]) / (2.0 * dy);
     125      3027466 :                     double dv_dz = (field->v[idx + stride_z] - field->v[idx - stride_z]) * inv_2dz;
     126              : 
     127      3027466 :                     double dw_dx = (field->w[idx + 1] - field->w[idx - 1]) / (2.0 * dx);
     128      3027466 :                     double dw_dy = (field->w[idx + nx] - field->w[idx - nx]) / (2.0 * dy);
     129      3027466 :                     double dw_dz = (field->w[idx + stride_z] - field->w[idx - stride_z]) * inv_2dz;
     130              : 
     131              :                     /* Convective terms: u·∇φ */
     132      3027466 :                     double conv_u = u * du_dx + v * du_dy + w * du_dz;
     133      3027466 :                     double conv_v = u * dv_dx + v * dv_dy + w * dv_dz;
     134      3027466 :                     double conv_w = u * dw_dx + v * dw_dy + w * dw_dz;
     135              : 
     136              :                     /* Second derivatives (viscous) */
     137      3027466 :                     double d2u_dx2 = (field->u[idx + 1] - 2.0 * u + field->u[idx - 1]) / (dx * dx);
     138      3027466 :                     double d2u_dy2 = (field->u[idx + nx] - 2.0 * u + field->u[idx - nx]) / (dy * dy);
     139      3027466 :                     double d2u_dz2 = (field->u[idx + stride_z] - 2.0 * u +
     140              :                                       field->u[idx - stride_z]) * inv_dz2;
     141              : 
     142      3027466 :                     double d2v_dx2 = (field->v[idx + 1] - 2.0 * v + field->v[idx - 1]) / (dx * dx);
     143      3027466 :                     double d2v_dy2 = (field->v[idx + nx] - 2.0 * v + field->v[idx - nx]) / (dy * dy);
     144      3027466 :                     double d2v_dz2 = (field->v[idx + stride_z] - 2.0 * v +
     145              :                                       field->v[idx - stride_z]) * inv_dz2;
     146              : 
     147      3027466 :                     double d2w_dx2 = (field->w[idx + 1] - 2.0 * w + field->w[idx - 1]) / (dx * dx);
     148      3027466 :                     double d2w_dy2 = (field->w[idx + nx] - 2.0 * w + field->w[idx - nx]) / (dy * dy);
     149      3027466 :                     double d2w_dz2 = (field->w[idx + stride_z] - 2.0 * w +
     150              :                                       field->w[idx - stride_z]) * inv_dz2;
     151              : 
     152      3027466 :                     double visc_u = nu * (d2u_dx2 + d2u_dy2 + d2u_dz2);
     153      3027466 :                     double visc_v = nu * (d2v_dx2 + d2v_dy2 + d2v_dz2);
     154      3027466 :                     double visc_w = nu * (d2w_dx2 + d2w_dy2 + d2w_dz2);
     155              : 
     156              :                     /* Source terms */
     157      3027466 :                     double source_u = 0.0, source_v = 0.0, source_w = 0.0;
     158      3027466 :                     double x_coord = grid->x[i];
     159      3027466 :                     double y_coord = grid->y[j];
     160      3027466 :                     double z_coord = (nz > 1 && grid->z) ? grid->z[k] : 0.0;
     161      3027466 :                     compute_source_terms(x_coord, y_coord, z_coord, iter, dt, params,
     162              :                                          &source_u, &source_v, &source_w);
     163              : 
     164              :                     /* Intermediate velocity (without pressure gradient) */
     165      3027466 :                     u_star[idx] = u + dt * (-conv_u + visc_u + source_u);
     166      3027466 :                     v_star[idx] = v + dt * (-conv_v + visc_v + source_v);
     167      3027466 :                     w_star[idx] = w + dt * (-conv_w + visc_w + source_w);
     168              : 
     169      3027466 :                     u_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, u_star[idx]));
     170      3027466 :                     v_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, v_star[idx]));
     171      3027466 :                     w_star[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, w_star[idx]));
     172              :                 }
     173              :             }
     174              :         }
     175              : 
     176              :         /* Copy boundary values from field to star arrays */
     177         4753 :         copy_boundary_velocities_3d(u_star, v_star, w_star,
     178         4753 :                                     field->u, field->v, field->w, nx, ny, nz);
     179              : 
     180              :         /* ============================================================
     181              :          * STEP 2: Solve Poisson equation for pressure
     182              :          * ∇²p = (ρ/dt) * ∇·u*
     183              :          * ============================================================ */
     184         4753 :         double rho = field->rho[0];
     185         4753 :         if (rho < 1e-10) {
     186            2 :             rho = 1.0;
     187              :         }
     188              : 
     189        16261 :         for (size_t k = k_start; k < k_end; k++) {
     190       185550 :             for (size_t j = 1; j < ny - 1; j++) {
     191      3201508 :                 for (size_t i = 1; i < nx - 1; i++) {
     192      3027466 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     193              : 
     194      3027466 :                     double du_star_dx = (u_star[idx + 1] - u_star[idx - 1]) / (2.0 * dx);
     195      3027466 :                     double dv_star_dy = (v_star[idx + nx] - v_star[idx - nx]) / (2.0 * dy);
     196      3027466 :                     double dw_star_dz = (w_star[idx + stride_z] -
     197      3027466 :                                          w_star[idx - stride_z]) * inv_2dz;
     198              : 
     199      3027466 :                     double divergence = du_star_dx + dv_star_dy + dw_star_dz;
     200      3027466 :                     rhs[idx] = (rho / dt) * divergence;
     201              :                 }
     202              :             }
     203              :         }
     204              : 
     205              :         /* Solve Poisson equation using library solver */
     206         4753 :         int poisson_iters = poisson_solve_3d(p_new, p_temp, rhs, nx, ny, nz, dx, dy, dz,
     207              :                                              POISSON_SOLVER_CG_SCALAR);
     208              : 
     209         4753 :         if (poisson_iters < 0) {
     210            0 :             cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     211            0 :             cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     212            0 :             return CFD_ERROR_MAX_ITER;
     213              :         }
     214              : 
     215              :         /* ============================================================
     216              :          * STEP 3: Corrector - Project velocity to be divergence-free
     217              :          * u^(n+1) = u* - (dt/ρ) * ∇p
     218              :          * ============================================================ */
     219         4753 :         double dt_over_rho = dt / rho;
     220              : 
     221        16261 :         for (size_t k = k_start; k < k_end; k++) {
     222       185550 :             for (size_t j = 1; j < ny - 1; j++) {
     223      3201508 :                 for (size_t i = 1; i < nx - 1; i++) {
     224      3027466 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     225              : 
     226      3027466 :                     double dp_dx = (p_new[idx + 1] - p_new[idx - 1]) / (2.0 * dx);
     227      3027466 :                     double dp_dy = (p_new[idx + nx] - p_new[idx - nx]) / (2.0 * dy);
     228      3027466 :                     double dp_dz = (p_new[idx + stride_z] - p_new[idx - stride_z]) * inv_2dz;
     229              : 
     230      3027466 :                     field->u[idx] = u_star[idx] - dt_over_rho * dp_dx;
     231      3027466 :                     field->v[idx] = v_star[idx] - dt_over_rho * dp_dy;
     232      3027466 :                     field->w[idx] = w_star[idx] - dt_over_rho * dp_dz;
     233              : 
     234      3027466 :                     field->u[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->u[idx]));
     235      3027466 :                     field->v[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->v[idx]));
     236      3027466 :                     field->w[idx] = fmax(-MAX_VELOCITY, fmin(MAX_VELOCITY, field->w[idx]));
     237              :                 }
     238              :             }
     239              :         }
     240              : 
     241              :         /* Update pressure */
     242         4753 :         memcpy(field->p, p_new, bytes);
     243              : 
     244              :         /* Restore caller-set boundary conditions */
     245         4753 :         copy_boundary_velocities_3d(field->u, field->v, field->w,
     246              :                                     u_star, v_star, w_star, nx, ny, nz);
     247              : 
     248              :         /* NaN/Inf check */
     249      4036907 :         for (size_t n = 0; n < total; n++) {
     250      4032154 :             if (!isfinite(field->u[n]) || !isfinite(field->v[n]) ||
     251      4032154 :                 !isfinite(field->w[n]) || !isfinite(field->p[n])) {
     252            0 :                 cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     253            0 :                 cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     254            0 :                 return CFD_ERROR_DIVERGED;
     255              :             }
     256              :         }
     257              :     }
     258              : 
     259         4753 :     cfd_free(u_star); cfd_free(v_star); cfd_free(w_star);
     260         4753 :     cfd_free(p_new); cfd_free(p_temp); cfd_free(rhs);
     261              : 
     262         4753 :     return CFD_SUCCESS;
     263              : }
        

Generated by: LCOV version 2.0-1