Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://www.mooseframework.org
3 : //*
4 : //* All rights reserved, see COPYRIGHT for full restrictions
5 : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 : //*
7 : //* Licensed under LGPL 2.1, please see LICENSE for details
8 : //* https://www.gnu.org/licenses/lgpl-2.1.html
9 :
10 : #include "KokkosFunctionParser.h"
11 :
12 : #include "MooseUtils.h"
13 :
14 : namespace Moose::Kokkos
15 : {
16 :
17 471 : PEGParser::PEGParser(const std::string & expression, const ConsoleStream * console)
18 260 : : _parser(R"(# Entry
19 : Start <- _ Expr _
20 :
21 : # Whitespace helpers (ignored: never appear as AST nodes)
22 : ~WS <- [ \t\r\n]*
23 : ~_ <- WS
24 :
25 : # Expressions (lowest precedence at the top)
26 : Expr <- OrExpr
27 :
28 : # Logical OR (left-assocative via tail recursion)
29 : OrExpr <- AndExpr OrTail?
30 : OrTail <- _ OrOp _ AndExpr OrTail?
31 : OrOp <- '|'
32 :
33 : # Logical AND (left-assocative via tail recursion)
34 : AndExpr <- CmpExpr AndTail?
35 : AndTail <- _ AndOp _ CmpExpr AndTail?
36 : AndOp <- '&'
37 :
38 : # Comparisons (binary, non-associative: at most one comparison)
39 : CmpExpr <- AddExpr (_ CompOp _ AddExpr)?
40 : CompOp <- '<=' / '>=' / '<' / '>' / '=' / '!='
41 :
42 : # Addition/subtraction (left-assocative via tail recursion)
43 : AddExpr <- MulExpr AddTail?
44 : AddTail <- _ AddOp _ MulExpr AddTail?
45 : AddOp <- '+' / '-'
46 :
47 : # Multiplication/division (left-assocative via tail recursion)
48 : MulExpr <- UnaryExpr MulTail?
49 : MulTail <- _ MulOp _ UnaryExpr MulTail?
50 : MulOp <- '*' / '/'
51 :
52 : # Unary prefix (strictly unary; right-recursive chain)
53 : UnaryExpr <- (_ UnaryOp _ UnaryExpr) / PowExpr
54 : UnaryOp <- '-' / '!'
55 :
56 : # Exponentiation (binary, right-associative)
57 : PowExpr <- Primary (_ PowOp _ PowExpr)?
58 : PowOp <- '^'
59 :
60 : # Atoms
61 : Primary <- FunctionCall
62 : / Number
63 : / Identifier
64 : / '(' _ Expr _ ')'
65 :
66 : # Function calls with zero or more arguments
67 : FunctionCall <- Identifier _ '(' _ ArgList? _ ')' { no_ast_opt }
68 : ArgList <- Expr (_ ',' _ Expr)*
69 :
70 : # Identifiers
71 : Identifier <- [a-zA-Z_] [a-zA-Z0-9_]*
72 :
73 : # Numbers (integer/real/scientific)
74 : Number <- (# .5 or .5e-2
75 : '.' [0-9]+ ([eE] [+-]? [0-9]+)?
76 : /
77 : # 2. or 2.0 or 42 or with exponent
78 : [0-9]+ ('.' [0-9]*)? ([eE] [+-]? [0-9]+)?)
79 : )"),
80 260 : _expression(expression)
81 : {
82 471 : _parser.enable_ast();
83 :
84 471 : if (console)
85 257 : _parser.set_logger(
86 306 : [&](std::size_t line, std::size_t col, const std::string & msg, const std::string &)
87 0 : { *console << line << ":" << col << ": " << msg << std::endl; });
88 :
89 471 : if (!_parser.parse(expression.c_str(), _ast))
90 26 : mooseError("Kokkos parsed function error: expression parsing failed.");
91 :
92 445 : _ast = _parser.optimize_ast(_ast);
93 523 : }
94 :
95 : void
96 2385 : RPNBuilder::build(const peg::Ast & ast)
97 : {
98 2385 : checkFinalized();
99 :
100 2385 : if (ast.name == "Start" || ast.name == "Primary" || ast.name == "ArgList")
101 140 : for (const auto & node : ast.nodes)
102 101 : build(*node);
103 2346 : else if (ast.name == "Number")
104 870 : _rpn.push_back({Opcode::NUM, ast.token_to_string(), addNumber(ast.token_to_number<Real>())});
105 1911 : else if (ast.name == "Identifier")
106 : {
107 712 : if (_function_opcode_map.count(ast.token_to_string()))
108 8 : builderError(ast, "variable name '" + ast.token_to_string() + "' is reserved for function.");
109 :
110 1420 : _rpn.push_back({Opcode::VAR, ast.token_to_string(), addVariable(ast.token_to_string())});
111 : }
112 1199 : else if (ast.name == "UnaryExpr")
113 : {
114 : // nodes: [UnaryOp, UnaryExpr]
115 22 : build(*ast.nodes[1]);
116 22 : const auto op = ast.nodes[0]->token_to_string();
117 22 : _rpn.push_back({libmesh_map_find(_unary_opcode_map, op), op});
118 22 : }
119 1774 : else if (ast.name == "OrExpr" || ast.name == "AndExpr" || ast.name == "AddExpr" ||
120 597 : ast.name == "MulExpr")
121 : {
122 : // nodes: [LeftOperand, Tail]
123 407 : build(*ast.nodes[0]);
124 407 : build(*ast.nodes[1]);
125 : }
126 1057 : else if (ast.name == "OrTail" || ast.name == "AndTail" || ast.name == "AddTail" ||
127 287 : ast.name == "MulTail")
128 : {
129 : // nodes: [Op, RightOperand, ?NextTail]
130 491 : build(*ast.nodes[1]);
131 491 : const auto op = ast.nodes[0]->token_to_string();
132 491 : _rpn.push_back({libmesh_map_find(_binary_opcode_map, op), op});
133 491 : if (ast.nodes.size() == 3)
134 84 : build(*ast.nodes[2]);
135 491 : }
136 279 : else if (ast.name == "CmpExpr" || ast.name == "PowExpr")
137 : {
138 : // nodes: [LeftOperand, Op, RightOperand]
139 155 : build(*ast.nodes[0]);
140 155 : build(*ast.nodes[2]);
141 155 : const auto op = ast.nodes[1]->token_to_string();
142 155 : _rpn.push_back({libmesh_map_find(_binary_opcode_map, op), op});
143 155 : }
144 124 : else if (ast.name == "FunctionCall")
145 : {
146 : // nodes: [Identifier, ?ArgList]
147 124 : const auto func = ast.nodes[0]->token_to_string();
148 :
149 124 : auto it = _function_opcode_map.find(func);
150 124 : if (it == _function_opcode_map.end())
151 6 : builderError(ast, "unsupported function '" + func + "()'.");
152 :
153 122 : if (ast.nodes.size() == 1)
154 6 : builderError(ast, "argument(s) missing for function '" + func + "()'.");
155 :
156 120 : const auto & args = *ast.nodes[1];
157 :
158 120 : unsigned int num_args = 1;
159 120 : if (args.name == "ArgList")
160 41 : num_args = args.nodes.size();
161 :
162 120 : if (num_args != it->second.second)
163 10 : builderError(ast, func + "() expects " + std::to_string(it->second.second) + " argument(s).");
164 :
165 118 : build(args);
166 :
167 118 : _rpn.push_back({it->second.first, func});
168 124 : }
169 5453 : }
170 :
171 : void
172 17 : RPNBuilder::printRPN(const ConsoleStream & console) const
173 : {
174 17 : std::vector<std::string> instructions;
175 :
176 289 : for (const auto & instruction : _rpn)
177 272 : instructions.push_back(instruction.text);
178 :
179 34 : console << std::endl << MooseUtils::join(instructions, " ") << std::endl << std::endl;
180 17 : }
181 :
182 : void
183 94 : RPNBuilder::addDefaultVariables()
184 : {
185 94 : checkFinalized();
186 :
187 94 : _has_default_variables = true;
188 :
189 188 : addVariable("x");
190 188 : addVariable("y");
191 188 : addVariable("z");
192 94 : addVariable("t");
193 94 : }
194 :
195 : unsigned int
196 435 : RPNBuilder::addNumber(Real number)
197 : {
198 435 : checkFinalized();
199 :
200 435 : _numbers.push_back(number);
201 435 : return _numbers.size() - 1;
202 : }
203 :
204 : unsigned int
205 3600 : RPNBuilder::addVariable(const std::string & name)
206 : {
207 3600 : checkFinalized();
208 :
209 3600 : auto it = _variables.find(name);
210 3600 : if (it != _variables.end())
211 710 : return it->second.idx;
212 :
213 : // Default variables: x, y, z, t
214 2890 : unsigned int idx = _variables.size() + (_has_default_variables ? 0 : 4);
215 :
216 2890 : _variables[name].idx = idx;
217 :
218 2890 : return idx;
219 : }
220 :
221 : void
222 2290 : RPNBuilder::associateScalar(const std::string & name, const Real * scalar)
223 : {
224 2290 : checkFinalized();
225 :
226 2290 : auto it = _variables.find(name);
227 2290 : if (it == _variables.end())
228 0 : mooseError("Kokkos parsed function error: variable '",
229 : name,
230 : "' was not added but attempted to associate a value.");
231 :
232 2290 : if (it->second.associated())
233 0 : mooseError("Kokkos parsed function error: variable '",
234 : name,
235 : "' is already associated and cannot associate another value.");
236 :
237 2290 : it->second.scalar = scalar;
238 2290 : }
239 :
240 : void
241 34 : RPNBuilder::associateProperty(const std::string & name, const MaterialProperty<Real> * property)
242 : {
243 34 : checkFinalized();
244 :
245 34 : auto it = _variables.find(name);
246 34 : if (it == _variables.end())
247 0 : mooseError("Kokkos parsed function error: variable '",
248 : name,
249 : "' was not added but attempted to associate a material property.");
250 :
251 34 : if (it->second.associated())
252 0 : mooseError("Kokkos parsed function error: variable '",
253 : name,
254 : "' is already associated and cannot associate another material property.");
255 :
256 34 : it->second.property = property;
257 34 : }
258 :
259 : void
260 163 : RPNBuilder::associateField(const std::string & name, const VariableValue * field)
261 : {
262 163 : checkFinalized();
263 :
264 163 : auto it = _variables.find(name);
265 163 : if (it == _variables.end())
266 0 : mooseError("Kokkos parsed function error: variable '",
267 : name,
268 : "' was not added but attempted to associate a field variable.");
269 :
270 163 : if (it->second.associated())
271 0 : mooseError("Kokkos parsed function error: variable '",
272 : name,
273 : "' is already associated and cannot associate another field variable.");
274 :
275 163 : it->second.field = field;
276 163 : }
277 :
278 : void
279 27 : RPNBuilder::associateFunction(const std::string & name, const Function * function)
280 : {
281 27 : checkFinalized();
282 :
283 27 : auto it = _variables.find(name);
284 27 : if (it == _variables.end())
285 0 : mooseError("Kokkos parsed function error: variable '",
286 : name,
287 : "' was not added but attempted to associate a function.");
288 :
289 27 : if (it->second.associated())
290 0 : mooseError("Kokkos parsed function error: variable '",
291 : name,
292 : "' is already associated and cannot associate another function.");
293 :
294 27 : it->second.function = function;
295 27 : }
296 :
297 : void
298 8 : RPNBuilder::builderError(const peg::Ast & ast, const std::string & message) const
299 : {
300 8 : std::string arrow(ast.column, ' ');
301 8 : arrow.back() = '^';
302 :
303 8 : mooseError("Kokkos parsed function error: ", message, "\n", _parser.expression(), "\n", arrow);
304 8 : }
305 :
306 : void
307 9028 : RPNBuilder::checkFinalized()
308 : {
309 9028 : if (_finalized)
310 0 : mooseError("Kokkos parsed function error: cannot update builder after finalization.");
311 9028 : }
312 :
313 20212 : RPNEvaluator::RPNEvaluator(const RPNEvaluator & evaluator)
314 17047 : : _rpn(evaluator._rpn),
315 17047 : _numbers(evaluator._numbers),
316 17047 : _variables(evaluator._variables),
317 17047 : _num_scalars(evaluator._num_scalars),
318 17047 : _num_fields(evaluator._num_fields),
319 17047 : _num_properties(evaluator._num_properties),
320 34094 : _num_functions(evaluator._num_functions)
321 : {
322 20212 : _scalars.create(_num_scalars);
323 20212 : _fields.create(_num_fields);
324 20212 : _properties.create<false>(_num_properties);
325 20212 : _functions.create<false>(_num_functions);
326 :
327 121422 : for (const auto i : make_range(_variables.size()))
328 : {
329 101210 : auto ptr = evaluator._pointers[i];
330 101210 : if (!ptr)
331 80564 : continue;
332 :
333 20646 : const auto type = _variables[i].first;
334 20646 : const auto idx = _variables[i].second;
335 :
336 20646 : if (type == VariableType::SCALAR)
337 6684 : _scalars[idx] = *static_cast<const Real *>(ptr);
338 13962 : else if (type == VariableType::FIELD)
339 8484 : _fields[idx] = *static_cast<const VariableValue *>(ptr);
340 5478 : else if (type == VariableType::MATERIAL)
341 1530 : new (&_properties[idx])
342 528 : MaterialProperty<Real>(*static_cast<const MaterialProperty<Real> *>(ptr));
343 4476 : else if (type == VariableType::FUNCTION)
344 4476 : new (&_functions[idx]) Function(*static_cast<const Function *>(ptr));
345 : }
346 :
347 20212 : _scalars.copyToDevice();
348 20212 : _fields.copyToDevice();
349 20212 : _properties.copyToDevice();
350 20212 : _functions.copyToDevice();
351 20212 : }
352 :
353 : void
354 437 : RPNEvaluator::init(const RPNBuilder & builder)
355 : {
356 437 : if (!builder.finalized())
357 0 : mooseError("Kokkos parsed function error: attempted to initialize evaluator with a builder "
358 : "which was not finalized.");
359 :
360 437 : const auto & numbers = builder.getNumbers();
361 437 : const auto & variables = builder.getVariables();
362 437 : const auto & rpn = builder.getRPN();
363 :
364 437 : _numbers = numbers;
365 :
366 437 : _variables.create(variables.size() + (builder.hasDefaultVariables() ? 0 : 4));
367 437 : _pointers.resize(_variables.size(), nullptr);
368 :
369 437 : _num_scalars = 0;
370 437 : _num_fields = 0;
371 437 : _num_properties = 0;
372 437 : _num_functions = 0;
373 :
374 3231 : for (const auto & [name, var] : variables)
375 : {
376 2794 : if (var.scalar)
377 : {
378 2194 : _variables[var.idx].first = VariableType::SCALAR;
379 2194 : _variables[var.idx].second = _num_scalars++;
380 2194 : _pointers[var.idx] = var.scalar;
381 : }
382 600 : else if (var.field)
383 : {
384 163 : _variables[var.idx].first = VariableType::FIELD;
385 163 : _variables[var.idx].second = _num_fields++;
386 163 : _pointers[var.idx] = var.field;
387 : }
388 437 : else if (var.property)
389 : {
390 34 : _variables[var.idx].first = VariableType::MATERIAL;
391 34 : _variables[var.idx].second = _num_properties++;
392 34 : _pointers[var.idx] = var.property;
393 : }
394 403 : else if (var.function)
395 : {
396 27 : _variables[var.idx].first = VariableType::FUNCTION;
397 27 : _variables[var.idx].second = _num_functions++;
398 27 : _pointers[var.idx] = var.function;
399 : }
400 376 : else if (var.idx >= 4)
401 0 : mooseError(
402 : "Kokkos parsed function error: variable '", name, "' was not associated with any value.");
403 : }
404 :
405 437 : _variables.copyToDevice();
406 :
407 437 : _rpn.create(rpn.size());
408 :
409 2368 : for (const auto i : make_range(rpn.size()))
410 1931 : _rpn[i] = {rpn[i].op, rpn[i].arg};
411 :
412 437 : _rpn.copyToDevice();
413 437 : }
414 :
415 : } // namespace Moose::Kokkos
|