GCC Code Coverage Report


Directory: ../../../builds/dumux-repositories/
File: dumux/dumux/common/functionfromstringexpression.hh
Date: 2025-04-12 19:19:20
Exec Total Coverage
Lines: 44 45 97.8%
Functions: 14 14 100.0%
Branches: 45 128 35.2%

Line Branch Exec Source
1 // -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // vi: set et ts=4 sw=4 sts=4:
3 //
4 // SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 /*!
8 * \file
9 * \ingroup Core
10 * \brief Evaluating string math expressions containing named variables
11 */
12 #ifndef DUMUX_COMMON_FUNCTION_FROM_STRING_EXPRESSION_HH
13 #define DUMUX_COMMON_FUNCTION_FROM_STRING_EXPRESSION_HH
14
15 #include <array>
16 #include <mutex>
17 #include <string>
18 #include <string_view>
19 #include <type_traits>
20 #include <iostream>
21
22 #include <dune/common/exceptions.hh>
23 #include <dune/common/fvector.hh>
24
25 #include <dumux/io/format.hh>
26 #include <dumux/io/expression/exprtk.hpp>
27
28 namespace Dumux {
29
30 /*!
31 * \ingroup Core
32 * \brief Evaluating string math expressions containing named variables
33 * \tparam numVars number of variables in the expression; number of function arguments of the call operator
34 * \tparam Scalar type of numerical values in the expression
35 *
36 * Example usage
37 \code{.cpp}
38 // Create a callable f(x,t) from a string expression containing variable literals.
39 // The constructor compiles the expression in the constructor making calls efficient.
40 // The constructor throws a Dune::IOError with detailed info if parsing fails.
41 std::string expr = getParam("Problem.Function"); // e.g. "5*x + x*sin(x*t)"
42 FunctionFromStringExpression<2> f(expr, "xt"); // variables "x" and "t"
43
44 // evaluate function, result is double (the default scalar type)
45 const double x = 1.0, t = 2.0;
46 const double result = f(x, t);
47 \endcode
48 *
49 * For variables with several characters construct
50 \code{.cpp}
51 // variables "pos" and "time"
52 FunctionFromStringExpression<2> f(expr, std::array<std::string, 2>{{"pos", "time"}});
53 \endcode
54 *
55 */
56 template<std::size_t numVars, class Scalar = double>
57 class FunctionFromStringExpression
58 {
59 using SymbolTable = exprtk::symbol_table<Scalar>;
60 using Expression = exprtk::expression<Scalar>;
61 using Parser = exprtk::parser<Scalar>;
62
63 public:
64 static constexpr std::size_t numVariables = numVars;
65
66 //! \brief Constructor from math expression and array of variable names
67 14 FunctionFromStringExpression(const std::string& expression, const std::array<std::string, numVars>& variableNames)
68
4/6
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 7 times.
✗ Branch 7 not taken.
✓ Branch 10 taken 6 times.
✓ Branch 11 taken 1 times.
32 { initialize_(expression, variableNames); }
69
70 //! \brief Delegating constructor using all characters of a string as variables
71 //! \note Calling FunctionFromStringExpression(expr, "xt") uses "x" and "t" as variable names
72 8 FunctionFromStringExpression(const std::string& expression, std::string_view variableNames)
73
3/6
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
14 : FunctionFromStringExpression(expression, extractVariableNames_(variableNames, std::make_index_sequence<numVars>{})) {}
74
75 template<class S, std::enable_if_t<std::is_convertible_v<Scalar, S>, int> = 0>
76 Scalar operator() (const std::array<S, numVars>& params) const
77 { return evalRandomAcessImpl_(params); }
78
79 template<class S, std::enable_if_t<std::is_convertible_v<Scalar, S>, int> = 0>
80 Scalar operator() (const Dune::FieldVector<S, numVars>& params) const
81 { return evalRandomAcessImpl_(params); }
82
83 template<class ...Params, std::enable_if_t<(sizeof...(Params) == numVars) && (std::is_convertible_v<Scalar, std::decay_t<Params>> && ...), int> = 0>
84 402 Scalar operator() (Params&&... params) const
85
5/28
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 100 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 15 not taken.
✓ Branch 16 taken 100 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 24 not taken.
✓ Branch 25 taken 100 times.
✗ Branch 27 not taken.
✗ Branch 28 not taken.
✗ Branch 30 not taken.
✗ Branch 31 not taken.
✗ Branch 33 not taken.
✓ Branch 34 taken 100 times.
✗ Branch 36 not taken.
✗ Branch 37 not taken.
✗ Branch 39 not taken.
✗ Branch 40 not taken.
401 { return evalRandomAcessImpl_(std::array<Scalar, numVars>{ std::forward<Params>(params)... }); }
86
87 1 void setVerbosity(unsigned int v)
88 1 { verbosity_ = v; }
89
90 private:
91 template<class RandomAccessContainer>
92 802 Scalar evalRandomAcessImpl_(const RandomAccessContainer& params) const
93 {
94 if constexpr (numVars > 0)
95 {
96 800 std::lock_guard lock(evalMutex_);
97
2/2
✓ Branch 0 taken 700 times.
✓ Branch 1 taken 400 times.
2200 for (std::size_t i = 0; i < numVars; ++i)
98 1400 variables_[i] = params[i];
99
1/2
✓ Branch 1 taken 400 times.
✗ Branch 2 not taken.
800 return expression_.value();
100 800 }
101 else
102
1/4
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 return expression_.value();
103 }
104
105 template<std::size_t... I>
106
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
10 std::array<std::string, numVars> extractVariableNames_(std::string_view names, std::index_sequence<I...>) const
107 {
108 static_assert(numVars == sizeof...(I), "Number of variables has to match size of index set.");
109
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
10 if (names.size() != numVars)
110 DUNE_THROW(Dune::IOError, "Number of variables in '"
111 << names << "' does not match number of function arguments: " << numVars);
112
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
14 return { std::string(1, names.at(I))... };
113 6 }
114
115 //! Parse the math expression and throw detailed error message when this fails
116 14 void initialize_(const std::string& expression, const std::array<std::string, numVars>& variableNames)
117 {
118
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 5 times.
32 for (std::size_t i = 0; i < numVars; ++i)
119
1/2
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
36 symbolTable_.add_variable(std::string{variableNames[i]}, variables_[i]);
120 14 symbolTable_.add_constants();
121 14 expression_.register_symbol_table(symbolTable_);
122
123
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6 times.
14 if (!parser_.compile(expression, expression_))
124 {
125 2 std::stringstream ss;
126
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
4 ss << Fmt::format("Parsing expression '{}' failed.\n", expression);
127
128
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 if (verbosity_ >= 1)
129 {
130
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
4 for (std::size_t i = 0; i < parser_.error_count(); ++i)
131 {
132
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 const auto error = parser_.get_error(i);
133
134 2 ss << Fmt::format(
135 "-- error (position: {:02d}, type: {}): {}\n",
136 2 static_cast<unsigned int>(error.token.position),
137
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
2 exprtk::parser_error::to_str(error.mode).c_str(),
138
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
4 error.diagnostic.c_str()
139 );
140 }
141 }
142
143
12/24
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 1 times.
✗ Branch 14 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
✓ Branch 19 taken 1 times.
✗ Branch 20 not taken.
✓ Branch 22 taken 1 times.
✗ Branch 23 not taken.
✓ Branch 25 taken 1 times.
✗ Branch 26 not taken.
✓ Branch 28 taken 1 times.
✗ Branch 29 not taken.
✓ Branch 32 taken 1 times.
✗ Branch 33 not taken.
✓ Branch 35 taken 1 times.
✗ Branch 36 not taken.
8 DUNE_THROW(Dune::IOError, ss.str());
144 2 }
145
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
12 else if (verbosity_ >= 2)
146 {
147 24 std::cout << Fmt::format(
148 "Successfully parsed math expression '{}'\n",
149 expression
150 );
151 }
152 12 }
153
154 unsigned int verbosity_ = 2;
155 SymbolTable symbolTable_;
156 Expression expression_;
157 Parser parser_;
158 mutable std::array<Scalar, numVars> variables_;
159 mutable std::mutex evalMutex_;
160 };
161
162 /*!
163 * \ingroup Core
164 * \brief Evaluating simple string math expressions
165 * \tparam Scalar type of expression evaluation result
166 */
167 template<class Scalar = double>
168 1 Scalar evalStringExpression(const std::string& expression, int verbosity = 0)
169 {
170 1 FunctionFromStringExpression<0, Scalar> f{ expression, "" };
171
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 f.setVerbosity(verbosity);
172 1 return f();
173 1 }
174
175 } // end namespace Dumux
176
177 #endif
178