LCOV - code coverage report
Current view: top level - solvers/navier_stokes/cpu - solver_explicit_euler.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.8 % 316 306
Test Date: 2026-03-04 10:22:18 Functions: 100.0 % 8 8

            Line data    Source code
       1              : #include "cfd/core/cfd_status.h"
       2              : #include "cfd/core/filesystem.h"
       3              : #include "cfd/core/grid.h"
       4              : #include "cfd/core/indexing.h"
       5              : #include "cfd/core/math_utils.h"
       6              : #include "cfd/core/memory.h"
       7              : 
       8              : 
       9              : #include "cfd/solvers/navier_stokes_solver.h"
      10              : 
      11              : #include "../boundary_copy_utils.h"
      12              : 
      13              : #include <math.h>
      14              : #include <stdio.h>
      15              : #include <string.h>
      16              : 
      17              : 
      18              : #ifndef M_PI
      19              : #define M_PI 3.14159265358979323846
      20              : #endif
      21              : 
      22              : // Physical stability limits for numerical computation
      23              : #define MAX_DERIVATIVE_LIMIT        100.0   // Maximum allowed first derivative magnitude (1/s)
      24              : #define MAX_SECOND_DERIVATIVE_LIMIT 1000.0  // Maximum allowed second derivative magnitude (1/s²)
      25              : #define MAX_VELOCITY_LIMIT          100.0   // Maximum allowed velocity magnitude (m/s)
      26              : #define MAX_DIVERGENCE_LIMIT        10.0    // Maximum allowed velocity divergence (1/s)
      27              : 
      28              : // Initial condition constants
      29              : #define INIT_U_BASE   1.0
      30              : #define INIT_U_VAR    0.1
      31              : #define INIT_V_VAR    0.05
      32              : #define INIT_PRESSURE 1.0
      33              : #define INIT_DENSITY  1.0
      34              : #define INIT_TEMP     300.0
      35              : 
      36              : // Perturbation constants
      37              : #define PERTURB_CENTER_X    1.0
      38              : #define PERTURB_CENTER_Y    0.5
      39              : #define PERTURB_RADIUS      0.2
      40              : #define PERTURB_WIDTH_SQ    0.02
      41              : #define PERTURB_MAG         0.1
      42              : #define PERTURB_GRAD_FACTOR 2.0
      43              : 
      44              : // Time stepping and stability constants
      45              : #define VELOCITY_EPSILON      1e-20
      46              : #define SPEED_EPSILON         1e-10
      47              : #define DT_MAX_LIMIT          0.01
      48              : #define DT_MIN_LIMIT          1e-6
      49              : #define DT_CONSERVATIVE_LIMIT 0.0001
      50              : 
      51              : // Update limits
      52              : #define UPDATE_LIMIT           1.0
      53              : #define PRESSURE_UPDATE_FACTOR 0.1
      54              : 
      55              : // Helper function to initialize ns_solver_params_t with default values
      56          216 : ns_solver_params_t ns_solver_params_default(void) {
      57          216 :     ns_solver_params_t params = {.dt = DEFAULT_TIME_STEP,
      58              :                             .cfl = DEFAULT_CFL_NUMBER,
      59              :                             .gamma = DEFAULT_GAMMA,
      60              :                             .mu = DEFAULT_VISCOSITY,
      61              :                             .k = DEFAULT_THERMAL_CONDUCTIVITY,
      62              :                             .max_iter = DEFAULT_MAX_ITERATIONS,
      63              :                             .tolerance = DEFAULT_TOLERANCE,
      64              :                             .source_amplitude_u = DEFAULT_SOURCE_AMPLITUDE_U,
      65              :                             .source_amplitude_v = DEFAULT_SOURCE_AMPLITUDE_V,
      66              :                             .source_decay_rate = DEFAULT_SOURCE_DECAY_RATE,
      67              :                             .pressure_coupling = DEFAULT_PRESSURE_COUPLING};
      68          216 :     return params;
      69              : }
      70          307 : flow_field* flow_field_create(size_t nx, size_t ny, size_t nz) {
      71          307 :     if (nx == 0 || ny == 0 || nz == 0) {
      72            3 :         cfd_set_error(CFD_ERROR_INVALID, "Flow field dimensions must be positive");
      73            3 :         return NULL;
      74              :     }
      75              : 
      76          304 :     flow_field* field = (flow_field*)cfd_calloc(1, sizeof(flow_field));
      77          304 :     if (field == NULL) {
      78              :         return NULL;
      79              :     }
      80              : 
      81          304 :     field->nx = nx;
      82          304 :     field->ny = ny;
      83          304 :     field->nz = nz;
      84              : 
      85          304 :     size_t total = nx * ny * nz;
      86              : 
      87              :     // Allocate 32-byte aligned memory for flow variables (optimized for SIMD operations)
      88          304 :     field->u = (double*)cfd_aligned_calloc(total, sizeof(double));
      89          304 :     field->v = (double*)cfd_aligned_calloc(total, sizeof(double));
      90          304 :     field->w = (double*)cfd_aligned_calloc(total, sizeof(double));
      91          304 :     field->p = (double*)cfd_aligned_calloc(total, sizeof(double));
      92          304 :     field->rho = (double*)cfd_aligned_calloc(total, sizeof(double));
      93          304 :     field->T = (double*)cfd_aligned_calloc(total, sizeof(double));
      94              : 
      95          304 :     if (!field->u || !field->v || !field->w || !field->p || !field->rho || !field->T) {
      96            0 :         flow_field_destroy(field);
      97            0 :         return NULL;
      98              :     }
      99              : 
     100              :     return field;
     101              : }
     102              : 
     103          305 : void flow_field_destroy(flow_field* field) {
     104          305 :     if (field != NULL) {
     105          304 :         cfd_aligned_free(field->u);
     106          304 :         cfd_aligned_free(field->v);
     107          304 :         cfd_aligned_free(field->w);
     108          304 :         cfd_aligned_free(field->p);
     109          304 :         cfd_aligned_free(field->rho);
     110          304 :         cfd_aligned_free(field->T);
     111          304 :         cfd_free(field);
     112              :     }
     113          305 : }
     114              : 
     115          106 : void initialize_flow_field(flow_field* field, const grid* grid) {
     116          106 :     size_t nx = field->nx;
     117          106 :     size_t ny = field->ny;
     118          106 :     size_t nz = field->nz;
     119          106 :     size_t plane = nx * ny;
     120              : 
     121          212 :     for (size_t k = 0; k < nz; k++) {
     122          106 :         size_t base = k * plane;
     123         1097 :         for (size_t j = 0; j < ny; j++) {
     124        10956 :             for (size_t i = 0; i < nx; i++) {
     125         9965 :                 size_t idx = base + IDX_2D(i, j, nx);
     126         9965 :                 double x = grid->x[i];
     127         9965 :                 double y = grid->y[j];
     128              : 
     129         9965 :                 field->u[idx] =
     130         9965 :                     INIT_U_BASE + (INIT_U_VAR * sin(M_PI * y));
     131         9965 :                 field->v[idx] = INIT_V_VAR * sin(2.0 * M_PI * x);
     132         9965 :                 field->w[idx] = 0.0;
     133         9965 :                 field->p[idx] = INIT_PRESSURE;
     134         9965 :                 field->rho[idx] = INIT_DENSITY;
     135         9965 :                 field->T[idx] = INIT_TEMP;
     136              : 
     137         9965 :                 double cx = PERTURB_CENTER_X, cy = PERTURB_CENTER_Y;
     138         9965 :                 double r = sqrt(((x - cx) * (x - cx)) + ((y - cy) * (y - cy)));
     139         9965 :                 if (r < PERTURB_RADIUS) {
     140          579 :                     field->p[idx] += PERTURB_MAG * exp(-r * r / PERTURB_WIDTH_SQ);
     141          579 :                     double dp_dx = -PERTURB_MAG * PERTURB_GRAD_FACTOR * (x - cx) / PERTURB_WIDTH_SQ *
     142          579 :                                    exp(-r * r / PERTURB_WIDTH_SQ);
     143          579 :                     double dp_dy = -PERTURB_MAG * PERTURB_GRAD_FACTOR * (y - cy) / PERTURB_WIDTH_SQ *
     144          579 :                                    exp(-r * r / PERTURB_WIDTH_SQ);
     145          579 :                     field->u[idx] += -PERTURB_MAG * dp_dx;
     146          579 :                     field->v[idx] += -PERTURB_MAG * dp_dy;
     147              :                 }
     148              :             }
     149              :         }
     150              :     }
     151          106 : }
     152              : 
     153           27 : void compute_time_step(flow_field* field, const grid* grid, ns_solver_params_t* params) {
     154           27 :     double max_speed = 0.0;
     155           27 :     double dx_min = grid->dx[0];
     156           27 :     double dy_min = grid->dy[0];
     157              : 
     158              :     // Find minimum grid spacing
     159          810 :     for (size_t i = 0; i < grid->nx - 1; i++) {
     160          783 :         dx_min = min_double(dx_min, grid->dx[i]);
     161              :     }
     162          810 :     for (size_t j = 0; j < grid->ny - 1; j++) {
     163          783 :         dy_min = min_double(dy_min, grid->dy[j]);
     164              :     }
     165              : 
     166              :     // Find maximum wave speed
     167           27 :     int has_w = (grid->nz > 1 && field->w);
     168          837 :     for (size_t j = 0; j < field->ny; j++) {
     169        35286 :         for (size_t i = 0; i < field->nx; i++) {
     170        34476 :             size_t idx = IDX_2D(i, j, field->nx);
     171        34476 :             double u_speed = fabs(field->u[idx]);
     172        34476 :             double v_speed = fabs(field->v[idx]);
     173        34476 :             double sound_speed = sqrt(params->gamma * field->p[idx] / field->rho[idx]);
     174              : 
     175              :             // Optimized velocity magnitude calculation - avoid sqrt when possible
     176        34476 :             double vel_mag_sq = (u_speed * u_speed) + (v_speed * v_speed);
     177        34476 :             if (has_w) {
     178          250 :                 double w_speed = fabs(field->w[idx]);
     179          250 :                 vel_mag_sq += w_speed * w_speed;
     180              :             }
     181        34476 :             double vel_mag = (vel_mag_sq > VELOCITY_EPSILON) ? sqrt(vel_mag_sq) : 0.0;
     182        34476 :             double local_speed = vel_mag + sound_speed;
     183        34476 :             max_speed = max_double(max_speed, local_speed);
     184              :         }
     185              :     }
     186              : 
     187              :     // Prevent division by zero and ensure reasonable time step
     188           27 :     if (max_speed < SPEED_EPSILON) {
     189            1 :         max_speed = 1.0;  // Use default if speeds are too small
     190              :     }
     191              : 
     192              :     // Include z-direction in CFL if 3D
     193           27 :     double dmin = min_double(dx_min, dy_min);
     194           27 :     if (grid->nz > 1 && grid->dz) {
     195            4 :         double dz_min = grid->dz[0];
     196           60 :         for (size_t k = 0; k < grid->nz - 1; k++) {
     197           56 :             dz_min = min_double(dz_min, grid->dz[k]);
     198              :         }
     199            4 :         dmin = min_double(dmin, dz_min);
     200              :     }
     201              : 
     202              :     // Compute time step based on CFL condition with safety factor
     203           27 :     double dt_cfl = params->cfl * dmin / max_speed;
     204              : 
     205              :     // Limit time step to reasonable bounds
     206           27 :     double dt_max = DT_MAX_LIMIT;  // Maximum allowed time step
     207           27 :     double dt_min = DT_MIN_LIMIT;  // Minimum allowed time step
     208              : 
     209           27 :     params->dt = max_double(dt_min, min_double(dt_max, dt_cfl));
     210           27 : }
     211              : 
     212        11027 : void apply_boundary_conditions(flow_field* field, const grid* grid) {
     213        11027 :     (void)grid;
     214        11027 :     size_t nx = field->nx;
     215        11027 :     size_t ny = field->ny;
     216        11027 :     size_t nz = field->nz;
     217        11027 :     size_t plane = nx * ny;
     218              : 
     219              :     /* Apply periodic BCs in x and y for each z-plane */
     220        22838 :     for (size_t k = 0; k < nz; k++) {
     221        11811 :         size_t base = k * plane;
     222              : 
     223              :         /* x-direction periodic */
     224       904046 :         for (size_t j = 0; j < ny; j++) {
     225       892235 :             size_t left = base + IDX_2D(0, j, nx);
     226       892235 :             size_t right = base + IDX_2D(nx - 1, j, nx);
     227       892235 :             size_t src_left = base + IDX_2D(nx - 2, j, nx);
     228       892235 :             size_t src_right = base + IDX_2D(1, j, nx);
     229              : 
     230       892235 :             field->u[left] = field->u[src_left];
     231       892235 :             field->v[left] = field->v[src_left];
     232       892235 :             field->w[left] = field->w[src_left];
     233       892235 :             field->p[left] = field->p[src_left];
     234       892235 :             field->rho[left] = field->rho[src_left];
     235       892235 :             field->T[left] = field->T[src_left];
     236              : 
     237       892235 :             field->u[right] = field->u[src_right];
     238       892235 :             field->v[right] = field->v[src_right];
     239       892235 :             field->w[right] = field->w[src_right];
     240       892235 :             field->p[right] = field->p[src_right];
     241       892235 :             field->rho[right] = field->rho[src_right];
     242       892235 :             field->T[right] = field->T[src_right];
     243              :         }
     244              : 
     245              :         /* y-direction periodic */
     246       907260 :         for (size_t i = 0; i < nx; i++) {
     247       895449 :             size_t bot = base + i;
     248       895449 :             size_t top = base + IDX_2D(i, ny - 1, nx);
     249       895449 :             size_t src_bot = base + IDX_2D(i, ny - 2, nx);
     250       895449 :             size_t src_top = base + IDX_2D(i, 1, nx);
     251              : 
     252       895449 :             field->u[bot] = field->u[src_bot];
     253       895449 :             field->v[bot] = field->v[src_bot];
     254       895449 :             field->w[bot] = field->w[src_bot];
     255       895449 :             field->p[bot] = field->p[src_bot];
     256       895449 :             field->rho[bot] = field->rho[src_bot];
     257       895449 :             field->T[bot] = field->T[src_bot];
     258              : 
     259       895449 :             field->u[top] = field->u[src_top];
     260       895449 :             field->v[top] = field->v[src_top];
     261       895449 :             field->w[top] = field->w[src_top];
     262       895449 :             field->p[top] = field->p[src_top];
     263       895449 :             field->rho[top] = field->rho[src_top];
     264       895449 :             field->T[top] = field->T[src_top];
     265              :         }
     266              :     }
     267              : 
     268              :     /* z-direction periodic (only when nz > 1) */
     269        11027 :     if (nz > 1) {
     270          112 :         size_t front_base = 0;
     271          112 :         size_t back_base = (nz - 1) * plane;
     272          112 :         size_t src_front = (nz - 2) * plane;
     273          112 :         size_t src_back = 1 * plane;
     274              : 
     275         1008 :         for (size_t j = 0; j < ny; j++) {
     276         8064 :             for (size_t i = 0; i < nx; i++) {
     277         7168 :                 size_t off = IDX_2D(i, j, nx);
     278              : 
     279         7168 :                 field->u[front_base + off] = field->u[src_front + off];
     280         7168 :                 field->v[front_base + off] = field->v[src_front + off];
     281         7168 :                 field->w[front_base + off] = field->w[src_front + off];
     282         7168 :                 field->p[front_base + off] = field->p[src_front + off];
     283         7168 :                 field->rho[front_base + off] = field->rho[src_front + off];
     284         7168 :                 field->T[front_base + off] = field->T[src_front + off];
     285              : 
     286         7168 :                 field->u[back_base + off] = field->u[src_back + off];
     287         7168 :                 field->v[back_base + off] = field->v[src_back + off];
     288         7168 :                 field->w[back_base + off] = field->w[src_back + off];
     289         7168 :                 field->p[back_base + off] = field->p[src_back + off];
     290         7168 :                 field->rho[back_base + off] = field->rho[src_back + off];
     291         7168 :                 field->T[back_base + off] = field->T[src_back + off];
     292              :             }
     293              :         }
     294              :     }
     295        11027 : }
     296              : 
     297              : // Helper function to compute source terms consistently across all solvers
     298    136242249 : void compute_source_terms(double x, double y, double z, int iter, double dt,
     299              :                           const ns_solver_params_t* params,
     300              :                           double* source_u, double* source_v, double* source_w) {
     301              :     // Use custom source function if provided
     302    136242249 :     if (params->source_func) {
     303    126418056 :         double t = iter * dt;
     304    126418056 :         params->source_func(x, y, z, t, params->source_context, source_u, source_v, source_w);
     305    126418056 :         return;
     306              :     }
     307              : 
     308              :     // Default source term implementation (no default w-source)
     309      9824193 :     *source_u =
     310      9824193 :         params->source_amplitude_u * sin(M_PI * y) * exp(-params->source_decay_rate * iter * dt);
     311      9824193 :     *source_v = params->source_amplitude_v * sin(2.0 * M_PI * x) *
     312      9824193 :                 exp(-params->source_decay_rate * iter * dt);
     313      9824193 :     *source_w = 0.0;
     314              : }
     315              : 
     316              : // Internal explicit Euler implementation
     317              : // This is called by the solver registry - not part of public API
     318         6256 : cfd_status_t explicit_euler_impl(flow_field* field, const grid* grid, const ns_solver_params_t* params) {
     319         6256 :     if (field->nx < 3 || field->ny < 3 || (field->nz > 1 && field->nz < 3)) {
     320              :         return CFD_ERROR_INVALID;
     321              :     }
     322              : 
     323         6256 :     size_t nx = field->nx;
     324         6256 :     size_t ny = field->ny;
     325         6256 :     size_t nz = field->nz;
     326              : 
     327              :     /* Reject non-uniform z-spacing (solver uses constant inv_2dz/inv_dz2) */
     328         6256 :     if (nz > 1 && grid->dz) {
     329          371 :         for (size_t k = 1; k < nz - 1; k++) {
     330          318 :             if (fabs(grid->dz[k] - grid->dz[0]) > 1e-14) {
     331              :                 return CFD_ERROR_INVALID;
     332              :             }
     333              :         }
     334              :     }
     335              : 
     336         6256 :     size_t plane = nx * ny;
     337         6256 :     size_t total = plane * nz;
     338         6256 :     size_t bytes = total * sizeof(double);
     339              : 
     340              :     /* Branch-free 3D constants: when nz==1, stride_z=0, inv_2dz=0, inv_dz2=0
     341              :      * so all z-terms vanish, producing identical results to 2D. */
     342         6256 :     size_t stride_z = (nz > 1) ? plane : 0;
     343         6256 :     size_t k_start = (nz > 1) ? 1 : 0;
     344         6256 :     size_t k_end   = (nz > 1) ? (nz - 1) : 1;
     345         6256 :     double inv_2dz = (nz > 1 && grid->dz) ? 1.0 / (2.0 * grid->dz[0]) : 0.0;
     346           53 :     double inv_dz2 = (nz > 1 && grid->dz) ? 1.0 / (grid->dz[0] * grid->dz[0]) : 0.0;
     347              : 
     348         6256 :     double* u_new = (double*)cfd_calloc(total, sizeof(double));
     349         6256 :     double* v_new = (double*)cfd_calloc(total, sizeof(double));
     350         6256 :     double* w_new = (double*)cfd_calloc(total, sizeof(double));
     351         6256 :     double* p_new = (double*)cfd_calloc(total, sizeof(double));
     352         6256 :     double* rho_new = (double*)cfd_calloc(total, sizeof(double));
     353         6256 :     double* t_new = (double*)cfd_calloc(total, sizeof(double));
     354              : 
     355         6256 :     if (!u_new || !v_new || !w_new || !p_new || !rho_new || !t_new) {
     356            0 :         cfd_free(u_new); cfd_free(v_new); cfd_free(w_new);
     357            0 :         cfd_free(p_new); cfd_free(rho_new); cfd_free(t_new);
     358            0 :         return CFD_ERROR_NOMEM;
     359              :     }
     360              : 
     361         6256 :     memcpy(u_new, field->u, bytes);
     362         6256 :     memcpy(v_new, field->v, bytes);
     363         6256 :     memcpy(w_new, field->w, bytes);
     364         6256 :     memcpy(p_new, field->p, bytes);
     365         6256 :     memcpy(rho_new, field->rho, bytes);
     366         6256 :     memcpy(t_new, field->T, bytes);
     367              : 
     368         6256 :     double conservative_dt = fmin(params->dt, DT_CONSERVATIVE_LIMIT);
     369              : 
     370        12512 :     for (int iter = 0; iter < params->max_iter; iter++) {
     371        12777 :         for (size_t k = k_start; k < k_end; k++) {
     372       449164 :             for (size_t j = 1; j < ny - 1; j++) {
     373     45028798 :                 for (size_t i = 1; i < nx - 1; i++) {
     374     44586155 :                     size_t idx = k * stride_z + IDX_2D(i, j, nx);
     375              : 
     376     44586155 :                     if (field->rho[idx] <= 1e-10) {
     377         2548 :                         continue;
     378              :                     }
     379     44583607 :                     if (fabs(grid->dx[i]) < 1e-10 || fabs(grid->dy[j]) < 1e-10) {
     380            0 :                         continue;
     381              :                     }
     382              : 
     383     44583607 :                     double u_c = field->u[idx];
     384     44583607 :                     double v_c = field->v[idx];
     385     44583607 :                     double w_c = field->w[idx];
     386              : 
     387              :                     /* First derivatives (central differences) */
     388     44583607 :                     double du_dx = (field->u[idx + 1] - field->u[idx - 1]) / (2.0 * grid->dx[i]);
     389     44583607 :                     double du_dy = (field->u[idx + nx] - field->u[idx - nx]) / (2.0 * grid->dy[j]);
     390     44583607 :                     double du_dz = (field->u[idx + stride_z] - field->u[idx - stride_z]) * inv_2dz;
     391              : 
     392     44583607 :                     double dv_dx = (field->v[idx + 1] - field->v[idx - 1]) / (2.0 * grid->dx[i]);
     393     44583607 :                     double dv_dy = (field->v[idx + nx] - field->v[idx - nx]) / (2.0 * grid->dy[j]);
     394     44583607 :                     double dv_dz = (field->v[idx + stride_z] - field->v[idx - stride_z]) * inv_2dz;
     395              : 
     396     44583607 :                     double dw_dx = (field->w[idx + 1] - field->w[idx - 1]) / (2.0 * grid->dx[i]);
     397     44583607 :                     double dw_dy = (field->w[idx + nx] - field->w[idx - nx]) / (2.0 * grid->dy[j]);
     398     44583607 :                     double dw_dz = (field->w[idx + stride_z] - field->w[idx - stride_z]) * inv_2dz;
     399              : 
     400              :                     /* Pressure gradients */
     401     44583607 :                     double dp_dx = (field->p[idx + 1] - field->p[idx - 1]) / (2.0 * grid->dx[i]);
     402     44583607 :                     double dp_dy = (field->p[idx + nx] - field->p[idx - nx]) / (2.0 * grid->dy[j]);
     403     44583607 :                     double dp_dz = (field->p[idx + stride_z] - field->p[idx - stride_z]) * inv_2dz;
     404              : 
     405              :                     /* Second derivatives (viscous terms) */
     406     44583607 :                     double d2u_dx2 = (field->u[idx + 1] - 2.0 * u_c + field->u[idx - 1]) /
     407     44583607 :                                      (grid->dx[i] * grid->dx[i]);
     408     44583607 :                     double d2u_dy2 = (field->u[idx + nx] - 2.0 * u_c + field->u[idx - nx]) /
     409     44583607 :                                      (grid->dy[j] * grid->dy[j]);
     410     44583607 :                     double d2u_dz2 = (field->u[idx + stride_z] - 2.0 * u_c +
     411              :                                       field->u[idx - stride_z]) * inv_dz2;
     412              : 
     413     44583607 :                     double d2v_dx2 = (field->v[idx + 1] - 2.0 * v_c + field->v[idx - 1]) /
     414              :                                      (grid->dx[i] * grid->dx[i]);
     415     44583607 :                     double d2v_dy2 = (field->v[idx + nx] - 2.0 * v_c + field->v[idx - nx]) /
     416              :                                      (grid->dy[j] * grid->dy[j]);
     417     44583607 :                     double d2v_dz2 = (field->v[idx + stride_z] - 2.0 * v_c +
     418              :                                       field->v[idx - stride_z]) * inv_dz2;
     419              : 
     420     44583607 :                     double d2w_dx2 = (field->w[idx + 1] - 2.0 * w_c + field->w[idx - 1]) /
     421              :                                      (grid->dx[i] * grid->dx[i]);
     422     44583607 :                     double d2w_dy2 = (field->w[idx + nx] - 2.0 * w_c + field->w[idx - nx]) /
     423              :                                      (grid->dy[j] * grid->dy[j]);
     424     44583607 :                     double d2w_dz2 = (field->w[idx + stride_z] - 2.0 * w_c +
     425              :                                       field->w[idx - stride_z]) * inv_dz2;
     426              : 
     427     44583607 :                     double nu = params->mu / fmax(field->rho[idx], 1e-10);
     428     44583607 :                     nu = fmin(nu, 1.0);
     429              : 
     430              :                     /* Clamp derivatives */
     431     44583607 :                     du_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, du_dx));
     432     44583607 :                     du_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, du_dy));
     433     44583607 :                     du_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, du_dz));
     434     44583607 :                     dv_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dv_dx));
     435     44583607 :                     dv_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dv_dy));
     436     44583607 :                     dv_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dv_dz));
     437     44583607 :                     dw_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dw_dx));
     438     44583607 :                     dw_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dw_dy));
     439     44583607 :                     dw_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dw_dz));
     440     44583607 :                     dp_dx = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dp_dx));
     441     44583607 :                     dp_dy = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dp_dy));
     442     44583607 :                     dp_dz = fmax(-MAX_DERIVATIVE_LIMIT, fmin(MAX_DERIVATIVE_LIMIT, dp_dz));
     443     44583607 :                     d2u_dx2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2u_dx2));
     444     44583607 :                     d2u_dy2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2u_dy2));
     445     44583607 :                     d2u_dz2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2u_dz2));
     446     44583607 :                     d2v_dx2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2v_dx2));
     447     44583607 :                     d2v_dy2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2v_dy2));
     448     44583607 :                     d2v_dz2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2v_dz2));
     449     44583607 :                     d2w_dx2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2w_dx2));
     450     44583607 :                     d2w_dy2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2w_dy2));
     451     44583607 :                     d2w_dz2 = fmax(-MAX_SECOND_DERIVATIVE_LIMIT, fmin(MAX_SECOND_DERIVATIVE_LIMIT, d2w_dz2));
     452              : 
     453              :                     /* Source terms */
     454     44583607 :                     double x = grid->x[i];
     455     44583607 :                     double y = grid->y[j];
     456     44583607 :                     double z = (nz > 1 && grid->z) ? grid->z[k] : 0.0;
     457     44583607 :                     double source_u, source_v, source_w;
     458     44583607 :                     compute_source_terms(x, y, z, iter, conservative_dt, params,
     459              :                                          &source_u, &source_v, &source_w);
     460              : 
     461              :                     /* u-momentum */
     462     44583607 :                     double du = conservative_dt *
     463     44583607 :                         (-u_c * du_dx - v_c * du_dy - w_c * du_dz
     464     44583607 :                          - dp_dx / field->rho[idx]
     465     44583607 :                          + nu * (d2u_dx2 + d2u_dy2 + d2u_dz2)
     466     44583607 :                          + source_u);
     467              : 
     468              :                     /* v-momentum */
     469     44583607 :                     double dv = conservative_dt *
     470     44583607 :                         (-u_c * dv_dx - v_c * dv_dy - w_c * dv_dz
     471     44583607 :                          - dp_dy / field->rho[idx]
     472     44583607 :                          + nu * (d2v_dx2 + d2v_dy2 + d2v_dz2)
     473     44583607 :                          + source_v);
     474              : 
     475              :                     /* w-momentum */
     476     44583607 :                     double dw = conservative_dt *
     477     44583607 :                         (-u_c * dw_dx - v_c * dw_dy - w_c * dw_dz
     478     44583607 :                          - dp_dz / field->rho[idx]
     479     44583607 :                          + nu * (d2w_dx2 + d2w_dy2 + d2w_dz2)
     480     44583607 :                          + source_w);
     481              : 
     482     44583607 :                     du = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, du));
     483     44583607 :                     dv = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, dv));
     484     44583607 :                     dw = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, dw));
     485              : 
     486     44583607 :                     u_new[idx] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, u_c + du));
     487     44583607 :                     v_new[idx] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, v_c + dv));
     488     44583607 :                     w_new[idx] = fmax(-MAX_VELOCITY_LIMIT, fmin(MAX_VELOCITY_LIMIT, w_c + dw));
     489              : 
     490              :                     /* Pressure update from divergence */
     491     44583607 :                     double divergence = du_dx + dv_dy + dw_dz;
     492     44583607 :                     divergence = fmax(-MAX_DIVERGENCE_LIMIT, fmin(MAX_DIVERGENCE_LIMIT, divergence));
     493     44583607 :                     double dp = -PRESSURE_UPDATE_FACTOR * conservative_dt * field->rho[idx] * divergence;
     494     44583607 :                     dp = fmax(-UPDATE_LIMIT, fmin(UPDATE_LIMIT, dp));
     495     44583607 :                     p_new[idx] = field->p[idx] + dp;
     496              : 
     497     44583607 :                     rho_new[idx] = field->rho[idx];
     498     44583607 :                     t_new[idx] = field->T[idx];
     499              :                 }
     500              :             }
     501              :         }
     502              : 
     503              :         /* Copy new solution */
     504         6256 :         memcpy(field->u, u_new, bytes);
     505         6256 :         memcpy(field->v, v_new, bytes);
     506         6256 :         memcpy(field->w, w_new, bytes);
     507         6256 :         memcpy(field->p, p_new, bytes);
     508         6256 :         memcpy(field->rho, rho_new, bytes);
     509         6256 :         memcpy(field->T, t_new, bytes);
     510              : 
     511              :         /* Save caller BCs, apply periodic BCs, restore caller BCs.
     512              :          * Use _3d helper for 6-face copy when nz > 1. */
     513         6256 :         copy_boundary_velocities_3d(u_new, v_new, w_new,
     514         6256 :                                     field->u, field->v, field->w, nx, ny, nz);
     515         6256 :         apply_boundary_conditions(field, grid);
     516         6256 :         copy_boundary_velocities_3d(field->u, field->v, field->w,
     517              :                                     u_new, v_new, w_new, nx, ny, nz);
     518              : 
     519              :         /* NaN/Inf check */
     520         6256 :         int has_nan = 0;
     521     46399111 :         for (size_t n = 0; n < total; n++) {
     522     46392855 :             if (!isfinite(field->u[n]) || !isfinite(field->v[n]) ||
     523     46392855 :                 !isfinite(field->w[n]) || !isfinite(field->p[n])) {
     524              :                 has_nan = 1;
     525              :                 break;
     526              :             }
     527              :         }
     528         6256 :         if (has_nan) {
     529            0 :             cfd_free(u_new); cfd_free(v_new); cfd_free(w_new);
     530            0 :             cfd_free(p_new); cfd_free(rho_new); cfd_free(t_new);
     531            0 :             cfd_set_error(CFD_ERROR_DIVERGED,
     532              :                           "NaN/Inf detected in explicit_euler step");
     533            0 :             return CFD_ERROR_DIVERGED;
     534              :         }
     535              :     }
     536              : 
     537         6256 :     cfd_free(u_new); cfd_free(v_new); cfd_free(w_new);
     538         6256 :     cfd_free(p_new); cfd_free(rho_new); cfd_free(t_new);
     539              : 
     540         6256 :     return CFD_SUCCESS;
     541              : }
        

Generated by: LCOV version 2.0-1