Files
mfem/tests/unit/fem/test_fe_symmetry.cpp
T
camierjs 5c0b2a6b62 Add LDFLAGS for all runs
Fix float-conversion warnings
2025-06-17 15:43:41 -07:00

448 lines
13 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"
using namespace mfem;
// Use the Hungarian algorithm to find the column permutation of A which
// produces a matrix with the minimum trace.
Array<int> findMinTracePermutation(const DenseMatrix &A)
{
const int R = A.NumRows(), C = A.NumCols();
MFEM_VERIFY(R <= C, "Matrix must have at least as many columns as rows");
Array<int> perm(C + 1); perm = -1;
Vector potR(R); potR = 0.0;
Vector potC(C + 1); potC = 0.0;
const real_t inf = std::numeric_limits<real_t>::max();
for (int r_cur = 0; r_cur < R; ++r_cur)
{
int c_cur = C;
perm[c_cur] = r_cur;
Vector min_to(C + 1); min_to = inf;
Array<int> prv_col(C + 1); prv_col = -1;
Array<bool> col_in_path(C + 1); col_in_path = false;
while (perm[c_cur] != -1)
{
col_in_path[c_cur] = true;
const int r = perm[c_cur];
real_t delta = inf;
int c_next = 0;
for (int c = 0; c < C; ++c)
{
if (!col_in_path[c])
{
const real_t d = A(r,c) - potR[r] - potC[c];
if (d < min_to[c])
{
min_to[c] = d;
prv_col[c] = c_cur;
}
if (min_to[c] < delta)
{
delta = min_to[c];
c_next = c;
}
}
}
for (int c = 0; c <= C; ++c)
{
if (col_in_path[c])
{
potR[perm[c]] += delta;
potC[c] -= delta;
}
else
{
min_to[c] -= delta;
}
}
c_cur = c_next;
}
for (int c; c_cur != C; c_cur = c)
{
c = prv_col[c_cur];
perm[c_cur] = perm[c];
}
}
return perm;
}
/// We require that our pyramid basis functions possess four-fold rotational
/// symmetry. This implies that:
/// P s1 t1 = s0
/// Where s0 and s1 are the shape functions evaluated at a random point and its
/// image under rotation respectively. Also, t1 is the Piola transform
/// for the finte element type and P is a signed permutation matrix. The signs
/// of the permutation entries can be determined by the conventions used
/// for the DoFs of the various basis functions. These signs are passed to this
/// function in the ps argument. The remaining structure of the permutation is
/// computed using findMinTracePermutation with the matrix ps * s1 * t1 * s0^T.
///
real_t computeVShapeDifference(const DenseMatrix &s0,
const DenseMatrix &ts1,
const Vector &ps)
{
const int dof = s0.Height();
const int dim = s0.Width();
DenseMatrix pts1(ts1);
pts1.LeftScaling(ps);
DenseMatrix sts(dof);
MultABt(pts1, s0, sts); sts *= -1.0;
Array<int> perm = findMinTracePermutation(sts);
real_t nrm = 0.0;
for (int i=0; i<perm.Size() - 1; i++)
{
int i0 = i;
int i1 = perm[i];
for (int d=0; d<dim; d++)
{
nrm += pow(s0(i0, d) - pts1(i1, d), 2);
}
}
return nrm;
}
TEST_CASE("FE Symmetry",
"[H1_FuentesPyramidElemet]"
"[ND_FuentesPyramidElemet]"
"[RT_FuentesPyramidElemet]"
"[L2_FuentesPyramidElemet]")
{
const int order = 3;
const int npts = 3;
const real_t tol = 1e-13;
CAPTURE(order);
IsoparametricTransformation T;
T.SetIdentityTransformation(Geometry::PYRAMID);
{
DenseMatrix &ptMat = T.GetPointMat();
ptMat.SetCol(0, Vector({-0.5, -0.5, 0.0}));
ptMat.SetCol(1, Vector({ 0.5, -0.5, 0.0}));
ptMat.SetCol(2, Vector({ 0.5, 0.5, 0.0}));
ptMat.SetCol(3, Vector({-0.5, 0.5, 0.0}));
ptMat.SetCol(4, Vector({ 0.0, 0.0, M_SQRT1_2}));
T.Reset();
}
for (int k=0; k<npts; k++)
{
double a = rand() / double(RAND_MAX);
double b = rand() / double(RAND_MAX);
double c = 0.9 * rand() / double(RAND_MAX);
// Select a random point inside a pyramid
IntegrationPoint ip0;
ip0.x = a * (1.0 - c); ip0.y = b * (1.0 - c); ip0.z = c;
SECTION("H1_FuentesFiniteElement")
{
H1_FuentesPyramidElement fe(order);
DenseMatrix s0(fe.GetDof(), 1);
DenseMatrix s1(fe.GetDof(), 1);
Vector v0(s0.GetData(), fe.GetDof());
Vector v1(s1.GetData(), fe.GetDof());
T.SetIntPoint(&ip0);
fe.CalcPhysShape(T, v0);
// 90 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = ip0.y;
ip1.y = 1.0 - ip0.x - ip0.z;
ip1.z = ip0.z;
T.SetIntPoint(&ip1);
fe.CalcPhysShape(T, v1);
Vector ps(fe.GetDof()); ps = 1.0;
REQUIRE(computeVShapeDifference(s0, s1, ps) < tol);
}
// 180 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.x - ip0.z;
ip1.y = 1.0 - ip0.y - ip0.z;
ip1.z = ip0.z;
T.SetIntPoint(&ip1);
fe.CalcPhysShape(T, v1);
Vector ps(fe.GetDof()); ps = 1.0;
REQUIRE(computeVShapeDifference(s0, s1, ps) < tol);
}
// 270 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.y - ip0.z;
ip1.y = ip0.x;
ip1.z = ip0.z;
T.SetIntPoint(&ip1);
fe.CalcPhysShape(T, v1);
Vector ps(fe.GetDof()); ps = 1.0;
REQUIRE(computeVShapeDifference(s0, s1, ps) < tol);
}
}
SECTION("ND_FuentesPyramidElement")
{
const int ne = order; // Num DoFs per edge
const int nt = order * (order - 1); // Num DoF per tri face
const int nq = 2 * nt; // Num DoF per quad face
// Num DoF per interior dir
const int ni = order * (static_cast<int>(pow(order-1, 2)));
const int oq = 8 * ne; // Offset to first quad DoF
const int ot = oq + nq; // Offset to first tri DoF
const int oi = ot + 4 * nt; // Offset to first interior DoF
ND_FuentesPyramidElement fe(order);
DenseMatrix s0(fe.GetDof(), 3);
DenseMatrix s1(fe.GetDof(), 3);
DenseMatrix t1(3);
DenseMatrix t1Inv(3);
DenseMatrix ts1(fe.GetDof(), 3);
T.SetIntPoint(&ip0);
fe.CalcPhysVShape(T, s0);
// 90 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = ip0.y;
ip1.y = 1.0 - ip0.x - ip0.z;
ip1.z = ip0.z;
t1Inv = 0.0; t1Inv(0,1) = 1.0; t1Inv(1,0) = -1.0; t1Inv(2,2) = 1.0;
T.SetIntPoint(&ip1);
fe.CalcPhysVShape(T, s1); Mult(s1, t1Inv, ts1);
Vector ps(fe.GetDof()); ps = 1.0;
for (int i = ne; i < 2 * ne; i++) { ps[i] = -1; }
for (int i = 3 * ne; i<4*ne; i++) { ps[i] = -1; }
for (int i = oq; i < oq + nt; i++) { ps[i] = -1; }
for (int i = oi + ni; i < oi + 2 * ni; i++) { ps[i] = -1; }
REQUIRE(computeVShapeDifference(s0, ts1, ps) < tol);
}
// 180 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.x - ip0.z;
ip1.y = 1.0 - ip0.y - ip0.z;
ip1.z = ip0.z;
t1Inv = 0.0; t1Inv(0,0) = -1.0; t1Inv(1,1) = -1.0; t1Inv(2,2) = 1.0;
T.SetIntPoint(&ip1);
fe.CalcPhysVShape(T, s1); Mult(s1, t1Inv, ts1);
Vector ps(fe.GetDof()); ps = 1.0;
for (int i = 0; i < 4 * ne; i++) { ps[i] = -1; }
for (int i = oq; i < oq + nq; i++) { ps[i] = -1; }
for (int i = oi; i < oi + 2 * ni; i++) { ps[i] = -1; }
REQUIRE(computeVShapeDifference(s0, ts1, ps) < tol);
}
// 270 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.y - ip0.z;
ip1.y = ip0.x;
ip1.z = ip0.z;
t1Inv = 0.0; t1Inv(0,1) = -1.0; t1Inv(1,0) = 1.0; t1Inv(2,2) = 1.0;
T.SetIntPoint(&ip1);
fe.CalcPhysVShape(T, s1); Mult(s1, t1Inv, ts1);
Vector ps(fe.GetDof()); ps = 1.0;
for (int i = 0; i < ne; i++) { ps[i] = -1; }
for (int i = 2 * ne; i < 3 * ne; i++) { ps[i] = -1; }
for (int i = oq + nt; i < ot; i++) { ps[i] = -1; }
for (int i = oi; i < oi + ni; i++) { ps[i] = -1; }
REQUIRE(computeVShapeDifference(s0, ts1, ps) < tol);
}
}
SECTION("RT_FuentesPyramidElement")
{
const int nq = order * order; // Num DoF per quad face
const int nt = (order * (order + 1)) / 2; // Num DoF per tri face
const int ni = order * order * (order - 1); // Num interior DoF per dir
const int ot = nq; // Offset to first tri DoF
const int oi = ot + 4 * nt; // Offset to first interior DoF
RT_FuentesPyramidElement fe(order - 1);
DenseMatrix s0(fe.GetDof(), 3);
DenseMatrix s1(fe.GetDof(), 3);
DenseMatrix t1(3);
DenseMatrix ts1(fe.GetDof(), 3);
T.SetIntPoint(&ip0);
fe.CalcPhysVShape(T, s0);
// 90 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = ip0.y;
ip1.y = 1.0 - ip0.x - ip0.z;
ip1.z = ip0.z;
t1 = 0.0; t1(0,1) = -1.0; t1(1,0) = 1.0; t1(2,2) = 1.0;
T.SetIntPoint(&ip1);
fe.CalcPhysVShape(T, s1); MultABt(s1, t1, ts1);
Vector ps(fe.GetDof()); ps = 1.0;
for (int i = oi + ni; i < oi + 2 * ni; i++)
{
ps[i] = -1.0;
}
REQUIRE(computeVShapeDifference(s0, ts1, ps) < tol);
}
// 180 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.x - ip0.z;
ip1.y = 1.0 - ip0.y - ip0.z;
ip1.z = ip0.z;
t1 = 0.0; t1(0,0) = -1.0; t1(1,1) = -1.0; t1(2,2) = 1.0;
T.SetIntPoint(&ip1);
fe.CalcPhysVShape(T, s1); MultABt(s1, t1, ts1);
Vector ps(fe.GetDof()); ps = 1.0;
for (int i = oi; i < oi + 2 * ni; i++)
{
ps[i] = -1.0;
}
REQUIRE(computeVShapeDifference(s0, ts1, ps) < tol);
}
// 270 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.y - ip0.z;
ip1.y = ip0.x;
ip1.z = ip0.z;
t1 = 0.0; t1(0,1) = 1.0; t1(1,0) = -1.0; t1(2,2) = 1.0;
T.SetIntPoint(&ip1);
fe.CalcPhysVShape(T, s1); MultABt(s1, t1, ts1);
Vector ps(fe.GetDof()); ps = 1.0;
for (int i = oi; i < oi + ni; i++)
{
ps[i] = -1.0;
}
REQUIRE(computeVShapeDifference(s0, ts1, ps) < tol);
}
}
SECTION("L2_FuentesFiniteElement")
{
L2_FuentesPyramidElement fe(order);
DenseMatrix s0(fe.GetDof(), 1);
DenseMatrix s1(fe.GetDof(), 1);
Vector v0(s0.GetData(), fe.GetDof());
Vector v1(s1.GetData(), fe.GetDof());
T.SetIntPoint(&ip0);
fe.CalcPhysShape(T, v0);
// 90 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = ip0.y;
ip1.y = 1.0 - ip0.x - ip0.z;
ip1.z = ip0.z;
T.SetIntPoint(&ip1);
fe.CalcPhysShape(T, v1);
Vector ps(fe.GetDof()); ps = 1.0;
REQUIRE(computeVShapeDifference(s0, s1, ps) < tol);
}
// 180 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.x - ip0.z;
ip1.y = 1.0 - ip0.y - ip0.z;
ip1.z = ip0.z;
T.SetIntPoint(&ip1);
fe.CalcPhysShape(T, v1);
Vector ps(fe.GetDof()); ps = 1.0;
REQUIRE(computeVShapeDifference(s0, s1, ps) < tol);
}
// 270 Degree Rotational Symmetry
{
IntegrationPoint ip1;
ip1.x = 1.0 - ip0.y - ip0.z;
ip1.y = ip0.x;
ip1.z = ip0.z;
T.SetIntPoint(&ip1);
fe.CalcPhysShape(T, v1);
Vector ps(fe.GetDof()); ps = 1.0;
REQUIRE(computeVShapeDifference(s0, s1, ps) < tol);
}
}
}
}