LCOV - code coverage report
Current view: top level - src/kokkos/functions - KokkosFunctionParser.K (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #32971 (54bef8) with base c6cf66 Lines: 192 204 94.1 %
Date: 2026-05-29 20:35:17 Functions: 14 15 93.3 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14