PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
ParticleInitialConditions.c
Go to the documentation of this file.
1/**
2 * @file ParticleInitialConditions.c
3 * @brief Configured initial values of particle-carried fields, and the expression
4 * language that defines them.
5 *
6 * Expressions are parsed into a small tree, because a chained comparison evaluates its
7 * middle operand twice, and compiled to postfix code that a stack evaluator runs once per
8 * particle. Evaluation is pure: random draws are hashed from the particle's identity, so
9 * a value does not depend on rank count, particle order, or how often it is evaluated.
10 */
11
13
14#include <ctype.h>
15#include <math.h>
16#include <stdint.h>
17#include <stdlib.h>
18#include <string.h>
19
20#include "logging.h"
21
22/* ------------------------------------------------------------------------- */
23/* Expression tree */
24/* ------------------------------------------------------------------------- */
25
33
34/** @brief Name, arity, and opcode of every callable function. */
35static const struct {
36 const char *name;
37 int min_args;
38 int max_args;
39 ExpressionOp op;
40 PetscBool random;
41} kFunctions[] = {
42 {"abs", 1, 1, OP_ABS, PETSC_FALSE}, {"cos", 1, 1, OP_COS, PETSC_FALSE},
43 {"exp", 1, 1, OP_EXP, PETSC_FALSE}, {"sin", 1, 1, OP_SIN, PETSC_FALSE},
44 {"sqrt", 1, 1, OP_SQRT, PETSC_FALSE}, {"tan", 1, 1, OP_TAN, PETSC_FALSE},
45 {"maximum", 2, 2, OP_MAXIMUM, PETSC_FALSE}, {"minimum", 2, 2, OP_MINIMUM, PETSC_FALSE},
46 {"where", 3, 3, OP_WHERE, PETSC_FALSE},
47 {"uniform", 0, 1, OP_UNIFORM, PETSC_TRUE}, {"normal", 0, 1, OP_NORMAL, PETSC_TRUE},
48};
49
50/** @brief One tree node; children index the node pool. */
51typedef struct {
53 PetscReal value; /* OP_CONST value; explicit stream of a draw */
54 PetscInt var; /* OP_VAR index; 1 for a draw on an explicit stream */
55 PetscInt child[3];
56 PetscInt nchild;
58
59/** @brief One postfix instruction. */
60typedef struct {
62 PetscReal value;
63 PetscInt arg;
65
68 PetscInt length;
69 PetscInt max_depth;
70 PetscBool draws;
71};
72
73/** @brief Parser state: the text, a cursor, the node pool, and the names in scope. */
74typedef struct {
75 const char *text;
76 size_t pos;
78 PetscInt count;
79 PetscInt capacity;
80 const char *const *names;
81 PetscInt name_count;
82 PetscBool allow_random;
83 char error[256];
85
86/** @brief Record the first parse error with its position; later ones are consequences. */
87static void ParserFail(ExpressionParser *parser, const char *message)
88{
89 if (!parser->error[0]) {
90 snprintf(parser->error, sizeof(parser->error), "%s at position %zu of '%s'",
91 message, parser->pos + 1, parser->text);
92 }
93}
94
95/** @brief Skip whitespace before the next token. */
96static void SkipSpace(ExpressionParser *parser)
97{
98 while (parser->text[parser->pos] && isspace((unsigned char)parser->text[parser->pos])) parser->pos++;
99}
100
101/** @brief Consume @p token if it is next, and report whether it was. */
102static PetscBool Accept(ExpressionParser *parser, const char *token)
103{
104 size_t length = strlen(token);
105
106 SkipSpace(parser);
107 if (strncmp(parser->text + parser->pos, token, length) != 0) return PETSC_FALSE;
108 if (isalpha((unsigned char)token[0])) {
109 const char next = parser->text[parser->pos + length];
110 if (isalnum((unsigned char)next) || next == '_') return PETSC_FALSE;
111 }
112 /* `*` must not match the first half of `**`, nor `<` the first half of `<=`. */
113 if ((!strcmp(token, "*") && parser->text[parser->pos + 1] == '*') ||
114 ((!strcmp(token, "<") || !strcmp(token, ">")) && parser->text[parser->pos + 1] == '=')) {
115 return PETSC_FALSE;
116 }
117 parser->pos += length;
118 return PETSC_TRUE;
119}
120
121/** @brief Append a node to the pool and return its index, or -1 once an error is set. */
122static PetscInt NewNode(ExpressionParser *parser, ExpressionOp op, PetscInt nchild,
123 PetscInt a, PetscInt b, PetscInt c)
124{
125 if (parser->error[0]) return -1;
126 if (parser->count == parser->capacity) {
127 ParserFail(parser, "expression is too long");
128 return -1;
129 }
130 ExpressionNode *node = &parser->nodes[parser->count];
131 memset(node, 0, sizeof(*node));
132 node->op = op;
133 node->nchild = nchild;
134 node->child[0] = a;
135 node->child[1] = b;
136 node->child[2] = c;
137 return parser->count++;
138}
139
140static PetscInt ParseOr(ExpressionParser *parser);
141
142/** @brief number | name | name(args) | (expression) */
143static PetscInt ParseAtom(ExpressionParser *parser)
144{
145 SkipSpace(parser);
146 const char *start = parser->text + parser->pos;
147
148 if (isdigit((unsigned char)*start) || (*start == '.' && isdigit((unsigned char)start[1]))) {
149 /* Decimal literals only, as the Python validator requires. */
150 size_t length = 0;
151 while (isdigit((unsigned char)start[length])) length++;
152 if (start[length] == '.') { length++; while (isdigit((unsigned char)start[length])) length++; }
153 if (start[length] == 'e' || start[length] == 'E') {
154 size_t exponent = length + 1;
155 if (start[exponent] == '+' || start[exponent] == '-') exponent++;
156 if (!isdigit((unsigned char)start[exponent])) { ParserFail(parser, "malformed number"); return -1; }
157 while (isdigit((unsigned char)start[exponent])) exponent++;
158 length = exponent;
159 }
160 if (isalnum((unsigned char)start[length]) || start[length] == '_' || start[length] == '.') {
161 ParserFail(parser, "malformed number");
162 return -1;
163 }
164 const PetscReal value = strtod(start, NULL);
165 parser->pos += length;
166 PetscInt node = NewNode(parser, OP_CONST, 0, -1, -1, -1);
167 if (node >= 0) parser->nodes[node].value = value;
168 return node;
169 }
170 if (isalpha((unsigned char)*start) || *start == '_') {
171 char name[64];
172 size_t length = 0;
173 while (isalnum((unsigned char)start[length]) || start[length] == '_') length++;
174 if (length >= sizeof(name)) { ParserFail(parser, "name is too long"); return -1; }
175 memcpy(name, start, length);
176 name[length] = '\0';
177 if (!strcmp(name, "and") || !strcmp(name, "or") || !strcmp(name, "not")) {
178 ParserFail(parser, "operator where an operand was expected");
179 return -1;
180 }
181 parser->pos += length;
182 if (Accept(parser, "(")) {
183 for (size_t f = 0; f < sizeof(kFunctions) / sizeof(kFunctions[0]); ++f) {
184 if (strcmp(name, kFunctions[f].name)) continue;
185 if (kFunctions[f].random && !parser->allow_random) {
186 ParserFail(parser, "random draws are not available in this expression");
187 return -1;
188 }
189 PetscInt args[3] = {-1, -1, -1}, nargs = 0;
190 if (!Accept(parser, ")")) {
191 do {
192 if (nargs == 3) { ParserFail(parser, "too many arguments"); return -1; }
193 args[nargs++] = ParseOr(parser);
194 if (parser->error[0]) return -1;
195 } while (Accept(parser, ","));
196 if (!Accept(parser, ")")) { ParserFail(parser, "expected ')'"); return -1; }
197 }
198 if (nargs < kFunctions[f].min_args || nargs > kFunctions[f].max_args) {
199 ParserFail(parser, "wrong number of arguments");
200 return -1;
201 }
202 if (kFunctions[f].random) {
203 /* A draw's stream is a constant so that two expressions naming the
204 same stream draw the same number for the same particle. */
205 PetscInt node = NewNode(parser, kFunctions[f].op, 0, -1, -1, -1);
206 if (node < 0) return -1;
207 if (nargs == 1) {
208 const ExpressionNode *stream = &parser->nodes[args[0]];
209 if (stream->op != OP_CONST || stream->value < 0.0 ||
210 stream->value != PetscFloorReal(stream->value)) {
211 ParserFail(parser, "a draw's stream must be a non-negative integer literal");
212 return -1;
213 }
214 parser->nodes[node].value = stream->value;
215 parser->nodes[node].var = 1;
216 }
217 return node;
218 }
219 return NewNode(parser, kFunctions[f].op, nargs, args[0], args[1], args[2]);
220 }
221 ParserFail(parser, "unknown function");
222 return -1;
223 }
224 if (!strcmp(name, "pi")) {
225 PetscInt node = NewNode(parser, OP_CONST, 0, -1, -1, -1);
226 if (node >= 0) parser->nodes[node].value = PETSC_PI;
227 return node;
228 }
229 for (PetscInt v = 0; v < parser->name_count; ++v) {
230 if (!strcmp(name, parser->names[v])) {
231 PetscInt node = NewNode(parser, OP_VAR, 0, -1, -1, -1);
232 if (node >= 0) parser->nodes[node].var = v;
233 return node;
234 }
235 }
236 ParserFail(parser, "unknown name");
237 return -1;
238 }
239 if (Accept(parser, "(")) {
240 PetscInt inner = ParseOr(parser);
241 if (!Accept(parser, ")")) { ParserFail(parser, "expected ')'"); return -1; }
242 return inner;
243 }
244 ParserFail(parser, "expected a number, name, or '('");
245 return -1;
246}
247
248static PetscInt ParseUnary(ExpressionParser *parser);
249
250/**
251 * @brief Parses an atom and, when a power operator follows, appends a power node.
252 * @details Grammar: `atom ['**' unary]`.
253 * The exponent is parsed as a unary, so `2**-1` is valid and `a**b**c` groups to
254 * the right; a unary minus on the left is parsed by the caller, so `-2**2` is -4.
255 * @param[in,out] parser Parser state; its position advances past the power.
256 * @return Node index of the power (or of the bare atom), or -1 after an error.
257 */
258static PetscInt ParsePower(ExpressionParser *parser)
259{
260 PetscInt base = ParseAtom(parser);
261 if (Accept(parser, "**")) return NewNode(parser, OP_POW, 2, base, ParseUnary(parser), -1);
262 return base;
263}
264
265/**
266 * @brief Parses a sign prefix recursively and wraps the operand in a negate or identity node.
267 * @param[in,out] parser Parser state; its position advances past the operand.
268 * @return Node index of the signed operand, or -1 after an error.
269 */
270static PetscInt ParseUnary(ExpressionParser *parser)
271{
272 if (Accept(parser, "-")) return NewNode(parser, OP_NEG, 1, ParseUnary(parser), -1, -1);
273 if (Accept(parser, "+")) return NewNode(parser, OP_POS, 1, ParseUnary(parser), -1, -1);
274 return ParsePower(parser);
275}
276
277/**
278 * @brief Parses a left-associative chain of `*`, `/`, `%`, folding each operator into a node.
279 * @details `//` is refused explicitly, since the Python side does not accept floor division.
280 * @param[in,out] parser Parser state; its position advances past the term.
281 * @return Node index of the term, or -1 after an error.
282 */
283static PetscInt ParseTerm(ExpressionParser *parser)
284{
285 PetscInt left = ParseUnary(parser);
286 for (;;) {
287 if (Accept(parser, "*")) left = NewNode(parser, OP_MUL, 2, left, ParseUnary(parser), -1);
288 else if (Accept(parser, "/")) {
289 if (parser->text[parser->pos] == '/') { ParserFail(parser, "'//' is not supported"); return -1; }
290 left = NewNode(parser, OP_DIV, 2, left, ParseUnary(parser), -1);
291 } else if (Accept(parser, "%")) left = NewNode(parser, OP_MOD, 2, left, ParseUnary(parser), -1);
292 else return left;
293 }
294}
295
296/**
297 * @brief Parses a left-associative chain of `+` and `-`, folding each operator into a node.
298 * @param[in,out] parser Parser state; its position advances past the sum.
299 * @return Node index of the sum, or -1 after an error.
300 */
301static PetscInt ParseSum(ExpressionParser *parser)
302{
303 PetscInt left = ParseTerm(parser);
304 for (;;) {
305 if (Accept(parser, "+")) left = NewNode(parser, OP_ADD, 2, left, ParseTerm(parser), -1);
306 else if (Accept(parser, "-")) left = NewNode(parser, OP_SUB, 2, left, ParseTerm(parser), -1);
307 else return left;
308 }
309}
310
311/** @brief sum (comparison sum)*; a chain `a < b < c` is `(a < b) and (b < c)`. */
312static PetscInt ParseComparison(ExpressionParser *parser)
313{
314 static const struct { const char *token; ExpressionOp op; } kComparisons[] = {
315 {"==", OP_EQ}, {"!=", OP_NE}, {"<=", OP_LE}, {">=", OP_GE}, {"<", OP_LT}, {">", OP_GT},
316 };
317 PetscInt left = ParseSum(parser), result = -1;
318
319 for (;;) {
321 for (size_t c = 0; c < sizeof(kComparisons) / sizeof(kComparisons[0]); ++c) {
322 if (Accept(parser, kComparisons[c].token)) { op = kComparisons[c].op; break; }
323 }
324 if (op == OP_CONST) return (result < 0) ? left : result;
325 const PetscInt right = ParseSum(parser);
326 const PetscInt pair = NewNode(parser, op, 2, left, right, -1);
327 result = (result < 0) ? pair : NewNode(parser, OP_AND, 2, result, pair, -1);
328 left = right;
329 }
330}
331
332/** @brief 'not' not | comparison */
333static PetscInt ParseNot(ExpressionParser *parser)
334{
335 if (Accept(parser, "not")) return NewNode(parser, OP_NOT, 1, ParseNot(parser), -1, -1);
336 return ParseComparison(parser);
337}
338
339/** @brief not ('and' not)* */
340static PetscInt ParseAnd(ExpressionParser *parser)
341{
342 PetscInt left = ParseNot(parser);
343 while (Accept(parser, "and")) left = NewNode(parser, OP_AND, 2, left, ParseNot(parser), -1);
344 return left;
345}
346
347/** @brief and ('or' and)* */
348static PetscInt ParseOr(ExpressionParser *parser)
349{
350 PetscInt left = ParseAnd(parser);
351 while (Accept(parser, "or")) left = NewNode(parser, OP_OR, 2, left, ParseAnd(parser), -1);
352 return left;
353}
354
355/** @brief Emit postfix code for a subtree and track the stack depth it needs. */
356static void EmitNode(const ExpressionParser *parser, PetscInt index, PicurvExpression *expression,
357 PetscInt *depth)
358{
359 const ExpressionNode *node = &parser->nodes[index];
360
361 for (PetscInt c = 0; c < node->nchild; ++c) EmitNode(parser, node->child[c], expression, depth);
362 ExpressionInstruction *instruction = &expression->code[expression->length++];
363 instruction->op = node->op;
364 instruction->value = node->value;
365 instruction->arg = node->var;
366 *depth += 1 - node->nchild;
367 if (*depth > expression->max_depth) expression->max_depth = *depth;
368 if (node->op == OP_UNIFORM || node->op == OP_NORMAL) expression->draws = PETSC_TRUE;
369}
370
371/** @brief Count the instructions a subtree emits (shared subtrees count per use). */
372static PetscInt CountCode(const ExpressionParser *parser, PetscInt index)
373{
374 const ExpressionNode *node = &parser->nodes[index];
375 PetscInt total = 1;
376
377 for (PetscInt c = 0; c < node->nchild; ++c) total += CountCode(parser, node->child[c]);
378 return total;
379}
380
381/**
382 * @brief Implementation of \ref PicurvExpressionCompile().
383 * @see PicurvExpressionCompile()
384 */
385PetscErrorCode PicurvExpressionCompile(const char *text, const char *const *names, PetscInt name_count,
386 PetscBool allow_random, PicurvExpression **expression)
387{
388 ExpressionParser parser;
389 PetscInt root, depth = 0;
390
391 PetscFunctionBeginUser;
392 PetscCheck(text && expression, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Expression text and output are required.");
393 PetscCall(PetscMemzero(&parser, sizeof(parser)));
394 parser.text = text;
395 parser.names = names;
396 parser.name_count = name_count;
397 parser.allow_random = allow_random;
398 parser.capacity = 4 * (PetscInt)strlen(text) + 8;
399 PetscCall(PetscMalloc1(parser.capacity, &parser.nodes));
400
401 root = ParseOr(&parser);
402 SkipSpace(&parser);
403 if (!parser.error[0] && parser.text[parser.pos]) ParserFail(&parser, "unexpected text");
404 if (parser.error[0]) {
405 PetscCall(PetscFree(parser.nodes));
406 SETERRQ(PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Invalid expression: %s.", parser.error);
407 }
408
409 PetscCall(PetscNew(expression));
410 (*expression)->length = 0;
411 PetscCall(PetscMalloc1(CountCode(&parser, root), &(*expression)->code));
412 EmitNode(&parser, root, *expression, &depth);
413 PetscCall(PetscFree(parser.nodes));
414 PetscFunctionReturn(0);
415}
416
417/** @brief splitmix64 finalizer: a bijective mix of 64 bits. */
418static uint64_t Mix64(uint64_t z)
419{
420 z += 0x9E3779B97F4A7C15ULL;
421 z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL;
422 z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL;
423 return z ^ (z >> 31);
424}
425
426/** @brief Uniform on [0, 1) from the top 53 bits of a hash. */
427static PetscReal UnitFromHash(uint64_t hash)
428{
429 return (PetscReal)(hash >> 11) * (1.0 / 9007199254740992.0);
430}
431
432/** @brief A draw keyed on the particle, the event, the stream, and the function. */
433static PetscReal Draw(const PicurvExpressionDrawKey *key, const ExpressionInstruction *instruction)
434{
435 /* A default stream is the drawing field's own, so two fields draw independently; an
436 explicit stream is shared by every expression that names it. */
437 const uint64_t space = instruction->arg ? 1u : 0u;
438 const uint64_t stream = instruction->arg ? (uint64_t)instruction->value : (uint64_t)key->field;
439 uint64_t hash = Mix64((uint64_t)key->seed);
440 hash = Mix64(hash ^ space);
441 hash = Mix64(hash ^ stream);
442 hash = Mix64(hash ^ (uint64_t)key->pid);
443 hash = Mix64(hash ^ (uint64_t)key->salt);
444 hash = Mix64(hash ^ (uint64_t)instruction->op);
445 if (instruction->op == OP_UNIFORM) return UnitFromHash(hash);
446 /* Box-Muller from two independent uniforms; u1 lies in (0, 1]. */
447 const PetscReal u1 = 1.0 - UnitFromHash(hash);
448 const PetscReal u2 = UnitFromHash(Mix64(hash ^ 0xD1B54A32D192ED03ULL));
449 return PetscSqrtReal(-2.0 * PetscLogReal(u1)) * PetscCosReal(2.0 * PETSC_PI * u2);
450}
451
452/** @brief Maximum or minimum that propagates NaN, as numpy does. */
453static PetscReal Extreme(PetscReal a, PetscReal b, PetscBool maximum)
454{
455 if (PetscIsNanReal(a) || PetscIsNanReal(b)) return NAN;
456 return maximum ? PetscMax(a, b) : PetscMin(a, b);
457}
458
459/**
460 * @brief Implementation of \ref PicurvExpressionEvaluate().
461 * @see PicurvExpressionEvaluate()
462 */
463PetscErrorCode PicurvExpressionEvaluate(const PicurvExpression *expression, const PetscReal *values,
464 const PicurvExpressionDrawKey *key, PetscReal *result)
465{
466 PetscReal stack[expression->max_depth + 1];
467 PetscInt top = 0;
468
469 PetscFunctionBeginUser;
470 PetscCheck(!expression->draws || key, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL,
471 "An expression that draws random numbers needs a draw key.");
472 for (PetscInt i = 0; i < expression->length; ++i) {
473 const ExpressionInstruction *instruction = &expression->code[i];
474 PetscReal a, b, c;
475
476 switch (instruction->op) {
477 case OP_CONST: stack[top++] = instruction->value; break;
478 case OP_VAR: stack[top++] = values[instruction->arg]; break;
479 case OP_UNIFORM:
480 case OP_NORMAL: stack[top++] = Draw(key, instruction); break;
481 case OP_NEG: stack[top - 1] = -stack[top - 1]; break;
482 case OP_POS: break;
483 case OP_NOT: stack[top - 1] = (stack[top - 1] == 0.0) ? 1.0 : 0.0; break;
484 case OP_ABS: stack[top - 1] = PetscAbsReal(stack[top - 1]); break;
485 case OP_COS: stack[top - 1] = PetscCosReal(stack[top - 1]); break;
486 case OP_EXP: stack[top - 1] = PetscExpReal(stack[top - 1]); break;
487 case OP_SIN: stack[top - 1] = PetscSinReal(stack[top - 1]); break;
488 case OP_SQRT: stack[top - 1] = PetscSqrtReal(stack[top - 1]); break;
489 case OP_TAN: stack[top - 1] = PetscTanReal(stack[top - 1]); break;
490 case OP_WHERE:
491 c = stack[--top]; b = stack[--top]; a = stack[top - 1];
492 stack[top - 1] = (a != 0.0) ? b : c;
493 break;
494 default:
495 b = stack[--top]; a = stack[top - 1];
496 switch (instruction->op) {
497 case OP_ADD: a = a + b; break;
498 case OP_SUB: a = a - b; break;
499 case OP_MUL: a = a * b; break;
500 case OP_DIV: a = a / b; break;
501 /* Python's modulus takes the sign of the divisor. */
502 case OP_MOD: a = (b == 0.0) ? NAN : a - b * PetscFloorReal(a / b); break;
503 case OP_POW: a = PetscPowReal(a, b); break;
504 case OP_EQ: a = (a == b) ? 1.0 : 0.0; break;
505 case OP_NE: a = (a != b) ? 1.0 : 0.0; break;
506 case OP_LT: a = (a < b) ? 1.0 : 0.0; break;
507 case OP_LE: a = (a <= b) ? 1.0 : 0.0; break;
508 case OP_GT: a = (a > b) ? 1.0 : 0.0; break;
509 case OP_GE: a = (a >= b) ? 1.0 : 0.0; break;
510 case OP_AND: a = (a != 0.0 && b != 0.0) ? 1.0 : 0.0; break;
511 case OP_OR: a = (a != 0.0 || b != 0.0) ? 1.0 : 0.0; break;
512 case OP_MAXIMUM: a = Extreme(a, b, PETSC_TRUE); break;
513 case OP_MINIMUM: a = Extreme(a, b, PETSC_FALSE); break;
514 default: SETERRQ(PETSC_COMM_SELF, PETSC_ERR_PLIB, "Unknown expression opcode %d.", (int)instruction->op);
515 }
516 stack[top - 1] = a;
517 }
518 }
519 *result = stack[0];
520 PetscFunctionReturn(0);
521}
522
523/**
524 * @brief Implementation of \ref PicurvExpressionDestroy().
525 * @see PicurvExpressionDestroy()
526 */
527PetscErrorCode PicurvExpressionDestroy(PicurvExpression **expression)
528{
529 PetscFunctionBeginUser;
530 if (!expression || !*expression) PetscFunctionReturn(0);
531 PetscCall(PetscFree((*expression)->code));
532 PetscCall(PetscFree(*expression));
533 PetscFunctionReturn(0);
534}
535
536/* ------------------------------------------------------------------------- */
537/* Plans */
538/* ------------------------------------------------------------------------- */
539
540/** @brief Names a particle expression may use, in the order their values are supplied. */
541static const char *const kParticleVariables[] = {"x", "y", "z", "xn", "yn", "zn", "pid", "t"};
542#define PARTICLE_VARIABLE_COUNT ((PetscInt)(sizeof(kParticleVariables) / sizeof(kParticleVariables[0])))
543#define PARTICLE_FIELD_MAX_COMPONENTS 3
544#define PARTICLE_FIELD_EXPRESSION_LENGTH 4096
545#define PARTICLE_FIELD_HISTOGRAM_BINS 10
546
547/** @brief One configured field: its identity and one expression per component. */
553
558
559/**
560 * @brief Implementation of \ref ParticleFieldPlanCreate().
561 * @see ParticleFieldPlanCreate()
562 */
564{
565 PetscInt count = 0;
566 PetscBool found = PETSC_FALSE;
567 char option[256];
568
569 PetscFunctionBeginUser;
570 PetscCheck(plan, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Plan output is required.");
571 *plan = NULL;
572 PetscCall(PetscSNPrintf(option, sizeof(option), "-particle_fields_count"));
573 PetscCall(PetscOptionsGetInt(NULL, NULL, option, &count, &found));
574 if (!found || count == 0) PetscFunctionReturn(0);
575 PetscCheck(count > 0 && count <= PARTICLE_FIELD_ID_COUNT, PETSC_COMM_WORLD, PETSC_ERR_ARG_OUTOFRANGE,
576 "%s must be between 0 and %d (got %" PetscInt_FMT ").", option, (int)PARTICLE_FIELD_ID_COUNT, count);
577
578 PetscCall(PetscNew(plan));
579 PetscCall(PetscCalloc1(count, &(*plan)->bindings));
580 (*plan)->count = count;
581 for (PetscInt i = 0; i < count; ++i) {
582 ParticleFieldBinding *binding = &(*plan)->bindings[i];
583 const ParticleFieldDescriptor *descriptor = NULL;
584 char name[64];
585
586 PetscCall(PetscSNPrintf(option, sizeof(option), "-particle_fields_%" PetscInt_FMT "_name", i));
587 PetscCall(PetscOptionsGetString(NULL, NULL, option, name, sizeof(name), &found));
588 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "%s is missing.", option);
589 PetscCall(ParticleFieldIdFromName(name, &binding->field));
590 PetscCall(ParticleFieldGetDescriptor(binding->field, &descriptor));
591 PetscCheck(descriptor->capabilities & PARTICLE_FIELD_CAPABILITY_USER_INITIALIZE, PETSC_COMM_WORLD,
592 PETSC_ERR_ARG_WRONG,
593 "Particle field '%s' cannot be given an initial value: the runtime sets it from the "
594 "Eulerian fields or from particle location.", name);
595 PetscCheck(descriptor->dimension.kind == FIELD_DIMENSION_FIXED && descriptor->data_type == PETSC_REAL,
596 PETSC_COMM_WORLD, PETSC_ERR_PLIB, "Settable particle field '%s' must be a real quantity.", name);
597 for (PetscInt j = 0; j < i; ++j) {
598 PetscCheck((*plan)->bindings[j].field != binding->field, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG,
599 "Particle field '%s' is configured twice.", name);
600 }
601 binding->components = descriptor->components;
602 PetscCheck(binding->components <= PARTICLE_FIELD_MAX_COMPONENTS, PETSC_COMM_WORLD, PETSC_ERR_PLIB,
603 "Particle field '%s' has more components than an initial value supports.", name);
604 for (PetscInt c = 0; c < binding->components; ++c) {
606
607 PetscCall(PetscSNPrintf(option, sizeof(option), "-particle_fields_%" PetscInt_FMT "_expr_%" PetscInt_FMT, i, c));
608 PetscCall(PetscOptionsGetString(NULL, NULL, option, text, sizeof(text), &found));
609 PetscCheck(found, PETSC_COMM_WORLD, PETSC_ERR_ARG_WRONG, "%s is missing.", option);
611 &binding->component[c]));
612 }
613 }
614 PetscFunctionReturn(0);
615}
616
617/** @brief Global domain bounds, in solver units, from the replicated rank bounding boxes. */
618static PetscErrorCode DomainBounds(const SimCtx *simCtx, Cmpnts *lower, Cmpnts *upper)
619{
620 PetscMPIInt size = 1;
621
622 PetscFunctionBeginUser;
623 PetscCheck(simCtx->bboxlist, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONGSTATE,
624 "Rank bounding boxes are needed before a particle initial value is applied.");
625 PetscCallMPI(MPI_Comm_size(PETSC_COMM_WORLD, &size));
626 *lower = simCtx->bboxlist[0].min_coords;
627 *upper = simCtx->bboxlist[0].max_coords;
628 for (PetscInt r = 1; r < (PetscInt)size * simCtx->block_number; ++r) {
629 lower->x = PetscMin(lower->x, simCtx->bboxlist[r].min_coords.x);
630 lower->y = PetscMin(lower->y, simCtx->bboxlist[r].min_coords.y);
631 lower->z = PetscMin(lower->z, simCtx->bboxlist[r].min_coords.z);
632 upper->x = PetscMax(upper->x, simCtx->bboxlist[r].max_coords.x);
633 upper->y = PetscMax(upper->y, simCtx->bboxlist[r].max_coords.y);
634 upper->z = PetscMax(upper->z, simCtx->bboxlist[r].max_coords.z);
635 }
636 PetscFunctionReturn(0);
637}
638
639/** @brief Position within the domain along one axis, in [0, 1]; 0 across a flat axis. */
640static PetscReal Normalized(PetscReal value, PetscReal lower, PetscReal upper)
641{
642 return (upper > lower) ? (value - lower) / (upper - lower) : 0.0;
643}
644
645/**
646 * @brief Implementation of \ref ParticleFieldPlanApply().
647 * @see ParticleFieldPlanApply()
648 */
649PetscErrorCode ParticleFieldPlanApply(UserCtx *user, const ParticleFieldPlan *plan,
650 const ParticleFieldEvent *event, PetscInt first, PetscInt end)
651{
652 SimCtx *simCtx = NULL;
653 const PetscReal *positions = NULL;
654 const PetscInt64 *pids = NULL;
655 Cmpnts lower, upper;
656
657 PetscFunctionBeginUser;
658 if (!plan || end <= first) PetscFunctionReturn(0);
659 PetscCheck(user && user->swarm && event, PETSC_COMM_SELF, PETSC_ERR_ARG_NULL, "Swarm and event are required.");
660 simCtx = user->simCtx;
661 PetscCall(DomainBounds(simCtx, &lower, &upper));
662
663 PetscCall(DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&positions));
664 PetscCall(DMSwarmGetField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pids));
665 for (PetscInt b = 0; b < plan->count; ++b) {
666 const ParticleFieldBinding *binding = &plan->bindings[b];
667 const ParticleFieldDescriptor *descriptor = NULL;
668 PetscReal *field = NULL, scale = 1.0;
669
670 PetscCall(ParticleFieldGetDescriptor(binding->field, &descriptor));
671 /* A value is physical; the swarm stores solver units. */
672 PetscCall(FieldDimensionReferenceScale(&simCtx->scaling, descriptor->dimension, &scale));
673 PetscCall(DMSwarmGetField(user->swarm, descriptor->canonical_name, NULL, NULL, (void **)&field));
674 for (PetscInt p = first; p < end; ++p) {
675 const PetscReal *position = &positions[3 * p];
676 const PetscReal values[] = {
677 position[0] * simCtx->scaling.L_ref, position[1] * simCtx->scaling.L_ref,
678 position[2] * simCtx->scaling.L_ref,
679 Normalized(position[0], lower.x, upper.x), Normalized(position[1], lower.y, upper.y),
680 Normalized(position[2], lower.z, upper.z),
681 (PetscReal)pids[p], event->physical_time,
682 };
683 const PicurvExpressionDrawKey key = {
684 (PetscInt64)simCtx->particleRandomSeed, pids[p], event->salt, (PetscInt64)binding->field,
685 };
686 for (PetscInt c = 0; c < binding->components; ++c) {
687 PetscReal value = 0.0;
688
689 PetscCall(PicurvExpressionEvaluate(binding->component[c], values, &key, &value));
690 PetscCheck(!PetscIsInfOrNanReal(value), PETSC_COMM_SELF, PETSC_ERR_FP,
691 "The initial value of '%s' is not finite for particle %lld at (%g, %g, %g).",
692 descriptor->canonical_name, (long long)pids[p], (double)values[0],
693 (double)values[1], (double)values[2]);
694 field[binding->components * p + c] = value / scale;
695 }
696 }
697 PetscCall(DMSwarmRestoreField(user->swarm, descriptor->canonical_name, NULL, NULL, (void **)&field));
698 }
699 PetscCall(DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_PID), NULL, NULL, (void **)&pids));
700 PetscCall(DMSwarmRestoreField(user->swarm, ParticleFieldName(PARTICLE_FIELD_ID_POSITION), NULL, NULL, (void **)&positions));
701 PetscFunctionReturn(0);
702}
703
704/**
705 * @brief Implementation of \ref ParticleFieldPlanSummarize().
706 * @see ParticleFieldPlanSummarize()
707 */
708PetscErrorCode ParticleFieldPlanSummarize(UserCtx *user, const ParticleFieldPlan *plan)
709{
710 SimCtx *simCtx = NULL;
711 FILE *csv = NULL;
712 PetscInt nlocal = 0;
713
714 PetscFunctionBeginUser;
715 if (!plan) PetscFunctionReturn(0);
716 simCtx = user->simCtx;
717 PetscCall(DMSwarmGetLocalSize(user->swarm, &nlocal));
718 if (simCtx->rank == 0) {
719 char path[PETSC_MAX_PATH_LEN];
720
721 PetscCall(PetscSNPrintf(path, sizeof(path), "%s/particle_initial_fields.csv", simCtx->analysis_dir));
722 csv = fopen(path, "w");
723 if (!csv) {
724 LOG_ALLOW(GLOBAL, LOG_WARNING, "Could not write '%s'.\n", path);
725 } else {
726 fprintf(csv, "field,component,count,mean,variance,min,max");
727 for (int bin = 0; bin < PARTICLE_FIELD_HISTOGRAM_BINS; ++bin) fprintf(csv, ",bin_%d", bin);
728 fprintf(csv, "\n");
729 }
730 }
731
732 for (PetscInt b = 0; b < plan->count; ++b) {
733 const ParticleFieldBinding *binding = &plan->bindings[b];
734 const ParticleFieldDescriptor *descriptor = NULL;
735 const PetscReal *field = NULL;
736 PetscReal scale = 1.0;
737
738 PetscCall(ParticleFieldGetDescriptor(binding->field, &descriptor));
739 PetscCall(FieldDimensionReferenceScale(&simCtx->scaling, descriptor->dimension, &scale));
740 PetscCall(DMSwarmGetField(user->swarm, descriptor->canonical_name, NULL, NULL, (void **)&field));
741 for (PetscInt c = 0; c < binding->components; ++c) {
742 /* Sums and extremes in one reduction, then a histogram over the global range. */
743 PetscReal sums[3] = {0.0, 0.0, 0.0}, extremes[2] = {PETSC_MAX_REAL, PETSC_MAX_REAL};
744 PetscReal bins_local[PARTICLE_FIELD_HISTOGRAM_BINS] = {0.0}, bins[PARTICLE_FIELD_HISTOGRAM_BINS];
745
746 for (PetscInt p = 0; p < nlocal; ++p) {
747 const PetscReal value = field[binding->components * p + c] * scale;
748 sums[0] += 1.0;
749 sums[1] += value;
750 sums[2] += value * value;
751 extremes[0] = PetscMin(extremes[0], value);
752 extremes[1] = PetscMin(extremes[1], -value);
753 }
754 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, sums, 3, MPIU_REAL, MPI_SUM, PETSC_COMM_WORLD));
755 PetscCallMPI(MPI_Allreduce(MPI_IN_PLACE, extremes, 2, MPIU_REAL, MPI_MIN, PETSC_COMM_WORLD));
756 const PetscReal count = sums[0], lowest = extremes[0], highest = -extremes[1];
757 const PetscReal mean = (count > 0.0) ? sums[1] / count : 0.0;
758 const PetscReal variance = (count > 0.0) ? PetscMax(sums[2] / count - mean * mean, 0.0) : 0.0;
759 const PetscReal width = (highest > lowest) ? (highest - lowest) / PARTICLE_FIELD_HISTOGRAM_BINS : 1.0;
760
761 for (PetscInt p = 0; p < nlocal; ++p) {
762 const PetscReal value = field[binding->components * p + c] * scale;
763 PetscInt bin = (PetscInt)((value - lowest) / width);
764 bins_local[PetscMax(0, PetscMin(bin, PARTICLE_FIELD_HISTOGRAM_BINS - 1))] += 1.0;
765 }
766 PetscCallMPI(MPI_Allreduce(bins_local, bins, PARTICLE_FIELD_HISTOGRAM_BINS, MPIU_REAL, MPI_SUM,
767 PETSC_COMM_WORLD));
769 "[Particle IC] %s[%" PetscInt_FMT "]: n=%.0f mean=%.6g var=%.6g min=%.6g max=%.6g\n",
770 descriptor->canonical_name, c, (double)count, (double)mean, (double)variance,
771 (double)(count > 0.0 ? lowest : 0.0), (double)(count > 0.0 ? highest : 0.0));
772 if (csv) {
773 fprintf(csv, "%s,%d,%.0f,%.10e,%.10e,%.10e,%.10e", descriptor->canonical_name, (int)c,
774 (double)count, (double)mean, (double)variance,
775 (double)(count > 0.0 ? lowest : 0.0), (double)(count > 0.0 ? highest : 0.0));
776 for (int bin = 0; bin < PARTICLE_FIELD_HISTOGRAM_BINS; ++bin) fprintf(csv, ",%.0f", (double)bins[bin]);
777 fprintf(csv, "\n");
778 }
779 }
780 PetscCall(DMSwarmRestoreField(user->swarm, descriptor->canonical_name, NULL, NULL, (void **)&field));
781 }
782 if (csv) fclose(csv);
783 PetscFunctionReturn(0);
784}
785
786/**
787 * @brief Implementation of \ref ParticleFieldPlanDestroy().
788 * @see ParticleFieldPlanDestroy()
789 */
791{
792 PetscFunctionBeginUser;
793 if (!plan || !*plan) PetscFunctionReturn(0);
794 for (PetscInt b = 0; b < (*plan)->count; ++b) {
795 for (PetscInt c = 0; c < PARTICLE_FIELD_MAX_COMPONENTS; ++c) {
796 PetscCall(PicurvExpressionDestroy(&(*plan)->bindings[b].component[c]));
797 }
798 }
799 PetscCall(PetscFree((*plan)->bindings));
800 PetscCall(PetscFree(*plan));
801 PetscFunctionReturn(0);
802}
static const char *const kParticleVariables[]
Names a particle expression may use, in the order their values are supplied.
static PetscErrorCode DomainBounds(const SimCtx *simCtx, Cmpnts *lower, Cmpnts *upper)
Global domain bounds, in solver units, from the replicated rank bounding boxes.
static void EmitNode(const ExpressionParser *parser, PetscInt index, PicurvExpression *expression, PetscInt *depth)
Emit postfix code for a subtree and track the stack depth it needs.
static PetscInt ParseComparison(ExpressionParser *parser)
sum (comparison sum)*; a chain a < b < c is (a < b) and (b < c).
static PetscInt ParseAtom(ExpressionParser *parser)
number | name | name(args) | (expression)
#define PARTICLE_VARIABLE_COUNT
static void SkipSpace(ExpressionParser *parser)
Skip whitespace before the next token.
PetscErrorCode PicurvExpressionEvaluate(const PicurvExpression *expression, const PetscReal *values, const PicurvExpressionDrawKey *key, PetscReal *result)
Implementation of PicurvExpressionEvaluate().
PetscErrorCode PicurvExpressionDestroy(PicurvExpression **expression)
Implementation of PicurvExpressionDestroy().
ParticleFieldBinding * bindings
PetscErrorCode ParticleFieldPlanCreate(ParticleFieldPlan **plan)
Implementation of ParticleFieldPlanCreate().
PetscErrorCode ParticleFieldPlanSummarize(UserCtx *user, const ParticleFieldPlan *plan)
Implementation of ParticleFieldPlanSummarize().
#define PARTICLE_FIELD_MAX_COMPONENTS
static PetscInt ParseUnary(ExpressionParser *parser)
Parses a sign prefix recursively and wraps the operand in a negate or identity node.
static PetscInt ParseNot(ExpressionParser *parser)
'not' not | comparison
static PetscInt CountCode(const ExpressionParser *parser, PetscInt index)
Count the instructions a subtree emits (shared subtrees count per use).
#define PARTICLE_FIELD_HISTOGRAM_BINS
const char *const * names
static uint64_t Mix64(uint64_t z)
splitmix64 finalizer: a bijective mix of 64 bits.
static const struct @1 kFunctions[]
Name, arity, and opcode of every callable function.
ExpressionInstruction * code
static PetscBool Accept(ExpressionParser *parser, const char *token)
Consume token if it is next, and report whether it was.
PicurvExpression * component[3]
static PetscReal Draw(const PicurvExpressionDrawKey *key, const ExpressionInstruction *instruction)
A draw keyed on the particle, the event, the stream, and the function.
PetscErrorCode ParticleFieldPlanDestroy(ParticleFieldPlan **plan)
Implementation of ParticleFieldPlanDestroy().
static PetscReal Extreme(PetscReal a, PetscReal b, PetscBool maximum)
Maximum or minimum that propagates NaN, as numpy does.
static void ParserFail(ExpressionParser *parser, const char *message)
Record the first parse error with its position; later ones are consequences.
static PetscInt NewNode(ExpressionParser *parser, ExpressionOp op, PetscInt nchild, PetscInt a, PetscInt b, PetscInt c)
Append a node to the pool and return its index, or -1 once an error is set.
PetscErrorCode PicurvExpressionCompile(const char *text, const char *const *names, PetscInt name_count, PetscBool allow_random, PicurvExpression **expression)
Implementation of PicurvExpressionCompile().
static PetscInt ParseSum(ExpressionParser *parser)
Parses a left-associative chain of + and -, folding each operator into a node.
static PetscReal UnitFromHash(uint64_t hash)
Uniform on [0, 1) from the top 53 bits of a hash.
static PetscInt ParseTerm(ExpressionParser *parser)
Parses a left-associative chain of *, /, %, folding each operator into a node.
static PetscInt ParseOr(ExpressionParser *parser)
and ('or' and)*
static PetscInt ParseAnd(ExpressionParser *parser)
not ('and' not)*
PetscErrorCode ParticleFieldPlanApply(UserCtx *user, const ParticleFieldPlan *plan, const ParticleFieldEvent *event, PetscInt first, PetscInt end)
Implementation of ParticleFieldPlanApply().
static PetscReal Normalized(PetscReal value, PetscReal lower, PetscReal upper)
Position within the domain along one axis, in [0, 1]; 0 across a flat axis.
static PetscInt ParsePower(ExpressionParser *parser)
Parses an atom and, when a power operator follows, appends a power node.
#define PARTICLE_FIELD_EXPRESSION_LENGTH
One postfix instruction.
One tree node; children index the node pool.
Parser state: the text, a cursor, the node pool, and the names in scope.
One configured field: its identity and one expression per component.
Configured initial values of particle-carried fields, and the expression language that defines them.
PetscInt64 pid
Particle identity; draws are pure functions of it.
PetscInt64 salt
Event identity keying random draws: 0 for the t=0 population.
PetscInt64 salt
Event that produced the particle: 0 for the t=0 population.
PetscInt64 field
Field whose expression draws, the default stream's namespace.
PetscReal physical_time
Time, in seconds, the evaluated particles appear at.
PetscInt64 seed
Run seed (-particle_random_seed).
When and why a plan is applied.
What a random draw is keyed on, besides the particle and its stream.
@ FIELD_DIMENSION_FIXED
The exponents are the field's dimension.
PetscErrorCode FieldDimensionReferenceScale(const ScalingCtx *scaling, FieldDimension dimension, PetscReal *scale)
Return the factor that turns a solver value of one dimension into physical units.
FieldDimensionKind kind
Logging utilities and macros for PETSc-based applications.
#define GLOBAL
Scope for global logging across all processes.
Definition logging.h:46
#define LOG_ALLOW(scope, level, fmt,...)
Logging macro that checks both the log level and whether the calling function is in the allowed-funct...
Definition logging.h:200
@ LOG_INFO
Informational messages about program execution.
Definition logging.h:31
@ LOG_WARNING
Non-critical issues that warrant attention.
Definition logging.h:30
const char * ParticleFieldName(ParticleFieldId field_id)
Return the canonical PETSc DMSwarm name for an ID.
ParticleFieldId
Compile-time identity for a persistent solver-particle field.
@ PARTICLE_FIELD_ID_POSITION
@ PARTICLE_FIELD_ID_PID
@ PARTICLE_FIELD_ID_COUNT
PetscErrorCode ParticleFieldIdFromName(const char *field_name, ParticleFieldId *field_id)
Resolve a canonical name or registered alias at a text-ingress boundary.
@ PARTICLE_FIELD_CAPABILITY_USER_INITIALIZE
The particle carries the value, so a configured initial value survives: nothing re-derives it from th...
PetscErrorCode ParticleFieldGetDescriptor(ParticleFieldId field_id, const ParticleFieldDescriptor **descriptor)
Return immutable metadata for a valid particle field ID.
Immutable metadata for one persistent particle field.
PetscMPIInt rank
Definition variables.h:867
PetscInt block_number
Definition variables.h:958
SimCtx * simCtx
Back-pointer to the master simulation context.
Definition variables.h:1074
PetscReal L_ref
Definition variables.h:846
Cmpnts max_coords
Maximum x, y, z coordinates of the bounding box.
Definition variables.h:204
Cmpnts min_coords
Minimum x, y, z coordinates of the bounding box.
Definition variables.h:203
PetscScalar x
Definition variables.h:122
char analysis_dir[PETSC_MAX_PATH_LEN]
Definition variables.h:888
BoundingBox * bboxlist
Definition variables.h:995
PetscScalar z
Definition variables.h:122
ScalingCtx scaling
Definition variables.h:952
PetscInt particleRandomSeed
Base seed for every particle RNG stream (-particle_random_seed).
Definition variables.h:993
PetscScalar y
Definition variables.h:122
A 3D point or vector with PetscScalar components.
Definition variables.h:121
The master context for the entire simulation.
Definition variables.h:864
User-defined context containing data specific to a single computational grid level.
Definition variables.h:1071
A generic C-style linked list node for integers.
Definition variables.h:470