Skip to content

Commit

Permalink
Combination of MoveInsertable fixes (#2170)
Browse files Browse the repository at this point in the history
The master hashes of the commits cherry-picked and squashed to create this commit:

PR #2720
783091b

PR #2731
b67b3e4
0a1dbe4
73d58f2
3a61395
a6f14fe
00cd55d
1310eac

A similar "combined" commit should also be cherry-picked onto the
1.4.x and 1.5.x series and new patch-level releases should be made for
them, since this is an issue which prevents certain compilers from
being able to compile the library.

Refs #2710
  • Loading branch information
jwpeterson committed Oct 12, 2020
1 parent 8d1614a commit 5912de9
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 104 deletions.
103 changes: 70 additions & 33 deletions include/numerics/parsed_fem_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include <sstream>
#include <string>
#include <vector>
#include <memory>

namespace libMesh
{
Expand Down Expand Up @@ -66,21 +67,16 @@ class ParsedFEMFunction : public FEMFunctionBase<Output>
const std::vector<Output> * initial_vals=nullptr);

/**
* This class contains a const reference so it can't be copy or move-assigned.
* Constructors
* - This class contains a const reference so it can't be default
* copy or move-assigned. We manually implement the former
* - This class contains unique_ptrs so it can't be default copy
* constructed or assigned.
* - It can still be default moved and deleted.
*/
ParsedFEMFunction & operator= (const ParsedFEMFunction &) = delete;
ParsedFEMFunction & operator= (const ParsedFEMFunction &);
ParsedFEMFunction & operator= (ParsedFEMFunction &&) = delete;

/**
* The remaining 5 special functions can be safely defaulted.
*
* \note The underlying FunctionParserBase class has a copy
* constructor, so this class should be default-constructible. And,
* although FunctionParserBase's move constructor is deleted, _this_
* class should still be move-constructible because
* FunctionParserBase only appears in a vector.
*/
ParsedFEMFunction (const ParsedFEMFunction &) = default;
ParsedFEMFunction (const ParsedFEMFunction &);
ParsedFEMFunction (ParsedFEMFunction &&) = default;
virtual ~ParsedFEMFunction () = default;

Expand Down Expand Up @@ -166,9 +162,9 @@ class ParsedFEMFunction : public FEMFunctionBase<Output>
_n_requested_hess_components;
bool _requested_normals;
#ifdef LIBMESH_HAVE_FPARSER
std::vector<FunctionParserBase<Output>> parsers;
std::vector<std::unique_ptr<FunctionParserBase<Output>>> parsers;
#else
std::vector<char> parsers;
std::vector<char*> parsers;
#endif
std::vector<Output> _spacetime;

Expand Down Expand Up @@ -220,6 +216,47 @@ ParsedFEMFunction<Output>::ParsedFEMFunction (const System & sys,
}


template <typename Output>
inline
ParsedFEMFunction<Output>::ParsedFEMFunction (const ParsedFEMFunction<Output> & other) :
FEMFunctionBase<Output>(other),
_sys(other._sys),
_expression(other._expression),
_subexpressions(other._subexpressions),
_n_vars(other._n_vars),
_n_requested_vars(other._n_requested_vars),
_n_requested_grad_components(other._n_requested_grad_components),
_n_requested_hess_components(other._n_requested_hess_components),
_requested_normals(other._requested_normals),
_spacetime(other._spacetime),
_need_var(other._need_var),
_need_var_grad(other._need_var_grad),
#ifdef LIBMESH_ENABLE_SECOND_DERIVATIVES
_need_var_hess(other._need_var_hess),
#endif // LIBMESH_ENABLE_SECOND_DERIVATIVES
variables(other.variables),
_additional_vars(other._additional_vars),
_initial_vals(other._initial_vals)
{
this->reparse(_expression);
}


template <typename Output>
inline
ParsedFEMFunction<Output> &
ParsedFEMFunction<Output>::operator= (const ParsedFEMFunction<Output> & other)
{
// We can only be assigned another ParsedFEMFunction defined on the same System
libmesh_assert(&_sys == &other._sys);

// Use copy-and-swap idiom
ParsedFEMFunction<Output> tmp(other);
std::swap(tmp, *this);
return *this;
}


template <typename Output>
inline
void
Expand Down Expand Up @@ -399,7 +436,7 @@ ParsedFEMFunction<Output>::operator() (const FEMContext & c,
{
eval_args(c, p, time);

return eval(parsers[0], "f", 0);
return eval(*parsers[0], "f", 0);
}


Expand All @@ -419,7 +456,7 @@ ParsedFEMFunction<Output>::operator() (const FEMContext & c,
libmesh_assert_equal_to (size, parsers.size());

for (unsigned int i=0; i != size; ++i)
output(i) = eval(parsers[i], "f", i);
output(i) = eval(*parsers[i], "f", i);
}


Expand All @@ -434,7 +471,7 @@ ParsedFEMFunction<Output>::component (const FEMContext & c,
eval_args(c, p, time);

libmesh_assert_less (i, parsers.size());
return eval(parsers[i], "f", i);
return eval(*parsers[i], "f", i);
}

template <typename Output>
Expand Down Expand Up @@ -478,16 +515,16 @@ ParsedFEMFunction<Output>::get_inline_value(const std::string & inline_var_name)
#ifdef LIBMESH_HAVE_FPARSER
// Parse and evaluate the new subexpression.
// Add the same constants as we used originally.
FunctionParserBase<Output> fp;
fp.AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
fp.AddConstant("pi", std::acos(Real(-1)));
fp.AddConstant("e", std::exp(Real(1)));
if (fp.Parse(new_subexpression, variables) != -1) // -1 for success
auto fp = libmesh_make_unique<FunctionParserBase<Output>>();
fp->AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
fp->AddConstant("pi", std::acos(Real(-1)));
fp->AddConstant("e", std::exp(Real(1)));
if (fp->Parse(new_subexpression, variables) != -1) // -1 for success
libmesh_error_msg
("ERROR: FunctionParser is unable to parse modified expression: "
<< new_subexpression << '\n' << fp.ErrorMsg());
<< new_subexpression << '\n' << fp->ErrorMsg());

Output new_var_value = this->eval(fp, new_subexpression, 0);
Output new_var_value = this->eval(*fp, new_subexpression, 0);
#ifdef NDEBUG
return new_var_value;
#else
Expand Down Expand Up @@ -620,16 +657,16 @@ ParsedFEMFunction<Output>::partial_reparse (const std::string & expression)
#ifdef LIBMESH_HAVE_FPARSER
// Parse (and optimize if possible) the subexpression.
// Add some basic constants, to Real precision.
FunctionParserBase<Output> fp;
fp.AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
fp.AddConstant("pi", std::acos(Real(-1)));
fp.AddConstant("e", std::exp(Real(1)));
if (fp.Parse(_subexpressions.back(), variables) != -1) // -1 for success
auto fp = libmesh_make_unique<FunctionParserBase<Output>>();
fp->AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
fp->AddConstant("pi", std::acos(Real(-1)));
fp->AddConstant("e", std::exp(Real(1)));
if (fp->Parse(_subexpressions.back(), variables) != -1) // -1 for success
libmesh_error_msg
("ERROR: FunctionParser is unable to parse expression: "
<< _subexpressions.back() << '\n' << fp.ErrorMsg());
fp.Optimize();
parsers.push_back(fp);
<< _subexpressions.back() << '\n' << fp->ErrorMsg());
fp->Optimize();
parsers.push_back(std::move(fp));
#else
libmesh_error_msg("ERROR: This functionality requires fparser!");
#endif
Expand Down
123 changes: 71 additions & 52 deletions include/numerics/parsed_function.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,13 @@ class ParsedFunction : public FunctionBase<Output>
const std::vector<Output> * initial_vals=nullptr);

