LCOV - code coverage report
Current view: top level - solvers/linear - linear_solver.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 83.7 % 289 242
Test Date: 2026-03-04 10:22:18 Functions: 85.7 % 21 18

            Line data    Source code
       1              : /**
       2              :  * @file linear_solver.c
       3              :  * @brief Core linear solver implementation
       4              :  *
       5              :  * Implements:
       6              :  * - Default parameter functions
       7              :  * - Backend selection
       8              :  * - Solver lifecycle (create, init, destroy)
       9              :  * - Common solve loop
      10              :  * - Legacy poisson_solve() wrapper
      11              :  */
      12              : 
      13              : #include "cfd/solvers/poisson_solver.h"
      14              : #include "linear_solver_internal.h"
      15              : 
      16              : #include "cfd/boundary/boundary_conditions.h"
      17              : #include "cfd/core/indexing.h"
      18              : #include "cfd/core/memory.h"
      19              : 
      20              : #include <math.h>
      21              : #include <stdio.h>
      22              : #include <stdlib.h>
      23              : #include <string.h>
      24              : 
      25              : #ifdef _WIN32
      26              :     #define WIN32_LEAN_AND_MEAN
      27              :     #include <windows.h>
      28              : #else
      29              :     #include <sys/time.h>
      30              : #endif
      31              : 
      32              : /* ============================================================================
      33              :  * DEFAULT PARAMETERS
      34              :  * ============================================================================ */
      35              : 
      36          286 : poisson_solver_params_t poisson_solver_params_default(void) {
      37          286 :     poisson_solver_params_t params;
      38          286 :     params.tolerance = 1e-6;
      39          286 :     params.absolute_tolerance = 1e-10;
      40          286 :     params.max_iterations = 5000;  /* Increased from 1000 for CG on fine grids */
      41          286 :     params.omega = 1.5;
      42          286 :     params.check_interval = 1;
      43          286 :     params.verbose = false;
      44          286 :     params.preconditioner = POISSON_PRECOND_NONE;
      45          286 :     return params;
      46              : }
      47              : 
      48         4954 : poisson_solver_stats_t poisson_solver_stats_default(void) {
      49         4954 :     poisson_solver_stats_t stats;
      50         4954 :     stats.status = POISSON_ERROR;
      51         4954 :     stats.iterations = 0;
      52         4954 :     stats.initial_residual = 0.0;
      53         4954 :     stats.final_residual = 0.0;
      54         4954 :     stats.elapsed_time_ms = 0.0;
      55         4954 :     return stats;
      56              : }
      57              : 
      58              : /* ============================================================================
      59              :  * TIMING
      60              :  * ============================================================================ */
      61              : 
      62        19748 : double poisson_solver_get_time_ms(void) {
      63              : #ifdef _WIN32
      64              :     LARGE_INTEGER freq, counter;
      65              :     QueryPerformanceFrequency(&freq);
      66              :     QueryPerformanceCounter(&counter);
      67              :     return (double)counter.QuadPart * 1000.0 / (double)freq.QuadPart;
      68              : #else
      69        19748 :     struct timeval tv;
      70        19748 :     gettimeofday(&tv, NULL);
      71        19748 :     return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
      72              : #endif
      73              : }
      74              : 
      75              : /* ============================================================================
      76              :  * BACKEND SELECTION
      77              :  * ============================================================================ */
      78              : 
      79              : static poisson_solver_backend_t g_default_backend = POISSON_BACKEND_AUTO;
      80              : 
      81            2 : poisson_solver_backend_t poisson_solver_get_backend(void) {
      82            2 :     return g_default_backend;
      83              : }
      84              : 
      85            2 : const char* poisson_solver_get_backend_name(void) {
      86            2 :     switch (g_default_backend) {
      87              :         case POISSON_BACKEND_SCALAR:   return "scalar";
      88            0 :         case POISSON_BACKEND_OMP:      return "omp";
      89            0 :         case POISSON_BACKEND_SIMD: return "simd";
      90            0 :         case POISSON_BACKEND_GPU:      return "gpu";
      91            1 :         case POISSON_BACKEND_AUTO:
      92            1 :         default:                       return "auto";
      93              :     }
      94              : }
      95              : 
      96            2 : bool poisson_solver_set_backend(poisson_solver_backend_t backend) {
      97            2 :     if (!poisson_solver_backend_available(backend)) {
      98              :         return false;
      99              :     }
     100            2 :     g_default_backend = backend;
     101            2 :     return true;
     102              : }
     103              : 
     104           39 : bool poisson_solver_backend_available(poisson_solver_backend_t backend) {
     105           39 :     switch (backend) {
     106              :         case POISSON_BACKEND_AUTO:
     107              :         case POISSON_BACKEND_SCALAR:
     108              :             return true;
     109              : 
     110              :         case POISSON_BACKEND_OMP:
     111              : #ifdef CFD_ENABLE_OPENMP
     112              :             return true;
     113              : #else
     114              :             return false;
     115              : #endif
     116              : 
     117           15 :         case POISSON_BACKEND_SIMD:
     118           15 :             return poisson_solver_simd_backend_available();
     119              : 
     120              :         case POISSON_BACKEND_GPU:
     121              : #ifdef CFD_HAS_CUDA
     122              :             return true;
     123              : #else
     124              :             return false;
     125              : #endif
     126              : 
     127              :         default:
     128              :             return false;
     129              :     }
     130              : }
     131              : 
     132              : /* ============================================================================
     133              :  * SOLVER LIFECYCLE
     134              :  * ============================================================================ */
     135              : 
     136              : /**
     137              :  * Auto-select best available backend
     138              :  *
     139              :  * Priority: SIMD (runtime detection) > Scalar
     140              :  */
     141            2 : static poisson_solver_backend_t select_best_backend(void) {
     142              :     /* Prefer SIMD with runtime detection */
     143            2 :     if (poisson_solver_simd_backend_available()) {
     144            0 :         return POISSON_BACKEND_SIMD;
     145              :     }
     146              :     return POISSON_BACKEND_SCALAR;
     147              : }
     148              : 
     149          174 : poisson_solver_t* poisson_solver_create(
     150              :     poisson_solver_method_t method,
     151              :     poisson_solver_backend_t backend)
     152              : {
     153              :     /* Auto-select backend if requested */
     154          174 :     if (backend == POISSON_BACKEND_AUTO) {
     155            2 :         backend = select_best_backend();
     156              :     }
     157              : 
     158              :     /* Create appropriate solver - no silent fallbacks */
     159          174 :     switch (method) {
     160           27 :         case POISSON_METHOD_JACOBI:
     161           27 :             switch (backend) {
     162            1 :                 case POISSON_BACKEND_SIMD:
     163            1 :                     return create_jacobi_simd_solver();
     164           26 :                 case POISSON_BACKEND_SCALAR:
     165           26 :                     return create_jacobi_scalar_solver();
     166              :                 default:
     167              :                     return NULL;  /* Requested backend not available for Jacobi */
     168              :             }
     169              : 
     170           22 :         case POISSON_METHOD_SOR:
     171              :         case POISSON_METHOD_GAUSS_SEIDEL:
     172              :             /* SOR/GS are inherently sequential - only scalar backend */
     173           22 :             if (backend == POISSON_BACKEND_SCALAR) {
     174           22 :                 return create_sor_scalar_solver();
     175              :             }
     176              :             return NULL;  /* SOR not available for SIMD/OMP/GPU */
     177              : 
     178           17 :         case POISSON_METHOD_REDBLACK_SOR:
     179           17 :             switch (backend) {
     180            1 :                 case POISSON_BACKEND_SIMD:
     181            1 :                     return create_redblack_simd_solver();
     182              : #ifdef CFD_ENABLE_OPENMP
     183            2 :                 case POISSON_BACKEND_OMP:
     184            2 :                     return create_redblack_omp_solver();
     185              : #endif
     186           14 :                 case POISSON_BACKEND_SCALAR:
     187           14 :                     return create_redblack_scalar_solver();
     188              :                 default:
     189              :                     return NULL;  /* Requested backend not available for Red-Black */
     190              :             }
     191              : 
     192           94 :         case POISSON_METHOD_CG:
     193           94 :             switch (backend) {
     194           16 :                 case POISSON_BACKEND_SIMD:
     195           16 :                     return create_cg_simd_solver();
     196              : #ifdef CFD_ENABLE_OPENMP
     197           16 :                 case POISSON_BACKEND_OMP:
     198           16 :                     return create_cg_omp_solver();
     199              : #endif
     200           62 :                 case POISSON_BACKEND_SCALAR:
     201           62 :                     return create_cg_scalar_solver();
     202              :                 default:
     203              :                     return NULL;  /* Requested backend not available for CG */
     204              :             }
     205              : 
     206           13 :         case POISSON_METHOD_BICGSTAB:
     207           13 :             switch (backend) {
     208            0 :                 case POISSON_BACKEND_SIMD:
     209            0 :                     return create_bicgstab_simd_solver();
     210           11 :                 case POISSON_BACKEND_SCALAR:
     211           11 :                     return create_bicgstab_scalar_solver();
     212              :                 default:
     213              :                     return NULL;  /* Requested backend not available for BiCGSTAB */
     214              :             }
     215              : 
     216              :         case POISSON_METHOD_MULTIGRID:
     217              :             /* Not yet implemented */
     218              :             return NULL;
     219              : 
     220              :         default:
     221              :             return NULL;
     222              :     }
     223              : }
     224              : 
     225          139 : cfd_status_t poisson_solver_init(
     226              :     poisson_solver_t* solver,
     227              :     size_t nx, size_t ny, size_t nz,
     228              :     double dx, double dy, double dz,
     229              :     const poisson_solver_params_t* params)
     230              : {
     231          139 :     if (!solver) {
     232              :         return CFD_ERROR_INVALID;
     233              :     }
     234              : 
     235              :     /* Require at least one interior cell in each active dimension.
     236              :      * For 2D (nz <= 1): nx, ny >= 3.  For 3D (nz > 1): nx, ny, nz >= 3.
     237              :      * Rejects degenerate grids (e.g. nz==2) where k_start==k_end. */
     238          138 :     if (nx < 3 || ny < 3 || (nz > 1 && nz < 3)) {
     239              :         return CFD_ERROR_INVALID;
     240              :     }
     241              : 
     242          135 :     solver->nx = nx;
     243          135 :     solver->ny = ny;
     244          135 :     solver->nz = nz;
     245          135 :     solver->dx = dx;
     246          135 :     solver->dy = dy;
     247          135 :     solver->dz = dz;
     248              : 
     249          135 :     if (params) {
     250          102 :         solver->params = *params;
     251              :     } else {
     252           33 :         solver->params = poisson_solver_params_default();
     253              :     }
     254              : 
     255              :     /* Adjust max_iterations for Jacobi (needs more iterations) */
     256          135 :     if (solver->method == POISSON_METHOD_JACOBI && params == NULL) {
     257            2 :         solver->params.max_iterations = 2000;
     258              :     }
     259              : 
     260              :     /* Call solver-specific init if provided */
     261          135 :     if (solver->init) {
     262          135 :         return solver->init(solver, nx, ny, nz, dx, dy, dz, &solver->params);
     263              :     }
     264              : 
     265              :     return CFD_SUCCESS;
     266              : }
     267              : 
     268          155 : void poisson_solver_destroy(poisson_solver_t* solver) {
     269          155 :     if (!solver) {
     270              :         return;
     271              :     }
     272              : 
     273          153 :     if (solver->destroy) {
     274          153 :         solver->destroy(solver);
     275              :     }
     276              : 
     277          153 :     cfd_free(solver);
     278              : }
     279              : 
     280              : /* ============================================================================
     281              :  * SOLVER OPERATIONS
     282              :  * ============================================================================ */
     283              : 
     284       183576 : double poisson_solver_compute_residual(
     285              :     poisson_solver_t* solver,
     286              :     const double* x,
     287              :     const double* rhs)
     288              : {
     289       183576 :     if (!solver || !x || !rhs) {
     290              :         return -1.0;
     291              :     }
     292              : 
     293       183573 :     size_t nx = solver->nx;
     294       183573 :     size_t ny = solver->ny;
     295       183573 :     double dx2 = solver->dx * solver->dx;
     296       183573 :     double dy2 = solver->dy * solver->dy;
     297       183573 :     double inv_dz2 = poisson_solver_compute_inv_dz2(solver->dz);
     298              : 
     299       183573 :     size_t stride_z, k_start, k_end;
     300       183573 :     poisson_solver_compute_3d_bounds(solver->nz, nx, ny,
     301              :                                      &stride_z, &k_start, &k_end);
     302              : 
     303       183573 :     double max_residual = 0.0;
     304              : 
     305       404610 :     for (size_t k = k_start; k < k_end; k++) {
     306      6907444 :         for (size_t j = 1; j < ny - 1; j++) {
     307    257565032 :             for (size_t i = 1; i < nx - 1; i++) {
     308    250878625 :                 size_t idx = k * stride_z + IDX_2D(i, j, nx);
     309              : 
     310              :                 /* Compute Laplacian: d^2x/dx^2 + d^2x/dy^2 + d^2x/dz^2 */
     311    250878625 :                 double laplacian =
     312    250878625 :                     (x[idx + 1] - 2.0 * x[idx] + x[idx - 1]) / dx2
     313    250878625 :                   + (x[idx + nx] - 2.0 * x[idx] + x[idx - nx]) / dy2
     314    250878625 :                   + (x[idx + stride_z] + x[idx - stride_z]
     315    250878625 :                      - 2.0 * x[idx]) * inv_dz2;
     316              : 
     317    250878625 :                 double residual = fabs(laplacian - rhs[idx]);
     318    250878625 :                 if (residual > max_residual) {
     319      4226723 :                     max_residual = residual;
     320              :                 }
     321              :             }
     322              :         }
     323              :     }
     324              : 
     325              :     return max_residual;
     326              : }
     327              : 
     328       193221 : void poisson_solver_apply_bc(
     329              :     poisson_solver_t* solver,
     330              :     double* x)
     331              : {
     332       193221 :     if (!solver || !x) {
     333              :         return;
     334              :     }
     335              : 
     336       193220 :     if (solver->apply_bc) {
     337        10329 :         solver->apply_bc(solver, x);
     338        10329 :         return;
     339              :     }
     340              : 
     341              :     /* Default: Neumann BCs (zero gradient) on all faces */
     342       182891 :     size_t nx = solver->nx;
     343       182891 :     size_t ny = solver->ny;
     344       182891 :     size_t nz = solver->nz;
     345       182891 :     size_t plane_size = nx * ny;
     346              : 
     347              :     /* Z-face Neumann: copy adjacent interior plane to boundary planes */
     348       182891 :     if (nz > 1) {
     349         1052 :         memcpy(x, x + plane_size, plane_size * sizeof(double));
     350         1052 :         memcpy(x + (nz - 1) * plane_size,
     351         1052 :                x + (nz - 2) * plane_size,
     352              :                plane_size * sizeof(double));
     353              :     }
     354              : 
     355              :     /* Apply 2D Neumann BCs on each z-plane (x/y faces) */
     356       381146 :     for (size_t k = 0; k < nz; k++) {
     357       198255 :         bc_apply_scalar(x + k * plane_size, nx, ny, BC_TYPE_NEUMANN);
     358              :     }
     359              : }
     360              : 
     361              : /**
     362              :  * Common solve loop used by all solvers
     363              :  */
     364           32 : cfd_status_t poisson_solver_solve_common(
     365              :     poisson_solver_t* solver,
     366              :     double* x,
     367              :     double* x_temp,
     368              :     const double* rhs,
     369              :     poisson_solver_stats_t* stats)
     370              : {
     371           32 :     if (!solver || !x || !rhs) {
     372              :         return CFD_ERROR_INVALID;
     373              :     }
     374              : 
     375           32 :     if (!solver->iterate) {
     376              :         return CFD_ERROR_UNSUPPORTED;
     377              :     }
     378              : 
     379           32 :     poisson_solver_params_t* params = &solver->params;
     380           32 :     double start_time = poisson_solver_get_time_ms();
     381              : 
     382              :     /* Compute initial residual */
     383           32 :     double initial_res = poisson_solver_compute_residual(solver, x, rhs);
     384           32 :     double tolerance = params->tolerance * initial_res;
     385              : 
     386              :     /* Ensure minimum absolute tolerance */
     387           32 :     if (tolerance < params->absolute_tolerance) {
     388            9 :         tolerance = params->absolute_tolerance;
     389              :     }
     390              : 
     391           32 :     if (stats) {
     392           32 :         stats->initial_residual = initial_res;
     393              :     }
     394              : 
     395              :     /* Already converged? */
     396           32 :     if (initial_res < params->absolute_tolerance) {
     397            9 :         if (stats) {
     398            9 :             stats->status = POISSON_CONVERGED;
     399            9 :             stats->iterations = 0;
     400            9 :             stats->final_residual = initial_res;
     401            9 :             stats->elapsed_time_ms = poisson_solver_get_time_ms() - start_time;
     402              :         }
     403            9 :         return CFD_SUCCESS;
     404              :     }
     405              : 
     406        18138 :     int converged = 0;
     407              :     int iter;
     408              :     double res = initial_res;
     409              : 
     410        18138 :     for (iter = 0; iter < params->max_iterations; iter++) {
     411        18136 :         double new_res = 0.0;
     412              : 
     413              :         /* Perform one iteration */
     414        18136 :         cfd_status_t status = solver->iterate(solver, x, x_temp, rhs,
     415        18136 :             (iter % params->check_interval == 0) ? &new_res : NULL);
     416              : 
     417        18136 :         if (status != CFD_SUCCESS) {
     418            0 :             if (stats) {
     419            0 :                 stats->status = POISSON_ERROR;
     420            0 :                 stats->iterations = iter + 1;
     421            0 :                 stats->final_residual = res;
     422            0 :                 stats->elapsed_time_ms = poisson_solver_get_time_ms() - start_time;
     423              :             }
     424            0 :             return status;
     425              :         }
     426              : 
     427              :         /* Check convergence at intervals */
     428        18136 :         if (iter % params->check_interval == 0) {
     429        18136 :             res = new_res;
     430              : 
     431        18136 :             if (params->verbose) {
     432            0 :                 printf("  Iter %d: residual = %.6e\n", iter, res);
     433              :             }
     434              : 
     435        18136 :             if (res < tolerance || res < params->absolute_tolerance) {
     436           21 :                 converged = 1;
     437           21 :                 break;
     438              :             }
     439              :         }
     440              :     }
     441              : 
     442           23 :     double end_time = poisson_solver_get_time_ms();
     443              : 
     444           23 :     if (stats) {
     445           23 :         stats->iterations = iter + 1;
     446           23 :         stats->final_residual = res;
     447           23 :         stats->elapsed_time_ms = end_time - start_time;
     448           23 :         stats->status = converged ? POISSON_CONVERGED : POISSON_MAX_ITER;
     449              :     }
     450              : 
     451           23 :     return converged ? CFD_SUCCESS : CFD_ERROR_MAX_ITER;
     452              : }
     453              : 
     454         4953 : cfd_status_t poisson_solver_solve(
     455              :     poisson_solver_t* solver,
     456              :     double* x,
     457              :     double* x_temp,
     458              :     const double* rhs,
     459              :     poisson_solver_stats_t* stats)
     460              : {
     461         4953 :     if (!solver) {
     462              :         return CFD_ERROR_INVALID;
     463              :     }
     464              : 
     465              :     /* Use solver-specific solve if provided, otherwise common loop */
     466         4953 :     if (solver->solve) {
     467         4921 :         double start_time = poisson_solver_get_time_ms();
     468         4921 :         cfd_status_t status = solver->solve(solver, x, x_temp, rhs, stats);
     469         4921 :         if (stats) {
     470         4921 :             stats->elapsed_time_ms = poisson_solver_get_time_ms() - start_time;
     471              :         }
     472         4921 :         return status;
     473              :     }
     474              : 
     475           32 :     return poisson_solver_solve_common(solver, x, x_temp, rhs, stats);
     476              : }
     477              : 
     478       165400 : cfd_status_t poisson_solver_iterate(
     479              :     poisson_solver_t* solver,
     480              :     double* x,
     481              :     double* x_temp,
     482              :     const double* rhs,
     483              :     double* residual)
     484              : {
     485       165400 :     if (!solver || !x || !rhs) {
     486              :         return CFD_ERROR_INVALID;
     487              :     }
     488              : 
     489       165400 :     if (!solver->iterate) {
     490              :         return CFD_ERROR_UNSUPPORTED;
     491              :     }
     492              : 
     493       165400 :     return solver->iterate(solver, x, x_temp, rhs, residual);
     494              : }
     495              : 
     496              : /* ============================================================================
     497              :  * CACHED SOLVER INSTANCES
     498              :  * ============================================================================ */
     499              : 
     500              : /*
     501              :  * Cached solver instances for poisson_solve() convenience API.
     502              :  * Avoids creating/destroying solvers on each call.
     503              :  */
     504              : static poisson_solver_t* g_cached_jacobi_simd = NULL;
     505              : static poisson_solver_t* g_cached_sor = NULL;
     506              : static poisson_solver_t* g_cached_redblack_simd = NULL;
     507              : static poisson_solver_t* g_cached_redblack_omp = NULL;
     508              : static poisson_solver_t* g_cached_redblack_scalar = NULL;
     509              : static poisson_solver_t* g_cached_cg_scalar = NULL;
     510              : static poisson_solver_t* g_cached_cg_omp = NULL;
     511              : static poisson_solver_t* g_cached_cg_simd = NULL;
     512              : 
     513              : /**
     514              :  * Cleanup cached solvers (called at program exit)
     515              :  */
     516           10 : static void cleanup_cached_solvers(void) {
     517           10 :     if (g_cached_jacobi_simd) {
     518            0 :         poisson_solver_destroy(g_cached_jacobi_simd);
     519            0 :         g_cached_jacobi_simd = NULL;
     520              :     }
     521           10 :     if (g_cached_sor) {
     522            1 :         poisson_solver_destroy(g_cached_sor);
     523            1 :         g_cached_sor = NULL;
     524              :     }
     525           10 :     if (g_cached_redblack_simd) {
     526            0 :         poisson_solver_destroy(g_cached_redblack_simd);
     527            0 :         g_cached_redblack_simd = NULL;
     528              :     }
     529           10 :     if (g_cached_redblack_omp) {
     530            0 :         poisson_solver_destroy(g_cached_redblack_omp);
     531            0 :         g_cached_redblack_omp = NULL;
     532              :     }
     533           10 :     if (g_cached_redblack_scalar) {
     534            0 :         poisson_solver_destroy(g_cached_redblack_scalar);
     535            0 :         g_cached_redblack_scalar = NULL;
     536              :     }
     537           10 :     if (g_cached_cg_scalar) {
     538            9 :         poisson_solver_destroy(g_cached_cg_scalar);
     539            9 :         g_cached_cg_scalar = NULL;
     540              :     }
     541           10 :     if (g_cached_cg_omp) {
     542            4 :         poisson_solver_destroy(g_cached_cg_omp);
     543            4 :         g_cached_cg_omp = NULL;
     544              :     }
     545           10 :     if (g_cached_cg_simd) {
     546            0 :         poisson_solver_destroy(g_cached_cg_simd);
     547            0 :         g_cached_cg_simd = NULL;
     548              :     }
     549           10 : }
     550              : 
     551         4878 : int poisson_solve_3d(
     552              :     double* p, double* p_temp, const double* rhs,
     553              :     size_t nx, size_t ny, size_t nz,
     554              :     double dx, double dy, double dz,
     555              :     poisson_solver_type solver_type)
     556              : {
     557         4878 :     poisson_solver_t** solver_ptr;
     558         4878 :     poisson_solver_method_t method;
     559         4878 :     poisson_solver_backend_t backend;
     560              : 
     561         4878 :     switch (solver_type) {
     562              :         case POISSON_SOLVER_JACOBI_SIMD:
     563              :             solver_ptr = &g_cached_jacobi_simd;
     564              :             method = POISSON_METHOD_JACOBI;
     565              :             backend = POISSON_BACKEND_SIMD;
     566              :             break;
     567              : 
     568            1 :         case POISSON_SOLVER_REDBLACK_SIMD:
     569            1 :             solver_ptr = &g_cached_redblack_simd;
     570            1 :             method = POISSON_METHOD_REDBLACK_SOR;
     571            1 :             backend = POISSON_BACKEND_SIMD;
     572            1 :             break;
     573              : 
     574            0 :         case POISSON_SOLVER_REDBLACK_OMP:
     575            0 :             solver_ptr = &g_cached_redblack_omp;
     576            0 :             method = POISSON_METHOD_REDBLACK_SOR;
     577            0 :             backend = POISSON_BACKEND_OMP;
     578            0 :             break;
     579              : 
     580            1 :         case POISSON_SOLVER_SOR_SCALAR:
     581            1 :             solver_ptr = &g_cached_sor;
     582            1 :             method = POISSON_METHOD_SOR;
     583            1 :             backend = POISSON_BACKEND_SCALAR;
     584            1 :             break;
     585              : 
     586            0 :         case POISSON_SOLVER_REDBLACK_SCALAR:
     587            0 :             solver_ptr = &g_cached_redblack_scalar;
     588            0 :             method = POISSON_METHOD_REDBLACK_SOR;
     589            0 :             backend = POISSON_BACKEND_SCALAR;
     590            0 :             break;
     591              : 
     592         4753 :         case POISSON_SOLVER_CG_SCALAR:
     593         4753 :             solver_ptr = &g_cached_cg_scalar;
     594         4753 :             method = POISSON_METHOD_CG;
     595         4753 :             backend = POISSON_BACKEND_SCALAR;
     596         4753 :             break;
     597              : 
     598            0 :         case POISSON_SOLVER_CG_SIMD:
     599            0 :             solver_ptr = &g_cached_cg_simd;
     600            0 :             method = POISSON_METHOD_CG;
     601            0 :             backend = POISSON_BACKEND_SIMD;
     602            0 :             break;
     603              : 
     604          122 :         case POISSON_SOLVER_CG_OMP:
     605          122 :             solver_ptr = &g_cached_cg_omp;
     606          122 :             method = POISSON_METHOD_CG;
     607          122 :             backend = POISSON_BACKEND_OMP;
     608          122 :             break;
     609              : 
     610            0 :         default:
     611            0 :             fprintf(stderr, "poisson_solve_3d: Unknown solver type %d\n", solver_type);
     612            0 :             return -1;
     613              :     }
     614              : 
     615              :     /* Recreate solver if grid dimensions or spacing changed.
     616              :      * Compare against the solver's own stored dimensions, not shared globals,
     617              :      * because each solver type is cached independently. */
     618         4878 :     if (*solver_ptr == NULL
     619         4862 :         || (*solver_ptr)->nx != nx || (*solver_ptr)->ny != ny
     620         4847 :         || (*solver_ptr)->nz != nz
     621         4847 :         || (*solver_ptr)->dx != dx || (*solver_ptr)->dy != dy
     622         4847 :         || (*solver_ptr)->dz != dz) {
     623              :         /* Register cleanup on first use */
     624           31 :         static int cleanup_registered = 0;
     625           31 :         if (!cleanup_registered) {
     626           10 :             atexit(cleanup_cached_solvers);
     627           10 :             cleanup_registered = 1;
     628              :         }
     629              : 
     630              :         /* Destroy old solver if exists */
     631           31 :         if (*solver_ptr) {
     632           15 :             poisson_solver_destroy(*solver_ptr);
     633           15 :             *solver_ptr = NULL;
     634              :         }
     635              : 
     636              :         /* Create new solver */
     637           31 :         *solver_ptr = poisson_solver_create(method, backend);
     638              : 
     639           31 :         if (*solver_ptr) {
     640           29 :             poisson_solver_init(*solver_ptr, nx, ny, nz, dx, dy, dz, NULL);
     641              :         }
     642              :     }
     643              : 
     644         4878 :     if (!*solver_ptr) {
     645              :         return -1;
     646              :     }
     647              : 
     648         4876 :     poisson_solver_stats_t stats = poisson_solver_stats_default();
     649         4876 :     cfd_status_t status = poisson_solver_solve(*solver_ptr, p, p_temp, rhs, &stats);
     650              : 
     651         4876 :     if (status == CFD_SUCCESS && stats.status == POISSON_CONVERGED) {
     652         4876 :         return stats.iterations;
     653              :     }
     654              : 
     655              :     /* Return iterations even if not converged (legacy behavior) */
     656              :     if (stats.iterations > 0) {
     657              :         return -1;  /* Signal non-convergence */
     658              :     }
     659              : 
     660              :     return -1;
     661              : }
     662              : 
     663            3 : int poisson_solve(
     664              :     double* p, double* p_temp, const double* rhs,
     665              :     size_t nx, size_t ny, double dx, double dy,
     666              :     poisson_solver_type solver_type)
     667              : {
     668            3 :     return poisson_solve_3d(p, p_temp, rhs, nx, ny, 1, dx, dy, 0.0, solver_type);
     669              : }
     670              : 
     671              : /* Direct solver functions - delegate to unified interface */
     672            0 : int poisson_solve_sor_scalar(
     673              :     double* p, const double* rhs,
     674              :     size_t nx, size_t ny, double dx, double dy)
     675              : {
     676              :     /* SOR doesn't need temp buffer, pass NULL */
     677            0 :     return poisson_solve(p, NULL, rhs, nx, ny, dx, dy, POISSON_SOLVER_SOR_SCALAR);
     678              : }
     679              : 
     680              : /* SIMD functions with runtime CPU detection */
     681            0 : int poisson_solve_jacobi_simd(
     682              :     double* p, double* p_temp, const double* rhs,
     683              :     size_t nx, size_t ny, double dx, double dy)
     684              : {
     685            0 :     return poisson_solve(p, p_temp, rhs, nx, ny, dx, dy, POISSON_SOLVER_JACOBI_SIMD);
     686              : }
     687              : 
     688            0 : int poisson_solve_redblack_simd(
     689              :     double* p, double* p_temp, const double* rhs,
     690              :     size_t nx, size_t ny, double dx, double dy)
     691              : {
     692            0 :     return poisson_solve(p, p_temp, rhs, nx, ny, dx, dy, POISSON_SOLVER_REDBLACK_SIMD);
     693              : }
        

Generated by: LCOV version 2.0-1