563 lines
16 KiB
C++
563 lines
16 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 "unit_tests.hpp"
|
|
#include "mfem.hpp"
|
|
|
|
using namespace mfem;
|
|
#ifdef MFEM_USE_GSLIB
|
|
namespace gslib_test
|
|
{
|
|
|
|
int func_order;
|
|
|
|
// Scalar function to project
|
|
double scalar_func(const Vector &x)
|
|
{
|
|
const int dim = x.Size();
|
|
double res = 0.0;
|
|
for (int d = 0; d < dim; d++) { res += std::pow(x(d), func_order); }
|
|
return res;
|
|
}
|
|
|
|
void F_exact(const Vector &p, Vector &F)
|
|
{
|
|
F(0) = scalar_func(p);
|
|
for (int i = 1; i < F.Size(); i++) { F(i) = (i+1)*F(0); }
|
|
}
|
|
|
|
enum class Space { H1, L2 };
|
|
|
|
TEST_CASE("GSLIBInterpolate", "[GSLIBInterpolate][GSLIB]")
|
|
{
|
|
auto space = GENERATE(Space::H1, Space::L2);
|
|
auto simplex = GENERATE(true, false);
|
|
int dim = GENERATE(2, 3);
|
|
func_order = GENERATE(1, 2);
|
|
int mesh_order = GENERATE(1, 2);
|
|
int mesh_node_ordering = GENERATE(0, 1);
|
|
int point_ordering = GENERATE(0, 1);
|
|
int ncomp = GENERATE(1, 2);
|
|
int gf_ordering = GENERATE(0, 1);
|
|
int func_out_ordering = GENERATE(0, 1);
|
|
bool href = GENERATE(true, false);
|
|
bool pref = GENERATE(true, false);
|
|
|
|
int ne = 4;
|
|
|
|
CAPTURE(space, simplex, dim, func_order, mesh_order, mesh_node_ordering,
|
|
point_ordering, ncomp, gf_ordering, func_out_ordering, href, pref);
|
|
|
|
if (ncomp == 1 && gf_ordering == 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Mesh mesh;
|
|
if (dim == 2)
|
|
{
|
|
Element::Type type = simplex ? Element::TRIANGLE : Element::QUADRILATERAL;
|
|
mesh = Mesh::MakeCartesian2D(ne, ne, type, 1, 1.0, 1.0);
|
|
}
|
|
else
|
|
{
|
|
Element::Type type = simplex ? Element::TETRAHEDRON : Element::HEXAHEDRON;
|
|
mesh = Mesh::MakeCartesian3D(ne, ne, ne, type, 1.0, 1.0, 1.0);
|
|
}
|
|
|
|
if (href || pref) { mesh.EnsureNCMesh(); }
|
|
if (href) { mesh.RandomRefinement(0.5); }
|
|
|
|
// Set Mesh NodalFESpace
|
|
H1_FECollection fecm(mesh_order, dim);
|
|
FiniteElementSpace fespacem(&mesh, &fecm, dim, mesh_node_ordering);
|
|
mesh.SetNodalFESpace(&fespacem);
|
|
|
|
// Set GridFunction to be interpolated
|
|
FiniteElementCollection *c_fec = nullptr;
|
|
|
|
switch (space)
|
|
{
|
|
case Space::H1:
|
|
c_fec = new H1_FECollection(func_order, dim);
|
|
break;
|
|
case Space::L2:
|
|
c_fec = new L2_FECollection(func_order, dim);
|
|
break;
|
|
}
|
|
|
|
FiniteElementSpace c_fespace =
|
|
FiniteElementSpace(&mesh, c_fec, ncomp, gf_ordering);
|
|
GridFunction field_vals(&c_fespace);
|
|
|
|
VectorFunctionCoefficient F(ncomp, F_exact);
|
|
field_vals.ProjectCoefficient(F);
|
|
|
|
// Generate points in the domain
|
|
Vector pos_min, pos_max;
|
|
mesh.GetBoundingBox(pos_min, pos_max, mesh_order);
|
|
const int pts_cnt_1D = 5;
|
|
int pts_cnt = pow(pts_cnt_1D, dim);
|
|
Vector vxyz(pts_cnt * dim);
|
|
NodalTensorFiniteElement *el = NULL;
|
|
if (dim == 2)
|
|
{
|
|
el = new L2_QuadrilateralElement(pts_cnt_1D-1,BasisType::ClosedUniform);
|
|
}
|
|
else
|
|
{
|
|
el = new L2_HexahedronElement(pts_cnt_1D - 1, BasisType::ClosedUniform);
|
|
}
|
|
const IntegrationRule &ir = el->GetNodes();
|
|
for (int i = 0; i < ir.GetNPoints(); i++)
|
|
{
|
|
const IntegrationPoint &ip = ir.IntPoint(i);
|
|
if (point_ordering == Ordering::byNODES)
|
|
{
|
|
vxyz(i) = pos_min(0) + ip.x * (pos_max(0)-pos_min(0));
|
|
vxyz(pts_cnt + i) = pos_min(1) + ip.y * (pos_max(1)-pos_min(1));
|
|
if (dim == 3)
|
|
{
|
|
vxyz(2*pts_cnt + i) = pos_min(2) + ip.z * (pos_max(2)-pos_min(2));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vxyz(i*dim + 0) = pos_min(0) + ip.x * (pos_max(0)-pos_min(0));
|
|
vxyz(i*dim + 1) = pos_min(1) + ip.y * (pos_max(1)-pos_min(1));
|
|
if (dim == 3)
|
|
{
|
|
vxyz(i*dim + 2) = pos_min(2) + ip.z * (pos_max(2)-pos_min(2));
|
|
}
|
|
}
|
|
}
|
|
delete el;
|
|
|
|
// Find and interpolate FE Function values
|
|
Vector interp_vals(pts_cnt*ncomp);
|
|
FindPointsGSLIB finder;
|
|
finder.Setup(mesh);
|
|
finder.SetL2AvgType(FindPointsGSLIB::NONE);
|
|
finder.Interpolate(vxyz, field_vals, interp_vals, point_ordering,
|
|
func_out_ordering);
|
|
Array<unsigned int> code_out = finder.GetCode();
|
|
Vector dist_p_out = finder.GetDist();
|
|
|
|
int not_found = 0;
|
|
double err = 0.0, max_err = 0.0, max_dist = 0.0;
|
|
Vector pos(dim);
|
|
|
|
for (int i = 0; i < pts_cnt; i++)
|
|
{
|
|
max_dist = std::max(max_dist, dist_p_out(i));
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
pos(d) = point_ordering == Ordering::byNODES ?
|
|
vxyz(d*pts_cnt + i) :
|
|
vxyz(i*dim + d);
|
|
}
|
|
Vector exact_val(ncomp);
|
|
F_exact(pos, exact_val);
|
|
for (int j = 0; j < ncomp; j++)
|
|
{
|
|
if (code_out[i] < 2)
|
|
{
|
|
err = func_out_ordering == Ordering::byNODES ?
|
|
fabs(exact_val(j) - interp_vals[i + j*pts_cnt]) :
|
|
fabs(exact_val(j) - interp_vals[i*ncomp + j]);
|
|
max_err = std::max(max_err, err);
|
|
}
|
|
else
|
|
{
|
|
if (j == 0) { not_found++; }
|
|
}
|
|
}
|
|
}
|
|
|
|
REQUIRE(max_err < 1e-12);
|
|
REQUIRE(max_dist < 1e-10);
|
|
REQUIRE(not_found == 0);
|
|
|
|
finder.FreeData();
|
|
delete c_fec;
|
|
}
|
|
|
|
// Generates meshes with different element types, followed by points at
|
|
// element faces and interior, and finally checks to see if these points are
|
|
// correctly detected at element boundary or not.
|
|
TEST_CASE("GSLIBFindAtElementBoundary",
|
|
"[GSLIBFindAtElementBoundary][GSLIB]")
|
|
{
|
|
int dim = GENERATE(2, 3);
|
|
CAPTURE(dim);
|
|
int nex = 4;
|
|
int mesh_order = 4;
|
|
int l2_order = 4;
|
|
|
|
int netype = dim == 2 ? 2 : 4; // 2 element types in 2D, 4 in 3D.
|
|
int estart = dim == 2 ? 2 : 4; // starts at index 2 in 2D, 4 in 3D
|
|
|
|
for (int et = estart; et < estart+netype; et++)
|
|
{
|
|
// H1 - order 1, L2 - order 0 for pyramids
|
|
if (et == 7)
|
|
{
|
|
mesh_order = 1;
|
|
l2_order = 0;
|
|
}
|
|
Mesh mesh;
|
|
if (dim == 2)
|
|
{
|
|
mesh = Mesh::MakeCartesian2D(nex, nex, (Element::Type)et);
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::MakeCartesian3D(nex, nex, nex, (Element::Type)et);
|
|
}
|
|
|
|
mesh.SetCurvature(mesh_order);
|
|
const FiniteElementSpace *n_fespace = mesh.GetNodalFESpace();
|
|
const GridFunction *nodes = mesh.GetNodes();
|
|
|
|
Array<double> xyz;
|
|
|
|
// Generate points on each element's face/edge
|
|
for (int e = 0; e < mesh.GetNE(); e++)
|
|
{
|
|
Array<int> faces,ori;
|
|
if (dim == 2)
|
|
{
|
|
mesh.GetElementEdges(e, faces, ori);
|
|
}
|
|
else
|
|
{
|
|
mesh.GetElementFaces(e, faces, ori);
|
|
}
|
|
|
|
for (int f = 0; f < faces.Size(); f++)
|
|
{
|
|
const FiniteElement *fe = n_fespace->GetFaceElement(faces[f]);
|
|
const IntegrationRule ir = fe->GetNodes();
|
|
|
|
DenseMatrix vals;
|
|
DenseMatrix tr;
|
|
nodes->GetFaceVectorValues(faces[f], 0, ir, vals, tr);
|
|
xyz.Append(vals.GetData(), vals.Height()*vals.Width());
|
|
}
|
|
}
|
|
|
|
int nptface = xyz.Size()/dim;
|
|
|
|
// Generate points inside each element
|
|
FiniteElementCollection *l2_fec = new L2_FECollection(l2_order, dim);
|
|
FiniteElementSpace l2_fespace =
|
|
FiniteElementSpace(&mesh, l2_fec, 1);
|
|
DenseMatrix vals;
|
|
DenseMatrix tr;
|
|
for (int e = 0; e < mesh.GetNE(); e++)
|
|
{
|
|
const FiniteElement *fe = l2_fespace.GetFE(e);
|
|
const IntegrationRule ir = fe->GetNodes();
|
|
|
|
nodes->GetVectorValues(e, ir, vals, tr);
|
|
xyz.Append(vals.GetData(), vals.Height()*vals.Width());
|
|
}
|
|
|
|
Vector xyzv(xyz.GetData(), xyz.Size());
|
|
int npt = xyzv.Size()/dim;
|
|
|
|
FindPointsGSLIB finder;
|
|
finder.Setup(mesh);
|
|
finder.FindPoints(xyzv, Ordering::byVDIM);
|
|
Array<unsigned int> code_out = finder.GetCode();
|
|
unsigned int cmin = 5,
|
|
cmax = 0;
|
|
for (int i = 0; i < nptface; i++)
|
|
{
|
|
cmin = std::min(code_out[i], cmin);
|
|
cmax = std::max(code_out[i], cmax);
|
|
}
|
|
REQUIRE((cmin == 1 && cmax == 1)); // should be found on element boundary
|
|
|
|
cmin = 5;
|
|
cmax = 0;
|
|
for (int i = nptface; i < npt; i++)
|
|
{
|
|
cmin = std::min(code_out[i], cmin);
|
|
cmax = std::max(code_out[i], cmax);
|
|
}
|
|
REQUIRE((cmin == 0 && cmax == 0)); // should be found inside element
|
|
delete l2_fec;
|
|
}
|
|
}
|
|
|
|
// Generate a 4x4 Quad/Hex Mesh and interpolate point in the center of domain
|
|
// at element boundary. This tests L2 projection with and without averaging.
|
|
TEST_CASE("GSLIBInterpolateL2ElementBoundary",
|
|
"[GSLIBInterpolateL2ElementBoundary][GSLIB]")
|
|
{
|
|
int dim = GENERATE(2, 3);
|
|
CAPTURE(dim);
|
|
|
|
int nex = 4;
|
|
int mesh_order = 2;
|
|
Mesh mesh;
|
|
if (dim == 2)
|
|
{
|
|
mesh = Mesh::MakeCartesian2D(nex, nex, Element::QUADRILATERAL);
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::MakeCartesian3D(nex, nex, nex, Element::HEXAHEDRON);
|
|
}
|
|
|
|
mesh.SetCurvature(mesh_order);
|
|
|
|
// Set GridFunction to be interpolated
|
|
FiniteElementCollection *c_fec = new L2_FECollection(3, dim);
|
|
FiniteElementSpace c_fespace =
|
|
FiniteElementSpace(&mesh, c_fec, 1);
|
|
GridFunction field_vals(&c_fespace);
|
|
Array<int> dofs;
|
|
double leftval = 1.0;
|
|
double rightval = 3.0;
|
|
for (int e = 0; e < mesh.GetNE(); e++)
|
|
{
|
|
Vector center(dim);
|
|
mesh.GetElementCenter(e, center);
|
|
double val_to_set = center(0) < 0.5 ? leftval : rightval;
|
|
c_fespace.GetElementDofs(e, dofs);
|
|
Vector vals(dofs.Size());
|
|
vals = val_to_set;
|
|
field_vals.SetSubVector(dofs, vals);
|
|
}
|
|
|
|
int npt = 1;
|
|
Vector xyz(npt*dim);
|
|
xyz = 0.0;
|
|
xyz(0) = 0.5;
|
|
|
|
// Find and interpolate FE Function values
|
|
Vector interp_vals(npt);
|
|
FindPointsGSLIB finder;
|
|
finder.Setup(mesh);
|
|
finder.SetL2AvgType(FindPointsGSLIB::NONE);
|
|
finder.Interpolate(xyz, field_vals, interp_vals, 1);
|
|
Array<unsigned int> code_out = finder.GetCode();
|
|
|
|
// This point should have been found on element border. But the interpolated
|
|
// value will come from either of the elements that share this edge/face.
|
|
REQUIRE(code_out[0] == 1);
|
|
REQUIRE((interp_vals(0) == MFEM_Approx(leftval) ||
|
|
interp_vals(0) == MFEM_Approx(rightval)));
|
|
|
|
// Interpolated value should now be average of solution coming from
|
|
// adjacent elements.
|
|
finder.SetL2AvgType(FindPointsGSLIB::ARITHMETIC);
|
|
finder.Interpolate(xyz, field_vals, interp_vals, 1);
|
|
REQUIRE(interp_vals(0) == MFEM_Approx(0.5*(leftval+rightval)));
|
|
|
|
finder.FreeData();
|
|
delete c_fec;
|
|
}
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
// Custom interpolation procedure with gslib
|
|
TEST_CASE("GSLIBCustomInterpolation",
|
|
"[GSLIBCustomInterpolation][Parallel][GSLIB]")
|
|
{
|
|
int myid;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
|
|
|
|
int dim = GENERATE(2, 3);
|
|
bool simplex = GENERATE(true, false);
|
|
|
|
CAPTURE(dim, simplex);
|
|
|
|
int nex = 4;
|
|
int mesh_order = 2;
|
|
Mesh mesh;
|
|
if (dim == 2)
|
|
{
|
|
Element::Type type = simplex ? Element::TRIANGLE : Element::QUADRILATERAL;
|
|
mesh = Mesh::MakeCartesian2D(nex, nex, type);
|
|
}
|
|
else
|
|
{
|
|
Element::Type type = simplex ? Element::TETRAHEDRON : Element::HEXAHEDRON;
|
|
mesh = Mesh::MakeCartesian3D(nex, nex, nex, type);
|
|
}
|
|
|
|
mesh.SetCurvature(mesh_order);
|
|
ParMesh pmesh(MPI_COMM_WORLD, mesh);
|
|
|
|
// f(x,y,z) = x^2 + y^2 + z^2
|
|
auto func = [](const Vector &x)
|
|
{
|
|
const int dim = x.Size();
|
|
double res = 0.0;
|
|
for (int d = 0; d < dim; d++) { res += std::pow(x(d), 2); }
|
|
return res;
|
|
};
|
|
|
|
// \nabla f(x,y,z) = [2*x,2*y,2*z]
|
|
auto func_grad = [](const Vector &x, Vector &p)
|
|
{
|
|
const int dim = x.Size();
|
|
p.SetSize(dim);
|
|
for (int d = 0; d < dim; d++) { p(d) = 2.0*x(d); }
|
|
};
|
|
|
|
// Set GridFunction to be interpolated
|
|
H1_FECollection c_fec(3, dim);
|
|
FiniteElementSpace c_fespace(&pmesh, &c_fec, 1);
|
|
GridFunction field_vals(&c_fespace);
|
|
|
|
FunctionCoefficient f(func);
|
|
field_vals.ProjectCoefficient(f);
|
|
|
|
// Generate randomized points in [0, 1]^D. Assume ordering by VDIM.
|
|
int npt = 101;
|
|
Vector xyz(npt*dim);
|
|
xyz.Randomize(myid + 1);
|
|
if (myid == 1) // zero out # of points on rank 1
|
|
{
|
|
xyz.SetSize(0);
|
|
}
|
|
|
|
// Find points on the ParMesh
|
|
Vector interp_vals(npt);
|
|
FindPointsGSLIB finder;
|
|
finder.Setup(pmesh);
|
|
finder.FindPoints(xyz, Ordering::byVDIM);
|
|
|
|
/** Interpolate gradient using custom interpolation procedure. */
|
|
// We first send information to MPI ranks that own the element corresponding
|
|
// to each point.
|
|
Array<unsigned int> recv_elem, recv_code;
|
|
Vector recv_rst;
|
|
finder.DistributePointInfoToOwningMPIRanks(recv_elem, recv_rst, recv_code);
|
|
int npt_recv = recv_elem.Size();
|
|
// Compute gradient locally
|
|
Vector grad(npt_recv*dim);
|
|
for (int i = 0; i < npt_recv; i++)
|
|
{
|
|
const int e = recv_elem[i];
|
|
|
|
IntegrationPoint ip;
|
|
if (dim == 2)
|
|
{
|
|
ip.Set2(recv_rst(dim*i + 0),recv_rst(dim*i + 1));
|
|
}
|
|
else
|
|
{
|
|
ip.Set3(recv_rst(dim*i + 0),recv_rst(dim*i + 1),
|
|
recv_rst(dim*i + 2));
|
|
}
|
|
ElementTransformation *Tr = c_fespace.GetElementTransformation(e);
|
|
Tr->SetIntPoint(&ip);
|
|
|
|
Vector gradloc(grad.GetData()+i*dim,dim);
|
|
field_vals.GetGradient(*Tr, gradloc);
|
|
}
|
|
|
|
// Send the computed gradient back to the ranks that requested it.
|
|
Vector recv_grad;
|
|
finder.DistributeInterpolatedValues(grad, dim, Ordering::byVDIM, recv_grad);
|
|
|
|
// Check if the received gradient matched analytic gradient.
|
|
for (int i = 0; i < npt && myid == 0; i++)
|
|
{
|
|
Vector x(xyz.GetData()+i*dim,dim);
|
|
Vector grad_exact(dim);
|
|
func_grad(x, grad_exact);
|
|
|
|
Vector recv_grad_i(recv_grad.GetData()+i*dim,dim);
|
|
|
|
for (int d = 0; d < dim; d++)
|
|
{
|
|
REQUIRE(grad_exact(d) == Approx(recv_grad(i*dim + d)));
|
|
}
|
|
}
|
|
|
|
finder.FreeData();
|
|
}
|
|
|
|
TEST_CASE("GSLIBGSOP", "[GSLIBGSOP][Parallel][GSLIB]")
|
|
{
|
|
int myid;
|
|
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
|
|
|
|
int nlen = 5 + rand() % 1000;
|
|
MPI_Allreduce(MPI_IN_PLACE, &nlen, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD);
|
|
|
|
Array<long long> ids(nlen);
|
|
Vector vals(nlen);
|
|
vals.Randomize(myid+1);
|
|
|
|
// Force minimum values based on the identifier for deterministic behavior
|
|
// on rank 0 and randomize the identifier on other ranks.
|
|
if (myid == 0)
|
|
{
|
|
for (int i = 0; i < nlen; i++)
|
|
{
|
|
ids[i] = i+1;
|
|
vals(i) = -ids[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < nlen; i++)
|
|
{
|
|
int num = rand() % nlen + 1;
|
|
ids[i] = num;
|
|
}
|
|
}
|
|
|
|
// Test GSOp::MIN
|
|
GSOPGSLIB gs = GSOPGSLIB(MPI_COMM_WORLD, ids);
|
|
gs.GS(vals, GSOPGSLIB::GSOp::MIN);
|
|
|
|
// Check for minimum value
|
|
for (int i = 0; i < nlen; i++)
|
|
{
|
|
int id = ids[i];
|
|
REQUIRE(vals(i) == -1.0*id);
|
|
}
|
|
|
|
// Test GSOp::ADD
|
|
// Set all values to 0 except on rank 0, and then add them.
|
|
if (myid != 0) { vals = 0.0; }
|
|
gs.GS(vals, GSOPGSLIB::GSOp::ADD);
|
|
|
|
// Check for added value to match what was originally set on rank 0.
|
|
for (int i = 0; i < nlen; i++)
|
|
{
|
|
int id = ids[i];
|
|
REQUIRE(vals(i) == -1.0*id);
|
|
}
|
|
|
|
// Test GSOp::MUL
|
|
// Randomize values on all ranks except rank 0 such that they are positive.
|
|
if (myid != 0) { vals.Randomize(); }
|
|
gs.GS(vals, GSOPGSLIB::GSOp::MUL);
|
|
|
|
// Check for multiplied values to be negative
|
|
for (int i = 0; i < nlen; i++)
|
|
{
|
|
REQUIRE(vals(i) < 0);
|
|
}
|
|
}
|
|
#endif // MFEM_USE_MPI
|
|
|
|
} //namespace_gslib
|
|
#endif
|