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 1167 : PEGParser::PEGParser(const std::string & expression, const ConsoleStream * console)
18 612 : : _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 612 : _expression(expression)
81 : {
82 1167 : _parser.enable_ast();
83 :
84 1167 : if (console)
85 953 : _parser.set_logger(
86 1010 : [&](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 1167 : if (!_parser.parse(expression.c_str(), _ast))
90 26 : mooseError("Kokkos parsed function error: expression parsing failed.");
91 :
92 1141 : _ast = _parser.optimize_ast(_ast);
93 1219 : }
94 :
95 : void
96 7649 : RPNBuilder::build(const peg::Ast & ast)
97 : {
98 7649 : checkFinalized();
99 :
100 7649 : if (ast.name == "Start" || ast.name == "Primary" || ast.name == "ArgList")
101 140 : for (const auto & node : ast.nodes)
102 101 : build(*node);
103 7610 : else if (ast.name == "Number")
104 3254 : _rpn.push_back({Opcode::NUM, ast.token_to_string(), addNumber(ast.token_to_number<Real>())});
105 5983 : else if (ast.name == "Identifier")
106 : {
107 1832 : 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 3660 : _rpn.push_back({Opcode::VAR, ast.token_to_string(), addVariable(ast.token_to_string())});
111 : }
112 4151 : else if (ast.name == "UnaryExpr")
113 : {
114 : // nodes: [UnaryOp, UnaryExpr]
115 62 : build(*ast.nodes[1]);
116 62 : const auto op = ast.nodes[0]->token_to_string();
117 62 : _rpn.push_back({libmesh_map_find(_unary_opcode_map, op), op});
118 62 : }
119 5858 : else if (ast.name == "OrExpr" || ast.name == "AndExpr" || ast.name == "AddExpr" ||
120 1769 : ast.name == "MulExpr")
121 : {
122 : // nodes: [LeftOperand, Tail]
123 1703 : build(*ast.nodes[0]);
124 1703 : build(*ast.nodes[1]);
125 : }
126 3155 : else if (ast.name == "OrTail" || ast.name == "AndTail" || ast.name == "AddTail" ||
127 769 : ast.name == "MulTail")
128 : {
129 : // nodes: [Op, RightOperand, ?NextTail]
130 2107 : build(*ast.nodes[1]);
131 2107 : const auto op = ast.nodes[0]->token_to_string();
132 2107 : _rpn.push_back({libmesh_map_find(_binary_opcode_map, op), op});
133 2107 : if (ast.nodes.size() == 3)
134 404 : build(*ast.nodes[2]);
135 2107 : }
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 16997 : }
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 790 : RPNBuilder::addDefaultVariables()
184 : {
185 790 : checkFinalized();
186 :
187 790 : _has_default_variables = true;
188 :
189 1580 : addVariable("x");
190 1580 : addVariable("y");
191 1580 : addVariable("z");
192 790 : addVariable("t");
193 790 : }
194 :
195 : unsigned int
196 1627 : RPNBuilder::addNumber(Real number)
197 : {
198 1627 : checkFinalized();
199 :
200 1627 : _numbers.push_back(number);
201 1627 : return _numbers.size() - 1;
202 : }
203 :
204 : unsigned int
205 7504 : RPNBuilder::addVariable(const std::string & name)
206 : {
207 7504 : checkFinalized();
208 :
209 7504 : auto it = _variables.find(name);
210 7504 : if (it != _variables.end())
211 1830 : return it->second.idx;
212 :
213 : // Default variables: x, y, z, t
214 5674 : unsigned int idx = _variables.size() + (_has_default_variables ? 0 : 4);
215 :
216 5674 : _variables[name].idx = idx;
217 :
218 5674 : 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 20084 : RPNBuilder::checkFinalized()
308 : {
309 20084 : if (_finalized)
310 0 : mooseError("Kokkos parsed function error: cannot update builder after finalization.");
311 20084 : }
312 :
313 24295 : RPNEvaluator::RPNEvaluator(const RPNEvaluator & evaluator)
314 19132 : : _rpn(evaluator._rpn),
315 19132 : _numbers(evaluator._numbers),
316 19132 : _variables(evaluator._variables),
317 19132 : _num_scalars(evaluator._num_scalars),
318 19132 : _num_fields(evaluator._num_fields),
319 19132 : _num_properties(evaluator._num_properties),
320 38264 : _num_functions(evaluator._num_functions)
321 : {
322 24295 : _scalars.create(_num_scalars);
323 24295 : _fields.create(_num_fields);
324 24295 : _properties.create<false>(_num_properties);
325 24295 : _functions.create<false>(_num_functions);
326 :
327 138309 : for (const auto i : make_range(_variables.size()))
328 : {
329 114014 : auto ptr = evaluator._pointers[i];
330 114014 : if (!ptr)
331 93368 : 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 1002 : _properties.emplace({idx}, *static_cast<const MaterialProperty<Real> *>(ptr));
342 4476 : else if (type == VariableType::FUNCTION)
343 4476 : _functions.emplace({idx}, *static_cast<const Function *>(ptr));
344 : }
345 :
346 24295 : _scalars.copyToDevice();
347 24295 : _fields.copyToDevice();
348 24295 : _properties.copyToDevice();
349 24295 : _functions.copyToDevice();
350 24295 : }
351 :
352 : void
353 1133 : RPNEvaluator::init(const RPNBuilder & builder)
354 : {
355 1133 : if (!builder.finalized())
356 0 : mooseError("Kokkos parsed function error: attempted to initialize evaluator with a builder "
357 : "which was not finalized.");
358 :
359 1133 : const auto & numbers = builder.getNumbers();
360 1133 : const auto & variables = builder.getVariables();
361 1133 : const auto & rpn = builder.getRPN();
362 :
363 1133 : _numbers = numbers;
364 :
365 1133 : _variables.create(variables.size() + (builder.hasDefaultVariables() ? 0 : 4));
366 1133 : _pointers.resize(_variables.size(), nullptr);
367 :
368 1133 : _num_scalars = 0;
369 1133 : _num_fields = 0;
370 1133 : _num_properties = 0;
371 1133 : _num_functions = 0;
372 :
373 6711 : for (const auto & [name, var] : variables)
374 : {
375 5578 : if (var.scalar)
376 : {
377 2194 : _variables[var.idx].first = VariableType::SCALAR;
378 2194 : _variables[var.idx].second = _num_scalars++;
379 2194 : _pointers[var.idx] = var.scalar;
380 : }
381 3384 : else if (var.field)
382 : {
383 163 : _variables[var.idx].first = VariableType::FIELD;
384 163 : _variables[var.idx].second = _num_fields++;
385 163 : _pointers[var.idx] = var.field;
386 : }
387 3221 : else if (var.property)
388 : {
389 34 : _variables[var.idx].first = VariableType::MATERIAL;
390 34 : _variables[var.idx].second = _num_properties++;
391 34 : _pointers[var.idx] = var.property;
392 : }
393 3187 : else if (var.function)
394 : {
395 27 : _variables[var.idx].first = VariableType::FUNCTION;
396 27 : _variables[var.idx].second = _num_functions++;
397 27 : _pointers[var.idx] = var.function;
398 : }
399 3160 : else if (var.idx >= 4)
400 0 : mooseError(
401 : "Kokkos parsed function error: variable '", name, "' was not associated with any value.");
402 : }
403 :
404 1133 : _variables.copyToDevice();
405 :
406 1133 : _rpn.create(rpn.size());
407 :
408 7032 : for (const auto i : make_range(rpn.size()))
409 5899 : _rpn[i] = {rpn[i].op, rpn[i].arg};
410 :
411 1133 : _rpn.copyToDevice();
412 1133 : }
413 :
414 : } // namespace Moose::Kokkos
|