/**
* This class cannot be (default) copy assigned because the
* underlying FunctionParserADBase class does not define a custom
* copy assignment operator, and manually manages memory.
* Constructors
* - This class contains unique_ptrs so it can't be default copy
* constructed or assigned, only default moved and deleted.
*/
ParsedFunction & operator= (const ParsedFunction &) = delete;
ParsedFunction (const ParsedFunction &);
ParsedFunction & operator= (const ParsedFunction &);

/**
* The remaining special functions can be defaulted for this class.
*
* \note Despite the fact that the underlying FunctionParserADBase
* class is not move-assignable or move-constructible, it is still
* possible for _this_ class to be move-assigned and
* move-constructed, because FunctionParserADBase objects only
* appear within std::vectors in this class, and std::vectors can
* generally still be move-assigned and move-constructed even when
* their contents cannot. There are some allocator-specific
* exceptions to this, but it should be guaranteed to work for
* std::allocator in C++14 and beyond. See also:
* https://stackoverflow.com/q/42051917/659433
*/
ParsedFunction (const ParsedFunction &) = default;
ParsedFunction (ParsedFunction &&) = default;
ParsedFunction & operator= (ParsedFunction &&) = default;
virtual ~ParsedFunction () = default;
Expand Down Expand Up @@ -182,18 +168,18 @@ class ParsedFunction : public FunctionBase<Output>

