268 lines
8.4 KiB
C++
268 lines
8.4 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.
|
|
//
|
|
// -----------------------------------------------------------------
|
|
// 3:1 Refinement Miniapp: Perform 3:1 anisotropic mesh refinements
|
|
// -----------------------------------------------------------------
|
|
//
|
|
// This miniapp performs random 3:1 refinements of a quadrilateral or hexahedral
|
|
// mesh. A diffusion equation is solved in an H1 finite element space defined on
|
|
// the refined mesh, and its continuity is verified.
|
|
//
|
|
// Compile with: make ref321
|
|
//
|
|
// Sample runs: ref321 -mm -dim 2 -o 2 -r 100
|
|
// ref321 -mm -dim 3 -o 2 -r 100
|
|
// ref321 -m ../../data/star.mesh -o 2 -r 100
|
|
|
|
#include "mfem.hpp"
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
using namespace std;
|
|
using namespace mfem;
|
|
|
|
real_t CheckH1Continuity(GridFunction & x);
|
|
|
|
// Find the two children of parent element `elem` after its refinement in one
|
|
// direction.
|
|
void FindChildren(const Mesh & mesh, int elem, Array<int> & children)
|
|
{
|
|
const CoarseFineTransformations& cf = mesh.ncmesh->GetRefinementTransforms();
|
|
MFEM_ASSERT(mesh.GetNE() == cf.embeddings.Size(), "");
|
|
|
|
// Note that row `elem` of the table constructed by cf.MakeCoarseToFineTable
|
|
// is an alternative to this global loop, but constructing the table is also
|
|
// a global operation with global storage.
|
|
for (int i = 0; i < mesh.GetNE(); i++)
|
|
{
|
|
const int p = cf.embeddings[i].parent;
|
|
if (p == elem)
|
|
{
|
|
children.Append(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Refine 3:1 via 2 refinements with scalings 2/3 and 1/2.
|
|
void Refine31(Mesh & mesh, int elem, char type)
|
|
{
|
|
Array<Refinement> refs; // Refinement is defined in ncmesh.hpp
|
|
refs.Append(Refinement(elem, type, 2.0 / 3.0));
|
|
mesh.GeneralRefinement(refs);
|
|
|
|
// Find the elements with parent `elem`
|
|
Array<int> children;
|
|
FindChildren(mesh, elem, children);
|
|
MFEM_ASSERT(children.Size() == 2, "");
|
|
|
|
const int elem1 = children[0];
|
|
|
|
refs.SetSize(0);
|
|
refs.Append(Refinement(elem1, type)); // Default scaling of 0.5
|
|
mesh.GeneralRefinement(refs);
|
|
}
|
|
|
|
// Deterministic, somewhat random integer generator
|
|
int MyRand(int & s)
|
|
{
|
|
s++;
|
|
const double a = 1000 * sin(s * 1.1234 * M_PI);
|
|
return int(std::abs(a));
|
|
}
|
|
|
|
// Randomly select elements for 3:1 refinements in random directions.
|
|
void TestAnisoRefRandom(int iter, int dim, Mesh & mesh)
|
|
{
|
|
int seed = 0;
|
|
for (int i = 0; i < iter; i++)
|
|
{
|
|
const int elem = MyRand(seed) % mesh.GetNE();
|
|
const int t = MyRand(seed) % dim;
|
|
auto type = t == 0 ? Refinement::X :
|
|
(t == 1 ? Refinement::Y : Refinement::Z);
|
|
Refine31(mesh, elem, type);
|
|
}
|
|
|
|
mesh.EnsureNodes();
|
|
mesh.SetScaledNCMesh();
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
// 1. Parse command-line options.
|
|
const char *mesh_file = "../../data/star.mesh";
|
|
int order = 1;
|
|
bool visualization = true;
|
|
bool makeMesh = false;
|
|
int num_refs = 1;
|
|
int tdim = 2; // Mesh dimension
|
|
|
|
OptionsParser args(argc, argv);
|
|
args.AddOption(&mesh_file, "-m", "--mesh",
|
|
"Mesh file to use.");
|
|
args.AddOption(&order, "-o", "--order",
|
|
"Finite element order (polynomial degree) or -1 for"
|
|
" isoparametric space.");
|
|
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
|
|
"--no-visualization",
|
|
"Enable or disable GLVis visualization.");
|
|
args.AddOption(&makeMesh, "-mm", "--make-mesh", "-no-mm",
|
|
"--no-make-mesh", "Create Cartesian mesh");
|
|
args.AddOption(&tdim, "-dim", "--dimension", "Dimension for Cartesian mesh");
|
|
args.AddOption(&num_refs, "-r", "--refs", "Number of 3:1 refinements");
|
|
args.Parse();
|
|
if (!args.Good())
|
|
{
|
|
args.PrintUsage(cout);
|
|
return 1;
|
|
}
|
|
args.PrintOptions(cout);
|
|
|
|
// 2. Create or read the mesh from the given mesh file.
|
|
Mesh mesh;
|
|
if (makeMesh)
|
|
{
|
|
mesh = tdim == 3 ? Mesh::MakeCartesian3D(2, 2, 2, Element::HEXAHEDRON) :
|
|
Mesh::MakeCartesian2D(2, 2, Element::QUADRILATERAL);
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::LoadFromFile(mesh_file, 1, 1);
|
|
}
|
|
|
|
const int dim = mesh.Dimension();
|
|
|
|
// 3. Randomly perform 3:1 refinements in the mesh.
|
|
TestAnisoRefRandom(num_refs, tdim, mesh);
|
|
|
|
// 4. Define a finite element space on the mesh. Here we use continuous
|
|
// Lagrange finite elements of the specified order.
|
|
H1_FECollection fec(order, dim);
|
|
FiniteElementSpace fespace(&mesh, &fec);
|
|
cout << "Number of finite element unknowns: "
|
|
<< fespace.GetTrueVSize() << endl;
|
|
|
|
// 5. Define the solution vector x as a finite element grid function
|
|
// corresponding to fespace. Solve the Poisson problem, as in ex1.
|
|
GridFunction x(&fespace);
|
|
|
|
{
|
|
x = 0.0;
|
|
LinearForm b(&fespace);
|
|
ConstantCoefficient one(1.0);
|
|
b.AddDomainIntegrator(new DomainLFIntegrator(one));
|
|
b.Assemble();
|
|
|
|
BilinearForm a(&fespace);
|
|
a.AddDomainIntegrator(new DiffusionIntegrator());
|
|
a.Assemble();
|
|
|
|
OperatorPtr A;
|
|
Vector B, X;
|
|
Array<int> ess_tdof_list;
|
|
if (mesh.bdr_attributes.Size())
|
|
{
|
|
Array<int> ess_bdr(mesh.bdr_attributes.Max());
|
|
ess_bdr = 1;
|
|
fespace.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);
|
|
}
|
|
|
|
a.FormLinearSystem(ess_tdof_list, x, b, A, X, B);
|
|
|
|
GSSmoother M((SparseMatrix&)(*A));
|
|
PCG(*A, M, B, X, 1, 2000, 1e-12, 0.0);
|
|
a.RecoverFEMSolution(X, b, x);
|
|
}
|
|
|
|
// 6. Verify the continuity of the projected function in H1.
|
|
const real_t h1err = CheckH1Continuity(x);
|
|
cout << "Error of H1 continuity: " << h1err << endl;
|
|
MFEM_VERIFY(h1err < 1.0e-7, "");
|
|
|
|
// 7. Save the refined mesh and the solution. This output can be viewed later
|
|
// using GLVis: "glvis -m ref321.mesh -g sol.gf".
|
|
ofstream mesh_ofs("ref321.mesh");
|
|
mesh_ofs.precision(8);
|
|
mesh.Print(mesh_ofs);
|
|
ofstream sol_ofs("sol.gf");
|
|
sol_ofs.precision(8);
|
|
x.Save(sol_ofs);
|
|
|
|
// 8. Send the solution by socket to a GLVis server.
|
|
if (visualization)
|
|
{
|
|
char vishost[] = "localhost";
|
|
int visport = 19916;
|
|
socketstream sol_sock(vishost, visport);
|
|
sol_sock.precision(8);
|
|
sol_sock << "solution\n" << mesh << x << flush;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
real_t CheckH1Continuity(GridFunction & x)
|
|
{
|
|
const FiniteElementSpace *fes = x.FESpace();
|
|
Mesh *mesh = fes->GetMesh();
|
|
|
|
const int dim = mesh->Dimension();
|
|
|
|
// Following the example of KellyErrorEstimator::ComputeEstimates(), we loop
|
|
// over interior faces and then shared faces.
|
|
|
|
// Compute error contribution from local interior faces
|
|
real_t errorMax = 0.0;
|
|
for (int f = 0; f < mesh->GetNumFaces(); f++)
|
|
{
|
|
if (mesh->FaceIsInterior(f))
|
|
{
|
|
int Inf1, Inf2, NCFace;
|
|
mesh->GetFaceInfos(f, &Inf1, &Inf2, &NCFace);
|
|
|
|
auto FT = mesh->GetFaceElementTransformations(f);
|
|
|
|
const int faceOrder = dim == 3 ? fes->GetFaceOrder(f) :
|
|
fes->GetEdgeOrder(f);
|
|
auto &int_rule = IntRules.Get(FT->FaceGeom, 2 * faceOrder);
|
|
const auto nip = int_rule.GetNPoints();
|
|
|
|
// Convention:
|
|
// * Conforming face: Face side with smaller element id handles the
|
|
// integration
|
|
// * Non-conforming face: The slave handles the integration.
|
|
// See FaceInfo documentation for details.
|
|
bool isNCSlave = FT->Elem2No >= 0 && NCFace >= 0;
|
|
bool isConforming = FT->Elem2No >= 0 && NCFace == -1;
|
|
if ((FT->Elem1No < FT->Elem2No && isConforming) || isNCSlave)
|
|
{
|
|
for (int i = 0; i < nip; i++)
|
|
{
|
|
const auto &fip = int_rule.IntPoint(i);
|
|
IntegrationPoint ip;
|
|
|
|
FT->Loc1.Transform(fip, ip);
|
|
const real_t v1 = x.GetValue(FT->Elem1No, ip);
|
|
|
|
FT->Loc2.Transform(fip, ip);
|
|
const real_t v2 = x.GetValue(FT->Elem2No, ip);
|
|
|
|
const real_t err_i = std::abs(v1 - v2);
|
|
errorMax = std::max(errorMax, err_i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return errorMax;
|
|
}
|