LCOV - code coverage report
Current view: top level - src/kokkos/functions - KokkosFunctionParser.K (source / functions) Hit Total Coverage
Test: idaholab/moose framework: #33416 (b10b36) with base 9fbd27 Lines: 191 203 94.1 %
Date: 2026-07-23 16:15:30 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        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

Generated by: LCOV version 1.14