std::string _expression;
std::vector<std::string> _subexpressions;
std::vector<FunctionParserADBase<Output>> parsers;
std::vector<std::unique_ptr<FunctionParserADBase<Output>>> parsers;
std::vector<Output> _spacetime;

// derivative functions
std::vector<FunctionParserADBase<Output>> dx_parsers;
std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dx_parsers;
#if LIBMESH_DIM > 1
std::vector<FunctionParserADBase<Output>> dy_parsers;
std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dy_parsers;
#endif
#if LIBMESH_DIM > 2
std::vector<FunctionParserADBase<Output>> dz_parsers;
std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dz_parsers;
#endif
std::vector<FunctionParserADBase<Output>> dt_parsers;
std::vector<std::unique_ptr<FunctionParserADBase<Output>>> dt_parsers;
bool _valid_derivatives;

// Variables/values that can be parsed and handled by the function parser
Expand Down Expand Up @@ -224,6 +210,37 @@ ParsedFunction<Output,OutputGradient>::ParsedFunction (const std::string & expre
}


template <typename Output, typename OutputGradient>
inline
ParsedFunction<Output,OutputGradient>::ParsedFunction (const ParsedFunction<Output,OutputGradient> & other) :
FunctionBase<Output>(other),
_expression(other._expression),
_subexpressions(other._subexpressions),
_spacetime(other._spacetime),
_valid_derivatives(other._valid_derivatives),
variables(other.variables),
_additional_vars(other._additional_vars),
_initial_vals(other._initial_vals)
{
// parsers can be generated from scratch by reparsing expression
this->reparse(this->_expression);
this->_initialized = true;
}



template <typename Output, typename OutputGradient>
inline
ParsedFunction<Output,OutputGradient> &
ParsedFunction<Output,OutputGradient>::operator= (const ParsedFunction<Output,OutputGradient> & other)
{
// Use copy-and-swap idiom
ParsedFunction<Output,OutputGradient> tmp(other);
std::swap(tmp, *this);
return *this;
}


template <typename Output, typename OutputGradient>
inline
void
Expand Down Expand Up @@ -261,7 +278,7 @@ Output
ParsedFunction<Output,OutputGradient>::operator() (const Point & p, const Real time)
{
set_spacetime(p, time);
return eval(parsers[0], "f", 0);
return eval(*parsers[0], "f", 0);
}

template <typename Output, typename OutputGradient>
Expand All @@ -270,7 +287,7 @@ Output
ParsedFunction<Output,OutputGradient>::dot (const Point & p, const Real time)
{
set_spacetime(p, time);
return eval(dt_parsers[0], "df/dt", 0);
return eval(*dt_parsers[0], "df/dt", 0);
}

template <typename Output, typename OutputGradient>
Expand All @@ -281,12 +298,12 @@ ParsedFunction<Output,OutputGradient>::gradient (const Point & p, const Real tim
OutputGradient grad;
set_spacetime(p, time);

grad(0) = eval(dx_parsers[0], "df/dx", 0);
grad(0) = eval(*dx_parsers[0], "df/dx", 0);
#if LIBMESH_DIM > 1
grad(1) = eval(dy_parsers[0], "df/dy", 0);
grad(1) = eval(*dy_parsers[0], "df/dy", 0);
#endif
#if LIBMESH_DIM > 2
grad(2) = eval(dz_parsers[0], "df/dz", 0);
grad(2) = eval(*dz_parsers[0], "df/dz", 0);
#endif

return grad;
Expand All @@ -309,7 +326,7 @@ ParsedFunction<Output,OutputGradient>::operator()
// The remaining locations in _spacetime are currently fixed at construction
// but could potentially be made dynamic
for (unsigned int i=0; i != size; ++i)
output(i) = eval(parsers[i], "f", i);
output(i) = eval(*parsers[i], "f", i);
}

