PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
test_particle_kernels.c
Go to the documentation of this file.
1/**
2 * @file test_particle_kernels.c
3 * @brief C unit tests for particle walking-search helpers and the initial-value expression engine.
4 */
5
6#include "test_support.h"
7
8#include "walkingsearch.h"
10
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14/**
15 * @brief Tests whether a cell index lies within the local grid ownership range.
16 */
17
18static PetscErrorCode TestCheckCellWithinLocalGrid(void)
19{
20 SimCtx *simCtx = NULL;
21 UserCtx *user = NULL;
22 PetscBool within = PETSC_FALSE;
23
24 PetscFunctionBeginUser;
25 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
26
27 PetscCall(CheckCellWithinLocalGrid(user, 1, 1, 1, &within));
28 PetscCall(PicurvAssertBool(within, "cell (1,1,1) should be within the serial local grid"));
29
30 PetscCall(CheckCellWithinLocalGrid(user, 4, 1, 1, &within));
31 PetscCall(PicurvAssertBool((PetscBool)!within, "cell (4,1,1) should fall outside the valid local cell range"));
32
33 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
34 PetscFunctionReturn(0);
35}
36/**
37 * @brief Tests initialization of traversal parameters for particle search.
38 */
39
40static PetscErrorCode TestInitializeTraversalParameters(void)
41{
42 SimCtx *simCtx = NULL;
43 UserCtx *user = NULL;
44 Particle particle;
45 PetscInt idx = -1, idy = -1, idz = -1, steps = -1;
46
47 PetscFunctionBeginUser;
48 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
49 PetscCall(PetscMemzero(&particle, sizeof(particle)));
50
51 particle.PID = 42;
52 particle.cell[0] = 1;
53 particle.cell[1] = 1;
54 particle.cell[2] = 1;
55
56 PetscCall(InitializeTraversalParameters(user, &particle, &idx, &idy, &idz, &steps));
57 PetscCall(PicurvAssertIntEqual(1, idx, "InitializeTraversalParameters should preserve prior i"));
58 PetscCall(PicurvAssertIntEqual(1, idy, "InitializeTraversalParameters should preserve prior j"));
59 PetscCall(PicurvAssertIntEqual(1, idz, "InitializeTraversalParameters should preserve prior k"));
60 PetscCall(PicurvAssertIntEqual(0, steps, "InitializeTraversalParameters should reset traversal steps"));
61
62 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
63 PetscFunctionReturn(0);
64}
65/**
66 * @brief Tests retrieval of the current cell from logical particle coordinates.
67 */
68
69static PetscErrorCode TestRetrieveCurrentCell(void)
70{
71 SimCtx *simCtx = NULL;
72 UserCtx *user = NULL;
73 Cell cell;
74
75 PetscFunctionBeginUser;
76 PetscCall(PicurvCreateMinimalContexts(&simCtx, &user, 4, 4, 4));
77 PetscCall(PetscMemzero(&cell, sizeof(cell)));
78
79 PetscCall(RetrieveCurrentCell(user, 1, 1, 1, &cell));
80 PetscCall(PicurvAssertRealNear(0.25, cell.vertices[0].x, 1.0e-12, "vertex 0 x coordinate"));
81 PetscCall(PicurvAssertRealNear(0.25, cell.vertices[0].y, 1.0e-12, "vertex 0 y coordinate"));
82 PetscCall(PicurvAssertRealNear(0.25, cell.vertices[0].z, 1.0e-12, "vertex 0 z coordinate"));
83 PetscCall(PicurvAssertRealNear(0.50, cell.vertices[5].x, 1.0e-12, "vertex 5 x coordinate"));
84 PetscCall(PicurvAssertRealNear(0.50, cell.vertices[5].y, 1.0e-12, "vertex 5 y coordinate"));
85 PetscCall(PicurvAssertRealNear(0.50, cell.vertices[5].z, 1.0e-12, "vertex 5 z coordinate"));
86
87 PetscCall(PicurvDestroyMinimalContexts(&simCtx, &user));
88 PetscFunctionReturn(0);
89}
90static const char *const kConformanceNames[] = {"x", "y", "z"};
91
92/**
93 * @brief Tests the runtime evaluator against every shared conformance case.
94 * @details `tests/fixtures/expression_conformance.txt` is also evaluated by
95 * `generators/ic.gen` in `tests/test_expression_language.py`, so the two
96 * implementations of the language cannot drift apart unnoticed.
97 */
98static PetscErrorCode TestExpressionConformance(void)
99{
100 FILE *file = fopen("tests/fixtures/expression_conformance.txt", "r");
101 char line[1024];
102 PetscInt cases = 0;
103
104 PetscFunctionBeginUser;
105 PetscCheck(file, PETSC_COMM_SELF, PETSC_ERR_FILE_OPEN, "Cannot open the expression conformance cases.");
106 while (fgets(line, sizeof(line), file)) {
107 char *fields[5];
108 PetscInt count = 0;
109 PicurvExpression *expression = NULL;
110 PetscReal values[3], result = 0.0;
111
112 if (line[0] == '#' || line[0] == '\n') continue;
113 for (char *cursor = line; count < 5; ++count) {
114 fields[count] = cursor;
115 char *separator = strchr(cursor, ';');
116 if (!separator) { ++count; break; }
117 *separator = '\0';
118 cursor = separator + 1;
119 }
120 PetscCheck(count == 5, PETSC_COMM_SELF, PETSC_ERR_FILE_UNEXPECTED, "Malformed conformance line.");
121 for (int v = 0; v < 3; ++v) values[v] = strtod(fields[v + 1], NULL);
122 PetscCall(PicurvExpressionCompile(fields[0], kConformanceNames, 3, PETSC_FALSE, &expression));
123 PetscCall(PicurvExpressionEvaluate(expression, values, NULL, &result));
124 PetscCall(PicurvAssertRealNear(strtod(fields[4], NULL), result, 1.0e-12, fields[0]));
125 PetscCall(PicurvExpressionDestroy(&expression));
126 ++cases;
127 }
128 fclose(file);
129 PetscCall(PicurvAssertBool((PetscBool)(cases >= 20), "the conformance file should hold its cases"));
130 PetscFunctionReturn(0);
131}
132
133/**
134 * @brief Tests that the compiler refuses what the Python validator also refuses.
135 */
136static PetscErrorCode TestExpressionRejectsOutsideLanguage(void)
137{
138 const char *bad[] = {"0x1F", "1_000", "x // 2", "abs", "abs(1, 2)", "where(x, 1)", "foo",
139 "x +", "(x", "x y", "uniform(x)", "uniform(1.5)", "1.e", "not"};
140
141 PetscFunctionBeginUser;
142 for (size_t n = 0; n < sizeof(bad) / sizeof(bad[0]); ++n) {
143 PicurvExpression *expression = NULL;
144 PetscErrorCode refused;
145
146 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
147 refused = PicurvExpressionCompile(bad[n], kConformanceNames, 3, PETSC_TRUE, &expression);
148 PetscCall(PetscPopErrorHandler());
149 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, refused, bad[n]));
150 }
151 {
152 PicurvExpression *expression = NULL;
153 PetscErrorCode refused;
154
155 PetscCall(PetscPushErrorHandler(PetscIgnoreErrorHandler, NULL));
156 refused = PicurvExpressionCompile("uniform()", kConformanceNames, 3, PETSC_FALSE, &expression);
157 PetscCall(PetscPopErrorHandler());
158 PetscCall(PicurvAssertIntEqual(PETSC_ERR_ARG_WRONG, refused, "draws need allow_random"));
159 }
160 PetscFunctionReturn(0);
161}
162
163/**
164 * @brief Tests that random draws are pure functions of their key.
165 * @details The same particle always draws the same number; a different particle, event,
166 * or field default stream draws independently; an explicit stream is shared
167 * across fields; and the normal draw has zero mean and unit variance.
168 */
169static PetscErrorCode TestExpressionDrawsArePureAndKeyed(void)
170{
171 PicurvExpression *uniform = NULL, *shared = NULL, *normal = NULL;
172 PicurvExpressionDrawKey key = {12345, 7, 0, 6};
173 PetscReal a, b, sum = 0.0, square = 0.0;
174 const PetscInt samples = 20000;
175
176 PetscFunctionBeginUser;
177 PetscCall(PicurvExpressionCompile("uniform()", kConformanceNames, 3, PETSC_TRUE, &uniform));
178 PetscCall(PicurvExpressionCompile("uniform(3)", kConformanceNames, 3, PETSC_TRUE, &shared));
179 PetscCall(PicurvExpressionCompile("normal()", kConformanceNames, 3, PETSC_TRUE, &normal));
180
181 PetscCall(PicurvExpressionEvaluate(uniform, NULL, &key, &a));
182 PetscCall(PicurvExpressionEvaluate(uniform, NULL, &key, &b));
183 PetscCall(PicurvAssertBool((PetscBool)(a == b && a >= 0.0 && a < 1.0), "a draw is pure and in [0, 1)"));
184 key.pid = 8;
185 PetscCall(PicurvExpressionEvaluate(uniform, NULL, &key, &b));
186 PetscCall(PicurvAssertBool((PetscBool)(a != b), "another particle draws independently"));
187 key.pid = 7;
188 key.salt = 1;
189 PetscCall(PicurvExpressionEvaluate(uniform, NULL, &key, &b));
190 PetscCall(PicurvAssertBool((PetscBool)(a != b), "another event draws independently"));
191 key.salt = 0;
192 key.field = 5;
193 PetscCall(PicurvExpressionEvaluate(uniform, NULL, &key, &b));
194 PetscCall(PicurvAssertBool((PetscBool)(a != b), "another field's default stream is independent"));
195 PetscCall(PicurvExpressionEvaluate(shared, NULL, &key, &a));
196 key.field = 6;
197 PetscCall(PicurvExpressionEvaluate(shared, NULL, &key, &b));
198 PetscCall(PicurvAssertBool((PetscBool)(a == b), "an explicit stream is shared across fields"));
199
200 for (PetscInt pid = 0; pid < samples; ++pid) {
201 key.pid = pid;
202 PetscCall(PicurvExpressionEvaluate(normal, NULL, &key, &a));
203 sum += a;
204 square += a * a;
205 }
206 PetscCall(PicurvAssertRealNear(0.0, sum / samples, 0.03, "normal draws have zero mean"));
207 PetscCall(PicurvAssertRealNear(1.0, square / samples, 0.04, "normal draws have unit variance"));
208
209 PetscCall(PicurvExpressionDestroy(&uniform));
210 PetscCall(PicurvExpressionDestroy(&shared));
211 PetscCall(PicurvExpressionDestroy(&normal));
212 PetscFunctionReturn(0);
213}
214
215/**
216 * @brief Runs the unit-particles PETSc test binary.
217 */
218
219int main(int argc, char **argv)
220{
221 PetscErrorCode ierr;
222 const PicurvTestCase cases[] = {
223 {"check-cell-within-local-grid", TestCheckCellWithinLocalGrid},
224 {"initialize-traversal-parameters", TestInitializeTraversalParameters},
225 {"retrieve-current-cell", TestRetrieveCurrentCell},
226 {"expression-conformance", TestExpressionConformance},
227 {"expression-rejects-outside-language", TestExpressionRejectsOutsideLanguage},
228 {"expression-draws-are-pure-and-keyed", TestExpressionDrawsArePureAndKeyed},
229 };
230
231 ierr = PetscInitialize(&argc, &argv, NULL, "PICurv particle kernel tests");
232 if (ierr) {
233 return (int)ierr;
234 }
235
236 ierr = PicurvRunTests("unit-particles", cases, sizeof(cases) / sizeof(cases[0]));
237 if (ierr) {
238 PetscFinalize();
239 return (int)ierr;
240 }
241
242 ierr = PetscFinalize();
243 return (int)ierr;
244}
Configured initial values of particle-carried fields, and the expression language that defines them.
PetscErrorCode PicurvExpressionEvaluate(const PicurvExpression *expression, const PetscReal *values, const PicurvExpressionDrawKey *key, PetscReal *result)
Evaluate a compiled expression.
PetscErrorCode PicurvExpressionDestroy(PicurvExpression **expression)
Free a compiled expression.
PetscInt64 pid
Particle identity; draws are pure functions of it.
PetscErrorCode PicurvExpressionCompile(const char *text, const char *const *names, PetscInt name_count, PetscBool allow_random, PicurvExpression **expression)
Compile one expression against a list of variable names.
PetscInt64 salt
Event that produced the particle: 0 for the t=0 population.
PetscInt64 field
Field whose expression draws, the default stream's namespace.
What a random draw is keyed on, besides the particle and its stream.
static PetscErrorCode TestCheckCellWithinLocalGrid(void)
Tests whether a cell index lies within the local grid ownership range.
static PetscErrorCode TestExpressionConformance(void)
Tests the runtime evaluator against every shared conformance case.
static PetscErrorCode TestExpressionRejectsOutsideLanguage(void)
Tests that the compiler refuses what the Python validator also refuses.
int main(int argc, char **argv)
Runs the unit-particles PETSc test binary.
static PetscErrorCode TestInitializeTraversalParameters(void)
Tests initialization of traversal parameters for particle search.
static PetscErrorCode TestRetrieveCurrentCell(void)
Tests retrieval of the current cell from logical particle coordinates.
static PetscErrorCode TestExpressionDrawsArePureAndKeyed(void)
Tests that random draws are pure functions of their key.
static const char *const kConformanceNames[]
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 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 PicurvAssertIntEqual(PetscInt expected, PetscInt actual, const char *context)
Asserts that two integer values are equal.
PetscErrorCode PicurvAssertBool(PetscBool value, const char *context)
Asserts that one boolean condition is true.
Shared declarations for the PICurv C test fixture and assertion layer.
Named test case descriptor consumed by PicurvRunTests.
PetscInt cell[3]
Definition variables.h:210
PetscScalar x
Definition variables.h:122
PetscScalar z
Definition variables.h:122
PetscScalar y
Definition variables.h:122
PetscInt64 PID
Definition variables.h:209
Cmpnts vertices[8]
Coordinates of the eight vertices of the cell.
Definition variables.h:204
Defines the vertices of a single hexahedral grid cell.
Definition variables.h:203
Defines a particle's core properties for Lagrangian tracking.
Definition variables.h:208
The master context for the entire simulation.
Definition variables.h:859
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1074
Header file for particle location functions using the walking search algorithm.
PetscErrorCode RetrieveCurrentCell(UserCtx *user, PetscInt idx, PetscInt idy, PetscInt idz, Cell *cell)
Retrieves the coordinates of the eight vertices of the current cell.
PetscErrorCode InitializeTraversalParameters(UserCtx *user, Particle *particle, PetscInt *idx, PetscInt *idy, PetscInt *idz, PetscInt *traversal_steps)
Initializes traversal parameters for locating a particle.
PetscErrorCode CheckCellWithinLocalGrid(UserCtx *user, PetscInt idx, PetscInt idy, PetscInt idz, PetscBool *is_within)
Checks if the current cell indices are within the local grid boundaries.