added: allow use of automatic differentation in analytic solutions

This commit is contained in:
Arne Morten Kvarving 2023-09-27 09:01:20 +02:00
parent cca533fa1c
commit abb21feef7
7 changed files with 246 additions and 36 deletions

View File

@ -208,6 +208,9 @@ bool SIMbase::preprocess (const IntVec& ignored, bool fixDup)
static int substep = 10; static int substep = 10;
this->printHeading(substep); this->printHeading(substep);
if (mySol)
mySol->setupSecondarySolutions();
// Perform some sub-class specific pre-preprocessing, if any // Perform some sub-class specific pre-preprocessing, if any
this->preprocessA(); this->preprocessA();

View File

@ -17,13 +17,51 @@
#include "Utilities.h" #include "Utilities.h"
#include "IFEM.h" #include "IFEM.h"
#include "tinyxml.h" #include "tinyxml.h"
#include <autodiff/reverse/var.hpp>
#ifdef HAS_HDF5 #ifdef HAS_HDF5
#include "ProcessAdm.h"
#include "HDF5Writer.h"
#include "FieldFunctions.h" #include "FieldFunctions.h"
#endif #endif
namespace {
//! \brief Function returning the gradient of a scalar or vector function.
template<class FromFunc, class ToFunc, class Ret>
class GradientFunc : public ToFunc
{
public:
//! \brief The constructor initializes the function to use.
GradientFunc(const FromFunc& f) : func(f) {}
protected:
//! \brief Evaluates the gradient in a point.
Ret evaluate(const Vec3& X) const { return func.gradient(X); }
private:
const FromFunc& func; //!< Reference to scalar/vector function
};
//! \brief Specialization for symmetric tensors.
//! \details We need to construct a symmtensor from the returned tensor.
template<>
SymmTensor GradientFunc<VecFunc,STensorFunc,SymmTensor>::evaluate(const Vec3& X) const
{
Tensor tmp = func.gradient(X);
const size_t nsd = tmp.dim();
SymmTensor ret(nsd);
for (size_t i = 1; i <= nsd; ++i)
for (size_t j = i; j <= nsd; ++j)
ret(i,j) = tmp(i,j);
return ret;
}
}
AnaSol::AnaSol (RealFunc* s1, VecFunc* s2, AnaSol::AnaSol (RealFunc* s1, VecFunc* s2,
VecFunc* v1, TensorFunc* v2, STensorFunc* v3) VecFunc* v1, TensorFunc* v2, STensorFunc* v3)
: vecSol(v1), vecSecSol(v2), stressSol(v3) : vecSol(v1), vecSecSol(v2), stressSol(v3)
@ -92,8 +130,15 @@ AnaSol::AnaSol (const TiXmlElement* elem, bool scalarSol)
const char* type = elem->Attribute("type"); const char* type = elem->Attribute("type");
if (type && !strcasecmp(type,"fields")) if (type && !strcasecmp(type,"fields"))
this->parseFieldFunctions(elem,scalarSol); this->parseFieldFunctions(elem,scalarSol);
else {
bool useAD = false;
utl::getAttribute(elem, "autodiff", useAD);
utl::getAttribute(elem, "symmetric", symmetric);
if (useAD)
this->parseExpressionFunctions<autodiff::var>(elem,scalarSol);
else else
this->parseExpressionFunctions(elem,scalarSol); this->parseExpressionFunctions<Real>(elem,scalarSol);
}
} }
@ -128,8 +173,13 @@ void AnaSol::initPatch (size_t pIdx)
} }
template<class Scalar>
void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol) void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol)
{ {
using EvalF = EvalFuncSpatial<Scalar>;
using VecF = EvalMultiFunction<VecFunc,Vec3,Scalar>;
using TensorF = EvalMultiFunction<TensorFunc,Tensor,Scalar>;
using STensorF = EvalMultiFunction<STensorFunc,SymmTensor,Scalar>;
std::string variables; std::string variables;
const TiXmlElement* var = elem->FirstChildElement("variables"); const TiXmlElement* var = elem->FirstChildElement("variables");
if (var && var->FirstChild()) if (var && var->FirstChild())
@ -168,16 +218,16 @@ void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol)
if (scalarSol) if (scalarSol)
{ {
if (type == "expression") { if (type == "expression") {
scalSol.push_back(new EvalFunction((variables+primary).c_str())); scalSol.push_back(new EvalF((variables+primary).c_str()));
parseDerivatives(static_cast<EvalFunction*>(scalSol.back()),prim); parseDerivatives(static_cast<EvalF*>(scalSol.back()),prim);
} else } else
scalSol.push_back(utl::parseRealFunc(primary,type,false)); scalSol.push_back(utl::parseRealFunc(primary,type,false));
} }
else else
{ {
if (type == "expression") { if (type == "expression") {
vecSol = new VecFuncExpr(primary,variables); vecSol = new VecF(primary,variables);
parseDerivatives(static_cast<VecFuncExpr*>(vecSol),prim); parseDerivatives(static_cast<VecF*>(vecSol),prim);
} else } else
vecSol = utl::parseVecFunc(primary, type); vecSol = utl::parseVecFunc(primary, type);
} }
@ -190,9 +240,9 @@ void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol)
utl::getAttribute(prim, "type", type); utl::getAttribute(prim, "type", type);
std::string prType = (type == "expression" ? "" : "("+type+") "); std::string prType = (type == "expression" ? "" : "("+type+") ");
std::string primary = prim->FirstChild()->Value(); std::string primary = prim->FirstChild()->Value();
IFEM::cout <<"\tScalar Primary " << prType << "=" << primary << std::endl; IFEM::cout <<"\tScalar Primary" << prType << "=" << primary << std::endl;
if (type == "expression") if (type == "expression")
scalSol.push_back(new EvalFunction((variables+primary).c_str())); scalSol.push_back(new EvalF((variables+primary).c_str()));
else else
scalSol.push_back(utl::parseRealFunc(primary, type, false)); scalSol.push_back(utl::parseRealFunc(primary, type, false));
prim = prim->NextSiblingElement("scalarprimary"); prim = prim->NextSiblingElement("scalarprimary");
@ -209,18 +259,18 @@ void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol)
if (scalarSol) if (scalarSol)
{ {
if (type == "expression") { if (type == "expression") {
scalSecSol.push_back(new VecFuncExpr(secondary,variables)); scalSecSol.push_back(new VecF(secondary,variables));
parseDerivatives(static_cast<VecFuncExpr*>(scalSecSol.back()),sec); parseDerivatives(static_cast<VecF*>(scalSecSol.back()),sec);
} else } else
scalSecSol.push_back(utl::parseVecFunc(secondary, type)); scalSecSol.push_back(utl::parseVecFunc(secondary, type));
} }
else else
{ {
if (type == "expression") if (type == "expression") {
vecSecSol = new TensorFuncExpr(secondary,variables); vecSecSol = new TensorF(secondary,variables);
else parseDerivatives(static_cast<TensorF*>(vecSecSol),sec);
} else
vecSecSol = utl::parseTensorFunc(secondary, type); vecSecSol = utl::parseTensorFunc(secondary, type);
parseDerivatives(static_cast<TensorFuncExpr*>(vecSecSol),sec);
} }
} }
@ -232,7 +282,7 @@ void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol)
std::string secondary = sec->FirstChild()->Value(); std::string secondary = sec->FirstChild()->Value();
IFEM::cout <<"\tScalar Secondary="<< secondary << std::endl; IFEM::cout <<"\tScalar Secondary="<< secondary << std::endl;
if (type == "expression") if (type == "expression")
scalSecSol.push_back(new VecFuncExpr(secondary,variables)); scalSecSol.push_back(new VecF(secondary,variables));
else else
scalSecSol.push_back(utl::parseVecFunc(secondary, type)); scalSecSol.push_back(utl::parseVecFunc(secondary, type));
sec = sec->NextSiblingElement("scalarsecondary"); sec = sec->NextSiblingElement("scalarsecondary");
@ -241,10 +291,11 @@ void AnaSol::parseExpressionFunctions (const TiXmlElement* elem, bool scalarSol)
const TiXmlElement* stress = elem->FirstChildElement("stress"); const TiXmlElement* stress = elem->FirstChildElement("stress");
if (stress && stress->FirstChild()) if (stress && stress->FirstChild())
{ {
symmetric = true;
std::string sigma = stress->FirstChild()->Value(); std::string sigma = stress->FirstChild()->Value();
IFEM::cout <<"\tStress="<< sigma << std::endl; IFEM::cout <<"\tStress="<< sigma << std::endl;
stressSol = new STensorFuncExpr(sigma,variables); stressSol = new STensorF(sigma,variables);
parseDerivatives(static_cast<STensorFuncExpr*>(stressSol),stress); parseDerivatives(static_cast<STensorF*>(stressSol),stress);
} }
} }
@ -310,3 +361,23 @@ void AnaSol::parseFieldFunctions (const TiXmlElement* elem, bool scalarSol)
<<", no fields read."<< std::endl; <<", no fields read."<< std::endl;
#endif #endif
} }
void AnaSol::setupSecondarySolutions ()
{
// if we are given a stress sol, no scalar secondaries should be registered.
// this is tailored to assumptions in elasticity applications.
if (!stressSol && !scalSol.empty()) {
scalSecSol.resize(scalSol.size());
for (size_t i = 0; i < scalSol.size(); ++i)
if (!scalSecSol[i])
scalSecSol[i] = new GradientFunc<RealFunc,VecFunc,Vec3>(*scalSol[i]);
}
if (vecSol) {
if (symmetric && !stressSol)
stressSol = new GradientFunc<VecFunc,STensorFunc,SymmTensor>(*vecSol);
else if (!symmetric && !vecSecSol)
vecSecSol = new GradientFunc<VecFunc,TensorFunc,Tensor>(*vecSol);
}
}

