Files
mfem/miniapps/tools/display-basis.cpp
T
2025-08-31 09:48:16 -07:00

898 lines
23 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.
//
// ----------------------------------------------------------------
// Display Basis Miniapp: Visualize finite element basis functions
// ----------------------------------------------------------------
//
// This miniapp visualizes various types of finite element basis functions on a
// single mesh element in 1D, 2D and 3D. The order and the type of finite
// element space can be changed, and the mesh element is either the reference
// one, or a simple transformation of it. Dynamic creation and interaction with
// multiple GLVis windows is demonstrated.
//
// Compile with: make display-basis
//
// Sample runs: display-basis
// display-basis -e 2 -b 3 -o 3
// display-basis -e 5 -b 1 -o 1
// display-basis -e 3 -b 7 -o 3
// display-basis -e 3 -b 7 -o 5 -only 16
#include "mfem.hpp"
#include "../common/mfem-common.hpp"
#include <vector>
#include <iostream>
using namespace std;
using namespace mfem;
using namespace mfem::common;
// Data structure used to collect visualization window layout parameters
struct VisWinLayout
{
int nx;
int ny;
int w;
int h;
};
// Data structure used to define simple coordinate transformations
struct DeformationData
{
real_t uniformScale;
int squeezeAxis;
real_t squeezeFactor;
int shearAxis;
Vector shearVec;
};
/** The Deformation class implements three simple coordinate transformations:
Uniform Scaling:
u = a v for a scalar constant 'a'
Compression or Squeeze (along a coordinate axis):
/ 1/b 0 \ / 1/b 0 0 \ for a scalar constant b
u = \ 0 b / v or u = | 0 c 0 | v, and c = sqrt(b)
\ 0 0 c / the axis can also be chosen
Shear:
u = v + v_i * s where 's' is the shear vector
and 'i' is the shear axis
*/
class Deformation : public VectorCoefficient
{
public:
enum DefType {INVALID, UNIFORM, SQUEEZE, SHEAR};
Deformation(int dim, DefType dType, const DeformationData & data)
: VectorCoefficient(dim), dim_(dim), dType_(dType), data_(data) {}
void Eval(Vector &v, ElementTransformation &T,
const IntegrationPoint &ip) override;
using VectorCoefficient::Eval;
private:
void Def1D(const Vector & u, Vector & v);
void Def2D(const Vector & u, Vector & v);
void Def3D(const Vector & u, Vector & v);
int dim_;
DefType dType_;
const DeformationData & data_;
};
string elemTypeStr(const Element::Type & eType);
inline bool elemIs1D(const Element::Type & eType);
inline bool elemIs2D(const Element::Type & eType);
inline bool elemIs3D(const Element::Type & eType);
string basisTypeStr(char bType);
inline bool basisIs1D(char bType);
inline bool basisIs2D(char bType);
inline bool basisIs3D(char bType);
string mapTypeStr(int mType);
int update_basis(vector<socketstream*> & sock, const VisWinLayout & vwl,
Element::Type e, char bType, int bOrder, int mType,
Deformation::DefType dType, const DeformationData & defData,
bool visualization, int &onlySome, int visport = 19916);
int main(int argc, char *argv[])
{
// Parse command-line options.
Element::Type eType = Element::TRIANGLE;
char bType = 'h';
int bOrder = 2;
int mType = 0;
int eInt = -1;
int bInt = -1;
VisWinLayout vwl;
vwl.nx = 5;
vwl.ny = 3;
vwl.w = 250;
vwl.h = 250;
Deformation::DefType dType = Deformation::INVALID;
DeformationData defData;
bool visualization = true;
int onlySome = -1;
int visport = 19916;
vector<socketstream*> sock;
OptionsParser args(argc, argv);
args.AddOption(&eInt, "-e", "--elem-type",
"Element Type: (1-Segment, 2-Triangle, 3-Quadrilateral, "
"4-Tetrahedron, 5-Hexahedron, 6-Wedge"
/* , 7-Pyramid not currently supported */
")");
args.AddOption(&bInt, "-b", "--basis-type",
"Basis Function Type (0-H1, 1-Nedelec, 2-Raviart-Thomas, "
"3-L2, 4-Fixed Order Cont.,\n\t5-Gaussian Discontinuous (2D),"
" 6-Crouzeix-Raviart, 7-Serendipity)");
args.AddOption(&bOrder, "-o", "--order", "Basis function order");
args.AddOption(&vwl.nx, "-nx", "--num-win-x",
"Number of Viz windows in X");
args.AddOption(&vwl.ny, "-ny", "--num-win-y",
"Number of Viz windows in y");
args.AddOption(&vwl.w, "-w", "--width",
"Width of Viz windows");
args.AddOption(&vwl.h, "-h", "--height",
"Height of Viz windows");
args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
"--no-visualization",
"Enable or disable GLVis visualization.");
args.AddOption(&onlySome, "-only", "--onlySome",
"Only view 10 dofs, starting with the specified one.");
args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
args.Parse();
if (!args.Good())
{
args.PrintUsage(cout);
return 1;
}
{
args.PrintOptions(cout);
}
if ( eInt > 0 && eInt < 7 )
{
eType = (Element::Type)eInt;
}
switch (bInt)
{
case 0:
bType = 'h';
break;
case 1:
bType = 'n';
break;
case 2:
bType = 'r';
break;
case 3:
bType = 'l';
break;
case 4:
bType = 'f';
break;
case 5:
bType = 'g';
break;
case 6:
bType = 'c';
break;
case 7:
bType = 's';
break;
default:
bType = 'h';
}
// Collect user input
bool print_char = true;
while (true)
{
if (print_char)
{
cout << endl;
cout << "Element Type: " << elemTypeStr(eType) << endl;
cout << "Basis Type: " << basisTypeStr(bType) << endl;
cout << "Basis function order: " << bOrder << endl;
cout << "Map Type: " << mapTypeStr(mType) << endl;
}
if ( update_basis(sock, vwl, eType, bType, bOrder, mType,
dType, defData, visualization, onlySome, visport) )
{
cerr << "Invalid combination of basis info (try again)" << endl;
}
if (!visualization) { break; }
print_char = false;
cout << endl;
cout << "What would you like to do?\n"
"q) Quit\n"
"c) Close Windows and Quit\n"
"e) Change Element Type\n"
"b) Change Basis Type\n";
if ( bType == 'h' || bType == 'p' || bType == 'n' || bType == 'r' ||
bType == 'l' || bType == 'f' || bType == 'g' || bType == 's')
{
cout << "o) Change Basis Order\n";
}
// The following is disabled pending updates to GLVis
if ( bType == 'l' && false )
{
cout << "m) Change Map Type\n";
}
cout << "t) Transform Element\n";
cout << "--> " << flush;
char mk;
cin >> mk;
if (mk == 'q')
{
break;
}
if (mk == 'c')
{
for (unsigned int i=0; i<sock.size(); i++)
{
*sock[i] << "keys q";
}
break;
}
if (mk == 'e')
{
eInt = 0;
cout << "valid element types:\n";
if ( basisIs1D(bType) )
{
cout <<
"1) Segment\n";
}
if ( basisIs2D(bType) )
{
cout <<
"2) Triangle\n"
"3) Quadrilateral\n";
}
if ( basisIs3D(bType) )
{
cout <<
"4) Tetrahedron\n"
"5) Hexahedron\n"
"6) Wedge\n"
"7) Pyramid (** not currently supported **)\n";
}
cout << "enter new element type --> " << flush;
cin >> eInt;
if ( eInt <= 0 || eInt > 7 )
{
cout << "invalid element type \"" << eInt << "\"" << endl << flush;
}
else if ( (elemIs1D((Element::Type)eInt) && basisIs1D(bType)) ||
(elemIs2D((Element::Type)eInt) && basisIs2D(bType)) ||
(elemIs3D((Element::Type)eInt) && basisIs3D(bType)) )
{
if ( (elemIs1D((Element::Type)eInt) && !elemIs1D(eType)) ||
(elemIs2D((Element::Type)eInt) && !elemIs2D(eType)) ||
(elemIs3D((Element::Type)eInt) && !elemIs3D(eType)) )
{
dType = Deformation::INVALID;
}
eType = (Element::Type)eInt;
print_char = true;
}
else
{
cout << "invalid element type \"" << eInt <<
"\" for basis type \"" << basisTypeStr(bType) << "\"." << endl;
}
}
if (mk == 'b')
{
char bChar = 0;
cout << "valid basis types:\n";
cout << "h) H1 Finite Element\n";
cout << "p) H1 Positive Finite Element\n";
if ( elemIs2D(eType) || elemIs3D(eType) )
{
cout << "s) H1 Serendipity Finite Element\n";
cout << "n) Nedelec Finite Element\n";
cout << "r) Raviart-Thomas Finite Element\n";
}
cout << "l) L2 Finite Element\n";
if ( elemIs1D(eType) || elemIs2D(eType) )
{
cout << "c) Crouzeix-Raviart Finite Element\n";
}
cout << "f) Fixed Order Continuous Finite Element\n";
if ( elemIs2D(eType) )
{
cout << "g) Gauss Discontinuous Finite Element\n";
}
cout << "enter new basis type --> " << flush;
cin >> bChar;
if (bChar == 'h' || bChar == 'p' || bChar == 'l' || bChar == 'f' ||
bChar == 's' ||
((bChar == 'n' || bChar == 'r') && (elemIs2D(eType) || elemIs3D(eType))) ||
(bChar == 'c' && (elemIs1D(eType) || elemIs2D(eType))) ||
(bChar == 'g' && elemIs2D(eType)))
{
bType = bChar;
if ( bType == 'h' )
{
mType = FiniteElement::VALUE;
}
else if ( bType == 'p' )
{
mType = FiniteElement::VALUE;
}
else if (bType == 's')
{
mType = FiniteElement::VALUE;
}
else if ( bType == 'n' )
{
mType = FiniteElement::H_CURL;
}
else if ( bType == 'r' )
{
mType = FiniteElement::H_DIV;
}
else if ( bType == 'l' )
{
if ( mType != FiniteElement::VALUE &&
mType != FiniteElement::INTEGRAL )
{
mType = FiniteElement::VALUE;
}
}
else if ( bType == 'c' )
{
bOrder = 1;
mType = FiniteElement::VALUE;
}
else if ( bType == 'f' )
{
if ( bOrder < 1 || bOrder > 3)
{
bOrder = 1;
}
mType = FiniteElement::VALUE;
}
else if ( bType == 'g' )
{
if ( bOrder < 1 || bOrder > 2)
{
bOrder = 1;
}
mType = FiniteElement::VALUE;
}
print_char = true;
}
else
{
cout << "invalid basis type \"" << bChar << "\"." << endl;
}
}
if (mk == 'm' && bType == 'l')
{
int mInt = 0;
cout << "valid map types:\n"
"0) VALUE\n"
"1) INTEGRAL\n";
cout << "enter new map type --> " << flush;
cin >> mInt;
if (mInt >=0 && mInt <= 1)
{
mType = mInt;
print_char = true;
}
else
{
cout << "invalid map type \"" << mInt << "\"." << endl;
}
}
if (mk == 'o')
{
int oInt = 1;
int oMin = ( bType == 'h' || bType == 'p' || bType == 'n' ||
bType == 'f' || bType == 'g' || bType == 's')?1:0;
int oMax = -1;
switch (bType)
{
case 'g':
oMax = 2;
break;
case 'f':
oMax = 3;
break;
default:
oMax = -1;
}
cout << "basis function order must be >= " << oMin;
if ( oMax >= 0 )
{
cout << " and <= " << oMax;
}
cout << endl;
cout << "enter new basis function order --> " << flush;
cin >> oInt;
if ( oInt >= oMin && oInt <= ((oMax>=0)?oMax:oInt) )
{
bOrder = oInt;
print_char = true;
}
else
{
cout << "invalid basis order \"" << oInt << "\"." << endl;
}
}
if (mk == 't')
{
cout << "transformation options:\n";
cout << "r) reset to reference element\n";
cout << "u) uniform scaling\n";
if ( elemIs2D(eType) || elemIs3D(eType) )
{
cout << "c) compression\n";
cout << "s) shear\n";
}
cout << "enter transformation type --> " << flush;
char tk;
cin >> tk;
if (tk == 'r')
{
dType = Deformation::INVALID;
}
else if (tk == 'u')
{
cout << "enter scaling constant --> " << flush;
cin >> defData.uniformScale;
if ( defData.uniformScale > 0.0 )
{
dType = Deformation::UNIFORM;
}
}
else if (tk == 'c' && !elemIs1D(eType))
{
int dim = elemIs2D(eType)?2:3;
cout << "enter compression factor --> " << flush;
cin >> defData.squeezeFactor;
cout << "enter compression axis (0-" << dim-1 << ") --> " << flush;
cin >> defData.squeezeAxis;
if ( defData.squeezeFactor > 0.0 &&
(defData.squeezeAxis >= 0 && defData.squeezeAxis < dim))
{
dType = Deformation::SQUEEZE;
}
}
else if (tk == 's' && !elemIs1D(eType))
{
int dim = elemIs2D(eType)?2:3;
cout << "enter shear vector (components separated by spaces) --> "
<< flush;
defData.shearVec.SetSize(dim);
for (int i=0; i<dim; i++)
{
cin >> defData.shearVec[i];
}
cout << "enter shear axis (0-" << dim-1 << ") --> " << flush;
cin >> defData.shearAxis;
if ( defData.shearAxis >= 0 && defData.shearAxis < dim )
{
dType = Deformation::SHEAR;
}
}
}
}
// Cleanup
for (unsigned int i=0; i<sock.size(); i++)
{
delete sock[i];
}
// Exit
return 0;
}
string elemTypeStr(const Element::Type & eType)
{
switch (eType)
{
case Element::POINT:
return "POINT";
case Element::SEGMENT:
return "SEGMENT";
case Element::TRIANGLE:
return "TRIANGLE";
case Element::QUADRILATERAL:
return "QUADRILATERAL";
case Element::TETRAHEDRON:
return "TETRAHEDRON";
case Element::HEXAHEDRON:
return "HEXAHEDRON";
case Element::WEDGE:
return "WEDGE";
case Element::PYRAMID:
return "PYRAMID";
default:
return "INVALID";
};
}
bool
elemIs1D(const Element::Type & eType)
{
return eType == Element::SEGMENT;
}
bool
elemIs2D(const Element::Type & eType)
{
return eType == Element::TRIANGLE || eType == Element::QUADRILATERAL;
}
bool
elemIs3D(const Element::Type & eType)
{
return eType == Element::TETRAHEDRON || eType == Element::HEXAHEDRON ||
eType == Element::WEDGE || eType == Element::PYRAMID;
}
string
basisTypeStr(char bType)
{
switch (bType)
{
case 'h':
return "Continuous (H1)";
case 'p':
return "Continuous Positive (H1)";
case 's':
return "Continuous Serendipity (H1)";
case 'n':
return "Nedelec";
case 'r':
return "Raviart-Thomas";
case 'l':
return "Discontinuous (L2)";
case 'f':
return "Fixed Order Continuous";
case 'g':
return "Gaussian Discontinuous";
case 'c':
return "Crouzeix-Raviart";
default:
return "INVALID";
};
}
bool
basisIs1D(char bType)
{
return bType == 'h' || bType == 'p' || bType == 'l' || bType == 'c' ||
bType == 'f';
}
bool
basisIs2D(char bType)
{
return bType == 'h' || bType == 'p' || bType == 'n' || bType == 'r' ||
bType == 'l' || bType == 'c' || bType == 'f' || bType == 'g' ||
bType == 's';
}
bool
basisIs3D(char bType)
{
return bType == 'h' || bType == 'p' || bType == 'n' || bType == 'r' ||
bType == 'f' || bType == 'l';
}
string
mapTypeStr(int mType)
{
switch (mType)
{
case FiniteElement::VALUE:
return "VALUE";
case FiniteElement::H_CURL:
return "H_CURL";
case FiniteElement::H_DIV:
return "H_DIV";
case FiniteElement::INTEGRAL:
return "INTEGRAL";
default:
return "INVALID";
}
}
void
Deformation::Eval(Vector &v, ElementTransformation &T,
const IntegrationPoint &ip)
{
Vector u(dim_);
T.Transform(ip, u);
switch (dim_)
{
case 1:
Def1D(u, v);
break;
case 2:
Def2D(u, v);
break;
case 3:
Def3D(u, v);
break;
}
}
void
Deformation::Def1D(const Vector & u, Vector & v)
{
v = u;
if ( dType_ == UNIFORM )
{
v *= data_.uniformScale;
}
}
void
Deformation::Def2D(const Vector & u, Vector & v)
{
switch (dType_)
{
case UNIFORM:
v = u;
v *= data_.uniformScale;
break;
case SQUEEZE:
v = u;
v[ data_.squeezeAxis ] /= data_.squeezeFactor;
v[(data_.squeezeAxis+1)%2] *= data_.squeezeFactor;
break;
case SHEAR:
v = u;
v.Add(v[data_.shearAxis], data_.shearVec);
break;
default:
v = u;
}
}
void
Deformation::Def3D(const Vector & u, Vector & v)
{
switch (dType_)
{
case UNIFORM:
v = u;
v *= data_.uniformScale;
break;
case SQUEEZE:
v = u;
v[ data_.squeezeAxis ] /= data_.squeezeFactor;
v[(data_.squeezeAxis+1)%2] *= sqrt(data_.squeezeFactor);
v[(data_.squeezeAxis+2)%2] *= sqrt(data_.squeezeFactor);
break;
case SHEAR:
v = u;
v.Add(v[data_.shearAxis], data_.shearVec);
break;
default:
v = u;
}
}
int
update_basis(vector<socketstream*> & sock, const VisWinLayout & vwl,
Element::Type e, char bType, int bOrder, int mType,
Deformation::DefType dType, const DeformationData & defData,
bool visualization, int &onlySome, int visport)
{
bool vec = false;
Mesh *mesh;
ElementMeshStream imesh(e);
if (!imesh)
{
{
cerr << "\nProblem with meshstream object\n" << endl;
}
return 2;
}
mesh = new Mesh(imesh, 1, 1);
int dim = mesh->Dimension();
if ( dType != Deformation::INVALID )
{
Deformation defCoef(dim, dType, defData);
mesh->Transform(defCoef);
}
FiniteElementCollection * FEC = NULL;
switch (bType)
{
case 'h':
FEC = new H1_FECollection(bOrder, dim);
vec = false;
break;
case 'p':
FEC = new H1Pos_FECollection(bOrder, dim);
vec = false;
break;
case 's':
if (bOrder == 1)
{
FEC = new H1_FECollection(bOrder, dim);
}
else
{
FEC = new H1Ser_FECollection(bOrder, dim);
}
vec = false;
break;
case 'n':
FEC = new ND_FECollection(bOrder, dim);
vec = true;
break;
case 'r':
FEC = new RT_FECollection(bOrder-1, dim);
vec = true;
break;
case 'l':
FEC = new L2_FECollection(bOrder, dim, BasisType::GaussLegendre,
mType);
vec = false;
break;
case 'c':
FEC = new CrouzeixRaviartFECollection();
break;
case 'f':
if ( bOrder == 1 )
{
FEC = new LinearFECollection();
}
else if ( bOrder == 2 )
{
FEC = new QuadraticFECollection();
}
else if ( bOrder == 3 )
{
FEC = new CubicFECollection();
}
break;
case 'g':
if ( bOrder == 1 )
{
FEC = new GaussLinearDiscont2DFECollection();
}
else if ( bOrder == 2 )
{
FEC = new GaussQuadraticDiscont2DFECollection();
}
break;
}
if ( FEC == NULL)
{
delete mesh;
return 1;
}
FiniteElementSpace FESpace(mesh, FEC);
int ndof = FESpace.GetVSize();
Array<int> vdofs;
FESpace.GetElementVDofs(0,vdofs);
char vishost[] = "localhost";
int offx = vwl.w+10, offy = vwl.h+45; // window offsets
for (unsigned int i=0; i<sock.size(); i++)
{
*sock[i] << "keys q";
delete sock[i];
}
sock.resize(ndof);
for (int i=0; i<ndof; i++)
{
sock[i] = new socketstream; sock[i]->precision(8);
}
GridFunction ** x = new GridFunction*[ndof];
for (int i=0; i<ndof; i++)
{
x[i] = new GridFunction(&FESpace);
*x[i] = 0.0;
if ( vdofs[i] < 0 )
{
(*x[i])(-1-vdofs[i]) = -1.0;
}
else
{
(*x[i])(vdofs[i]) = 1.0;
}
}
int ref = 0;
int exOrder = 0;
if ( bType == 'n' ) { exOrder++; }
if ( bType == 'r' ) { exOrder += 2; }
while ( 1<<ref < bOrder + exOrder || ref == 0 )
{
mesh->UniformRefinement();
FESpace.Update();
for (int i=0; i<ndof; i++)
{
x[i]->Update();
}
ref++;
}
int stopAt = ndof;
if (ndof > 25 && onlySome == -1)
{
cout << endl;
cout << "There are more than 25 windows to open.\n"
<< "Only showing Dofs 1-10 to avoid crashing.\n"
<< "Use the option -only N to show Dofs N to N+9 instead.\n";
onlySome = 1;
}
for (int i = 0; i < stopAt; i++)
{
if (i ==0 && onlySome > 0 && onlySome <ndof)
{
i = onlySome-1;
stopAt = min(ndof,onlySome+9);
}
ostringstream oss;
oss << "DoF " << i + 1;
if (visualization)
{
VisualizeField(*sock[i], vishost, visport, *x[i], oss.str().c_str(),
(i % vwl.nx) * offx, ((i / vwl.nx) % vwl.ny) * offy,
vwl.w, vwl.h,
"aaAc", vec);
}
}
for (int i=0; i<ndof; i++)
{
delete x[i];
}
delete [] x;
delete FEC;
delete mesh;
return 0;
}