libMesh
Loading...
Searching...
No Matches
parsed_function.h
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18#ifndef LIBMESH_PARSED_FUNCTION_H
19#define LIBMESH_PARSED_FUNCTION_H
20
21#include "libmesh/libmesh_config.h"
22#include "libmesh/function_base.h"
23
24#ifdef LIBMESH_HAVE_FPARSER
25
26// Local includes
27#include "libmesh/dense_vector.h"
28#include "libmesh/int_range.h"
29#include "libmesh/vector_value.h"
30#include "libmesh/point.h"
31
32// FParser includes
33#include "libmesh/fparser_ad.hh"
34
35// C++ includes
36#include <algorithm> // std::find
37#include <cmath>
38#include <cmath>
39#include <cstddef>
40#include <iomanip>
41#include <limits>
42#include <memory>
43#include <sstream>
44#include <string>
45#include <vector>
46
47namespace libMesh
48{
49
59template <typename Output=Number, typename OutputGradient=Gradient>
60class ParsedFunction : public FunctionBase<Output>
61{
62public:
63 explicit
65 const std::vector<std::string> * additional_vars=nullptr,
66 const std::vector<Output> * initial_vals=nullptr);
67
75
78 virtual ~ParsedFunction () = default;
79
83 void reparse (std::string expression);
84
85 virtual Output operator() (const Point & p,
86 const Real time = 0) override;
87
91 virtual bool has_derivatives() { return _valid_derivatives; }
92
93 virtual Output dot(const Point & p,
94 const Real time = 0);
95
96 virtual OutputGradient gradient(const Point & p,
97 const Real time = 0);
98
99 virtual void operator() (const Point & p,
100 const Real time,
101 DenseVector<Output> & output) override;
102
103 virtual Output component (unsigned int i,
104 const Point & p,
105 Real time) override;
106
107 const std::string & expression() { return _expression; }
108
112 virtual Output & getVarAddress(std::string_view variable_name);
113
114 virtual std::unique_ptr<FunctionBase<Output>> clone() const override;
115
124 Output get_inline_value(std::string_view inline_var_name) const;
125
136 void set_inline_value(std::string_view inline_var_name,
137 Output newval);
138
139protected:
143 void partial_reparse (std::string expression);
144
148 std::size_t find_name (std::string_view varname,
149 std::string_view expr) const;
150
154 bool expression_is_time_dependent( std::string_view expression ) const;
155
156private:
160 void set_spacetime(const Point & p,
161 const Real time = 0);
162
166 inline Output eval(FunctionParserADBase<Output> & parser,
167 std::string_view libmesh_dbg_var(function_name),
168 unsigned int libmesh_dbg_var(component_idx)) const;
169
170 std::string _expression;
171 std::vector<std::string> _subexpressions;
172 std::vector<std::unique_ptr<FunctionParserADBase<Output>>> parsers;
173 std::vector<Output> _spacetime;
174
175 // derivative functions
176 std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dx_parsers;
177#if LIBMESH_DIM > 1
178 std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dy_parsers;
179#endif
180#if LIBMESH_DIM > 2
181 std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dz_parsers;
182#endif
183 std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dt_parsers;
185
186 // Variables/values that can be parsed and handled by the function parser
187 std::string variables;
188 std::vector<std::string> _additional_vars;
189 std::vector<Output> _initial_vals;
190};
191
192
193/*----------------------- Inline functions ----------------------------------*/
194
195template <typename Output, typename OutputGradient>
196inline
198 const std::vector<std::string> * additional_vars,
199 const std::vector<Output> * initial_vals) :
200 _expression (), // overridden by parse()
201 // Size the spacetime vector to account for space, time, and any additional
202 // variables passed
203 _spacetime (LIBMESH_DIM+1 + (additional_vars ? additional_vars->size() : 0)),
204 _valid_derivatives (true),
205 _additional_vars (additional_vars ? *additional_vars : std::vector<std::string>()),
206 _initial_vals (initial_vals ? *initial_vals : std::vector<Output>())
207{
208 // time-dependence established in reparse function
209 this->reparse(std::move(expression));
210 this->_initialized = true;
211}
212
213
214template <typename Output, typename OutputGradient>
215inline
217 FunctionBase<Output>(other),
218 _expression(other._expression),
219 _subexpressions(other._subexpressions),
220 _spacetime(other._spacetime),
221 _valid_derivatives(other._valid_derivatives),
222 variables(other.variables),
223 _additional_vars(other._additional_vars),
224 _initial_vals(other._initial_vals)
225{
226 // parsers can be generated from scratch by reparsing expression
227 this->reparse(this->_expression);
228 this->_initialized = true;
229}
230
231
232
233template <typename Output, typename OutputGradient>
234inline
237{
238 // Use copy-and-swap idiom
240 std::swap(tmp, *this);
241 return *this;
242}
243
244
245template <typename Output, typename OutputGradient>
246inline
247void
249{
250 variables = "x";
251#if LIBMESH_DIM > 1
252 variables += ",y";
253#endif
254#if LIBMESH_DIM > 2
255 variables += ",z";
256#endif
257 variables += ",t";
258
259 // If additional vars were passed, append them to the string
260 // that we send to the function parser. Also add them to the
261 // end of our spacetime vector
262 for (auto i : index_range(_additional_vars))
263 {
264 variables += "," + _additional_vars[i];
265 // Initialize extra variables to the vector passed in or zero
266 // Note: The initial_vals vector can be shorter than the additional_vars vector
267 _spacetime[LIBMESH_DIM+1 + i] =
268 (i < _initial_vals.size()) ? _initial_vals[i] : 0;
269 }
270
271 this->_is_time_dependent = this->expression_is_time_dependent(expression);
272
273 this->partial_reparse(std::move(expression));
274}
275
276template <typename Output, typename OutputGradient>
277inline
278Output
280{
281 set_spacetime(p, time);
282 return eval(*parsers[0], "f", 0);
283}
284
285template <typename Output, typename OutputGradient>
286inline
287Output
289{
290 set_spacetime(p, time);
291 return eval(*dt_parsers[0], "df/dt", 0);
292}
293
294template <typename Output, typename OutputGradient>
295inline
296OutputGradient
298{
299 OutputGradient grad;
300 set_spacetime(p, time);
301
302 grad(0) = eval(*dx_parsers[0], "df/dx", 0);
303#if LIBMESH_DIM > 1
304 grad(1) = eval(*dy_parsers[0], "df/dy", 0);
305#endif
306#if LIBMESH_DIM > 2
307 grad(2) = eval(*dz_parsers[0], "df/dz", 0);
308#endif
309
310 return grad;
311}
312
313template <typename Output, typename OutputGradient>
314inline
315void
317 (const Point & p,
318 const Real time,
319 DenseVector<Output> & output)
320{
321 set_spacetime(p, time);
322
323 unsigned int size = output.size();
324
325 libmesh_assert_equal_to (size, parsers.size());
326
327 // The remaining locations in _spacetime are currently fixed at construction
328 // but could potentially be made dynamic
329 for (unsigned int i=0; i != size; ++i)
330 output(i) = eval(*parsers[i], "f", i);
331}
332
337template <typename Output, typename OutputGradient>
338inline
339Output
341 const Point & p,
342 Real time)
343{
344 set_spacetime(p, time);
345 libmesh_assert_less (i, parsers.size());
346
347 // The remaining locations in _spacetime are currently fixed at construction
348 // but could potentially be made dynamic
349 libmesh_assert_less(i, parsers.size());
350 return eval(*parsers[i], "f", i);
351}
352
356template <typename Output, typename OutputGradient>
357inline
358Output &
360{
361 const std::vector<std::string>::iterator it =
362 std::find(_additional_vars.begin(), _additional_vars.end(), variable_name);
363
364 libmesh_error_msg_if(it == _additional_vars.end(),
365 "ERROR: Requested variable not found in parsed function");
366
367 // Iterator Arithmetic (How far from the end of the array is our target address?)
368 return _spacetime[_spacetime.size() - (_additional_vars.end() - it)];
369}
370
371
372template <typename Output, typename OutputGradient>
373inline
374std::unique_ptr<FunctionBase<Output>>
376{
377 return std::make_unique<ParsedFunction>(_expression,
378 &_additional_vars,
379 &_initial_vals);
380}
381
382template <typename Output, typename OutputGradient>
383inline
384Output
385ParsedFunction<Output,OutputGradient>::get_inline_value (std::string_view inline_var_name) const
386{
387 libmesh_assert_greater (_subexpressions.size(), 0);
388
389#ifndef NDEBUG
390 bool found_var_name = false;
391#endif
392 Output old_var_value(0.);
393
394 for (const auto & subexpression : _subexpressions)
395 {
396 const std::size_t varname_i =
397 find_name(inline_var_name, subexpression);
398 if (varname_i == std::string::npos)
399 continue;
400
401 const std::size_t assignment_i =
402 subexpression.find(":", varname_i+1);
403
404 libmesh_assert_not_equal_to(assignment_i, std::string::npos);
405
406 libmesh_assert_equal_to(subexpression[assignment_i+1], '=');
407 for (std::size_t i = varname_i+1; i != assignment_i; ++i)
408 libmesh_assert_equal_to(subexpression[i], ' ');
409
410 std::size_t end_assignment_i =
411 subexpression.find(";", assignment_i+1);
412
413 libmesh_assert_not_equal_to(end_assignment_i, std::string::npos);
414
415 std::string new_subexpression =
416 subexpression.substr(0, end_assignment_i+1).append(inline_var_name);
417
418#ifdef LIBMESH_HAVE_FPARSER
419 // Parse and evaluate the new subexpression.
420 // Add the same constants as we used originally.
421 FunctionParserADBase<Output> fp;
422 fp.AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
423 fp.AddConstant("pi", std::acos(Real(-1)));
424 fp.AddConstant("e", std::exp(Real(1)));
425 libmesh_error_msg_if
426 (fp.Parse(new_subexpression, variables) != -1, // -1 for success
427 "ERROR: FunctionParser is unable to parse modified expression: "
428 << new_subexpression << '\n' << fp.ErrorMsg());
429
430 Output new_var_value = this->eval(fp, new_subexpression, 0);
431#ifdef NDEBUG
432 return new_var_value;
433#else
434 if (found_var_name)
435 {
436 libmesh_assert_equal_to(old_var_value, new_var_value);
437 }
438 else
439 {
440 old_var_value = new_var_value;
441 found_var_name = true;
442 }
443#endif
444
445#else
446 libmesh_error_msg("ERROR: This functionality requires fparser!");
447#endif
448 }
449
450 libmesh_assert(found_var_name);
451 return old_var_value;
452}
453
454
455template <typename Output, typename OutputGradient>
456inline
457void
459 Output newval)
460{
461 libmesh_assert_greater (_subexpressions.size(), 0);
462
463#ifndef NDEBUG
464 bool found_var_name = false;
465#endif
466 for (auto & subexpression : _subexpressions)
467 {
468 const std::size_t varname_i =
469 find_name(inline_var_name, subexpression);
470 if (varname_i == std::string::npos)
471 continue;
472
473#ifndef NDEBUG
474 found_var_name = true;
475#endif
476 const std::size_t assignment_i =
477 subexpression.find(":", varname_i+1);
478
479 libmesh_assert_not_equal_to(assignment_i, std::string::npos);
480
481 libmesh_assert_equal_to(subexpression[assignment_i+1], '=');
482 for (std::size_t i = varname_i+1; i != assignment_i; ++i)
483 libmesh_assert_equal_to(subexpression[i], ' ');
484
485 std::size_t end_assignment_i =
486 subexpression.find(";", assignment_i+1);
487
488 libmesh_assert_not_equal_to(end_assignment_i, std::string::npos);
489
490 std::ostringstream new_subexpression;
491 new_subexpression << subexpression.substr(0, assignment_i+2)
492 << std::setprecision(std::numeric_limits<Output>::digits10+2)
493#ifdef LIBMESH_USE_COMPLEX_NUMBERS
494 << '(' << newval.real() << '+'
495 << newval.imag() << 'i' << ')'
496#else
497 << newval
498#endif
499 << subexpression.substr(end_assignment_i,
500 std::string::npos);
501 subexpression = new_subexpression.str();
502 }
503
504 libmesh_assert(found_var_name);
505
506 std::string new_expression;
507
508 for (const auto & subexpression : _subexpressions)
509 {
510 new_expression += '{';
511 new_expression += subexpression;
512 new_expression += '}';
513 }
514
515 this->partial_reparse(new_expression);
516}
517
518
519template <typename Output, typename OutputGradient>
520inline
521void
523{
524 _expression = std::move(expression);
525 _subexpressions.clear();
526 parsers.clear();
527
528 size_t nextstart = 0, end = 0;
529
530 while (end != std::string::npos)
531 {
532 // If we're past the end of the string, we can't make any more
533 // subparsers
534 if (nextstart >= _expression.size())
535 break;
536
537 // If we're at the start of a brace delimited section, then we
538 // parse just that section:
539 if (_expression[nextstart] == '{')
540 {
541 nextstart++;
542 end = _expression.find('}', nextstart);
543 }
544 // otherwise we parse the whole thing
545 else
546 end = std::string::npos;
547
548 // We either want the whole end of the string (end == npos) or
549 // a substring in the middle.
550 _subexpressions.push_back
551 (_expression.substr(nextstart, (end == std::string::npos) ?
552 std::string::npos : end - nextstart));
553
554 // fparser can crash on empty expressions
555 libmesh_error_msg_if(_subexpressions.back().empty(),
556 "ERROR: FunctionParser is unable to parse empty expression.\n");
557
558 // Parse (and optimize if possible) the subexpression.
559 // Add some basic constants, to Real precision.
560 auto fp = std::make_unique<FunctionParserADBase<Output>>();
561 fp->AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
562 fp->AddConstant("pi", std::acos(Real(-1)));
563 fp->AddConstant("e", std::exp(Real(1)));
564 libmesh_error_msg_if
565 (fp->Parse(_subexpressions.back(), variables) != -1, // -1 for success
566 "ERROR: FunctionParser is unable to parse expression: "
567 << _subexpressions.back() << '\n' << fp->ErrorMsg());
568
569 // use of derivatives is optional. suppress error output on the console
570 // use the has_derivatives() method to check if AutoDiff was successful.
571 // also enable immediate optimization
572 fp->SetADFlags(FunctionParserADBase<Output>::ADSilenceErrors |
573 FunctionParserADBase<Output>::ADAutoOptimize);
574
575 // optimize original function
576 fp->Optimize();
577
578 // generate derivatives through automatic differentiation
579 auto dx_fp = std::make_unique<FunctionParserADBase<Output>>(*fp);
580 if (dx_fp->AutoDiff("x") != -1) // -1 for success
581 _valid_derivatives = false;
582 dx_parsers.push_back(std::move(dx_fp));
583#if LIBMESH_DIM > 1
584 auto dy_fp = std::make_unique<FunctionParserADBase<Output>>(*fp);
585 if (dy_fp->AutoDiff("y") != -1) // -1 for success
586 _valid_derivatives = false;
587 dy_parsers.push_back(std::move(dy_fp));
588#endif
589#if LIBMESH_DIM > 2
590 auto dz_fp = std::make_unique<FunctionParserADBase<Output>>(*fp);
591 if (dz_fp->AutoDiff("z") != -1) // -1 for success
592 _valid_derivatives = false;
593 dz_parsers.push_back(std::move(dz_fp));
594#endif
595 auto dt_fp = std::make_unique<FunctionParserADBase<Output>>(*fp);
596 if (dt_fp->AutoDiff("t") != -1) // -1 for success
597 _valid_derivatives = false;
598 dt_parsers.push_back(std::move(dt_fp));
599
600 // If at end, use nextstart=maxSize. Else start at next
601 // character.
602 nextstart = (end == std::string::npos) ?
603 std::string::npos : end + 1;
604
605 // Store fp for later use
606 parsers.push_back(std::move(fp));
607 }
608}
609
610
611template <typename Output, typename OutputGradient>
612inline
613std::size_t
615 std::string_view expr) const
616{
617 const std::size_t namesize = varname.size();
618 std::size_t varname_i = expr.find(varname);
619
620 while ((varname_i != std::string::npos) &&
621 (((varname_i > 0) &&
622 (std::isalnum(expr[varname_i-1]) ||
623 (expr[varname_i-1] == '_'))) ||
624 ((varname_i+namesize < expr.size()) &&
625 (std::isalnum(expr[varname_i+namesize]) ||
626 (expr[varname_i+namesize] == '_')))))
627 {
628 varname_i = expr.find(varname, varname_i+1);
629 }
630
631 return varname_i;
632}
633template <typename Output, typename OutputGradient>
634inline
635bool
637{
638 bool is_time_dependent = false;
639
640 // By definition, time is "t" for FunctionBase-based objects, so we just need to
641 // see if this expression has the variable "t" in it.
642 if (this->find_name(std::string("t"), expression) != std::string::npos)
643 is_time_dependent = true;
644
645 return is_time_dependent;
646}
647
648// Set the _spacetime argument vector
649template <typename Output, typename OutputGradient>
650inline
651void
653 const Real time)
654{
655 _spacetime[0] = p(0);
656#if LIBMESH_DIM > 1
657 _spacetime[1] = p(1);
658#endif
659#if LIBMESH_DIM > 2
660 _spacetime[2] = p(2);
661#endif
662 _spacetime[LIBMESH_DIM] = time;
663
664 // The remaining locations in _spacetime are currently fixed at construction
665 // but could potentially be made dynamic
666}
667
668// Evaluate the ith FunctionParser and check the result
669template <typename Output, typename OutputGradient>
670inline
671Output
672ParsedFunction<Output,OutputGradient>::eval (FunctionParserADBase<Output> & parser,
673 std::string_view function_name,
674 unsigned int component_idx) const
675{
676 Output result = parser.Eval(_spacetime.data());
677 int error_code = parser.EvalError();
678 if (error_code)
679 {
680 libMesh::err << "ERROR: FunctionParser is unable to evaluate component "
681 << component_idx
682 << " for '"
683 << function_name;
684
685 for (auto i : index_range(parsers))
686 if (parsers[i].get() == &parser)
687 libMesh::err << "' of expression '"
688 << _subexpressions[i];
689
690 libMesh::err << "' with arguments:\n";
691 for (const auto & item : _spacetime)
692 libMesh::err << '\t' << item << '\n';
693 libMesh::err << '\n';
694
695 // Currently no API to report error messages, we'll do it manually
696 std::string error_message = "Reason: ";
697
698 switch (error_code)
699 {
700 case 1:
701 error_message += "Division by zero";
702 break;
703 case 2:
704 error_message += "Square Root error (negative value)";
705 break;
706 case 3:
707 error_message += "Log error (negative value)";
708 break;
709 case 4:
710 error_message += "Trigonometric error (asin or acos of illegal value)";
711 break;
712 case 5:
713 error_message += "Maximum recursion level reached";
714 break;
715 default:
716 error_message += "Unknown";
717 break;
718 }
719 libmesh_error_msg(error_message);
720 }
721
722 return result;
723}
724
725} // namespace libMesh
726
727
728#else // !LIBMESH_HAVE_FPARSER
729
730
731namespace libMesh {
732
733
734template <typename Output=Number>
735class ParsedFunction : public FunctionBase<Output>
736{
737public:
738 ParsedFunction (std::string /* expression */,
739 const std::vector<std::string> * = nullptr,
740 const std::vector<Output> * = nullptr) : _dummy(0)
741 {
742 libmesh_not_implemented();
743 }
744
750 ParsedFunction (const ParsedFunction &) = delete;
753 virtual ~ParsedFunction () = default;
754
755 virtual Output operator() (const Point &,
756 const Real /* time */ = 0)
757 { return 0.; }
758
759 virtual void operator() (const Point &,
760 const Real /* time */,
761 DenseVector<Output> & /* output */) {}
762
763 virtual void init() {}
764 virtual void clear() {}
765 virtual Output & getVarAddress(std::string_view /*variable_name*/) { return _dummy; }
766 virtual std::unique_ptr<FunctionBase<Output>> clone() const
767 {
768 return std::make_unique<ParsedFunction<Output>>("");
769 }
770private:
771 Output _dummy;
772};
773
774
775
776} // namespace libMesh
777
778
779#endif // LIBMESH_HAVE_FPARSER
780
781#endif // LIBMESH_PARSED_FUNCTION_H
Defines a dense vector for use in Finite Element-type computations.
Base class for functors that can be evaluated at a point and (optionally) time.
bool _initialized
When init() was called so that everything is ready for calls to operator() (...), then this bool is t...
A Function generated (via FParser) by parsing a mathematical expression.
void set_spacetime(const Point &p, const Real time=0)
Set the _spacetime argument vector.
virtual void clear()
Clears the function.
virtual Output & getVarAddress(std::string_view variable_name)
const std::string & expression()
void reparse(std::string expression)
Re-parse with new expression.
std::vector< std::string > _subexpressions
ParsedFunction(ParsedFunction &&)=delete
When !LIBMESH_HAVE_FPARSER, this class is not implemented, so let's make that explicit by deleting th...
Output eval(FunctionParserADBase< Output > &parser, std::string_view libmesh_dbg_var(function_name), unsigned int libmesh_dbg_var(component_idx)) const
Evaluate the ith FunctionParser and check the result.
std::vector< std::unique_ptr< FunctionParserADBase< Output > > > dz_parsers
void partial_reparse(std::string expression)
Re-parse with minor changes to expression.
ParsedFunction(ParsedFunction &&)=default
void set_inline_value(std::string_view inline_var_name, Output newval)
Changes the value of an inline variable.
std::vector< Output > _initial_vals
virtual bool has_derivatives()
Query if the automatic derivative generation was successful.
ParsedFunction(std::string expression, const std::vector< std::string > *additional_vars=nullptr, const std::vector< Output > *initial_vals=nullptr)
ParsedFunction & operator=(const ParsedFunction &)
ParsedFunction(const ParsedFunction &)
Constructors.
Output get_inline_value(std::string_view inline_var_name) const
virtual ~ParsedFunction()=default
virtual OutputGradient gradient(const Point &p, const Real time=0)
std::vector< std::string > _additional_vars
std::vector< std::unique_ptr< FunctionParserADBase< Output > > > dy_parsers
std::vector< std::unique_ptr< FunctionParserADBase< Output > > > dt_parsers
virtual Output dot(const Point &p, const Real time=0)
bool expression_is_time_dependent(std::string_view expression) const
virtual Output component(unsigned int i, const Point &p, Real time) override
std::vector< std::unique_ptr< FunctionParserADBase< Output > > > parsers
virtual std::unique_ptr< FunctionBase< Output > > clone() const override
std::vector< Output > _spacetime
std::vector< std::unique_ptr< FunctionParserADBase< Output > > > dx_parsers
ParsedFunction(const ParsedFunction &)=delete
virtual Output operator()(const Point &p, const Real time=0) override
virtual Output & getVarAddress(std::string_view)
ParsedFunction(std::string, const std::vector< std::string > *=nullptr, const std::vector< Output > *=nullptr)
std::size_t find_name(std::string_view varname, std::string_view expr) const
Helper function for parsing out variable names.
virtual std::unique_ptr< FunctionBase< Output > > clone() const
virtual void init()
The actual initialization process.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
The libMesh namespace provides an interface to certain functionality in the library.
OStreamProxy err
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
libmesh_assert(ctx)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real