View File

@ -123,14 +123,22 @@ public:
//! \brief Sets the patch to use. //! \brief Sets the patch to use.
void initPatch(size_t pIdx); void initPatch(size_t pIdx);
//! \brief Make sure we have a secondary solution.
//! \details If none is given, we use derivation (automatic or finite difference)
//! to obtain one.
virtual void setupSecondarySolutions();
private: private:
//! \brief Parses expression functions from XML definition. //! \brief Parses expression functions from XML definition.
template<class Scalar>
void parseExpressionFunctions(const TiXmlElement* elem, bool scalarSol); void parseExpressionFunctions(const TiXmlElement* elem, bool scalarSol);
//! \brief Parses field functions from XML definition. //! \brief Parses field functions from XML definition.
void parseFieldFunctions(const TiXmlElement* elem, bool scalarSol); void parseFieldFunctions(const TiXmlElement* elem, bool scalarSol);
protected: protected:
bool symmetric = false; //!< True to use symmetric secondary solution
std::vector<RealFunc*> scalSol; //!< Primary scalar solution fields std::vector<RealFunc*> scalSol; //!< Primary scalar solution fields
std::vector<VecFunc*> scalSecSol; //!< Secondary scalar solution fields std::vector<VecFunc*> scalSecSol; //!< Secondary scalar solution fields

