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"
12#include "statistics_window.h"
13
14#include <fcntl.h>
15#include <math.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <unistd.h>
20/**
21 * @brief Asserts that one text file contains a required substring.
22 */
23
24static PetscErrorCode AssertFileContains(const char *path, const char *needle, const char *context)
25{
26 FILE *fp = NULL;
27 long file_size = 0;
28 char *buffer = NULL;
29
30 PetscFunctionBeginUser;
31 fp = fopen(path, "rb");
32 if (!fp) {
33 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open '%s' for assertion.", path);
34 }
35 if (fseek(fp, 0, SEEK_END) != 0) {
36 fclose(fp);
37 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to seek '%s'.", path);
38 }
39 file_size = ftell(fp);
40 if (file_size < 0) {
41 fclose(fp);
42 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to measure '%s'.", path);
43 }
44 if (fseek(fp, 0, SEEK_SET) != 0) {
45 fclose(fp);
46 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to rewind '%s'.", path);
47 }
48
49 PetscCall(PetscMalloc1((size_t)file_size + 1, &buffer));
50 if (file_size > 0) {
51 size_t bytes_read = fread(buffer, 1, (size_t)file_size, fp);
52 if (bytes_read != (size_t)file_size) {
53 fclose(fp);
54 PetscCall(PetscFree(buffer));
55 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read '%s'.", path);
56 }
57 }
58 buffer[file_size] = '\0';
59 fclose(fp);
60
61 PetscCall(PicurvAssertBool((PetscBool)(strstr(buffer, needle) != NULL), context));
62 PetscCall(PetscFree(buffer));
63 PetscFunctionReturn(0);
64}
65/**
66 * @brief Asserts that one text file does not contain an excluded substring.
67 */
68
69static PetscErrorCode AssertFileNotContains(const char *path, const char *needle, const char *context)
70{
71 FILE *fp = NULL;
72 long file_size = 0;
73 char *buffer = NULL;
74
75 PetscFunctionBeginUser;
76 fp = fopen(path, "rb");
77 if (!fp) {
78 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open '%s' for assertion.", path);
79 }
80 if (fseek(fp, 0, SEEK_END) != 0) {
81 fclose(fp);
82 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to seek '%s'.", path);
83 }
84 file_size = ftell(fp);
85 if (file_size < 0) {
86 fclose(fp);
87 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to measure '%s'.", path);
88 }
89 if (fseek(fp, 0, SEEK_SET) != 0) {
90 fclose(fp);
91 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to rewind '%s'.", path);
92 }
93
94 PetscCall(PetscMalloc1((size_t)file_size + 1, &buffer));
95 if (file_size > 0) {
96 size_t bytes_read = fread(buffer, 1, (size_t)file_size, fp);
97 if (bytes_read != (size_t)file_size) {
98 fclose(fp);
99 PetscCall(PetscFree(buffer));
100 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read '%s'.", path);
101 }
102 }
103 buffer[file_size] = '\0';
104 fclose(fp);
105
106 PetscCall(PicurvAssertBool((PetscBool)(strstr(buffer, needle) == NULL), context));
107 PetscCall(PetscFree(buffer));
108 PetscFunctionReturn(0);
109}
110
111/**
112 * @brief Reads the CSV header and the requested 1-based data row from a text file.
113 */
114static PetscErrorCode ReadCsvHeaderAndRow(const char *path,
115 PetscInt row_index,
116 char *header,
117 size_t header_len,
118 char *row,
119 size_t row_len)
120{
121 FILE *fp = NULL;
122 PetscInt current_row = 0;
123
124 PetscFunctionBeginUser;
125 PetscCheck(path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV path cannot be NULL.");
126 PetscCheck(header != NULL && header_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV header buffer cannot be NULL or empty.");
127 PetscCheck(row != NULL && row_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV row buffer cannot be NULL or empty.");
128 PetscCheck(row_index >= 1, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "CSV row index must be >= 1.");
129
130 fp = fopen(path, "r");
131 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open CSV '%s'.", path);
132 PetscCheck(fgets(header, (int)header_len, fp) != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "CSV header missing in '%s'.", path);
133
134 while (fgets(row, (int)row_len, fp) != NULL) {
135 current_row++;
136 if (current_row == row_index) {
137 fclose(fp);
138 PetscFunctionReturn(0);
139 }
140 }
141
142 fclose(fp);
143 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "CSV '%s' does not contain data row %d.", path, (int)row_index);
144}
145
146/**
147 * @brief Returns the zero-based column index for one CSV header field.
148 */
149static PetscErrorCode CsvFindColumnIndex(const char *header, const char *column_name, PetscInt *index_out)
150{
151 char local_header[4096];
152 char *saveptr = NULL;
153 char *token = NULL;
154 PetscInt index = 0;
155
156 PetscFunctionBeginUser;
157 PetscCheck(header != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV header cannot be NULL.");
158 PetscCheck(column_name != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV column name cannot be NULL.");
159 PetscCheck(index_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV column index output cannot be NULL.");
160 PetscCheck(strlen(header) < sizeof(local_header), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "CSV header is too long for the local parser buffer.");
161
162 PetscCall(PetscStrncpy(local_header, header, sizeof(local_header)));
163 token = strtok_r(local_header, ",\r\n", &saveptr);
164 while (token != NULL) {
165 if (strcmp(token, column_name) == 0) {
166 *index_out = index;
167 PetscFunctionReturn(0);
168 }
169 token = strtok_r(NULL, ",\r\n", &saveptr);
170 index++;
171 }
172
173 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV column '%s' was not found.", column_name);
174}
175
176/**
177 * @brief Extracts one CSV cell as text by header name.
178 */
179static PetscErrorCode CsvGetColumnText(const char *header,
180 const char *row,
181 const char *column_name,
182 char *value,
183 size_t value_len)
184{
185 char local_row[4096];
186 char *saveptr = NULL;
187 char *token = NULL;
188 PetscInt target_index = -1;
189 PetscInt index = 0;
190
191 PetscFunctionBeginUser;
192 PetscCheck(row != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV row cannot be NULL.");
193 PetscCheck(value != NULL && value_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV value buffer cannot be NULL or empty.");
194 PetscCheck(strlen(row) < sizeof(local_row), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "CSV row is too long for the local parser buffer.");
195
196 PetscCall(CsvFindColumnIndex(header, column_name, &target_index));
197 PetscCall(PetscStrncpy(local_row, row, sizeof(local_row)));
198
199 token = strtok_r(local_row, ",\r\n", &saveptr);
200 while (token != NULL) {
201 if (index == target_index) {
202 PetscCall(PetscStrncpy(value, token, value_len));
203 PetscFunctionReturn(0);
204 }
205 token = strtok_r(NULL, ",\r\n", &saveptr);
206 index++;
207 }
208
209 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV row is missing column '%s'.", column_name);
210}
211
212/**
213 * @brief Extracts one CSV cell as an integer by header name.
214 */
215static PetscErrorCode CsvGetColumnInt(const char *header, const char *row, const char *column_name, PetscInt *value_out)
216{
217 char text[256];
218 char *endptr = NULL;
219 long parsed = 0;
220
221 PetscFunctionBeginUser;
222 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV integer output cannot be NULL.");
223 PetscCall(CsvGetColumnText(header, row, column_name, text, sizeof(text)));
224 parsed = strtol(text, &endptr, 10);
225 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV column '%s' did not contain an integer.", column_name);
226 *value_out = (PetscInt)parsed;
227 PetscFunctionReturn(0);
228}
229
230/**
231 * @brief Extracts one CSV cell as a real by header name.
232 */
233static PetscErrorCode CsvGetColumnReal(const char *header, const char *row, const char *column_name, PetscReal *value_out)
234{
235 char text[256];
236 char *endptr = NULL;
237 double parsed = 0.0;
238
239 PetscFunctionBeginUser;
240 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "CSV real output cannot be NULL.");
241 PetscCall(CsvGetColumnText(header, row, column_name, text, sizeof(text)));
242 parsed = strtod(text, &endptr);
243 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "CSV column '%s' did not contain a real value.", column_name);
244 *value_out = (PetscReal)parsed;
245 PetscFunctionReturn(0);
246}
247
248/**
249 * @brief Reads the column header and the requested 1-based data row from a
250 * pipe-delimited solution-convergence log file.
251 *
252 * Lines starting with '=' (banner) or '-' (separator) are skipped. The first
253 * non-skipped line is treated as the column header; subsequent non-skipped
254 * lines are data rows numbered from 1.
255 */
256static PetscErrorCode ReadLogHeaderAndRow(const char *path,
257 PetscInt row_index,
258 char *header,
259 size_t header_len,
260 char *row,
261 size_t row_len)
262{
263 FILE *fp = NULL;
264 PetscInt current_row = 0;
265 char line[8192];
266 PetscBool header_found = PETSC_FALSE;
267
268 PetscFunctionBeginUser;
269 PetscCheck(path != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log path cannot be NULL.");
270 PetscCheck(header != NULL && header_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log header buffer cannot be NULL.");
271 PetscCheck(row != NULL && row_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log row buffer cannot be NULL.");
272 PetscCheck(row_index >= 1, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Log row index must be >= 1.");
273
274 fp = fopen(path, "r");
275 PetscCheck(fp != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open log '%s'.", path);
276
277 while (fgets(line, sizeof(line), fp) != NULL) {
278 if (line[0] == '=' || line[0] == '-' || line[0] == '\n' || line[0] == '\r') continue;
279 if (!header_found) {
280 PetscCall(PetscStrncpy(header, line, header_len));
281 header_found = PETSC_TRUE;
282 continue;
283 }
284 current_row++;
285 if (current_row == row_index) {
286 PetscCall(PetscStrncpy(row, line, row_len));
287 fclose(fp);
288 PetscFunctionReturn(0);
289 }
290 }
291
292 fclose(fp);
293 if (!header_found) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Log '%s' has no column header.", path);
294 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_FILE_READ, "Log '%s' does not contain data row %d.", path, (int)row_index);
295}
296
297/**
298 * @brief Returns the zero-based column index for one pipe-delimited header field.
299 */
300static PetscErrorCode LogFindColumnIndex(const char *header, const char *column_name, PetscInt *index_out)
301{
302 char local_header[8192];
303 char *saveptr = NULL;
304 char *token = NULL;
305 PetscInt index = 0;
306
307 PetscFunctionBeginUser;
308 PetscCheck(header != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log header cannot be NULL.");
309 PetscCheck(column_name != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log column name cannot be NULL.");
310 PetscCheck(index_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log column index output cannot be NULL.");
311
312 PetscCall(PetscStrncpy(local_header, header, sizeof(local_header)));
313 token = strtok_r(local_header, "|\r\n", &saveptr);
314 while (token != NULL) {
315 while (*token == ' ') token++;
316 char *end = token + strlen(token) - 1;
317 while (end > token && (*end == ' ' || *end == '\r' || *end == '\n')) end--;
318 *(end + 1) = '\0';
319 if (strcmp(token, column_name) == 0) {
320 *index_out = index;
321 PetscFunctionReturn(0);
322 }
323 token = strtok_r(NULL, "|\r\n", &saveptr);
324 index++;
325 }
326 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log column '%s' was not found.", column_name);
327}
328
329/**
330 * @brief Extracts one pipe-delimited log cell as text by header name.
331 */
332static PetscErrorCode LogGetColumnText(const char *header,
333 const char *row,
334 const char *column_name,
335 char *value,
336 size_t value_len)
337{
338 char local_row[8192];
339 char *saveptr = NULL;
340 char *token = NULL;
341 PetscInt target_index = -1;
342 PetscInt index = 0;
343
344 PetscFunctionBeginUser;
345 PetscCheck(row != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log row cannot be NULL.");
346 PetscCheck(value != NULL && value_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log value buffer cannot be NULL.");
347
348 PetscCall(LogFindColumnIndex(header, column_name, &target_index));
349 PetscCall(PetscStrncpy(local_row, row, sizeof(local_row)));
350
351 token = strtok_r(local_row, "|\r\n", &saveptr);
352 while (token != NULL) {
353 if (index == target_index) {
354 while (*token == ' ') token++;
355 char *end = token + strlen(token) - 1;
356 while (end > token && (*end == ' ' || *end == '\r' || *end == '\n')) end--;
357 *(end + 1) = '\0';
358 PetscCall(PetscStrncpy(value, token, value_len));
359 PetscFunctionReturn(0);
360 }
361 token = strtok_r(NULL, "|\r\n", &saveptr);
362 index++;
363 }
364 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log row is missing column '%s'.", column_name);
365}
366
367/**
368 * @brief Extracts one pipe-delimited log cell as an integer by header name.
369 */
370static PetscErrorCode LogGetColumnInt(const char *header, const char *row, const char *column_name, PetscInt *value_out)
371{
372 char text[256];
373 char *endptr = NULL;
374 long parsed = 0;
375
376 PetscFunctionBeginUser;
377 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log integer output cannot be NULL.");
378 PetscCall(LogGetColumnText(header, row, column_name, text, sizeof(text)));
379 parsed = strtol(text, &endptr, 10);
380 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log column '%s' did not contain an integer.", column_name);
381 *value_out = (PetscInt)parsed;
382 PetscFunctionReturn(0);
383}
384
385/**
386 * @brief Extracts one pipe-delimited log cell as a real by header name.
387 */
388static PetscErrorCode LogGetColumnReal(const char *header, const char *row, const char *column_name, PetscReal *value_out)
389{
390 char text[256];
391 char *endptr = NULL;
392 double parsed = 0.0;
393
394 PetscFunctionBeginUser;
395 PetscCheck(value_out != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Log real output cannot be NULL.");
396 PetscCall(LogGetColumnText(header, row, column_name, text, sizeof(text)));
397 parsed = strtod(text, &endptr);
398 PetscCheck(endptr != text, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Log column '%s' did not contain a real value.", column_name);
399 *value_out = (PetscReal)parsed;
400 PetscFunctionReturn(0);
401}
402
403typedef PetscErrorCode (*CapturedLoggingFn)(UserCtx *user, SimCtx *simCtx, void *ctx);
404
409
410/**
411 * @brief Captures stdout emitted by one logging helper into a temporary file-backed buffer.
412 */
413static PetscErrorCode CaptureLoggingOutput(UserCtx *user,
414 SimCtx *simCtx,
416 void *ctx,
417 char *captured,
418 size_t captured_len)
419{
420 char tmpdir[PETSC_MAX_PATH_LEN];
421 char capture_path[PETSC_MAX_PATH_LEN];
422 FILE *capture_file = NULL;
423 int saved_stdout = -1;
424 int capture_fd = -1;
425 size_t bytes_read = 0;
426 PetscErrorCode ierr;
427
428 PetscFunctionBeginUser;
429 PetscCheck(fn != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Capture callback cannot be NULL.");
430 PetscCheck(captured != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Capture buffer cannot be NULL.");
431 PetscCheck(captured_len > 0, PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "Capture buffer must be non-empty.");
432
433 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
434 PetscCall(PetscSNPrintf(capture_path, sizeof(capture_path), "%s/logging.out", tmpdir));
435
436 fflush(stdout);
437 saved_stdout = dup(STDOUT_FILENO);
438 PetscCheck(saved_stdout >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup(STDOUT_FILENO) failed.");
439 capture_fd = open(capture_path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
440 PetscCheck(capture_fd >= 0, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to open capture file '%s'.", capture_path);
441 PetscCheck(dup2(capture_fd, STDOUT_FILENO) >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup2() failed while redirecting stdout.");
442 close(capture_fd);
443 capture_fd = -1;
444
445 ierr = fn(user, simCtx, ctx);
446 fflush(stdout);
447 PetscCheck(dup2(saved_stdout, STDOUT_FILENO) >= 0, PETSC_COMM_SELF, PETSC_ERR_SYS, "dup2() failed while restoring stdout.");
448 close(saved_stdout);
449 saved_stdout = -1;
450 PetscCall(ierr);
451
452 capture_file = fopen(capture_path, "r");
453 PetscCheck(capture_file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to read capture file '%s'.", capture_path);
454 bytes_read = fread(captured, 1, captured_len - 1, capture_file);
455 captured[bytes_read] = '\0';
456 fclose(capture_file);
457 PetscCall(PicurvRemoveTempDir(tmpdir));
458 PetscFunctionReturn(0);
459}
460
461/**
462 * @brief Adapts `LOG_PARTICLE_FIELDS()` to the generic stdout-capture callback shape.
463 */
464static PetscErrorCode InvokeParticleFieldLog(UserCtx *user, SimCtx *simCtx, void *ctx)
465{
466 PetscInt print_interval = *((PetscInt *)ctx);
467
468 PetscFunctionBeginUser;
469 (void)simCtx;
470 PetscCall(LOG_PARTICLE_FIELDS(user, print_interval));
471 PetscFunctionReturn(0);
472}
473
474/**
475 * @brief Adapts `EmitParticleConsoleSnapshot()` to the generic stdout-capture callback shape.
476 */
477static PetscErrorCode InvokeParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, void *ctx)
478{
479 PetscInt step = *((PetscInt *)ctx);
480
481 PetscFunctionBeginUser;
482 PetscCall(EmitParticleConsoleSnapshot(user, simCtx, step));
483 PetscFunctionReturn(0);
484}
485
486/**
487 * @brief Adapts `EmitStatisticsConsoleSnapshot()` to the generic stdout-capture callback shape.
488 */
489static PetscErrorCode InvokeStatisticsConsoleSnapshot(UserCtx *user, SimCtx *simCtx, void *ctx)
490{
491 PetscInt step = *((PetscInt *)ctx);
492
493 PetscFunctionBeginUser;
494 PetscCall(EmitStatisticsConsoleSnapshot(user, simCtx, step));
495 PetscFunctionReturn(0);
496}
497
498/**
499 * @brief Adapts `LOG_FIELD_ANATOMY()` to the generic stdout-capture callback shape.
500 */
501static PetscErrorCode InvokeFieldAnatomyLog(UserCtx *user, SimCtx *simCtx, void *ctx)
502{
503 const AnatomyCaptureCtx *anatomy_ctx = (const AnatomyCaptureCtx *)ctx;
504
505 PetscFunctionBeginUser;
506 (void)simCtx;
507 PetscCall(LOG_FIELD_ANATOMY(user, anatomy_ctx->field_id, anatomy_ctx->stage_name));
508 PetscFunctionReturn(0);
509}
510
511/**
512 * @brief Creates a small particle-bearing runtime fixture used by logging tests.
513 */
514static PetscErrorCode SeedLoggingParticleFixture(SimCtx **simCtx_out, UserCtx **user_out)
515{
516 PetscReal *positions = NULL;
517 PetscReal *velocities = NULL;
518 PetscReal *weights = NULL;
519 PetscInt *cell_ids = NULL;
520 PetscInt *status = NULL;
521 PetscReal *psi = NULL;
522
523 PetscFunctionBeginUser;
524 PetscCall(PicurvCreateMinimalContexts(simCtx_out, user_out, 4, 4, 4));
525 PetscCall(PicurvCreateSwarmPair(*user_out, 2, "ske"));
526 (*simCtx_out)->np = 2;
527 (*simCtx_out)->particleConsoleOutputFreq = 2;
528 (*simCtx_out)->LoggingFrequency = 1;
529
530 PetscCall(DMSwarmGetField((*user_out)->swarm, "position", NULL, NULL, (void **)&positions));
531 PetscCall(DMSwarmGetField((*user_out)->swarm, "velocity", NULL, NULL, (void **)&velocities));
532 PetscCall(DMSwarmGetField((*user_out)->swarm, "weight", NULL, NULL, (void **)&weights));
533 PetscCall(DMSwarmGetField((*user_out)->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
534 PetscCall(DMSwarmGetField((*user_out)->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
535 PetscCall(DMSwarmGetField((*user_out)->swarm, "Psi", NULL, NULL, (void **)&psi));
536
537 positions[0] = 0.25; positions[1] = 0.50; positions[2] = 0.75;
538 positions[3] = 0.50; positions[4] = 0.50; positions[5] = 0.50;
539 velocities[0] = 1.0; velocities[1] = 2.0; velocities[2] = 3.0;
540 velocities[3] = 4.0; velocities[4] = 5.0; velocities[5] = 6.0;
541 weights[0] = 0.2; weights[1] = 0.3; weights[2] = 0.4;
542 weights[3] = 0.5; weights[4] = 0.5; weights[5] = 0.5;
543 cell_ids[0] = 0; cell_ids[1] = 0; cell_ids[2] = 0;
544 cell_ids[3] = 1; cell_ids[4] = 1; cell_ids[5] = 1;
545 status[0] = ACTIVE_AND_LOCATED;
546 status[1] = ACTIVE_AND_LOCATED;
547 psi[0] = 1.0;
548 psi[1] = 3.0;
549
550 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "Psi", NULL, NULL, (void **)&psi));
551 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
552 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
553 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "weight", NULL, NULL, (void **)&weights));
554 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "velocity", NULL, NULL, (void **)&velocities));
555 PetscCall(DMSwarmRestoreField((*user_out)->swarm, "position", NULL, NULL, (void **)&positions));
556 PetscFunctionReturn(0);
557}
558
559/**
560 * @brief Fills one Cartesian velocity field with a uniform constant state.
561 */
562static PetscErrorCode SetUniformVelocityField(UserCtx *user, Vec field, PetscReal ux, PetscReal uy, PetscReal uz)
563{
564 Cmpnts ***arr = NULL;
565
566 PetscFunctionBeginUser;
567 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
568 PetscCheck(field != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Velocity field cannot be NULL.");
569
570 PetscCall(DMDAVecGetArray(user->fda, field, &arr));
571 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
572 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
573 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
574 arr[k][j][i].x = ux;
575 arr[k][j][i].y = uy;
576 arr[k][j][i].z = uz;
577 }
578 }
579 }
580 PetscCall(DMDAVecRestoreArray(user->fda, field, &arr));
581 PetscFunctionReturn(0);
582}
583
584/**
585 * @brief Fills one scalar field with a uniform constant state.
586 */
587static PetscErrorCode SetUniformScalarField(UserCtx *user, Vec field, PetscReal value)
588{
589 PetscReal ***arr = NULL;
590
591 PetscFunctionBeginUser;
592 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
593 PetscCheck(field != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Scalar field cannot be NULL.");
594
595 PetscCall(DMDAVecGetArray(user->da, field, &arr));
596 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
597 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
598 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
599 arr[k][j][i] = value;
600 }
601 }
602 }
603 PetscCall(DMDAVecRestoreArray(user->da, field, &arr));
604 PetscFunctionReturn(0);
605}
606/**
607 * @brief Fills one Cartesian velocity field with u=0, v=v_amp*sin(2π*k/km), w=w_const.
608 *
609 * Sets the sinusoidal profile at ALL owned nodes (including the periodic duplicate endpoint),
610 * matching how the IC routines populate fields before any periodic-endpoint fix is applied.
611 */
612static PetscErrorCode SetSinusoidalVZField(UserCtx *user, Vec field, PetscReal v_amp, PetscReal w_const)
613{
614 Cmpnts ***arr = NULL;
615 PetscInt km = user->KM;
616
617 PetscFunctionBeginUser;
618 PetscCheck(user != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "UserCtx cannot be NULL.");
619 PetscCheck(field != NULL, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Velocity field cannot be NULL.");
620
621 PetscCall(DMDAVecGetArray(user->fda, field, &arr));
622 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
623 PetscReal z_phase = (2.0 * PETSC_PI * (PetscReal)k) / (PetscReal)km;
624 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
625 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
626 arr[k][j][i].x = 0.0;
627 arr[k][j][i].y = v_amp * PetscSinReal(z_phase);
628 arr[k][j][i].z = w_const;
629 }
630 }
631 }
632 PetscCall(DMDAVecRestoreArray(user->fda, field, &arr));
633 PetscFunctionReturn(0);
634}
635
636/**
637 * @brief Tests string-conversion helpers for configured enums and unknown values.
638 */
639
640static PetscErrorCode TestStringConversionHelpers(void)
641{
642 PetscFunctionBeginUser;
643 PetscCall(PicurvAssertBool(strcmp(BCFaceToString(BC_FACE_NEG_X), "-Xi (I-Min)") == 0,
644 "BCFaceToString should report the negative-x face"));
645 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_ZERO), "Zero") == 0,
646 "InitialConditionModeToString should report the zero mode"));
647 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_CONSTANT_CARTESIAN), "Cartesian Constant") == 0,
648 "InitialConditionModeToString should report the Cartesian constant mode"));
649 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_CONSTANT_STREAMWISE), "Streamwise Constant") == 0,
650 "InitialConditionModeToString should report the streamwise constant mode"));
651 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_POISEUILLE), "Poiseuille") == 0,
652 "InitialConditionModeToString should report the Poiseuille mode"));
653 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString(IC_MODE_FILE), "File") == 0,
654 "InitialConditionModeToString should report the file mode"));
655 PetscCall(PicurvAssertBool(strcmp(InitialConditionModeToString((InitialConditionMode)99), "Unknown Initial Condition") == 0,
656 "InitialConditionModeToString should reject unknown selectors"));
658 "ParticleInitializationToString should report the volume mode"));
659 PetscCall(PicurvAssertBool(strcmp(LESModelToString(CONSTANT_SMAGORINSKY), "Constant Smagorinsky") == 0,
660 "LESModelToString should report the constant model"));
661 PetscCall(PicurvAssertBool(strcmp(MomentumSolverTypeToString(MOMENTUM_SOLVER_EXPLICIT_RK), "Explicit 4 stage Runge-Kutta ") == 0,
662 "MomentumSolverTypeToString should report the explicit solver"));
663 PetscCall(PicurvAssertBool(strcmp(MomentumSolverTypeToString(MOMENTUM_SOLVER_NEWTON_KRYLOV), "Newton Krylov") == 0,
664 "MomentumSolverTypeToString should report the Newton Krylov solver"));
665 PetscCall(PicurvAssertBool(strcmp(BCTypeToString(PERIODIC), "PERIODIC") == 0,
666 "BCTypeToString should report periodic boundaries"));
668 "BCHandlerTypeToString should report the driven periodic handler"));
669 PetscCall(PicurvAssertBool(strcmp(ParticleLocationStatusToString(LOST), "LOST") == 0,
670 "ParticleLocationStatusToString should report LOST state"));
671 PetscFunctionReturn(0);
672}
673/**
674 * @brief Tests that log level selection honors the environment variable.
675 */
676
677static PetscErrorCode TestGetLogLevelFromEnvironment(void)
678{
679 PetscFunctionBeginUser;
681 "get_log_level should honor LOG_LEVEL=INFO in this test binary"));
682 PetscCall(print_log_level());
683 PetscFunctionReturn(0);
684}
685/**
686 * @brief Tests the function allow-list filter used by the logging layer.
687 */
688
689static PetscErrorCode TestAllowedFunctionsFilter(void)
690{
691 const char *allow_list[] = {"ComputeSpecificKE", "WriteEulerianFile"};
692
693 PetscFunctionBeginUser;
694 set_allowed_functions(allow_list, 2);
695 PetscCall(PicurvAssertBool(is_function_allowed("ComputeSpecificKE"),
696 "Allowed list should include ComputeSpecificKE"));
697 PetscCall(PicurvAssertBool((PetscBool)!is_function_allowed("UnlistedFunction"),
698 "Allowed list should exclude unknown function names"));
699
700 set_allowed_functions(NULL, 0);
701 PetscCall(PicurvAssertBool(is_function_allowed("AnyFunction"),
702 "Empty allow-list should permit all functions"));
703 PetscFunctionReturn(0);
704}
705/**
706 * @brief Tests periodic particle console snapshot enablement and cadence.
707 */
708
709static PetscErrorCode TestParticleConsoleSnapshotCadence(void)
710{
711 SimCtx simCtx;
712
713 PetscFunctionBeginUser;
714 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
715 simCtx.np = 32;
716 simCtx.particleConsoleOutputFreq = 4;
717
719 "Particle snapshot contract should be enabled when particles and cadence are configured"));
721 "Snapshot should emit on cadence-aligned completed steps"));
722 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicParticleConsoleSnapshot(&simCtx, 7),
723 "Snapshot should not emit off-cadence"));
724
725 simCtx.particleConsoleOutputFreq = 0;
726 PetscCall(PicurvAssertBool((PetscBool)!IsParticleConsoleSnapshotEnabled(&simCtx),
727 "Zero cadence should disable periodic particle snapshots"));
728 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicParticleConsoleSnapshot(NULL, 4),
729 "NULL SimCtx should never emit periodic snapshots"));
730 PetscFunctionReturn(0);
731}
732/**
733 * @brief Builds the window definition backing this suite's statistics console fixture.
734 *
735 * One bounded pressure window on a per-step cadence: bounded so the snapshot exercises
736 * its percentage-progress branch, and a single field so the fixture stays cheap.
737 */
739{
740 PicurvWindowDefinition definition;
741
742 memset(&definition, 0, sizeof(definition));
743 strncpy(definition.name, "console_window", PICURV_WINDOW_NAME_LENGTH - 1);
746 definition.step_cadence = 1;
747 definition.bounded = PETSC_TRUE;
748 definition.end_time = 4.0;
749 definition.field_count = 1;
750 definition.fields[0].field_id = FIELD_ID_P;
751 return definition;
752}
753/**
754 * @brief Tests periodic statistics console snapshot enablement and cadence.
755 */
756
757static PetscErrorCode TestStatisticsConsoleSnapshotCadence(void)
758{
759 SimCtx simCtx;
760 PicurvWindow window;
762
763 PetscFunctionBeginUser;
764 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
765 PetscCall(PicurvWindowInit(&window, &definition));
766 simCtx.fieldStatisticsEnabled = PETSC_TRUE;
768 simCtx.fieldStatisticsWindows = &window;
770
772 "Statistics snapshot contract should be enabled when a window and cadence are configured"));
774 "Snapshot should emit on cadence-aligned completed steps"));
775 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicStatisticsConsoleSnapshot(&simCtx, 7),
776 "Snapshot should not emit off-cadence"));
777 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicStatisticsConsoleSnapshot(&simCtx, -1),
778 "Snapshot should not emit for a step that has not completed"));
779
781 PetscCall(PicurvAssertBool((PetscBool)!IsStatisticsConsoleSnapshotEnabled(&simCtx),
782 "Zero cadence should disable periodic statistics snapshots"));
783 PetscCall(PicurvAssertBool((PetscBool)!ShouldEmitPeriodicStatisticsConsoleSnapshot(&simCtx, 0),
784 "Zero cadence should not emit even on the step that opens a run"));
785
786 /* The console cadence is a reporting contract layered on the subsystem gate: with
787 * nothing accumulating there is nothing to report, whatever the cadence says. */
790 PetscCall(PicurvAssertBool((PetscBool)!IsStatisticsConsoleSnapshotEnabled(&simCtx),
791 "A configured cadence should not enable snapshots without an accumulating window"));
792
794 simCtx.fieldStatisticsEnabled = PETSC_FALSE;
795 PetscCall(PicurvAssertBool((PetscBool)!IsStatisticsConsoleSnapshotEnabled(&simCtx),
796 "A disabled subsystem should not emit statistics snapshots"));
797
799 "NULL SimCtx should never emit periodic snapshots"));
800 PetscFunctionReturn(0);
801}
802/**
803 * @brief Tests logging-side file parsing, helper formatting, and progress utilities.
804 */
805
807{
808 char tmpdir[PETSC_MAX_PATH_LEN];
809 char allow_path[PETSC_MAX_PATH_LEN];
810 char dual_log_path[PETSC_MAX_PATH_LEN];
811 FILE *file = NULL;
812 char **funcs = NULL;
813 PetscInt nfuncs = 0;
814 Cell cell;
815 PetscReal distances[6] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
816 DualMonitorCtx *monctx = NULL;
817 void *ctx = NULL;
818
819 PetscFunctionBeginUser;
820 PetscCall(PetscMemzero(&cell, sizeof(cell)));
821 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
822 PetscCall(PetscSNPrintf(allow_path, sizeof(allow_path), "%s/allowed_functions.txt", tmpdir));
823 PetscCall(PetscSNPrintf(dual_log_path, sizeof(dual_log_path), "%s/dual-monitor.log", tmpdir));
824
825 file = fopen(allow_path, "w");
826 PetscCheck(file != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to create allow-list file '%s'.", allow_path);
827 fputs(" ComputeSpecificKE \n", file);
828 fputs("# comment-only line\n", file);
829 for (PetscInt i = 0; i < 17; ++i) {
830 fprintf(file, "Helper_%02d # trailing comment\n", (int)i);
831 }
832 fclose(file);
833 file = NULL;
834
835 PetscCall(LoadAllowedFunctionsFromFile(allow_path, &funcs, &nfuncs));
836 PetscCall(PicurvAssertIntEqual(18, nfuncs, "LoadAllowedFunctionsFromFile should trim comments and keep all identifiers"));
837 PetscCall(PicurvAssertBool((PetscBool)(strcmp(funcs[0], "ComputeSpecificKE") == 0),
838 "LoadAllowedFunctionsFromFile should trim leading and trailing whitespace"));
839 PetscCall(PicurvAssertBool((PetscBool)(strcmp(funcs[17], "Helper_16") == 0),
840 "LoadAllowedFunctionsFromFile should grow past the initial pointer capacity"));
841 PetscCall(FreeAllowedFunctions(funcs, nfuncs));
842
843 for (PetscInt i = 0; i < 8; ++i) {
844 cell.vertices[i].x = (PetscReal)i;
845 cell.vertices[i].y = (PetscReal)(i + 1);
846 cell.vertices[i].z = (PetscReal)(i + 2);
847 }
848 PetscCall(LOG_CELL_VERTICES(&cell, 0));
849 PetscCall(LOG_FACE_DISTANCES(distances));
850
851 PetscCall(PetscCalloc1(1, &monctx));
852 monctx->file_handle = fopen(dual_log_path, "w");
853 PetscCheck(monctx->file_handle != NULL, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Failed to create dual-monitor log '%s'.", dual_log_path);
854 ctx = monctx;
855 PetscCall(DualMonitorDestroy(&ctx));
856 PetscCall(PicurvAssertBool((PetscBool)(ctx == NULL),
857 "DualMonitorDestroy should clear the caller-owned context pointer"));
858
859 PrintProgressBar(0, 0, 4, 0.10);
860 PrintProgressBar(3, 0, 4, 0.40);
861 PrintProgressBar(0, 0, 0, 0.00);
862 PetscCall(PetscPrintf(PETSC_COMM_SELF, "\n"));
863
864 PetscCall(PicurvRemoveTempDir(tmpdir));
865 PetscFunctionReturn(0);
866}
867/**
868 * @brief Tests continuity, min/max, and anatomy logging helpers on minimal runtime fixtures.
869 */
870
872{
873 SimCtx *simCtx = NULL;
874 UserCtx *user = NULL;
875 char tmpdir[PETSC_MAX_PATH_LEN];
876 char continuity_path[PETSC_MAX_PATH_LEN];
877 PetscReal ***p = NULL;
878 Cmpnts ***ucat = NULL;
879 Cmpnts ***ucont = NULL;
880 PetscErrorCode ierr_minmax = 0;
881
882 PetscFunctionBeginUser;
883 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
884 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
885 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
886
887 simCtx->StartStep = 0;
888 simCtx->step = 1;
889 simCtx->MaxDiv = 1.25;
890 simCtx->MaxDivx = 1;
891 simCtx->MaxDivy = 2;
892 simCtx->MaxDivz = 3;
893 simCtx->MaxDivFlatArg = 17;
894 simCtx->summationRHS = 8.5;
895 simCtx->FluxInSum = 5.0;
896 simCtx->FluxOutSum = 3.25;
897 PetscCall(LOG_CONTINUITY_METRICS(user));
898
899 simCtx->step = 2;
900 simCtx->MaxDiv = 0.75;
901 simCtx->summationRHS = 4.5;
902 simCtx->FluxInSum = 2.5;
903 simCtx->FluxOutSum = 1.0;
904 PetscCall(LOG_CONTINUITY_METRICS(user));
905
906 PetscCall(PetscSNPrintf(continuity_path, sizeof(continuity_path), "%s/Continuity_Metrics.log", simCtx->log_dir));
907 PetscCall(PicurvAssertFileExists(continuity_path, "continuity metrics log should be written"));
908 PetscCall(AssertFileContains(continuity_path, "Timestep", "continuity metrics log should include the header"));
909 PetscCall(AssertFileContains(continuity_path, "([3][2][1] = 17)", "continuity metrics log should include the divergence location"));
910 PetscCall(AssertFileContains(continuity_path, "2 | 0", "continuity metrics log should append later timesteps"));
911
912 PetscCall(DMDAVecGetArray(user->da, user->P, &p));
913 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
914 PetscCall(DMDAVecGetArray(user->fda, user->Ucont, &ucont));
915 for (PetscInt k = user->info.zs; k < user->info.zs + user->info.zm; ++k) {
916 for (PetscInt j = user->info.ys; j < user->info.ys + user->info.ym; ++j) {
917 for (PetscInt i = user->info.xs; i < user->info.xs + user->info.xm; ++i) {
918 p[k][j][i] = (PetscReal)(i + j + k);
919 ucat[k][j][i].x = (PetscReal)i;
920 ucat[k][j][i].y = (PetscReal)(-j);
921 ucat[k][j][i].z = (PetscReal)(2 * k);
922 ucont[k][j][i].x = (PetscReal)(10 + i);
923 ucont[k][j][i].y = (PetscReal)(20 + j);
924 ucont[k][j][i].z = (PetscReal)(30 + k);
925 }
926 }
927 }
928 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucont, &ucont));
929 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
930 PetscCall(DMDAVecRestoreArray(user->da, user->P, &p));
931
932 PetscCall(DMGlobalToLocalBegin(user->da, user->P, INSERT_VALUES, user->lP));
933 PetscCall(DMGlobalToLocalEnd(user->da, user->P, INSERT_VALUES, user->lP));
934 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
935 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucat, INSERT_VALUES, user->lUcat));
936 PetscCall(DMGlobalToLocalBegin(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
937 PetscCall(DMGlobalToLocalEnd(user->fda, user->Ucont, INSERT_VALUES, user->lUcont));
938
939 PetscCall(LOG_FIELD_MIN_MAX(user, FIELD_ID_P));
940 PetscCall(LOG_FIELD_MIN_MAX(user, FIELD_ID_UCAT));
941 PetscCall(LOG_FIELD_MIN_MAX(user, FIELD_ID_COORDINATES));
942 PetscCall(LOG_FIELD_MIN_MAX(user, FIELD_ID_UCONT));
943
944 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
945 ierr_minmax = LOG_FIELD_MIN_MAX(user, FIELD_ID_INVALID);
946 PetscCall(PetscPopErrorHandler());
947 PetscCall(PicurvAssertBool((PetscBool)(ierr_minmax != 0),
948 "LOG_FIELD_MIN_MAX should reject invalid field IDs"));
949
950 PetscCall(PicurvRemoveTempDir(tmpdir));
951 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
952 PetscFunctionReturn(0);
953}
954/**
955 * @brief Tests interpolation-error logging against an analytically matched particle field.
956 */
957
958static PetscErrorCode TestInterpolationErrorLogging(void)
959{
960 SimCtx *simCtx = NULL;
961 UserCtx *user = NULL;
962 PetscReal (*pos_arr)[3] = NULL;
963 PetscReal (*vel_arr)[3] = NULL;
964 Vec position_vec = NULL;
965 Vec analytical_vec = NULL;
966 const PetscScalar *analytical_arr = NULL;
967
968 PetscFunctionBeginUser;
969 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
970 PetscCall(PicurvCreateSwarmPair(user, 2, "ske"));
971 PetscCall(PetscStrncpy(simCtx->AnalyticalSolutionType, "TGV3D", sizeof(simCtx->AnalyticalSolutionType)));
972 simCtx->ren = 1.0;
973 simCtx->ti = 0.0;
974
975 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void *)&pos_arr));
976 pos_arr[0][0] = 0.5 * PETSC_PI; pos_arr[0][1] = 0.0; pos_arr[0][2] = 0.0;
977 pos_arr[1][0] = 0.0; pos_arr[1][1] = 0.5 * PETSC_PI; pos_arr[1][2] = 0.0;
978 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void *)&pos_arr));
979
980 PetscCall(DMSwarmCreateGlobalVectorFromField(user->swarm, "position", &position_vec));
981 PetscCall(VecDuplicate(position_vec, &analytical_vec));
982 PetscCall(VecCopy(position_vec, analytical_vec));
983 PetscCall(SetAnalyticalSolutionForParticles(analytical_vec, simCtx));
984
985 PetscCall(DMSwarmGetField(user->swarm, "velocity", NULL, NULL, (void *)&vel_arr));
986 PetscCall(VecGetArrayRead(analytical_vec, &analytical_arr));
987 for (PetscInt particle = 0; particle < 2; ++particle) {
988 vel_arr[particle][0] = PetscRealPart(analytical_arr[3 * particle + 0]);
989 vel_arr[particle][1] = PetscRealPart(analytical_arr[3 * particle + 1]);
990 vel_arr[particle][2] = PetscRealPart(analytical_arr[3 * particle + 2]);
991 }
992 PetscCall(VecRestoreArrayRead(analytical_vec, &analytical_arr));
993 PetscCall(DMSwarmRestoreField(user->swarm, "velocity", NULL, NULL, (void *)&vel_arr));
994
995 PetscCall(VecDestroy(&analytical_vec));
996 PetscCall(DMSwarmDestroyGlobalVectorFromField(user->swarm, "position", &position_vec));
997 PetscCall(LOG_INTERPOLATION_ERROR(user));
998 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
999 PetscFunctionReturn(0);
1000}
1001/**
1002 * @brief Tests file-backed scatter metrics logging against a fully occupied constant field.
1003 */
1004
1005static PetscErrorCode TestScatterMetricsLogging(void)
1006{
1007 SimCtx *simCtx = NULL;
1008 UserCtx *user = NULL;
1009 char tmpdir[PETSC_MAX_PATH_LEN];
1010 char metrics_path[PETSC_MAX_PATH_LEN];
1011 PetscReal *positions = NULL;
1012 PetscReal *psi = NULL;
1013 PetscInt *cell_ids = NULL;
1014 PetscInt *status = NULL;
1015 PetscInt particle = 0;
1016
1017 PetscFunctionBeginUser;
1018 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1019 PetscCall(PicurvCreateSwarmPair(user, 27, "ske"));
1020 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1021 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1022 simCtx->np = 27;
1023 simCtx->step = 3;
1024 simCtx->ti = 0.3;
1025 simCtx->verificationScalar.enabled = PETSC_TRUE;
1026 PetscCall(PetscStrncpy(simCtx->verificationScalar.mode,
1027 "analytical",
1028 sizeof(simCtx->verificationScalar.mode)));
1029 PetscCall(PetscStrncpy(simCtx->verificationScalar.profile,
1030 "CONSTANT",
1031 sizeof(simCtx->verificationScalar.profile)));
1032 simCtx->verificationScalar.value = 2.0;
1033
1034 PetscCall(DMSwarmGetField(user->swarm, "position", NULL, NULL, (void **)&positions));
1035 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1036 PetscCall(DMSwarmGetField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1037 PetscCall(DMSwarmGetField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
1038
1039 for (PetscInt k = 0; k < 3; ++k) {
1040 for (PetscInt j = 0; j < 3; ++j) {
1041 for (PetscInt i = 0; i < 3; ++i) {
1042 positions[3 * particle + 0] = (i + 0.5) / 4.0;
1043 positions[3 * particle + 1] = (j + 0.5) / 4.0;
1044 positions[3 * particle + 2] = (k + 0.5) / 4.0;
1045 cell_ids[3 * particle + 0] = i;
1046 cell_ids[3 * particle + 1] = j;
1047 cell_ids[3 * particle + 2] = k;
1048 status[particle] = ACTIVE_AND_LOCATED;
1049 psi[particle] = 2.0;
1050 ++particle;
1051 }
1052 }
1053 }
1054
1055 PetscCall(DMSwarmRestoreField(user->swarm, "Psi", NULL, NULL, (void **)&psi));
1056 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_location_status", NULL, NULL, (void **)&status));
1057 PetscCall(DMSwarmRestoreField(user->swarm, "DMSwarm_CellID", NULL, NULL, (void **)&cell_ids));
1058 PetscCall(DMSwarmRestoreField(user->swarm, "position", NULL, NULL, (void **)&positions));
1059
1061 PetscCall(LOG_SCATTER_METRICS(user));
1062
1063 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/scatter_metrics.csv", simCtx->log_dir));
1064 PetscCall(PicurvAssertFileExists(metrics_path, "LOG_SCATTER_METRICS should write scatter_metrics.csv"));
1065 PetscCall(AssertFileContains(metrics_path, "relative_L2_error",
1066 "Scatter metrics CSV header should include relative_L2_error"));
1067 PetscCall(AssertFileContains(metrics_path,
1068 "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",
1069 "Scatter metrics CSV should record the expected constant-field zero-error row"));
1070
1071 PetscCall(PicurvRemoveTempDir(tmpdir));
1072 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1073 PetscFunctionReturn(0);
1074}
1075/**
1076 * @brief Tests stdout particle-table logging on a production-like swarm fixture.
1077 */
1078
1079static PetscErrorCode TestParticleFieldTableLogging(void)
1080{
1081 SimCtx *simCtx = NULL;
1082 UserCtx *user = NULL;
1083 PetscInt print_interval = 1;
1084 char captured[8192];
1085
1086 PetscFunctionBeginUser;
1087 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1088 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeParticleFieldLog, &print_interval, captured, sizeof(captured)));
1089 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Position (x,y,z)") != NULL),
1090 "LOG_PARTICLE_FIELDS should print the particle table header"));
1091 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Weights (a1,a2,a3)") != NULL),
1092 "LOG_PARTICLE_FIELDS should print the weight-column header"));
1093 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1094 PetscFunctionReturn(0);
1095}
1096/**
1097 * @brief Tests console snapshot logging against the public periodic-snapshot helper.
1098 */
1099
1100static PetscErrorCode TestParticleConsoleSnapshotLogging(void)
1101{
1102 SimCtx *simCtx = NULL;
1103 UserCtx *user = NULL;
1104 PetscInt step = 4;
1105 char captured[8192];
1106
1107 PetscFunctionBeginUser;
1108 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1109 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeParticleConsoleSnapshot, &step, captured, sizeof(captured)));
1110 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Particle states at step 4") != NULL),
1111 "EmitParticleConsoleSnapshot should print the step banner"));
1112 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Position (x,y,z)") != NULL),
1113 "EmitParticleConsoleSnapshot should reuse the particle table output"));
1114 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1115 PetscFunctionReturn(0);
1116}
1117/**
1118 * @brief Tests statistics console snapshot content, and its silence when disabled.
1119 *
1120 * Covers both halves of the monitoring contract from one fixture: an active window
1121 * reports its window-level scalars, and the identical call on a disabled subsystem
1122 * writes nothing at all, which is the observable form of the gate the runloop relies
1123 * on to keep a non-statistics run's log untouched.
1124 */
1125
1126static PetscErrorCode TestStatisticsConsoleSnapshotLogging(void)
1127{
1128 SimCtx *simCtx = NULL;
1129 UserCtx *user = NULL;
1130 PicurvWindow window;
1131 PicurvWindowStorage storage;
1133 PetscInt step = 2;
1134 char captured[8192];
1135
1136 PetscFunctionBeginUser;
1137 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1138 PetscCall(VecSet(user->Nvert, 0.0));
1139 PetscCall(PicurvWindowInit(&window, &definition));
1140 PetscCall(PicurvWindowStorageCreate(user, &definition, &storage));
1141
1142 simCtx->fieldStatisticsEnabled = PETSC_TRUE;
1143 simCtx->fieldStatisticsWindowCount = 1;
1144 simCtx->fieldStatisticsWindows = &window;
1145 simCtx->statisticsConsoleOutputFreq = 1;
1146 user->fieldStatisticsStorage = &storage;
1147
1148 /* Step 0 anchors the origin without sampling; steps 1 and 2 each represent one
1149 * unit of time, so the snapshot has two accepted samples to report. */
1150 for (PetscInt offered = 0; offered <= step; ++offered) {
1151 PetscCall(FieldStatisticsUpdateWindows(simCtx, offered, (PetscReal)offered));
1152 }
1153
1154 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeStatisticsConsoleSnapshot, &step,
1155 captured, sizeof(captured)));
1156 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Statistics windows at step 2") != NULL),
1157 "EmitStatisticsConsoleSnapshot should print the step banner"));
1158 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "console_window") != NULL),
1159 "EmitStatisticsConsoleSnapshot should name each configured window"));
1160 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "samples=2") != NULL),
1161 "EmitStatisticsConsoleSnapshot should report the accepted sample count"));
1162 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "valid=[") != NULL),
1163 "EmitStatisticsConsoleSnapshot should report mask coverage once storage carries samples"));
1164
1165 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
1166 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeStatisticsConsoleSnapshot, &step,
1167 captured, sizeof(captured)));
1168 PetscCall(PicurvAssertIntEqual(0, (PetscInt)strlen(captured),
1169 "a disabled subsystem emits no statistics console output"));
1170
1171 simCtx->fieldStatisticsWindows = NULL;
1172 simCtx->fieldStatisticsWindowCount = 0;
1173 user->fieldStatisticsStorage = NULL;
1174 PetscCall(PicurvWindowStorageDestroy(&storage));
1175 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1176 PetscFunctionReturn(0);
1177}
1178/**
1179 * @brief Sets the pressure field to a uniform value across the owned range.
1180 */
1181static PetscErrorCode SetUniformPressure(UserCtx *user, PetscReal value)
1182{
1183 PetscReal ***p = NULL;
1184 const DMDALocalInfo info = user->info;
1185
1186 PetscFunctionBeginUser;
1187 PetscCall(DMDAVecGetArray(user->da, user->P, &p));
1188 for (PetscInt k = info.zs; k < info.zs + info.zm; ++k)
1189 for (PetscInt j = info.ys; j < info.ys + info.ym; ++j)
1190 for (PetscInt i = info.xs; i < info.xs + info.xm; ++i) p[k][j][i] = value;
1191 PetscCall(DMDAVecRestoreArray(user->da, user->P, &p));
1192 PetscFunctionReturn(0);
1193}
1194/**
1195 * @brief Drives one accumulation run at a given console cadence, in the runloop's order.
1196 *
1197 * Reproduces the sequence the run loop applies for each completed step so the console
1198 * observes exactly the states a real run would present to it.
1199 */
1200static PetscErrorCode AccumulateAtConsoleCadence(SimCtx *simCtx, UserCtx *user,
1201 PicurvWindow *window,
1202 PicurvWindowStorage *storage,
1203 const PicurvWindowDefinition *definition,
1204 PetscInt console_frequency)
1205{
1206 PetscFunctionBeginUser;
1207 PetscCall(PicurvWindowInit(window, definition));
1208 simCtx->fieldStatisticsEnabled = PETSC_TRUE;
1209 simCtx->fieldStatisticsWindowCount = 1;
1210 simCtx->fieldStatisticsWindows = window;
1211 simCtx->statisticsConsoleOutputFreq = console_frequency;
1212 user->fieldStatisticsStorage = storage;
1213
1214 for (PetscInt step = 0; step <= 3; ++step) {
1215 PetscCall(SetUniformPressure(user, 1.0 + (PetscReal)step));
1216 PetscCall(FieldStatisticsUpdateWindows(simCtx, step, (PetscReal)step));
1218 PetscCall(EmitStatisticsConsoleSnapshot(user, simCtx, step));
1219 }
1220 }
1221 PetscFunctionReturn(0);
1222}
1223/**
1224 * @brief Asserts two accumulator vectors are bit-identical.
1225 */
1226static PetscErrorCode AssertVecsIdentical(Vec expected, Vec actual, const char *context)
1227{
1228 PetscBool equal = PETSC_FALSE;
1229
1230 PetscFunctionBeginUser;
1231 PetscCall(VecEqual(expected, actual, &equal));
1232 PetscCall(PicurvAssertBool(equal, context));
1233 PetscFunctionReturn(0);
1234}
1235/**
1236 * @brief Tests that the console cadence has no effect on any accumulated result.
1237 *
1238 * This is the observable form of the console cadence's exclusion from the window
1239 * definition hash: two runs over an identical field series, one reporting every step
1240 * and one reporting never, must leave bit-identical accumulator state. Running it
1241 * where the log level is at least `LOG_INFO` matters, because a snapshot that silently
1242 * declined to emit would make the comparison vacuous.
1243 */
1244
1246{
1247 SimCtx *simCtx = NULL;
1248 UserCtx *user = NULL;
1249 PicurvWindow reported, silent;
1250 PicurvWindowStorage reported_storage, silent_storage;
1252 char captured[8192];
1253 PetscInt step = 3;
1254
1255 PetscFunctionBeginUser;
1256 definition.fields[0].want_second = PETSC_TRUE;
1257 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1258 PetscCall(VecSet(user->Nvert, 0.0));
1259 PetscCall(PicurvWindowStorageCreate(user, &definition, &reported_storage));
1260 PetscCall(PicurvWindowStorageCreate(user, &definition, &silent_storage));
1261
1262 PetscCall(AccumulateAtConsoleCadence(simCtx, user, &reported, &reported_storage, &definition, 1));
1263 PetscCall(AccumulateAtConsoleCadence(simCtx, user, &silent, &silent_storage, &definition, 0));
1264
1265 PetscCall(PicurvAssertIntEqual(reported.sample_count, silent.sample_count,
1266 "both cadences accept the same states"));
1267 PetscCall(PicurvAssertRealNear(reported.total_weight, silent.total_weight, 0.0,
1268 "both cadences accumulate the same total weight"));
1269 PetscCall(AssertVecsIdentical(reported_storage.count, silent_storage.count,
1270 "per-point sample counts are independent of the console cadence"));
1271 PetscCall(AssertVecsIdentical(reported_storage.weight, silent_storage.weight,
1272 "per-point weights are independent of the console cadence"));
1273 PetscCall(AssertVecsIdentical(reported_storage.mean[0], silent_storage.mean[0],
1274 "means are independent of the console cadence"));
1275 PetscCall(AssertVecsIdentical(reported_storage.m2[0], silent_storage.m2[0],
1276 "second moments are independent of the console cadence"));
1277
1278 /* The reporting cadence did drive real output, so the comparison above compared a
1279 * reported run against a silent one rather than two silent ones. */
1280 simCtx->statisticsConsoleOutputFreq = 1;
1281 simCtx->fieldStatisticsWindows = &reported;
1282 user->fieldStatisticsStorage = &reported_storage;
1283 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeStatisticsConsoleSnapshot, &step,
1284 captured, sizeof(captured)));
1285 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "console_window") != NULL),
1286 "the reporting cadence emits console output at this log level"));
1287
1288 simCtx->fieldStatisticsEnabled = PETSC_FALSE;
1289 simCtx->fieldStatisticsWindows = NULL;
1290 simCtx->fieldStatisticsWindowCount = 0;
1291 user->fieldStatisticsStorage = NULL;
1292 PetscCall(PicurvWindowStorageDestroy(&silent_storage));
1293 PetscCall(PicurvWindowStorageDestroy(&reported_storage));
1294 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1295 PetscFunctionReturn(0);
1296}
1297/**
1298 * @brief Tests file-backed particle metrics logging after derived metrics are computed.
1299 */
1300
1301static PetscErrorCode TestParticleMetricsLogging(void)
1302{
1303 SimCtx *simCtx = NULL;
1304 UserCtx *user = NULL;
1305 char tmpdir[PETSC_MAX_PATH_LEN];
1306 char metrics_path[PETSC_MAX_PATH_LEN];
1307
1308 PetscFunctionBeginUser;
1309 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1310 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1311 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1312 simCtx->StartStep = 0;
1313 simCtx->step = 1;
1314 simCtx->particlesLostLastStep = 1;
1315 simCtx->particlesLostCumulative = 7;
1316 simCtx->particlesMigratedLastStep = 2;
1317 simCtx->migrationPassesLastStep = 3;
1318
1319 PetscCall(CalculateParticleCountPerCell(user));
1320 PetscCall(CalculateAdvancedParticleMetrics(user));
1321 PetscCall(LOG_PARTICLE_METRICS(user, "Timestep Metrics"));
1322
1323 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/Particle_Metrics.log", simCtx->log_dir));
1324 PetscCall(PicurvAssertFileExists(metrics_path, "LOG_PARTICLE_METRICS should write Particle_Metrics.log"));
1325 PetscCall(AssertFileContains(metrics_path, "Timestep Metrics", "Particle metrics log should include the caller-provided stage label"));
1326 PetscCall(AssertFileContains(metrics_path, "Occupied Cells", "Particle metrics log should include the metrics table header"));
1327 PetscCall(AssertFileContains(metrics_path, "Lost Total", "Particle metrics log should include the cumulative-loss column"));
1328 PetscCall(AssertFileContains(metrics_path, "| 1 | 7 | 2", "Particle metrics log should record both per-step and cumulative loss values"));
1329 PetscCall(PicurvRemoveTempDir(tmpdir));
1330 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1331 PetscFunctionReturn(0);
1332}
1333/**
1334 * @brief Tests file-backed search metrics logging with the compact CSV contract.
1335 */
1336
1337static PetscErrorCode TestSearchMetricsLogging(void)
1338{
1339 SimCtx *simCtx = NULL;
1340 UserCtx *user = NULL;
1341 char tmpdir[PETSC_MAX_PATH_LEN];
1342 char metrics_path[PETSC_MAX_PATH_LEN];
1343
1344 PetscFunctionBeginUser;
1345 PetscCall(SeedLoggingParticleFixture(&simCtx, &user));
1346 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1347 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1348 simCtx->step = 2;
1349 simCtx->ti = 0.2;
1350 simCtx->particlesLostLastStep = 1;
1351 simCtx->particlesLostCumulative = 7;
1352 simCtx->particlesMigratedLastStep = 2;
1353 simCtx->migrationPassesLastStep = 3;
1354 simCtx->particleLoadImbalance = 1.5;
1355 simCtx->searchMetrics.searchAttempts = 4;
1356 simCtx->searchMetrics.searchPopulation = 2;
1358 simCtx->searchMetrics.searchLostCount = 1;
1359 simCtx->searchMetrics.traversalStepsSum = 10;
1360 simCtx->searchMetrics.reSearchCount = 2;
1361 simCtx->searchMetrics.maxTraversalSteps = 6;
1363 simCtx->searchMetrics.tieBreakCount = 1;
1368
1369 PetscCall(LOG_SEARCH_METRICS(user));
1370
1371 PetscCall(PetscSNPrintf(metrics_path, sizeof(metrics_path), "%s/search_metrics.csv", simCtx->log_dir));
1372 PetscCall(PicurvAssertFileExists(metrics_path, "LOG_SEARCH_METRICS should write search_metrics.csv"));
1373 PetscCall(AssertFileContains(metrics_path, "search_attempts", "Search metrics CSV header should include search_attempts"));
1374 PetscCall(AssertFileContains(metrics_path, "search_population", "Search metrics CSV header should include search_population"));
1375 PetscCall(AssertFileContains(metrics_path, "max_particle_pass_depth", "Search metrics CSV header should include max_particle_pass_depth"));
1376 PetscCall(AssertFileContains(metrics_path, "search_work_index", "Search metrics CSV header should include search_work_index"));
1377 PetscCall(AssertFileContains(metrics_path, "re_search_fraction", "Search metrics CSV header should include re_search_fraction"));
1378 PetscCall(AssertFileContains(metrics_path, "lost_cumulative", "Search metrics CSV header should include the cumulative-loss column"));
1379 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"));
1380 PetscCall(PicurvRemoveTempDir(tmpdir));
1381 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1382 PetscFunctionReturn(0);
1383}
1384/**
1385 * @brief Tests stdout field-anatomy logging on the corrected production-like DM fixture.
1386 */
1387
1388static PetscErrorCode TestFieldAnatomyLogging(void)
1389{
1390 SimCtx *simCtx = NULL;
1391 UserCtx *user = NULL;
1392 char captured[8192];
1393 AnatomyCaptureCtx anatomy_ctx = {FIELD_ID_P, "unit-test"};
1394
1395 PetscFunctionBeginUser;
1396 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1397 PetscCall(VecSet(user->P, 7.0));
1398 PetscCall(DMGlobalToLocalBegin(user->da, user->P, INSERT_VALUES, user->lP));
1399 PetscCall(DMGlobalToLocalEnd(user->da, user->P, INSERT_VALUES, user->lP));
1400 PetscCall(CaptureLoggingOutput(user, simCtx, InvokeFieldAnatomyLog, &anatomy_ctx, captured, sizeof(captured)));
1401 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Field Anatomy Log: [P]") != NULL),
1402 "LOG_FIELD_ANATOMY should print the requested field name"));
1403 PetscCall(PicurvAssertBool((PetscBool)(strstr(captured, "Layout: [Cell-Centered]") != NULL),
1404 "LOG_FIELD_ANATOMY should report the inferred data layout"));
1405 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1406 PetscFunctionReturn(0);
1407}
1408/**
1409 * @brief Tests profiling helper lifecycle logging for timestep and final-summary outputs.
1410 */
1411
1412static PetscErrorCode TestProfilingLifecycleHelpers(void)
1413{
1414 SimCtx simCtx;
1415 char tmpdir[PETSC_MAX_PATH_LEN];
1416 char timestep_path[PETSC_MAX_PATH_LEN];
1417 char summary_path[PETSC_MAX_PATH_LEN];
1418 static char selected_name[] = "FlowSolver";
1419 char *selected_funcs[] = {selected_name};
1420
1421 PetscFunctionBeginUser;
1422 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
1423 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1424 PetscCall(PetscStrncpy(simCtx.log_dir, tmpdir, sizeof(simCtx.log_dir)));
1425 PetscCall(PetscStrncpy(simCtx.profilingTimestepMode, "selected", sizeof(simCtx.profilingTimestepMode)));
1426 PetscCall(PetscStrncpy(simCtx.profilingTimestepFile, "Profiling_Timestep_Summary.csv", sizeof(simCtx.profilingTimestepFile)));
1427 simCtx.rank = 0;
1428 simCtx.exec_mode = EXEC_MODE_SOLVER;
1429 simCtx.StartStep = 0;
1430 simCtx.nProfilingSelectedFuncs = 1;
1431 simCtx.profilingSelectedFuncs = selected_funcs;
1432 simCtx.profilingFinalSummary = PETSC_TRUE;
1433
1434 PetscCall(ProfilingInitialize(&simCtx));
1435
1436 _ProfilingStart("FlowSolver");
1437 _ProfilingEnd("FlowSolver");
1438 _ProfilingStart("UnselectedHelper");
1439 _ProfilingEnd("UnselectedHelper");
1440 PetscCall(ProfilingLogTimestepSummary(&simCtx, 1));
1441
1442 _ProfilingStart("FlowSolver");
1443 _ProfilingEnd("FlowSolver");
1444 PetscCall(ProfilingResetTimestepCounters());
1445 PetscCall(ProfilingLogTimestepSummary(&simCtx, 2));
1446 PetscCall(ProfilingFinalize(&simCtx));
1447
1448 PetscCall(PetscSNPrintf(timestep_path, sizeof(timestep_path), "%s/%s", simCtx.log_dir, simCtx.profilingTimestepFile));
1449 PetscCall(PetscSNPrintf(summary_path, sizeof(summary_path), "%s/ProfilingSummary_Solver.log", simCtx.log_dir));
1450 PetscCall(PicurvAssertFileExists(timestep_path, "profiling timestep summary should be written"));
1451 PetscCall(PicurvAssertFileExists(summary_path, "profiling final summary should be written"));
1452 PetscCall(AssertFileContains(timestep_path, "step,function,calls,step_time_s",
1453 "profiling timestep summary should contain the CSV header"));
1454 PetscCall(AssertFileContains(timestep_path, "1,FlowSolver,1,",
1455 "profiling timestep summary should log selected functions"));
1456 PetscCall(AssertFileNotContains(timestep_path, "UnselectedHelper",
1457 "profiling timestep summary should omit unselected functions in selected mode"));
1458 PetscCall(AssertFileContains(summary_path, "FINAL PROFILING SUMMARY",
1459 "profiling final summary should include its table banner"));
1460 PetscCall(AssertFileContains(summary_path, "FlowSolver",
1461 "profiling final summary should include selected functions"));
1462 PetscCall(AssertFileContains(summary_path, "UnselectedHelper",
1463 "profiling final summary should include total-time entries for unselected functions"));
1464 PetscCall(PicurvRemoveTempDir(tmpdir));
1465 PetscFunctionReturn(0);
1466}
1467
1468/**
1469 * @brief Tests runtime memory log header, step rows, final rows, and disabled mode.
1470 */
1471static PetscErrorCode TestRuntimeMemoryLogHelpers(void)
1472{
1473 SimCtx simCtx;
1474 char tmpdir[PETSC_MAX_PATH_LEN];
1475 char memory_path[PETSC_MAX_PATH_LEN];
1476
1477 PetscFunctionBeginUser;
1478 PetscCall(PetscMemzero(&simCtx, sizeof(simCtx)));
1479 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1480 PetscCall(PetscStrncpy(simCtx.log_dir, tmpdir, sizeof(simCtx.log_dir)));
1481 PetscCall(PetscStrncpy(simCtx.runtimeMemoryLogFile, "Runtime_Memory.log", sizeof(simCtx.runtimeMemoryLogFile)));
1482 simCtx.rank = 0;
1483 simCtx.runtimeMemoryLogEnabled = PETSC_TRUE;
1484 simCtx.runtimeMemoryLogStarted = PETSC_FALSE;
1485 simCtx.runtimeMemoryLogHasPrevious = PETSC_FALSE;
1486 simCtx.continueMode = PETSC_FALSE;
1487 simCtx.StartStep = 0;
1488 PetscCall(PetscMemorySetGetMaximumUsage());
1489
1490 PetscCall(RuntimeMemoryLogSample(&simCtx, 1, "Step", "-"));
1491 PetscCall(RuntimeMemoryLogSample(&simCtx, 1, "Final", "Complete"));
1492 PetscCall(PetscSNPrintf(memory_path, sizeof(memory_path), "%s/%s", simCtx.log_dir, simCtx.runtimeMemoryLogFile));
1493 PetscCall(PicurvAssertFileExists(memory_path, "runtime memory log should be written"));
1494 PetscCall(AssertFileContains(memory_path, "Process Current MB Max",
1495 "runtime memory log should contain readable column labels"));
1496 PetscCall(AssertFileContains(memory_path, "Step",
1497 "runtime memory log should contain a step row"));
1498 PetscCall(AssertFileContains(memory_path, "Final",
1499 "runtime memory log should contain a final row"));
1500 PetscCall(AssertFileContains(memory_path, "Complete",
1501 "runtime memory log should record final reason"));
1502
1503 simCtx.runtimeMemoryLogEnabled = PETSC_FALSE;
1504 PetscCall(PetscStrncpy(simCtx.runtimeMemoryLogFile, "Runtime_Memory_Disabled.log", sizeof(simCtx.runtimeMemoryLogFile)));
1505 PetscCall(RuntimeMemoryLogSample(&simCtx, 2, "Step", "-"));
1506 PetscCall(PetscSNPrintf(memory_path, sizeof(memory_path), "%s/%s", simCtx.log_dir, simCtx.runtimeMemoryLogFile));
1507 PetscCall(PicurvAssertBool((PetscBool)(access(memory_path, F_OK) != 0),
1508 "disabled runtime memory log should not create a file"));
1509 PetscCall(PicurvRemoveTempDir(tmpdir));
1510 PetscFunctionReturn(0);
1511}
1512
1513/**
1514 * @brief Verifies -solution_convergence_enabled=false suppresses the convergence writer.
1515 */
1516static PetscErrorCode TestSolutionConvergenceDisabled(void)
1517{
1518 SimCtx *simCtx = NULL;
1519 UserCtx *user = NULL;
1520 char tmpdir[PETSC_MAX_PATH_LEN];
1521 char log_path[PETSC_MAX_PATH_LEN];
1522 PetscBool exists = PETSC_FALSE;
1523
1524 PetscFunctionBeginUser;
1525 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1526 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1527 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1528 simCtx->solutionConvergenceEnabled = PETSC_FALSE;
1529
1530 PetscCall(InitializeSolutionConvergenceState(simCtx));
1531 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1532 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1533 PetscCall(PetscTestFile(log_path, 'r', &exists));
1534 PetscCall(PicurvAssertBool((PetscBool)!exists,
1535 "disabled solution convergence must not create a log"));
1536
1537 PetscCall(PicurvRemoveTempDir(tmpdir));
1538 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1539 PetscFunctionReturn(0);
1540}
1541
1542/**
1543 * @brief Tests steady solution-convergence log output, IBM masking, and gauge-invariant pressure drift.
1544 */
1545static PetscErrorCode TestSolutionConvergenceSteadyLogging(void)
1546{
1547 SimCtx *simCtx = NULL;
1548 UserCtx *user = NULL;
1549 char tmpdir[PETSC_MAX_PATH_LEN];
1550 char log_path[PETSC_MAX_PATH_LEN];
1551 char header[4096];
1552 char row1[4096];
1553 char row2[4096];
1554 char mode[128];
1555 Cmpnts ***ucat = NULL;
1556 PetscReal ***nvert = NULL;
1557 PetscReal mean_speed = NAN;
1558 PetscReal mean_speed_ref = NAN;
1559 PetscReal mean_speed_abs = NAN;
1560 PetscReal p_abs = NAN;
1561 PetscInt has_reference_1 = 0;
1562 PetscInt has_reference_2 = 0;
1563
1564 PetscFunctionBeginUser;
1565 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1566 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1567 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1569
1570 PetscCall(InitializeSolutionConvergenceState(simCtx));
1571
1572 simCtx->step = 1;
1573 simCtx->ti = 0.1;
1574 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.0, 0.0, 0.0));
1575 PetscCall(SetUniformScalarField(user, user->P, 11.0));
1576 PetscCall(DMDAVecGetArray(user->da, user->Nvert, &nvert));
1577 nvert[1][1][1] = 1.0;
1578 PetscCall(DMDAVecRestoreArray(user->da, user->Nvert, &nvert));
1579 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
1580 ucat[1][1][1].x = 999.0;
1581 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
1582 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1583
1584 simCtx->step = 2;
1585 simCtx->ti = 0.2;
1586 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.0, 0.0, 0.0));
1587 PetscCall(SetUniformVelocityField(user, user->Ucat_o, 0.5, 0.0, 0.0));
1588 PetscCall(SetUniformScalarField(user, user->P, 11.0));
1589 PetscCall(SetUniformScalarField(user, user->P_o, 7.0));
1590 PetscCall(DMDAVecGetArray(user->fda, user->Ucat, &ucat));
1591 ucat[1][1][1].x = 999.0;
1592 PetscCall(DMDAVecRestoreArray(user->fda, user->Ucat, &ucat));
1593 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1594
1595 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1596 PetscCall(PicurvAssertFileExists(log_path, "steady solution-convergence logging should write the log"));
1597 PetscCall(ReadLogHeaderAndRow(log_path, 1, header, sizeof(header), row1, sizeof(row1)));
1598 PetscCall(ReadLogHeaderAndRow(log_path, 2, header, sizeof(header), row2, sizeof(row2)));
1599 PetscCall(LogGetColumnText(header, row1, "mode", mode, sizeof(mode)));
1600 PetscCall(LogGetColumnInt(header, row1, "ref", &has_reference_1));
1601 PetscCall(LogGetColumnInt(header, row2, "ref", &has_reference_2));
1602 PetscCall(LogGetColumnReal(header, row2, "mean_speed", &mean_speed));
1603 PetscCall(LogGetColumnReal(header, row2, "spd_ref", &mean_speed_ref));
1604 PetscCall(LogGetColumnReal(header, row2, "spd_abs", &mean_speed_abs));
1605 PetscCall(LogGetColumnReal(header, row2, "p_abs_l2", &p_abs));
1606
1607 PetscCall(PicurvAssertBool((PetscBool)(strcmp(mode, "steady_deterministic") == 0),
1608 "steady solution convergence row should record the mode name"));
1609 PetscCall(PicurvAssertIntEqual(0, has_reference_1,
1610 "the first steady solution-convergence row should be a warmup row"));
1611 PetscCall(PicurvAssertIntEqual(1, has_reference_2,
1612 "steady solution convergence should compare against the previous solved step"));
1613 PetscCall(PicurvAssertRealNear(1.0, mean_speed, 1.0e-12,
1614 "steady solution convergence should mask IBM-marked solid cells"));
1615 PetscCall(PicurvAssertRealNear(0.5, mean_speed_ref, 1.0e-12,
1616 "steady solution convergence should report the previous-step mean speed"));
1617 PetscCall(PicurvAssertRealNear(0.5, mean_speed_abs, 1.0e-12,
1618 "steady solution convergence should report mean-speed drift"));
1619 PetscCall(PicurvAssertRealNear(0.0, p_abs, 1.0e-12,
1620 "steady solution convergence pressure drift should be gauge invariant"));
1621
1622 PetscCall(PicurvRemoveTempDir(tmpdir));
1623 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1624 PetscFunctionReturn(0);
1625}
1626
1627/**
1628 * @brief Tests periodic solution-convergence warmup and phase-aligned reference reuse.
1629 */
1631{
1632 SimCtx *simCtx = NULL;
1633 UserCtx *user = NULL;
1634 char tmpdir[PETSC_MAX_PATH_LEN];
1635 char log_path[PETSC_MAX_PATH_LEN];
1636 char header[4096];
1637 char row1[4096];
1638 char row2[4096];
1639 char row3[4096];
1640 PetscInt has_reference_1 = 0;
1641 PetscInt has_reference_2 = 0;
1642 PetscInt has_reference_3 = 0;
1643 PetscInt phase_step_1 = -1;
1644 PetscInt phase_step_2 = -1;
1645 PetscInt phase_step_3 = -1;
1646 PetscReal mean_speed_ref_2 = NAN;
1647 PetscReal mean_speed_abs_2 = NAN;
1648
1649 PetscFunctionBeginUser;
1650 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1651 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1652 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1655
1656 PetscCall(InitializeSolutionConvergenceState(simCtx));
1657
1658 simCtx->step = 1;
1659 simCtx->ti = 0.1;
1660 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.0, 0.0, 0.0));
1661 PetscCall(SetUniformScalarField(user, user->P, 2.0));
1662 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1663
1664 simCtx->step = 2;
1665 simCtx->ti = 0.2;
1666 PetscCall(SetUniformVelocityField(user, user->Ucat, 2.0, 0.0, 0.0));
1667 PetscCall(SetUniformScalarField(user, user->P, 5.0));
1668 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1669
1670 simCtx->step = 3;
1671 simCtx->ti = 0.3;
1672 PetscCall(SetUniformVelocityField(user, user->Ucat, 1.25, 0.0, 0.0));
1673 PetscCall(SetUniformScalarField(user, user->P, 9.0));
1674 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1675
1676 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1677 PetscCall(PicurvAssertFileExists(log_path, "periodic solution-convergence logging should write the log"));
1678 PetscCall(ReadLogHeaderAndRow(log_path, 1, header, sizeof(header), row1, sizeof(row1)));
1679 PetscCall(ReadLogHeaderAndRow(log_path, 2, header, sizeof(header), row2, sizeof(row2)));
1680 PetscCall(ReadLogHeaderAndRow(log_path, 3, header, sizeof(header), row3, sizeof(row3)));
1681 PetscCall(LogGetColumnInt(header, row1, "ref", &has_reference_1));
1682 PetscCall(LogGetColumnInt(header, row2, "ref", &has_reference_2));
1683 PetscCall(LogGetColumnInt(header, row3, "ref", &has_reference_3));
1684 PetscCall(LogGetColumnInt(header, row1, "ph", &phase_step_1));
1685 PetscCall(LogGetColumnInt(header, row2, "ph", &phase_step_2));
1686 PetscCall(LogGetColumnInt(header, row3, "ph", &phase_step_3));
1687 PetscCall(LogGetColumnReal(header, row3, "spd_ref", &mean_speed_ref_2));
1688 PetscCall(LogGetColumnReal(header, row3, "spd_abs", &mean_speed_abs_2));
1689
1690 PetscCall(PicurvAssertIntEqual(0, has_reference_1,
1691 "the first periodic phase visit should log warmup without a reference"));
1692 PetscCall(PicurvAssertIntEqual(0, has_reference_2,
1693 "the first periodic cycle should fully warm up before comparisons begin"));
1694 PetscCall(PicurvAssertIntEqual(1, has_reference_3,
1695 "the repeated periodic phase visit should compare against the stored reference"));
1696 PetscCall(PicurvAssertIntEqual(1, phase_step_1,
1697 "periodic solution convergence should log the current phase slot"));
1698 PetscCall(PicurvAssertIntEqual(0, phase_step_2,
1699 "periodic solution convergence should log distinct phase slots during warmup"));
1700 PetscCall(PicurvAssertIntEqual(1, phase_step_3,
1701 "periodic solution convergence should reuse the same phase slot on later cycles"));
1702 PetscCall(PicurvAssertRealNear(1.0, mean_speed_ref_2, 1.0e-12,
1703 "periodic solution convergence should report the stored phase-aligned reference"));
1704 PetscCall(PicurvAssertRealNear(0.25, mean_speed_abs_2, 1.0e-12,
1705 "periodic solution convergence should report the phase-aligned drift"));
1706
1707 PetscCall(PicurvRemoveTempDir(tmpdir));
1708 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1709 PetscFunctionReturn(0);
1710}
1711
1712/**
1713 * @brief Tests statistical solution-convergence sliding-window metrics.
1714 */
1716{
1717 SimCtx *simCtx = NULL;
1718 UserCtx *user = NULL;
1719 char tmpdir[PETSC_MAX_PATH_LEN];
1720 char log_path[PETSC_MAX_PATH_LEN];
1721 char header[4096];
1722 char row[4096];
1723 PetscReal mean_speed_window = NAN;
1724 PetscReal mean_speed_window_prev = NAN;
1725 PetscReal mean_speed_window_abs = NAN;
1726 PetscReal mean_speed_rms_window = NAN;
1727 PetscReal mean_ke_window = NAN;
1728 PetscReal mean_ke_window_prev = NAN;
1729 PetscReal mean_ke_window_abs = NAN;
1730 PetscReal mean_ke_rms_window_abs = NAN;
1731 PetscInt has_reference = 0;
1732 const PetscReal speed_samples[4] = {1.0, 3.0, 2.0, 4.0};
1733
1734 PetscFunctionBeginUser;
1735 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
1736 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1737 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1740
1741 PetscCall(InitializeSolutionConvergenceState(simCtx));
1742 for (PetscInt step = 0; step < 4; ++step) {
1743 simCtx->step = step + 1;
1744 simCtx->ti = 0.1 * (PetscReal)(step + 1);
1745 PetscCall(SetUniformVelocityField(user, user->Ucat, speed_samples[step], 0.0, 0.0));
1746 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1747 }
1748
1749 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1750 PetscCall(PicurvAssertFileExists(log_path, "statistical solution-convergence logging should write the log"));
1751 PetscCall(ReadLogHeaderAndRow(log_path, 4, header, sizeof(header), row, sizeof(row)));
1752 PetscCall(LogGetColumnInt(header, row, "ref", &has_reference));
1753 PetscCall(LogGetColumnReal(header, row, "spd_win", &mean_speed_window));
1754 PetscCall(LogGetColumnReal(header, row, "spd_win_prev", &mean_speed_window_prev));
1755 PetscCall(LogGetColumnReal(header, row, "spd_win_abs", &mean_speed_window_abs));
1756 PetscCall(LogGetColumnReal(header, row, "spd_rms_win", &mean_speed_rms_window));
1757 PetscCall(LogGetColumnReal(header, row, "ke_win", &mean_ke_window));
1758 PetscCall(LogGetColumnReal(header, row, "ke_win_prev", &mean_ke_window_prev));
1759 PetscCall(LogGetColumnReal(header, row, "ke_win_abs", &mean_ke_window_abs));
1760 PetscCall(LogGetColumnReal(header, row, "ke_rms_abs", &mean_ke_rms_window_abs));
1761
1762 PetscCall(PicurvAssertIntEqual(1, has_reference,
1763 "statistical solution convergence should emit adjacent-window drift once two windows exist"));
1764 PetscCall(PicurvAssertRealNear(3.0, mean_speed_window, 1.0e-12,
1765 "statistical solution convergence should report the current mean-speed window"));
1766 PetscCall(PicurvAssertRealNear(2.0, mean_speed_window_prev, 1.0e-12,
1767 "statistical solution convergence should report the previous mean-speed window"));
1768 PetscCall(PicurvAssertRealNear(1.0, mean_speed_window_abs, 1.0e-12,
1769 "statistical solution convergence should report mean-speed window drift"));
1770 PetscCall(PicurvAssertRealNear(1.0, mean_speed_rms_window, 1.0e-12,
1771 "statistical solution convergence should report current mean-speed RMS"));
1772 PetscCall(PicurvAssertRealNear(5.0, mean_ke_window, 1.0e-12,
1773 "statistical solution convergence should report the current kinetic-energy window"));
1774 PetscCall(PicurvAssertRealNear(2.5, mean_ke_window_prev, 1.0e-12,
1775 "statistical solution convergence should report the previous kinetic-energy window"));
1776 PetscCall(PicurvAssertRealNear(2.5, mean_ke_window_abs, 1.0e-12,
1777 "statistical solution convergence should report kinetic-energy window drift"));
1778 PetscCall(PicurvAssertRealNear(1.0, mean_ke_rms_window_abs, 1.0e-12,
1779 "statistical solution convergence should report RMS kinetic-energy drift"));
1780
1781 PetscCall(PicurvRemoveTempDir(tmpdir));
1782 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1783 PetscFunctionReturn(0);
1784}
1785/**
1786 * @brief Regression test: volume-averaged mean KE of a sinusoidal field in a fully periodic domain.
1787 *
1788 * 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.
1789 * With identity metrics (Aj=1) the discrete mean over the N unique cells also equals 0.5025
1790 * for any even N, because Σ_{k=0}^{N-1} sin²(2πk/N) = N/2.
1791 *
1792 * Before the fix the statistics loops iterated over N+1 nodes (including the duplicated periodic
1793 * endpoint at k=N), inflating the denominator and yielding mean_ke ≈ 0.5·(1 + 0.01·N/(2(N+1)³))
1794 * instead of 0.5025. This test verifies the endpoint is excluded.
1795 */
1796static PetscErrorCode TestPeriodicSinusoidalMeanKE(void)
1797{
1798 const PetscInt km_values[] = {8, 16};
1799 const PetscInt n_cases = (PetscInt)(sizeof(km_values) / sizeof(km_values[0]));
1800
1801 PetscFunctionBeginUser;
1802 for (PetscInt t = 0; t < n_cases; ++t) {
1803 const PetscInt km = km_values[t];
1804 SimCtx *simCtx = NULL;
1805 UserCtx *user = NULL;
1806 char tmpdir[PETSC_MAX_PATH_LEN];
1807 char log_path[PETSC_MAX_PATH_LEN];
1808 char header[4096];
1809 char row[4096];
1810 PetscReal mean_ke = NAN;
1811
1812 PetscCall(PicurvCreateMinimalContextsWithPeriodicity(&simCtx, &user, km, km, km,
1813 PETSC_TRUE, PETSC_TRUE, PETSC_TRUE));
1814 PetscCall(PicurvMakeTempDir(tmpdir, sizeof(tmpdir)));
1815 PetscCall(PetscStrncpy(simCtx->log_dir, tmpdir, sizeof(simCtx->log_dir)));
1816 PetscCall(PicurvPopulateIdentityMetrics(user));
1817 PetscCall(SetSinusoidalVZField(user, user->Ucat, 0.1, 1.0));
1818
1821 PetscCall(InitializeSolutionConvergenceState(simCtx));
1822
1823 simCtx->step = 1;
1824 simCtx->ti = 0.1;
1825 PetscCall(LOG_SOLUTION_CONVERGENCE(simCtx));
1826
1827 PetscCall(PetscSNPrintf(log_path, sizeof(log_path), "%s/solution_convergence.log", simCtx->log_dir));
1828 PetscCall(ReadLogHeaderAndRow(log_path, 1, header, sizeof(header), row, sizeof(row)));
1829 PetscCall(LogGetColumnReal(header, row, "mean_ke", &mean_ke));
1830 PetscCall(PicurvAssertRealNear(0.5025, mean_ke, 1.0e-10,
1831 "periodic sinusoidal field mean KE must equal 0.5025 (no duplicate endpoint)"));
1832
1833 PetscCall(PicurvRemoveTempDir(tmpdir));
1834 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
1835 }
1836 PetscFunctionReturn(0);
1837}
1838
1839/**
1840 * @brief Runs the unit-logging PETSc test binary.
1841 */
1842
1843int main(int argc, char **argv)
1844{
1845 PetscErrorCode ierr;
1846 const PicurvTestCase cases[] = {
1847 {"string-conversion-helpers", TestStringConversionHelpers},
1848 {"get-log-level-from-environment", TestGetLogLevelFromEnvironment},
1849 {"allowed-functions-filter", TestAllowedFunctionsFilter},
1850 {"particle-console-snapshot-cadence", TestParticleConsoleSnapshotCadence},
1851 {"statistics-console-snapshot-cadence", TestStatisticsConsoleSnapshotCadence},
1852 {"logging-file-parsing-and-formatting-helpers", TestLoggingFileParsingAndFormattingHelpers},
1853 {"logging-continuity-and-field-diagnostics", TestLoggingContinuityAndFieldDiagnostics},
1854 {"interpolation-error-logging", TestInterpolationErrorLogging},
1855 {"scatter-metrics-logging", TestScatterMetricsLogging},
1856 {"particle-field-table-logging", TestParticleFieldTableLogging},
1857 {"particle-console-snapshot-logging", TestParticleConsoleSnapshotLogging},
1858 {"statistics-console-snapshot-logging", TestStatisticsConsoleSnapshotLogging},
1859 {"console-cadence-does-not-change-accumulation", TestConsoleCadenceDoesNotChangeAccumulation},
1860 {"particle-metrics-logging", TestParticleMetricsLogging},
1861 {"search-metrics-logging", TestSearchMetricsLogging},
1862 {"field-anatomy-logging", TestFieldAnatomyLogging},
1863 {"profiling-lifecycle-helpers", TestProfilingLifecycleHelpers},
1864 {"runtime-memory-log-helpers", TestRuntimeMemoryLogHelpers},
1865 {"solution-convergence-disabled", TestSolutionConvergenceDisabled},
1866 {"solution-convergence-steady-logging", TestSolutionConvergenceSteadyLogging},
1867 {"solution-convergence-periodic-logging", TestSolutionConvergencePeriodicLogging},
1868 {"solution-convergence-statistical-logging", TestSolutionConvergenceStatisticalLogging},
1869 {"periodic-sinusoidal-mean-ke", TestPeriodicSinusoidalMeanKE},
1870 };
1871
1872 (void)setenv("LOG_LEVEL", "INFO", 1);
1873
1874 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv logging tests");
1875 if (ierr) {
1876 return (int)ierr;
1877 }
1878
1879 ierr = PicurvRunTests("unit-logging", cases, sizeof(cases) / sizeof(cases[0]));
1880 if (ierr) {
1881 PetscFinalize();
1882 return (int)ierr;
1883 }
1884
1885 set_allowed_functions(NULL, 0);
1886
1887 ierr = PetscFinalize();
1888 return (int)ierr;
1889}
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.
FieldId
Compile-time identity for a catalogued Eulerian field.
@ FIELD_ID_UCAT
@ FIELD_ID_COORDINATES
@ FIELD_ID_UCONT
@ FIELD_ID_INVALID
@ FIELD_ID_P
PetscErrorCode ScatterAllParticleFieldsToEulerFields(UserCtx *user)
Scatters a predefined set of particle fields to their corresponding Eulerian fields.
Logging utilities and macros for PETSc-based applications.
PetscErrorCode LOG_FIELD_MIN_MAX(UserCtx *user, FieldId field_id)
Computes and logs the local and global min/max values of a 3-component vector field.
Definition logging.c:2349
void set_allowed_functions(const char **functionList, int count)
Sets the global list of function names that are allowed to log.
Definition logging.c:155
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:3337
PetscBool ShouldEmitPeriodicStatisticsConsoleSnapshot(const struct SimCtx *simCtx, PetscInt completed_step)
Reports whether a completed step falls on the console snapshot cadence.
const char * BCHandlerTypeToString(BCHandlerType handler_type)
Converts a BCHandlerType enum to its string representation.
Definition logging.c:793
PetscBool is_function_allowed(const char *functionName)
Checks if a given function is in the allow-list.
Definition logging.c:186
PetscErrorCode DualMonitorDestroy(void **ctx)
Destroys the DualMonitorCtx.
Definition logging.c:831
PetscBool IsStatisticsConsoleSnapshotEnabled(const struct SimCtx *simCtx)
Reports whether the periodic statistics console snapshot is enabled.
PetscErrorCode LOG_INTERPOLATION_ERROR(UserCtx *user)
Logs the interpolation error between the analytical and computed solutions.
Definition logging.c:2865
PetscBool ShouldEmitPeriodicParticleConsoleSnapshot(const SimCtx *simCtx, PetscInt completed_step)
Returns whether a particle console snapshot should be emitted for the.
Definition logging.c:545
const char * BCFaceToString(BCFace face)
Returns the canonical log token for a boundary-face enum value.
Definition logging.c:671
PetscErrorCode FreeAllowedFunctions(char **funcs, PetscInt n)
Free an array previously returned by LoadAllowedFunctionsFromFile().
Definition logging.c:652
PetscBool IsParticleConsoleSnapshotEnabled(const SimCtx *simCtx)
Returns whether periodic particle console snapshots are enabled.
Definition logging.c:528
PetscErrorCode print_log_level(void)
Prints the current logging level to the console.
Definition logging.c:119
PetscErrorCode EmitParticleConsoleSnapshot(UserCtx *user, SimCtx *simCtx, PetscInt step)
Emits one particle console snapshot into the main solver log.
Definition logging.c:559
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:598
PetscErrorCode EmitStatisticsConsoleSnapshot(UserCtx *user, const struct SimCtx *simCtx, PetscInt step)
Emits one console snapshot of window progress.
void PrintProgressBar(PetscInt step, PetscInt startStep, PetscInt totalSteps, PetscReal currentTime)
Prints a progress bar to the console.
Definition logging.c:2302
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:2086
LogLevel get_log_level()
Retrieves the current logging level from the environment variable LOG_LEVEL.
Definition logging.c:87
PetscErrorCode ProfilingLogTimestepSummary(SimCtx *simCtx, PetscInt step)
Logs the performance summary for the current timestep and resets timers.
Definition logging.c:2005
PetscErrorCode LOG_FACE_DISTANCES(PetscReal *d)
Prints the signed distances to each face of the cell.
Definition logging.c:233
PetscErrorCode LOG_PARTICLE_FIELDS(UserCtx *user, PetscInt printInterval)
Prints particle fields in a table that automatically adjusts its column widths.
Definition logging.c:400
void _ProfilingEnd(const char *func_name)
Internal profiling hook invoked by PROFILE_FUNCTION_END.
Definition logging.c:1966
const char * BCTypeToString(BCType type)
Returns the canonical log token for a boundary mathematical type.
Definition logging.c:773
PetscErrorCode CalculateAdvancedParticleMetrics(UserCtx *user)
Computes advanced particle statistics and stores them in SimCtx.
Definition logging.c:3283
const char * ParticleLocationStatusToString(ParticleLocationStatus level)
A function that outputs the name of the current level in the ParticleLocation enum.
Definition logging.c:1858
PetscErrorCode LOG_SCATTER_METRICS(UserCtx *user)
Logs particle-to-grid scatter verification metrics for the prescribed scalar truth path.
Definition logging.c:2944
PetscErrorCode LOG_SOLUTION_CONVERGENCE(SimCtx *simCtx)
Logs physical solution-convergence metrics once per completed timestep.
Definition logging.c:1600
PetscErrorCode LOG_CONTINUITY_METRICS(UserCtx *user)
Logs continuity metrics for a single block to a file.
Definition logging.c:1796
PetscErrorCode LOG_FIELD_ANATOMY(UserCtx *user, FieldId field_id, const char *stage_name)
Logs the anatomy of a specified field at key boundary locations, respecting the solver's specific gri...
Definition logging.c:2752
PetscErrorCode LOG_SEARCH_METRICS(UserCtx *user)
Writes compact runtime search metrics to CSV and optionally to console.
Definition logging.c:3129
const char * InitialConditionModeToString(InitialConditionMode mode)
Convert an initial-condition mode to a string representation.
Definition logging.c:689
PetscErrorCode ProfilingInitialize(SimCtx *simCtx)
Initializes the custom profiling system using configuration from SimCtx.
Definition logging.c:1928
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
const char * LESModelToString(LESModelType LESFlag)
Returns the canonical log token for an LES model selector.
Definition logging.c:741
PetscErrorCode LOG_CELL_VERTICES(const Cell *cell, PetscMPIInt rank)
Prints the coordinates of a cell's vertices.
Definition logging.c:208
PetscErrorCode ProfilingResetTimestepCounters(void)
Resets per-timestep profiling counters for the next solver step.
Definition logging.c:1988
const char * MomentumSolverTypeToString(MomentumSolverType SolverFlag)
Returns the canonical log token for a momentum-solver selector.
Definition logging.c:757
FILE * file_handle
Definition logging.h:57
const char * ParticleInitializationToString(ParticleInitializationType ParticleInitialization)
Returns the canonical log token for a particle-initialization mode.
Definition logging.c:724
void _ProfilingStart(const char *func_name)
Internal profiling hook invoked by PROFILE_FUNCTION_BEGIN.
Definition logging.c:1952
Context for a dual-purpose KSP monitor.
Definition logging.h:56
PetscErrorCode InitializeSolutionConvergenceState(SimCtx *simCtx)
Allocates any runtime storage required by solution-convergence logging.
Definition setup.c:49
Per-window PETSc accumulator storage and pointwise application.
Vec weight
Per-point valid weight.
Vec * mean
One per field, matching that field's layout.
PetscErrorCode PicurvWindowStorageCreate(UserCtx *user, const PicurvWindowDefinition *definition, PicurvWindowStorage *storage)
Allocates the accumulator state one window owns on one block.
Vec * m2
One per field; NULL when no second moment was requested.
Vec count
Per-point accepted sample count.
PetscErrorCode PicurvWindowStorageDestroy(PicurvWindowStorage *storage)
Releases accumulator state previously created for one window.
Independent accumulator state for one window on one block.
Window lifecycle, scheduling, and weighting for the field-statistics pipeline.
PetscInt sample_count
PicurvWindowFieldRequest fields[16]
PetscErrorCode FieldStatisticsUpdateWindows(struct SimCtx *simCtx, PetscInt step, PetscReal time)
Offers one completed state to every configured window.
PetscReal end_time
Requested end; ignored when bounded is false.
PicurvCadenceKind cadence_kind
PetscReal total_weight
PetscInt step_cadence
Used when cadence_kind is step; must be positive.
#define PICURV_WINDOW_NAME_LENGTH
Maximum stored length of a window name, including the terminator.
PetscErrorCode PicurvWindowInit(PicurvWindow *window, const PicurvWindowDefinition *definition)
Validates a definition and initializes a window to the pending state.
PetscBool want_second
Also keep the centered second moment.
PetscBool bounded
False for an open-ended window.
PetscInt field_id
Catalogued Eulerian field identity.
@ PICURV_WEIGHTING_SAMPLE
Equal weight per accepted state.
@ PICURV_CADENCE_STEP
Every n completed steps from activation.
Runtime state of one window.
The scientifically immutable definition of one window.
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 TestStatisticsConsoleSnapshotLogging(void)
Tests statistics console snapshot content, and its silence when disabled.
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 TestStatisticsConsoleSnapshotCadence(void)
Tests periodic statistics console snapshot enablement and cadence.
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 AssertVecsIdentical(Vec expected, Vec actual, const char *context)
Asserts two accumulator vectors are bit-identical.
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 SetUniformPressure(UserCtx *user, PetscReal value)
Sets the pressure field to a uniform value across the owned range.
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 AccumulateAtConsoleCadence(SimCtx *simCtx, UserCtx *user, PicurvWindow *window, PicurvWindowStorage *storage, const PicurvWindowDefinition *definition, PetscInt console_frequency)
Drives one accumulation run at a given console cadence, in the runloop's order.
static PicurvWindowDefinition LoggingStatisticsDefinition(void)
Builds the window definition backing this suite's statistics console fixture.
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 InvokeStatisticsConsoleSnapshot(UserCtx *user, SimCtx *simCtx, void *ctx)
Adapts EmitStatisticsConsoleSnapshot() to the generic stdout-capture callback shape.
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 TestSolutionConvergenceDisabled(void)
Verifies -solution_convergence_enabled=false suppresses the convergence writer.
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.
static PetscErrorCode TestConsoleCadenceDoesNotChangeAccumulation(void)
Tests that the console cadence has no effect on any accumulated result.
const char * stage_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:522
PetscInt fieldStatisticsWindowCount
Definition variables.h:770
@ PERIODIC
Definition variables.h:292
PetscBool continueMode
Definition variables.h:712
PetscBool profilingFinalSummary
Definition variables.h:868
PetscMPIInt rank
Definition variables.h:698
char profilingTimestepFile[PETSC_MAX_PATH_LEN]
Definition variables.h:867
PetscInt64 searchLocatedCount
Definition variables.h:241
PetscInt statisticsConsoleOutputFreq
Definition variables.h:772
PetscInt64 searchLostCount
Definition variables.h:242
@ PARTICLE_INIT_VOLUME
Random volumetric distribution across the domain.
Definition variables.h:553
@ LOST
Definition variables.h:141
@ ACTIVE_AND_LOCATED
Definition variables.h:139
PetscReal FluxOutSum
Definition variables.h:799
PetscBool runtimeMemoryLogEnabled
Enable the rank-reduced runtime memory log.
Definition variables.h:883
PetscInt64 boundaryClampCount
Definition variables.h:248
PetscInt particlesLostLastStep
Definition variables.h:834
PetscInt KM
Definition variables.h:920
PetscInt64 traversalStepsSum
Definition variables.h:243
@ BC_HANDLER_PERIODIC_DRIVEN_CONSTANT_FLUX
Definition variables.h:318
PetscReal ren
Definition variables.h:744
PetscInt64 searchPopulation
Definition variables.h:240
PetscBool solutionConvergenceEnabled
Definition variables.h:761
char runtimeMemoryLogFile[PETSC_MAX_PATH_LEN]
File name written under log_dir.
Definition variables.h:884
PetscBool runtimeMemoryLogStarted
True after rank 0 writes the log header.
Definition variables.h:885
char profilingTimestepMode[32]
Definition variables.h:866
PetscInt np
Definition variables.h:827
PetscBool fieldStatisticsEnabled
Definition variables.h:769
Vec Ucont
Definition variables.h:939
PetscInt StartStep
Definition variables.h:705
@ MOMENTUM_SOLVER_EXPLICIT_RK
Definition variables.h:535
@ MOMENTUM_SOLVER_NEWTON_KRYLOV
Definition variables.h:537
PetscInt solutionConvergencePeriodSteps
Definition variables.h:763
PetscScalar x
Definition variables.h:103
PetscInt64 reSearchCount
Definition variables.h:244
PetscReal MaxDiv
Definition variables.h:859
PetscInt64 bboxGuessFallbackCount
Definition variables.h:250
VerificationScalarConfig verificationScalar
Definition variables.h:778
Vec Ucat_o
Definition variables.h:946
PetscInt MaxDivx
Definition variables.h:860
PetscInt MaxDivy
Definition variables.h:860
PetscInt64 bboxGuessSuccessCount
Definition variables.h:249
PetscInt MaxDivz
Definition variables.h:860
struct PicurvWindow * fieldStatisticsWindows
Definition variables.h:771
char log_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:718
PetscInt MaxDivFlatArg
Definition variables.h:860
PetscReal FluxInSum
Definition variables.h:799
PetscInt64 maxParticlePassDepth
Definition variables.h:251
PetscInt64 maxTraversalSteps
Definition variables.h:245
PetscScalar z
Definition variables.h:103
Vec Ucat
Definition variables.h:939
PetscBool runtimeMemoryLogHasPrevious
True after the first process-memory sample.
Definition variables.h:886
char ** profilingSelectedFuncs
Definition variables.h:864
PetscInt solutionConvergenceWindowSteps
Definition variables.h:764
PetscInt particlesLostCumulative
Definition variables.h:835
PetscInt nProfilingSelectedFuncs
Definition variables.h:865
PetscInt particlesMigratedLastStep
Definition variables.h:837
struct PicurvWindowStorage * fieldStatisticsStorage
Definition variables.h:962
char AnalyticalSolutionType[PETSC_MAX_PATH_LEN]
Definition variables.h:729
InitialConditionMode
Selects the algorithm used to populate a fresh Eulerian velocity field.
Definition variables.h:151
@ IC_MODE_CONSTANT_CARTESIAN
Definition variables.h:153
@ IC_MODE_POISEUILLE
Definition variables.h:154
@ IC_MODE_CONSTANT_STREAMWISE
Definition variables.h:155
@ IC_MODE_FILE
Definition variables.h:156
@ IC_MODE_ZERO
Definition variables.h:152
PetscInt particleConsoleOutputFreq
Definition variables.h:708
SearchMetricsState searchMetrics
Definition variables.h:840
Vec lUcont
Definition variables.h:939
PetscInt step
Definition variables.h:703
DMDALocalInfo info
Definition variables.h:918
Vec lUcat
Definition variables.h:939
PetscInt migrationPassesLastStep
Definition variables.h:836
PetscScalar y
Definition variables.h:103
@ EXEC_MODE_SOLVER
Definition variables.h:668
Vec Nvert
Definition variables.h:939
@ SOLUTION_CONVERGENCE_PERIODIC_DETERMINISTIC
Definition variables.h:545
@ SOLUTION_CONVERGENCE_STATISTICAL_STEADY
Definition variables.h:546
@ SOLUTION_CONVERGENCE_STEADY_DETERMINISTIC
Definition variables.h:544
SolutionConvergenceMode solutionConvergenceMode
Definition variables.h:762
PetscInt64 searchAttempts
Definition variables.h:239
ExecutionMode exec_mode
Definition variables.h:714
PetscInt64 tieBreakCount
Definition variables.h:247
PetscReal ti
Definition variables.h:704
PetscReal summationRHS
Definition variables.h:858
PetscInt64 maxTraversalFailCount
Definition variables.h:246
Cmpnts vertices[8]
Coordinates of the eight vertices of the cell.
Definition variables.h:178
PetscReal particleLoadImbalance
Definition variables.h:839
Vec P_o
Definition variables.h:946
@ BC_FACE_NEG_X
Definition variables.h:262
Defines the vertices of a single hexahedral grid cell.
Definition variables.h:177
A 3D point or vector with PetscScalar components.
Definition variables.h:102
The master context for the entire simulation.
Definition variables.h:695
User-defined context containing data specific to a single computational grid level.
Definition variables.h:906