Files
mfem/miniapps/meshing/mesh-bounding-boxes.cpp
T

393 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.
//
// ---------------------------------------------------------------------
// Bounding Boxes Miniapp: Construct Bounding Boxes of Quad/Hex Meshes
// ---------------------------------------------------------------------
//
// This miniapp computes bounding boxes for each element in a given mesh, and
// also computes the bounds on the determinant of the Jacobian of the
// transformation for each element. The bounding approach is based on the
// method described in:
//
// (1) Section 3 of Mittal et al., "General Field Evaluation in High-Order
// Meshes on GPUs"
// and
// (2) Dzanic et al., "A method for bounding high-order finite element
// functions: Applications to mesh validity and bounds-preserving limiters".
//
//
// Compile with: make mesh-bounding-boxes
//
// Sample runs:
// mpirun -np 4 mesh-bounding-boxes -m ../../data/klein-bottle.mesh
// mpirun -np 4 mesh-bounding-boxes -m ../gslib/triple-pt-1.mesh
// mpirun -np 4 mesh-bounding-boxes -m ../../data/star-surf.mesh
// mpirun -np 4 mesh-bounding-boxes -m ../../data/fichera-q2.mesh
#include "mfem.hpp"
#include <iostream>
#include <fstream>
using namespace mfem;
using namespace std;
Mesh MakeBoundingBoxMesh(Mesh &mesh, GridFunction &nodal_bb_gf);
void GetDeterminantJacobianGF(ParMesh *mesh, ParGridFunction *detgf);
void VisualizeBB(Mesh &mesh, char *title, int pos_x, int pos_y);
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y);
int main (int argc, char *argv[])
{
// 0. Initialize MPI and HYPRE.
Mpi::Init(argc, argv);
Hypre::Init();
// Set the method's default parameters.
const char *mesh_file = "../../data/klein-bottle.mesh";
int mesh_poly_deg = 2;
bool visualization = true;
bool visit = false;
bool jacobian = true;
// Parse command-line options.
OptionsParser args(argc, argv);
args.AddOption(&mesh_file, "-m", "--mesh",
"Mesh file to use.");
args.AddOption(&mesh_poly_deg, "-o", "--order",
"Polynomial degree of mesh finite element space.");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&visit, "-visit", "--visit", "-no-visit",
"--no-visit",
"Enable or disable VisIt output.");
args.AddOption(&jacobian, "-jac", "--jacobian", "-no-jac",
"--no-jacobian",
"Compute bounds on determinant of mesh Jacobian");
args.ParseCheck();
// Initialize and refine the starting mesh.
Mesh mesh(mesh_file, 1, 1, false);
const int rdim = mesh.Dimension();
const int sdim = mesh.SpaceDimension();
ParMesh pmesh(MPI_COMM_WORLD, mesh);
if (pmesh.GetNodes() == NULL) { pmesh.SetCurvature(mesh_poly_deg); }
else { mesh_poly_deg = pmesh.GetNodes()->FESpace()->GetMaxElementOrder(); }
mesh.Clear();
// Setup finite element space and gridfunction to store bounding box
// x/y/z min & max for each element.
L2_FECollection fec_pc(0, rdim);
ParFiniteElementSpace fes_l2_bb(&pmesh, &fec_pc, sdim*2, Ordering::byVDIM);
ParGridFunction nodal_bb(&fes_l2_bb);
Array<int> vdofs;
GridFunction *nodes = pmesh.GetNodes();
int nelem = pmesh.GetNE();
// Compute bounds on nodal positions and save in nodal_bb gridfunction.
Vector lower, upper;
nodes->GetElementBounds(lower, upper, 2, -1);
for (int e = 0; e < nelem; e++)
{
fes_l2_bb.GetElementVDofs(e, vdofs);
Vector lower_upper(vdofs.Size());
for (int d = 0; d < sdim; d++)
{
lower_upper(d) = lower(e + d*nelem);
lower_upper(d+sdim) = upper(e + d*nelem);
}
nodal_bb.SetSubVector(vdofs, lower_upper);
}
// Make a mesh of bounding boxes to output.
Mesh pmesh_ser = pmesh.GetSerialMesh(0);
GridFunction nodal_bb_ser = nodal_bb.GetSerialGridFunction(0, pmesh_ser);
Mesh meshbb = MakeBoundingBoxMesh(pmesh_ser, nodal_bb_ser);
// Output in GLVis and VisIt
if (visualization && Mpi::Root())
{
char title1[] = "Input mesh";
VisualizeBB(pmesh_ser, title1, 0, 0);
char title2[] = "Bounding box mesh";
VisualizeBB(meshbb, title2, 400, 0);
}
if (visit && Mpi::Root())
{
VisItDataCollection visit_dc("bounding-box-input", &pmesh_ser);
visit_dc.SetFormat(DataCollection::SERIAL_FORMAT);
visit_dc.Save();
VisItDataCollection visit_dc_bb("bounding-box", &meshbb);
visit_dc_bb.SetFormat(DataCollection::SERIAL_FORMAT);
visit_dc_bb.Save();
}
// Print min and max bound of nodal gridfunction
int ref_factor = 4;
nodes->GetBounds(lower, upper, ref_factor);
if (Mpi::Root())
{
out << "Nodal position minimum bounds:" << endl;
lower.Print();
out << "Nodal position maximum bounds:" << endl;
upper.Print();
}
if (!jacobian) { return 0; }
// Setup gridfunction for the determinant of the Jacobian.
// Note: determinant order = rdim*mesh_order - 1 for quads/hexes
int det_order = rdim*mesh_poly_deg-1;
L2_FECollection fec_det(det_order, rdim, BasisType::GaussLobatto);
ParFiniteElementSpace fespace_det(&pmesh, &fec_det);
ParGridFunction detgf(&fespace_det);
GetDeterminantJacobianGF(&pmesh, &detgf);
// Setup piecewise constant gridfunction to save bounds on the determinant
// of the Jacobian
L2_FECollection fec_det_pc(0, rdim);
ParFiniteElementSpace fes_det_pc(&pmesh, &fec_det_pc);
ParGridFunction bounds_detgf_lower(&fes_det_pc);
ParGridFunction bounds_detgf_upper(&fes_det_pc);
// Compute bounds
detgf.GetElementBounds(bounds_detgf_lower, bounds_detgf_upper, ref_factor);
// GLVis Visualization
if (visualization)
{
char title1[] = "Determinant of Jacobian (det J)";
VisualizeField(pmesh, detgf, title1, 0, 465);
char title2[] = "Element-wise lower bound on det J";
VisualizeField(pmesh, bounds_detgf_lower, title2, 400, 465);
char title3[] = "Element-wise upper bound on det J";
VisualizeField(pmesh, bounds_detgf_upper, title3, 800, 465);
}
// Visit Visualization
if (visit)
{
VisItDataCollection visit_dc("jacobian-determinant-bounds", &pmesh);
visit_dc.SetFormat(DataCollection::PARALLEL_FORMAT);
visit_dc.RegisterField("determinant", &detgf);
visit_dc.RegisterField("det-lower-bound", &bounds_detgf_lower);
visit_dc.RegisterField("det-upper-bound", &bounds_detgf_upper);
visit_dc.Save();
}
// Print min and max bound of determinant gridfunction
detgf.GetBounds(lower, upper, ref_factor);
if (Mpi::Root())
{
out << "Jacobian determinant minimum bound: " << lower(0) << endl;
out << "Jacobian determinant maximum bound: " << upper(0) << endl;
}
return 0;
}
Mesh MakeBoundingBoxMesh(Mesh &mesh, GridFunction &nodal_bb_gf)
{
int nelem = mesh.GetNE();
int sdim = mesh.SpaceDimension();
int nverts = pow(2,sdim)*nelem;
Mesh meshbb(sdim, nverts, nelem, 0, sdim);
int eidx = 0;
int vidx = 0;
for (int e = 0; e < nelem; e++)
{
Vector xyzminmax_el;
nodal_bb_gf.GetElementDofValues(e, xyzminmax_el);
if (sdim == 2)
{
Vector xyz(2);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(1);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(2);
xyz(1) = xyzminmax_el(1);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(2);
xyz(1) = xyzminmax_el(3);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(3);
meshbb.AddVertex(xyz);
const int inds[4] = {vidx++, vidx++, vidx++, vidx++};
int attr = eidx+1;
meshbb.AddQuad(inds, attr);
eidx++;
}
else if (sdim == 3)
{
Vector xyz(3);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(2);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(1);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(3);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
xyz(0) = xyzminmax_el(0);
xyz(1) = xyzminmax_el(4);
xyz(2) = xyzminmax_el(5);
meshbb.AddVertex(xyz);
const int inds[8] = {vidx++, vidx++, vidx++, vidx++,
vidx++, vidx++, vidx++, vidx++
};
meshbb.AddHex(inds, (eidx++)+1);
}
}
if (sdim == 2)
{
meshbb.FinalizeQuadMesh(1, 1, true);
}
else
{
meshbb.FinalizeHexMesh(1, 1, true);
}
return meshbb;
}
IntegrationRule PermuteIR(const IntegrationRule &irule,
const Array<int> ordering)
{
const int np = irule.GetNPoints();
MFEM_VERIFY(np == ordering.Size(), "Invalid permutation size");
IntegrationRule ir(np);
ir.SetOrder(irule.GetOrder());
for (int i = 0; i < np; i++)
{
IntegrationPoint &ip_new = ir.IntPoint(i);
const IntegrationPoint &ip_old = irule.IntPoint(ordering[i]);
ip_new.Set(ip_old.x, ip_old.y, ip_old.z, ip_old.weight);
}
return ir;
}
void GetDeterminantJacobianGF(ParMesh *mesh, ParGridFunction *detgf)
{
int dim = mesh->Dimension();
FiniteElementSpace *fespace = detgf->FESpace();
Array<int> dofs;
for (int e = 0; e < mesh->GetNE(); e++)
{
const FiniteElement *fe = fespace->GetFE(e);
const IntegrationRule ir = fe->GetNodes();
ElementTransformation *transf = mesh->GetElementTransformation(e);
DenseMatrix Jac(fe->GetDim());
const NodalFiniteElement *nfe = dynamic_cast<const NodalFiniteElement*>
(fe);
const Array<int> &irordering = nfe->GetLexicographicOrdering();
IntegrationRule ir2 = irordering.Size() ?
PermuteIR(ir, irordering) :
ir;
Vector detvals(ir2.GetNPoints());
Vector loc(dim);
for (int q = 0; q < ir2.GetNPoints(); q++)
{
IntegrationPoint ip = ir2.IntPoint(q);
transf->SetIntPoint(&ip);
transf->Transform(ip, loc);
Jac = transf->Jacobian();
detvals(q) = Jac.Weight();
}
fespace->GetElementDofs(e, dofs);
if (irordering.Size())
{
for (int i = 0; i < dofs.Size(); i++)
{
(*detgf)(dofs[i]) = detvals(irordering[i]);
}
}
else
{
detgf->SetSubVector(dofs, detvals);
}
}
}
void VisualizeBB(Mesh &mesh, char *title, int pos_x, int pos_y)
{
socketstream sock;
sock.open("localhost", 19916);
sock << "mesh\n";
mesh.Print(sock);
std::string keystrokes = mesh.SpaceDimension() == 2 ? "keys em" : "keys )";
sock << "window_title '"<< title << "'\n"
<< "window_geometry "
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
// << "keys jRmclA//]]]]]]]]" << endl;
<< keystrokes << endl;
}
void VisualizeField(ParMesh &pmesh, ParGridFunction &input,
char *title, int pos_x, int pos_y)
{
socketstream sock;
if (pmesh.GetMyRank() == 0)
{
sock.open("localhost", 19916);
sock << "solution\n";
}
pmesh.PrintAsOne(sock);
input.SaveAsOne(sock);
if (pmesh.GetMyRank() == 0)
{
sock << "window_title '"<< title << "'\n"
<< "window_geometry "
<< pos_x << " " << pos_y << " " << 400 << " " << 400 << "\n"
<< "keys jRmclApppppppppppp//]]]]]]]]" << endl;
}
}