View File

@ -22,7 +22,7 @@
#include <autodiff/reverse/var.hpp> #include <autodiff/reverse/var.hpp>
template<> int EvalFunc::numError = 0; template<> int EvalFuncScalar<Real>::numError = 0; //!< Explicit instantiation
namespace { namespace {
@ -90,7 +90,7 @@ void ExprException (const ExprEval::Exception& exc, const char* task,
std::cerr <<": Unknown exception"; std::cerr <<": Unknown exception";
} }
std::cerr << std::endl; std::cerr << std::endl;
EvalFunc::numError++; EvalFuncScalar<Real>::numError++;
} }

View File

@ -99,14 +99,16 @@ class EvalFuncSpatial : public RealFunc
Scalar* z; //!< Z-coordinate Scalar* z; //!< Z-coordinate
Scalar* t; //!< Time Scalar* t; //!< Time
//! \brief Returns a const ref to a member.
//! \param dir One-based index to member
const Scalar& get(int dir) const const Scalar& get(int dir) const
{ {
switch (dir) { switch (dir) {
default:
case 1: return *x; case 1: return *x;
case 2: return *y; case 2: return *y;
case 3: return *z; case 3: return *z;
case 4: return *t; case 4: return *t;
default: return *x;
} }
} }
}; };
@ -268,4 +270,7 @@ using TensorFuncExpr = EvalMultiFunction<TensorFunc,Tensor,Real>;
//! Symmetric tensor-valued function expression //! Symmetric tensor-valued function expression
using STensorFuncExpr = EvalMultiFunction<STensorFunc,SymmTensor,Real>; using STensorFuncExpr = EvalMultiFunction<STensorFunc,SymmTensor,Real>;
//! \brief Explicit instantiation of error flag.
template<> int EvalFunc::numError;
#endif #endif

View File

