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-03-04 10:22:18 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/memory.h"
      13              : 
      14              : #include <math.h>
      15              : #include <stdio.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       150649 : static inline int size_to_int(size_t val) {
      50       150649 :     if (val > (size_t)INT_MAX) {
      51              :         return INT_MAX;
      52              :     }
      53              :     return (int)val;
      54              : }
      55              : 
      56        64652 : 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        64652 :     double sum = 0.0;
      60        64652 :     int ny_int = size_to_int(ny);
      61        64652 :     int nx_int = size_to_int(nx);
      62              : 
      63       129454 :     for (size_t k = k_start; k < k_end; k++) {
      64        64802 :         int j;
      65        64802 : #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        64652 :     return sum;
      74              : }
      75              : 
      76        42936 : 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        42936 :     int ny_int = size_to_int(ny);
      80        42936 :     int nx_int = size_to_int(nx);
      81              : 
      82        85928 :     for (size_t k = k_start; k < k_end; k++) {
      83        42992 :         int j;
      84        42992 : #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        42936 : }
      93              : 
      94        21468 : 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        21468 :     double dx2_inv = 1.0 / dx2;
      99        21468 :     double dy2_inv = 1.0 / dy2;
     100        21468 :     int ny_int = size_to_int(ny);
     101        21468 :     int nx_int = size_to_int(nx);
     102              : 
     103        42964 :     for (size_t k = k_start; k < k_end; k++) {
     104        21496 :         int j;
     105        21496 : #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        21468 : }
     118              : 
     119          124 : 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          124 :     double dx2_inv = 1.0 / dx2;
     124          124 :     double dy2_inv = 1.0 / dy2;
     125          124 :     int ny_int = size_to_int(ny);
     126          124 :     int nx_int = size_to_int(nx);
     127              : 
     128          281 :     for (size_t k = k_start; k < k_end; k++) {
     129          157 :         int j;
     130          157 : #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          124 : }
     143              : 
     144          124 : 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          124 :     int ny_int = size_to_int(ny);
     148              : 
     149          281 :     for (size_t k = k_start; k < k_end; k++) {
     150          157 :         int j;
     151          157 : #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        21345 : 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        21345 :     int ny_int = size_to_int(ny);
     182        21345 :     int nx_int = size_to_int(nx);
     183              : 
     184        42690 :     for (size_t k = k_start; k < k_end; k++) {
     185        21345 :         int j;
     186        21345 : #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        21345 : }
     195              : 
     196              : /* ============================================================================
     197              :  * CG OMP IMPLEMENTATION
     198              :  * ============================================================================ */
     199              : 
     200            8 : 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            8 :     cg_omp_context_t* ctx = (cg_omp_context_t*)cfd_calloc(1, sizeof(cg_omp_context_t));
     207            8 :     if (!ctx) {
     208              :         return CFD_ERROR_NOMEM;
     209              :     }
     210              : 
     211            8 :     ctx->dx2 = dx * dx;
     212            8 :     ctx->dy2 = dy * dy;
     213            8 :     ctx->inv_dz2 = poisson_solver_compute_inv_dz2(dz);
     214            8 :     poisson_solver_compute_3d_bounds(nz, nx, ny, &ctx->stride_z, &ctx->k_start, &ctx->k_end);
     215            8 :     ctx->diag_inv = 1.0 / (2.0 / ctx->dx2 + 2.0 / ctx->dy2 + 2.0 * ctx->inv_dz2);
     216            8 :     ctx->use_precond = (params && params->preconditioner == POISSON_PRECOND_JACOBI);
     217              : 
     218            8 :     size_t n = nx * ny * nz;
     219            8 :     ctx->r = (double*)cfd_calloc(n, sizeof(double));
     220            8 :     ctx->p = (double*)cfd_calloc(n, sizeof(double));
     221            8 :     ctx->Ap = (double*)cfd_calloc(n, sizeof(double));
     222            8 :     ctx->z = NULL;
     223              : 
     224            8 :     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            8 :     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            8 :     ctx->initialized = 1;
     244            8 :     solver->context = ctx;
     245            8 :     return CFD_SUCCESS;
     246              : }
     247              : 
     248           16 : static void cg_omp_destroy(poisson_solver_t* solver) {
     249           16 :     if (solver && solver->context) {
     250            8 :         cg_omp_context_t* ctx = (cg_omp_context_t*)solver->context;
     251            8 :         cfd_free(ctx->r);
     252            8 :         cfd_free(ctx->z);
     253            8 :         cfd_free(ctx->p);
     254            8 :         cfd_free(ctx->Ap);
     255            8 :         cfd_free(ctx);
     256            8 :         solver->context = NULL;
     257              :     }
     258           16 : }
     259              : 
     260          124 : 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          124 :     (void)x_temp;
     268              : 
     269          124 :     cg_omp_context_t* ctx = (cg_omp_context_t*)solver->context;
     270          124 :     size_t nx = solver->nx;
     271          124 :     size_t ny = solver->ny;
     272          124 :     double dx2 = ctx->dx2;
     273          124 :     double dy2 = ctx->dy2;
     274          124 :     double inv_dz2 = ctx->inv_dz2;
     275          124 :     size_t k_start = ctx->k_start;
     276          124 :     size_t k_end = ctx->k_end;
     277          124 :     size_t stride_z = ctx->stride_z;
     278              : 
     279          124 :     double* r = ctx->r;
     280          124 :     double* z = ctx->z;
     281          124 :     double* p = ctx->p;
     282          124 :     double* Ap = ctx->Ap;
     283          124 :     int use_precond = ctx->use_precond;
     284          124 :     double diag_inv = ctx->diag_inv;
     285              : 
     286          124 :     poisson_solver_params_t* params = &solver->params;
     287          124 :     double start_time = poisson_solver_get_time_ms();
     288              : 
     289          124 :     poisson_solver_apply_bc(solver, x);
     290              : 
     291          124 :     compute_residual_omp(x, rhs, r, nx, ny, dx2, dy2, inv_dz2,
     292              :                          k_start, k_end, stride_z);
     293              : 
     294          124 :     double rho;
     295          124 :     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          124 :         copy_vector_omp(r, p, nx, ny, k_start, k_end, stride_z);
     302          124 :         rho = dot_product_omp(r, r, nx, ny, k_start, k_end, stride_z);
     303              :     }
     304              : 
     305          124 :     double initial_res = sqrt(dot_product_omp(r, r, nx, ny,
     306              :                                               k_start, k_end, stride_z));
     307              : 
     308          124 :     if (stats) {
     309          124 :         stats->initial_residual = initial_res;
     310              :     }
     311              : 
     312          124 :     double tolerance = params->tolerance * initial_res;
     313          124 :     if (tolerance < params->absolute_tolerance) {
     314            1 :         tolerance = params->absolute_tolerance;
     315              :     }
     316              : 
     317          124 :     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        21468 :     int converged = 0;
     328              :     int iter;
     329              :     double res_norm = initial_res;
     330              : 
     331        21468 :     for (iter = 0; iter < params->max_iterations; iter++) {
     332        21468 :         apply_laplacian_omp(p, Ap, nx, ny, dx2, dy2, inv_dz2,
     333              :                             k_start, k_end, stride_z);
     334              : 
     335        21468 :         double p_dot_Ap = dot_product_omp(p, Ap, nx, ny,
     336              :                                           k_start, k_end, stride_z);
     337        21468 :         CG_CHECK_BREAKDOWN(p_dot_Ap, stats, iter, res_norm, start_time);
     338              : 
     339        21468 :         double alpha = rho / p_dot_Ap;
     340              : 
     341        21468 :         axpy_omp(alpha, p, x, nx, ny, k_start, k_end, stride_z);
     342        21468 :         axpy_omp(-alpha, Ap, r, nx, ny, k_start, k_end, stride_z);
     343              : 
     344        21468 :         double rho_new;
     345        21468 :         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        21468 :             rho_new = dot_product_omp(r, r, nx, ny,
     352              :                                       k_start, k_end, stride_z);
     353              :         }
     354              : 
     355        21468 :         res_norm = sqrt(dot_product_omp(r, r, nx, ny,
     356              :                                         k_start, k_end, stride_z));
     357              : 
     358        21468 :         if (iter % params->check_interval == 0) {
     359        21468 :             if (params->verbose) {
     360            0 :                 printf("  CG-OMP Iter %d: residual = %.6e\n", iter, res_norm);
     361              :             }
     362              : 
     363        21468 :             if (res_norm < tolerance || res_norm < params->absolute_tolerance) {
     364              :                 converged = 1;
     365              :                 break;
     366              :             }
     367              :         }
     368              : 
     369        21345 :         CG_CHECK_BREAKDOWN(rho, stats, iter, res_norm, start_time);
     370              : 
     371        21345 :         double beta = rho_new / rho;
     372        42690 :         update_search_direction_omp(use_precond ? z : r, p, beta, nx, ny,
     373              :                                     k_start, k_end, stride_z);
     374              : 
     375        21345 :         rho = rho_new;
     376              :     }
     377              : 
     378          123 :     if (!converged && (res_norm < tolerance || res_norm < params->absolute_tolerance)) {
     379          123 :         converged = 1;
     380              :     }
     381              : 
     382          123 :     poisson_solver_apply_bc(solver, x);
     383              : 
     384          123 :     double end_time = poisson_solver_get_time_ms();
     385              : 
     386          123 :     if (stats) {
     387          123 :         stats->iterations = (iter < params->max_iterations) ? (iter + 1) : iter;
     388          123 :         stats->final_residual = res_norm;
     389          123 :         stats->elapsed_time_ms = end_time - start_time;
     390          123 :         stats->status = converged ? POISSON_CONVERGED : POISSON_MAX_ITER;
     391              :     }
     392              : 
     393          123 :     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           16 : poisson_solver_t* create_cg_omp_solver(void) {
     416           16 :     poisson_solver_t* solver = (poisson_solver_t*)cfd_calloc(1, sizeof(poisson_solver_t));
     417           16 :     if (!solver) {
     418              :         return NULL;
     419              :     }
     420              : 
     421           16 :     solver->name = POISSON_SOLVER_TYPE_CG_OMP;
     422           16 :     solver->description = "Conjugate Gradient (OpenMP)";
     423           16 :     solver->method = POISSON_METHOD_CG;
     424           16 :     solver->backend = POISSON_BACKEND_OMP;
     425           16 :     solver->params = poisson_solver_params_default();
     426              : 
     427           16 :     solver->init = cg_omp_init;
     428           16 :     solver->destroy = cg_omp_destroy;
     429           16 :     solver->solve = cg_omp_solve;
     430           16 :     solver->iterate = cg_omp_iterate;
     431           16 :     solver->apply_bc = NULL;
     432              : 
     433           16 :     return solver;
     434              : }
     435              : 
     436              : #endif /* CFD_ENABLE_OPENMP */
        

Generated by: LCOV version 2.0-1