981 lines
28 KiB
C++
981 lines
28 KiB
C++
// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
|
|
#ifdef _WIN32
|
|
#define _USE_MATH_DEFINES
|
|
#include <cmath>
|
|
#endif
|
|
|
|
#include "unit_tests.hpp"
|
|
#include "mfem.hpp"
|
|
|
|
using namespace mfem;
|
|
|
|
namespace pa_kernels
|
|
{
|
|
|
|
enum class FECType
|
|
{
|
|
H1,
|
|
L2_VALUE,
|
|
L2_INTEGRAL
|
|
};
|
|
|
|
std::unique_ptr<FiniteElementCollection> create_fec(
|
|
FECType fec_type, int order, int dim)
|
|
{
|
|
using Ptr = std::unique_ptr<FiniteElementCollection>;
|
|
switch (fec_type)
|
|
{
|
|
case FECType::H1:
|
|
return Ptr(new H1_FECollection(order, dim));
|
|
case FECType::L2_VALUE:
|
|
return Ptr(new L2_FECollection(order, dim, BasisType::GaussLegendre,
|
|
FiniteElement::VALUE));
|
|
case FECType::L2_INTEGRAL:
|
|
return Ptr(new L2_FECollection(order, dim, BasisType::GaussLegendre,
|
|
FiniteElement::INTEGRAL));
|
|
default:
|
|
MFEM_ABORT("Invalid FECType");
|
|
}
|
|
}
|
|
|
|
Mesh MakeCartesianNonaligned(const int dim, const int ne)
|
|
{
|
|
Mesh mesh;
|
|
if (dim == 2)
|
|
{
|
|
mesh = Mesh::MakeCartesian2D(ne, ne, Element::QUADRILATERAL, 1, 1.0, 1.0);
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::MakeCartesian3D(ne, ne, ne, Element::HEXAHEDRON, 1.0, 1.0, 1.0);
|
|
}
|
|
|
|
// Remap vertices so that the mesh is not aligned with axes.
|
|
for (int i=0; i<mesh.GetNV(); ++i)
|
|
{
|
|
real_t *vcrd = mesh.GetVertex(i);
|
|
vcrd[1] += 0.2 * vcrd[0];
|
|
if (dim == 3) { vcrd[2] += 0.3 * vcrd[0]; }
|
|
}
|
|
|
|
return mesh;
|
|
}
|
|
|
|
real_t zero_field(const Vector &x)
|
|
{
|
|
MFEM_CONTRACT_VAR(x);
|
|
return 0.0;
|
|
}
|
|
|
|
void solenoidal_field2d(const Vector &x, Vector &u)
|
|
{
|
|
u(0) = x(1);
|
|
u(1) = -x(0);
|
|
}
|
|
|
|
void non_solenoidal_field2d(const Vector &x, Vector &u)
|
|
{
|
|
u(0) = x(0) * x(1);
|
|
u(1) = -x(0) + x(1);
|
|
}
|
|
|
|
real_t div_non_solenoidal_field2d(const Vector &x)
|
|
{
|
|
return 1.0 + x(1);
|
|
}
|
|
|
|
void solenoidal_field3d(const Vector &x, Vector &u)
|
|
{
|
|
u(0) = -x(0)*x(0);
|
|
u(1) = x(0)*x(1);
|
|
u(2) = x(0)*x(2);
|
|
}
|
|
|
|
void non_solenoidal_field3d(const Vector &x, Vector &u)
|
|
{
|
|
u(0) = x(0)*x(0);
|
|
u(1) = x(1)*x(1);
|
|
u(2) = x(2)*x(2);
|
|
}
|
|
|
|
real_t div_non_solenoidal_field3d(const Vector &x)
|
|
{
|
|
return 2*(x(0) + x(1) + x(2));
|
|
}
|
|
|
|
void pa_divergence_testnd(int dim,
|
|
void (*f1)(const Vector &, Vector &),
|
|
real_t (*divf1)(const Vector &))
|
|
{
|
|
Mesh mesh = MakeCartesianNonaligned(dim, 2);
|
|
int order = 4;
|
|
|
|
// Vector valued
|
|
H1_FECollection fec1(order, dim);
|
|
FiniteElementSpace fes1(&mesh, &fec1, dim);
|
|
|
|
// Scalar
|
|
H1_FECollection fec2(order, dim);
|
|
FiniteElementSpace fes2(&mesh, &fec2);
|
|
|
|
GridFunction field(&fes1), field2(&fes2);
|
|
|
|
MixedBilinearForm dform(&fes1, &fes2);
|
|
dform.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
dform.AddDomainIntegrator(new VectorDivergenceIntegrator);
|
|
dform.Assemble();
|
|
|
|
// Project u = f1
|
|
VectorFunctionCoefficient fcoeff1(dim, f1);
|
|
field.ProjectCoefficient(fcoeff1);
|
|
|
|
// Check if div(u) = divf1
|
|
dform.Mult(field, field2);
|
|
FunctionCoefficient fcoeff2(divf1);
|
|
LinearForm lf(&fes2);
|
|
lf.AddDomainIntegrator(new DomainLFIntegrator(fcoeff2));
|
|
lf.Assemble();
|
|
field2 -= lf;
|
|
|
|
REQUIRE(field2.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
template <typename INTEGRATOR>
|
|
void pa_mixed_transpose_test(FiniteElementSpace &fes1,
|
|
FiniteElementSpace &fes2)
|
|
{
|
|
MixedBilinearForm bform_pa(&fes1, &fes2);
|
|
bform_pa.AddDomainIntegrator(new TransposeIntegrator(new INTEGRATOR));
|
|
bform_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
bform_pa.Assemble();
|
|
|
|
MixedBilinearForm bform_fa(&fes1, &fes2);
|
|
bform_fa.AddDomainIntegrator(new TransposeIntegrator(new INTEGRATOR));
|
|
bform_fa.Assemble();
|
|
bform_fa.Finalize();
|
|
|
|
GridFunction x(&fes1), y_pa(&fes2), y_fa(&fes2);
|
|
x.Randomize(1);
|
|
|
|
bform_pa.Mult(x, y_pa);
|
|
bform_fa.Mult(x, y_fa);
|
|
|
|
y_pa -= y_fa;
|
|
REQUIRE(y_pa.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
void pa_divergence_transpose_testnd(int dim)
|
|
{
|
|
Mesh mesh = MakeCartesianNonaligned(dim, 2);
|
|
int order = 4;
|
|
|
|
// Scalar
|
|
H1_FECollection fec1(order, dim);
|
|
FiniteElementSpace fes1(&mesh, &fec1);
|
|
|
|
// Vector valued
|
|
H1_FECollection fec2(order, dim);
|
|
FiniteElementSpace fes2(&mesh, &fec2, dim);
|
|
|
|
pa_mixed_transpose_test<VectorDivergenceIntegrator>(fes1, fes2);
|
|
}
|
|
|
|
TEST_CASE("PA VectorDivergence", "[PartialAssembly], [GPU]")
|
|
{
|
|
SECTION("2D")
|
|
{
|
|
// Check if div([y, -x]) == 0
|
|
pa_divergence_testnd(2, solenoidal_field2d, zero_field);
|
|
// Check if div([x*y, -x+y]) == 1 + y
|
|
pa_divergence_testnd(2, non_solenoidal_field2d, div_non_solenoidal_field2d);
|
|
// Check transpose
|
|
pa_divergence_transpose_testnd(2);
|
|
}
|
|
|
|
SECTION("3D")
|
|
{
|
|
// Check if div([-x^2, xy, xz]) == 0
|
|
pa_divergence_testnd(3, solenoidal_field3d, zero_field);
|
|
// Check if div([x^2, y^2, z^2]) == 2(x + y + z)
|
|
pa_divergence_testnd(3, non_solenoidal_field3d, div_non_solenoidal_field3d);
|
|
// Check transpose
|
|
pa_divergence_transpose_testnd(3);
|
|
}
|
|
}
|
|
|
|
real_t f1(const Vector &x)
|
|
{
|
|
real_t r = pow(x(0),2);
|
|
if (x.Size() >= 2) { r += pow(x(1), 3); }
|
|
if (x.Size() >= 3) { r += pow(x(2), 4); }
|
|
return r;
|
|
}
|
|
|
|
void gradf1(const Vector &x, Vector &u)
|
|
{
|
|
u(0) = 2*x(0);
|
|
if (x.Size() >= 2) { u(1) = 3*pow(x(1), 2); }
|
|
if (x.Size() >= 3) { u(2) = 4*pow(x(2), 3); }
|
|
}
|
|
|
|
void pa_gradient_testnd(int dim, FECType fec_type,
|
|
real_t (*f1)(const Vector &),
|
|
void (*gradf1)(const Vector &, Vector &))
|
|
{
|
|
Mesh mesh = MakeCartesianNonaligned(dim, 2);
|
|
int order = 4;
|
|
|
|
// Scalar
|
|
H1_FECollection fec1(order, dim);
|
|
FiniteElementSpace fes1(&mesh, &fec1);
|
|
GridFunction field(&fes1);
|
|
|
|
// Vector valued
|
|
auto fec2 = create_fec(fec_type, order, dim);
|
|
FiniteElementSpace fes2(&mesh, fec2.get(), dim);
|
|
GridFunction field2(&fes2);
|
|
|
|
MixedBilinearForm gform(&fes1, &fes2);
|
|
gform.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
gform.AddDomainIntegrator(new GradientIntegrator);
|
|
gform.Assemble();
|
|
|
|
// Project u = f1
|
|
FunctionCoefficient fcoeff1(f1);
|
|
field.ProjectCoefficient(fcoeff1);
|
|
|
|
// Check if grad(u) = gradf1
|
|
gform.Mult(field, field2);
|
|
VectorFunctionCoefficient fcoeff2(dim, gradf1);
|
|
LinearForm lf(&fes2);
|
|
lf.AddDomainIntegrator(new VectorDomainLFIntegrator(fcoeff2));
|
|
lf.Assemble();
|
|
field2 -= lf;
|
|
|
|
REQUIRE(field2.Norml2() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
void pa_gradient_transpose_testnd(int dim, FECType fec_type)
|
|
{
|
|
Mesh mesh = MakeCartesianNonaligned(dim, 2);
|
|
int order = 4;
|
|
|
|
// Scalar
|
|
H1_FECollection fec2(order, dim);
|
|
FiniteElementSpace fes2(&mesh, &fec2);
|
|
GridFunction y_pa(&fes2), y_fa(&fes2);
|
|
|
|
// Vector valued
|
|
auto fec1 = create_fec(fec_type, order, dim);
|
|
FiniteElementSpace fes1(&mesh, fec1.get(), dim);
|
|
|
|
pa_mixed_transpose_test<GradientIntegrator>(fes1, fes2);
|
|
}
|
|
|
|
TEST_CASE("PA Gradient", "[PartialAssembly], [GPU]")
|
|
{
|
|
auto fec_type = GENERATE(FECType::H1, FECType::L2_VALUE,
|
|
FECType::L2_INTEGRAL);
|
|
|
|
SECTION("2D")
|
|
{
|
|
// Check if grad(x^2 + y^3) == [2x, 3y^2]
|
|
pa_gradient_testnd(2, fec_type, f1, gradf1);
|
|
// Check transpose
|
|
pa_gradient_transpose_testnd(2, fec_type);
|
|
}
|
|
|
|
SECTION("3D")
|
|
{
|
|
// Check if grad(x^2 + y^3 + z^4) == [2x, 3y^2, 4z^3]
|
|
pa_gradient_testnd(3, fec_type, f1, gradf1);
|
|
// Check transpose
|
|
pa_gradient_transpose_testnd(3, fec_type);
|
|
}
|
|
}
|
|
|
|
real_t test_nl_convection_nd(int dim)
|
|
{
|
|
Mesh mesh = MakeCartesianNonaligned(dim, 2);
|
|
int order = 2;
|
|
H1_FECollection fec(order, dim);
|
|
FiniteElementSpace fes(&mesh, &fec, dim);
|
|
|
|
GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
|
|
x.Randomize(3);
|
|
|
|
NonlinearForm nlf_fa(&fes);
|
|
nlf_fa.AddDomainIntegrator(new VectorConvectionNLFIntegrator);
|
|
nlf_fa.Mult(x, y_fa);
|
|
|
|
NonlinearForm nlf_pa(&fes);
|
|
nlf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
nlf_pa.AddDomainIntegrator(new VectorConvectionNLFIntegrator);
|
|
nlf_pa.Setup();
|
|
nlf_pa.Mult(x, y_pa);
|
|
|
|
y_fa -= y_pa;
|
|
real_t difference = y_fa.Norml2();
|
|
|
|
|
|
return difference;
|
|
}
|
|
|
|
TEST_CASE("Nonlinear Convection", "[PartialAssembly], [NonlinearPA], [GPU]")
|
|
{
|
|
SECTION("2D")
|
|
{
|
|
REQUIRE(test_nl_convection_nd(2) == MFEM_Approx(0.0));
|
|
}
|
|
|
|
SECTION("3D")
|
|
{
|
|
REQUIRE(test_nl_convection_nd(3) == MFEM_Approx(0.0));
|
|
}
|
|
}
|
|
|
|
template <typename INTEGRATOR>
|
|
real_t test_pa_vector_integrator(int dim, int sdim)
|
|
{
|
|
const bool all = launch_all_non_regression_tests;
|
|
const auto NE = all ? GENERATE(1, 2, 3) : 2;
|
|
const auto p = all ? GENERATE(1, 2, 3): 2;
|
|
CAPTURE(p, NE);
|
|
|
|
Mesh mesh = MakeCartesianNonaligned(dim, NE);
|
|
mesh.SetCurvature(p, false, sdim);
|
|
|
|
H1_FECollection fec(p, dim);
|
|
FiniteElementSpace fes(&mesh, &fec, sdim);
|
|
|
|
GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
|
|
x.Randomize(1);
|
|
|
|
ConstantCoefficient const_coeff(M_PI_2);
|
|
FunctionCoefficient funct_coeff([](const Vector &x) { return M_1_PI + x[0]*x[0]; });
|
|
|
|
Vector val(dim); val = 1.0;
|
|
VectorConstantCoefficient v_const_coeff(val);
|
|
VectorFunctionCoefficient v_funct_coeff(dim, [&](const Vector &x, Vector &v)
|
|
{
|
|
v(0) = M_LN2 * x(0);
|
|
if (dim > 1) { v(1) = M_E * x(1); }
|
|
if (dim > 2) { v(2) = M_PI * x(2); }
|
|
});
|
|
|
|
MatrixFunctionCoefficient m_funct_coeff(dim, [&](const Vector &x,
|
|
DenseMatrix &f)
|
|
{
|
|
f = 0.0;
|
|
if (dim == 1)
|
|
{
|
|
f(0,0) = 1.1 + sin(M_PI * x[0]); // 1,1
|
|
}
|
|
else if (dim == 2)
|
|
{
|
|
f(0,0) = 1.1 + sin(M_PI * x[1]); // 1,1
|
|
f(1,0) = cos(1.3 * M_PI * x[1]); // 2,1
|
|
f(0,1) = cos(2.5 * M_PI * x[0]); // 1,2
|
|
f(1,1) = 1.1 + sin(4.9 * M_PI * x[0]); // 2,2
|
|
}
|
|
else if (dim == 3)
|
|
{
|
|
f(0,0) = 1.1 + sin(M_PI * x[1]); // 1,1
|
|
f(0,1) = cos(2.5 * M_PI * x[0]); // 1,2
|
|
f(0,2) = sin(4.9 * M_PI * x[2]); // 1,3
|
|
f(1,0) = cos(M_PI * x[0]); // 2,1
|
|
f(1,1) = 1.1 + sin(6.1 * M_PI * x[1]); // 2,2
|
|
f(1,2) = cos(6.1 * M_PI * x[2]); // 2,3
|
|
f(2,0) = sin(1.5 * M_PI * x[1]); // 3,1
|
|
f(2,1) = cos(2.9 * M_PI * x[0]); // 3,2
|
|
f(2,2) = 1.1 + sin(6.1 * M_PI * x[2]); // 3,3
|
|
}
|
|
});
|
|
REQUIRE((sdim > dim || m_funct_coeff.GetVDim() == fes.GetVDim()));
|
|
|
|
BilinearForm blf_fa(&fes);
|
|
blf_fa.SetAssemblyLevel(AssemblyLevel::LEGACY);
|
|
// scalar coefficients
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR);
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR(const_coeff));
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR(funct_coeff));
|
|
if (sdim == dim)
|
|
{
|
|
// vector coefficients
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR(v_const_coeff));
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR(v_funct_coeff));
|
|
// matrix coefficients
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR(m_funct_coeff));
|
|
}
|
|
blf_fa.Assemble();
|
|
blf_fa.Finalize();
|
|
blf_fa.Mult(x, y_fa);
|
|
|
|
BilinearForm blf_pa(&fes);
|
|
blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
// scalar coefficients
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR);
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR(const_coeff));
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR(funct_coeff));
|
|
if (sdim == dim)
|
|
{
|
|
// vector coefficients
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR(v_const_coeff));
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR(v_funct_coeff));
|
|
// matrix coefficients
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR(m_funct_coeff));
|
|
}
|
|
blf_pa.Assemble();
|
|
blf_pa.Mult(x, y_pa);
|
|
|
|
y_fa -= y_pa;
|
|
|
|
return y_fa.Norml2();
|
|
}
|
|
|
|
TEST_CASE("PA Vector Mass",
|
|
"[PartialAssembly][VectorPA][VectorMassPA][GPU]")
|
|
{
|
|
const auto DIM = GENERATE(2, 3);
|
|
CAPTURE(DIM);
|
|
REQUIRE(test_pa_vector_integrator<VectorMassIntegrator>(DIM, DIM)
|
|
== MFEM_Approx(0.0));
|
|
}
|
|
|
|
TEST_CASE("PA Vector Diffusion",
|
|
"[PartialAssembly][VectorPA][VectorDiffusionPA][GPU]")
|
|
{
|
|
const auto DIM = GENERATE(2, 3);
|
|
CAPTURE(DIM);
|
|
REQUIRE(test_pa_vector_integrator<VectorDiffusionIntegrator>(DIM, DIM)
|
|
== MFEM_Approx(0.0));
|
|
}
|
|
|
|
TEST_CASE("PA Vector Diffusion 2D/3D",
|
|
"[PartialAssembly][VectorPA][VectorDiffusionPA][CUDA]")
|
|
{
|
|
const int DIM = 2, SDIM = 3;
|
|
CAPTURE(DIM, SDIM);
|
|
REQUIRE(test_pa_vector_integrator<VectorDiffusionIntegrator>(DIM, SDIM)
|
|
== MFEM_Approx(0.0));
|
|
}
|
|
|
|
void velocity_function(const Vector &x, Vector &v)
|
|
{
|
|
int dim = x.Size();
|
|
switch (dim)
|
|
{
|
|
case 1: v(0) = 1.0; break;
|
|
case 2: v(0) = x(1); v(1) = -x(0); break;
|
|
case 3: v(0) = x(1); v(1) = -x(0); v(2) = x(0); break;
|
|
}
|
|
}
|
|
|
|
void AddConvectionIntegrators(BilinearForm &k, Coefficient &rho,
|
|
VectorCoefficient &velocity, bool dg)
|
|
{
|
|
k.AddDomainIntegrator(new ConvectionIntegrator(velocity, -1.0));
|
|
|
|
if (dg)
|
|
{
|
|
k.AddInteriorFaceIntegrator(
|
|
new TransposeIntegrator(new DGTraceIntegrator(rho, velocity, 1.0, -0.5)));
|
|
k.AddBdrFaceIntegrator(
|
|
new TransposeIntegrator(new DGTraceIntegrator(rho, velocity, 1.0, -0.5)));
|
|
}
|
|
}
|
|
|
|
void test_pa_convection(const std::string &meshname, int order, int prob,
|
|
int refinement)
|
|
{
|
|
INFO("mesh=" << meshname << ", order=" << order << ", prob=" << prob
|
|
<< ", refinement=" << refinement );
|
|
Mesh mesh(meshname.c_str(), 1, 1);
|
|
mesh.EnsureNodes();
|
|
mesh.SetCurvature(mesh.GetNodalFESpace()->GetElementOrder(0));
|
|
for (int r = 0; r < refinement; r++)
|
|
{
|
|
mesh.RandomRefinement(0.6,false,1,4);
|
|
}
|
|
int dim = mesh.Dimension();
|
|
|
|
std::unique_ptr<FiniteElementCollection> fec;
|
|
if (prob)
|
|
{
|
|
auto basis = prob==3 ? BasisType::Positive : BasisType::GaussLobatto;
|
|
fec.reset(new L2_FECollection(order, dim, basis));
|
|
}
|
|
else
|
|
{
|
|
fec.reset(new H1_FECollection(order, dim));
|
|
}
|
|
FiniteElementSpace fespace(&mesh, fec.get());
|
|
|
|
L2_FECollection vel_fec(order, dim, BasisType::GaussLobatto);
|
|
FiniteElementSpace vel_fespace(&mesh, &vel_fec, dim);
|
|
GridFunction vel_gf(&vel_fespace);
|
|
GridFunction rho_gf(&fespace);
|
|
|
|
BilinearForm k_pa(&fespace);
|
|
BilinearForm k_fa(&fespace);
|
|
|
|
std::unique_ptr<VectorCoefficient> vel_coeff;
|
|
std::unique_ptr<Coefficient> rho;
|
|
|
|
// prob: 0: CG, 1: DG continuous coeff, 2: DG discontinuous coeff
|
|
if (prob >= 2)
|
|
{
|
|
vel_gf.Randomize(1);
|
|
vel_coeff.reset(new VectorGridFunctionCoefficient(&vel_gf));
|
|
rho_gf.Randomize(1);
|
|
rho.reset(new GridFunctionCoefficient(&rho_gf));
|
|
}
|
|
else
|
|
{
|
|
vel_coeff.reset(new VectorFunctionCoefficient(dim, velocity_function));
|
|
rho.reset(new ConstantCoefficient(1.0));
|
|
}
|
|
|
|
|
|
AddConvectionIntegrators(k_fa, *rho, *vel_coeff, prob > 0);
|
|
AddConvectionIntegrators(k_pa, *rho, *vel_coeff, prob > 0);
|
|
|
|
k_fa.Assemble();
|
|
k_fa.Finalize();
|
|
|
|
k_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
k_pa.Assemble();
|
|
|
|
GridFunction x(&fespace), y_fa(&fespace), y_pa(&fespace);
|
|
|
|
x.Randomize(1);
|
|
|
|
// Testing Mult
|
|
k_fa.Mult(x,y_fa);
|
|
k_pa.Mult(x,y_pa);
|
|
|
|
y_pa -= y_fa;
|
|
|
|
REQUIRE(y_pa.Norml2() < 1.e-12);
|
|
|
|
// Testing MultTranspose
|
|
k_fa.MultTranspose(x,y_fa);
|
|
k_pa.MultTranspose(x,y_pa);
|
|
|
|
y_pa -= y_fa;
|
|
|
|
REQUIRE(y_pa.Norml2() < 1.e-12);
|
|
}
|
|
|
|
// Basic unit tests for convection
|
|
TEST_CASE("PA Convection", "[PartialAssembly], [GPU]")
|
|
{
|
|
// prob:
|
|
// - 0: CG,
|
|
// - 1: DG continuous coeff,
|
|
// - 2: DG discontinuous coeff,
|
|
// - 3: DG Bernstein discontinuous coeff.
|
|
auto prob = GENERATE(0, 1, 2, 3);
|
|
auto order = GENERATE(2);
|
|
// refinement > 0 => Non-conforming mesh
|
|
auto refinement = GENERATE(0, 1);
|
|
|
|
SECTION("2D")
|
|
{
|
|
test_pa_convection("../../data/periodic-square.mesh", order, prob,
|
|
refinement);
|
|
}
|
|
|
|
SECTION("3D")
|
|
{
|
|
test_pa_convection("../../data/periodic-cube.mesh", order, prob,
|
|
refinement);
|
|
}
|
|
} // test case
|
|
|
|
// Advanced unit tests for convection
|
|
TEST_CASE("PA Convection advanced", "[PartialAssembly], [MFEMData], [GPU]")
|
|
{
|
|
if (launch_all_non_regression_tests)
|
|
{
|
|
// prob:
|
|
// - 0: CG,
|
|
// - 1: DG continuous coeff,
|
|
// - 2: DG discontinuous coeff,
|
|
// - 3: DG Bernstein discontinuous coeff.
|
|
auto prob = GENERATE(0, 1, 2, 3);
|
|
auto order = GENERATE(2);
|
|
// refinement > 0 => Non-conforming mesh
|
|
auto refinement = GENERATE(0,1);
|
|
|
|
SECTION("2D")
|
|
{
|
|
test_pa_convection("../../data/periodic-hexagon.mesh", order, prob,
|
|
refinement);
|
|
test_pa_convection("../../data/star-q3.mesh", order, prob,
|
|
refinement);
|
|
test_pa_convection(mfem_data_dir+"/gmsh/v22/unstructured_quad.v22.msh",
|
|
order, prob, refinement);
|
|
}
|
|
|
|
SECTION("3D")
|
|
{
|
|
test_pa_convection("../../data/fichera-q3.mesh", order, prob,
|
|
refinement);
|
|
test_pa_convection(mfem_data_dir+"/gmsh/v22/unstructured_hex.v22.msh",
|
|
order, prob, refinement);
|
|
}
|
|
}
|
|
} // PA Convection test case
|
|
|
|
template <typename INTEGRATOR>
|
|
static void test_pa_integrator()
|
|
{
|
|
const bool all_tests = launch_all_non_regression_tests;
|
|
|
|
auto fname = GENERATE("../../data/star.mesh", "../../data/star-q3.mesh",
|
|
"../../data/fichera.mesh", "../../data/fichera-q3.mesh");
|
|
auto map_type = GENERATE(FiniteElement::VALUE, FiniteElement::INTEGRAL);
|
|
|
|
auto order = !all_tests ? 2 : GENERATE(1, 2, 3);
|
|
auto q_order_inc = !all_tests ? 0 : GENERATE(0, 1, 3);
|
|
|
|
Mesh mesh(fname);
|
|
int dim = mesh.Dimension();
|
|
L2_FECollection fec(order, dim, BasisType::GaussLobatto, map_type);
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
|
|
const int q_order = 2*order + q_order_inc;
|
|
// Don't use a special integration rule if q_order_inc == 0
|
|
const bool use_ir = q_order_inc > 0;
|
|
const IntegrationRule *ir =
|
|
use_ir ? &IntRules.Get(mesh.GetTypicalElementGeometry(), q_order) : nullptr;
|
|
|
|
GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
|
|
x.Randomize(1);
|
|
|
|
FunctionCoefficient coeff(f1);
|
|
|
|
BilinearForm blf_fa(&fes);
|
|
blf_fa.AddDomainIntegrator(new INTEGRATOR(coeff,ir));
|
|
blf_fa.Assemble();
|
|
blf_fa.Finalize();
|
|
blf_fa.Mult(x, y_fa);
|
|
|
|
BilinearForm blf_pa(&fes);
|
|
blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
blf_pa.AddDomainIntegrator(new INTEGRATOR(coeff,ir));
|
|
blf_pa.Assemble();
|
|
blf_pa.Mult(x, y_pa);
|
|
|
|
y_fa -= y_pa;
|
|
|
|
REQUIRE(y_fa.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
TEST_CASE("PA Mass", "[PartialAssembly], [GPU]")
|
|
{
|
|
test_pa_integrator<MassIntegrator>();
|
|
} // PA Mass test case
|
|
|
|
TEST_CASE("PA Diffusion", "[PartialAssembly], [GPU]")
|
|
{
|
|
test_pa_integrator<DiffusionIntegrator>();
|
|
} // PA Diffusion test case
|
|
|
|
TEST_CASE("PA Markers", "[PartialAssembly], [GPU]")
|
|
{
|
|
const bool all_tests = launch_all_non_regression_tests;
|
|
auto fname = GENERATE("../../data/star.mesh", "../../data/star-q3.mesh",
|
|
"../../data/fichera.mesh", "../../data/fichera-q3.mesh");
|
|
auto order = !all_tests ? 2 : GENERATE(1, 2, 3);
|
|
auto dg = GENERATE(false, true);
|
|
CAPTURE(fname, order, dg);
|
|
|
|
Mesh mesh(fname);
|
|
int dim = mesh.Dimension();
|
|
std::unique_ptr<FiniteElementCollection> fec;
|
|
if (dg) { fec.reset(new L2_FECollection(order, dim, BasisType::GaussLobatto)); }
|
|
else { fec.reset(new H1_FECollection(order, dim)); }
|
|
FiniteElementSpace fes(&mesh, fec.get());
|
|
|
|
for (int i = 0; i < mesh.GetNE(); ++i) { mesh.SetAttribute(i, 1 + i%2); }
|
|
for (int i = 0; i < mesh.GetNBE(); ++i) { mesh.SetBdrAttribute(i, 1 + i%2); }
|
|
mesh.SetAttributes();
|
|
|
|
Array<int> marker(2);
|
|
marker[0] = 0;
|
|
marker[1] = 1;
|
|
|
|
Vector vel_vec(dim);
|
|
vel_vec.Randomize(1);
|
|
VectorConstantCoefficient vel(vel_vec);
|
|
|
|
GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
|
|
x.Randomize(1);
|
|
|
|
BilinearForm blf_fa(&fes);
|
|
blf_fa.AddDomainIntegrator(new MassIntegrator, marker);
|
|
if (dg) { blf_fa.AddBdrFaceIntegrator(new DGTraceIntegrator(vel, 1.0)); }
|
|
else { blf_fa.AddBoundaryIntegrator(new MassIntegrator, marker); }
|
|
blf_fa.Assemble();
|
|
blf_fa.Finalize();
|
|
|
|
BilinearForm blf_pa(&fes);
|
|
blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
blf_pa.AddDomainIntegrator(new MassIntegrator, marker);
|
|
if (dg) { blf_pa.AddBdrFaceIntegrator(new DGTraceIntegrator(vel, 1.0)); }
|
|
else { blf_pa.AddBoundaryIntegrator(new MassIntegrator, marker); }
|
|
blf_pa.Assemble();
|
|
|
|
blf_fa.Mult(x, y_fa);
|
|
blf_pa.Mult(x, y_pa);
|
|
y_fa -= y_pa;
|
|
REQUIRE(y_fa.Normlinf() == MFEM_Approx(0.0));
|
|
|
|
blf_fa.MultTranspose(x, y_fa);
|
|
blf_pa.MultTranspose(x, y_pa);
|
|
y_fa -= y_pa;
|
|
REQUIRE(y_fa.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
TEST_CASE("PA Boundary Mass", "[PartialAssembly], [GPU]")
|
|
{
|
|
const bool all_tests = launch_all_non_regression_tests;
|
|
|
|
auto fname = GENERATE("../../data/star.mesh", "../../data/star-q3.mesh",
|
|
"../../data/fichera.mesh", "../../data/fichera-q3.mesh");
|
|
auto order = !all_tests ? 2 : GENERATE(1, 2, 3);
|
|
|
|
Mesh mesh(fname);
|
|
int dim = mesh.Dimension();
|
|
RT_FECollection fec(order, dim);
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
|
|
GridFunction x(&fes), y_fa(&fes), y_pa(&fes);
|
|
x.Randomize(1);
|
|
|
|
FunctionCoefficient coeff(f1);
|
|
|
|
BilinearForm blf_fa(&fes);
|
|
blf_fa.AddBoundaryIntegrator(new MassIntegrator(coeff));
|
|
blf_fa.Assemble();
|
|
blf_fa.Finalize();
|
|
blf_fa.Mult(x, y_fa);
|
|
|
|
BilinearForm blf_pa(&fes);
|
|
blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
blf_pa.AddBoundaryIntegrator(new MassIntegrator(coeff));
|
|
blf_pa.Assemble();
|
|
blf_pa.Mult(x, y_pa);
|
|
|
|
y_fa -= y_pa;
|
|
|
|
REQUIRE(y_fa.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template <typename T> struct ParTypeHelper { };
|
|
template <> struct ParTypeHelper<FiniteElementSpace>
|
|
{
|
|
using GF_t = GridFunction;
|
|
using BLF_t = BilinearForm;
|
|
};
|
|
#ifdef MFEM_USE_MPI
|
|
template <> struct ParTypeHelper<ParFiniteElementSpace>
|
|
{
|
|
using GF_t = ParGridFunction;
|
|
using BLF_t = ParBilinearForm;
|
|
};
|
|
#endif
|
|
}
|
|
|
|
template <typename CoeffType>
|
|
std::unique_ptr<CoeffType> MakeCoeff(int);
|
|
|
|
template <>
|
|
std::unique_ptr<ConstantCoefficient> MakeCoeff<ConstantCoefficient>(int)
|
|
{
|
|
return std::make_unique<ConstantCoefficient>(3.14159);
|
|
}
|
|
|
|
template <>
|
|
std::unique_ptr<MatrixConstantCoefficient> MakeCoeff<MatrixConstantCoefficient>
|
|
(int dim)
|
|
{
|
|
DenseMatrix A(dim);
|
|
for (int i = 0; i < dim*dim; ++i)
|
|
{
|
|
A.GetData()[i] = 1.0 / (i + 3.0);
|
|
}
|
|
for (int i = 0; i < dim; ++i)
|
|
{
|
|
A(i,i) += 2.0 + i;
|
|
}
|
|
return std::make_unique<MatrixConstantCoefficient>(A);
|
|
}
|
|
|
|
template <> std::unique_ptr<SymmetricMatrixConstantCoefficient>
|
|
MakeCoeff<SymmetricMatrixConstantCoefficient>(int dim)
|
|
{
|
|
DenseSymmetricMatrix A(dim);
|
|
for (int i = 0; i < A.GetStoredSize(); ++i)
|
|
{
|
|
A.GetData()[i] = 1.0 / (i + 3.0);
|
|
}
|
|
for (int i = 0; i < dim; ++i)
|
|
{
|
|
A(i,i) += 2.0 + i;
|
|
}
|
|
return std::make_unique<SymmetricMatrixConstantCoefficient>(A);
|
|
}
|
|
|
|
template <typename CoeffType = ConstantCoefficient,
|
|
typename FES = FiniteElementSpace>
|
|
void test_dg_diffusion(FES &fes)
|
|
{
|
|
using GF_t = typename ParTypeHelper<FES>::GF_t;
|
|
using BLF_t = typename ParTypeHelper<FES>::BLF_t;
|
|
|
|
GF_t x(&fes), y_fa(&fes), y_pa(&fes);
|
|
x.Randomize(1);
|
|
|
|
const int dim = fes.GetMesh()->Dimension();
|
|
auto coeff = MakeCoeff<CoeffType>(dim);
|
|
|
|
const real_t sigma = -1.0;
|
|
const real_t kappa = 10.0;
|
|
|
|
IntegrationRules irs(0, Quadrature1D::GaussLobatto);
|
|
const IntegrationRule &ir = irs.Get(fes.GetMesh()->GetTypicalFaceGeometry(),
|
|
2*fes.GetMaxElementOrder());
|
|
|
|
BLF_t blf_fa(&fes);
|
|
blf_fa.AddInteriorFaceIntegrator(
|
|
new DGDiffusionIntegrator(*coeff, sigma, kappa));
|
|
blf_fa.AddBdrFaceIntegrator(new DGDiffusionIntegrator(*coeff, sigma, kappa));
|
|
(*blf_fa.GetFBFI())[0]->SetIntegrationRule(ir);
|
|
(*blf_fa.GetBFBFI())[0]->SetIntegrationRule(ir);
|
|
blf_fa.Assemble();
|
|
blf_fa.Finalize();
|
|
OperatorHandle A_fa;
|
|
Array<int> empty;
|
|
blf_fa.FormSystemMatrix(empty, A_fa);
|
|
A_fa->Mult(x, y_fa);
|
|
|
|
BLF_t blf_pa(&fes);
|
|
blf_pa.SetAssemblyLevel(AssemblyLevel::PARTIAL);
|
|
blf_pa.AddInteriorFaceIntegrator(
|
|
new DGDiffusionIntegrator(*coeff, sigma, kappa));
|
|
blf_pa.AddBdrFaceIntegrator(new DGDiffusionIntegrator(*coeff, sigma, kappa));
|
|
(*blf_pa.GetFBFI())[0]->SetIntegrationRule(ir);
|
|
(*blf_pa.GetBFBFI())[0]->SetIntegrationRule(ir);
|
|
blf_pa.Assemble();
|
|
blf_pa.Mult(x, y_pa);
|
|
|
|
y_fa -= y_pa;
|
|
|
|
REQUIRE(y_fa.Normlinf() == MFEM_Approx(0.0));
|
|
}
|
|
|
|
std::vector<std::string> get_dg_test_meshes()
|
|
{
|
|
std::vector<std::string> mesh_filenames =
|
|
{
|
|
"../../data/star.mesh",
|
|
"../../data/star-q3.mesh",
|
|
"../../data/fichera.mesh",
|
|
"../../data/fichera-q3.mesh",
|
|
};
|
|
const bool have_data_dir = mfem_data_dir != "";
|
|
if (have_data_dir)
|
|
{
|
|
mesh_filenames.push_back(mfem_data_dir + "/gmsh/v22/unstructured_quad.v22.msh");
|
|
mesh_filenames.push_back(mfem_data_dir + "/gmsh/v22/unstructured_hex.v22.msh");
|
|
}
|
|
return mesh_filenames;
|
|
}
|
|
|
|
TEST_CASE("PA DG Diffusion", "[PartialAssembly], [GPU]")
|
|
{
|
|
const auto mesh_fname = GENERATE_COPY(from_range(get_dg_test_meshes()));
|
|
const int order = GENERATE(1, 2);
|
|
CAPTURE(order, mesh_fname);
|
|
|
|
Mesh mesh = Mesh::LoadFromFile(mesh_fname.c_str());
|
|
const int dim = mesh.Dimension();
|
|
|
|
DG_FECollection fec(order, dim, BasisType::GaussLobatto);
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
|
|
test_dg_diffusion<ConstantCoefficient>(fes);
|
|
test_dg_diffusion<MatrixConstantCoefficient>(fes);
|
|
test_dg_diffusion<SymmetricMatrixConstantCoefficient>(fes);
|
|
}
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
|
|
TEST_CASE("Parallel PA DG Diffusion", "[PartialAssembly][Parallel][GPU]")
|
|
{
|
|
const auto mesh_fname = GENERATE_COPY(from_range(get_dg_test_meshes()));
|
|
const int order = GENERATE(1, 2);
|
|
CAPTURE(order, mesh_fname);
|
|
|
|
Mesh serial_mesh = Mesh::LoadFromFile(mesh_fname.c_str());
|
|
ParMesh mesh(MPI_COMM_WORLD, serial_mesh);
|
|
serial_mesh.Clear();
|
|
|
|
const int dim = mesh.Dimension();
|
|
|
|
DG_FECollection fec(order, dim, BasisType::GaussLobatto);
|
|
ParFiniteElementSpace fes(&mesh, &fec);
|
|
|
|
test_dg_diffusion<ConstantCoefficient>(fes);
|
|
test_dg_diffusion<MatrixConstantCoefficient>(fes);
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace pa_kernels
|
|
|
|
TEST_CASE("Dispatch Map Specializations")
|
|
{
|
|
// The kernel specializations are registered the first time the associated
|
|
// object is created (in the constructor of a static local variable in the
|
|
// object's constructor). We create a dummy objects here to ensure that the
|
|
// kernels are registered before testing.
|
|
|
|
MassIntegrator{};
|
|
REQUIRE_FALSE(MassIntegrator::ApplyPAKernels::GetDispatchTable().empty());
|
|
REQUIRE_FALSE(MassIntegrator::DiagonalPAKernels::GetDispatchTable().empty());
|
|
|
|
DiffusionIntegrator{};
|
|
REQUIRE_FALSE(
|
|
DiffusionIntegrator::ApplyPAKernels::GetDispatchTable().empty());
|
|
REQUIRE_FALSE(
|
|
DiffusionIntegrator::DiagonalPAKernels::GetDispatchTable().empty());
|
|
|
|
Mesh mesh = Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL);
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fes(&mesh, &fec);
|
|
fes.GetQuadratureInterpolator(IntRules.Get(mesh.GetElementGeometry(0), 1));
|
|
|
|
using QI = QuadratureInterpolator;
|
|
REQUIRE_FALSE(QI::TensorEvalKernels::GetDispatchTable().empty());
|
|
REQUIRE_FALSE(QI::GradKernels::GetDispatchTable().empty());
|
|
REQUIRE_FALSE(QI::DetKernels::GetDispatchTable().empty());
|
|
REQUIRE_FALSE(QI::EvalKernels::GetDispatchTable().empty());
|
|
REQUIRE_FALSE(QI::CollocatedGradKernels::GetDispatchTable().empty());
|
|
}
|