Files
mfem/mesh/mesh_readers.cpp
T
2026-01-07 10:42:45 -08:00

4468 lines
156 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 "mesh_headers.hpp"
#include "ncnurbs.hpp"
#include "../fem/fem.hpp"
#include "../general/binaryio.hpp"
#include "../general/text.hpp"
#include "../general/tinyxml2.h"
#include "gmsh.hpp"
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <map>
#ifdef MFEM_USE_NETCDF
#include "netcdf.h"
#endif
#ifdef MFEM_USE_ZLIB
#include <zlib.h>
#endif
using namespace std;
namespace mfem
{
bool Mesh::remove_unused_vertices = true;
void Mesh::ReadMFEMMesh(std::istream &input, int version, int &curved)
{
// Read MFEM mesh v1.0, v1.2, or v1.3 format
MFEM_VERIFY(version == 10 || version == 12 || version == 13,
"unknown MFEM mesh version");
string ident;
// read lines beginning with '#' (comments)
skip_comment_lines(input, '#');
input >> ident; // 'dimension'
MFEM_VERIFY(ident == "dimension", "invalid mesh file");
input >> Dim;
skip_comment_lines(input, '#');
input >> ident; // 'elements'
MFEM_VERIFY(ident == "elements", "invalid mesh file");
input >> NumOfElements;
elements.SetSize(NumOfElements);
for (int j = 0; j < NumOfElements; j++)
{
elements[j] = ReadElement(input);
}
if (version == 13)
{
skip_comment_lines(input, '#');
input >> ident; // 'attribute_sets'
MFEM_VERIFY(ident == "attribute_sets", "invalid mesh file");
attribute_sets.attr_sets.Load(input);
attribute_sets.attr_sets.SortAll();
attribute_sets.attr_sets.UniqueAll();
}
skip_comment_lines(input, '#');
input >> ident; // 'boundary'
MFEM_VERIFY(ident == "boundary", "invalid mesh file");
input >> NumOfBdrElements;
boundary.SetSize(NumOfBdrElements);
for (int j = 0; j < NumOfBdrElements; j++)
{
boundary[j] = ReadElement(input);
}
if (version == 13)
{
skip_comment_lines(input, '#');
input >> ident; // 'bdr_attribute_sets'
MFEM_VERIFY(ident == "bdr_attribute_sets", "invalid mesh file");
bdr_attribute_sets.attr_sets.Load(input);
bdr_attribute_sets.attr_sets.SortAll();
bdr_attribute_sets.attr_sets.UniqueAll();
}
skip_comment_lines(input, '#');
input >> ident; // 'vertices'
MFEM_VERIFY(ident == "vertices", "invalid mesh file");
input >> NumOfVertices;
vertices.SetSize(NumOfVertices);
input >> ws >> ident;
if (ident != "nodes")
{
// read the vertices
spaceDim = atoi(ident.c_str());
for (int j = 0; j < NumOfVertices; j++)
{
for (int i = 0; i < spaceDim; i++)
{
input >> vertices[j](i);
}
}
}
else
{
// prepare to read the nodes
input >> ws;
curved = 1;
}
// When visualizing solutions on non-conforming grids, PETSc
// may dump additional vertices
if (remove_unused_vertices) { RemoveUnusedVertices(); }
}
void Mesh::ReadLineMesh(std::istream &input)
{
int j,p1,p2,a;
Dim = 1;
input >> NumOfVertices;
vertices.SetSize(NumOfVertices);
// Sets vertices and the corresponding coordinates
for (j = 0; j < NumOfVertices; j++)
{
input >> vertices[j](0);
}
input >> NumOfElements;
elements.SetSize(NumOfElements);
// Sets elements and the corresponding indices of vertices
for (j = 0; j < NumOfElements; j++)
{
input >> a >> p1 >> p2;
elements[j] = new Segment(p1-1, p2-1, a);
}
int ind[1];
input >> NumOfBdrElements;
boundary.SetSize(NumOfBdrElements);
for (j = 0; j < NumOfBdrElements; j++)
{
input >> a >> ind[0];
ind[0]--;
boundary[j] = new Point(ind,a);
}
}
void Mesh::ReadNetgen2DMesh(std::istream &input, int &curved)
{
int ints[32], attr, n;
// Read planar mesh in Netgen format.
Dim = 2;
// Read the boundary elements.
input >> NumOfBdrElements;
boundary.SetSize(NumOfBdrElements);
for (int i = 0; i < NumOfBdrElements; i++)
{
input >> attr
>> ints[0] >> ints[1];
ints[0]--; ints[1]--;
boundary[i] = new Segment(ints, attr);
}
// Read the elements.
input >> NumOfElements;
elements.SetSize(NumOfElements);
for (int i = 0; i < NumOfElements; i++)
{
input >> attr >> n;
for (int j = 0; j < n; j++)
{
input >> ints[j];
ints[j]--;
}
switch (n)
{
case 2:
elements[i] = new Segment(ints, attr);
break;
case 3:
elements[i] = new Triangle(ints, attr);
break;
case 4:
elements[i] = new Quadrilateral(ints, attr);
break;
}
}
if (!curved)
{
// Read the vertices.
input >> NumOfVertices;
vertices.SetSize(NumOfVertices);
for (int i = 0; i < NumOfVertices; i++)
for (int j = 0; j < Dim; j++)
{
input >> vertices[i](j);
}
}
else
{
input >> NumOfVertices;
vertices.SetSize(NumOfVertices);
input >> ws;
}
}
void Mesh::ReadNetgen3DMesh(std::istream &input)
{
int ints[32], attr;
// Read a Netgen format mesh of tetrahedra.
Dim = 3;
// Read the vertices
input >> NumOfVertices;
vertices.SetSize(NumOfVertices);
for (int i = 0; i < NumOfVertices; i++)
for (int j = 0; j < Dim; j++)
{
input >> vertices[i](j);
}
// Read the elements
input >> NumOfElements;
elements.SetSize(NumOfElements);
for (int i = 0; i < NumOfElements; i++)
{
input >> attr;
for (int j = 0; j < 4; j++)
{
input >> ints[j];
ints[j]--;
}
#ifdef MFEM_USE_MEMALLOC
Tetrahedron *tet;
tet = TetMemory.Alloc();
tet->SetVertices(ints);
tet->SetAttribute(attr);
elements[i] = tet;
#else
elements[i] = new Tetrahedron(ints, attr);
#endif
}
// Read the boundary information.
input >> NumOfBdrElements;
boundary.SetSize(NumOfBdrElements);
for (int i = 0; i < NumOfBdrElements; i++)
{
input >> attr;
for (int j = 0; j < 3; j++)
{
input >> ints[j];
ints[j]--;
}
boundary[i] = new Triangle(ints, attr);
}
}
void Mesh::ReadTrueGridMesh(std::istream &input)
{
int i, j, ints[32], attr;
const int buflen = 1024;
char buf[buflen];
// TODO: find the actual dimension
Dim = 3;
if (Dim == 2)
{
int vari;
real_t varf;
input >> vari >> NumOfVertices >> vari >> vari >> NumOfElements;
input.getline(buf, buflen);
input.getline(buf, buflen);
input >> vari;
input.getline(buf, buflen);
input.getline(buf, buflen);
input.getline(buf, buflen);
// Read the vertices.
vertices.SetSize(NumOfVertices);
for (i = 0; i < NumOfVertices; i++)
{
input >> vari >> varf >> vertices[i](0) >> vertices[i](1);
input.getline(buf, buflen);
}
// Read the elements.
elements.SetSize(NumOfElements);
for (i = 0; i < NumOfElements; i++)
{
input >> vari >> attr;
for (j = 0; j < 4; j++)
{
input >> ints[j];
ints[j]--;
}
input.getline(buf, buflen);
input.getline(buf, buflen);
elements[i] = new Quadrilateral(ints, attr);
}
}
else if (Dim == 3)
{
int vari;
real_t varf;
input >> vari >> NumOfVertices >> NumOfElements;
input.getline(buf, buflen);
input.getline(buf, buflen);
input >> vari >> vari >> NumOfBdrElements;
input.getline(buf, buflen);
input.getline(buf, buflen);
input.getline(buf, buflen);
// Read the vertices.
vertices.SetSize(NumOfVertices);
for (i = 0; i < NumOfVertices; i++)
{
input >> vari >> varf >> vertices[i](0) >> vertices[i](1)
>> vertices[i](2);
input.getline(buf, buflen);
}
// Read the elements.
elements.SetSize(NumOfElements);
for (i = 0; i < NumOfElements; i++)
{
input >> vari >> attr;
for (j = 0; j < 8; j++)
{
input >> ints[j];
ints[j]--;
}
input.getline(buf, buflen);
elements[i] = new Hexahedron(ints, attr);
}
// Read the boundary elements.
boundary.SetSize(NumOfBdrElements);
for (i = 0; i < NumOfBdrElements; i++)
{
input >> attr;
for (j = 0; j < 4; j++)
{
input >> ints[j];
ints[j]--;
}
input.getline(buf, buflen);
boundary[i] = new Quadrilateral(ints, attr);
}
}
}
// see Tetrahedron::edges
const int Mesh::vtk_quadratic_tet[10] =
{ 0, 1, 2, 3, 4, 7, 5, 6, 8, 9 };
// see Pyramid::edges & Mesh::GenerateFaces
// https://www.vtk.org/doc/nightly/html/classvtkBiQuadraticQuadraticWedge.html
const int Mesh::vtk_quadratic_pyramid[13] =
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// see Wedge::edges & Mesh::GenerateFaces
// https://www.vtk.org/doc/nightly/html/classvtkBiQuadraticQuadraticWedge.html
const int Mesh::vtk_quadratic_wedge[18] =
{ 0, 2, 1, 3, 5, 4, 8, 7, 6, 11, 10, 9, 12, 14, 13, 17, 16, 15};
// see Hexahedron::edges & Mesh::GenerateFaces
const int Mesh::vtk_quadratic_hex[27] =
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
24, 22, 21, 23, 20, 25, 26
};
void Mesh::CreateVTKMesh(const Vector &points, const Array<int> &cell_data,
const Array<int> &cell_offsets,
const Array<int> &cell_types,
const Array<int> &cell_attributes,
int &curved, int &read_gf, bool &finalize_topo)
{
int np = points.Size()/3;
Dim = -1;
NumOfElements = cell_types.Size();
elements.SetSize(NumOfElements);
int order = -1;
bool legacy_elem = false, lagrange_elem = false;
for (int i = 0; i < NumOfElements; i++)
{
int j = (i > 0) ? cell_offsets[i-1] : 0;
int ct = cell_types[i];
Geometry::Type geom = VTKGeometry::GetMFEMGeometry(ct);
elements[i] = NewElement(geom);
if (cell_attributes.Size() > 0)
{
elements[i]->SetAttribute(cell_attributes[i]);
}
// VTK ordering of vertices is the same as MFEM ordering of vertices
// for all element types *except* prisms, which require a permutation
if (geom == Geometry::PRISM && ct != VTKGeometry::LAGRANGE_PRISM)
{
int prism_vertices[6];
for (int k=0; k<6; ++k)
{
prism_vertices[k] = cell_data[j+VTKGeometry::PrismMap[k]];
}
elements[i]->SetVertices(prism_vertices);
}
else
{
elements[i]->SetVertices(&cell_data[j]);
}
int elem_dim = Geometry::Dimension[geom];
int elem_order = VTKGeometry::GetOrder(ct, cell_offsets[i] - j);
if (VTKGeometry::IsLagrange(ct)) { lagrange_elem = true; }
else { legacy_elem = true; }
MFEM_VERIFY(Dim == -1 || Dim == elem_dim,
"Elements with different dimensions are not supported");
MFEM_VERIFY(order == -1 || order == elem_order,
"Elements with different orders are not supported");
MFEM_VERIFY(legacy_elem != lagrange_elem,
"Mixing of legacy and Lagrange cell types is not supported");
Dim = elem_dim;
order = elem_order;
}
// determine spaceDim based on min/max differences detected each dimension
spaceDim = 0;
if (np > 0)
{
real_t min_value, max_value;
for (int d = 3; d > 0; --d)
{
min_value = max_value = points(3*0 + d-1);
for (int i = 1; i < np; i++)
{
min_value = std::min(min_value, points(3*i + d-1));
max_value = std::max(max_value, points(3*i + d-1));
if (min_value != max_value)
{
spaceDim = d;
break;
}
}
if (spaceDim > 0) { break; }
}
}
if (order == 1 && !lagrange_elem)
{
NumOfVertices = np;
vertices.SetSize(np);
for (int i = 0; i < np; i++)
{
vertices[i](0) = points(3*i+0);
vertices[i](1) = points(3*i+1);
vertices[i](2) = points(3*i+2);
}
// No boundary is defined in a VTK mesh
NumOfBdrElements = 0;
FinalizeTopology();
CheckElementOrientation(true);
}
else
{
// The following section of code is shared for legacy quadratic and the
// Lagrange high order elements
curved = 1;
// generate new enumeration for the vertices
Array<int> pts_dof(np);
pts_dof = -1;
// mark vertex points
for (int i = 0; i < NumOfElements; i++)
{
int *v = elements[i]->GetVertices();
int nv = elements[i]->GetNVertices();
for (int j = 0; j < nv; j++)
{
if (pts_dof[v[j]] == -1) { pts_dof[v[j]] = 0; }
}
}
// The following loop reorders pts_dofs so vertices are visited in
// canonical order
// Keep the original ordering of the vertices
NumOfVertices = 0;
for (int i = 0; i < np; i++)
{
if (pts_dof[i] != -1)
{
pts_dof[i] = NumOfVertices++;
}
}
// update the element vertices
for (int i = 0; i < NumOfElements; i++)
{
int *v = elements[i]->GetVertices();
int nv = elements[i]->GetNVertices();
for (int j = 0; j < nv; j++)
{
v[j] = pts_dof[v[j]];
}
}
// Define the 'vertices' from the 'points' through the 'pts_dof' map
vertices.SetSize(NumOfVertices);
for (int i = 0; i < np; i++)
{
int j = pts_dof[i];
if (j != -1)
{
vertices[j](0) = points(3*i+0);
vertices[j](1) = points(3*i+1);
vertices[j](2) = points(3*i+2);
}
}
// No boundary is defined in a VTK mesh
NumOfBdrElements = 0;
// Generate faces and edges so that we can define FE space on the mesh
FinalizeTopology();
FiniteElementCollection *fec;
FiniteElementSpace *fes;
if (legacy_elem)
{
// Define quadratic FE space
fec = new QuadraticFECollection;
fes = new FiniteElementSpace(this, fec, spaceDim);
Nodes = new GridFunction(fes);
Nodes->MakeOwner(fec); // Nodes will destroy 'fec' and 'fes'
own_nodes = 1;
// Map vtk points to edge/face/element dofs
Array<int> dofs;
for (int i = 0; i < NumOfElements; i++)
{
fes->GetElementDofs(i, dofs);
const int *vtk_mfem;
switch (elements[i]->GetGeometryType())
{
case Geometry::TRIANGLE:
case Geometry::SQUARE:
vtk_mfem = vtk_quadratic_hex; break; // identity map
case Geometry::TETRAHEDRON:
vtk_mfem = vtk_quadratic_tet; break;
case Geometry::CUBE:
vtk_mfem = vtk_quadratic_hex; break;
case Geometry::PRISM:
vtk_mfem = vtk_quadratic_wedge; break;
case Geometry::PYRAMID:
vtk_mfem = vtk_quadratic_pyramid; break;
default:
vtk_mfem = NULL; // suppress a warning
break;
}
int offset = (i == 0) ? 0 : cell_offsets[i-1];
for (int j = 0; j < dofs.Size(); j++)
{
if (pts_dof[cell_data[offset+j]] == -1)
{
pts_dof[cell_data[offset+j]] = dofs[vtk_mfem[j]];
}
else
{
if (pts_dof[cell_data[offset+j]] != dofs[vtk_mfem[j]])
{
MFEM_ABORT("VTK mesh: inconsistent quadratic mesh!");
}
}
}
}
}
else
{
// Define H1 FE space
fec = new H1_FECollection(order,Dim,BasisType::ClosedUniform);
fes = new FiniteElementSpace(this, fec, spaceDim);
Nodes = new GridFunction(fes);
Nodes->MakeOwner(fec); // Nodes will destroy 'fec' and 'fes'
own_nodes = 1;
Array<int> dofs;
std::map<Geometry::Type,Array<int>> vtk_inv_maps;
std::map<Geometry::Type,const Array<int>*> lex_orderings;
int i, n;
for (n = i = 0; i < NumOfElements; i++)
{
Geometry::Type geom = GetElementBaseGeometry(i);
fes->GetElementDofs(i, dofs);
Array<int> &vtk_inv_map = vtk_inv_maps[geom];
if (vtk_inv_map.Size() == 0)
{
Array<int> vtk_map;
CreateVTKElementConnectivity(vtk_map, geom, order);
vtk_inv_map.SetSize(vtk_map.Size());
for (int j=0; j<vtk_map.Size(); ++j)
{
vtk_inv_map[vtk_map[j]] = j;
}
}
const Array<int> *&lex_ordering = lex_orderings[geom];
if (!lex_ordering)
{
const FiniteElement *fe = fes->GetFE(i);
const NodalFiniteElement *nodal_fe =
dynamic_cast<const NodalFiniteElement*>(fe);
MFEM_ASSERT(nodal_fe != NULL, "Unsupported element type");
lex_ordering = &nodal_fe->GetLexicographicOrdering();
}
for (int lex_idx = 0; lex_idx < dofs.Size(); lex_idx++)
{
int mfem_idx = (*lex_ordering)[lex_idx];
int vtk_idx = vtk_inv_map[lex_idx];
int pt_idx = cell_data[n + vtk_idx];
if (pts_dof[pt_idx] == -1)
{
pts_dof[pt_idx] = dofs[mfem_idx];
}
else
{
if (pts_dof[pt_idx] != dofs[mfem_idx])
{
MFEM_ABORT("VTK mesh: inconsistent Lagrange mesh!");
}
}
}
n += dofs.Size();
}
}
// Define the 'Nodes' from the 'points' through the 'pts_dof' map
Array<int> dofs;
for (int i = 0; i < np; i++)
{
dofs.SetSize(1);
if (pts_dof[i] != -1)
{
dofs[0] = pts_dof[i];
fes->DofsToVDofs(dofs);
for (int d = 0; d < dofs.Size(); d++)
{
(*Nodes)(dofs[d]) = points(3*i+d);
}
}
}
read_gf = 0;
}
}
namespace vtk_xml
{
using namespace tinyxml2;
/// Return false if either string is NULL or if the strings differ, return true
/// if the strings are the same.
bool StringCompare(const char *s1, const char *s2)
{
if (s1 == NULL || s2 == NULL) { return false; }
return strcmp(s1, s2) == 0;
}
/// Abstract base class for reading contiguous arrays of (potentially
/// compressed, potentially base-64 encoded) binary data from a buffer into a
/// destination array. The types of the source and destination arrays may be
/// different (e.g. read data of type uint8_t into destination array of
/// uint32_t), which is handled by the templated derived class @a BufferReader.
struct BufferReaderBase
{
enum HeaderType { UINT32_HEADER, UINT64_HEADER };
virtual void ReadBinary(const char *buf, void *dest, int n) const = 0;
virtual void ReadBase64(const char *txt, void *dest, int n) const = 0;
virtual ~BufferReaderBase() { }
};
/// Read an array of source data stored as (potentially compressed, potentially
/// base-64 encoded) into a destination array. The types of the elements in the
/// source array are given by template parameter @a F ("from") and the types of
/// the elements of the destination array are given by @a T ("to"). The binary
/// data has a header, which is one integer if the data is uncompressed, and is
/// four integers if the data is compressed. The integers may either by uint32_t
/// or uint64_t, according to the @a header_type. If the data is compressed and
/// base-64 encoded, then the header is encoded separately from the data. If the
/// data is uncompressed and base-64 encoded, then the header and data are
/// encoded together.
template <typename T, typename F>
struct BufferReader : BufferReaderBase
{
bool compressed;
HeaderType header_type;
BufferReader(bool compressed_, HeaderType header_type_)
: compressed(compressed_), header_type(header_type_) { }
/// Return the number of bytes of each header entry.
size_t HeaderEntrySize() const
{
return header_type == UINT64_HEADER ? sizeof(uint64_t) : sizeof(uint32_t);
}
/// Return the value of the header entry pointer to by @a header_buf. The
/// value is stored as either uint32_t or uint64_t, according to the @a
/// header_type, and is returned as uint64_t.
uint64_t ReadHeaderEntry(const char *header_buf) const
{
return (header_type == UINT64_HEADER) ? bin_io::read<uint64_t>(header_buf)
: bin_io::read<uint32_t>(header_buf);
}
/// Return the number of bytes in the header. The header consists of one
/// integer if the data is uncompressed, and @a N + 3 integers if the data is
/// compressed, where @a N is the number of blocks. The integers are either
/// 32 or 64 bytes depending on the value of @a header_type. The number of
/// blocks is determined by reading the first integer (of type @a
/// header_type) pointed to by @a header_buf.
int NumHeaderBytes(const char *header_buf) const
{
if (!compressed) { return static_cast<int>(HeaderEntrySize()); }
return (3 + ReadHeaderEntry(header_buf))*HeaderEntrySize();
}
/// Read @a n elements of type @a F from the source buffer @a buf into the
/// (pre-allocated) destination array of elements of type @a T stored in
/// the buffer @a dest_void. The header is stored @b separately from the
/// rest of the data, in the buffer @a header_buf. The data buffer @a buf
/// does @b not contain a header.
void ReadBinaryWithHeader(const char *header_buf, const char *buf,
void *dest_void, int n) const
{
std::vector<char> uncompressed_data;
T *dest = static_cast<T*>(dest_void);
if (compressed)
{
#ifdef MFEM_USE_ZLIB
// The header has format (where header_t is uint32_t or uint64_t):
// header_t number_of_blocks;
// header_t uncompressed_block_size;
// header_t uncompressed_last_block_size;
// header_t compressed_size[number_of_blocks];
int header_entry_size = HeaderEntrySize();
int nblocks = ReadHeaderEntry(header_buf);
header_buf += header_entry_size;
std::vector<size_t> header(nblocks + 2);
for (int i=0; i<nblocks+2; ++i)
{
header[i] = ReadHeaderEntry(header_buf);
header_buf += header_entry_size;
}
uncompressed_data.resize((nblocks-1)*header[0] + header[1]);
Bytef *dest_ptr = (Bytef *)uncompressed_data.data();
Bytef *dest_start = dest_ptr;
const Bytef *source_ptr = (const Bytef *)buf;
for (int i=0; i<nblocks; ++i)
{
uLongf source_len = header[i+2];
uLong dest_len = (i == nblocks-1) ? header[1] : header[0];
int res = uncompress(dest_ptr, &dest_len, source_ptr, source_len);
MFEM_VERIFY(res == Z_OK, "Error uncompressing");
dest_ptr += dest_len;
source_ptr += source_len;
}
MFEM_VERIFY(size_t(sizeof(F)*n) == size_t(dest_ptr - dest_start),
"AppendedData: wrong data size");
buf = uncompressed_data.data();
#else
MFEM_ABORT("MFEM must be compiled with zlib enabled to uncompress.")
#endif
}
else
{
// Each "data block" is preceded by a header that is either UInt32 or
// UInt64. The rest of the data follows.
MFEM_VERIFY(sizeof(F)*n == ReadHeaderEntry(header_buf),
"AppendedData: wrong data size");
}
if (std::is_same_v<T, F>)
{
// Special case: no type conversions necessary, so can just memcpy
memcpy(dest, buf, sizeof(T)*n);
}
else
{
for (int i=0; i<n; ++i)
{
// Read binary data as type F, place in array as type T
dest[i] = bin_io::read<F>(buf + i*sizeof(F));
}
}
}
/// Read @a n elements of type @a F from source buffer @a buf into
/// (pre-allocated) array of elements of type @a T stored in destination
/// buffer @a dest. The input buffer contains both the header and the data.
void ReadBinary(const char *buf, void *dest, int n) const override
{
ReadBinaryWithHeader(buf, buf + NumHeaderBytes(buf), dest, n);
}
/// Read @a n elements of type @a F from base-64 encoded source buffer into
/// (pre-allocated) array of elements of type @a T stored in destination
/// buffer @a dest. The base-64-encoded data is given by the null-terminated
/// string @a txt, which contains both the header and the data.
void ReadBase64(const char *txt, void *dest, int n) const override
{
// Skip whitespace
while (*txt)
{
if (*txt != ' ' && *txt != '\n') { break; }
++txt;
}
if (compressed)
{
// Decode the first entry of the header, which we need to determine
// how long the rest of the header is.
std::vector<char> nblocks_buf;
int nblocks_b64 = static_cast<int>(bin_io::NumBase64Chars(HeaderEntrySize()));
bin_io::DecodeBase64(txt, nblocks_b64, nblocks_buf);
std::vector<char> data, header;
// Compute number of characters needed to encode header in base 64,
// then round to nearest multiple of 4 to take padding into account.
int header_b64 = static_cast<int>(bin_io::NumBase64Chars(NumHeaderBytes(
nblocks_buf.data())));
// If data is compressed, header is encoded separately
bin_io::DecodeBase64(txt, header_b64, header);
bin_io::DecodeBase64(txt + header_b64, strlen(txt)-header_b64, data);
ReadBinaryWithHeader(header.data(), data.data(), dest, n);
}
else
{
std::vector<char> data;
bin_io::DecodeBase64(txt, strlen(txt), data);
ReadBinary(data.data(), dest, n);
}
}
};
/// Class to read data from VTK's @a DataArary elements. Each @a DataArray can
/// contain inline ASCII data, inline base-64-encoded data (potentially
/// compressed), or reference "appended data", which may be raw or base-64, and
/// may be compressed or uncompressed.
struct XMLDataReader
{
const char *appended_data, *byte_order, *compressor;
enum AppendedDataEncoding { RAW, BASE64 };
map<string,BufferReaderBase*> type_map;
AppendedDataEncoding encoding;
/// Create the data reader, where @a vtk is the @a VTKFile XML element, and
/// @a vtu is the child @a UnstructuredGrid XML element. This will determine
/// the header type (32 or 64 bit integers) and whether compression is
/// enabled or not. The appended data will be loaded.
XMLDataReader(const XMLElement *vtk, const XMLElement *vtu)
{
// Determine whether binary data header is 32 or 64 bit integer
BufferReaderBase::HeaderType htype;
if (StringCompare(vtk->Attribute("header_type"), "UInt64"))
{
htype = BufferReaderBase::UINT64_HEADER;
}
else
{
htype = BufferReaderBase::UINT32_HEADER;
}
// Get the byte order of the file (will check if we encounter binary data)
byte_order = vtk->Attribute("byte_order");
// Get the compressor. We will check that MFEM can handle the compression
// if we encounter binary data.
compressor = vtk->Attribute("compressor");
bool compressed = (compressor != NULL);
// Find the appended data.
appended_data = NULL;
for (const XMLElement *xml_elem = vtu->NextSiblingElement();
xml_elem != NULL;
xml_elem = xml_elem->NextSiblingElement())
{
if (StringCompare(xml_elem->Name(), "AppendedData"))
{
const char *encoding_str = xml_elem->Attribute("encoding");
if (StringCompare(encoding_str, "raw"))
{
appended_data = xml_elem->GetAppendedData();
encoding = RAW;
}
else if (StringCompare(encoding_str, "base64"))
{
appended_data = xml_elem->GetText();
encoding = BASE64;
}
MFEM_VERIFY(appended_data != NULL, "Invalid AppendedData");
// Appended data follows first underscore
bool found_leading_underscore = false;
while (*appended_data)
{
++appended_data;
if (*appended_data == '_')
{
found_leading_underscore = true;
++appended_data;
break;
}
}
MFEM_VERIFY(found_leading_underscore, "Invalid AppendedData");
break;
}
}
type_map["Int8"] = new BufferReader<int, int8_t>(compressed, htype);
type_map["Int16"] = new BufferReader<int, int16_t>(compressed, htype);
type_map["Int32"] = new BufferReader<int, int32_t>(compressed, htype);
type_map["Int64"] = new BufferReader<int, int64_t>(compressed, htype);
type_map["UInt8"] = new BufferReader<int, uint8_t>(compressed, htype);
type_map["UInt16"] = new BufferReader<int, uint16_t>(compressed, htype);
type_map["UInt32"] = new BufferReader<int, uint32_t>(compressed, htype);
type_map["UInt64"] = new BufferReader<int, uint64_t>(compressed, htype);
type_map["Float32"] = new BufferReader<double, float>(compressed, htype);
type_map["Float64"] = new BufferReader<double, double>(compressed, htype);
}
/// Read the @a DataArray XML element given by @a xml_elem into
/// (pre-allocated) destination array @a dest, where @a dest stores @a n
/// elements of type @a T.
template <typename T>
void Read(const XMLElement *xml_elem, T *dest, int n)
{
static const char *erstr = "Error reading XML DataArray";
MFEM_VERIFY(StringCompare(xml_elem->Name(), "DataArray"), erstr);
const char *format = xml_elem->Attribute("format");
if (StringCompare(format, "ascii"))
{
const char *txt = xml_elem->GetText();
MFEM_VERIFY(txt != NULL, erstr);
std::istringstream data_stream(txt);
for (int i=0; i<n; ++i) { data_stream >> dest[i]; }
}
else if (StringCompare(format, "appended"))
{
VerifyBinaryOptions();
int offset = xml_elem->IntAttribute("offset");
const char *type = xml_elem->Attribute("type");
MFEM_VERIFY(type != NULL, erstr);
BufferReaderBase *reader = type_map[type];
MFEM_VERIFY(reader != NULL, erstr);
MFEM_VERIFY(appended_data != NULL, "No AppendedData found");
if (encoding == RAW)
{
reader->ReadBinary(appended_data + offset, dest, n);
}
else
{
reader->ReadBase64(appended_data + offset, dest, n);
}
}
else if (StringCompare(format, "binary"))
{
VerifyBinaryOptions();
const char *txt = xml_elem->GetText();
MFEM_VERIFY(txt != NULL, erstr);
const char *type = xml_elem->Attribute("type");
if (type == NULL) { MFEM_ABORT(erstr); }
BufferReaderBase *reader = type_map[type];
if (reader == NULL) { MFEM_ABORT(erstr); }
reader->ReadBase64(txt, dest, n);
}
else
{
MFEM_ABORT("Invalid XML VTK DataArray format");
}
}
/// Check that the byte order of the file is the same as the native byte
/// order that we're running with. We don't currently support converting
/// between byte orders. The byte order is only verified if we encounter
/// binary data.
void VerifyByteOrder() const
{
// Can't handle reading big endian from little endian or vice versa
if (byte_order && !StringCompare(byte_order, VTKByteOrder()))
{
MFEM_ABORT("Converting between different byte orders is unsupported.");
}
}
/// Check that the compressor is compatible (MFEM currently only supports
/// zlib compression). If MFEM is not compiled with zlib, then we cannot
/// read binary data with compression.
void VerifyCompressor() const
{
if (compressor && !StringCompare(compressor, "vtkZLibDataCompressor"))
{
MFEM_ABORT("Unsupported compressor. Only zlib is supported.")
}
#ifndef MFEM_USE_ZLIB
MFEM_VERIFY(compressor == NULL, "MFEM must be compiled with zlib enabled "
"to support reading compressed data.");
#endif
}
/// Verify that the binary data is stored with compatible options (i.e.
/// native byte order and compatible compression).
void VerifyBinaryOptions() const
{
VerifyByteOrder();
VerifyCompressor();
}
~XMLDataReader()
{
for (auto &x : type_map) { delete x.second; }
}
};
} // namespace vtk_xml
void Mesh::ReadXML_VTKMesh(std::istream &input, int &curved, int &read_gf,
bool &finalize_topo, const std::string &xml_prefix)
{
using namespace vtk_xml;
static const char *erstr = "XML parsing error";
// Create buffer beginning with xml_prefix, then read the rest of the stream
std::vector<char> buf(xml_prefix.begin(), xml_prefix.end());
std::istreambuf_iterator<char> eos;
buf.insert(buf.end(), std::istreambuf_iterator<char>(input), eos);
buf.push_back('\0'); // null-terminate buffer
XMLDocument xml;
xml.Parse(buf.data(), buf.size());
if (xml.ErrorID() != XML_SUCCESS)
{
MFEM_ABORT("Error parsing XML VTK file.\n" << xml.ErrorStr());
}
const XMLElement *vtkfile = xml.FirstChildElement();
MFEM_VERIFY(vtkfile, erstr);
MFEM_VERIFY(StringCompare(vtkfile->Name(), "VTKFile"), erstr);
const XMLElement *vtu = vtkfile->FirstChildElement();
MFEM_VERIFY(vtu, erstr);
MFEM_VERIFY(StringCompare(vtu->Name(), "UnstructuredGrid"), erstr);
XMLDataReader data_reader(vtkfile, vtu);
// Count the number of points and cells
const XMLElement *piece = vtu->FirstChildElement();
MFEM_VERIFY(StringCompare(piece->Name(), "Piece"), erstr);
MFEM_VERIFY(piece->NextSiblingElement() == NULL,
"XML VTK meshes with more than one Piece are not supported");
int npts = piece->IntAttribute("NumberOfPoints");
int ncells = piece->IntAttribute("NumberOfCells");
// Read the points
Vector points(3*npts);
const XMLElement *pts_xml;
for (pts_xml = piece->FirstChildElement();
pts_xml != NULL;
pts_xml = pts_xml->NextSiblingElement())
{
if (StringCompare(pts_xml->Name(), "Points"))
{
const XMLElement *pts_data = pts_xml->FirstChildElement();
MFEM_VERIFY(pts_data->IntAttribute("NumberOfComponents") == 3,
"XML VTK Points DataArray must have 3 components");
data_reader.Read(pts_data, points.GetData(), points.Size());
break;
}
}
if (pts_xml == NULL) { MFEM_ABORT(erstr); }
// Read the cells
Array<int> cell_data, cell_offsets(ncells), cell_types(ncells);
const XMLElement *cells_xml;
for (cells_xml = piece->FirstChildElement();
cells_xml != NULL;
cells_xml = cells_xml->NextSiblingElement())
{
if (StringCompare(cells_xml->Name(), "Cells"))
{
const XMLElement *cell_data_xml = NULL;
for (const XMLElement *data_xml = cells_xml->FirstChildElement();
data_xml != NULL;
data_xml = data_xml->NextSiblingElement())
{
const char *data_name = data_xml->Attribute("Name");
if (StringCompare(data_name, "offsets"))
{
data_reader.Read(data_xml, cell_offsets.GetData(), ncells);
}
else if (StringCompare(data_name, "types"))
{
data_reader.Read(data_xml, cell_types.GetData(), ncells);
}
else if (StringCompare(data_name, "connectivity"))
{
// Have to read the connectivity after the offsets, because we
// don't know how many points to read until we have the offsets
// (size of connectivity array is equal to the last offset), so
// store the XML element pointer and read this data later.
cell_data_xml = data_xml;
}
}
MFEM_VERIFY(cell_data_xml != NULL, erstr);
int cell_data_size = cell_offsets.Last();
cell_data.SetSize(cell_data_size);
data_reader.Read(cell_data_xml, cell_data.GetData(), cell_data_size);
break;
}
}
if (cells_xml == NULL) { MFEM_ABORT(erstr); }
// Read the element attributes, which are stored as CellData named either
// "material" or "attribute". We prioritize "material" over "attribute" for
// backwards compatibility.
Array<int> cell_attributes;
bool found_attributes = false;
for (const XMLElement *cell_data_xml = piece->FirstChildElement();
cell_data_xml != NULL;
cell_data_xml = cell_data_xml->NextSiblingElement())
{
const bool is_cell_data =
StringCompare(cell_data_xml->Name(), "CellData");
const bool is_material =
StringCompare(cell_data_xml->Attribute("Scalars"), "material");
const bool is_attribute =
StringCompare(cell_data_xml->Attribute("Scalars"), "attribute");
if (is_cell_data && (is_material || (is_attribute && !found_attributes)))
{
found_attributes = true;
const XMLElement *data_xml = cell_data_xml->FirstChildElement();
if (data_xml != NULL && StringCompare(data_xml->Name(), "DataArray"))
{
cell_attributes.SetSize(ncells);
data_reader.Read(data_xml, cell_attributes.GetData(), ncells);
}
}
}
CreateVTKMesh(points, cell_data, cell_offsets, cell_types, cell_attributes,
curved, read_gf, finalize_topo);
}
void Mesh::ReadVTKMesh(std::istream &input, int &curved, int &read_gf,
bool &finalize_topo)
{
// VTK resources:
// * https://www.vtk.org/doc/nightly/html/vtkCellType_8h_source.html
// * https://www.vtk.org/doc/nightly/html/classvtkCell.html
// * https://lorensen.github.io/VTKExamples/site/VTKFileFormats
// * https://www.kitware.com/products/books/VTKUsersGuide.pdf
string buff;
getline(input, buff); // comment line
getline(input, buff);
filter_dos(buff);
if (buff != "ASCII")
{
MFEM_ABORT("VTK mesh is not in ASCII format!");
return;
}
do
{
getline(input, buff);
filter_dos(buff);
if (!input.good()) { MFEM_ABORT("VTK mesh is not UNSTRUCTURED_GRID!"); }
}
while (buff != "DATASET UNSTRUCTURED_GRID");
// Read the points, skipping optional sections such as the FIELD data from
// VisIt's VTK export (or from Mesh::PrintVTK with field_data==1).
do
{
input >> buff;
if (!input.good())
{
MFEM_ABORT("VTK mesh does not have POINTS data!");
}
}
while (buff != "POINTS");
Vector points;
int np;
input >> np >> ws;
getline(input, buff); // "double"
points.Load(input, 3*np);
//skip metadata
// Looks like:
// METADATA
//INFORMATION 2
//NAME L2_NORM_RANGE LOCATION vtkDataArray
//DATA 2 0 5.19615
//NAME L2_NORM_FINITE_RANGE LOCATION vtkDataArray
//DATA 2 0 5.19615
do
{
input >> buff;
if (!input.good())
{
MFEM_ABORT("VTK mesh does not have CELLS data!");
}
}
while (buff != "CELLS");
// Read the cells
Array<int> cell_data, cell_offsets;
if (buff == "CELLS")
{
int ncells, n;
input >> ncells >> n >> ws;
cell_offsets.SetSize(ncells);
cell_data.SetSize(n - ncells);
int offset = 0;
for (int i=0; i<ncells; ++i)
{
int nv;
input >> nv;
cell_offsets[i] = offset + nv;
for (int j=0; j<nv; ++j)
{
input >> cell_data[offset + j];
}
offset += nv;
}
}
// Read the cell types
input >> ws >> buff;
Array<int> cell_types;
int ncells;
MFEM_VERIFY(buff == "CELL_TYPES", "CELL_TYPES not provided in VTK mesh.")
input >> ncells;
cell_types.Load(ncells, input);
while ((input.good()) && (buff != "CELL_DATA"))
{
input >> buff;
}
getline(input, buff); // finish the line
// Read the cell materials
// bool found_material = false;
Array<int> cell_attributes;
bool found_attributes = false;
while ((input.good()))
{
getline(input, buff);
if (buff.rfind("POINT_DATA") == 0)
{
break; // We have entered the POINT_DATA block. Quit.
}
else if (buff.rfind("SCALARS material") == 0 ||
(buff.rfind("SCALARS attribute") == 0 && !found_attributes))
{
found_attributes = true;
getline(input, buff); // LOOKUP_TABLE default
if (buff.rfind("LOOKUP_TABLE default") != 0)
{
MFEM_ABORT("Invalid LOOKUP_TABLE for material array in VTK file.");
}
cell_attributes.Load(ncells, input);
// found_material = true;
break;
}
}
// if (!found_material)
// {
// MFEM_WARNING("Material array not found in VTK file. "
// "Assuming uniform material composition.");
// }
CreateVTKMesh(points, cell_data, cell_offsets, cell_types, cell_attributes,
curved, read_gf, finalize_topo);
} // end ReadVTKMesh
void Mesh::ReadNURBSMesh(std::istream &input, int &curved, int &read_gf,
bool spacing, bool nc)
{
NURBSext = nc ? new NCNURBSExtension(input, spacing):
new NURBSExtension(input, spacing);
Dim = NURBSext->Dimension();
NumOfVertices = NURBSext->GetNV();
NumOfElements = NURBSext->GetNE();
NumOfBdrElements = NURBSext->GetNBE();
NURBSext->GetElementTopo(elements);
NURBSext->GetBdrElementTopo(boundary);
vertices.SetSize(NumOfVertices);
curved = 1;
if (NURBSext->HavePatches())
{
NURBSFECollection *fec = new NURBSFECollection(NURBSext->GetOrder());
const int vdim = NURBSext->GetPatchSpaceDimension();
FiniteElementSpace *fes = new FiniteElementSpace(this, fec, vdim,
Ordering::byVDIM);
Nodes = new GridFunction(fes);
Nodes->MakeOwner(fec);
NURBSext->SetCoordsFromPatches(*Nodes, vdim);
own_nodes = 1;
read_gf = 0;
spaceDim = Nodes->VectorDim();
for (int i = 0; i < spaceDim; i++)
{
Vector vert_val;
Nodes->GetNodalValues(vert_val, i+1);
for (int j = 0; j < NumOfVertices; j++)
{
vertices[j](i) = vert_val(j);
}
}
}
else
{
read_gf = 1;
}
}
void Mesh::ReadInlineMesh(std::istream &input, bool generate_edges)
{
// Initialize to negative numbers so that we know if they've been set. We're
// using Element::POINT as our flag, since we're not going to make a 0D mesh,
// ever.
int nx = -1;
int ny = -1;
int nz = -1;
real_t sx = -1.0;
real_t sy = -1.0;
real_t sz = -1.0;
Element::Type type = Element::POINT;
while (true)
{
skip_comment_lines(input, '#');
// Break out if we reached the end of the file after gobbling up the
// whitespace and comments after the last keyword.
if (!input.good())
{
break;
}
// Read the next keyword
std::string name;
input >> name;
input >> std::ws;
// Make sure there's an equal sign
MFEM_VERIFY(input.get() == '=',
"Inline mesh expected '=' after keyword " << name);
input >> std::ws;
if (name == "nx")
{
input >> nx;
}
else if (name == "ny")
{
input >> ny;
}
else if (name == "nz")
{
input >> nz;
}
else if (name == "sx")
{
input >> sx;
}
else if (name == "sy")
{
input >> sy;
}
else if (name == "sz")
{
input >> sz;
}
else if (name == "type")
{
std::string eltype;
input >> eltype;
if (eltype == "segment")
{
type = Element::SEGMENT;
}
else if (eltype == "quad")
{
type = Element::QUADRILATERAL;
}
else if (eltype == "tri")
{
type = Element::TRIANGLE;
}
else if (eltype == "hex")
{
type = Element::HEXAHEDRON;
}
else if (eltype == "wedge")
{
type = Element::WEDGE;
}
else if (eltype == "pyramid")
{
type = Element::PYRAMID;
}
else if (eltype == "tet")
{
type = Element::TETRAHEDRON;
}
else
{
MFEM_ABORT("unrecognized element type (read '" << eltype
<< "') in inline mesh format. "
"Allowed: segment, tri, quad, tet, hex, wedge");
}
}
else
{
MFEM_ABORT("unrecognized keyword (" << name
<< ") in inline mesh format. "
"Allowed: nx, ny, nz, type, sx, sy, sz");
}
input >> std::ws;
// Allow an optional semi-colon at the end of each line.
if (input.peek() == ';')
{
input.get();
}
// Done reading file
if (!input)
{
break;
}
}
// Now make the mesh.
if (type == Element::SEGMENT)
{
MFEM_VERIFY(nx > 0 && sx > 0.0,
"invalid 1D inline mesh format, all values must be "
"positive\n"
<< " nx = " << nx << "\n"
<< " sx = " << sx << "\n");
Make1D(nx, sx);
}
else if (type == Element::TRIANGLE || type == Element::QUADRILATERAL)
{
MFEM_VERIFY(nx > 0 && ny > 0 && sx > 0.0 && sy > 0.0,
"invalid 2D inline mesh format, all values must be "
"positive\n"
<< " nx = " << nx << "\n"
<< " ny = " << ny << "\n"
<< " sx = " << sx << "\n"
<< " sy = " << sy << "\n");
Make2D(nx, ny, type, sx, sy, generate_edges, true);
}
else if (type == Element::TETRAHEDRON || type == Element::WEDGE ||
type == Element::HEXAHEDRON || type == Element::PYRAMID)
{
MFEM_VERIFY(nx > 0 && ny > 0 && nz > 0 &&
sx > 0.0 && sy > 0.0 && sz > 0.0,
"invalid 3D inline mesh format, all values must be "
"positive\n"
<< " nx = " << nx << "\n"
<< " ny = " << ny << "\n"
<< " nz = " << nz << "\n"
<< " sx = " << sx << "\n"
<< " sy = " << sy << "\n"
<< " sz = " << sz << "\n");
Make3D(nx, ny, nz, type, sx, sy, sz, true);
// TODO: maybe have an option in the file to control ordering?
}
else
{
MFEM_ABORT("For inline mesh, must specify an element type ="
" [segment, tri, quad, tet, hex, wedge]");
}
}
void Mesh::ReadGmshMesh(std::istream &input, int &curved, int &read_gf)
{
string buff;
real_t version;
int binary, dsize;
input >> version >> binary >> dsize;
if (version < 2.2)
{
MFEM_ABORT("Gmsh file version < 2.2");
}
if (dsize != sizeof(double))
{
MFEM_ABORT("Gmsh file : dsize != sizeof(double)");
}
getline(input, buff);
// There is a number 1 in binary format
if (binary)
{
int one;
input.read(reinterpret_cast<char*>(&one), sizeof(one));
if (one != 1)
{
MFEM_ABORT("Gmsh file : wrong binary format");
}
}
// A map between a serial number of the vertex and its number in the file
// (there may be gaps in the numbering, and also Gmsh enumerates vertices
// starting from 1, not 0)
map<int, int> vertices_map;
// A map containing names of physical curves, surfaces, and volumes.
// The first index is the dimension of the physical manifold, the second
// index is the element attribute number of the set, and the string is
// the assigned name.
map<int,map<int,std::string> > phys_names_by_dim;
// Gmsh always outputs coordinates in 3D, but MFEM distinguishes between the
// mesh element dimension (Dim) and the dimension of the space in which the
// mesh is embedded (spaceDim). For example, a 2D MFEM mesh has Dim = 2 and
// spaceDim = 2, while a 2D surface mesh in 3D has Dim = 2 but spaceDim = 3.
// Below we set spaceDim by measuring the mesh bounding box and checking for
// a lower dimensional subspace. The assumption is that the mesh is at least
// 2D if the y-dimension of the box is non-trivial and 3D if the z-dimension
// is non-trivial. Note that with these assumptions a 2D mesh parallel to the
// yz plane will be considered a surface mesh embedded in 3D whereas the same
// 2D mesh parallel to the xy plane will be considered a 2D mesh.
real_t bb_tol = 1e-14;
real_t bb_min[3];
real_t bb_max[3];
// Mesh order
int mesh_order = 1;
// Mesh type
bool periodic = false;
// Vector field to store uniformly spaced Gmsh high order mesh coords
GridFunction Nodes_gf;
// Read the lines of the mesh file. If we face specific keyword, we'll treat
// the section.
while (input >> buff)
{
if (buff == "$Nodes") // reading mesh vertices
{
input >> NumOfVertices;
getline(input, buff);
vertices.SetSize(NumOfVertices);
int serial_number;
const int gmsh_dim = 3; // Gmsh always outputs 3 coordinates
real_t coord[gmsh_dim];
for (int ver = 0; ver < NumOfVertices; ++ver)
{
if (binary)
{
input.read(reinterpret_cast<char*>(&serial_number), sizeof(int));
input.read(reinterpret_cast<char*>(coord), gmsh_dim*sizeof(double));
}
else // ASCII
{
input >> serial_number;
for (int ci = 0; ci < gmsh_dim; ++ci)
{
input >> coord[ci];
}
}
vertices[ver] = Vertex(coord, gmsh_dim);
vertices_map[serial_number] = ver;
for (int ci = 0; ci < gmsh_dim; ++ci)
{
bb_min[ci] = (ver == 0) ? coord[ci] :
std::min(bb_min[ci], coord[ci]);
bb_max[ci] = (ver == 0) ? coord[ci] :
std::max(bb_max[ci], coord[ci]);
}
}
real_t bb_size = std::max(bb_max[0] - bb_min[0],
std::max(bb_max[1] - bb_min[1],
bb_max[2] - bb_min[2]));
spaceDim = 1;
if (bb_max[1] - bb_min[1] > bb_size * bb_tol)
{
spaceDim++;
}
if (bb_max[2] - bb_min[2] > bb_size * bb_tol)
{
spaceDim++;
}
if (static_cast<int>(vertices_map.size()) != NumOfVertices)
{
MFEM_ABORT("Gmsh file : vertices indices are not unique");
}
} // section '$Nodes'
else if (buff == "$Elements") // reading mesh elements
{
int num_of_all_elements;
input >> num_of_all_elements;
// = NumOfElements + NumOfBdrElements + (maybe, PhysicalPoints)
getline(input, buff);
int serial_number; // serial number of an element
int type_of_element; // ID describing a type of a mesh element
int n_tags; // number of different tags describing an element
int phys_domain; // element's attribute
int elem_domain; // another element's attribute (rarely used)
int n_partitions; // number of partitions where an element takes place
// number of nodes for each type of Gmsh elements, type is the index of
// the array + 1
int nodes_of_gmsh_element[] =
{
2, // 2-node line.
3, // 3-node triangle.
4, // 4-node quadrangle.
4, // 4-node tetrahedron.
8, // 8-node hexahedron.
6, // 6-node prism.
5, // 5-node pyramid.
3, /* 3-node second order line (2 nodes associated with the
vertices and 1 with the edge). */
6, /* 6-node second order triangle (3 nodes associated with the
vertices and 3 with the edges). */
9, /* 9-node second order quadrangle (4 nodes associated with the
vertices, 4 with the edges and 1 with the face). */
10, /* 10-node second order tetrahedron (4 nodes associated with
the vertices and 6 with the edges). */
27, /* 27-node second order hexahedron (8 nodes associated with the
vertices, 12 with the edges, 6 with the faces and 1 with the
volume). */
18, /* 18-node second order prism (6 nodes associated with the
vertices, 9 with the edges and 3 with the quadrangular
faces). */
14, /* 14-node second order pyramid (5 nodes associated with the
vertices, 8 with the edges and 1 with the quadrangular
face). */
1, // 1-node point.
8, /* 8-node second order quadrangle (4 nodes associated with the
vertices and 4 with the edges). */
20, /* 20-node second order hexahedron (8 nodes associated with the
vertices and 12 with the edges). */
15, /* 15-node second order prism (6 nodes associated with the
vertices and 9 with the edges). */
13, /* 13-node second order pyramid (5 nodes associated with the
vertices and 8 with the edges). */
9, /* 9-node third order incomplete triangle (3 nodes associated
with the vertices, 6 with the edges) */
10, /* 10-node third order triangle (3 nodes associated with the
vertices, 6 with the edges, 1 with the face) */
12, /* 12-node fourth order incomplete triangle (3 nodes associated
with the vertices, 9 with the edges) */
15, /* 15-node fourth order triangle (3 nodes associated with the
vertices, 9 with the edges, 3 with the face) */
15, /* 15-node fifth order incomplete triangle (3 nodes associated
with the vertices, 12 with the edges) */
21, /* 21-node fifth order complete triangle (3 nodes associated
with the vertices, 12 with the edges, 6 with the face) */
4, /* 4-node third order edge (2 nodes associated with the
vertices, 2 internal to the edge) */
5, /* 5-node fourth order edge (2 nodes associated with the
vertices, 3 internal to the edge) */
6, /* 6-node fifth order edge (2 nodes associated with the
vertices, 4 internal to the edge) */
20, /* 20-node third order tetrahedron (4 nodes associated with the
vertices, 12 with the edges, 4 with the faces) */
35, /* 35-node fourth order tetrahedron (4 nodes associated with
the vertices, 18 with the edges, 12 with the faces, and 1
with the volume) */
56, /* 56-node fifth order tetrahedron (4 nodes associated with the
vertices, 24 with the edges, 24 with the faces, and 4 with
the volume) */
-1,-1, /* unsupported tetrahedral types */
-1,-1, /* unsupported polygonal and polyhedral types */
16, /* 16-node third order quadrilateral (4 nodes associated with
the vertices, 8 with the edges, 4 with the face) */
25, /* 25-node fourth order quadrilateral (4 nodes associated with
the vertices, 12 with the edges, 9 with the face) */
36, /* 36-node fifth order quadrilateral (4 nodes associated with
the vertices, 16 with the edges, 16 with the face) */
-1,-1,-1, /* unsupported quadrilateral types */
28, /* 28-node sixth order complete triangle (3 nodes associated
with the vertices, 15 with the edges, 10 with the face) */
36, /* 36-node seventh order complete triangle (3 nodes associated
with the vertices, 18 with the edges, 15 with the face) */
45, /* 45-node eighth order complete triangle (3 nodes associated
with the vertices, 21 with the edges, 21 with the face) */
55, /* 55-node ninth order complete triangle (3 nodes associated
with the vertices, 24 with the edges, 28 with the face) */
66, /* 66-node tenth order complete triangle (3 nodes associated
with the vertices, 27 with the edges, 36 with the face) */
49, /* 49-node sixth order quadrilateral (4 nodes associated with
the vertices, 20 with the edges, 25 with the face) */
64, /* 64-node seventh order quadrilateral (4 nodes associated with
the vertices, 24 with the edges, 36 with the face) */
81, /* 81-node eighth order quadrilateral (4 nodes associated with
the vertices, 28 with the edges, 49 with the face) */
100, /* 100-node ninth order quadrilateral (4 nodes associated with
the vertices, 32 with the edges, 64 with the face) */
121, /* 121-node tenth order quadrilateral (4 nodes associated with
the vertices, 36 with the edges, 81 with the face) */
-1,-1,-1,-1,-1, /* unsupported triangular types */
-1,-1,-1,-1,-1, /* unsupported quadrilateral types */
7, /* 7-node sixth order edge (2 nodes associated with the
vertices, 5 internal to the edge) */
8, /* 8-node seventh order edge (2 nodes associated with the
vertices, 6 internal to the edge) */
9, /* 9-node eighth order edge (2 nodes associated with the
vertices, 7 internal to the edge) */
10, /* 10-node ninth order edge (2 nodes associated with the
vertices, 8 internal to the edge) */
11, /* 11-node tenth order edge (2 nodes associated with the
vertices, 9 internal to the edge) */
-1, /* unsupported linear types */
-1,-1,-1, /* unsupported types */
84, /* 84-node sixth order tetrahedron (4 nodes associated with the
vertices, 30 with the edges, 40 with the faces, and 10 with
the volume) */
120, /* 120-node seventh order tetrahedron (4 nodes associated with
the vertices, 36 with the edges, 60 with the faces, and 20
with the volume) */
165, /* 165-node eighth order tetrahedron (4 nodes associated with
the vertices, 42 with the edges, 84 with the faces, and 35
with the volume) */
220, /* 220-node ninth order tetrahedron (4 nodes associated with
the vertices, 48 with the edges, 112 with the faces, and 56
with the volume) */
286, /* 286-node tenth order tetrahedron (4 nodes associated with
the vertices, 54 with the edges, 144 with the faces, and 84
with the volume) */
-1,-1,-1, /* undefined types */
-1,-1,-1,-1,-1, /* unsupported tetrahedral types */
-1,-1,-1,-1,-1,-1, /* unsupported types */
40, /* 40-node third order prism (6 nodes associated with the
vertices, 18 with the edges, 14 with the faces, and 2 with
the volume) */
75, /* 75-node fourth order prism (6 nodes associated with the
vertices, 27 with the edges, 33 with the faces, and 9 with
the volume) */
64, /* 64-node third order hexahedron (8 nodes associated with the
vertices, 24 with the edges, 24 with the faces and 8 with
the volume).*/
125, /* 125-node fourth order hexahedron (8 nodes associated with
the vertices, 36 with the edges, 54 with the faces and 27
with the volume).*/
216, /* 216-node fifth order hexahedron (8 nodes associated with the
vertices, 48 with the edges, 96 with the faces and 64 with
the volume).*/
343, /* 343-node sixth order hexahedron (8 nodes associated with the
vertices, 60 with the edges, 150 with the faces and 125 with
the volume).*/
512, /* 512-node seventh order hexahedron (8 nodes associated with
the vertices, 72 with the edges, 216 with the faces and 216
with the volume).*/
729, /* 729-node eighth order hexahedron (8 nodes associated with
the vertices, 84 with the edges, 294 with the faces and 343
with the volume).*/
1000,/* 1000-node ninth order hexahedron (8 nodes associated with
the vertices, 96 with the edges, 384 with the faces and 512
with the volume).*/
-1,-1,-1,-1,-1,-1,-1, /* unsupported hexahedron types */
126, /* 126-node fifth order prism (6 nodes associated with the
vertices, 36 with the edges, 60 with the faces, and 24 with
the volume) */
196, /* 196-node sixth order prism (6 nodes associated with the
vertices, 45 with the edges, 95 with the faces, and 50 with
the volume) */
288, /* 288-node seventh order prism (6 nodes associated with the
vertices, 54 with the edges, 138 with the faces, and 90 with
the volume) */
405, /* 405-node eighth order prism (6 nodes associated with the
vertices, 63 with the edges, 189 with the faces, and 147
with the volume) */
550, /* 550-node ninth order prism (6 nodes associated with the
vertices, 72 with the edges, 248 with the faces, and 224
with the volume) */
-1,-1,-1,-1,-1,-1,-1, /* unsupported prism types */
30, /* 30-node third order pyramid (5 nodes associated with the
vertices, 16 with the edges and 8 with the faces, and 1 with
the volume). */
55, /* 55-node fourth order pyramid (5 nodes associated with the
vertices, 24 with the edges and 21 with the faces, and 5
with the volume). */
91, /* 91-node fifth order pyramid (5 nodes associated with the
vertices, 32 with the edges and 40 with the faces, and 14
with the volume). */
140, /* 140-node sixth order pyramid (5 nodes associated with the
vertices, 40 with the edges and 65 with the faces, and 30
with the volume). */
204, /* 204-node seventh order pyramid (5 nodes associated with the
vertices, 48 with the edges and 96 with the faces, and 55
with the volume). */
285, /* 285-node eighth order pyramid (5 nodes associated with the
vertices, 56 with the edges and 133 with the faces, and 91
with the volume). */
385 /* 385-node ninth order pyramid (5 nodes associated with the
vertices, 64 with the edges and 176 with the faces, and 140
with the volume). */
};
/** The following mappings convert the Gmsh node orderings for high
order elements to MFEM's L2 degree of freedom ordering. To support
more options examine Gmsh's ordering and read off the indices in
MFEM's order. For example 2nd order Gmsh quadrilaterals use the
following ordering:
3--6--2
| | |
7 8 5
| | |
0--4--1
(from https://gmsh.info/doc/texinfo/gmsh.html#Node-ordering)
Whereas MFEM uses a tensor product ordering with the horizontal
axis cycling fastest so we would read off:
0 4 1 7 8 5 3 6 2
This corresponds to the quad9 mapping below.
*/
int lin3[] = {0,2,1}; // 2nd order segment
int lin4[] = {0,2,3,1}; // 3rd order segment
int tri6[] = {0,3,1,5,4,2}; // 2nd order triangle
int tri10[] = {0,3,4,1,8,9,5,7,6,2}; // 3rd order triangle
int quad9[] = {0,4,1,7,8,5,3,6,2}; // 2nd order quadrilateral
int quad16[] = {0,4,5,1,11,12,13,6, // 3rd order quadrilateral
10,15,14,7,3,9,8,2
};
int tet10[] {0,4,1,6,5,2,7,9,8,3}; // 2nd order tetrahedron
int tet20[] = {0,4,5,1,9,16,6,8,7,2, // 3rd order tetrahedron
11,17,15,18,19,13,10,14,12,3
};
int hex27[] {0,8,1,9,20,11,3,13,2, // 2nd order hexahedron
10,21,12,22,26,23,15,24,14,
4,16,5,17,25,18,7,19,6
};
int hex64[] {0,8,9,1,10,32,35,14, // 3rd order hexahedron
11,33,34,15,3,19,18,2,
12,36,37,16,40,56,57,44,
43,59,58,45,22,49,48,20,
13,39,38,17,41,60,61,47,
42,63,62,46,23,50,51,21,
4,24,25,5,26,52,53,28,
27,55,54,29,7,31,30,6
};
int wdg18[] = {0,6,1,7,9,2,8,15,10, // 2nd order wedge/prism
16,17,11,3,12,4,13,14,5
};
int wdg40[] = {0,6,7,1,8,24,12,9,13,2, // 3rd order wedge/prism
10,26,27,14,30,38,34,33,35,16,
11,29,28,15,31,39,37,32,36,17,
3,18,19,4,20,25,22,21,23,5
};
int pyr14[] = {0,5,1,6,13,8,3, // 2nd order pyramid
10,2,7,9,12,11,4
};
int pyr30[] = {0,5,6,1,7,25,28,11,8,26, // 3rd order pyramid
27,12,3,16,15,2,9,21,13,22,
29,23,19,24,17,10,14,20,18,4
};
vector<Element*> elements_0D, elements_1D, elements_2D, elements_3D;
elements_0D.reserve(num_of_all_elements);
elements_1D.reserve(num_of_all_elements);
elements_2D.reserve(num_of_all_elements);
elements_3D.reserve(num_of_all_elements);
// Temporary storage for high order vertices, if present
vector<Array<int>*> ho_verts_1D, ho_verts_2D, ho_verts_3D;
ho_verts_1D.reserve(num_of_all_elements);
ho_verts_2D.reserve(num_of_all_elements);
ho_verts_3D.reserve(num_of_all_elements);
// Temporary storage for order of elements
vector<int> ho_el_order_1D, ho_el_order_2D, ho_el_order_3D;
ho_el_order_1D.reserve(num_of_all_elements);
ho_el_order_2D.reserve(num_of_all_elements);
ho_el_order_3D.reserve(num_of_all_elements);
// Vertex order mappings
Array<int*> ho_lin(11); ho_lin = NULL;
Array<int*> ho_tri(11); ho_tri = NULL;
Array<int*> ho_sqr(11); ho_sqr = NULL;
Array<int*> ho_tet(11); ho_tet = NULL;
Array<int*> ho_hex(10); ho_hex = NULL;
Array<int*> ho_wdg(10); ho_wdg = NULL;
Array<int*> ho_pyr(10); ho_pyr = NULL;
// Use predefined arrays at lowest orders (for efficiency)
ho_lin[2] = lin3; ho_lin[3] = lin4;
ho_tri[2] = tri6; ho_tri[3] = tri10;
ho_sqr[2] = quad9; ho_sqr[3] = quad16;
ho_tet[2] = tet10; ho_tet[3] = tet20;
ho_hex[2] = hex27; ho_hex[3] = hex64;
ho_wdg[2] = wdg18; ho_wdg[3] = wdg40;
ho_pyr[2] = pyr14; ho_pyr[3] = pyr30;
bool has_nonpositive_phys_domain = false;
bool has_positive_phys_domain = false;
if (binary)
{
int n_elem_part = 0; // partial sum of elements that are read
const int header_size = 3;
// header consists of 3 numbers: type of the element, number of
// elements of this type, and number of tags
int header[header_size];
int n_elem_one_type; // number of elements of a specific type
while (n_elem_part < num_of_all_elements)
{
input.read(reinterpret_cast<char*>(header),
header_size*sizeof(int));
type_of_element = header[0];
n_elem_one_type = header[1];
n_tags = header[2];
n_elem_part += n_elem_one_type;
const int n_elem_nodes = nodes_of_gmsh_element[type_of_element-1];
vector<int> data(1+n_tags+n_elem_nodes);
for (int el = 0; el < n_elem_one_type; ++el)
{
input.read(reinterpret_cast<char*>(&data[0]),
data.size()*sizeof(int));
int dd = 0; // index for data array
serial_number = data[dd++];
// physical domain - the most important value (to distinguish
// materials with different properties)
phys_domain = (n_tags > 0) ? data[dd++] : 1;
// elementary domain - to distinguish different geometrical
// domains (typically, it's used rarely)
elem_domain = (n_tags > 1) ? data[dd++] : 0;
// the number of tags is bigger than 2 if there are some
// partitions (domain decompositions)
n_partitions = (n_tags > 2) ? data[dd++] : 0;
// we currently just skip the partitions if they exist, and go
// directly to vertices describing the mesh element
vector<int> vert_indices(n_elem_nodes);
for (int vi = 0; vi < n_elem_nodes; ++vi)
{
map<int, int>::const_iterator it =
vertices_map.find(data[1+n_tags+vi]);
if (it == vertices_map.end())
{
MFEM_ABORT("Gmsh file : vertex index doesn't exist");
}
vert_indices[vi] = it->second;
}
// Non-positive attributes are not allowed in MFEM. However,
// by default, Gmsh sets the physical domain of all elements
// to zero. In the case that all elements have physical domain
// zero, we will given them attribute 1. If only some elements
// have physical domain zero, we will throw an error.
if (phys_domain <= 0)
{
has_nonpositive_phys_domain = true;
phys_domain = 1;
}
else
{
has_positive_phys_domain = true;
}
// initialize the mesh element
int el_order = 11;
switch (type_of_element)
{
case 1: // 2-node line
case 8: // 3-node line (2nd order)
case 26: // 4-node line (3rd order)
case 27: // 5-node line (4th order)
case 28: // 6-node line (5th order)
case 62: // 7-node line (6th order)
case 63: // 8-node line (7th order)
case 64: // 9-node line (8th order)
case 65: // 10-node line (9th order)
case 66: // 11-node line (10th order)
{
elements_1D.push_back(
new Segment(&vert_indices[0], phys_domain));
if (type_of_element != 1)
{
el_order = n_elem_nodes - 1;
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_1D.push_back(hov);
ho_el_order_1D.push_back(el_order);
}
break;
}
case 2: el_order--; // 3-node triangle
case 9: el_order--; // 6-node triangle (2nd order)
case 21: el_order--; // 10-node triangle (3rd order)
case 23: el_order--; // 15-node triangle (4th order)
case 25: el_order--; // 21-node triangle (5th order)
case 42: el_order--; // 28-node triangle (6th order)
case 43: el_order--; // 36-node triangle (7th order)
case 44: el_order--; // 45-node triangle (8th order)
case 45: el_order--; // 55-node triangle (9th order)
case 46: el_order--; // 66-node triangle (10th order)
{
elements_2D.push_back(
new Triangle(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_2D.push_back(hov);
ho_el_order_2D.push_back(el_order);
}
break;
}
case 3: el_order--; // 4-node quadrangle
case 10: el_order--; // 9-node quadrangle (2nd order)
case 36: el_order--; // 16-node quadrangle (3rd order)
case 37: el_order--; // 25-node quadrangle (4th order)
case 38: el_order--; // 36-node quadrangle (5th order)
case 47: el_order--; // 49-node quadrangle (6th order)
case 48: el_order--; // 64-node quadrangle (7th order)
case 49: el_order--; // 81-node quadrangle (8th order)
case 50: el_order--; // 100-node quadrangle (9th order)
case 51: el_order--; // 121-node quadrangle (10th order)
{
elements_2D.push_back(
new Quadrilateral(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_2D.push_back(hov);
ho_el_order_2D.push_back(el_order);
}
break;
}
case 4: el_order--; // 4-node tetrahedron
case 11: el_order--; // 10-node tetrahedron (2nd order)
case 29: el_order--; // 20-node tetrahedron (3rd order)
case 30: el_order--; // 35-node tetrahedron (4th order)
case 31: el_order--; // 56-node tetrahedron (5th order)
case 71: el_order--; // 84-node tetrahedron (6th order)
case 72: el_order--; // 120-node tetrahedron (7th order)
case 73: el_order--; // 165-node tetrahedron (8th order)
case 74: el_order--; // 220-node tetrahedron (9th order)
case 75: el_order--; // 286-node tetrahedron (10th order)
{
#ifdef MFEM_USE_MEMALLOC
elements_3D.push_back(TetMemory.Alloc());
elements_3D.back()->SetVertices(&vert_indices[0]);
elements_3D.back()->SetAttribute(phys_domain);
#else
elements_3D.push_back(
new Tetrahedron(&vert_indices[0], phys_domain));
#endif
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 5: el_order--; // 8-node hexahedron
case 12: el_order--; // 27-node hexahedron (2nd order)
case 92: el_order--; // 64-node hexahedron (3rd order)
case 93: el_order--; // 125-node hexahedron (4th order)
case 94: el_order--; // 216-node hexahedron (5th order)
case 95: el_order--; // 343-node hexahedron (6th order)
case 96: el_order--; // 512-node hexahedron (7th order)
case 97: el_order--; // 729-node hexahedron (8th order)
case 98: el_order--; // 1000-node hexahedron (9th order)
{
el_order--; // Gmsh does not define an order 10 hex
elements_3D.push_back(
new Hexahedron(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 6: el_order--; // 6-node wedge
case 13: el_order--; // 18-node wedge (2nd order)
case 90: el_order--; // 40-node wedge (3rd order)
case 91: el_order--; // 75-node wedge (4th order)
case 106: el_order--; // 126-node wedge (5th order)
case 107: el_order--; // 196-node wedge (6th order)
case 108: el_order--; // 288-node wedge (7th order)
case 109: el_order--; // 405-node wedge (8th order)
case 110: el_order--; // 550-node wedge (9th order)
{
el_order--; // Gmsh does not define an order 10 wedge
elements_3D.push_back(
new Wedge(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 7: el_order--; // 5-node pyramid
case 14: el_order--; // 14-node pyramid (2nd order)
case 118: el_order--; // 30-node pyramid (3rd order)
case 119: el_order--; // 55-node pyramid (4th order)
case 120: el_order--; // 91-node pyramid (5th order)
case 121: el_order--; // 140-node pyramid (6th order)
case 122: el_order--; // 204-node pyramid (7th order)
case 123: el_order--; // 285-node pyramid (8th order)
case 124: el_order--; // 385-node pyramid (9th order)
{
el_order--; // Gmsh does not define an order 10 pyr
elements_3D.push_back(
new Pyramid(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 15: // 1-node point
{
elements_0D.push_back(
new Point(&vert_indices[0], phys_domain));
break;
}
default: // any other element
MFEM_WARNING("Unsupported Gmsh element type.");
break;
} // switch (type_of_element)
} // el (elements of one type)
} // all elements
} // if binary
else // ASCII
{
for (int el = 0; el < num_of_all_elements; ++el)
{
input >> serial_number >> type_of_element >> n_tags;
vector<int> data(n_tags);
for (int i = 0; i < n_tags; ++i) { input >> data[i]; }
// physical domain - the most important value (to distinguish
// materials with different properties)
phys_domain = (n_tags > 0) ? data[0] : 1;
// elementary domain - to distinguish different geometrical
// domains (typically, it's used rarely)
elem_domain = (n_tags > 1) ? data[1] : 0;
// the number of tags is bigger than 2 if there are some
// partitions (domain decompositions)
n_partitions = (n_tags > 2) ? data[2] : 0;
// we currently just skip the partitions if they exist, and go
// directly to vertices describing the mesh element
const int n_elem_nodes = nodes_of_gmsh_element[type_of_element-1];
vector<int> vert_indices(n_elem_nodes);
int index;
for (int vi = 0; vi < n_elem_nodes; ++vi)
{
input >> index;
map<int, int>::const_iterator it = vertices_map.find(index);
if (it == vertices_map.end())
{
MFEM_ABORT("Gmsh file : vertex index doesn't exist");
}
vert_indices[vi] = it->second;
}
// Non-positive attributes are not allowed in MFEM. However,
// by default, Gmsh sets the physical domain of all elements
// to zero. In the case that all elements have physical domain
// zero, we will given them attribute 1. If only some elements
// have physical domain zero, we will throw an error.
if (phys_domain <= 0)
{
has_nonpositive_phys_domain = true;
phys_domain = 1;
}
else
{
has_positive_phys_domain = true;
}
// initialize the mesh element
int el_order = 11;
switch (type_of_element)
{
case 1: // 2-node line
case 8: // 3-node line (2nd order)
case 26: // 4-node line (3rd order)
case 27: // 5-node line (4th order)
case 28: // 6-node line (5th order)
case 62: // 7-node line (6th order)
case 63: // 8-node line (7th order)
case 64: // 9-node line (8th order)
case 65: // 10-node line (9th order)
case 66: // 11-node line (10th order)
{
elements_1D.push_back(
new Segment(&vert_indices[0], phys_domain));
if (type_of_element != 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_1D.push_back(hov);
el_order = n_elem_nodes - 1;
ho_el_order_1D.push_back(el_order);
}
break;
}
case 2: el_order--; // 3-node triangle
case 9: el_order--; // 6-node triangle (2nd order)
case 21: el_order--; // 10-node triangle (3rd order)
case 23: el_order--; // 15-node triangle (4th order)
case 25: el_order--; // 21-node triangle (5th order)
case 42: el_order--; // 28-node triangle (6th order)
case 43: el_order--; // 36-node triangle (7th order)
case 44: el_order--; // 45-node triangle (8th order)
case 45: el_order--; // 55-node triangle (9th order)
case 46: el_order--; // 66-node triangle (10th order)
{
elements_2D.push_back(
new Triangle(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_2D.push_back(hov);
ho_el_order_2D.push_back(el_order);
}
break;
}
case 3: el_order--; // 4-node quadrangle
case 10: el_order--; // 9-node quadrangle (2nd order)
case 36: el_order--; // 16-node quadrangle (3rd order)
case 37: el_order--; // 25-node quadrangle (4th order)
case 38: el_order--; // 36-node quadrangle (5th order)
case 47: el_order--; // 49-node quadrangle (6th order)
case 48: el_order--; // 64-node quadrangle (7th order)
case 49: el_order--; // 81-node quadrangle (8th order)
case 50: el_order--; // 100-node quadrangle (9th order)
case 51: el_order--; // 121-node quadrangle (10th order)
{
elements_2D.push_back(
new Quadrilateral(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_2D.push_back(hov);
ho_el_order_2D.push_back(el_order);
}
break;
}
case 4: el_order--; // 4-node tetrahedron
case 11: el_order--; // 10-node tetrahedron (2nd order)
case 29: el_order--; // 20-node tetrahedron (3rd order)
case 30: el_order--; // 35-node tetrahedron (4th order)
case 31: el_order--; // 56-node tetrahedron (5th order)
case 71: el_order--; // 84-node tetrahedron (6th order)
case 72: el_order--; // 120-node tetrahedron (7th order)
case 73: el_order--; // 165-node tetrahedron (8th order)
case 74: el_order--; // 220-node tetrahedron (9th order)
case 75: el_order--; // 286-node tetrahedron (10th order)
{
#ifdef MFEM_USE_MEMALLOC
elements_3D.push_back(TetMemory.Alloc());
elements_3D.back()->SetVertices(&vert_indices[0]);
elements_3D.back()->SetAttribute(phys_domain);
#else
elements_3D.push_back(
new Tetrahedron(&vert_indices[0], phys_domain));
#endif
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 5: el_order--; // 8-node hexahedron
case 12: el_order--; // 27-node hexahedron (2nd order)
case 92: el_order--; // 64-node hexahedron (3rd order)
case 93: el_order--; // 125-node hexahedron (4th order)
case 94: el_order--; // 216-node hexahedron (5th order)
case 95: el_order--; // 343-node hexahedron (6th order)
case 96: el_order--; // 512-node hexahedron (7th order)
case 97: el_order--; // 729-node hexahedron (8th order)
case 98: el_order--; // 1000-node hexahedron (9th order)
{
el_order--;
elements_3D.push_back(
new Hexahedron(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 6: el_order--; // 6-node wedge
case 13: el_order--; // 18-node wedge (2nd order)
case 90: el_order--; // 40-node wedge (3rd order)
case 91: el_order--; // 75-node wedge (4th order)
case 106: el_order--; // 126-node wedge (5th order)
case 107: el_order--; // 196-node wedge (6th order)
case 108: el_order--; // 288-node wedge (7th order)
case 109: el_order--; // 405-node wedge (8th order)
case 110: el_order--; // 550-node wedge (9th order)
{
el_order--;
elements_3D.push_back(
new Wedge(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 7: el_order--; // 5-node pyramid
case 14: el_order--; // 14-node pyramid (2nd order)
case 118: el_order--; // 30-node pyramid (3rd order)
case 119: el_order--; // 55-node pyramid (4th order)
case 120: el_order--; // 91-node pyramid (5th order)
case 121: el_order--; // 140-node pyramid (6th order)
case 122: el_order--; // 204-node pyramid (7th order)
case 123: el_order--; // 285-node pyramid (8th order)
case 124: el_order--; // 385-node pyramid (9th order)
{
el_order--;
elements_3D.push_back(
new Pyramid(&vert_indices[0], phys_domain));
if (el_order > 1)
{
Array<int> * hov = new Array<int>;
hov->Append(&vert_indices[0], n_elem_nodes);
ho_verts_3D.push_back(hov);
ho_el_order_3D.push_back(el_order);
}
break;
}
case 15: // 1-node point
{
elements_0D.push_back(
new Point(&vert_indices[0], phys_domain));
break;
}
default: // any other element
MFEM_WARNING("Unsupported Gmsh element type.");
break;
} // switch (type_of_element)
} // el (all elements)
} // if ASCII
if (has_positive_phys_domain && has_nonpositive_phys_domain)
{
MFEM_ABORT("Non-positive element attribute in Gmsh mesh!\n"
"By default Gmsh sets element tags (attributes)"
" to '0' but MFEM requires that they be"
" positive integers.\n"
"Use \"Physical Curve\", \"Physical Surface\","
" or \"Physical Volume\" to set tags/attributes"
" for all curves, surfaces, or volumes in your"
" Gmsh geometry to values which are >= 1.");
}
else if (has_nonpositive_phys_domain)
{
mfem::out << "\nGmsh reader: all element attributes were zero.\n"
<< "MFEM only supports positive element attributes.\n"
<< "Setting element attributes to 1.\n\n";
}
if (!elements_3D.empty())
{
Dim = 3;
NumOfElements = static_cast<int>(elements_3D.size());
elements.SetSize(NumOfElements);
for (int el = 0; el < NumOfElements; ++el)
{
elements[el] = elements_3D[el];
}
NumOfBdrElements = static_cast<int>(elements_2D.size());
boundary.SetSize(NumOfBdrElements);
for (int el = 0; el < NumOfBdrElements; ++el)
{
boundary[el] = elements_2D[el];
}
for (size_t el = 0; el < ho_el_order_3D.size(); el++)
{
mesh_order = max(mesh_order, ho_el_order_3D[el]);
}
// discard other elements
for (size_t el = 0; el < elements_1D.size(); ++el)
{
delete elements_1D[el];
}
for (size_t el = 0; el < elements_0D.size(); ++el)
{
delete elements_0D[el];
}
}
else if (!elements_2D.empty())
{
Dim = 2;
NumOfElements = static_cast<int>(elements_2D.size());
elements.SetSize(NumOfElements);
for (int el = 0; el < NumOfElements; ++el)
{
elements[el] = elements_2D[el];
}
NumOfBdrElements = static_cast<int>(elements_1D.size());
boundary.SetSize(NumOfBdrElements);
for (int el = 0; el < NumOfBdrElements; ++el)
{
boundary[el] = elements_1D[el];
}
for (size_t el = 0; el < ho_el_order_2D.size(); el++)
{
mesh_order = max(mesh_order, ho_el_order_2D[el]);
}
// discard other elements
for (size_t el = 0; el < elements_0D.size(); ++el)
{
delete elements_0D[el];
}
}
else if (!elements_1D.empty())
{
Dim = 1;
NumOfElements = static_cast<int>(elements_1D.size());
elements.SetSize(NumOfElements);
for (int el = 0; el < NumOfElements; ++el)
{
elements[el] = elements_1D[el];
}
NumOfBdrElements = static_cast<int>(elements_0D.size());
boundary.SetSize(NumOfBdrElements);
for (int el = 0; el < NumOfBdrElements; ++el)
{
boundary[el] = elements_0D[el];
}
for (size_t el = 0; el < ho_el_order_1D.size(); el++)
{
mesh_order = max(mesh_order, ho_el_order_1D[el]);
}
}
else
{
MFEM_ABORT("Gmsh file : no elements found");
return;
}
if (mesh_order > 1)
{
curved = 1;
read_gf = 0;
// initialize mesh_geoms so we can create Nodes FE space below
this->SetMeshGen();
// Generate faces and edges so that we can define
// FE space on the mesh
this->FinalizeTopology();
// Construct GridFunction for uniformly spaced high order coords
FiniteElementCollection* nfec;
FiniteElementSpace* nfes;
nfec = new L2_FECollection(mesh_order, Dim,
BasisType::ClosedUniform);
nfes = new FiniteElementSpace(this, nfec, spaceDim,
Ordering::byVDIM);
Nodes_gf.SetSpace(nfes);
Nodes_gf.MakeOwner(nfec);
int o = 0;
int el_order = 1;
for (int el = 0; el < NumOfElements; el++)
{
const int * vm = NULL;
Array<int> * ho_verts = NULL;
switch (GetElementType(el))
{
case Element::SEGMENT:
ho_verts = ho_verts_1D[el];
el_order = ho_el_order_1D[el];
if (!ho_lin[el_order])
{
ho_lin[el_order] = new int[ho_verts->Size()];
GmshHOSegmentMapping(el_order, ho_lin[el_order]);
}
vm = ho_lin[el_order];
break;
case Element::TRIANGLE:
ho_verts = ho_verts_2D[el];
el_order = ho_el_order_2D[el];
if (!ho_tri[el_order])
{
ho_tri[el_order] = new int[ho_verts->Size()];
GmshHOTriangleMapping(el_order, ho_tri[el_order]);
}
vm = ho_tri[el_order];
break;
case Element::QUADRILATERAL:
ho_verts = ho_verts_2D[el];
el_order = ho_el_order_2D[el];
if (!ho_sqr[el_order])
{
ho_sqr[el_order] = new int[ho_verts->Size()];
GmshHOQuadrilateralMapping(el_order, ho_sqr[el_order]);
}
vm = ho_sqr[el_order];
break;
case Element::TETRAHEDRON:
ho_verts = ho_verts_3D[el];
el_order = ho_el_order_3D[el];
if (!ho_tet[el_order])
{
ho_tet[el_order] = new int[ho_verts->Size()];
GmshHOTetrahedronMapping(el_order, ho_tet[el_order]);
}
vm = ho_tet[el_order];
break;
case Element::HEXAHEDRON:
ho_verts = ho_verts_3D[el];
el_order = ho_el_order_3D[el];
if (!ho_hex[el_order])
{
ho_hex[el_order] = new int[ho_verts->Size()];
GmshHOHexahedronMapping(el_order, ho_hex[el_order]);
}
vm = ho_hex[el_order];
break;
case Element::WEDGE:
ho_verts = ho_verts_3D[el];
el_order = ho_el_order_3D[el];
if (!ho_wdg[el_order])
{
ho_wdg[el_order] = new int[ho_verts->Size()];
GmshHOWedgeMapping(el_order, ho_wdg[el_order]);
}
vm = ho_wdg[el_order];
break;
case Element::PYRAMID:
ho_verts = ho_verts_3D[el];
el_order = ho_el_order_3D[el];
if (!ho_pyr[el_order])
{
ho_pyr[el_order] = new int[ho_verts->Size()];
GmshHOPyramidMapping(el_order, ho_pyr[el_order]);
}
vm = ho_pyr[el_order];
break;
default: // Any other element type
MFEM_WARNING("Unsupported Gmsh element type.");
break;
}
int nv = (ho_verts) ? ho_verts->Size() : 0;
for (int v = 0; v<nv; v++)
{
real_t * c = GetVertex((*ho_verts)[vm[v]]);
for (int d=0; d<spaceDim; d++)
{
Nodes_gf(spaceDim * (o + v) + d) = c[d];
}
}
o += nv;
}
}
// Delete any high order element to vertex connectivity
for (size_t el=0; el<ho_verts_1D.size(); el++)
{
delete ho_verts_1D[el];
}
for (size_t el=0; el<ho_verts_2D.size(); el++)
{
delete ho_verts_2D[el];
}
for (size_t el=0; el<ho_verts_3D.size(); el++)
{
delete ho_verts_3D[el];
}
// Delete dynamically allocated high vertex order mappings
for (int ord=4; ord<ho_lin.Size(); ord++)
{
if (ho_lin[ord] != NULL) { delete [] ho_lin[ord]; }
}
for (int ord=4; ord<ho_tri.Size(); ord++)
{
if (ho_tri[ord] != NULL) { delete [] ho_tri[ord]; }
}
for (int ord=4; ord<ho_sqr.Size(); ord++)
{
if (ho_sqr[ord] != NULL) { delete [] ho_sqr[ord]; }
}
for (int ord=4; ord<ho_tet.Size(); ord++)
{
if (ho_tet[ord] != NULL) { delete [] ho_tet[ord]; }
}
for (int ord=4; ord<ho_hex.Size(); ord++)
{
if (ho_hex[ord] != NULL) { delete [] ho_hex[ord]; }
}
for (int ord=4; ord<ho_wdg.Size(); ord++)
{
if (ho_wdg[ord] != NULL) { delete [] ho_wdg[ord]; }
}
for (int ord=4; ord<ho_pyr.Size(); ord++)
{
if (ho_pyr[ord] != NULL) { delete [] ho_pyr[ord]; }
}
// Suppress warnings (MFEM_CONTRACT_VAR does not work here with nvcc):
++n_partitions;
++elem_domain;
MFEM_CONTRACT_VAR(n_partitions);
MFEM_CONTRACT_VAR(elem_domain);
} // section '$Elements'
else if (buff == "$PhysicalNames") // Named element sets
{
int num_names = 0;
int mdim,num;
string name;
input >> num_names;
for (int i=0; i < num_names; i++)
{
input >> mdim >> num;
getline(input, name);
// Trim leading white space
while (!name.empty() &&
(*name.begin() == ' ' || *name.begin() == '\t'))
{ name.erase(0,1);}
// Trim trailing white space
while (!name.empty() &&
(*name.rbegin() == ' ' || *name.rbegin() == '\t' ||
*name.rbegin() == '\n' || *name.rbegin() == '\r'))
{ name.resize(name.length()-1);}
// Remove enclosing quotes
if ( (*name.begin() == '"' || *name.begin() == '\'') &&
(*name.rbegin() == '"' || *name.rbegin() == '\''))
{
name = name.substr(1,name.length()-2);
}
phys_names_by_dim[mdim][num] = name;
}
}
else if (buff == "$Periodic") // Reading master/slave node pairs
{
curved = 1;
read_gf = 0;
periodic = true;
Array<int> v2v(NumOfVertices);
for (int i = 0; i < v2v.Size(); i++)
{
v2v[i] = i;
}
int num_per_ent;
int num_nodes;
input >> num_per_ent;
getline(input, buff); // Read end-of-line
for (int i = 0; i < num_per_ent; i++)
{
getline(input, buff); // Read and ignore entity dimension and tags
getline(input, buff); // If affine mapping exist, read and ignore
if (!strncmp(buff.c_str(), "Affine", 6))
{
input >> num_nodes;
}
else
{
num_nodes = atoi(buff.c_str());
}
for (int j=0; j<num_nodes; j++)
{
int slave, master;
input >> slave >> master;
v2v[slave - 1] = master - 1;
}
getline(input, buff); // Read end-of-line
}
// Follow existing long chains of slave->master in v2v array.
// Upon completion of this loop, each v2v[slave] will point to a true
// master vertex. This algorithm is useful for periodicity defined in
// multiple directions.
for (int slave = 0; slave < v2v.Size(); slave++)
{
int master = v2v[slave];
if (master != slave)
{
// This loop will end if it finds a circular dependency.
while (v2v[master] != master && master != slave)
{
master = v2v[master];
}
if (master == slave)
{
// if master and slave are the same vertex, circular dependency
// exists. We need to fix the problem, we choose slave.
v2v[slave] = slave;
}
else
{
// the long chain has ended on the true master vertex.
v2v[slave] = master;
}
}
}
// Convert nodes to discontinuous GridFunction (if they aren't already)
if (mesh_order == 1)
{
this->FinalizeTopology();
this->SetMeshGen();
this->SetCurvature(1, true, spaceDim, Ordering::byVDIM);
}
// Replace "slave" vertex indices in the element connectivity
// with their corresponding "master" vertex indices.
for (int i = 0; i < this->GetNE(); i++)
{
Element *el = this->GetElement(i);
int *v = el->GetVertices();
int nv = el->GetNVertices();
for (int j = 0; j < nv; j++)
{
v[j] = v2v[v[j]];
}
}
// Replace "slave" vertex indices in the boundary element connectivity
// with their corresponding "master" vertex indices.
for (int i = 0; i < this->GetNBE(); i++)
{
Element *el = this->GetBdrElement(i);
int *v = el->GetVertices();
int nv = el->GetNVertices();
for (int j = 0; j < nv; j++)
{
v[j] = v2v[v[j]];
}
}
}
} // we reach the end of the file
// Process set names
if (phys_names_by_dim.size() > 0)
{
// Process boundary attribute set names
for (auto const &bdr_attr : phys_names_by_dim[Dim-1])
{
if (!bdr_attribute_sets.AttributeSetExists(bdr_attr.second))
{
bdr_attribute_sets.CreateAttributeSet(bdr_attr.second);
}
bdr_attribute_sets.AddToAttributeSet(bdr_attr.second, bdr_attr.first);
}
// Process element attribute set names
for (auto const &attr : phys_names_by_dim[Dim])
{
if (!attribute_sets.AttributeSetExists(attr.second))
{
attribute_sets.CreateAttributeSet(attr.second);
}
attribute_sets.AddToAttributeSet(attr.second, attr.first);
}
}
this->RemoveUnusedVertices();
this->FinalizeTopology();
// If a high order coordinate field was created project it onto the mesh
if (mesh_order > 1)
{
SetCurvature(mesh_order, periodic, spaceDim, Ordering::byVDIM);
VectorGridFunctionCoefficient NodesCoef(&Nodes_gf);
Nodes->ProjectCoefficient(NodesCoef);
}
}
#ifdef MFEM_USE_NETCDF
namespace cubit
{
const int mfem_to_genesis_tet10[10] =
{
// 1,2,3,4,5,6,7,8,9,10
1,2,3,4,5,7,8,6,9,10
};
const int mfem_to_genesis_hex27[27] =
{
// 1,2,3,4,5,6,7,8,9,10,11,
1,2,3,4,5,6,7,8,9,10,11,
// 12,13,14,15,16,17,18,19
12,17,18,19,20,13,14,15,
// 20,21,22,23,24,25,26,27
16,22,26,25,27,24,23,21
};
const int mfem_to_genesis_pyramid14[14] =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
};
const int mfem_to_genesis_wedge18[18] =
{
1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 14, 15, 10, 11, 12, 16, 17, 18
};
const int mfem_to_genesis_tri6[6] =
{
1,2,3,4,5,6
};
const int mfem_to_genesis_quad9[9] =
{
1,2,3,4,5,6,7,8,9
};
const int cubit_side_map_tri3[3][2] =
{
{1,2}, // 1
{2,3}, // 2
{3,1}, // 3
};
const int cubit_side_map_quad4[4][2] =
{
{1,2}, // 1
{2,3}, // 2
{3,4}, // 3
{4,1}, // 4
};
const int cubit_side_map_tet4[4][3] =
{
{1,2,4}, // 1
{2,3,4}, // 2
{1,4,3}, // 3
{1,3,2} // 4
};
const int cubit_side_map_hex8[6][4] =
{
{1,2,6,5}, // 1 <-- Exodus II side_ids
{2,3,7,6}, // 2
{3,4,8,7}, // 3
{1,5,8,4}, // 4
{1,4,3,2}, // 5
{5,6,7,8} // 6
};
const int cubit_side_map_wedge6[5][4] =
{
{1,2,5,4}, // 1 (Quad4)
{2,3,6,5}, // 2
{3,1,4,6}, // 3
{1,3,2,0}, // 4 (Tri3; NB: 0 is placeholder!)
{4,5,6,0} // 5
};
const int cubit_side_map_pyramid5[5][4] =
{
{1, 2, 5, 0}, // 1 (Tri3)
{2, 3, 5, 0}, // 2
{3, 4, 5, 0}, // 3
{1, 5, 4, 0}, // 4
{1, 4, 3, 2} // 5 (Quad4)
};
enum CubitFaceType
{
FACE_EDGE2,
FACE_EDGE3,
FACE_TRI3,
FACE_TRI6,
FACE_QUAD4,
FACE_QUAD9 // Order = 2; center node.
};
enum CubitElementType
{
ELEMENT_TRI3,
ELEMENT_TRI6,
ELEMENT_QUAD4,
ELEMENT_QUAD9,
ELEMENT_TET4,
ELEMENT_TET10,
ELEMENT_HEX8,
ELEMENT_HEX27,
ELEMENT_WEDGE6,
ELEMENT_WEDGE18,
ELEMENT_PYRAMID5,
ELEMENT_PYRAMID14
};
/**
* CubitElement
*
* Stores information about a particular element.
*/
class CubitElement
{
public:
/// Default constructor.
CubitElement(CubitElementType element_type);
CubitElement() = delete;
/// Destructor.
~CubitElement() = default;
/// Returns the Cubit element type.
inline CubitElementType GetElementType() const { return _element_type; }
/// Returns the face type for a specified face. NB: sides have 1-based indexing.
CubitFaceType GetFaceType(size_t side_id = 1) const;
/// Returns the number of faces.
inline size_t GetNumFaces() const { return _num_faces; }
/// Returns the number of vertices.
inline size_t GetNumVertices() const { return _num_vertices; }
/// Returns the number of nodes (vertices + higher-order control points).
inline size_t GetNumNodes() const { return _num_nodes; }
/// Returns the number of vertices for a particular face.
size_t GetNumFaceVertices(size_t iface = 1) const;
/// Returns the order of the element.
inline uint8_t GetOrder() const { return _order; }
/// Creates an MFEM equivalent element using the supplied vertex IDs and block ID.
Element * BuildElement(Mesh & mesh, const int * vertex_ids,
const int block_id) const;
/// Creates an MFEM boundary element using the supplied vertex IDs and block ID.
Element * BuildBoundaryElement(Mesh & mesh, const int iface,
const int * vertex_ids, const int sideset_id) const;
/// Static method returning the element type for a given number of nodes per element and dimension.
static CubitElementType GetElementType(size_t num_nodes,
uint8_t dimension = 3);
protected:
/// Static method which returns the 2D Cubit element type for the number of nodes per element.
static CubitElementType Get2DElementType(size_t num_nodes);
/// Static method which returns the 3D Cubit element type for the number of nodes per element.
static CubitElementType Get3DElementType(size_t num_nodes);
/// Creates a new MFEM element. Used internally in BuildElement and BuildBoundaryElement.
Element * NewElement(Mesh & mesh, Geometry::Type geom, const int *vertices,
const int attribute) const;
private:
CubitElementType _element_type;
uint8_t _order;
size_t _num_vertices;
size_t _num_faces;
size_t _num_nodes;
};
CubitElement::CubitElement(CubitElementType element_type)
{
_element_type = element_type;
switch (element_type)
{
case ELEMENT_TRI3: // 2D.
_order = 1;
_num_vertices = 3;
_num_nodes = 3;
_num_faces = 3;
break;
case ELEMENT_TRI6:
_order = 2;
_num_vertices = 3;
_num_nodes = 6;
_num_faces = 3;
break;
case ELEMENT_QUAD4:
_order = 1;
_num_vertices = 4;
_num_nodes = 4;
_num_faces = 4;
break;
case ELEMENT_QUAD9:
_order = 2;
_num_vertices = 4;
_num_nodes = 9;
_num_faces = 4;
break;
case ELEMENT_TET4: // 3D.
_order = 1;
_num_vertices = 4;
_num_nodes = 4;
_num_faces = 4;
break;
case ELEMENT_TET10:
_order = 2;
_num_vertices = 4;
_num_nodes = 10;
_num_faces = 4;
break;
case ELEMENT_HEX8:
_order = 1;
_num_vertices = 8;
_num_nodes = 8;
_num_faces = 6;
break;
case ELEMENT_HEX27:
_order = 2;
_num_vertices = 8;
_num_nodes = 27;
_num_faces = 6;
break;
case ELEMENT_WEDGE6:
_order = 1;
_num_vertices = 6;
_num_nodes = 6;
_num_faces = 5;
break;
case ELEMENT_WEDGE18:
_order = 2;
_num_vertices = 6;
_num_nodes = 18;
_num_faces = 5;
break;
case ELEMENT_PYRAMID5:
_order = 1;
_num_vertices = 5;
_num_nodes = 5;
_num_faces = 5;
break;
case ELEMENT_PYRAMID14:
_order = 2;
_num_vertices = 5;
_num_nodes = 14;
_num_faces = 5;
break;
default:
MFEM_ABORT("Unsupported Cubit element type " << element_type << ".");
break;
}
}
CubitElementType CubitElement::Get3DElementType(size_t num_nodes)
{
switch (num_nodes)
{
case 4:
return ELEMENT_TET4;
case 10:
return ELEMENT_TET10;
case 8:
return ELEMENT_HEX8;
case 27:
return ELEMENT_HEX27;
case 6:
return ELEMENT_WEDGE6;
case 18:
return ELEMENT_WEDGE18;
case 5:
return ELEMENT_PYRAMID5;
case 14:
return ELEMENT_PYRAMID14;
default:
MFEM_ABORT("Unsupported 3D element with " << num_nodes << " nodes.");
}
}
CubitElementType CubitElement::Get2DElementType(size_t num_nodes)
{
switch (num_nodes)
{
case 3:
return ELEMENT_TRI3;
case 6:
return ELEMENT_TRI6;
case 4:
return ELEMENT_QUAD4;
case 9:
return ELEMENT_QUAD9;
default:
MFEM_ABORT("Unsupported 2D element with " << num_nodes << " nodes.");
}
}
CubitElementType CubitElement::GetElementType(size_t num_nodes,
uint8_t dimension)
{
if (dimension == 2)
{
return Get2DElementType(num_nodes);
}
else if (dimension == 3)
{
return Get3DElementType(num_nodes);
}
else
{
MFEM_ABORT("Unsupported Cubit dimension " << dimension << ".");
}
}
CubitFaceType CubitElement::GetFaceType(size_t side_id) const
{
// NB: 1-based indexing. See Exodus II file format specifications.
bool valid_id = (side_id >= 1 &&
side_id <= GetNumFaces());
if (!valid_id)
{
MFEM_ABORT("Encountered invalid side ID: " << side_id << ".");
}
switch (_element_type)
{
case ELEMENT_TRI3: // 2D.
return FACE_EDGE2;
case ELEMENT_TRI6:
return FACE_EDGE3;
case ELEMENT_QUAD4:
return FACE_EDGE2;
case ELEMENT_QUAD9:
return FACE_EDGE3;
case ELEMENT_TET4: // 3D.
return FACE_TRI3;
case ELEMENT_TET10:
return FACE_TRI6;
case ELEMENT_HEX8:
return FACE_QUAD4;
case ELEMENT_HEX27:
return FACE_QUAD9;
case ELEMENT_WEDGE6: // [Quad4, Quad4, Quad4, Tri3, Tri3]
return (side_id < 4 ? FACE_QUAD4 : FACE_TRI3);
case ELEMENT_WEDGE18: // [Quad9, Quad9, Quad9, Tri6, Tri6]
return (side_id < 4 ? FACE_QUAD9 : FACE_TRI6);
case ELEMENT_PYRAMID5: // [Tri3, Tri3, Tri3, Tri3, Quad4]
return (side_id < 5 ? FACE_TRI3 : FACE_QUAD4);
case ELEMENT_PYRAMID14: // [Tri6, Tri6, Tri6, Tri6, Quad9]
return (side_id < 5 ? FACE_TRI6 : FACE_QUAD9);
default:
MFEM_ABORT("Unknown element type: " << _element_type << ".");
}
}
size_t CubitElement::GetNumFaceVertices(size_t side_id) const
{
switch (GetFaceType(side_id))
{
case FACE_EDGE2:
case FACE_EDGE3:
return 2;
case FACE_TRI3:
case FACE_TRI6:
return 3;
case FACE_QUAD4:
case FACE_QUAD9:
return 4;
default:
MFEM_ABORT("Unrecognized Cubit face type " << GetFaceType(side_id) << ".");
}
}
mfem::Element * CubitElement::NewElement(Mesh &mesh, Geometry::Type geom,
const int *vertices,
const int attribute) const
{
Element *new_element = mesh.NewElement(geom);
new_element->SetVertices(vertices);
new_element->SetAttribute(attribute);
return new_element;
}
mfem::Element * CubitElement::BuildElement(Mesh &mesh,
const int *vertex_ids,
const int block_id) const
{
switch (GetElementType())
{
case ELEMENT_TRI3:
case ELEMENT_TRI6:
return NewElement(mesh, Geometry::TRIANGLE, vertex_ids, block_id);
case ELEMENT_QUAD4:
case ELEMENT_QUAD9:
return NewElement(mesh, Geometry::SQUARE, vertex_ids, block_id);
case ELEMENT_TET4:
case ELEMENT_TET10:
return NewElement(mesh, Geometry::TETRAHEDRON, vertex_ids, block_id);
case ELEMENT_HEX8:
case ELEMENT_HEX27:
return NewElement(mesh, Geometry::CUBE, vertex_ids, block_id);
case ELEMENT_WEDGE6:
case ELEMENT_WEDGE18:
return NewElement(mesh, Geometry::PRISM, vertex_ids, block_id);
case ELEMENT_PYRAMID5:
case ELEMENT_PYRAMID14:
return NewElement(mesh, Geometry::PYRAMID, vertex_ids, block_id);
default:
MFEM_ABORT("Unsupported Cubit element type encountered.");
}
}
mfem::Element * CubitElement::BuildBoundaryElement(Mesh &mesh,
const int face_id,
const int *vertex_ids,
const int sideset_id) const
{
switch (GetFaceType(face_id))
{
case FACE_EDGE2:
case FACE_EDGE3:
return NewElement(mesh, Geometry::SEGMENT, vertex_ids, sideset_id);
case FACE_TRI3:
case FACE_TRI6:
return NewElement(mesh, Geometry::TRIANGLE, vertex_ids, sideset_id);
case FACE_QUAD4:
case FACE_QUAD9:
return NewElement(mesh, Geometry::SQUARE, vertex_ids, sideset_id);
default:
MFEM_ABORT("Unsupported Cubit face type encountered.");
}
}
/**
* CubitBlock
*
* Stores the information about each block in a mesh. Each block can contain a different
* element type (although all element types must be of the same order and dimension).
*/
class CubitBlock
{
public:
CubitBlock() = delete;
~CubitBlock() = default;
/**
* Default initializer.
*/
CubitBlock(int dimension);
/**
* Returns a constant reference to the element info for a particular block.
*/
const CubitElement & GetBlockElement(int block_id) const;
/**
* Call to add each block individually.
*/
void AddBlockElement(int block_id, CubitElementType element_type);
/**
* Accessors.
*/
uint8_t GetOrder() const;
inline uint8_t GetDimension() const { return _dimension; }
inline size_t GetNumBlocks() const { return BlockIDs().size(); }
inline bool HasBlocks() const { return !BlockIDs().empty(); }
protected:
/**
* Checks that the order of a new block element matches the order of existing blocks. Called
* internally in method "addBlockElement".
*/
void CheckElementBlockIsCompatible(const CubitElement & new_block_element)
const;
/**
* Reset all block elements. Called internally in initializer.
*/
void ClearBlockElements();
/**
* Helper methods.
*/
inline const std::set<int> & BlockIDs() const { return _block_ids; }
bool HasBlockID(int block_id) const;
bool ValidBlockID(int block_id) const;
bool ValidDimension(int dimension) const;
private:
/**
* Stores all block IDs.
*/
std::set<int> _block_ids;
/**
* Maps from block ID to element.
*/
std::map<int, CubitElement> _block_element_for_block_id;
/**
* Dimension and order of block elements.
*/
uint8_t _dimension;
uint8_t _order;
};
CubitBlock::CubitBlock(int dimension)
{
if (!ValidDimension(dimension))
{
MFEM_ABORT("Invalid dimension '" << dimension << "' specified.");
}
_dimension = dimension;
ClearBlockElements();
}
void
CubitBlock::AddBlockElement(int block_id, CubitElementType element_type)
{
if (HasBlockID(block_id))
{
MFEM_ABORT("Block with ID '" << block_id << "' has already been added.");
}
else if (!ValidBlockID(block_id))
{
MFEM_ABORT("Illegal block ID '" << block_id << "'.");
}
CubitElement block_element = CubitElement(element_type);
/**
* Check element is compatible with existing element blocks.
*/
CheckElementBlockIsCompatible(block_element);
if (!HasBlocks()) // Set order of elements.
{
_order = block_element.GetOrder();
}
_block_ids.insert(block_id);
_block_element_for_block_id.emplace(block_id,
block_element);
}
uint8_t
CubitBlock::GetOrder() const
{
if (!HasBlocks())
{
MFEM_ABORT("No elements have been added.");
}
return _order;
}
void
CubitBlock::ClearBlockElements()
{
_order = 0;
_block_ids.clear();
_block_element_for_block_id.clear();
}
bool
CubitBlock::HasBlockID(int block_id) const
{
return (_block_ids.count(block_id) > 0);
}
bool
CubitBlock::ValidBlockID(int block_id) const
{
return (block_id > 0); // 1-based indexing.
}
bool
CubitBlock::ValidDimension(int dimension) const
{
return (dimension == 2 || dimension == 3);
}
const CubitElement &
CubitBlock::GetBlockElement(int block_id) const
{
if (!HasBlockID(block_id))
{
MFEM_ABORT("No element info for block ID '" << block_id << "'.");
}
return _block_element_for_block_id.at(block_id);
}
void
CubitBlock::CheckElementBlockIsCompatible(const CubitElement &
new_block_element) const
{
if (!HasBlocks())
{
return;
}
// Enforce block orders to be the same for now.
if (GetOrder() != new_block_element.GetOrder())
{
MFEM_ABORT("All block elements must be of the same order.");
}
}
/**
* Lightweight wrapper around NetCDF C functions.
*/
class NetCDFReader
{
public:
NetCDFReader() = delete;
NetCDFReader(const std::string fname);
~NetCDFReader();
/// Returns true if variable id for that name exists.
bool HasVariable(const char * name);
/// Read variable info from file and write to int buffer.
void ReadVariable(const char * name, int * data);
/// Read variable info from file and write to double buffer.
void ReadVariable(const char * name, double * data);
/// Returns true if dimension id for that name exists.
bool HasDimension(const char * name);
/// Read dimension info from file.
void ReadDimension(const char * name, size_t *dimension);
/// Build the map from quantity ID to name, e.g. block ID to block name or boundary ID to boundary name
void BuildIDToNameMap(const vector<int> & ids,
unordered_map<int, string> & ids_to_names,
const string & quantity_name);
protected:
/// Called internally. Calls HandleNetCDFError if _netcdf_status is not "NC_NOERR".
void CheckForNetCDFError();
/// Called in "ReadVariable" methods to extract variable id.
int ReadVariableID(const char * name);
/// Called in "ReadDimension" to extract dimension id.
int ReadDimensionID(const char * name);
private:
/// Calls MFEM_Abort with string description of NetCDF error.
void HandleNetCDFError(const int error_code);
int _netcdf_status{NC_NOERR};
int _netcdf_descriptor;
/// Internal buffer. Used in ReadDimension to write unwanted name to.
char *_name_buffer{NULL};
};
NetCDFReader::NetCDFReader(const std::string fname)
{
_netcdf_status = nc_open(fname.c_str(), NC_NOWRITE, &_netcdf_descriptor);
CheckForNetCDFError();
// NB: add byte for '\0' terminating char.
_name_buffer = new char[NC_MAX_NAME + 1];
}
NetCDFReader::~NetCDFReader()
{
_netcdf_status = nc_close(_netcdf_descriptor);
CheckForNetCDFError();
if (_name_buffer)
{
delete[] _name_buffer;
}
}
void NetCDFReader::CheckForNetCDFError()
{
if (_netcdf_status != NC_NOERR)
{
HandleNetCDFError(_netcdf_status);
}
}
void NetCDFReader::HandleNetCDFError(const int error_code)
{
MFEM_ABORT("Fatal NetCDF error: " << nc_strerror(error_code));
}
int NetCDFReader::ReadVariableID(const char * var_name)
{
int variable_id;
_netcdf_status = nc_inq_varid(_netcdf_descriptor, var_name,
&variable_id);
CheckForNetCDFError();
return variable_id;
}
int NetCDFReader::ReadDimensionID(const char * name)
{
int dim_id;
_netcdf_status = nc_inq_dimid(_netcdf_descriptor, name, &dim_id);
CheckForNetCDFError();
return dim_id;
}
void NetCDFReader::ReadDimension(const char * name, size_t *dimension)
{
const int dimension_id = ReadDimensionID(name);
// NB: ignore name output (write to private buffer).
_netcdf_status = nc_inq_dim(_netcdf_descriptor, dimension_id, _name_buffer,
dimension);
CheckForNetCDFError();
}
bool NetCDFReader::HasVariable(const char * name)
{
int var_id;
const int status = nc_inq_varid(_netcdf_descriptor, name, &var_id);
switch (status)
{
case NC_NOERR: // Found!
return true;
case NC_ENOTVAR: // Not found.
return false;
default:
HandleNetCDFError(status);
return false;
}
}
bool NetCDFReader::HasDimension(const char * name)
{
int dim_id;
const int status = nc_inq_dimid(_netcdf_descriptor, name, &dim_id);
switch (status)
{
case NC_NOERR: // Found!
return true;
case NC_EBADDIM: // Not found.
return false;
default:
HandleNetCDFError(status);
return false;
}
}
void NetCDFReader::ReadVariable(const char * name, int * data)
{
const int variable_id = ReadVariableID(name);
_netcdf_status = nc_get_var_int(_netcdf_descriptor, variable_id, data);
CheckForNetCDFError();
}
void NetCDFReader::ReadVariable(const char * name, double * data)
{
const int variable_id = ReadVariableID(name);
_netcdf_status = nc_get_var_double(_netcdf_descriptor, variable_id, data);
CheckForNetCDFError();
}
void NetCDFReader::BuildIDToNameMap(const vector<int> & ids,
unordered_map<int, string> & ids_to_names,
const string & quantity_name)
{
int varid_names;
// Find the variable ID for the given quantity_name (e.g. eb_names, ss_names)
_netcdf_status = nc_inq_varid(_netcdf_descriptor, quantity_name.c_str(),
&varid_names);
// It's possible the netcdf file doesn't contain the variable, in which case
// there's nothing to do
if (_netcdf_status == NC_ENOTVAR)
{
return;
}
else
{
CheckForNetCDFError();
}
// Get type of quantity_name
nc_type var_type;
_netcdf_status = nc_inq_vartype(_netcdf_descriptor, varid_names,
&var_type);
CheckForNetCDFError();
if (var_type == NC_CHAR)
{
int dimids_names[2], names_ndim;
size_t num_names, name_len;
_netcdf_status = nc_inq_varndims(_netcdf_descriptor, varid_names,
&names_ndim);
CheckForNetCDFError();
MFEM_ASSERT(names_ndim == 2, "This variable should have two dimensions");
_netcdf_status = nc_inq_vardimid(_netcdf_descriptor, varid_names,
dimids_names);
CheckForNetCDFError();
_netcdf_status = nc_inq_dimlen(_netcdf_descriptor, dimids_names[0], &num_names);
CheckForNetCDFError();
MFEM_ASSERT(num_names == ids.size(),
"The block id and block name lengths don't match");
// Check the maximum string length
_netcdf_status = nc_inq_dimlen(_netcdf_descriptor, dimids_names[1], &name_len);
CheckForNetCDFError();
// Read the block names
vector<char> names(ids.size() * name_len);
_netcdf_status = nc_get_var_text(_netcdf_descriptor, varid_names,
names.data());
CheckForNetCDFError();
for (size_t i = 0; i < ids.size(); ++i)
{
string name(&names[i * name_len], name_len);
// shorten string
name.resize(name.find('\0'));
ids_to_names[ids[i]] = name;
}
}
else
{
mfem_error("Unexpected netcdf variable type");
}
}
/// @brief Reads the coordinate data from the Genesis file.
static void ReadCubitNodeCoordinates(NetCDFReader & cubit_reader,
double *coordx,
double *coordy,
double *coordz)
{
cubit_reader.ReadVariable("coordx", coordx);
cubit_reader.ReadVariable("coordy", coordy);
if (coordz)
{
cubit_reader.ReadVariable("coordz", coordz);
}
}
/// @brief Reads the number of elements in each block.
static void ReadCubitNumElementsInBlock(NetCDFReader & cubit_reader,
const vector<int> & block_ids,
map<int, size_t> &num_elements_for_block_id)
{
num_elements_for_block_id.clear();
// NB: need to add 1 for '\0' terminating character.
const int buffer_size = NC_MAX_NAME + 1;
char string_buffer[buffer_size];
int iblock = 1;
for (const auto block_id : block_ids)
{
// Write variable name to buffer.
snprintf(string_buffer, buffer_size, "num_el_in_blk%d", iblock++);
size_t num_elements_for_block = 0;
cubit_reader.ReadDimension(string_buffer, &num_elements_for_block);
num_elements_for_block_id[block_id] = num_elements_for_block;
}
}
/// @brief Builds the mappings:
/// (blockID --> (elements in block)); (elementID --> blockID)
static void BuildElementIDsForBlockID(
const vector<int> & block_ids,
const map<int, size_t> & num_elements_for_block_id,
map<int, vector<int>> & element_ids_for_block_id,
map<int, int> & block_id_for_element_id)
{
element_ids_for_block_id.clear();
block_id_for_element_id.clear();
// From the Exodus II specifications, the element ID is numbered contiguously starting
// from 1 across the element blocks.
int element_id = 1;
for (int block_id : block_ids)
{
const int num_elements_for_block = num_elements_for_block_id.at(block_id);
vector<int> element_ids(num_elements_for_block);
for (int i = 0; i < num_elements_for_block; i++, element_id++)
{
element_ids[i] = element_id;
block_id_for_element_id[element_id] = block_id;
}
element_ids_for_block_id[block_id] = std::move(element_ids);
}
}
/// @brief Reads the element types for each block.
static void ReadCubitBlocks(NetCDFReader & cubit_reader,
const vector<int> block_ids,
CubitBlock & cubit_blocks)
{
const int buffer_size = NC_MAX_NAME + 1;
char string_buffer[buffer_size];
size_t num_nodes_per_element;
int iblock = 1;
for (int block_id : block_ids)
{
// Write variable name to buffer.
snprintf(string_buffer, buffer_size, "num_nod_per_el%d", iblock++);
cubit_reader.ReadDimension(string_buffer, &num_nodes_per_element);
// Determine the element type:
CubitElementType element_type = CubitElement::GetElementType(
num_nodes_per_element, cubit_blocks.GetDimension());
cubit_blocks.AddBlockElement(block_id, element_type);
}
}
/// @brief Extracts core dimension information from Genesis file.
static void ReadCubitDimensions(NetCDFReader & cubit_reader,
size_t &num_dim,
size_t &num_nodes,
size_t &num_elem,
size_t &num_el_blk,
size_t &num_side_sets)
{
cubit_reader.ReadDimension("num_dim", &num_dim);
cubit_reader.ReadDimension("num_nodes", &num_nodes);
cubit_reader.ReadDimension("num_elem", &num_elem);
cubit_reader.ReadDimension("num_el_blk", &num_el_blk);
// Optional: if not present, num_side_sets = 0.
if (cubit_reader.HasDimension("num_side_sets"))
{
cubit_reader.ReadDimension("num_side_sets", &num_side_sets);
}
else
{
num_side_sets = 0;
}
}
/// @brief Extracts the element ids corresponding to elements that lie on each boundary;
/// also extracts the side ids of those elements which lie on the boundary.
static void ReadCubitBoundaries(NetCDFReader & cubit_reader,
const vector<int> & boundary_ids,
map<int, vector<int>> & element_ids_for_boundary_id,
map<int, vector<int>> & side_ids_for_boundary_id)
{
const int buffer_size = NC_MAX_NAME + 1;
char string_buffer[buffer_size];
int ibdr = 1;
for (int boundary_id : boundary_ids)
{
// 1. Extract number of elements/sides for boundary.
size_t num_sides = 0;
snprintf(string_buffer, buffer_size, "num_side_ss%d", ibdr);
cubit_reader.ReadDimension(string_buffer, &num_sides);
// 2. Extract elements and sides on each boundary (1-indexed!)
vector<int> boundary_element_ids(num_sides); // (element, face) pairs.
vector<int> boundary_side_ids(num_sides);
//
snprintf(string_buffer, buffer_size, "elem_ss%d", ibdr);
cubit_reader.ReadVariable(string_buffer, boundary_element_ids.data());
//
snprintf(string_buffer, buffer_size,"side_ss%d", ibdr++);
cubit_reader.ReadVariable(string_buffer, boundary_side_ids.data());
// 3. Add to maps.
element_ids_for_boundary_id[boundary_id] = std::move(boundary_element_ids);
side_ids_for_boundary_id[boundary_id] = std::move(boundary_side_ids);
}
}
/// @brief Reads the block ids from the Genesis file.
static void BuildCubitBlockIDs(NetCDFReader & cubit_reader,
const int num_element_blocks,
vector<int> & block_ids)
{
block_ids.resize(num_element_blocks);
cubit_reader.ReadVariable("eb_prop1", block_ids.data());
}
/// @brief Reads the boundary ids from the Genesis file.
static void ReadCubitBoundaryIDs(NetCDFReader & cubit_reader,
const int num_boundaries, vector<int> & boundary_ids)
{
boundary_ids.clear();
if (num_boundaries < 1) { return; }
boundary_ids.resize(num_boundaries);
cubit_reader.ReadVariable("ss_prop1", boundary_ids.data());
}
/// @brief Reads the node ids for each element from the Genesis file.
static void ReadCubitElementBlocks(NetCDFReader & cubit_reader,
const CubitBlock & cubit_blocks,
const vector<int> & block_ids,
const map<int, vector<int>> & element_ids_for_block_id,
map<int, vector<int>> &node_ids_for_element_id)
{
const int buffer_size = NC_MAX_NAME + 1;
char string_buffer[buffer_size];
int iblock = 1;
for (const int block_id : block_ids)
{
const CubitElement & block_element = cubit_blocks.GetBlockElement(block_id);
const vector<int> & block_element_ids = element_ids_for_block_id.at(block_id);
const size_t num_nodes_for_block = block_element_ids.size() *
block_element.GetNumNodes();
vector<int> node_ids_for_block(num_nodes_for_block);
// Write variable name to buffer.
snprintf(string_buffer, buffer_size, "connect%d", iblock++);
cubit_reader.ReadVariable(string_buffer, node_ids_for_block.data());
// Now map from the element id to the nodes:
int ielement = 0;
for (int element_id : block_element_ids)
{
vector<int> element_node_ids(block_element.GetNumNodes());
for (int i = 0; i < (int)block_element.GetNumNodes(); i++)
{
element_node_ids[i] = node_ids_for_block[ielement * block_element.GetNumNodes()
+ i];
}
ielement++;
node_ids_for_element_id[element_id] = std::move(element_node_ids);
}
}
}
/// @brief Builds a mapping from the boundary ID to the face vertices of each element that lie on the boundary.
static void BuildBoundaryNodeIDs(const vector<int> & boundary_ids,
const CubitBlock & blocks,
const map<int, vector<int>> & node_ids_for_element_id,
const map<int, vector<int>> & element_ids_for_boundary_id,
const map<int, vector<int>> & side_ids_for_boundary_id,
const map<int, int> & block_id_for_element_id,
map<int, vector<vector<int>>> & node_ids_for_boundary_id)
{
for (int boundary_id : boundary_ids)
{
// Get element IDs of element on boundary (and their sides that are on boundary).
auto & boundary_element_ids = element_ids_for_boundary_id.at(
boundary_id);
auto & boundary_element_sides = side_ids_for_boundary_id.at(
boundary_id);
// Create vector to store the node ids of all boundary nodes.
vector<vector<int>> boundary_node_ids(
boundary_element_ids.size());
// Iterate over elements on boundary.
for (int jelement = 0; jelement < (int)boundary_element_ids.size(); jelement++)
{
// Get element ID and the boundary side.
const int boundary_element_global_id = boundary_element_ids[jelement];
const int boundary_side = boundary_element_sides[jelement];
// Get the element information:
const int block_id = block_id_for_element_id.at(boundary_element_global_id);
const CubitElement & block_element = blocks.GetBlockElement(block_id);
const int num_face_vertices = block_element.GetNumFaceVertices(boundary_side);
vector<int> nodes_of_element_on_side(num_face_vertices);
// Get all of the element's nodes on boundary side of element.
const vector<int> & element_node_ids =
node_ids_for_element_id.at(boundary_element_global_id);
// Iterate over the element's face nodes on the matching side.
// NB: only adding vertices on face (ignore higher-order).
for (int knode = 0; knode < num_face_vertices; knode++)
{
int inode;
switch (block_element.GetElementType())
{
case ELEMENT_TRI3:
case ELEMENT_TRI6:
inode = cubit_side_map_tri3[boundary_side - 1][knode];
break;
case ELEMENT_QUAD4:
case ELEMENT_QUAD9:
inode = cubit_side_map_quad4[boundary_side - 1][knode];
break;
case ELEMENT_TET4:
case ELEMENT_TET10:
inode = cubit_side_map_tet4[boundary_side - 1][knode];
break;
case ELEMENT_HEX8:
case ELEMENT_HEX27:
inode = cubit_side_map_hex8[boundary_side - 1][knode];
break;
case ELEMENT_WEDGE6:
case ELEMENT_WEDGE18:
inode = cubit_side_map_wedge6[boundary_side - 1][knode];
break;
case ELEMENT_PYRAMID5:
case ELEMENT_PYRAMID14:
inode = cubit_side_map_pyramid5[boundary_side - 1][knode];
break;
default:
MFEM_ABORT("Unsupported element type encountered.\n");
break;
}
nodes_of_element_on_side[knode] = element_node_ids[inode - 1];
}
boundary_node_ids[jelement] = std::move(nodes_of_element_on_side);
}
// Add to the map.
node_ids_for_boundary_id[boundary_id] = std::move(boundary_node_ids);
}
}
/// @brief Generates a vector of unique vertex ID.
static void BuildUniqueVertexIDs(const vector<int> & unique_block_ids,
const CubitBlock & blocks,
const map<int, vector<int>> & element_ids_for_block_id,
const map<int, vector<int>> & node_ids_for_element_id,
vector<int> & unique_vertex_ids)
{
// Iterate through all vertices and add their global IDs to the unique_vertex_ids vector.
for (int block_id : unique_block_ids)
{
auto & element_ids = element_ids_for_block_id.at(block_id);
auto & block_element = blocks.GetBlockElement(block_id);
for (int element_id : element_ids)
{
auto & node_ids = node_ids_for_element_id.at(element_id);
for (size_t knode = 0; knode < block_element.GetNumVertices(); knode++)
{
unique_vertex_ids.push_back(node_ids[knode]);
}
}
}
// Sort unique_vertex_ids in ascending order and remove duplicate node IDs.
std::sort(unique_vertex_ids.begin(), unique_vertex_ids.end());
auto new_end = std::unique(unique_vertex_ids.begin(), unique_vertex_ids.end());
unique_vertex_ids.resize(std::distance(unique_vertex_ids.begin(), new_end));
}
/// @brief unique_vertex_ids contains a 1-based sorted list of vertex IDs used by the mesh. We
/// now create a map by running over the vertex IDs and remapping to a contiguous
/// 1-based array of integers.
static void BuildCubitToMFEMVertexMap(const vector<int> & unique_vertex_ids,
map<int, int> & cubit_to_mfem_vertex_map)
{
cubit_to_mfem_vertex_map.clear();
int ivertex = 1;
for (int vertex_id : unique_vertex_ids)
{
cubit_to_mfem_vertex_map[vertex_id] = ivertex++;
}
}
/// @brief The final step in constructing the mesh from a Genesis file. This is
/// only called if the mesh order == 2 (determined internally from the cubit
/// element type).
static void FinalizeCubitSecondOrderMesh(Mesh &mesh,
const vector<int> & unique_block_ids,
const CubitBlock & blocks,
const map<int, vector<int>> & element_ids_for_block_id,
const map<int, vector<int>> & node_ids_for_element_id,
const double *coordx,
const double *coordy,
const double *coordz)
{
mesh.FinalizeTopology();
// Define quadratic FE space.
const int Dim = mesh.Dimension();
FiniteElementCollection *fec = new H1_FECollection(2, Dim);
FiniteElementSpace *fes = new FiniteElementSpace(&mesh, fec, Dim,
Ordering::byVDIM);
GridFunction *Nodes = new GridFunction(fes);
Nodes->MakeOwner(fec); // Nodes will destroy 'fec' and 'fes'
mesh.SetNodalGridFunction(Nodes, true);
for (int block_id : unique_block_ids)
{
const CubitElement & block_element = blocks.GetBlockElement(block_id);
int *mfem_to_genesis_map = NULL;
switch (block_element.GetElementType())
{
case ELEMENT_TRI6:
mfem_to_genesis_map = (int *) mfem_to_genesis_tri6;
break;
case ELEMENT_QUAD9:
mfem_to_genesis_map = (int *) mfem_to_genesis_quad9;
break;
case ELEMENT_TET10:
mfem_to_genesis_map = (int *) mfem_to_genesis_tet10;
break;
case ELEMENT_HEX27:
mfem_to_genesis_map = (int *) mfem_to_genesis_hex27;
break;
case ELEMENT_WEDGE18:
mfem_to_genesis_map = (int *) mfem_to_genesis_wedge18;
break;
case ELEMENT_PYRAMID14:
mfem_to_genesis_map = (int *) mfem_to_genesis_pyramid14;
break;
default:
MFEM_ABORT("Something went wrong. Linear elements detected when order is 2.");
}
auto & element_ids = element_ids_for_block_id.at(block_id);
for (int element_id : element_ids)
{
// NB: 1-index (Exodus) --> 0-index (MFEM).
Array<int> dofs;
fes->GetElementDofs(element_id - 1, dofs);
Array<int> vdofs = dofs; // Deep copy.
fes->DofsToVDofs(vdofs);
const vector<int> & element_node_ids = node_ids_for_element_id.at(element_id);
for (int jnode = 0; jnode < dofs.Size(); jnode++)
{
const int node_index = element_node_ids[mfem_to_genesis_map[jnode] - 1] - 1;
(*Nodes)(vdofs[jnode]) = coordx[node_index];
(*Nodes)(vdofs[jnode] + 1) = coordy[node_index];
if (Dim == 3)
{
(*Nodes)(vdofs[jnode] + 2) = coordz[node_index];
}
}
}
}
}
} // end of namespace cubit.
/// @brief Set the coordinates of the Cubit vertices.
void Mesh::BuildCubitVertices(const vector<int> & unique_vertex_ids,
const vector<double> & coordx,
const vector<double> & coordy,
const vector<double> & coordz)
{
NumOfVertices = unique_vertex_ids.size();
vertices.SetSize(NumOfVertices);
for (int ivertex = 0; ivertex < NumOfVertices; ivertex++)
{
const int original_1based_id = unique_vertex_ids[ivertex];
vertices[ivertex](0) = coordx[original_1based_id - 1];
vertices[ivertex](1) = coordy[original_1based_id - 1];
if (Dim == 3)
{
vertices[ivertex](2) = coordz[original_1based_id - 1];
}
}
}
/// @brief Create Cubit elements.
void Mesh::BuildCubitElements(const int num_elements,
const cubit::CubitBlock * blocks,
const vector<int> & block_ids,
const map<int, vector<int>> & element_ids_for_block_id,
const map<int, vector<int>> & node_ids_for_element_id,
const map<int, int> & cubit_to_mfem_vertex_map)
{
using namespace cubit;
NumOfElements = num_elements;
elements.SetSize(num_elements);
int element_counter = 0;
// Iterate over blocks.
for (int block_id : block_ids)
{
const CubitElement & block_element = blocks->GetBlockElement(block_id);
vector<int> renumbered_vertex_ids(block_element.GetNumVertices());
const vector<int> &block_element_ids = element_ids_for_block_id.at(block_id);
// Iterate over elements in block.
for (int element_id : block_element_ids)
{
const vector<int> & element_node_ids = node_ids_for_element_id.at(element_id);
// Iterate over linear (vertex) nodes in block.
for (size_t knode = 0; knode < block_element.GetNumVertices(); knode++)
{
const int node_id = element_node_ids[knode];
// Renumber using the mapping.
renumbered_vertex_ids[knode] = cubit_to_mfem_vertex_map.at(node_id) - 1;
}
// Create element.
elements[element_counter++] = block_element.BuildElement(*this,
renumbered_vertex_ids.data(),
block_id);
}
}
}
/// @brief Build the Cubit boundaries.
void Mesh::BuildCubitBoundaries(
const cubit::CubitBlock * blocks,
const vector<int> & boundary_ids,
const map<int, vector<int>> & element_ids_for_boundary_id,
const map<int, vector<vector<int>>> & node_ids_for_boundary_id,
const map<int, vector<int>> & side_ids_for_boundary_id,
const map<int, int> & block_id_for_element_id,
const map<int, int> & cubit_to_mfem_vertex_map)
{
using namespace cubit;
NumOfBdrElements = 0;
for (int boundary_id : boundary_ids)
{
NumOfBdrElements += element_ids_for_boundary_id.at(boundary_id).size();
}
boundary.SetSize(NumOfBdrElements);
array<int, 8> renumbered_vertex_ids; // Set to max number of vertices (Hex27).
// Iterate over boundaries.
int boundary_counter = 0;
for (int boundary_id : boundary_ids)
{
const vector<int> &elements_on_boundary = element_ids_for_boundary_id.at(
boundary_id);
const vector<vector<int>> &nodes_on_boundary = node_ids_for_boundary_id.at(
boundary_id);
int jelement = 0;
for (int side_id : side_ids_for_boundary_id.at(boundary_id))
{
// Determine the block the element originates from and the element type.
const int element_id = elements_on_boundary.at(jelement);
const int element_block = block_id_for_element_id.at(element_id);
const CubitElement & block_element = blocks->GetBlockElement(element_block);
const vector<int> & element_nodes_on_side = nodes_on_boundary.at(jelement);
// Iterate over element's face vertices.
for (size_t knode = 0; knode < element_nodes_on_side.size(); knode++)
{
const int node_id = element_nodes_on_side[knode];
// Renumber using the mapping.
renumbered_vertex_ids[knode] = cubit_to_mfem_vertex_map.at(node_id) - 1;
}
// Create boundary element.
boundary[boundary_counter++] = block_element.BuildBoundaryElement(*this,
side_id,
renumbered_vertex_ids.data(),
boundary_id);
jelement++;
}
}
}
void Mesh::ReadCubit(const std::string &filename, int &curved, int &read_gf)
{
using namespace cubit;
read_gf = 0;
curved = 0; // Set to 1 if mesh is curved.
//
// Open the file.
//
NetCDFReader cubit_reader(filename);
//
// Read important dimensions from file.
//
size_t num_dimensions, num_nodes, num_elements, num_element_blocks,
num_boundaries;
ReadCubitDimensions(cubit_reader, num_dimensions, num_nodes, num_elements,
num_element_blocks, num_boundaries);
Dim = num_dimensions;
//
// Read the blocks.
//
vector<int> block_ids;
BuildCubitBlockIDs(cubit_reader, num_element_blocks, block_ids);
unordered_map<int, string> blk_ids_to_names;
cubit_reader.BuildIDToNameMap(block_ids, blk_ids_to_names, "eb_names");
for (const auto & pr : blk_ids_to_names)
{
const auto blk_id = pr.first;
const auto & blk_name = pr.second;
if (!blk_name.empty())
{
if (!attribute_sets.AttributeSetExists(blk_name))
{
attribute_sets.CreateAttributeSet(blk_name);
}
attribute_sets.AddToAttributeSet(blk_name, blk_id);
}
}
map<int, size_t> num_elements_for_block_id;
ReadCubitNumElementsInBlock(cubit_reader, block_ids,
num_elements_for_block_id);
map<int, vector<int>> element_ids_for_block_id;
map<int, int> block_id_for_element_id;
BuildElementIDsForBlockID(
block_ids, num_elements_for_block_id, element_ids_for_block_id,
block_id_for_element_id);
//
// Read number of nodes for each element.
CubitBlock blocks(num_dimensions);
ReadCubitBlocks(cubit_reader, block_ids, blocks);
// Read the elements that make-up each block.
map<int, vector<int>> node_ids_for_element_id;
ReadCubitElementBlocks(cubit_reader,
blocks,
block_ids,
element_ids_for_block_id,
node_ids_for_element_id);
//
// Read the boundary ids.
//
vector<int> boundary_ids;
ReadCubitBoundaryIDs(cubit_reader, num_boundaries, boundary_ids);
unordered_map<int, string> bnd_ids_to_names;
cubit_reader.BuildIDToNameMap(boundary_ids, bnd_ids_to_names, "ss_names");
for (const auto & pr : bnd_ids_to_names)
{
const auto bnd_id = pr.first;
const auto & bnd_name = pr.second;
if (!bnd_name.empty())
{
if (!bdr_attribute_sets.AttributeSetExists(bnd_name))
{
bdr_attribute_sets.CreateAttributeSet(bnd_name);
}
bdr_attribute_sets.AddToAttributeSet(bnd_name, bnd_id);
}
}
//
// Read the (element, corresponding side) on each of the boundaries.
//
map<int, vector<int>> element_ids_for_boundary_id;
map<int, vector<int>> side_ids_for_boundary_id;
ReadCubitBoundaries(cubit_reader, boundary_ids,
element_ids_for_boundary_id, side_ids_for_boundary_id);
map<int, vector<vector<int>>> node_ids_for_boundary_id;
BuildBoundaryNodeIDs(boundary_ids, blocks, node_ids_for_element_id,
element_ids_for_boundary_id, side_ids_for_boundary_id,
block_id_for_element_id,
node_ids_for_boundary_id);
//
// Read the xyz coordinates for each node.
//
vector<double> coordx(num_nodes);
vector<double> coordy(num_nodes);
vector<double> coordz(num_dimensions == 3 ? num_nodes : 0);
ReadCubitNodeCoordinates(cubit_reader, coordx.data(), coordy.data(),
coordz.data());
//
// We need another node ID mapping since MFEM needs contiguous vertex ids.
//
vector<int> unique_vertex_ids;
BuildUniqueVertexIDs(block_ids, blocks, element_ids_for_block_id,
node_ids_for_element_id, unique_vertex_ids);
//
// unique_vertex_ids now contains a 1-based sorted list of node IDs for each
// node used by the mesh. We now create a map by running over the node IDs
// and remapping to contiguous 1-based integers.
// ie. [1, 4, 5, 8, 9] --> [1, 2, 3, 4, 5].
//
map<int, int> cubit_to_mfem_vertex_map;
BuildCubitToMFEMVertexMap(unique_vertex_ids, cubit_to_mfem_vertex_map);
//
// Load up the vertices.
//
BuildCubitVertices(unique_vertex_ids, coordx, coordy, coordz);
//
// Now load the elements.
//
BuildCubitElements(num_elements, &blocks, block_ids,
element_ids_for_block_id,
node_ids_for_element_id, cubit_to_mfem_vertex_map);
//
// Load up the boundary elements.
//
BuildCubitBoundaries(&blocks, boundary_ids,
element_ids_for_boundary_id, node_ids_for_boundary_id, side_ids_for_boundary_id,
block_id_for_element_id,
cubit_to_mfem_vertex_map);
//
// Additional setup for second order.
//
if (blocks.GetOrder() == 2)
{
curved = 1;
FinalizeCubitSecondOrderMesh(*this,
block_ids,
blocks,
element_ids_for_block_id,
node_ids_for_element_id,
coordx.data(),
coordy.data(),
coordz.data());
}
}
#endif // #ifdef MFEM_USE_NETCDF
} // namespace mfem