@ -717,7 +717,7 @@ static const ScalarFunc* parseFunction (const char* type, char* cline, Real C)
{ {
cline = strtok(cline,":"); cline = strtok(cline,":");
IFEM::cout << cline; IFEM::cout << cline;
EvalFunc::numError = 0; EvalFuncScalar<Real>::numError = 0;
ScalarFunc* sf = new EvalFunc(cline,"t",C); ScalarFunc* sf = new EvalFunc(cline,"t",C);
if (EvalFunc::numError > 0) if (EvalFunc::numError > 0)
{ {

View File

@ -12,6 +12,8 @@
#include "AnaSol.h" #include "AnaSol.h"
#include "Function.h" #include "Function.h"
#include "Tensor.h"
#include "TensorFunction.h"
#include "Vec3Oper.h" #include "Vec3Oper.h"
#include "tinyxml.h" #include "tinyxml.h"
@ -48,15 +50,15 @@ TEST(TestAnaSol, ParseDerivatives)
ASSERT_TRUE(v != nullptr); ASSERT_TRUE(v != nullptr);
Vec3 X(1.0,2.0,3.0); Vec3 X(1.0,2.0,3.0);
EXPECT_FLOAT_EQ((*v)(X),8.0); EXPECT_DOUBLE_EQ((*v)(X),8.0);
EXPECT_FLOAT_EQ(v->deriv(X,1),3.0); EXPECT_DOUBLE_EQ(v->deriv(X,1),3.0);
EXPECT_FLOAT_EQ(v->deriv(X,2),4.0); EXPECT_DOUBLE_EQ(v->deriv(X,2),4.0);
EXPECT_FLOAT_EQ(v->deriv(X,3),1.0); EXPECT_DOUBLE_EQ(v->deriv(X,3),1.0);
EXPECT_FLOAT_EQ(v->dderiv(X,1,1),6.0); EXPECT_DOUBLE_EQ(v->dderiv(X,1,1),6.0);
EXPECT_FLOAT_EQ(v->dderiv(X,2,2),2.0); EXPECT_DOUBLE_EQ(v->dderiv(X,2,2),2.0);
EXPECT_FLOAT_EQ(v->dderiv(X,3,3),0.0); EXPECT_DOUBLE_EQ(v->dderiv(X,3,3),0.0);
EXPECT_FLOAT_EQ(v->dderiv(X,1,2),0.0); EXPECT_DOUBLE_EQ(v->dderiv(X,1,2),0.0);
EXPECT_FLOAT_EQ(v->dderiv(X,3,2),0.0); EXPECT_DOUBLE_EQ(v->dderiv(X,3,2),0.0);
VecFunc* v2 = mySol.getScalarSecSol(); VecFunc* v2 = mySol.getScalarSecSol();
ASSERT_TRUE(v2 != nullptr); ASSERT_TRUE(v2 != nullptr);
@ -65,3 +67,124 @@ TEST(TestAnaSol, ParseDerivatives)
EXPECT_TRUE(v2->deriv(X,2) == Vec3(0.0,2.0,0.0)); EXPECT_TRUE(v2->deriv(X,2) == Vec3(0.0,2.0,0.0));
EXPECT_TRUE(v2->dderiv(X,1,1) == Vec3(6.0,0.0,0.0)); EXPECT_TRUE(v2->dderiv(X,1,1) == Vec3(6.0,0.0,0.0));
} }
TEST(TestAnaSol, ParseAD)
{
const char* input = "<anasol type=\"expression\" autodiff=\"true\">"
" <primary>"
" x^3+y^2+z"
" </primary>"
"</anasol>";
TiXmlDocument doc;
doc.Parse(input,nullptr,TIXML_ENCODING_UTF8);
const TiXmlElement* tag = doc.RootElement();
ASSERT_TRUE(tag != nullptr);
ASSERT_EQ(strcmp(tag->Value(),"anasol"),0);
AnaSol mySol(tag,true);
RealFunc* v = mySol.getScalarSol();
ASSERT_TRUE(v != nullptr);
mySol.setupSecondarySolutions();
Vec3 X(1.0,2.0,3.0);
const Vec3 grad1 = v->gradient(X);
const SymmTensor hess = v->hessian(X);
VecFunc* v2 = mySol.getScalarSecSol();
ASSERT_TRUE(v2 != nullptr);
const Vec3 grad2 = (*v2)(X);
EXPECT_DOUBLE_EQ((*v)(X),8.0);
EXPECT_DOUBLE_EQ(v->deriv(X,1),3.0);
EXPECT_DOUBLE_EQ(grad1[0],3.0);
EXPECT_DOUBLE_EQ(grad2[0],3.0);
EXPECT_DOUBLE_EQ(v->deriv(X,2),4.0);
EXPECT_DOUBLE_EQ(grad1[1],4.0);
EXPECT_DOUBLE_EQ(grad2[1],4.0);
EXPECT_DOUBLE_EQ(v->deriv(X,3),1.0);
EXPECT_DOUBLE_EQ(grad1[2],1.0);
EXPECT_DOUBLE_EQ(grad2[2],1.0);
EXPECT_DOUBLE_EQ(v->dderiv(X,1,1),6.0);
EXPECT_DOUBLE_EQ(hess(1,1), 6.0);
EXPECT_DOUBLE_EQ(v->dderiv(X,2,2),2.0);
EXPECT_DOUBLE_EQ(hess(2,2), 2.0);
EXPECT_DOUBLE_EQ(v->dderiv(X,3,3),0.0);
EXPECT_DOUBLE_EQ(hess(3,3), 0.0);
EXPECT_DOUBLE_EQ(v->dderiv(X,1,2),0.0);
EXPECT_DOUBLE_EQ(hess(1,2), 0.0);
EXPECT_DOUBLE_EQ(v->dderiv(X,3,2),0.0);
EXPECT_DOUBLE_EQ(hess(3,2), 0.0);
}
TEST(TestAnaSol, ParseFD)
{
const char* input = "<anasol type=\"expression\">"
" <primary>"
" x^3+y^2+z"
" </primary>"
"</anasol>";
TiXmlDocument doc;
doc.Parse(input,nullptr,TIXML_ENCODING_UTF8);
const TiXmlElement* tag = doc.RootElement();
ASSERT_TRUE(tag != nullptr);
ASSERT_EQ(strcmp(tag->Value(),"anasol"),0);
AnaSol mySol(tag,true);
RealFunc* v = mySol.getScalarSol();
ASSERT_TRUE(v != nullptr);
mySol.setupSecondarySolutions();
Vec3 X(1.0,2.0,3.0);
const Vec3 grad1 = v->gradient(X);
VecFunc* v2 = mySol.getScalarSecSol();
ASSERT_TRUE(v2 != nullptr);
const Vec3 grad2 = (*v2)(X);
EXPECT_DOUBLE_EQ((*v)(X),8.0);
EXPECT_NEAR(v->deriv(X,1),3.0,1e-6);
EXPECT_NEAR(grad1[0],3.0,1e-6);
EXPECT_NEAR(grad2[0],3.0,1e-6);
EXPECT_NEAR(v->deriv(X,2),4.0,1e-6);
EXPECT_NEAR(grad1[1],4.0,1e-6);
EXPECT_NEAR(grad2[1],4.0,1e-6);
EXPECT_NEAR(v->deriv(X,3),1.0,1e-6);
EXPECT_NEAR(grad1[2],1.0,1e-6);
EXPECT_NEAR(grad2[2],1.0,1e-6);
}
TEST(TestAnaSol, ParseADStress)
{
const char* input = "<anasol type=\"expression\" symmetric=\"true\" autodiff=\"true\">"
" <primary>"
" x^3+y^2+z | x^2+y^3+z"
" </primary>"
"</anasol>";
TiXmlDocument doc;
doc.Parse(input,nullptr,TIXML_ENCODING_UTF8);
const TiXmlElement* tag = doc.RootElement();
ASSERT_TRUE(tag != nullptr);
ASSERT_EQ(strcmp(tag->Value(),"anasol"),0);
AnaSol mySol(tag,false);
ASSERT_TRUE(mySol.getScalarSol() == nullptr);
ASSERT_TRUE(mySol.getVectorSol() != nullptr);
mySol.setupSecondarySolutions();
ASSERT_TRUE(mySol.getVectorSecSol() == nullptr);
const STensorFunc* v = mySol.getStressSol();
ASSERT_TRUE(v != nullptr);
Vec3 X(1.0,2.0,3.0);
const SymmTensor grad1 = (*v)(X);
EXPECT_DOUBLE_EQ(grad1(1,1), 3*X.x*X.x);
EXPECT_DOUBLE_EQ(grad1(1,2), 2*X.y);
EXPECT_DOUBLE_EQ(grad1(2,1), 2*X.y);
EXPECT_DOUBLE_EQ(grad1(2,2), 3*X.y*X.y);
}