327 lines
9.2 KiB
C++
327 lines
9.2 KiB
C++
// Copyright (c) 2010-2022, 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"
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
static double exact_sln(const Vector &p);
|
|
static void TestSolve(FiniteElementSpace &fespace);
|
|
|
|
// Check basic functioning of variable order spaces, hp interpolation and
|
|
// some corner cases.
|
|
TEST_CASE("Variable Order FiniteElementSpace",
|
|
"[FiniteElementCollection]"
|
|
"[FiniteElementSpace]"
|
|
"[NCMesh]")
|
|
{
|
|
SECTION("Quad mesh")
|
|
{
|
|
// 2-element quad mesh
|
|
Mesh mesh = Mesh::MakeCartesian2D(2, 1, Element::QUADRILATERAL);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// standard H1 space with order 1 elements
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 6);
|
|
REQUIRE(fespace.GetNConformingDofs() == 6);
|
|
|
|
// convert to variable order space: p-refine second element
|
|
fespace.SetElementOrder(1, 2);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 11);
|
|
REQUIRE(fespace.GetNConformingDofs() == 10);
|
|
|
|
// h-refine first element in the y axis
|
|
Array<Refinement> refs;
|
|
refs.Append(Refinement(0, 2));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 13);
|
|
REQUIRE(fespace.GetNConformingDofs() == 11);
|
|
|
|
// relax the master edge to be quadratic
|
|
fespace.SetRelaxedHpConformity(true);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 13);
|
|
REQUIRE(fespace.GetNConformingDofs() == 12);
|
|
|
|
// increase order
|
|
for (int i = 0; i < mesh.GetNE(); i++)
|
|
{
|
|
fespace.SetElementOrder(i, fespace.GetElementOrder(i) + 1);
|
|
}
|
|
fespace.Update(false);
|
|
|
|
// 15 quadratic + 16 cubic DOFs - 2 shared vertices:
|
|
REQUIRE(fespace.GetNDofs() == 29);
|
|
// 3 constrained DOFs on slave side, inexact interpolation
|
|
REQUIRE(fespace.GetNConformingDofs() == 26);
|
|
|
|
// relaxed off
|
|
fespace.SetRelaxedHpConformity(false);
|
|
|
|
// new quadratic DOF on master edge:
|
|
REQUIRE(fespace.GetNDofs() == 30);
|
|
// 3 constrained DOFs on slave side, 2 on master side:
|
|
REQUIRE(fespace.GetNConformingDofs() == 25);
|
|
|
|
TestSolve(fespace);
|
|
|
|
// refine
|
|
mesh.UniformRefinement();
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 93);
|
|
REQUIRE(fespace.GetNConformingDofs() == 83);
|
|
|
|
TestSolve(fespace);
|
|
}
|
|
|
|
SECTION("Quad/hex mesh projection")
|
|
{
|
|
for (int dim=2; dim<=3; ++dim)
|
|
{
|
|
// 2-element mesh
|
|
Mesh mesh = dim == 2 ? Mesh::MakeCartesian2D(2, 1, Element::QUADRILATERAL) :
|
|
Mesh::MakeCartesian3D(2, 1, 1, Element::HEXAHEDRON);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// h-refine element 1
|
|
Array<Refinement> refinements;
|
|
refinements.Append(Refinement(1));
|
|
|
|
int nonconformity_limit = 0; // 0 meaning allow unlimited ratio
|
|
mesh.GeneralRefinement(refinements, 1, nonconformity_limit); // h-refinement
|
|
|
|
// standard H1 space with order 2 elements
|
|
H1_FECollection fec(2, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
GridFunction x(&fespace);
|
|
|
|
// p-refine element 0
|
|
fespace.SetElementOrder(0, 3);
|
|
|
|
fespace.Update(false);
|
|
x.SetSpace(&fespace);
|
|
|
|
// Test projection of the coefficient
|
|
FunctionCoefficient exsol(exact_sln);
|
|
x.ProjectCoefficient(exsol);
|
|
|
|
// Enforce space constraints on locally interpolated GridFunction x
|
|
const SparseMatrix *R = fespace.GetHpRestrictionMatrix();
|
|
const SparseMatrix *P = fespace.GetConformingProlongation();
|
|
Vector y(fespace.GetTrueVSize());
|
|
R->Mult(x, y);
|
|
P->Mult(y, x);
|
|
|
|
const double error = x.ComputeL2Error(exsol);
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
}
|
|
}
|
|
|
|
SECTION("Hex mesh")
|
|
{
|
|
// 2-element hex mesh
|
|
Mesh mesh = Mesh::MakeCartesian3D(2, 1, 1, Element::HEXAHEDRON);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// standard H1 space with order 1 elements
|
|
H1_FECollection fec(1, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 12);
|
|
REQUIRE(fespace.GetNConformingDofs() == 12);
|
|
|
|
// convert to variable order space: p-refine second element
|
|
fespace.SetElementOrder(1, 2);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 31);
|
|
REQUIRE(fespace.GetNConformingDofs() == 26);
|
|
|
|
// h-refine first element in the z axis
|
|
Array<Refinement> refs;
|
|
refs.Append(Refinement(0, 4));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 35);
|
|
REQUIRE(fespace.GetNConformingDofs() == 28);
|
|
|
|
// relax the master face to be quadratic
|
|
fespace.SetRelaxedHpConformity(true);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 35);
|
|
REQUIRE(fespace.GetNConformingDofs() == 31);
|
|
|
|
// increase order
|
|
for (int i = 0; i < mesh.GetNE(); i++)
|
|
{
|
|
fespace.SetElementOrder(i, fespace.GetElementOrder(i) + 1);
|
|
}
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 105);
|
|
REQUIRE(fespace.GetNConformingDofs() == 92);
|
|
|
|
// relaxed off
|
|
fespace.SetRelaxedHpConformity(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 108);
|
|
REQUIRE(fespace.GetNConformingDofs() == 87);
|
|
|
|
// refine one of the small elements into four
|
|
refs[0].ref_type = 3;
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update();
|
|
|
|
REQUIRE(fespace.GetNDofs() == 162);
|
|
REQUIRE(fespace.GetNConformingDofs() == 115);
|
|
|
|
TestSolve(fespace);
|
|
|
|
// lower the order of one of the four new elements to 1 - this minimum
|
|
// order will propagate through two master faces and severely constrain
|
|
// the space (since relaxed hp is off)
|
|
fespace.SetElementOrder(0, 1);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 152);
|
|
REQUIRE(fespace.GetNConformingDofs() == 92);
|
|
}
|
|
|
|
SECTION("Prism mesh")
|
|
{
|
|
// 2-element prism mesh
|
|
Mesh mesh = Mesh::MakeCartesian3D(1, 1, 1, Element::WEDGE);
|
|
mesh.EnsureNCMesh();
|
|
|
|
// standard H1 space with order 2 elements
|
|
H1_FECollection fec(2, mesh.Dimension());
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 27);
|
|
REQUIRE(fespace.GetNConformingDofs() == 27);
|
|
|
|
// convert to variable order space: p-refine first element
|
|
fespace.SetElementOrder(0, 3);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 54);
|
|
REQUIRE(fespace.GetNConformingDofs() == 42);
|
|
|
|
// refine to form an edge-face constraint similar to
|
|
// https://github.com/mfem/mfem/pull/713#issuecomment-495786362
|
|
Array<Refinement> refs;
|
|
refs.Append(Refinement(1, 3));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
|
|
refs[0].ref_type = 4;
|
|
refs.Append(Refinement(2, 4));
|
|
mesh.GeneralRefinement(refs);
|
|
fespace.Update(false);
|
|
|
|
REQUIRE(fespace.GetNDofs() == 113);
|
|
REQUIRE(fespace.GetNConformingDofs() == 67);
|
|
|
|
TestSolve(fespace);
|
|
}
|
|
}
|
|
|
|
|
|
// Exact solution: x^2 + y^2 + z^2
|
|
static double exact_sln(const Vector &p)
|
|
{
|
|
double x = p(0), y = p(1);
|
|
if (p.Size() == 3)
|
|
{
|
|
double z = p(2);
|
|
return x*x + y*y + z*z;
|
|
}
|
|
else
|
|
{
|
|
return x*x + y*y;
|
|
}
|
|
}
|
|
|
|
static double exact_rhs(const Vector &p)
|
|
{
|
|
return (p.Size() == 3) ? -6.0 : -4.0;
|
|
}
|
|
|
|
static void TestSolve(FiniteElementSpace &fespace)
|
|
{
|
|
Mesh *mesh = fespace.GetMesh();
|
|
|
|
// exact solution and RHS for the problem -\Delta u = 1
|
|
FunctionCoefficient exsol(exact_sln);
|
|
FunctionCoefficient rhs(exact_rhs);
|
|
|
|
// set up Dirichlet BC on the boundary
|
|
Array<int> ess_attr(mesh->bdr_attributes.Max());
|
|
ess_attr = 1;
|
|
|
|
Array<int> ess_tdof_list;
|
|
fespace.GetEssentialTrueDofs(ess_attr, ess_tdof_list);
|
|
|
|
GridFunction x(&fespace);
|
|
x = 0.0;
|
|
x.ProjectBdrCoefficient(exsol, ess_attr);
|
|
|
|
// assemble the linear form
|
|
LinearForm lf(&fespace);
|
|
lf.AddDomainIntegrator(new DomainLFIntegrator(rhs));
|
|
lf.Assemble();
|
|
|
|
// assemble the bilinear form.
|
|
BilinearForm bf(&fespace);
|
|
bf.AddDomainIntegrator(new DiffusionIntegrator());
|
|
bf.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
bf.FormLinearSystem(ess_tdof_list, x, lf, A, X, B);
|
|
|
|
// solve
|
|
GSSmoother M((SparseMatrix&)(*A));
|
|
PCG(*A, M, B, X, 0, 500, 1e-30, 0.0);
|
|
|
|
bf.RecoverFEMSolution(X, lf, x);
|
|
|
|
// compute L2 error from the exact solution
|
|
double error = x.ComputeL2Error(exsol);
|
|
|
|
REQUIRE(error == MFEM_Approx(0.0));
|
|
|
|
// visualize
|
|
#ifdef MFEM_UNIT_DEBUG_VISUALIZE
|
|
const char vishost[] = "localhost";
|
|
const int visport = 19916;
|
|
GridFunction *vis_x = ProlongToMaxOrder(&x);
|
|
socketstream sol_sock(vishost, visport);
|
|
sol_sock.precision(8);
|
|
sol_sock << "solution\n" << *mesh << *vis_x;
|
|
delete vis_x;
|
|
#endif
|
|
}
|
|
|
|
}
|