/**
Expand All @@ -329,7 +346,7 @@ ParsedFunction<Output,OutputGradient>::component (unsigned int i,
// The remaining locations in _spacetime are currently fixed at construction
// but could potentially be made dynamic
libmesh_assert_less(i, parsers.size());
return eval(parsers[i], "f", i);
return eval(*parsers[i], "f", i);
}

/**
Expand Down Expand Up @@ -540,51 +557,53 @@ ParsedFunction<Output,OutputGradient>::partial_reparse (const std::string & expr

// Parse (and optimize if possible) the subexpression.
// Add some basic constants, to Real precision.
FunctionParserADBase<Output> fp;
fp.AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
fp.AddConstant("pi", std::acos(Real(-1)));
fp.AddConstant("e", std::exp(Real(1)));
if (fp.Parse(_subexpressions.back(), variables) != -1) // -1 for success
auto fp = libmesh_make_unique<FunctionParserADBase<Output>>();
fp->AddConstant("NaN", std::numeric_limits<Real>::quiet_NaN());
fp->AddConstant("pi", std::acos(Real(-1)));
fp->AddConstant("e", std::exp(Real(1)));
if (fp->Parse(_subexpressions.back(), variables) != -1) // -1 for success
libmesh_error_msg
("ERROR: FunctionParser is unable to parse expression: "
<< _subexpressions.back() << '\n' << fp.ErrorMsg());
<< _subexpressions.back() << '\n' << fp->ErrorMsg());

// use of derivatives is optional. suppress error output on the console
// use the has_derivatives() method to check if AutoDiff was successful.
// also enable immediate optimization
fp.SetADFlags(FunctionParserADBase<Output>::ADSilenceErrors |
fp->SetADFlags(FunctionParserADBase<Output>::ADSilenceErrors |
FunctionParserADBase<Output>::ADAutoOptimize);

// optimize original function
fp.Optimize();
parsers.push_back(fp);
fp->Optimize();

// generate derivatives through automatic differentiation
FunctionParserADBase<Output> dx_fp(fp);
if (dx_fp.AutoDiff("x") != -1) // -1 for success
auto dx_fp = libmesh_make_unique<FunctionParserADBase<Output>>(*fp);
if (dx_fp->AutoDiff("x") != -1) // -1 for success
_valid_derivatives = false;
dx_parsers.push_back(dx_fp);
dx_parsers.push_back(std::move(dx_fp));
#if LIBMESH_DIM > 1
FunctionParserADBase<Output> dy_fp(fp);
if (dy_fp.AutoDiff("y") != -1) // -1 for success
auto dy_fp = libmesh_make_unique<FunctionParserADBase<Output>>(*fp);
if (dy_fp->AutoDiff("y") != -1) // -1 for success
_valid_derivatives = false;
dy_parsers.push_back(dy_fp);
dy_parsers.push_back(std::move(dy_fp));
#endif
#if LIBMESH_DIM > 2
FunctionParserADBase<Output> dz_fp(fp);
if (dz_fp.AutoDiff("z") != -1) // -1 for success
auto dz_fp = libmesh_make_unique<FunctionParserADBase<Output>>(*fp);
if (dz_fp->AutoDiff("z") != -1) // -1 for success
_valid_derivatives = false;
dz_parsers.push_back(dz_fp);
dz_parsers.push_back(std::move(dz_fp));
#endif
FunctionParserADBase<Output> dt_fp(fp);
if (dt_fp.AutoDiff("t") != -1) // -1 for success
auto dt_fp = libmesh_make_unique<FunctionParserADBase<Output>>(*fp);
if (dt_fp->AutoDiff("t") != -1) // -1 for success
_valid_derivatives = false;
dt_parsers.push_back(dt_fp);
dt_parsers.push_back(std::move(dt_fp));

// If at end, use nextstart=maxSize. Else start at next
// character.
nextstart = (end == std::string::npos) ?
std::string::npos : end + 1;

// Store fp for later use
parsers.push_back(std::move(fp));
}
}

Expand Down
Loading

0 comments on commit 5912de9

Please sign in to comment.