Files
mfem/tests/unit/fem/test_linearform_ext.cpp
T
2026-01-15 11:40:57 -08:00

400 lines
12 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.
#include "mfem.hpp"
#include "unit_tests.hpp"
#include <functional>
#include <ctime>
using namespace mfem;
static double f(const Vector &xvec)
{
const int dim = xvec.Size();
double val = 2*xvec[0];
if (dim >= 2)
{
val += 3*xvec[1]*xvec[0];
}
if (dim >= 3)
{
val += 0.25*xvec[2]*xvec[1];
}
return val;
}
static void fvec_dim(const Vector &xvec, Vector &v)
{
v.SetSize(xvec.Size());
for (int d = 0; d < xvec.Size(); ++d)
{
v[d] = f(xvec) / double(d + 1);
}
}
enum QuadratureType { Lobatto, Legendre, LegendreUnderIntegration };
struct LinearFormExtTest
{
enum { DLFEval, QLFEval, DLFGrad, // scalar domain
VDLFEval, VQLFEval, VDLFGrad, // vector domain
BLFEval, BNLFEval // boundary
};
const double abs_tol = 1e-14, rel_tol = 1e-14;
const char *mesh_filename;
Mesh mesh;
const int dim, vdim, ordering, problem, p, q, SEED = 0x100001b3;
H1_FECollection fec;
FiniteElementSpace vfes;
const Geometry::Type geom_type;
IntegrationRules IntRulesGLL;
const IntegrationRule *irGLL, *ir;
Array<int> elem_marker;
Vector vdim_vec, vdim_dim_vec;
FunctionCoefficient fn_coeff;
VectorFunctionCoefficient dim_fn_coeff;
VectorConstantCoefficient vdim_cst_coeff, vdim_dim_cst_coeff;
QuadratureSpace qspace;
QuadratureFunction q_function, q_vdim_function;
QuadratureFunctionCoefficient qfc;
VectorQuadratureFunctionCoefficient qfvc;
LinearForm lf_dev, lf_std;
LinearFormIntegrator *lfi_dev, *lfi_std;
static int GetQOrder(QuadratureType qtype, int p)
{
switch (qtype)
{
case Lobatto: return 2*p - 1;
case Legendre: return 2*p + 3;
case LegendreUnderIntegration: return 2*p - 1;
}
MFEM_ABORT("Unknown QuadratureType.");
return 0;
}
LinearFormExtTest(const char *mesh_filename,
int vdim, int ordering, QuadratureType qtype, int problem,
int p):
mesh_filename(mesh_filename),
mesh(Mesh::LoadFromFile(mesh_filename)),
dim(mesh.Dimension()), vdim(vdim), ordering(ordering),
problem(problem), p(p), q(GetQOrder(qtype, p)), fec(p, dim),
vfes(&mesh, &fec, vdim, ordering),
geom_type(mesh.GetTypicalElementGeometry()),
IntRulesGLL(0, Quadrature1D::GaussLobatto),
irGLL(&IntRulesGLL.Get(geom_type, q)), ir(&IntRules.Get(geom_type, q)),
elem_marker(), vdim_vec(vdim), vdim_dim_vec(vdim*dim),
fn_coeff(f), dim_fn_coeff(dim, fvec_dim),
vdim_cst_coeff((vdim_vec.Randomize(SEED), vdim_vec)),
vdim_dim_cst_coeff((vdim_dim_vec.Randomize(SEED), vdim_dim_vec)),
qspace(&mesh, q),
q_function(&qspace, 1),
q_vdim_function(&qspace, vdim),
qfc((q_function.Randomize(SEED), q_function)),
qfvc((q_vdim_function.Randomize(SEED), q_vdim_function)),
lf_dev(&vfes), lf_std(&vfes), lfi_dev(nullptr), lfi_std(nullptr)
{
for (int e = 0; e < mesh.GetNE(); e++) { mesh.SetAttribute(e, e%2?1:2); }
mesh.SetAttributes();
MFEM_VERIFY(mesh.attributes.Size() == 2, "mesh attributes size error!");
elem_marker.SetSize(2);
elem_marker[0] = 0;
elem_marker[1] = 1;
if (problem == DLFEval)
{
lfi_dev = new DomainLFIntegrator(fn_coeff);
lfi_std = new DomainLFIntegrator(fn_coeff);
}
else if (problem == QLFEval)
{
lfi_dev = new QuadratureLFIntegrator(qfc,NULL);
lfi_std = new QuadratureLFIntegrator(qfc,NULL);
}
else if (problem == DLFGrad)
{
lfi_dev = new DomainLFGradIntegrator(dim_fn_coeff);
lfi_std = new DomainLFGradIntegrator(dim_fn_coeff);
}
else if (problem == VDLFEval)
{
lfi_dev = new VectorDomainLFIntegrator(vdim_cst_coeff);
lfi_std = new VectorDomainLFIntegrator(vdim_cst_coeff);
}
else if (problem == VQLFEval)
{
lfi_dev = new VectorQuadratureLFIntegrator(qfvc,NULL);
lfi_std = new VectorQuadratureLFIntegrator(qfvc,NULL);
}
else if (problem == VDLFGrad)
{
lfi_dev = new VectorDomainLFGradIntegrator(vdim_dim_cst_coeff);
lfi_std = new VectorDomainLFGradIntegrator(vdim_dim_cst_coeff);
}
else if (problem == BLFEval)
{
lfi_dev = new BoundaryLFIntegrator(fn_coeff);
lfi_std = new BoundaryLFIntegrator(fn_coeff);
}
else if (problem == BNLFEval)
{
lfi_dev = new BoundaryNormalLFIntegrator(dim_fn_coeff);
lfi_std = new BoundaryNormalLFIntegrator(dim_fn_coeff);
}
else { REQUIRE(false); }
if (problem != QLFEval && problem != VQLFEval && problem != BLFEval &&
problem != BNLFEval)
{
lfi_dev->SetIntRule((qtype == Lobatto) ? irGLL : ir);
lfi_std->SetIntRule((qtype == Lobatto) ? irGLL : ir);
}
if (problem != BLFEval && problem != BNLFEval)
{
lf_dev.AddDomainIntegrator(lfi_dev, elem_marker);
lf_std.AddDomainIntegrator(lfi_std, elem_marker);
}
else
{
lf_dev.AddBoundaryIntegrator(lfi_dev);
lf_std.AddBoundaryIntegrator(lfi_std);
}
// Test accumulation of integrators
if (vdim == 1)
{
lf_dev.AddDomainIntegrator(new DomainLFIntegrator(fn_coeff));
lf_std.AddDomainIntegrator(new DomainLFIntegrator(fn_coeff));
}
}
void Run()
{
const bool scalar = problem == LinearFormExtTest::DLFEval ||
problem == LinearFormExtTest::QLFEval ||
problem == LinearFormExtTest::DLFGrad;
REQUIRE((!scalar || vdim == 1));
const bool grad = problem == LinearFormExtTest::DLFGrad ||
problem == LinearFormExtTest::VDLFGrad;
CAPTURE(mesh_filename, dim, p, q, ordering, vdim, scalar, grad);
lf_dev.UseFastAssembly(true);
lf_dev.Assemble();
lf_std.UseFastAssembly(false);
lf_std.Assemble();
lf_std -= lf_dev;
REQUIRE(0.0 == MFEM_Approx(lf_std*lf_std, abs_tol, rel_tol));
}
};
TEST_CASE("Linear Form Extension", "[LinearFormExtension], [GPU]")
{
const bool all = launch_all_non_regression_tests;
const auto mesh_file =
all ? GENERATE("../../data/star.mesh", "../../data/star-q3.mesh",
"../../data/fichera.mesh", "../../data/fichera-q3.mesh") :
GENERATE("../../data/star-q3.mesh", "../../data/fichera-q3.mesh");
const auto p = all ? GENERATE(1,2,3,4,5,6) : GENERATE(1,3);
SECTION("Scalar")
{
const auto qtype = GENERATE(Lobatto, Legendre, LegendreUnderIntegration);
const auto problem = GENERATE(LinearFormExtTest::DLFEval,
LinearFormExtTest::QLFEval,
LinearFormExtTest::DLFGrad,
LinearFormExtTest::BLFEval,
LinearFormExtTest::BNLFEval);
LinearFormExtTest(mesh_file, 1, Ordering::byNODES, qtype, problem, p).Run();
}
SECTION("Vector")
{
const auto qtype = GENERATE(Lobatto, Legendre, LegendreUnderIntegration);
const auto vdim = all ? GENERATE(1,5,7) : GENERATE(1,5);
const auto ordering = GENERATE(Ordering::byVDIM, Ordering::byNODES);
const auto problem = GENERATE(LinearFormExtTest::VDLFEval,
LinearFormExtTest::VQLFEval,
LinearFormExtTest::VDLFGrad);
LinearFormExtTest(mesh_file, vdim, ordering, qtype, problem, p).Run();
}
SECTION("SetIntPoint")
{
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
CAPTURE(mesh_file, dim, p);
H1_FECollection H1(p, dim);
FiniteElementSpace H1fes(&mesh, &H1);
FunctionCoefficient f([](const Vector& x)
{ return std::sin(M_PI*x(0)) * std::sin(M_PI*x(1)); });
GridFunction x(&H1fes);
x.ProjectCoefficient(f);
GradientGridFunctionCoefficient grad_x(&x);
InnerProductCoefficient norm2_grad_x(grad_x,grad_x);
L2_FECollection L2(p-1, dim);
FiniteElementSpace L2fes(&mesh, &L2);
LinearForm d1(&L2fes);
d1.AddDomainIntegrator(new DomainLFIntegrator(norm2_grad_x));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&L2fes);
d2.AddDomainIntegrator(new DomainLFIntegrator(norm2_grad_x));
d2.UseFastAssembly(false);
d2.Assemble();
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
SECTION("L2 MapType")
{
auto map_type = GENERATE(FiniteElement::VALUE, FiniteElement::INTEGRAL);
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
CAPTURE(mesh_file, dim, p, map_type);
L2_FECollection fec(p, dim, BasisType::GaussLegendre, map_type);
FiniteElementSpace fes(&mesh, &fec);
ConstantCoefficient coeff(1.0);
LinearForm d1(&fes);
d1.AddDomainIntegrator(new DomainLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&fes);
d2.AddDomainIntegrator(new DomainLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
SECTION("VectorFE")
{
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
CAPTURE(mesh_file, dim, p);
RT_FECollection fec(p-1, dim);
FiniteElementSpace fes(&mesh, &fec);
FunctionCoefficient coeff(f);
LinearForm d1(&fes);
d1.AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&fes);
d2.AddBoundaryIntegrator(new VectorFEBoundaryFluxLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
}
TEST_CASE("H(div) Linear Form Extension", "[LinearFormExtension], [GPU]")
{
const bool all = launch_all_non_regression_tests;
const auto mesh_file =
all ? GENERATE("../../data/star.mesh", "../../data/star-q3.mesh",
"../../data/fichera.mesh", "../../data/fichera-q3.mesh") :
GENERATE("../../data/star-q3.mesh", "../../data/fichera-q3.mesh");
const auto p = all ? GENERATE(1,2,3,4,5,6) : GENERATE(1,3);
Mesh mesh(mesh_file);
const int dim = mesh.Dimension();
CAPTURE(mesh_file, dim, p);
RT_FECollection fec(p, dim);
FiniteElementSpace fes(&mesh, &fec);
VectorFunctionCoefficient coeff(dim, fvec_dim);
LinearForm d1(&fes);
d1.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d1.UseFastAssembly(true);
d1.Assemble();
LinearForm d2(&fes);
d2.AddDomainIntegrator(new VectorFEDomainLFIntegrator(coeff));
d2.UseFastAssembly(false);
d2.Assemble();
CAPTURE(d1.Norml2(), d2.Norml2());
d1 -= d2;
REQUIRE(d1.Norml2() == MFEM_Approx(0.0));
}
#ifdef MFEM_USE_MPI
TEST_CASE("Parallel Fast LinearForm Assembly",
"[AssemblyLevel], [Parallel], [GPU]")
{
auto order = GENERATE(1, 2);
auto mesh_fname = GENERATE(
"../../data/amr-quad.mesh",
"../../data/fichera-amr.mesh"
);
Mesh serial_mesh(mesh_fname);
ParMesh mesh(MPI_COMM_WORLD, serial_mesh);
serial_mesh.Clear();
Array<int> ess_bdr(mesh.bdr_attributes.Max());
ess_bdr = 0;
mesh.MarkExternalBoundaries(ess_bdr);
H1_FECollection fec(order, mesh.Dimension());
ParFiniteElementSpace fespace(&mesh, &fec);
Array<int> ess_tdof_list;
fespace.GetBoundaryTrueDofs(ess_tdof_list);
ParLinearForm b(&fespace);
ConstantCoefficient one(1.0);
b.AddDomainIntegrator(new DomainLFIntegrator(one));
b.AddBoundaryIntegrator(new BoundaryLFIntegrator(one), ess_bdr);
b.UseFastAssembly(true);
b.Assemble();
}
#endif