LCOV - code coverage report
Current view: top level - api - simulation_api.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 87.1 % 178 155
Test Date: 2026-03-04 10:22:18 Functions: 94.7 % 19 18

            Line data    Source code
       1              : #include "cfd/api/simulation_api.h"
       2              : #include "cfd/core/cfd_status.h"
       3              : #include "cfd/core/derived_fields.h"
       4              : #include "cfd/core/grid.h"
       5              : #include "cfd/core/memory.h"
       6              : #include "cfd/io/output_registry.h"
       7              : #include "cfd/solvers/navier_stokes_solver.h"
       8              : 
       9              : 
      10              : #include "cfd/core/cfd_init.h"
      11              : #include <math.h>
      12              : #include <stdio.h>
      13              : #include <stdlib.h>
      14              : #include <string.h>
      15              : 
      16              : // simulation_data struct is defined in simulation_api.h
      17              : 
      18              : // Default solver type used when none is specified
      19              : #define DEFAULT_SOLVER_TYPE NS_SOLVER_TYPE_EXPLICIT_EULER
      20              : 
      21              : // Ensure registry is initialized
      22              : // Internal helper to create simulation with a specific solver
      23           74 : static simulation_data* create_simulation_with_solver(size_t nx, size_t ny, size_t nz,
      24              :                                                        double xmin, double xmax,
      25              :                                                        double ymin, double ymax,
      26              :                                                        double zmin, double zmax,
      27              :                                                        const char* solver_type) {
      28              :     // Lazy initialization of library
      29           74 :     if (!cfd_is_initialized()) {
      30            7 :         if (cfd_init() != CFD_SUCCESS) {
      31            0 :             cfd_set_error(CFD_ERROR, "Failed to initialize CFD library");
      32            0 :             return NULL;
      33              :         }
      34              :     }
      35              : 
      36           74 :     if (nx == 0 || ny == 0 || nz == 0) {
      37            2 :         cfd_set_error(CFD_ERROR_INVALID, "Simulation grid dimensions must be positive");
      38            2 :         return NULL;
      39              :     }
      40           72 :     if (xmax <= xmin || ymax <= ymin || (nz > 1 && zmax <= zmin)) {
      41            2 :         cfd_set_error(CFD_ERROR_INVALID, "Simulation bounds invalid");
      42            2 :         return NULL;
      43              :     }
      44              : 
      45           70 :     simulation_data* sim_data = (simulation_data*)cfd_malloc(sizeof(simulation_data));
      46           70 :     if (!sim_data) {
      47              :         return NULL;
      48              :     }
      49              : 
      50              :     // Initialize base output directory
      51           70 :     snprintf(sim_data->output_base_dir, sizeof(sim_data->output_base_dir), "../../artifacts");
      52              : 
      53              :     // Create and initialize grid
      54           70 :     sim_data->grid = grid_create(nx, ny, nz, xmin, xmax, ymin, ymax, zmin, zmax);
      55           70 :     if (!sim_data->grid) {
      56            0 :         cfd_free(sim_data);
      57            0 :         return NULL;
      58              :     }
      59           70 :     grid_initialize_uniform(sim_data->grid);
      60              : 
      61              :     // Create flow_field
      62           70 :     sim_data->field = flow_field_create(nx, ny, nz);
      63           70 :     if (!sim_data->field) {
      64            0 :         grid_destroy(sim_data->grid);
      65            0 :         cfd_free(sim_data);
      66            0 :         return NULL;
      67              :     }
      68           70 :     initialize_flow_field(sim_data->field, sim_data->grid);
      69              : 
      70              :     // Initialize solver parameters with defaults
      71           70 :     sim_data->params = ns_solver_params_default();
      72           70 :     sim_data->params.dt = 0.001;
      73           70 :     sim_data->params.cfl = 0.2;
      74           70 :     sim_data->params.mu = 0.01;
      75           70 :     sim_data->params.max_iter = 1;
      76              : 
      77              :     // Initialize stats
      78           70 :     sim_data->last_stats = ns_solver_stats_default();
      79              : 
      80              :     // Create output registry
      81           70 :     sim_data->outputs = output_registry_create();
      82           70 :     if (!sim_data->outputs) {
      83            0 :         flow_field_destroy(sim_data->field);
      84            0 :         grid_destroy(sim_data->grid);
      85            0 :         cfd_free(sim_data);
      86            0 :         return NULL;
      87              :     }
      88           70 :     sim_data->run_prefix = NULL;
      89           70 :     sim_data->current_time = 0.0;
      90              : 
      91              :     // Initialize NSSolver Registry
      92           70 :     sim_data->registry = cfd_registry_create();
      93           70 :     if (!sim_data->registry) {
      94            0 :         output_registry_destroy(sim_data->outputs);
      95            0 :         flow_field_destroy(sim_data->field);
      96            0 :         grid_destroy(sim_data->grid);
      97            0 :         cfd_free(sim_data);
      98            0 :         return NULL;
      99              :     }
     100           70 :     cfd_registry_register_defaults(sim_data->registry);
     101              : 
     102              :     // Create and initialize the solver
     103           70 :     sim_data->solver = cfd_solver_create(sim_data->registry, solver_type);
     104           70 :     if (!sim_data->solver) {
     105              :         // Failed to create solver - cleanup and return NULL
     106            1 :         cfd_registry_destroy(sim_data->registry);
     107            1 :         output_registry_destroy(sim_data->outputs);
     108            1 :         flow_field_destroy(sim_data->field);
     109            1 :         grid_destroy(sim_data->grid);
     110            1 :         cfd_free(sim_data);
     111            1 :         return NULL;
     112              :     }
     113              : 
     114           69 :     solver_init(sim_data->solver, sim_data->grid, &sim_data->params);
     115              : 
     116           69 :     return sim_data;
     117              : }
     118              : 
     119              : // Initialize simulation with default solver
     120           70 : simulation_data* init_simulation(size_t nx, size_t ny, size_t nz,
     121              :                                   double xmin, double xmax,
     122              :                                   double ymin, double ymax,
     123              :                                   double zmin, double zmax) {
     124           70 :     return create_simulation_with_solver(nx, ny, nz, xmin, xmax, ymin, ymax, zmin, zmax,
     125              :                                           DEFAULT_SOLVER_TYPE);
     126              : }
     127              : 
     128              : // Initialize simulation with a specific solver type
     129            4 : simulation_data* init_simulation_with_solver(size_t nx, size_t ny, size_t nz,
     130              :                                               double xmin, double xmax,
     131              :                                               double ymin, double ymax,
     132              :                                               double zmin, double zmax,
     133              :                                               const char* solver_type) {
     134            4 :     if (!solver_type) {
     135            1 :         solver_type = DEFAULT_SOLVER_TYPE;
     136              :     }
     137            4 :     return create_simulation_with_solver(nx, ny, nz, xmin, xmax, ymin, ymax, zmin, zmax,
     138              :                                           solver_type);
     139              : }
     140              : 
     141              : // Set the solver for an existing simulation
     142            3 : void simulation_set_solver(simulation_data* sim_data, struct NSSolver* solver) {
     143            3 :     if (!sim_data || !solver) {
     144            2 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid arguments for simulation_set_solver");
     145            2 :         return;
     146              :     }
     147              : 
     148              :     // Destroy existing solver
     149            1 :     if (sim_data->solver) {
     150            1 :         solver_destroy(sim_data->solver);
     151              :     }
     152              : 
     153            1 :     sim_data->solver = solver;
     154            1 :     solver_init(solver, sim_data->grid, &sim_data->params);
     155              : }
     156              : 
     157              : // Set the solver by type name
     158            6 : int simulation_set_solver_by_name(simulation_data* sim_data, const char* solver_type) {
     159            6 :     if (!sim_data || !solver_type) {
     160            4 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid arguments for simulation solver");
     161            4 :         return -1;
     162              :     }
     163              : 
     164            2 :     struct NSSolver* solver = cfd_solver_create(sim_data->registry, solver_type);
     165            2 :     if (!solver) {
     166              :         return -1;
     167              :     }
     168              : 
     169            1 :     simulation_set_solver(sim_data, solver);
     170            1 :     return 0;
     171              : }
     172              : 
     173              : // Get the current solver
     174            2 : struct NSSolver* simulation_get_solver(simulation_data* sim_data) {
     175            2 :     return sim_data ? sim_data->solver : NULL;
     176              : }
     177              : 
     178              : // Get statistics from the last solve
     179            3 : const ns_solver_stats_t* simulation_get_stats(const simulation_data* sim_data) {
     180            3 :     return sim_data ? &sim_data->last_stats : NULL;
     181              : }
     182              : 
     183              : // Run simulation step
     184           21 : cfd_status_t run_simulation_step(simulation_data* sim_data) {
     185           21 :     if (!sim_data || !sim_data->solver) {
     186              :         return CFD_ERROR_INVALID;
     187              :     }
     188              : 
     189              :     // Use fixed time step for animation stability
     190           20 :     sim_data->params.dt = 0.005;
     191              : 
     192           40 :     cfd_status_t status = solver_step(sim_data->solver, sim_data->field, sim_data->grid,
     193           20 :                                        &sim_data->params, &sim_data->last_stats);
     194           20 :     if (status != CFD_SUCCESS) {
     195              :         return status;
     196              :     }
     197              : 
     198              :     // Accumulate simulation time
     199           20 :     sim_data->current_time += sim_data->params.dt;
     200           20 :     return CFD_SUCCESS;
     201              : }
     202              : 
     203            0 : cfd_status_t run_simulation_solve(simulation_data* sim_data) {
     204            0 :     if (!sim_data || !sim_data->solver) {
     205              :         return CFD_ERROR_INVALID;
     206              :     }
     207              : 
     208              :     // Use fixed time step for animation stability
     209            0 :     sim_data->params.dt = 0.005;
     210              : 
     211            0 :     cfd_status_t status = solver_solve(sim_data->solver, sim_data->field, sim_data->grid,
     212            0 :                                         &sim_data->params, &sim_data->last_stats);
     213              : 
     214              :     // Accumulate simulation time based on iterations performed
     215            0 :     sim_data->current_time += sim_data->params.dt * sim_data->last_stats.iterations;
     216              : 
     217            0 :     return status;
     218              : }
     219              : 
     220              : // Free simulation data
     221              : // Free simulation data
     222           69 : void free_simulation(simulation_data* sim_data) {
     223           69 :     if (!sim_data) {
     224              :         return;
     225              :     }
     226              : 
     227           69 :     if (sim_data->solver) {
     228           69 :         solver_destroy(sim_data->solver);
     229           69 :         sim_data->solver = NULL;
     230              :     }
     231              : 
     232           69 :     if (sim_data->registry) {
     233           69 :         cfd_registry_destroy(sim_data->registry);
     234           69 :         sim_data->registry = NULL;
     235              :     }
     236              : 
     237           69 :     if (sim_data->outputs) {
     238           69 :         output_registry_destroy(sim_data->outputs);
     239           69 :         sim_data->outputs = NULL;
     240              :     }
     241              : 
     242           69 :     if (sim_data->run_prefix) {
     243            6 :         cfd_free(sim_data->run_prefix);
     244            6 :         sim_data->run_prefix = NULL;
     245              :     }
     246              : 
     247           69 :     flow_field_destroy(sim_data->field);
     248           69 :     grid_destroy(sim_data->grid);
     249           69 :     cfd_free(sim_data);
     250              : }
     251              : 
     252              : // Static list of available solver names (const strings have static lifetime)
     253              : // This avoids use-after-free issues with dynamically created registries
     254              : static const char* const s_solver_names[] = {
     255              :     NS_SOLVER_TYPE_EXPLICIT_EULER,
     256              :     NS_SOLVER_TYPE_EXPLICIT_EULER_OPTIMIZED,
     257              :     NS_SOLVER_TYPE_PROJECTION,
     258              :     NS_SOLVER_TYPE_PROJECTION_OPTIMIZED,
     259              :     NS_SOLVER_TYPE_EXPLICIT_EULER_GPU,
     260              :     NS_SOLVER_TYPE_PROJECTION_JACOBI_GPU,
     261              : #ifdef CFD_ENABLE_OPENMP
     262              :     NS_SOLVER_TYPE_EXPLICIT_EULER_OMP,
     263              :     NS_SOLVER_TYPE_PROJECTION_OMP,
     264              : #endif
     265              : };
     266              : 
     267              : static const int s_solver_count = (int)(sizeof(s_solver_names) / sizeof(s_solver_names[0]));
     268              : 
     269              : // NSSolver discovery and listing
     270            6 : int simulation_list_solvers(const char** names, int max_count) {
     271            6 :     if (names && max_count > 0) {
     272            5 :         int fill_count = (s_solver_count < max_count) ? s_solver_count : max_count;
     273           39 :         for (int i = 0; i < fill_count; i++) {
     274           34 :             names[i] = s_solver_names[i];
     275              :         }
     276              :     }
     277            6 :     return s_solver_count;
     278              : }
     279              : 
     280            3 : int simulation_has_solver(const char* solver_type) {
     281            3 :     if (!solver_type) {
     282              :         return 0;
     283              :     }
     284           13 :     for (int i = 0; i < s_solver_count; i++) {
     285           12 :         if (strcmp(s_solver_names[i], solver_type) == 0) {
     286              :             return 1;
     287              :         }
     288              :     }
     289              :     return 0;
     290              : }
     291              : 
     292              : 
     293              : //=============================================================================
     294              : // OUTPUT REGISTRY API
     295              : //=============================================================================
     296              : 
     297              : // Register output for automatic generation
     298           15 : void simulation_register_output(simulation_data* sim_data, output_field_type field_type,
     299              :                                 int interval, const char* prefix) {
     300           15 :     if (!sim_data || !sim_data->outputs) {
     301            2 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid simulation data");
     302            2 :         return;
     303              :     }
     304           13 :     output_registry_add(sim_data->outputs, field_type, interval, prefix);
     305              : }
     306              : 
     307              : // Clear all registered outputs
     308            8 : void simulation_clear_outputs(simulation_data* sim_data) {
     309            8 :     if (!sim_data || !sim_data->outputs) {
     310              :         return;
     311              :     }
     312            7 :     output_registry_clear(sim_data->outputs);
     313              : }
     314              : 
     315              : // Set base output directory
     316            2 : void simulation_set_output_dir(simulation_data* sim_data, const char* base_dir) {
     317            2 :     if (sim_data && base_dir && strlen(base_dir) > 0) {
     318            2 :         snprintf(sim_data->output_base_dir, sizeof(sim_data->output_base_dir), "%s", base_dir);
     319              :     }
     320            2 : }
     321              : 
     322              : // Set run name prefix
     323           10 : void simulation_set_run_prefix(simulation_data* sim_data, const char* prefix) {
     324           10 :     if (!sim_data) {
     325              :         return;
     326              :     }
     327              : 
     328              :     // Free existing prefix
     329            9 :     if (sim_data->run_prefix) {
     330            2 :         cfd_free(sim_data->run_prefix);
     331            2 :         sim_data->run_prefix = NULL;
     332              :     }
     333              : 
     334              :     // Set new prefix
     335            9 :     if (prefix) {
     336            8 :         size_t len = strlen(prefix) + 1;
     337            8 :         sim_data->run_prefix = (char*)cfd_malloc(len);
     338            8 :         if (sim_data->run_prefix) {
     339            8 :             snprintf(sim_data->run_prefix, len, "%s", prefix);
     340              :         }
     341              :     }
     342              : }
     343              : 
     344              : //=============================================================================
     345              : // AUTOMATIC OUTPUT GENERATION
     346              : //=============================================================================
     347              : 
     348              : // Check if any output type that uses velocity magnitude is registered
     349            9 : static int needs_velocity_magnitude(const output_registry* outputs) {
     350           17 :     return output_registry_has_type(outputs, OUTPUT_VELOCITY_MAGNITUDE) ||
     351           15 :            output_registry_has_type(outputs, OUTPUT_CSV_TIMESERIES) ||
     352           23 :            output_registry_has_type(outputs, OUTPUT_CSV_CENTERLINE) ||
     353            7 :            output_registry_has_type(outputs, OUTPUT_CSV_STATISTICS);
     354              : }
     355              : 
     356              : // Check if any output type that uses statistics is registered
     357            9 : static int needs_statistics(const output_registry* outputs) {
     358           17 :     return output_registry_has_type(outputs, OUTPUT_CSV_TIMESERIES) ||
     359            8 :            output_registry_has_type(outputs, OUTPUT_CSV_STATISTICS);
     360              : }
     361              : 
     362              : // Automatically write all registered outputs for current step
     363           11 : void simulation_write_outputs(simulation_data* sim_data, int step) {
     364           11 :     if (!sim_data || !sim_data->outputs) {
     365            2 :         cfd_set_error(CFD_ERROR_INVALID, "Invalid arguments for simulation_write_outputs");
     366            2 :         return;
     367              :     }
     368              : 
     369              :     // Get run directory (creates it if needed)
     370            9 :     const char* run_dir =
     371           18 :         output_registry_get_run_dir(sim_data->outputs, sim_data->output_base_dir,
     372            9 :                                     sim_data->run_prefix, sim_data->grid->nx, sim_data->grid->ny);
     373              : 
     374              :     // Compute derived fields only when needed
     375            9 :     derived_fields* derived = NULL;
     376            9 :     int compute_vel_mag = needs_velocity_magnitude(sim_data->outputs);
     377            9 :     int compute_stats = needs_statistics(sim_data->outputs);
     378              : 
     379            9 :     if (compute_vel_mag || compute_stats) {
     380           14 :         derived = derived_fields_create(sim_data->grid->nx, sim_data->grid->ny,
     381            7 :                                             sim_data->grid->nz);
     382            7 :         if (derived) {
     383              :             // Compute velocity magnitude if needed (CSV outputs)
     384            7 :             if (compute_vel_mag) {
     385            7 :                 derived_fields_compute_velocity_magnitude(derived, sim_data->field);
     386              :             }
     387              :             // Compute statistics if needed (CSV timeseries or statistics outputs)
     388            7 :             if (compute_stats) {
     389            6 :                 derived_fields_compute_statistics(derived, sim_data->field);
     390              :             }
     391              :         }
     392              :     }
     393              : 
     394              :     // Write all registered outputs with pre-computed derived fields
     395            9 :     output_registry_write_outputs(sim_data->outputs, run_dir, step, sim_data->current_time,
     396            9 :                                   sim_data->field, derived, sim_data->grid, &sim_data->params,
     397            9 :                                   &sim_data->last_stats);
     398              : 
     399              :     // Clean up derived fields
     400            9 :     if (derived) {
     401            7 :         derived_fields_destroy(derived);
     402              :     }
     403              : }
        

Generated by: LCOV version 2.0-1