LCOV - code coverage report
Current view: top level - api - solver_registry.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 88.8 % 608 540
Test Date: 2026-06-23 13:41:07 Functions: 92.2 % 51 47

            Line data    Source code
       1              : #include "cfd/core/cfd_status.h"
       2              : #include "cfd/core/cpu_features.h"
       3              : #include "cfd/core/filesystem.h"
       4              : #include "cfd/core/gpu_device.h"
       5              : #include "cfd/core/grid.h"
       6              : #include "cfd/core/logging.h"
       7              : #include "cfd/core/memory.h"
       8              : #include "cfd/solvers/navier_stokes_solver.h"
       9              : #include "cfd/solvers/poisson_solver.h"
      10              : 
      11              : 
      12              : #ifdef _WIN32
      13              : #define WIN32_LEAN_AND_MEAN
      14              : #include <windows.h>
      15              : #endif
      16              : 
      17              : #include <math.h>
      18              : #include <stddef.h>
      19              : #include <stdio.h>
      20              : #include <stdlib.h>
      21              : #include <string.h>
      22              : 
      23              : #ifdef CFD_ENABLE_OPENMP
      24              : #include <omp.h>
      25              : #endif
      26              : 
      27              : /* Compute max T for stats reporting */
      28         8121 : static double compute_max_temperature(const flow_field* field) {
      29         8121 :     double max_t = 0.0;
      30         8121 :     if (field->T) {
      31         8121 :         size_t n = field->nx * field->ny * field->nz;
      32         8121 :         max_t = field->T[0];
      33     11490115 :         for (size_t i = 1; i < n; i++) {
      34     11481994 :             if (field->T[i] > max_t) max_t = field->T[i];
      35              :         }
      36              :     }
      37         8121 :     return max_t;
      38              : }
      39              : 
      40              : #ifdef CFD_HAS_CUDA
      41              : /* Reject energy-equation params on the GPU backend, which does not yet
      42              :  * implement the energy equation. The CPU, OMP, and SIMD backends now support
      43              :  * it, so only the CUDA wrappers (below) still need this guard. */
      44              : static cfd_status_t check_energy_unsupported(const ns_solver_params_t* params) {
      45              :     if (params->alpha > 0.0 || params->beta != 0.0) {
      46              :         cfd_set_error(CFD_ERROR_UNSUPPORTED,
      47              :                       "Energy equation (alpha/beta) not supported by the GPU backend; "
      48              :                       "use a CPU, OMP, or SIMD solver");
      49              :         return CFD_ERROR_UNSUPPORTED;
      50              :     }
      51              :     return CFD_SUCCESS;
      52              : }
      53              : #endif /* CFD_HAS_CUDA */
      54              : 
      55              : // Forward declarations for internal solver implementations
      56              : // These are not part of the public API
      57              : cfd_status_t explicit_euler_impl(flow_field* field, const grid* grid, const ns_solver_params_t* params);
      58              : cfd_status_t rk2_impl(flow_field* field, const grid* grid, const ns_solver_params_t* params);
      59              : void explicit_euler_optimized_impl(flow_field* field, const grid* grid,
      60              :                                    const ns_solver_params_t* params);
      61              : #ifdef CFD_ENABLE_OPENMP
      62              : cfd_status_t explicit_euler_omp_impl(flow_field* field, const grid* grid,
      63              :                                      const ns_solver_params_t* params);
      64              : cfd_status_t rk2_omp_impl(flow_field* field, const grid* grid,
      65              :                            const ns_solver_params_t* params);
      66              : cfd_status_t solve_projection_method_omp(flow_field* field, const grid* grid,
      67              :                                          const ns_solver_params_t* params);
      68              : #endif
      69              : 
      70              : // GPU solver functions
      71              : cfd_status_t solve_projection_method_gpu(flow_field* field, const grid* grid,
      72              :                                          const ns_solver_params_t* params,
      73              :                                          const gpu_config_t* config);
      74              : int gpu_is_available(void);
      75              : 
      76              : // SIMD solver functions
      77              : 
      78              : cfd_status_t explicit_euler_simd_init(struct NSSolver* solver, const grid* grid,
      79              :                                       const ns_solver_params_t* params);
      80              : void explicit_euler_simd_destroy(struct NSSolver* solver);
      81              : 
      82              : cfd_status_t explicit_euler_simd_step(struct NSSolver* solver, flow_field* field, const grid* grid,
      83              :                                       const ns_solver_params_t* params, ns_solver_stats_t* stats);
      84              : 
      85              : 
      86              : cfd_status_t projection_simd_init(ns_solver_t* solver, const grid* grid, const ns_solver_params_t* params);
      87              : void projection_simd_destroy(ns_solver_t* solver);
      88              : 
      89              : cfd_status_t projection_simd_step(ns_solver_t* solver, flow_field* field, const grid* grid,
      90              :                                   const ns_solver_params_t* params, ns_solver_stats_t* stats);
      91              : 
      92              : cfd_status_t rk2_avx2_init(ns_solver_t* solver, const grid* grid,
      93              :                              const ns_solver_params_t* params);
      94              : void         rk2_avx2_destroy(ns_solver_t* solver);
      95              : cfd_status_t rk2_avx2_step(ns_solver_t* solver, flow_field* field, const grid* grid,
      96              :                              const ns_solver_params_t* params, ns_solver_stats_t* stats);
      97              : cfd_status_t rk2_avx2_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
      98              :                               const ns_solver_params_t* params, ns_solver_stats_t* stats);
      99              : 
     100              : #ifdef _WIN32
     101              : #else
     102              : #include <sys/time.h>
     103              : #endif
     104              : 
     105              : // Maximum number of registered solver types
     106              : #define MAX_REGISTERED_SOLVERS 32
     107              : 
     108              : // Registry entry
     109              : typedef struct {
     110              :     char name[64];
     111              :     ns_solver_factory_func factory;
     112              :     const char* description;
     113              :     ns_solver_backend_t backend;  // Backend type for efficient filtering
     114              : } solver_registry_entry;
     115              : 
     116              : // ns_solver_registry_t structure
     117              : struct NSSolverRegistry {
     118              :     solver_registry_entry entries[MAX_REGISTERED_SOLVERS];
     119              :     int count;
     120              : };
     121              : 
     122              : // Forward declarations for built-in solver factories
     123              : static ns_solver_t* create_explicit_euler_solver(void);
     124              : static ns_solver_t* create_rk2_solver(void);
     125              : static ns_solver_t* create_rk2_optimized_solver(void);
     126              : static ns_solver_t* create_explicit_euler_optimized_solver(void);
     127              : static ns_solver_t* create_projection_solver(void);
     128              : static ns_solver_t* create_projection_optimized_solver(void);
     129              : #ifdef CFD_HAS_CUDA
     130              : static ns_solver_t* create_explicit_euler_gpu_solver(void);
     131              : static ns_solver_t* create_projection_gpu_solver(void);
     132              : #endif
     133              : #ifdef CFD_ENABLE_OPENMP
     134              : static ns_solver_t* create_explicit_euler_omp_solver(void);
     135              : static ns_solver_t* create_projection_omp_solver(void);
     136              : static ns_solver_t* create_rk2_omp_solver(void);
     137              : #endif
     138              : 
     139              : // External projection method solver functions
     140              : extern cfd_status_t solve_projection_method(flow_field* field, const grid* grid,
     141              :                                             const ns_solver_params_t* params);
     142              : extern void solve_projection_method_optimized(flow_field* field, const grid* grid,
     143              :                                               const ns_solver_params_t* params);
     144              : 
     145              : // External GPU solver functions (from solver_gpu.cu or solver_gpu_stub.c)
     146              : #include "cfd/core/cfd_status.h"
     147              : #include "cfd/core/gpu_device.h"
     148              : 
     149              : 
     150              : // Helper to get current time in milliseconds
     151        16854 : static double get_time_ms(void) {
     152              : #ifdef _WIN32
     153              :     LARGE_INTEGER freq, counter;
     154              :     QueryPerformanceFrequency(&freq);
     155              :     QueryPerformanceCounter(&counter);
     156              :     return (double)counter.QuadPart * 1000.0 / (double)freq.QuadPart;
     157              : #else
     158        16854 :     struct timeval tv;
     159        16854 :     gettimeofday(&tv, NULL);
     160        16854 :     return tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
     161              : #endif
     162              : }
     163              : 
     164              : /**
     165              :  * solver Registry Implementation
     166              :  */
     167              : 
     168          274 : ns_solver_registry_t* cfd_registry_create(void) {
     169          274 :     ns_solver_registry_t* registry = (ns_solver_registry_t*)cfd_calloc(1, sizeof(ns_solver_registry_t));
     170          274 :     return registry;
     171              : }
     172              : 
     173          274 : void cfd_registry_destroy(ns_solver_registry_t* registry) {
     174          274 :     if (registry) {
     175          274 :         cfd_free(registry);
     176              :     }
     177          274 : }
     178              : 
     179          271 : void cfd_registry_register_defaults(ns_solver_registry_t* registry) {
     180          271 :     if (!registry) {
     181              :         return;
     182              :     }
     183              : 
     184              :     // Register built-in solvers
     185          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_EXPLICIT_EULER, create_explicit_euler_solver);
     186          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_RK2, create_rk2_solver);
     187          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_RK2_OPTIMIZED, create_rk2_optimized_solver);
     188          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_EXPLICIT_EULER_OPTIMIZED,
     189              :                           create_explicit_euler_optimized_solver);
     190              : 
     191              :     // Register projection method solvers
     192          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_PROJECTION, create_projection_solver);
     193          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_PROJECTION_OPTIMIZED,
     194              :                           create_projection_optimized_solver);
     195              : 
     196              :     // Register GPU solvers (requires CUDA)
     197              : #ifdef CFD_HAS_CUDA
     198              :     cfd_registry_register(registry, NS_SOLVER_TYPE_EXPLICIT_EULER_GPU,
     199              :                           create_explicit_euler_gpu_solver);
     200              :     cfd_registry_register(registry, NS_SOLVER_TYPE_PROJECTION_JACOBI_GPU,
     201              :                           create_projection_gpu_solver);
     202              : #endif
     203              : 
     204              :     // Register OpenMP solvers
     205              : #ifdef CFD_ENABLE_OPENMP
     206          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_EXPLICIT_EULER_OMP,
     207              :                           create_explicit_euler_omp_solver);
     208          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_PROJECTION_OMP, create_projection_omp_solver);
     209          271 :     cfd_registry_register(registry, NS_SOLVER_TYPE_RK2_OMP, create_rk2_omp_solver);
     210              : #endif
     211              : }
     212              : 
     213              : /**
     214              :  * Infer backend from solver type name based on naming convention.
     215              :  * Returns the likely backend, or NS_SOLVER_BACKEND_SCALAR as default.
     216              :  * Note: Defined here (before first use) and also used by cfd_solver_create_checked.
     217              :  */
     218         2481 : static ns_solver_backend_t infer_backend_from_type(const char* type_name) {
     219         2481 :     if (!type_name) {
     220              :         return NS_SOLVER_BACKEND_SCALAR;
     221              :     }
     222              : 
     223              :     /* Check for GPU suffix first (most specific) */
     224         2481 :     if (strstr(type_name, "_gpu") != NULL) {
     225              :         return NS_SOLVER_BACKEND_CUDA;
     226              :     }
     227              : 
     228              :     /* Check for OMP suffix */
     229         2479 :     if (strstr(type_name, "_omp") != NULL) {
     230              :         return NS_SOLVER_BACKEND_OMP;
     231              :     }
     232              : 
     233              :     /* Check for optimized suffix (SIMD) */
     234         1664 :     if (strstr(type_name, "_optimized") != NULL) {
     235          816 :         return NS_SOLVER_BACKEND_SIMD;
     236              :     }
     237              : 
     238              :     /* Default to scalar */
     239              :     return NS_SOLVER_BACKEND_SCALAR;
     240              : }
     241              : 
     242         2475 : int cfd_registry_register(ns_solver_registry_t* registry, const char* type_name,
     243              :                           ns_solver_factory_func factory) {
     244         2475 :     if (!registry || !type_name || !factory) {
     245            2 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid arguments for solver registration");
     246            2 :         return -1;
     247              :     }
     248         2473 :     if (strlen(type_name) == 0) {
     249            1 :         cfd_set_error(CFD_ERROR_INVALID, "solver type name cannot be empty");
     250            1 :         return -1;
     251              :     }
     252         2472 :     if (registry->count >= MAX_REGISTERED_SOLVERS) {
     253            1 :         cfd_set_error(CFD_ERROR_LIMIT_EXCEEDED, "Max registered solvers limit reached");
     254            1 :         return -1;
     255              :     }
     256              : 
     257              :     /* Infer backend from type name for efficient filtering later */
     258         2471 :     ns_solver_backend_t backend = infer_backend_from_type(type_name);
     259              : 
     260              :     // Check if already registered
     261        12723 :     for (int i = 0; i < registry->count; i++) {
     262        10252 :         if (strcmp(registry->entries[i].name, type_name) == 0) {
     263              :             // Update existing entry
     264            0 :             registry->entries[i].factory = factory;
     265            0 :             registry->entries[i].backend = backend;
     266            0 :             return 0;
     267              :         }
     268              :     }
     269              : 
     270              :     // Add new entry
     271         2471 :     snprintf(registry->entries[registry->count].name,
     272              :              sizeof(registry->entries[registry->count].name), "%s", type_name);
     273         2471 :     registry->entries[registry->count].factory = factory;
     274         2471 :     registry->entries[registry->count].backend = backend;
     275         2471 :     registry->count++;
     276              : 
     277         2471 :     return 0;
     278              : }
     279              : 
     280            0 : int cfd_registry_unregister(ns_solver_registry_t* registry, const char* type_name) {
     281            0 :     if (!registry || !type_name) {
     282              :         return -1;
     283              :     }
     284              : 
     285            0 :     for (int i = 0; i < registry->count; i++) {
     286            0 :         if (strcmp(registry->entries[i].name, type_name) == 0) {
     287              :             // Shift remaining entries
     288            0 :             for (int j = i; j < registry->count - 1; j++) {
     289            0 :                 registry->entries[j] = registry->entries[j + 1];
     290              :             }
     291            0 :             registry->count--;
     292            0 :             return 0;
     293              :         }
     294              :     }
     295              :     return -1;
     296              : }
     297              : 
     298            1 : int cfd_registry_list(ns_solver_registry_t* registry, const char** names, int max_count) {
     299            1 :     if (!registry) {
     300              :         return 0;
     301              :     }
     302              : 
     303            1 :     int count = (registry->count < max_count) ? registry->count : max_count;
     304            1 :     if (names) {
     305           10 :         for (int i = 0; i < count; i++) {
     306            9 :             names[i] = registry->entries[i].name;
     307              :         }
     308              :     }
     309              :     return registry->count;
     310              : }
     311              : 
     312            0 : int cfd_registry_has(ns_solver_registry_t* registry, const char* type_name) {
     313            0 :     if (!registry || !type_name) {
     314              :         return 0;
     315              :     }
     316              : 
     317            0 :     for (int i = 0; i < registry->count; i++) {
     318            0 :         if (strcmp(registry->entries[i].name, type_name) == 0) {
     319              :             return 1;
     320              :         }
     321              :     }
     322              :     return 0;
     323              : }
     324              : 
     325            0 : const char* cfd_registry_get_description(ns_solver_registry_t* registry, const char* type_name) {
     326            0 :     if (!registry || !type_name) {
     327              :         return NULL;
     328              :     }
     329              : 
     330              :     // Create a temporary solver to get its description
     331            0 :     ns_solver_t* solver = cfd_solver_create(registry, type_name);
     332            0 :     if (solver) {
     333            0 :         const char* desc = solver->description;
     334            0 :         solver_destroy(solver);
     335            0 :         return desc;
     336              :     }
     337              :     return NULL;
     338              : }
     339              : 
     340              : /**
     341              :  * solver Creation and Management
     342              :  */
     343              : 
     344          291 : ns_solver_t* cfd_solver_create(ns_solver_registry_t* registry, const char* type_name) {
     345          291 :     if (!registry || !type_name) {
     346            1 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid arguments for solver creation");
     347            1 :         return NULL;
     348              :     }
     349              : 
     350         1073 :     for (int i = 0; i < registry->count; i++) {
     351         1062 :         if (strcmp(registry->entries[i].name, type_name) == 0) {
     352          279 :             ns_solver_t* solver = registry->entries[i].factory();
     353          279 :             if (!solver) {
     354              :                 /* Factory returned NULL - check if error was already set by the factory.
     355              :                  * Factories should set specific errors:
     356              :                  * - CFD_ERROR_UNSUPPORTED: backend not available (GPU, SIMD, etc.)
     357              :                  * - CFD_ERROR_NOMEM: allocation failed
     358              :                  * If no error was set, assume memory allocation failure. */
     359            0 :                 if (cfd_get_last_status() == CFD_SUCCESS) {
     360            0 :                     cfd_set_error(CFD_ERROR_NOMEM, "Failed to allocate solver");
     361              :                 }
     362              :             }
     363          279 :             return solver;
     364              :         }
     365              :     }
     366              : 
     367              :     /* Solver type not found in registry - use CFD_ERROR_NOT_FOUND
     368              :      * to distinguish from CFD_ERROR_UNSUPPORTED (backend unavailable) */
     369           11 :     char error_msg[128];
     370           11 :     snprintf(error_msg, sizeof(error_msg), "Solver type '%s' not registered", type_name);
     371           11 :     cfd_set_error(CFD_ERROR_NOT_FOUND, error_msg);
     372           11 :     return NULL;
     373              : }
     374              : 
     375          280 : void solver_destroy(ns_solver_t* solver) {
     376          280 :     if (!solver) {
     377              :         return;
     378              :     }
     379              : 
     380          279 :     if (solver->destroy) {
     381          279 :         solver->destroy(solver);
     382              :     }
     383          279 :     cfd_free(solver);
     384              : }
     385              : 
     386              : 
     387          225 : cfd_status_t solver_init(ns_solver_t* solver, const grid* grid, const ns_solver_params_t* params) {
     388          225 :     if (!solver) {
     389              :         return CFD_ERROR_INVALID;
     390              :     }
     391          224 :     if (!solver->init) {
     392              :         return CFD_SUCCESS;  // Optional
     393              :     }
     394              : 
     395          224 :     return solver->init(solver, grid, params);
     396              : }
     397              : 
     398              : 
     399         8421 : cfd_status_t solver_step(ns_solver_t* solver, flow_field* field, const grid* grid,
     400              :                          const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     401         8421 :     if (!solver || !field || !grid || !params) {
     402              :         return CFD_ERROR_INVALID;
     403              :     }
     404         8417 :     if (!solver->step) {
     405              :         return CFD_ERROR;
     406              :     }
     407              : 
     408         8417 :     double start_time = get_time_ms();
     409              : 
     410         8417 :     cfd_status_t status = solver->step(solver, field, grid, params, stats);
     411         8417 :     double end_time = get_time_ms();
     412              : 
     413         8417 :     if (stats) {
     414         8417 :         stats->elapsed_time_ms = end_time - start_time;
     415         8417 :         stats->status = status;
     416              :     }
     417              : 
     418              :     return status;
     419              : }
     420              : 
     421              : 
     422           11 : cfd_status_t solver_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
     423              :                           const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     424           11 :     if (!solver || !field || !grid || !params) {
     425              :         return CFD_ERROR_INVALID;
     426              :     }
     427           10 :     if (!solver->solve) {
     428              :         return CFD_ERROR;
     429              :     }
     430              : 
     431           10 :     double start_time = get_time_ms();
     432              : 
     433           10 :     cfd_status_t status = solver->solve(solver, field, grid, params, stats);
     434           10 :     double end_time = get_time_ms();
     435              : 
     436           10 :     if (stats) {
     437           10 :         stats->elapsed_time_ms = end_time - start_time;
     438           10 :         stats->status = status;
     439              :     }
     440              : 
     441              :     return status;
     442              : }
     443              : 
     444            1 : void solver_apply_boundary(ns_solver_t* solver, flow_field* field, const grid* grid) {
     445            1 :     if (!solver || !field || !grid) {
     446              :         return;
     447              :     }
     448              : 
     449            0 :     if (solver->apply_boundary) {
     450            0 :         solver->apply_boundary(solver, field, grid);
     451              :     } else {
     452              :         // Fall back to default boundary conditions
     453            0 :         apply_boundary_conditions(field, grid);
     454              :     }
     455              : }
     456              : 
     457            1 : double solver_compute_dt(ns_solver_t* solver, const flow_field* field, const grid* grid,
     458              :                          const ns_solver_params_t* params) {
     459            1 :     if (!solver || !field || !grid || !params) {
     460              :         return 0.0;
     461              :     }
     462              : 
     463            0 :     if (solver->compute_dt) {
     464            0 :         return solver->compute_dt(solver, field, grid, params);
     465              :     }
     466              : 
     467              :     // Default implementation
     468            0 :     double max_vel = 0.0;
     469            0 :     double min_dx = grid->dx[0];
     470            0 :     double min_dy = grid->dy[0];
     471              : 
     472            0 :     for (size_t i = 0; i < grid->nx - 1; i++) {
     473            0 :         if (grid->dx[i] < min_dx) {
     474            0 :             min_dx = grid->dx[i];
     475              :         }
     476              :     }
     477            0 :     for (size_t j = 0; j < grid->ny - 1; j++) {
     478            0 :         if (grid->dy[j] < min_dy) {
     479            0 :             min_dy = grid->dy[j];
     480              :         }
     481              :     }
     482              : 
     483            0 :     for (size_t i = 0; i < field->nx * field->ny; i++) {
     484            0 :         double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     485            0 :         if (vel > max_vel) {
     486            0 :             max_vel = vel;
     487              :         }
     488              :     }
     489              : 
     490            0 :     if (max_vel < 1e-10) {
     491            0 :         max_vel = 1.0;
     492              :     }
     493              : 
     494            0 :     double dt = params->cfl * fmin(min_dx, min_dy) / max_vel;
     495            0 :     return fmin(fmax(dt, 1e-6), 0.01);
     496              : }
     497              : 
     498              : /**
     499              :  * Built-in solver: Explicit Euler
     500              :  * Wraps the existing solve_navier_stokes function
     501              :  */
     502              : 
     503              : typedef struct {
     504              :     int initialized;
     505              : } explicit_euler_context;
     506              : 
     507          139 : static cfd_status_t explicit_euler_init(ns_solver_t* solver, const grid* grid,
     508              :                                         const ns_solver_params_t* params) {
     509          139 :     (void)grid;
     510          139 :     (void)params;
     511              : 
     512          139 :     explicit_euler_context* ctx =
     513          139 :         (explicit_euler_context*)cfd_malloc(sizeof(explicit_euler_context));
     514          139 :     if (!ctx) {
     515              :         return CFD_ERROR;
     516              :     }
     517              : 
     518          139 :     ctx->initialized = 1;
     519          139 :     solver->context = ctx;
     520          139 :     return CFD_SUCCESS;
     521              : }
     522              : 
     523          175 : static void explicit_euler_destroy(ns_solver_t* solver) {
     524          175 :     if (solver->context) {
     525          139 :         cfd_free(solver->context);
     526          139 :         solver->context = NULL;
     527              :     }
     528          175 : }
     529              : 
     530         6206 : static cfd_status_t explicit_euler_step(ns_solver_t* solver, flow_field* field, const grid* grid,
     531              :                                         const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     532         6206 :     (void)solver;
     533              : 
     534         6206 :     if (field->nx < 3 || field->ny < 3) {
     535              :         return CFD_ERROR_INVALID;
     536              :     }
     537              : 
     538              :     // Create params with single iteration
     539         6206 :     ns_solver_params_t step_params = *params;
     540         6206 :     step_params.max_iter = 1;
     541              : 
     542         6206 :     explicit_euler_impl(field, grid, &step_params);
     543              : 
     544         6206 :     if (stats) {
     545          848 :         stats->iterations = 1;
     546              : 
     547              :         // Compute max velocity
     548          848 :         double max_vel = 0.0;
     549          848 :         double max_p = 0.0;
     550      1047079 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     551      1046231 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     552      1046231 :             if (vel > max_vel) {
     553         8160 :                 max_vel = vel;
     554              :             }
     555      1046231 :             if (fabs(field->p[i]) > max_p) {
     556        15552 :                 max_p = fabs(field->p[i]);
     557              :             }
     558              :         }
     559          848 :         stats->max_velocity = max_vel;
     560          848 :         stats->max_pressure = max_p;
     561         1696 :         stats->max_temperature = compute_max_temperature(field);
     562              :     }
     563              : 
     564              :     return CFD_SUCCESS;
     565              : }
     566              : 
     567            2 : static cfd_status_t explicit_euler_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
     568              :                                          const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     569            2 :     (void)solver;
     570              : 
     571            2 :     if (field->nx < 3 || field->ny < 3) {
     572              :         return CFD_ERROR_INVALID;
     573              :     }
     574              : 
     575            2 :     explicit_euler_impl(field, grid, params);
     576              : 
     577            2 :     if (stats) {
     578            2 :         stats->iterations = params->max_iter;
     579              : 
     580            2 :         double max_vel = 0.0;
     581            2 :         double max_p = 0.0;
     582          164 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     583          162 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     584          162 :             if (vel > max_vel) {
     585           24 :                 max_vel = vel;
     586              :             }
     587          162 :             if (fabs(field->p[i]) > max_p) {
     588           14 :                 max_p = fabs(field->p[i]);
     589              :             }
     590              :         }
     591            2 :         stats->max_velocity = max_vel;
     592            2 :         stats->max_pressure = max_p;
     593            4 :         stats->max_temperature = compute_max_temperature(field);
     594              :     }
     595              : 
     596              :     return CFD_SUCCESS;
     597              : }
     598              : 
     599          116 : static ns_solver_t* create_explicit_euler_solver(void) {
     600          116 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
     601          116 :     if (!s) {
     602              :         return NULL;
     603              :     }
     604              : 
     605          116 :     s->name = NS_SOLVER_TYPE_EXPLICIT_EULER;
     606          116 :     s->description = "Basic explicit Euler finite difference solver for 2D Navier-Stokes";
     607          116 :     s->version = "1.0.0";
     608          116 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT;
     609          116 :     s->backend = NS_SOLVER_BACKEND_SCALAR;
     610              : 
     611          116 :     s->init = explicit_euler_init;
     612          116 :     s->destroy = explicit_euler_destroy;
     613          116 :     s->step = explicit_euler_step;
     614          116 :     s->solve = explicit_euler_solve;
     615          116 :     s->apply_boundary = NULL;  // Use default
     616          116 :     s->compute_dt = NULL;      // Use default
     617              : 
     618          116 :     return s;
     619              : }
     620              : 
     621              : /**
     622              :  * Built-in solver: RK2 (Heun's Method)
     623              :  * Second-order Runge-Kutta time integration
     624              :  */
     625              : 
     626         3992 : static cfd_status_t rk2_step(ns_solver_t* solver, flow_field* field, const grid* grid,
     627              :                               const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     628         3992 :     (void)solver;
     629              : 
     630         3992 :     if (field->nx < 3 || field->ny < 3) {
     631              :         return CFD_ERROR_INVALID;
     632              :     }
     633              : 
     634         3992 :     ns_solver_params_t step_params = *params;
     635         3992 :     step_params.max_iter = 1;
     636              : 
     637         3992 :     cfd_status_t status = rk2_impl(field, grid, &step_params);
     638              : 
     639         3992 :     if (stats) {
     640          634 :         stats->iterations = 1;
     641          634 :         double max_vel = 0.0;
     642          634 :         double max_p = 0.0;
     643       866362 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     644       865728 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     645       865728 :             if (vel > max_vel) max_vel = vel;
     646       865728 :             if (fabs(field->p[i]) > max_p) max_p = fabs(field->p[i]);
     647              :         }
     648          634 :         stats->max_velocity = max_vel;
     649          634 :         stats->max_pressure = max_p;
     650         1268 :         stats->max_temperature = compute_max_temperature(field);
     651              :     }
     652              : 
     653              :     return status;
     654              : }
     655              : 
     656            2 : static cfd_status_t rk2_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
     657              :                                const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     658            2 :     (void)solver;
     659              : 
     660            2 :     if (field->nx < 3 || field->ny < 3) {
     661              :         return CFD_ERROR_INVALID;
     662              :     }
     663              : 
     664            2 :     cfd_status_t status = rk2_impl(field, grid, params);
     665              : 
     666            2 :     if (stats) {
     667            2 :         stats->iterations = params->max_iter;
     668            2 :         double max_vel = 0.0;
     669            2 :         double max_p = 0.0;
     670          164 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     671          162 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     672          162 :             if (vel > max_vel) max_vel = vel;
     673          162 :             if (fabs(field->p[i]) > max_p) max_p = fabs(field->p[i]);
     674              :         }
     675            2 :         stats->max_velocity = max_vel;
     676            2 :         stats->max_pressure = max_p;
     677            4 :         stats->max_temperature = compute_max_temperature(field);
     678              :     }
     679              : 
     680              :     return status;
     681              : }
     682              : 
     683           30 : static ns_solver_t* create_rk2_solver(void) {
     684           30 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
     685           30 :     if (!s) {
     686              :         return NULL;
     687              :     }
     688              : 
     689           30 :     s->name = NS_SOLVER_TYPE_RK2;
     690           30 :     s->description = "RK2 (Heun's method) - 2nd order explicit time integration";
     691           30 :     s->version = "1.0.0";
     692           30 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT;
     693           30 :     s->backend = NS_SOLVER_BACKEND_SCALAR;
     694              : 
     695           30 :     s->init = explicit_euler_init;
     696           30 :     s->destroy = explicit_euler_destroy;
     697           30 :     s->step = rk2_step;
     698           30 :     s->solve = rk2_solve;
     699           30 :     s->apply_boundary = NULL;
     700           30 :     s->compute_dt = NULL;
     701              : 
     702           30 :     return s;
     703              : }
     704              : 
     705            7 : static ns_solver_t* create_rk2_optimized_solver(void) {
     706            7 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
     707            7 :     if (!s) {
     708              :         return NULL;
     709              :     }
     710              : 
     711            7 :     s->name        = NS_SOLVER_TYPE_RK2_OPTIMIZED;
     712            7 :     s->description = "AVX2/SIMD + OpenMP RK2 (Heun's method)";
     713            7 :     s->version     = "1.0.0";
     714            7 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT |
     715              :                       NS_SOLVER_CAP_SIMD;
     716            7 :     s->backend     = NS_SOLVER_BACKEND_SIMD;
     717              : 
     718            7 :     s->init           = rk2_avx2_init;
     719            7 :     s->destroy        = rk2_avx2_destroy;
     720            7 :     s->step           = rk2_avx2_step;
     721            7 :     s->solve          = rk2_avx2_solve;
     722            7 :     s->apply_boundary = NULL;
     723            7 :     s->compute_dt     = NULL;
     724              : 
     725            7 :     return s;
     726              : }
     727              : 
     728          305 : static cfd_status_t explicit_euler_simd_step_guarded(ns_solver_t* solver, flow_field* field,
     729              :                                                       const grid* grid, const ns_solver_params_t* params,
     730              :                                                       ns_solver_stats_t* stats) {
     731          305 :     return explicit_euler_simd_step(solver, field, grid, params, stats);
     732              : }
     733              : 
     734            1 : static cfd_status_t explicit_euler_simd_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
     735              :                                               const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     736            1 :     if (!solver || !field || !grid || !params) {
     737              :         return CFD_ERROR_INVALID;
     738              :     }
     739           21 :     for (int i = 0; i < params->max_iter; i++) {
     740           20 :         cfd_status_t status = explicit_euler_simd_step(solver, field, grid, params, NULL);
     741           20 :         if (status != CFD_SUCCESS) {
     742            0 :             return status;
     743              :         }
     744              :     }
     745              : 
     746            1 :     if (stats) {
     747            1 :         stats->iterations = params->max_iter;
     748            1 :         double max_vel = 0.0;
     749            1 :         double max_p = 0.0;
     750           82 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     751           81 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     752           81 :             if (vel > max_vel) {
     753           12 :                 max_vel = vel;
     754              :             }
     755           81 :             if (fabs(field->p[i]) > max_p) {
     756            7 :                 max_p = fabs(field->p[i]);
     757              :             }
     758              :         }
     759            1 :         stats->max_velocity = max_vel;
     760            1 :         stats->max_pressure = max_p;
     761            2 :         stats->max_temperature = compute_max_temperature(field);
     762              :     }
     763              :     return CFD_SUCCESS;
     764              : }
     765              : 
     766           15 : static ns_solver_t* create_explicit_euler_optimized_solver(void) {
     767           15 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
     768           15 :     if (!s) {
     769              :         return NULL;
     770              :     }
     771              : 
     772           15 :     s->name = NS_SOLVER_TYPE_EXPLICIT_EULER_OPTIMIZED;
     773           15 :     s->description = "SIMD-optimized explicit Euler solver (AVX2)";
     774           15 :     s->version = "1.0.0";
     775           15 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_SIMD;
     776           15 :     s->backend = NS_SOLVER_BACKEND_SIMD;
     777              : 
     778           15 :     s->init = explicit_euler_simd_init;
     779           15 :     s->destroy = explicit_euler_simd_destroy;
     780           15 :     s->step = explicit_euler_simd_step_guarded;
     781           15 :     s->solve = explicit_euler_simd_solve;
     782           15 :     s->apply_boundary = NULL;
     783           15 :     s->compute_dt = NULL;
     784              : 
     785           15 :     return s;
     786              : }
     787              : 
     788              : /**
     789              :  * Built-in solver: Projection Method (Chorin's Method)
     790              :  */
     791              : 
     792              : typedef struct {
     793              :     int initialized;
     794              : } projection_context;
     795              : 
     796           43 : static cfd_status_t projection_init(ns_solver_t* solver, const grid* grid, const ns_solver_params_t* params) {
     797           43 :     (void)grid;
     798           43 :     (void)params;
     799           43 :     projection_context* ctx = (projection_context*)cfd_malloc(sizeof(projection_context));
     800           43 :     if (!ctx) {
     801              :         return CFD_ERROR;
     802              :     }
     803           43 :     ctx->initialized = 1;
     804           43 :     solver->context = ctx;
     805           43 :     return CFD_SUCCESS;
     806              : }
     807              : 
     808           63 : static void projection_destroy(ns_solver_t* solver) {
     809           63 :     if (solver->context) {
     810           52 :         cfd_free(solver->context);
     811           52 :         solver->context = NULL;
     812              :     }
     813           63 : }
     814              : 
     815         6083 : static cfd_status_t projection_step(ns_solver_t* solver, flow_field* field, const grid* grid,
     816              :                                     const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     817         6083 :     (void)solver;
     818         6083 :     if (field->nx < 3 || field->ny < 3) {
     819              :         return CFD_ERROR_INVALID;
     820              :     }
     821              : 
     822         6083 :     ns_solver_params_t step_params = *params;
     823         6083 :     step_params.max_iter = 1;
     824              : 
     825         6083 :     cfd_status_t status = solve_projection_method(field, grid, &step_params);
     826         6083 :     if (status != CFD_SUCCESS) {
     827              :         return status;
     828              :     }
     829              : 
     830         6083 :     if (stats) {
     831         6083 :         stats->iterations = 1;
     832              :         // Compute max velocity/pressure
     833         6083 :         double max_vel = 0.0;
     834         6083 :         double max_p = 0.0;
     835      5123077 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     836      5116994 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     837      5116994 :             if (vel > max_vel) {
     838       391455 :                 max_vel = vel;
     839              :             }
     840      5116994 :             if (fabs(field->p[i]) > max_p) {
     841        93869 :                 max_p = fabs(field->p[i]);
     842              :             }
     843              :         }
     844         6083 :         stats->max_velocity = max_vel;
     845         6083 :         stats->max_pressure = max_p;
     846        12166 :         stats->max_temperature = compute_max_temperature(field);
     847              :     }
     848              :     return CFD_SUCCESS;
     849              : }
     850              : 
     851            2 : static cfd_status_t projection_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
     852              :                                      const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     853            2 :     (void)solver;
     854            2 :     if (field->nx < 3 || field->ny < 3) {
     855              :         return CFD_ERROR_INVALID;
     856              :     }
     857              : 
     858            2 :     cfd_status_t status = solve_projection_method(field, grid, params);
     859            2 :     if (status != CFD_SUCCESS) {
     860              :         return status;
     861              :     }
     862              : 
     863            2 :     if (stats) {
     864            2 :         stats->iterations = params->max_iter;
     865              :         // Compute max velocity/pressure
     866            2 :         double max_vel = 0.0;
     867            2 :         double max_p = 0.0;
     868          164 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     869          162 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     870          162 :             if (vel > max_vel) {
     871           14 :                 max_vel = vel;
     872              :             }
     873          162 :             if (fabs(field->p[i]) > max_p) {
     874            2 :                 max_p = fabs(field->p[i]);
     875              :             }
     876              :         }
     877            2 :         stats->max_velocity = max_vel;
     878            2 :         stats->max_pressure = max_p;
     879            4 :         stats->max_temperature = compute_max_temperature(field);
     880              :     }
     881              :     return CFD_SUCCESS;
     882              : }
     883              : 
     884           47 : static ns_solver_t* create_projection_solver(void) {
     885           47 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
     886           47 :     if (!s) {
     887              :         return NULL;
     888              :     }
     889              : 
     890           47 :     s->name = NS_SOLVER_TYPE_PROJECTION;
     891           47 :     s->description = "Projection method (Chorin's method)";
     892           47 :     s->version = "1.0.0";
     893           47 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT;
     894           47 :     s->backend = NS_SOLVER_BACKEND_SCALAR;
     895              : 
     896           47 :     s->init = projection_init;
     897           47 :     s->destroy = projection_destroy;
     898           47 :     s->step = projection_step;
     899           47 :     s->solve = projection_solve;
     900           47 :     s->apply_boundary = NULL;
     901           47 :     s->compute_dt = NULL;
     902              : 
     903           47 :     return s;
     904              : }
     905              : 
     906            1 : static cfd_status_t projection_simd_step_guarded(ns_solver_t* solver, flow_field* field,
     907              :                                                    const grid* grid, const ns_solver_params_t* params,
     908              :                                                    ns_solver_stats_t* stats) {
     909            1 :     return projection_simd_step(solver, field, grid, params, stats);
     910              : }
     911              : 
     912            0 : static cfd_status_t projection_simd_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
     913              :                                           const ns_solver_params_t* params, ns_solver_stats_t* stats) {
     914            0 :     if (!solver || !field || !grid || !params) {
     915              :         return CFD_ERROR_INVALID;
     916              :     }
     917              :     // Use the step function which utilizes the persistent context
     918            0 :     for (int i = 0; i < params->max_iter; i++) {
     919            0 :         cfd_status_t status = projection_simd_step(solver, field, grid, params,
     920              :                                                    NULL);  // Pass NULL stats for individual steps
     921            0 :         if (status != CFD_SUCCESS) {
     922            0 :             return status;
     923              :         }
     924              :     }
     925              : 
     926            0 :     if (stats) {
     927            0 :         stats->iterations = params->max_iter;
     928              :         // Compute max velocity/pressure
     929            0 :         double max_vel = 0.0;
     930            0 :         double max_p = 0.0;
     931            0 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
     932            0 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
     933            0 :             if (vel > max_vel) {
     934            0 :                 max_vel = vel;
     935              :             }
     936            0 :             if (fabs(field->p[i]) > max_p) {
     937            0 :                 max_p = fabs(field->p[i]);
     938              :             }
     939              :         }
     940            0 :         stats->max_velocity = max_vel;
     941            0 :         stats->max_pressure = max_p;
     942            0 :         stats->max_temperature = compute_max_temperature(field);
     943              :     }
     944              :     return CFD_SUCCESS;
     945              : }
     946              : 
     947           19 : static ns_solver_t* create_projection_optimized_solver(void) {
     948           19 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
     949           19 :     if (!s) {
     950              :         return NULL;
     951              :     }
     952              : 
     953           19 :     s->name = NS_SOLVER_TYPE_PROJECTION_OPTIMIZED;
     954           19 :     s->description = "SIMD-optimized Projection solver (AVX2)";
     955           19 :     s->version = "1.0.0";
     956           19 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_SIMD;
     957           19 :     s->backend = NS_SOLVER_BACKEND_SIMD;
     958              : 
     959           19 :     s->init = projection_simd_init;
     960           19 :     s->destroy = projection_simd_destroy;
     961           19 :     s->step = projection_simd_step_guarded;
     962           19 :     s->solve = projection_simd_solve;
     963           19 :     s->apply_boundary = NULL;
     964           19 :     s->compute_dt = NULL;
     965              : 
     966           19 :     return s;
     967              : }
     968              : 
     969              : #ifdef CFD_HAS_CUDA
     970              : /**
     971              :  * Built-in solver: GPU-Accelerated Explicit Euler
     972              :  * Uses CUDA for GPU acceleration (requires CUDA support)
     973              :  */
     974              : 
     975              : typedef struct {
     976              :     gpu_solver_context_t* gpu_ctx;
     977              :     gpu_config_t gpu_config_t;
     978              :     int use_gpu;
     979              : } gpu_solver_wrapper_context;
     980              : 
     981              : static cfd_status_t gpu_euler_init(ns_solver_t* solver, const grid* grid, const ns_solver_params_t* params) {
     982              :     gpu_solver_wrapper_context* ctx =
     983              :         (gpu_solver_wrapper_context*)cfd_malloc(sizeof(gpu_solver_wrapper_context));
     984              :     if (!ctx) {
     985              :         return CFD_ERROR;
     986              :     }
     987              : 
     988              :     ctx->gpu_config_t = gpu_config_default();
     989              :     ctx->use_gpu = gpu_should_use(&ctx->gpu_config_t, grid->nx, grid->ny, grid->nz, params->max_iter);
     990              :     ctx->gpu_ctx = NULL;
     991              : 
     992              :     if (ctx->use_gpu) {
     993              :         ctx->gpu_ctx = gpu_solver_create(grid->nx, grid->ny, grid->nz, &ctx->gpu_config_t);
     994              :         if (!ctx->gpu_ctx) {
     995              :             cfd_free(ctx);
     996              :             CFD_LOG_ERROR("gpu", "GPU Euler init: Failed to create GPU context");
     997              :             return CFD_ERROR_UNSUPPORTED;
     998              :         }
     999              :     }
    1000              : 
    1001              :     solver->context = ctx;
    1002              :     return CFD_SUCCESS;
    1003              : }
    1004              : 
    1005              : static void gpu_euler_destroy(ns_solver_t* solver) {
    1006              :     if (solver->context) {
    1007              :         gpu_solver_wrapper_context* ctx = (gpu_solver_wrapper_context*)solver->context;
    1008              :         if (ctx->gpu_ctx) {
    1009              :             gpu_solver_destroy(ctx->gpu_ctx);
    1010              :         }
    1011              :         cfd_free(ctx);
    1012              :         solver->context = NULL;
    1013              :     }
    1014              : }
    1015              : 
    1016              : static cfd_status_t gpu_euler_step(ns_solver_t* solver, flow_field* field, const grid* grid,
    1017              :                                    const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1018              :     gpu_solver_wrapper_context* ctx = (gpu_solver_wrapper_context*)solver->context;
    1019              : 
    1020              :     if (field->nx < 3 || field->ny < 3) {
    1021              :         return CFD_ERROR_INVALID;
    1022              :     }
    1023              :     cfd_status_t rc = check_energy_unsupported(params);
    1024              :     if (rc != CFD_SUCCESS) return rc;
    1025              : 
    1026              :     ns_solver_params_t step_params = *params;
    1027              :     step_params.max_iter = 1;
    1028              : 
    1029              :     if (ctx && ctx->use_gpu && ctx->gpu_ctx) {
    1030              :         // Upload, step, download
    1031              :         if (gpu_solver_upload(ctx->gpu_ctx, field) == 0) {
    1032              :             gpu_solver_stats_t gpu_stats;
    1033              :             if (gpu_solver_step(ctx->gpu_ctx, grid, &step_params, &gpu_stats) == 0) {
    1034              :                 gpu_solver_download(ctx->gpu_ctx, field);
    1035              : 
    1036              :                 if (stats) {
    1037              :                     stats->iterations = 1;
    1038              :                     stats->elapsed_time_ms = gpu_stats.kernel_time_ms;
    1039              :                     // Compute max velocity/pressure
    1040              :                     double max_vel = 0.0, max_p = 0.0;
    1041              :                     for (size_t i = 0; i < field->nx * field->ny; i++) {
    1042              :                         double vel =
    1043              :                             sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1044              :                         if (vel > max_vel) {
    1045              :                             max_vel = vel;
    1046              :                         }
    1047              :                         if (fabs(field->p[i]) > max_p) {
    1048              :                             max_p = fabs(field->p[i]);
    1049              :                         }
    1050              :                     }
    1051              :                     stats->max_velocity = max_vel;
    1052              :                     stats->max_pressure = max_p;
    1053              :                     stats->max_temperature = compute_max_temperature(field);
    1054              :                 }
    1055              :                 return CFD_SUCCESS;
    1056              :             }
    1057              :         }
    1058              :         // GPU operation failed
    1059              :         CFD_LOG_ERROR("gpu", "GPU Euler step: GPU operation failed");
    1060              :         return CFD_ERROR_INVALID;
    1061              :     }
    1062              : 
    1063              :     // GPU not available - could be: CUDA not compiled in, gpu_should_use() returned false,
    1064              :     // or GPU initialization failed
    1065              :     CFD_LOG_ERROR("gpu", "GPU Euler step: GPU solver not initialized");
    1066              :     return CFD_ERROR_UNSUPPORTED;
    1067              : }
    1068              : 
    1069              : static cfd_status_t gpu_euler_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
    1070              :                                     const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1071              :     gpu_solver_wrapper_context* ctx = (gpu_solver_wrapper_context*)solver->context;
    1072              : 
    1073              :     if (field->nx < 3 || field->ny < 3) {
    1074              :         return CFD_ERROR_INVALID;
    1075              :     }
    1076              :     cfd_status_t rc = check_energy_unsupported(params);
    1077              :     if (rc != CFD_SUCCESS) return rc;
    1078              : 
    1079              :     if (ctx && ctx->use_gpu && ctx->gpu_ctx) {
    1080              :         // Use full GPU solver
    1081              :         solve_navier_stokes_gpu(field, grid, params, &ctx->gpu_config_t);
    1082              : 
    1083              :         if (stats) {
    1084              :             stats->iterations = params->max_iter;
    1085              :             gpu_solver_stats_t gpu_stats = gpu_solver_get_stats(ctx->gpu_ctx);
    1086              :             stats->elapsed_time_ms = gpu_stats.kernel_time_ms;
    1087              : 
    1088              :             double max_vel = 0.0, max_p = 0.0;
    1089              :             for (size_t i = 0; i < field->nx * field->ny; i++) {
    1090              :                 double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1091              :                 if (vel > max_vel) {
    1092              :                     max_vel = vel;
    1093              :                 }
    1094              :                 if (fabs(field->p[i]) > max_p) {
    1095              :                     max_p = fabs(field->p[i]);
    1096              :                 }
    1097              :             }
    1098              :             stats->max_velocity = max_vel;
    1099              :             stats->max_pressure = max_p;
    1100              :             stats->max_temperature = compute_max_temperature(field);
    1101              :         }
    1102              :         return CFD_SUCCESS;
    1103              :     }
    1104              : 
    1105              :     // GPU not available - could be: CUDA not compiled in, gpu_should_use() returned false,
    1106              :     // or GPU initialization failed
    1107              :     CFD_LOG_ERROR("gpu", "GPU Euler solve: GPU solver not initialized");
    1108              :     return CFD_ERROR_UNSUPPORTED;
    1109              : }
    1110              : 
    1111              : static ns_solver_t* create_explicit_euler_gpu_solver(void) {
    1112              :     /* Check if GPU is available at runtime before creating the solver */
    1113              :     if (!gpu_is_available()) {
    1114              :         cfd_set_error(CFD_ERROR_UNSUPPORTED, "CUDA GPU not available at runtime");
    1115              :         return NULL;
    1116              :     }
    1117              : 
    1118              :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
    1119              :     if (!s) {
    1120              :         return NULL;
    1121              :     }
    1122              : 
    1123              :     s->name = NS_SOLVER_TYPE_EXPLICIT_EULER_GPU;
    1124              :     s->description = "GPU-accelerated explicit Euler solver (CUDA)";
    1125              :     s->version = "1.0.0";
    1126              :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_GPU;
    1127              :     s->backend = NS_SOLVER_BACKEND_CUDA;
    1128              : 
    1129              :     s->init = gpu_euler_init;
    1130              :     s->destroy = gpu_euler_destroy;
    1131              :     s->step = gpu_euler_step;
    1132              :     s->solve = gpu_euler_solve;
    1133              :     s->apply_boundary = NULL;
    1134              :     s->compute_dt = NULL;
    1135              : 
    1136              :     return s;
    1137              : }
    1138              : 
    1139              : /**
    1140              :  * Built-in solver: GPU-Accelerated Projection Method
    1141              :  */
    1142              : 
    1143              : static cfd_status_t gpu_projection_step(ns_solver_t* solver, flow_field* field, const grid* grid,
    1144              :                                         const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1145              :     (void)solver;
    1146              :     /* The GPU projection backend implements the energy equation (advection-
    1147              :      * diffusion + Boussinesq buoyancy + per-face thermal BCs). Heat-source
    1148              :      * callbacks and unsupported thermal BC types are rejected inside
    1149              :      * solve_projection_method_gpu. */
    1150              :     ns_solver_params_t step_params = *params;
    1151              :     step_params.max_iter = 1;
    1152              :     /* Override thresholds to allow single-step GPU execution on small grids */
    1153              :     gpu_config_t cfg = gpu_config_default();
    1154              :     cfg.min_grid_size = 1;
    1155              :     cfg.min_steps = 1;
    1156              :     cfd_status_t rc = solve_projection_method_gpu(field, grid, &step_params, &cfg);
    1157              :     if (rc == CFD_SUCCESS && stats) {
    1158              :         stats->max_temperature = compute_max_temperature(field);
    1159              :     }
    1160              :     return rc;
    1161              : }
    1162              : 
    1163              : static cfd_status_t gpu_projection_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
    1164              :                                          const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1165              :     (void)solver;
    1166              :     /* Override thresholds to allow GPU execution on small grids */
    1167              :     gpu_config_t cfg = gpu_config_default();
    1168              :     cfg.min_grid_size = 1;
    1169              :     cfg.min_steps = 1;
    1170              :     cfd_status_t rc = solve_projection_method_gpu(field, grid, params, &cfg);
    1171              :     if (rc == CFD_SUCCESS && stats) {
    1172              :         stats->max_temperature = compute_max_temperature(field);
    1173              :     }
    1174              :     return rc;
    1175              : }
    1176              : 
    1177              : static ns_solver_t* create_projection_gpu_solver(void) {
    1178              :     /* Check if GPU is available at runtime before creating the solver */
    1179              :     if (!gpu_is_available()) {
    1180              :         cfd_set_error(CFD_ERROR_UNSUPPORTED, "CUDA GPU not available at runtime");
    1181              :         return NULL;
    1182              :     }
    1183              : 
    1184              :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
    1185              :     if (!s) {
    1186              :         return NULL;
    1187              :     }
    1188              : 
    1189              :     s->name = NS_SOLVER_TYPE_PROJECTION_JACOBI_GPU;
    1190              :     s->description = "GPU-accelerated projection method with Jacobi iteration (CUDA)";
    1191              :     s->version = "1.0.0";
    1192              :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_GPU;
    1193              :     s->backend = NS_SOLVER_BACKEND_CUDA;
    1194              : 
    1195              :     s->init = NULL;
    1196              :     s->destroy = NULL;
    1197              :     s->step = gpu_projection_step;
    1198              :     s->solve = gpu_projection_solve;
    1199              :     s->apply_boundary = NULL;
    1200              :     s->compute_dt = NULL;
    1201              : 
    1202              :     return s;
    1203              : }
    1204              : #endif /* CFD_HAS_CUDA */
    1205              : 
    1206              : #ifdef CFD_ENABLE_OPENMP
    1207              : /**
    1208              :  * Built-in solver: Explicit Euler OpenMP
    1209              :  */
    1210              : 
    1211          212 : static cfd_status_t explicit_euler_omp_step(ns_solver_t* solver, flow_field* field, const grid* grid,
    1212              :                                             const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1213          212 :     (void)solver;
    1214          212 :     if (field->nx < 3 || field->ny < 3) {
    1215              :         return CFD_ERROR_INVALID;
    1216              :     }
    1217          212 :     ns_solver_params_t step_params = *params;
    1218          212 :     step_params.max_iter = 1;
    1219              : 
    1220          212 :     explicit_euler_omp_impl(field, grid, &step_params);
    1221              : 
    1222          212 :     if (stats) {
    1223          212 :         stats->iterations = 1;
    1224          212 :         double max_vel = 0.0, max_p = 0.0;
    1225      1751380 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
    1226      1751168 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1227      1751168 :             if (vel > max_vel) {
    1228         4080 :                 max_vel = vel;
    1229              :             }
    1230      1751168 :             if (fabs(field->p[i]) > max_p) {
    1231         6785 :                 max_p = fabs(field->p[i]);
    1232              :             }
    1233              :         }
    1234          212 :         stats->max_velocity = max_vel;
    1235          212 :         stats->max_pressure = max_p;
    1236          424 :         stats->max_temperature = compute_max_temperature(field);
    1237              :     }
    1238              :     return CFD_SUCCESS;
    1239              : }
    1240              : 
    1241            1 : static cfd_status_t explicit_euler_omp_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
    1242              :                                              const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1243            1 :     (void)solver;
    1244            1 :     if (field->nx < 3 || field->ny < 3) {
    1245              :         return CFD_ERROR_INVALID;
    1246              :     }
    1247            1 :     explicit_euler_omp_impl(field, grid, params);
    1248              : 
    1249            1 :     if (stats) {
    1250            1 :         stats->iterations = params->max_iter;
    1251            1 :         double max_vel = 0.0, max_p = 0.0;
    1252           82 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
    1253           81 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1254           81 :             if (vel > max_vel) {
    1255           12 :                 max_vel = vel;
    1256              :             }
    1257           81 :             if (fabs(field->p[i]) > max_p) {
    1258            7 :                 max_p = fabs(field->p[i]);
    1259              :             }
    1260              :         }
    1261            1 :         stats->max_velocity = max_vel;
    1262            1 :         stats->max_pressure = max_p;
    1263            2 :         stats->max_temperature = compute_max_temperature(field);
    1264              :     }
    1265              :     return CFD_SUCCESS;
    1266              : }
    1267              : 
    1268           16 : static ns_solver_t* create_explicit_euler_omp_solver(void) {
    1269           16 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
    1270           16 :     if (!s) {
    1271              :         return NULL;
    1272              :     }
    1273              : 
    1274           16 :     s->name = NS_SOLVER_TYPE_EXPLICIT_EULER_OMP;
    1275           16 :     s->description = "OpenMP-parallelized explicit Euler solver";
    1276           16 :     s->version = "1.0.0";
    1277           16 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_PARALLEL;
    1278           16 :     s->backend = NS_SOLVER_BACKEND_OMP;
    1279              : 
    1280           16 :     s->init = explicit_euler_init;        // Can reuse existing init
    1281           16 :     s->destroy = explicit_euler_destroy;  // Can reuse existing destroy
    1282           16 :     s->step = explicit_euler_omp_step;
    1283           16 :     s->solve = explicit_euler_omp_solve;
    1284           16 :     s->apply_boundary = NULL;
    1285           16 :     s->compute_dt = NULL;
    1286              : 
    1287           16 :     return s;
    1288              : }
    1289              : 
    1290              : /**
    1291              :  * Built-in NSSolver: RK2 OpenMP
    1292              :  */
    1293              : 
    1294          212 : static cfd_status_t rk2_omp_step(ns_solver_t* solver, flow_field* field, const grid* grid,
    1295              :                                   const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1296          212 :     (void)solver;
    1297          212 :     if (field->nx < 3 || field->ny < 3) {
    1298              :         return CFD_ERROR_INVALID;
    1299              :     }
    1300          212 :     ns_solver_params_t step_params = *params;
    1301          212 :     step_params.max_iter = 1;
    1302              : 
    1303          212 :     cfd_status_t status = rk2_omp_impl(field, grid, &step_params);
    1304              : 
    1305          212 :     if (stats) {
    1306          212 :         stats->iterations = 1;
    1307          212 :         double max_vel = 0.0, max_p = 0.0;
    1308          212 :         ptrdiff_t i;
    1309          212 :         ptrdiff_t n = (ptrdiff_t)(field->nx * field->ny);
    1310              : #if _OPENMP >= 201107
    1311          212 :         #pragma omp parallel for reduction(max: max_vel, max_p) schedule(static)
    1312              : #endif
    1313              :         for (i = 0; i < n; i++) {
    1314              :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1315              :             if (vel > max_vel) {
    1316              :                 max_vel = vel;
    1317              :             }
    1318              :             double ap = fabs(field->p[i]);
    1319              :             if (ap > max_p) {
    1320              :                 max_p = ap;
    1321              :             }
    1322              :         }
    1323          212 :         stats->max_velocity = max_vel;
    1324          212 :         stats->max_pressure = max_p;
    1325          424 :         stats->max_temperature = compute_max_temperature(field);
    1326              :     }
    1327              :     return status;
    1328              : }
    1329              : 
    1330            1 : static cfd_status_t rk2_omp_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
    1331              :                                    const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1332            1 :     (void)solver;
    1333            1 :     if (field->nx < 3 || field->ny < 3) {
    1334              :         return CFD_ERROR_INVALID;
    1335              :     }
    1336            1 :     cfd_status_t status = rk2_omp_impl(field, grid, params);
    1337              : 
    1338            1 :     if (stats) {
    1339            1 :         stats->iterations = params->max_iter;
    1340            1 :         double max_vel = 0.0, max_p = 0.0;
    1341            1 :         ptrdiff_t i;
    1342            1 :         ptrdiff_t n = (ptrdiff_t)(field->nx * field->ny);
    1343              : #if _OPENMP >= 201107
    1344            1 :         #pragma omp parallel for reduction(max: max_vel, max_p) schedule(static)
    1345              : #endif
    1346              :         for (i = 0; i < n; i++) {
    1347              :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1348              :             if (vel > max_vel) {
    1349              :                 max_vel = vel;
    1350              :             }
    1351              :             double ap = fabs(field->p[i]);
    1352              :             if (ap > max_p) {
    1353              :                 max_p = ap;
    1354              :             }
    1355              :         }
    1356            1 :         stats->max_velocity = max_vel;
    1357            1 :         stats->max_pressure = max_p;
    1358            2 :         stats->max_temperature = compute_max_temperature(field);
    1359              :     }
    1360              :     return status;
    1361              : }
    1362              : 
    1363           13 : static ns_solver_t* create_rk2_omp_solver(void) {
    1364           13 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
    1365           13 :     if (!s) {
    1366              :         return NULL;
    1367              :     }
    1368              : 
    1369           13 :     s->name = NS_SOLVER_TYPE_RK2_OMP;
    1370           13 :     s->description = "OpenMP-parallelized RK2 (Heun's method)";
    1371           13 :     s->version = "1.0.0";
    1372           13 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_PARALLEL;
    1373           13 :     s->backend = NS_SOLVER_BACKEND_OMP;
    1374              : 
    1375           13 :     s->init = explicit_euler_init;
    1376           13 :     s->destroy = explicit_euler_destroy;
    1377           13 :     s->step = rk2_omp_step;
    1378           13 :     s->solve = rk2_omp_solve;
    1379           13 :     s->apply_boundary = NULL;
    1380           13 :     s->compute_dt = NULL;
    1381              : 
    1382           13 :     return s;
    1383              : }
    1384              : 
    1385              : /**
    1386              :  * Built-in NSSolver: Projection OpenMP
    1387              :  */
    1388              : 
    1389            9 : static cfd_status_t projection_omp_init(ns_solver_t* solver, const grid* grid,
    1390              :                                         const ns_solver_params_t* params) {
    1391            9 :     (void)params;
    1392            9 :     if (!solver || !grid) {
    1393              :         return CFD_ERROR_INVALID;
    1394              :     }
    1395              : 
    1396              :     /* OMP projection requires OMP CG Poisson solver.
    1397              :      * Never fall back to scalar CG — it would serialize the Poisson solve. */
    1398            9 :     poisson_solver_t* test_solver = poisson_solver_create(
    1399              :         POISSON_METHOD_CG, POISSON_BACKEND_OMP);
    1400            9 :     if (!test_solver) {
    1401            0 :         CFD_LOG_WARNING("projection", "OMP CG Poisson solver not available");
    1402            0 :         return CFD_ERROR_UNSUPPORTED;
    1403              :     }
    1404            9 :     poisson_solver_destroy(test_solver);
    1405              : 
    1406            9 :     projection_context* ctx = (projection_context*)cfd_malloc(sizeof(projection_context));
    1407            9 :     if (!ctx) {
    1408              :         return CFD_ERROR_NOMEM;
    1409              :     }
    1410            9 :     ctx->initialized = 1;
    1411            9 :     solver->context = ctx;
    1412            9 :     return CFD_SUCCESS;
    1413              : }
    1414              : 
    1415          122 : static cfd_status_t projection_omp_step(ns_solver_t* solver, flow_field* field, const grid* grid,
    1416              :                                         const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1417          122 :     (void)solver;
    1418          122 :     if (field->nx < 3 || field->ny < 3) {
    1419              :         return CFD_ERROR_INVALID;
    1420              :     }
    1421          122 :     ns_solver_params_t step_params = *params;
    1422          122 :     step_params.max_iter = 1;
    1423              : 
    1424          122 :     cfd_status_t status = solve_projection_method_omp(field, grid, &step_params);
    1425          122 :     if (status != CFD_SUCCESS) {
    1426              :         return status;
    1427              :     }
    1428              : 
    1429          122 :     if (stats) {
    1430          122 :         stats->iterations = 1;
    1431          122 :         double max_vel = 0.0, max_p = 0.0;
    1432          122 :         ptrdiff_t i;
    1433          122 :         ptrdiff_t n = (ptrdiff_t)(field->nx * field->ny);
    1434              : #if _OPENMP >= 201107
    1435          122 :         #pragma omp parallel for reduction(max: max_vel, max_p) schedule(static)
    1436              : #endif
    1437              :         for (i = 0; i < n; i++) {
    1438              :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1439              :             if (vel > max_vel) {
    1440              :                 max_vel = vel;
    1441              :             }
    1442              :             double ap = fabs(field->p[i]);
    1443              :             if (ap > max_p) {
    1444              :                 max_p = ap;
    1445              :             }
    1446              :         }
    1447          122 :         stats->max_velocity = max_vel;
    1448          122 :         stats->max_pressure = max_p;
    1449          244 :         stats->max_temperature = compute_max_temperature(field);
    1450              :     }
    1451              :     return CFD_SUCCESS;
    1452              : }
    1453              : 
    1454            1 : static cfd_status_t projection_omp_solve(ns_solver_t* solver, flow_field* field, const grid* grid,
    1455              :                                          const ns_solver_params_t* params, ns_solver_stats_t* stats) {
    1456            1 :     (void)solver;
    1457            1 :     if (field->nx < 3 || field->ny < 3) {
    1458              :         return CFD_ERROR_INVALID;
    1459              :     }
    1460            1 :     cfd_status_t status = solve_projection_method_omp(field, grid, params);
    1461            1 :     if (status != CFD_SUCCESS) {
    1462              :         return status;
    1463              :     }
    1464              : 
    1465            1 :     if (stats) {
    1466            1 :         stats->iterations = params->max_iter;
    1467            1 :         double max_vel = 0.0, max_p = 0.0;
    1468           82 :         for (size_t i = 0; i < field->nx * field->ny; i++) {
    1469           81 :             double vel = sqrt((field->u[i] * field->u[i]) + (field->v[i] * field->v[i]));
    1470           81 :             if (vel > max_vel) {
    1471            7 :                 max_vel = vel;
    1472              :             }
    1473           81 :             if (fabs(field->p[i]) > max_p) {
    1474            1 :                 max_p = fabs(field->p[i]);
    1475              :             }
    1476              :         }
    1477            1 :         stats->max_velocity = max_vel;
    1478            1 :         stats->max_pressure = max_p;
    1479            2 :         stats->max_temperature = compute_max_temperature(field);
    1480              :     }
    1481              :     return CFD_SUCCESS;
    1482              : }
    1483              : 
    1484           16 : static ns_solver_t* create_projection_omp_solver(void) {
    1485           16 :     ns_solver_t* s = (ns_solver_t*)cfd_calloc(1, sizeof(*s));
    1486           16 :     if (!s) {
    1487              :         return NULL;
    1488              :     }
    1489              : 
    1490           16 :     s->name = NS_SOLVER_TYPE_PROJECTION_OMP;
    1491           16 :     s->description = "OpenMP-parallelized Projection solver";
    1492           16 :     s->version = "1.0.0";
    1493           16 :     s->capabilities = NS_SOLVER_CAP_INCOMPRESSIBLE | NS_SOLVER_CAP_TRANSIENT | NS_SOLVER_CAP_PARALLEL;
    1494              : 
    1495           16 :     s->init = projection_omp_init;    // Checks OMP CG availability
    1496           16 :     s->destroy = projection_destroy;
    1497           16 :     s->step = projection_omp_step;
    1498           16 :     s->solve = projection_omp_solve;
    1499           16 :     s->apply_boundary = NULL;
    1500           16 :     s->compute_dt = NULL;
    1501           16 :     s->backend = NS_SOLVER_BACKEND_OMP;
    1502              : 
    1503           16 :     return s;
    1504              : }
    1505              : #endif
    1506              : 
    1507              : //=============================================================================
    1508              : // Backend Availability API
    1509              : //=============================================================================
    1510              : 
    1511           30 : int cfd_backend_is_available(ns_solver_backend_t backend) {
    1512           30 :     switch (backend) {
    1513              :         case NS_SOLVER_BACKEND_SCALAR:
    1514              :             return 1;  // Always available
    1515              : 
    1516           12 :         case NS_SOLVER_BACKEND_SIMD:
    1517           12 :             return cfd_has_simd();
    1518              : 
    1519              :         case NS_SOLVER_BACKEND_OMP:
    1520              : #ifdef CFD_ENABLE_OPENMP
    1521              :             return 1;
    1522              : #else
    1523              :             return 0;
    1524              : #endif
    1525              : 
    1526            4 :         case NS_SOLVER_BACKEND_CUDA:
    1527            4 :             return gpu_is_available();
    1528              : 
    1529            1 :         default:
    1530            1 :             return 0;
    1531              :     }
    1532              : }
    1533              : 
    1534            8 : const char* cfd_backend_get_name(ns_solver_backend_t backend) {
    1535            8 :     switch (backend) {
    1536              :         case NS_SOLVER_BACKEND_SCALAR:
    1537              :             return "scalar";
    1538            2 :         case NS_SOLVER_BACKEND_SIMD:
    1539            2 :             return "simd";
    1540            1 :         case NS_SOLVER_BACKEND_OMP:
    1541            1 :             return "openmp";
    1542            3 :         case NS_SOLVER_BACKEND_CUDA:
    1543            3 :             return "cuda";
    1544            1 :         default:
    1545            1 :             return "unknown";
    1546              :     }
    1547              : }
    1548              : 
    1549           13 : int cfd_registry_list_by_backend(ns_solver_registry_t* registry, ns_solver_backend_t backend,
    1550              :                                   const char** names, int max_count) {
    1551           13 :     if (!registry) {
    1552              :         return 0;
    1553              :     }
    1554              : 
    1555              :     int count = 0;
    1556              : 
    1557              :     /* If names is NULL, just count total matches (discovery mode).
    1558              :      * Otherwise, fill the array up to max_count and return number filled. */
    1559          111 :     for (int i = 0; i < registry->count; i++) {
    1560              :         /* Use the stored backend instead of creating temporary solvers.
    1561              :          * This is much more efficient and avoids side effects from factory calls. */
    1562          100 :         if (registry->entries[i].backend == backend) {
    1563           28 :             if (names == NULL) {
    1564              :                 /* Discovery mode: count all matches */
    1565            3 :                 count++;
    1566           25 :             } else if (count < max_count) {
    1567              :                 /* Fill mode: add to array if space available */
    1568           24 :                 names[count] = registry->entries[i].name;
    1569           24 :                 count++;
    1570              :             } else {
    1571              :                 /* Array full, stop filling but don't count more */
    1572              :                 break;
    1573              :             }
    1574              :         }
    1575              :     }
    1576              : 
    1577              :     return count;
    1578              : }
    1579              : 
    1580           12 : ns_solver_t* cfd_solver_create_checked(ns_solver_registry_t* registry, const char* type_name) {
    1581           12 :     if (!registry || !type_name) {
    1582            2 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid arguments for solver creation");
    1583            2 :         return NULL;
    1584              :     }
    1585              : 
    1586              :     /* Check backend availability BEFORE creating the solver */
    1587           10 :     ns_solver_backend_t expected_backend = infer_backend_from_type(type_name);
    1588           10 :     if (!cfd_backend_is_available(expected_backend)) {
    1589            2 :         const char* backend_name = cfd_backend_get_name(expected_backend);
    1590            2 :         char error_msg[128];
    1591            2 :         snprintf(error_msg, sizeof(error_msg),
    1592              :                  "Backend '%s' is not available on this system", backend_name);
    1593            2 :         cfd_set_error(CFD_ERROR_UNSUPPORTED, error_msg);
    1594            2 :         return NULL;
    1595              :     }
    1596              : 
    1597              :     /* Now create the solver - backend is available */
    1598            8 :     ns_solver_t* solver = cfd_solver_create(registry, type_name);
    1599            8 :     if (!solver) {
    1600              :         /* Error already set by cfd_solver_create */
    1601              :         return NULL;
    1602              :     }
    1603              : 
    1604              :     return solver;
    1605              : }
        

Generated by: LCOV version 2.0-1