Line data Source code
1 : #include "cfd/io/csv_output.h"
2 : #include "cfd/core/derived_fields.h"
3 : #include "cfd/core/grid.h"
4 : #include "cfd/core/indexing.h"
5 : #include "cfd/core/logging.h"
6 : #include "cfd/solvers/navier_stokes_solver.h"
7 : #include "csv_output_internal.h"
8 :
9 :
10 : #include <math.h>
11 : #include <stdio.h>
12 : #include <string.h>
13 : #include <sys/stat.h>
14 :
15 : //=============================================================================
16 : // UTILITY FUNCTIONS
17 : //=============================================================================
18 :
19 : // Helper to build filepath
20 2 : static void build_filepath(char* filepath, size_t filepath_size, const char* run_dir,
21 : const char* filename) {
22 : #ifdef _WIN32
23 : snprintf(filepath, filepath_size, "%s\\%s", run_dir, filename);
24 : #else
25 2 : snprintf(filepath, filepath_size, "%s/%s", run_dir, filename);
26 : #endif
27 2 : }
28 :
29 : // Check if file exists
30 5 : static int file_exists(const char* filename) {
31 5 : struct stat buffer;
32 5 : return (stat(filename, &buffer) == 0);
33 : }
34 :
35 : //=============================================================================
36 : // CSV OUTPUT DISPATCH
37 : //=============================================================================
38 :
39 : // Function pointer type for CSV output handlers
40 : typedef void (*csv_output_handler)(const char* run_dir, const char* prefix, int step,
41 : double current_time, const flow_field* field,
42 : const derived_fields* derived, const grid* grid,
43 : const ns_solver_params_t* params, const ns_solver_stats_t* stats);
44 :
45 : // CSV output handler functions
46 1 : static void write_timeseries_csv(const char* run_dir, const char* prefix, int step,
47 : double current_time, const flow_field* field,
48 : const derived_fields* derived, const grid* grid,
49 : const ns_solver_params_t* params, const ns_solver_stats_t* stats) {
50 1 : const char* name = prefix ? prefix : "timeseries";
51 1 : char filename[256], filepath[512];
52 :
53 1 : snprintf(filename, sizeof(filename), "%s.csv", name);
54 : build_filepath(filepath, sizeof(filepath), run_dir, filename);
55 :
56 1 : int create_new = (step == 0);
57 1 : write_csv_timeseries(filepath, step, current_time, field, derived, params, stats, grid->nx,
58 1 : grid->ny, create_new);
59 1 : }
60 :
61 0 : static void write_centerline_csv(const char* run_dir, const char* prefix, int step,
62 : double current_time, const flow_field* field,
63 : const derived_fields* derived, const grid* grid,
64 : const ns_solver_params_t* params, const ns_solver_stats_t* stats) {
65 0 : (void)current_time;
66 0 : (void)params;
67 0 : (void)stats; // Unused for centerline
68 0 : const char* name = prefix ? prefix : "centerline";
69 0 : char filename[256], filepath[512];
70 :
71 0 : snprintf(filename, sizeof(filename), "%s_%03d.csv", name, step);
72 0 : build_filepath(filepath, sizeof(filepath), run_dir, filename);
73 :
74 0 : write_csv_centerline(filepath, field, derived, grid->x, grid->y, grid->nx, grid->ny,
75 : PROFILE_HORIZONTAL);
76 0 : }
77 :
78 1 : static void write_statistics_csv(const char* run_dir, const char* prefix, int step,
79 : double current_time, const flow_field* field,
80 : const derived_fields* derived, const grid* grid,
81 : const ns_solver_params_t* params, const ns_solver_stats_t* stats) {
82 1 : (void)params;
83 1 : (void)stats; // Unused for statistics
84 1 : const char* name = prefix ? prefix : "statistics";
85 1 : char filename[256], filepath[512];
86 :
87 1 : snprintf(filename, sizeof(filename), "%s.csv", name);
88 1 : build_filepath(filepath, sizeof(filepath), run_dir, filename);
89 :
90 1 : int create_new = (step == 0);
91 1 : write_csv_statistics(filepath, step, current_time, field, derived, grid->nx, grid->ny,
92 : create_new);
93 1 : }
94 :
95 : // CSV output handler table - indexed by csv_output_type
96 : static const csv_output_handler csv_output_table[] = {
97 : write_timeseries_csv, // CSV_OUTPUT_TIMESERIES = 0
98 : write_centerline_csv, // CSV_OUTPUT_CENTERLINE = 1
99 : write_statistics_csv // CSV_OUTPUT_STATISTICS = 2
100 : };
101 :
102 : #define CSV_OUTPUT_TABLE_SIZE (sizeof(csv_output_table) / sizeof(csv_output_table[0]))
103 :
104 2 : void csv_dispatch_output(csv_output_type csv_type, const char* run_dir, const char* prefix,
105 : int step, double current_time, const flow_field* field,
106 : const derived_fields* derived, const grid* grid,
107 : const ns_solver_params_t* params, const ns_solver_stats_t* stats) {
108 : // Bounds check and dispatch via function table
109 2 : if (csv_type < CSV_OUTPUT_TABLE_SIZE) {
110 2 : csv_output_table[csv_type](run_dir, prefix, step, current_time, field, derived, grid,
111 : params, stats);
112 : } else {
113 0 : cfd_warning("Unknown CSV output type");
114 : }
115 2 : }
116 :
117 : //=============================================================================
118 : // CSV TIMESERIES OUTPUT
119 : //=============================================================================
120 :
121 12 : void write_csv_timeseries(const char* filename, int step, double time, const flow_field* field,
122 : const derived_fields* derived, const ns_solver_params_t* params,
123 : const ns_solver_stats_t* stats, size_t nx, size_t ny, int create_new) {
124 12 : (void)field; // Statistics come from derived
125 12 : (void)nx;
126 12 : (void)ny;
127 :
128 12 : if (!filename || !derived || !derived->stats_computed || !params || !stats) {
129 : return;
130 : }
131 :
132 9 : int write_header = create_new || !file_exists(filename);
133 7 : const char* mode = write_header ? "w" : "a";
134 7 : int has_vel_mag = derived->velocity_magnitude != NULL;
135 :
136 7 : FILE* fp = fopen(filename, mode);
137 7 : if (!fp) {
138 1 : cfd_warning("Failed to open CSV timeseries file for writing");
139 1 : return;
140 : }
141 :
142 : // Write header if new file
143 6 : if (write_header) {
144 4 : fprintf(fp, "step,time,dt,max_u,max_v,max_w,max_p,avg_u,avg_v,avg_w,avg_p");
145 4 : if (has_vel_mag) {
146 4 : fprintf(fp, ",max_vel_mag,avg_vel_mag");
147 : }
148 4 : fprintf(fp, ",iterations,residual,elapsed_ms\n");
149 : }
150 :
151 : // Write data row using pre-computed statistics
152 12 : fprintf(fp, "%d,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e", step, time, params->dt,
153 6 : derived->u_stats.max_val, derived->v_stats.max_val, derived->w_stats.max_val,
154 6 : derived->p_stats.max_val, derived->u_stats.avg_val, derived->v_stats.avg_val,
155 6 : derived->w_stats.avg_val, derived->p_stats.avg_val);
156 :
157 6 : if (has_vel_mag) {
158 6 : fprintf(fp, ",%.6e,%.6e", derived->vel_mag_stats.max_val, derived->vel_mag_stats.avg_val);
159 : }
160 :
161 6 : fprintf(fp, ",%d,%.6e,%.2f\n", stats->iterations, stats->residual, stats->elapsed_time_ms);
162 :
163 6 : fclose(fp);
164 : }
165 :
166 : //=============================================================================
167 : // CSV CENTERLINE PROFILE OUTPUT
168 : //=============================================================================
169 :
170 6 : void write_csv_centerline(const char* filename, const flow_field* field,
171 : const derived_fields* derived, const double* x_coords,
172 : const double* y_coords, size_t nx, size_t ny,
173 : profile_direction direction) {
174 6 : if (!filename || !field || !x_coords || !y_coords) {
175 : return;
176 : }
177 :
178 2 : int has_vel_mag = derived && derived->velocity_magnitude;
179 :
180 2 : FILE* fp = fopen(filename, "w");
181 2 : if (!fp) {
182 0 : cfd_warning("Failed to open CSV centerline file for writing");
183 0 : return;
184 : }
185 :
186 2 : if (direction == PROFILE_HORIZONTAL) {
187 : // Horizontal centerline: along x at y = ny/2
188 1 : size_t j_mid = ny / 2;
189 :
190 1 : fprintf(fp, "x,u,v,w,p,rho,T");
191 1 : if (has_vel_mag) {
192 1 : fprintf(fp, ",vel_mag");
193 : }
194 1 : fprintf(fp, "\n");
195 :
196 11 : for (size_t i = 0; i < nx; i++) {
197 10 : size_t idx = IDX_2D(i, j_mid, nx);
198 10 : double w_val = field->w ? field->w[idx] : 0.0;
199 20 : fprintf(fp, "%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e", x_coords[i], field->u[idx],
200 10 : field->v[idx], w_val, field->p[idx], field->rho[idx], field->T[idx]);
201 10 : if (has_vel_mag) {
202 10 : fprintf(fp, ",%.6e", derived->velocity_magnitude[idx]);
203 : }
204 10 : fprintf(fp, "\n");
205 : }
206 : } else {
207 : // Vertical centerline: along y at x = nx/2
208 1 : size_t i_mid = nx / 2;
209 :
210 1 : fprintf(fp, "y,u,v,w,p,rho,T");
211 1 : if (has_vel_mag) {
212 1 : fprintf(fp, ",vel_mag");
213 : }
214 1 : fprintf(fp, "\n");
215 :
216 11 : for (size_t j = 0; j < ny; j++) {
217 10 : size_t idx = IDX_2D(i_mid, j, nx);
218 10 : double w_val = field->w ? field->w[idx] : 0.0;
219 20 : fprintf(fp, "%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e", y_coords[j], field->u[idx],
220 10 : field->v[idx], w_val, field->p[idx], field->rho[idx], field->T[idx]);
221 10 : if (has_vel_mag) {
222 10 : fprintf(fp, ",%.6e", derived->velocity_magnitude[idx]);
223 : }
224 10 : fprintf(fp, "\n");
225 : }
226 : }
227 :
228 2 : fclose(fp);
229 : }
230 :
231 : //=============================================================================
232 : // CSV STATISTICS OUTPUT
233 : //=============================================================================
234 :
235 10 : void write_csv_statistics(const char* filename, int step, double time, const flow_field* field,
236 : const derived_fields* derived, size_t nx, size_t ny, int create_new) {
237 10 : (void)field; // Statistics come from derived
238 10 : (void)nx;
239 10 : (void)ny;
240 :
241 10 : if (!filename || !derived || !derived->stats_computed) {
242 : return;
243 : }
244 :
245 10 : int write_header = create_new || !file_exists(filename);
246 7 : const char* mode = write_header ? "w" : "a";
247 7 : int has_vel_mag = derived->velocity_magnitude != NULL;
248 :
249 7 : FILE* fp = fopen(filename, mode);
250 7 : if (!fp) {
251 1 : cfd_warning("Failed to open CSV statistics file for writing");
252 1 : return;
253 : }
254 :
255 : // Write header if new file
256 6 : if (write_header) {
257 3 : fprintf(fp, "step,time,min_u,max_u,avg_u,min_v,max_v,avg_v,min_w,max_w,avg_w,"
258 : "min_p,max_p,avg_p,min_rho,max_rho,avg_rho,min_T,max_T,avg_T");
259 3 : if (has_vel_mag) {
260 3 : fprintf(fp, ",min_vel_mag,max_vel_mag,avg_vel_mag");
261 : }
262 3 : fprintf(fp, "\n");
263 : }
264 :
265 : // Write data row using pre-computed statistics
266 12 : fprintf(fp,
267 : "%d,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,"
268 : "%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e,%.6e",
269 6 : step, time, derived->u_stats.min_val, derived->u_stats.max_val,
270 6 : derived->u_stats.avg_val, derived->v_stats.min_val, derived->v_stats.max_val,
271 6 : derived->v_stats.avg_val, derived->w_stats.min_val, derived->w_stats.max_val,
272 6 : derived->w_stats.avg_val, derived->p_stats.min_val, derived->p_stats.max_val,
273 6 : derived->p_stats.avg_val, derived->rho_stats.min_val, derived->rho_stats.max_val,
274 6 : derived->rho_stats.avg_val, derived->T_stats.min_val, derived->T_stats.max_val,
275 6 : derived->T_stats.avg_val);
276 :
277 6 : if (has_vel_mag) {
278 6 : fprintf(fp, ",%.6e,%.6e,%.6e", derived->vel_mag_stats.min_val,
279 6 : derived->vel_mag_stats.max_val, derived->vel_mag_stats.avg_val);
280 : }
281 :
282 6 : fprintf(fp, "\n");
283 :
284 6 : fclose(fp);
285 : }
|