PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
test_logging.c
Go to the documentation of this file.
1/**
2 * @file test_logging.c
3 * @brief C unit tests for runtime log-level, allow-list, conversion, and profiling helpers.
4 */
5
6#include "test_support.h"
7
8#include "logging.h"
9#include "interpolation.h"
10#include "setup.h"
11
12#include <fcntl.h>
13#include <math.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <unistd.h>
18/**
19 * @brief Asserts that one text file contains a required substring.
20 */
21
22static PetscErrorCode AssertFileContains(const char *path, const char *needle, const char *context)
23{
24 FILE *fp = NULL;
25 long file_size = 0;
26 char *buffer = NULL;
27
28 PetscFunctionBeginUser;
29 fp = fopen(path, "rb");
30 if (!fp) {
31 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open '%s' for assertion.", path);
32 }
33 if (fseek(fp, 0, SEEK_END) != 0) {
34 fclose(fp);
35 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to seek '%s'.", path);
36 }
37 file_size = ftell(fp);
38 if (file_size < 0) {
39 fclose(fp);
40 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to measure '%s'.", path);
41 }
42 if (fseek(fp, 0, SEEK_SET) != 0) {
43 fclose(fp);
44 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to rewind '%s'.", path);
45 }
46
47 PetscCall(PetscMalloc1((size_t)file_size + 1, &buffer));
48 if (file_size > 0) {
49 size_t bytes_read = fread(buffer, 1, (size_t)file_size, fp);
50 if (bytes_read != (size_t)file_size) {
51 fclose(fp);
52 PetscCall(PetscFree(buffer));
53 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read '%s'.", path);
54 }
55 }
56 buffer[file_size] = '\0';
57 fclose(fp);
58
59 PetscCall(PicurvAssertBool((PetscBool)(strstr(buffer, needle) != NULL), context));
60 PetscCall(PetscFree(buffer));
61 PetscFunctionReturn(0);
62}
63/**
64 * @brief Asserts that one text file does not contain an excluded substring.
65 */
66
67static PetscErrorCode AssertFileNotContains(const char *path, const char *needle, const char *context)
68{
69 FILE *fp = NULL;
70 long file_size = 0;
71 char *buffer = NULL;
72
73 PetscFunctionBeginUser;
74 fp = fopen(path, "rb");
75 if (!fp) {
76 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open '%s' for assertion.", path);
77 }
78 if (fseek(fp, 0, SEEK_END) != 0) {
79 fclose(fp);
80 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to seek '%s'.", path);
81 }
82 file_size = ftell(fp);
83 if (file_size < 0) {
84 fclose(fp);
85 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to measure '%s'.", path);
86 }
87 if (fseek(fp, 0, SEEK_SET) != 0) {
88 fclose(fp);
89 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to rewind '%s'.", path);
90 }
91
92 PetscCall(PetscMalloc1((size_t)file_size + 1, &buffer));
93 if (file_size > 0) {
94 size_t bytes_read = fread(buffer, 1, (size_t)file_size, fp);
95 if (bytes_read != (size_t)file_size) {
96 fclose(fp);
97 PetscCall(PetscFree(buffer));
98 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read '%s'.", path);
99 }
100 }
101 buffer[file_size] = '\0';
102 fclose(fp);
103
104 PetscCall(PicurvAssertBool((PetscBool)(strstr(buffer, needle) == NULL), context));
105 PetscCall(PetscFree(buffer));
106 PetscFunctionReturn(0);
107}
108
109/**
110 * @brief Reads the CSV header and the requested 1-based data row from a text file.
111 */
112static PetscErrorCode ReadCsvHeaderAndRow(const char *path,
113 PetscInt row_index,
114 char *header,
115 size_t header_len,
116 char *row,
117 size_t row_len)
118{
119 FILE *fp = NULL;
120 PetscInt current_row = 0;
121
122 PetscFunctionBeginUser;
123 PetscCheck(path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV path cannot be NULL.");
124 PetscCheck(header != NULL && header_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV header buffer cannot be NULL or empty.");
125 PetscCheck(row != NULL && row_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV row buffer cannot be NULL or empty.");
126 PetscCheck(row_index >= 1, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "CSV row index must be >= 1.");
127
128 fp = fopen(path, "r");
129 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open CSV '%s'.", path);
130 PetscCheck(fgets(header, (int)header_len, fp) != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "CSV header missing in '%s'.", path);
131
132 while (fgets(row, (int)row_len, fp) != NULL) {
133 current_row++;
134 if (current_row == row_index) {
135 fclose(fp);
136 PetscFunctionReturn(0);
137 }
138 }
139
140 fclose(fp);
141 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "CSV '%s' does not contain data row %d.", path, (int)row_index);
142}
143
144/**
145 * @brief Returns the zero-based column index for one CSV header field.
146 */
147static PetscErrorCode CsvFindColumnIndex(const char *header, const char *column_name, PetscInt *index_out)
148{
149 char local_header[4096];
150 char *saveptr = NULL;
151 char *token = NULL;
152 PetscInt index = 0;
153
154 PetscFunctionBeginUser;
155 PetscCheck(header != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV header cannot be NULL.");
156 PetscCheck(column_name != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV column name cannot be NULL.");
157 PetscCheck(index_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV column index output cannot be NULL.");
158 PetscCheck(strlen(header) < sizeof(local_header), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "CSV header is too long for the local parser buffer.");
159
160 PetscCall(PetscStrncpy(local_header, header, sizeof(local_header)));
161 token = strtok_r(local_header, ",\r\n", &saveptr);
162 while (token != NULL) {
163 if (strcmp(token, column_name) == 0) {
164 *index_out = index;
165 PetscFunctionReturn(0);
166 }
167 token = strtok_r(NULL, ",\r\n", &saveptr);
168 index++;
169 }
170
171 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV column '%s' was not found.", column_name);
172}
173
174/**
175 * @brief Extracts one CSV cell as text by header name.
176 */
177static PetscErrorCode CsvGetColumnText(const char *header,
178 const char *row,
179 const char *column_name,
180 char *value,
181 size_t value_len)
182{
183 char local_row[4096];
184 char *saveptr = NULL;
185 char *token = NULL;
186 PetscInt target_index = -1;
187 PetscInt index = 0;
188
189 PetscFunctionBeginUser;
190 PetscCheck(row != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV row cannot be NULL.");
191 PetscCheck(value != NULL && value_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV value buffer cannot be NULL or empty.");
192 PetscCheck(strlen(row) < sizeof(local_row), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "CSV row is too long for the local parser buffer.");
193
194 PetscCall(CsvFindColumnIndex(header, column_name, &target_index));
195 PetscCall(PetscStrncpy(local_row, row, sizeof(local_row)));
196
197 token = strtok_r(local_row, ",\r\n", &saveptr);
198 while (token != NULL) {
199 if (index == target_index) {
200 PetscCall(PetscStrncpy(value, token, value_len));
201 PetscFunctionReturn(0);
202 }
203 token = strtok_r(NULL, ",\r\n", &saveptr);
204 index++;
205 }
206
207 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV row is missing column '%s'.", column_name);
208}
209
210/**
211 * @brief Extracts one CSV cell as an integer by header name.
212 */
213static PetscErrorCode CsvGetColumnInt(const char *header, const char *row, const char *column_name, PetscInt *value_out)
214{
215 char text[256];
216 char *endptr = NULL;
217 long parsed = 0;
218
219 PetscFunctionBeginUser;
220 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV integer output cannot be NULL.");
221 PetscCall(CsvGetColumnText(header, row, column_name, text, sizeof(text)));
222 parsed = strtol(text, &endptr, 10);
223 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV column '%s' did not contain an integer.", column_name);
224 *value_out = (PetscInt)parsed;
225 PetscFunctionReturn(0);
226}
227
228/**
229 * @brief Extracts one CSV cell as a real by header name.
230 */
231static PetscErrorCode CsvGetColumnReal(const char *header, const char *row, const char *column_name, PetscReal *value_out)
232{
233 char text[256];
234 char *endptr = NULL;
235 double parsed = 0.0;
236
237 PetscFunctionBeginUser;
238 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV real output cannot be NULL.");
239 PetscCall(CsvGetColumnText(header, row, column_name, text, sizeof(text)));
240 parsed = strtod(text, &endptr);
241 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV column '%s' did not contain a real value.", column_name);
242 *value_out = (PetscReal)parsed;
243 PetscFunctionReturn(0);
244}
245
246/**
247 * @brief Reads the column header and the requested 1-based data row from a
248 * pipe-delimited solution-convergence log file.
249 *
250 * Lines starting with '=' (banner) or '-' (separator) are skipped. The first
251 * non-skipped line is treated as the column header; subsequent non-skipped
252 * lines are data rows numbered from 1.
253 */
254static PetscErrorCode ReadLogHeaderAndRow(const char *path,
255 PetscInt row_index,
256 char *header,
257 size_t header_len,
258 char *row,
259 size_t row_len)
260{
261 FILE *fp = NULL;
262 PetscInt current_row = 0;
263 char line[8192];
264 PetscBool header_found = PETSC_FALSE;
265
266 PetscFunctionBeginUser;
267 PetscCheck(path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log path cannot be NULL.");
268 PetscCheck(header != NULL && header_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log header buffer cannot be NULL.");
269 PetscCheck(row != NULL && row_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log row buffer cannot be NULL.");
270 PetscCheck(row_index >= 1, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Log row index must be >= 1.");
271
272 fp = fopen(path, "r");
273 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open log '%s'.", path);
274
275 while (fgets(line, sizeof(line), fp) != NULL) {
276 if (line[0] == '=' || line[0] == '-' || line[0] == '\n' || line[0] == '\r') continue;
277 if (!header_found) {
278 PetscCall(PetscStrncpy(header, line, header_len));
279 header_found = PETSC_TRUE;
280 continue;
281 }
282 current_row++;
283 if (current_row == row_index) {
284 PetscCall(PetscStrncpy(row, line, row_len));
285 fclose(fp);
286 PetscFunctionReturn(0);
287 }
288 }
289
290 fclose(fp);
291 if (!header_found) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Log '%s' has no column header.", path);
292 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Log '%s' does not contain data row %d.", path, (int)row_index);
293}
294
295/**
296 * @brief Returns the zero-based column index for one pipe-delimited header field.
297 */
298static PetscErrorCode LogFindColumnIndex(const char *header, const char *column_name, PetscInt *index_out)
299{
300 char local_header[8192];
301 char *saveptr = NULL;
302 char *token = NULL;
303 PetscInt index = 0;
304
305 PetscFunctionBeginUser;
306 PetscCheck(header != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log header cannot be NULL.");
307 PetscCheck(column_name != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log column name cannot be NULL.");
308 PetscCheck(index_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log column index output cannot be NULL.");
309
310 PetscCall(PetscStrncpy(local_header, header, sizeof(local_header)));
311 token = strtok_r(local_header, "|\r\n", &saveptr);
312 while (token != NULL) {
313 while (*token == ' ') token++;
314 char *end = token + strlen(token) - 1;
315 while (end > token && (*end == ' ' || *end == '\r' || *end == '\n')) end--;
316 *(end + 1) = '\0';
317 if (strcmp(token, column_name) == 0) {
318 *index_out = index;
319 PetscFunctionReturn(0);
320 }
321 token = strtok_r(NULL, "|\r\n", &saveptr);
322 index++;
323 }
324 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log column '%s' was not found.", column_name);
325}
326
327/**
328 * @brief Extracts one pipe-delimited log cell as text by header name.
329 */
330static PetscErrorCode LogGetColumnText(const char *header,
331 const char *row,
332 const char *column_name,
333 char *value,
334 size_t value_len)
335{
336 char local_row[8192];
337 char *saveptr = NULL;
338 char *token = NULL;
339 PetscInt target_index = -1;
340 PetscInt index = 0;
341
342 PetscFunctionBeginUser;
343 PetscCheck(row != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log row cannot be NULL.");
344 PetscCheck(value != NULL && value_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log value buffer cannot be NULL.");
345
346 PetscCall(LogFindColumnIndex(header, column_name, &target_index));
347 PetscCall(PetscStrncpy(local_row, row, sizeof(local_row)));
348
349 token = strtok_r(local_row, "|\r\n", &saveptr);
350 while (token != NULL) {
351 if (index == target_index) {
352 while (*token == ' ') token++;
353 char *end = token + strlen(token) - 1;
354 while (end > token && (*end == ' ' || *end == '\r' || *end == '\n')) end--;
355 *(end + 1) = '\0';
356 PetscCall(PetscStrncpy(value, token, value_len));
357 PetscFunctionReturn(0);
358 }
359 token = strtok_r(NULL, "|\r\n", &saveptr);
360 index++;
361 }
362 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log row is missing column '%s'.", column_name);
363}
364
365/**
366 * @brief Extracts one pipe-delimited log cell as an integer by header name.
367 */
368static PetscErrorCode LogGetColumnInt(const char *header, const char *row, const char *column_name, PetscInt *value_out)
369{
370 char text[256];
371 char *endptr = NULL;
372 long parsed = 0;
373
374 PetscFunctionBeginUser;
375 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log integer output cannot be NULL.");
376 PetscCall(LogGetColumnText(header, row, column_name, text, sizeof(text)));
377 parsed = strtol(text, &endptr, 10);
378 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log column '%s' did not contain an integer.", column_name);
379 *value_out = (PetscInt)parsed;
380 PetscFunctionReturn(0);
381}
382
383/**
384 * @brief Extracts one pipe-delimited log cell as a real by header name.
385 */
386static PetscErrorCode LogGetColumnReal(const char *header, const char *row, const char *column_name, PetscReal *value_out)
387{
388 char text[256];
389 char *endptr = NULL;
390 double parsed = 0.0;
391
392 PetscFunctionBeginUser;
393 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log real output cannot be NULL.");
394 PetscCall(LogGetColumnText(header, row, column_name, text, sizeof(text)));
395 parsed = strtod(text, &endptr);
396 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log column '%s' did not contain a real value.", column_name);
397 *value_out = (PetscReal)parsed;
398 PetscFunctionReturn(0);
399}
400
401typedef PetscErrorCode (*CapturedLoggingFn)(UserCtx *user, SimCtx *simCtx, void *ctx);
402
403typedef struct AnatomyCaptureCtx {
404 const char *field_name;
405 const char *stage_name;
407
408/**
409 * @brief Captures stdout emitted by one logging helper into a temporary file-backed buffer.
410 */
411static PetscErrorCode CaptureLoggingOutput(UserCtx *user,
412 SimCtx *simCtx,
414 void *ctx,
415 char *captured,
416 size_t captured_len)
417{
418 char tmpdir[PETSC_MAX_PATH_LEN];
419 char capture_path[PETSC_MAX_PATH_LEN];
420 FILE *capture_file = NULL;
421 int saved_stdout = -1;
422 int capture_fd = -1;
423 size_t bytes_read = 0;
424 PetscErrorCode ierr;
425
426 PetscFunctionBeginUser;
427 PetscCheck(fn != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Capture callback cannot be NULL.");
428 PetscCheck(captured != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Capture buffer cannot be NULL.");
429 PetscCheck(captured_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "Capture buffer must be non-empty.");
430
431 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
432 PetscCall(PetscSNPrintf(capture_path, sizeof(capture_path), "%s/logging.out", tmpdir));
433
434 fflush(stdout);
435 saved_stdout = dup(STDOUT_FILENO);
436 PetscCheck(saved_stdout >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup(STDOUT_FILENO) failed.");
437 capture_fd = open(capture_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
438 PetscCheck(capture_fd >= 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open capture file '%s'.", capture_path);
439 PetscCheck(dup2(capture_fd, STDOUT_FILENO) >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup2() failed while redirecting stdout.");
440 close(capture_fd);
441 capture_fd = -1;
442
443 ierr = fn(user, simCtx, ctx);
444 fflush(stdout);
445 PetscCheck(dup2(saved_stdout, STDOUT_FILENO) >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup2() failed while restoring stdout.");
446 close(saved_stdout);
447 saved_stdout = -1;
448 PetscCall(ierr);
449
450 capture_file = fopen(capture_path, "r");
451 PetscCheck(capture_file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read capture file '%s'.", capture_path);
452 bytes_read = fread(captured, 1, captured_len - 1, capture_file);
453 captured[bytes_read] = '\0';
454 fclose(capture_file);
455 PetscCall(PicurvRemoveTempDir(tmpdir));
456 PetscFunctionReturn(0);
457}
458
459/**
460 * @brief Adapts `LOG_PARTICLE_FIELDS()` to the generic stdout-capture callback shape.
461 */
462static PetscErrorCode InvokeParticleFieldLog(UserCtx *user, SimCtx *simCtx, void *ctx)
463{
464 PetscInt print_interval = *((PetscInt *)ctx);
465
466 PetscFunctionBeginUser;
467 (void)simCtx;
468 PetscCall(LOG_PARTICLE_FIELDS(user, print_interval));
469 PetscFunctionReturn(0);
470}
471
472/**
473 * @brief Adapts `EmitParticleConsoleSnapshot()` to the generic stdout-capture callback shape.
474 */
475static PetscErrorCode InvokeParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, void *ctx)
476{
477 PetscInt step = *((PetscInt *)ctx);
478
479 PetscFunctionBeginUser;
480 PetscCall(EmitParticleConsoleSnapshot(user, simCtx, step));
481 PetscFunctionReturn(0);
482}
483
484/**
485 * @brief Adapts `LOG_FIELD_ANATOMY()` to the generic stdout-capture callback shape.
486 */
487static PetscErrorCode InvokeFieldAnatomyLog(UserCtx *user, SimCtx *simCtx, void *ctx)
488{
489 const AnatomyCaptureCtx *anatomy_ctx = (const AnatomyCaptureCtx *)ctx;
490
491 PetscFunctionBeginUser;
492 (void)simCtx;
493 PetscCall(LOG_FIELD_ANATOMY(user, anatomy_ctx->field_name, anatomy_ctx->stage_name));
494 PetscFunctionReturn(0);
495}
496
497/**
498 * @brief Creates a small particle-bearing runtime fixture used by logging tests.
499 */
500static PetscErrorCode SeedLoggingParticleFixture(SimCtx **simCtx_out, UserCtx **user_out)
501{
502 PetscReal *positions = NULL;
503 PetscReal *velocities = NULL;
504 PetscReal *weights = NULL;
505 PetscInt *cell_ids = NULL;
506 PetscInt *status = NULL;
507 PetscReal *psi = NULL;
508
509 PetscFunctionBeginUser;
510 PetscCall(PicurvCreateMinimalContexts(simCtx_out, user_out, 4, 4, 4));
511 PetscCall(PicurvCreateSwarmPair(*user_out, 2, "ske"));
512 (*simCtx_out)->np = 2;
513 (*simCtx_out)->particleConsoleOutputFreq = 2;
514 (*simCtx_out)->LoggingFrequency = 1;
515
516 PetscCall(DMSwarmGetField((*user_out)->swarm, "position", NULL, NULL, (void **)&positions));
517 PetscCall(DMSwarmGetField((*user_out)->swarm, "velocity", NULL, NULL, (void **)&velocities));
518 PetscCall(DMSwarmGetField((*user_out)->swarm, "weight", NULL, NULL, (void **)&weights));
519 PetscCall(DMSwarmGetField((*user_out)->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
520 PetscCall(DMSwarmGetField((*user_out)->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
521 PetscCall(DMSwarmGetField((*user_out)->swarm, "Psi", NULL, NULL, (void **)&psi));
522
523 positions[0] = 0.25; positions[1] = 0.50; positions[2] = 0.75;
524 positions[3] = 0.50; positions[4] = 0.50; positions[5] = 0.50;
525 velocities[0] = 1.0; velocities[1] = 2.0; velocities[2] = 3.0;
526 velocities[3] = 4.0; velocities[4] = 5.0; velocities[5] = 6.0;
527 weights[0] = 0.2; weights[1] = 0.3; weights[2] = 0.4;
528 weights[3] = 0.5; weights[4] = 0.5; weights[5] = 0.5;
529 cell_ids[0] = 0; cell_ids[1] = 0; cell_ids[2] = 0;
530 cell_ids[3] = 1; cell_ids[4] = 1; cell_ids[5] = 1;
531 status[0] = ACTIVE_AND_LOCATED;
532 status[1] = ACTIVE_AND_LOCATED;
533 psi[0] = 1.0;
534 psi[1] = 3.0;
535
536 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "Psi", NULL, NULL, (void **)&psi));
537 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
538 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
539 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "weight", NULL, NULL, (void **)&weights));
540 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "velocity", NULL, NULL, (void **)&velocities));
541 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "position", NULL, NULL, (void **)&positions));
542 PetscFunctionReturn(0);
543}
544
545/**
546 * @brief Fills one Cartesian velocity field with a uniform constant state.
547 */
548static PetscErrorCode SetUniformVelocityField(UserCtx *user, Vec field, PetscReal ux, PetscReal uy, PetscReal uz)
549{
550 Cmpnts ***arr = NULL;
551
552 PetscFunctionBeginUser;
553 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
554 PetscCheck(field != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Velocity field cannot be NULL.");
555
556 PetscCall(DMDAVecGetArray(user->fda, field, &arr));
557 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
558 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
559 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
560 arr[k][j][i].x = ux;
561 arr[k][j][i].y = uy;
562 arr[k][j][i].z = uz;
563 }
564 }
565 }
566 PetscCall(DMDAVecRestoreArray(user->fda, field, &arr));
567 PetscFunctionReturn(0);
568}
569
570/**
571 * @brief Fills one scalar field with a uniform constant state.
572 */
573static PetscErrorCode SetUniformScalarField(UserCtx *user, Vec field, PetscReal value)
574{
575 PetscReal ***arr = NULL;
576
577 PetscFunctionBeginUser;
578 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
579 PetscCheck(field != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Scalar field cannot be NULL.");
580
581 PetscCall(DMDAVecGetArray(user->da, field, &arr));
582 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
583 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
584 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
585 arr[k][j][i] = value;
586 }
587 }
588 }
589 PetscCall(DMDAVecRestoreArray(user->da, field, &arr));
590 PetscFunctionReturn(0);
591}
592/**
593 * @brief Fills one Cartesian velocity field with u=0, v=v_amp*sin(2π*k/km), w=w_const.
594 *
595 * Sets the sinusoidal profile at ALL owned nodes (including the periodic duplicate endpoint),
596 * matching how the IC routines populate fields before any periodic-endpoint fix is applied.
597 */
598static PetscErrorCode SetSinusoidalVZField(UserCtx *user, Vec field, PetscReal v_amp, PetscReal w_const)
599{
600 Cmpnts ***arr = NULL;
601 PetscInt km = user->KM;
602
603 PetscFunctionBeginUser;
604 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
605 PetscCheck(field != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Velocity field cannot be NULL.");
606
607 PetscCall(DMDAVecGetArray(user->fda, field, &arr));
608 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
609 PetscReal z_phase = (2.0 * PETSC_PI * (PetscReal)k) / (PetscReal)km;
610 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
611 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
612 arr[k][j][i].x = 0.0;
613 arr[k][j][i].y = v_amp * PetscSinReal(z_phase);
614 arr[k][j][i].z = w_const;
615 }
616 }
617 }
618 PetscCall(DMDAVecRestoreArray(user->fda, field, &arr));
619 PetscFunctionReturn(0);
620}
621
622/**
623 * @brief Tests string-conversion helpers for configured enums and unknown values.
624 */
625
626static PetscErrorCode TestStringConversionHelpers(void)
627{
628 PetscFunctionBeginUser;
629 PetscCall(PicurvAssertBool(strcmp(BCFaceToString(BC_FACE_NEG_X), "-Xi (I-Min)") == 0,
630 "BCFaceToString should report the negative-x face"));
631 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_ZERO), "Zero") == 0,
632 "InitialConditionModeToString should report the zero mode"));
633 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_CONSTANT_CARTESIAN), "Cartesian Constant") == 0,
634 "InitialConditionModeToString should report the Cartesian constant mode"));
635 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_CONSTANT_STREAMWISE), "Streamwise Constant") == 0,
636 "InitialConditionModeToString should report the streamwise constant mode"));
637 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_POISEUILLE), "Poiseuille") == 0,
638 "InitialConditionModeToString should report the Poiseuille mode"));
639 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_FILE), "File") == 0,
640 "InitialConditionModeToString should report the file mode"));
641 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString((InitialConditionMode)99), "Unknown Initial Condition") == 0,
642 "InitialConditionModeToString should reject unknown selectors"));
644 "ParticleInitializationToString should report the volume mode"));
645 PetscCall(PicurvAssertBool(strcmp(LESModelToString(CONSTANT_SMAGORINSKY), "Constant Smagorinsky") == 0,
646 "LESModelToString should report the constant model"));
647 PetscCall(PicurvAssertBool(strcmp(MomentumSolverTypeToString(MOMENTUM_SOLVER_EXPLICIT_RK), "Explicit 4 stage Runge-Kutta ") == 0,
648 "MomentumSolverTypeToString should report the explicit solver"));
649 PetscCall(PicurvAssertBool(strcmp(MomentumSolverTypeToString(MOMENTUM_SOLVER_NEWTON_KRYLOV), "Newton Krylov") == 0,
650 "MomentumSolverTypeToString should report the Newton Krylov solver"));
651 PetscCall(PicurvAssertBool(strcmp(BCTypeToString(PERIODIC), "PERIODIC") == 0,
652 "BCTypeToString should report periodic boundaries"));
654 "BCHandlerTypeToString should report the driven periodic handler"));
655 PetscCall(PicurvAssertBool(strcmp(ParticleLocationStatusToString(LOST), "LOST") == 0,
656 "ParticleLocationStatusToString should report LOST state"));
657 PetscFunctionReturn(0);
658}
659/**
660 * @brief Tests that log level selection honors the environment variable.
661 */
662
663static PetscErrorCode TestGetLogLevelFromEnvironment(void)
664{
665 PetscFunctionBeginUser;
667 "get_log_level should honor LOG_LEVEL=INFO in this test binary"));
668 PetscCall(print_log_level());
669 PetscFunctionReturn(0);
670}
671/**
672 * @brief Tests the function allow-list filter used by the logging layer.
673 */
674
675static PetscErrorCode TestAllowedFunctionsFilter(void)
676{
677 const char *allow_list[] = {"ComputeSpecificKE", "WriteEulerianFile"};
678
679 PetscFunctionBeginUser;
680 set_allowed_functions(allow_list, 2);
681 PetscCall(PicurvAssertBool(is_function_allowed("ComputeSpecificKE"),
682 "Allowed list should include ComputeSpecificKE"));
683 PetscCall(PicurvAssertBool((PetscBool)!is_function_allowed("UnlistedFunction"),
684 "Allowed list should exclude unknown function names"));
685
686 set_allowed_functions(NULL, 0);
687 PetscCall(PicurvAssertBool(is_function_allowed("AnyFunction"),
688 "Empty allow-list should permit all functions"));
689 PetscFunctionReturn(0);
690}
691/**
692 * @brief Tests periodic particle console snapshot enablement and cadence.
693 */
694
695static PetscErrorCode TestParticleConsoleSnapshotCadence(void)
696{
697 SimCtx simCtx;
698
699 PetscFunctionBeginUser;
700 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
701 simCtx.np = 32;
702 simCtx.particleConsoleOutputFreq = 4;
703
705 "Particle snapshot contract should be enabled when particles and cadence are configured"));
707 "Snapshot should emit on cadence-aligned completed steps"));
708 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicParticleConsoleSnapshot(&simCtx, 7),
709 "Snapshot should not emit off-cadence"));
710
711 simCtx.particleConsoleOutputFreq = 0;
712 PetscCall(PicurvAssertBool((PetscBool)!IsParticleConsoleSnapshotEnabled(&simCtx),
713 "Zero cadence should disable periodic particle snapshots"));
714 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicParticleConsoleSnapshot(NULL, 4),
715 "NULL SimCtx should never emit periodic snapshots"));
716 PetscFunctionReturn(0);
717}
718/**
719 * @brief Tests logging-side file parsing, helper formatting, and progress utilities.
720 */
721
723{
724 char tmpdir[PETSC_MAX_PATH_LEN];
725 char allow_path[PETSC_MAX_PATH_LEN];
726 char dual_log_path[PETSC_MAX_PATH_LEN];
727 FILE *file = NULL;
728 char **funcs = NULL;
729 PetscInt nfuncs = 0;
730 Cell cell;
731 PetscReal distances[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
732 DualMonitorCtx *monctx = NULL;
733 void *ctx = NULL;
734
735 PetscFunctionBeginUser;
736 PetscCall(PetscMemzero(&cell, sizeof(cell)));
737 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
738 PetscCall(PetscSNPrintf(allow_path, sizeof(allow_path), "%s/allowed_functions.txt", tmpdir));
739 PetscCall(PetscSNPrintf(dual_log_path, sizeof(dual_log_path), "%s/dual-monitor.log", tmpdir));
740
741 file = fopen(allow_path, "w");
742 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to create allow-list file '%s'.", allow_path);
743 fputs(" ComputeSpecificKE \n", file);
744 fputs("# comment-only line\n", file);
745 for (PetscInt i = 0; i < 17; ++i) {
746 fprintf(file, "Helper_%02d # trailing comment\n", (int)i);
747 }
748 fclose(file);
749 file = NULL;
750
751 PetscCall(LoadAllowedFunctionsFromFile(allow_path, &funcs, &nfuncs));
752 PetscCall(PicurvAssertIntEqual(18, nfuncs, "LoadAllowedFunctionsFromFile should trim comments and keep all identifiers"));
753 PetscCall(PicurvAssertBool((PetscBool)(strcmp(funcs[0], "ComputeSpecificKE") == 0),
754 "LoadAllowedFunctionsFromFile should trim leading and trailing whitespace"));
755 PetscCall(PicurvAssertBool((PetscBool)(strcmp(funcs[17], "Helper_16") == 0),
756 "LoadAllowedFunctionsFromFile should grow past the initial pointer capacity"));
757 PetscCall(FreeAllowedFunctions(funcs, nfuncs));
758
759 for (PetscInt i = 0; i < 8; ++i) {
760 cell.vertices[i].x = (PetscReal)i;
761 cell.vertices[i].y = (PetscReal)(i + 1);
762 cell.vertices[i].z = (PetscReal)(i + 2);
763 }
764 PetscCall(LOG_CELL_VERTICES(&cell, 0));
765 PetscCall(LOG_FACE_DISTANCES(distances));
766
767 PetscCall(PetscCalloc1(1, &monctx));
768 monctx->file_handle = fopen(dual_log_path, "w");
769 PetscCheck(monctx->file_handle != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to create dual-monitor log '%s'.", dual_log_path);
770 ctx = monctx;
771 PetscCall(DualMonitorDestroy(&ctx));
772 PetscCall(PicurvAssertBool((PetscBool)(ctx == NULL),
773 "DualMonitorDestroy should clear the caller-owned context pointer"));
774
775 PrintProgressBar(0, 0, 4, 0.10);
776 PrintProgressBar(3, 0, 4, 0.40);
777 PrintProgressBar(0, 0, 0, 0.00);
778 PetscCall(PetscPrintf(PETSC_COMM_SELF, "\n"));
779
780 PetscCall(PicurvRemoveTempDir(tmpdir));
781 PetscFunctionReturn(0);
782}
783/**
784 * @brief Tests continuity, min/max, and anatomy logging helpers on minimal runtime fixtures.
785 */
786
788{
789 SimCtx *simCtx = NULL;
790 UserCtx *user = NULL;
791 char tmpdir[PETSC_MAX_PATH_LEN];
792 char continuity_path[PETSC_MAX_PATH_LEN];
793 PetscReal ***p = NULL;
794 Cmpnts ***ucat = NULL;
795 Cmpnts ***ucont = NULL;
796 PetscErrorCode ierr_minmax = 0;
797
798 PetscFunctionBeginUser;
799 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
800 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
801 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
802
803 simCtx->StartStep = 0;
804 simCtx->step = 1;
805 simCtx->MaxDiv = 1.25;
806 simCtx->MaxDivx = 1;
807 simCtx->MaxDivy = 2;
808 simCtx->MaxDivz = 3;
809 simCtx->MaxDivFlatArg = 17;
810 simCtx->summationRHS = 8.5;
811 simCtx->FluxInSum = 5.0;
812 simCtx->FluxOutSum = 3.25;
813 PetscCall(LOG_CONTINUITY_METRICS(user));
814
815 simCtx->step = 2;
816 simCtx->MaxDiv = 0.75;
817 simCtx->summationRHS = 4.5;
818 simCtx->FluxInSum = 2.5;
819 simCtx->FluxOutSum = 1.0;
820 PetscCall(LOG_CONTINUITY_METRICS(user));
821
822 PetscCall(PetscSNPrintf(continuity_path, sizeof(continuity_path), "%s/Continuity_Metrics.log", simCtx->log_dir));
823 PetscCall(PicurvAssertFileExists(continuity_path, "continuity metrics log should be written"));
824 PetscCall(AssertFileContains(continuity_path, "Timestep", "continuity metrics log should include the header"));
825 PetscCall(AssertFileContains(continuity_path, "([3][2][1] = 17)", "continuity metrics log should include the divergence location"));
826 PetscCall(AssertFileContains(continuity_path, "2 | 0", "continuity metrics log should append later timesteps"));
827
828 PetscCall(DMDAVecGetArray(user->da, user->P, &p));
829 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
830 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &ucont));
831 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
832 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
833 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
834 p[k][j][i] = (PetscReal)(i + j + k);
835 ucat[k][j][i].x = (PetscReal)i;
836 ucat[k][j][i].y = (PetscReal)(-j);
837 ucat[k][j][i].z = (PetscReal)(2 * k);
838 ucont[k][j][i].x = (PetscReal)(10 + i);
839 ucont[k][j][i].y = (PetscReal)(20 + j);
840 ucont[k][j][i].z = (PetscReal)(30 + k);
841 }
842 }
843 }
844 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &ucont));
845 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
846 PetscCall(DMDAVecRestoreArray(user->da, user->P, &p));
847
848 PetscCall(DMGlobalToLocalBegin(user->da, user->P, INSERT_VALUES, user->lP));
849 PetscCall(DMGlobalToLocalEnd(user->da, user->P, INSERT_VALUES, user->lP));
850 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
851 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
852 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
853 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
854
855 PetscCall(LOG_FIELD_MIN_MAX(user, "P"));
856 PetscCall(LOG_FIELD_MIN_MAX(user, "Ucat"));
857 PetscCall(LOG_FIELD_MIN_MAX(user, "Coordinates"));
858 PetscCall(LOG_FIELD_MIN_MAX(user, "Ucont"));
859
860 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
861 ierr_minmax = LOG_FIELD_MIN_MAX(user, "NotARealField");
862 PetscCall(PetscPopErrorHandler());
863 PetscCall(PicurvAssertBool((PetscBool)(ierr_minmax != 0),
864 "LOG_FIELD_MIN_MAX should reject unknown field names"));
865
866 PetscCall(PicurvRemoveTempDir(tmpdir));
867 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
868 PetscFunctionReturn(0);
869}
870/**
871 * @brief Tests interpolation-error logging against an analytically matched particle field.
872 */
873
874static PetscErrorCode TestInterpolationErrorLogging(void)
875{
876 SimCtx *simCtx = NULL;
877 UserCtx *user = NULL;
878 PetscReal (*pos_arr)[3] = NULL;
879 PetscReal (*vel_arr)[3] = NULL;
880 Vec position_vec = NULL;
881 Vec analytical_vec = NULL;
882 const PetscScalar *analytical_arr = NULL;
883
884 PetscFunctionBeginUser;
885 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
886 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
887 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "TGV3D", sizeof(simCtx->AnalyticalSolutionType)));
888 simCtx->ren = 1.0;
889 simCtx->ti = 0.0;
890
891 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void *)&pos_arr));
892 pos_arr[0][0] = 0.5 * PETSC_PI; pos_arr[0][1] = 0.0; pos_arr[0][2] = 0.0;
893 pos_arr[1][0] = 0.0; pos_arr[1][1] = 0.5 * PETSC_PI; pos_arr[1][2] = 0.0;
894 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void *)&pos_arr));
895
896 PetscCall(DMSwarmCreateGlobalVectorFromField(user->swarm, "position", &position_vec));
897 PetscCall(VecDuplicate(position_vec, &analytical_vec));
898 PetscCall(VecCopy(position_vec, analytical_vec));
899 PetscCall(SetAnalyticalSolutionForParticles(analytical_vec, simCtx));
900
901 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void *)&vel_arr));
902 PetscCall(VecGetArrayRead(analytical_vec, &analytical_arr));
903 for (PetscInt particle = 0; particle < 2; ++particle) {
904 vel_arr[particle][0] = PetscRealPart(analytical_arr[3 * particle + 0]);
905 vel_arr[particle][1] = PetscRealPart(analytical_arr[3 * particle + 1]);
906 vel_arr[particle][2] = PetscRealPart(analytical_arr[3 * particle + 2]);
907 }
908 PetscCall(VecRestoreArrayRead(analytical_vec, &analytical_arr));
909 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void *)&vel_arr));
910
911 PetscCall(VecDestroy(&analytical_vec));
912 PetscCall(DMSwarmDestroyGlobalVectorFromField(user->swarm, "position", &position_vec));
913 PetscCall(LOG_INTERPOLATION_ERROR(user));
914 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
915 PetscFunctionReturn(0);
916}
917/**
918 * @brief Tests file-backed scatter metrics logging against a fully occupied constant field.
919 */
920
921static PetscErrorCode TestScatterMetricsLogging(void)
922{
923 SimCtx *simCtx = NULL;
924 UserCtx *user = NULL;
925 char tmpdir[PETSC_MAX_PATH_LEN];
926 char metrics_path[PETSC_MAX_PATH_LEN];
927 PetscReal *positions = NULL;
928 PetscReal *psi = NULL;
929 PetscInt *cell_ids = NULL;
930 PetscInt *status = NULL;
931 PetscInt particle = 0;
932
933 PetscFunctionBeginUser;
934 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
935 PetscCall(PicurvCreateSwarmPair(user, 27, "ske"));
936 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
937 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
938 simCtx->np = 27;
939 simCtx->step = 3;
940 simCtx->ti = 0.3;
941 simCtx->verificationScalar.enabled = PETSC_TRUE;
942 PetscCall(PetscStrncpy(simCtx->verificationScalar.mode,
943 "analytical",
944 sizeof(simCtx->verificationScalar.mode)));
945 PetscCall(PetscStrncpy(simCtx->verificationScalar.profile,
946 "CONSTANT",
947 sizeof(simCtx->verificationScalar.profile)));
948 simCtx->verificationScalar.value = 2.0;
949
950 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
951 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
952 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
953 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
954
955 for (PetscInt k = 0; k < 3; ++k) {
956 for (PetscInt j = 0; j < 3; ++j) {
957 for (PetscInt i = 0; i < 3; ++i) {
958 positions[3 * particle + 0] = (i + 0.5) / 4.0;
959 positions[3 * particle + 1] = (j + 0.5) / 4.0;
960 positions[3 * particle + 2] = (k + 0.5) / 4.0;
961 cell_ids[3 * particle + 0] = i;
962 cell_ids[3 * particle + 1] = j;
963 cell_ids[3 * particle + 2] = k;
964 status[particle] = ACTIVE_AND_LOCATED;
965 psi[particle] = 2.0;
966 ++particle;
967 }
968 }
969 }
970
971 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
972 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
973 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
974 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
975
977 PetscCall(LOG_SCATTER_METRICS(user));
978
979 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/scatter_metrics.csv", simCtx->log_dir));
980 PetscCall(PicurvAssertFileExists(metrics_path, "LOG_SCATTER_METRICS should write scatter_metrics.csv"));
981 PetscCall(AssertFileContains(metrics_path, "relative_L2_error",
982 "Scatter metrics CSV header should include relative_L2_error"));
983 PetscCall(AssertFileContains(metrics_path,
984 "3,3.000000e-01,27,27,27,1.000000e+00,1.000000e+00,5.400000e+01,5.400000e+01,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00,0.000000e+00",
985 "Scatter metrics CSV should record the expected constant-field zero-error row"));
986
987 PetscCall(PicurvRemoveTempDir(tmpdir));
988 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
989 PetscFunctionReturn(0);
990}
991/**
992 * @brief Tests stdout particle-table logging on a production-like swarm fixture.
993 */
994
995static PetscErrorCode TestParticleFieldTableLogging(void)
996{
997 SimCtx *simCtx = NULL;
998 UserCtx *user = NULL;
999 PetscInt print_interval = 1;
1000 char captured[8192];
1001
1002 PetscFunctionBeginUser;
1003 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1004 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeParticleFieldLog, &print_interval, captured, sizeof(captured)));
1005 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Position (x,y,z)") != NULL),
1006 "LOG_PARTICLE_FIELDS should print the particle table header"));
1007 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Weights (a1,a2,a3)") != NULL),
1008 "LOG_PARTICLE_FIELDS should print the weight-column header"));
1009 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1010 PetscFunctionReturn(0);
1011}
1012/**
1013 * @brief Tests console snapshot logging against the public periodic-snapshot helper.
1014 */
1015
1016static PetscErrorCode TestParticleConsoleSnapshotLogging(void)
1017{
1018 SimCtx *simCtx = NULL;
1019 UserCtx *user = NULL;
1020 PetscInt step = 4;
1021 char captured[8192];
1022
1023 PetscFunctionBeginUser;
1024 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1025 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeParticleConsoleSnapshot, &step, captured, sizeof(captured)));
1026 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Particle states at step 4") != NULL),
1027 "EmitParticleConsoleSnapshot should print the step banner"));
1028 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Position (x,y,z)") != NULL),
1029 "EmitParticleConsoleSnapshot should reuse the particle table output"));
1030 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1031 PetscFunctionReturn(0);
1032}
1033/**
1034 * @brief Tests file-backed particle metrics logging after derived metrics are computed.
1035 */
1036
1037static PetscErrorCode TestParticleMetricsLogging(void)
1038{
1039 SimCtx *simCtx = NULL;
1040 UserCtx *user = NULL;
1041 char tmpdir[PETSC_MAX_PATH_LEN];
1042 char metrics_path[PETSC_MAX_PATH_LEN];
1043
1044 PetscFunctionBeginUser;
1045 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1046 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1047 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1048 simCtx->StartStep = 0;
1049 simCtx->step = 1;
1050 simCtx->particlesLostLastStep = 1;
1051 simCtx->particlesLostCumulative = 7;
1052 simCtx->particlesMigratedLastStep = 2;
1053 simCtx->migrationPassesLastStep = 3;
1054
1055 PetscCall(CalculateParticleCountPerCell(user));
1056 PetscCall(CalculateAdvancedParticleMetrics(user));
1057 PetscCall(LOG_PARTICLE_METRICS(user, "Timestep Metrics"));
1058
1059 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/Particle_Metrics.log", simCtx->log_dir));
1060 PetscCall(PicurvAssertFileExists(metrics_path, "LOG_PARTICLE_METRICS should write Particle_Metrics.log"));
1061 PetscCall(AssertFileContains(metrics_path, "Timestep Metrics", "Particle metrics log should include the caller-provided stage label"));
1062 PetscCall(AssertFileContains(metrics_path, "Occupied Cells", "Particle metrics log should include the metrics table header"));
1063 PetscCall(AssertFileContains(metrics_path, "Lost Total", "Particle metrics log should include the cumulative-loss column"));
1064 PetscCall(AssertFileContains(metrics_path, "| 1 | 7 | 2", "Particle metrics log should record both per-step and cumulative loss values"));
1065 PetscCall(PicurvRemoveTempDir(tmpdir));
1066 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1067 PetscFunctionReturn(0);
1068}
1069/**
1070 * @brief Tests file-backed search metrics logging with the compact CSV contract.
1071 */
1072
1073static PetscErrorCode TestSearchMetricsLogging(void)
1074{
1075 SimCtx *simCtx = NULL;
1076 UserCtx *user = NULL;
1077 char tmpdir[PETSC_MAX_PATH_LEN];
1078 char metrics_path[PETSC_MAX_PATH_LEN];
1079
1080 PetscFunctionBeginUser;
1081 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1082 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1083 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1084 simCtx->step = 2;
1085 simCtx->ti = 0.2;
1086 simCtx->particlesLostLastStep = 1;
1087 simCtx->particlesLostCumulative = 7;
1088 simCtx->particlesMigratedLastStep = 2;
1089 simCtx->migrationPassesLastStep = 3;
1090 simCtx->particleLoadImbalance = 1.5;
1091 simCtx->searchMetrics.searchAttempts = 4;
1092 simCtx->searchMetrics.searchPopulation = 2;
1094 simCtx->searchMetrics.searchLostCount = 1;
1095 simCtx->searchMetrics.traversalStepsSum = 10;
1096 simCtx->searchMetrics.reSearchCount = 2;
1097 simCtx->searchMetrics.maxTraversalSteps = 6;
1099 simCtx->searchMetrics.tieBreakCount = 1;
1104
1105 PetscCall(LOG_SEARCH_METRICS(user));
1106
1107 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/search_metrics.csv", simCtx->log_dir));
1108 PetscCall(PicurvAssertFileExists(metrics_path, "LOG_SEARCH_METRICS should write search_metrics.csv"));
1109 PetscCall(AssertFileContains(metrics_path, "search_attempts", "Search metrics CSV header should include search_attempts"));
1110 PetscCall(AssertFileContains(metrics_path, "search_population", "Search metrics CSV header should include search_population"));
1111 PetscCall(AssertFileContains(metrics_path, "max_particle_pass_depth", "Search metrics CSV header should include max_particle_pass_depth"));
1112 PetscCall(AssertFileContains(metrics_path, "search_work_index", "Search metrics CSV header should include search_work_index"));
1113 PetscCall(AssertFileContains(metrics_path, "re_search_fraction", "Search metrics CSV header should include re_search_fraction"));
1114 PetscCall(AssertFileContains(metrics_path, "lost_cumulative", "Search metrics CSV header should include the cumulative-loss column"));
1115 PetscCall(AssertFileContains(metrics_path, "2,2.000000e-01,2,1,7,2,3,4,2.500000e+00,6,1,2,3,1,3,1.500000e+00,2,1,1,10,2,1,5.000000e-01,5.000000e+00,1.000000e+00", "Search metrics CSV should record the V2 raw and derived search metrics"));
1116 PetscCall(PicurvRemoveTempDir(tmpdir));
1117 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1118 PetscFunctionReturn(0);
1119}
1120/**
1121 * @brief Tests stdout field-anatomy logging on the corrected production-like DM fixture.
1122 */
1123
1124static PetscErrorCode TestFieldAnatomyLogging(void)
1125{
1126 SimCtx *simCtx = NULL;
1127 UserCtx *user = NULL;
1128 char captured[8192];
1129 AnatomyCaptureCtx anatomy_ctx = {"P", "unit-test"};
1130
1131 PetscFunctionBeginUser;
1132 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1133 PetscCall(VecSet(user->P, 7.0));
1134 PetscCall(DMGlobalToLocalBegin(user->da, user->P, INSERT_VALUES, user->lP));
1135 PetscCall(DMGlobalToLocalEnd(user->da, user->P, INSERT_VALUES, user->lP));
1136 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeFieldAnatomyLog, &anatomy_ctx, captured, sizeof(captured)));
1137 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Field Anatomy Log: [P]") != NULL),
1138 "LOG_FIELD_ANATOMY should print the requested field name"));
1139 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Layout: [Cell-Centered]") != NULL),
1140 "LOG_FIELD_ANATOMY should report the inferred data layout"));
1141 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1142 PetscFunctionReturn(0);
1143}
1144/**
1145 * @brief Tests profiling helper lifecycle logging for timestep and final-summary outputs.
1146 */
1147
1148static PetscErrorCode TestProfilingLifecycleHelpers(void)
1149{
1150 SimCtx simCtx;
1151 char tmpdir[PETSC_MAX_PATH_LEN];
1152 char timestep_path[PETSC_MAX_PATH_LEN];
1153 char summary_path[PETSC_MAX_PATH_LEN];
1154 static char selected_name[] = "FlowSolver";
1155 char *selected_funcs[] = {selected_name};
1156
1157 PetscFunctionBeginUser;
1158 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
1159 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1160 PetscCall(PetscStrncpy(simCtx.log_dir, tmpdir, sizeof(simCtx.log_dir)));
1161 PetscCall(PetscStrncpy(simCtx.profilingTimestepMode, "selected", sizeof(simCtx.profilingTimestepMode)));
1162 PetscCall(PetscStrncpy(simCtx.profilingTimestepFile, "Profiling_Timestep_Summary.csv", sizeof(simCtx.profilingTimestepFile)));
1163 simCtx.rank = 0;
1164 simCtx.exec_mode = EXEC_MODE_SOLVER;
1165 simCtx.StartStep = 0;
1166 simCtx.nProfilingSelectedFuncs = 1;
1167 simCtx.profilingSelectedFuncs = selected_funcs;
1168 simCtx.profilingFinalSummary = PETSC_TRUE;
1169
1170 PetscCall(ProfilingInitialize(&simCtx));
1171
1172 _ProfilingStart("FlowSolver");
1173 _ProfilingEnd("FlowSolver");
1174 _ProfilingStart("UnselectedHelper");
1175 _ProfilingEnd("UnselectedHelper");
1176 PetscCall(ProfilingLogTimestepSummary(&simCtx, 1));
1177
1178 _ProfilingStart("FlowSolver");
1179 _ProfilingEnd("FlowSolver");
1180 PetscCall(ProfilingResetTimestepCounters());
1181 PetscCall(ProfilingLogTimestepSummary(&simCtx, 2));
1182 PetscCall(ProfilingFinalize(&simCtx));
1183
1184 PetscCall(PetscSNPrintf(timestep_path, sizeof(timestep_path), "%s/%s", simCtx.log_dir, simCtx.profilingTimestepFile));
1185 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path), "%s/ProfilingSummary_Solver.log", simCtx.log_dir));
1186 PetscCall(PicurvAssertFileExists(timestep_path, "profiling timestep summary should be written"));
1187 PetscCall(PicurvAssertFileExists(summary_path, "profiling final summary should be written"));
1188 PetscCall(AssertFileContains(timestep_path, "step,function,calls,step_time_s",
1189 "profiling timestep summary should contain the CSV header"));
1190 PetscCall(AssertFileContains(timestep_path, "1,FlowSolver,1,",
1191 "profiling timestep summary should log selected functions"));
1192 PetscCall(AssertFileNotContains(timestep_path, "UnselectedHelper",
1193 "profiling timestep summary should omit unselected functions in selected mode"));
1194 PetscCall(AssertFileContains(summary_path, "FINAL PROFILING SUMMARY",
1195 "profiling final summary should include its table banner"));
1196 PetscCall(AssertFileContains(summary_path, "FlowSolver",
1197 "profiling final summary should include selected functions"));
1198 PetscCall(AssertFileContains(summary_path, "UnselectedHelper",
1199 "profiling final summary should include total-time entries for unselected functions"));
1200 PetscCall(PicurvRemoveTempDir(tmpdir));
1201 PetscFunctionReturn(0);
1202}
1203
1204/**
1205 * @brief Tests runtime memory log header, step rows, final rows, and disabled mode.
1206 */
1207static PetscErrorCode TestRuntimeMemoryLogHelpers(void)
1208{
1209 SimCtx simCtx;
1210 char tmpdir[PETSC_MAX_PATH_LEN];
1211 char memory_path[PETSC_MAX_PATH_LEN];
1212
1213 PetscFunctionBeginUser;
1214 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
1215 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1216 PetscCall(PetscStrncpy(simCtx.log_dir, tmpdir, sizeof(simCtx.log_dir)));
1217 PetscCall(PetscStrncpy(simCtx.runtimeMemoryLogFile, "Runtime_Memory.log", sizeof(simCtx.runtimeMemoryLogFile)));
1218 simCtx.rank = 0;
1219 simCtx.runtimeMemoryLogEnabled = PETSC_TRUE;
1220 simCtx.runtimeMemoryLogStarted = PETSC_FALSE;
1221 simCtx.runtimeMemoryLogHasPrevious = PETSC_FALSE;
1222 simCtx.continueMode = PETSC_FALSE;
1223 simCtx.StartStep = 0;
1224 PetscCall(PetscMemorySetGetMaximumUsage());
1225
1226 PetscCall(RuntimeMemoryLogSample(&simCtx, 1, "Step", "-"));
1227 PetscCall(RuntimeMemoryLogSample(&simCtx, 1, "Final", "Complete"));
1228 PetscCall(PetscSNPrintf(memory_path, sizeof(memory_path), "%s/%s", simCtx.log_dir, simCtx.runtimeMemoryLogFile));
1229 PetscCall(PicurvAssertFileExists(memory_path, "runtime memory log should be written"));
1230 PetscCall(AssertFileContains(memory_path, "Process Current MB Max",
1231 "runtime memory log should contain readable column labels"));
1232 PetscCall(AssertFileContains(memory_path, "Step",
1233 "runtime memory log should contain a step row"));
1234 PetscCall(AssertFileContains(memory_path, "Final",
1235 "runtime memory log should contain a final row"));
1236 PetscCall(AssertFileContains(memory_path, "Complete",
1237 "runtime memory log should record final reason"));
1238
1239 simCtx.runtimeMemoryLogEnabled = PETSC_FALSE;
1240 PetscCall(PetscStrncpy(simCtx.runtimeMemoryLogFile, "Runtime_Memory_Disabled.log", sizeof(simCtx.runtimeMemoryLogFile)));
1241 PetscCall(RuntimeMemoryLogSample(&simCtx, 2, "Step", "-"));
1242 PetscCall(PetscSNPrintf(memory_path, sizeof(memory_path), "%s/%s", simCtx.log_dir, simCtx.runtimeMemoryLogFile));
1243 PetscCall(PicurvAssertBool((PetscBool)(access(memory_path, F_OK) != 0),
1244 "disabled runtime memory log should not create a file"));
1245 PetscCall(PicurvRemoveTempDir(tmpdir));
1246 PetscFunctionReturn(0);
1247}
1248
1249/**
1250 * @brief Tests steady solution-convergence log output, IBM masking, and gauge-invariant pressure drift.
1251 */
1252static PetscErrorCode TestSolutionConvergenceSteadyLogging(void)
1253{
1254 SimCtx *simCtx = NULL;
1255 UserCtx *user = NULL;
1256 char tmpdir[PETSC_MAX_PATH_LEN];
1257 char log_path[PETSC_MAX_PATH_LEN];
1258 char header[4096];
1259 char row1[4096];
1260 char row2[4096];
1261 char mode[128];
1262 Cmpnts ***ucat = NULL;
1263 PetscReal ***nvert = NULL;
1264 PetscReal mean_speed = NAN;
1265 PetscReal mean_speed_ref = NAN;
1266 PetscReal mean_speed_abs = NAN;
1267 PetscReal p_abs = NAN;
1268 PetscInt has_reference_1 = 0;
1269 PetscInt has_reference_2 = 0;
1270
1271 PetscFunctionBeginUser;
1272 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1273 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1274 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1276
1277 PetscCall(InitializeSolutionConvergenceState(simCtx));
1278
1279 simCtx->step = 1;
1280 simCtx->ti = 0.1;
1281 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.0, 0.0, 0.0));
1282 PetscCall(SetUniformScalarField(user, user->P, 11.0));
1283 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
1284 nvert[1][1][1] = 1.0;
1285 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
1286 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
1287 ucat[1][1][1].x = 999.0;
1288 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
1289 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1290
1291 simCtx->step = 2;
1292 simCtx->ti = 0.2;
1293 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.0, 0.0, 0.0));
1294 PetscCall(SetUniformVelocityField(user, user->Ucat_o, 0.5, 0.0, 0.0));
1295 PetscCall(SetUniformScalarField(user, user->P, 11.0));
1296 PetscCall(SetUniformScalarField(user, user->P_o, 7.0));
1297 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
1298 ucat[1][1][1].x = 999.0;
1299 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
1300 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1301
1302 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1303 PetscCall(PicurvAssertFileExists(log_path, "steady solution-convergence logging should write the log"));
1304 PetscCall(ReadLogHeaderAndRow(log_path, 1, header, sizeof(header), row1, sizeof(row1)));
1305 PetscCall(ReadLogHeaderAndRow(log_path, 2, header, sizeof(header), row2, sizeof(row2)));
1306 PetscCall(LogGetColumnText(header, row1, "mode", mode, sizeof(mode)));
1307 PetscCall(LogGetColumnInt(header, row1, "ref", &has_reference_1));
1308 PetscCall(LogGetColumnInt(header, row2, "ref", &has_reference_2));
1309 PetscCall(LogGetColumnReal(header, row2, "mean_speed", &mean_speed));
1310 PetscCall(LogGetColumnReal(header, row2, "spd_ref", &mean_speed_ref));
1311 PetscCall(LogGetColumnReal(header, row2, "spd_abs", &mean_speed_abs));
1312 PetscCall(LogGetColumnReal(header, row2, "p_abs_l2", &p_abs));
1313
1314 PetscCall(PicurvAssertBool((PetscBool)(strcmp(mode, "steady_deterministic") == 0),
1315 "steady solution convergence row should record the mode name"));
1316 PetscCall(PicurvAssertIntEqual(0, has_reference_1,
1317 "the first steady solution-convergence row should be a warmup row"));
1318 PetscCall(PicurvAssertIntEqual(1, has_reference_2,
1319 "steady solution convergence should compare against the previous solved step"));
1320 PetscCall(PicurvAssertRealNear(1.0, mean_speed, 1.0e-12,
1321 "steady solution convergence should mask IBM-marked solid cells"));
1322 PetscCall(PicurvAssertRealNear(0.5, mean_speed_ref, 1.0e-12,
1323 "steady solution convergence should report the previous-step mean speed"));
1324 PetscCall(PicurvAssertRealNear(0.5, mean_speed_abs, 1.0e-12,
1325 "steady solution convergence should report mean-speed drift"));
1326 PetscCall(PicurvAssertRealNear(0.0, p_abs, 1.0e-12,
1327 "steady solution convergence pressure drift should be gauge invariant"));
1328
1329 PetscCall(PicurvRemoveTempDir(tmpdir));
1330 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1331 PetscFunctionReturn(0);
1332}
1333
1334/**
1335 * @brief Tests periodic solution-convergence warmup and phase-aligned reference reuse.
1336 */
1338{
1339 SimCtx *simCtx = NULL;
1340 UserCtx *user = NULL;
1341 char tmpdir[PETSC_MAX_PATH_LEN];
1342 char log_path[PETSC_MAX_PATH_LEN];
1343 char header[4096];
1344 char row1[4096];
1345 char row2[4096];
1346 char row3[4096];
1347 PetscInt has_reference_1 = 0;
1348 PetscInt has_reference_2 = 0;
1349 PetscInt has_reference_3 = 0;
1350 PetscInt phase_step_1 = -1;
1351 PetscInt phase_step_2 = -1;
1352 PetscInt phase_step_3 = -1;
1353 PetscReal mean_speed_ref_2 = NAN;
1354 PetscReal mean_speed_abs_2 = NAN;
1355
1356 PetscFunctionBeginUser;
1357 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1358 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1359 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1362
1363 PetscCall(InitializeSolutionConvergenceState(simCtx));
1364
1365 simCtx->step = 1;
1366 simCtx->ti = 0.1;
1367 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.0, 0.0, 0.0));
1368 PetscCall(SetUniformScalarField(user, user->P, 2.0));
1369 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1370
1371 simCtx->step = 2;
1372 simCtx->ti = 0.2;
1373 PetscCall(SetUniformVelocityField(user, user->Ucat, 2.0, 0.0, 0.0));
1374 PetscCall(SetUniformScalarField(user, user->P, 5.0));
1375 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1376
1377 simCtx->step = 3;
1378 simCtx->ti = 0.3;
1379 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.25, 0.0, 0.0));
1380 PetscCall(SetUniformScalarField(user, user->P, 9.0));
1381 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1382
1383 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1384 PetscCall(PicurvAssertFileExists(log_path, "periodic solution-convergence logging should write the log"));
1385 PetscCall(ReadLogHeaderAndRow(log_path, 1, header, sizeof(header), row1, sizeof(row1)));
1386 PetscCall(ReadLogHeaderAndRow(log_path, 2, header, sizeof(header), row2, sizeof(row2)));
1387 PetscCall(ReadLogHeaderAndRow(log_path, 3, header, sizeof(header), row3, sizeof(row3)));
1388 PetscCall(LogGetColumnInt(header, row1, "ref", &has_reference_1));
1389 PetscCall(LogGetColumnInt(header, row2, "ref", &has_reference_2));
1390 PetscCall(LogGetColumnInt(header, row3, "ref", &has_reference_3));
1391 PetscCall(LogGetColumnInt(header, row1, "ph", &phase_step_1));
1392 PetscCall(LogGetColumnInt(header, row2, "ph", &phase_step_2));
1393 PetscCall(LogGetColumnInt(header, row3, "ph", &phase_step_3));
1394 PetscCall(LogGetColumnReal(header, row3, "spd_ref", &mean_speed_ref_2));
1395 PetscCall(LogGetColumnReal(header, row3, "spd_abs", &mean_speed_abs_2));
1396
1397 PetscCall(PicurvAssertIntEqual(0, has_reference_1,
1398 "the first periodic phase visit should log warmup without a reference"));
1399 PetscCall(PicurvAssertIntEqual(0, has_reference_2,
1400 "the first periodic cycle should fully warm up before comparisons begin"));
1401 PetscCall(PicurvAssertIntEqual(1, has_reference_3,
1402 "the repeated periodic phase visit should compare against the stored reference"));
1403 PetscCall(PicurvAssertIntEqual(1, phase_step_1,
1404 "periodic solution convergence should log the current phase slot"));
1405 PetscCall(PicurvAssertIntEqual(0, phase_step_2,
1406 "periodic solution convergence should log distinct phase slots during warmup"));
1407 PetscCall(PicurvAssertIntEqual(1, phase_step_3,
1408 "periodic solution convergence should reuse the same phase slot on later cycles"));
1409 PetscCall(PicurvAssertRealNear(1.0, mean_speed_ref_2, 1.0e-12,
1410 "periodic solution convergence should report the stored phase-aligned reference"));
1411 PetscCall(PicurvAssertRealNear(0.25, mean_speed_abs_2, 1.0e-12,
1412 "periodic solution convergence should report the phase-aligned drift"));
1413
1414 PetscCall(PicurvRemoveTempDir(tmpdir));
1415 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1416 PetscFunctionReturn(0);
1417}
1418
1419/**
1420 * @brief Tests statistical solution-convergence sliding-window metrics.
1421 */
1423{
1424 SimCtx *simCtx = NULL;
1425 UserCtx *user = NULL;
1426 char tmpdir[PETSC_MAX_PATH_LEN];
1427 char log_path[PETSC_MAX_PATH_LEN];
1428 char header[4096];
1429 char row[4096];
1430 PetscReal mean_speed_window = NAN;
1431 PetscReal mean_speed_window_prev = NAN;
1432 PetscReal mean_speed_window_abs = NAN;
1433 PetscReal mean_speed_rms_window = NAN;
1434 PetscReal mean_ke_window = NAN;
1435 PetscReal mean_ke_window_prev = NAN;
1436 PetscReal mean_ke_window_abs = NAN;
1437 PetscReal mean_ke_rms_window_abs = NAN;
1438 PetscInt has_reference = 0;
1439 const PetscReal speed_samples[4] = {1.0, 3.0, 2.0, 4.0};
1440
1441 PetscFunctionBeginUser;
1442 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1443 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1444 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1447
1448 PetscCall(InitializeSolutionConvergenceState(simCtx));
1449 for (PetscInt step = 0; step < 4; ++step) {
1450 simCtx->step = step + 1;
1451 simCtx->ti = 0.1 * (PetscReal)(step + 1);
1452 PetscCall(SetUniformVelocityField(user, user->Ucat, speed_samples[step], 0.0, 0.0));
1453 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1454 }
1455
1456 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1457 PetscCall(PicurvAssertFileExists(log_path, "statistical solution-convergence logging should write the log"));
1458 PetscCall(ReadLogHeaderAndRow(log_path, 4, header, sizeof(header), row, sizeof(row)));
1459 PetscCall(LogGetColumnInt(header, row, "ref", &has_reference));
1460 PetscCall(LogGetColumnReal(header, row, "spd_win", &mean_speed_window));
1461 PetscCall(LogGetColumnReal(header, row, "spd_win_prev", &mean_speed_window_prev));
1462 PetscCall(LogGetColumnReal(header, row, "spd_win_abs", &mean_speed_window_abs));
1463 PetscCall(LogGetColumnReal(header, row, "spd_rms_win", &mean_speed_rms_window));
1464 PetscCall(LogGetColumnReal(header, row, "ke_win", &mean_ke_window));
1465 PetscCall(LogGetColumnReal(header, row, "ke_win_prev", &mean_ke_window_prev));
1466 PetscCall(LogGetColumnReal(header, row, "ke_win_abs", &mean_ke_window_abs));
1467 PetscCall(LogGetColumnReal(header, row, "ke_rms_abs", &mean_ke_rms_window_abs));
1468
1469 PetscCall(PicurvAssertIntEqual(1, has_reference,
1470 "statistical solution convergence should emit adjacent-window drift once two windows exist"));
1471 PetscCall(PicurvAssertRealNear(3.0, mean_speed_window, 1.0e-12,
1472 "statistical solution convergence should report the current mean-speed window"));
1473 PetscCall(PicurvAssertRealNear(2.0, mean_speed_window_prev, 1.0e-12,
1474 "statistical solution convergence should report the previous mean-speed window"));
1475 PetscCall(PicurvAssertRealNear(1.0, mean_speed_window_abs, 1.0e-12,
1476 "statistical solution convergence should report mean-speed window drift"));
1477 PetscCall(PicurvAssertRealNear(1.0, mean_speed_rms_window, 1.0e-12,
1478 "statistical solution convergence should report current mean-speed RMS"));
1479 PetscCall(PicurvAssertRealNear(5.0, mean_ke_window, 1.0e-12,
1480 "statistical solution convergence should report the current kinetic-energy window"));
1481 PetscCall(PicurvAssertRealNear(2.5, mean_ke_window_prev, 1.0e-12,
1482 "statistical solution convergence should report the previous kinetic-energy window"));
1483 PetscCall(PicurvAssertRealNear(2.5, mean_ke_window_abs, 1.0e-12,
1484 "statistical solution convergence should report kinetic-energy window drift"));
1485 PetscCall(PicurvAssertRealNear(1.0, mean_ke_rms_window_abs, 1.0e-12,
1486 "statistical solution convergence should report RMS kinetic-energy drift"));
1487
1488 PetscCall(PicurvRemoveTempDir(tmpdir));
1489 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1490 PetscFunctionReturn(0);
1491}
1492/**
1493 * @brief Regression test: volume-averaged mean KE of a sinusoidal field in a fully periodic domain.
1494 *
1495 * For u=0, v=0.1*sin(2πz), w=1 on a uniform [0,1]³ grid the exact continuous mean KE is 0.5025.
1496 * With identity metrics (Aj=1) the discrete mean over the N unique cells also equals 0.5025
1497 * for any even N, because Σ_{k=0}^{N-1} sin²(2πk/N) = N/2.
1498 *
1499 * Before the fix the statistics loops iterated over N+1 nodes (including the duplicated periodic
1500 * endpoint at k=N), inflating the denominator and yielding mean_ke ≈ 0.5·(1 + 0.01·N/(2(N+1)³))
1501 * instead of 0.5025. This test verifies the endpoint is excluded.
1502 */
1503static PetscErrorCode TestPeriodicSinusoidalMeanKE(void)
1504{
1505 const PetscInt km_values[] = {8, 16};
1506 const PetscInt n_cases = (PetscInt)(sizeof(km_values) / sizeof(km_values[0]));
1507
1508 PetscFunctionBeginUser;
1509 for (PetscInt t = 0; t < n_cases; ++t) {
1510 const PetscInt km = km_values[t];
1511 SimCtx *simCtx = NULL;
1512 UserCtx *user = NULL;
1513 char tmpdir[PETSC_MAX_PATH_LEN];
1514 char log_path[PETSC_MAX_PATH_LEN];
1515 char header[4096];
1516 char row[4096];
1517 PetscReal mean_ke = NAN;
1518
1519 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, km, km, km,
1520 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1521 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1522 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1523 PetscCall(PicurvPopulateIdentityMetrics(user));
1524 PetscCall(SetSinusoidalVZField(user, user->Ucat, 0.1, 1.0));
1525
1528 PetscCall(InitializeSolutionConvergenceState(simCtx));
1529
1530 simCtx->step = 1;
1531 simCtx->ti = 0.1;
1532 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1533
1534 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1535 PetscCall(ReadLogHeaderAndRow(log_path, 1, header, sizeof(header), row, sizeof(row)));
1536 PetscCall(LogGetColumnReal(header, row, "mean_ke", &mean_ke));
1537 PetscCall(PicurvAssertRealNear(0.5025, mean_ke, 1.0e-10,
1538 "periodic sinusoidal field mean KE must equal 0.5025 (no duplicate endpoint)"));
1539
1540 PetscCall(PicurvRemoveTempDir(tmpdir));
1541 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1542 }
1543 PetscFunctionReturn(0);
1544}
1545
1546/**
1547 * @brief Runs the unit-logging PETSc test binary.
1548 */
1549
1550int main(int argc, char **argv)
1551{
1552 PetscErrorCode ierr;
1553 const PicurvTestCase cases[] = {
1554 {"string-conversion-helpers", TestStringConversionHelpers},
1555 {"get-log-level-from-environment", TestGetLogLevelFromEnvironment},
1556 {"allowed-functions-filter", TestAllowedFunctionsFilter},
1557 {"particle-console-snapshot-cadence", TestParticleConsoleSnapshotCadence},
1558 {"logging-file-parsing-and-formatting-helpers", TestLoggingFileParsingAndFormattingHelpers},
1559 {"logging-continuity-and-field-diagnostics", TestLoggingContinuityAndFieldDiagnostics},
1560 {"interpolation-error-logging", TestInterpolationErrorLogging},
1561 {"scatter-metrics-logging", TestScatterMetricsLogging},
1562 {"particle-field-table-logging", TestParticleFieldTableLogging},
1563 {"particle-console-snapshot-logging", TestParticleConsoleSnapshotLogging},
1564 {"particle-metrics-logging", TestParticleMetricsLogging},
1565 {"search-metrics-logging", TestSearchMetricsLogging},
1566 {"field-anatomy-logging", TestFieldAnatomyLogging},
1567 {"profiling-lifecycle-helpers", TestProfilingLifecycleHelpers},
1568 {"runtime-memory-log-helpers", TestRuntimeMemoryLogHelpers},
1569 {"solution-convergence-steady-logging", TestSolutionConvergenceSteadyLogging},
1570 {"solution-convergence-periodic-logging", TestSolutionConvergencePeriodicLogging},
1571 {"solution-convergence-statistical-logging", TestSolutionConvergenceStatisticalLogging},
1572 {"periodic-sinusoidal-mean-ke", TestPeriodicSinusoidalMeanKE},
1573 };
1574
1575 (void)setenv("LOG_LEVEL", "INFO", 1);
1576
1577 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv logging tests");
1578 if (ierr) {
1579 return (int)ierr;
1580 }
1581
1582 ierr = PicurvRunTests("unit-logging", cases, sizeof(cases) / sizeof(cases[0]));
1583 if (ierr) {
1584 PetscFinalize();
1585 return (int)ierr;
1586 }
1587
1588 set_allowed_functions(NULL, 0);
1589
1590 ierr = PetscFinalize();
1591 return (int)ierr;
1592}
PetscErrorCode SetAnalyticalSolutionForParticles(Vec tempVec, SimCtx *simCtx)
Applies the analytical solution to particle velocity vector.
PetscErrorCode CalculateParticleCountPerCell(UserCtx *user)
Counts particles in each cell of the DMDA 'da' and stores the result in user->ParticleCount.
PetscErrorCode ScatterAllParticleFieldsToEulerFields(UserCtx *user)
Scatters a predefined set of particle fields to their corresponding Eulerian fields.
Logging utilities and macros for PETSc-based applications.
void set_allowed_functions(const char **functionList, int count)
Sets the global list of function names that are allowed to log.
Definition logging.c:152
PetscErrorCode LOG_PARTICLE_METRICS(UserCtx *user, const char *stageName)
Logs particle swarm metrics, adapting its behavior based on a boolean flag in SimCtx.
Definition logging.c:3297
const char * BCHandlerTypeToString(BCHandlerType handler_type)
Converts a BCHandlerType enum to its string representation.
Definition logging.c:792
PetscBool is_function_allowed(const char *functionName)
Checks if a given function is in the allow-list.
Definition logging.c:183
PetscErrorCode DualMonitorDestroy(void **ctx)
Destroys the DualMonitorCtx.
Definition logging.c:830
PetscErrorCode LOG_INTERPOLATION_ERROR(UserCtx *user)
Logs the interpolation error between the analytical and computed solutions.
Definition logging.c:2825
PetscBool ShouldEmitPeriodicParticleConsoleSnapshot(const SimCtx *simCtx, PetscInt completed_step)
Returns whether a particle console snapshot should be emitted for the.
Definition logging.c:542
const char * BCFaceToString(BCFace face)
Helper function to convert BCFace enum to a string representation.
Definition logging.c:669
PetscErrorCode FreeAllowedFunctions(char **funcs, PetscInt n)
Free an array previously returned by LoadAllowedFunctionsFromFile().
Definition logging.c:650
PetscBool IsParticleConsoleSnapshotEnabled(const SimCtx *simCtx)
Returns whether periodic particle console snapshots are enabled.
Definition logging.c:525
PetscErrorCode print_log_level(void)
Prints the current logging level to the console.
Definition logging.c:116
PetscErrorCode EmitParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, PetscInt step)
Emits one particle console snapshot into the main solver log.
Definition logging.c:556
PetscErrorCode ProfilingFinalize(SimCtx *simCtx)
the profiling excercise and build a profiling summary which is then printed to a log file.
Definition logging.c:2196
PetscErrorCode LoadAllowedFunctionsFromFile(const char filename[], char ***funcsOut, PetscInt *nOut)
Load function names from a text file.
Definition logging.c:596
PetscErrorCode LOG_FIELD_MIN_MAX(UserCtx *user, const char *fieldName)
Computes and logs the local and global min/max values of a 3-component vector field.
Definition logging.c:2349
void PrintProgressBar(PetscInt step, PetscInt startStep, PetscInt totalSteps, PetscReal currentTime)
Prints a progress bar to the console.
Definition logging.c:2302
PetscErrorCode LOG_FIELD_ANATOMY(UserCtx *user, const char *field_name, const char *stage_name)
Logs the anatomy of a specified field at key boundary locations, respecting the solver's specific gri...
Definition logging.c:2536
PetscErrorCode RuntimeMemoryLogSample(SimCtx *simCtx, PetscInt step, const char *event, const char *reason)
Append a reduced runtime memory sample to the configured memory log.
Definition logging.c:2085
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:84
PetscErrorCode ProfilingLogTimestepSummary(SimCtx *simCtx, PetscInt step)
Logs the performance summary for the current timestep and resets timers.
Definition logging.c:2004
PetscErrorCode LOG_FACE_DISTANCES(PetscReal *d)
Prints the signed distances to each face of the cell.
Definition logging.c:230
PetscErrorCode LOG_PARTICLE_FIELDS(UserCtx *user, PetscInt printInterval)
Prints particle fields in a table that automatically adjusts its column widths.
Definition logging.c:397
void _ProfilingEnd(const char *func_name)
Internal profiling hook invoked by PROFILE_FUNCTION_END.
Definition logging.c:1965
const char * BCTypeToString(BCType type)
Helper function to convert BCType enum to a string representation.
Definition logging.c:772
PetscErrorCode CalculateAdvancedParticleMetrics(UserCtx *user)
Computes advanced particle statistics and stores them in SimCtx.
Definition logging.c:3243
const char * ParticleLocationStatusToString(ParticleLocationStatus level)
A function that outputs the name of the current level in the ParticleLocation enum.
Definition logging.c:1856
PetscErrorCode LOG_SCATTER_METRICS(UserCtx *user)
Logs particle-to-grid scatter verification metrics for the prescribed scalar truth path.
Definition logging.c:2904
PetscErrorCode LOG_SOLUTION_CONVERGENCE(SimCtx *simCtx)
Logs physical solution-convergence metrics once per completed timestep.
Definition logging.c:1599
PetscErrorCode LOG_CONTINUITY_METRICS(UserCtx *user)
Logs continuity metrics for a single block to a file.
Definition logging.c:1794
PetscErrorCode LOG_SEARCH_METRICS(UserCtx *user)
Writes compact runtime search metrics to CSV and optionally to console.
Definition logging.c:3089
const char * InitialConditionModeToString(InitialConditionMode mode)
Convert an initial-condition mode to a string representation.
Definition logging.c:687
PetscErrorCode ProfilingInitialize(SimCtx *simCtx)
Initializes the custom profiling system using configuration from SimCtx.
Definition logging.c:1927
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:30
const char * LESModelToString(LESModelType LESFlag)
Helper function to convert LES Flag to a string representation.
Definition logging.c:740
PetscErrorCode LOG_CELL_VERTICES(const Cell *cell, PetscMPIInt rank)
Prints the coordinates of a cell's vertices.
Definition logging.c:205
PetscErrorCode ProfilingResetTimestepCounters(void)
Resets per-timestep profiling counters for the next solver step.
Definition logging.c:1987
const char * MomentumSolverTypeToString(MomentumSolverType SolverFlag)
Helper function to convert Momentum Solver flag to a string representation.
Definition logging.c:756
FILE * file_handle
Definition logging.h:56
const char * ParticleInitializationToString(ParticleInitializationType ParticleInitialization)
Helper function to convert ParticleInitialization to a string representation.
Definition logging.c:723
void _ProfilingStart(const char *func_name)
Internal profiling hook invoked by PROFILE_FUNCTION_BEGIN.
Definition logging.c:1951
Context for a dual-purpose KSP monitor.
Definition logging.h:55
PetscErrorCode InitializeSolutionConvergenceState(SimCtx *simCtx)
Allocates any runtime storage required by solution-convergence logging.
Definition setup.c:47
static PetscErrorCode TestParticleConsoleSnapshotCadence(void)
Tests periodic particle console snapshot enablement and cadence.
static PetscErrorCode SetUniformScalarField(UserCtx *user, Vec field, PetscReal value)
Fills one scalar field with a uniform constant state.
static PetscErrorCode TestFieldAnatomyLogging(void)
Tests stdout field-anatomy logging on the corrected production-like DM fixture.
static PetscErrorCode LogGetColumnText(const char *header, const char *row, const char *column_name, char *value, size_t value_len)
Extracts one pipe-delimited log cell as text by header name.
static PetscErrorCode InvokeParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, void *ctx)
Adapts EmitParticleConsoleSnapshot() to the generic stdout-capture callback shape.
static PetscErrorCode TestParticleConsoleSnapshotLogging(void)
Tests console snapshot logging against the public periodic-snapshot helper.
static PetscErrorCode CsvGetColumnText(const char *header, const char *row, const char *column_name, char *value, size_t value_len)
Extracts one CSV cell as text by header name.
static PetscErrorCode CaptureLoggingOutput(UserCtx *user, SimCtx *simCtx, CapturedLoggingFn fn, void *ctx, char *captured, size_t captured_len)
Captures stdout emitted by one logging helper into a temporary file-backed buffer.
static PetscErrorCode ReadCsvHeaderAndRow(const char *path, PetscInt row_index, char *header, size_t header_len, char *row, size_t row_len)
Reads the CSV header and the requested 1-based data row from a text file.
static PetscErrorCode TestGetLogLevelFromEnvironment(void)
Tests that log level selection honors the environment variable.
int main(int argc, char **argv)
Runs the unit-logging PETSc test binary.
static PetscErrorCode TestLoggingContinuityAndFieldDiagnostics(void)
Tests continuity, min/max, and anatomy logging helpers on minimal runtime fixtures.
static PetscErrorCode TestPeriodicSinusoidalMeanKE(void)
Regression test: volume-averaged mean KE of a sinusoidal field in a fully periodic domain.
PetscErrorCode(* CapturedLoggingFn)(UserCtx *user, SimCtx *simCtx, void *ctx)
static PetscErrorCode TestSolutionConvergenceStatisticalLogging(void)
Tests statistical solution-convergence sliding-window metrics.
static PetscErrorCode AssertFileNotContains(const char *path, const char *needle, const char *context)
Asserts that one text file does not contain an excluded substring.
static PetscErrorCode InvokeFieldAnatomyLog(UserCtx *user, SimCtx *simCtx, void *ctx)
Adapts LOG_FIELD_ANATOMY() to the generic stdout-capture callback shape.
static PetscErrorCode TestSolutionConvergencePeriodicLogging(void)
Tests periodic solution-convergence warmup and phase-aligned reference reuse.
static PetscErrorCode TestStringConversionHelpers(void)
Tests string-conversion helpers for configured enums and unknown values.
static PetscErrorCode SetSinusoidalVZField(UserCtx *user, Vec field, PetscReal v_amp, PetscReal w_const)
Fills one Cartesian velocity field with u=0, v=v_amp*sin(2π*k/km), w=w_const.
static PetscErrorCode TestLoggingFileParsingAndFormattingHelpers(void)
Tests logging-side file parsing, helper formatting, and progress utilities.
static PetscErrorCode TestAllowedFunctionsFilter(void)
Tests the function allow-list filter used by the logging layer.
static PetscErrorCode TestInterpolationErrorLogging(void)
Tests interpolation-error logging against an analytically matched particle field.
static PetscErrorCode SeedLoggingParticleFixture(SimCtx **simCtx_out, UserCtx **user_out)
Creates a small particle-bearing runtime fixture used by logging tests.
static PetscErrorCode TestScatterMetricsLogging(void)
Tests file-backed scatter metrics logging against a fully occupied constant field.
static PetscErrorCode TestParticleMetricsLogging(void)
Tests file-backed particle metrics logging after derived metrics are computed.
static PetscErrorCode CsvGetColumnReal(const char *header, const char *row, const char *column_name, PetscReal *value_out)
Extracts one CSV cell as a real by header name.
static PetscErrorCode TestSearchMetricsLogging(void)
Tests file-backed search metrics logging with the compact CSV contract.
static PetscErrorCode TestParticleFieldTableLogging(void)
Tests stdout particle-table logging on a production-like swarm fixture.
static PetscErrorCode CsvFindColumnIndex(const char *header, const char *column_name, PetscInt *index_out)
Returns the zero-based column index for one CSV header field.
static PetscErrorCode LogFindColumnIndex(const char *header, const char *column_name, PetscInt *index_out)
Returns the zero-based column index for one pipe-delimited header field.
static PetscErrorCode SetUniformVelocityField(UserCtx *user, Vec field, PetscReal ux, PetscReal uy, PetscReal uz)
Fills one Cartesian velocity field with a uniform constant state.
static PetscErrorCode ReadLogHeaderAndRow(const char *path, PetscInt row_index, char *header, size_t header_len, char *row, size_t row_len)
Reads the column header and the requested 1-based data row from a pipe-delimited solution-convergence...
static PetscErrorCode AssertFileContains(const char *path, const char *needle, const char *context)
Asserts that one text file contains a required substring.
static PetscErrorCode CsvGetColumnInt(const char *header, const char *row, const char *column_name, PetscInt *value_out)
Extracts one CSV cell as an integer by header name.
static PetscErrorCode LogGetColumnReal(const char *header, const char *row, const char *column_name, PetscReal *value_out)
Extracts one pipe-delimited log cell as a real by header name.
static PetscErrorCode TestSolutionConvergenceSteadyLogging(void)
Tests steady solution-convergence log output, IBM masking, and gauge-invariant pressure drift.
static PetscErrorCode TestProfilingLifecycleHelpers(void)
Tests profiling helper lifecycle logging for timestep and final-summary outputs.
static PetscErrorCode LogGetColumnInt(const char *header, const char *row, const char *column_name, PetscInt *value_out)
Extracts one pipe-delimited log cell as an integer by header name.
const char * stage_name
const char * field_name
static PetscErrorCode InvokeParticleFieldLog(UserCtx *user, SimCtx *simCtx, void *ctx)
Adapts LOG_PARTICLE_FIELDS() to the generic stdout-capture callback shape.
static PetscErrorCode TestRuntimeMemoryLogHelpers(void)
Tests runtime memory log header, step rows, final rows, and disabled mode.
PetscErrorCode PicurvMakeTempDir(char *path, size_t path_len)
Creates a unique temporary directory for one test case.
PetscErrorCode PicurvCreateMinimalContexts(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz)
Builds minimal SimCtx and UserCtx fixtures for C unit tests.
PetscErrorCode PicurvAssertRealNear(PetscReal expected, PetscReal actual, PetscReal tol, const char *context)
Asserts that two real values agree within tolerance.
PetscErrorCode PicurvDestroyMinimalContexts(SimCtx **simCtx_ptr, UserCtx **user_ptr)
Destroys minimal SimCtx/UserCtx fixtures and all owned PETSc objects.
PetscErrorCode PicurvCreateMinimalContextsWithPeriodicity(SimCtx **simCtx_out, UserCtx **user_out, PetscInt mx, PetscInt my, PetscInt mz, PetscBool x_periodic, PetscBool y_periodic, PetscBool z_periodic)
Builds minimal SimCtx and UserCtx fixtures for C unit tests with configurable periodicity.
PetscErrorCode PicurvCreateSwarmPair(UserCtx *user, PetscInt nlocal, const char *post_field_name)
Creates matched solver and post-processing swarms for tests.
PetscErrorCode PicurvRunTests(const char *suite_name, const PicurvTestCase *cases, size_t case_count)
Runs a named C test suite and prints pass/fail progress markers.
PetscErrorCode PicurvAssertFileExists(const char *path, const char *context)
Asserts that a filesystem path exists as a readable file.
PetscErrorCode PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvPopulateIdentityMetrics(UserCtx *user)
Populates identity metric vectors on the minimal grid fixture.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
PetscErrorCode PicurvRemoveTempDir(const char *path)
Recursively removes a temporary directory created by PicurvMakeTempDir.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
@ CONSTANT_SMAGORINSKY
Definition variables.h:520
@ PERIODIC
Definition variables.h:290
PetscBool continueMode
Definition variables.h:701
PetscBool profilingFinalSummary
Definition variables.h:837
PetscMPIInt rank
Definition variables.h:687
char profilingTimestepFile[PETSC_MAX_PATH_LEN]
Definition variables.h:836
PetscInt64 searchLocatedCount
Definition variables.h:239
PetscInt64 searchLostCount
Definition variables.h:240
@ PARTICLE_INIT_VOLUME
Random volumetric distribution across the domain.
Definition variables.h:551
@ LOST
Definition variables.h:139
@ ACTIVE_AND_LOCATED
Definition variables.h:137
PetscReal FluxOutSum
Definition variables.h:777
PetscBool runtimeMemoryLogEnabled
Enable the rank-reduced runtime memory log.
Definition variables.h:852
PetscInt64 boundaryClampCount
Definition variables.h:246
PetscInt particlesLostLastStep
Definition variables.h:803
PetscInt KM
Definition variables.h:885
PetscInt64 traversalStepsSum
Definition variables.h:241
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:316
PetscReal ren
Definition variables.h:732
PetscInt64 searchPopulation
Definition variables.h:238
char runtimeMemoryLogFile[PETSC_MAX_PATH_LEN]
File name written under log_dir.
Definition variables.h:853
PetscBool runtimeMemoryLogStarted
True after rank 0 writes the log header.
Definition variables.h:854
char profilingTimestepMode[32]
Definition variables.h:835
PetscInt np
Definition variables.h:796
Vec Ucont
Definition variables.h:904
PetscInt StartStep
Definition variables.h:694
@ MOMENTUM_SOLVER_EXPLICIT_RK
Definition variables.h:533
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:535
PetscInt solutionConvergencePeriodSteps
Definition variables.h:750
PetscScalar x
Definition variables.h:101
PetscInt64 reSearchCount
Definition variables.h:242
PetscReal MaxDiv
Definition variables.h:828
PetscInt64 bboxGuessFallbackCount
Definition variables.h:248
VerificationScalarConfig verificationScalar
Definition variables.h:756
Vec Ucat_o
Definition variables.h:911
PetscInt MaxDivx
Definition variables.h:829
PetscInt MaxDivy
Definition variables.h:829
PetscInt64 bboxGuessSuccessCount
Definition variables.h:247
PetscInt MaxDivz
Definition variables.h:829
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:709
PetscInt MaxDivFlatArg
Definition variables.h:829
PetscReal FluxInSum
Definition variables.h:777
PetscInt64 maxParticlePassDepth
Definition variables.h:249
PetscInt64 maxTraversalSteps
Definition variables.h:243
PetscScalar z
Definition variables.h:101
Vec Ucat
Definition variables.h:904
PetscBool runtimeMemoryLogHasPrevious
True after the first process-memory sample.
Definition variables.h:855
char ** profilingSelectedFuncs
Definition variables.h:833
PetscInt solutionConvergenceWindowSteps
Definition variables.h:751
PetscInt particlesLostCumulative
Definition variables.h:804
PetscInt nProfilingSelectedFuncs
Definition variables.h:834
PetscInt particlesMigratedLastStep
Definition variables.h:806
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:717
InitialConditionMode
Selects the algorithm used to populate a fresh Eulerian velocity field.
Definition variables.h:149
@ IC_MODE_CONSTANT_CARTESIAN
Definition variables.h:151
@ IC_MODE_POISEUILLE
Definition variables.h:152
@ IC_MODE_CONSTANT_STREAMWISE
Definition variables.h:153
@ IC_MODE_FILE
Definition variables.h:154
@ IC_MODE_ZERO
Definition variables.h:150
PetscInt particleConsoleOutputFreq
Definition variables.h:697
SearchMetricsState searchMetrics
Definition variables.h:809
Vec lUcont
Definition variables.h:904
PetscInt step
Definition variables.h:692
DMDALocalInfo info
Definition variables.h:883
Vec lUcat
Definition variables.h:904
PetscInt migrationPassesLastStep
Definition variables.h:805
PetscScalar y
Definition variables.h:101
@ EXEC_MODE_SOLVER
Definition variables.h:657
Vec Nvert
Definition variables.h:904
@ SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC
Definition variables.h:543
@ SOLUTION_CONVERGENCE_STATISTICAL_STEADY
Definition variables.h:544
@ SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC
Definition variables.h:542
SolutionConvergenceMode solutionConvergenceMode
Definition variables.h:749
PetscInt64 searchAttempts
Definition variables.h:237
ExecutionMode exec_mode
Definition variables.h:703
PetscInt64 tieBreakCount
Definition variables.h:245
PetscReal ti
Definition variables.h:693
PetscReal summationRHS
Definition variables.h:827
PetscInt64 maxTraversalFailCount
Definition variables.h:244
Cmpnts vertices[8]
Coordinates of the eight vertices of the cell.
Definition variables.h:176
PetscReal particleLoadImbalance
Definition variables.h:808
Vec P_o
Definition variables.h:911
@ BC_FACE_NEG_X
Definition variables.h:260
Defines the vertices of a single hexahedral grid cell.
Definition variables.h:175
A 3D point or vector with PetscScalar components.
Definition variables.h:100
The master context for the entire simulation.
Definition variables.h:684
User-defined context containing data specific to a single computational grid level.
Definition variables.h:876