Compare commits

...
19 Commits
Author SHA1 Message Date
Julian Andrej 7167757d39 renaming and alternative interface 2020-12-03 08:49:51 -08:00
Julian Andrej 60ef17a321 example test 2020-11-30 13:43:45 -08:00
Julian Andrej 97272d60e8 support coordinates in qfunc kernel 2020-11-25 09:59:55 -08:00
Julian Andrej bb9eb4872b fixed parallel 2020-11-24 14:29:27 -08:00
Julian Andrej 41798f7028 more tests and example updated 2020-11-24 12:05:45 -08:00
Julian Andrej 633ee29601 more unit tests 2020-11-23 18:10:52 -08:00
Julian Andrej bb194bbcd5 plaplacian ready 2020-11-23 16:51:43 -08:00
Julian Andrej 9d5977165f more ad functions 2020-11-23 16:03:57 -08:00
Julian Andrej 68fb8e80a9 add unit tests and work towards p-Laplacian 2020-11-23 14:51:46 -08:00
Julian Andrej 274416caf5 add temporary safeguards for independent variables during AD 2020-11-20 17:53:40 -08:00
Julian Andrej 5853221745 ad working 2020-11-20 16:50:04 -08:00
Sam Mish c79cccdc08 changes to get variadic args working 2020-11-19 08:53:35 -08:00
Julian Andrej 91f30e88c9 refactor 2020-11-18 15:44:09 -08:00
Julian Andrej 1432ac14a6 working example 2020-11-18 08:26:51 -08:00
Julian Andrej 02cfe4ff0a parameter pack expansion 2020-11-17 09:00:58 -08:00
Julian Andrej 447214ef57 switch to tuple return type 2020-11-16 10:50:46 -08:00
Julian Andrej fba6b4d832 gradient matvec 2020-11-13 16:32:48 -08:00
Julian Andrej 12ec6db87c working operator application 2020-11-11 18:18:38 -08:00
Julian Andrej f117c0a4e7 first try 2020-11-11 16:04:23 -08:00
11 changed files with 1345 additions and 1 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ set(USER_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/config/user.cmake" CACHE PATH
"Path to optional user configuration file.")
# Require C++11 and disable compiler-specific extensions
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
+1
View File
@@ -26,3 +26,4 @@ add_subdirectory(tools)
add_subdirectory(toys)
add_subdirectory(nurbs)
add_subdirectory(gslib)
add_subdirectory(variationalform)
+15
View File
@@ -0,0 +1,15 @@
add_library(parvariationalform parvariationalform.cpp)
target_include_directories(parvariationalform PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(parvariationalform mfem)
add_executable(parvariationalform_example parvariationalform_example.cpp)
target_include_directories(parvariationalform_example PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(parvariationalform_example parvariationalform mfem)
add_executable(parvariationalform_ex1 parvariationalform_ex1.cpp)
target_include_directories(parvariationalform_ex1 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(parvariationalform_ex1 parvariationalform mfem)
add_executable(parvariationalform_unit_tests test_tensor_ad.cpp)
target_include_directories(parvariationalform_unit_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/tests/unit)
target_link_libraries(parvariationalform_unit_tests parvariationalform mfem)
@@ -0,0 +1,23 @@
#include "mfem.hpp"
#pragma once
namespace mfem
{
class GenericIntegrator
{
public:
GenericIntegrator(const IntegrationRule *ir = nullptr) : IntRule(ir) {}
virtual void Setup(const FiniteElementSpace &) = 0;
virtual void Apply(const Vector &x, Vector &y) const = 0;
virtual void ApplyGradient(const Vector &x,
const Vector &v,
Vector &y) const = 0;
protected:
const IntegrationRule *IntRule;
};
} // namespace mfem
@@ -0,0 +1,94 @@
#include "parvariationalform.hpp"
#include "qfuncintegrator.hpp"
namespace mfem
{
ParVariationalForm::ParVariationalForm(ParFiniteElementSpace *f)
: Operator(f->GetTrueVSize()), fes(f), P(f->GetProlongationMatrix()),
grad(*this)
{
G = fes->GetElementRestriction(ElementDofOrdering::LEXICOGRAPHIC);
MFEM_ASSERT(G, "Some GetElementRestriction error");
x_local.SetSize(G->Height(), Device::GetMemoryType());
v_local.SetSize(G->Height(), Device::GetMemoryType());
y_local.SetSize(G->Height(), Device::GetMemoryType());
}
void ParVariationalForm::Mult(const Vector &x, Vector &y) const
{
px.SetSize(P->Height());
py.SetSize(P->Height());
P->Mult(x, px);
G->Mult(px, x_local);
y_local = 0.0;
for (int i = 0; i < domain_integrators.Size(); ++i)
{
// y += F(x)
domain_integrators[i]->Apply(x_local, y_local);
}
G->MultTranspose(y_local, py);
P->MultTranspose(py, y);
y.HostReadWrite();
for (int i = 0; i < ess_tdof_list.Size(); i++)
{
y(ess_tdof_list[i]) = 0.0;
}
}
void ParVariationalForm::GradientMult(const Vector &v, Vector &y) const
{
px.SetSize(P->Height());
py.SetSize(P->Height());
pv.SetSize(P->Height());
P->Mult(v, pv);
G->Mult(pv, v_local);
P->Mult(x_lin, px);
G->Mult(px, x_local);
y_local = 0.0;
for (int i = 0; i < domain_integrators.Size(); ++i)
{
// y += dF(x)/dx * v
if (is_linear)
{
// take care of RHS
// domain_integrators[i]->Apply(v_local, y_local);
}
domain_integrators[i]->ApplyGradient(x_local, v_local, y_local);
}
G->MultTranspose(y_local, py);
P->MultTranspose(py, y);
y.HostReadWrite();
for (int i = 0; i < ess_tdof_list.Size(); i++)
{
y(ess_tdof_list[i]) = v(ess_tdof_list[i]);
}
}
Operator &ParVariationalForm::GetGradient(const Vector &x) const
{
x_lin = x;
return grad;
}
HypreParMatrix *ParVariationalForm::GetGradientMatrix(const Vector &x)
{
delete gradient_matrix;
gradient_matrix = new HypreParMatrix;
return gradient_matrix;
}
void ParVariationalForm::SetEssentialBC(const Array<int> &ess_attr)
{
fes->GetEssentialTrueDofs(ess_attr, ess_tdof_list);
}
} // namespace mfem
@@ -0,0 +1,109 @@
#include "mfem.hpp"
#include "genericintegrator.hpp"
#include "qfuncintegrator.hpp"
#pragma once
namespace mfem
{
class ParVariationalForm : public Operator
{
class Gradient : public Operator
{
public:
Gradient(ParVariationalForm &f) : Operator(f.Height()), form(f){};
void Mult(const Vector &x, Vector &y) const override
{
form.GradientMult(x, y);
}
private:
ParVariationalForm &form;
};
public:
ParVariationalForm(ParFiniteElementSpace *f);
void Mult(const Vector &x, Vector &y) const override;
void AddDomainIntegrator(GenericIntegrator *i)
{
domain_integrators.Append(i);
i->Setup(*fes);
}
template<typename integrator_type,
typename qfunc_type,
typename... qfunc_args_type>
void AddDomainIntegrator(qfunc_type f, qfunc_args_type const &... fargs)
{
if constexpr (std::is_same_v<integrator_type, DomainLFIntegrator>)
{
auto i = new QFunctionIntegrator(
[&](auto... args) {
auto du = std::get<1>(std::tuple{args...});
return std::tuple{f(args...), decltype(du){}};
},
0,
*fes->GetParMesh(),
fargs...);
domain_integrators.Append(i);
i->Setup(*fes);
}
if constexpr (std::is_same_v<integrator_type, DiffusionIntegrator>)
{
auto i = new QFunctionIntegrator(
[&](auto... args) {
auto du = std::get<1>(std::tuple{args...});
return std::tuple{0.0, f(args...) * du};
},
0,
*fes->GetParMesh(),
fargs...);
domain_integrators.Append(i);
i->Setup(*fes);
}
}
void AssumeLinear() { is_linear = true; }
// Return an Operator that provides a Mult(x, y) which is the MatVec of the
// gradient of the ParVariationalForm wrt x. Acts as a "passthrough" to
// ::GradientMult in order to satisfy mfem interfaces.
Operator &GetGradient(const Vector &x) const override;
// Return an assmbled parallel matrix which represents the gradient of the
// ParVariationalForm wrt to x.
HypreParMatrix *GetGradientMatrix(const Vector &x);
void SetEssentialBC(const Array<int> &ess_attr);
protected:
// y = F'(x_lin) * v
void GradientMult(const Vector &v, Vector &y) const;
bool is_linear = false;
ParFiniteElementSpace *fes;
Array<GenericIntegrator *> domain_integrators;
Array<int> ess_tdof_list;
// T -> L
const Operator *P;
// L -> E
const Operator *G;
mutable Vector x_local, y_local, v_local, px, py, pv;
// State to build the Gradient on, single source of the true state.
mutable Vector x_lin;
mutable Gradient grad;
HypreParMatrix *gradient_matrix = nullptr;
};
} // namespace mfem
@@ -0,0 +1,164 @@
#include "mfem.hpp"
#include "parvariationalform.hpp"
#include "tensor.hpp"
#include <fstream>
#include <iostream>
using namespace std;
using namespace mfem;
int main(int argc, char *argv[])
{
int num_procs, myid;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
const char *mesh_file = "../data/inline-quad.mesh";
int order = 1;
bool static_cond = false;
bool pa = false;
const char *device_config = "cpu";
bool visualization = true;
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh", "Mesh file to use.");
args.AddOption(&order,
"-o",
"--order",
"Finite element order (polynomial degree) or -1 for"
" isoparametric space.");
args.AddOption(&visualization,
"-vis",
"--visualization",
"-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.Parse();
if (!args.Good())
{
if (myid == 0)
{
args.PrintUsage(cout);
}
MPI_Finalize();
return 1;
}
if (myid == 0)
{
args.PrintOptions(cout);
}
Mesh mesh(mesh_file, 1, 1);
int dim = mesh.Dimension();
{
int ref_levels = (int) floor(log(10. / mesh.GetNE()) / log(2.) / dim);
for (int l = 0; l < ref_levels; l++)
{
mesh.UniformRefinement();
}
}
ParMesh pmesh(MPI_COMM_WORLD, mesh);
mesh.Clear();
{
int par_ref_levels = 2;
for (int l = 0; l < par_ref_levels; l++)
{
pmesh.UniformRefinement();
}
}
FiniteElementCollection *fec;
bool delete_fec;
if (order > 0)
{
fec = new H1_FECollection(order, dim);
delete_fec = true;
}
else if (pmesh.GetNodes())
{
fec = pmesh.GetNodes()->OwnFEC();
delete_fec = false;
if (myid == 0)
{
cout << "Using isoparametric FEs: " << fec->Name() << endl;
}
}
else
{
fec = new H1_FECollection(order = 1, dim);
delete_fec = true;
}
ParFiniteElementSpace fespace(&pmesh, fec);
HYPRE_Int size = fespace.GlobalTrueVSize();
if (myid == 0)
{
cout << "Number of finite element unknowns: " << size << endl;
}
Array<int> ess_bdr(pmesh.bdr_attributes.Max());
ess_bdr = 1;
FunctionCoefficient u_excoeff([&](const Vector &coords) {
double x = coords(0);
double y = coords(1);
return x * x + y * y;
});
ParGridFunction x(&fespace);
x.ProjectBdrCoefficient(u_excoeff, ess_bdr);
ParVariationalForm form(&fespace);
auto b_coeff = [&](auto u, auto du, auto x) {
return 4.0 * (1.0 + 2.0 * x[0] * x[0] + 2.0 * x[1] * x[1]);
};
form.AddDomainIntegrator<DomainLFIntegrator>(b_coeff);
auto a_coeff = [&](auto u, auto du, auto x) { return 1.0 + u; };
form.AddDomainIntegrator<DiffusionIntegrator>(a_coeff);
form.SetEssentialBC(ess_bdr);
CGSolver cg(MPI_COMM_WORLD);
cg.SetRelTol(1e-6);
cg.SetMaxIter(2000);
cg.SetPrintLevel(1);
NewtonSolver newton(MPI_COMM_WORLD);
newton.SetOperator(form);
newton.SetSolver(cg);
newton.SetPrintLevel(1);
newton.SetRelTol(1e-8);
newton.SetMaxIter(100);
Vector zero;
Vector X;
x.GetTrueDofs(X);
newton.Mult(zero, X);
x.Distribute(X);
if (visualization)
{
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock << "parallel " << num_procs << " " << myid << "\n";
sol_sock.precision(8);
sol_sock << "solution\n" << pmesh << x << flush;
}
if (delete_fec)
{
delete fec;
}
MPI_Finalize();
return 0;
}
@@ -0,0 +1,118 @@
#include "mfem.hpp"
#include "parvariationalform.hpp"
#include "qfuncintegrator.hpp"
#include "tensor.hpp"
#include <fstream>
#include <iostream>
using namespace std;
using namespace mfem;
int main(int argc, char *argv[])
{
int num_procs, myid;
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &num_procs);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
const char *mesh_file = "../data/inline-quad.mesh";
int order = 1;
int refinements = 0;
double p = 5.0;
OptionsParser args(argc, argv);
args.AddOption(&refinements, "-r", "--ref", "");
args.AddOption(&order, "-o", "--order", "");
args.Parse();
if (!args.Good())
{
if (myid == 0)
{
args.PrintUsage(cout);
}
MPI_Finalize();
return 1;
}
if (myid == 0)
{
args.PrintOptions(cout);
}
Mesh mesh(mesh_file, 1, 1);
int dim = mesh.Dimension();
{
for (int l = 0; l < refinements; l++)
{
mesh.UniformRefinement();
}
}
ParMesh pmesh(MPI_COMM_WORLD, mesh);
auto fec = H1_FECollection(order, dim);
ParFiniteElementSpace fespace(&pmesh, &fec);
Array<int> ess_bdr(pmesh.bdr_attributes.Max());
ess_bdr = 1;
FunctionCoefficient u_excoeff([&](const Vector &coords) {
double x = coords(0);
double y = coords(1);
// return 1.0 - pow(sqrt(x * x + y * y), p / (p - 1.0));
return x * x + y * y;
});
ParGridFunction x(&fespace);
x.Randomize();
x.ProjectBdrCoefficient(u_excoeff, ess_bdr);
ParVariationalForm form(&fespace);
auto plaplacian = new QFunctionIntegrator([&](auto u, auto du, auto x) {
// auto f0 = -1.0;
// auto f1 = pow(norm(du), p - 2.0) * du;
auto f0 = 4.0 * (1.0 + 2.0 * x[0] * x[0] + 2.0 * x[1] * x[1]);
auto f1 = (1.0 + u) * du;
return std::tuple{f0, f1};
}, 0, pmesh);
form.AddDomainIntegrator(plaplacian);
form.SetEssentialBC(ess_bdr);
CGSolver cg(MPI_COMM_WORLD);
cg.SetRelTol(1e-6);
cg.SetMaxIter(2000);
cg.SetPrintLevel(1);
NewtonSolver newton(MPI_COMM_WORLD);
newton.SetOperator(form);
newton.SetSolver(cg);
newton.SetPrintLevel(1);
newton.SetRelTol(1e-8);
newton.SetMaxIter(100);
Vector zero;
Vector X;
x.GetTrueDofs(X);
newton.Mult(zero, X);
x.Distribute(X);
// x.ProjectCoefficient(u_excoeff);
char vishost[] = "localhost";
int visport = 19916;
socketstream sol_sock(vishost, visport);
sol_sock << "parallel " << num_procs << " " << myid << "\n";
sol_sock.precision(8);
sol_sock << "solution\n" << pmesh << x << flush;
MPI_Finalize();
return 0;
}
@@ -0,0 +1,404 @@
#include "mfem.hpp"
#include "../../general/forall.hpp"
#include "genericintegrator.hpp"
#include "tensor.hpp"
#pragma once
namespace mfem
{
template<typename T>
struct supported_type
{
static constexpr bool value = false;
};
template<>
struct supported_type<ParMesh>
{
static constexpr bool value = true;
};
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
class QFunctionIntegrator : public GenericIntegrator
{
public:
QFunctionIntegrator(qfunc_type f,
qfunc_grad_type f_grad,
qfunc_args_type const &... fargs);
QFunctionIntegrator(qfunc_type f, qfunc_args_type const &... fargs);
void Setup(const FiniteElementSpace &fes) override;
void Apply(const Vector &, Vector &) const override;
// y += F'(x) * v
void ApplyGradient(const Vector &x,
const Vector &v,
Vector &y) const override;
protected:
template<int D1D, int Q1D>
void Apply2D(const Vector &u_in_, Vector &y_) const;
template<int D1D, int Q1D>
void ApplyGradient2D(const Vector &u_in_,
const Vector &v_in_,
Vector &y_) const;
auto EvaluateFargValue(const Mesh &m, const double qx, const double qy) const;
const FiniteElementSpace *fespace;
const DofToQuad *maps; ///< Not owned
const GeometricFactors *geom; ///< Not owned
int dim, ne, nq, dofs1D, quad1D;
// Geometric factors
Vector J_;
Vector W_;
qfunc_type qf;
qfunc_grad_type qf_grad;
std::tuple<qfunc_args_type...> qf_farg_values;
};
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
QFunctionIntegrator(qfunc_type f,
qfunc_grad_type df,
qfunc_args_type const &... fargs)
: GenericIntegrator(nullptr), maps(nullptr), geom(nullptr), qf(f),
qf_grad(df), qf_farg_values(std::tuple{fargs...})
{
static_assert((supported_type<qfunc_args_type>::value && ...),
"Type not supported for parameter expansion. See "
"documentation for supported types.");
}
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
QFunctionIntegrator(qfunc_type f, qfunc_args_type const &... fargs)
: GenericIntegrator(nullptr), maps(nullptr), geom(nullptr), qf(f),
qf_grad(qfunc_grad_type{}), qf_farg_values(std::tuple{fargs...})
{
static_assert((supported_type<qfunc_args_type>::value && ...),
"Type not supported for parameter expansion. See "
"documentation for supported types.");
}
template<typename qfunc_type, typename... qfunc_args_type>
QFunctionIntegrator(qfunc_type, qfunc_args_type const &...)
-> QFunctionIntegrator<qfunc_type, int, qfunc_args_type const &...>;
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::Setup(
const FiniteElementSpace &fes)
{
// Assuming the same element type
fespace = &fes;
Mesh *mesh = fes.GetMesh();
if (mesh->GetNE() == 0)
{
return;
}
const FiniteElement &el = *fes.GetFE(0);
ElementTransformation *T = mesh->GetElementTransformation(0);
const IntegrationRule *ir = nullptr;
if (!IntRule)
{
IntRule = &IntRules.Get(el.GetGeomType(), el.GetOrder() * 2);
}
ir = IntRule;
dim = mesh->Dimension();
ne = fes.GetMesh()->GetNE();
nq = ir->GetNPoints();
geom = mesh->GetGeometricFactors(*ir,
GeometricFactors::COORDINATES
| GeometricFactors::JACOBIANS);
maps = &el.GetDofToQuad(*ir, DofToQuad::TENSOR);
dofs1D = maps->ndof;
quad1D = maps->nqpt;
// pa_data.SetSize(ne * nq, Device::GetDeviceMemoryType());
W_.SetSize(nq, Device::GetDeviceMemoryType());
W_.GetMemory().CopyFrom(ir->GetWeights().GetMemory(), nq);
// J.SetSize(ne * nq, Device::GetDeviceMemoryType());
J_ = geom->J;
}
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
auto QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
EvaluateFargValue(const Mesh &m, const double qx, const double qy) const
{
Vector trip(3);
trip = 0.0;
ElementTransformation *tr = const_cast<Mesh &>(m).GetElementTransformation(0);
tr->Transform(IntRule->IntPoint(qx + quad1D * qy), trip);
return tensor<double, 3>{
{trip(0), trip(1), m.SpaceDimension() == 2 ? 0.0 : trip(2)}};
}
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::Apply(
const Vector &x, Vector &y) const
{
if (dim == 2)
{
switch ((dofs1D << 4) | quad1D)
{
case 0x22:
return Apply2D<2, 2>(x, y);
default:
MFEM_ASSERT(false, "NOPE");
}
}
}
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
template<int D1D, int Q1D>
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::Apply2D(
const Vector &u_in_, Vector &y_) const
{
int NE = ne;
auto v1d = Reshape(maps->B.Read(), Q1D, D1D);
auto dv1d_dX = Reshape(maps->G.Read(), Q1D, D1D);
// (NQ x SDIM x DIM x NE)
auto J = Reshape(J_.Read(), Q1D, Q1D, 2, 2, NE);
auto W = Reshape(W_.Read(), Q1D, Q1D);
auto u = Reshape(u_in_.Read(), D1D, D1D, NE);
auto y = Reshape(y_.ReadWrite(), D1D, D1D, NE);
// MFEM_FORALL(e, NE, {
for (int e = 0; e < NE; e++)
{
// loop over quadrature points
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
double u_q = 0.0;
double du_dX_q[2] = {0.0};
for (int ix = 0; ix < D1D; ix++)
{
for (int iy = 0; iy < D1D; iy++)
{
u_q += u(ix, iy, e) * v1d(qx, ix) * v1d(qy, iy);
du_dX_q[0] += u(ix, iy, e) * dv1d_dX(qx, ix) * v1d(qy, iy);
du_dX_q[1] += u(ix, iy, e) * v1d(qx, ix) * dv1d_dX(qy, iy);
}
}
// du_dx_q = invJ^T * du_dX_q
// = (adjJ^T * du_dX_q) / detJ
double J_q[2][2] = {{J(qx, qy, 0, 0, e),
J(qx, qy, 0, 1, e)}, // J_q[0][0], J_q[0][1]
{J(qx, qy, 1, 0, e),
J(qx, qy, 1, 1, e)}}; // J_q[1][0], J_q[1][1]
double detJ_q = (J_q[0][0] * J_q[1][1]) - (J_q[0][1] * J_q[1][0]);
double adjJ[2][2] = {{J_q[1][1], -J_q[0][1]},
{-J_q[1][0], J_q[0][0]}};
tensor<double, 2> du_dx_q
= {(adjJ[0][0] * du_dX_q[0] + adjJ[1][0] * du_dX_q[1]) / detJ_q,
(adjJ[0][1] * du_dX_q[0] + adjJ[1][1] * du_dX_q[1]) / detJ_q};
auto processed_qf_farg_values = std::apply(
[=](auto &... a) {
return std::make_tuple(u_q,
du_dx_q,
EvaluateFargValue(a, qx, qy)...);
},
qf_farg_values);
auto [f0, f1] = std::apply(qf, processed_qf_farg_values);
double f0_X = f0 * detJ_q;
// f1_X = invJ * f1 * detJ
// = adjJ * f1
double f1_X[2] = {
adjJ[0][0] * f1[0] + adjJ[0][1] * f1[1],
adjJ[1][0] * f1[0] + adjJ[1][1] * f1[1],
};
for (int ix = 0; ix < D1D; ix++)
{
for (int iy = 0; iy < D1D; iy++)
{
// accumulate v * f0 + dot(dv_dx, f1)
y(ix, iy, e) += (f0_X * v1d(qx, ix) * v1d(qy, iy)
+ f1_X[0] * dv1d_dX(qx, ix) * v1d(qy, iy)
+ f1_X[1] * dv1d_dX(qy, iy) * v1d(qx, ix))
* W(qx, qy);
}
}
}
}
}
// });
}
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
ApplyGradient(const Vector &x, const Vector &v, Vector &y) const
{
ApplyGradient2D<2, 2>(x, v, y);
}
template<typename qfunc_type, typename qfunc_grad_type, typename... qfunc_args_type>
template<int D1D, int Q1D>
void QFunctionIntegrator<qfunc_type, qfunc_grad_type, qfunc_args_type...>::
ApplyGradient2D(const Vector &u_in_, const Vector &v_in_, Vector &y_) const
{
int NE = ne;
auto v1d = Reshape(maps->B.Read(), Q1D, D1D);
auto dv1d_dX = Reshape(maps->G.Read(), Q1D, D1D);
// (NQ x SDIM x DIM x NE)
auto J = Reshape(J_.Read(), Q1D, Q1D, 2, 2, NE);
auto W = Reshape(W_.Read(), Q1D, Q1D);
auto u = Reshape(u_in_.Read(), D1D, D1D, NE);
auto v = Reshape(v_in_.Read(), D1D, D1D, NE);
auto y = Reshape(y_.ReadWrite(), D1D, D1D, NE);
for (int e = 0; e < NE; e++)
{
// loop over quadrature points
for (int qy = 0; qy < Q1D; ++qy)
{
for (int qx = 0; qx < Q1D; ++qx)
{
double u_q = 0.0;
double du_dX_q[2] = {0.0};
double v_q = 0.0;
double dv_dX_q[2] = {0.0};
for (int ix = 0; ix < D1D; ix++)
{
for (int iy = 0; iy < D1D; iy++)
{
u_q += u(ix, iy, e) * v1d(qx, ix) * v1d(qy, iy);
du_dX_q[0] += u(ix, iy, e) * dv1d_dX(qx, ix) * v1d(qy, iy);
du_dX_q[1] += u(ix, iy, e) * v1d(qx, ix) * dv1d_dX(qy, iy);
v_q += v(ix, iy, e) * v1d(qx, ix) * v1d(qy, iy);
dv_dX_q[0] += v(ix, iy, e) * dv1d_dX(qx, ix) * v1d(qy, iy);
dv_dX_q[1] += v(ix, iy, e) * v1d(qx, ix) * dv1d_dX(qy, iy);
}
}
// du_dx_q = invJ^T * du_dX_q
// = (adjJ^T * du_dX_q) / detJ
double J_q[2][2] = {{J(qx, qy, 0, 0, e),
J(qx, qy, 0, 1, e)}, // J_q[0][0], J_q[0][1]
{J(qx, qy, 1, 0, e),
J(qx, qy, 1, 1, e)}}; // J_q[1][0], J_q[1][1]
double detJ_q = (J_q[0][0] * J_q[1][1]) - (J_q[0][1] * J_q[1][0]);
double adjJ[2][2] = {{J_q[1][1], -J_q[0][1]},
{-J_q[1][0], J_q[0][0]}};
tensor<double, 2> du_dx_q
= {(adjJ[0][0] * du_dX_q[0] + adjJ[1][0] * du_dX_q[1]) / detJ_q,
(adjJ[0][1] * du_dX_q[0] + adjJ[1][1] * du_dX_q[1]) / detJ_q};
double dv_dx_q[2]
= {(adjJ[0][0] * dv_dX_q[0] + adjJ[1][0] * dv_dX_q[1]) / detJ_q,
(adjJ[0][1] * dv_dX_q[0] + adjJ[1][1] * dv_dX_q[1]) / detJ_q};
// compute dF(u, du)/du
auto processed_qf_farg_values_u = std::apply(
[=](auto &... a) {
return std::make_tuple(derivative_wrt(u_q),
du_dx_q,
EvaluateFargValue(a, qx, qy)...);
},
qf_farg_values);
auto [f0u, f1u] = std::apply(qf, processed_qf_farg_values_u);
double f00 = 0.0;
if constexpr (std::is_same_v<decltype(f0u), double>)
{
f00 = 0.0;
}
else
{
f00 = f0u.gradient;
}
tensor<double, 2> f10;
if constexpr (std::is_same_v<decltype(f1u), tensor<double, 2>>)
{
f10 = {0.0, 0.0};
}
else
{
f10 = {f1u[0].gradient, f1u[1].gradient};
}
// compute dF(u, du)/ddu
auto processed_qf_farg_values_du = std::apply(
[=](auto &... a) {
return std::make_tuple(u_q,
derivative_wrt(du_dx_q),
EvaluateFargValue(a, qx, qy)...);
},
qf_farg_values);
auto [f0du, f1du] = std::apply(qf, processed_qf_farg_values_du);
tensor<double, 2> f01;
if constexpr (std::is_same_v<decltype(f0du), double>)
{
f01 = {0.0, 0.0};
}
else
{
f01 = f0du.gradient;
}
tensor<double, 2, 2> f11 = {
{{f1du[0].gradient[0], f1du[0].gradient[1]},
{f1du[1].gradient[0], f1du[1].gradient[1]}}};
double W0 = f00 * v_q + f01[0] * dv_dx_q[0] + f01[1] * dv_dx_q[1];
double W1[2] = {f10[0] * v_q + f11[0][0] * dv_dx_q[0]
+ f11[0][1] * dv_dx_q[1],
f10[1] * v_q + f11[1][0] * dv_dx_q[0]
+ +f11[1][1] * dv_dx_q[1]};
double W0_X = W0 * detJ_q;
// W1_X = invJ * W1 * detJ
// = adjJ * W1
double W1_X[2] = {
adjJ[0][0] * W1[0] + adjJ[0][1] * W1[1],
adjJ[1][0] * W1[0] + adjJ[1][1] * W1[1],
};
for (int ix = 0; ix < D1D; ix++)
{
for (int iy = 0; iy < D1D; iy++)
{
// @TODO: proper comment
y(ix, iy, e) += (W0_X * v1d(qx, ix) * v1d(qy, iy)
+ W1_X[0] * dv1d_dX(qx, ix) * v1d(qy, iy)
+ W1_X[1] * dv1d_dX(qy, iy) * v1d(qx, ix))
* W(qx, qy);
}
}
}
}
}
}
} // namespace mfem
+347
View File
@@ -0,0 +1,347 @@
// tensor
#include <iostream>
#pragma once
template<typename T, int... n>
struct tensor;
template<typename T>
struct tensor<T, 1>
{
static constexpr int shape[1] = {1};
operator T() { return value; }
T value;
};
template<typename T, int n>
struct tensor<T, n>
{
static constexpr int shape[1] = {n};
constexpr auto &operator[](int i) { return value[i]; };
constexpr auto operator[](int i) const { return value[i]; };
T value[n];
};
template<typename T, int first, int... rest>
struct tensor<T, first, rest...>
{
static constexpr int shape[1 + sizeof...(rest)] = {first, rest...};
constexpr auto &operator[](int i) { return value[i]; };
constexpr auto operator[](int i) const { return value[i]; };
tensor<T, rest...> value[first];
};
template<int n>
constexpr int product(int (&values)[n])
{
int p = 1;
for (int i = 0; i < n; i++)
{
p *= values[i];
}
return p;
}
template<typename S, typename T, int... n>
auto operator+(tensor<S, n...> A, tensor<T, n...> B)
{
tensor<decltype(S{} + T{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
{
C[i] = A[i] + B[i];
}
return C;
}
template<typename S, typename T, int... n>
auto operator-(tensor<S, n...> A, tensor<T, n...> B)
{
tensor<decltype(S{} - T{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
{
C[i] = A[i] - B[i];
}
return C;
}
template<typename S, typename T, int... n>
auto operator*(S scale, tensor<T, n...> A)
{
tensor<decltype(S{} * T{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
{
C[i] = scale * A[i];
}
return C;
}
template<typename S, typename T, int... n>
auto operator*(tensor<T, n...> A, S scale)
{
tensor<decltype(T{} * S{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
{
C[i] = A[i] * scale;
}
return C;
}
template<typename S, typename T, int... n>
auto operator/(S scale, tensor<T, n...> A)
{
tensor<decltype(S{} / T{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
{
C[i] = scale / A[i];
}
return C;
}
template<typename S, typename T, int... n>
auto operator/(tensor<T, n...> A, S scale)
{
tensor<decltype(T{} / S{}), n...> C{};
for (int i = 0; i < tensor<T, n...>::shape[0]; i++)
{
C[i] = A[i] / scale;
}
return C;
}
template<typename S, typename T, int m, int n, int p>
auto dot(tensor<S, m, n> A, tensor<T, n, p> B)
{
tensor<decltype(S{} * T{}), m, p> AB{};
for (int i = 0; i < m; i++)
{
for (int j = 0; j < p; j++)
{
for (int k = 0; k < n; k++)
{
AB[i][j] = AB[i][j] + A[i][k] * B[k][j];
}
}
}
return AB;
}
template<typename T, int m, int n>
auto inner(tensor<T, m, n> A, tensor<T, m, n> B)
{
double value = 0.0;
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
value += A[i][j] * B[i][j];
}
}
return value;
}
auto inner(double a, double b)
{
return a * b;
}
template<typename T, int n>
auto tr(tensor<T, n, n> A)
{
T trA{};
for (int i = 0; i < n; i++)
{
trA = trA + A[i][i];
}
return trA;
}
template<int dim>
constexpr tensor<double, dim, dim> Identity()
{
tensor<double, dim, dim> I{};
for (int i = 0; i < dim; i++)
{
for (int j = 0; j < dim; j++)
{
I[i][j] = (i == j);
}
}
return I;
}
template<typename T, int m, int n>
auto transpose(const tensor<T, m, n> &A)
{
tensor<T, n, m> AT{};
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
AT[i][j] = A[j][i];
}
}
return AT;
}
template<typename T, int n>
auto norm(const tensor<T, n> &A)
{
T r = {};
for (int i = 0; i < n; i++)
{
r = r + A[i] * A[i];
}
return pow(r, 0.5);
}
template<typename gradient_type>
struct dual
{
double value;
gradient_type gradient;
};
template<typename gradient_type>
auto operator+(dual<gradient_type> a, double b)
{
return dual<gradient_type>{a.value + b, a.gradient};
}
template<typename gradient_type>
auto operator+(double a, dual<gradient_type> b)
{
return dual<gradient_type>{a + b.value, b.gradient};
}
template<typename gradient_type>
auto operator+(dual<gradient_type> a, dual<gradient_type> b)
{
return dual<gradient_type>{a.value + b.value, a.gradient + b.gradient};
}
template<typename gradient_type>
auto operator*(dual<gradient_type> a, double b)
{
return dual<gradient_type>{a.value * b, a.gradient * b};
}
template<typename gradient_type>
auto operator*(double a, dual<gradient_type> b)
{
return dual<gradient_type>{a * b.value, a * b.gradient};
}
template<typename gradient_type>
auto operator*(dual<gradient_type> a, dual<gradient_type> b)
{
return dual<gradient_type>{a.value * b.value,
b.value * a.gradient + a.value * b.gradient};
}
template<typename gradient_type>
auto cos(dual<gradient_type> a)
{
return dual<gradient_type>{cos(a.value), -a.gradient * sin(a.value)};
}
template<typename gradient_type>
auto exp(dual<gradient_type> a)
{
return dual<gradient_type>{exp(a.value), exp(a.value)};
}
template<typename gradient_type>
auto log(dual<gradient_type> a)
{
return dual<gradient_type>{log(a.value), a.gradient / a.value};
}
template<typename gradient_type>
auto pow(dual<gradient_type> a, dual<gradient_type> b)
{
double value = pow(a.value, b.value);
return dual<gradient_type>{value,
value
* (a.gradient * (b.value / a.value)
+ b.gradient * log(a.value))};
}
template<typename gradient_type>
auto pow(double a, dual<gradient_type> b)
{
double value = pow(a, b.value);
return dual<gradient_type>{value, value * b.gradient * log(a)};
}
template<typename gradient_type>
auto pow(dual<gradient_type> a, double b)
{
double value = pow(a.value, b);
return dual<gradient_type>{value, value * a.gradient * b / a.value};
}
template<typename T, int... n>
auto &operator<<(std::ostream &out, dual<T> A)
{
out << '(' << A.value << ' ' << A.gradient << ')';
return out;
}
template<typename T, int... n>
auto &operator<<(std::ostream &out, tensor<T, n...> A)
{
out << '{' << A[0];
for (int i = 1; i < tensor<T, n...>::shape[0]; i++)
{
out << ", " << A[i];
}
out << '}';
return out;
}
auto derivative_wrt(double a)
{
return dual<double>{a, 1};
}
template<typename T, int m>
auto derivative_wrt(tensor<T, m> A)
{
tensor<dual<tensor<double, m>>, m> A_dual{};
for (int i = 0; i < m; i++)
{
A_dual[i].value = A[i];
A_dual[i].gradient[i] = 1.0;
}
return A_dual;
}
template<typename T, int m, int n>
auto derivative_wrt(tensor<T, m, n> A)
{
tensor<dual<tensor<double, m, n>>, m, n> A_dual{};
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
A_dual[i][j].value = A[i][j];
A_dual[i][j].gradient[i][j] = 1.0;
}
}
return A_dual;
}
template<typename grad_type, int nrows, int ncols>
auto directional_derivative(tensor<dual<grad_type>, nrows, ncols> A, grad_type n)
{
tensor<double, nrows, ncols> dA_dn{};
for (int i = 0; i < nrows; i++)
{
for (int j = 0; j < ncols; j++)
{
dA_dn[i][j] = inner(A[i][j].gradient, n);
}
}
return dA_dn;
}
@@ -0,0 +1,69 @@
#include "mfem.hpp"
#include "tensor.hpp"
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
using namespace mfem;
TEST_CASE("Tensor tests", "[tensor]")
{
SECTION("norm")
{
tensor<double, 5> a = {{1.0, 2.0, 3.0, 4.0, 5.0}};
REQUIRE(norm(a) - sqrt(55) == Approx(0.0));
}
}
TEST_CASE("Dual number tensor tests", "[DualNumber]")
{
auto eps = std::numeric_limits<double>::epsilon();
double x = 0.5;
SECTION("cos")
{
auto xd = cos(derivative_wrt(x));
REQUIRE(abs(-sin(x) - xd.gradient) == Approx(0.0));
}
SECTION("exp")
{
auto xd = exp(derivative_wrt(x));
REQUIRE(abs(exp(x) - xd.gradient) == Approx(0.0));
}
SECTION("log")
{
auto xd = log(derivative_wrt(x));
REQUIRE(abs(1.0 / x - xd.gradient) == Approx(0.0));
}
SECTION("pow")
{
// f(x) = x^3/2
auto xd = pow(derivative_wrt(x), 1.5);
REQUIRE(abs(1.5 * pow(x, 0.5) - xd.gradient) == Approx(0.0));
}
SECTION("mixed operations")
{
auto xd = derivative_wrt(x);
auto r = cos(xd) * cos(xd);
REQUIRE(abs(-2.0 * sin(x) * cos(x) - r.gradient) == Approx(0.0));
r = exp(xd) * cos(xd);
REQUIRE(abs(exp(x) * (cos(x) - sin(x)) - r.gradient) < eps);
r = log(xd) * cos(xd);
REQUIRE(abs((cos(x) / x - log(x) * sin(x)) - r.gradient) < eps);
r = exp(xd) * pow(xd, 1.5);
REQUIRE(abs((exp(x) * (pow(x, 1.5) + 1.5 * pow(x, 0.5))) - r.gradient)
< eps);
tensor<double, 2> vx = {{0.5, 0.25}};
tensor<double, 2> vre = {{0.894427190999916, 0.4472135954999579}};
auto vr = norm(derivative_wrt(vx));
REQUIRE(norm(vr.gradient - vre) < eps);
}
}