LCOV - code coverage report
Current view: top level - solvers/linear/omp - linear_solver_cg_omp.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 84.0 % 187 157
Test Date: 2026-06-23 13:41:07 Functions: 81.8 % 11 9

            Line data    Source code
       1              : /**
       2              :  * @file linear_solver_cg_omp.c
       3              :  * @brief Conjugate Gradient solver - OpenMP parallelized implementation
       4              :  *
       5              :  * Same algorithm as scalar CG but with OpenMP-parallelized primitives
       6              :  * (dot_product, axpy, apply_laplacian, etc.).
       7              :  */
       8              : 
       9              : #include "../linear_solver_internal.h"
      10              : 
      11              : #include "cfd/core/indexing.h"
      12              : #include "cfd/core/logging.h"
      13              : #include "cfd/core/memory.h"
      14              : 
      15              : #include <math.h>
      16              : #include <string.h>
      17              : 
      18              : #ifdef CFD_ENABLE_OPENMP
      19              : 
      20              : #include <omp.h>
      21              : 
      22              : /* ============================================================================
      23              :  * CG CONTEXT
      24              :  * ============================================================================ */
      25              : 
      26              : typedef struct {
      27              :     double dx2;
      28              :     double dy2;
      29              :     double inv_dz2;    /* 1/dz^2 (0 for 2D) */
      30              :     double diag_inv;
      31              : 
      32              :     size_t stride_z;   /* nx*ny for 3D, 0 for 2D */
      33              :     size_t k_start;    /* first interior k index */
      34              :     size_t k_end;      /* one-past-last interior k index */
      35              : 
      36              :     double* r;
      37              :     double* z;
      38              :     double* p;
      39              :     double* Ap;
      40              : 
      41              :     int use_precond;
      42              :     int initialized;
      43              : } cg_omp_context_t;
      44              : 
      45              : /* ============================================================================
      46              :  * OMP-PARALLELIZED PRIMITIVES
      47              :  * ============================================================================ */
      48              : 
      49       153281 : static inline int size_to_int(size_t val) {
      50       153281 :     if (val > (size_t)INT_MAX) {
      51              :         return INT_MAX;
      52              :     }
      53              :     return (int)val;
      54              : }
      55              : 
      56        65795 : static double dot_product_omp(const double* a, const double* b,
      57              :                               size_t nx, size_t ny,
      58              :                               size_t k_start, size_t k_end, size_t stride_z) {
      59        65795 :     double sum = 0.0;
      60        65795 :     int ny_int = size_to_int(ny);
      61        65795 :     int nx_int = size_to_int(nx);
      62              : 
      63       131740 :     for (size_t k = k_start; k < k_end; k++) {
      64        65945 :         int j;
      65        65945 : #pragma omp parallel for schedule(static) reduction(+:sum)
      66              :         for (j = 1; j < ny_int - 1; j++) {
      67              :             for (int i = 1; i < nx_int - 1; i++) {
      68              :                 size_t idx = k * stride_z + IDX_2D((size_t)i, (size_t)j, nx);
      69              :                 sum += a[idx] * b[idx];
      70              :             }
      71              :         }
      72              :     }
      73        65795 :     return sum;
      74              : }
      75              : 
      76        43670 : static void axpy_omp(double alpha, const double* x, double* y,
      77              :                      size_t nx, size_t ny,
      78              :                      size_t k_start, size_t k_end, size_t stride_z) {
      79        43670 :     int ny_int = size_to_int(ny);
      80        43670 :     int nx_int = size_to_int(nx);
      81              : 
      82        87396 :     for (size_t k = k_start; k < k_end; k++) {
      83        43726 :         int j;
      84        43726 : #pragma omp parallel for schedule(static)
      85              :         for (j = 1; j < ny_int - 1; j++) {
      86              :             for (int i = 1; i < nx_int - 1; i++) {
      87              :                 size_t idx = k * stride_z + IDX_2D((size_t)i, (size_t)j, nx);
      88              :                 y[idx] += alpha * x[idx];
      89              :             }
      90              :         }
      91              :     }
      92        43670 : }
      93              : 
      94        21835 : static void apply_laplacian_omp(const double* p, double* Ap,
      95              :                                 size_t nx, size_t ny,
      96              :                                 double dx2, double dy2, double inv_dz2,
      97              :                                 size_t k_start, size_t k_end, size_t stride_z) {
      98        21835 :     double dx2_inv = 1.0 / dx2;
      99        21835 :     double dy2_inv = 1.0 / dy2;
     100        21835 :     int ny_int = size_to_int(ny);
     101        21835 :     int nx_int = size_to_int(nx);
     102              : 
     103        43698 :     for (size_t k = k_start; k < k_end; k++) {
     104        21863 :         int j;
     105        21863 : #pragma omp parallel for schedule(static)
     106              :         for (j = 1; j < ny_int - 1; j++) {
     107              :             for (int i = 1; i < nx_int - 1; i++) {
     108              :                 size_t idx = k * stride_z + IDX_2D((size_t)i, (size_t)j, nx);
     109              :                 double laplacian =
     110              :                     (p[idx + 1] - 2.0 * p[idx] + p[idx - 1]) * dx2_inv
     111              :                   + (p[idx + nx] - 2.0 * p[idx] + p[idx - nx]) * dy2_inv
     112              :                   + (p[idx + stride_z] + p[idx - stride_z] - 2.0 * p[idx]) * inv_dz2;
     113              :                 Ap[idx] = -laplacian;
     114              :             }
     115              :         }
     116              :     }
     117        21835 : }
     118              : 
     119          145 : static void compute_residual_omp(const double* x, const double* rhs, double* r,
     120              :                                  size_t nx, size_t ny,
     121              :                                  double dx2, double dy2, double inv_dz2,
     122              :                                  size_t k_start, size_t k_end, size_t stride_z) {
     123          145 :     double dx2_inv = 1.0 / dx2;
     124          145 :     double dy2_inv = 1.0 / dy2;
     125          145 :     int ny_int = size_to_int(ny);
     126          145 :     int nx_int = size_to_int(nx);
     127              : 
     128          323 :     for (size_t k = k_start; k < k_end; k++) {
     129          178 :         int j;
     130          178 : #pragma omp parallel for schedule(static)
     131              :         for (j = 1; j < ny_int - 1; j++) {
     132              :             for (int i = 1; i < nx_int - 1; i++) {
     133              :                 size_t idx = k * stride_z + IDX_2D((size_t)i, (size_t)j, nx);
     134              :                 double laplacian =
     135              :                     (x[idx + 1] - 2.0 * x[idx] + x[idx - 1]) * dx2_inv
     136              :                   + (x[idx + nx] - 2.0 * x[idx] + x[idx - nx]) * dy2_inv
     137              :                   + (x[idx + stride_z] + x[idx - stride_z] - 2.0 * x[idx]) * inv_dz2;
     138              :                 r[idx] = -rhs[idx] + laplacian;
     139              :             }
     140              :         }
     141              :     }
     142          145 : }
     143              : 
     144          145 : static void copy_vector_omp(const double* src, double* dst,
     145              :                             size_t nx, size_t ny,
     146              :                             size_t k_start, size_t k_end, size_t stride_z) {
     147          145 :     int ny_int = size_to_int(ny);
     148              : 
     149          323 :     for (size_t k = k_start; k < k_end; k++) {
     150          178 :         int j;
     151          178 : #pragma omp parallel for schedule(static)
     152              :         for (j = 1; j < ny_int - 1; j++) {
     153              :             size_t row_start = k * stride_z + (size_t)j * nx;
     154              :             memcpy(&dst[row_start + 1], &src[row_start + 1], (nx - 2) * sizeof(double));
     155              :         }
     156              :     }
     157              : }
     158              : 
     159            0 : static void apply_jacobi_precond_omp(const double* r, double* z,
     160              :                                      size_t nx, size_t ny,
     161              :                                      double diag_inv,
     162              :                                      size_t k_start, size_t k_end, size_t stride_z) {
     163            0 :     int ny_int = size_to_int(ny);
     164            0 :     int nx_int = size_to_int(nx);
     165              : 
     166            0 :     for (size_t k = k_start; k < k_end; k++) {
     167            0 :         int j;
     168            0 : #pragma omp parallel for schedule(static)
     169              :         for (j = 1; j < ny_int - 1; j++) {
     170              :             for (int i = 1; i < nx_int - 1; i++) {
     171              :                 size_t idx = k * stride_z + IDX_2D((size_t)i, (size_t)j, nx);
     172              :                 z[idx] = diag_inv * r[idx];
     173              :             }
     174              :         }
     175              :     }
     176            0 : }
     177              : 
     178        21691 : static void update_search_direction_omp(const double* src, double* p,
     179              :                                         double beta, size_t nx, size_t ny,
     180              :                                         size_t k_start, size_t k_end, size_t stride_z) {
     181        21691 :     int ny_int = size_to_int(ny);
     182        21691 :     int nx_int = size_to_int(nx);
     183              : 
     184        43382 :     for (size_t k = k_start; k < k_end; k++) {
     185        21691 :         int j;
     186        21691 : #pragma omp parallel for schedule(static)
     187              :         for (j = 1; j < ny_int - 1; j++) {
     188              :             for (int i = 1; i < nx_int - 1; i++) {
     189              :                 size_t idx = k * stride_z + IDX_2D((size_t)i, (size_t)j, nx);
     190              :                 p[idx] = src[idx] + beta * p[idx];
     191              :             }
     192              :         }
     193              :     }
     194        21691 : }
     195              : 
     196              : /* ============================================================================
     197              :  * CG OMP IMPLEMENTATION
     198              :  * ============================================================================ */
     199              : 
     200           10 : static cfd_status_t cg_omp_init(
     201              :     poisson_solver_t* solver,
     202              :     size_t nx, size_t ny, size_t nz,
     203              :     double dx, double dy, double dz,
     204              :     const poisson_solver_params_t* params)
     205              : {
     206           10 :     cg_omp_context_t* ctx = (cg_omp_context_t*)cfd_calloc(1, sizeof(cg_omp_context_t));
     207           10 :     if (!ctx) {
     208              :         return CFD_ERROR_NOMEM;
     209              :     }
     210              : 
     211           10 :     ctx->dx2 = dx * dx;
     212           10 :     ctx->dy2 = dy * dy;
     213           10 :     ctx->inv_dz2 = poisson_solver_compute_inv_dz2(dz);
     214           10 :     poisson_solver_compute_3d_bounds(nz, nx, ny, &ctx->stride_z, &ctx->k_start, &ctx->k_end);
     215           10 :     ctx->diag_inv = 1.0 / (2.0 / ctx->dx2 + 2.0 / ctx->dy2 + 2.0 * ctx->inv_dz2);
     216           10 :     ctx->use_precond = (params && params->preconditioner == POISSON_PRECOND_JACOBI);
     217              : 
     218           10 :     size_t n = nx * ny * nz;
     219           10 :     ctx->r = (double*)cfd_calloc(n, sizeof(double));
     220           10 :     ctx->p = (double*)cfd_calloc(n, sizeof(double));
     221           10 :     ctx->Ap = (double*)cfd_calloc(n, sizeof(double));
     222           10 :     ctx->z = NULL;
     223              : 
     224           10 :     if (!ctx->r || !ctx->p || !ctx->Ap) {
     225            0 :         cfd_free(ctx->r);
     226            0 :         cfd_free(ctx->p);
     227            0 :         cfd_free(ctx->Ap);
     228            0 :         cfd_free(ctx);
     229            0 :         return CFD_ERROR_NOMEM;
     230              :     }
     231              : 
     232           10 :     if (ctx->use_precond) {
     233            0 :         ctx->z = (double*)cfd_calloc(n, sizeof(double));
     234            0 :         if (!ctx->z) {
     235            0 :             cfd_free(ctx->r);
     236            0 :             cfd_free(ctx->p);
     237            0 :             cfd_free(ctx->Ap);
     238            0 :             cfd_free(ctx);
     239            0 :             return CFD_ERROR_NOMEM;
     240              :         }
     241              :     }
     242              : 
     243           10 :     ctx->initialized = 1;
     244           10 :     solver->context = ctx;
     245           10 :     return CFD_SUCCESS;
     246              : }
     247              : 
     248           19 : static void cg_omp_destroy(poisson_solver_t* solver) {
     249           19 :     if (solver && solver->context) {
     250           10 :         cg_omp_context_t* ctx = (cg_omp_context_t*)solver->context;
     251           10 :         cfd_free(ctx->r);
     252           10 :         cfd_free(ctx->z);
     253           10 :         cfd_free(ctx->p);
     254           10 :         cfd_free(ctx->Ap);
     255           10 :         cfd_free(ctx);
     256           10 :         solver->context = NULL;
     257              :     }
     258           19 : }
     259              : 
     260          145 : static cfd_status_t cg_omp_solve(
     261              :     poisson_solver_t* solver,
     262              :     double* x,
     263              :     double* x_temp,
     264              :     const double* rhs,
     265              :     poisson_solver_stats_t* stats)
     266              : {
     267          145 :     (void)x_temp;
     268              : 
     269          145 :     cg_omp_context_t* ctx = (cg_omp_context_t*)solver->context;
     270          145 :     size_t nx = solver->nx;
     271          145 :     size_t ny = solver->ny;
     272          145 :     double dx2 = ctx->dx2;
     273          145 :     double dy2 = ctx->dy2;
     274          145 :     double inv_dz2 = ctx->inv_dz2;
     275          145 :     size_t k_start = ctx->k_start;
     276          145 :     size_t k_end = ctx->k_end;
     277          145 :     size_t stride_z = ctx->stride_z;
     278              : 
     279          145 :     double* r = ctx->r;
     280          145 :     double* z = ctx->z;
     281          145 :     double* p = ctx->p;
     282          145 :     double* Ap = ctx->Ap;
     283          145 :     int use_precond = ctx->use_precond;
     284          145 :     double diag_inv = ctx->diag_inv;
     285              : 
     286          145 :     poisson_solver_params_t* params = &solver->params;
     287          145 :     double start_time = poisson_solver_get_time_ms();
     288              : 
     289          145 :     poisson_solver_apply_bc(solver, x);
     290              : 
     291          145 :     compute_residual_omp(x, rhs, r, nx, ny, dx2, dy2, inv_dz2,
     292              :                          k_start, k_end, stride_z);
     293              : 
     294          145 :     double rho;
     295          145 :     if (use_precond) {
     296            0 :         apply_jacobi_precond_omp(r, z, nx, ny, diag_inv,
     297              :                                  k_start, k_end, stride_z);
     298            0 :         copy_vector_omp(z, p, nx, ny, k_start, k_end, stride_z);
     299            0 :         rho = dot_product_omp(r, z, nx, ny, k_start, k_end, stride_z);
     300              :     } else {
     301          145 :         copy_vector_omp(r, p, nx, ny, k_start, k_end, stride_z);
     302          145 :         rho = dot_product_omp(r, r, nx, ny, k_start, k_end, stride_z);
     303              :     }
     304              : 
     305          145 :     double initial_res = sqrt(dot_product_omp(r, r, nx, ny,
     306              :                                               k_start, k_end, stride_z));
     307              : 
     308          145 :     if (stats) {
     309          145 :         stats->initial_residual = initial_res;
     310              :     }
     311              : 
     312          145 :     double tolerance = params->tolerance * initial_res;
     313          145 :     if (tolerance < params->absolute_tolerance) {
     314            1 :         tolerance = params->absolute_tolerance;
     315              :     }
     316              : 
     317          145 :     if (initial_res < params->absolute_tolerance) {
     318            1 :         if (stats) {
     319            1 :             stats->status = POISSON_CONVERGED;
     320            1 :             stats->iterations = 0;
     321            1 :             stats->final_residual = initial_res;
     322            1 :             stats->elapsed_time_ms = poisson_solver_get_time_ms() - start_time;
     323              :         }
     324            1 :         return CFD_SUCCESS;
     325              :     }
     326              : 
     327        21835 :     int converged = 0;
     328              :     int iter;
     329              :     double res_norm = initial_res;
     330              : 
     331        21835 :     for (iter = 0; iter < params->max_iterations; iter++) {
     332        21835 :         apply_laplacian_omp(p, Ap, nx, ny, dx2, dy2, inv_dz2,
     333              :                             k_start, k_end, stride_z);
     334              : 
     335        21835 :         double p_dot_Ap = dot_product_omp(p, Ap, nx, ny,
     336              :                                           k_start, k_end, stride_z);
     337        21835 :         CG_CHECK_BREAKDOWN(p_dot_Ap, stats, iter, res_norm, start_time);
     338              : 
     339        21835 :         double alpha = rho / p_dot_Ap;
     340              : 
     341        21835 :         axpy_omp(alpha, p, x, nx, ny, k_start, k_end, stride_z);
     342        21835 :         axpy_omp(-alpha, Ap, r, nx, ny, k_start, k_end, stride_z);
     343              : 
     344        21835 :         double rho_new;
     345        21835 :         if (use_precond) {
     346            0 :             apply_jacobi_precond_omp(r, z, nx, ny, diag_inv,
     347              :                                      k_start, k_end, stride_z);
     348            0 :             rho_new = dot_product_omp(r, z, nx, ny,
     349              :                                       k_start, k_end, stride_z);
     350              :         } else {
     351        21835 :             rho_new = dot_product_omp(r, r, nx, ny,
     352              :                                       k_start, k_end, stride_z);
     353              :         }
     354              : 
     355        21835 :         res_norm = sqrt(dot_product_omp(r, r, nx, ny,
     356              :                                         k_start, k_end, stride_z));
     357              : 
     358        21835 :         if (iter % params->check_interval == 0) {
     359        21835 :             if (params->verbose) {
     360            0 :                 CFD_LOG_DEBUG("poisson", "CG-OMP Iter %d: residual = %.6e", iter, res_norm);
     361              :             }
     362              : 
     363        21835 :             if (res_norm < tolerance || res_norm < params->absolute_tolerance) {
     364              :                 converged = 1;
     365              :                 break;
     366              :             }
     367              :         }
     368              : 
     369        21691 :         CG_CHECK_BREAKDOWN(rho, stats, iter, res_norm, start_time);
     370              : 
     371        21691 :         double beta = rho_new / rho;
     372        43382 :         update_search_direction_omp(use_precond ? z : r, p, beta, nx, ny,
     373              :                                     k_start, k_end, stride_z);
     374              : 
     375        21691 :         rho = rho_new;
     376              :     }
     377              : 
     378          144 :     if (!converged && (res_norm < tolerance || res_norm < params->absolute_tolerance)) {
     379          144 :         converged = 1;
     380              :     }
     381              : 
     382          144 :     poisson_solver_apply_bc(solver, x);
     383              : 
     384          144 :     double end_time = poisson_solver_get_time_ms();
     385              : 
     386          144 :     if (stats) {
     387          144 :         stats->iterations = (iter < params->max_iterations) ? (iter + 1) : iter;
     388          144 :         stats->final_residual = res_norm;
     389          144 :         stats->elapsed_time_ms = end_time - start_time;
     390          144 :         stats->status = converged ? POISSON_CONVERGED : POISSON_MAX_ITER;
     391              :     }
     392              : 
     393          144 :     return converged ? CFD_SUCCESS : CFD_ERROR_MAX_ITER;
     394              : }
     395              : 
     396            0 : static cfd_status_t cg_omp_iterate(
     397              :     poisson_solver_t* solver,
     398              :     double* x,
     399              :     double* x_temp,
     400              :     const double* rhs,
     401              :     double* residual)
     402              : {
     403            0 :     (void)x_temp;
     404              : 
     405            0 :     if (residual) {
     406            0 :         *residual = poisson_solver_compute_residual(solver, x, rhs);
     407              :     }
     408            0 :     return CFD_SUCCESS;
     409              : }
     410              : 
     411              : /* ============================================================================
     412              :  * FACTORY FUNCTION
     413              :  * ============================================================================ */
     414              : 
     415           19 : poisson_solver_t* create_cg_omp_solver(void) {
     416           19 :     poisson_solver_t* solver = (poisson_solver_t*)cfd_calloc(1, sizeof(poisson_solver_t));
     417           19 :     if (!solver) {
     418              :         return NULL;
     419              :     }
     420              : 
     421           19 :     solver->name = POISSON_SOLVER_TYPE_CG_OMP;
     422           19 :     solver->description = "Conjugate Gradient (OpenMP)";
     423           19 :     solver->method = POISSON_METHOD_CG;
     424           19 :     solver->backend = POISSON_BACKEND_OMP;
     425           19 :     solver->params = poisson_solver_params_default();
     426              : 
     427           19 :     solver->init = cg_omp_init;
     428           19 :     solver->destroy = cg_omp_destroy;
     429           19 :     solver->solve = cg_omp_solve;
     430           19 :     solver->iterate = cg_omp_iterate;
     431           19 :     solver->apply_bc = NULL;
     432              : 
     433           19 :     return solver;
     434              : }
     435              : 
     436              : #endif /* CFD_ENABLE_OPENMP */
        

Generated by: LCOV version 2.0-1