15939 lines
449 KiB
C++
15939 lines
449 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.
|
|
|
|
// Implementation of data type mesh
|
|
|
|
#include "mesh_headers.hpp"
|
|
#include "vtkhdf.hpp"
|
|
#include "../fem/fem.hpp"
|
|
#include "../general/sort_pairs.hpp"
|
|
#include "../general/binaryio.hpp"
|
|
#include "../general/text.hpp"
|
|
#include "../general/device.hpp"
|
|
#include "../general/tic_toc.hpp"
|
|
#include "../general/gecko.hpp"
|
|
#include "../general/kdtree.hpp"
|
|
#include "../general/sets.hpp"
|
|
#include "../fem/quadinterpolator.hpp"
|
|
|
|
// headers already included by mesh.hpp: <iostream>, <array>, <map>, <memory>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <limits>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <functional>
|
|
#include <set>
|
|
#include <numeric>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <list>
|
|
|
|
// Include the METIS header, if using version 5. If using METIS 4, the needed
|
|
// declarations are inlined below, i.e. no header is needed.
|
|
#if defined(MFEM_USE_METIS) && defined(MFEM_USE_METIS_5)
|
|
#include "metis.h"
|
|
#endif
|
|
|
|
// METIS 4 prototypes
|
|
#if defined(MFEM_USE_METIS) && !defined(MFEM_USE_METIS_5)
|
|
typedef int idx_t;
|
|
typedef int idxtype;
|
|
extern "C" {
|
|
void METIS_PartGraphRecursive(int*, idxtype*, idxtype*, idxtype*, idxtype*,
|
|
int*, int*, int*, int*, int*, idxtype*);
|
|
void METIS_PartGraphKway(int*, idxtype*, idxtype*, idxtype*, idxtype*,
|
|
int*, int*, int*, int*, int*, idxtype*);
|
|
void METIS_PartGraphVKway(int*, idxtype*, idxtype*, idxtype*, idxtype*,
|
|
int*, int*, int*, int*, int*, idxtype*);
|
|
}
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
void Mesh::GetElementJacobian(int i, DenseMatrix &J, const IntegrationPoint *ip)
|
|
{
|
|
Geometry::Type geom = GetElementBaseGeometry(i);
|
|
ElementTransformation *eltransf = GetElementTransformation(i);
|
|
if (ip == NULL)
|
|
{
|
|
eltransf->SetIntPoint(&Geometries.GetCenter(geom));
|
|
}
|
|
else
|
|
{
|
|
eltransf->SetIntPoint(ip);
|
|
}
|
|
Geometries.JacToPerfJac(geom, eltransf->Jacobian(), J);
|
|
}
|
|
|
|
void Mesh::GetElementCenter(int i, Vector ¢er)
|
|
{
|
|
center.SetSize(spaceDim);
|
|
int geom = GetElementBaseGeometry(i);
|
|
ElementTransformation *eltransf = GetElementTransformation(i);
|
|
eltransf->Transform(Geometries.GetCenter(geom), center);
|
|
}
|
|
|
|
real_t Mesh::GetElementSize(ElementTransformation *T, int type) const
|
|
{
|
|
DenseMatrix J(spaceDim, Dim);
|
|
|
|
Geometry::Type geom = T->GetGeometryType();
|
|
T->SetIntPoint(&Geometries.GetCenter(geom));
|
|
Geometries.JacToPerfJac(geom, T->Jacobian(), J);
|
|
|
|
if (type == 0)
|
|
{
|
|
return pow(fabs(J.Weight()), 1./Dim);
|
|
}
|
|
else if (type == 1)
|
|
{
|
|
return J.CalcSingularvalue(Dim-1); // h_min
|
|
}
|
|
else
|
|
{
|
|
return J.CalcSingularvalue(0); // h_max
|
|
}
|
|
}
|
|
|
|
real_t Mesh::GetElementSize(int i, int type)
|
|
{
|
|
return GetElementSize(GetElementTransformation(i), type);
|
|
}
|
|
|
|
real_t Mesh::GetElementSize(int i, const Vector &dir)
|
|
{
|
|
DenseMatrix J(spaceDim, Dim);
|
|
Vector d_hat(Dim);
|
|
GetElementJacobian(i, J);
|
|
J.MultTranspose(dir, d_hat);
|
|
return sqrt((d_hat * d_hat) / (dir * dir));
|
|
}
|
|
|
|
real_t Mesh::GetElementVolume(int i)
|
|
{
|
|
ElementTransformation *et = GetElementTransformation(i);
|
|
const IntegrationRule &ir = IntRules.Get(GetElementBaseGeometry(i),
|
|
et->OrderJ());
|
|
real_t volume = 0.0;
|
|
for (int j = 0; j < ir.GetNPoints(); j++)
|
|
{
|
|
const IntegrationPoint &ip = ir.IntPoint(j);
|
|
et->SetIntPoint(&ip);
|
|
volume += ip.weight * et->Weight();
|
|
}
|
|
|
|
return volume;
|
|
}
|
|
|
|
// Similar to VisualizationSceneSolution3d::FindNewBox in GLVis
|
|
void Mesh::GetBoundingBox(Vector &min, Vector &max, int ref)
|
|
{
|
|
min.SetSize(spaceDim);
|
|
max.SetSize(spaceDim);
|
|
|
|
for (int d = 0; d < spaceDim; d++)
|
|
{
|
|
min(d) = infinity();
|
|
max(d) = -infinity();
|
|
}
|
|
|
|
if (Nodes == NULL)
|
|
{
|
|
real_t *coord;
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
coord = GetVertex(i);
|
|
for (int d = 0; d < spaceDim; d++)
|
|
{
|
|
if (coord[d] < min(d)) { min(d) = coord[d]; }
|
|
if (coord[d] > max(d)) { max(d) = coord[d]; }
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool use_boundary = false; // make this a parameter?
|
|
int ne = use_boundary ? GetNBE() : GetNE();
|
|
int fn, fo;
|
|
DenseMatrix pointmat;
|
|
RefinedGeometry *RefG;
|
|
IntegrationRule eir;
|
|
FaceElementTransformations *Tr;
|
|
ElementTransformation *T;
|
|
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
if (use_boundary)
|
|
{
|
|
GetBdrElementFace(i, &fn, &fo);
|
|
RefG = GlobGeometryRefiner.Refine(GetFaceGeometry(fn), ref);
|
|
Tr = GetFaceElementTransformations(fn, 5);
|
|
eir.SetSize(RefG->RefPts.GetNPoints());
|
|
Tr->Loc1.Transform(RefG->RefPts, eir);
|
|
Tr->Elem1->Transform(eir, pointmat);
|
|
}
|
|
else
|
|
{
|
|
T = GetElementTransformation(i);
|
|
RefG = GlobGeometryRefiner.Refine(GetElementBaseGeometry(i), ref);
|
|
T->Transform(RefG->RefPts, pointmat);
|
|
}
|
|
for (int j = 0; j < pointmat.Width(); j++)
|
|
{
|
|
for (int d = 0; d < pointmat.Height(); d++)
|
|
{
|
|
if (pointmat(d,j) < min(d)) { min(d) = pointmat(d,j); }
|
|
if (pointmat(d,j) > max(d)) { max(d) = pointmat(d,j); }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::GetCharacteristics(real_t &h_min, real_t &h_max,
|
|
real_t &kappa_min, real_t &kappa_max,
|
|
Vector *Vh, Vector *Vk)
|
|
{
|
|
int i, dim, sdim;
|
|
DenseMatrix J;
|
|
real_t h, kappa;
|
|
|
|
dim = Dimension();
|
|
sdim = SpaceDimension();
|
|
|
|
if (Vh) { Vh->SetSize(NumOfElements); }
|
|
if (Vk) { Vk->SetSize(NumOfElements); }
|
|
|
|
h_min = kappa_min = infinity();
|
|
h_max = kappa_max = -h_min;
|
|
if (dim == 0) { if (Vh) { *Vh = 1.0; } if (Vk) {*Vk = 1.0; } return; }
|
|
J.SetSize(sdim, dim);
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
GetElementJacobian(i, J);
|
|
h = pow(fabs(J.Weight()), 1.0/real_t(dim));
|
|
kappa = (dim == sdim) ?
|
|
J.CalcSingularvalue(0) / J.CalcSingularvalue(dim-1) : -1.0;
|
|
if (Vh) { (*Vh)(i) = h; }
|
|
if (Vk) { (*Vk)(i) = kappa; }
|
|
|
|
if (h < h_min) { h_min = h; }
|
|
if (h > h_max) { h_max = h; }
|
|
if (kappa < kappa_min) { kappa_min = kappa; }
|
|
if (kappa > kappa_max) { kappa_max = kappa; }
|
|
}
|
|
}
|
|
|
|
// static method
|
|
void Mesh::PrintElementsByGeometry(int dim,
|
|
const Array<int> &num_elems_by_geom,
|
|
std::ostream &os)
|
|
{
|
|
for (int g = Geometry::DimStart[dim], first = 1;
|
|
g < Geometry::DimStart[dim+1]; g++)
|
|
{
|
|
if (!num_elems_by_geom[g]) { continue; }
|
|
if (!first) { os << " + "; }
|
|
else { first = 0; }
|
|
os << num_elems_by_geom[g] << ' ' << Geometry::Name[g] << "(s)";
|
|
}
|
|
}
|
|
|
|
void Mesh::PrintCharacteristics(Vector *Vh, Vector *Vk, std::ostream &os)
|
|
{
|
|
real_t h_min, h_max, kappa_min, kappa_max;
|
|
|
|
os << "Mesh Characteristics:";
|
|
|
|
this->GetCharacteristics(h_min, h_max, kappa_min, kappa_max, Vh, Vk);
|
|
|
|
Array<int> num_elems_by_geom(Geometry::NumGeom);
|
|
num_elems_by_geom = 0;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
num_elems_by_geom[GetElementBaseGeometry(i)]++;
|
|
}
|
|
|
|
os << '\n'
|
|
<< "Dimension : " << Dimension() << '\n'
|
|
<< "Space dimension : " << SpaceDimension();
|
|
if (Dim == 0)
|
|
{
|
|
os << '\n'
|
|
<< "Number of vertices : " << GetNV() << '\n'
|
|
<< "Number of elements : " << GetNE() << '\n'
|
|
<< "Number of bdr elem : " << GetNBE() << '\n';
|
|
}
|
|
else if (Dim == 1)
|
|
{
|
|
os << '\n'
|
|
<< "Number of vertices : " << GetNV() << '\n'
|
|
<< "Number of elements : " << GetNE() << '\n'
|
|
<< "Number of bdr elem : " << GetNBE() << '\n'
|
|
<< "h_min : " << h_min << '\n'
|
|
<< "h_max : " << h_max << '\n';
|
|
}
|
|
else if (Dim == 2)
|
|
{
|
|
os << '\n'
|
|
<< "Number of vertices : " << GetNV() << '\n'
|
|
<< "Number of edges : " << GetNEdges() << '\n'
|
|
<< "Number of elements : " << GetNE() << " -- ";
|
|
PrintElementsByGeometry(2, num_elems_by_geom, os);
|
|
os << '\n'
|
|
<< "Number of bdr elem : " << GetNBE() << '\n'
|
|
<< "Euler Number : " << EulerNumber2D() << '\n'
|
|
<< "h_min : " << h_min << '\n'
|
|
<< "h_max : " << h_max << '\n'
|
|
<< "kappa_min : " << kappa_min << '\n'
|
|
<< "kappa_max : " << kappa_max << '\n';
|
|
}
|
|
else
|
|
{
|
|
Array<int> num_bdr_elems_by_geom(Geometry::NumGeom);
|
|
num_bdr_elems_by_geom = 0;
|
|
for (int i = 0; i < GetNBE(); i++)
|
|
{
|
|
num_bdr_elems_by_geom[GetBdrElementGeometry(i)]++;
|
|
}
|
|
Array<int> num_faces_by_geom(Geometry::NumGeom);
|
|
num_faces_by_geom = 0;
|
|
for (int i = 0; i < GetNFaces(); i++)
|
|
{
|
|
num_faces_by_geom[GetFaceGeometry(i)]++;
|
|
}
|
|
|
|
os << '\n'
|
|
<< "Number of vertices : " << GetNV() << '\n'
|
|
<< "Number of edges : " << GetNEdges() << '\n'
|
|
<< "Number of faces : " << GetNFaces() << " -- ";
|
|
PrintElementsByGeometry(Dim-1, num_faces_by_geom, os);
|
|
os << '\n'
|
|
<< "Number of elements : " << GetNE() << " -- ";
|
|
PrintElementsByGeometry(Dim, num_elems_by_geom, os);
|
|
os << '\n'
|
|
<< "Number of bdr elem : " << GetNBE() << " -- ";
|
|
PrintElementsByGeometry(Dim-1, num_bdr_elems_by_geom, os);
|
|
os << '\n'
|
|
<< "Euler Number : " << EulerNumber() << '\n'
|
|
<< "h_min : " << h_min << '\n'
|
|
<< "h_max : " << h_max << '\n'
|
|
<< "kappa_min : " << kappa_min << '\n'
|
|
<< "kappa_max : " << kappa_max << '\n';
|
|
}
|
|
os << '\n' << std::flush;
|
|
}
|
|
|
|
FiniteElement *Mesh::GetTransformationFEforElementType(Element::Type ElemType)
|
|
{
|
|
switch (ElemType)
|
|
{
|
|
case Element::POINT : return &PointFE;
|
|
case Element::SEGMENT : return &SegmentFE;
|
|
case Element::TRIANGLE : return &TriangleFE;
|
|
case Element::QUADRILATERAL : return &QuadrilateralFE;
|
|
case Element::TETRAHEDRON : return &TetrahedronFE;
|
|
case Element::HEXAHEDRON : return &HexahedronFE;
|
|
case Element::WEDGE : return &WedgeFE;
|
|
case Element::PYRAMID : return &PyramidFE;
|
|
default:
|
|
MFEM_ABORT("Unknown element type \"" << ElemType << "\"");
|
|
break;
|
|
}
|
|
MFEM_ABORT("Unknown element type");
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void Mesh::GetElementTransformation(int i,
|
|
IsoparametricTransformation *ElTr) const
|
|
{
|
|
ElTr->Attribute = GetAttribute(i);
|
|
ElTr->ElementNo = i;
|
|
ElTr->ElementType = ElementTransformation::ELEMENT;
|
|
ElTr->mesh = this;
|
|
ElTr->Reset();
|
|
if (Nodes == NULL)
|
|
{
|
|
GetPointMatrix(i, ElTr->GetPointMat());
|
|
ElTr->SetFE(GetTransformationFEforElementType(GetElementType(i)));
|
|
}
|
|
else
|
|
{
|
|
DenseMatrix &pm = ElTr->GetPointMat();
|
|
Array<int> vdofs;
|
|
Nodes->FESpace()->GetElementVDofs(i, vdofs);
|
|
Nodes->HostRead();
|
|
const GridFunction &nodes = *Nodes;
|
|
int n = vdofs.Size()/spaceDim;
|
|
pm.SetSize(spaceDim, n);
|
|
for (int k = 0; k < spaceDim; k++)
|
|
{
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
pm(k,j) = nodes(vdofs[n*k+j]);
|
|
}
|
|
}
|
|
ElTr->SetFE(Nodes->FESpace()->GetFE(i));
|
|
}
|
|
}
|
|
|
|
ElementTransformation *Mesh::GetTypicalElementTransformation()
|
|
{
|
|
if (GetNE() > 0) { return GetElementTransformation(0); }
|
|
|
|
Transformation.Attribute = -1;
|
|
Transformation.ElementNo = -1;
|
|
Transformation.ElementType = ElementTransformation::ELEMENT;
|
|
Transformation.mesh = this;
|
|
Transformation.Reset();
|
|
const Geometry::Type geom = GetTypicalElementGeometry();
|
|
if (Nodes == NULL)
|
|
{
|
|
Element::Type el_type = Element::TypeFromGeometry(geom);
|
|
Transformation.SetFE(GetTransformationFEforElementType(el_type));
|
|
}
|
|
else
|
|
{
|
|
Transformation.SetFE(GetNodalFESpace()->GetTypicalFE());
|
|
}
|
|
Transformation.SetIdentityTransformation(geom);
|
|
return &Transformation;
|
|
}
|
|
|
|
ElementTransformation *Mesh::GetElementTransformation(int i)
|
|
{
|
|
GetElementTransformation(i, &Transformation);
|
|
return &Transformation;
|
|
}
|
|
|
|
void Mesh::GetElementTransformation(int i, const Vector &nodes,
|
|
IsoparametricTransformation *ElTr) const
|
|
{
|
|
ElTr->Attribute = GetAttribute(i);
|
|
ElTr->ElementNo = i;
|
|
ElTr->ElementType = ElementTransformation::ELEMENT;
|
|
ElTr->mesh = this;
|
|
DenseMatrix &pm = ElTr->GetPointMat();
|
|
ElTr->Reset();
|
|
nodes.HostRead();
|
|
if (Nodes == NULL)
|
|
{
|
|
MFEM_ASSERT(nodes.Size() == spaceDim*GetNV(), "");
|
|
int nv = elements[i]->GetNVertices();
|
|
const int *v = elements[i]->GetVertices();
|
|
int n = vertices.Size();
|
|
pm.SetSize(spaceDim, nv);
|
|
for (int k = 0; k < spaceDim; k++)
|
|
{
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
pm(k, j) = nodes(k*n+v[j]);
|
|
}
|
|
}
|
|
ElTr->SetFE(GetTransformationFEforElementType(GetElementType(i)));
|
|
}
|
|
else
|
|
{
|
|
MFEM_ASSERT(nodes.Size() == Nodes->Size(), "");
|
|
Array<int> vdofs;
|
|
Nodes->FESpace()->GetElementVDofs(i, vdofs);
|
|
int n = vdofs.Size()/spaceDim;
|
|
pm.SetSize(spaceDim, n);
|
|
for (int k = 0; k < spaceDim; k++)
|
|
{
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
pm(k,j) = nodes(vdofs[n*k+j]);
|
|
}
|
|
}
|
|
ElTr->SetFE(Nodes->FESpace()->GetFE(i));
|
|
}
|
|
}
|
|
|
|
void Mesh::GetBdrElementTransformation(int i,
|
|
IsoparametricTransformation* ElTr) const
|
|
{
|
|
ElTr->Attribute = GetBdrAttribute(i);
|
|
ElTr->ElementNo = i; // boundary element number
|
|
ElTr->ElementType = ElementTransformation::BDR_ELEMENT;
|
|
ElTr->mesh = this;
|
|
DenseMatrix &pm = ElTr->GetPointMat();
|
|
ElTr->Reset();
|
|
if (Nodes == NULL)
|
|
{
|
|
GetBdrPointMatrix(i, pm);
|
|
ElTr->SetFE(GetTransformationFEforElementType(GetBdrElementType(i)));
|
|
}
|
|
else
|
|
{
|
|
const FiniteElement *bdr_el = Nodes->FESpace()->GetBE(i);
|
|
Nodes->HostRead();
|
|
const GridFunction &nodes = *Nodes;
|
|
if (bdr_el)
|
|
{
|
|
Array<int> vdofs;
|
|
Nodes->FESpace()->GetBdrElementVDofs(i, vdofs);
|
|
int n = vdofs.Size()/spaceDim;
|
|
pm.SetSize(spaceDim, n);
|
|
for (int k = 0; k < spaceDim; k++)
|
|
{
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
pm(k,j) = nodes(UnsignIndex(vdofs[n*k+j]));
|
|
}
|
|
}
|
|
ElTr->SetFE(bdr_el);
|
|
}
|
|
else // L2 Nodes (e.g., periodic mesh)
|
|
{
|
|
int elem_id, face_info;
|
|
GetBdrElementAdjacentElement(i, elem_id, face_info);
|
|
Geometry::Type face_geom = GetBdrElementGeometry(i);
|
|
face_info = EncodeFaceInfo(
|
|
DecodeFaceInfoLocalIndex(face_info),
|
|
Geometry::GetInverseOrientation(
|
|
face_geom, DecodeFaceInfoOrientation(face_info))
|
|
);
|
|
|
|
IntegrationPointTransformation Loc1;
|
|
GetLocalFaceTransformation(GetBdrElementType(i),
|
|
GetElementType(elem_id),
|
|
Loc1.Transf, face_info);
|
|
const FiniteElement *face_el =
|
|
Nodes->FESpace()->GetTraceElement(elem_id, face_geom);
|
|
MFEM_VERIFY(dynamic_cast<const NodalFiniteElement*>(face_el),
|
|
"Mesh requires nodal Finite Element.");
|
|
|
|
IntegrationRule eir(face_el->GetDof());
|
|
Loc1.Transf.ElementNo = elem_id;
|
|
Loc1.Transf.mesh = this;
|
|
Loc1.Transf.ElementType = ElementTransformation::ELEMENT;
|
|
Loc1.Transform(face_el->GetNodes(), eir);
|
|
Nodes->GetVectorValues(Loc1.Transf, eir, pm);
|
|
|
|
ElTr->SetFE(face_el);
|
|
}
|
|
}
|
|
}
|
|
|
|
ElementTransformation *Mesh::GetBdrElementTransformation(int i)
|
|
{
|
|
GetBdrElementTransformation(i, &BdrTransformation);
|
|
return &BdrTransformation;
|
|
}
|
|
|
|
void Mesh::GetFaceTransformation(int FaceNo,
|
|
IsoparametricTransformation *FTr) const
|
|
{
|
|
FTr->Attribute = (Dim == 1) ? 1 : faces[FaceNo]->GetAttribute();
|
|
FTr->ElementNo = FaceNo;
|
|
FTr->ElementType = ElementTransformation::FACE;
|
|
FTr->mesh = this;
|
|
DenseMatrix &pm = FTr->GetPointMat();
|
|
FTr->Reset();
|
|
if (Nodes == NULL)
|
|
{
|
|
const int *v = (Dim == 1) ? &FaceNo : faces[FaceNo]->GetVertices();
|
|
const int nv = (Dim == 1) ? 1 : faces[FaceNo]->GetNVertices();
|
|
pm.SetSize(spaceDim, nv);
|
|
for (int i = 0; i < spaceDim; i++)
|
|
{
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
pm(i, j) = vertices[v[j]](i);
|
|
}
|
|
}
|
|
FTr->SetFE(GetTransformationFEforElementType(GetFaceElementType(FaceNo)));
|
|
}
|
|
else // curved mesh
|
|
{
|
|
const FiniteElement *face_el = Nodes->FESpace()->GetFaceElement(FaceNo);
|
|
Nodes->HostRead();
|
|
const GridFunction &nodes = *Nodes;
|
|
if (face_el)
|
|
{
|
|
Array<int> vdofs;
|
|
Nodes->FESpace()->GetFaceVDofs(FaceNo, vdofs);
|
|
int n = vdofs.Size()/spaceDim;
|
|
pm.SetSize(spaceDim, n);
|
|
for (int i = 0; i < spaceDim; i++)
|
|
{
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
pm(i, j) = nodes(vdofs[n*i+j]);
|
|
}
|
|
}
|
|
FTr->SetFE(face_el);
|
|
}
|
|
else // L2 Nodes (e.g., periodic mesh), go through the volume of Elem1
|
|
{
|
|
const FaceInfo &face_info = faces_info[FaceNo];
|
|
Geometry::Type face_geom = GetFaceGeometry(FaceNo);
|
|
Element::Type face_type = GetFaceElementType(FaceNo);
|
|
|
|
IntegrationPointTransformation Loc1;
|
|
GetLocalFaceTransformation(face_type,
|
|
GetElementType(face_info.Elem1No),
|
|
Loc1.Transf, face_info.Elem1Inf);
|
|
|
|
face_el = Nodes->FESpace()->GetTraceElement(face_info.Elem1No,
|
|
face_geom);
|
|
MFEM_VERIFY(dynamic_cast<const NodalFiniteElement*>(face_el),
|
|
"Mesh requires nodal Finite Element.");
|
|
|
|
IntegrationRule eir(face_el->GetDof());
|
|
Loc1.Transf.ElementNo = face_info.Elem1No;
|
|
Loc1.Transf.ElementType = ElementTransformation::ELEMENT;
|
|
Loc1.Transf.mesh = this;
|
|
Loc1.Transform(face_el->GetNodes(), eir);
|
|
Nodes->GetVectorValues(Loc1.Transf, eir, pm);
|
|
|
|
FTr->SetFE(face_el);
|
|
}
|
|
}
|
|
}
|
|
|
|
ElementTransformation *Mesh::GetFaceTransformation(int FaceNo)
|
|
{
|
|
GetFaceTransformation(FaceNo, &FaceTransformation);
|
|
return &FaceTransformation;
|
|
}
|
|
|
|
void Mesh::GetEdgeTransformation(int EdgeNo,
|
|
IsoparametricTransformation *EdTr) const
|
|
{
|
|
if (Dim == 2)
|
|
{
|
|
GetFaceTransformation(EdgeNo, EdTr);
|
|
return;
|
|
}
|
|
if (Dim == 1)
|
|
{
|
|
mfem_error("Mesh::GetEdgeTransformation not defined in 1D \n");
|
|
}
|
|
|
|
EdTr->Attribute = 1;
|
|
EdTr->ElementNo = EdgeNo;
|
|
EdTr->ElementType = ElementTransformation::EDGE;
|
|
EdTr->mesh = this;
|
|
DenseMatrix &pm = EdTr->GetPointMat();
|
|
EdTr->Reset();
|
|
if (Nodes == NULL)
|
|
{
|
|
Array<int> v;
|
|
GetEdgeVertices(EdgeNo, v);
|
|
const int nv = 2;
|
|
pm.SetSize(spaceDim, nv);
|
|
for (int i = 0; i < spaceDim; i++)
|
|
{
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
pm(i, j) = vertices[v[j]](i);
|
|
}
|
|
}
|
|
EdTr->SetFE(GetTransformationFEforElementType(Element::SEGMENT));
|
|
}
|
|
else
|
|
{
|
|
const FiniteElement *edge_el = Nodes->FESpace()->GetEdgeElement(EdgeNo);
|
|
Nodes->HostRead();
|
|
const GridFunction &nodes = *Nodes;
|
|
if (edge_el)
|
|
{
|
|
Array<int> vdofs;
|
|
Nodes->FESpace()->GetEdgeVDofs(EdgeNo, vdofs);
|
|
int n = vdofs.Size()/spaceDim;
|
|
pm.SetSize(spaceDim, n);
|
|
for (int i = 0; i < spaceDim; i++)
|
|
{
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
pm(i, j) = nodes(vdofs[n*i+j]);
|
|
}
|
|
}
|
|
EdTr->SetFE(edge_el);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Not implemented.");
|
|
}
|
|
}
|
|
}
|
|
|
|
ElementTransformation *Mesh::GetEdgeTransformation(int EdgeNo)
|
|
{
|
|
GetEdgeTransformation(EdgeNo, &EdgeTransformation);
|
|
return &EdgeTransformation;
|
|
}
|
|
|
|
|
|
void Mesh::GetLocalPtToSegTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
const IntegrationRule *SegVert;
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&PointFE);
|
|
SegVert = Geometries.GetVertices(Geometry::SEGMENT);
|
|
locpm.SetSize(1, 1);
|
|
locpm(0, 0) = SegVert->IntPoint(i/64).x;
|
|
// (i/64) is the local face no. in the segment
|
|
// (i%64) is the orientation of the point (not used)
|
|
}
|
|
|
|
void Mesh::GetLocalSegToTriTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
const int *tv, *so;
|
|
const IntegrationRule *TriVert;
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&SegmentFE);
|
|
tv = tri_t::Edges[i/64]; // (i/64) is the local face no. in the triangle
|
|
so = seg_t::Orient[i%64]; // (i%64) is the orientation of the segment
|
|
TriVert = Geometries.GetVertices(Geometry::TRIANGLE);
|
|
locpm.SetSize(2, 2);
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
locpm(0, so[j]) = TriVert->IntPoint(tv[j]).x;
|
|
locpm(1, so[j]) = TriVert->IntPoint(tv[j]).y;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalSegToQuadTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
const int *qv, *so;
|
|
const IntegrationRule *QuadVert;
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&SegmentFE);
|
|
qv = quad_t::Edges[i/64]; // (i/64) is the local face no. in the quad
|
|
so = seg_t::Orient[i%64]; // (i%64) is the orientation of the segment
|
|
QuadVert = Geometries.GetVertices(Geometry::SQUARE);
|
|
locpm.SetSize(2, 2);
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
locpm(0, so[j]) = QuadVert->IntPoint(qv[j]).x;
|
|
locpm(1, so[j]) = QuadVert->IntPoint(qv[j]).y;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalTriToTetTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&TriangleFE);
|
|
// (i/64) is the local face no. in the tet
|
|
const int *tv = tet_t::FaceVert[i/64];
|
|
// (i%64) is the orientation of the tetrahedron face
|
|
// w.r.t. the face element
|
|
const int *to = tri_t::Orient[i%64];
|
|
const IntegrationRule *TetVert =
|
|
Geometries.GetVertices(Geometry::TETRAHEDRON);
|
|
locpm.SetSize(3, 3);
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
const IntegrationPoint &vert = TetVert->IntPoint(tv[to[j]]);
|
|
locpm(0, j) = vert.x;
|
|
locpm(1, j) = vert.y;
|
|
locpm(2, j) = vert.z;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalTriToWdgTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&TriangleFE);
|
|
// (i/64) is the local face no. in the pri
|
|
MFEM_VERIFY(i < 128, "Local face index " << i/64
|
|
<< " is not a triangular face of a wedge.");
|
|
const int *pv = pri_t::FaceVert[i/64];
|
|
// (i%64) is the orientation of the wedge face
|
|
// w.r.t. the face element
|
|
const int *to = tri_t::Orient[i%64];
|
|
const IntegrationRule *PriVert =
|
|
Geometries.GetVertices(Geometry::PRISM);
|
|
locpm.SetSize(3, 3);
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
const IntegrationPoint &vert = PriVert->IntPoint(pv[to[j]]);
|
|
locpm(0, j) = vert.x;
|
|
locpm(1, j) = vert.y;
|
|
locpm(2, j) = vert.z;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalTriToPyrTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
|
|
Transf.SetFE(&TriangleFE);
|
|
// (i/64) is the local face no. in the pyr
|
|
MFEM_VERIFY(i >= 64, "Local face index " << i/64
|
|
<< " is not a triangular face of a pyramid.");
|
|
const int *pv = pyr_t::FaceVert[i/64];
|
|
// (i%64) is the orientation of the pyramid face
|
|
// w.r.t. the face element
|
|
const int *to = tri_t::Orient[i%64];
|
|
const IntegrationRule *PyrVert =
|
|
Geometries.GetVertices(Geometry::PYRAMID);
|
|
locpm.SetSize(3, 3);
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
const IntegrationPoint &vert = PyrVert->IntPoint(pv[to[j]]);
|
|
locpm(0, j) = vert.x;
|
|
locpm(1, j) = vert.y;
|
|
locpm(2, j) = vert.z;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalQuadToHexTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&QuadrilateralFE);
|
|
// (i/64) is the local face no. in the hex
|
|
const int *hv = hex_t::FaceVert[i/64];
|
|
// (i%64) is the orientation of the quad
|
|
const int *qo = quad_t::Orient[i%64];
|
|
const IntegrationRule *HexVert = Geometries.GetVertices(Geometry::CUBE);
|
|
locpm.SetSize(3, 4);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
const IntegrationPoint &vert = HexVert->IntPoint(hv[qo[j]]);
|
|
locpm(0, j) = vert.x;
|
|
locpm(1, j) = vert.y;
|
|
locpm(2, j) = vert.z;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalQuadToWdgTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
Transf.Reset();
|
|
|
|
Transf.SetFE(&QuadrilateralFE);
|
|
// (i/64) is the local face no. in the pri
|
|
MFEM_VERIFY(i >= 128, "Local face index " << i/64
|
|
<< " is not a quadrilateral face of a wedge.");
|
|
const int *pv = pri_t::FaceVert[i/64];
|
|
// (i%64) is the orientation of the quad
|
|
const int *qo = quad_t::Orient[i%64];
|
|
const IntegrationRule *PriVert = Geometries.GetVertices(Geometry::PRISM);
|
|
locpm.SetSize(3, 4);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
const IntegrationPoint &vert = PriVert->IntPoint(pv[qo[j]]);
|
|
locpm(0, j) = vert.x;
|
|
locpm(1, j) = vert.y;
|
|
locpm(2, j) = vert.z;
|
|
}
|
|
}
|
|
|
|
void Mesh::GetLocalQuadToPyrTransformation(
|
|
IsoparametricTransformation &Transf, int i) const
|
|
{
|
|
DenseMatrix &locpm = Transf.GetPointMat();
|
|
|
|
Transf.SetFE(&QuadrilateralFE);
|
|
// (i/64) is the local face no. in the pyr
|
|
MFEM_VERIFY(i < 64, "Local face index " << i/64
|
|
<< " is not a quadrilateral face of a pyramid.");
|
|
const int *pv = pyr_t::FaceVert[i/64];
|
|
// (i%64) is the orientation of the quad
|
|
const int *qo = quad_t::Orient[i%64];
|
|
const IntegrationRule *PyrVert = Geometries.GetVertices(Geometry::PYRAMID);
|
|
locpm.SetSize(3, 4);
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
const IntegrationPoint &vert = PyrVert->IntPoint(pv[qo[j]]);
|
|
locpm(0, j) = vert.x;
|
|
locpm(1, j) = vert.y;
|
|
locpm(2, j) = vert.z;
|
|
}
|
|
}
|
|
|
|
const GeometricFactors* Mesh::GetGeometricFactors(const IntegrationRule& ir,
|
|
const int flags,
|
|
MemoryType d_mt)
|
|
{
|
|
for (int i = 0; i < geom_factors.Size(); i++)
|
|
{
|
|
GeometricFactors *gf = geom_factors[i];
|
|
if (gf->IntRule == &ir && (gf->computed_factors & flags) == flags)
|
|
{
|
|
return gf;
|
|
}
|
|
}
|
|
|
|
this->EnsureNodes();
|
|
|
|
GeometricFactors *gf = new GeometricFactors(this, ir, flags, d_mt);
|
|
geom_factors.Append(gf);
|
|
return gf;
|
|
}
|
|
|
|
const FaceGeometricFactors* Mesh::GetFaceGeometricFactors(
|
|
const IntegrationRule& ir,
|
|
const int flags, FaceType type, MemoryType d_mt)
|
|
{
|
|
for (int i = 0; i < face_geom_factors.Size(); i++)
|
|
{
|
|
FaceGeometricFactors *gf = face_geom_factors[i];
|
|
if (gf->IntRule == &ir && (gf->computed_factors & flags) == flags &&
|
|
gf->type==type)
|
|
{
|
|
return gf;
|
|
}
|
|
}
|
|
|
|
this->EnsureNodes();
|
|
|
|
FaceGeometricFactors *gf = new FaceGeometricFactors(this, ir, flags, type,
|
|
d_mt);
|
|
face_geom_factors.Append(gf);
|
|
return gf;
|
|
}
|
|
|
|
const Array<int>& Mesh::GetBdrFaceAttributes() const
|
|
{
|
|
if (bdr_face_attrs_cache.Size() == 0)
|
|
{
|
|
std::unordered_map<int, int> f_to_be;
|
|
for (int i = 0; i < GetNBE(); ++i)
|
|
{
|
|
const int f = GetBdrElementFaceIndex(i);
|
|
f_to_be[f] = i;
|
|
}
|
|
const int nf_bdr = GetNFbyType(FaceType::Boundary);
|
|
// MFEM_VERIFY(size_t(nf_bdr) == f_to_be.size(), "Incompatible sizes");
|
|
bdr_face_attrs_cache.SetSize(nf_bdr);
|
|
int f_ind = 0;
|
|
const int nf = GetNumFaces();
|
|
for (int f = 0; f < nf; ++f)
|
|
{
|
|
if (!GetFaceInformation(f).IsOfFaceType(FaceType::Boundary))
|
|
{
|
|
continue;
|
|
}
|
|
int attribute = -1; // default value
|
|
auto iter = f_to_be.find(f);
|
|
if (iter != f_to_be.end())
|
|
{
|
|
const int be = iter->second;
|
|
attribute = GetBdrAttribute(be);
|
|
}
|
|
else
|
|
{
|
|
// If a boundary face does not correspond to the a boundary element,
|
|
// we assign it the default attribute of -1.
|
|
}
|
|
bdr_face_attrs_cache[f_ind] = attribute;
|
|
++f_ind;
|
|
}
|
|
}
|
|
return bdr_face_attrs_cache;
|
|
}
|
|
|
|
const Array<int>& Mesh::GetElementAttributes() const
|
|
{
|
|
if (elem_attrs_cache.Size() == 0)
|
|
{
|
|
// re-compute cache
|
|
elem_attrs_cache.SetSize(GetNE());
|
|
elem_attrs_cache.HostWrite();
|
|
for (int i = 0; i < GetNE(); ++i)
|
|
{
|
|
elem_attrs_cache[i] = GetAttribute(i);
|
|
MFEM_ASSERT(elem_attrs_cache[i] > 0,
|
|
"Negative attribute on element " << i);
|
|
}
|
|
}
|
|
return elem_attrs_cache;
|
|
}
|
|
|
|
void Mesh::ComputeFaceInfo(FaceType ftype) const
|
|
{
|
|
auto &fidcs = face_indices[static_cast<int>(ftype)];
|
|
auto &ifidcs = inv_face_indices[static_cast<int>(ftype)];
|
|
fidcs.SetSize(GetNFbyType(ftype));
|
|
fidcs.HostWrite();
|
|
ifidcs.reserve(fidcs.Size());
|
|
int f_idx = 0;
|
|
for (int i = 0; i < GetNumFacesWithGhost(); ++i)
|
|
{
|
|
const FaceInformation face = GetFaceInformation(i);
|
|
if (face.IsNonconformingCoarse() || !face.IsOfFaceType(ftype))
|
|
{
|
|
continue;
|
|
}
|
|
fidcs[f_idx] = i;
|
|
ifidcs[i] = f_idx;
|
|
++f_idx;
|
|
}
|
|
}
|
|
|
|
const Array<int> &Mesh::GetFaceIndices(FaceType ftype) const
|
|
{
|
|
if (face_indices[static_cast<int>(ftype)].Size() == 0)
|
|
{
|
|
ComputeFaceInfo(ftype);
|
|
}
|
|
return face_indices[static_cast<int>(ftype)];
|
|
}
|
|
|
|
const std::unordered_map<int, int> &
|
|
Mesh::GetInvFaceIndices(FaceType ftype) const
|
|
{
|
|
if (inv_face_indices[static_cast<int>(ftype)].empty())
|
|
{
|
|
ComputeFaceInfo(ftype);
|
|
}
|
|
return inv_face_indices[static_cast<int>(ftype)];
|
|
}
|
|
|
|
void Mesh::DeleteGeometricFactors()
|
|
{
|
|
for (int i = 0; i < geom_factors.Size(); i++)
|
|
{
|
|
delete geom_factors[i];
|
|
}
|
|
geom_factors.SetSize(0);
|
|
for (int i = 0; i < face_geom_factors.Size(); i++)
|
|
{
|
|
delete face_geom_factors[i];
|
|
}
|
|
face_geom_factors.SetSize(0);
|
|
|
|
++nodes_sequence;
|
|
}
|
|
|
|
void Mesh::GetLocalFaceTransformation(int face_type, int elem_type,
|
|
IsoparametricTransformation &Transf,
|
|
int info) const
|
|
{
|
|
switch (face_type)
|
|
{
|
|
case Element::POINT:
|
|
GetLocalPtToSegTransformation(Transf, info);
|
|
break;
|
|
|
|
case Element::SEGMENT:
|
|
if (elem_type == Element::TRIANGLE)
|
|
{
|
|
GetLocalSegToTriTransformation(Transf, info);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ASSERT(elem_type == Element::QUADRILATERAL, "");
|
|
GetLocalSegToQuadTransformation(Transf, info);
|
|
}
|
|
break;
|
|
|
|
case Element::TRIANGLE:
|
|
if (elem_type == Element::TETRAHEDRON)
|
|
{
|
|
GetLocalTriToTetTransformation(Transf, info);
|
|
}
|
|
else if (elem_type == Element::WEDGE)
|
|
{
|
|
GetLocalTriToWdgTransformation(Transf, info);
|
|
}
|
|
else if (elem_type == Element::PYRAMID)
|
|
{
|
|
GetLocalTriToPyrTransformation(Transf, info);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Mesh::GetLocalFaceTransformation not defined for "
|
|
"face type " << face_type
|
|
<< " and element type " << elem_type << "\n");
|
|
}
|
|
break;
|
|
|
|
case Element::QUADRILATERAL:
|
|
if (elem_type == Element::HEXAHEDRON)
|
|
{
|
|
GetLocalQuadToHexTransformation(Transf, info);
|
|
}
|
|
else if (elem_type == Element::WEDGE)
|
|
{
|
|
GetLocalQuadToWdgTransformation(Transf, info);
|
|
}
|
|
else if (elem_type == Element::PYRAMID)
|
|
{
|
|
GetLocalQuadToPyrTransformation(Transf, info);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Mesh::GetLocalFaceTransformation not defined for "
|
|
"face type " << face_type
|
|
<< " and element type " << elem_type << "\n");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
FaceElementTransformations *Mesh::GetFaceElementTransformations(int FaceNo,
|
|
int mask)
|
|
{
|
|
GetFaceElementTransformations(FaceNo, FaceElemTr, Transformation,
|
|
Transformation2, mask);
|
|
return &FaceElemTr;
|
|
}
|
|
|
|
void Mesh::GetFaceElementTransformations(int FaceNo,
|
|
FaceElementTransformations &FElTr,
|
|
IsoparametricTransformation &ElTr1,
|
|
IsoparametricTransformation &ElTr2,
|
|
int mask) const
|
|
{
|
|
const FaceInfo &face_info = faces_info[FaceNo];
|
|
|
|
int cmask = 0;
|
|
FElTr.SetConfigurationMask(cmask);
|
|
FElTr.Elem1 = NULL;
|
|
FElTr.Elem2 = NULL;
|
|
|
|
// setup the transformation for the first element
|
|
FElTr.Elem1No = face_info.Elem1No;
|
|
if (mask & FaceElementTransformations::HAVE_ELEM1)
|
|
{
|
|
GetElementTransformation(FElTr.Elem1No, &ElTr1);
|
|
FElTr.Elem1 = &ElTr1;
|
|
cmask |= 1;
|
|
}
|
|
|
|
// setup the transformation for the second element
|
|
// return NULL in the Elem2 field if there's no second element, i.e.
|
|
// the face is on the "boundary"
|
|
FElTr.Elem2No = face_info.Elem2No;
|
|
if ((mask & FaceElementTransformations::HAVE_ELEM2) &&
|
|
FElTr.Elem2No >= 0)
|
|
{
|
|
#ifdef MFEM_DEBUG
|
|
if (NURBSext && (mask & FaceElementTransformations::HAVE_ELEM1))
|
|
{ MFEM_ABORT("NURBS mesh not supported!"); }
|
|
#endif
|
|
GetElementTransformation(FElTr.Elem2No, &ElTr2);
|
|
FElTr.Elem2 = &ElTr2;
|
|
cmask |= 2;
|
|
}
|
|
|
|
// setup the face transformation
|
|
if (mask & FaceElementTransformations::HAVE_FACE)
|
|
{
|
|
GetFaceTransformation(FaceNo, &FElTr);
|
|
cmask |= 16;
|
|
}
|
|
else
|
|
{
|
|
FElTr.SetGeometryType(GetFaceGeometry(FaceNo));
|
|
}
|
|
|
|
// setup Loc1 & Loc2
|
|
int face_type = GetFaceElementType(FaceNo);
|
|
if (mask & FaceElementTransformations::HAVE_LOC1)
|
|
{
|
|
int elem_type = GetElementType(face_info.Elem1No);
|
|
GetLocalFaceTransformation(face_type, elem_type,
|
|
FElTr.Loc1.Transf, face_info.Elem1Inf);
|
|
cmask |= 4;
|
|
}
|
|
if ((mask & FaceElementTransformations::HAVE_LOC2) &&
|
|
FElTr.Elem2No >= 0)
|
|
{
|
|
int elem_type = GetElementType(face_info.Elem2No);
|
|
GetLocalFaceTransformation(face_type, elem_type,
|
|
FElTr.Loc2.Transf, face_info.Elem2Inf);
|
|
|
|
// NC meshes: prepend slave edge/face transformation to Loc2
|
|
if (Nonconforming() && IsSlaveFace(face_info))
|
|
{
|
|
ApplyLocalSlaveTransformation(FElTr, face_info, false);
|
|
}
|
|
cmask |= 8;
|
|
}
|
|
|
|
FElTr.SetConfigurationMask(cmask);
|
|
|
|
// This check can be useful for internal debugging, however it will fail on
|
|
// periodic boundary faces, so we keep it disabled in general.
|
|
#if 0
|
|
#ifdef MFEM_DEBUG
|
|
real_t dist = FElTr.CheckConsistency();
|
|
if (dist >= 1e-12)
|
|
{
|
|
mfem::out << "\nInternal error: face id = " << FaceNo
|
|
<< ", dist = " << dist << '\n';
|
|
FElTr.CheckConsistency(1); // print coordinates
|
|
MFEM_ABORT("internal error");
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
FaceElementTransformations *Mesh::GetInteriorFaceTransformations(int FaceNo)
|
|
{
|
|
GetInteriorFaceTransformations(FaceNo, FaceElemTr, Transformation,
|
|
Transformation2);
|
|
return (FaceElemTr.geom == Geometry::INVALID) ? nullptr : &FaceElemTr;
|
|
}
|
|
|
|
void Mesh::GetInteriorFaceTransformations(int FaceNo,
|
|
FaceElementTransformations &FElTr,
|
|
IsoparametricTransformation &ElTr1,
|
|
IsoparametricTransformation &ElTr2) const
|
|
{
|
|
if (faces_info[FaceNo].Elem2No < 0)
|
|
{
|
|
FElTr.SetGeometryType(Geometry::INVALID);
|
|
return;
|
|
}
|
|
GetFaceElementTransformations(FaceNo, FElTr, ElTr1, ElTr2);
|
|
}
|
|
|
|
FaceElementTransformations *Mesh::GetBdrFaceTransformations(int BdrElemNo)
|
|
{
|
|
GetBdrFaceTransformations(BdrElemNo, FaceElemTr, Transformation,
|
|
Transformation2);
|
|
return (FaceElemTr.geom == Geometry::INVALID) ? nullptr : &FaceElemTr;
|
|
}
|
|
|
|
void Mesh::GetBdrFaceTransformations(int BdrElemNo,
|
|
FaceElementTransformations &FElTr,
|
|
IsoparametricTransformation &ElTr1,
|
|
IsoparametricTransformation &ElTr2) const
|
|
{
|
|
// Check if the face is interior, shared, or nonconforming.
|
|
int fn = GetBdrElementFaceIndex(BdrElemNo);
|
|
if (FaceIsTrueInterior(fn) || faces_info[fn].NCFace >= 0)
|
|
{
|
|
FElTr.SetGeometryType(Geometry::INVALID);
|
|
return;
|
|
}
|
|
GetFaceElementTransformations(fn, FElTr, ElTr1, ElTr2, 21);
|
|
FElTr.Attribute = boundary[BdrElemNo]->GetAttribute();
|
|
FElTr.ElementNo = BdrElemNo;
|
|
FElTr.ElementType = ElementTransformation::BDR_FACE;
|
|
FElTr.mesh = this;
|
|
}
|
|
|
|
bool Mesh::IsSlaveFace(const FaceInfo &fi) const
|
|
{
|
|
return fi.NCFace >= 0 && nc_faces_info[fi.NCFace].Slave;
|
|
}
|
|
|
|
void Mesh::ApplyLocalSlaveTransformation(FaceElementTransformations &FT,
|
|
const FaceInfo &fi, bool is_ghost) const
|
|
{
|
|
#ifdef MFEM_THREAD_SAFE
|
|
DenseMatrix composition;
|
|
#else
|
|
static DenseMatrix composition;
|
|
#endif
|
|
MFEM_ASSERT(fi.NCFace >= 0, "");
|
|
MFEM_ASSERT(nc_faces_info[fi.NCFace].Slave, "internal error");
|
|
if (!is_ghost)
|
|
{
|
|
// side 1 -> child side, side 2 -> parent side
|
|
IsoparametricTransformation < = FT.Loc2.Transf;
|
|
LT.Transform(*nc_faces_info[fi.NCFace].PointMatrix, composition);
|
|
// In 2D, we need to flip the point matrix since it is aligned with the
|
|
// parent side.
|
|
if (Dim == 2)
|
|
{
|
|
// swap points (columns) 0 and 1
|
|
std::swap(composition(0,0), composition(0,1));
|
|
std::swap(composition(1,0), composition(1,1));
|
|
}
|
|
LT.SetPointMat(composition);
|
|
}
|
|
else // is_ghost == true
|
|
{
|
|
// side 1 -> parent side, side 2 -> child side
|
|
IsoparametricTransformation < = FT.Loc1.Transf;
|
|
LT.Transform(*nc_faces_info[fi.NCFace].PointMatrix, composition);
|
|
// In 2D, there is no need to flip the point matrix since it is already
|
|
// aligned with the parent side, see also ParNCMesh::GetFaceNeighbors.
|
|
// In 3D the point matrix was flipped during construction in
|
|
// ParNCMesh::GetFaceNeighbors and due to that it is already aligned with
|
|
// the parent side.
|
|
LT.SetPointMat(composition);
|
|
}
|
|
}
|
|
|
|
Mesh::FaceInformation Mesh::GetFaceInformation(int f) const
|
|
{
|
|
FaceInformation face;
|
|
int e1, e2;
|
|
int inf1, inf2;
|
|
int ncface;
|
|
GetFaceElements(f, &e1, &e2);
|
|
GetFaceInfos(f, &inf1, &inf2, &ncface);
|
|
face.element[0].index = e1;
|
|
face.element[0].location = ElementLocation::Local;
|
|
face.element[0].orientation = inf1%64;
|
|
face.element[0].local_face_id = inf1/64;
|
|
face.element[1].local_face_id = inf2/64;
|
|
face.ncface = ncface;
|
|
face.point_matrix = nullptr;
|
|
// The following figures out face.location, face.conformity,
|
|
// face.element[1].index, and face.element[1].orientation.
|
|
if (f < GetNumFaces()) // Non-ghost face
|
|
{
|
|
if (e2>=0)
|
|
{
|
|
if (ncface==-1)
|
|
{
|
|
face.tag = FaceInfoTag::LocalConforming;
|
|
face.topology = FaceTopology::Conforming;
|
|
face.element[1].location = ElementLocation::Local;
|
|
face.element[0].conformity = ElementConformity::Coincident;
|
|
face.element[1].conformity = ElementConformity::Coincident;
|
|
face.element[1].index = e2;
|
|
face.element[1].orientation = inf2%64;
|
|
}
|
|
else // ncface >= 0
|
|
{
|
|
face.tag = FaceInfoTag::LocalSlaveNonconforming;
|
|
face.topology = FaceTopology::Nonconforming;
|
|
face.element[1].location = ElementLocation::Local;
|
|
face.element[0].conformity = ElementConformity::Coincident;
|
|
face.element[1].conformity = ElementConformity::Superset;
|
|
face.element[1].index = e2;
|
|
MFEM_ASSERT(inf2%64==0, "unexpected slave face orientation.");
|
|
face.element[1].orientation = inf2%64;
|
|
face.point_matrix = nc_faces_info[ncface].PointMatrix;
|
|
}
|
|
}
|
|
else // e2<0
|
|
{
|
|
if (ncface==-1)
|
|
{
|
|
if (inf2<0)
|
|
{
|
|
face.tag = FaceInfoTag::Boundary;
|
|
face.topology = FaceTopology::Boundary;
|
|
face.element[1].location = ElementLocation::NA;
|
|
face.element[0].conformity = ElementConformity::Coincident;
|
|
face.element[1].conformity = ElementConformity::NA;
|
|
face.element[1].index = -1;
|
|
face.element[1].orientation = -1;
|
|
}
|
|
else // inf2 >= 0
|
|
{
|
|
face.tag = FaceInfoTag::SharedConforming;
|
|
face.topology = FaceTopology::Conforming;
|
|
face.element[0].conformity = ElementConformity::Coincident;
|
|
face.element[1].conformity = ElementConformity::Coincident;
|
|
face.element[1].location = ElementLocation::FaceNbr;
|
|
face.element[1].index = FlipIndexSign(e2);
|
|
face.element[1].orientation = inf2%64;
|
|
}
|
|
}
|
|
else // ncface >= 0
|
|
{
|
|
if (inf2 < 0)
|
|
{
|
|
face.tag = FaceInfoTag::MasterNonconforming;
|
|
face.topology = FaceTopology::Nonconforming;
|
|
face.element[1].location = ElementLocation::NA;
|
|
face.element[0].conformity = ElementConformity::Coincident;
|
|
face.element[1].conformity = ElementConformity::Subset;
|
|
face.element[1].index = -1;
|
|
face.element[1].orientation = -1;
|
|
}
|
|
else
|
|
{
|
|
face.tag = FaceInfoTag::SharedSlaveNonconforming;
|
|
face.topology = FaceTopology::Nonconforming;
|
|
face.element[1].location = ElementLocation::FaceNbr;
|
|
face.element[0].conformity = ElementConformity::Coincident;
|
|
face.element[1].conformity = ElementConformity::Superset;
|
|
face.element[1].index = FlipIndexSign(e2);
|
|
face.element[1].orientation = inf2%64;
|
|
}
|
|
face.point_matrix = nc_faces_info[ncface].PointMatrix;
|
|
}
|
|
}
|
|
}
|
|
else // Ghost face
|
|
{
|
|
if (e1==-1)
|
|
{
|
|
face.tag = FaceInfoTag::GhostMaster;
|
|
face.topology = FaceTopology::NA;
|
|
face.element[1].location = ElementLocation::NA;
|
|
face.element[0].conformity = ElementConformity::NA;
|
|
face.element[1].conformity = ElementConformity::NA;
|
|
face.element[1].index = -1;
|
|
face.element[1].orientation = -1;
|
|
}
|
|
else
|
|
{
|
|
face.tag = FaceInfoTag::GhostSlave;
|
|
face.topology = FaceTopology::Nonconforming;
|
|
face.element[1].location = ElementLocation::FaceNbr;
|
|
face.element[0].conformity = ElementConformity::Superset;
|
|
face.element[1].conformity = ElementConformity::Coincident;
|
|
face.element[1].index = FlipIndexSign(e2);
|
|
face.element[1].orientation = inf2%64;
|
|
face.point_matrix = nc_faces_info[ncface].PointMatrix;
|
|
}
|
|
}
|
|
return face;
|
|
}
|
|
|
|
Mesh::FaceInformation::operator Mesh::FaceInfo() const
|
|
{
|
|
FaceInfo res {-1, -1, -1, -1, -1};
|
|
switch (tag)
|
|
{
|
|
case FaceInfoTag::LocalConforming:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem2No = element[1].index;
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
res.Elem2Inf = element[1].orientation + element[1].local_face_id*64;
|
|
res.NCFace = ncface;
|
|
break;
|
|
case FaceInfoTag::LocalSlaveNonconforming:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem2No = element[1].index;
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
res.Elem2Inf = element[1].orientation + element[1].local_face_id*64;
|
|
res.NCFace = ncface;
|
|
break;
|
|
case FaceInfoTag::Boundary:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
break;
|
|
case FaceInfoTag::SharedConforming:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem2No = FlipIndexSign(element[1].index);
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
res.Elem2Inf = element[1].orientation + element[1].local_face_id*64;
|
|
break;
|
|
case FaceInfoTag::MasterNonconforming:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
break;
|
|
case FaceInfoTag::SharedSlaveNonconforming:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem2No = FlipIndexSign(element[1].index);
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
res.Elem2Inf = element[1].orientation + element[1].local_face_id*64;
|
|
break;
|
|
case FaceInfoTag::GhostMaster:
|
|
break;
|
|
case FaceInfoTag::GhostSlave:
|
|
res.Elem1No = element[0].index;
|
|
res.Elem2No = FlipIndexSign(element[1].index);
|
|
res.Elem1Inf = element[0].orientation + element[0].local_face_id*64;
|
|
res.Elem2Inf = element[1].orientation + element[1].local_face_id*64;
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, const Mesh::FaceInformation& info)
|
|
{
|
|
os << "face topology=";
|
|
switch (info.topology)
|
|
{
|
|
case Mesh::FaceTopology::Boundary:
|
|
os << "Boundary";
|
|
break;
|
|
case Mesh::FaceTopology::Conforming:
|
|
os << "Conforming";
|
|
break;
|
|
case Mesh::FaceTopology::Nonconforming:
|
|
os << "Non-conforming";
|
|
break;
|
|
case Mesh::FaceTopology::NA:
|
|
os << "NA";
|
|
break;
|
|
}
|
|
os << '\n';
|
|
os << "element[0].location=";
|
|
switch (info.element[0].location)
|
|
{
|
|
case Mesh::ElementLocation::Local:
|
|
os << "Local";
|
|
break;
|
|
case Mesh::ElementLocation::FaceNbr:
|
|
os << "FaceNbr";
|
|
break;
|
|
case Mesh::ElementLocation::NA:
|
|
os << "NA";
|
|
break;
|
|
}
|
|
os << '\n';
|
|
os << "element[1].location=";
|
|
switch (info.element[1].location)
|
|
{
|
|
case Mesh::ElementLocation::Local:
|
|
os << "Local";
|
|
break;
|
|
case Mesh::ElementLocation::FaceNbr:
|
|
os << "FaceNbr";
|
|
break;
|
|
case Mesh::ElementLocation::NA:
|
|
os << "NA";
|
|
break;
|
|
}
|
|
os << '\n';
|
|
os << "element[0].conformity=";
|
|
switch (info.element[0].conformity)
|
|
{
|
|
case Mesh::ElementConformity::Coincident:
|
|
os << "Coincident";
|
|
break;
|
|
case Mesh::ElementConformity::Superset:
|
|
os << "Superset";
|
|
break;
|
|
case Mesh::ElementConformity::Subset:
|
|
os << "Subset";
|
|
break;
|
|
case Mesh::ElementConformity::NA:
|
|
os << "NA";
|
|
break;
|
|
}
|
|
os << '\n';
|
|
os << "element[1].conformity=";
|
|
switch (info.element[1].conformity)
|
|
{
|
|
case Mesh::ElementConformity::Coincident:
|
|
os << "Coincident";
|
|
break;
|
|
case Mesh::ElementConformity::Superset:
|
|
os << "Superset";
|
|
break;
|
|
case Mesh::ElementConformity::Subset:
|
|
os << "Subset";
|
|
break;
|
|
case Mesh::ElementConformity::NA:
|
|
os << "NA";
|
|
break;
|
|
}
|
|
os << '\n';
|
|
os << "element[0].index=" << info.element[0].index << '\n'
|
|
<< "element[1].index=" << info.element[1].index << '\n'
|
|
<< "element[0].local_face_id=" << info.element[0].local_face_id << '\n'
|
|
<< "element[1].local_face_id=" << info.element[1].local_face_id << '\n'
|
|
<< "element[0].orientation=" << info.element[0].orientation << '\n'
|
|
<< "element[1].orientation=" << info.element[1].orientation << '\n'
|
|
<< "ncface=" << info.ncface << std::endl;
|
|
return os;
|
|
}
|
|
|
|
void Mesh::GetFaceElements(int Face, int *Elem1, int *Elem2) const
|
|
{
|
|
*Elem1 = faces_info[Face].Elem1No;
|
|
*Elem2 = faces_info[Face].Elem2No;
|
|
}
|
|
|
|
void Mesh::GetFaceInfos(int Face, int *Inf1, int *Inf2) const
|
|
{
|
|
*Inf1 = faces_info[Face].Elem1Inf;
|
|
*Inf2 = faces_info[Face].Elem2Inf;
|
|
}
|
|
|
|
void Mesh::GetFaceInfos(int Face, int *Inf1, int *Inf2, int *NCFace) const
|
|
{
|
|
*Inf1 = faces_info[Face].Elem1Inf;
|
|
*Inf2 = faces_info[Face].Elem2Inf;
|
|
*NCFace = faces_info[Face].NCFace;
|
|
}
|
|
|
|
Geometry::Type Mesh::GetFaceGeometry(int Face) const
|
|
{
|
|
switch (Dim)
|
|
{
|
|
case 1: return Geometry::POINT;
|
|
case 2: return Geometry::SEGMENT;
|
|
case 3:
|
|
if (Face < NumOfFaces) // local (non-ghost) face
|
|
{
|
|
return faces[Face]->GetGeometryType();
|
|
}
|
|
// ghost face
|
|
const int nc_face_id = faces_info[Face].NCFace;
|
|
|
|
MFEM_ASSERT(nc_face_id >= 0, "parent ghost faces are not supported");
|
|
return faces[nc_faces_info[nc_face_id].MasterFace]->GetGeometryType();
|
|
}
|
|
return Geometry::INVALID;
|
|
}
|
|
|
|
Geometry::Type Mesh::GetTypicalFaceGeometry() const
|
|
{
|
|
Geometry::Type elem_geom = GetTypicalElementGeometry();
|
|
switch (elem_geom)
|
|
{
|
|
case Geometry::SEGMENT: return Geometry::POINT;
|
|
case Geometry::TRIANGLE: return Geometry::SEGMENT;
|
|
case Geometry::SQUARE: return Geometry::SEGMENT;
|
|
case Geometry::TETRAHEDRON: return Geometry::TRIANGLE;
|
|
case Geometry::CUBE: return Geometry::SQUARE;
|
|
case Geometry::PRISM: return Geometry::TRIANGLE;
|
|
case Geometry::PYRAMID: return Geometry::TRIANGLE;
|
|
default: return Geometry::INVALID;
|
|
}
|
|
}
|
|
|
|
Element::Type Mesh::GetFaceElementType(int Face) const
|
|
{
|
|
return (Dim == 1) ? Element::POINT : faces[Face]->GetType();
|
|
}
|
|
|
|
Array<int> Mesh::GetFaceToBdrElMap() const
|
|
{
|
|
Array<int> face_to_be(Dim == 2 ? NumOfEdges : NumOfFaces);
|
|
face_to_be = -1;
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
face_to_be[GetBdrElementFaceIndex(i)] = i;
|
|
}
|
|
return face_to_be;
|
|
}
|
|
|
|
Geometry::Type Mesh::GetTypicalElementGeometry() const
|
|
{
|
|
if (GetNE() > 0) { return GetElementGeometry(0); }
|
|
|
|
const int dim = Dimension();
|
|
if (dim == 1)
|
|
{
|
|
return Geometry::SEGMENT;
|
|
}
|
|
Geometry::Type geom = Geometry::INVALID;
|
|
if (dim == 2)
|
|
{
|
|
geom = ((meshgen & 1) ? Geometry::TRIANGLE :
|
|
((meshgen & 2) ? Geometry::SQUARE : Geometry::INVALID));
|
|
}
|
|
else if (dim == 3)
|
|
{
|
|
geom = ((meshgen & 1) ? Geometry::TETRAHEDRON :
|
|
((meshgen & 2) ? Geometry::CUBE :
|
|
((meshgen & 4) ? Geometry::PRISM :
|
|
((meshgen & 8) ? Geometry::PYRAMID : Geometry::INVALID))));
|
|
}
|
|
MFEM_VERIFY(geom != Geometry::INVALID,
|
|
"Could not determine a typical element Geometry!");
|
|
return geom;
|
|
}
|
|
|
|
|
|
void Mesh::GetExteriorFaceMarker(Array<int> & face_marker) const
|
|
{
|
|
const int num_faces = GetNumFaces();
|
|
|
|
face_marker.SetSize(num_faces);
|
|
|
|
for (int f = 0; f < num_faces; f++)
|
|
{
|
|
if (FaceIsTrueInterior(f))
|
|
{
|
|
face_marker[f] = 0;
|
|
}
|
|
else
|
|
{
|
|
face_marker[f] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::UnmarkInternalBoundaries(Array<int> &bdr_marker, bool excl) const
|
|
{
|
|
const int max_bdr_attr = bdr_attributes.Max();
|
|
|
|
MFEM_VERIFY(bdr_marker.Size() >= max_bdr_attr,
|
|
"bdr_marker must be at least bdr_attriburtes.Max() in length");
|
|
|
|
Array<bool> interior_bdr(max_bdr_attr); interior_bdr = false;
|
|
Array<bool> exterior_bdr(max_bdr_attr); exterior_bdr = false;
|
|
|
|
// Identify attributes which contain interior faces and those which
|
|
// contain exterior faces.
|
|
for (int be = 0; be < boundary.Size(); be++)
|
|
{
|
|
const int bea = boundary[be]->GetAttribute();
|
|
|
|
if (bdr_marker[bea-1] != 0)
|
|
{
|
|
const int f = be_to_face[be];
|
|
|
|
if (FaceIsTrueInterior(f))
|
|
{
|
|
interior_bdr[bea-1] = true;
|
|
}
|
|
else
|
|
{
|
|
exterior_bdr[bea-1] = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Unmark attributes which are currently marked, contain interior faces,
|
|
// and satisfy the appropriate exclusivity requirement.
|
|
for (int b = 0; b < max_bdr_attr; b++)
|
|
{
|
|
if (bdr_marker[b] != 0 && interior_bdr[b])
|
|
{
|
|
if (!excl || !exterior_bdr[b])
|
|
{
|
|
bdr_marker[b] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::UnmarkNamedBoundaries(const std::string &set_name,
|
|
Array<int> &bdr_marker) const
|
|
{
|
|
const int max_bdr_attr = bdr_attributes.Max();
|
|
|
|
MFEM_VERIFY(bdr_attribute_sets.AttributeSetExists(set_name),
|
|
"Named set is not defined in this mesh!");
|
|
MFEM_VERIFY(bdr_marker.Size() >= bdr_attributes.Max(),
|
|
"bdr_marker must be at least bdr_attriburtes.Max() in length");
|
|
|
|
Array<int> set_marker = bdr_attribute_sets.GetAttributeSetMarker(set_name);
|
|
|
|
for (int b = 0; b < max_bdr_attr; b++)
|
|
{
|
|
if (set_marker[b])
|
|
{
|
|
bdr_marker[b] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::MarkExternalBoundaries(Array<int> &bdr_marker, bool excl) const
|
|
{
|
|
const int max_bdr_attr = bdr_attributes.Max();
|
|
|
|
MFEM_VERIFY(bdr_marker.Size() >= max_bdr_attr,
|
|
"bdr_marker must be at least bdr_attriburtes.Max() in length");
|
|
|
|
Array<bool> interior_bdr(max_bdr_attr); interior_bdr = false;
|
|
Array<bool> exterior_bdr(max_bdr_attr); exterior_bdr = false;
|
|
|
|
// Mark boundary attributes containing exterior faces while keeping track of
|
|
// those which also contain interior faces.
|
|
for (int be = 0; be < boundary.Size(); be++)
|
|
{
|
|
const int bea = boundary[be]->GetAttribute();
|
|
|
|
const int f = be_to_face[be];
|
|
|
|
if (FaceIsTrueInterior(f))
|
|
{
|
|
interior_bdr[bea-1] = true;
|
|
}
|
|
else
|
|
{
|
|
exterior_bdr[bea-1] = true;
|
|
}
|
|
}
|
|
|
|
// Mark attributes which were found to contain exterior faces and satisfy
|
|
// the appropriate exclusivity requirement.
|
|
for (int b = 0; b < max_bdr_attr; b++)
|
|
{
|
|
if (bdr_marker[b] == 0 && exterior_bdr[b])
|
|
{
|
|
if (!excl || !interior_bdr[b])
|
|
{
|
|
bdr_marker[b] = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::MarkNamedBoundaries(const std::string &set_name,
|
|
Array<int> &bdr_marker) const
|
|
{
|
|
const int max_bdr_attr = bdr_attributes.Max();
|
|
|
|
MFEM_VERIFY(bdr_attribute_sets.AttributeSetExists(set_name),
|
|
"Named set is not defined in this mesh!");
|
|
MFEM_VERIFY(bdr_marker.Size() >= max_bdr_attr,
|
|
"bdr_marker must be at least bdr_attriburtes.Max() in length");
|
|
|
|
Array<int> set_marker = bdr_attribute_sets.GetAttributeSetMarker(set_name);
|
|
|
|
for (int b = 0; b < max_bdr_attr; b++)
|
|
{
|
|
if (set_marker[b])
|
|
{
|
|
bdr_marker[b] = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::Init()
|
|
{
|
|
// in order of declaration:
|
|
Dim = spaceDim = 0;
|
|
NumOfVertices = -1;
|
|
NumOfElements = NumOfBdrElements = 0;
|
|
NumOfEdges = NumOfFaces = 0;
|
|
nbInteriorFaces = -1;
|
|
nbBoundaryFaces = -1;
|
|
meshgen = mesh_geoms = 0;
|
|
sequence = 0;
|
|
nodes_sequence = 0;
|
|
Nodes = NULL;
|
|
own_nodes = 1;
|
|
NURBSext = NULL;
|
|
ncmesh = NULL;
|
|
last_operation = Mesh::NONE;
|
|
}
|
|
|
|
void Mesh::InitTables()
|
|
{
|
|
el_to_edge =
|
|
el_to_face = el_to_el = bel_to_edge = face_edge = edge_vertex = NULL;
|
|
face_to_elem = NULL;
|
|
}
|
|
|
|
void Mesh::SetEmpty()
|
|
{
|
|
Init();
|
|
InitTables();
|
|
}
|
|
|
|
void Mesh::DestroyTables()
|
|
{
|
|
delete el_to_edge;
|
|
delete el_to_face;
|
|
delete el_to_el;
|
|
DeleteGeometricFactors();
|
|
|
|
if (Dim == 3)
|
|
{
|
|
delete bel_to_edge;
|
|
}
|
|
|
|
delete face_edge;
|
|
delete edge_vertex;
|
|
|
|
delete face_to_elem;
|
|
face_to_elem = NULL;
|
|
}
|
|
|
|
void Mesh::DestroyPointers()
|
|
{
|
|
if (own_nodes) { delete Nodes; }
|
|
|
|
delete ncmesh;
|
|
|
|
delete NURBSext;
|
|
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
FreeElement(elements[i]);
|
|
}
|
|
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
FreeElement(boundary[i]);
|
|
}
|
|
|
|
for (int i = 0; i < faces.Size(); i++)
|
|
{
|
|
FreeElement(faces[i]);
|
|
}
|
|
|
|
DestroyTables();
|
|
}
|
|
|
|
void Mesh::Destroy()
|
|
{
|
|
DestroyPointers();
|
|
|
|
elements.DeleteAll();
|
|
vertices.DeleteAll();
|
|
boundary.DeleteAll();
|
|
faces.DeleteAll();
|
|
faces_info.DeleteAll();
|
|
nc_faces_info.DeleteAll();
|
|
be_to_face.DeleteAll();
|
|
|
|
// TODO:
|
|
// IsoparametricTransformations
|
|
// Transformation, Transformation2, BdrTransformation, FaceTransformation,
|
|
// EdgeTransformation;
|
|
// FaceElementTransformations FaceElemTr;
|
|
|
|
CoarseFineTr.Clear();
|
|
|
|
#ifdef MFEM_USE_MEMALLOC
|
|
TetMemory.Clear();
|
|
#endif
|
|
|
|
elem_attrs_cache.DeleteAll();
|
|
bdr_face_attrs_cache.DeleteAll();
|
|
attributes.DeleteAll();
|
|
bdr_attributes.DeleteAll();
|
|
|
|
face_indices[0].DeleteAll();
|
|
face_indices[1].DeleteAll();
|
|
// force de-allocation so after this mesh has the smallest memory footprint
|
|
// possible
|
|
inv_face_indices[0] = std::unordered_map<int, int>();
|
|
inv_face_indices[1] = std::unordered_map<int, int>();
|
|
}
|
|
|
|
void Mesh::ResetLazyData()
|
|
{
|
|
delete el_to_el; el_to_el = NULL;
|
|
delete face_edge; face_edge = NULL;
|
|
delete face_to_elem; face_to_elem = NULL;
|
|
delete edge_vertex; edge_vertex = NULL;
|
|
DeleteGeometricFactors();
|
|
nbInteriorFaces = -1;
|
|
nbBoundaryFaces = -1;
|
|
// set size to 0 so re-computations can potentially avoid a new allocation
|
|
bdr_face_attrs_cache.SetSize(0);
|
|
elem_attrs_cache.SetSize(0);
|
|
|
|
face_indices[0].SetSize(0);
|
|
face_indices[1].SetSize(0);
|
|
inv_face_indices[0].clear();
|
|
inv_face_indices[1].clear();
|
|
}
|
|
|
|
void Mesh::SetAttributes(bool elem_attrs_changed, bool bdr_face_attrs_changed)
|
|
{
|
|
if (bdr_face_attrs_changed)
|
|
{
|
|
bdr_face_attrs_cache.SetSize(0); // Invalidate the cache
|
|
|
|
// Get sorted list of unique boundary element attributes
|
|
std::set<int> attribs;
|
|
for (int i = 0; i < GetNBE(); i++)
|
|
{
|
|
attribs.emplace(GetBdrAttribute(i));
|
|
}
|
|
|
|
bdr_attributes.SetSize(attribs.size());
|
|
bdr_attributes.HostWrite();
|
|
std::copy(attribs.begin(), attribs.end(), bdr_attributes.begin());
|
|
if (bdr_attributes.Size() > 0 && bdr_attributes[0] <= 0)
|
|
{
|
|
MFEM_WARNING("Non-positive attributes on the boundary!");
|
|
}
|
|
}
|
|
|
|
if (elem_attrs_changed)
|
|
{
|
|
// Re-compute the attributes cache
|
|
elem_attrs_cache.SetSize(0);
|
|
GetElementAttributes();
|
|
// Get sorted list of unique element attributes
|
|
std::set<int> attribs(elem_attrs_cache.begin(), elem_attrs_cache.end());
|
|
attributes.SetSize(attribs.size());
|
|
attributes.HostWrite();
|
|
std::copy(attribs.begin(), attribs.end(), attributes.begin());
|
|
|
|
if (attributes.Size() > 0 && attributes[0] <= 0)
|
|
{
|
|
MFEM_WARNING("Non-positive attributes in the domain!");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::InitMesh(int Dim_, int spaceDim_, int NVert, int NElem, int NBdrElem)
|
|
{
|
|
SetEmpty();
|
|
|
|
Dim = Dim_;
|
|
spaceDim = spaceDim_;
|
|
|
|
NumOfVertices = 0;
|
|
vertices.SetSize(NVert); // just allocate space for vertices
|
|
|
|
NumOfElements = 0;
|
|
elements.SetSize(NElem); // just allocate space for Element *
|
|
|
|
NumOfBdrElements = 0;
|
|
boundary.SetSize(NBdrElem); // just allocate space for Element *
|
|
}
|
|
|
|
template<typename T>
|
|
static void CheckEnlarge(Array<T> &array, int size)
|
|
{
|
|
if (size >= array.Size()) { array.SetSize(size + 1); }
|
|
}
|
|
|
|
int Mesh::AddVertex(real_t x, real_t y, real_t z)
|
|
{
|
|
CheckEnlarge(vertices, NumOfVertices);
|
|
real_t *v = vertices[NumOfVertices]();
|
|
v[0] = x;
|
|
v[1] = y;
|
|
v[2] = z;
|
|
return NumOfVertices++;
|
|
}
|
|
|
|
int Mesh::AddVertex(const real_t *coords)
|
|
{
|
|
CheckEnlarge(vertices, NumOfVertices);
|
|
vertices[NumOfVertices].SetCoords(spaceDim, coords);
|
|
return NumOfVertices++;
|
|
}
|
|
|
|
int Mesh::AddVertex(const Vector &coords)
|
|
{
|
|
MFEM_ASSERT(coords.Size() >= spaceDim,
|
|
"invalid 'coords' size: " << coords.Size());
|
|
return AddVertex(coords.GetData());
|
|
}
|
|
|
|
void Mesh::AddVertexParents(int i, int p1, int p2)
|
|
{
|
|
tmp_vertex_parents.Append(Triple<int, int, int>(i, p1, p2));
|
|
|
|
// if vertex coordinates are defined, make sure the hanging vertex has the
|
|
// correct position
|
|
if (i < vertices.Size())
|
|
{
|
|
real_t *vi = vertices[i](), *vp1 = vertices[p1](), *vp2 = vertices[p2]();
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
vi[j] = (vp1[j] + vp2[j]) * 0.5;
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mesh::AddVertexAtMeanCenter(const int *vi, int nverts, int dim)
|
|
{
|
|
Vector vii(dim);
|
|
vii = 0.0;
|
|
for (int i = 0; i < nverts; i++)
|
|
{
|
|
real_t *vp = vertices[vi[i]]();
|
|
for (int j = 0; j < dim; j++)
|
|
{
|
|
vii(j) += vp[j];
|
|
}
|
|
}
|
|
vii /= nverts;
|
|
AddVertex(vii);
|
|
return NumOfVertices;
|
|
}
|
|
|
|
int Mesh::AddSegment(int v1, int v2, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Segment(v1, v2, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddSegment(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Segment(vi, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddTriangle(int v1, int v2, int v3, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Triangle(v1, v2, v3, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddTriangle(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Triangle(vi, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddQuad(int v1, int v2, int v3, int v4, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Quadrilateral(v1, v2, v3, v4, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddQuad(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Quadrilateral(vi, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddTet(int v1, int v2, int v3, int v4, int attr)
|
|
{
|
|
int vi[4] = {v1, v2, v3, v4};
|
|
return AddTet(vi, attr);
|
|
}
|
|
|
|
int Mesh::AddTet(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
#ifdef MFEM_USE_MEMALLOC
|
|
Tetrahedron *tet;
|
|
tet = TetMemory.Alloc();
|
|
tet->SetVertices(vi);
|
|
tet->SetAttribute(attr);
|
|
elements[NumOfElements] = tet;
|
|
#else
|
|
elements[NumOfElements] = new Tetrahedron(vi, attr);
|
|
#endif
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddWedge(int v1, int v2, int v3, int v4, int v5, int v6, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Wedge(v1, v2, v3, v4, v5, v6, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddWedge(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Wedge(vi, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddPyramid(int v1, int v2, int v3, int v4, int v5, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Pyramid(v1, v2, v3, v4, v5, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddPyramid(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Pyramid(vi, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddHex(int v1, int v2, int v3, int v4, int v5, int v6, int v7, int v8,
|
|
int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] =
|
|
new Hexahedron(v1, v2, v3, v4, v5, v6, v7, v8, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddHex(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = new Hexahedron(vi, attr);
|
|
return NumOfElements++;
|
|
}
|
|
|
|
void Mesh::AddHexAsTets(const int *vi, int attr)
|
|
{
|
|
static const int hex_to_tet[6][4] =
|
|
{
|
|
{ 0, 1, 2, 6 }, { 0, 5, 1, 6 }, { 0, 4, 5, 6 },
|
|
{ 0, 2, 3, 6 }, { 0, 3, 7, 6 }, { 0, 7, 4, 6 }
|
|
};
|
|
int ti[4];
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
ti[j] = vi[hex_to_tet[i][j]];
|
|
}
|
|
AddTet(ti, attr);
|
|
}
|
|
}
|
|
|
|
void Mesh::AddHexAsWedges(const int *vi, int attr)
|
|
{
|
|
static const int hex_to_wdg[2][6] =
|
|
{
|
|
{ 0, 1, 2, 4, 5, 6 }, { 0, 2, 3, 4, 6, 7 }
|
|
};
|
|
int ti[6];
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
ti[j] = vi[hex_to_wdg[i][j]];
|
|
}
|
|
AddWedge(ti, attr);
|
|
}
|
|
}
|
|
|
|
void Mesh::AddHexAsPyramids(const int *vi, int attr)
|
|
{
|
|
static const int hex_to_pyr[6][5] =
|
|
{
|
|
{ 0, 1, 2, 3, 8 }, { 0, 4, 5, 1, 8 }, { 1, 5, 6, 2, 8 },
|
|
{ 2, 6, 7, 3, 8 }, { 3, 7, 4, 0, 8 }, { 7, 6, 5, 4, 8 }
|
|
};
|
|
int ti[5];
|
|
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
for (int j = 0; j < 5; j++)
|
|
{
|
|
ti[j] = vi[hex_to_pyr[i][j]];
|
|
}
|
|
AddPyramid(ti, attr);
|
|
}
|
|
}
|
|
|
|
void Mesh::AddQuadAs4TrisWithPoints(int *vi, int attr)
|
|
{
|
|
int num_faces = 4;
|
|
static const int quad_to_tri[4][2] =
|
|
{
|
|
{0, 1}, {1, 2}, {2, 3}, {3, 0}
|
|
};
|
|
|
|
int elem_center_index = AddVertexAtMeanCenter(vi, 4, 2) - 1;
|
|
|
|
int ti[3];
|
|
ti[2] = elem_center_index;
|
|
for (int i = 0; i < num_faces; i++)
|
|
{
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
ti[j] = vi[quad_to_tri[i][j]];
|
|
}
|
|
AddTri(ti, attr);
|
|
}
|
|
}
|
|
|
|
void Mesh::AddQuadAs5QuadsWithPoints(int *vi, int attr)
|
|
{
|
|
int num_faces = 4;
|
|
static const int quad_faces[4][2] =
|
|
{
|
|
{0, 1}, {1, 2}, {2, 3}, {3, 0}
|
|
};
|
|
|
|
Vector px(4), py(4);
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
real_t *vp = vertices[vi[i]]();
|
|
px(i) = vp[0];
|
|
py(i) = vp[1];
|
|
}
|
|
|
|
int vnew_index[4];
|
|
real_t vnew[2];
|
|
real_t r = 0.25, s = 0.25;
|
|
vnew[0] = px(0)*(1-r)*(1-s) + px(1)*(r)*(1-s) + px(2)*r*s + px(3)*(1-r)*s;
|
|
vnew[1] = py(0)*(1-r)*(1-s) + py(1)*(r)*(1-s) + py(2)*r*s + py(3)*(1-r)*s;
|
|
AddVertex(vnew);
|
|
vnew_index[0] = NumOfVertices-1;
|
|
|
|
r = 0.75, s = 0.25;
|
|
vnew[0] = px(0)*(1-r)*(1-s) + px(1)*(r)*(1-s) + px(2)*r*s + px(3)*(1-r)*s;
|
|
vnew[1] = py(0)*(1-r)*(1-s) + py(1)*(r)*(1-s) + py(2)*r*s + py(3)*(1-r)*s;
|
|
AddVertex(vnew);
|
|
vnew_index[1] = NumOfVertices-1;
|
|
|
|
r = 0.75, s = 0.75;
|
|
vnew[0] = px(0)*(1-r)*(1-s) + px(1)*(r)*(1-s) + px(2)*r*s + px(3)*(1-r)*s;
|
|
vnew[1] = py(0)*(1-r)*(1-s) + py(1)*(r)*(1-s) + py(2)*r*s + py(3)*(1-r)*s;
|
|
AddVertex(vnew);
|
|
vnew_index[2] = NumOfVertices-1;
|
|
|
|
r = 0.25, s = 0.75;
|
|
vnew[0] = px(0)*(1-r)*(1-s) + px(1)*(r)*(1-s) + px(2)*r*s + px(3)*(1-r)*s;
|
|
vnew[1] = py(0)*(1-r)*(1-s) + py(1)*(r)*(1-s) + py(2)*r*s + py(3)*(1-r)*s;
|
|
AddVertex(vnew);
|
|
vnew_index[3] = NumOfVertices-1;
|
|
|
|
static const int quad_faces_new[4][2] =
|
|
{
|
|
{ 1, 0}, { 2, 1}, { 3, 2}, { 0, 3}
|
|
};
|
|
|
|
int ti[4];
|
|
for (int i = 0; i < num_faces; i++)
|
|
{
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
ti[j] = vi[quad_faces[i][j]];
|
|
ti[j+2] = vnew_index[quad_faces_new[i][j]];
|
|
}
|
|
AddQuad(ti, attr);
|
|
}
|
|
AddQuad(vnew_index, attr);
|
|
}
|
|
|
|
void Mesh::AddHexAs24TetsWithPoints(int *vi,
|
|
std::map<std::array<int, 4>, int> &hex_face_verts,
|
|
int attr)
|
|
{
|
|
auto get4arraysorted = [](Array<int> v)
|
|
{
|
|
v.Sort();
|
|
return std::array<int, 4> {v[0], v[1], v[2], v[3]};
|
|
};
|
|
|
|
int num_faces = 6;
|
|
static const int hex_to_tet[6][4] =
|
|
{
|
|
{ 0, 1, 2, 3 }, { 1, 2, 6, 5 }, { 5, 4, 7, 6},
|
|
{ 0, 1, 5, 4 }, { 2, 3, 7, 6 }, { 0,3, 7, 4}
|
|
};
|
|
|
|
int elem_center_index = AddVertexAtMeanCenter(vi, 8, 3) - 1;
|
|
|
|
Array<int> flist(4);
|
|
|
|
// local vertex indices for each of the 4 edges of the face
|
|
static const int tet_face[4][2] =
|
|
{
|
|
{0, 1}, {1, 2}, {3, 2}, {3, 0}
|
|
};
|
|
|
|
for (int i = 0; i < num_faces; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
flist[j] = vi[hex_to_tet[i][j]];
|
|
}
|
|
int face_center_index;
|
|
|
|
auto t = get4arraysorted(flist);
|
|
auto it = hex_face_verts.find(t);
|
|
if (it == hex_face_verts.end())
|
|
{
|
|
face_center_index = AddVertexAtMeanCenter(flist.GetData(),
|
|
flist.Size(), 3) - 1;
|
|
hex_face_verts.insert({t, face_center_index});
|
|
}
|
|
else
|
|
{
|
|
face_center_index = it->second;
|
|
}
|
|
int fti[4];
|
|
fti[2] = face_center_index;
|
|
fti[3] = elem_center_index;
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
fti[k] = flist[tet_face[j][k]];
|
|
}
|
|
AddTet(fti, attr);
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mesh::AddElement(Element *elem)
|
|
{
|
|
CheckEnlarge(elements, NumOfElements);
|
|
elements[NumOfElements] = elem;
|
|
return NumOfElements++;
|
|
}
|
|
|
|
int Mesh::AddBdrElement(Element *elem)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = elem;
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
void Mesh::AddBdrElements(Array<Element *> &bdr_elems,
|
|
const Array<int> &new_be_to_face)
|
|
{
|
|
boundary.Reserve(boundary.Size() + bdr_elems.Size());
|
|
MFEM_ASSERT(bdr_elems.Size() == new_be_to_face.Size(), "wrong size");
|
|
for (int i = 0; i < bdr_elems.Size(); i++)
|
|
{
|
|
AddBdrElement(bdr_elems[i]);
|
|
}
|
|
be_to_face.Append(new_be_to_face);
|
|
}
|
|
|
|
int Mesh::AddBdrSegment(int v1, int v2, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Segment(v1, v2, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
int Mesh::AddBdrSegment(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Segment(vi, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
int Mesh::AddBdrTriangle(int v1, int v2, int v3, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Triangle(v1, v2, v3, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
int Mesh::AddBdrTriangle(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Triangle(vi, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
int Mesh::AddBdrQuad(int v1, int v2, int v3, int v4, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Quadrilateral(v1, v2, v3, v4, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
int Mesh::AddBdrQuad(const int *vi, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Quadrilateral(vi, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
void Mesh::AddBdrQuadAsTriangles(const int *vi, int attr)
|
|
{
|
|
static const int quad_to_tri[2][3] = { { 0, 1, 2 }, { 0, 2, 3 } };
|
|
int ti[3];
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
ti[j] = vi[quad_to_tri[i][j]];
|
|
}
|
|
AddBdrTriangle(ti, attr);
|
|
}
|
|
}
|
|
|
|
int Mesh::AddBdrPoint(int v, int attr)
|
|
{
|
|
CheckEnlarge(boundary, NumOfBdrElements);
|
|
boundary[NumOfBdrElements] = new Point(&v, attr);
|
|
return NumOfBdrElements++;
|
|
}
|
|
|
|
void Mesh::GenerateBoundaryElements()
|
|
{
|
|
for (auto &b : boundary)
|
|
{
|
|
FreeElement(b);
|
|
}
|
|
|
|
if (Dim == 3)
|
|
{
|
|
delete bel_to_edge;
|
|
bel_to_edge = NULL;
|
|
}
|
|
|
|
// count the 'NumOfBdrElements'
|
|
NumOfBdrElements = 0;
|
|
for (const auto &fi : faces_info)
|
|
{
|
|
if (fi.Elem2No < 0) { ++NumOfBdrElements; }
|
|
}
|
|
|
|
// Add the boundary elements
|
|
boundary.SetSize(NumOfBdrElements);
|
|
be_to_face.SetSize(NumOfBdrElements);
|
|
for (int i = 0, j = 0; i < faces_info.Size(); i++)
|
|
{
|
|
if (faces_info[i].Elem2No < 0)
|
|
{
|
|
boundary[j] = faces[i]->Duplicate(this);
|
|
be_to_face[j++] = i;
|
|
}
|
|
}
|
|
|
|
// Note: in 3D, 'bel_to_edge' is destroyed but it's not updated.
|
|
}
|
|
|
|
void Mesh::FinalizeCheck()
|
|
{
|
|
MFEM_VERIFY(vertices.Size() == NumOfVertices ||
|
|
vertices.Size() == 0,
|
|
"incorrect number of vertices: preallocated: " << vertices.Size()
|
|
<< ", actually added: " << NumOfVertices);
|
|
MFEM_VERIFY(elements.Size() == NumOfElements,
|
|
"incorrect number of elements: preallocated: " << elements.Size()
|
|
<< ", actually added: " << NumOfElements);
|
|
MFEM_VERIFY(boundary.Size() == NumOfBdrElements,
|
|
"incorrect number of boundary elements: preallocated: "
|
|
<< boundary.Size() << ", actually added: " << NumOfBdrElements);
|
|
}
|
|
|
|
void Mesh::FinalizeTriMesh(int generate_edges, int refine, bool fix_orientation)
|
|
{
|
|
FinalizeCheck();
|
|
CheckElementOrientation(fix_orientation);
|
|
|
|
if (refine)
|
|
{
|
|
MarkTriMeshForRefinement();
|
|
}
|
|
|
|
if (generate_edges)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
CheckBdrElementOrientation();
|
|
}
|
|
else
|
|
{
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
NumOfFaces = 0;
|
|
|
|
SetAttributes();
|
|
|
|
SetMeshGen();
|
|
}
|
|
|
|
void Mesh::FinalizeQuadMesh(int generate_edges, int refine,
|
|
bool fix_orientation)
|
|
{
|
|
FinalizeCheck();
|
|
if (fix_orientation)
|
|
{
|
|
CheckElementOrientation(fix_orientation);
|
|
}
|
|
|
|
if (generate_edges)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
CheckBdrElementOrientation();
|
|
}
|
|
else
|
|
{
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
NumOfFaces = 0;
|
|
|
|
SetAttributes();
|
|
|
|
SetMeshGen();
|
|
}
|
|
|
|
|
|
class GeckoProgress : public Gecko::Progress
|
|
{
|
|
real_t limit;
|
|
mutable StopWatch sw;
|
|
public:
|
|
GeckoProgress(real_t limit) : limit(limit) { sw.Start(); }
|
|
bool quit() const override { return limit > 0 && sw.UserTime() > limit; }
|
|
};
|
|
|
|
class GeckoVerboseProgress : public GeckoProgress
|
|
{
|
|
using Float = Gecko::Float;
|
|
using Graph = Gecko::Graph;
|
|
using uint = Gecko::uint;
|
|
public:
|
|
GeckoVerboseProgress(real_t limit) : GeckoProgress(limit) {}
|
|
|
|
void beginorder(const Graph* graph, Float cost) const override
|
|
{ mfem::out << "Begin Gecko ordering, cost = " << cost << std::endl; }
|
|
void endorder(const Graph* graph, Float cost) const override
|
|
{ mfem::out << "End ordering, cost = " << cost << std::endl; }
|
|
|
|
void beginiter(const Graph* graph,
|
|
uint iter, uint maxiter, uint window) const override
|
|
{
|
|
mfem::out << "Iteration " << iter << "/" << maxiter << ", window "
|
|
<< window << std::flush;
|
|
}
|
|
void enditer(const Graph* graph, Float mincost, Float cost) const override
|
|
{ mfem::out << ", cost = " << cost << endl; }
|
|
};
|
|
|
|
|
|
real_t Mesh::GetGeckoElementOrdering(Array<int> &ordering,
|
|
int iterations, int window,
|
|
int period, int seed, bool verbose,
|
|
real_t time_limit)
|
|
{
|
|
Gecko::Graph graph;
|
|
Gecko::FunctionalGeometric functional; // edge product cost
|
|
|
|
GeckoProgress progress(time_limit);
|
|
GeckoVerboseProgress vprogress(time_limit);
|
|
|
|
// insert elements as nodes in the graph
|
|
for (int elemid = 0; elemid < GetNE(); ++elemid)
|
|
{
|
|
graph.insert_node();
|
|
}
|
|
|
|
// insert graph edges for element neighbors
|
|
// NOTE: indices in Gecko are 1 based hence the +1 on insertion
|
|
const Table &my_el_to_el = ElementToElementTable();
|
|
for (int elemid = 0; elemid < GetNE(); ++elemid)
|
|
{
|
|
const int *neighid = my_el_to_el.GetRow(elemid);
|
|
for (int i = 0; i < my_el_to_el.RowSize(elemid); ++i)
|
|
{
|
|
graph.insert_arc(elemid + 1, neighid[i] + 1);
|
|
}
|
|
}
|
|
|
|
// get the ordering from Gecko and copy it into the Array<int>
|
|
graph.order(&functional, iterations, window, period, seed,
|
|
verbose ? &vprogress : &progress);
|
|
|
|
ordering.SetSize(GetNE());
|
|
Gecko::Node::Index NE = GetNE();
|
|
for (Gecko::Node::Index gnodeid = 1; gnodeid <= NE; ++gnodeid)
|
|
{
|
|
ordering[gnodeid - 1] = graph.rank(gnodeid);
|
|
}
|
|
|
|
return graph.cost();
|
|
}
|
|
|
|
|
|
struct HilbertCmp
|
|
{
|
|
int coord;
|
|
bool dir;
|
|
const Array<real_t> &points;
|
|
real_t mid;
|
|
|
|
HilbertCmp(int coord, bool dir, const Array<real_t> &points, real_t mid)
|
|
: coord(coord), dir(dir), points(points), mid(mid) {}
|
|
|
|
bool operator()(int i) const
|
|
{
|
|
return (points[3*i + coord] < mid) != dir;
|
|
}
|
|
};
|
|
|
|
static void HilbertSort2D(int coord1, // major coordinate to sort points by
|
|
bool dir1, // sort coord1 ascending/descending?
|
|
bool dir2, // sort coord2 ascending/descending?
|
|
const Array<real_t> &points, int *beg, int *end,
|
|
real_t xmin, real_t ymin, real_t xmax, real_t ymax)
|
|
{
|
|
if (end - beg <= 1) { return; }
|
|
|
|
real_t xmid = (xmin + xmax)*0.5;
|
|
real_t ymid = (ymin + ymax)*0.5;
|
|
|
|
int coord2 = (coord1 + 1) % 2; // the 'other' coordinate
|
|
|
|
// sort (partition) points into four quadrants
|
|
int *p0 = beg, *p4 = end;
|
|
int *p2 = std::partition(p0, p4, HilbertCmp(coord1, dir1, points, xmid));
|
|
int *p1 = std::partition(p0, p2, HilbertCmp(coord2, dir2, points, ymid));
|
|
int *p3 = std::partition(p2, p4, HilbertCmp(coord2, !dir2, points, ymid));
|
|
|
|
if (p1 != p4)
|
|
{
|
|
HilbertSort2D(coord2, dir2, dir1, points, p0, p1,
|
|
ymin, xmin, ymid, xmid);
|
|
}
|
|
if (p1 != p0 || p2 != p4)
|
|
{
|
|
HilbertSort2D(coord1, dir1, dir2, points, p1, p2,
|
|
xmin, ymid, xmid, ymax);
|
|
}
|
|
if (p2 != p0 || p3 != p4)
|
|
{
|
|
HilbertSort2D(coord1, dir1, dir2, points, p2, p3,
|
|
xmid, ymid, xmax, ymax);
|
|
}
|
|
if (p3 != p0)
|
|
{
|
|
HilbertSort2D(coord2, !dir2, !dir1, points, p3, p4,
|
|
ymid, xmax, ymin, xmid);
|
|
}
|
|
}
|
|
|
|
static void HilbertSort3D(int coord1, bool dir1, bool dir2, bool dir3,
|
|
const Array<real_t> &points, int *beg, int *end,
|
|
real_t xmin, real_t ymin, real_t zmin,
|
|
real_t xmax, real_t ymax, real_t zmax)
|
|
{
|
|
if (end - beg <= 1) { return; }
|
|
|
|
real_t xmid = (xmin + xmax)*0.5;
|
|
real_t ymid = (ymin + ymax)*0.5;
|
|
real_t zmid = (zmin + zmax)*0.5;
|
|
|
|
int coord2 = (coord1 + 1) % 3;
|
|
int coord3 = (coord1 + 2) % 3;
|
|
|
|
// sort (partition) points into eight octants
|
|
int *p0 = beg, *p8 = end;
|
|
int *p4 = std::partition(p0, p8, HilbertCmp(coord1, dir1, points, xmid));
|
|
int *p2 = std::partition(p0, p4, HilbertCmp(coord2, dir2, points, ymid));
|
|
int *p6 = std::partition(p4, p8, HilbertCmp(coord2, !dir2, points, ymid));
|
|
int *p1 = std::partition(p0, p2, HilbertCmp(coord3, dir3, points, zmid));
|
|
int *p3 = std::partition(p2, p4, HilbertCmp(coord3, !dir3, points, zmid));
|
|
int *p5 = std::partition(p4, p6, HilbertCmp(coord3, dir3, points, zmid));
|
|
int *p7 = std::partition(p6, p8, HilbertCmp(coord3, !dir3, points, zmid));
|
|
|
|
if (p1 != p8)
|
|
{
|
|
HilbertSort3D(coord3, dir3, dir1, dir2, points, p0, p1,
|
|
zmin, xmin, ymin, zmid, xmid, ymid);
|
|
}
|
|
if (p1 != p0 || p2 != p8)
|
|
{
|
|
HilbertSort3D(coord2, dir2, dir3, dir1, points, p1, p2,
|
|
ymin, zmid, xmin, ymid, zmax, xmid);
|
|
}
|
|
if (p2 != p0 || p3 != p8)
|
|
{
|
|
HilbertSort3D(coord2, dir2, dir3, dir1, points, p2, p3,
|
|
ymid, zmid, xmin, ymax, zmax, xmid);
|
|
}
|
|
if (p3 != p0 || p4 != p8)
|
|
{
|
|
HilbertSort3D(coord1, dir1, !dir2, !dir3, points, p3, p4,
|
|
xmin, ymax, zmid, xmid, ymid, zmin);
|
|
}
|
|
if (p4 != p0 || p5 != p8)
|
|
{
|
|
HilbertSort3D(coord1, dir1, !dir2, !dir3, points, p4, p5,
|
|
xmid, ymax, zmid, xmax, ymid, zmin);
|
|
}
|
|
if (p5 != p0 || p6 != p8)
|
|
{
|
|
HilbertSort3D(coord2, !dir2, dir3, !dir1, points, p5, p6,
|
|
ymax, zmid, xmax, ymid, zmax, xmid);
|
|
}
|
|
if (p6 != p0 || p7 != p8)
|
|
{
|
|
HilbertSort3D(coord2, !dir2, dir3, !dir1, points, p6, p7,
|
|
ymid, zmid, xmax, ymin, zmax, xmid);
|
|
}
|
|
if (p7 != p0)
|
|
{
|
|
HilbertSort3D(coord3, !dir3, !dir1, dir2, points, p7, p8,
|
|
zmid, xmax, ymin, zmin, xmid, ymid);
|
|
}
|
|
}
|
|
|
|
void Mesh::GetHilbertElementOrdering(Array<int> &ordering)
|
|
{
|
|
MFEM_VERIFY(spaceDim <= 3, "");
|
|
|
|
Vector min, max, center;
|
|
GetBoundingBox(min, max);
|
|
|
|
Array<int> indices(GetNE());
|
|
Array<real_t> points(3*GetNE());
|
|
|
|
if (spaceDim < 3) { points = 0.0; }
|
|
|
|
// calculate element centers
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
GetElementCenter(i, center);
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
points[3*i + j] = center(j);
|
|
}
|
|
indices[i] = i;
|
|
}
|
|
|
|
if (spaceDim == 1)
|
|
{
|
|
indices.Sort([&](int a, int b)
|
|
{ return points[3*a] < points[3*b]; });
|
|
}
|
|
else if (spaceDim == 2)
|
|
{
|
|
// recursively partition the points in 2D
|
|
HilbertSort2D(0, false, false,
|
|
points, indices.begin(), indices.end(),
|
|
min(0), min(1), max(0), max(1));
|
|
}
|
|
else
|
|
{
|
|
// recursively partition the points in 3D
|
|
HilbertSort3D(0, false, false, false,
|
|
points, indices.begin(), indices.end(),
|
|
min(0), min(1), min(2), max(0), max(1), max(2));
|
|
}
|
|
|
|
// return ordering in the format required by ReorderElements
|
|
ordering.SetSize(GetNE());
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
ordering[indices[i]] = i;
|
|
}
|
|
}
|
|
|
|
|
|
void Mesh::ReorderElements(const Array<int> &ordering, bool reorder_vertices)
|
|
{
|
|
if (NURBSext)
|
|
{
|
|
MFEM_WARNING("element reordering of NURBS meshes is not supported.");
|
|
return;
|
|
}
|
|
if (ncmesh)
|
|
{
|
|
MFEM_WARNING("element reordering of non-conforming meshes is not"
|
|
" supported.");
|
|
return;
|
|
}
|
|
MFEM_VERIFY(ordering.Size() == GetNE(), "invalid reordering array.")
|
|
|
|
// Data members that need to be updated:
|
|
|
|
// - elements - reorder of the pointers and the vertex ids if reordering
|
|
// the vertices
|
|
// - vertices - if reordering the vertices
|
|
// - boundary - update the vertex ids, if reordering the vertices
|
|
// - faces - regenerate
|
|
// - faces_info - regenerate
|
|
|
|
// Deleted by DeleteTables():
|
|
// - el_to_edge - rebuild in 2D and 3D only
|
|
// - el_to_face - rebuild in 3D only
|
|
// - bel_to_edge - rebuild in 3D only
|
|
// - el_to_el - no need to rebuild
|
|
// - face_edge - no need to rebuild
|
|
// - edge_vertex - no need to rebuild
|
|
// - geom_factors - no need to rebuild
|
|
|
|
// - be_to_face
|
|
|
|
// - Nodes
|
|
|
|
// Save the locations of the Nodes so we can rebuild them later
|
|
Array<Vector*> old_elem_node_vals;
|
|
FiniteElementSpace *nodes_fes = NULL;
|
|
if (Nodes)
|
|
{
|
|
old_elem_node_vals.SetSize(GetNE());
|
|
nodes_fes = Nodes->FESpace();
|
|
Array<int> old_dofs;
|
|
Vector vals;
|
|
for (int old_elid = 0; old_elid < GetNE(); ++old_elid)
|
|
{
|
|
nodes_fes->GetElementVDofs(old_elid, old_dofs);
|
|
Nodes->GetSubVector(old_dofs, vals);
|
|
old_elem_node_vals[old_elid] = new Vector(vals);
|
|
}
|
|
}
|
|
|
|
// Get the newly ordered elements
|
|
Array<Element *> new_elements(GetNE());
|
|
for (int old_elid = 0; old_elid < ordering.Size(); ++old_elid)
|
|
{
|
|
int new_elid = ordering[old_elid];
|
|
new_elements[new_elid] = elements[old_elid];
|
|
}
|
|
mfem::Swap(elements, new_elements);
|
|
new_elements.DeleteAll();
|
|
|
|
if (reorder_vertices)
|
|
{
|
|
// Get the new vertex ordering permutation vectors and fill the new
|
|
// vertices
|
|
Array<int> vertex_ordering(GetNV());
|
|
vertex_ordering = -1;
|
|
Array<Vertex> new_vertices(GetNV());
|
|
int new_vertex_ind = 0;
|
|
for (int new_elid = 0; new_elid < GetNE(); ++new_elid)
|
|
{
|
|
int *elem_vert = elements[new_elid]->GetVertices();
|
|
int nv = elements[new_elid]->GetNVertices();
|
|
for (int vi = 0; vi < nv; ++vi)
|
|
{
|
|
int old_vertex_ind = elem_vert[vi];
|
|
if (vertex_ordering[old_vertex_ind] == -1)
|
|
{
|
|
vertex_ordering[old_vertex_ind] = new_vertex_ind;
|
|
new_vertices[new_vertex_ind] = vertices[old_vertex_ind];
|
|
new_vertex_ind++;
|
|
}
|
|
}
|
|
}
|
|
mfem::Swap(vertices, new_vertices);
|
|
new_vertices.DeleteAll();
|
|
|
|
// Replace the vertex ids in the elements with the reordered vertex
|
|
// numbers
|
|
for (int new_elid = 0; new_elid < GetNE(); ++new_elid)
|
|
{
|
|
int *elem_vert = elements[new_elid]->GetVertices();
|
|
int nv = elements[new_elid]->GetNVertices();
|
|
for (int vi = 0; vi < nv; ++vi)
|
|
{
|
|
elem_vert[vi] = vertex_ordering[elem_vert[vi]];
|
|
}
|
|
}
|
|
|
|
// Replace the vertex ids in the boundary with reordered vertex numbers
|
|
for (int belid = 0; belid < GetNBE(); ++belid)
|
|
{
|
|
int *be_vert = boundary[belid]->GetVertices();
|
|
int nv = boundary[belid]->GetNVertices();
|
|
for (int vi = 0; vi < nv; ++vi)
|
|
{
|
|
be_vert[vi] = vertex_ordering[be_vert[vi]];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Destroy tables that need to be rebuild
|
|
DeleteTables();
|
|
|
|
if (Dim > 1)
|
|
{
|
|
// generate el_to_edge, be_to_face (2D), bel_to_edge (3D)
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
if (Dim > 2)
|
|
{
|
|
// generate el_to_face, be_to_face
|
|
GetElementToFaceTable();
|
|
}
|
|
// Update faces and faces_info
|
|
GenerateFaces();
|
|
|
|
// Build the nodes from the saved locations if they were around before
|
|
if (Nodes)
|
|
{
|
|
// To force FE space update, we need to increase 'sequence':
|
|
sequence++;
|
|
nodes_sequence++;
|
|
last_operation = Mesh::NONE;
|
|
nodes_fes->Update(false); // want_transform = false
|
|
Nodes->Update(); // just needed to update Nodes->sequence
|
|
Array<int> new_dofs;
|
|
for (int old_elid = 0; old_elid < GetNE(); ++old_elid)
|
|
{
|
|
int new_elid = ordering[old_elid];
|
|
nodes_fes->GetElementVDofs(new_elid, new_dofs);
|
|
Nodes->SetSubVector(new_dofs, *(old_elem_node_vals[old_elid]));
|
|
delete old_elem_node_vals[old_elid];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Mesh::MarkForRefinement()
|
|
{
|
|
if (meshgen & 1)
|
|
{
|
|
if (Dim == 2)
|
|
{
|
|
MarkTriMeshForRefinement();
|
|
}
|
|
else if (Dim == 3)
|
|
{
|
|
DSTable v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(v_to_v);
|
|
MarkTetMeshForRefinement(v_to_v);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::MarkTriMeshForRefinement()
|
|
{
|
|
// Mark the longest triangle edge by rotating the indices so that
|
|
// vertex 0 - vertex 1 is the longest edge in the triangle.
|
|
DenseMatrix pmat;
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
if (elements[i]->GetType() == Element::TRIANGLE)
|
|
{
|
|
GetPointMatrix(i, pmat);
|
|
static_cast<Triangle*>(elements[i])->MarkEdge(pmat);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::GetEdgeOrdering(const DSTable &v_to_v, Array<int> &order)
|
|
{
|
|
NumOfEdges = v_to_v.NumberOfEntries();
|
|
order.SetSize(NumOfEdges);
|
|
Array<Pair<real_t, int> > length_idx(NumOfEdges);
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
for (DSTable::RowIterator it(v_to_v, i); !it; ++it)
|
|
{
|
|
int j = it.Index();
|
|
length_idx[j].one = GetLength(i, it.Column());
|
|
length_idx[j].two = j;
|
|
}
|
|
}
|
|
|
|
// Sort by increasing edge-length.
|
|
length_idx.Sort();
|
|
|
|
for (int i = 0; i < NumOfEdges; i++)
|
|
{
|
|
order[length_idx[i].two] = i;
|
|
}
|
|
}
|
|
|
|
void Mesh::MarkTetMeshForRefinement(const DSTable &v_to_v)
|
|
{
|
|
// Mark the longest tetrahedral edge by rotating the indices so that
|
|
// vertex 0 - vertex 1 is the longest edge in the element.
|
|
Array<int> order;
|
|
GetEdgeOrdering(v_to_v, order);
|
|
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
if (elements[i]->GetType() == Element::TETRAHEDRON)
|
|
{
|
|
elements[i]->MarkEdge(v_to_v, order);
|
|
}
|
|
}
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
if (boundary[i]->GetType() == Element::TRIANGLE)
|
|
{
|
|
boundary[i]->MarkEdge(v_to_v, order);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::PrepareNodeReorder(DSTable **old_v_to_v, Table **old_elem_vert)
|
|
{
|
|
if (*old_v_to_v && *old_elem_vert)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FiniteElementSpace *fes = Nodes->FESpace();
|
|
|
|
if (*old_v_to_v == NULL)
|
|
{
|
|
bool need_v_to_v = false;
|
|
Array<int> dofs;
|
|
for (int i = 0; i < GetNEdges(); i++)
|
|
{
|
|
// Since edge indices may change, we need to permute edge interior dofs
|
|
// any time an edge index changes and there is at least one dof on that
|
|
// edge.
|
|
fes->GetEdgeInteriorDofs(i, dofs);
|
|
if (dofs.Size() > 0)
|
|
{
|
|
need_v_to_v = true;
|
|
break;
|
|
}
|
|
}
|
|
if (need_v_to_v)
|
|
{
|
|
*old_v_to_v = new DSTable(NumOfVertices);
|
|
GetVertexToVertexTable(*(*old_v_to_v));
|
|
}
|
|
}
|
|
if (*old_elem_vert == NULL)
|
|
{
|
|
bool need_elem_vert = false;
|
|
Array<int> dofs;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
// Since element indices do not change, we need to permute element
|
|
// interior dofs only when there are at least 2 interior dofs in an
|
|
// element (assuming the nodal dofs are non-directional).
|
|
fes->GetElementInteriorDofs(i, dofs);
|
|
if (dofs.Size() > 1)
|
|
{
|
|
need_elem_vert = true;
|
|
break;
|
|
}
|
|
}
|
|
if (need_elem_vert)
|
|
{
|
|
*old_elem_vert = new Table;
|
|
(*old_elem_vert)->MakeI(GetNE());
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
(*old_elem_vert)->AddColumnsInRow(i, elements[i]->GetNVertices());
|
|
}
|
|
(*old_elem_vert)->MakeJ();
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
(*old_elem_vert)->AddConnections(i, elements[i]->GetVertices(),
|
|
elements[i]->GetNVertices());
|
|
}
|
|
(*old_elem_vert)->ShiftUpI();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::DoNodeReorder(DSTable *old_v_to_v, Table *old_elem_vert)
|
|
{
|
|
FiniteElementSpace *fes = Nodes->FESpace();
|
|
const FiniteElementCollection *fec = fes->FEColl();
|
|
Array<int> old_dofs, new_dofs;
|
|
|
|
// assuming that all edges have the same number of dofs
|
|
if (NumOfEdges) { fes->GetEdgeInteriorDofs(0, old_dofs); }
|
|
const int num_edge_dofs = old_dofs.Size();
|
|
|
|
// Save the original nodes
|
|
Nodes->HostReadWrite(); // for "(*Nodes)() = "
|
|
const Vector onodes = *Nodes;
|
|
|
|
// vertex dofs do not need to be moved
|
|
fes->GetVertexDofs(0, old_dofs);
|
|
int offset = NumOfVertices * old_dofs.Size();
|
|
|
|
// edge dofs:
|
|
// edge enumeration may be different but edge orientation is the same
|
|
if (num_edge_dofs > 0)
|
|
{
|
|
DSTable new_v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(new_v_to_v);
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
for (DSTable::RowIterator it(new_v_to_v, i); !it; ++it)
|
|
{
|
|
const int old_i = (*old_v_to_v)(i, it.Column());
|
|
const int new_i = it.Index();
|
|
if (new_i == old_i) { continue; }
|
|
|
|
old_dofs.SetSize(num_edge_dofs);
|
|
new_dofs.SetSize(num_edge_dofs);
|
|
for (int j = 0; j < num_edge_dofs; j++)
|
|
{
|
|
old_dofs[j] = offset + old_i * num_edge_dofs + j;
|
|
new_dofs[j] = offset + new_i * num_edge_dofs + j;
|
|
}
|
|
fes->DofsToVDofs(old_dofs);
|
|
fes->DofsToVDofs(new_dofs);
|
|
for (int j = 0; j < old_dofs.Size(); j++)
|
|
{
|
|
(*Nodes)(new_dofs[j]) = onodes(old_dofs[j]);
|
|
}
|
|
}
|
|
}
|
|
offset += NumOfEdges * num_edge_dofs;
|
|
}
|
|
|
|
// face dofs:
|
|
// both enumeration and orientation of the faces may be different
|
|
if (fes->GetNFDofs() > 0)
|
|
{
|
|
// generate the old face-vertex table using the unmodified 'faces'
|
|
Table old_face_vertex;
|
|
old_face_vertex.MakeI(NumOfFaces);
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
{
|
|
old_face_vertex.AddColumnsInRow(i, faces[i]->GetNVertices());
|
|
}
|
|
old_face_vertex.MakeJ();
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
old_face_vertex.AddConnections(i, faces[i]->GetVertices(),
|
|
faces[i]->GetNVertices());
|
|
old_face_vertex.ShiftUpI();
|
|
|
|
// update 'el_to_face', 'be_to_face', 'faces', and 'faces_info'
|
|
STable3D *faces_tbl = GetElementToFaceTable(1);
|
|
GenerateFaces();
|
|
|
|
// compute the new face dof offsets
|
|
Array<int> new_fdofs(NumOfFaces+1);
|
|
new_fdofs[0] = 0;
|
|
for (int i = 0; i < NumOfFaces; i++) // i = old face index
|
|
{
|
|
const int *old_v = old_face_vertex.GetRow(i);
|
|
int new_i; // new face index
|
|
switch (old_face_vertex.RowSize(i))
|
|
{
|
|
case 3:
|
|
new_i = (*faces_tbl)(old_v[0], old_v[1], old_v[2]);
|
|
break;
|
|
case 4:
|
|
default:
|
|
new_i = (*faces_tbl)(old_v[0], old_v[1], old_v[2], old_v[3]);
|
|
break;
|
|
}
|
|
fes->GetFaceInteriorDofs(i, old_dofs);
|
|
new_fdofs[new_i+1] = old_dofs.Size();
|
|
}
|
|
new_fdofs.PartialSum();
|
|
|
|
// loop over the old face numbers
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
{
|
|
const int *old_v = old_face_vertex.GetRow(i), *new_v;
|
|
const int *dof_ord;
|
|
int new_i, new_or;
|
|
switch (old_face_vertex.RowSize(i))
|
|
{
|
|
case 3:
|
|
new_i = (*faces_tbl)(old_v[0], old_v[1], old_v[2]);
|
|
new_v = faces[new_i]->GetVertices();
|
|
new_or = GetTriOrientation(old_v, new_v);
|
|
dof_ord = fec->DofOrderForOrientation(Geometry::TRIANGLE, new_or);
|
|
break;
|
|
case 4:
|
|
default:
|
|
new_i = (*faces_tbl)(old_v[0], old_v[1], old_v[2], old_v[3]);
|
|
new_v = faces[new_i]->GetVertices();
|
|
new_or = GetQuadOrientation(old_v, new_v);
|
|
dof_ord = fec->DofOrderForOrientation(Geometry::SQUARE, new_or);
|
|
break;
|
|
}
|
|
|
|
fes->GetFaceInteriorDofs(i, old_dofs);
|
|
new_dofs.SetSize(old_dofs.Size());
|
|
for (int j = 0; j < old_dofs.Size(); j++)
|
|
{
|
|
// we assume the dofs are non-directional, i.e. dof_ord[j] is >= 0
|
|
const int old_j = dof_ord[j];
|
|
new_dofs[old_j] = offset + new_fdofs[new_i] + j;
|
|
}
|
|
fes->DofsToVDofs(old_dofs);
|
|
fes->DofsToVDofs(new_dofs);
|
|
for (int j = 0; j < old_dofs.Size(); j++)
|
|
{
|
|
(*Nodes)(new_dofs[j]) = onodes(old_dofs[j]);
|
|
}
|
|
}
|
|
|
|
offset += fes->GetNFDofs();
|
|
delete faces_tbl;
|
|
}
|
|
|
|
// element dofs:
|
|
// element orientation may be different
|
|
if (old_elem_vert) // have elements with 2 or more dofs
|
|
{
|
|
// matters when the 'fec' is
|
|
// (this code is executed only for triangles/tets)
|
|
// - Pk on triangles, k >= 4
|
|
// - Qk on quads, k >= 3
|
|
// - Pk on tets, k >= 5
|
|
// - Qk on hexes, k >= 3
|
|
// - DG spaces
|
|
// - ...
|
|
|
|
// loop over all elements
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
const int *old_v = old_elem_vert->GetRow(i);
|
|
const int *new_v = elements[i]->GetVertices();
|
|
const int *dof_ord;
|
|
int new_or;
|
|
const Geometry::Type geom = elements[i]->GetGeometryType();
|
|
switch (geom)
|
|
{
|
|
case Geometry::SEGMENT:
|
|
new_or = (old_v[0] == new_v[0]) ? +1 : -1;
|
|
break;
|
|
case Geometry::TRIANGLE:
|
|
new_or = GetTriOrientation(old_v, new_v);
|
|
break;
|
|
case Geometry::SQUARE:
|
|
new_or = GetQuadOrientation(old_v, new_v);
|
|
break;
|
|
case Geometry::TETRAHEDRON:
|
|
new_or = GetTetOrientation(old_v, new_v);
|
|
break;
|
|
default:
|
|
new_or = 0;
|
|
MFEM_ABORT(Geometry::Name[geom] << " elements (" << fec->Name()
|
|
<< " FE collection) are not supported yet!");
|
|
break;
|
|
}
|
|
dof_ord = fec->DofOrderForOrientation(geom, new_or);
|
|
MFEM_VERIFY(dof_ord != NULL,
|
|
"FE collection '" << fec->Name()
|
|
<< "' does not define reordering for "
|
|
<< Geometry::Name[geom] << " elements!");
|
|
fes->GetElementInteriorDofs(i, old_dofs);
|
|
new_dofs.SetSize(old_dofs.Size());
|
|
for (int j = 0; j < new_dofs.Size(); j++)
|
|
{
|
|
// we assume the dofs are non-directional, i.e. dof_ord[j] is >= 0
|
|
const int old_j = dof_ord[j];
|
|
new_dofs[old_j] = offset + j;
|
|
}
|
|
offset += new_dofs.Size();
|
|
fes->DofsToVDofs(old_dofs);
|
|
fes->DofsToVDofs(new_dofs);
|
|
for (int j = 0; j < old_dofs.Size(); j++)
|
|
{
|
|
(*Nodes)(new_dofs[j]) = onodes(old_dofs[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update Tables, faces, etc
|
|
if (Dim > 2)
|
|
{
|
|
if (fes->GetNFDofs() == 0)
|
|
{
|
|
// needed for FE spaces that have face dofs, even if
|
|
// the 'Nodes' do not have face dofs.
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
}
|
|
CheckBdrElementOrientation();
|
|
}
|
|
if (el_to_edge)
|
|
{
|
|
// update 'el_to_edge', 'be_to_face' (2D), 'bel_to_edge' (3D)
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
if (Dim == 2)
|
|
{
|
|
// update 'faces' and 'faces_info'
|
|
GenerateFaces();
|
|
CheckBdrElementOrientation();
|
|
}
|
|
}
|
|
// To force FE space update, we need to increase 'sequence':
|
|
sequence++;
|
|
nodes_sequence++;
|
|
last_operation = Mesh::NONE;
|
|
fes->Update(false); // want_transform = false
|
|
Nodes->Update(); // just needed to update Nodes->sequence
|
|
}
|
|
|
|
void Mesh::SetPatchAttribute(int i, int attr)
|
|
{
|
|
MFEM_ASSERT(NURBSext, "SetPatchAttribute is only for NURBS meshes");
|
|
NURBSext->SetPatchAttribute(i, attr);
|
|
const Array<int>& elems = NURBSext->GetPatchElements(i);
|
|
for (auto e : elems)
|
|
{
|
|
SetAttribute(e, attr);
|
|
}
|
|
}
|
|
|
|
int Mesh::GetPatchAttribute(int i) const
|
|
{
|
|
MFEM_ASSERT(NURBSext, "GetPatchAttribute is only for NURBS meshes");
|
|
return NURBSext->GetPatchAttribute(i);
|
|
}
|
|
|
|
void Mesh::SetPatchBdrAttribute(int i, int attr)
|
|
{
|
|
MFEM_ASSERT(NURBSext, "SetPatchBdrAttribute is only for NURBS meshes");
|
|
NURBSext->SetPatchBdrAttribute(i, attr);
|
|
|
|
const Array<int>& bdryelems = NURBSext->GetPatchBdrElements(i);
|
|
for (auto be : bdryelems)
|
|
{
|
|
SetBdrAttribute(be, attr);
|
|
}
|
|
}
|
|
|
|
int Mesh::GetPatchBdrAttribute(int i) const
|
|
{
|
|
MFEM_ASSERT(NURBSext, "GetBdrPatchBdrAttribute is only for NURBS meshes");
|
|
return NURBSext->GetPatchBdrAttribute(i);
|
|
}
|
|
|
|
void Mesh::GetNURBSPatches(Array<NURBSPatch*> &patches)
|
|
{
|
|
MFEM_VERIFY(NURBSext, "Must be a NURBS mesh");
|
|
// This sets the data in NURBSPatch(es) from the control points (Nodes)
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
// Deep copy patches
|
|
NURBSext->GetPatches(patches);
|
|
|
|
// Among other things, this deletes patches in NURBSext
|
|
UpdateNURBS();
|
|
}
|
|
|
|
void Mesh::FinalizeTetMesh(int generate_edges, int refine, bool fix_orientation)
|
|
{
|
|
FinalizeCheck();
|
|
CheckElementOrientation(fix_orientation);
|
|
|
|
if (!HasBoundaryElements())
|
|
{
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
GenerateBoundaryElements();
|
|
}
|
|
|
|
if (refine)
|
|
{
|
|
DSTable v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(v_to_v);
|
|
MarkTetMeshForRefinement(v_to_v);
|
|
}
|
|
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
|
|
CheckBdrElementOrientation();
|
|
|
|
if (generate_edges == 1)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
else
|
|
{
|
|
el_to_edge = NULL; // Not really necessary -- InitTables was called
|
|
bel_to_edge = NULL;
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
SetAttributes();
|
|
|
|
SetMeshGen();
|
|
}
|
|
|
|
void Mesh::FinalizeWedgeMesh(int generate_edges, int refine,
|
|
bool fix_orientation)
|
|
{
|
|
FinalizeCheck();
|
|
CheckElementOrientation(fix_orientation);
|
|
|
|
if (!HasBoundaryElements())
|
|
{
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
GenerateBoundaryElements();
|
|
}
|
|
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
|
|
CheckBdrElementOrientation();
|
|
|
|
if (generate_edges == 1)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
else
|
|
{
|
|
el_to_edge = NULL; // Not really necessary -- InitTables was called
|
|
bel_to_edge = NULL;
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
SetAttributes();
|
|
|
|
SetMeshGen();
|
|
}
|
|
|
|
void Mesh::FinalizeHexMesh(int generate_edges, int refine, bool fix_orientation)
|
|
{
|
|
FinalizeCheck();
|
|
CheckElementOrientation(fix_orientation);
|
|
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
|
|
if (!HasBoundaryElements())
|
|
{
|
|
GenerateBoundaryElements();
|
|
}
|
|
|
|
CheckBdrElementOrientation();
|
|
|
|
if (generate_edges)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
else
|
|
{
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
SetAttributes();
|
|
|
|
SetMeshGen();
|
|
}
|
|
|
|
void Mesh::FinalizeMesh(int refine, bool fix_orientation)
|
|
{
|
|
FinalizeTopology();
|
|
Finalize(refine, fix_orientation);
|
|
}
|
|
|
|
void Mesh::FinalizeTopology(bool generate_bdr)
|
|
{
|
|
// Requirements: the following should be defined:
|
|
// 1) Dim
|
|
// 2) NumOfElements, elements
|
|
// 3) NumOfBdrElements, boundary
|
|
// 4) NumOfVertices
|
|
// Optional:
|
|
// 2) ncmesh may be defined
|
|
// 3) el_to_edge may be allocated (it will be re-computed)
|
|
|
|
FinalizeCheck();
|
|
bool generate_edges = true;
|
|
|
|
if (spaceDim == 0) { spaceDim = Dim; }
|
|
if (ncmesh) { ncmesh->spaceDim = spaceDim; }
|
|
|
|
// if the user defined any hanging nodes (see AddVertexParent),
|
|
// we're initializing a non-conforming mesh
|
|
if (tmp_vertex_parents.Size())
|
|
{
|
|
MFEM_VERIFY(ncmesh == NULL, "");
|
|
ncmesh = new NCMesh(this);
|
|
|
|
// we need to recreate the Mesh because NCMesh reorders the vertices
|
|
// (see NCMesh::UpdateVertices())
|
|
InitFromNCMesh(*ncmesh);
|
|
ncmesh->OnMeshUpdated(this);
|
|
GenerateNCFaceInfo();
|
|
|
|
SetAttributes();
|
|
|
|
tmp_vertex_parents.DeleteAll();
|
|
return;
|
|
}
|
|
|
|
// set the mesh type: 'meshgen', ...
|
|
SetMeshGen();
|
|
|
|
// generate the faces
|
|
if (Dim > 2)
|
|
{
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
if (!HasBoundaryElements() && generate_bdr)
|
|
{
|
|
GenerateBoundaryElements();
|
|
GetElementToFaceTable(); // update be_to_face
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumOfFaces = 0;
|
|
}
|
|
|
|
// generate edges if requested
|
|
if (Dim > 1 && generate_edges)
|
|
{
|
|
// el_to_edge may already be allocated (P2 VTK meshes)
|
|
if (!el_to_edge) { el_to_edge = new Table; }
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
if (Dim == 2)
|
|
{
|
|
GenerateFaces(); // 'Faces' in 2D refers to the edges
|
|
if (!HasBoundaryElements() && generate_bdr)
|
|
{
|
|
GenerateBoundaryElements();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
if (Dim == 1)
|
|
{
|
|
GenerateFaces();
|
|
if (!HasBoundaryElements() && generate_bdr)
|
|
{
|
|
// be_to_face will be set inside GenerateBoundaryElements
|
|
GenerateBoundaryElements();
|
|
}
|
|
else
|
|
{
|
|
be_to_face.SetSize(NumOfBdrElements);
|
|
for (int i = 0; i < NumOfBdrElements; ++i)
|
|
{
|
|
be_to_face[i] = boundary[i]->GetVertices()[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ncmesh)
|
|
{
|
|
// tell NCMesh the numbering of edges/faces
|
|
ncmesh->OnMeshUpdated(this);
|
|
|
|
// update faces_info with NC relations
|
|
GenerateNCFaceInfo();
|
|
}
|
|
|
|
// generate the arrays 'attributes' and 'bdr_attributes'
|
|
SetAttributes();
|
|
}
|
|
|
|
void Mesh::Finalize(bool refine, bool fix_orientation)
|
|
{
|
|
if (NURBSext || ncmesh)
|
|
{
|
|
MFEM_ASSERT(CheckElementOrientation(false) == 0, "");
|
|
MFEM_ASSERT(CheckBdrElementOrientation() == 0, "");
|
|
return;
|
|
}
|
|
|
|
// Requirements:
|
|
// 1) FinalizeTopology() or equivalent was called
|
|
// 2) if (Nodes == NULL), vertices must be defined
|
|
// 3) if (Nodes != NULL), Nodes must be defined
|
|
|
|
const bool check_orientation = true; // for regular elements, not boundary
|
|
const bool curved = (Nodes != NULL);
|
|
const bool may_change_topology =
|
|
( refine && (Dim > 1 && (meshgen & 1)) ) ||
|
|
( check_orientation && fix_orientation &&
|
|
(Dim == 2 || (Dim == 3 && (meshgen & 1))) );
|
|
|
|
DSTable *old_v_to_v = NULL;
|
|
Table *old_elem_vert = NULL;
|
|
|
|
if (curved && may_change_topology)
|
|
{
|
|
PrepareNodeReorder(&old_v_to_v, &old_elem_vert);
|
|
}
|
|
|
|
if (check_orientation)
|
|
{
|
|
// check and optionally fix element orientation
|
|
CheckElementOrientation(fix_orientation);
|
|
}
|
|
if (refine)
|
|
{
|
|
MarkForRefinement(); // may change topology!
|
|
}
|
|
|
|
if (may_change_topology)
|
|
{
|
|
if (curved)
|
|
{
|
|
DoNodeReorder(old_v_to_v, old_elem_vert); // updates the mesh topology
|
|
delete old_elem_vert;
|
|
delete old_v_to_v;
|
|
}
|
|
else
|
|
{
|
|
FinalizeTopology(); // Re-computes some data unnecessarily.
|
|
}
|
|
|
|
// TODO: maybe introduce Mesh::NODE_REORDER operation and FESpace::
|
|
// NodeReorderMatrix and do Nodes->Update() instead of DoNodeReorder?
|
|
}
|
|
|
|
// check and fix boundary element orientation
|
|
CheckBdrElementOrientation();
|
|
|
|
#ifdef MFEM_DEBUG
|
|
// For non-orientable surfaces/manifolds, the check below will fail, so we
|
|
// only perform it when Dim == spaceDim.
|
|
if (Dim >= 2 && Dim == spaceDim)
|
|
{
|
|
const int num_faces = GetNumFaces();
|
|
for (int i = 0; i < num_faces; i++)
|
|
{
|
|
MFEM_VERIFY(faces_info[i].Elem2No < 0 ||
|
|
faces_info[i].Elem2Inf%2 != 0, "Invalid mesh topology."
|
|
" Interior face with incompatible orientations.");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Mesh::Make3D(int nx, int ny, int nz, Element::Type type,
|
|
real_t sx, real_t sy, real_t sz, bool sfc_ordering)
|
|
{
|
|
int x, y, z;
|
|
|
|
int NVert, NElem, NBdrElem;
|
|
|
|
NVert = (nx+1) * (ny+1) * (nz+1);
|
|
NElem = nx * ny * nz;
|
|
NBdrElem = 2*(nx*ny+nx*nz+ny*nz);
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
NElem *= 6;
|
|
NBdrElem *= 2;
|
|
}
|
|
else if (type == Element::WEDGE)
|
|
{
|
|
NElem *= 2;
|
|
NBdrElem += 2*nx*ny;
|
|
}
|
|
else if (type == Element::PYRAMID)
|
|
{
|
|
NElem *= 6;
|
|
NVert += nx * ny * nz;
|
|
}
|
|
|
|
InitMesh(3, 3, NVert, NElem, NBdrElem);
|
|
|
|
real_t coord[3];
|
|
int ind[9];
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
for (z = 0; z <= nz; z++)
|
|
{
|
|
coord[2] = ((real_t) z / nz) * sz;
|
|
for (y = 0; y <= ny; y++)
|
|
{
|
|
coord[1] = ((real_t) y / ny) * sy;
|
|
for (x = 0; x <= nx; x++)
|
|
{
|
|
coord[0] = ((real_t) x / nx) * sx;
|
|
AddVertex(coord);
|
|
}
|
|
}
|
|
}
|
|
if (type == Element::PYRAMID)
|
|
{
|
|
for (z = 0; z < nz; z++)
|
|
{
|
|
coord[2] = (((real_t) z + 0.5) / nz) * sz;
|
|
for (y = 0; y < ny; y++)
|
|
{
|
|
coord[1] = (((real_t) y + 0.5) / ny) * sy;
|
|
for (x = 0; x < nx; x++)
|
|
{
|
|
coord[0] = (((real_t) x + 0.5) / nx) * sx;
|
|
AddVertex(coord);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#define VTX(XC, YC, ZC) ((XC)+((YC)+(ZC)*(ny+1))*(nx+1))
|
|
#define VTXP(XC, YC, ZC) ((nx+1)*(ny+1)*(nz+1)+(XC)+((YC)+(ZC)*ny)*nx)
|
|
|
|
// Sets elements and the corresponding indices of vertices
|
|
if (sfc_ordering && type == Element::HEXAHEDRON)
|
|
{
|
|
Array<int> sfc;
|
|
NCMesh::GridSfcOrdering3D(nx, ny, nz, sfc);
|
|
MFEM_VERIFY(sfc.Size() == 3*nx*ny*nz, "");
|
|
|
|
for (int k = 0; k < nx*ny*nz; k++)
|
|
{
|
|
x = sfc[3*k + 0];
|
|
y = sfc[3*k + 1];
|
|
z = sfc[3*k + 2];
|
|
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(x , y , z );
|
|
ind[1] = VTX(x+1, y , z );
|
|
ind[2] = VTX(x+1, y+1, z );
|
|
ind[3] = VTX(x , y+1, z );
|
|
ind[4] = VTX(x , y , z+1);
|
|
ind[5] = VTX(x+1, y , z+1);
|
|
ind[6] = VTX(x+1, y+1, z+1);
|
|
ind[7] = VTX(x , y+1, z+1);
|
|
// *INDENT-ON*
|
|
|
|
AddHex(ind, 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (z = 0; z < nz; z++)
|
|
{
|
|
for (y = 0; y < ny; y++)
|
|
{
|
|
for (x = 0; x < nx; x++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(x , y , z );
|
|
ind[1] = VTX(x+1, y , z );
|
|
ind[2] = VTX(x+1, y+1, z );
|
|
ind[3] = VTX(x , y+1, z );
|
|
ind[4] = VTX(x , y , z+1);
|
|
ind[5] = VTX(x+1, y , z+1);
|
|
ind[6] = VTX(x+1, y+1, z+1);
|
|
ind[7] = VTX( x, y+1, z+1);
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddHexAsTets(ind, 1);
|
|
}
|
|
else if (type == Element::WEDGE)
|
|
{
|
|
AddHexAsWedges(ind, 1);
|
|
}
|
|
else if (type == Element::PYRAMID)
|
|
{
|
|
ind[8] = VTXP(x, y, z);
|
|
AddHexAsPyramids(ind, 1);
|
|
}
|
|
else
|
|
{
|
|
AddHex(ind, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sets boundary elements and the corresponding indices of vertices
|
|
// bottom, bdr. attribute 1
|
|
for (y = 0; y < ny; y++)
|
|
{
|
|
for (x = 0; x < nx; x++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(x , y , 0);
|
|
ind[1] = VTX(x , y+1, 0);
|
|
ind[2] = VTX(x+1, y+1, 0);
|
|
ind[3] = VTX(x+1, y , 0);
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 1);
|
|
}
|
|
else if (type == Element::WEDGE)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 1);
|
|
}
|
|
else
|
|
{
|
|
AddBdrQuad(ind, 1);
|
|
}
|
|
}
|
|
}
|
|
// top, bdr. attribute 6
|
|
for (y = 0; y < ny; y++)
|
|
{
|
|
for (x = 0; x < nx; x++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(x , y , nz);
|
|
ind[1] = VTX(x+1, y , nz);
|
|
ind[2] = VTX(x+1, y+1, nz);
|
|
ind[3] = VTX(x , y+1, nz);
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 6);
|
|
}
|
|
else if (type == Element::WEDGE)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 6);
|
|
}
|
|
else
|
|
{
|
|
AddBdrQuad(ind, 6);
|
|
}
|
|
}
|
|
}
|
|
// left, bdr. attribute 5
|
|
for (z = 0; z < nz; z++)
|
|
{
|
|
for (y = 0; y < ny; y++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(0 , y , z );
|
|
ind[1] = VTX(0 , y , z+1);
|
|
ind[2] = VTX(0 , y+1, z+1);
|
|
ind[3] = VTX(0 , y+1, z );
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 5);
|
|
}
|
|
else
|
|
{
|
|
AddBdrQuad(ind, 5);
|
|
}
|
|
}
|
|
}
|
|
// right, bdr. attribute 3
|
|
for (z = 0; z < nz; z++)
|
|
{
|
|
for (y = 0; y < ny; y++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(nx, y , z );
|
|
ind[1] = VTX(nx, y+1, z );
|
|
ind[2] = VTX(nx, y+1, z+1);
|
|
ind[3] = VTX(nx, y , z+1);
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 3);
|
|
}
|
|
else
|
|
{
|
|
AddBdrQuad(ind, 3);
|
|
}
|
|
}
|
|
}
|
|
// front, bdr. attribute 2
|
|
for (x = 0; x < nx; x++)
|
|
{
|
|
for (z = 0; z < nz; z++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(x , 0, z );
|
|
ind[1] = VTX(x+1, 0, z );
|
|
ind[2] = VTX(x+1, 0, z+1);
|
|
ind[3] = VTX(x , 0, z+1);
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 2);
|
|
}
|
|
else
|
|
{
|
|
AddBdrQuad(ind, 2);
|
|
}
|
|
}
|
|
}
|
|
// back, bdr. attribute 4
|
|
for (x = 0; x < nx; x++)
|
|
{
|
|
for (z = 0; z < nz; z++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VTX(x , ny, z );
|
|
ind[1] = VTX(x , ny, z+1);
|
|
ind[2] = VTX(x+1, ny, z+1);
|
|
ind[3] = VTX(x+1, ny, z );
|
|
// *INDENT-ON*
|
|
if (type == Element::TETRAHEDRON)
|
|
{
|
|
AddBdrQuadAsTriangles(ind, 4);
|
|
}
|
|
else
|
|
{
|
|
AddBdrQuad(ind, 4);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef VTX
|
|
#undef VTXP
|
|
|
|
#if 0
|
|
ofstream test_stream("debug.mesh");
|
|
Print(test_stream);
|
|
test_stream.close();
|
|
#endif
|
|
|
|
FinalizeTopology();
|
|
|
|
// Finalize(...) can be called after this method, if needed
|
|
}
|
|
|
|
|
|
void Mesh::Make2D4TrisFromQuad(int nx, int ny, real_t sx, real_t sy)
|
|
{
|
|
SetEmpty();
|
|
|
|
Dim = 2;
|
|
spaceDim = 2;
|
|
|
|
NumOfVertices = (nx+1) * (ny+1);
|
|
NumOfElements = nx * ny * 4;
|
|
NumOfBdrElements = (2 * nx + 2 * ny);
|
|
vertices.SetSize(NumOfVertices);
|
|
elements.SetSize(NumOfElements);
|
|
boundary.SetSize(NumOfBdrElements);
|
|
NumOfElements = 0;
|
|
|
|
int ind[4];
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
int k = 0;
|
|
for (real_t j = 0; j < ny+1; j++)
|
|
{
|
|
real_t cy = (j / ny) * sy;
|
|
for (real_t i = 0; i < nx+1; i++)
|
|
{
|
|
real_t cx = (i / nx) * sx;
|
|
vertices[k](0) = cx;
|
|
vertices[k](1) = cy;
|
|
k++;
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < ny; y++)
|
|
{
|
|
for (int x = 0; x < nx; x++)
|
|
{
|
|
ind[0] = x + y*(nx+1);
|
|
ind[1] = x + 1 +y*(nx+1);
|
|
ind[2] = x + 1 + (y+1)*(nx+1);
|
|
ind[3] = x + (y+1)*(nx+1);
|
|
AddQuadAs4TrisWithPoints(ind, 1);
|
|
}
|
|
}
|
|
|
|
int m = (nx+1)*ny;
|
|
for (int i = 0; i < nx; i++)
|
|
{
|
|
boundary[i] = new Segment(i, i+1, 1);
|
|
boundary[nx+i] = new Segment(m+i+1, m+i, 3);
|
|
}
|
|
m = nx+1;
|
|
for (int j = 0; j < ny; j++)
|
|
{
|
|
boundary[2*nx+j] = new Segment((j+1)*m, j*m, 4);
|
|
boundary[2*nx+ny+j] = new Segment(j*m+nx, (j+1)*m+nx, 2);
|
|
}
|
|
|
|
SetMeshGen();
|
|
CheckElementOrientation(true);
|
|
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
CheckBdrElementOrientation();
|
|
|
|
NumOfFaces = 0;
|
|
|
|
attributes.Append(1);
|
|
bdr_attributes.Append(1); bdr_attributes.Append(2);
|
|
bdr_attributes.Append(3); bdr_attributes.Append(4);
|
|
|
|
FinalizeTopology();
|
|
}
|
|
|
|
void Mesh::Make2D5QuadsFromQuad(int nx, int ny,
|
|
real_t sx, real_t sy)
|
|
{
|
|
SetEmpty();
|
|
|
|
Dim = 2;
|
|
spaceDim = 2;
|
|
|
|
NumOfElements = nx * ny * 5;
|
|
NumOfVertices = (nx+1) * (ny+1); //it will be enlarged later on
|
|
NumOfBdrElements = (2 * nx + 2 * ny);
|
|
vertices.SetSize(NumOfVertices);
|
|
elements.SetSize(NumOfElements);
|
|
boundary.SetSize(NumOfBdrElements);
|
|
NumOfElements = 0;
|
|
|
|
int ind[4];
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
int k = 0;
|
|
for (real_t j = 0; j < ny+1; j++)
|
|
{
|
|
real_t cy = (j / ny) * sy;
|
|
for (real_t i = 0; i < nx+1; i++)
|
|
{
|
|
real_t cx = (i / nx) * sx;
|
|
vertices[k](0) = cx;
|
|
vertices[k](1) = cy;
|
|
k++;
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < ny; y++)
|
|
{
|
|
for (int x = 0; x < nx; x++)
|
|
{
|
|
ind[0] = x + y*(nx+1);
|
|
ind[1] = x + 1 +y*(nx+1);
|
|
ind[2] = x + 1 + (y+1)*(nx+1);
|
|
ind[3] = x + (y+1)*(nx+1);
|
|
AddQuadAs5QuadsWithPoints(ind, 1);
|
|
}
|
|
}
|
|
|
|
int m = (nx+1)*ny;
|
|
for (int i = 0; i < nx; i++)
|
|
{
|
|
boundary[i] = new Segment(i, i+1, 1);
|
|
boundary[nx+i] = new Segment(m+i+1, m+i, 3);
|
|
}
|
|
m = nx+1;
|
|
for (int j = 0; j < ny; j++)
|
|
{
|
|
boundary[2*nx+j] = new Segment((j+1)*m, j*m, 4);
|
|
boundary[2*nx+ny+j] = new Segment(j*m+nx, (j+1)*m+nx, 2);
|
|
}
|
|
|
|
SetMeshGen();
|
|
CheckElementOrientation(true);
|
|
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
CheckBdrElementOrientation();
|
|
|
|
NumOfFaces = 0;
|
|
|
|
attributes.Append(1);
|
|
bdr_attributes.Append(1); bdr_attributes.Append(2);
|
|
bdr_attributes.Append(3); bdr_attributes.Append(4);
|
|
|
|
FinalizeTopology();
|
|
}
|
|
|
|
void Mesh::Make3D24TetsFromHex(int nx, int ny, int nz,
|
|
real_t sx, real_t sy, real_t sz)
|
|
{
|
|
const int NVert = (nx+1) * (ny+1) * (nz+1);
|
|
const int NElem = nx * ny * nz * 24;
|
|
const int NBdrElem = 2*(nx*ny+nx*nz+ny*nz)*4;
|
|
|
|
InitMesh(3, 3, NVert, NElem, NBdrElem);
|
|
|
|
real_t coord[3];
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
for (real_t z = 0; z <= nz; z++)
|
|
{
|
|
coord[2] = ( z / nz) * sz;
|
|
for (real_t y = 0; y <= ny; y++)
|
|
{
|
|
coord[1] = (y / ny) * sy;
|
|
for (real_t x = 0; x <= nx; x++)
|
|
{
|
|
coord[0] = (x / nx) * sx;
|
|
AddVertex(coord);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<std::array<int, 4>, int> hex_face_verts;
|
|
auto VertexIndex = [nx, ny](int xc, int yc, int zc)
|
|
{
|
|
return xc + (yc + zc*(ny+1))*(nx+1);
|
|
};
|
|
|
|
int ind[9];
|
|
for (int z = 0; z < nz; z++)
|
|
{
|
|
for (int y = 0; y < ny; y++)
|
|
{
|
|
for (int x = 0; x < nx; x++)
|
|
{
|
|
// *INDENT-OFF*
|
|
ind[0] = VertexIndex(x , y , z );
|
|
ind[1] = VertexIndex(x+1, y , z );
|
|
ind[2] = VertexIndex(x+1, y+1, z );
|
|
ind[3] = VertexIndex(x , y+1, z );
|
|
ind[4] = VertexIndex(x , y , z+1);
|
|
ind[5] = VertexIndex(x+1, y , z+1);
|
|
ind[6] = VertexIndex(x+1, y+1, z+1);
|
|
ind[7] = VertexIndex( x, y+1, z+1);
|
|
// *INDENT-ON*
|
|
|
|
AddHexAs24TetsWithPoints(ind, hex_face_verts, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
hex_face_verts.clear();
|
|
CheckElementOrientation(true);
|
|
|
|
// Done adding Tets
|
|
// Now figure out elements that are on the boundary
|
|
GetElementToFaceTable(false);
|
|
GenerateFaces();
|
|
|
|
// Map to count number of tets sharing a face
|
|
std::map<std::array<int, 3>, int> tet_face_count;
|
|
// Map from tet face defined by three vertices to the local face number
|
|
std::map<std::array<int, 3>, int> face_count_map;
|
|
|
|
auto get3array = [](Array<int> v)
|
|
{
|
|
v.Sort();
|
|
return std::array<int, 3> {v[0], v[1], v[2]};
|
|
};
|
|
|
|
Array<int> el_faces;
|
|
Array<int> ori;
|
|
Array<int> vertidxs;
|
|
for (int i = 0; i < el_to_face->Size(); i++)
|
|
{
|
|
el_to_face->GetRow(i, el_faces);
|
|
for (int j = 0; j < el_faces.Size(); j++)
|
|
{
|
|
GetFaceVertices(el_faces[j], vertidxs);
|
|
auto t = get3array(vertidxs);
|
|
auto it = tet_face_count.find(t);
|
|
if (it == tet_face_count.end()) //edge does not already exist
|
|
{
|
|
tet_face_count.insert({t, 1});
|
|
face_count_map.insert({t, el_faces[j]});
|
|
}
|
|
else
|
|
{
|
|
it->second++; // increase edge count value by 1.
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto &edge : tet_face_count)
|
|
{
|
|
if (edge.second == 1) //if this only appears once, it is a boundary edge
|
|
{
|
|
int facenum = (face_count_map.find(edge.first))->second;
|
|
GetFaceVertices(facenum, vertidxs);
|
|
AddBdrTriangle(vertidxs, 1);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
ofstream test_stream("debug.mesh");
|
|
Print(test_stream);
|
|
test_stream.close();
|
|
#endif
|
|
|
|
FinalizeTopology();
|
|
// Finalize(...) can be called after this method, if needed
|
|
}
|
|
|
|
void Mesh::Make2D(int nx, int ny, Element::Type type,
|
|
real_t sx, real_t sy,
|
|
bool generate_edges, bool sfc_ordering)
|
|
{
|
|
int i, j, k;
|
|
|
|
SetEmpty();
|
|
|
|
Dim = spaceDim = 2;
|
|
|
|
// Creates quadrilateral mesh
|
|
if (type == Element::QUADRILATERAL)
|
|
{
|
|
NumOfVertices = (nx+1) * (ny+1);
|
|
NumOfElements = nx * ny;
|
|
NumOfBdrElements = 2 * nx + 2 * ny;
|
|
|
|
vertices.SetSize(NumOfVertices);
|
|
elements.SetSize(NumOfElements);
|
|
boundary.SetSize(NumOfBdrElements);
|
|
|
|
real_t cx, cy;
|
|
int ind[4];
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
k = 0;
|
|
for (j = 0; j < ny+1; j++)
|
|
{
|
|
cy = ((real_t) j / ny) * sy;
|
|
for (i = 0; i < nx+1; i++)
|
|
{
|
|
cx = ((real_t) i / nx) * sx;
|
|
vertices[k](0) = cx;
|
|
vertices[k](1) = cy;
|
|
k++;
|
|
}
|
|
}
|
|
|
|
// Sets elements and the corresponding indices of vertices
|
|
if (sfc_ordering)
|
|
{
|
|
Array<int> sfc;
|
|
NCMesh::GridSfcOrdering2D(nx, ny, sfc);
|
|
MFEM_VERIFY(sfc.Size() == 2*nx*ny, "");
|
|
|
|
for (k = 0; k < nx*ny; k++)
|
|
{
|
|
i = sfc[2*k + 0];
|
|
j = sfc[2*k + 1];
|
|
ind[0] = i + j*(nx+1);
|
|
ind[1] = i + 1 +j*(nx+1);
|
|
ind[2] = i + 1 + (j+1)*(nx+1);
|
|
ind[3] = i + (j+1)*(nx+1);
|
|
elements[k] = new Quadrilateral(ind);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
k = 0;
|
|
for (j = 0; j < ny; j++)
|
|
{
|
|
for (i = 0; i < nx; i++)
|
|
{
|
|
ind[0] = i + j*(nx+1);
|
|
ind[1] = i + 1 +j*(nx+1);
|
|
ind[2] = i + 1 + (j+1)*(nx+1);
|
|
ind[3] = i + (j+1)*(nx+1);
|
|
elements[k] = new Quadrilateral(ind);
|
|
k++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sets boundary elements and the corresponding indices of vertices
|
|
int m = (nx+1)*ny;
|
|
for (i = 0; i < nx; i++)
|
|
{
|
|
boundary[i] = new Segment(i, i+1, 1);
|
|
boundary[nx+i] = new Segment(m+i+1, m+i, 3);
|
|
}
|
|
m = nx+1;
|
|
for (j = 0; j < ny; j++)
|
|
{
|
|
boundary[2*nx+j] = new Segment((j+1)*m, j*m, 4);
|
|
boundary[2*nx+ny+j] = new Segment(j*m+nx, (j+1)*m+nx, 2);
|
|
}
|
|
}
|
|
// Creates triangular mesh
|
|
else if (type == Element::TRIANGLE)
|
|
{
|
|
NumOfVertices = (nx+1) * (ny+1);
|
|
NumOfElements = 2 * nx * ny;
|
|
NumOfBdrElements = 2 * nx + 2 * ny;
|
|
|
|
vertices.SetSize(NumOfVertices);
|
|
elements.SetSize(NumOfElements);
|
|
boundary.SetSize(NumOfBdrElements);
|
|
|
|
real_t cx, cy;
|
|
int ind[3];
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
k = 0;
|
|
for (j = 0; j < ny+1; j++)
|
|
{
|
|
cy = ((real_t) j / ny) * sy;
|
|
for (i = 0; i < nx+1; i++)
|
|
{
|
|
cx = ((real_t) i / nx) * sx;
|
|
vertices[k](0) = cx;
|
|
vertices[k](1) = cy;
|
|
k++;
|
|
}
|
|
}
|
|
|
|
// Sets the elements and the corresponding indices of vertices
|
|
k = 0;
|
|
for (j = 0; j < ny; j++)
|
|
{
|
|
for (i = 0; i < nx; i++)
|
|
{
|
|
ind[0] = i + j*(nx+1);
|
|
ind[1] = i + 1 + (j+1)*(nx+1);
|
|
ind[2] = i + (j+1)*(nx+1);
|
|
elements[k] = new Triangle(ind);
|
|
k++;
|
|
ind[1] = i + 1 + j*(nx+1);
|
|
ind[2] = i + 1 + (j+1)*(nx+1);
|
|
elements[k] = new Triangle(ind);
|
|
k++;
|
|
}
|
|
}
|
|
|
|
// Sets boundary elements and the corresponding indices of vertices
|
|
int m = (nx+1)*ny;
|
|
for (i = 0; i < nx; i++)
|
|
{
|
|
boundary[i] = new Segment(i, i+1, 1);
|
|
boundary[nx+i] = new Segment(m+i+1, m+i, 3);
|
|
}
|
|
m = nx+1;
|
|
for (j = 0; j < ny; j++)
|
|
{
|
|
boundary[2*nx+j] = new Segment((j+1)*m, j*m, 4);
|
|
boundary[2*nx+ny+j] = new Segment(j*m+nx, (j+1)*m+nx, 2);
|
|
}
|
|
|
|
// MarkTriMeshForRefinement(); // done in Finalize(...)
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Unsupported element type.");
|
|
}
|
|
|
|
SetMeshGen();
|
|
CheckElementOrientation();
|
|
|
|
if (generate_edges == 1)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
CheckBdrElementOrientation();
|
|
}
|
|
else
|
|
{
|
|
NumOfEdges = 0;
|
|
}
|
|
|
|
NumOfFaces = 0;
|
|
|
|
attributes.Append(1);
|
|
bdr_attributes.Append(1); bdr_attributes.Append(2);
|
|
bdr_attributes.Append(3); bdr_attributes.Append(4);
|
|
|
|
// Finalize(...) can be called after this method, if needed
|
|
}
|
|
|
|
void Mesh::Make1D(int n, real_t sx)
|
|
{
|
|
int j, ind[1];
|
|
|
|
SetEmpty();
|
|
|
|
Dim = 1;
|
|
spaceDim = 1;
|
|
|
|
NumOfVertices = n + 1;
|
|
NumOfElements = n;
|
|
NumOfBdrElements = 2;
|
|
vertices.SetSize(NumOfVertices);
|
|
elements.SetSize(NumOfElements);
|
|
boundary.SetSize(NumOfBdrElements);
|
|
|
|
// Sets vertices and the corresponding coordinates
|
|
for (j = 0; j < n+1; j++)
|
|
{
|
|
vertices[j](0) = ((real_t) j / n) * sx;
|
|
}
|
|
|
|
// Sets elements and the corresponding indices of vertices
|
|
for (j = 0; j < n; j++)
|
|
{
|
|
elements[j] = new Segment(j, j+1, 1);
|
|
}
|
|
|
|
// Sets the boundary elements
|
|
ind[0] = 0;
|
|
boundary[0] = new Point(ind, 1);
|
|
ind[0] = n;
|
|
boundary[1] = new Point(ind, 2);
|
|
|
|
NumOfEdges = 0;
|
|
NumOfFaces = 0;
|
|
|
|
SetMeshGen();
|
|
GenerateFaces();
|
|
|
|
// Set be_to_face
|
|
be_to_face.SetSize(2);
|
|
be_to_face[0] = 0;
|
|
be_to_face[1] = n;
|
|
|
|
attributes.Append(1);
|
|
bdr_attributes.Append(1); bdr_attributes.Append(2);
|
|
}
|
|
|
|
Mesh::Mesh(const Mesh &mesh, bool copy_nodes)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
Dim = mesh.Dim;
|
|
spaceDim = mesh.spaceDim;
|
|
|
|
NumOfVertices = mesh.NumOfVertices;
|
|
NumOfElements = mesh.NumOfElements;
|
|
NumOfBdrElements = mesh.NumOfBdrElements;
|
|
NumOfEdges = mesh.NumOfEdges;
|
|
NumOfFaces = mesh.NumOfFaces;
|
|
nbInteriorFaces = mesh.nbInteriorFaces;
|
|
nbBoundaryFaces = mesh.nbBoundaryFaces;
|
|
|
|
meshgen = mesh.meshgen;
|
|
mesh_geoms = mesh.mesh_geoms;
|
|
|
|
// Create the new Mesh instance without a record of its refinement history
|
|
sequence = 0;
|
|
nodes_sequence = 0;
|
|
last_operation = Mesh::NONE;
|
|
|
|
// Duplicate the elements
|
|
elements.SetSize(NumOfElements);
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
elements[i] = mesh.elements[i]->Duplicate(this);
|
|
}
|
|
|
|
// Copy the vertices
|
|
mesh.vertices.Copy(vertices);
|
|
|
|
// Duplicate the boundary
|
|
boundary.SetSize(NumOfBdrElements);
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
boundary[i] = mesh.boundary[i]->Duplicate(this);
|
|
}
|
|
|
|
// Copy the element-to-face Table, el_to_face
|
|
el_to_face = (mesh.el_to_face) ? new Table(*mesh.el_to_face) : NULL;
|
|
|
|
// Copy the boundary-to-face Array, be_to_face.
|
|
mesh.be_to_face.Copy(be_to_face);
|
|
|
|
// Copy the element-to-edge Table, el_to_edge
|
|
el_to_edge = (mesh.el_to_edge) ? new Table(*mesh.el_to_edge) : NULL;
|
|
|
|
// Copy the boundary-to-edge Table, bel_to_edge (3D)
|
|
bel_to_edge = (mesh.bel_to_edge) ? new Table(*mesh.bel_to_edge) : NULL;
|
|
|
|
// Duplicate the faces and faces_info.
|
|
faces.SetSize(mesh.faces.Size());
|
|
for (int i = 0; i < faces.Size(); i++)
|
|
{
|
|
Element *face = mesh.faces[i]; // in 1D the faces are NULL
|
|
faces[i] = (face) ? face->Duplicate(this) : NULL;
|
|
}
|
|
mesh.faces_info.Copy(faces_info);
|
|
mesh.nc_faces_info.Copy(nc_faces_info);
|
|
|
|
// Do NOT copy the element-to-element Table, el_to_el
|
|
el_to_el = NULL;
|
|
|
|
// Do NOT copy the face-to-edge Table, face_edge
|
|
face_edge = NULL;
|
|
face_to_elem = NULL;
|
|
|
|
// Copy the edge-to-vertex Table, edge_vertex
|
|
edge_vertex = (mesh.edge_vertex) ? new Table(*mesh.edge_vertex) : NULL;
|
|
|
|
// Copy the attributes and bdr_attributes
|
|
mesh.attributes.Copy(attributes);
|
|
mesh.bdr_attributes.Copy(bdr_attributes);
|
|
|
|
// Copy attribute and bdr_attribute names
|
|
mesh.attribute_sets.Copy(attribute_sets);
|
|
mesh.bdr_attribute_sets.Copy(bdr_attribute_sets);
|
|
|
|
// Deep copy the NURBSExtension.
|
|
#ifdef MFEM_USE_MPI
|
|
ParNURBSExtension *pNURBSext =
|
|
dynamic_cast<ParNURBSExtension *>(mesh.NURBSext);
|
|
if (pNURBSext)
|
|
{
|
|
NURBSext = new ParNURBSExtension(*pNURBSext);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
NURBSext = mesh.NURBSext ? new NURBSExtension(*mesh.NURBSext) : NULL;
|
|
}
|
|
|
|
// Deep copy the NCMesh.
|
|
#ifdef MFEM_USE_MPI
|
|
if (dynamic_cast<const ParMesh*>(&mesh))
|
|
{
|
|
ncmesh = NULL; // skip; will be done in ParMesh copy ctor
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ncmesh = mesh.ncmesh ? new NCMesh(*mesh.ncmesh) : NULL;
|
|
}
|
|
|
|
// Duplicate the Nodes, including the FiniteElementCollection and the
|
|
// FiniteElementSpace
|
|
if (mesh.Nodes && copy_nodes)
|
|
{
|
|
FiniteElementSpace *fes = mesh.Nodes->FESpace();
|
|
const FiniteElementCollection *fec = fes->FEColl();
|
|
FiniteElementCollection *fec_copy =
|
|
FiniteElementCollection::New(fec->Name());
|
|
FiniteElementSpace *fes_copy =
|
|
new FiniteElementSpace(*fes, this, fec_copy);
|
|
Nodes = new GridFunction(fes_copy);
|
|
Nodes->MakeOwner(fec_copy);
|
|
*Nodes = *mesh.Nodes;
|
|
own_nodes = 1;
|
|
}
|
|
else
|
|
{
|
|
Nodes = mesh.Nodes;
|
|
own_nodes = 0;
|
|
}
|
|
|
|
// copy attribute caches
|
|
elem_attrs_cache = mesh.elem_attrs_cache;
|
|
bdr_face_attrs_cache = mesh.bdr_face_attrs_cache;
|
|
}
|
|
|
|
Mesh::Mesh(Mesh &&mesh) : Mesh()
|
|
{
|
|
Swap(mesh, true);
|
|
}
|
|
|
|
Mesh& Mesh::operator=(Mesh &&mesh)
|
|
{
|
|
Swap(mesh, true);
|
|
return *this;
|
|
}
|
|
|
|
Mesh Mesh::LoadFromFile(const std::string &filename, int generate_edges,
|
|
int refine, bool fix_orientation)
|
|
{
|
|
Mesh mesh;
|
|
named_ifgzstream imesh(filename);
|
|
if (!imesh) { MFEM_ABORT("Mesh file not found: " << filename << '\n'); }
|
|
else { mesh.Load(imesh, generate_edges, refine, fix_orientation); }
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeCartesian1D(int n, real_t sx)
|
|
{
|
|
Mesh mesh;
|
|
mesh.Make1D(n, sx);
|
|
// mesh.Finalize(); not needed in this case
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeCartesian2D(
|
|
int nx, int ny, Element::Type type, bool generate_edges,
|
|
real_t sx, real_t sy, bool sfc_ordering)
|
|
{
|
|
Mesh mesh;
|
|
mesh.Make2D(nx, ny, type, sx, sy, generate_edges, sfc_ordering);
|
|
mesh.Finalize(true); // refine = true
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeCartesian3D(
|
|
int nx, int ny, int nz, Element::Type type,
|
|
real_t sx, real_t sy, real_t sz, bool sfc_ordering)
|
|
{
|
|
Mesh mesh;
|
|
mesh.Make3D(nx, ny, nz, type, sx, sy, sz, sfc_ordering);
|
|
mesh.Finalize(true); // refine = true
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeCartesian3DWith24TetsPerHex(int nx, int ny, int nz,
|
|
real_t sx, real_t sy, real_t sz)
|
|
{
|
|
Mesh mesh;
|
|
mesh.Make3D24TetsFromHex(nx, ny, nz, sx, sy, sz);
|
|
mesh.Finalize(false, false);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeCartesian2DWith4TrisPerQuad(int nx, int ny,
|
|
real_t sx, real_t sy)
|
|
{
|
|
Mesh mesh;
|
|
mesh.Make2D4TrisFromQuad(nx, ny, sx, sy);
|
|
mesh.Finalize(false, false);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeCartesian2DWith5QuadsPerQuad(int nx, int ny,
|
|
real_t sx, real_t sy)
|
|
{
|
|
Mesh mesh;
|
|
mesh.Make2D5QuadsFromQuad(nx, ny, sx, sy);
|
|
mesh.Finalize(false, false);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeRefined(Mesh &orig_mesh, int ref_factor, int ref_type)
|
|
{
|
|
Mesh mesh;
|
|
Array<int> ref_factors(orig_mesh.GetNE());
|
|
ref_factors = ref_factor;
|
|
mesh.MakeRefined_(orig_mesh, ref_factors, ref_type);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh Mesh::MakeRefined(Mesh &orig_mesh, const Array<int> &ref_factors,
|
|
int ref_type)
|
|
{
|
|
Mesh mesh;
|
|
mesh.MakeRefined_(orig_mesh, ref_factors, ref_type);
|
|
return mesh;
|
|
}
|
|
|
|
Mesh::Mesh(const std::string &filename, int generate_edges, int refine,
|
|
bool fix_orientation)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
// Initialization as in the default constructor
|
|
SetEmpty();
|
|
|
|
named_ifgzstream imesh(filename);
|
|
if (!imesh)
|
|
{
|
|
// Abort with an error message.
|
|
MFEM_ABORT("Mesh file not found: " << filename << '\n');
|
|
}
|
|
else
|
|
{
|
|
Load(imesh, generate_edges, refine, fix_orientation);
|
|
}
|
|
}
|
|
|
|
Mesh::Mesh(std::istream &input, int generate_edges, int refine,
|
|
bool fix_orientation)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
SetEmpty();
|
|
Load(input, generate_edges, refine, fix_orientation);
|
|
}
|
|
|
|
void Mesh::ChangeVertexDataOwnership(real_t *vertex_data, int len_vertex_data,
|
|
bool zerocopy)
|
|
{
|
|
// A dimension of 3 is now required since we use mfem::Vertex objects as PODs
|
|
// and these object have a hardcoded double[3] entry
|
|
MFEM_VERIFY(len_vertex_data >= NumOfVertices * 3,
|
|
"Not enough vertices in external array : "
|
|
"len_vertex_data = "<< len_vertex_data << ", "
|
|
"NumOfVertices * 3 = " << NumOfVertices * 3);
|
|
// Allow multiple calls to this method with the same vertex_data
|
|
if (vertex_data == (real_t *)(vertices.GetData()))
|
|
{
|
|
MFEM_ASSERT(!vertices.OwnsData(), "invalid ownership");
|
|
return;
|
|
}
|
|
if (!zerocopy)
|
|
{
|
|
memcpy(vertex_data, vertices.GetData(),
|
|
NumOfVertices * 3 * sizeof(real_t));
|
|
}
|
|
// Vertex is POD double[3]
|
|
vertices.MakeRef(reinterpret_cast<Vertex*>(vertex_data), NumOfVertices);
|
|
}
|
|
|
|
Mesh::Mesh(real_t *vertices_, int num_vertices,
|
|
int *element_indices, Geometry::Type element_type,
|
|
int *element_attributes, int num_elements,
|
|
int *boundary_indices, Geometry::Type boundary_type,
|
|
int *boundary_attributes, int num_boundary_elements,
|
|
int dimension, int space_dimension)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
if (space_dimension == -1)
|
|
{
|
|
space_dimension = dimension;
|
|
}
|
|
|
|
InitMesh(dimension, space_dimension, /*num_vertices*/ 0, num_elements,
|
|
num_boundary_elements);
|
|
|
|
int element_index_stride = Geometry::NumVerts[element_type];
|
|
int boundary_index_stride = num_boundary_elements > 0 ?
|
|
Geometry::NumVerts[boundary_type] : 0;
|
|
|
|
// assuming Vertex is POD
|
|
vertices.MakeRef(reinterpret_cast<Vertex*>(vertices_), num_vertices);
|
|
NumOfVertices = num_vertices;
|
|
|
|
for (int i = 0; i < num_elements; i++)
|
|
{
|
|
elements[i] = NewElement(element_type);
|
|
elements[i]->SetVertices(element_indices + i * element_index_stride);
|
|
elements[i]->SetAttribute(element_attributes[i]);
|
|
}
|
|
NumOfElements = num_elements;
|
|
|
|
for (int i = 0; i < num_boundary_elements; i++)
|
|
{
|
|
boundary[i] = NewElement(boundary_type);
|
|
boundary[i]->SetVertices(boundary_indices + i * boundary_index_stride);
|
|
boundary[i]->SetAttribute(boundary_attributes[i]);
|
|
}
|
|
NumOfBdrElements = num_boundary_elements;
|
|
|
|
FinalizeTopology();
|
|
}
|
|
|
|
Mesh::Mesh(const NURBSExtension& ext)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
SetEmpty();
|
|
/// make an internal copy of the NURBSExtension
|
|
NURBSext = new NURBSExtension(ext);
|
|
|
|
Dim = NURBSext->Dimension();
|
|
NumOfVertices = NURBSext->GetNV();
|
|
NumOfElements = NURBSext->GetNE();
|
|
NumOfBdrElements = NURBSext->GetNBE();
|
|
|
|
NURBSext->GetElementTopo(elements);
|
|
NURBSext->GetBdrElementTopo(boundary);
|
|
|
|
vertices.SetSize(NumOfVertices);
|
|
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;
|
|
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
|
|
{
|
|
MFEM_ABORT("NURBS mesh has no patches.");
|
|
}
|
|
FinalizeMesh();
|
|
}
|
|
|
|
Element *Mesh::NewElement(int geom)
|
|
{
|
|
switch (geom)
|
|
{
|
|
case Geometry::POINT: return (new Point);
|
|
case Geometry::SEGMENT: return (new Segment);
|
|
case Geometry::TRIANGLE: return (new Triangle);
|
|
case Geometry::SQUARE: return (new Quadrilateral);
|
|
case Geometry::TETRAHEDRON:
|
|
#ifdef MFEM_USE_MEMALLOC
|
|
return TetMemory.Alloc();
|
|
#else
|
|
return (new Tetrahedron);
|
|
#endif
|
|
case Geometry::CUBE: return (new Hexahedron);
|
|
case Geometry::PRISM: return (new Wedge);
|
|
case Geometry::PYRAMID: return (new Pyramid);
|
|
default:
|
|
MFEM_ABORT("invalid Geometry::Type, geom = " << geom);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
Element *Mesh::ReadElementWithoutAttr(std::istream &input)
|
|
{
|
|
int geom, nv, *v;
|
|
Element *el;
|
|
|
|
input >> geom;
|
|
el = NewElement(geom);
|
|
MFEM_VERIFY(el, "Unsupported element type: " << geom);
|
|
nv = el->GetNVertices();
|
|
v = el->GetVertices();
|
|
for (int i = 0; i < nv; i++)
|
|
{
|
|
input >> v[i];
|
|
}
|
|
|
|
return el;
|
|
}
|
|
|
|
void Mesh::PrintElementWithoutAttr(const Element *el, std::ostream &os)
|
|
{
|
|
os << el->GetGeometryType();
|
|
const int nv = el->GetNVertices();
|
|
const int *v = el->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << v[j];
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
Element *Mesh::ReadElement(std::istream &input)
|
|
{
|
|
int attr;
|
|
Element *el;
|
|
|
|
input >> attr;
|
|
el = ReadElementWithoutAttr(input);
|
|
el->SetAttribute(attr);
|
|
|
|
return el;
|
|
}
|
|
|
|
void Mesh::PrintElement(const Element *el, std::ostream &os)
|
|
{
|
|
os << el->GetAttribute() << ' ';
|
|
PrintElementWithoutAttr(el, os);
|
|
}
|
|
|
|
void Mesh::SetMeshGen()
|
|
{
|
|
meshgen = mesh_geoms = 0;
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const Element::Type type = GetElement(i)->GetType();
|
|
switch (type)
|
|
{
|
|
case Element::TETRAHEDRON:
|
|
mesh_geoms |= (1 << Geometry::TETRAHEDRON);
|
|
case Element::TRIANGLE:
|
|
mesh_geoms |= (1 << Geometry::TRIANGLE);
|
|
case Element::SEGMENT:
|
|
mesh_geoms |= (1 << Geometry::SEGMENT);
|
|
case Element::POINT:
|
|
mesh_geoms |= (1 << Geometry::POINT);
|
|
meshgen |= 1;
|
|
break;
|
|
|
|
case Element::HEXAHEDRON:
|
|
mesh_geoms |= (1 << Geometry::CUBE);
|
|
case Element::QUADRILATERAL:
|
|
mesh_geoms |= (1 << Geometry::SQUARE);
|
|
mesh_geoms |= (1 << Geometry::SEGMENT);
|
|
mesh_geoms |= (1 << Geometry::POINT);
|
|
meshgen |= 2;
|
|
break;
|
|
|
|
case Element::WEDGE:
|
|
mesh_geoms |= (1 << Geometry::PRISM);
|
|
mesh_geoms |= (1 << Geometry::SQUARE);
|
|
mesh_geoms |= (1 << Geometry::TRIANGLE);
|
|
mesh_geoms |= (1 << Geometry::SEGMENT);
|
|
mesh_geoms |= (1 << Geometry::POINT);
|
|
meshgen |= 4;
|
|
break;
|
|
|
|
case Element::PYRAMID:
|
|
mesh_geoms |= (1 << Geometry::PYRAMID);
|
|
mesh_geoms |= (1 << Geometry::SQUARE);
|
|
mesh_geoms |= (1 << Geometry::TRIANGLE);
|
|
mesh_geoms |= (1 << Geometry::SEGMENT);
|
|
mesh_geoms |= (1 << Geometry::POINT);
|
|
meshgen |= 8;
|
|
break;
|
|
|
|
default:
|
|
MFEM_ABORT("invalid element type: " << type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::Loader(std::istream &input, int generate_edges,
|
|
std::string parse_tag)
|
|
{
|
|
int curved = 0, read_gf = 1;
|
|
bool finalize_topo = true;
|
|
|
|
if (!input)
|
|
{
|
|
MFEM_ABORT("Input stream is not open");
|
|
}
|
|
|
|
Clear();
|
|
|
|
string mesh_type;
|
|
input >> ws;
|
|
getline(input, mesh_type);
|
|
filter_dos(mesh_type);
|
|
|
|
// MFEM's conforming mesh formats
|
|
int mfem_version = 0;
|
|
if (mesh_type == "MFEM mesh v1.0") { mfem_version = 10; } // serial
|
|
else if (mesh_type == "MFEM mesh v1.2") { mfem_version = 12; } // parallel
|
|
else if (mesh_type == "MFEM mesh v1.3") { mfem_version = 13; } // attr sets
|
|
|
|
// MFEM nonconforming mesh format
|
|
// (NOTE: previous v1.1 is now under this branch for backward compatibility)
|
|
int mfem_nc_version = 0;
|
|
if (mesh_type == "MFEM NC mesh v1.0") { mfem_nc_version = 10; }
|
|
else if (mesh_type == "MFEM NC mesh v1.1") { mfem_nc_version = 11; }
|
|
else if (mesh_type == "MFEM mesh v1.1") { mfem_nc_version = 1 /*legacy*/; }
|
|
|
|
if (mfem_version)
|
|
{
|
|
// Formats mfem_v12 and newer have a tag indicating the end of the mesh
|
|
// section in the stream. A user provided parse tag can also be provided
|
|
// via the arguments. For example, if this is called from parallel mesh
|
|
// object, it can indicate to read until parallel mesh section begins.
|
|
if (mfem_version >= 12 && parse_tag.empty())
|
|
{
|
|
parse_tag = "mfem_mesh_end";
|
|
}
|
|
ReadMFEMMesh(input, mfem_version, curved);
|
|
}
|
|
else if (mfem_nc_version)
|
|
{
|
|
MFEM_ASSERT(ncmesh == NULL, "internal error");
|
|
int is_nc = 1;
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
ParMesh *pmesh = dynamic_cast<ParMesh*>(this);
|
|
if (pmesh)
|
|
{
|
|
MFEM_VERIFY(mfem_nc_version >= 10,
|
|
"Legacy nonconforming format (MFEM mesh v1.1) cannot be "
|
|
"used to load a parallel nonconforming mesh, sorry.");
|
|
|
|
ncmesh = new ParNCMesh(pmesh->GetComm(),
|
|
input, mfem_nc_version, curved, is_nc);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
ncmesh = new NCMesh(input, mfem_nc_version, curved, is_nc);
|
|
}
|
|
InitFromNCMesh(*ncmesh);
|
|
|
|
if (!is_nc)
|
|
{
|
|
// special case for backward compatibility with MFEM <=4.2:
|
|
// if the "vertex_parents" section is missing in the v1.1 format,
|
|
// the mesh is treated as conforming
|
|
delete ncmesh;
|
|
ncmesh = NULL;
|
|
}
|
|
}
|
|
else if (mesh_type == "linemesh") // 1D mesh
|
|
{
|
|
ReadLineMesh(input);
|
|
}
|
|
else if (mesh_type == "areamesh2" || mesh_type == "curved_areamesh2")
|
|
{
|
|
if (mesh_type == "curved_areamesh2")
|
|
{
|
|
curved = 1;
|
|
}
|
|
ReadNetgen2DMesh(input, curved);
|
|
}
|
|
else if (mesh_type == "NETGEN" || mesh_type == "NETGEN_Neutral_Format")
|
|
{
|
|
ReadNetgen3DMesh(input);
|
|
}
|
|
else if (mesh_type == "TrueGrid")
|
|
{
|
|
ReadTrueGridMesh(input);
|
|
}
|
|
else if (mesh_type.rfind("# vtk DataFile Version") == 0)
|
|
{
|
|
int major_vtk_version = mesh_type[mesh_type.length()-3] - '0';
|
|
// int minor_vtk_version = mesh_type[mesh_type.length()-1] - '0';
|
|
MFEM_VERIFY(major_vtk_version >= 2 && major_vtk_version <= 4,
|
|
"Unsupported VTK format");
|
|
ReadVTKMesh(input, curved, read_gf, finalize_topo);
|
|
}
|
|
else if (mesh_type.rfind("<VTKFile ") == 0 || mesh_type.rfind("<?xml") == 0)
|
|
{
|
|
ReadXML_VTKMesh(input, curved, read_gf, finalize_topo, mesh_type);
|
|
}
|
|
else if (mesh_type == "MFEM NURBS mesh v1.0")
|
|
{
|
|
ReadNURBSMesh(input, curved, read_gf);
|
|
}
|
|
else if (mesh_type == "MFEM NURBS NC-patch mesh v1.0")
|
|
{
|
|
ReadNURBSMesh(input, curved, read_gf, true, true); // Spacing is required
|
|
}
|
|
else if (mesh_type == "MFEM NURBS mesh v1.1")
|
|
{
|
|
ReadNURBSMesh(input, curved, read_gf, true);
|
|
}
|
|
else if (mesh_type == "MFEM INLINE mesh v1.0")
|
|
{
|
|
ReadInlineMesh(input, generate_edges);
|
|
return; // done with inline mesh construction
|
|
}
|
|
else if (mesh_type == "$MeshFormat") // Gmsh
|
|
{
|
|
ReadGmshMesh(input, curved, read_gf);
|
|
}
|
|
else if
|
|
((mesh_type.size() > 2 &&
|
|
mesh_type[0] == 'C' && mesh_type[1] == 'D' && mesh_type[2] == 'F') ||
|
|
(mesh_type.size() > 3 &&
|
|
mesh_type[1] == 'H' && mesh_type[2] == 'D' && mesh_type[3] == 'F'))
|
|
{
|
|
named_ifgzstream *mesh_input = dynamic_cast<named_ifgzstream *>(&input);
|
|
if (mesh_input)
|
|
{
|
|
#ifdef MFEM_USE_NETCDF
|
|
ReadCubit(mesh_input->filename, curved, read_gf);
|
|
#else
|
|
MFEM_ABORT("NetCDF support requires configuration with"
|
|
" MFEM_USE_NETCDF=YES");
|
|
return;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Can not determine Cubit mesh filename!"
|
|
" Use mfem::named_ifgzstream for input.");
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Unknown input mesh format: " << mesh_type);
|
|
return;
|
|
}
|
|
|
|
// at this point the following should be defined:
|
|
// 1) Dim
|
|
// 2) NumOfElements, elements
|
|
// 3) NumOfBdrElements, boundary
|
|
// 4) NumOfVertices, with allocated space in vertices
|
|
// 5) curved
|
|
// 5a) if curved == 0, vertices must be defined
|
|
// 5b) if curved != 0 and read_gf != 0,
|
|
// 'input' must point to a GridFunction
|
|
// 5c) if curved != 0 and read_gf == 0,
|
|
// vertices and Nodes must be defined
|
|
// optional:
|
|
// 1) el_to_edge may be allocated (as in the case of P2 VTK meshes)
|
|
// 2) ncmesh may be allocated
|
|
|
|
// FinalizeTopology() will:
|
|
// - assume that generate_edges is true
|
|
// - assume that refine is false
|
|
// - does not check the orientation of regular and boundary elements
|
|
if (finalize_topo)
|
|
{
|
|
// don't generate any boundary elements, especially in parallel
|
|
bool generate_bdr = false;
|
|
|
|
FinalizeTopology(generate_bdr);
|
|
}
|
|
|
|
if (curved && read_gf)
|
|
{
|
|
Nodes = new GridFunction(this, input);
|
|
|
|
own_nodes = 1;
|
|
spaceDim = Nodes->VectorDim();
|
|
if (ncmesh) { ncmesh->spaceDim = spaceDim; }
|
|
|
|
// Set vertex coordinates from the 'Nodes'
|
|
SetVerticesFromNodes(Nodes);
|
|
}
|
|
|
|
// If a parse tag was supplied, keep reading the stream until the tag is
|
|
// encountered.
|
|
if (mfem_version >= 12)
|
|
{
|
|
string line;
|
|
do
|
|
{
|
|
skip_comment_lines(input, '#');
|
|
MFEM_VERIFY(input.good(), "Required mesh-end tag not found");
|
|
getline(input, line);
|
|
filter_dos(line);
|
|
// mfem v1.2 may not have parse_tag in it, e.g. if trying to read a
|
|
// serial mfem v1.2 mesh as parallel with "mfem_serial_mesh_end" as
|
|
// parse_tag. That's why, regardless of parse_tag, we stop reading if
|
|
// we find "mfem_mesh_end" which is required by mfem v1.2 format.
|
|
if (line == "mfem_mesh_end") { break; }
|
|
}
|
|
while (line != parse_tag);
|
|
}
|
|
else if (mfem_nc_version >= 10)
|
|
{
|
|
string ident;
|
|
skip_comment_lines(input, '#');
|
|
input >> ident;
|
|
MFEM_VERIFY(ident == "mfem_mesh_end",
|
|
"invalid mesh: end of file tag not found");
|
|
}
|
|
|
|
if (NURBSext && NURBSext->NonconformingPatches())
|
|
{
|
|
string ident;
|
|
skip_comment_lines(input, '#');
|
|
// Check for the optional section "patch_cp"
|
|
if (input.peek() == 'p')
|
|
{
|
|
input >> ident;
|
|
MFEM_VERIFY(ident == "patch_cp", "Invalid mesh format");
|
|
NURBSext->ReadCoarsePatchCP(input);
|
|
}
|
|
}
|
|
|
|
// Finalize(...) should be called after this, if needed.
|
|
}
|
|
|
|
Mesh::Mesh(Mesh *mesh_array[], int num_pieces)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
int i, j, ie, ib, iv, *v, nv;
|
|
Element *el;
|
|
Mesh *m;
|
|
|
|
SetEmpty();
|
|
|
|
Dim = mesh_array[0]->Dimension();
|
|
spaceDim = mesh_array[0]->SpaceDimension();
|
|
|
|
if (mesh_array[0]->NURBSext)
|
|
{
|
|
// assuming the pieces form a partition of a NURBS mesh
|
|
NURBSext = new NURBSExtension(mesh_array, num_pieces);
|
|
|
|
NumOfVertices = NURBSext->GetNV();
|
|
NumOfElements = NURBSext->GetNE();
|
|
|
|
NURBSext->GetElementTopo(elements);
|
|
|
|
// NumOfBdrElements = NURBSext->GetNBE();
|
|
// NURBSext->GetBdrElementTopo(boundary);
|
|
|
|
Array<int> lvert_vert, lelem_elem;
|
|
|
|
// Here, for visualization purposes, we copy the boundary elements from
|
|
// the individual pieces which include the interior boundaries. This
|
|
// creates 'boundary' array that is different from the one generated by
|
|
// the NURBSExtension which, in particular, makes the boundary-dof table
|
|
// invalid. This, in turn, causes GetBdrElementTransformation to not
|
|
// function properly.
|
|
NumOfBdrElements = 0;
|
|
for (i = 0; i < num_pieces; i++)
|
|
{
|
|
NumOfBdrElements += mesh_array[i]->GetNBE();
|
|
}
|
|
boundary.SetSize(NumOfBdrElements);
|
|
vertices.SetSize(NumOfVertices);
|
|
ib = 0;
|
|
for (i = 0; i < num_pieces; i++)
|
|
{
|
|
m = mesh_array[i];
|
|
m->NURBSext->GetVertexLocalToGlobal(lvert_vert);
|
|
m->NURBSext->GetElementLocalToGlobal(lelem_elem);
|
|
// copy the element attributes
|
|
for (j = 0; j < m->GetNE(); j++)
|
|
{
|
|
elements[lelem_elem[j]]->SetAttribute(m->GetAttribute(j));
|
|
}
|
|
// copy the boundary
|
|
for (j = 0; j < m->GetNBE(); j++)
|
|
{
|
|
el = m->GetBdrElement(j)->Duplicate(this);
|
|
v = el->GetVertices();
|
|
nv = el->GetNVertices();
|
|
for (int k = 0; k < nv; k++)
|
|
{
|
|
v[k] = lvert_vert[v[k]];
|
|
}
|
|
boundary[ib++] = el;
|
|
}
|
|
// copy the vertices
|
|
for (j = 0; j < m->GetNV(); j++)
|
|
{
|
|
vertices[lvert_vert[j]].SetCoords(m->SpaceDimension(),
|
|
m->GetVertex(j));
|
|
}
|
|
}
|
|
}
|
|
else // not a NURBS mesh
|
|
{
|
|
NumOfElements = 0;
|
|
NumOfBdrElements = 0;
|
|
NumOfVertices = 0;
|
|
for (i = 0; i < num_pieces; i++)
|
|
{
|
|
m = mesh_array[i];
|
|
NumOfElements += m->GetNE();
|
|
NumOfBdrElements += m->GetNBE();
|
|
NumOfVertices += m->GetNV();
|
|
}
|
|
elements.SetSize(NumOfElements);
|
|
boundary.SetSize(NumOfBdrElements);
|
|
vertices.SetSize(NumOfVertices);
|
|
ie = ib = iv = 0;
|
|
for (i = 0; i < num_pieces; i++)
|
|
{
|
|
m = mesh_array[i];
|
|
// copy the elements
|
|
for (j = 0; j < m->GetNE(); j++)
|
|
{
|
|
el = m->GetElement(j)->Duplicate(this);
|
|
v = el->GetVertices();
|
|
nv = el->GetNVertices();
|
|
for (int k = 0; k < nv; k++)
|
|
{
|
|
v[k] += iv;
|
|
}
|
|
elements[ie++] = el;
|
|
}
|
|
// copy the boundary elements
|
|
for (j = 0; j < m->GetNBE(); j++)
|
|
{
|
|
el = m->GetBdrElement(j)->Duplicate(this);
|
|
v = el->GetVertices();
|
|
nv = el->GetNVertices();
|
|
for (int k = 0; k < nv; k++)
|
|
{
|
|
v[k] += iv;
|
|
}
|
|
boundary[ib++] = el;
|
|
}
|
|
// copy the vertices
|
|
for (j = 0; j < m->GetNV(); j++)
|
|
{
|
|
vertices[iv++].SetCoords(m->SpaceDimension(), m->GetVertex(j));
|
|
}
|
|
}
|
|
}
|
|
|
|
FinalizeTopology();
|
|
|
|
// copy the nodes (curvilinear meshes)
|
|
GridFunction *g = mesh_array[0]->GetNodes();
|
|
if (g)
|
|
{
|
|
Array<GridFunction *> gf_array(num_pieces);
|
|
for (i = 0; i < num_pieces; i++)
|
|
{
|
|
gf_array[i] = mesh_array[i]->GetNodes();
|
|
}
|
|
Nodes = new GridFunction(this, gf_array, num_pieces);
|
|
own_nodes = 1;
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
CheckElementOrientation(false);
|
|
CheckBdrElementOrientation(false);
|
|
#endif
|
|
}
|
|
|
|
Mesh::Mesh(Mesh *orig_mesh, int ref_factor, int ref_type)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
Array<int> ref_factors(orig_mesh->GetNE());
|
|
ref_factors = ref_factor;
|
|
MakeRefined_(*orig_mesh, ref_factors, ref_type);
|
|
}
|
|
|
|
void Mesh::MakeRefined_(Mesh &orig_mesh, const Array<int> &ref_factors,
|
|
int ref_type)
|
|
{
|
|
SetEmpty();
|
|
Dim = orig_mesh.Dimension();
|
|
spaceDim = orig_mesh.SpaceDimension();
|
|
|
|
int orig_ne = orig_mesh.GetNE();
|
|
MFEM_VERIFY(ref_factors.Size() == orig_ne,
|
|
"Number of refinement factors must equal number of elements")
|
|
MFEM_VERIFY(orig_ne == 0 ||
|
|
ref_factors.Min() >= 1, "Refinement factor must be >= 1");
|
|
const int q_type = BasisType::GetQuadrature1D(ref_type);
|
|
MFEM_VERIFY(Quadrature1D::CheckClosed(q_type) != Quadrature1D::Invalid,
|
|
"Invalid refinement type. Must use closed basis type.");
|
|
|
|
int min_ref = orig_ne > 0 ? ref_factors.Min() : 1;
|
|
int max_ref = orig_ne > 0 ? ref_factors.Max() : 1;
|
|
|
|
bool var_order = (min_ref != max_ref);
|
|
|
|
// variable order space can only be constructed over an NC mesh
|
|
if (var_order) { orig_mesh.EnsureNCMesh(true); }
|
|
|
|
// Construct a scalar H1 FE space of order ref_factor and use its dofs as
|
|
// the indices of the new, refined vertices.
|
|
H1_FECollection rfec(min_ref, Dim, ref_type);
|
|
FiniteElementSpace rfes(&orig_mesh, &rfec);
|
|
|
|
if (var_order)
|
|
{
|
|
rfes.SetRelaxedHpConformity(false);
|
|
for (int i = 0; i < orig_ne; i++)
|
|
{
|
|
rfes.SetElementOrder(i, ref_factors[i]);
|
|
}
|
|
rfes.Update(false);
|
|
}
|
|
|
|
// Set the number of vertices, set the actual coordinates later
|
|
NumOfVertices = rfes.GetNDofs();
|
|
vertices.SetSize(NumOfVertices);
|
|
|
|
Array<int> rdofs;
|
|
DenseMatrix phys_pts;
|
|
GeometryRefiner refiner(q_type);
|
|
|
|
// Add refined elements and set vertex coordinates
|
|
for (int el = 0; el < orig_ne; el++)
|
|
{
|
|
Geometry::Type geom = orig_mesh.GetElementGeometry(el);
|
|
int attrib = orig_mesh.GetAttribute(el);
|
|
int nvert = Geometry::NumVerts[geom];
|
|
RefinedGeometry &RG = *refiner.Refine(geom, ref_factors[el]);
|
|
|
|
rfes.GetElementDofs(el, rdofs);
|
|
MFEM_ASSERT(rdofs.Size() == RG.RefPts.Size(), "");
|
|
const FiniteElement *rfe = rfes.GetFE(el);
|
|
orig_mesh.GetElementTransformation(el)->Transform(rfe->GetNodes(),
|
|
phys_pts);
|
|
const int *c2h_map = rfec.GetDofMap(geom, ref_factors[el]);
|
|
for (int i = 0; i < phys_pts.Width(); i++)
|
|
{
|
|
vertices[rdofs[i]].SetCoords(spaceDim, phys_pts.GetColumn(i));
|
|
}
|
|
for (int j = 0; j < RG.RefGeoms.Size()/nvert; j++)
|
|
{
|
|
Element *elem = NewElement(geom);
|
|
elem->SetAttribute(attrib);
|
|
int *v = elem->GetVertices();
|
|
for (int k = 0; k < nvert; k++)
|
|
{
|
|
int cid = RG.RefGeoms[k+nvert*j]; // local Cartesian index
|
|
v[k] = rdofs[c2h_map[cid]];
|
|
}
|
|
AddElement(elem);
|
|
}
|
|
}
|
|
|
|
if (Dim > 2)
|
|
{
|
|
GetElementToFaceTable(false);
|
|
GenerateFaces();
|
|
}
|
|
|
|
// Add refined boundary elements
|
|
for (int el = 0; el < orig_mesh.GetNBE(); el++)
|
|
{
|
|
int i, info;
|
|
orig_mesh.GetBdrElementAdjacentElement(el, i, info);
|
|
Geometry::Type geom = orig_mesh.GetBdrElementGeometry(el);
|
|
int attrib = orig_mesh.GetBdrAttribute(el);
|
|
int nvert = Geometry::NumVerts[geom];
|
|
RefinedGeometry &RG = *refiner.Refine(geom, ref_factors[i]);
|
|
|
|
rfes.GetBdrElementDofs(el, rdofs);
|
|
MFEM_ASSERT(rdofs.Size() == RG.RefPts.Size(), "");
|
|
const int *c2h_map = rfec.GetDofMap(geom, ref_factors[i]);
|
|
for (int j = 0; j < RG.RefGeoms.Size()/nvert; j++)
|
|
{
|
|
Element *elem = NewElement(geom);
|
|
elem->SetAttribute(attrib);
|
|
int *v = elem->GetVertices();
|
|
for (int k = 0; k < nvert; k++)
|
|
{
|
|
int cid = RG.RefGeoms[k+nvert*j]; // local Cartesian index
|
|
v[k] = rdofs[c2h_map[cid]];
|
|
}
|
|
AddBdrElement(elem);
|
|
}
|
|
}
|
|
FinalizeTopology(false);
|
|
sequence = orig_mesh.GetSequence() + 1;
|
|
last_operation = Mesh::REFINE;
|
|
|
|
// Set up the nodes of the new mesh (if the original mesh has nodes). The new
|
|
// mesh is always straight-sided (i.e. degree 1 finite element space), but
|
|
// the nodes are required for e.g. periodic meshes.
|
|
if (orig_mesh.GetNodes())
|
|
{
|
|
bool discont = orig_mesh.GetNodalFESpace()->IsDGSpace();
|
|
Ordering::Type dof_ordering = orig_mesh.GetNodalFESpace()->GetOrdering();
|
|
Mesh::SetCurvature(1, discont, spaceDim, dof_ordering);
|
|
FiniteElementSpace *nodal_fes = Nodes->FESpace();
|
|
const FiniteElementCollection *nodal_fec = nodal_fes->FEColl();
|
|
H1_FECollection vertex_fec(1, Dim);
|
|
Array<int> dofs;
|
|
int el_counter = 0;
|
|
for (int iel = 0; iel < orig_ne; iel++)
|
|
{
|
|
Geometry::Type geom = orig_mesh.GetElementBaseGeometry(iel);
|
|
int nvert = Geometry::NumVerts[geom];
|
|
RefinedGeometry &RG = *refiner.Refine(geom, ref_factors[iel]);
|
|
rfes.GetElementDofs(iel, rdofs);
|
|
const FiniteElement *rfe = rfes.GetFE(iel);
|
|
orig_mesh.GetElementTransformation(iel)->Transform(rfe->GetNodes(),
|
|
phys_pts);
|
|
const int *node_map = NULL;
|
|
const H1_FECollection *h1_fec =
|
|
dynamic_cast<const H1_FECollection *>(nodal_fec);
|
|
if (h1_fec != NULL) { node_map = h1_fec->GetDofMap(geom); }
|
|
const int *vertex_map = vertex_fec.GetDofMap(geom);
|
|
const int *c2h_map = rfec.GetDofMap(geom, ref_factors[iel]);
|
|
for (int jel = 0; jel < RG.RefGeoms.Size()/nvert; jel++)
|
|
{
|
|
nodal_fes->GetElementVDofs(el_counter++, dofs);
|
|
for (int iv_lex=0; iv_lex<nvert; ++iv_lex)
|
|
{
|
|
// convert from lexicographic to vertex index
|
|
int iv = vertex_map[iv_lex];
|
|
// index of vertex of current element in phys_pts matrix
|
|
int pt_idx = c2h_map[RG.RefGeoms[iv+nvert*jel]];
|
|
// index of current vertex into DOF array
|
|
int node_idx = node_map ? node_map[iv_lex] : iv_lex;
|
|
for (int d=0; d<spaceDim; ++d)
|
|
{
|
|
(*Nodes)[dofs[node_idx + d*nvert]] = phys_pts(d,pt_idx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Setup the data for the coarse-fine refinement transformations
|
|
CoarseFineTr.embeddings.SetSize(GetNE());
|
|
// First, compute total number of point matrices that we need per geometry
|
|
// and the offsets into that array
|
|
using GeomRef = std::pair<Geometry::Type, int>;
|
|
std::map<GeomRef, int> point_matrices_offsets;
|
|
int n_point_matrices[Geometry::NumGeom] = {}; // initialize to zero
|
|
for (int el_coarse = 0; el_coarse < orig_ne; ++el_coarse)
|
|
{
|
|
Geometry::Type geom = orig_mesh.GetElementBaseGeometry(el_coarse);
|
|
// Have we seen this pair of (goemetry, refinement level) before?
|
|
GeomRef id(geom, ref_factors[el_coarse]);
|
|
if (point_matrices_offsets.find(id) == point_matrices_offsets.end())
|
|
{
|
|
RefinedGeometry &RG = *refiner.Refine(geom, ref_factors[el_coarse]);
|
|
int nvert = Geometry::NumVerts[geom];
|
|
int nref_el = RG.RefGeoms.Size()/nvert;
|
|
// If not, then store the offset and add to the size required
|
|
point_matrices_offsets[id] = n_point_matrices[geom];
|
|
n_point_matrices[geom] += nref_el;
|
|
}
|
|
}
|
|
|
|
// Set up the sizes
|
|
for (int geom = 0; geom < Geometry::NumGeom; ++geom)
|
|
{
|
|
int nmatrices = n_point_matrices[geom];
|
|
int nvert = Geometry::NumVerts[geom];
|
|
CoarseFineTr.point_matrices[geom].SetSize(Dim, nvert, nmatrices);
|
|
}
|
|
|
|
// Compute the point matrices and embeddings
|
|
int el_fine = 0;
|
|
for (int el_coarse = 0; el_coarse < orig_ne; ++el_coarse)
|
|
{
|
|
Geometry::Type geom = orig_mesh.GetElementBaseGeometry(el_coarse);
|
|
int ref = ref_factors[el_coarse];
|
|
int offset = point_matrices_offsets[GeomRef(geom, ref)];
|
|
int nvert = Geometry::NumVerts[geom];
|
|
RefinedGeometry &RG = *refiner.Refine(geom, ref);
|
|
for (int j = 0; j < RG.RefGeoms.Size()/nvert; j++)
|
|
{
|
|
DenseMatrix &Pj = CoarseFineTr.point_matrices[geom](offset + j);
|
|
for (int k = 0; k < nvert; k++)
|
|
{
|
|
int cid = RG.RefGeoms[k+nvert*j]; // local Cartesian index
|
|
const IntegrationPoint &ip = RG.RefPts[cid];
|
|
ip.Get(Pj.GetColumn(k), Dim);
|
|
}
|
|
|
|
Embedding &emb = CoarseFineTr.embeddings[el_fine];
|
|
emb.geom = geom;
|
|
emb.parent = el_coarse;
|
|
emb.matrix = offset + j;
|
|
++el_fine;
|
|
}
|
|
}
|
|
|
|
MFEM_ASSERT(CheckElementOrientation(false) == 0, "");
|
|
|
|
// The check below is disabled because is fails for parallel meshes with
|
|
// interior "boundary" element that, when such "boundary" element is between
|
|
// two elements on different processors.
|
|
// MFEM_ASSERT(CheckBdrElementOrientation(false) == 0, "");
|
|
}
|
|
|
|
Mesh Mesh::MakeSimplicial(const Mesh &orig_mesh)
|
|
{
|
|
Mesh mesh;
|
|
auto parent_elements = mesh.MakeSimplicial_(orig_mesh, NULL);
|
|
if (orig_mesh.GetNodes() != nullptr)
|
|
{
|
|
mesh.MakeHigherOrderSimplicial_(orig_mesh, parent_elements);
|
|
}
|
|
return mesh;
|
|
}
|
|
|
|
Array<int> Mesh::MakeSimplicial_(const Mesh &orig_mesh, int *vglobal)
|
|
{
|
|
MFEM_VERIFY(const_cast<Mesh&>(orig_mesh).CheckElementOrientation(false) == 0,
|
|
"Mesh::MakeSimplicial requires a properly oriented input mesh");
|
|
MFEM_VERIFY(orig_mesh.Conforming(),
|
|
"Mesh::MakeSimplicial does not support non-conforming meshes.")
|
|
|
|
int dim = orig_mesh.Dimension();
|
|
int sdim = orig_mesh.SpaceDimension();
|
|
|
|
if (dim == 1)
|
|
{
|
|
Mesh copy(orig_mesh);
|
|
Swap(copy, true);
|
|
Array<int> parent_elements(GetNE());
|
|
std::iota(parent_elements.begin(), parent_elements.end(), 0);
|
|
return parent_elements;
|
|
}
|
|
|
|
int nv = orig_mesh.GetNV();
|
|
int ne = orig_mesh.GetNE();
|
|
int nbe = orig_mesh.GetNBE();
|
|
|
|
static int num_subdivisions[Geometry::NUM_GEOMETRIES];
|
|
num_subdivisions[Geometry::POINT] = 1;
|
|
num_subdivisions[Geometry::SEGMENT] = 1;
|
|
num_subdivisions[Geometry::TRIANGLE] = 1;
|
|
num_subdivisions[Geometry::TETRAHEDRON] = 1;
|
|
num_subdivisions[Geometry::SQUARE] = 2;
|
|
num_subdivisions[Geometry::PRISM] = 3;
|
|
num_subdivisions[Geometry::CUBE] = 6;
|
|
// NOTE: some hexes may be subdivided into only 5 tets, so this is an
|
|
// estimate only. The actual number of created tets may be less, so the
|
|
// elements array will need to be shrunk after mesh creation.
|
|
int new_ne = 0, new_nbe = 0;
|
|
for (int i=0; i<ne; ++i)
|
|
{
|
|
new_ne += num_subdivisions[orig_mesh.GetElementBaseGeometry(i)];
|
|
}
|
|
for (int i=0; i<nbe; ++i)
|
|
{
|
|
new_nbe += num_subdivisions[orig_mesh.GetBdrElementGeometry(i)];
|
|
}
|
|
|
|
InitMesh(dim, sdim, nv, new_ne, new_nbe);
|
|
|
|
// Vertices of the new mesh are same as the original mesh
|
|
NumOfVertices = nv;
|
|
for (int i=0; i<nv; ++i)
|
|
{
|
|
vertices[i].SetCoords(sdim, orig_mesh.vertices[i]());
|
|
}
|
|
|
|
// We need a global vertex numbering to identify which diagonals to split
|
|
// (quad faces are split using the diagonal originating from the smallest
|
|
// global vertex number). Use the supplied global numbering, if it is
|
|
// non-NULL, otherwise use the local numbering.
|
|
Array<int> vglobal_id;
|
|
if (vglobal == nullptr)
|
|
{
|
|
vglobal_id.SetSize(nv);
|
|
std::iota(vglobal_id.begin(), vglobal_id.end(), 0);
|
|
vglobal = vglobal_id.GetData();
|
|
}
|
|
|
|
// Number of vertices per element
|
|
constexpr int nv_tri = 3, nv_quad = 4, nv_tet = 4, nv_prism = 6, nv_hex = 8;
|
|
constexpr int quad_ntris = 2; // NTriangles per quad
|
|
constexpr int prism_ntets = 3; // NTets per prism
|
|
// Map verts of quad to verts of tri, in two possible configurations.
|
|
// quad_trimap[i][0,2,4] is the first triangle, and quad_trimap[i][1,3,5] is
|
|
// the second, for each configuration.
|
|
static const int quad_trimap[2][nv_tri*quad_ntris] =
|
|
{
|
|
{
|
|
0, 0,
|
|
1, 2,
|
|
2, 3
|
|
},{
|
|
0, 1,
|
|
1, 2,
|
|
3, 3
|
|
}
|
|
};
|
|
static const int prism_rot[nv_prism*nv_prism] =
|
|
{
|
|
0, 1, 2, 3, 4, 5,
|
|
1, 2, 0, 4, 5, 3,
|
|
2, 0, 1, 5, 3, 4,
|
|
3, 5, 4, 0, 2, 1,
|
|
4, 3, 5, 1, 0, 2,
|
|
5, 4, 3, 2, 1, 0
|
|
};
|
|
static const int prism_f[nv_quad] = {1, 2, 5, 4};
|
|
static const int prism_tetmaps[2][nv_prism*prism_ntets] =
|
|
{
|
|
{
|
|
0, 0, 0,
|
|
1, 1, 4,
|
|
2, 5, 5,
|
|
5, 4, 3
|
|
},{
|
|
0, 0, 0,
|
|
1, 4, 4,
|
|
2, 2, 5,
|
|
4, 5, 3
|
|
}
|
|
};
|
|
static const int hex_rot[nv_hex*nv_hex] =
|
|
{
|
|
0, 1, 2, 3, 4, 5, 6, 7,
|
|
1, 0, 4, 5, 2, 3, 7, 6,
|
|
2, 1, 5, 6, 3, 0, 4, 7,
|
|
3, 0, 1, 2, 7, 4, 5, 6,
|
|
4, 0, 3, 7, 5, 1, 2, 6,
|
|
5, 1, 0, 4, 6, 2, 3, 7,
|
|
6, 2, 1, 5, 7, 3, 0, 4,
|
|
7, 3, 2, 6, 4, 0, 1, 5
|
|
};
|
|
static const int hex_f0[nv_quad] = {1, 2, 6, 5};
|
|
static const int hex_f1[nv_quad] = {2, 3, 7, 6};
|
|
static const int hex_f2[nv_quad] = {4, 5, 6, 7};
|
|
static const int num_rot[8] = {0, 1, 2, 0, 0, 2, 1, 0};
|
|
static const int hex_tetmap0[nv_tet*5] =
|
|
{
|
|
0, 0, 0, 0, 2,
|
|
1, 2, 2, 5, 7,
|
|
2, 7, 3, 7, 5,
|
|
5, 5, 7, 4, 6
|
|
};
|
|
static const int hex_tetmap1[nv_tet*6] =
|
|
{
|
|
0, 0, 1, 0, 0, 1,
|
|
5, 1, 6, 7, 7, 7,
|
|
7, 7, 7, 2, 1, 6,
|
|
4, 5, 5, 3, 2, 2
|
|
};
|
|
static const int hex_tetmap2[nv_tet*6] =
|
|
{
|
|
0, 0, 0, 0, 0, 0,
|
|
4, 3, 7, 1, 3, 6,
|
|
5, 7, 4, 2, 6, 5,
|
|
6, 6, 6, 5, 2, 2
|
|
};
|
|
static const int hex_tetmap3[nv_tet*6] =
|
|
{
|
|
0, 0, 0, 0, 1, 1,
|
|
2, 3, 7, 5, 5, 6,
|
|
3, 7, 4, 6, 6, 2,
|
|
6, 6, 6, 4, 0, 0
|
|
};
|
|
static const int *hex_tetmaps[4] =
|
|
{
|
|
hex_tetmap0, hex_tetmap1, hex_tetmap2, hex_tetmap3
|
|
};
|
|
|
|
auto find_min = [](const int *a, int n) { return std::min_element(a,a+n)-a; };
|
|
|
|
Array<int> parent_elems;
|
|
for (int i=0; i<ne; ++i)
|
|
{
|
|
const int *v = orig_mesh.elements[i]->GetVertices();
|
|
const int attrib = orig_mesh.GetAttribute(i);
|
|
const Geometry::Type orig_geom = orig_mesh.GetElementBaseGeometry(i);
|
|
|
|
if (num_subdivisions[orig_geom] == 1)
|
|
{
|
|
// (num_subdivisions[orig_geom] == 1) implies that the element does not
|
|
// need to be further split (it is either a segment, triangle, or
|
|
// tetrahedron), and so it is left unchanged.
|
|
Element *e = NewElement(orig_geom);
|
|
e->SetAttribute(attrib);
|
|
e->SetVertices(v);
|
|
AddElement(e);
|
|
parent_elems.Append(i);
|
|
}
|
|
else if (orig_geom == Geometry::SQUARE)
|
|
{
|
|
for (int itri=0; itri<quad_ntris; ++itri)
|
|
{
|
|
Element *e = NewElement(Geometry::TRIANGLE);
|
|
e->SetAttribute(attrib);
|
|
int *v2 = e->GetVertices();
|
|
for (int iv=0; iv<nv_tri; ++iv)
|
|
{
|
|
v2[iv] = v[quad_trimap[0][itri + iv*quad_ntris]];
|
|
}
|
|
AddElement(e);
|
|
parent_elems.Append(i);
|
|
}
|
|
}
|
|
else if (orig_geom == Geometry::PRISM)
|
|
{
|
|
int vg[nv_prism];
|
|
for (int iv=0; iv<nv_prism; ++iv) { vg[iv] = vglobal[v[iv]]; }
|
|
// Rotate the vertices of the prism so that the smallest vertex index
|
|
// is in the first place
|
|
int irot = find_min(vg, nv_prism);
|
|
for (int iv=0; iv<nv_prism; ++iv)
|
|
{
|
|
int jv = prism_rot[iv + irot*nv_prism];
|
|
vg[iv] = v[jv];
|
|
}
|
|
// Two cases according to which diagonal splits third quad face
|
|
int q[nv_quad];
|
|
for (int iv=0; iv<nv_quad; ++iv) { q[iv] = vglobal[vg[prism_f[iv]]]; }
|
|
int j = find_min(q, nv_quad);
|
|
const int *tetmap = (j == 0 || j == 2) ? prism_tetmaps[0] : prism_tetmaps[1];
|
|
for (int itet=0; itet<prism_ntets; ++itet)
|
|
{
|
|
Element *e = NewElement(Geometry::TETRAHEDRON);
|
|
e->SetAttribute(attrib);
|
|
int *v2 = e->GetVertices();
|
|
for (int iv=0; iv<nv_tet; ++iv)
|
|
{
|
|
v2[iv] = vg[tetmap[itet + iv*prism_ntets]];
|
|
}
|
|
AddElement(e);
|
|
parent_elems.Append(i);
|
|
}
|
|
}
|
|
else if (orig_geom == Geometry::CUBE)
|
|
{
|
|
int vg[nv_hex];
|
|
for (int iv=0; iv<nv_hex; ++iv) { vg[iv] = vglobal[v[iv]]; }
|
|
|
|
// Rotate the vertices of the hex so that the smallest vertex index is
|
|
// in the first place
|
|
int irot = find_min(vg, nv_hex);
|
|
for (int iv=0; iv<nv_hex; ++iv)
|
|
{
|
|
int jv = hex_rot[iv + irot*nv_hex];
|
|
vg[iv] = v[jv];
|
|
}
|
|
|
|
int q[nv_quad];
|
|
// Bitmask is three binary digits, each digit is 1 if the diagonal of
|
|
// the corresponding face goes through the 7th vertex, and 0 if not.
|
|
int bitmask = 0;
|
|
int j;
|
|
// First quad face
|
|
for (int iv=0; iv<nv_quad; ++iv) { q[iv] = vglobal[vg[hex_f0[iv]]]; }
|
|
j = find_min(q, nv_quad);
|
|
if (j == 0 || j == 2) { bitmask += 4; }
|
|
// Second quad face
|
|
for (int iv=0; iv<nv_quad; ++iv) { q[iv] = vglobal[vg[hex_f1[iv]]]; }
|
|
j = find_min(q, nv_quad);
|
|
if (j == 1 || j == 3) { bitmask += 2; }
|
|
// Third quad face
|
|
for (int iv=0; iv<nv_quad; ++iv) { q[iv] = vglobal[vg[hex_f2[iv]]]; }
|
|
j = find_min(q, nv_quad);
|
|
if (j == 0 || j == 2) { bitmask += 1; }
|
|
|
|
// Apply rotations
|
|
int nrot = num_rot[bitmask];
|
|
for (int k=0; k<nrot; ++k)
|
|
{
|
|
int vtemp;
|
|
vtemp = vg[1];
|
|
vg[1] = vg[4];
|
|
vg[4] = vg[3];
|
|
vg[3] = vtemp;
|
|
vtemp = vg[5];
|
|
vg[5] = vg[7];
|
|
vg[7] = vg[2];
|
|
vg[2] = vtemp;
|
|
}
|
|
|
|
// Sum up nonzero bits in bitmask
|
|
int ndiags = ((bitmask&4) >> 2) + ((bitmask&2) >> 1) + (bitmask&1);
|
|
int ntets = (ndiags == 0) ? 5 : 6;
|
|
const int *tetmap = hex_tetmaps[ndiags];
|
|
for (int itet=0; itet<ntets; ++itet)
|
|
{
|
|
Element *e = NewElement(Geometry::TETRAHEDRON);
|
|
e->SetAttribute(attrib);
|
|
int *v2 = e->GetVertices();
|
|
for (int iv=0; iv<nv_tet; ++iv)
|
|
{
|
|
v2[iv] = vg[tetmap[itet + iv*ntets]];
|
|
}
|
|
AddElement(e);
|
|
parent_elems.Append(i);
|
|
}
|
|
}
|
|
}
|
|
// In 3D, shrink the element array because some hexes have only 5 tets
|
|
if (dim == 3) { elements.SetSize(NumOfElements); }
|
|
|
|
for (int i=0; i<nbe; ++i)
|
|
{
|
|
const int *v = orig_mesh.boundary[i]->GetVertices();
|
|
const int attrib = orig_mesh.GetBdrAttribute(i);
|
|
const Geometry::Type orig_geom = orig_mesh.GetBdrElementGeometry(i);
|
|
if (num_subdivisions[orig_geom] == 1)
|
|
{
|
|
Element *be = NewElement(orig_geom);
|
|
be->SetAttribute(attrib);
|
|
be->SetVertices(v);
|
|
AddBdrElement(be);
|
|
}
|
|
else if (orig_geom == Geometry::SQUARE)
|
|
{
|
|
int vg[nv_quad];
|
|
for (int iv=0; iv<nv_quad; ++iv) { vg[iv] = vglobal[v[iv]]; }
|
|
// Split quad according the smallest (global) vertex
|
|
int iv_min = find_min(vg, nv_quad);
|
|
int isplit = (iv_min == 0 || iv_min == 2) ? 0 : 1;
|
|
for (int itri=0; itri<quad_ntris; ++itri)
|
|
{
|
|
Element *be = NewElement(Geometry::TRIANGLE);
|
|
be->SetAttribute(attrib);
|
|
int *v2 = be->GetVertices();
|
|
for (int iv=0; iv<nv_tri; ++iv)
|
|
{
|
|
v2[iv] = v[quad_trimap[isplit][itri + iv*quad_ntris]];
|
|
}
|
|
AddBdrElement(be);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Unreachable");
|
|
}
|
|
}
|
|
|
|
FinalizeTopology(false);
|
|
sequence = orig_mesh.GetSequence();
|
|
last_operation = orig_mesh.last_operation;
|
|
|
|
MFEM_ASSERT(CheckElementOrientation(false) == 0, "");
|
|
MFEM_ASSERT(CheckBdrElementOrientation(false) == 0, "");
|
|
|
|
return parent_elems;
|
|
}
|
|
|
|
|
|
void Mesh::MakeHigherOrderSimplicial_(const Mesh &orig_mesh,
|
|
const Array<int> &parent_elements)
|
|
{
|
|
// Higher order associated to vertices are unchanged, and those for
|
|
// previously existing edges. DOFs associated to new elements need to be set.
|
|
const int sdim = orig_mesh.SpaceDimension();
|
|
auto *orig_fespace = orig_mesh.GetNodes()->FESpace();
|
|
SetCurvature(orig_fespace->GetMaxElementOrder(), orig_fespace->IsDGSpace(),
|
|
orig_mesh.SpaceDimension(), orig_fespace->GetOrdering());
|
|
|
|
// The dofs associated with vertices are unchanged, but there can be new dofs
|
|
// associated to edges, faces and volumes. Additionally, because we know that
|
|
// the set of vertices is unchanged by the splitting operation, we can use
|
|
// the vertices to map local coordinates of the "child" elements (the new
|
|
// simplices introduced), from the "parent" element (the quad, prism, hex
|
|
// that was split).
|
|
|
|
// For segment, triangle and tetrahedron, the dof values are copied directly.
|
|
// For the others, we have to construct a map from the Node locations in the
|
|
// new simplex to the parent non-simplex element. This could be sped up by
|
|
// not repeatedly access the original FE as the accesses will be coherent
|
|
// (i.e. all child elems are consecutive).
|
|
|
|
Array<int> edofs; // element dofs in new element
|
|
Array<int> parent_vertices, child_vertices; // vertices of parent and child.
|
|
Array<int> node_map; // node indices of parent from child.
|
|
Vector edofvals; // values of elements dofs in original element
|
|
// Storage for evaluating node function on parent element, at node locations
|
|
// of child element
|
|
DenseMatrix shape; // ndof_coarse x nnode_refined.
|
|
DenseMatrix point_matrix; // sdim x nnode_refined
|
|
IntegrationRule
|
|
child_nodes_in_parent; // The parent nodes that correspond to the child nodes
|
|
for (int i = 0; i < parent_elements.Size(); i++)
|
|
{
|
|
const int ip = parent_elements[i];
|
|
const Geometry::Type orig_geom = orig_mesh.GetElementBaseGeometry(ip);
|
|
orig_mesh.GetNodes()->GetElementDofValues(ip, edofvals);
|
|
switch (orig_geom)
|
|
{
|
|
case Geometry::Type::SEGMENT : // fall through
|
|
case Geometry::Type::TRIANGLE : // fall through
|
|
case Geometry::Type::TETRAHEDRON :
|
|
GetNodes()->FESpace()->GetElementVDofs(i, edofs);
|
|
GetNodes()->SetSubVector(edofs, edofvals);
|
|
break;
|
|
case Geometry::Type::CUBE : // fall through
|
|
case Geometry::Type::PRISM : // fall through
|
|
case Geometry::Type::PYRAMID : // fall through
|
|
case Geometry::Type::SQUARE :
|
|
{
|
|
// Extract the vertices of parent and child, can then form the
|
|
// map from child reference coordinates to parent reference
|
|
// coordinates. Exploit the fact that for Nodes, the vertex
|
|
// entries come first, and their indexing matches the vertex
|
|
// numbering. Thus we have already have an inverse index map.
|
|
orig_mesh.GetElementVertices(ip, parent_vertices);
|
|
GetElementVertices(i, child_vertices);
|
|
node_map.SetSize(0);
|
|
for (auto cv : child_vertices)
|
|
for (int ipv = 0; ipv < parent_vertices.Size(); ipv++)
|
|
if (cv == parent_vertices[ipv])
|
|
{
|
|
node_map.Append(ipv);
|
|
break;
|
|
}
|
|
MFEM_ASSERT(node_map.Size() == Geometry::NumVerts[GetElementBaseGeometry(i)],
|
|
"!");
|
|
// node_map now says which of the parent vertex nodes map to each
|
|
// of the child vertex nodes. Using this can build a basis in the
|
|
// parent element from child Node values, exploit the linearity
|
|
// to then transform all nodes.
|
|
child_nodes_in_parent.SetSize(0);
|
|
const auto *orig_FE = orig_mesh.GetNodes()->FESpace()->GetFE(ip);
|
|
for (auto pn : node_map)
|
|
{
|
|
child_nodes_in_parent.Append(orig_FE->GetNodes()[pn]);
|
|
}
|
|
const auto *simplex_FE = GetNodes()->FESpace()->GetFE(i);
|
|
shape.SetSize(orig_FE->GetDof(),
|
|
simplex_FE->GetDof()); // One set of evaluations per simplex dof.
|
|
Vector col;
|
|
for (int j = 0; j < simplex_FE->GetNodes().Size(); j++)
|
|
{
|
|
const auto &simplex_node = simplex_FE->GetNodes()[j];
|
|
IntegrationPoint simplex_node_in_orig;
|
|
// Handle the 2D vs 3D case by multiplying .z by zero.
|
|
simplex_node_in_orig.Set3(
|
|
child_nodes_in_parent[0].x +
|
|
simplex_node.x * (child_nodes_in_parent[1].x - child_nodes_in_parent[0].x)
|
|
+ simplex_node.y * (child_nodes_in_parent[2].x - child_nodes_in_parent[0].x)
|
|
+ simplex_node.z * (child_nodes_in_parent[(Dim > 2) ? 3 : 0].x -
|
|
child_nodes_in_parent[0].x),
|
|
child_nodes_in_parent[0].y +
|
|
simplex_node.x * (child_nodes_in_parent[1].y - child_nodes_in_parent[0].y)
|
|
+ simplex_node.y * (child_nodes_in_parent[2].y - child_nodes_in_parent[0].y)
|
|
+ simplex_node.z * (child_nodes_in_parent[(Dim > 2) ? 3 : 0].y -
|
|
child_nodes_in_parent[0].y),
|
|
child_nodes_in_parent[0].z +
|
|
simplex_node.x * (child_nodes_in_parent[1].z - child_nodes_in_parent[0].z)
|
|
+ simplex_node.y * (child_nodes_in_parent[2].z - child_nodes_in_parent[0].z)
|
|
+ simplex_node.z * (child_nodes_in_parent[(Dim > 2) ? 3 : 0].z -
|
|
child_nodes_in_parent[0].z));
|
|
shape.GetColumnReference(j, col);
|
|
orig_FE->CalcShape(simplex_node_in_orig, col);
|
|
}
|
|
// All the non-simplex basis functions have now been evaluated at
|
|
// all the simplex basis function node locations. Now evaluate
|
|
// the summations and place back into the Nodes vector.
|
|
orig_mesh.GetNodes()->GetElementDofValues(ip, edofvals);
|
|
// Dof values are always returned as
|
|
// [[x_1,x_2,x_3,...],
|
|
// [y_1,y_2,y_3,...],
|
|
// [z_1,z_2,z_3,...]]
|
|
DenseMatrix edofvals_mat(edofvals.GetData(), orig_FE->GetDof(), sdim);
|
|
point_matrix.SetSize(simplex_FE->GetDof(), sdim);
|
|
MultAtB(shape, edofvals_mat, point_matrix);
|
|
GetNodes()->FESpace()->GetElementVDofs(i, edofs);
|
|
GetNodes()->SetSubVector(edofs, point_matrix.GetData());
|
|
}
|
|
break;
|
|
case Geometry::Type::POINT : // fall through
|
|
case Geometry::Type::INVALID :
|
|
case Geometry::Type::NUM_GEOMETRIES :
|
|
MFEM_ABORT("Internal Error!");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Mesh Mesh::MakePeriodic(const Mesh &orig_mesh, const std::vector<int> &v2v)
|
|
{
|
|
Mesh periodic_mesh(orig_mesh, true); // Make a copy of the original mesh
|
|
const FiniteElementSpace *nodal_fes = orig_mesh.GetNodalFESpace();
|
|
int nodal_order = nodal_fes ? nodal_fes->GetMaxElementOrder() : 1;
|
|
periodic_mesh.SetCurvature(nodal_order, true);
|
|
|
|
// renumber element vertices
|
|
for (int i = 0; i < periodic_mesh.GetNE(); i++)
|
|
{
|
|
Element *el = periodic_mesh.GetElement(i);
|
|
int *v = el->GetVertices();
|
|
int nv = el->GetNVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
v[j] = v2v[v[j]];
|
|
}
|
|
}
|
|
// renumber boundary element vertices
|
|
for (int i = 0; i < periodic_mesh.GetNBE(); i++)
|
|
{
|
|
Element *el = periodic_mesh.GetBdrElement(i);
|
|
int *v = el->GetVertices();
|
|
int nv = el->GetNVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
v[j] = v2v[v[j]];
|
|
}
|
|
}
|
|
|
|
periodic_mesh.RemoveUnusedVertices();
|
|
return periodic_mesh;
|
|
}
|
|
|
|
std::vector<int> Mesh::CreatePeriodicVertexMapping(
|
|
const std::vector<Vector> &translations, real_t tol) const
|
|
{
|
|
const int sdim = SpaceDimension();
|
|
|
|
Vector coord(sdim), at(sdim), dx(sdim);
|
|
Vector xMax(sdim), xMin(sdim), xDiff(sdim);
|
|
xMax = xMin = xDiff = 0.0;
|
|
|
|
// Get a list of all vertices on the boundary
|
|
unordered_set<int> bdr_v;
|
|
for (int be = 0; be < GetNBE(); be++)
|
|
{
|
|
Array<int> dofs;
|
|
GetBdrElementVertices(be,dofs);
|
|
|
|
for (int i = 0; i < dofs.Size(); i++)
|
|
{
|
|
bdr_v.insert(dofs[i]);
|
|
|
|
coord = GetVertex(dofs[i]);
|
|
for (int j = 0; j < sdim; j++)
|
|
{
|
|
xMax[j] = max(xMax[j], coord[j]);
|
|
xMin[j] = min(xMin[j], coord[j]);
|
|
}
|
|
}
|
|
}
|
|
add(xMax, -1.0, xMin, xDiff);
|
|
real_t dia = xDiff.Norml2(); // compute mesh diameter
|
|
|
|
// We now identify coincident vertices. Several originally distinct vertices
|
|
// may become coincident under the periodic mapping. One of these vertices
|
|
// will be identified as the "primary" vertex, and all other coincident
|
|
// vertices will be considered as "replicas".
|
|
|
|
// replica2primary[v] is the index of the primary vertex of replica `v`
|
|
unordered_map<int, int> replica2primary;
|
|
// primary2replicas[v] is a set of indices of replicas of primary vertex `v`
|
|
unordered_map<int, unordered_set<int>> primary2replicas;
|
|
|
|
// Create a KD-tree containing all the boundary vertices
|
|
std::unique_ptr<KDTreeBase<int,real_t>> kdtree;
|
|
if (sdim == 1) { kdtree.reset(new KDTree1D); }
|
|
else if (sdim == 2) { kdtree.reset(new KDTree2D); }
|
|
else if (sdim == 3) { kdtree.reset(new KDTree3D); }
|
|
else { MFEM_ABORT("Invalid space dimension."); }
|
|
|
|
// We begin with the assumption that all vertices are primary, and that there
|
|
// are no replicas.
|
|
for (const int v : bdr_v)
|
|
{
|
|
primary2replicas[v];
|
|
kdtree->AddPoint(GetVertex(v), v);
|
|
}
|
|
|
|
kdtree->Sort();
|
|
|
|
// Make `r` and all of `r`'s replicas be replicas of `p`. Delete `r` from the
|
|
// list of primary vertices.
|
|
auto make_replica = [&replica2primary, &primary2replicas](int r, int p)
|
|
{
|
|
if (r == p) { return; }
|
|
primary2replicas[p].insert(r);
|
|
replica2primary[r] = p;
|
|
for (const int s : primary2replicas[r])
|
|
{
|
|
primary2replicas[p].insert(s);
|
|
replica2primary[s] = p;
|
|
}
|
|
primary2replicas.erase(r);
|
|
};
|
|
|
|
for (unsigned int i = 0; i < translations.size(); i++)
|
|
{
|
|
for (int vi : bdr_v)
|
|
{
|
|
coord = GetVertex(vi);
|
|
add(coord, translations[i], at);
|
|
|
|
const int vj = kdtree->FindClosestPoint(at.GetData());
|
|
coord = GetVertex(vj);
|
|
add(at, -1.0, coord, dx);
|
|
|
|
if (dx.Norml2() > dia*tol) { continue; }
|
|
|
|
// The two vertices vi and vj are coincident.
|
|
|
|
// Are vertices `vi` and `vj` already primary?
|
|
const bool pi = primary2replicas.find(vi) != primary2replicas.end();
|
|
const bool pj = primary2replicas.find(vj) != primary2replicas.end();
|
|
|
|
if (pi && pj)
|
|
{
|
|
// Both vertices are currently primary
|
|
// Demote `vj` to be a replica of `vi`
|
|
make_replica(vj, vi);
|
|
}
|
|
else if (pi && !pj)
|
|
{
|
|
// `vi` is primary and `vj` is a replica
|
|
const int owner_of_vj = replica2primary[vj];
|
|
// Make `vi` and its replicas be replicas of `vj`'s owner
|
|
make_replica(vi, owner_of_vj);
|
|
}
|
|
else if (!pi && pj)
|
|
{
|
|
// `vi` is currently a replica and `vj` is currently primary
|
|
// Make `vj` and its replicas be replicas of `vi`'s owner
|
|
const int owner_of_vi = replica2primary[vi];
|
|
make_replica(vj, owner_of_vi);
|
|
}
|
|
else
|
|
{
|
|
// Both vertices are currently replicas
|
|
// Make `vj`'s owner and all of its owner's replicas be replicas
|
|
// of `vi`'s owner
|
|
const int owner_of_vi = replica2primary[vi];
|
|
const int owner_of_vj = replica2primary[vj];
|
|
make_replica(owner_of_vj, owner_of_vi);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<int> v2v(GetNV());
|
|
for (size_t i = 0; i < v2v.size(); i++)
|
|
{
|
|
v2v[i] = static_cast<int>(i);
|
|
}
|
|
for (const auto &r2p : replica2primary)
|
|
{
|
|
v2v[r2p.first] = r2p.second;
|
|
}
|
|
return v2v;
|
|
}
|
|
|
|
void Mesh::RefineNURBSFromFile(std::string ref_file)
|
|
{
|
|
MFEM_VERIFY(NURBSext,"Mesh::RefineNURBSFromFile: Not a NURBS mesh!");
|
|
mfem::out<<"Refining NURBS from refinement file: "<<ref_file<<endl;
|
|
|
|
int nkv;
|
|
ifstream input(ref_file);
|
|
input >> nkv;
|
|
|
|
// Check if the number of knot vectors in the refinement file and mesh match
|
|
if ( nkv != NURBSext->GetNKV())
|
|
{
|
|
mfem::out<<endl;
|
|
mfem::out<<"Knot vectors in ref_file: "<<nkv<<endl;
|
|
mfem::out<<"Knot vectors in NURBSExt: "<<NURBSext->GetNKV()<<endl;
|
|
MFEM_ABORT("Refine file does not have the correct number of knot vectors");
|
|
}
|
|
|
|
// Read knot vectors from file
|
|
Array<Vector *> knotVec(nkv);
|
|
for (int kv = 0; kv < nkv; kv++)
|
|
{
|
|
knotVec[kv] = new Vector();
|
|
knotVec[kv]-> Load(input);
|
|
}
|
|
input.close();
|
|
|
|
// Insert knots
|
|
KnotInsert(knotVec);
|
|
|
|
// Delete knots
|
|
for (int kv = 0; kv < nkv; kv++)
|
|
{
|
|
delete knotVec[kv];
|
|
}
|
|
}
|
|
|
|
void Mesh::KnotInsert(Array<KnotVector *> &kv)
|
|
{
|
|
if (NURBSext == NULL)
|
|
{
|
|
mfem_error("Mesh::KnotInsert : Not a NURBS mesh!");
|
|
}
|
|
|
|
if (kv.Size() != NURBSext->GetNKV())
|
|
{
|
|
mfem_error("Mesh::KnotInsert : KnotVector array size mismatch!");
|
|
}
|
|
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
NURBSext->KnotInsert(kv);
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
}
|
|
|
|
void Mesh::KnotInsert(Array<Vector *> &kv)
|
|
{
|
|
if (NURBSext == NULL)
|
|
{
|
|
mfem_error("Mesh::KnotInsert : Not a NURBS mesh!");
|
|
}
|
|
|
|
if (kv.Size() != NURBSext->GetNKV())
|
|
{
|
|
mfem_error("Mesh::KnotInsert : KnotVector array size mismatch!");
|
|
}
|
|
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
NURBSext->KnotInsert(kv);
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
}
|
|
|
|
void Mesh::KnotRemove(Array<Vector *> &kv)
|
|
{
|
|
if (NURBSext == NULL)
|
|
{
|
|
mfem_error("Mesh::KnotRemove : Not a NURBS mesh!");
|
|
}
|
|
|
|
if (kv.Size() != NURBSext->GetNKV())
|
|
{
|
|
mfem_error("Mesh::KnotRemove : KnotVector array size mismatch!");
|
|
}
|
|
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
NURBSext->KnotRemove(kv);
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
}
|
|
|
|
void Mesh::RefineNURBSWithKVFactors(int rf, const std::string &kvf)
|
|
{
|
|
RefineNURBS(true, 0.0, Array<int>(&rf, 1), kvf);
|
|
}
|
|
|
|
void Mesh::NURBSUniformRefinement(int rf, real_t tol)
|
|
{
|
|
Array<int> rf_array(Dim);
|
|
rf_array = rf;
|
|
NURBSUniformRefinement(rf_array, tol);
|
|
}
|
|
|
|
void Mesh::NURBSUniformRefinement(Array<int> const& rf, real_t tol)
|
|
{
|
|
MFEM_VERIFY(rf.Size() == Dim,
|
|
"Refinement factors must be defined for each dimension");
|
|
|
|
RefineNURBS(false, tol, rf, "");
|
|
}
|
|
|
|
void Mesh::RefineNURBS(bool usingKVF, real_t tol, const Array<int> &rf,
|
|
const std::string &kvf)
|
|
{
|
|
MFEM_VERIFY(NURBSext, "This type of refinement is only for NURBS meshes");
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
Array<int> cf;
|
|
NURBSext->GetCoarseningFactors(cf);
|
|
|
|
bool cf1 = true;
|
|
for (auto f : cf)
|
|
{
|
|
cf1 = (cf1 && f == 1);
|
|
}
|
|
|
|
if (!cf1 && NURBSext->NonconformingPatches())
|
|
{
|
|
NURBSext->FullyCoarsen();
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
}
|
|
else if (!cf1 && !NURBSext->NonconformingPatches())
|
|
{
|
|
MFEM_VERIFY(!usingKVF, "This refinement type is not supported for this"
|
|
" NURBS mesh type");
|
|
NURBSext->Coarsen(cf, tol);
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
UpdateNURBS();
|
|
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
for (int i=0; i<cf.Size(); ++i) { cf[i] *= rf[i]; }
|
|
NURBSext->UniformRefinement(cf);
|
|
}
|
|
|
|
if (cf1 || NURBSext->NonconformingPatches())
|
|
{
|
|
if (usingKVF || NURBSext->NonconformingPatches())
|
|
{
|
|
NURBSext->RefineWithKVFactors(rf[0], kvf, !cf1);
|
|
}
|
|
else
|
|
{
|
|
NURBSext->UniformRefinement(rf);
|
|
}
|
|
}
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
}
|
|
|
|
void Mesh::DegreeElevate(int rel_degree, int degree)
|
|
{
|
|
if (NURBSext == NULL)
|
|
{
|
|
mfem_error("Mesh::DegreeElevate : Not a NURBS mesh!");
|
|
}
|
|
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
NURBSext->DegreeElevate(rel_degree, degree);
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
}
|
|
|
|
void Mesh::UpdateNURBS()
|
|
{
|
|
ResetLazyData();
|
|
|
|
NURBSext->SetKnotsFromPatches();
|
|
|
|
Dim = NURBSext->Dimension();
|
|
spaceDim = Nodes->FESpace()->GetVDim();
|
|
|
|
if (NumOfElements != NURBSext->GetNE())
|
|
{
|
|
for (int i = 0; i < elements.Size(); i++)
|
|
{
|
|
FreeElement(elements[i]);
|
|
}
|
|
NumOfElements = NURBSext->GetNE();
|
|
NURBSext->GetElementTopo(elements);
|
|
}
|
|
|
|
if (NumOfBdrElements != NURBSext->GetNBE())
|
|
{
|
|
for (int i = 0; i < boundary.Size(); i++)
|
|
{
|
|
FreeElement(boundary[i]);
|
|
}
|
|
NumOfBdrElements = NURBSext->GetNBE();
|
|
NURBSext->GetBdrElementTopo(boundary);
|
|
}
|
|
|
|
Nodes->FESpace()->Update();
|
|
Nodes->Update();
|
|
NodesUpdated();
|
|
const int vdim = Nodes->FESpace()->GetVDim();
|
|
NURBSext->SetCoordsFromPatches(*Nodes, vdim);
|
|
|
|
if (NumOfVertices != NURBSext->GetNV())
|
|
{
|
|
NumOfVertices = NURBSext->GetNV();
|
|
vertices.SetSize(NumOfVertices);
|
|
int vd = Nodes->VectorDim();
|
|
for (int i = 0; i < vd; i++)
|
|
{
|
|
Vector vert_val;
|
|
Nodes->GetNodalValues(vert_val, i+1);
|
|
for (int j = 0; j < NumOfVertices; j++)
|
|
{
|
|
vertices[j](i) = vert_val(j);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (el_to_edge)
|
|
{
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
|
|
if (el_to_face)
|
|
{
|
|
GetElementToFaceTable();
|
|
}
|
|
GenerateFaces();
|
|
}
|
|
|
|
void Mesh::LoadPatchTopo(std::istream &input, Array<int> &edge_to_ukv)
|
|
{
|
|
SetEmpty();
|
|
|
|
// Read MFEM NURBS mesh v1.0 or 1.1 format
|
|
string ident;
|
|
|
|
skip_comment_lines(input, '#');
|
|
|
|
input >> ident; // 'dimension'
|
|
input >> Dim;
|
|
spaceDim = Dim;
|
|
|
|
skip_comment_lines(input, '#');
|
|
|
|
input >> ident; // 'elements'
|
|
input >> NumOfElements;
|
|
elements.SetSize(NumOfElements);
|
|
for (int j = 0; j < NumOfElements; j++)
|
|
{
|
|
elements[j] = ReadElement(input);
|
|
}
|
|
|
|
skip_comment_lines(input, '#');
|
|
|
|
input >> ident; // 'boundary'
|
|
input >> NumOfBdrElements;
|
|
boundary.SetSize(NumOfBdrElements);
|
|
for (int j = 0; j < NumOfBdrElements; j++)
|
|
{
|
|
boundary[j] = ReadElement(input);
|
|
}
|
|
|
|
skip_comment_lines(input, '#');
|
|
|
|
input >> ident; // 'edges'
|
|
input >> NumOfEdges;
|
|
if (NumOfEdges > 0)
|
|
{
|
|
edge_vertex = new Table(NumOfEdges, 2);
|
|
edge_to_ukv.SetSize(NumOfEdges);
|
|
for (int j = 0; j < NumOfEdges; j++)
|
|
{
|
|
int *v = edge_vertex->GetRow(j);
|
|
input >> edge_to_ukv[j] >> v[0] >> v[1];
|
|
if (v[0] > v[1])
|
|
{
|
|
edge_to_ukv[j] = FlipIndexSign(edge_to_ukv[j]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
edge_to_ukv.SetSize(0);
|
|
}
|
|
|
|
skip_comment_lines(input, '#');
|
|
|
|
input >> ident; // 'vertices'
|
|
input >> NumOfVertices;
|
|
vertices.SetSize(0);
|
|
|
|
FinalizeTopology();
|
|
CheckBdrElementOrientation(); // check and fix boundary element orientation
|
|
|
|
/* Generate edge to knotvector mapping if edges are not specified in the
|
|
mesh file. See miniapps/nurbs/meshes/two-squares-nurbs-autoedge.mesh
|
|
for an example */
|
|
if (edge_to_ukv.Size() == 0)
|
|
{
|
|
Array<int> ukv_to_rpkv;
|
|
GetEdgeToUniqueKnotvector(edge_to_ukv, ukv_to_rpkv);
|
|
}
|
|
|
|
CorrectPatchTopoOrientations(edge_to_ukv);
|
|
}
|
|
|
|
void Mesh::GetEdgeToUniqueKnotvector(Array<int> &edge_to_ukv,
|
|
Array<int> &ukv_to_rpkv) const
|
|
{
|
|
const int dim = Dimension(); // topological (not physical) dimension
|
|
const int NP = NumOfElements; // number of patches
|
|
const int NPKV = NP * dim; // number of patch knotvectors
|
|
constexpr int notset = -9999999;
|
|
// Local edge index -> dimension convention
|
|
auto edge_to_dim = [](int i) { return (i < 8) ? ((i & 1) ? 1 : 0) : 2; };
|
|
|
|
Array<int> v(2); // vertices of an edge
|
|
|
|
// 1D case is special: edge index = signed element index
|
|
// ukv_to_rpkv = Identity
|
|
if (dim == 1)
|
|
{
|
|
edge_to_ukv.SetSize(NP);
|
|
ukv_to_rpkv.SetSize(NP);
|
|
for (int i = 0; i < NP; i++)
|
|
{
|
|
GetElementVertices(i, v);
|
|
// Sign is based on the edge's vertex indices
|
|
edge_to_ukv[i] = (v[1] > v[0]) ? i : FlipIndexSign(i);
|
|
ukv_to_rpkv[i] = i;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Local (per-patch) variables
|
|
Array<int> edges, oedges;
|
|
// Edge index -> signed patch knotvector index (p*dim + d)
|
|
Array<int> edge_to_pkv(NumOfEdges);
|
|
edge_to_pkv.SetSize(NumOfEdges);
|
|
edge_to_pkv = notset;
|
|
|
|
// Initialize pkv_map as identity - this is the storage for the
|
|
// disjoint-set/union-find algorithm which will later be used
|
|
// to get the map pkv_to_rpkv
|
|
Array<int> pkv_map(NPKV);
|
|
for (int i = 0; i < NPKV; i++)
|
|
{
|
|
pkv_map[i] = i;
|
|
}
|
|
std::function<int(int)> get_root;
|
|
get_root = [&pkv_map, &get_root](int i) -> int
|
|
{
|
|
return (pkv_map[i] == i) ? i : get_root(pkv_map[i]);
|
|
};
|
|
auto unite = [&pkv_map, &get_root](int i, int j)
|
|
{
|
|
const int ri = get_root(i);
|
|
const int rj = get_root(j);
|
|
if (ri == rj) { return; }
|
|
// keep the lowest index
|
|
(ri < rj) ? pkv_map[rj] = ri : pkv_map[ri] = rj;
|
|
};
|
|
|
|
// Get edge_to_pkv (one edge can link to multiple pkv) and pkv_map
|
|
for (int p = 0; p < NP; p++)
|
|
{
|
|
GetElementEdges(p, edges, oedges);
|
|
|
|
// First loop checks for if edge has already been set
|
|
for (int i = 0; i < edges.Size(); i++)
|
|
{
|
|
const int edge = edges[i];
|
|
const int d = edge_to_dim(i);
|
|
const int pkv = p*dim+d;
|
|
|
|
// We've set this edge already - link this index to it
|
|
if (edge_to_pkv[edge] != notset)
|
|
{
|
|
const int pkv_other = UnsignIndex(edge_to_pkv[edge]);
|
|
unite(pkv, pkv_other);
|
|
}
|
|
else
|
|
{
|
|
GetEdgeVertices(edge, v);
|
|
// Sign is based on the edge's vertex indices
|
|
edge_to_pkv[edge] = (v[1] > v[0]) ? pkv : FlipIndexSign(pkv);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct the pkv_to_rpkv map by finding the lowest/root index
|
|
Array<int> pkv_to_rpkv(NPKV);
|
|
ukv_to_rpkv.SetSize(NPKV);
|
|
for (int i = 0; i < NPKV; i++)
|
|
{
|
|
pkv_to_rpkv[i] = get_root(pkv_map[i]);
|
|
ukv_to_rpkv[i] = pkv_to_rpkv[i];
|
|
}
|
|
ukv_to_rpkv.Sort(); // ukv is just a renumbering of rpkv
|
|
ukv_to_rpkv.Unique();
|
|
|
|
// Create inverse map
|
|
std::map<int, int> rpkv_to_ukv;
|
|
for (int i = 0; i < ukv_to_rpkv.Size(); i++)
|
|
{
|
|
rpkv_to_ukv[ukv_to_rpkv[i]] = i;
|
|
}
|
|
|
|
// Get edge_to_ukv = edge_to_pkv -> pkv_to_rpkv -> rpkv_to_ukv
|
|
edge_to_ukv.SetSize(NumOfEdges);
|
|
for (int i = 0; i < NumOfEdges; i++)
|
|
{
|
|
const int pkv = UnsignIndex(edge_to_pkv[i]);
|
|
const int rpkv = pkv_to_rpkv[pkv];
|
|
const int ukv = rpkv_to_ukv[rpkv];
|
|
edge_to_ukv[i] = (edge_to_pkv[i] < 0) ? FlipIndexSign(ukv) : ukv;
|
|
}
|
|
|
|
CorrectPatchTopoOrientations(edge_to_ukv);
|
|
}
|
|
|
|
void Mesh::CorrectPatchTopoOrientations(Array<int> &edge_to_ukv) const
|
|
{
|
|
const int dim = Dimension(); // Topological (not physical) dimension
|
|
if (dim == 1) { return; }
|
|
|
|
const Table *face2elem = GetFaceToElementTable();
|
|
Array<int> pfaces, orient;
|
|
Array<int> fe, feo;
|
|
|
|
// Finds elements sharing a face containing knotvector kv.
|
|
auto faceNeighbors = [&](int p, int kv, std::unordered_set<int> &nghb)
|
|
{
|
|
if (dim == 2) { GetElementEdges(p, pfaces, orient); }
|
|
else { GetElementFaces(p, pfaces, orient); }
|
|
|
|
for (auto face : pfaces)
|
|
{
|
|
// Check whether this face contains kv.
|
|
GetFaceEdges(face, fe, feo);
|
|
bool hasKV = false;
|
|
for (auto e : fe)
|
|
{
|
|
const int skv = edge_to_ukv[e];
|
|
if (skv == kv || FlipIndexSign(skv) == kv) { hasKV = true; }
|
|
}
|
|
if (hasKV)
|
|
{
|
|
Array<int> row;
|
|
face2elem->GetRow(face, row);
|
|
for (auto elem : row) { nghb.insert(elem); }
|
|
}
|
|
}
|
|
};
|
|
|
|
std::vector<std::vector<int>> dir_edges;
|
|
if (dim == 2)
|
|
{
|
|
dir_edges =
|
|
{
|
|
{0,2},
|
|
{1,3}
|
|
};
|
|
}
|
|
else
|
|
{
|
|
dir_edges =
|
|
{
|
|
{0,2,4,6},
|
|
{1,3,5,7},
|
|
{8,9,10,11}
|
|
};
|
|
}
|
|
|
|
Array<int> ukvs((dim == 2) ? 4 : 12);
|
|
Array<int> pe, oe;
|
|
bool initKV = false;
|
|
|
|
auto setPatchDirections = [&](int p, int kv, Array<bool> &edgeSet,
|
|
std::unordered_set<int> &visited)
|
|
{
|
|
// Edges and orientations for this patch
|
|
GetElementEdges(p, pe, oe);
|
|
|
|
// Get the signed unique knot vector indices
|
|
for (int i = 0; i < pe.Size(); i++)
|
|
{
|
|
ukvs[i] = edge_to_ukv[pe[i]];
|
|
ukvs[i] = (oe[i] < 0) ? FlipIndexSign(ukvs[i]) : ukvs[i];
|
|
}
|
|
|
|
// Find the direction with this kv.
|
|
int thisDir = -1;
|
|
for (int d=0; d<dim; ++d) // Loop over directions.
|
|
{
|
|
const int skv = edge_to_ukv[pe[dir_edges[d][0]]];
|
|
if (skv == kv || FlipIndexSign(skv) == kv)
|
|
{
|
|
for (auto e : dir_edges[d])
|
|
if (!edgeSet[pe[e]])
|
|
{
|
|
thisDir = d;
|
|
}
|
|
}
|
|
}
|
|
if (thisDir == -1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// For this direction, find any edge already set. If no edge is set, we
|
|
// arbitrarily take the first.
|
|
int ref_edge0 = dir_edges[thisDir][0];
|
|
for (auto ref_edge : dir_edges[thisDir])
|
|
{
|
|
const int edge = pe[ref_edge];
|
|
if (edgeSet[edge])
|
|
{
|
|
ref_edge0 = ref_edge;
|
|
}
|
|
}
|
|
|
|
if (initKV && !edgeSet[pe[ref_edge0]])
|
|
{
|
|
visited.erase(p);
|
|
return false; // There is no set edge in this direction on this patch.
|
|
}
|
|
|
|
initKV = true;
|
|
|
|
// Use ref_edge0 to set other edges in this direction.
|
|
edgeSet[pe[ref_edge0]] = true;
|
|
for (auto i : dir_edges[thisDir])
|
|
{
|
|
if (i == ref_edge0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const int edge = pe[i];
|
|
if ((dim == 2 && ukvs[i] != FlipIndexSign(ukvs[ref_edge0])) ||
|
|
(dim == 3 && ukvs[i] == FlipIndexSign(ukvs[ref_edge0])))
|
|
{
|
|
// Flip the sign of this edge
|
|
MFEM_ASSERT(!edgeSet[edge], "");
|
|
edge_to_ukv[edge] = FlipIndexSign(edge_to_ukv[edge]);
|
|
}
|
|
|
|
edgeSet[edge] = true;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
Array<bool> edgeSet(NumOfEdges); // Whether edge has orientation set
|
|
edgeSet = false;
|
|
|
|
std::unordered_set<int> unset; // Patches with an unset edge
|
|
for (int i=0; i<NumOfElements; ++i) { unset.insert(i); }
|
|
|
|
const int max_iter = 3 * NumOfElements;
|
|
for (int iter=0; iter<max_iter; ++iter)
|
|
{
|
|
// Iteratively choose an unset patch (meaning not all edges have
|
|
// orientation set), choose a knotvector index for which the corresponding
|
|
// edges on this patch are not set, and sweep over all patches containing
|
|
// this knotvector. The patch sweep is ordered, by maintaining an ordered
|
|
// list `nextPatches` set by finding face-neighbor patches of visited
|
|
// patches, where the common face contains the knotvector. When each patch
|
|
// is visited, the edge orientations are set consistently. This iteration
|
|
// terminates when all edges have been set on all patches.
|
|
|
|
std::list<int> nextPatches; // Next patches to visit, ordered
|
|
std::unordered_set<int> nextSet; // nextPatches as a set
|
|
std::unordered_set<int> visited; // Visit each patch only once
|
|
|
|
if (unset.size() == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
const int p0 = *unset.begin();
|
|
nextPatches.push_back(p0); // Start from arbitrary unset patch
|
|
nextSet.insert(p0);
|
|
|
|
// Choose an arbitrary unset direction for the first patch.
|
|
GetElementEdges(p0, pe, oe);
|
|
int unsetDim = -1;
|
|
for (int d=0; d<dim; ++d) // Loop over dimensions.
|
|
{
|
|
for (auto e : dir_edges[d])
|
|
if (!edgeSet[pe[e]])
|
|
{
|
|
unsetDim = d;
|
|
}
|
|
}
|
|
|
|
if (unsetDim == -1)
|
|
{
|
|
unset.erase(p0);
|
|
continue;
|
|
}
|
|
|
|
const int kv = UnsignIndex(edge_to_ukv[pe[dir_edges[unsetDim][0]]]);
|
|
|
|
initKV = false;
|
|
|
|
while (nextPatches.size() > 0)
|
|
{
|
|
const int p = nextPatches.front();
|
|
nextPatches.pop_front();
|
|
nextSet.erase(p);
|
|
visited.insert(p);
|
|
|
|
const bool somethingSet = setPatchDirections(p, kv, edgeSet, visited);
|
|
if (!somethingSet)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Find neighbors of patch p sharing a conforming face, via face2elem.
|
|
std::unordered_set<int> neighbors;
|
|
faceNeighbors(p, kv, neighbors);
|
|
|
|
bool allSet = true;
|
|
GetElementEdges(p, pe, oe);
|
|
for (auto edge : pe)
|
|
{
|
|
if (!edgeSet[edge])
|
|
{
|
|
allSet = false;
|
|
}
|
|
}
|
|
if (allSet)
|
|
{
|
|
unset.erase(p);
|
|
}
|
|
|
|
// Add neighbors not done to nextPatches.
|
|
for (auto n : neighbors)
|
|
{
|
|
if (n != p && visited.count(n) == 0 && unset.count(n) > 0)
|
|
{
|
|
if (nextSet.count(n) == 0)
|
|
{
|
|
nextPatches.push_back(n);
|
|
nextSet.insert(n);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
bool allSet = true;
|
|
for (auto eset : edgeSet)
|
|
{
|
|
if (!eset)
|
|
{
|
|
allSet = false;
|
|
}
|
|
}
|
|
MFEM_ASSERT(allSet && unset.size() == 0, "Some edge is not set");
|
|
#endif
|
|
|
|
delete face2elem;
|
|
}
|
|
|
|
void Mesh::LoadNonconformingPatchTopo(std::istream &input,
|
|
Array<int> &edge_to_ukv)
|
|
{
|
|
SetEmpty();
|
|
|
|
// Read MFEM NURBS NC-patch mesh v1.0 format
|
|
int curved = 0;
|
|
int is_nc = 1;
|
|
|
|
ncmesh = new NCMesh(input, 10, curved, is_nc);
|
|
|
|
InitFromNCMesh(*ncmesh);
|
|
|
|
skip_comment_lines(input, '#');
|
|
|
|
string ident;
|
|
int inputNumOfEdges = -1;
|
|
|
|
input >> ident; // 'edges'
|
|
input >> inputNumOfEdges;
|
|
|
|
MFEM_VERIFY(NumOfEdges == inputNumOfEdges, "");
|
|
|
|
edge_to_ukv.SetSize(NumOfEdges);
|
|
for (int j = 0; j < NumOfEdges; j++)
|
|
{
|
|
int v[2]; // Vertex indices
|
|
int ukv; // Unique KnotVector index
|
|
input >> ukv >> v[0] >> v[1];
|
|
|
|
for (int i=0; i<2; ++i)
|
|
{
|
|
v[i] = ncmesh->vertex_nodeId[v[i]];
|
|
}
|
|
|
|
if (v[0] > v[1])
|
|
{
|
|
ukv = FlipIndexSign(ukv);
|
|
}
|
|
edge_to_ukv[j] = ukv;
|
|
}
|
|
|
|
FinalizeTopology();
|
|
CheckBdrElementOrientation(); // check and fix boundary element orientation
|
|
}
|
|
|
|
void XYZ_VectorFunction(const Vector &p, Vector &v)
|
|
{
|
|
if (p.Size() >= v.Size())
|
|
{
|
|
for (int d = 0; d < v.Size(); d++)
|
|
{
|
|
v(d) = p(d);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int d;
|
|
for (d = 0; d < p.Size(); d++)
|
|
{
|
|
v(d) = p(d);
|
|
}
|
|
for ( ; d < v.Size(); d++)
|
|
{
|
|
v(d) = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::GetNodes(GridFunction &nodes) const
|
|
{
|
|
if (Nodes == NULL || Nodes->FESpace() != nodes.FESpace())
|
|
{
|
|
const int newSpaceDim = nodes.FESpace()->GetVDim();
|
|
VectorFunctionCoefficient xyz(newSpaceDim, XYZ_VectorFunction);
|
|
nodes.ProjectCoefficient(xyz);
|
|
}
|
|
else
|
|
{
|
|
nodes = *Nodes;
|
|
}
|
|
}
|
|
|
|
void Mesh::SetNodalFESpace(FiniteElementSpace *nfes)
|
|
{
|
|
GridFunction *nodes = new GridFunction(nfes);
|
|
SetNodalGridFunction(nodes, true);
|
|
}
|
|
|
|
void Mesh::EnsureNodes()
|
|
{
|
|
if (Nodes)
|
|
{
|
|
const FiniteElementCollection *fec = GetNodalFESpace()->FEColl();
|
|
if (dynamic_cast<const H1_FECollection*>(fec)
|
|
|| dynamic_cast<const L2_FECollection*>(fec))
|
|
{
|
|
return;
|
|
}
|
|
else // Mesh using a legacy FE_Collection
|
|
{
|
|
const int order = GetNodalFESpace()->GetMaxElementOrder();
|
|
if (NURBSext)
|
|
{
|
|
#ifndef MFEM_USE_MPI
|
|
const bool warn = true;
|
|
#else
|
|
ParMesh *pmesh = dynamic_cast<ParMesh*>(this);
|
|
const bool warn = !pmesh || pmesh->GetMyRank() == 0;
|
|
#endif
|
|
if (warn)
|
|
{
|
|
MFEM_WARNING("converting NURBS mesh to order " << order <<
|
|
" H1-continuous mesh!\n "
|
|
"If this is the desired behavior, you can silence"
|
|
" this warning by converting\n "
|
|
"the NURBS mesh to high-order mesh in advance by"
|
|
" calling the method\n "
|
|
"Mesh::SetCurvature().");
|
|
}
|
|
}
|
|
SetCurvature(order, false, -1, Ordering::byVDIM);
|
|
}
|
|
}
|
|
else // First order H1 mesh
|
|
{
|
|
SetCurvature(1, false, -1, Ordering::byVDIM);
|
|
}
|
|
}
|
|
|
|
void Mesh::SetNodalGridFunction(GridFunction *nodes, bool make_owner)
|
|
{
|
|
GetNodes(*nodes);
|
|
NewNodes(*nodes, make_owner);
|
|
}
|
|
|
|
const FiniteElementSpace *Mesh::GetNodalFESpace() const
|
|
{
|
|
return ((Nodes) ? Nodes->FESpace() : NULL);
|
|
}
|
|
|
|
void Mesh::SetCurvature(int order, bool discont, int space_dim, int ordering)
|
|
{
|
|
if (order <= 0)
|
|
{
|
|
delete Nodes;
|
|
Nodes = nullptr;
|
|
return;
|
|
}
|
|
space_dim = (space_dim == -1) ? spaceDim : space_dim;
|
|
FiniteElementCollection* nfec;
|
|
if (discont)
|
|
{
|
|
const int type = 1; // Gauss-Lobatto points
|
|
nfec = new L2_FECollection(order, Dim, type);
|
|
}
|
|
else
|
|
{
|
|
nfec = new H1_FECollection(order, Dim);
|
|
}
|
|
FiniteElementSpace* nfes = new FiniteElementSpace(this, nfec, space_dim,
|
|
ordering);
|
|
|
|
const int old_space_dim = spaceDim;
|
|
SetNodalFESpace(nfes);
|
|
Nodes->MakeOwner(nfec);
|
|
|
|
if (spaceDim != old_space_dim)
|
|
{
|
|
// Fix dimension of the vertices if the space dimension changes
|
|
SetVerticesFromNodes(Nodes);
|
|
}
|
|
}
|
|
|
|
void Mesh::SetVerticesFromNodes(const GridFunction *nodes)
|
|
{
|
|
MFEM_ASSERT(nodes != NULL, "");
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mesh::GetNumFaces() const
|
|
{
|
|
switch (Dim)
|
|
{
|
|
case 1: return GetNV();
|
|
case 2: return GetNEdges();
|
|
case 3: return GetNFaces();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Mesh::GetNumFacesWithGhost() const
|
|
{
|
|
return faces_info.Size();
|
|
}
|
|
|
|
int Mesh::GetNFbyType(FaceType type) const
|
|
{
|
|
const bool isInt = type==FaceType::Interior;
|
|
int &nf = isInt ? nbInteriorFaces : nbBoundaryFaces;
|
|
if (nf<0)
|
|
{
|
|
nf = 0;
|
|
for (int f = 0; f < GetNumFacesWithGhost(); ++f)
|
|
{
|
|
FaceInformation face = GetFaceInformation(f);
|
|
if (face.IsOfFaceType(type))
|
|
{
|
|
if (face.IsNonconformingCoarse())
|
|
{
|
|
// We don't count nonconforming coarse faces.
|
|
continue;
|
|
}
|
|
nf++;
|
|
}
|
|
}
|
|
}
|
|
return nf;
|
|
}
|
|
|
|
#if (!defined(MFEM_USE_MPI) || defined(MFEM_DEBUG))
|
|
static const char *fixed_or_not[] = { "fixed", "NOT FIXED" };
|
|
#endif
|
|
|
|
int Mesh::CheckElementOrientation(bool fix_it)
|
|
{
|
|
int i, j, k, wo = 0, fo = 0;
|
|
real_t *v[4];
|
|
|
|
if (Dim == 2 && spaceDim == 2)
|
|
{
|
|
DenseMatrix J(2, 2);
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
int *vi = elements[i]->GetVertices();
|
|
if (Nodes == NULL)
|
|
{
|
|
for (j = 0; j < 3; j++)
|
|
{
|
|
v[j] = vertices[vi[j]]();
|
|
}
|
|
for (j = 0; j < 2; j++)
|
|
for (k = 0; k < 2; k++)
|
|
{
|
|
J(j, k) = v[j+1][k] - v[0][k];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// only check the Jacobian at the center of the element
|
|
GetElementJacobian(i, J);
|
|
}
|
|
if (J.Det() < 0.0)
|
|
{
|
|
if (fix_it)
|
|
{
|
|
switch (GetElementType(i))
|
|
{
|
|
case Element::TRIANGLE:
|
|
mfem::Swap(vi[0], vi[1]);
|
|
break;
|
|
case Element::QUADRILATERAL:
|
|
mfem::Swap(vi[1], vi[3]);
|
|
break;
|
|
default:
|
|
MFEM_ABORT("Invalid 2D element type \""
|
|
<< GetElementType(i) << "\"");
|
|
break;
|
|
}
|
|
fo++;
|
|
}
|
|
wo++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Dim == 3)
|
|
{
|
|
DenseMatrix J(3, 3);
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
int *vi = elements[i]->GetVertices();
|
|
switch (GetElementType(i))
|
|
{
|
|
case Element::TETRAHEDRON:
|
|
if (Nodes == NULL)
|
|
{
|
|
for (j = 0; j < 4; j++)
|
|
{
|
|
v[j] = vertices[vi[j]]();
|
|
}
|
|
for (j = 0; j < 3; j++)
|
|
for (k = 0; k < 3; k++)
|
|
{
|
|
J(j, k) = v[j+1][k] - v[0][k];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// only check the Jacobian at the center of the element
|
|
GetElementJacobian(i, J);
|
|
}
|
|
if (J.Det() < 0.0)
|
|
{
|
|
wo++;
|
|
if (fix_it)
|
|
{
|
|
mfem::Swap(vi[0], vi[1]);
|
|
fo++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Element::WEDGE:
|
|
// only check the Jacobian at the center of the element
|
|
GetElementJacobian(i, J);
|
|
if (J.Det() < 0.0)
|
|
{
|
|
wo++;
|
|
if (fix_it)
|
|
{
|
|
// how?
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Element::PYRAMID:
|
|
// only check the Jacobian at the center of the element
|
|
GetElementJacobian(i, J);
|
|
if (J.Det() < 0.0)
|
|
{
|
|
wo++;
|
|
if (fix_it)
|
|
{
|
|
mfem::Swap(vi[1], vi[3]);
|
|
fo++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Element::HEXAHEDRON:
|
|
// only check the Jacobian at the center of the element
|
|
GetElementJacobian(i, J);
|
|
if (J.Det() < 0.0)
|
|
{
|
|
wo++;
|
|
if (fix_it)
|
|
{
|
|
// how?
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MFEM_ABORT("Invalid 3D element type \""
|
|
<< GetElementType(i) << "\"");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#if (!defined(MFEM_USE_MPI) || defined(MFEM_DEBUG))
|
|
if (wo > 0)
|
|
{
|
|
mfem::out << "Elements with wrong orientation: " << wo << " / "
|
|
<< NumOfElements << " (" << fixed_or_not[(wo == fo) ? 0 : 1]
|
|
<< ")" << endl;
|
|
}
|
|
#else
|
|
MFEM_CONTRACT_VAR(fo);
|
|
#endif
|
|
return wo;
|
|
}
|
|
|
|
int Mesh::GetTriOrientation(const int *base, const int *test)
|
|
{
|
|
// Static method.
|
|
// This function computes the index 'j' of the permutation that transforms
|
|
// test into base: test[tri_orientation[j][i]]=base[i].
|
|
// tri_orientation = Geometry::Constants<Geometry::TRIANGLE>::Orient
|
|
int orient;
|
|
|
|
if (test[0] == base[0])
|
|
if (test[1] == base[1])
|
|
{
|
|
orient = 0; // (0, 1, 2)
|
|
}
|
|
else
|
|
{
|
|
orient = 5; // (0, 2, 1)
|
|
}
|
|
else if (test[0] == base[1])
|
|
if (test[1] == base[0])
|
|
{
|
|
orient = 1; // (1, 0, 2)
|
|
}
|
|
else
|
|
{
|
|
orient = 2; // (1, 2, 0)
|
|
}
|
|
else // test[0] == base[2]
|
|
if (test[1] == base[0])
|
|
{
|
|
orient = 4; // (2, 0, 1)
|
|
}
|
|
else
|
|
{
|
|
orient = 3; // (2, 1, 0)
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
const int *aor = tri_t::Orient[orient];
|
|
for (int j = 0; j < 3; j++)
|
|
if (test[aor[j]] != base[j])
|
|
{
|
|
mfem::err << "Mesh::GetTriOrientation(...)" << endl;
|
|
mfem::err << " base = [";
|
|
for (int k = 0; k < 3; k++)
|
|
{
|
|
mfem::err << " " << base[k];
|
|
}
|
|
mfem::err << " ]\n test = [";
|
|
for (int k = 0; k < 3; k++)
|
|
{
|
|
mfem::err << " " << test[k];
|
|
}
|
|
mfem::err << " ]" << endl;
|
|
mfem_error();
|
|
}
|
|
#endif
|
|
|
|
return orient;
|
|
}
|
|
|
|
int Mesh::ComposeTriOrientations(int ori_a_b, int ori_b_c)
|
|
{
|
|
// Static method.
|
|
// Given three, possibly different, configurations of triangular face
|
|
// vertices: va, vb, and vc. This function returns the relative orientation
|
|
// GetTriOrientation(va, vc) by composing previously computed orientations
|
|
// ori_a_b = GetTriOrientation(va, vb) and
|
|
// ori_b_c = GetTriOrientation(vb, vc) without accessing the vertices.
|
|
|
|
const int oo[6][6] =
|
|
{
|
|
{0, 1, 2, 3, 4, 5},
|
|
{1, 0, 5, 4, 3, 2},
|
|
{2, 3, 4, 5, 0, 1},
|
|
{3, 2, 1, 0, 5, 4},
|
|
{4, 5, 0, 1, 2, 3},
|
|
{5, 4, 3, 2, 1, 0}
|
|
};
|
|
|
|
int ori_a_c = oo[ori_a_b][ori_b_c];
|
|
return ori_a_c;
|
|
}
|
|
|
|
int Mesh::InvertTriOrientation(int ori)
|
|
{
|
|
const int inv_ori[6] = {0, 1, 4, 3, 2, 5};
|
|
return inv_ori[ori];
|
|
}
|
|
|
|
int Mesh::GetQuadOrientation(const int *base, const int *test)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
if (test[i] == base[0])
|
|
{
|
|
break;
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
int orient;
|
|
if (test[(i+1)%4] == base[1])
|
|
{
|
|
orient = 2*i;
|
|
}
|
|
else
|
|
{
|
|
orient = 2*i+1;
|
|
}
|
|
const int *aor = quad_t::Orient[orient];
|
|
for (int j = 0; j < 4; j++)
|
|
if (test[aor[j]] != base[j])
|
|
{
|
|
mfem::err << "Mesh::GetQuadOrientation(...)" << endl;
|
|
mfem::err << " base = [";
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
mfem::err << " " << base[k];
|
|
}
|
|
mfem::err << " ]\n test = [";
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
mfem::err << " " << test[k];
|
|
}
|
|
mfem::err << " ]" << endl;
|
|
mfem_error();
|
|
}
|
|
#endif
|
|
|
|
if (test[(i+1)%4] == base[1])
|
|
{
|
|
return 2*i;
|
|
}
|
|
|
|
return 2*i+1;
|
|
}
|
|
|
|
int Mesh::ComposeQuadOrientations(int ori_a_b, int ori_b_c)
|
|
{
|
|
// Static method.
|
|
// Given three, possibly different, configurations of quadrilateral face
|
|
// vertices: va, vb, and vc. This function returns the relative orientation
|
|
// GetQuadOrientation(va, vc) by composing previously computed orientations
|
|
// ori_a_b = GetQuadOrientation(va, vb) and
|
|
// ori_b_c = GetQuadOrientation(vb, vc) without accessing the vertices.
|
|
|
|
const int oo[8][8] =
|
|
{
|
|
{0, 1, 2, 3, 4, 5, 6, 7},
|
|
{1, 0, 3, 2, 5, 4, 7, 6},
|
|
{2, 7, 4, 1, 6, 3, 0, 5},
|
|
{3, 6, 5, 0, 7, 2, 1, 4},
|
|
{4, 5, 6, 7, 0, 1, 2, 3},
|
|
{5, 4, 7, 6, 1, 0, 3, 2},
|
|
{6, 3, 0, 5, 2, 7, 4, 1},
|
|
{7, 2, 1, 4, 3, 6, 5, 0}
|
|
};
|
|
|
|
int ori_a_c = oo[ori_a_b][ori_b_c];
|
|
return ori_a_c;
|
|
}
|
|
|
|
int Mesh::InvertQuadOrientation(int ori)
|
|
{
|
|
const int inv_ori[8] = {0, 1, 6, 3, 4, 5, 2, 7};
|
|
return inv_ori[ori];
|
|
}
|
|
|
|
int Mesh::GetTetOrientation(const int *base, const int *test)
|
|
{
|
|
// Static method.
|
|
// This function computes the index 'j' of the permutation that transforms
|
|
// test into base: test[tet_orientation[j][i]]=base[i].
|
|
// tet_orientation = Geometry::Constants<Geometry::TETRAHEDRON>::Orient
|
|
int orient;
|
|
|
|
if (test[0] == base[0])
|
|
if (test[1] == base[1])
|
|
if (test[2] == base[2])
|
|
{
|
|
orient = 0; // (0, 1, 2, 3)
|
|
}
|
|
else
|
|
{
|
|
orient = 1; // (0, 1, 3, 2)
|
|
}
|
|
else if (test[2] == base[1])
|
|
if (test[3] == base[2])
|
|
{
|
|
orient = 2; // (0, 2, 3, 1)
|
|
}
|
|
else
|
|
{
|
|
orient = 3; // (0, 2, 1, 3)
|
|
}
|
|
else // test[3] == base[1]
|
|
if (test[1] == base[2])
|
|
{
|
|
orient = 4; // (0, 3, 1, 2)
|
|
}
|
|
else
|
|
{
|
|
orient = 5; // (0, 3, 2, 1)
|
|
}
|
|
else if (test[1] == base[0])
|
|
if (test[2] == base[1])
|
|
if (test[0] == base[2])
|
|
{
|
|
orient = 6; // (1, 2, 0, 3)
|
|
}
|
|
else
|
|
{
|
|
orient = 7; // (1, 2, 3, 0)
|
|
}
|
|
else if (test[3] == base[1])
|
|
if (test[2] == base[2])
|
|
{
|
|
orient = 8; // (1, 3, 2, 0)
|
|
}
|
|
else
|
|
{
|
|
orient = 9; // (1, 3, 0, 2)
|
|
}
|
|
else // test[0] == base[1]
|
|
if (test[3] == base[2])
|
|
{
|
|
orient = 10; // (1, 0, 3, 2)
|
|
}
|
|
else
|
|
{
|
|
orient = 11; // (1, 0, 2, 3)
|
|
}
|
|
else if (test[2] == base[0])
|
|
if (test[3] == base[1])
|
|
if (test[0] == base[2])
|
|
{
|
|
orient = 12; // (2, 3, 0, 1)
|
|
}
|
|
else
|
|
{
|
|
orient = 13; // (2, 3, 1, 0)
|
|
}
|
|
else if (test[0] == base[1])
|
|
if (test[1] == base[2])
|
|
{
|
|
orient = 14; // (2, 0, 1, 3)
|
|
}
|
|
else
|
|
{
|
|
orient = 15; // (2, 0, 3, 1)
|
|
}
|
|
else // test[1] == base[1]
|
|
if (test[3] == base[2])
|
|
{
|
|
orient = 16; // (2, 1, 3, 0)
|
|
}
|
|
else
|
|
{
|
|
orient = 17; // (2, 1, 0, 3)
|
|
}
|
|
else // (test[3] == base[0])
|
|
if (test[0] == base[1])
|
|
if (test[2] == base[2])
|
|
{
|
|
orient = 18; // (3, 0, 2, 1)
|
|
}
|
|
else
|
|
{
|
|
orient = 19; // (3, 0, 1, 2)
|
|
}
|
|
else if (test[1] == base[1])
|
|
if (test[0] == base[2])
|
|
{
|
|
orient = 20; // (3, 1, 0, 2)
|
|
}
|
|
else
|
|
{
|
|
orient = 21; // (3, 1, 2, 0)
|
|
}
|
|
else // test[2] == base[1]
|
|
if (test[1] == base[2])
|
|
{
|
|
orient = 22; // (3, 2, 1, 0)
|
|
}
|
|
else
|
|
{
|
|
orient = 23; // (3, 2, 0, 1)
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
const int *aor = tet_t::Orient[orient];
|
|
for (int j = 0; j < 4; j++)
|
|
if (test[aor[j]] != base[j])
|
|
{
|
|
mfem_error("Mesh::GetTetOrientation(...)");
|
|
}
|
|
#endif
|
|
|
|
return orient;
|
|
}
|
|
|
|
int Mesh::CheckBdrElementOrientation(bool fix_it)
|
|
{
|
|
int wo = 0; // count wrong orientations
|
|
|
|
if (Dim == 2)
|
|
{
|
|
if (el_to_edge == NULL) // edges were not generated
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces(); // 'Faces' in 2D refers to the edges
|
|
}
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
if (faces_info[be_to_face[i]].Elem2No < 0) // boundary face
|
|
{
|
|
int *bv = boundary[i]->GetVertices();
|
|
int *fv = faces[be_to_face[i]]->GetVertices();
|
|
if (bv[0] != fv[0])
|
|
{
|
|
if (fix_it)
|
|
{
|
|
mfem::Swap<int>(bv[0], bv[1]);
|
|
}
|
|
wo++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Dim == 3)
|
|
{
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
const int fi = be_to_face[i];
|
|
|
|
if (faces_info[fi].Elem2No >= 0) { continue; }
|
|
|
|
// boundary face
|
|
int *bv = boundary[i]->GetVertices();
|
|
// Make sure the 'faces' are generated:
|
|
MFEM_ASSERT(fi < faces.Size(), "internal error");
|
|
const int *fv = faces[fi]->GetVertices();
|
|
int orientation; // orientation of the bdr. elem. w.r.t. the
|
|
// corresponding face element (that's the base)
|
|
const Element::Type bdr_type = GetBdrElementType(i);
|
|
switch (bdr_type)
|
|
{
|
|
case Element::TRIANGLE:
|
|
{
|
|
orientation = GetTriOrientation(fv, bv);
|
|
break;
|
|
}
|
|
case Element::QUADRILATERAL:
|
|
{
|
|
orientation = GetQuadOrientation(fv, bv);
|
|
break;
|
|
}
|
|
default:
|
|
MFEM_ABORT("Invalid 2D boundary element type \""
|
|
<< bdr_type << "\"");
|
|
orientation = 0; // suppress a warning
|
|
break;
|
|
}
|
|
|
|
if (orientation % 2 == 0) { continue; }
|
|
wo++;
|
|
if (!fix_it) { continue; }
|
|
|
|
switch (bdr_type)
|
|
{
|
|
case Element::TRIANGLE:
|
|
{
|
|
// swap vertices 0 and 1 so that we don't change the marked edge:
|
|
// (0,1,2) -> (1,0,2)
|
|
mfem::Swap(bv[0], bv[1]);
|
|
if (bel_to_edge)
|
|
{
|
|
int *be = bel_to_edge->GetRow(i);
|
|
mfem::Swap(be[1], be[2]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::QUADRILATERAL:
|
|
{
|
|
mfem::Swap(bv[0], bv[2]);
|
|
if (bel_to_edge)
|
|
{
|
|
int *be = bel_to_edge->GetRow(i);
|
|
mfem::Swap(be[0], be[1]);
|
|
mfem::Swap(be[2], be[3]);
|
|
}
|
|
break;
|
|
}
|
|
default: // unreachable
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// #if (!defined(MFEM_USE_MPI) || defined(MFEM_DEBUG))
|
|
#ifdef MFEM_DEBUG
|
|
if (wo > 0)
|
|
{
|
|
mfem::out << "Boundary elements with wrong orientation: " << wo << " / "
|
|
<< NumOfBdrElements << " (" << fixed_or_not[fix_it ? 0 : 1]
|
|
<< ")" << endl;
|
|
}
|
|
#endif
|
|
return wo;
|
|
}
|
|
|
|
IntegrationPoint Mesh::TransformBdrElementToFace(Geometry::Type geom, int o,
|
|
const IntegrationPoint &ip)
|
|
{
|
|
IntegrationPoint fip = ip;
|
|
if (geom == Geometry::POINT)
|
|
{
|
|
return fip;
|
|
}
|
|
else if (geom == Geometry::SEGMENT)
|
|
{
|
|
MFEM_ASSERT(o >= 0 && o < 2, "Invalid orientation for Geometry::SEGMENT!");
|
|
if (o == 0)
|
|
{
|
|
fip.x = ip.x;
|
|
}
|
|
else if (o == 1)
|
|
{
|
|
fip.x = 1.0 - ip.x;
|
|
}
|
|
}
|
|
else if (geom == Geometry::TRIANGLE)
|
|
{
|
|
MFEM_ASSERT(o >= 0 && o < 6, "Invalid orientation for Geometry::TRIANGLE!");
|
|
if (o == 0) // 0, 1, 2
|
|
{
|
|
fip.x = ip.x;
|
|
fip.y = ip.y;
|
|
}
|
|
else if (o == 5) // 0, 2, 1
|
|
{
|
|
fip.x = ip.y;
|
|
fip.y = ip.x;
|
|
}
|
|
else if (o == 2) // 1, 2, 0
|
|
{
|
|
fip.x = 1.0 - ip.x - ip.y;
|
|
fip.y = ip.x;
|
|
}
|
|
else if (o == 1) // 1, 0, 2
|
|
{
|
|
fip.x = 1.0 - ip.x - ip.y;
|
|
fip.y = ip.y;
|
|
}
|
|
else if (o == 4) // 2, 0, 1
|
|
{
|
|
fip.x = ip.y;
|
|
fip.y = 1.0 - ip.x - ip.y;
|
|
}
|
|
else if (o == 3) // 2, 1, 0
|
|
{
|
|
fip.x = ip.x;
|
|
fip.y = 1.0 - ip.x - ip.y;
|
|
}
|
|
}
|
|
else if (geom == Geometry::SQUARE)
|
|
{
|
|
MFEM_ASSERT(o >= 0 && o < 8, "Invalid orientation for Geometry::SQUARE!");
|
|
if (o == 0) // 0, 1, 2, 3
|
|
{
|
|
fip.x = ip.x;
|
|
fip.y = ip.y;
|
|
}
|
|
else if (o == 1) // 0, 3, 2, 1
|
|
{
|
|
fip.x = ip.y;
|
|
fip.y = ip.x;
|
|
}
|
|
else if (o == 2) // 1, 2, 3, 0
|
|
{
|
|
fip.x = ip.y;
|
|
fip.y = 1.0 - ip.x;
|
|
}
|
|
else if (o == 3) // 1, 0, 3, 2
|
|
{
|
|
fip.x = 1.0 - ip.x;
|
|
fip.y = ip.y;
|
|
}
|
|
else if (o == 4) // 2, 3, 0, 1
|
|
{
|
|
fip.x = 1.0 - ip.x;
|
|
fip.y = 1.0 - ip.y;
|
|
}
|
|
else if (o == 5) // 2, 1, 0, 3
|
|
{
|
|
fip.x = 1.0 - ip.y;
|
|
fip.y = 1.0 - ip.x;
|
|
}
|
|
else if (o == 6) // 3, 0, 1, 2
|
|
{
|
|
fip.x = 1.0 - ip.y;
|
|
fip.y = ip.x;
|
|
}
|
|
else if (o == 7) // 3, 2, 1, 0
|
|
{
|
|
fip.x = ip.x;
|
|
fip.y = 1.0 - ip.y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Unsupported face geometry for TransformBdrElementToFace!");
|
|
}
|
|
return fip;
|
|
}
|
|
|
|
int Mesh::GetNumGeometries(int dim) const
|
|
{
|
|
MFEM_ASSERT(0 <= dim && dim <= Dim, "invalid dim: " << dim);
|
|
int num_geoms = 0;
|
|
for (int g = Geometry::DimStart[dim]; g < Geometry::DimStart[dim+1]; g++)
|
|
{
|
|
if (HasGeometry(Geometry::Type(g))) { num_geoms++; }
|
|
}
|
|
return num_geoms;
|
|
}
|
|
|
|
void Mesh::GetGeometries(int dim, Array<Geometry::Type> &el_geoms) const
|
|
{
|
|
MFEM_ASSERT(0 <= dim && dim <= Dim, "invalid dim: " << dim);
|
|
el_geoms.SetSize(0);
|
|
for (int g = Geometry::DimStart[dim]; g < Geometry::DimStart[dim+1]; g++)
|
|
{
|
|
if (HasGeometry(Geometry::Type(g)))
|
|
{
|
|
el_geoms.Append(Geometry::Type(g));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Mesh::IsMixedMesh() const
|
|
{
|
|
// Return true if meshgen has more than one bit set, zero otherwise
|
|
return meshgen & (meshgen - 1);
|
|
}
|
|
|
|
void Mesh::GetElementEdges(int i, Array<int> &edges, Array<int> &cor) const
|
|
{
|
|
if (Dim == 1)
|
|
{
|
|
// In 1D, elements are segments and can be treated as edges.
|
|
edges.SetSize(1);
|
|
cor.SetSize(1);
|
|
edges[0] = i;
|
|
const int *v = elements[i]->GetVertices();
|
|
cor[0] = (v[0] < v[1]) ? (1) : (-1);
|
|
return;
|
|
}
|
|
|
|
if (el_to_edge)
|
|
{
|
|
el_to_edge->GetRow(i, edges);
|
|
}
|
|
else
|
|
{
|
|
mfem_error("Mesh::GetElementEdges(...) element to edge table "
|
|
"is not generated.");
|
|
}
|
|
|
|
const int *v = elements[i]->GetVertices();
|
|
const int ne = elements[i]->GetNEdges();
|
|
cor.SetSize(ne);
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
const int *e = elements[i]->GetEdgeVertices(j);
|
|
cor[j] = (v[e[0]] < v[e[1]]) ? (1) : (-1);
|
|
}
|
|
}
|
|
|
|
void Mesh::GetBdrElementEdges(int i, Array<int> &edges, Array<int> &cor) const
|
|
{
|
|
if (Dim == 2)
|
|
{
|
|
edges.SetSize(1);
|
|
cor.SetSize(1);
|
|
edges[0] = be_to_face[i];
|
|
const int *v = boundary[i]->GetVertices();
|
|
cor[0] = (v[0] < v[1]) ? (1) : (-1);
|
|
}
|
|
else if (Dim == 3)
|
|
{
|
|
if (bel_to_edge)
|
|
{
|
|
bel_to_edge->GetRow(i, edges);
|
|
}
|
|
else
|
|
{
|
|
mfem_error("Mesh::GetBdrElementEdges(...)");
|
|
}
|
|
|
|
const int *v = boundary[i]->GetVertices();
|
|
const int ne = boundary[i]->GetNEdges();
|
|
cor.SetSize(ne);
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
const int *e = boundary[i]->GetEdgeVertices(j);
|
|
cor[j] = (v[e[0]] < v[e[1]]) ? (1) : (-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::GetFaceEdges(int i, Array<int> &edges, Array<int> &o) const
|
|
{
|
|
if (Dim == 2)
|
|
{
|
|
edges.SetSize(1);
|
|
edges[0] = i;
|
|
o.SetSize(1);
|
|
const int *v = faces[i]->GetVertices();
|
|
o[0] = (v[0] < v[1]) ? (1) : (-1);
|
|
}
|
|
|
|
if (Dim != 3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetFaceEdgeTable(); // generate face_edge Table (if not generated)
|
|
|
|
face_edge->GetRow(i, edges);
|
|
|
|
const int *v = faces[i]->GetVertices();
|
|
const int ne = faces[i]->GetNEdges();
|
|
o.SetSize(ne);
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
const int *e = faces[i]->GetEdgeVertices(j);
|
|
o[j] = (v[e[0]] < v[e[1]]) ? (1) : (-1);
|
|
}
|
|
}
|
|
|
|
void Mesh::GetEdgeVertices(int i, Array<int> &vert) const
|
|
{
|
|
// the two vertices are sorted: vert[0] < vert[1]
|
|
// this is consistent with the global edge orientation
|
|
// generate edge_vertex Table (if not generated)
|
|
if (!edge_vertex) { GetEdgeVertexTable(); }
|
|
edge_vertex->GetRow(i, vert);
|
|
}
|
|
|
|
Table *Mesh::GetFaceEdgeTable() const
|
|
{
|
|
if (face_edge)
|
|
{
|
|
return face_edge;
|
|
}
|
|
|
|
if (Dim != 3)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
if (faces.Size() != NumOfFaces)
|
|
{
|
|
mfem_error("Mesh::GetFaceEdgeTable : faces were not generated!");
|
|
}
|
|
#endif
|
|
|
|
DSTable v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(v_to_v);
|
|
|
|
face_edge = new Table;
|
|
GetElementArrayEdgeTable(faces, v_to_v, *face_edge);
|
|
|
|
return (face_edge);
|
|
}
|
|
|
|
Table *Mesh::GetEdgeVertexTable() const
|
|
{
|
|
if (edge_vertex)
|
|
{
|
|
return edge_vertex;
|
|
}
|
|
|
|
DSTable v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(v_to_v);
|
|
|
|
int nedges = v_to_v.NumberOfEntries();
|
|
edge_vertex = new Table(nedges, 2);
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
for (DSTable::RowIterator it(v_to_v, i); !it; ++it)
|
|
{
|
|
int j = it.Index();
|
|
edge_vertex->Push(j, i);
|
|
edge_vertex->Push(j, it.Column());
|
|
}
|
|
}
|
|
edge_vertex->Finalize();
|
|
|
|
return edge_vertex;
|
|
}
|
|
|
|
Table *Mesh::GetVertexToElementTable()
|
|
{
|
|
Table *vert_elem = new Table;
|
|
|
|
vert_elem->MakeI(NumOfVertices);
|
|
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const int nv = elements[i]->GetNVertices();
|
|
const int *v = elements[i]->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
vert_elem->AddAColumnInRow(v[j]);
|
|
}
|
|
}
|
|
|
|
vert_elem->MakeJ();
|
|
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const int nv = elements[i]->GetNVertices();
|
|
const int *v = elements[i]->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
vert_elem->AddConnection(v[j], i);
|
|
}
|
|
}
|
|
|
|
vert_elem->ShiftUpI();
|
|
|
|
return vert_elem;
|
|
}
|
|
|
|
Table *Mesh::GetVertexToBdrElementTable()
|
|
{
|
|
Table *vert_bdr_elem = new Table;
|
|
|
|
vert_bdr_elem->MakeI(NumOfVertices);
|
|
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
const int nv = boundary[i]->GetNVertices();
|
|
const int *v = boundary[i]->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
vert_bdr_elem->AddAColumnInRow(v[j]);
|
|
}
|
|
}
|
|
|
|
vert_bdr_elem->MakeJ();
|
|
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
const int nv = boundary[i]->GetNVertices();
|
|
const int *v = boundary[i]->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
vert_bdr_elem->AddConnection(v[j], i);
|
|
}
|
|
}
|
|
|
|
vert_bdr_elem->ShiftUpI();
|
|
|
|
return vert_bdr_elem;
|
|
}
|
|
|
|
Table *Mesh::GetFaceToElementTable() const
|
|
{
|
|
Table *face_elem = new Table;
|
|
|
|
face_elem->MakeI(faces_info.Size());
|
|
|
|
for (int i = 0; i < faces_info.Size(); i++)
|
|
{
|
|
if (faces_info[i].Elem2No >= 0)
|
|
{
|
|
face_elem->AddColumnsInRow(i, 2);
|
|
}
|
|
else
|
|
{
|
|
face_elem->AddAColumnInRow(i);
|
|
}
|
|
}
|
|
|
|
face_elem->MakeJ();
|
|
|
|
for (int i = 0; i < faces_info.Size(); i++)
|
|
{
|
|
face_elem->AddConnection(i, faces_info[i].Elem1No);
|
|
if (faces_info[i].Elem2No >= 0)
|
|
{
|
|
face_elem->AddConnection(i, faces_info[i].Elem2No);
|
|
}
|
|
}
|
|
|
|
face_elem->ShiftUpI();
|
|
|
|
return face_elem;
|
|
}
|
|
|
|
void Mesh::GetElementFaces(int i, Array<int> &el_faces, Array<int> &ori) const
|
|
{
|
|
MFEM_VERIFY(el_to_face != NULL, "el_to_face not generated");
|
|
|
|
el_to_face->GetRow(i, el_faces);
|
|
|
|
int n = el_faces.Size();
|
|
ori.SetSize(n);
|
|
|
|
for (int j = 0; j < n; j++)
|
|
{
|
|
if (faces_info[el_faces[j]].Elem1No == i)
|
|
{
|
|
ori[j] = faces_info[el_faces[j]].Elem1Inf % 64;
|
|
}
|
|
else
|
|
{
|
|
MFEM_ASSERT(faces_info[el_faces[j]].Elem2No == i, "internal error");
|
|
ori[j] = faces_info[el_faces[j]].Elem2Inf % 64;
|
|
}
|
|
}
|
|
}
|
|
|
|
Array<int> Mesh::FindFaceNeighbors(const int elem) const
|
|
{
|
|
if (face_to_elem == NULL)
|
|
{
|
|
face_to_elem = GetFaceToElementTable();
|
|
}
|
|
|
|
Array<int> elem_faces;
|
|
Array<int> ori;
|
|
GetElementFaces(elem, elem_faces, ori);
|
|
|
|
Array<int> nghb;
|
|
for (auto f : elem_faces)
|
|
{
|
|
Array<int> row;
|
|
face_to_elem->GetRow(f, row);
|
|
for (auto r : row)
|
|
{
|
|
nghb.Append(r);
|
|
}
|
|
}
|
|
|
|
nghb.Sort();
|
|
nghb.Unique();
|
|
|
|
return nghb;
|
|
}
|
|
|
|
void Mesh::GetBdrElementFace(int i, int *f, int *o) const
|
|
{
|
|
*f = GetBdrElementFaceIndex(i);
|
|
|
|
const int *fv = (Dim > 1) ? faces[*f]->GetVertices() : NULL;
|
|
const int *bv = boundary[i]->GetVertices();
|
|
|
|
// find the orientation of the bdr. elem. w.r.t.
|
|
// the corresponding face element (that's the base)
|
|
switch (GetBdrElementGeometry(i))
|
|
{
|
|
case Geometry::POINT: *o = 0; break;
|
|
case Geometry::SEGMENT: *o = (fv[0] == bv[0]) ? 0 : 1; break;
|
|
case Geometry::TRIANGLE: *o = GetTriOrientation(fv, bv); break;
|
|
case Geometry::SQUARE: *o = GetQuadOrientation(fv, bv); break;
|
|
default: MFEM_ABORT("invalid geometry");
|
|
}
|
|
}
|
|
|
|
void Mesh::GetBdrElementAdjacentElement(int bdr_el, int &el, int &info) const
|
|
{
|
|
int fid = GetBdrElementFaceIndex(bdr_el);
|
|
|
|
const FaceInfo &fi = faces_info[fid];
|
|
MFEM_ASSERT(fi.Elem1Inf % 64 == 0, "internal error"); // orientation == 0
|
|
|
|
const int *fv = (Dim > 1) ? faces[fid]->GetVertices() : NULL;
|
|
const int *bv = boundary[bdr_el]->GetVertices();
|
|
int ori;
|
|
switch (GetBdrElementGeometry(bdr_el))
|
|
{
|
|
case Geometry::POINT: ori = 0; break;
|
|
case Geometry::SEGMENT: ori = (fv[0] == bv[0]) ? 0 : 1; break;
|
|
case Geometry::TRIANGLE: ori = GetTriOrientation(fv, bv); break;
|
|
case Geometry::SQUARE: ori = GetQuadOrientation(fv, bv); break;
|
|
default: MFEM_ABORT("boundary element type not implemented"); ori = 0;
|
|
}
|
|
el = fi.Elem1No;
|
|
info = fi.Elem1Inf + ori;
|
|
}
|
|
|
|
void Mesh::GetBdrElementAdjacentElement2(
|
|
int bdr_el, int &el, int &info) const
|
|
{
|
|
int fid = GetBdrElementFaceIndex(bdr_el);
|
|
|
|
const FaceInfo &fi = faces_info[fid];
|
|
MFEM_ASSERT(fi.Elem1Inf % 64 == 0, "internal error"); // orientation == 0
|
|
|
|
const int *fv = (Dim > 1) ? faces[fid]->GetVertices() : NULL;
|
|
const int *bv = boundary[bdr_el]->GetVertices();
|
|
int ori;
|
|
switch (GetBdrElementGeometry(bdr_el))
|
|
{
|
|
case Geometry::POINT: ori = 0; break;
|
|
case Geometry::SEGMENT: ori = (fv[0] == bv[0]) ? 0 : 1; break;
|
|
case Geometry::TRIANGLE: ori = GetTriOrientation(bv, fv); break;
|
|
case Geometry::SQUARE: ori = GetQuadOrientation(bv, fv); break;
|
|
default: MFEM_ABORT("boundary element type not implemented"); ori = 0;
|
|
}
|
|
el = fi.Elem1No;
|
|
info = fi.Elem1Inf + ori;
|
|
}
|
|
|
|
void Mesh::SetAttribute(int i, int attr)
|
|
{
|
|
elements[i]->SetAttribute(attr);
|
|
if (elem_attrs_cache.Size() == GetNE())
|
|
{
|
|
// update the existing cache instead of deleting it
|
|
elem_attrs_cache.HostReadWrite();
|
|
elem_attrs_cache[i] = attr;
|
|
}
|
|
if (ncmesh) { ncmesh->SetAttribute(i, attr); }
|
|
}
|
|
|
|
Element::Type Mesh::GetElementType(int i) const
|
|
{
|
|
return elements[i]->GetType();
|
|
}
|
|
|
|
Element::Type Mesh::GetBdrElementType(int i) const
|
|
{
|
|
return boundary[i]->GetType();
|
|
}
|
|
|
|
void Mesh::GetPointMatrix(int i, DenseMatrix &pointmat) const
|
|
{
|
|
int k, j, nv;
|
|
const int *v;
|
|
|
|
v = elements[i]->GetVertices();
|
|
nv = elements[i]->GetNVertices();
|
|
|
|
pointmat.SetSize(spaceDim, nv);
|
|
for (k = 0; k < spaceDim; k++)
|
|
{
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
pointmat(k, j) = vertices[v[j]](k);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::GetBdrPointMatrix(int i,DenseMatrix &pointmat) const
|
|
{
|
|
int k, j, nv;
|
|
const int *v;
|
|
|
|
v = boundary[i]->GetVertices();
|
|
nv = boundary[i]->GetNVertices();
|
|
|
|
pointmat.SetSize(spaceDim, nv);
|
|
for (k = 0; k < spaceDim; k++)
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
pointmat(k, j) = vertices[v[j]](k);
|
|
}
|
|
}
|
|
|
|
real_t Mesh::GetLength(int i, int j) const
|
|
{
|
|
const real_t *vi = vertices[i]();
|
|
const real_t *vj = vertices[j]();
|
|
real_t length = 0.;
|
|
|
|
for (int k = 0; k < spaceDim; k++)
|
|
{
|
|
length += (vi[k]-vj[k])*(vi[k]-vj[k]);
|
|
}
|
|
|
|
return sqrt(length);
|
|
}
|
|
|
|
// static method
|
|
void Mesh::GetElementArrayEdgeTable(const Array<Element*> &elem_array,
|
|
const DSTable &v_to_v, Table &el_to_edge)
|
|
{
|
|
el_to_edge.MakeI(elem_array.Size());
|
|
for (int i = 0; i < elem_array.Size(); i++)
|
|
{
|
|
el_to_edge.AddColumnsInRow(i, elem_array[i]->GetNEdges());
|
|
}
|
|
el_to_edge.MakeJ();
|
|
for (int i = 0; i < elem_array.Size(); i++)
|
|
{
|
|
const int *v = elem_array[i]->GetVertices();
|
|
const int ne = elem_array[i]->GetNEdges();
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
const int *e = elem_array[i]->GetEdgeVertices(j);
|
|
el_to_edge.AddConnection(i, v_to_v(v[e[0]], v[e[1]]));
|
|
}
|
|
}
|
|
el_to_edge.ShiftUpI();
|
|
}
|
|
|
|
void Mesh::GetVertexToVertexTable(DSTable &v_to_v) const
|
|
{
|
|
if (edge_vertex)
|
|
{
|
|
for (int i = 0; i < edge_vertex->Size(); i++)
|
|
{
|
|
const int *v = edge_vertex->GetRow(i);
|
|
v_to_v.Push(v[0], v[1]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const int *v = elements[i]->GetVertices();
|
|
const int ne = elements[i]->GetNEdges();
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
const int *e = elements[i]->GetEdgeVertices(j);
|
|
v_to_v.Push(v[e[0]], v[e[1]]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mesh::GetElementToEdgeTable(Table &e_to_f)
|
|
{
|
|
int i, NumberOfEdges;
|
|
|
|
DSTable v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(v_to_v);
|
|
|
|
NumberOfEdges = v_to_v.NumberOfEntries();
|
|
|
|
// Fill the element to edge table
|
|
GetElementArrayEdgeTable(elements, v_to_v, e_to_f);
|
|
|
|
if (Dim == 2)
|
|
{
|
|
// Initialize the indices for the boundary elements.
|
|
be_to_face.SetSize(NumOfBdrElements);
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
const int *v = boundary[i]->GetVertices();
|
|
be_to_face[i] = v_to_v(v[0], v[1]);
|
|
}
|
|
}
|
|
else if (Dim == 3)
|
|
{
|
|
if (bel_to_edge == NULL)
|
|
{
|
|
bel_to_edge = new Table;
|
|
}
|
|
GetElementArrayEdgeTable(boundary, v_to_v, *bel_to_edge);
|
|
}
|
|
else
|
|
{
|
|
mfem_error("1D GetElementToEdgeTable is not yet implemented.");
|
|
}
|
|
|
|
// Return the number of edges
|
|
return NumberOfEdges;
|
|
}
|
|
|
|
const Table & Mesh::ElementToElementTable()
|
|
{
|
|
if (el_to_el)
|
|
{
|
|
return *el_to_el;
|
|
}
|
|
|
|
// Note that, for ParNCMeshes, faces_info will contain also the ghost faces
|
|
MFEM_ASSERT(faces_info.Size() >= GetNumFaces(), "faces were not generated!");
|
|
|
|
Array<Connection> conn;
|
|
conn.Reserve(2*faces_info.Size());
|
|
|
|
for (int i = 0; i < faces_info.Size(); i++)
|
|
{
|
|
const FaceInfo &fi = faces_info[i];
|
|
if (fi.Elem2No >= 0)
|
|
{
|
|
conn.Append(Connection(fi.Elem1No, fi.Elem2No));
|
|
conn.Append(Connection(fi.Elem2No, fi.Elem1No));
|
|
}
|
|
else if (fi.Elem2Inf >= 0)
|
|
{
|
|
int nbr_elem_idx = NumOfElements - 1 - fi.Elem2No;
|
|
conn.Append(Connection(fi.Elem1No, nbr_elem_idx));
|
|
conn.Append(Connection(nbr_elem_idx, fi.Elem1No));
|
|
}
|
|
}
|
|
|
|
conn.Sort();
|
|
conn.Unique();
|
|
el_to_el = new Table(NumOfElements, conn);
|
|
|
|
return *el_to_el;
|
|
}
|
|
|
|
const Table & Mesh::ElementToFaceTable() const
|
|
{
|
|
if (el_to_face == NULL)
|
|
{
|
|
mfem_error("Mesh::ElementToFaceTable()");
|
|
}
|
|
return *el_to_face;
|
|
}
|
|
|
|
const Table & Mesh::ElementToEdgeTable() const
|
|
{
|
|
if (el_to_edge == NULL)
|
|
{
|
|
mfem_error("Mesh::ElementToEdgeTable()");
|
|
}
|
|
return *el_to_edge;
|
|
}
|
|
|
|
void Mesh::AddPointFaceElement(int lf, int gf, int el)
|
|
{
|
|
if (faces[gf] == NULL) // this will be elem1
|
|
{
|
|
faces[gf] = new Point(&gf);
|
|
faces_info[gf].Elem1No = el;
|
|
faces_info[gf].Elem1Inf = 64 * lf; // face lf with orientation 0
|
|
faces_info[gf].Elem2No = -1; // in case there's no other side
|
|
faces_info[gf].Elem2Inf = -1; // face is not shared
|
|
}
|
|
else // this will be elem2
|
|
{
|
|
/* WARNING: Without the following check the mesh faces_info data structure
|
|
may contain unreliable data. Normally, the order in which elements are
|
|
processed could swap which elements appear as Elem1No and Elem2No. In
|
|
branched meshes, where more than two elements can meet at a given node,
|
|
the indices stored in Elem1No and Elem2No will be the first and last,
|
|
respectively, elements found which touch a given node. This can lead to
|
|
inconsistencies in any algorithms which rely on this data structure. To
|
|
properly support branched meshes this data structure should be extended
|
|
to support multiple elements per face. */
|
|
/*
|
|
MFEM_VERIFY(faces_info[gf].Elem2No < 0, "Invalid mesh topology. "
|
|
"Interior point found connecting 1D elements "
|
|
<< faces_info[gf].Elem1No << ", " << faces_info[gf].Elem2No
|
|
<< " and " << el << ".");
|
|
*/
|
|
faces_info[gf].Elem2No = el;
|
|
faces_info[gf].Elem2Inf = 64 * lf + 1;
|
|
}
|
|
}
|
|
|
|
void Mesh::AddSegmentFaceElement(int lf, int gf, int el, int v0, int v1)
|
|
{
|
|
if (faces[gf] == NULL) // this will be elem1
|
|
{
|
|
faces[gf] = new Segment(v0, v1);
|
|
faces_info[gf].Elem1No = el;
|
|
faces_info[gf].Elem1Inf = 64 * lf; // face lf with orientation 0
|
|
faces_info[gf].Elem2No = -1; // in case there's no other side
|
|
faces_info[gf].Elem2Inf = -1; // face is not shared
|
|
}
|
|
else // this will be elem2
|
|
{
|
|
MFEM_VERIFY(faces_info[gf].Elem2No < 0, "Invalid mesh topology. "
|
|
"Interior edge found between 2D elements "
|
|
<< faces_info[gf].Elem1No << ", " << faces_info[gf].Elem2No
|
|
<< " and " << el << ".");
|
|
int *v = faces[gf]->GetVertices();
|
|
faces_info[gf].Elem2No = el;
|
|
if (v[1] == v0 && v[0] == v1)
|
|
{
|
|
faces_info[gf].Elem2Inf = 64 * lf + 1;
|
|
}
|
|
else if (v[0] == v0 && v[1] == v1)
|
|
{
|
|
// Temporarily allow even edge orientations: see the remark in
|
|
// AddTriangleFaceElement().
|
|
// Also, in a non-orientable surface mesh, the orientation will be even
|
|
// for edges that connect elements with opposite orientations.
|
|
faces_info[gf].Elem2Inf = 64 * lf;
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("internal error");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::AddTriangleFaceElement(int lf, int gf, int el,
|
|
int v0, int v1, int v2)
|
|
{
|
|
if (faces[gf] == NULL) // this will be elem1
|
|
{
|
|
faces[gf] = new Triangle(v0, v1, v2);
|
|
faces_info[gf].Elem1No = el;
|
|
faces_info[gf].Elem1Inf = 64 * lf; // face lf with orientation 0
|
|
faces_info[gf].Elem2No = -1; // in case there's no other side
|
|
faces_info[gf].Elem2Inf = -1; // face is not shared
|
|
}
|
|
else // this will be elem2
|
|
{
|
|
MFEM_VERIFY(faces_info[gf].Elem2No < 0, "Invalid mesh topology. "
|
|
"Interior triangular face found connecting elements "
|
|
<< faces_info[gf].Elem1No << ", " << faces_info[gf].Elem2No
|
|
<< " and " << el << ".");
|
|
int orientation, vv[3] = { v0, v1, v2 };
|
|
orientation = GetTriOrientation(faces[gf]->GetVertices(), vv);
|
|
// In a valid mesh, we should have (orientation % 2 != 0), however, if
|
|
// one of the adjacent elements has wrong orientation, both face
|
|
// orientations can be even, until the element orientations are fixed.
|
|
// MFEM_ASSERT(orientation % 2 != 0, "");
|
|
faces_info[gf].Elem2No = el;
|
|
faces_info[gf].Elem2Inf = 64 * lf + orientation;
|
|
}
|
|
}
|
|
|
|
void Mesh::AddQuadFaceElement(int lf, int gf, int el,
|
|
int v0, int v1, int v2, int v3)
|
|
{
|
|
if (faces_info[gf].Elem1No < 0) // this will be elem1
|
|
{
|
|
faces[gf] = new Quadrilateral(v0, v1, v2, v3);
|
|
faces_info[gf].Elem1No = el;
|
|
faces_info[gf].Elem1Inf = 64 * lf; // face lf with orientation 0
|
|
faces_info[gf].Elem2No = -1; // in case there's no other side
|
|
faces_info[gf].Elem2Inf = -1; // face is not shared
|
|
}
|
|
else // this will be elem2
|
|
{
|
|
MFEM_VERIFY(faces_info[gf].Elem2No < 0, "Invalid mesh topology. "
|
|
"Interior quadrilateral face found connecting elements "
|
|
<< faces_info[gf].Elem1No << ", " << faces_info[gf].Elem2No
|
|
<< " and " << el << ".");
|
|
int vv[4] = { v0, v1, v2, v3 };
|
|
int oo = GetQuadOrientation(faces[gf]->GetVertices(), vv);
|
|
// Temporarily allow even face orientations: see the remark in
|
|
// AddTriangleFaceElement().
|
|
// MFEM_ASSERT(oo % 2 != 0, "");
|
|
faces_info[gf].Elem2No = el;
|
|
faces_info[gf].Elem2Inf = 64 * lf + oo;
|
|
}
|
|
}
|
|
|
|
void Mesh::GenerateFaces()
|
|
{
|
|
int nfaces = GetNumFaces();
|
|
for (auto &f : faces)
|
|
{
|
|
FreeElement(f);
|
|
}
|
|
|
|
// delete caches
|
|
face_indices[0].SetSize(0);
|
|
face_indices[1].SetSize(0);
|
|
inv_face_indices[0].clear();
|
|
inv_face_indices[1].clear();
|
|
|
|
// (re)generate the interior faces and the info for them
|
|
faces.SetSize(nfaces);
|
|
faces_info.SetSize(nfaces);
|
|
for (int i = 0; i < nfaces; ++i)
|
|
{
|
|
faces[i] = NULL;
|
|
faces_info[i].Elem1No = -1;
|
|
faces_info[i].NCFace = -1;
|
|
}
|
|
|
|
Array<int> v;
|
|
for (int i = 0; i < NumOfElements; ++i)
|
|
{
|
|
elements[i]->GetVertices(v);
|
|
if (Dim == 1)
|
|
{
|
|
AddPointFaceElement(0, v[0], i);
|
|
AddPointFaceElement(1, v[1], i);
|
|
}
|
|
else if (Dim == 2)
|
|
{
|
|
const int * const ef = el_to_edge->GetRow(i);
|
|
const int ne = elements[i]->GetNEdges();
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
const int *e = elements[i]->GetEdgeVertices(j);
|
|
AddSegmentFaceElement(j, ef[j], i, v[e[0]], v[e[1]]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const int * const ef = el_to_face->GetRow(i);
|
|
switch (GetElementType(i))
|
|
{
|
|
case Element::TETRAHEDRON:
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
const int *fv = tet_t::FaceVert[j];
|
|
AddTriangleFaceElement(j, ef[j], i,
|
|
v[fv[0]], v[fv[1]], v[fv[2]]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::WEDGE:
|
|
{
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
const int *fv = pri_t::FaceVert[j];
|
|
AddTriangleFaceElement(j, ef[j], i,
|
|
v[fv[0]], v[fv[1]], v[fv[2]]);
|
|
}
|
|
for (int j = 2; j < 5; j++)
|
|
{
|
|
const int *fv = pri_t::FaceVert[j];
|
|
AddQuadFaceElement(j, ef[j], i,
|
|
v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::PYRAMID:
|
|
{
|
|
for (int j = 0; j < 1; j++)
|
|
{
|
|
const int *fv = pyr_t::FaceVert[j];
|
|
AddQuadFaceElement(j, ef[j], i,
|
|
v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]);
|
|
}
|
|
for (int j = 1; j < 5; j++)
|
|
{
|
|
const int *fv = pyr_t::FaceVert[j];
|
|
AddTriangleFaceElement(j, ef[j], i,
|
|
v[fv[0]], v[fv[1]], v[fv[2]]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::HEXAHEDRON:
|
|
{
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
const int *fv = hex_t::FaceVert[j];
|
|
AddQuadFaceElement(j, ef[j], i,
|
|
v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
MFEM_ABORT("Unexpected type of Element.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::GenerateNCFaceInfo()
|
|
{
|
|
MFEM_VERIFY(ncmesh, "missing NCMesh.");
|
|
|
|
for (auto &x : faces_info)
|
|
{
|
|
x.NCFace = -1;
|
|
}
|
|
|
|
const NCMesh::NCList &list =
|
|
(Dim == 2) ? ncmesh->GetEdgeList() : ncmesh->GetFaceList();
|
|
|
|
nc_faces_info.SetSize(0);
|
|
nc_faces_info.Reserve(list.masters.Size() + list.slaves.Size());
|
|
|
|
int nfaces = GetNumFaces();
|
|
|
|
// add records for master faces
|
|
for (const NCMesh::Master &master : list.masters)
|
|
{
|
|
if (master.index >= nfaces) { continue; }
|
|
|
|
FaceInfo &master_fi = faces_info[master.index];
|
|
master_fi.NCFace = nc_faces_info.Size();
|
|
nc_faces_info.Append(NCFaceInfo(false, master.local, NULL));
|
|
// NOTE: one of the unused members stores local face no. to be used below
|
|
MFEM_ASSERT(master_fi.Elem2No == -1, "internal error");
|
|
MFEM_ASSERT(master_fi.Elem2Inf == -1, "internal error");
|
|
}
|
|
|
|
// add records for slave faces
|
|
for (const NCMesh::Slave &slave : list.slaves)
|
|
{
|
|
if (slave.index < 0 || // degenerate slave face
|
|
slave.index >= nfaces || // ghost slave
|
|
slave.master >= nfaces) // has ghost master
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FaceInfo &slave_fi = faces_info[slave.index];
|
|
FaceInfo &master_fi = faces_info[slave.master];
|
|
NCFaceInfo &master_nc = nc_faces_info[master_fi.NCFace];
|
|
|
|
slave_fi.NCFace = nc_faces_info.Size();
|
|
slave_fi.Elem2No = master_fi.Elem1No;
|
|
slave_fi.Elem2Inf = 64 * master_nc.MasterFace; // get lf no. stored above
|
|
// NOTE: In 3D, the orientation part of Elem2Inf is encoded in the point
|
|
// matrix. In 2D, the point matrix has the orientation of the parent
|
|
// edge, so its columns need to be flipped when applying it, see
|
|
// ApplyLocalSlaveTransformation.
|
|
|
|
nc_faces_info.Append(
|
|
NCFaceInfo(true, slave.master,
|
|
list.point_matrices[slave.geom][slave.matrix]));
|
|
}
|
|
}
|
|
|
|
STable3D *Mesh::GetFacesTable()
|
|
{
|
|
STable3D *faces_tbl = new STable3D(NumOfVertices);
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const int *v = elements[i]->GetVertices();
|
|
switch (GetElementType(i))
|
|
{
|
|
case Element::TETRAHEDRON:
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
const int *fv = tet_t::FaceVert[j];
|
|
faces_tbl->Push(v[fv[0]], v[fv[1]], v[fv[2]]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::PYRAMID:
|
|
{
|
|
for (int j = 0; j < 1; j++)
|
|
{
|
|
const int *fv = pyr_t::FaceVert[j];
|
|
faces_tbl->Push4(v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]);
|
|
}
|
|
for (int j = 1; j < 5; j++)
|
|
{
|
|
const int *fv = pyr_t::FaceVert[j];
|
|
faces_tbl->Push(v[fv[0]], v[fv[1]], v[fv[2]]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::WEDGE:
|
|
{
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
const int *fv = pri_t::FaceVert[j];
|
|
faces_tbl->Push(v[fv[0]], v[fv[1]], v[fv[2]]);
|
|
}
|
|
for (int j = 2; j < 5; j++)
|
|
{
|
|
const int *fv = pri_t::FaceVert[j];
|
|
faces_tbl->Push4(v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]);
|
|
}
|
|
break;
|
|
}
|
|
case Element::HEXAHEDRON:
|
|
{
|
|
// find the face by the vertices with the smallest 3 numbers
|
|
// z = 0, y = 0, x = 1, y = 1, x = 0, z = 1
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
const int *fv = hex_t::FaceVert[j];
|
|
faces_tbl->Push4(v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
MFEM_ABORT("Unexpected type of Element: " << GetElementType(i));
|
|
}
|
|
}
|
|
return faces_tbl;
|
|
}
|
|
|
|
STable3D *Mesh::GetElementToFaceTable(int ret_ftbl)
|
|
{
|
|
Array<int> v;
|
|
STable3D *faces_tbl;
|
|
|
|
if (el_to_face != NULL)
|
|
{
|
|
delete el_to_face;
|
|
}
|
|
el_to_face = new Table(NumOfElements, 6); // must be 6 for hexahedra
|
|
faces_tbl = new STable3D(NumOfVertices);
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
elements[i]->GetVertices(v);
|
|
switch (GetElementType(i))
|
|
{
|
|
case Element::TETRAHEDRON:
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
const int *fv = tet_t::FaceVert[j];
|
|
el_to_face->Push(
|
|
i, faces_tbl->Push(v[fv[0]], v[fv[1]], v[fv[2]]));
|
|
}
|
|
break;
|
|
}
|
|
case Element::WEDGE:
|
|
{
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
const int *fv = pri_t::FaceVert[j];
|
|
el_to_face->Push(
|
|
i, faces_tbl->Push(v[fv[0]], v[fv[1]], v[fv[2]]));
|
|
}
|
|
for (int j = 2; j < 5; j++)
|
|
{
|
|
const int *fv = pri_t::FaceVert[j];
|
|
el_to_face->Push(
|
|
i, faces_tbl->Push4(v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]));
|
|
}
|
|
break;
|
|
}
|
|
case Element::PYRAMID:
|
|
{
|
|
for (int j = 0; j < 1; j++)
|
|
{
|
|
const int *fv = pyr_t::FaceVert[j];
|
|
el_to_face->Push(
|
|
i, faces_tbl->Push4(v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]));
|
|
}
|
|
for (int j = 1; j < 5; j++)
|
|
{
|
|
const int *fv = pyr_t::FaceVert[j];
|
|
el_to_face->Push(
|
|
i, faces_tbl->Push(v[fv[0]], v[fv[1]], v[fv[2]]));
|
|
}
|
|
break;
|
|
}
|
|
case Element::HEXAHEDRON:
|
|
{
|
|
// find the face by the vertices with the smallest 3 numbers
|
|
// z = 0, y = 0, x = 1, y = 1, x = 0, z = 1
|
|
for (int j = 0; j < 6; j++)
|
|
{
|
|
const int *fv = hex_t::FaceVert[j];
|
|
el_to_face->Push(
|
|
i, faces_tbl->Push4(v[fv[0]], v[fv[1]], v[fv[2]], v[fv[3]]));
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
MFEM_ABORT("Unexpected type of Element.");
|
|
}
|
|
}
|
|
el_to_face->Finalize();
|
|
NumOfFaces = faces_tbl->NumberOfElements();
|
|
be_to_face.SetSize(NumOfBdrElements);
|
|
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
boundary[i]->GetVertices(v);
|
|
switch (GetBdrElementType(i))
|
|
{
|
|
case Element::TRIANGLE:
|
|
{
|
|
be_to_face[i] = (*faces_tbl)(v[0], v[1], v[2]);
|
|
break;
|
|
}
|
|
case Element::QUADRILATERAL:
|
|
{
|
|
be_to_face[i] = (*faces_tbl)(v[0], v[1], v[2], v[3]);
|
|
break;
|
|
}
|
|
default:
|
|
MFEM_ABORT("Unexpected type of boundary Element.");
|
|
}
|
|
}
|
|
|
|
if (ret_ftbl)
|
|
{
|
|
return faces_tbl;
|
|
}
|
|
delete faces_tbl;
|
|
return NULL;
|
|
}
|
|
|
|
// shift cyclically 3 integers so that the smallest is first
|
|
static inline
|
|
void Rotate3(int &a, int &b, int &c)
|
|
{
|
|
if (a < b)
|
|
{
|
|
if (a > c)
|
|
{
|
|
ShiftRight(a, b, c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (b < c)
|
|
{
|
|
ShiftRight(c, b, a);
|
|
}
|
|
else
|
|
{
|
|
ShiftRight(a, b, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::ReorientTetMesh()
|
|
{
|
|
if (Dim != 3 || !(meshgen & 1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ResetLazyData();
|
|
|
|
DSTable *old_v_to_v = NULL;
|
|
Table *old_elem_vert = NULL;
|
|
|
|
if (Nodes)
|
|
{
|
|
PrepareNodeReorder(&old_v_to_v, &old_elem_vert);
|
|
}
|
|
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
if (GetElementType(i) == Element::TETRAHEDRON)
|
|
{
|
|
int *v = elements[i]->GetVertices();
|
|
|
|
Rotate3(v[0], v[1], v[2]);
|
|
if (v[0] < v[3])
|
|
{
|
|
Rotate3(v[1], v[2], v[3]);
|
|
}
|
|
else
|
|
{
|
|
ShiftRight(v[0], v[1], v[3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
if (GetBdrElementType(i) == Element::TRIANGLE)
|
|
{
|
|
int *v = boundary[i]->GetVertices();
|
|
|
|
Rotate3(v[0], v[1], v[2]);
|
|
}
|
|
}
|
|
|
|
if (!Nodes)
|
|
{
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
if (el_to_edge)
|
|
{
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DoNodeReorder(old_v_to_v, old_elem_vert);
|
|
delete old_elem_vert;
|
|
delete old_v_to_v;
|
|
}
|
|
}
|
|
|
|
int *Mesh::CartesianPartitioning(int nxyz[])
|
|
{
|
|
int *partitioning;
|
|
real_t pmin[3] = { infinity(), infinity(), infinity() };
|
|
real_t pmax[3] = { -infinity(), -infinity(), -infinity() };
|
|
// find a bounding box using the vertices
|
|
for (int vi = 0; vi < NumOfVertices; vi++)
|
|
{
|
|
const real_t *p = vertices[vi]();
|
|
for (int i = 0; i < spaceDim; i++)
|
|
{
|
|
if (p[i] < pmin[i]) { pmin[i] = p[i]; }
|
|
if (p[i] > pmax[i]) { pmax[i] = p[i]; }
|
|
}
|
|
}
|
|
|
|
partitioning = new int[NumOfElements];
|
|
|
|
// determine the partitioning using the centers of the elements
|
|
real_t ppt[3];
|
|
Vector pt(ppt, spaceDim);
|
|
for (int el = 0; el < NumOfElements; el++)
|
|
{
|
|
GetElementTransformation(el)->Transform(
|
|
Geometries.GetCenter(GetElementBaseGeometry(el)), pt);
|
|
int part = 0;
|
|
for (int i = spaceDim-1; i >= 0; i--)
|
|
{
|
|
int idx = (int)floor(nxyz[i]*((pt(i) - pmin[i])/(pmax[i] - pmin[i])));
|
|
if (idx < 0) { idx = 0; }
|
|
if (idx >= nxyz[i]) { idx = nxyz[i]-1; }
|
|
part = part * nxyz[i] + idx;
|
|
}
|
|
partitioning[el] = part;
|
|
}
|
|
|
|
return partitioning;
|
|
}
|
|
|
|
void FindPartitioningComponents(Table &elem_elem,
|
|
const Array<int> &partitioning,
|
|
Array<int> &component,
|
|
Array<int> &num_comp);
|
|
|
|
int *Mesh::GeneratePartitioning(int nparts, int part_method)
|
|
{
|
|
#ifdef MFEM_USE_METIS
|
|
|
|
int print_messages = 1;
|
|
// If running in parallel, print messages only from rank 0.
|
|
#ifdef MFEM_USE_MPI
|
|
int init_flag, fin_flag;
|
|
MPI_Initialized(&init_flag);
|
|
MPI_Finalized(&fin_flag);
|
|
if (init_flag && !fin_flag)
|
|
{
|
|
int rank;
|
|
MPI_Comm_rank(GetGlobalMPI_Comm(), &rank);
|
|
if (rank != 0) { print_messages = 0; }
|
|
}
|
|
#endif
|
|
|
|
int i, *partitioning;
|
|
|
|
ElementToElementTable();
|
|
|
|
partitioning = new int[NumOfElements];
|
|
|
|
if (nparts == 1)
|
|
{
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
partitioning[i] = 0;
|
|
}
|
|
}
|
|
else if (NumOfElements <= nparts)
|
|
{
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
partitioning[i] = i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
idx_t *I, *J, n;
|
|
#ifndef MFEM_USE_METIS_5
|
|
idx_t wgtflag = 0;
|
|
idx_t numflag = 0;
|
|
idx_t options[5];
|
|
#else
|
|
idx_t ncon = 1;
|
|
idx_t errflag;
|
|
idx_t options[40];
|
|
#endif
|
|
idx_t edgecut;
|
|
|
|
// In case METIS have been compiled with 64bit indices
|
|
bool freedata = false;
|
|
idx_t mparts = (idx_t) nparts;
|
|
idx_t *mpartitioning;
|
|
|
|
n = NumOfElements;
|
|
if (sizeof(idx_t) == sizeof(int))
|
|
{
|
|
I = (idx_t*) el_to_el->GetI();
|
|
J = (idx_t*) el_to_el->GetJ();
|
|
mpartitioning = (idx_t*) partitioning;
|
|
}
|
|
else
|
|
{
|
|
int *iI = el_to_el->GetI();
|
|
int *iJ = el_to_el->GetJ();
|
|
int m = iI[n];
|
|
I = new idx_t[n+1];
|
|
J = new idx_t[m];
|
|
for (int k = 0; k < n+1; k++) { I[k] = iI[k]; }
|
|
for (int k = 0; k < m; k++) { J[k] = iJ[k]; }
|
|
mpartitioning = new idx_t[n];
|
|
freedata = true;
|
|
}
|
|
#ifndef MFEM_USE_METIS_5
|
|
options[0] = 0;
|
|
#else
|
|
METIS_SetDefaultOptions(options);
|
|
options[METIS_OPTION_CONTIG] = 1; // set METIS_OPTION_CONTIG
|
|
// If the mesh is disconnected, disable METIS_OPTION_CONTIG.
|
|
{
|
|
Array<int> part(partitioning, NumOfElements);
|
|
part = 0; // single part for the whole mesh
|
|
Array<int> component; // size will be set to num. elem.
|
|
Array<int> num_comp; // size will be set to num. parts (1)
|
|
FindPartitioningComponents(*el_to_el, part, component, num_comp);
|
|
if (num_comp[0] > 1) { options[METIS_OPTION_CONTIG] = 0; }
|
|
}
|
|
#endif
|
|
|
|
// Sort the neighbor lists
|
|
if (part_method >= 0 && part_method <= 2)
|
|
{
|
|
for (i = 0; i < n; i++)
|
|
{
|
|
// Sort in increasing order.
|
|
// std::sort(J+I[i], J+I[i+1]);
|
|
|
|
// Sort in decreasing order, as in previous versions of MFEM.
|
|
std::sort(J+I[i], J+I[i+1], std::greater<idx_t>());
|
|
}
|
|
}
|
|
|
|
// This function should be used to partition a graph into a small
|
|
// number of partitions (less than 8).
|
|
if (part_method == 0 || part_method == 3)
|
|
{
|
|
#ifndef MFEM_USE_METIS_5
|
|
METIS_PartGraphRecursive(&n,
|
|
I,
|
|
J,
|
|
NULL,
|
|
NULL,
|
|
&wgtflag,
|
|
&numflag,
|
|
&mparts,
|
|
options,
|
|
&edgecut,
|
|
mpartitioning);
|
|
#else
|
|
errflag = METIS_PartGraphRecursive(&n,
|
|
&ncon,
|
|
I,
|
|
J,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&mparts,
|
|
NULL,
|
|
NULL,
|
|
options,
|
|
&edgecut,
|
|
mpartitioning);
|
|
if (errflag != 1)
|
|
{
|
|
mfem_error("Mesh::GeneratePartitioning: "
|
|
" error in METIS_PartGraphRecursive!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// This function should be used to partition a graph into a large
|
|
// number of partitions (greater than 8).
|
|
if (part_method == 1 || part_method == 4)
|
|
{
|
|
#ifndef MFEM_USE_METIS_5
|
|
METIS_PartGraphKway(&n,
|
|
I,
|
|
J,
|
|
NULL,
|
|
NULL,
|
|
&wgtflag,
|
|
&numflag,
|
|
&mparts,
|
|
options,
|
|
&edgecut,
|
|
mpartitioning);
|
|
#else
|
|
errflag = METIS_PartGraphKway(&n,
|
|
&ncon,
|
|
I,
|
|
J,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&mparts,
|
|
NULL,
|
|
NULL,
|
|
options,
|
|
&edgecut,
|
|
mpartitioning);
|
|
if (errflag != 1)
|
|
{
|
|
mfem_error("Mesh::GeneratePartitioning: "
|
|
" error in METIS_PartGraphKway!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// The objective of this partitioning is to minimize the total
|
|
// communication volume
|
|
if (part_method == 2 || part_method == 5)
|
|
{
|
|
#ifndef MFEM_USE_METIS_5
|
|
METIS_PartGraphVKway(&n,
|
|
I,
|
|
J,
|
|
NULL,
|
|
NULL,
|
|
&wgtflag,
|
|
&numflag,
|
|
&mparts,
|
|
options,
|
|
&edgecut,
|
|
mpartitioning);
|
|
#else
|
|
options[METIS_OPTION_OBJTYPE] = METIS_OBJTYPE_VOL;
|
|
errflag = METIS_PartGraphKway(&n,
|
|
&ncon,
|
|
I,
|
|
J,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&mparts,
|
|
NULL,
|
|
NULL,
|
|
options,
|
|
&edgecut,
|
|
mpartitioning);
|
|
if (errflag != 1)
|
|
{
|
|
mfem_error("Mesh::GeneratePartitioning: "
|
|
" error in METIS_PartGraphKway!");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
if (print_messages)
|
|
{
|
|
mfem::out << "Mesh::GeneratePartitioning(...): edgecut = "
|
|
<< edgecut << endl;
|
|
}
|
|
#endif
|
|
nparts = (int) mparts;
|
|
if (mpartitioning != (idx_t*)partitioning)
|
|
{
|
|
for (int k = 0; k<NumOfElements; k++)
|
|
{
|
|
partitioning[k] = mpartitioning[k];
|
|
}
|
|
}
|
|
if (freedata)
|
|
{
|
|
delete[] I;
|
|
delete[] J;
|
|
delete[] mpartitioning;
|
|
}
|
|
}
|
|
|
|
delete el_to_el;
|
|
el_to_el = NULL;
|
|
|
|
// Check for empty partitionings (a "feature" in METIS)
|
|
if (nparts > 1 && NumOfElements > nparts)
|
|
{
|
|
Array< Pair<int,int> > psize(nparts);
|
|
int empty_parts;
|
|
|
|
// Count how many elements are in each partition, and store the result in
|
|
// psize, where psize[i].one is the number of elements, and psize[i].two
|
|
// is partition index. Keep track of the number of empty parts.
|
|
auto count_partition_elements = [&]()
|
|
{
|
|
for (i = 0; i < nparts; i++)
|
|
{
|
|
psize[i].one = 0;
|
|
psize[i].two = i;
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
psize[partitioning[i]].one++;
|
|
}
|
|
|
|
empty_parts = 0;
|
|
for (i = 0; i < nparts; i++)
|
|
{
|
|
if (psize[i].one == 0) { empty_parts++; }
|
|
}
|
|
};
|
|
|
|
count_partition_elements();
|
|
|
|
// This code just split the largest partitionings in two.
|
|
// Do we need to replace it with something better?
|
|
while (empty_parts)
|
|
{
|
|
if (print_messages)
|
|
{
|
|
mfem::err << "Mesh::GeneratePartitioning(...): METIS returned "
|
|
<< empty_parts << " empty parts!"
|
|
<< " Applying a simple fix ..." << endl;
|
|
}
|
|
|
|
SortPairs<int,int>(psize, nparts);
|
|
|
|
for (i = nparts-1; i > nparts-1-empty_parts; i--)
|
|
{
|
|
psize[i].one /= 2;
|
|
}
|
|
|
|
for (int j = 0; j < NumOfElements; j++)
|
|
{
|
|
for (i = nparts-1; i > nparts-1-empty_parts; i--)
|
|
{
|
|
if (psize[i].one == 0 || partitioning[j] != psize[i].two)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
partitioning[j] = psize[nparts-1-i].two;
|
|
psize[i].one--;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for empty partitionings again
|
|
count_partition_elements();
|
|
}
|
|
}
|
|
|
|
return partitioning;
|
|
|
|
#else
|
|
|
|
mfem_error("Mesh::GeneratePartitioning(...): "
|
|
"MFEM was compiled without Metis.");
|
|
|
|
return NULL;
|
|
|
|
#endif
|
|
}
|
|
|
|
/* required: 0 <= partitioning[i] < num_part */
|
|
void FindPartitioningComponents(Table &elem_elem,
|
|
const Array<int> &partitioning,
|
|
Array<int> &component,
|
|
Array<int> &num_comp)
|
|
{
|
|
int i, j, k;
|
|
int num_elem, *i_elem_elem, *j_elem_elem;
|
|
|
|
num_elem = elem_elem.Size();
|
|
i_elem_elem = elem_elem.GetI();
|
|
j_elem_elem = elem_elem.GetJ();
|
|
|
|
component.SetSize(num_elem);
|
|
|
|
Array<int> elem_stack(num_elem);
|
|
int stack_p, stack_top_p, elem;
|
|
int num_part;
|
|
|
|
num_part = -1;
|
|
for (i = 0; i < num_elem; i++)
|
|
{
|
|
if (partitioning[i] > num_part)
|
|
{
|
|
num_part = partitioning[i];
|
|
}
|
|
component[i] = -1;
|
|
}
|
|
num_part++;
|
|
|
|
num_comp.SetSize(num_part);
|
|
for (i = 0; i < num_part; i++)
|
|
{
|
|
num_comp[i] = 0;
|
|
}
|
|
|
|
stack_p = 0;
|
|
stack_top_p = 0; // points to the first unused element in the stack
|
|
for (elem = 0; elem < num_elem; elem++)
|
|
{
|
|
if (component[elem] >= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
component[elem] = num_comp[partitioning[elem]]++;
|
|
|
|
elem_stack[stack_top_p++] = elem;
|
|
|
|
for ( ; stack_p < stack_top_p; stack_p++)
|
|
{
|
|
i = elem_stack[stack_p];
|
|
for (j = i_elem_elem[i]; j < i_elem_elem[i+1]; j++)
|
|
{
|
|
k = j_elem_elem[j];
|
|
if (partitioning[k] == partitioning[i])
|
|
{
|
|
if (component[k] < 0)
|
|
{
|
|
component[k] = component[i];
|
|
elem_stack[stack_top_p++] = k;
|
|
}
|
|
else if (component[k] != component[i])
|
|
{
|
|
mfem_error("FindPartitioningComponents");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::CheckPartitioning(int *partitioning_)
|
|
{
|
|
int i, n_empty, n_mcomp;
|
|
Array<int> component, num_comp;
|
|
const Array<int> partitioning(partitioning_, GetNE());
|
|
|
|
ElementToElementTable();
|
|
|
|
FindPartitioningComponents(*el_to_el, partitioning, component, num_comp);
|
|
|
|
n_empty = n_mcomp = 0;
|
|
for (i = 0; i < num_comp.Size(); i++)
|
|
if (num_comp[i] == 0)
|
|
{
|
|
n_empty++;
|
|
}
|
|
else if (num_comp[i] > 1)
|
|
{
|
|
n_mcomp++;
|
|
}
|
|
|
|
if (n_empty > 0)
|
|
{
|
|
mfem::out << "Mesh::CheckPartitioning(...) :\n"
|
|
<< "The following subdomains are empty :\n";
|
|
for (i = 0; i < num_comp.Size(); i++)
|
|
if (num_comp[i] == 0)
|
|
{
|
|
mfem::out << ' ' << i;
|
|
}
|
|
mfem::out << endl;
|
|
}
|
|
if (n_mcomp > 0)
|
|
{
|
|
mfem::out << "Mesh::CheckPartitioning(...) :\n"
|
|
<< "The following subdomains are NOT connected :\n";
|
|
for (i = 0; i < num_comp.Size(); i++)
|
|
if (num_comp[i] > 1)
|
|
{
|
|
mfem::out << ' ' << i;
|
|
}
|
|
mfem::out << endl;
|
|
}
|
|
if (n_empty == 0 && n_mcomp == 0)
|
|
mfem::out << "Mesh::CheckPartitioning(...) : "
|
|
"All subdomains are connected." << endl;
|
|
|
|
if (el_to_el)
|
|
{
|
|
delete el_to_el;
|
|
}
|
|
el_to_el = NULL;
|
|
}
|
|
|
|
// compute the coefficients of the polynomial in t:
|
|
// c(0)+c(1)*t+...+c(d)*t^d = det(A+t*B)
|
|
// where A, B are (d x d), d=2,3
|
|
void DetOfLinComb(const DenseMatrix &A, const DenseMatrix &B, Vector &c)
|
|
{
|
|
const real_t *a = A.Data();
|
|
const real_t *b = B.Data();
|
|
|
|
c.SetSize(A.Width()+1);
|
|
switch (A.Width())
|
|
{
|
|
case 2:
|
|
{
|
|
// det(A+t*B) = |a0 a2| / |a0 b2| + |b0 a2| \ |b0 b2|
|
|
// |a1 a3| + \ |a1 b3| |b1 a3| / * t + |b1 b3| * t^2
|
|
c(0) = a[0]*a[3]-a[1]*a[2];
|
|
c(1) = a[0]*b[3]-a[1]*b[2]+b[0]*a[3]-b[1]*a[2];
|
|
c(2) = b[0]*b[3]-b[1]*b[2];
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
{
|
|
/* |a0 a3 a6|
|
|
* det(A+t*B) = |a1 a4 a7| +
|
|
* |a2 a5 a8|
|
|
|
|
* / |b0 a3 a6| |a0 b3 a6| |a0 a3 b6| \
|
|
* + | |b1 a4 a7| + |a1 b4 a7| + |a1 a4 b7| | * t +
|
|
* \ |b2 a5 a8| |a2 b5 a8| |a2 a5 b8| /
|
|
|
|
* / |a0 b3 b6| |b0 a3 b6| |b0 b3 a6| \
|
|
* + | |a1 b4 b7| + |b1 a4 b7| + |b1 b4 a7| | * t^2 +
|
|
* \ |a2 b5 b8| |b2 a5 b8| |b2 b5 a8| /
|
|
|
|
* |b0 b3 b6|
|
|
* + |b1 b4 b7| * t^3
|
|
* |b2 b5 b8| */
|
|
c(0) = (a[0] * (a[4] * a[8] - a[5] * a[7]) +
|
|
a[1] * (a[5] * a[6] - a[3] * a[8]) +
|
|
a[2] * (a[3] * a[7] - a[4] * a[6]));
|
|
|
|
c(1) = (b[0] * (a[4] * a[8] - a[5] * a[7]) +
|
|
b[1] * (a[5] * a[6] - a[3] * a[8]) +
|
|
b[2] * (a[3] * a[7] - a[4] * a[6]) +
|
|
|
|
a[0] * (b[4] * a[8] - b[5] * a[7]) +
|
|
a[1] * (b[5] * a[6] - b[3] * a[8]) +
|
|
a[2] * (b[3] * a[7] - b[4] * a[6]) +
|
|
|
|
a[0] * (a[4] * b[8] - a[5] * b[7]) +
|
|
a[1] * (a[5] * b[6] - a[3] * b[8]) +
|
|
a[2] * (a[3] * b[7] - a[4] * b[6]));
|
|
|
|
c(2) = (a[0] * (b[4] * b[8] - b[5] * b[7]) +
|
|
a[1] * (b[5] * b[6] - b[3] * b[8]) +
|
|
a[2] * (b[3] * b[7] - b[4] * b[6]) +
|
|
|
|
b[0] * (a[4] * b[8] - a[5] * b[7]) +
|
|
b[1] * (a[5] * b[6] - a[3] * b[8]) +
|
|
b[2] * (a[3] * b[7] - a[4] * b[6]) +
|
|
|
|
b[0] * (b[4] * a[8] - b[5] * a[7]) +
|
|
b[1] * (b[5] * a[6] - b[3] * a[8]) +
|
|
b[2] * (b[3] * a[7] - b[4] * a[6]));
|
|
|
|
c(3) = (b[0] * (b[4] * b[8] - b[5] * b[7]) +
|
|
b[1] * (b[5] * b[6] - b[3] * b[8]) +
|
|
b[2] * (b[3] * b[7] - b[4] * b[6]));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
mfem_error("DetOfLinComb(...)");
|
|
}
|
|
}
|
|
|
|
// compute the real roots of
|
|
// z(0)+z(1)*x+...+z(d)*x^d = 0, d=2,3;
|
|
// the roots are returned in x, sorted in increasing order;
|
|
// it is assumed that x is at least of size d;
|
|
// return the number of roots counting multiplicity;
|
|
// return -1 if all z(i) are 0.
|
|
int FindRoots(const Vector &z, Vector &x)
|
|
{
|
|
int d = z.Size()-1;
|
|
if (d > 3 || d < 0)
|
|
{
|
|
mfem_error("FindRoots(...)");
|
|
}
|
|
|
|
while (z(d) == 0.0)
|
|
{
|
|
if (d == 0)
|
|
{
|
|
return (-1);
|
|
}
|
|
d--;
|
|
}
|
|
switch (d)
|
|
{
|
|
case 0:
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
case 1:
|
|
{
|
|
x(0) = -z(0)/z(1);
|
|
return 1;
|
|
}
|
|
|
|
case 2:
|
|
{
|
|
real_t a = z(2), b = z(1), c = z(0);
|
|
real_t D = b*b-4*a*c;
|
|
if (D < 0.0)
|
|
{
|
|
return 0;
|
|
}
|
|
if (D == 0.0)
|
|
{
|
|
x(0) = x(1) = -0.5 * b / a;
|
|
return 2; // root with multiplicity 2
|
|
}
|
|
if (b == 0.0)
|
|
{
|
|
x(0) = -(x(1) = fabs(0.5 * sqrt(D) / a));
|
|
return 2;
|
|
}
|
|
else
|
|
{
|
|
real_t t;
|
|
if (b > 0.0)
|
|
{
|
|
t = -0.5 * (b + sqrt(D));
|
|
}
|
|
else
|
|
{
|
|
t = -0.5 * (b - sqrt(D));
|
|
}
|
|
x(0) = t / a;
|
|
x(1) = c / t;
|
|
if (x(0) > x(1))
|
|
{
|
|
Swap<real_t>(x(0), x(1));
|
|
}
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
case 3:
|
|
{
|
|
real_t a = z(2)/z(3), b = z(1)/z(3), c = z(0)/z(3);
|
|
|
|
// find the real roots of x^3 + a x^2 + b x + c = 0
|
|
real_t Q = (a * a - 3 * b) / 9;
|
|
real_t R = (2 * a * a * a - 9 * a * b + 27 * c) / 54;
|
|
real_t Q3 = Q * Q * Q;
|
|
real_t R2 = R * R;
|
|
|
|
if (R2 == Q3)
|
|
{
|
|
if (Q == 0)
|
|
{
|
|
x(0) = x(1) = x(2) = - a / 3;
|
|
}
|
|
else
|
|
{
|
|
real_t sqrtQ = sqrt(Q);
|
|
|
|
if (R > 0)
|
|
{
|
|
x(0) = -2 * sqrtQ - a / 3;
|
|
x(1) = x(2) = sqrtQ - a / 3;
|
|
}
|
|
else
|
|
{
|
|
x(0) = x(1) = - sqrtQ - a / 3;
|
|
x(2) = 2 * sqrtQ - a / 3;
|
|
}
|
|
}
|
|
return 3;
|
|
}
|
|
else if (R2 < Q3)
|
|
{
|
|
real_t theta = acos(R / sqrt(Q3));
|
|
real_t A = -2 * sqrt(Q);
|
|
real_t x0, x1, x2;
|
|
x0 = A * cos(theta / 3) - a / 3;
|
|
x1 = A * cos((theta + 2.0 * M_PI) / 3) - a / 3;
|
|
x2 = A * cos((theta - 2.0 * M_PI) / 3) - a / 3;
|
|
|
|
/* Sort x0, x1, x2 */
|
|
if (x0 > x1)
|
|
{
|
|
Swap<real_t>(x0, x1);
|
|
}
|
|
if (x1 > x2)
|
|
{
|
|
Swap<real_t>(x1, x2);
|
|
if (x0 > x1)
|
|
{
|
|
Swap<real_t>(x0, x1);
|
|
}
|
|
}
|
|
x(0) = x0;
|
|
x(1) = x1;
|
|
x(2) = x2;
|
|
return 3;
|
|
}
|
|
else
|
|
{
|
|
real_t A;
|
|
if (R >= 0.0)
|
|
{
|
|
A = -pow(sqrt(R2 - Q3) + R, 1.0/3.0);
|
|
}
|
|
else
|
|
{
|
|
A = pow(sqrt(R2 - Q3) - R, 1.0/3.0);
|
|
}
|
|
x(0) = A + Q / A - a / 3;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void FindTMax(Vector &c, Vector &x, real_t &tmax,
|
|
const real_t factor, const int Dim)
|
|
{
|
|
const real_t c0 = c(0);
|
|
c(0) = c0 * (1.0 - pow(factor, -Dim));
|
|
int nr = FindRoots(c, x);
|
|
for (int j = 0; j < nr; j++)
|
|
{
|
|
if (x(j) > tmax)
|
|
{
|
|
break;
|
|
}
|
|
if (x(j) >= 0.0)
|
|
{
|
|
tmax = x(j);
|
|
break;
|
|
}
|
|
}
|
|
c(0) = c0 * (1.0 - pow(factor, Dim));
|
|
nr = FindRoots(c, x);
|
|
for (int j = 0; j < nr; j++)
|
|
{
|
|
if (x(j) > tmax)
|
|
{
|
|
break;
|
|
}
|
|
if (x(j) >= 0.0)
|
|
{
|
|
tmax = x(j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::CheckDisplacements(const Vector &displacements, real_t &tmax)
|
|
{
|
|
int nvs = vertices.Size();
|
|
DenseMatrix P, V, DS, PDS(spaceDim), VDS(spaceDim);
|
|
Vector c(spaceDim+1), x(spaceDim);
|
|
const real_t factor = 2.0;
|
|
|
|
// check for tangling assuming constant speed
|
|
if (tmax < 1.0)
|
|
{
|
|
tmax = 1.0;
|
|
}
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
Element *el = elements[i];
|
|
int nv = el->GetNVertices();
|
|
int *v = el->GetVertices();
|
|
P.SetSize(spaceDim, nv);
|
|
V.SetSize(spaceDim, nv);
|
|
for (int j = 0; j < spaceDim; j++)
|
|
for (int k = 0; k < nv; k++)
|
|
{
|
|
P(j, k) = vertices[v[k]](j);
|
|
V(j, k) = displacements(v[k]+j*nvs);
|
|
}
|
|
DS.SetSize(nv, spaceDim);
|
|
const FiniteElement *fe =
|
|
GetTransformationFEforElementType(el->GetType());
|
|
// check if det(P.DShape+t*V.DShape) > 0 for all x and 0<=t<=1
|
|
switch (el->GetType())
|
|
{
|
|
case Element::TRIANGLE:
|
|
case Element::TETRAHEDRON:
|
|
{
|
|
// DS is constant
|
|
fe->CalcDShape(Geometries.GetCenter(fe->GetGeomType()), DS);
|
|
Mult(P, DS, PDS);
|
|
Mult(V, DS, VDS);
|
|
DetOfLinComb(PDS, VDS, c);
|
|
if (c(0) <= 0.0)
|
|
{
|
|
tmax = 0.0;
|
|
}
|
|
else
|
|
{
|
|
FindTMax(c, x, tmax, factor, Dim);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Element::QUADRILATERAL:
|
|
{
|
|
const IntegrationRule &ir = fe->GetNodes();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
fe->CalcDShape(ir.IntPoint(j), DS);
|
|
Mult(P, DS, PDS);
|
|
Mult(V, DS, VDS);
|
|
DetOfLinComb(PDS, VDS, c);
|
|
if (c(0) <= 0.0)
|
|
{
|
|
tmax = 0.0;
|
|
}
|
|
else
|
|
{
|
|
FindTMax(c, x, tmax, factor, Dim);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
mfem_error("Mesh::CheckDisplacements(...)");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::MoveVertices(const Vector &displacements)
|
|
{
|
|
for (int i = 0, nv = vertices.Size(); i < nv; i++)
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
vertices[i](j) += displacements(j*nv+i);
|
|
}
|
|
}
|
|
|
|
void Mesh::GetVertices(Vector &vert_coord) const
|
|
{
|
|
int nv = vertices.Size();
|
|
vert_coord.SetSize(nv*spaceDim);
|
|
for (int i = 0; i < nv; i++)
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
vert_coord(j*nv+i) = vertices[i](j);
|
|
}
|
|
}
|
|
|
|
void Mesh::SetVertices(const Vector &vert_coord)
|
|
{
|
|
MFEM_VERIFY(vert_coord.Size() == spaceDim * NumOfVertices, "");
|
|
vertices.SetSize(NumOfVertices);
|
|
for (int i = 0, nv = vertices.Size(); i < nv; i++)
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
vertices[i](j) = vert_coord(j*nv+i);
|
|
}
|
|
}
|
|
|
|
void Mesh::GetNode(int i, real_t *coord) const
|
|
{
|
|
if (Nodes)
|
|
{
|
|
FiniteElementSpace *fes = Nodes->FESpace();
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
coord[j] = AsConst(*Nodes)(fes->DofToVDof(i, j));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
coord[j] = vertices[i](j);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::SetNode(int i, const real_t *coord)
|
|
{
|
|
if (Nodes)
|
|
{
|
|
FiniteElementSpace *fes = Nodes->FESpace();
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
(*Nodes)(fes->DofToVDof(i, j)) = coord[j];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
vertices[i](j) = coord[j];
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void Mesh::MoveNodes(const Vector &displacements)
|
|
{
|
|
if (Nodes)
|
|
{
|
|
(*Nodes) += displacements;
|
|
}
|
|
else
|
|
{
|
|
MoveVertices(displacements);
|
|
}
|
|
|
|
// Invalidate the old geometric factors
|
|
NodesUpdated();
|
|
}
|
|
|
|
void Mesh::GetNodes(Vector &node_coord) const
|
|
{
|
|
if (Nodes)
|
|
{
|
|
node_coord = (*Nodes);
|
|
}
|
|
else
|
|
{
|
|
GetVertices(node_coord);
|
|
}
|
|
}
|
|
|
|
void Mesh::SetNodes(const Vector &node_coord)
|
|
{
|
|
if (Nodes)
|
|
{
|
|
(*Nodes) = node_coord;
|
|
}
|
|
else
|
|
{
|
|
SetVertices(node_coord);
|
|
}
|
|
|
|
// Invalidate the old geometric factors
|
|
NodesUpdated();
|
|
}
|
|
|
|
void Mesh::NewNodes(GridFunction &nodes, bool make_owner)
|
|
{
|
|
if (own_nodes) { delete Nodes; }
|
|
Nodes = &nodes;
|
|
spaceDim = Nodes->FESpace()->GetVDim();
|
|
own_nodes = (int)make_owner;
|
|
|
|
if (NURBSext != nodes.FESpace()->GetNURBSext())
|
|
{
|
|
delete NURBSext;
|
|
NURBSext = nodes.FESpace()->StealNURBSext();
|
|
}
|
|
|
|
if (ncmesh)
|
|
{
|
|
ncmesh->MakeTopologyOnly();
|
|
}
|
|
|
|
// Invalidate the old geometric factors
|
|
NodesUpdated();
|
|
}
|
|
|
|
void Mesh::SwapNodes(GridFunction *&nodes, int &own_nodes_)
|
|
{
|
|
// If this is a nonconforming mesh without nodes, ncmesh->coordinates will
|
|
// be non-empty; so if the 'nodes' argument is not NULL, we will create an
|
|
// inconsistent state where the Mesh has nodes and ncmesh->coordinates is not
|
|
// empty. This was creating an issue for Mesh::Print() since both the
|
|
// "coordinates" and "nodes" sections were written, leading to crashes during
|
|
// loading. This issue is now fixed in Mesh::Printer() by temporarily
|
|
// swapping ncmesh->coordinates with an empty array when the Mesh has nodes.
|
|
|
|
mfem::Swap<GridFunction*>(Nodes, nodes);
|
|
mfem::Swap<int>(own_nodes, own_nodes_);
|
|
// TODO:
|
|
// if (nodes)
|
|
// nodes->FESpace()->MakeNURBSextOwner();
|
|
// NURBSext = (Nodes) ? Nodes->FESpace()->StealNURBSext() : NULL;
|
|
|
|
// Invalidate the old geometric factors
|
|
NodesUpdated();
|
|
}
|
|
|
|
void Mesh::AverageVertices(const int *indexes, int n, int result)
|
|
{
|
|
int j, k;
|
|
|
|
for (k = 0; k < spaceDim; k++)
|
|
{
|
|
vertices[result](k) = vertices[indexes[0]](k);
|
|
}
|
|
|
|
for (j = 1; j < n; j++)
|
|
for (k = 0; k < spaceDim; k++)
|
|
{
|
|
vertices[result](k) += vertices[indexes[j]](k);
|
|
}
|
|
|
|
for (k = 0; k < spaceDim; k++)
|
|
{
|
|
vertices[result](k) *= (1.0 / n);
|
|
}
|
|
}
|
|
|
|
void Mesh::UpdateNodes()
|
|
{
|
|
if (Nodes)
|
|
{
|
|
Nodes->FESpace()->Update();
|
|
Nodes->Update();
|
|
|
|
// update vertex coordinates for compatibility (e.g., GetVertex())
|
|
SetVerticesFromNodes(Nodes);
|
|
|
|
// Invalidate the old geometric factors
|
|
NodesUpdated();
|
|
}
|
|
}
|
|
|
|
void Mesh::UniformRefinement2D_base(bool update_nodes)
|
|
{
|
|
ResetLazyData();
|
|
|
|
if (el_to_edge == NULL)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
|
|
int quad_counter = 0;
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
if (elements[i]->GetType() == Element::QUADRILATERAL)
|
|
{
|
|
quad_counter++;
|
|
}
|
|
}
|
|
|
|
const int oedge = NumOfVertices;
|
|
const int oelem = oedge + NumOfEdges;
|
|
|
|
Array<Element*> new_elements;
|
|
Array<Element*> new_boundary;
|
|
|
|
vertices.SetSize(oelem + quad_counter);
|
|
new_elements.SetSize(4 * NumOfElements);
|
|
quad_counter = 0;
|
|
|
|
for (int i = 0, j = 0; i < NumOfElements; i++)
|
|
{
|
|
const Element::Type el_type = elements[i]->GetType();
|
|
const int attr = elements[i]->GetAttribute();
|
|
int *v = elements[i]->GetVertices();
|
|
const int *e = el_to_edge->GetRow(i);
|
|
int vv[2];
|
|
|
|
if (el_type == Element::TRIANGLE)
|
|
{
|
|
for (int ei = 0; ei < 3; ei++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
vv[k] = v[tri_t::Edges[ei][k]];
|
|
}
|
|
AverageVertices(vv, 2, oedge+e[ei]);
|
|
}
|
|
|
|
new_elements[j++] =
|
|
new Triangle(v[0], oedge+e[0], oedge+e[2], attr);
|
|
new_elements[j++] =
|
|
new Triangle(oedge+e[1], oedge+e[2], oedge+e[0], attr);
|
|
new_elements[j++] =
|
|
new Triangle(oedge+e[0], v[1], oedge+e[1], attr);
|
|
new_elements[j++] =
|
|
new Triangle(oedge+e[2], oedge+e[1], v[2], attr);
|
|
}
|
|
else if (el_type == Element::QUADRILATERAL)
|
|
{
|
|
const int qe = quad_counter;
|
|
quad_counter++;
|
|
AverageVertices(v, 4, oelem+qe);
|
|
|
|
for (int ei = 0; ei < 4; ei++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
vv[k] = v[quad_t::Edges[ei][k]];
|
|
}
|
|
AverageVertices(vv, 2, oedge+e[ei]);
|
|
}
|
|
|
|
new_elements[j++] =
|
|
new Quadrilateral(v[0], oedge+e[0], oelem+qe, oedge+e[3], attr);
|
|
new_elements[j++] =
|
|
new Quadrilateral(oedge+e[0], v[1], oedge+e[1], oelem+qe, attr);
|
|
new_elements[j++] =
|
|
new Quadrilateral(oelem+qe, oedge+e[1], v[2], oedge+e[2], attr);
|
|
new_elements[j++] =
|
|
new Quadrilateral(oedge+e[3], oelem+qe, oedge+e[2], v[3], attr);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("unknown element type: " << el_type);
|
|
}
|
|
FreeElement(elements[i]);
|
|
}
|
|
mfem::Swap(elements, new_elements);
|
|
|
|
// refine boundary elements
|
|
new_boundary.SetSize(2 * NumOfBdrElements);
|
|
for (int i = 0, j = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
const int attr = boundary[i]->GetAttribute();
|
|
int *v = boundary[i]->GetVertices();
|
|
|
|
new_boundary[j++] = new Segment(v[0], oedge+be_to_face[i], attr);
|
|
new_boundary[j++] = new Segment(oedge+be_to_face[i], v[1], attr);
|
|
|
|
FreeElement(boundary[i]);
|
|
}
|
|
mfem::Swap(boundary, new_boundary);
|
|
|
|
static const real_t A = 0.0, B = 0.5, C = 1.0;
|
|
static real_t tri_children[2*3*4] =
|
|
{
|
|
A,A, B,A, A,B,
|
|
B,B, A,B, B,A,
|
|
B,A, C,A, B,B,
|
|
A,B, B,B, A,C
|
|
};
|
|
static real_t quad_children[2*4*4] =
|
|
{
|
|
A,A, B,A, B,B, A,B, // lower-left
|
|
B,A, C,A, C,B, B,B, // lower-right
|
|
B,B, C,B, C,C, B,C, // upper-right
|
|
A,B, B,B, B,C, A,C // upper-left
|
|
};
|
|
|
|
CoarseFineTr.point_matrices[Geometry::TRIANGLE]
|
|
.UseExternalData(tri_children, 2, 3, 4);
|
|
CoarseFineTr.point_matrices[Geometry::SQUARE]
|
|
.UseExternalData(quad_children, 2, 4, 4);
|
|
CoarseFineTr.embeddings.SetSize(elements.Size());
|
|
|
|
for (int i = 0; i < elements.Size(); i++)
|
|
{
|
|
Embedding &emb = CoarseFineTr.embeddings[i];
|
|
emb.parent = i / 4;
|
|
emb.matrix = i % 4;
|
|
}
|
|
|
|
NumOfVertices = vertices.Size();
|
|
NumOfElements = 4 * NumOfElements;
|
|
NumOfBdrElements = 2 * NumOfBdrElements;
|
|
NumOfFaces = 0;
|
|
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
|
|
last_operation = Mesh::REFINE;
|
|
sequence++;
|
|
|
|
if (update_nodes) { UpdateNodes(); }
|
|
|
|
#ifdef MFEM_DEBUG
|
|
if (!Nodes || update_nodes)
|
|
{
|
|
CheckElementOrientation(false);
|
|
}
|
|
CheckBdrElementOrientation(false);
|
|
#endif
|
|
}
|
|
|
|
static inline real_t sqr(const real_t &x)
|
|
{
|
|
return x*x;
|
|
}
|
|
|
|
void Mesh::UniformRefinement3D_base(Array<int> *f2qf_ptr, DSTable *v_to_v_p,
|
|
bool update_nodes)
|
|
{
|
|
ResetLazyData();
|
|
|
|
if (el_to_edge == NULL)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
|
|
if (el_to_face == NULL)
|
|
{
|
|
GetElementToFaceTable();
|
|
}
|
|
|
|
Array<int> f2qf_loc;
|
|
Array<int> &f2qf = f2qf_ptr ? *f2qf_ptr : f2qf_loc;
|
|
f2qf.SetSize(0);
|
|
|
|
int NumOfQuadFaces = 0;
|
|
if (HasGeometry(Geometry::SQUARE))
|
|
{
|
|
if (HasGeometry(Geometry::TRIANGLE))
|
|
{
|
|
f2qf.SetSize(faces.Size());
|
|
for (int i = 0; i < faces.Size(); i++)
|
|
{
|
|
if (faces[i]->GetType() == Element::QUADRILATERAL)
|
|
{
|
|
f2qf[i] = NumOfQuadFaces;
|
|
NumOfQuadFaces++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumOfQuadFaces = faces.Size();
|
|
}
|
|
}
|
|
|
|
int hex_counter = 0;
|
|
if (HasGeometry(Geometry::CUBE))
|
|
{
|
|
for (int i = 0; i < elements.Size(); i++)
|
|
{
|
|
if (elements[i]->GetType() == Element::HEXAHEDRON)
|
|
{
|
|
hex_counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
int pyr_counter = 0;
|
|
if (HasGeometry(Geometry::PYRAMID))
|
|
{
|
|
for (int i = 0; i < elements.Size(); i++)
|
|
{
|
|
if (elements[i]->GetType() == Element::PYRAMID)
|
|
{
|
|
pyr_counter++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Map from edge-index to vertex-index, needed for ReorientTetMesh() for
|
|
// parallel meshes.
|
|
// Note: with the removal of ReorientTetMesh() this may no longer
|
|
// be needed. Unfortunately, it's hard to be sure.
|
|
Array<int> e2v;
|
|
if (HasGeometry(Geometry::TETRAHEDRON))
|
|
{
|
|
e2v.SetSize(NumOfEdges);
|
|
|
|
DSTable *v_to_v_ptr = v_to_v_p;
|
|
if (!v_to_v_p)
|
|
{
|
|
v_to_v_ptr = new DSTable(NumOfVertices);
|
|
GetVertexToVertexTable(*v_to_v_ptr);
|
|
}
|
|
|
|
Array<Pair<int,int> > J_v2v(NumOfEdges); // (second vertex id, edge id)
|
|
J_v2v.SetSize(0);
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
Pair<int,int> *row_start = J_v2v.end();
|
|
for (DSTable::RowIterator it(*v_to_v_ptr, i); !it; ++it)
|
|
{
|
|
J_v2v.Append(Pair<int,int>(it.Column(), it.Index()));
|
|
}
|
|
std::sort(row_start, J_v2v.end());
|
|
}
|
|
|
|
for (int i = 0; i < J_v2v.Size(); i++)
|
|
{
|
|
e2v[J_v2v[i].two] = i;
|
|
}
|
|
|
|
if (!v_to_v_p)
|
|
{
|
|
delete v_to_v_ptr;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
for (DSTable::RowIterator it(*v_to_v_ptr, i); !it; ++it)
|
|
{
|
|
it.SetIndex(e2v[it.Index()]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Offsets for new vertices from edges, faces (quads only), and elements
|
|
// (hexes only); each of these entities generates one new vertex.
|
|
const int oedge = NumOfVertices;
|
|
const int oface = oedge + NumOfEdges;
|
|
const int oelem = oface + NumOfQuadFaces;
|
|
|
|
Array<Element*> new_elements;
|
|
Array<Element*> new_boundary;
|
|
|
|
vertices.SetSize(oelem + hex_counter);
|
|
new_elements.SetSize(8 * NumOfElements + 2 * pyr_counter);
|
|
CoarseFineTr.embeddings.SetSize(new_elements.Size());
|
|
|
|
hex_counter = 0;
|
|
for (int i = 0, j = 0; i < NumOfElements; i++)
|
|
{
|
|
const Element::Type el_type = elements[i]->GetType();
|
|
const int attr = elements[i]->GetAttribute();
|
|
int *v = elements[i]->GetVertices();
|
|
const int *e = el_to_edge->GetRow(i);
|
|
int vv[4], ev[12];
|
|
|
|
if (e2v.Size())
|
|
{
|
|
const int ne = el_to_edge->RowSize(i);
|
|
for (int k = 0; k < ne; k++) { ev[k] = e2v[e[k]]; }
|
|
e = ev;
|
|
}
|
|
|
|
switch (el_type)
|
|
{
|
|
case Element::TETRAHEDRON:
|
|
{
|
|
for (int ei = 0; ei < 6; ei++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
vv[k] = v[tet_t::Edges[ei][k]];
|
|
}
|
|
AverageVertices(vv, 2, oedge+e[ei]);
|
|
}
|
|
|
|
// Algorithm for choosing refinement type:
|
|
// 0: smallest octahedron diagonal
|
|
// 1: best aspect ratio
|
|
const int rt_algo = 1;
|
|
// Refinement type:
|
|
// 0: (v0,v1)-(v2,v3), 1: (v0,v2)-(v1,v3), 2: (v0,v3)-(v1,v2)
|
|
// 0: e0-e5, 1: e1-e4, 2: e2-e3
|
|
int rt;
|
|
ElementTransformation *T = GetElementTransformation(i);
|
|
T->SetIntPoint(&Geometries.GetCenter(Geometry::TETRAHEDRON));
|
|
const DenseMatrix &J = T->Jacobian();
|
|
if (rt_algo == 0)
|
|
{
|
|
// smallest octahedron diagonal
|
|
real_t len_sqr, min_len;
|
|
|
|
min_len = sqr(J(0,0)-J(0,1)-J(0,2)) +
|
|
sqr(J(1,0)-J(1,1)-J(1,2)) +
|
|
sqr(J(2,0)-J(2,1)-J(2,2));
|
|
rt = 0;
|
|
|
|
len_sqr = sqr(J(0,1)-J(0,0)-J(0,2)) +
|
|
sqr(J(1,1)-J(1,0)-J(1,2)) +
|
|
sqr(J(2,1)-J(2,0)-J(2,2));
|
|
if (len_sqr < min_len) { min_len = len_sqr; rt = 1; }
|
|
|
|
len_sqr = sqr(J(0,2)-J(0,0)-J(0,1)) +
|
|
sqr(J(1,2)-J(1,0)-J(1,1)) +
|
|
sqr(J(2,2)-J(2,0)-J(2,1));
|
|
if (len_sqr < min_len) { rt = 2; }
|
|
}
|
|
else
|
|
{
|
|
// best aspect ratio
|
|
real_t Em_data[18], Js_data[9], Jp_data[9];
|
|
DenseMatrix Em(Em_data, 3, 6);
|
|
DenseMatrix Js(Js_data, 3, 3), Jp(Jp_data, 3, 3);
|
|
real_t ar1, ar2, kappa, kappa_min;
|
|
|
|
for (int s = 0; s < 3; s++)
|
|
{
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Em(t,s) = 0.5*J(t,s);
|
|
}
|
|
}
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Em(t,3) = 0.5*(J(t,0)+J(t,1));
|
|
Em(t,4) = 0.5*(J(t,0)+J(t,2));
|
|
Em(t,5) = 0.5*(J(t,1)+J(t,2));
|
|
}
|
|
|
|
// rt = 0; Em: {0,5,1,2}, {0,5,2,4}
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Js(t,0) = Em(t,5)-Em(t,0);
|
|
Js(t,1) = Em(t,1)-Em(t,0);
|
|
Js(t,2) = Em(t,2)-Em(t,0);
|
|
}
|
|
Geometries.JacToPerfJac(Geometry::TETRAHEDRON, Js, Jp);
|
|
ar1 = Jp.CalcSingularvalue(0)/Jp.CalcSingularvalue(2);
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Js(t,0) = Em(t,5)-Em(t,0);
|
|
Js(t,1) = Em(t,2)-Em(t,0);
|
|
Js(t,2) = Em(t,4)-Em(t,0);
|
|
}
|
|
Geometries.JacToPerfJac(Geometry::TETRAHEDRON, Js, Jp);
|
|
ar2 = Jp.CalcSingularvalue(0)/Jp.CalcSingularvalue(2);
|
|
kappa_min = std::max(ar1, ar2);
|
|
rt = 0;
|
|
|
|
// rt = 1; Em: {1,0,4,2}, {1,2,4,5}
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Js(t,0) = Em(t,0)-Em(t,1);
|
|
Js(t,1) = Em(t,4)-Em(t,1);
|
|
Js(t,2) = Em(t,2)-Em(t,1);
|
|
}
|
|
Geometries.JacToPerfJac(Geometry::TETRAHEDRON, Js, Jp);
|
|
ar1 = Jp.CalcSingularvalue(0)/Jp.CalcSingularvalue(2);
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Js(t,0) = Em(t,2)-Em(t,1);
|
|
Js(t,1) = Em(t,4)-Em(t,1);
|
|
Js(t,2) = Em(t,5)-Em(t,1);
|
|
}
|
|
Geometries.JacToPerfJac(Geometry::TETRAHEDRON, Js, Jp);
|
|
ar2 = Jp.CalcSingularvalue(0)/Jp.CalcSingularvalue(2);
|
|
kappa = std::max(ar1, ar2);
|
|
if (kappa < kappa_min) { kappa_min = kappa; rt = 1; }
|
|
|
|
// rt = 2; Em: {2,0,1,3}, {2,1,5,3}
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Js(t,0) = Em(t,0)-Em(t,2);
|
|
Js(t,1) = Em(t,1)-Em(t,2);
|
|
Js(t,2) = Em(t,3)-Em(t,2);
|
|
}
|
|
Geometries.JacToPerfJac(Geometry::TETRAHEDRON, Js, Jp);
|
|
ar1 = Jp.CalcSingularvalue(0)/Jp.CalcSingularvalue(2);
|
|
for (int t = 0; t < 3; t++)
|
|
{
|
|
Js(t,0) = Em(t,1)-Em(t,2);
|
|
Js(t,1) = Em(t,5)-Em(t,2);
|
|
Js(t,2) = Em(t,3)-Em(t,2);
|
|
}
|
|
Geometries.JacToPerfJac(Geometry::TETRAHEDRON, Js, Jp);
|
|
ar2 = Jp.CalcSingularvalue(0)/Jp.CalcSingularvalue(2);
|
|
kappa = std::max(ar1, ar2);
|
|
if (kappa < kappa_min) { rt = 2; }
|
|
}
|
|
|
|
static const int mv_all[3][4][4] =
|
|
{
|
|
{ {0,5,1,2}, {0,5,2,4}, {0,5,4,3}, {0,5,3,1} }, // rt = 0
|
|
{ {1,0,4,2}, {1,2,4,5}, {1,5,4,3}, {1,3,4,0} }, // rt = 1
|
|
{ {2,0,1,3}, {2,1,5,3}, {2,5,4,3}, {2,4,0,3} } // rt = 2
|
|
};
|
|
const int (&mv)[4][4] = mv_all[rt];
|
|
|
|
#ifndef MFEM_USE_MEMALLOC
|
|
new_elements[j+0] =
|
|
new Tetrahedron(v[0], oedge+e[0], oedge+e[1], oedge+e[2], attr);
|
|
new_elements[j+1] =
|
|
new Tetrahedron(oedge+e[0], v[1], oedge+e[3], oedge+e[4], attr);
|
|
new_elements[j+2] =
|
|
new Tetrahedron(oedge+e[1], oedge+e[3], v[2], oedge+e[5], attr);
|
|
new_elements[j+3] =
|
|
new Tetrahedron(oedge+e[2], oedge+e[4], oedge+e[5], v[3], attr);
|
|
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
new_elements[j+4+k] =
|
|
new Tetrahedron(oedge+e[mv[k][0]], oedge+e[mv[k][1]],
|
|
oedge+e[mv[k][2]], oedge+e[mv[k][3]], attr);
|
|
}
|
|
#else
|
|
Tetrahedron *tet;
|
|
new_elements[j+0] = tet = TetMemory.Alloc();
|
|
tet->Init(v[0], oedge+e[0], oedge+e[1], oedge+e[2], attr);
|
|
|
|
new_elements[j+1] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[0], v[1], oedge+e[3], oedge+e[4], attr);
|
|
|
|
new_elements[j+2] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[1], oedge+e[3], v[2], oedge+e[5], attr);
|
|
|
|
new_elements[j+3] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[2], oedge+e[4], oedge+e[5], v[3], attr);
|
|
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
new_elements[j+4+k] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[mv[k][0]], oedge+e[mv[k][1]],
|
|
oedge+e[mv[k][2]], oedge+e[mv[k][3]], attr);
|
|
}
|
|
#endif
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
CoarseFineTr.embeddings[j+k].parent = i;
|
|
CoarseFineTr.embeddings[j+k].matrix = k;
|
|
}
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
CoarseFineTr.embeddings[j+4+k].parent = i;
|
|
CoarseFineTr.embeddings[j+4+k].matrix = 4*(rt+1)+k;
|
|
}
|
|
|
|
j += 8;
|
|
}
|
|
break;
|
|
|
|
case Element::WEDGE:
|
|
{
|
|
const int *f = el_to_face->GetRow(i);
|
|
|
|
for (int fi = 2; fi < 5; fi++)
|
|
{
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
vv[k] = v[pri_t::FaceVert[fi][k]];
|
|
}
|
|
AverageVertices(vv, 4, oface + f2qf[f[fi]]);
|
|
}
|
|
|
|
for (int ei = 0; ei < 9; ei++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
vv[k] = v[pri_t::Edges[ei][k]];
|
|
}
|
|
AverageVertices(vv, 2, oedge+e[ei]);
|
|
}
|
|
|
|
const int qf2 = f2qf[f[2]];
|
|
const int qf3 = f2qf[f[3]];
|
|
const int qf4 = f2qf[f[4]];
|
|
|
|
new_elements[j++] =
|
|
new Wedge(v[0], oedge+e[0], oedge+e[2],
|
|
oedge+e[6], oface+qf2, oface+qf4, attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oedge+e[1], oedge+e[2], oedge+e[0],
|
|
oface+qf3, oface+qf4, oface+qf2, attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oedge+e[0], v[1], oedge+e[1],
|
|
oface+qf2, oedge+e[7], oface+qf3, attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oedge+e[2], oedge+e[1], v[2],
|
|
oface+qf4, oface+qf3, oedge+e[8], attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oedge+e[6], oface+qf2, oface+qf4,
|
|
v[3], oedge+e[3], oedge+e[5], attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oface+qf3, oface+qf4, oface+qf2,
|
|
oedge+e[4], oedge+e[5], oedge+e[3], attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oface+qf2, oedge+e[7], oface+qf3,
|
|
oedge+e[3], v[4], oedge+e[4], attr);
|
|
|
|
new_elements[j++] =
|
|
new Wedge(oface+qf4, oface+qf3, oedge+e[8],
|
|
oedge+e[5], oedge+e[4], v[5], attr);
|
|
}
|
|
break;
|
|
|
|
case Element::PYRAMID:
|
|
{
|
|
const int *f = el_to_face->GetRow(i);
|
|
// pyr_counter++;
|
|
|
|
for (int fi = 0; fi < 1; fi++)
|
|
{
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
vv[k] = v[pyr_t::FaceVert[fi][k]];
|
|
}
|
|
AverageVertices(vv, 4, oface + f2qf[f[fi]]);
|
|
}
|
|
|
|
for (int ei = 0; ei < 8; ei++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
vv[k] = v[pyr_t::Edges[ei][k]];
|
|
}
|
|
AverageVertices(vv, 2, oedge+e[ei]);
|
|
}
|
|
|
|
const int qf0 = f2qf[f[0]];
|
|
|
|
new_elements[j++] =
|
|
new Pyramid(v[0], oedge+e[0], oface+qf0,
|
|
oedge+e[3], oedge+e[4], attr);
|
|
|
|
new_elements[j++] =
|
|
new Pyramid(oedge+e[0], v[1], oedge+e[1],
|
|
oface+qf0, oedge+e[5], attr);
|
|
|
|
new_elements[j++] =
|
|
new Pyramid(oface+qf0, oedge+e[1], v[2],
|
|
oedge+e[2], oedge+e[6], attr);
|
|
|
|
new_elements[j++] =
|
|
new Pyramid(oedge+e[3], oface+qf0, oedge+e[2],
|
|
v[3], oedge+e[7], attr);
|
|
|
|
new_elements[j++] =
|
|
new Pyramid(oedge+e[4], oedge+e[5], oedge+e[6],
|
|
oedge+e[7], v[4], attr);
|
|
|
|
new_elements[j++] =
|
|
new Pyramid(oedge+e[7], oedge+e[6], oedge+e[5],
|
|
oedge+e[4], oface+qf0, attr);
|
|
|
|
#ifndef MFEM_USE_MEMALLOC
|
|
new_elements[j++] =
|
|
new Tetrahedron(oedge+e[0], oedge+e[4], oedge+e[5],
|
|
oface+qf0, attr);
|
|
|
|
new_elements[j++] =
|
|
new Tetrahedron(oedge+e[1], oedge+e[5], oedge+e[6],
|
|
oface+qf0, attr);
|
|
|
|
new_elements[j++] =
|
|
new Tetrahedron(oedge+e[2], oedge+e[6], oedge+e[7],
|
|
oface+qf0, attr);
|
|
|
|
new_elements[j++] =
|
|
new Tetrahedron(oedge+e[3], oedge+e[7], oedge+e[4],
|
|
oface+qf0, attr);
|
|
#else
|
|
Tetrahedron *tet;
|
|
new_elements[j++] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[0], oedge+e[4], oedge+e[5],
|
|
oface+qf0, attr);
|
|
|
|
new_elements[j++] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[1], oedge+e[5], oedge+e[6],
|
|
oface+qf0, attr);
|
|
|
|
new_elements[j++] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[2], oedge+e[6], oedge+e[7],
|
|
oface+qf0, attr);
|
|
|
|
new_elements[j++] = tet = TetMemory.Alloc();
|
|
tet->Init(oedge+e[3], oedge+e[7], oedge+e[4],
|
|
oface+qf0, attr);
|
|
#endif
|
|
// Tetrahedral elements may be new to this mesh so ensure that
|
|
// the relevant flags are switched on
|
|
mesh_geoms |= (1 << Geometry::TETRAHEDRON);
|
|
meshgen |= 1;
|
|
}
|
|
break;
|
|
|
|
case Element::HEXAHEDRON:
|
|
{
|
|
const int *f = el_to_face->GetRow(i);
|
|
const int he = hex_counter;
|
|
hex_counter++;
|
|
|
|
const int *qf;
|
|
int qf_data[6];
|
|
if (f2qf.Size() == 0)
|
|
{
|
|
qf = f;
|
|
}
|
|
else
|
|
{
|
|
for (int k = 0; k < 6; k++) { qf_data[k] = f2qf[f[k]]; }
|
|
qf = qf_data;
|
|
}
|
|
|
|
AverageVertices(v, 8, oelem+he);
|
|
|
|
for (int fi = 0; fi < 6; fi++)
|
|
{
|
|
for (int k = 0; k < 4; k++)
|
|
{
|
|
vv[k] = v[hex_t::FaceVert[fi][k]];
|
|
}
|
|
AverageVertices(vv, 4, oface + qf[fi]);
|
|
}
|
|
|
|
for (int ei = 0; ei < 12; ei++)
|
|
{
|
|
for (int k = 0; k < 2; k++)
|
|
{
|
|
vv[k] = v[hex_t::Edges[ei][k]];
|
|
}
|
|
AverageVertices(vv, 2, oedge+e[ei]);
|
|
}
|
|
|
|
new_elements[j++] =
|
|
new Hexahedron(v[0], oedge+e[0], oface+qf[0],
|
|
oedge+e[3], oedge+e[8], oface+qf[1],
|
|
oelem+he, oface+qf[4], attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oedge+e[0], v[1], oedge+e[1],
|
|
oface+qf[0], oface+qf[1], oedge+e[9],
|
|
oface+qf[2], oelem+he, attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oface+qf[0], oedge+e[1], v[2],
|
|
oedge+e[2], oelem+he, oface+qf[2],
|
|
oedge+e[10], oface+qf[3], attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oedge+e[3], oface+qf[0], oedge+e[2],
|
|
v[3], oface+qf[4], oelem+he,
|
|
oface+qf[3], oedge+e[11], attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oedge+e[8], oface+qf[1], oelem+he,
|
|
oface+qf[4], v[4], oedge+e[4],
|
|
oface+qf[5], oedge+e[7], attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oface+qf[1], oedge+e[9], oface+qf[2],
|
|
oelem+he, oedge+e[4], v[5],
|
|
oedge+e[5], oface+qf[5], attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oelem+he, oface+qf[2], oedge+e[10],
|
|
oface+qf[3], oface+qf[5], oedge+e[5],
|
|
v[6], oedge+e[6], attr);
|
|
new_elements[j++] =
|
|
new Hexahedron(oface+qf[4], oelem+he, oface+qf[3],
|
|
oedge+e[11], oedge+e[7], oface+qf[5],
|
|
oedge+e[6], v[7], attr);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MFEM_ABORT("Unknown 3D element type \"" << el_type << "\"");
|
|
break;
|
|
}
|
|
FreeElement(elements[i]);
|
|
}
|
|
mfem::Swap(elements, new_elements);
|
|
|
|
// refine boundary elements
|
|
new_boundary.SetSize(4 * NumOfBdrElements);
|
|
for (int i = 0, j = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
const Element::Type bdr_el_type = boundary[i]->GetType();
|
|
const int attr = boundary[i]->GetAttribute();
|
|
int *v = boundary[i]->GetVertices();
|
|
const int *e = bel_to_edge->GetRow(i);
|
|
int ev[4];
|
|
|
|
if (e2v.Size())
|
|
{
|
|
const int ne = bel_to_edge->RowSize(i);
|
|
for (int k = 0; k < ne; k++) { ev[k] = e2v[e[k]]; }
|
|
e = ev;
|
|
}
|
|
|
|
if (bdr_el_type == Element::TRIANGLE)
|
|
{
|
|
new_boundary[j++] =
|
|
new Triangle(v[0], oedge+e[0], oedge+e[2], attr);
|
|
new_boundary[j++] =
|
|
new Triangle(oedge+e[1], oedge+e[2], oedge+e[0], attr);
|
|
new_boundary[j++] =
|
|
new Triangle(oedge+e[0], v[1], oedge+e[1], attr);
|
|
new_boundary[j++] =
|
|
new Triangle(oedge+e[2], oedge+e[1], v[2], attr);
|
|
}
|
|
else if (bdr_el_type == Element::QUADRILATERAL)
|
|
{
|
|
const int qf =
|
|
(f2qf.Size() == 0) ? be_to_face[i] : f2qf[be_to_face[i]];
|
|
|
|
new_boundary[j++] =
|
|
new Quadrilateral(v[0], oedge+e[0], oface+qf, oedge+e[3], attr);
|
|
new_boundary[j++] =
|
|
new Quadrilateral(oedge+e[0], v[1], oedge+e[1], oface+qf, attr);
|
|
new_boundary[j++] =
|
|
new Quadrilateral(oface+qf, oedge+e[1], v[2], oedge+e[2], attr);
|
|
new_boundary[j++] =
|
|
new Quadrilateral(oedge+e[3], oface+qf, oedge+e[2], v[3], attr);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("boundary Element is not a triangle or a quad!");
|
|
}
|
|
FreeElement(boundary[i]);
|
|
}
|
|
mfem::Swap(boundary, new_boundary);
|
|
|
|
static const real_t A = 0.0, B = 0.5, C = 1.0, D = -1.0;
|
|
static real_t tet_children[3*4*16] =
|
|
{
|
|
A,A,A, B,A,A, A,B,A, A,A,B,
|
|
B,A,A, C,A,A, B,B,A, B,A,B,
|
|
A,B,A, B,B,A, A,C,A, A,B,B,
|
|
A,A,B, B,A,B, A,B,B, A,A,C,
|
|
// edge coordinates:
|
|
// 0 -> B,A,A 1 -> A,B,A 2 -> A,A,B
|
|
// 3 -> B,B,A 4 -> B,A,B 5 -> A,B,B
|
|
// rt = 0: {0,5,1,2}, {0,5,2,4}, {0,5,4,3}, {0,5,3,1}
|
|
B,A,A, A,B,B, A,B,A, A,A,B,
|
|
B,A,A, A,B,B, A,A,B, B,A,B,
|
|
B,A,A, A,B,B, B,A,B, B,B,A,
|
|
B,A,A, A,B,B, B,B,A, A,B,A,
|
|
// rt = 1: {1,0,4,2}, {1,2,4,5}, {1,5,4,3}, {1,3,4,0}
|
|
A,B,A, B,A,A, B,A,B, A,A,B,
|
|
A,B,A, A,A,B, B,A,B, A,B,B,
|
|
A,B,A, A,B,B, B,A,B, B,B,A,
|
|
A,B,A, B,B,A, B,A,B, B,A,A,
|
|
// rt = 2: {2,0,1,3}, {2,1,5,3}, {2,5,4,3}, {2,4,0,3}
|
|
A,A,B, B,A,A, A,B,A, B,B,A,
|
|
A,A,B, A,B,A, A,B,B, B,B,A,
|
|
A,A,B, A,B,B, B,A,B, B,B,A,
|
|
A,A,B, B,A,B, B,A,A, B,B,A
|
|
};
|
|
static real_t pyr_children[3*5*10] =
|
|
{
|
|
A,A,A, B,A,A, B,B,A, A,B,A, A,A,B,
|
|
B,A,A, C,A,A, C,B,A, B,B,A, B,A,B,
|
|
B,B,A, C,B,A, C,C,A, B,C,A, B,B,B,
|
|
A,B,A, B,B,A, B,C,A, A,C,A, A,B,B,
|
|
A,A,B, B,A,B, B,B,B, A,B,B, A,A,C,
|
|
A,B,B, B,B,B, B,A,B, A,A,B, B,B,A,
|
|
B,A,A, A,A,B, B,A,B, B,B,A, D,D,D,
|
|
C,B,A, B,A,B, B,B,B, B,B,A, D,D,D,
|
|
B,C,A, B,B,B, A,B,B, B,B,A, D,D,D,
|
|
A,B,A, A,B,B, A,A,B, B,B,A, D,D,D
|
|
};
|
|
static real_t pri_children[3*6*8] =
|
|
{
|
|
A,A,A, B,A,A, A,B,A, A,A,B, B,A,B, A,B,B,
|
|
B,B,A, A,B,A, B,A,A, B,B,B, A,B,B, B,A,B,
|
|
B,A,A, C,A,A, B,B,A, B,A,B, C,A,B, B,B,B,
|
|
A,B,A, B,B,A, A,C,A, A,B,B, B,B,B, A,C,B,
|
|
A,A,B, B,A,B, A,B,B, A,A,C, B,A,C, A,B,C,
|
|
B,B,B, A,B,B, B,A,B, B,B,C, A,B,C, B,A,C,
|
|
B,A,B, C,A,B, B,B,B, B,A,C, C,A,C, B,B,C,
|
|
A,B,B, B,B,B, A,C,B, A,B,C, B,B,C, A,C,C
|
|
};
|
|
static real_t hex_children[3*8*8] =
|
|
{
|
|
A,A,A, B,A,A, B,B,A, A,B,A, A,A,B, B,A,B, B,B,B, A,B,B,
|
|
B,A,A, C,A,A, C,B,A, B,B,A, B,A,B, C,A,B, C,B,B, B,B,B,
|
|
B,B,A, C,B,A, C,C,A, B,C,A, B,B,B, C,B,B, C,C,B, B,C,B,
|
|
A,B,A, B,B,A, B,C,A, A,C,A, A,B,B, B,B,B, B,C,B, A,C,B,
|
|
A,A,B, B,A,B, B,B,B, A,B,B, A,A,C, B,A,C, B,B,C, A,B,C,
|
|
B,A,B, C,A,B, C,B,B, B,B,B, B,A,C, C,A,C, C,B,C, B,B,C,
|
|
B,B,B, C,B,B, C,C,B, B,C,B, B,B,C, C,B,C, C,C,C, B,C,C,
|
|
A,B,B, B,B,B, B,C,B, A,C,B, A,B,C, B,B,C, B,C,C, A,C,C
|
|
};
|
|
|
|
CoarseFineTr.point_matrices[Geometry::TETRAHEDRON]
|
|
.UseExternalData(tet_children, 3, 4, 16);
|
|
CoarseFineTr.point_matrices[Geometry::PYRAMID]
|
|
.UseExternalData(pyr_children, 3, 5, 10);
|
|
CoarseFineTr.point_matrices[Geometry::PRISM]
|
|
.UseExternalData(pri_children, 3, 6, 8);
|
|
CoarseFineTr.point_matrices[Geometry::CUBE]
|
|
.UseExternalData(hex_children, 3, 8, 8);
|
|
|
|
for (int i = 0; i < elements.Size(); i++)
|
|
{
|
|
// tetrahedron elements are handled above:
|
|
if (elements[i]->GetType() == Element::TETRAHEDRON) { continue; }
|
|
|
|
Embedding &emb = CoarseFineTr.embeddings[i];
|
|
emb.parent = i / 8;
|
|
emb.matrix = i % 8;
|
|
}
|
|
|
|
NumOfVertices = vertices.Size();
|
|
NumOfElements = 8 * NumOfElements + 2 * pyr_counter;
|
|
NumOfBdrElements = 4 * NumOfBdrElements;
|
|
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
|
|
#ifdef MFEM_DEBUG
|
|
CheckBdrElementOrientation(false);
|
|
#endif
|
|
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
|
|
last_operation = Mesh::REFINE;
|
|
sequence++;
|
|
|
|
if (update_nodes) { UpdateNodes(); }
|
|
}
|
|
|
|
void Mesh::LocalRefinement(const Array<int> &marked_el, int type)
|
|
{
|
|
int i, j, ind, nedges;
|
|
Array<int> v;
|
|
|
|
ResetLazyData();
|
|
|
|
if (ncmesh)
|
|
{
|
|
MFEM_ABORT("Local and nonconforming refinements cannot be mixed.");
|
|
}
|
|
|
|
InitRefinementTransforms();
|
|
|
|
if (Dim == 1) // --------------------------------------------------------
|
|
{
|
|
int cne = NumOfElements, cnv = NumOfVertices;
|
|
NumOfVertices += marked_el.Size();
|
|
NumOfElements += marked_el.Size();
|
|
vertices.SetSize(NumOfVertices);
|
|
elements.SetSize(NumOfElements);
|
|
CoarseFineTr.embeddings.SetSize(NumOfElements);
|
|
|
|
for (j = 0; j < marked_el.Size(); j++)
|
|
{
|
|
i = marked_el[j];
|
|
Segment *c_seg = (Segment *)elements[i];
|
|
int *vert = c_seg->GetVertices(), attr = c_seg->GetAttribute();
|
|
int new_v = cnv + j, new_e = cne + j;
|
|
AverageVertices(vert, 2, new_v);
|
|
elements[new_e] = new Segment(new_v, vert[1], attr);
|
|
vert[1] = new_v;
|
|
|
|
CoarseFineTr.embeddings[i] = Embedding(i, Geometry::SEGMENT, 1);
|
|
CoarseFineTr.embeddings[new_e] = Embedding(i, Geometry::SEGMENT, 2);
|
|
}
|
|
|
|
static real_t seg_children[3*2] = { 0.0,1.0, 0.0,0.5, 0.5,1.0 };
|
|
CoarseFineTr.point_matrices[Geometry::SEGMENT].
|
|
UseExternalData(seg_children, 1, 2, 3);
|
|
|
|
GenerateFaces();
|
|
|
|
} // end of 'if (Dim == 1)'
|
|
else if (Dim == 2) // ---------------------------------------------------
|
|
{
|
|
// 1. Get table of vertex to vertex connections.
|
|
DSTable v_to_v(NumOfVertices);
|
|
GetVertexToVertexTable(v_to_v);
|
|
|
|
// 2. Get edge to element connections in arrays edge1 and edge2
|
|
nedges = v_to_v.NumberOfEntries();
|
|
int *edge1 = new int[nedges];
|
|
int *edge2 = new int[nedges];
|
|
int *middle = new int[nedges];
|
|
|
|
for (i = 0; i < nedges; i++)
|
|
{
|
|
edge1[i] = edge2[i] = middle[i] = -1;
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
elements[i]->GetVertices(v);
|
|
for (j = 1; j < v.Size(); j++)
|
|
{
|
|
ind = v_to_v(v[j-1], v[j]);
|
|
(edge1[ind] == -1) ? (edge1[ind] = i) : (edge2[ind] = i);
|
|
}
|
|
ind = v_to_v(v[0], v[v.Size()-1]);
|
|
(edge1[ind] == -1) ? (edge1[ind] = i) : (edge2[ind] = i);
|
|
}
|
|
|
|
// 3. Do the red refinement.
|
|
for (i = 0; i < marked_el.Size(); i++)
|
|
{
|
|
RedRefinement(marked_el[i], v_to_v, edge1, edge2, middle);
|
|
}
|
|
|
|
// 4. Do the green refinement (to get conforming mesh).
|
|
int need_refinement;
|
|
do
|
|
{
|
|
need_refinement = 0;
|
|
for (i = 0; i < nedges; i++)
|
|
{
|
|
if (middle[i] != -1 && edge1[i] != -1)
|
|
{
|
|
need_refinement = 1;
|
|
GreenRefinement(edge1[i], v_to_v, edge1, edge2, middle);
|
|
}
|
|
}
|
|
}
|
|
while (need_refinement == 1);
|
|
|
|
// 5. Update the boundary elements.
|
|
int v1[2], v2[2], bisect, temp;
|
|
temp = NumOfBdrElements;
|
|
for (i = 0; i < temp; i++)
|
|
{
|
|
boundary[i]->GetVertices(v);
|
|
bisect = v_to_v(v[0], v[1]);
|
|
if (middle[bisect] != -1) // the element was refined (needs updating)
|
|
{
|
|
if (boundary[i]->GetType() == Element::SEGMENT)
|
|
{
|
|
v1[0] = v[0]; v1[1] = middle[bisect];
|
|
v2[0] = middle[bisect]; v2[1] = v[1];
|
|
|
|
boundary[i]->SetVertices(v1);
|
|
boundary.Append(new Segment(v2, boundary[i]->GetAttribute()));
|
|
}
|
|
else
|
|
mfem_error("Only bisection of segment is implemented"
|
|
" for bdr elem.");
|
|
}
|
|
}
|
|
NumOfBdrElements = boundary.Size();
|
|
|
|
// 6. Free the allocated memory.
|
|
delete [] edge1;
|
|
delete [] edge2;
|
|
delete [] middle;
|
|
|
|
if (el_to_edge != NULL)
|
|
{
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
GenerateFaces();
|
|
}
|
|
|
|
}
|
|
else if (Dim == 3) // ---------------------------------------------------
|
|
{
|
|
// 1. Hash table of vertex to vertex connections corresponding to refined
|
|
// edges.
|
|
HashTable<Hashed2> v_to_v;
|
|
|
|
MFEM_VERIFY(GetNE() == 0 ||
|
|
((Tetrahedron*)elements[0])->GetRefinementFlag() != 0,
|
|
"tetrahedral mesh is not marked for refinement:"
|
|
" call Finalize(true)");
|
|
|
|
// 2. Do the red refinement.
|
|
int ii;
|
|
switch (type)
|
|
{
|
|
case 1:
|
|
for (i = 0; i < marked_el.Size(); i++)
|
|
{
|
|
Bisection(marked_el[i], v_to_v);
|
|
}
|
|
break;
|
|
case 2:
|
|
for (i = 0; i < marked_el.Size(); i++)
|
|
{
|
|
Bisection(marked_el[i], v_to_v);
|
|
|
|
Bisection(NumOfElements - 1, v_to_v);
|
|
Bisection(marked_el[i], v_to_v);
|
|
}
|
|
break;
|
|
case 3:
|
|
for (i = 0; i < marked_el.Size(); i++)
|
|
{
|
|
Bisection(marked_el[i], v_to_v);
|
|
|
|
ii = NumOfElements - 1;
|
|
Bisection(ii, v_to_v);
|
|
Bisection(NumOfElements - 1, v_to_v);
|
|
Bisection(ii, v_to_v);
|
|
|
|
Bisection(marked_el[i], v_to_v);
|
|
Bisection(NumOfElements-1, v_to_v);
|
|
Bisection(marked_el[i], v_to_v);
|
|
}
|
|
break;
|
|
}
|
|
|
|
// 3. Do the green refinement (to get conforming mesh).
|
|
int need_refinement;
|
|
// int need_refinement, onoe, max_gen = 0;
|
|
do
|
|
{
|
|
// int redges[2], type, flag;
|
|
need_refinement = 0;
|
|
// onoe = NumOfElements;
|
|
// for (i = 0; i < onoe; i++)
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
// ((Tetrahedron *)elements[i])->
|
|
// ParseRefinementFlag(redges, type, flag);
|
|
// if (flag > max_gen) max_gen = flag;
|
|
if (elements[i]->NeedRefinement(v_to_v))
|
|
{
|
|
need_refinement = 1;
|
|
Bisection(i, v_to_v);
|
|
}
|
|
}
|
|
}
|
|
while (need_refinement == 1);
|
|
|
|
// mfem::out << "Maximum generation: " << max_gen << endl;
|
|
|
|
// 4. Update the boundary elements.
|
|
do
|
|
{
|
|
need_refinement = 0;
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
if (boundary[i]->NeedRefinement(v_to_v))
|
|
{
|
|
need_refinement = 1;
|
|
BdrBisection(i, v_to_v);
|
|
}
|
|
}
|
|
while (need_refinement == 1);
|
|
|
|
NumOfVertices = vertices.Size();
|
|
NumOfBdrElements = boundary.Size();
|
|
|
|
// 5. Update element-to-edge and element-to-face relations.
|
|
if (el_to_edge != NULL)
|
|
{
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
if (el_to_face != NULL)
|
|
{
|
|
GetElementToFaceTable();
|
|
GenerateFaces();
|
|
}
|
|
|
|
} // end 'if (Dim == 3)'
|
|
|
|
last_operation = Mesh::REFINE;
|
|
sequence++;
|
|
|
|
UpdateNodes();
|
|
|
|
#ifdef MFEM_DEBUG
|
|
CheckElementOrientation(false);
|
|
#endif
|
|
}
|
|
|
|
void Mesh::NonconformingRefinement(const Array<Refinement> &refinements,
|
|
int nc_limit)
|
|
{
|
|
MFEM_VERIFY(!NURBSext, "Nonconforming refinement of NURBS meshes is "
|
|
"not supported. Project the NURBS to Nodes first.");
|
|
|
|
ResetLazyData();
|
|
|
|
if (!ncmesh)
|
|
{
|
|
// start tracking refinement hierarchy
|
|
ncmesh = new NCMesh(this);
|
|
}
|
|
|
|
if (!refinements.Size())
|
|
{
|
|
last_operation = Mesh::NONE;
|
|
return;
|
|
}
|
|
|
|
// do the refinements
|
|
ncmesh->MarkCoarseLevel();
|
|
ncmesh->Refine(refinements);
|
|
|
|
if (nc_limit > 0)
|
|
{
|
|
ncmesh->LimitNCLevel(nc_limit);
|
|
}
|
|
|
|
// create a second mesh containing the finest elements from 'ncmesh'
|
|
Mesh* mesh2 = new Mesh(*ncmesh);
|
|
ncmesh->OnMeshUpdated(mesh2);
|
|
|
|
// now swap the meshes, the second mesh will become the old coarse mesh
|
|
// and this mesh will be the new fine mesh
|
|
Swap(*mesh2, false);
|
|
delete mesh2;
|
|
|
|
GenerateNCFaceInfo();
|
|
|
|
last_operation = Mesh::REFINE;
|
|
sequence++;
|
|
|
|
UpdateNodes();
|
|
}
|
|
|
|
real_t Mesh::AggregateError(const Array<real_t> &elem_error,
|
|
const int *fine, int nfine, int op)
|
|
{
|
|
real_t error = (op == 3) ? std::pow(elem_error[fine[0]],
|
|
2.0) : elem_error[fine[0]];
|
|
|
|
for (int i = 1; i < nfine; i++)
|
|
{
|
|
MFEM_VERIFY(fine[i] < elem_error.Size(), "");
|
|
|
|
real_t err_fine = elem_error[fine[i]];
|
|
switch (op)
|
|
{
|
|
case 0: error = std::min(error, err_fine); break;
|
|
case 1: error += err_fine; break;
|
|
case 2: error = std::max(error, err_fine); break;
|
|
case 3: error += std::pow(err_fine, 2.0); break;
|
|
default: MFEM_ABORT("Invalid operation.");
|
|
}
|
|
}
|
|
return (op == 3) ? std::sqrt(error) : error;
|
|
}
|
|
|
|
bool Mesh::NonconformingDerefinement(Array<real_t> &elem_error,
|
|
real_t threshold, int nc_limit, int op)
|
|
{
|
|
MFEM_VERIFY(ncmesh, "Only supported for non-conforming meshes.");
|
|
MFEM_VERIFY(!NURBSext, "Derefinement of NURBS meshes is not supported. "
|
|
"Project the NURBS to Nodes first.");
|
|
|
|
ResetLazyData();
|
|
|
|
const Table &dt = ncmesh->GetDerefinementTable();
|
|
|
|
Array<int> level_ok;
|
|
if (nc_limit > 0)
|
|
{
|
|
ncmesh->CheckDerefinementNCLevel(dt, level_ok, nc_limit);
|
|
}
|
|
|
|
Array<int> derefs;
|
|
for (int i = 0; i < dt.Size(); i++)
|
|
{
|
|
if (nc_limit > 0 && !level_ok[i]) { continue; }
|
|
|
|
real_t error =
|
|
AggregateError(elem_error, dt.GetRow(i), dt.RowSize(i), op);
|
|
|
|
if (error < threshold) { derefs.Append(i); }
|
|
}
|
|
|
|
if (!derefs.Size()) { return false; }
|
|
|
|
ncmesh->Derefine(derefs);
|
|
|
|
Mesh* mesh2 = new Mesh(*ncmesh);
|
|
ncmesh->OnMeshUpdated(mesh2);
|
|
|
|
Swap(*mesh2, false);
|
|
delete mesh2;
|
|
|
|
GenerateNCFaceInfo();
|
|
|
|
last_operation = Mesh::DEREFINE;
|
|
sequence++;
|
|
|
|
UpdateNodes();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Mesh::DerefineByError(Array<real_t> &elem_error, real_t threshold,
|
|
int nc_limit, int op)
|
|
{
|
|
// NOTE: the error array is not const because it will be expanded in parallel
|
|
// by ghost element errors
|
|
if (Nonconforming())
|
|
{
|
|
return NonconformingDerefinement(elem_error, threshold, nc_limit, op);
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Derefinement is currently supported for non-conforming "
|
|
"meshes only.");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Mesh::DerefineByError(const Vector &elem_error, real_t threshold,
|
|
int nc_limit, int op)
|
|
{
|
|
Array<real_t> tmp(elem_error.Size());
|
|
for (int i = 0; i < tmp.Size(); i++)
|
|
{
|
|
tmp[i] = elem_error(i);
|
|
}
|
|
return DerefineByError(tmp, threshold, nc_limit, op);
|
|
}
|
|
|
|
|
|
void Mesh::InitFromNCMesh(const NCMesh &ncmesh_)
|
|
{
|
|
Dim = ncmesh_.Dimension();
|
|
spaceDim = ncmesh_.SpaceDimension();
|
|
|
|
DeleteTables();
|
|
|
|
ncmesh_.GetMeshComponents(*this);
|
|
|
|
NumOfVertices = vertices.Size();
|
|
NumOfElements = elements.Size();
|
|
NumOfBdrElements = boundary.Size();
|
|
|
|
SetMeshGen(); // set the mesh type: 'meshgen', ...
|
|
|
|
NumOfEdges = NumOfFaces = 0;
|
|
nbInteriorFaces = nbBoundaryFaces = -1;
|
|
|
|
if (Dim > 1)
|
|
{
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
if (Dim > 2)
|
|
{
|
|
GetElementToFaceTable();
|
|
}
|
|
GenerateFaces();
|
|
#ifdef MFEM_DEBUG
|
|
CheckBdrElementOrientation(false);
|
|
#endif
|
|
|
|
// NOTE: ncmesh->OnMeshUpdated() and GenerateNCFaceInfo() should be called
|
|
// outside after this method.
|
|
}
|
|
|
|
Mesh::Mesh(const NCMesh &ncmesh_)
|
|
: attribute_sets(attributes), bdr_attribute_sets(bdr_attributes)
|
|
{
|
|
Init();
|
|
InitTables();
|
|
InitFromNCMesh(ncmesh_);
|
|
SetAttributes();
|
|
}
|
|
|
|
void Mesh::Swap(Mesh& other, bool non_geometry)
|
|
{
|
|
mfem::Swap(Dim, other.Dim);
|
|
mfem::Swap(spaceDim, other.spaceDim);
|
|
|
|
mfem::Swap(NumOfVertices, other.NumOfVertices);
|
|
mfem::Swap(NumOfElements, other.NumOfElements);
|
|
mfem::Swap(NumOfBdrElements, other.NumOfBdrElements);
|
|
mfem::Swap(NumOfEdges, other.NumOfEdges);
|
|
mfem::Swap(NumOfFaces, other.NumOfFaces);
|
|
|
|
mfem::Swap(meshgen, other.meshgen);
|
|
mfem::Swap(mesh_geoms, other.mesh_geoms);
|
|
|
|
mfem::Swap(elements, other.elements);
|
|
mfem::Swap(vertices, other.vertices);
|
|
mfem::Swap(boundary, other.boundary);
|
|
mfem::Swap(faces, other.faces);
|
|
mfem::Swap(faces_info, other.faces_info);
|
|
mfem::Swap(nc_faces_info, other.nc_faces_info);
|
|
mfem::Swap(nbInteriorFaces, other.nbInteriorFaces);
|
|
mfem::Swap(nbBoundaryFaces, other.nbBoundaryFaces);
|
|
|
|
mfem::Swap(el_to_edge, other.el_to_edge);
|
|
mfem::Swap(el_to_face, other.el_to_face);
|
|
mfem::Swap(el_to_el, other.el_to_el);
|
|
mfem::Swap(bel_to_edge, other.bel_to_edge);
|
|
mfem::Swap(be_to_face, other.be_to_face);
|
|
mfem::Swap(face_edge, other.face_edge);
|
|
mfem::Swap(face_to_elem, other.face_to_elem);
|
|
mfem::Swap(edge_vertex, other.edge_vertex);
|
|
|
|
mfem::Swap(attributes, other.attributes);
|
|
mfem::Swap(bdr_attributes, other.bdr_attributes);
|
|
|
|
mfem::Swap(geom_factors, other.geom_factors);
|
|
mfem::Swap(face_geom_factors, other.face_geom_factors);
|
|
|
|
#ifdef MFEM_USE_MEMALLOC
|
|
TetMemory.Swap(other.TetMemory);
|
|
#endif
|
|
|
|
if (non_geometry)
|
|
{
|
|
mfem::Swap(NURBSext, other.NURBSext);
|
|
mfem::Swap(ncmesh, other.ncmesh);
|
|
|
|
mfem::Swap(Nodes, other.Nodes);
|
|
if (Nodes) { Nodes->FESpace()->UpdateMeshPointer(this); }
|
|
if (other.Nodes) { other.Nodes->FESpace()->UpdateMeshPointer(&other); }
|
|
mfem::Swap(own_nodes, other.own_nodes);
|
|
|
|
mfem::Swap(CoarseFineTr, other.CoarseFineTr);
|
|
|
|
mfem::Swap(sequence, other.sequence);
|
|
mfem::Swap(nodes_sequence, other.nodes_sequence);
|
|
mfem::Swap(last_operation, other.last_operation);
|
|
}
|
|
|
|
// copy attribute caches
|
|
mfem::Swap(elem_attrs_cache, other.elem_attrs_cache);
|
|
mfem::Swap(bdr_face_attrs_cache, other.bdr_face_attrs_cache);
|
|
|
|
mfem::Swap(face_indices[0], other.face_indices[0]);
|
|
mfem::Swap(face_indices[1], other.face_indices[1]);
|
|
inv_face_indices[0].swap(other.inv_face_indices[0]);
|
|
inv_face_indices[1].swap(other.inv_face_indices[1]);
|
|
}
|
|
|
|
void Mesh::GetElementData(const Array<Element*> &elem_array, int geom,
|
|
Array<int> &elem_vtx, Array<int> &attr) const
|
|
{
|
|
// protected method
|
|
const int nv = Geometry::NumVerts[geom];
|
|
int num_elems = 0;
|
|
for (int i = 0; i < elem_array.Size(); i++)
|
|
{
|
|
if (elem_array[i]->GetGeometryType() == geom)
|
|
{
|
|
num_elems++;
|
|
}
|
|
}
|
|
elem_vtx.SetSize(nv*num_elems);
|
|
attr.SetSize(num_elems);
|
|
elem_vtx.SetSize(0);
|
|
attr.SetSize(0);
|
|
for (int i = 0; i < elem_array.Size(); i++)
|
|
{
|
|
Element *el = elem_array[i];
|
|
if (el->GetGeometryType() != geom) { continue; }
|
|
|
|
Array<int> loc_vtx(el->GetVertices(), nv);
|
|
elem_vtx.Append(loc_vtx);
|
|
attr.Append(el->GetAttribute());
|
|
}
|
|
}
|
|
|
|
static Array<int>& AllElements(Array<int> &list, int nelem)
|
|
{
|
|
list.SetSize(nelem);
|
|
for (int i = 0; i < nelem; i++) { list[i] = i; }
|
|
return list;
|
|
}
|
|
|
|
void Mesh::UniformRefinement(int ref_algo)
|
|
{
|
|
Array<int> list;
|
|
|
|
if (NURBSext)
|
|
{
|
|
NURBSUniformRefinement();
|
|
}
|
|
else if (ncmesh)
|
|
{
|
|
GeneralRefinement(AllElements(list, GetNE()));
|
|
}
|
|
else if (ref_algo == 1 && meshgen == 1 && Dim == 3)
|
|
{
|
|
// algorithm "B" for an all-tet mesh
|
|
LocalRefinement(AllElements(list, GetNE()));
|
|
}
|
|
else
|
|
{
|
|
switch (Dim)
|
|
{
|
|
case 1: LocalRefinement(AllElements(list, GetNE())); break;
|
|
case 2: UniformRefinement2D(); break;
|
|
case 3: UniformRefinement3D(); break;
|
|
default: MFEM_ABORT("internal error");
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::NURBSCoarsening(int cf, real_t tol)
|
|
{
|
|
if (NURBSext && cf > 1)
|
|
{
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
Array<int> initialCoarsening; // Initial coarsening factors
|
|
NURBSext->GetCoarseningFactors(initialCoarsening);
|
|
|
|
// If refinement formulas are nested, then initial coarsening is skipped.
|
|
bool noInitialCoarsening = true;
|
|
for (auto f : initialCoarsening)
|
|
{
|
|
noInitialCoarsening = (noInitialCoarsening && f == 1);
|
|
}
|
|
|
|
if (noInitialCoarsening)
|
|
{
|
|
NURBSext->Coarsen(cf, tol);
|
|
}
|
|
else
|
|
{
|
|
// Perform an initial full coarsening, and then refine. This is
|
|
// necessary only for non-nested refinement formulas.
|
|
NURBSext->Coarsen(initialCoarsening, tol);
|
|
|
|
// FiniteElementSpace::Update is not supported
|
|
last_operation = Mesh::NONE;
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
|
|
// Prepare for refinement by factors.
|
|
NURBSext->ConvertToPatches(*Nodes);
|
|
|
|
Array<int> rf(initialCoarsening);
|
|
bool divisible = true;
|
|
for (int i=0; i<rf.Size(); ++i)
|
|
{
|
|
rf[i] /= cf;
|
|
divisible = divisible && cf * rf[i] == initialCoarsening[i];
|
|
}
|
|
|
|
MFEM_VERIFY(divisible, "Invalid coarsening");
|
|
|
|
// Refine from the fully coarsened mesh to the mesh coarsened by the
|
|
// factor cf.
|
|
NURBSext->UniformRefinement(rf);
|
|
}
|
|
|
|
last_operation = Mesh::NONE; // FiniteElementSpace::Update is not supported
|
|
sequence++;
|
|
|
|
UpdateNURBS();
|
|
}
|
|
}
|
|
|
|
void Mesh::GeneralRefinement(const Array<Refinement> &refinements,
|
|
int nonconforming, int nc_limit)
|
|
{
|
|
if (ncmesh)
|
|
{
|
|
nonconforming = 1;
|
|
}
|
|
else if (Dim == 1 || (Dim == 3 && (meshgen & 1)))
|
|
{
|
|
nonconforming = 0;
|
|
}
|
|
else if (nonconforming < 0)
|
|
{
|
|
// determine if nonconforming refinement is suitable
|
|
if ((meshgen & 2) || (meshgen & 4) || (meshgen & 8))
|
|
{
|
|
nonconforming = 1; // tensor product elements and wedges
|
|
}
|
|
else
|
|
{
|
|
nonconforming = 0; // simplices
|
|
}
|
|
}
|
|
|
|
if (nonconforming)
|
|
{
|
|
// non-conforming refinement (hanging nodes)
|
|
NonconformingRefinement(refinements, nc_limit);
|
|
}
|
|
else
|
|
{
|
|
Array<int> el_to_refine(refinements.Size());
|
|
for (int i = 0; i < refinements.Size(); i++)
|
|
{
|
|
el_to_refine[i] = refinements[i].index;
|
|
}
|
|
|
|
// infer 'type' of local refinement from first element's 'ref_type'
|
|
int type, rt = (refinements.Size() ? refinements[0].GetType() : 7);
|
|
if (rt == 1 || rt == 2 || rt == 4)
|
|
{
|
|
type = 1; // bisection
|
|
}
|
|
else if (rt == 3 || rt == 5 || rt == 6)
|
|
{
|
|
type = 2; // quadrisection
|
|
}
|
|
else
|
|
{
|
|
type = 3; // octasection
|
|
}
|
|
|
|
// red-green refinement and bisection, no hanging nodes
|
|
LocalRefinement(el_to_refine, type);
|
|
}
|
|
}
|
|
|
|
void Mesh::GeneralRefinement(const Array<int> &el_to_refine, int nonconforming,
|
|
int nc_limit)
|
|
{
|
|
Array<Refinement> refinements(el_to_refine.Size());
|
|
for (int i = 0; i < el_to_refine.Size(); i++)
|
|
{
|
|
refinements[i] = Refinement(el_to_refine[i]);
|
|
}
|
|
GeneralRefinement(refinements, nonconforming, nc_limit);
|
|
}
|
|
|
|
void Mesh::EnsureNCMesh(bool simplices_nonconforming)
|
|
{
|
|
MFEM_VERIFY(!NURBSext, "Cannot convert a NURBS mesh to an NC mesh. "
|
|
"Please project the NURBS to Nodes first, with SetCurvature().");
|
|
|
|
#ifdef MFEM_USE_MPI
|
|
MFEM_VERIFY(ncmesh != NULL || dynamic_cast<const ParMesh*>(this) == NULL,
|
|
"Sorry, converting a conforming ParMesh to an NC mesh is "
|
|
"not possible.");
|
|
#endif
|
|
|
|
if (!ncmesh)
|
|
{
|
|
if ((meshgen & 0x2) /* quads/hexes */ ||
|
|
(meshgen & 0x4) /* wedges */ ||
|
|
(simplices_nonconforming && (meshgen & 0x1)) /* simplices */)
|
|
{
|
|
ncmesh = new NCMesh(this);
|
|
ncmesh->OnMeshUpdated(this);
|
|
GenerateNCFaceInfo();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::RandomRefinement(real_t prob, bool aniso, int nonconforming,
|
|
int nc_limit)
|
|
{
|
|
Array<Refinement> refs;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
if ((real_t) rand() / real_t(RAND_MAX) < prob)
|
|
{
|
|
int type = 7;
|
|
if (aniso)
|
|
{
|
|
type = (Dim == 3) ? (rand() % 7 + 1) : (rand() % 3 + 1);
|
|
}
|
|
refs.Append(Refinement(i, type));
|
|
}
|
|
}
|
|
GeneralRefinement(refs, nonconforming, nc_limit);
|
|
}
|
|
|
|
void Mesh::RefineAtVertex(const Vertex& vert, real_t eps, int nonconforming)
|
|
{
|
|
Array<int> v;
|
|
Array<Refinement> refs;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
GetElementVertices(i, v);
|
|
bool refine = false;
|
|
for (int j = 0; j < v.Size(); j++)
|
|
{
|
|
real_t dist = 0.0;
|
|
for (int l = 0; l < spaceDim; l++)
|
|
{
|
|
real_t d = vert(l) - vertices[v[j]](l);
|
|
dist += d*d;
|
|
}
|
|
if (dist <= eps*eps) { refine = true; break; }
|
|
}
|
|
if (refine)
|
|
{
|
|
refs.Append(Refinement(i));
|
|
}
|
|
}
|
|
GeneralRefinement(refs, nonconforming);
|
|
}
|
|
|
|
bool Mesh::RefineByError(const Array<real_t> &elem_error, real_t threshold,
|
|
int nonconforming, int nc_limit)
|
|
{
|
|
MFEM_VERIFY(elem_error.Size() == GetNE(), "");
|
|
Array<Refinement> refs;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
if (elem_error[i] > threshold)
|
|
{
|
|
refs.Append(Refinement(i));
|
|
}
|
|
}
|
|
if (ReduceInt(refs.Size()))
|
|
{
|
|
GeneralRefinement(refs, nonconforming, nc_limit);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Mesh::RefineByError(const Vector &elem_error, real_t threshold,
|
|
int nonconforming, int nc_limit)
|
|
{
|
|
Array<real_t> tmp(const_cast<real_t*>(elem_error.GetData()),
|
|
elem_error.Size());
|
|
return RefineByError(tmp, threshold, nonconforming, nc_limit);
|
|
}
|
|
|
|
|
|
void Mesh::Bisection(int i, const DSTable &v_to_v,
|
|
int *edge1, int *edge2, int *middle)
|
|
{
|
|
int *vert;
|
|
int v[2][4], v_new, bisect, t;
|
|
Element *el = elements[i];
|
|
Vertex V;
|
|
|
|
t = el->GetType();
|
|
if (t == Element::TRIANGLE)
|
|
{
|
|
Triangle *tri = (Triangle *) el;
|
|
|
|
vert = tri->GetVertices();
|
|
|
|
// 1. Get the index for the new vertex in v_new.
|
|
bisect = v_to_v(vert[0], vert[1]);
|
|
MFEM_ASSERT(bisect >= 0, "");
|
|
|
|
if (middle[bisect] == -1)
|
|
{
|
|
v_new = NumOfVertices++;
|
|
for (int d = 0; d < spaceDim; d++)
|
|
{
|
|
V(d) = 0.5 * (vertices[vert[0]](d) + vertices[vert[1]](d));
|
|
}
|
|
vertices.Append(V);
|
|
|
|
// Put the element that may need refinement (because of this
|
|
// bisection) in edge1, or -1 if no more refinement is needed.
|
|
if (edge1[bisect] == i)
|
|
{
|
|
edge1[bisect] = edge2[bisect];
|
|
}
|
|
|
|
middle[bisect] = v_new;
|
|
}
|
|
else
|
|
{
|
|
v_new = middle[bisect];
|
|
|
|
// This edge will require no more refinement.
|
|
edge1[bisect] = -1;
|
|
}
|
|
|
|
// 2. Set the node indices for the new elements in v[0] and v[1] so that
|
|
// the edge marked for refinement is between the first two nodes.
|
|
v[0][0] = vert[2]; v[0][1] = vert[0]; v[0][2] = v_new;
|
|
v[1][0] = vert[1]; v[1][1] = vert[2]; v[1][2] = v_new;
|
|
|
|
tri->SetVertices(v[0]); // changes vert[0..2] !!!
|
|
|
|
Triangle* tri_new = new Triangle(v[1], tri->GetAttribute());
|
|
elements.Append(tri_new);
|
|
|
|
int tr = tri->GetTransform();
|
|
tri_new->ResetTransform(tr);
|
|
|
|
// record the sequence of refinements
|
|
tri->PushTransform(4);
|
|
tri_new->PushTransform(5);
|
|
|
|
int coarse = FindCoarseElement(i);
|
|
CoarseFineTr.embeddings[i].parent = coarse;
|
|
CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE));
|
|
|
|
// 3. edge1 and edge2 may have to be changed for the second triangle.
|
|
if (v[1][0] < v_to_v.NumberOfRows() && v[1][1] < v_to_v.NumberOfRows())
|
|
{
|
|
bisect = v_to_v(v[1][0], v[1][1]);
|
|
MFEM_ASSERT(bisect >= 0, "");
|
|
|
|
if (edge1[bisect] == i)
|
|
{
|
|
edge1[bisect] = NumOfElements;
|
|
}
|
|
else if (edge2[bisect] == i)
|
|
{
|
|
edge2[bisect] = NumOfElements;
|
|
}
|
|
}
|
|
NumOfElements++;
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Bisection for now works only for triangles.");
|
|
}
|
|
}
|
|
|
|
void Mesh::Bisection(int i, HashTable<Hashed2> &v_to_v)
|
|
{
|
|
int *vert;
|
|
int v[2][4], v_new, bisect, t;
|
|
Element *el = elements[i];
|
|
Vertex V;
|
|
|
|
t = el->GetType();
|
|
if (t == Element::TETRAHEDRON)
|
|
{
|
|
Tetrahedron *tet = (Tetrahedron *) el;
|
|
|
|
MFEM_VERIFY(tet->GetRefinementFlag() != 0,
|
|
"TETRAHEDRON element is not marked for refinement.");
|
|
|
|
vert = tet->GetVertices();
|
|
|
|
// 1. Get the index for the new vertex in v_new.
|
|
bisect = v_to_v.FindId(vert[0], vert[1]);
|
|
if (bisect == -1)
|
|
{
|
|
v_new = NumOfVertices + v_to_v.GetId(vert[0],vert[1]);
|
|
for (int j = 0; j < 3; j++)
|
|
{
|
|
V(j) = 0.5 * (vertices[vert[0]](j) + vertices[vert[1]](j));
|
|
}
|
|
vertices.Append(V);
|
|
}
|
|
else
|
|
{
|
|
v_new = NumOfVertices + bisect;
|
|
}
|
|
|
|
// 2. Set the node indices for the new elements in v[2][4] so that
|
|
// the edge marked for refinement is between the first two nodes.
|
|
int type, old_redges[2], flag;
|
|
tet->ParseRefinementFlag(old_redges, type, flag);
|
|
|
|
int new_type, new_redges[2][2];
|
|
v[0][3] = v_new;
|
|
v[1][3] = v_new;
|
|
new_redges[0][0] = 2;
|
|
new_redges[0][1] = 1;
|
|
new_redges[1][0] = 2;
|
|
new_redges[1][1] = 1;
|
|
int tr1 = -1, tr2 = -1;
|
|
switch (old_redges[0])
|
|
{
|
|
case 2:
|
|
v[0][0] = vert[0]; v[0][1] = vert[2]; v[0][2] = vert[3];
|
|
if (type == Tetrahedron::TYPE_PF) { new_redges[0][1] = 4; }
|
|
tr1 = 0;
|
|
break;
|
|
case 3:
|
|
v[0][0] = vert[3]; v[0][1] = vert[0]; v[0][2] = vert[2];
|
|
tr1 = 2;
|
|
break;
|
|
case 5:
|
|
v[0][0] = vert[2]; v[0][1] = vert[3]; v[0][2] = vert[0];
|
|
tr1 = 4;
|
|
}
|
|
switch (old_redges[1])
|
|
{
|
|
case 1:
|
|
v[1][0] = vert[2]; v[1][1] = vert[1]; v[1][2] = vert[3];
|
|
if (type == Tetrahedron::TYPE_PF) { new_redges[1][0] = 3; }
|
|
tr2 = 1;
|
|
break;
|
|
case 4:
|
|
v[1][0] = vert[1]; v[1][1] = vert[3]; v[1][2] = vert[2];
|
|
tr2 = 3;
|
|
break;
|
|
case 5:
|
|
v[1][0] = vert[3]; v[1][1] = vert[2]; v[1][2] = vert[1];
|
|
tr2 = 5;
|
|
}
|
|
|
|
int attr = tet->GetAttribute();
|
|
tet->SetVertices(v[0]);
|
|
|
|
#ifdef MFEM_USE_MEMALLOC
|
|
Tetrahedron *tet2 = TetMemory.Alloc();
|
|
tet2->SetVertices(v[1]);
|
|
tet2->SetAttribute(attr);
|
|
#else
|
|
Tetrahedron *tet2 = new Tetrahedron(v[1], attr);
|
|
#endif
|
|
tet2->ResetTransform(tet->GetTransform());
|
|
elements.Append(tet2);
|
|
|
|
// record the sequence of refinements
|
|
tet->PushTransform(tr1);
|
|
tet2->PushTransform(tr2);
|
|
|
|
int coarse = FindCoarseElement(i);
|
|
CoarseFineTr.embeddings[i].parent = coarse;
|
|
CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TETRAHEDRON));
|
|
|
|
// 3. Set the bisection flag
|
|
switch (type)
|
|
{
|
|
case Tetrahedron::TYPE_PU:
|
|
new_type = Tetrahedron::TYPE_PF; break;
|
|
case Tetrahedron::TYPE_PF:
|
|
new_type = Tetrahedron::TYPE_A; break;
|
|
default:
|
|
new_type = Tetrahedron::TYPE_PU;
|
|
}
|
|
|
|
tet->CreateRefinementFlag(new_redges[0], new_type, flag+1);
|
|
tet2->CreateRefinementFlag(new_redges[1], new_type, flag+1);
|
|
|
|
NumOfElements++;
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Bisection with HashTable for now works only for tetrahedra.");
|
|
}
|
|
}
|
|
|
|
void Mesh::BdrBisection(int i, const HashTable<Hashed2> &v_to_v)
|
|
{
|
|
int *vert;
|
|
int v[2][3], v_new, bisect, t;
|
|
Element *bdr_el = boundary[i];
|
|
|
|
t = bdr_el->GetType();
|
|
if (t == Element::TRIANGLE)
|
|
{
|
|
Triangle *tri = (Triangle *) bdr_el;
|
|
|
|
vert = tri->GetVertices();
|
|
|
|
// 1. Get the index for the new vertex in v_new.
|
|
bisect = v_to_v.FindId(vert[0], vert[1]);
|
|
MFEM_ASSERT(bisect >= 0, "");
|
|
v_new = NumOfVertices + bisect;
|
|
MFEM_ASSERT(v_new != -1, "");
|
|
|
|
// 2. Set the node indices for the new elements in v[0] and v[1] so that
|
|
// the edge marked for refinement is between the first two nodes.
|
|
v[0][0] = vert[2]; v[0][1] = vert[0]; v[0][2] = v_new;
|
|
v[1][0] = vert[1]; v[1][1] = vert[2]; v[1][2] = v_new;
|
|
|
|
tri->SetVertices(v[0]);
|
|
|
|
boundary.Append(new Triangle(v[1], tri->GetAttribute()));
|
|
|
|
NumOfBdrElements++;
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Bisection of boundary elements with HashTable works only for"
|
|
" triangles!");
|
|
}
|
|
}
|
|
|
|
void Mesh::UniformRefinement(int i, const DSTable &v_to_v,
|
|
int *edge1, int *edge2, int *middle)
|
|
{
|
|
Array<int> v;
|
|
int j, v1[3], v2[3], v3[3], v4[3], v_new[3], bisect[3];
|
|
Vertex V;
|
|
|
|
if (elements[i]->GetType() == Element::TRIANGLE)
|
|
{
|
|
Triangle *tri0 = (Triangle*) elements[i];
|
|
tri0->GetVertices(v);
|
|
|
|
// 1. Get the indices for the new vertices in array v_new
|
|
bisect[0] = v_to_v(v[0],v[1]);
|
|
bisect[1] = v_to_v(v[1],v[2]);
|
|
bisect[2] = v_to_v(v[0],v[2]);
|
|
MFEM_ASSERT(bisect[0] >= 0 && bisect[1] >= 0 && bisect[2] >= 0, "");
|
|
|
|
for (j = 0; j < 3; j++) // for the 3 edges fix v_new
|
|
{
|
|
if (middle[bisect[j]] == -1)
|
|
{
|
|
v_new[j] = NumOfVertices++;
|
|
for (int d = 0; d < spaceDim; d++)
|
|
{
|
|
V(d) = (vertices[v[j]](d) + vertices[v[(j+1)%3]](d))/2.;
|
|
}
|
|
vertices.Append(V);
|
|
|
|
// Put the element that may need refinement (because of this
|
|
// bisection) in edge1, or -1 if no more refinement is needed.
|
|
if (edge1[bisect[j]] == i)
|
|
{
|
|
edge1[bisect[j]] = edge2[bisect[j]];
|
|
}
|
|
|
|
middle[bisect[j]] = v_new[j];
|
|
}
|
|
else
|
|
{
|
|
v_new[j] = middle[bisect[j]];
|
|
|
|
// This edge will require no more refinement.
|
|
edge1[bisect[j]] = -1;
|
|
}
|
|
}
|
|
|
|
// 2. Set the node indices for the new elements in v1, v2, v3 & v4 so that
|
|
// the edges marked for refinement be between the first two nodes.
|
|
v1[0] = v[0]; v1[1] = v_new[0]; v1[2] = v_new[2];
|
|
v2[0] = v_new[0]; v2[1] = v[1]; v2[2] = v_new[1];
|
|
v3[0] = v_new[2]; v3[1] = v_new[1]; v3[2] = v[2];
|
|
v4[0] = v_new[1]; v4[1] = v_new[2]; v4[2] = v_new[0];
|
|
|
|
Triangle* tri1 = new Triangle(v1, tri0->GetAttribute());
|
|
Triangle* tri2 = new Triangle(v2, tri0->GetAttribute());
|
|
Triangle* tri3 = new Triangle(v3, tri0->GetAttribute());
|
|
|
|
elements.Append(tri1);
|
|
elements.Append(tri2);
|
|
elements.Append(tri3);
|
|
|
|
tri0->SetVertices(v4);
|
|
|
|
// record the sequence of refinements
|
|
unsigned code = tri0->GetTransform();
|
|
tri1->ResetTransform(code);
|
|
tri2->ResetTransform(code);
|
|
tri3->ResetTransform(code);
|
|
|
|
tri0->PushTransform(3);
|
|
tri1->PushTransform(0);
|
|
tri2->PushTransform(1);
|
|
tri3->PushTransform(2);
|
|
|
|
// set parent indices
|
|
int coarse = FindCoarseElement(i);
|
|
CoarseFineTr.embeddings[i] = Embedding(coarse, Geometry::TRIANGLE);
|
|
CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE));
|
|
CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE));
|
|
CoarseFineTr.embeddings.Append(Embedding(coarse, Geometry::TRIANGLE));
|
|
|
|
NumOfElements += 3;
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Uniform refinement for now works only for triangles.");
|
|
}
|
|
}
|
|
|
|
void Mesh::InitRefinementTransforms()
|
|
{
|
|
// initialize CoarseFineTr
|
|
CoarseFineTr.Clear();
|
|
CoarseFineTr.embeddings.SetSize(NumOfElements);
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
elements[i]->ResetTransform(0);
|
|
CoarseFineTr.embeddings[i] = Embedding(i, GetElementGeometry(i));
|
|
}
|
|
}
|
|
|
|
int Mesh::FindCoarseElement(int i)
|
|
{
|
|
int coarse;
|
|
while ((coarse = CoarseFineTr.embeddings[i].parent) != i)
|
|
{
|
|
i = coarse;
|
|
}
|
|
return coarse;
|
|
}
|
|
|
|
const CoarseFineTransformations &Mesh::GetRefinementTransforms() const
|
|
{
|
|
MFEM_VERIFY(GetLastOperation() == Mesh::REFINE, "");
|
|
|
|
if (ncmesh)
|
|
{
|
|
return ncmesh->GetRefinementTransforms();
|
|
}
|
|
|
|
Mesh::GeometryList elem_geoms(*this);
|
|
for (int i = 0; i < elem_geoms.Size(); i++)
|
|
{
|
|
const Geometry::Type geom = elem_geoms[i];
|
|
if (CoarseFineTr.point_matrices[geom].SizeK()) { continue; }
|
|
|
|
if (geom == Geometry::TRIANGLE ||
|
|
geom == Geometry::TETRAHEDRON)
|
|
{
|
|
std::map<unsigned, int> mat_no;
|
|
mat_no[0] = 1; // identity
|
|
|
|
// assign matrix indices to element transformations
|
|
for (int j = 0; j < elements.Size(); j++)
|
|
{
|
|
int index = 0;
|
|
unsigned code = elements[j]->GetTransform();
|
|
if (code)
|
|
{
|
|
int &matrix = mat_no[code];
|
|
if (!matrix) { matrix = static_cast<int>(mat_no.size()); }
|
|
index = matrix-1;
|
|
}
|
|
CoarseFineTr.embeddings[j].matrix = index;
|
|
}
|
|
|
|
DenseTensor &pmats = CoarseFineTr.point_matrices[geom];
|
|
pmats.SetSize(Dim, Dim+1, static_cast<int>((mat_no.size())));
|
|
|
|
// calculate the point matrices used
|
|
std::map<unsigned, int>::iterator it;
|
|
for (it = mat_no.begin(); it != mat_no.end(); ++it)
|
|
{
|
|
if (geom == Geometry::TRIANGLE)
|
|
{
|
|
Triangle::GetPointMatrix(it->first, pmats(it->second-1));
|
|
}
|
|
else
|
|
{
|
|
Tetrahedron::GetPointMatrix(it->first, pmats(it->second-1));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MFEM_ABORT("Don't know how to construct CoarseFineTransformations for"
|
|
" geom = " << geom);
|
|
}
|
|
}
|
|
|
|
// NOTE: quads and hexes already have trivial transformations ready
|
|
return CoarseFineTr;
|
|
}
|
|
|
|
void Mesh::PrintXG(std::ostream &os) const
|
|
{
|
|
MFEM_ASSERT(Dim==spaceDim, "2D Manifold meshes not supported");
|
|
int i, j;
|
|
Array<int> v;
|
|
|
|
if (Dim == 2)
|
|
{
|
|
// Print the type of the mesh.
|
|
if (Nodes == NULL)
|
|
{
|
|
os << "areamesh2\n\n";
|
|
}
|
|
else
|
|
{
|
|
os << "curved_areamesh2\n\n";
|
|
}
|
|
|
|
// Print the boundary elements.
|
|
os << NumOfBdrElements << '\n';
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
boundary[i]->GetVertices(v);
|
|
|
|
os << boundary[i]->GetAttribute();
|
|
for (j = 0; j < v.Size(); j++)
|
|
{
|
|
os << ' ' << v[j] + 1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
// Print the elements.
|
|
os << NumOfElements << '\n';
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
elements[i]->GetVertices(v);
|
|
|
|
os << elements[i]->GetAttribute() << ' ' << v.Size();
|
|
for (j = 0; j < v.Size(); j++)
|
|
{
|
|
os << ' ' << v[j] + 1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
if (Nodes == NULL)
|
|
{
|
|
// Print the vertices.
|
|
os << NumOfVertices << '\n';
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
os << vertices[i](0);
|
|
for (j = 1; j < Dim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
os << NumOfVertices << '\n';
|
|
Nodes->Save(os);
|
|
}
|
|
}
|
|
else // ===== Dim != 2 =====
|
|
{
|
|
if (Nodes)
|
|
{
|
|
mfem_error("Mesh::PrintXG(...) : Curved mesh in 3D");
|
|
}
|
|
|
|
if (meshgen == 1)
|
|
{
|
|
int nv;
|
|
const int *ind;
|
|
|
|
os << "NETGEN_Neutral_Format\n";
|
|
// print the vertices
|
|
os << NumOfVertices << '\n';
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
for (j = 0; j < Dim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
// print the elements
|
|
os << NumOfElements << '\n';
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
nv = elements[i]->GetNVertices();
|
|
ind = elements[i]->GetVertices();
|
|
os << elements[i]->GetAttribute();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << ind[j]+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
// print the boundary information.
|
|
os << NumOfBdrElements << '\n';
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
nv = boundary[i]->GetNVertices();
|
|
ind = boundary[i]->GetVertices();
|
|
os << boundary[i]->GetAttribute();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << ind[j]+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
else if (meshgen == 2) // TrueGrid
|
|
{
|
|
int nv;
|
|
const int *ind;
|
|
|
|
os << "TrueGrid\n"
|
|
<< "1 " << NumOfVertices << " " << NumOfElements
|
|
<< " 0 0 0 0 0 0 0\n"
|
|
<< "0 0 0 1 0 0 0 0 0 0 0\n"
|
|
<< "0 0 " << NumOfBdrElements << " 0 0 0 0 0 0 0 0 0 0 0 0 0\n"
|
|
<< "0.0 0.0 0.0 0 0 0.0 0.0 0 0.0\n"
|
|
<< "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n";
|
|
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
os << i+1 << " 0.0 " << vertices[i](0) << ' ' << vertices[i](1)
|
|
<< ' ' << vertices[i](2) << " 0.0\n";
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
nv = elements[i]->GetNVertices();
|
|
ind = elements[i]->GetVertices();
|
|
os << i+1 << ' ' << elements[i]->GetAttribute();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << ind[j]+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
nv = boundary[i]->GetNVertices();
|
|
ind = boundary[i]->GetVertices();
|
|
os << boundary[i]->GetAttribute();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << ind[j]+1;
|
|
}
|
|
os << " 1.0 1.0 1.0 1.0\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
os << flush;
|
|
}
|
|
|
|
void Mesh::Printer(std::ostream &os, std::string section_delimiter,
|
|
const std::string &comments) const
|
|
{
|
|
int i, j;
|
|
|
|
if (NURBSext)
|
|
{
|
|
// general format
|
|
NURBSext->Print(os, comments);
|
|
os << '\n';
|
|
Nodes->Save(os);
|
|
|
|
NURBSext->PrintCoarsePatches(os);
|
|
// patch-wise format
|
|
// NURBSext->ConvertToPatches(*Nodes);
|
|
// NURBSext->Print(os);
|
|
|
|
return;
|
|
}
|
|
|
|
if (Nonconforming())
|
|
{
|
|
// Workaround for inconsistent Mesh state where the Mesh has nodes and
|
|
// ncmesh->coodrinates is not empty. Such state can be created with the
|
|
// method Mesh::SwapNodes(), see the comment at the beginning of its
|
|
// implementation.
|
|
Array<real_t> coords_save;
|
|
if (Nodes) { mfem::Swap(coords_save, ncmesh->coordinates); }
|
|
|
|
// nonconforming mesh format
|
|
ncmesh->Print(os, comments);
|
|
|
|
if (Nodes)
|
|
{
|
|
mfem::Swap(coords_save, ncmesh->coordinates);
|
|
|
|
os << "\n# mesh curvature GridFunction";
|
|
os << "\nnodes\n";
|
|
Nodes->Save(os);
|
|
}
|
|
|
|
os << "\nmfem_mesh_end" << endl;
|
|
return;
|
|
}
|
|
|
|
// serial/parallel conforming mesh format
|
|
const bool set_names = attribute_sets.SetsExist() ||
|
|
bdr_attribute_sets.SetsExist();
|
|
os << (!set_names && section_delimiter.empty()
|
|
? "MFEM mesh v1.0\n" :
|
|
(!set_names ? "MFEM mesh v1.2\n" : "MFEM mesh v1.3\n"));
|
|
|
|
if (set_names && section_delimiter.empty())
|
|
{
|
|
section_delimiter = "mfem_mesh_end";
|
|
}
|
|
|
|
// optional
|
|
if (!comments.empty()) { os << '\n' << comments << '\n'; }
|
|
|
|
os <<
|
|
"\n#\n# MFEM Geometry Types (see fem/geom.hpp):\n#\n"
|
|
"# POINT = 0\n"
|
|
"# SEGMENT = 1\n"
|
|
"# TRIANGLE = 2\n"
|
|
"# SQUARE = 3\n"
|
|
"# TETRAHEDRON = 4\n"
|
|
"# CUBE = 5\n"
|
|
"# PRISM = 6\n"
|
|
"# PYRAMID = 7\n"
|
|
"#\n";
|
|
|
|
os << "\ndimension\n" << Dim;
|
|
|
|
os << "\n\nelements\n" << NumOfElements << '\n';
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
PrintElement(elements[i], os);
|
|
}
|
|
|
|
if (set_names)
|
|
{
|
|
os << "\nattribute_sets\n";
|
|
attribute_sets.Print(os);
|
|
}
|
|
|
|
os << "\nboundary\n" << NumOfBdrElements << '\n';
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
PrintElement(boundary[i], os);
|
|
}
|
|
|
|
if (set_names)
|
|
{
|
|
os << "\nbdr_attribute_sets\n";
|
|
bdr_attribute_sets.Print(os);
|
|
}
|
|
|
|
os << "\nvertices\n" << NumOfVertices << '\n';
|
|
if (Nodes == NULL)
|
|
{
|
|
os << spaceDim << '\n';
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
os << vertices[i](0);
|
|
for (j = 1; j < spaceDim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
os << '\n';
|
|
}
|
|
os.flush();
|
|
}
|
|
else
|
|
{
|
|
os << "\nnodes\n";
|
|
Nodes->Save(os);
|
|
}
|
|
|
|
if (!section_delimiter.empty())
|
|
{
|
|
os << '\n'
|
|
<< section_delimiter << endl; // only with formats v1.2 and above
|
|
}
|
|
}
|
|
|
|
void Mesh::PrintTopo(std::ostream &os, const Array<int> &e_to_k,
|
|
const int version, const std::string &comments) const
|
|
{
|
|
MFEM_VERIFY(version == 10 || version == 11, "Invalid NURBS mesh version");
|
|
|
|
int i;
|
|
Array<int> vert;
|
|
|
|
os << "MFEM NURBS mesh v" << int(version / 10) << "." << version % 10 << "\n";
|
|
|
|
// optional
|
|
if (!comments.empty()) { os << '\n' << comments << '\n'; }
|
|
|
|
os <<
|
|
"\n#\n# MFEM Geometry Types (see fem/geom.hpp):\n#\n"
|
|
"# SEGMENT = 1\n"
|
|
"# SQUARE = 3\n"
|
|
"# CUBE = 5\n"
|
|
"#\n";
|
|
|
|
os << "\ndimension\n" << Dim
|
|
<< "\n\nelements\n" << NumOfElements << '\n';
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
PrintElement(elements[i], os);
|
|
}
|
|
|
|
os << "\nboundary\n" << NumOfBdrElements << '\n';
|
|
for (i = 0; i < NumOfBdrElements; i++)
|
|
{
|
|
PrintElement(boundary[i], os);
|
|
}
|
|
|
|
PrintTopoEdges(os, e_to_k);
|
|
}
|
|
|
|
void Mesh::PrintTopoEdges(std::ostream &os, const Array<int> &e_to_k,
|
|
bool vmap) const
|
|
{
|
|
Array<int> vert;
|
|
|
|
// In 1D patch-topology NURBS meshes, knotvector orientation is stored in the
|
|
// file's `edges` section, but the topological 1D mesh has NumOfEdges == 0
|
|
// (its "faces" are vertices). When a valid edge->knotvector map is provided,
|
|
// print a pseudo-edge list derived from the 1D elements so external tools
|
|
// (e.g. VisIt) can consume the mapping.
|
|
if (Dim == 1 && NumOfEdges == 0 && e_to_k.Size() == NumOfElements)
|
|
{
|
|
const int ne = NumOfElements;
|
|
os << "\nedges\n" << ne << '\n';
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
const int *v = elements[i]->GetVertices();
|
|
int v0 = v[0], v1 = v[1];
|
|
|
|
int ki = e_to_k[i];
|
|
const bool flip = (ki < 0); // desired output vertex order: descending
|
|
if (flip) { ki = -1 - ki; } // print the unsigned knotvector index
|
|
|
|
// Encode the sign of e_to_k in the vertex ordering, consistent with
|
|
// Mesh::LoadPatchTopo(): v0 > v1 => negative sign.
|
|
if ((v0 > v1) != flip) { std::swap(v0, v1); }
|
|
|
|
os << ki << ' ' << v0 << ' ' << v1 << '\n';
|
|
}
|
|
|
|
if (!vmap)
|
|
{
|
|
os << "\nvertices\n" << NumOfVertices << '\n';
|
|
}
|
|
return;
|
|
}
|
|
|
|
os << "\nedges\n" << NumOfEdges << '\n';
|
|
for (int i = 0; i < NumOfEdges; i++)
|
|
{
|
|
edge_vertex->GetRow(i, vert);
|
|
const int ki = UnsignIndex(e_to_k[i]);
|
|
|
|
if (vmap)
|
|
{
|
|
for (int j=0; j<2; ++j)
|
|
{
|
|
vert[j] = ncmesh->vertex_nodeId[vert[j]];
|
|
}
|
|
|
|
if (e_to_k[i] < 0)
|
|
{
|
|
// Swap the entries of vert
|
|
const int s = vert[0];
|
|
vert[0] = vert[1];
|
|
vert[1] = s;
|
|
}
|
|
}
|
|
|
|
os << ki << ' ' << vert[0] << ' ' << vert[1] << '\n';
|
|
}
|
|
|
|
if (!vmap)
|
|
{
|
|
os << "\nvertices\n" << NumOfVertices << '\n';
|
|
}
|
|
}
|
|
|
|
void Mesh::Save(const std::string &fname, int precision) const
|
|
{
|
|
ofstream ofs(fname);
|
|
ofs.precision(precision);
|
|
Print(ofs);
|
|
}
|
|
|
|
#ifdef MFEM_USE_ADIOS2
|
|
void Mesh::Print(adios2stream &os) const
|
|
{
|
|
os.Print(*this);
|
|
}
|
|
#endif
|
|
|
|
void Mesh::PrintVTK(std::ostream &os)
|
|
{
|
|
os <<
|
|
"# vtk DataFile Version 3.0\n"
|
|
"Generated by MFEM\n"
|
|
"ASCII\n"
|
|
"DATASET UNSTRUCTURED_GRID\n";
|
|
|
|
if (Nodes == NULL)
|
|
{
|
|
os << "POINTS " << NumOfVertices << " double\n";
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
os << vertices[i](0);
|
|
int j;
|
|
for (j = 1; j < spaceDim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
for ( ; j < 3; j++)
|
|
{
|
|
os << ' ' << 0.0;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Array<int> vdofs(3);
|
|
os << "POINTS " << Nodes->FESpace()->GetNDofs() << " double\n";
|
|
for (int i = 0; i < Nodes->FESpace()->GetNDofs(); i++)
|
|
{
|
|
vdofs.SetSize(1);
|
|
vdofs[0] = i;
|
|
Nodes->FESpace()->DofsToVDofs(vdofs);
|
|
os << (*Nodes)(vdofs[0]);
|
|
int j;
|
|
for (j = 1; j < spaceDim; j++)
|
|
{
|
|
os << ' ' << (*Nodes)(vdofs[j]);
|
|
}
|
|
for ( ; j < 3; j++)
|
|
{
|
|
os << ' ' << 0.0;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
|
|
int order = -1;
|
|
if (Nodes == NULL)
|
|
{
|
|
int size = 0;
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
size += elements[i]->GetNVertices() + 1;
|
|
}
|
|
os << "CELLS " << NumOfElements << ' ' << size << '\n';
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const int *v = elements[i]->GetVertices();
|
|
const int nv = elements[i]->GetNVertices();
|
|
os << nv;
|
|
Geometry::Type geom = elements[i]->GetGeometryType();
|
|
const int *perm = VTKGeometry::VertexPermutation[geom];
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << v[perm ? perm[j] : j];
|
|
}
|
|
os << '\n';
|
|
}
|
|
order = 1;
|
|
}
|
|
else
|
|
{
|
|
Array<int> dofs;
|
|
int size = 0;
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
Nodes->FESpace()->GetElementDofs(i, dofs);
|
|
MFEM_ASSERT(Dim != 0 || dofs.Size() == 1,
|
|
"Point meshes should have a single dof per element");
|
|
size += dofs.Size() + 1;
|
|
}
|
|
os << "CELLS " << NumOfElements << ' ' << size << '\n';
|
|
const char *fec_name = Nodes->FESpace()->FEColl()->Name();
|
|
|
|
if (!strcmp(fec_name, "Linear") ||
|
|
!strcmp(fec_name, "H1_0D_P1") ||
|
|
!strcmp(fec_name, "H1_1D_P1") ||
|
|
!strcmp(fec_name, "H1_2D_P1") ||
|
|
!strcmp(fec_name, "H1_3D_P1"))
|
|
{
|
|
order = 1;
|
|
}
|
|
else if (!strcmp(fec_name, "Quadratic") ||
|
|
!strcmp(fec_name, "H1_1D_P2") ||
|
|
!strcmp(fec_name, "H1_2D_P2") ||
|
|
!strcmp(fec_name, "H1_3D_P2"))
|
|
{
|
|
order = 2;
|
|
}
|
|
if (order == -1)
|
|
{
|
|
mfem::err << "Mesh::PrintVTK : can not save '"
|
|
<< fec_name << "' elements!" << endl;
|
|
mfem_error();
|
|
}
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
Nodes->FESpace()->GetElementDofs(i, dofs);
|
|
os << dofs.Size();
|
|
if (order == 1)
|
|
{
|
|
for (int j = 0; j < dofs.Size(); j++)
|
|
{
|
|
os << ' ' << dofs[j];
|
|
}
|
|
}
|
|
else if (order == 2)
|
|
{
|
|
const int *vtk_mfem;
|
|
switch (elements[i]->GetGeometryType())
|
|
{
|
|
case Geometry::SEGMENT:
|
|
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::PRISM:
|
|
vtk_mfem = vtk_quadratic_wedge; break;
|
|
case Geometry::CUBE:
|
|
default:
|
|
vtk_mfem = vtk_quadratic_hex; break;
|
|
}
|
|
for (int j = 0; j < dofs.Size(); j++)
|
|
{
|
|
os << ' ' << dofs[vtk_mfem[j]];
|
|
}
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
|
|
os << "CELL_TYPES " << NumOfElements << '\n';
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
int vtk_cell_type = 5;
|
|
Geometry::Type geom = GetElement(i)->GetGeometryType();
|
|
if (order == 1) { vtk_cell_type = VTKGeometry::Map[geom]; }
|
|
else if (order == 2) { vtk_cell_type = VTKGeometry::QuadraticMap[geom]; }
|
|
os << vtk_cell_type << '\n';
|
|
}
|
|
|
|
// write attributes
|
|
os << "CELL_DATA " << NumOfElements << '\n'
|
|
<< "SCALARS material int\n"
|
|
<< "LOOKUP_TABLE default\n";
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
os << elements[i]->GetAttribute() << '\n';
|
|
}
|
|
os.flush();
|
|
}
|
|
|
|
void Mesh::PrintVTU(std::string fname,
|
|
VTKFormat format,
|
|
bool high_order_output,
|
|
int compression_level,
|
|
bool bdr_elements)
|
|
{
|
|
int ref = (high_order_output && Nodes)
|
|
? Nodes->FESpace()->GetMaxElementOrder() : 1;
|
|
|
|
fname = fname + ".vtu";
|
|
std::fstream os(fname.c_str(),std::ios::out);
|
|
os << "<VTKFile type=\"UnstructuredGrid\" version=\"2.2\"";
|
|
if (compression_level != 0)
|
|
{
|
|
os << " compressor=\"vtkZLibDataCompressor\"";
|
|
}
|
|
os << " byte_order=\"" << VTKByteOrder() << "\">\n";
|
|
os << "<UnstructuredGrid>\n";
|
|
PrintVTU(os, ref, format, high_order_output, compression_level, bdr_elements);
|
|
os << "</Piece>\n"; // need to close the piece open in the PrintVTU method
|
|
os << "</UnstructuredGrid>\n";
|
|
os << "</VTKFile>" << std::endl;
|
|
|
|
os.close();
|
|
}
|
|
|
|
void Mesh::PrintBdrVTU(std::string fname,
|
|
VTKFormat format,
|
|
bool high_order_output,
|
|
int compression_level)
|
|
{
|
|
PrintVTU(fname, format, high_order_output, compression_level, true);
|
|
}
|
|
|
|
void Mesh::PrintVTU(std::ostream &os, int ref, VTKFormat format,
|
|
bool high_order_output, int compression_level,
|
|
bool bdr_elements)
|
|
{
|
|
RefinedGeometry *RefG;
|
|
DenseMatrix pmat;
|
|
|
|
const char *fmt_str = (format == VTKFormat::ASCII) ? "ascii" : "binary";
|
|
const char *type_str = (format != VTKFormat::BINARY32) ? "Float64" : "Float32";
|
|
std::vector<char> buf;
|
|
|
|
auto get_geom = [&](int i)
|
|
{
|
|
if (bdr_elements) { return GetBdrElementGeometry(i); }
|
|
else { return GetElementBaseGeometry(i); }
|
|
};
|
|
|
|
int ne = bdr_elements ? GetNBE() : GetNE();
|
|
// count the number of points and cells
|
|
int np = 0, nc_ref = 0;
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
Geometry::Type geom = get_geom(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
np += RefG->RefPts.GetNPoints();
|
|
nc_ref += RefG->RefGeoms.Size() / nv;
|
|
}
|
|
|
|
os << "<Piece NumberOfPoints=\"" << np << "\" NumberOfCells=\""
|
|
<< (high_order_output ? ne : nc_ref) << "\">\n";
|
|
|
|
// print out the points
|
|
os << "<Points>\n";
|
|
os << "<DataArray type=\"" << type_str
|
|
<< "\" NumberOfComponents=\"3\" format=\"" << fmt_str << "\">\n";
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
RefG = GlobGeometryRefiner.Refine(get_geom(i), ref, 1);
|
|
|
|
if (bdr_elements)
|
|
{
|
|
GetBdrElementTransformation(i)->Transform(RefG->RefPts, pmat);
|
|
}
|
|
else
|
|
{
|
|
GetElementTransformation(i)->Transform(RefG->RefPts, pmat);
|
|
}
|
|
|
|
for (int j = 0; j < pmat.Width(); j++)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, pmat(0,j), " ", format);
|
|
if (pmat.Height() > 1)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, pmat(1,j), " ", format);
|
|
}
|
|
else
|
|
{
|
|
WriteBinaryOrASCII(os, buf, 0.0, " ", format);
|
|
}
|
|
if (pmat.Height() > 2)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, pmat(2,j), "", format);
|
|
}
|
|
else
|
|
{
|
|
WriteBinaryOrASCII(os, buf, 0.0, "", format);
|
|
}
|
|
if (format == VTKFormat::ASCII) { os << '\n'; }
|
|
}
|
|
}
|
|
if (format != VTKFormat::ASCII)
|
|
{
|
|
WriteBase64WithSizeAndClear(os, buf, compression_level);
|
|
}
|
|
os << "</DataArray>" << std::endl;
|
|
os << "</Points>" << std::endl;
|
|
|
|
os << "<Cells>" << std::endl;
|
|
os << "<DataArray type=\"Int32\" Name=\"connectivity\" format=\""
|
|
<< fmt_str << "\">" << std::endl;
|
|
// connectivity
|
|
std::vector<int> offset;
|
|
|
|
np = 0;
|
|
if (high_order_output)
|
|
{
|
|
Array<int> local_connectivity;
|
|
for (int iel = 0; iel < ne; iel++)
|
|
{
|
|
Geometry::Type geom = get_geom(iel);
|
|
CreateVTKElementConnectivity(local_connectivity, geom, ref);
|
|
int nnodes = local_connectivity.Size();
|
|
for (int i=0; i<nnodes; ++i)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, np+local_connectivity[i], " ",
|
|
format);
|
|
}
|
|
if (format == VTKFormat::ASCII) { os << '\n'; }
|
|
np += nnodes;
|
|
offset.push_back(np);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int coff = 0;
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
Geometry::Type geom = get_geom(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
Array<int> &RG = RefG->RefGeoms;
|
|
for (int j = 0; j < RG.Size(); )
|
|
{
|
|
coff = coff+nv;
|
|
offset.push_back(coff);
|
|
const int *p = VTKGeometry::VertexPermutation[geom];
|
|
for (int k = 0; k < nv; k++, j++)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, np + RG[p ? (j - k + p[k]) : j], " ",
|
|
format);
|
|
}
|
|
if (format == VTKFormat::ASCII) { os << '\n'; }
|
|
}
|
|
np += RefG->RefPts.GetNPoints();
|
|
}
|
|
}
|
|
if (format != VTKFormat::ASCII)
|
|
{
|
|
WriteBase64WithSizeAndClear(os, buf, compression_level);
|
|
}
|
|
os << "</DataArray>" << std::endl;
|
|
|
|
os << "<DataArray type=\"Int32\" Name=\"offsets\" format=\""
|
|
<< fmt_str << "\">" << std::endl;
|
|
// offsets
|
|
for (size_t ii=0; ii<offset.size(); ii++)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, offset[ii], "\n", format);
|
|
}
|
|
if (format != VTKFormat::ASCII)
|
|
{
|
|
WriteBase64WithSizeAndClear(os, buf, compression_level);
|
|
}
|
|
os << "</DataArray>" << std::endl;
|
|
os << "<DataArray type=\"UInt8\" Name=\"types\" format=\""
|
|
<< fmt_str << "\">" << std::endl;
|
|
// cell types
|
|
const int *vtk_geom_map =
|
|
high_order_output ? VTKGeometry::HighOrderMap : VTKGeometry::Map;
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
Geometry::Type geom = get_geom(i);
|
|
uint8_t vtk_cell_type = 5;
|
|
|
|
vtk_cell_type = vtk_geom_map[geom];
|
|
|
|
if (high_order_output)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, vtk_cell_type, "\n", format);
|
|
}
|
|
else
|
|
{
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
Array<int> &RG = RefG->RefGeoms;
|
|
for (int j = 0; j < RG.Size(); j += nv)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, vtk_cell_type, "\n", format);
|
|
}
|
|
}
|
|
}
|
|
if (format != VTKFormat::ASCII)
|
|
{
|
|
WriteBase64WithSizeAndClear(os, buf, compression_level);
|
|
}
|
|
os << "</DataArray>" << std::endl;
|
|
os << "</Cells>" << std::endl;
|
|
|
|
os << "<CellData Scalars=\"attribute\">" << std::endl;
|
|
os << "<DataArray type=\"Int32\" Name=\"attribute\" format=\""
|
|
<< fmt_str << "\">" << std::endl;
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
int attr = bdr_elements ? GetBdrAttribute(i) : GetAttribute(i);
|
|
if (high_order_output)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, attr, "\n", format);
|
|
}
|
|
else
|
|
{
|
|
Geometry::Type geom = get_geom(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
for (int j = 0; j < RefG->RefGeoms.Size(); j += nv)
|
|
{
|
|
WriteBinaryOrASCII(os, buf, attr, "\n", format);
|
|
}
|
|
}
|
|
}
|
|
if (format != VTKFormat::ASCII)
|
|
{
|
|
WriteBase64WithSizeAndClear(os, buf, compression_level);
|
|
}
|
|
os << "</DataArray>" << std::endl;
|
|
os << "</CellData>" << std::endl;
|
|
}
|
|
|
|
|
|
void Mesh::PrintVTK(std::ostream &os, int ref, int field_data)
|
|
{
|
|
int np, nc, size;
|
|
RefinedGeometry *RefG;
|
|
DenseMatrix pmat;
|
|
|
|
os <<
|
|
"# vtk DataFile Version 3.0\n"
|
|
"Generated by MFEM\n"
|
|
"ASCII\n"
|
|
"DATASET UNSTRUCTURED_GRID\n";
|
|
|
|
// additional dataset information
|
|
if (field_data)
|
|
{
|
|
os << "FIELD FieldData 1\n"
|
|
<< "MaterialIds " << 1 << " " << attributes.Size() << " int\n";
|
|
for (int i = 0; i < attributes.Size(); i++)
|
|
{
|
|
os << ' ' << attributes[i];
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
// count the points, cells, size
|
|
np = nc = size = 0;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Geometry::Type geom = GetElementBaseGeometry(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
np += RefG->RefPts.GetNPoints();
|
|
nc += RefG->RefGeoms.Size() / nv;
|
|
size += (RefG->RefGeoms.Size() / nv) * (nv + 1);
|
|
}
|
|
os << "POINTS " << np << " double\n";
|
|
// write the points
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
RefG = GlobGeometryRefiner.Refine(
|
|
GetElementBaseGeometry(i), ref, 1);
|
|
|
|
GetElementTransformation(i)->Transform(RefG->RefPts, pmat);
|
|
|
|
for (int j = 0; j < pmat.Width(); j++)
|
|
{
|
|
os << pmat(0, j) << ' ';
|
|
if (pmat.Height() > 1)
|
|
{
|
|
os << pmat(1, j) << ' ';
|
|
if (pmat.Height() > 2)
|
|
{
|
|
os << pmat(2, j);
|
|
}
|
|
else
|
|
{
|
|
os << 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
os << 0.0 << ' ' << 0.0;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
|
|
// write the cells
|
|
os << "CELLS " << nc << ' ' << size << '\n';
|
|
np = 0;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Geometry::Type geom = GetElementBaseGeometry(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
Array<int> &RG = RefG->RefGeoms;
|
|
|
|
for (int j = 0; j < RG.Size(); )
|
|
{
|
|
os << nv;
|
|
for (int k = 0; k < nv; k++, j++)
|
|
{
|
|
os << ' ' << np + RG[j];
|
|
}
|
|
os << '\n';
|
|
}
|
|
np += RefG->RefPts.GetNPoints();
|
|
}
|
|
os << "CELL_TYPES " << nc << '\n';
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Geometry::Type geom = GetElementBaseGeometry(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
Array<int> &RG = RefG->RefGeoms;
|
|
int vtk_cell_type = VTKGeometry::Map[geom];
|
|
|
|
for (int j = 0; j < RG.Size(); j += nv)
|
|
{
|
|
os << vtk_cell_type << '\n';
|
|
}
|
|
}
|
|
// write attributes (materials)
|
|
os << "CELL_DATA " << nc << '\n'
|
|
<< "SCALARS material int\n"
|
|
<< "LOOKUP_TABLE default\n";
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Geometry::Type geom = GetElementBaseGeometry(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
int attr = GetAttribute(i);
|
|
for (int j = 0; j < RefG->RefGeoms.Size(); j += nv)
|
|
{
|
|
os << attr << '\n';
|
|
}
|
|
}
|
|
|
|
if (Dim > 1)
|
|
{
|
|
Array<int> coloring;
|
|
srand((unsigned)time(0));
|
|
real_t a = rand_real();
|
|
int el0 = (int)floor(a * GetNE());
|
|
GetElementColoring(coloring, el0);
|
|
os << "SCALARS element_coloring int\n"
|
|
<< "LOOKUP_TABLE default\n";
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Geometry::Type geom = GetElementBaseGeometry(i);
|
|
int nv = Geometries.GetVertices(geom)->GetNPoints();
|
|
RefG = GlobGeometryRefiner.Refine(geom, ref, 1);
|
|
for (int j = 0; j < RefG->RefGeoms.Size(); j += nv)
|
|
{
|
|
os << coloring[i] + 1 << '\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
// prepare to write data
|
|
os << "POINT_DATA " << np << '\n' << flush;
|
|
}
|
|
|
|
#ifdef MFEM_USE_HDF5
|
|
|
|
void Mesh::SaveVTKHDF(const std::string &fname, bool high_order)
|
|
{
|
|
#ifdef MFEM_USE_MPI
|
|
if (ParMesh *pmesh = dynamic_cast<ParMesh*>(this))
|
|
{
|
|
#ifdef MFEM_PARALLEL_HDF5
|
|
VTKHDF vtkhdf(fname, pmesh->GetComm());
|
|
vtkhdf.SaveMesh(*this, high_order);
|
|
return;
|
|
#else
|
|
MFEM_ABORT("Requires HDF5 library with parallel support enabled");
|
|
#endif
|
|
}
|
|
#endif
|
|
VTKHDF vtkhdf(fname);
|
|
vtkhdf.SaveMesh(*this, high_order);
|
|
}
|
|
|
|
#endif
|
|
|
|
void Mesh::GetElementColoring(Array<int> &colors, int el0)
|
|
{
|
|
int delete_el_to_el = (el_to_el) ? (0) : (1);
|
|
const Table &el_el = ElementToElementTable();
|
|
int num_el = GetNE(), stack_p, stack_top_p, max_num_col;
|
|
Array<int> el_stack(num_el);
|
|
|
|
const int *i_el_el = el_el.GetI();
|
|
const int *j_el_el = el_el.GetJ();
|
|
|
|
colors.SetSize(num_el);
|
|
colors = -2;
|
|
max_num_col = 1;
|
|
stack_p = stack_top_p = 0;
|
|
for (int el = el0; stack_top_p < num_el; el=(el+1)%num_el)
|
|
{
|
|
if (colors[el] != -2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
colors[el] = -1;
|
|
el_stack[stack_top_p++] = el;
|
|
|
|
for ( ; stack_p < stack_top_p; stack_p++)
|
|
{
|
|
int i = el_stack[stack_p];
|
|
int num_nb = i_el_el[i+1] - i_el_el[i];
|
|
if (max_num_col < num_nb + 1)
|
|
{
|
|
max_num_col = num_nb + 1;
|
|
}
|
|
for (int j = i_el_el[i]; j < i_el_el[i+1]; j++)
|
|
{
|
|
int k = j_el_el[j];
|
|
if (colors[k] == -2)
|
|
{
|
|
colors[k] = -1;
|
|
el_stack[stack_top_p++] = k;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Array<int> col_marker(max_num_col);
|
|
|
|
for (stack_p = 0; stack_p < stack_top_p; stack_p++)
|
|
{
|
|
int i = el_stack[stack_p], col;
|
|
col_marker = 0;
|
|
for (int j = i_el_el[i]; j < i_el_el[i+1]; j++)
|
|
{
|
|
col = colors[j_el_el[j]];
|
|
if (col != -1)
|
|
{
|
|
col_marker[col] = 1;
|
|
}
|
|
}
|
|
|
|
for (col = 0; col < max_num_col; col++)
|
|
if (col_marker[col] == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
colors[i] = col;
|
|
}
|
|
|
|
if (delete_el_to_el)
|
|
{
|
|
delete el_to_el;
|
|
el_to_el = NULL;
|
|
}
|
|
}
|
|
|
|
void Mesh::PrintWithPartitioning(int *partitioning, std::ostream &os,
|
|
int elem_attr) const
|
|
{
|
|
if (Dim != 3 && Dim != 2) { return; }
|
|
|
|
int i, j, k, l, nv, nbe, *v;
|
|
|
|
os << "MFEM mesh v1.0\n";
|
|
|
|
// optional
|
|
os <<
|
|
"\n#\n# MFEM Geometry Types (see fem/geom.hpp):\n#\n"
|
|
"# POINT = 0\n"
|
|
"# SEGMENT = 1\n"
|
|
"# TRIANGLE = 2\n"
|
|
"# SQUARE = 3\n"
|
|
"# TETRAHEDRON = 4\n"
|
|
"# CUBE = 5\n"
|
|
"# PRISM = 6\n"
|
|
"#\n";
|
|
|
|
os << "\ndimension\n" << Dim
|
|
<< "\n\nelements\n" << NumOfElements << '\n';
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
os << int((elem_attr) ? partitioning[i]+1 : elements[i]->GetAttribute())
|
|
<< ' ' << elements[i]->GetGeometryType();
|
|
nv = elements[i]->GetNVertices();
|
|
v = elements[i]->GetVertices();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << v[j];
|
|
}
|
|
os << '\n';
|
|
}
|
|
nbe = 0;
|
|
for (i = 0; i < faces_info.Size(); i++)
|
|
{
|
|
if ((l = faces_info[i].Elem2No) >= 0)
|
|
{
|
|
k = partitioning[faces_info[i].Elem1No];
|
|
l = partitioning[l];
|
|
if (k != l)
|
|
{
|
|
nbe++;
|
|
if (!Nonconforming() || !IsSlaveFace(faces_info[i]))
|
|
{
|
|
nbe++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nbe++;
|
|
}
|
|
}
|
|
os << "\nboundary\n" << nbe << '\n';
|
|
for (i = 0; i < faces_info.Size(); i++)
|
|
{
|
|
if ((l = faces_info[i].Elem2No) >= 0)
|
|
{
|
|
k = partitioning[faces_info[i].Elem1No];
|
|
l = partitioning[l];
|
|
if (k != l)
|
|
{
|
|
nv = faces[i]->GetNVertices();
|
|
v = faces[i]->GetVertices();
|
|
os << k+1 << ' ' << faces[i]->GetGeometryType();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << v[j];
|
|
}
|
|
os << '\n';
|
|
if (!Nonconforming() || !IsSlaveFace(faces_info[i]))
|
|
{
|
|
os << l+1 << ' ' << faces[i]->GetGeometryType();
|
|
for (j = nv-1; j >= 0; j--)
|
|
{
|
|
os << ' ' << v[j];
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
k = partitioning[faces_info[i].Elem1No];
|
|
nv = faces[i]->GetNVertices();
|
|
v = faces[i]->GetVertices();
|
|
os << k+1 << ' ' << faces[i]->GetGeometryType();
|
|
for (j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << v[j];
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
os << "\nvertices\n" << NumOfVertices << '\n';
|
|
if (Nodes == NULL)
|
|
{
|
|
os << spaceDim << '\n';
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
os << vertices[i](0);
|
|
for (j = 1; j < spaceDim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
os << '\n';
|
|
}
|
|
os.flush();
|
|
}
|
|
else
|
|
{
|
|
os << "\nnodes\n";
|
|
Nodes->Save(os);
|
|
}
|
|
}
|
|
|
|
void Mesh::PrintElementsWithPartitioning(int *partitioning,
|
|
std::ostream &os,
|
|
int interior_faces)
|
|
{
|
|
MFEM_ASSERT(Dim == spaceDim, "2D Manifolds not supported\n");
|
|
if (Dim != 3 && Dim != 2) { return; }
|
|
|
|
int *vcount = new int[NumOfVertices];
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vcount[i] = 0;
|
|
}
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
int nv = elements[i]->GetNVertices();
|
|
const int *ind = elements[i]->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
vcount[ind[j]]++;
|
|
}
|
|
}
|
|
|
|
int *voff = new int[NumOfVertices+1];
|
|
voff[0] = 0;
|
|
for (int i = 1; i <= NumOfVertices; i++)
|
|
{
|
|
voff[i] = vcount[i-1] + voff[i-1];
|
|
}
|
|
|
|
int **vown = new int*[NumOfVertices];
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vown[i] = new int[vcount[i]];
|
|
}
|
|
|
|
// 2D
|
|
if (Dim == 2)
|
|
{
|
|
Table edge_el;
|
|
Transpose(ElementToEdgeTable(), edge_el);
|
|
|
|
// Fake printing of the elements.
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
int nv = elements[i]->GetNVertices();
|
|
const int *ind = elements[i]->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
vcount[ind[j]]--;
|
|
vown[ind[j]][vcount[ind[j]]] = i;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vcount[i] = voff[i+1] - voff[i];
|
|
}
|
|
|
|
int nbe = 0;
|
|
for (int i = 0; i < edge_el.Size(); i++)
|
|
{
|
|
const int *el = edge_el.GetRow(i);
|
|
if (edge_el.RowSize(i) > 1)
|
|
{
|
|
int k = partitioning[el[0]];
|
|
int l = partitioning[el[1]];
|
|
if (interior_faces || k != l)
|
|
{
|
|
nbe += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nbe++;
|
|
}
|
|
}
|
|
|
|
// Print the type of the mesh and the boundary elements.
|
|
os << "areamesh2\n\n" << nbe << '\n';
|
|
|
|
for (int i = 0; i < edge_el.Size(); i++)
|
|
{
|
|
const int *el = edge_el.GetRow(i);
|
|
if (edge_el.RowSize(i) > 1)
|
|
{
|
|
int k = partitioning[el[0]];
|
|
int l = partitioning[el[1]];
|
|
if (interior_faces || k != l)
|
|
{
|
|
Array<int> ev;
|
|
GetEdgeVertices(i,ev);
|
|
os << k+1; // attribute
|
|
for (int j = 0; j < 2; j++)
|
|
for (int s = 0; s < vcount[ev[j]]; s++)
|
|
if (vown[ev[j]][s] == el[0])
|
|
{
|
|
os << ' ' << voff[ev[j]]+s+1;
|
|
}
|
|
os << '\n';
|
|
os << l+1; // attribute
|
|
for (int j = 1; j >= 0; j--)
|
|
for (int s = 0; s < vcount[ev[j]]; s++)
|
|
if (vown[ev[j]][s] == el[1])
|
|
{
|
|
os << ' ' << voff[ev[j]]+s+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int k = partitioning[el[0]];
|
|
Array<int> ev;
|
|
GetEdgeVertices(i,ev);
|
|
os << k+1; // attribute
|
|
for (int j = 0; j < 2; j++)
|
|
for (int s = 0; s < vcount[ev[j]]; s++)
|
|
if (vown[ev[j]][s] == el[0])
|
|
{
|
|
os << ' ' << voff[ev[j]]+s+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
|
|
// Print the elements.
|
|
os << NumOfElements << '\n';
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
int nv = elements[i]->GetNVertices();
|
|
const int *ind = elements[i]->GetVertices();
|
|
os << partitioning[i]+1 << ' '; // use subdomain number as attribute
|
|
os << nv << ' ';
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << voff[ind[j]]+vcount[ind[j]]--;
|
|
vown[ind[j]][vcount[ind[j]]] = i;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vcount[i] = voff[i+1] - voff[i];
|
|
}
|
|
|
|
// Print the vertices.
|
|
os << voff[NumOfVertices] << '\n';
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
for (int k = 0; k < vcount[i]; k++)
|
|
{
|
|
for (int j = 0; j < Dim; j++)
|
|
{
|
|
os << vertices[i](j) << ' ';
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
// Dim is 3
|
|
else if (meshgen == 1)
|
|
{
|
|
os << "NETGEN_Neutral_Format\n";
|
|
// print the vertices
|
|
os << voff[NumOfVertices] << '\n';
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
for (int k = 0; k < vcount[i]; k++)
|
|
{
|
|
for (int j = 0; j < Dim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
// print the elements
|
|
os << NumOfElements << '\n';
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
int nv = elements[i]->GetNVertices();
|
|
const int *ind = elements[i]->GetVertices();
|
|
os << partitioning[i]+1; // use subdomain number as attribute
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << voff[ind[j]]+vcount[ind[j]]--;
|
|
vown[ind[j]][vcount[ind[j]]] = i;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vcount[i] = voff[i+1] - voff[i];
|
|
}
|
|
|
|
// print the boundary information.
|
|
int nbe = 0;
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
{
|
|
int l = faces_info[i].Elem2No;
|
|
if (l >= 0)
|
|
{
|
|
int k = partitioning[faces_info[i].Elem1No];
|
|
l = partitioning[l];
|
|
if (interior_faces || k != l)
|
|
{
|
|
nbe += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nbe++;
|
|
}
|
|
}
|
|
|
|
os << nbe << '\n';
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
{
|
|
int l = faces_info[i].Elem2No;
|
|
if (l >= 0)
|
|
{
|
|
int k = partitioning[faces_info[i].Elem1No];
|
|
l = partitioning[l];
|
|
if (interior_faces || k != l)
|
|
{
|
|
int nv = faces[i]->GetNVertices();
|
|
const int *ind = faces[i]->GetVertices();
|
|
os << k+1; // attribute
|
|
for (int j = 0; j < nv; j++)
|
|
for (int s = 0; s < vcount[ind[j]]; s++)
|
|
if (vown[ind[j]][s] == faces_info[i].Elem1No)
|
|
{
|
|
os << ' ' << voff[ind[j]]+s+1;
|
|
}
|
|
os << '\n';
|
|
os << l+1; // attribute
|
|
for (int j = nv-1; j >= 0; j--)
|
|
for (int s = 0; s < vcount[ind[j]]; s++)
|
|
if (vown[ind[j]][s] == faces_info[i].Elem2No)
|
|
{
|
|
os << ' ' << voff[ind[j]]+s+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int k = partitioning[faces_info[i].Elem1No];
|
|
int nv = faces[i]->GetNVertices();
|
|
const int *ind = faces[i]->GetVertices();
|
|
os << k+1; // attribute
|
|
for (int j = 0; j < nv; j++)
|
|
for (int s = 0; s < vcount[ind[j]]; s++)
|
|
if (vown[ind[j]][s] == faces_info[i].Elem1No)
|
|
{
|
|
os << ' ' << voff[ind[j]]+s+1;
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
}
|
|
// Dim is 3
|
|
else if (meshgen == 2) // TrueGrid
|
|
{
|
|
// count the number of the boundary elements.
|
|
int nbe = 0;
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
{
|
|
int l = faces_info[i].Elem2No;
|
|
if (l >= 0)
|
|
{
|
|
int k = partitioning[faces_info[i].Elem1No];
|
|
l = partitioning[l];
|
|
if (interior_faces || k != l)
|
|
{
|
|
nbe += 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nbe++;
|
|
}
|
|
}
|
|
|
|
os << "TrueGrid\n"
|
|
<< "1 " << voff[NumOfVertices] << " " << NumOfElements
|
|
<< " 0 0 0 0 0 0 0\n"
|
|
<< "0 0 0 1 0 0 0 0 0 0 0\n"
|
|
<< "0 0 " << nbe << " 0 0 0 0 0 0 0 0 0 0 0 0 0\n"
|
|
<< "0.0 0.0 0.0 0 0 0.0 0.0 0 0.0\n"
|
|
<< "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n";
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
for (int k = 0; k < vcount[i]; k++)
|
|
os << voff[i]+k << " 0.0 " << vertices[i](0) << ' '
|
|
<< vertices[i](1) << ' ' << vertices[i](2) << " 0.0\n";
|
|
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
int nv = elements[i]->GetNVertices();
|
|
const int *ind = elements[i]->GetVertices();
|
|
os << i+1 << ' ' << partitioning[i]+1; // partitioning as attribute
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
os << ' ' << voff[ind[j]]+vcount[ind[j]]--;
|
|
vown[ind[j]][vcount[ind[j]]] = i;
|
|
}
|
|
os << '\n';
|
|
}
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vcount[i] = voff[i+1] - voff[i];
|
|
}
|
|
|
|
// boundary elements
|
|
for (int i = 0; i < NumOfFaces; i++)
|
|
{
|
|
int l = faces_info[i].Elem2No;
|
|
if (l >= 0)
|
|
{
|
|
int k = partitioning[faces_info[i].Elem1No];
|
|
l = partitioning[l];
|
|
if (interior_faces || k != l)
|
|
{
|
|
int nv = faces[i]->GetNVertices();
|
|
const int *ind = faces[i]->GetVertices();
|
|
os << k+1; // attribute
|
|
for (int j = 0; j < nv; j++)
|
|
for (int s = 0; s < vcount[ind[j]]; s++)
|
|
if (vown[ind[j]][s] == faces_info[i].Elem1No)
|
|
{
|
|
os << ' ' << voff[ind[j]]+s+1;
|
|
}
|
|
os << " 1.0 1.0 1.0 1.0\n";
|
|
os << l+1; // attribute
|
|
for (int j = nv-1; j >= 0; j--)
|
|
for (int s = 0; s < vcount[ind[j]]; s++)
|
|
if (vown[ind[j]][s] == faces_info[i].Elem2No)
|
|
{
|
|
os << ' ' << voff[ind[j]]+s+1;
|
|
}
|
|
os << " 1.0 1.0 1.0 1.0\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int k = partitioning[faces_info[i].Elem1No];
|
|
int nv = faces[i]->GetNVertices();
|
|
const int *ind = faces[i]->GetVertices();
|
|
os << k+1; // attribute
|
|
for (int j = 0; j < nv; j++)
|
|
for (int s = 0; s < vcount[ind[j]]; s++)
|
|
if (vown[ind[j]][s] == faces_info[i].Elem1No)
|
|
{
|
|
os << ' ' << voff[ind[j]]+s+1;
|
|
}
|
|
os << " 1.0 1.0 1.0 1.0\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
os << flush;
|
|
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
delete [] vown[i];
|
|
}
|
|
|
|
delete [] vcount;
|
|
delete [] voff;
|
|
delete [] vown;
|
|
}
|
|
|
|
void Mesh::PrintSurfaces(const Table & Aface_face, std::ostream &os) const
|
|
{
|
|
int i, j;
|
|
|
|
if (NURBSext)
|
|
{
|
|
mfem_error("Mesh::PrintSurfaces"
|
|
" NURBS mesh is not supported!");
|
|
return;
|
|
}
|
|
|
|
os << "MFEM mesh v1.0\n";
|
|
|
|
// optional
|
|
os <<
|
|
"\n#\n# MFEM Geometry Types (see fem/geom.hpp):\n#\n"
|
|
"# POINT = 0\n"
|
|
"# SEGMENT = 1\n"
|
|
"# TRIANGLE = 2\n"
|
|
"# SQUARE = 3\n"
|
|
"# TETRAHEDRON = 4\n"
|
|
"# CUBE = 5\n"
|
|
"# PRISM = 6\n"
|
|
"#\n";
|
|
|
|
os << "\ndimension\n" << Dim
|
|
<< "\n\nelements\n" << NumOfElements << '\n';
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
PrintElement(elements[i], os);
|
|
}
|
|
|
|
os << "\nboundary\n" << Aface_face.Size_of_connections() << '\n';
|
|
const int * const i_AF_f = Aface_face.GetI();
|
|
const int * const j_AF_f = Aface_face.GetJ();
|
|
|
|
for (int iAF=0; iAF < Aface_face.Size(); ++iAF)
|
|
for (const int * iface = j_AF_f + i_AF_f[iAF];
|
|
iface < j_AF_f + i_AF_f[iAF+1];
|
|
++iface)
|
|
{
|
|
os << iAF+1 << ' ';
|
|
PrintElementWithoutAttr(faces[*iface],os);
|
|
}
|
|
|
|
os << "\nvertices\n" << NumOfVertices << '\n';
|
|
if (Nodes == NULL)
|
|
{
|
|
os << spaceDim << '\n';
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
os << vertices[i](0);
|
|
for (j = 1; j < spaceDim; j++)
|
|
{
|
|
os << ' ' << vertices[i](j);
|
|
}
|
|
os << '\n';
|
|
}
|
|
os.flush();
|
|
}
|
|
else
|
|
{
|
|
os << "\nnodes\n";
|
|
Nodes->Save(os);
|
|
}
|
|
}
|
|
|
|
void Mesh::ScaleSubdomains(real_t sf)
|
|
{
|
|
int i,j,k;
|
|
Array<int> vert;
|
|
DenseMatrix pointmat;
|
|
int na = attributes.Size();
|
|
real_t *cg = new real_t[na*spaceDim];
|
|
int *nbea = new int[na];
|
|
|
|
int *vn = new int[NumOfVertices];
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vn[i] = 0;
|
|
}
|
|
for (i = 0; i < na; i++)
|
|
{
|
|
for (j = 0; j < spaceDim; j++)
|
|
{
|
|
cg[i*spaceDim+j] = 0.0;
|
|
}
|
|
nbea[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
GetElementVertices(i, vert);
|
|
for (k = 0; k < vert.Size(); k++)
|
|
{
|
|
vn[vert[k]] = 1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
int bea = GetAttribute(i)-1;
|
|
GetPointMatrix(i, pointmat);
|
|
GetElementVertices(i, vert);
|
|
|
|
for (k = 0; k < vert.Size(); k++)
|
|
if (vn[vert[k]] == 1)
|
|
{
|
|
nbea[bea]++;
|
|
for (j = 0; j < spaceDim; j++)
|
|
{
|
|
cg[bea*spaceDim+j] += pointmat(j,k);
|
|
}
|
|
vn[vert[k]] = 2;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
int bea = GetAttribute(i)-1;
|
|
GetElementVertices (i, vert);
|
|
|
|
for (k = 0; k < vert.Size(); k++)
|
|
if (vn[vert[k]])
|
|
{
|
|
for (j = 0; j < spaceDim; j++)
|
|
vertices[vert[k]](j) = sf*vertices[vert[k]](j) +
|
|
(1-sf)*cg[bea*spaceDim+j]/nbea[bea];
|
|
vn[vert[k]] = 0;
|
|
}
|
|
}
|
|
|
|
delete [] cg;
|
|
delete [] nbea;
|
|
delete [] vn;
|
|
}
|
|
|
|
void Mesh::ScaleElements(real_t sf)
|
|
{
|
|
int i,j,k;
|
|
Array<int> vert;
|
|
DenseMatrix pointmat;
|
|
int na = NumOfElements;
|
|
real_t *cg = new real_t[na*spaceDim];
|
|
int *nbea = new int[na];
|
|
|
|
int *vn = new int[NumOfVertices];
|
|
for (i = 0; i < NumOfVertices; i++)
|
|
{
|
|
vn[i] = 0;
|
|
}
|
|
for (i = 0; i < na; i++)
|
|
{
|
|
for (j = 0; j < spaceDim; j++)
|
|
{
|
|
cg[i*spaceDim+j] = 0.0;
|
|
}
|
|
nbea[i] = 0;
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
GetElementVertices(i, vert);
|
|
for (k = 0; k < vert.Size(); k++)
|
|
{
|
|
vn[vert[k]] = 1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
int bea = i;
|
|
GetPointMatrix(i, pointmat);
|
|
GetElementVertices(i, vert);
|
|
|
|
for (k = 0; k < vert.Size(); k++)
|
|
if (vn[vert[k]] == 1)
|
|
{
|
|
nbea[bea]++;
|
|
for (j = 0; j < spaceDim; j++)
|
|
{
|
|
cg[bea*spaceDim+j] += pointmat(j,k);
|
|
}
|
|
vn[vert[k]] = 2;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < NumOfElements; i++)
|
|
{
|
|
int bea = i;
|
|
GetElementVertices(i, vert);
|
|
|
|
for (k = 0; k < vert.Size(); k++)
|
|
if (vn[vert[k]])
|
|
{
|
|
for (j = 0; j < spaceDim; j++)
|
|
vertices[vert[k]](j) = sf*vertices[vert[k]](j) +
|
|
(1-sf)*cg[bea*spaceDim+j]/nbea[bea];
|
|
vn[vert[k]] = 0;
|
|
}
|
|
}
|
|
|
|
delete [] cg;
|
|
delete [] nbea;
|
|
delete [] vn;
|
|
}
|
|
|
|
void Mesh::Transform(std::function<void(const Vector &, Vector&)> f)
|
|
{
|
|
// TODO: support for different new spaceDim.
|
|
if (Nodes == NULL)
|
|
{
|
|
Vector vold(spaceDim), vnew(NULL, spaceDim);
|
|
for (int i = 0; i < vertices.Size(); i++)
|
|
{
|
|
for (int j = 0; j < spaceDim; j++)
|
|
{
|
|
vold(j) = vertices[i](j);
|
|
}
|
|
vnew.SetData(vertices[i]());
|
|
f(vold, vnew);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GridFunction xnew(Nodes->FESpace());
|
|
VectorFunctionCoefficient f_pert(spaceDim, f);
|
|
xnew.ProjectCoefficient(f_pert);
|
|
*Nodes = xnew;
|
|
}
|
|
NodesUpdated();
|
|
}
|
|
|
|
void Mesh::Transform(VectorCoefficient &deformation)
|
|
{
|
|
MFEM_VERIFY(spaceDim == deformation.GetVDim(),
|
|
"incompatible vector dimensions");
|
|
if (Nodes == NULL)
|
|
{
|
|
LinearFECollection fec;
|
|
FiniteElementSpace fes(this, &fec, spaceDim, Ordering::byVDIM);
|
|
GridFunction xnew(&fes);
|
|
xnew.ProjectCoefficient(deformation);
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
for (int d = 0; d < spaceDim; d++)
|
|
{
|
|
vertices[i](d) = xnew(d + spaceDim*i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GridFunction xnew(Nodes->FESpace());
|
|
xnew.ProjectCoefficient(deformation);
|
|
*Nodes = xnew;
|
|
}
|
|
NodesUpdated();
|
|
}
|
|
|
|
void Mesh::RemoveUnusedVertices()
|
|
{
|
|
if (NURBSext || ncmesh) { return; }
|
|
|
|
Array<int> v2v(GetNV());
|
|
v2v = -1;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Element *el = GetElement(i);
|
|
int nv = el->GetNVertices();
|
|
int *v = el->GetVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
v2v[v[j]] = 0;
|
|
}
|
|
}
|
|
for (int i = 0; i < GetNBE(); i++)
|
|
{
|
|
Element *el = GetBdrElement(i);
|
|
int *v = el->GetVertices();
|
|
int nv = el->GetNVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
v2v[v[j]] = 0;
|
|
}
|
|
}
|
|
int num_vert = 0;
|
|
for (int i = 0; i < v2v.Size(); i++)
|
|
{
|
|
if (v2v[i] == 0)
|
|
{
|
|
vertices[num_vert] = vertices[i];
|
|
v2v[i] = num_vert++;
|
|
}
|
|
}
|
|
|
|
if (num_vert == v2v.Size()) { return; }
|
|
|
|
Vector nodes_by_element;
|
|
Array<int> vdofs;
|
|
if (Nodes)
|
|
{
|
|
int s = 0;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Nodes->FESpace()->GetElementVDofs(i, vdofs);
|
|
s += vdofs.Size();
|
|
}
|
|
nodes_by_element.SetSize(s);
|
|
s = 0;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Nodes->FESpace()->GetElementVDofs(i, vdofs);
|
|
Nodes->GetSubVector(vdofs, &nodes_by_element(s));
|
|
s += vdofs.Size();
|
|
}
|
|
}
|
|
vertices.SetSize(num_vert);
|
|
NumOfVertices = num_vert;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Element *el = GetElement(i);
|
|
int *v = el->GetVertices();
|
|
int nv = el->GetNVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
v[j] = v2v[v[j]];
|
|
}
|
|
}
|
|
for (int i = 0; i < GetNBE(); i++)
|
|
{
|
|
Element *el = GetBdrElement(i);
|
|
int *v = el->GetVertices();
|
|
int nv = el->GetNVertices();
|
|
for (int j = 0; j < nv; j++)
|
|
{
|
|
v[j] = v2v[v[j]];
|
|
}
|
|
}
|
|
DeleteTables();
|
|
if (Dim > 1)
|
|
{
|
|
// generate el_to_edge, be_to_face (2D), bel_to_edge (3D)
|
|
el_to_edge = new Table;
|
|
NumOfEdges = GetElementToEdgeTable(*el_to_edge);
|
|
}
|
|
if (Dim > 2)
|
|
{
|
|
// generate el_to_face, be_to_face
|
|
GetElementToFaceTable();
|
|
}
|
|
// Update faces and faces_info
|
|
GenerateFaces();
|
|
if (Nodes)
|
|
{
|
|
Nodes->FESpace()->Update();
|
|
Nodes->Update();
|
|
int s = 0;
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
Nodes->FESpace()->GetElementVDofs(i, vdofs);
|
|
Nodes->SetSubVector(vdofs, &nodes_by_element(s));
|
|
s += vdofs.Size();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mesh::RemoveInternalBoundaries()
|
|
{
|
|
if (NURBSext || ncmesh) { return; }
|
|
|
|
int num_bdr_elem = 0;
|
|
int new_bel_to_edge_nnz = 0;
|
|
for (int i = 0; i < GetNBE(); i++)
|
|
{
|
|
if (FaceIsInterior(GetBdrElementFaceIndex(i)))
|
|
{
|
|
FreeElement(boundary[i]);
|
|
}
|
|
else
|
|
{
|
|
num_bdr_elem++;
|
|
if (Dim == 3)
|
|
{
|
|
new_bel_to_edge_nnz += bel_to_edge->RowSize(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (num_bdr_elem == GetNBE()) { return; }
|
|
|
|
Array<Element *> new_boundary(num_bdr_elem);
|
|
Array<int> new_be_to_face;
|
|
Table *new_bel_to_edge = NULL;
|
|
new_boundary.SetSize(0);
|
|
new_be_to_face.Reserve(num_bdr_elem);
|
|
if (Dim == 3)
|
|
{
|
|
new_bel_to_edge = new Table;
|
|
new_bel_to_edge->SetDims(num_bdr_elem, new_bel_to_edge_nnz);
|
|
}
|
|
for (int i = 0; i < GetNBE(); i++)
|
|
{
|
|
if (!FaceIsInterior(GetBdrElementFaceIndex(i)))
|
|
{
|
|
new_boundary.Append(boundary[i]);
|
|
int row = new_be_to_face.Size();
|
|
new_be_to_face.Append(be_to_face[i]);
|
|
if (Dim == 3)
|
|
{
|
|
int *e = bel_to_edge->GetRow(i);
|
|
int ne = bel_to_edge->RowSize(i);
|
|
int *new_e = new_bel_to_edge->GetRow(row);
|
|
for (int j = 0; j < ne; j++)
|
|
{
|
|
new_e[j] = e[j];
|
|
}
|
|
new_bel_to_edge->GetI()[row+1] = new_bel_to_edge->GetI()[row] + ne;
|
|
}
|
|
}
|
|
}
|
|
|
|
NumOfBdrElements = new_boundary.Size();
|
|
mfem::Swap(boundary, new_boundary);
|
|
|
|
mfem::Swap(be_to_face, new_be_to_face);
|
|
|
|
if (Dim == 3)
|
|
{
|
|
delete bel_to_edge;
|
|
bel_to_edge = new_bel_to_edge;
|
|
}
|
|
|
|
Array<int> attribs(num_bdr_elem);
|
|
for (int i = 0; i < attribs.Size(); i++)
|
|
{
|
|
attribs[i] = GetBdrAttribute(i);
|
|
}
|
|
attribs.Sort();
|
|
attribs.Unique();
|
|
bdr_attributes.DeleteAll();
|
|
attribs.Copy(bdr_attributes);
|
|
}
|
|
|
|
void Mesh::FreeElement(Element *E)
|
|
{
|
|
#ifdef MFEM_USE_MEMALLOC
|
|
if (E)
|
|
{
|
|
if (E->GetType() == Element::TETRAHEDRON)
|
|
{
|
|
TetMemory.Free((Tetrahedron*) E);
|
|
}
|
|
else
|
|
{
|
|
delete E;
|
|
}
|
|
}
|
|
#else
|
|
delete E;
|
|
#endif
|
|
}
|
|
|
|
std::ostream &operator<<(std::ostream &os, const Mesh &mesh)
|
|
{
|
|
mesh.Print(os);
|
|
return os;
|
|
}
|
|
|
|
int Mesh::FindPoints(DenseMatrix &point_mat, Array<int>& elem_ids,
|
|
Array<IntegrationPoint>& ips, bool warn,
|
|
InverseElementTransformation *inv_trans)
|
|
{
|
|
const int npts = point_mat.Width();
|
|
if (!npts) { return 0; }
|
|
MFEM_VERIFY(point_mat.Height() == spaceDim,"Invalid points matrix");
|
|
elem_ids.SetSize(npts);
|
|
ips.SetSize(npts);
|
|
elem_ids = -1;
|
|
if (!GetNE()) { return 0; }
|
|
|
|
real_t *data = point_mat.GetData();
|
|
InverseElementTransformation *inv_tr = inv_trans;
|
|
inv_tr = inv_tr ? inv_tr : new InverseElementTransformation;
|
|
|
|
// For each point in 'point_mat', find the element whose center is closest.
|
|
Vector min_dist(npts);
|
|
Array<int> e_idx(npts);
|
|
min_dist = std::numeric_limits<real_t>::max();
|
|
e_idx = -1;
|
|
|
|
Vector pt(spaceDim);
|
|
for (int i = 0; i < GetNE(); i++)
|
|
{
|
|
GetElementTransformation(i)->Transform(
|
|
Geometries.GetCenter(GetElementBaseGeometry(i)), pt);
|
|
for (int k = 0; k < npts; k++)
|
|
{
|
|
real_t dist = pt.DistanceTo(data+k*spaceDim);
|
|
if (dist < min_dist(k))
|
|
{
|
|
min_dist(k) = dist;
|
|
e_idx[k] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks if the points lie in the closest element
|
|
int pts_found = 0;
|
|
pt.NewDataAndSize(NULL, spaceDim);
|
|
for (int k = 0; k < npts; k++)
|
|
{
|
|
pt.SetData(data+k*spaceDim);
|
|
inv_tr->SetTransformation(*GetElementTransformation(e_idx[k]));
|
|
int res = inv_tr->Transform(pt, ips[k]);
|
|
if (res == InverseElementTransformation::Inside)
|
|
{
|
|
elem_ids[k] = e_idx[k];
|
|
pts_found++;
|
|
}
|
|
}
|
|
if (pts_found != npts)
|
|
{
|
|
Array<int> elvertices;
|
|
Table *vtoel = GetVertexToElementTable();
|
|
for (int k = 0; k < npts; k++)
|
|
{
|
|
if (elem_ids[k] != -1) { continue; }
|
|
// Try all vertex-neighbors of element e_idx[k]
|
|
pt.SetData(data+k*spaceDim);
|
|
GetElementVertices(e_idx[k], elvertices);
|
|
for (int v = 0; v < elvertices.Size(); v++)
|
|
{
|
|
int vv = elvertices[v];
|
|
int ne = vtoel->RowSize(vv);
|
|
const int* els = vtoel->GetRow(vv);
|
|
for (int e = 0; e < ne; e++)
|
|
{
|
|
if (els[e] == e_idx[k]) { continue; }
|
|
inv_tr->SetTransformation(*GetElementTransformation(els[e]));
|
|
int res = inv_tr->Transform(pt, ips[k]);
|
|
if (res == InverseElementTransformation::Inside)
|
|
{
|
|
elem_ids[k] = els[e];
|
|
pts_found++;
|
|
goto next_point;
|
|
}
|
|
}
|
|
}
|
|
// Try neighbors for non-conforming meshes
|
|
if (ncmesh)
|
|
{
|
|
Array<int> neigh;
|
|
int le = ncmesh->leaf_elements[e_idx[k]];
|
|
ncmesh->FindNeighbors(le,neigh);
|
|
for (int e = 0; e < neigh.Size(); e++)
|
|
{
|
|
int nn = neigh[e];
|
|
if (ncmesh->IsGhost(ncmesh->elements[nn])) { continue; }
|
|
int el = ncmesh->elements[nn].index;
|
|
inv_tr->SetTransformation(*GetElementTransformation(el));
|
|
int res = inv_tr->Transform(pt, ips[k]);
|
|
if (res == InverseElementTransformation::Inside)
|
|
{
|
|
elem_ids[k] = el;
|
|
pts_found++;
|
|
goto next_point;
|
|
}
|
|
}
|
|
}
|
|
next_point: ;
|
|
}
|
|
delete vtoel;
|
|
}
|
|
if (inv_trans == NULL) { delete inv_tr; }
|
|
|
|
if (warn && pts_found != npts)
|
|
{
|
|
MFEM_WARNING((npts-pts_found) << " points were not found");
|
|
}
|
|
return pts_found;
|
|
}
|
|
|
|
void Mesh::GetGeometricParametersFromJacobian(const DenseMatrix &J,
|
|
real_t &volume,
|
|
Vector &aspr,
|
|
Vector &skew,
|
|
Vector &ori) const
|
|
{
|
|
J.HostRead();
|
|
aspr.HostWrite();
|
|
skew.HostWrite();
|
|
ori.HostWrite();
|
|
MFEM_VERIFY(Dim == 2 || Dim == 3, "Only 2D/3D meshes supported right now.");
|
|
MFEM_VERIFY(Dim == spaceDim, "Surface meshes not currently supported.");
|
|
if (Dim == 2)
|
|
{
|
|
aspr.SetSize(1);
|
|
skew.SetSize(1);
|
|
ori.SetSize(1);
|
|
Vector col1, col2;
|
|
J.GetColumn(0, col1);
|
|
J.GetColumn(1, col2);
|
|
|
|
// Area/Volume
|
|
volume = J.Det();
|
|
|
|
// Aspect-ratio
|
|
aspr(0) = col2.Norml2()/col1.Norml2();
|
|
|
|
// Skewness
|
|
skew(0) = std::atan2(J.Det(), col1 * col2);
|
|
|
|
// Orientation
|
|
ori(0) = std::atan2(J(1,0), J(0,0));
|
|
}
|
|
else if (Dim == 3)
|
|
{
|
|
aspr.SetSize(4);
|
|
skew.SetSize(3);
|
|
ori.SetSize(4);
|
|
Vector col1, col2, col3;
|
|
J.GetColumn(0, col1);
|
|
J.GetColumn(1, col2);
|
|
J.GetColumn(2, col3);
|
|
real_t len1 = col1.Norml2(),
|
|
len2 = col2.Norml2(),
|
|
len3 = col3.Norml2();
|
|
|
|
Vector col1unit = col1,
|
|
col2unit = col2,
|
|
col3unit = col3;
|
|
col1unit *= 1.0/len1;
|
|
col2unit *= 1.0/len2;
|
|
col3unit *= 1.0/len3;
|
|
|
|
// Area/Volume
|
|
volume = J.Det();
|
|
|
|
// Aspect-ratio - non-dimensional
|
|
aspr(0) = len1/std::sqrt(len2*len3),
|
|
aspr(1) = len2/std::sqrt(len1*len3);
|
|
|
|
// Aspect-ratio - dimensional - needed for TMOP
|
|
aspr(2) = std::sqrt(len1/(len2*len3)),
|
|
aspr(3) = std::sqrt(len2/(len1*len3));
|
|
|
|
// Skewness
|
|
Vector crosscol12, crosscol13;
|
|
col1.cross3D(col2, crosscol12);
|
|
col1.cross3D(col3, crosscol13);
|
|
skew(0) = std::acos(col1unit*col2unit);
|
|
skew(1) = std::acos(col1unit*col3unit);
|
|
skew(2) = std::atan(len1*volume/(crosscol12*crosscol13));
|
|
|
|
// Orientation
|
|
// First we define the rotation matrix
|
|
DenseMatrix rot(Dim);
|
|
// First column
|
|
for (int d=0; d<Dim; d++) { rot(d, 0) = col1unit(d); }
|
|
// Second column
|
|
Vector rot2 = col2unit;
|
|
Vector rot1 = col1unit;
|
|
rot1 *= col1unit*col2unit;
|
|
rot2 -= rot1;
|
|
col1unit.cross3D(col2unit, rot1);
|
|
rot2 /= rot1.Norml2();
|
|
for (int d=0; d < Dim; d++) { rot(d, 1) = rot2(d); }
|
|
// Third column
|
|
rot1 /= rot1.Norml2();
|
|
for (int d=0; d < Dim; d++) { rot(d, 2) = rot1(d); }
|
|
real_t delta = sqrt(pow(rot(2,1)-rot(1,2), 2.0) +
|
|
pow(rot(0,2)-rot(2,0), 2.0) +
|
|
pow(rot(1,0)-rot(0,1), 2.0));
|
|
ori = 0.0;
|
|
if (delta == 0.0) // Matrix is symmetric. Check if it is Identity.
|
|
{
|
|
DenseMatrix Iden(Dim);
|
|
for (int d = 0; d < Dim; d++) { Iden(d, d) = 1.0; };
|
|
Iden -= rot;
|
|
if (Iden.FNorm2() != 0)
|
|
{
|
|
// TODO: Handling of these cases.
|
|
rot.Print();
|
|
MFEM_ABORT("Invalid rotation matrix. Contact TMOP Developers.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ori(0) = (1./delta)*(rot(2,1)-rot(1,2));
|
|
ori(1) = (1./delta)*(rot(0,2)-rot(2,0));
|
|
ori(2) = (1./delta)*(rot(1,0)-rot(0,1));
|
|
ori(3) = std::acos(0.5*(rot.Trace()-1.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
MeshPart::EntityHelper::EntityHelper(
|
|
int dim_, const Array<int> (&entity_to_vertex_)[Geometry::NumGeom])
|
|
: dim(dim_),
|
|
entity_to_vertex(entity_to_vertex_)
|
|
{
|
|
int geom_offset = 0;
|
|
for (int g = Geometry::DimStart[dim]; g < Geometry::DimStart[dim+1]; g++)
|
|
{
|
|
geom_offsets[g] = geom_offset;
|
|
geom_offset += entity_to_vertex[g].Size()/Geometry::NumVerts[g];
|
|
}
|
|
geom_offsets[Geometry::DimStart[dim+1]] = geom_offset;
|
|
num_entities = geom_offset;
|
|
}
|
|
|
|
MeshPart::Entity MeshPart::EntityHelper::FindEntity(int bytype_entity_id)
|
|
{
|
|
// Find the 'geom' that corresponds to 'bytype_entity_id'
|
|
int geom = Geometry::DimStart[dim];
|
|
while (geom_offsets[geom+1] <= bytype_entity_id) { geom++; }
|
|
MFEM_ASSERT(geom < Geometry::NumGeom, "internal error");
|
|
MFEM_ASSERT(Geometry::Dimension[geom] == dim, "internal error");
|
|
const int nv = Geometry::NumVerts[geom];
|
|
const int geom_elem_id = bytype_entity_id - geom_offsets[geom];
|
|
const int *v = &entity_to_vertex[geom][nv*geom_elem_id];
|
|
return { geom, nv, v };
|
|
}
|
|
|
|
void MeshPart::Print(std::ostream &os) const
|
|
{
|
|
os << "MFEM mesh v1.2\n";
|
|
|
|
// optional
|
|
os <<
|
|
"\n#\n# MFEM Geometry Types (see mesh/geom.hpp):\n#\n"
|
|
"# POINT = 0\n"
|
|
"# SEGMENT = 1\n"
|
|
"# TRIANGLE = 2\n"
|
|
"# SQUARE = 3\n"
|
|
"# TETRAHEDRON = 4\n"
|
|
"# CUBE = 5\n"
|
|
"# PRISM = 6\n"
|
|
"# PYRAMID = 7\n"
|
|
"#\n";
|
|
|
|
const int dim = dimension;
|
|
os << "\ndimension\n" << dim;
|
|
|
|
os << "\n\nelements\n" << num_elements << '\n';
|
|
{
|
|
const bool have_element_map = (element_map.Size() == num_elements);
|
|
MFEM_ASSERT(have_element_map || element_map.Size() == 0,
|
|
"invalid MeshPart state");
|
|
EntityHelper elem_helper(dim, entity_to_vertex);
|
|
MFEM_ASSERT(elem_helper.num_entities == num_elements,
|
|
"invalid MeshPart state");
|
|
for (int nat_elem_id = 0; nat_elem_id < num_elements; nat_elem_id++)
|
|
{
|
|
const int bytype_elem_id = have_element_map ?
|
|
element_map[nat_elem_id] : nat_elem_id;
|
|
const Entity ent = elem_helper.FindEntity(bytype_elem_id);
|
|
// Print the element
|
|
os << attributes[nat_elem_id] << ' ' << ent.geom;
|
|
for (int i = 0; i < ent.num_verts; i++)
|
|
{
|
|
os << ' ' << ent.verts[i];
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
|
|
os << "\nboundary\n" << num_bdr_elements << '\n';
|
|
{
|
|
const bool have_boundary_map = (boundary_map.Size() == num_bdr_elements);
|
|
MFEM_ASSERT(have_boundary_map || boundary_map.Size() == 0,
|
|
"invalid MeshPart state");
|
|
EntityHelper bdr_helper(dim-1, entity_to_vertex);
|
|
MFEM_ASSERT(bdr_helper.num_entities == num_bdr_elements,
|
|
"invalid MeshPart state");
|
|
for (int nat_bdr_id = 0; nat_bdr_id < num_bdr_elements; nat_bdr_id++)
|
|
{
|
|
const int bytype_bdr_id = have_boundary_map ?
|
|
boundary_map[nat_bdr_id] : nat_bdr_id;
|
|
const Entity ent = bdr_helper.FindEntity(bytype_bdr_id);
|
|
// Print the boundary element
|
|
os << bdr_attributes[nat_bdr_id] << ' ' << ent.geom;
|
|
for (int i = 0; i < ent.num_verts; i++)
|
|
{
|
|
os << ' ' << ent.verts[i];
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
|
|
os << "\nvertices\n" << num_vertices << '\n';
|
|
if (!nodes)
|
|
{
|
|
const int sdim = space_dimension;
|
|
os << sdim << '\n';
|
|
for (int i = 0; i < num_vertices; i++)
|
|
{
|
|
os << vertex_coordinates[i*sdim];
|
|
for (int d = 1; d < sdim; d++)
|
|
{
|
|
os << ' ' << vertex_coordinates[i*sdim+d];
|
|
}
|
|
os << '\n';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
os << "\nnodes\n";
|
|
nodes->Save(os);
|
|
}
|
|
|
|
os << "\nmfem_serial_mesh_end\n";
|
|
|
|
// Start: GroupTopology::Save
|
|
const int num_groups = my_groups.Size();
|
|
os << "\ncommunication_groups\n";
|
|
os << "number_of_groups " << num_groups << "\n\n";
|
|
|
|
os << "# number of entities in each group, followed by ranks in group\n";
|
|
for (int group_id = 0; group_id < num_groups; ++group_id)
|
|
{
|
|
const int group_size = my_groups.RowSize(group_id);
|
|
const int *group_ptr = my_groups.GetRow(group_id);
|
|
os << group_size;
|
|
for (int group_member_index = 0; group_member_index < group_size;
|
|
++group_member_index)
|
|
{
|
|
os << ' ' << group_ptr[group_member_index];
|
|
}
|
|
os << '\n';
|
|
}
|
|
// End: GroupTopology::Save
|
|
|
|
const Table &g2v = group_shared_entity_to_vertex[Geometry::POINT];
|
|
const Table &g2ev = group_shared_entity_to_vertex[Geometry::SEGMENT];
|
|
const Table &g2tv = group_shared_entity_to_vertex[Geometry::TRIANGLE];
|
|
const Table &g2qv = group_shared_entity_to_vertex[Geometry::SQUARE];
|
|
|
|
MFEM_VERIFY(g2v.RowSize(0) == 0, "internal erroor");
|
|
os << "\ntotal_shared_vertices " << g2v.Size_of_connections() << '\n';
|
|
if (dimension >= 2)
|
|
{
|
|
MFEM_VERIFY(g2ev.RowSize(0) == 0, "internal erroor");
|
|
os << "total_shared_edges " << g2ev.Size_of_connections()/2 << '\n';
|
|
}
|
|
if (dimension >= 3)
|
|
{
|
|
MFEM_VERIFY(g2tv.RowSize(0) == 0, "internal erroor");
|
|
MFEM_VERIFY(g2qv.RowSize(0) == 0, "internal erroor");
|
|
const int total_shared_faces =
|
|
g2tv.Size_of_connections()/3 + g2qv.Size_of_connections()/4;
|
|
os << "total_shared_faces " << total_shared_faces << '\n';
|
|
}
|
|
os << "\n# group 0 has no shared entities\n";
|
|
for (int gr = 1; gr < num_groups; gr++)
|
|
{
|
|
{
|
|
const int nv = g2v.RowSize(gr);
|
|
const int *sv = g2v.GetRow(gr);
|
|
os << "\n# group " << gr << "\nshared_vertices " << nv << '\n';
|
|
for (int i = 0; i < nv; i++)
|
|
{
|
|
os << sv[i] << '\n';
|
|
}
|
|
}
|
|
if (dimension >= 2)
|
|
{
|
|
const int ne = g2ev.RowSize(gr)/2;
|
|
const int *se = g2ev.GetRow(gr);
|
|
os << "\nshared_edges " << ne << '\n';
|
|
for (int i = 0; i < ne; i++)
|
|
{
|
|
const int *v = se + 2*i;
|
|
os << v[0] << ' ' << v[1] << '\n';
|
|
}
|
|
}
|
|
if (dimension >= 3)
|
|
{
|
|
const int nt = g2tv.RowSize(gr)/3;
|
|
const int *st = g2tv.GetRow(gr);
|
|
const int nq = g2qv.RowSize(gr)/4;
|
|
const int *sq = g2qv.GetRow(gr);
|
|
os << "\nshared_faces " << nt+nq << '\n';
|
|
for (int i = 0; i < nt; i++)
|
|
{
|
|
os << Geometry::TRIANGLE;
|
|
const int *v = st + 3*i;
|
|
for (int j = 0; j < 3; j++) { os << ' ' << v[j]; }
|
|
os << '\n';
|
|
}
|
|
for (int i = 0; i < nq; i++)
|
|
{
|
|
os << Geometry::SQUARE;
|
|
const int *v = sq + 4*i;
|
|
for (int j = 0; j < 4; j++) { os << ' ' << v[j]; }
|
|
os << '\n';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write out section end tag for mesh.
|
|
os << "\nmfem_mesh_end" << endl;
|
|
}
|
|
|
|
Mesh &MeshPart::GetMesh()
|
|
{
|
|
if (mesh) { return *mesh; }
|
|
|
|
mesh.reset(new Mesh(dimension,
|
|
num_vertices,
|
|
num_elements,
|
|
num_bdr_elements,
|
|
space_dimension));
|
|
|
|
// Add elements
|
|
{
|
|
const bool have_element_map = (element_map.Size() == num_elements);
|
|
MFEM_ASSERT(have_element_map || element_map.Size() == 0,
|
|
"invalid MeshPart state");
|
|
EntityHelper elem_helper(dimension, entity_to_vertex);
|
|
MFEM_ASSERT(elem_helper.num_entities == num_elements,
|
|
"invalid MeshPart state");
|
|
const bool have_tet_refine_flags = (tet_refine_flags.Size() > 0);
|
|
for (int nat_elem_id = 0; nat_elem_id < num_elements; nat_elem_id++)
|
|
{
|
|
const int bytype_elem_id = have_element_map ?
|
|
element_map[nat_elem_id] : nat_elem_id;
|
|
const Entity ent = elem_helper.FindEntity(bytype_elem_id);
|
|
Element *el = mesh->NewElement(ent.geom);
|
|
el->SetVertices(ent.verts);
|
|
el->SetAttribute(attributes[nat_elem_id]);
|
|
if (ent.geom == Geometry::TETRAHEDRON && have_tet_refine_flags)
|
|
{
|
|
constexpr int geom_tet = Geometry::TETRAHEDRON;
|
|
const int tet_id = (ent.verts - entity_to_vertex[geom_tet])/4;
|
|
const int ref_flag = tet_refine_flags[tet_id];
|
|
static_cast<Tetrahedron*>(el)->SetRefinementFlag(ref_flag);
|
|
}
|
|
mesh->AddElement(el);
|
|
}
|
|
}
|
|
|
|
// Add boundary elements
|
|
{
|
|
const bool have_boundary_map = (boundary_map.Size() == num_bdr_elements);
|
|
MFEM_ASSERT(have_boundary_map || boundary_map.Size() == 0,
|
|
"invalid MeshPart state");
|
|
EntityHelper bdr_helper(dimension-1, entity_to_vertex);
|
|
MFEM_ASSERT(bdr_helper.num_entities == num_bdr_elements,
|
|
"invalid MeshPart state");
|
|
for (int nat_bdr_id = 0; nat_bdr_id < num_bdr_elements; nat_bdr_id++)
|
|
{
|
|
const int bytype_bdr_id = have_boundary_map ?
|
|
boundary_map[nat_bdr_id] : nat_bdr_id;
|
|
const Entity ent = bdr_helper.FindEntity(bytype_bdr_id);
|
|
Element *bdr = mesh->NewElement(ent.geom);
|
|
bdr->SetVertices(ent.verts);
|
|
bdr->SetAttribute(bdr_attributes[nat_bdr_id]);
|
|
mesh->AddBdrElement(bdr);
|
|
}
|
|
}
|
|
|
|
// Add vertices
|
|
if (vertex_coordinates.Size() == space_dimension*num_vertices)
|
|
{
|
|
MFEM_ASSERT(!nodes, "invalid MeshPart state");
|
|
for (int vert_id = 0; vert_id < num_vertices; vert_id++)
|
|
{
|
|
mesh->AddVertex(vertex_coordinates + space_dimension*vert_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
MFEM_ASSERT(vertex_coordinates.Size() == 0, "invalid MeshPart state");
|
|
for (int vert_id = 0; vert_id < num_vertices; vert_id++)
|
|
{
|
|
mesh->AddVertex(0., 0., 0.);
|
|
}
|
|
// 'mesh.Nodes' cannot be set here -- they can be set later, if needed
|
|
}
|
|
|
|
mesh->FinalizeTopology(/* generate_bdr: */ false);
|
|
|
|
return *mesh;
|
|
}
|
|
|
|
|
|
MeshPartitioner::MeshPartitioner(Mesh &mesh_,
|
|
int num_parts_,
|
|
const int *partitioning_,
|
|
int part_method)
|
|
: mesh(mesh_)
|
|
{
|
|
if (partitioning_)
|
|
{
|
|
partitioning.MakeRef(const_cast<int *>(partitioning_), mesh.GetNE(),
|
|
false);
|
|
}
|
|
else
|
|
{
|
|
// Mesh::GeneratePartitioning always uses new[] to allocate the,
|
|
// partitioning, so we need to tell the memory manager to free it with
|
|
// delete[] (even if a different host memory type has been selected).
|
|
constexpr MemoryType mt = MemoryType::HOST;
|
|
partitioning.MakeRef(mesh.GeneratePartitioning(num_parts_, part_method),
|
|
mesh.GetNE(), mt, true);
|
|
}
|
|
|
|
Transpose(partitioning, part_to_element, num_parts_);
|
|
// Note: the element ids in each row of 'part_to_element' are sorted.
|
|
|
|
const int dim = mesh.Dimension();
|
|
if (dim >= 2)
|
|
{
|
|
Transpose(mesh.ElementToEdgeTable(), edge_to_element, mesh.GetNEdges());
|
|
}
|
|
|
|
Array<int> boundary_to_part(mesh.GetNBE());
|
|
// Same logic as in ParMesh::BuildLocalBoundary
|
|
if (dim >= 3)
|
|
{
|
|
for (int i = 0; i < boundary_to_part.Size(); i++)
|
|
{
|
|
int face, o, el1, el2;
|
|
mesh.GetBdrElementFace(i, &face, &o);
|
|
mesh.GetFaceElements(face, &el1, &el2);
|
|
boundary_to_part[i] =
|
|
partitioning[(o % 2 == 0 || el2 < 0) ? el1 : el2];
|
|
}
|
|
}
|
|
else if (dim == 2)
|
|
{
|
|
for (int i = 0; i < boundary_to_part.Size(); i++)
|
|
{
|
|
int edge = mesh.GetBdrElementFaceIndex(i);
|
|
int el1 = edge_to_element.GetRow(edge)[0];
|
|
boundary_to_part[i] = partitioning[el1];
|
|
}
|
|
}
|
|
else if (dim == 1)
|
|
{
|
|
for (int i = 0; i < boundary_to_part.Size(); i++)
|
|
{
|
|
int vert = mesh.GetBdrElementFaceIndex(i);
|
|
int el1, el2;
|
|
mesh.GetFaceElements(vert, &el1, &el2);
|
|
boundary_to_part[i] = partitioning[el1];
|
|
}
|
|
}
|
|
Transpose(boundary_to_part, part_to_boundary, num_parts_);
|
|
// Note: the boundary element ids in each row of 'part_to_boundary' are
|
|
// sorted.
|
|
boundary_to_part.DeleteAll();
|
|
|
|
Table *vert_element = mesh.GetVertexToElementTable(); // we must delete this
|
|
vertex_to_element.Swap(*vert_element);
|
|
delete vert_element;
|
|
}
|
|
|
|
void MeshPartitioner::ExtractPart(int part_id, MeshPart &mesh_part) const
|
|
{
|
|
const int num_parts = part_to_element.Size();
|
|
|
|
MFEM_VERIFY(0 <= part_id && part_id < num_parts,
|
|
"invalid part_id = " << part_id
|
|
<< ", num_parts = " << num_parts);
|
|
|
|
const int dim = mesh.Dimension();
|
|
const int sdim = mesh.SpaceDimension();
|
|
const int num_elems = part_to_element.RowSize(part_id);
|
|
const int *elem_list = part_to_element.GetRow(part_id); // sorted
|
|
const int num_bdr_elems = part_to_boundary.RowSize(part_id);
|
|
const int *bdr_elem_list = part_to_boundary.GetRow(part_id); // sorted
|
|
|
|
// Initialize 'mesh_part'
|
|
mesh_part.dimension = dim;
|
|
mesh_part.space_dimension = sdim;
|
|
mesh_part.num_vertices = 0;
|
|
mesh_part.num_elements = num_elems;
|
|
mesh_part.num_bdr_elements = num_bdr_elems;
|
|
for (int g = 0; g < Geometry::NumGeom; g++)
|
|
{
|
|
mesh_part.entity_to_vertex[g].SetSize(0); // can reuse Array allocation
|
|
}
|
|
mesh_part.tet_refine_flags.SetSize(0);
|
|
mesh_part.element_map.SetSize(0); // 0 or 'num_elements', if needed
|
|
mesh_part.boundary_map.SetSize(0); // 0 or 'num_bdr_elements', if needed
|
|
mesh_part.attributes.SetSize(num_elems);
|
|
mesh_part.bdr_attributes.SetSize(num_bdr_elems);
|
|
mesh_part.vertex_coordinates.SetSize(0);
|
|
|
|
mesh_part.num_parts = num_parts;
|
|
mesh_part.my_part_id = part_id;
|
|
mesh_part.my_groups.Clear();
|
|
for (int g = 0; g < Geometry::NumGeom; g++)
|
|
{
|
|
mesh_part.group_shared_entity_to_vertex[g].Clear();
|
|
}
|
|
mesh_part.nodes.reset(nullptr);
|
|
mesh_part.nodal_fes.reset(nullptr);
|
|
mesh_part.mesh.reset(nullptr);
|
|
|
|
// Initialize:
|
|
// - 'mesh_part.entity_to_vertex' for the elements (boundary elements are
|
|
// set later); vertex ids are global at this point - they will be mapped to
|
|
// local ids later
|
|
// - 'mesh_part.attributes'
|
|
// - 'mesh_part.tet_refine_flags' if needed
|
|
int geom_marker = 0, num_geom = 0;
|
|
for (int i = 0; i < num_elems; i++)
|
|
{
|
|
const Element *elem = mesh.GetElement(elem_list[i]);
|
|
const int geom = elem->GetGeometryType();
|
|
const int nv = Geometry::NumVerts[geom];
|
|
const int *v = elem->GetVertices();
|
|
MFEM_VERIFY(numeric_limits<int>::max() - nv >=
|
|
mesh_part.entity_to_vertex[geom].Size(),
|
|
"overflow in 'entity_to_vertex[geom]', geom: "
|
|
<< Geometry::Name[geom]);
|
|
mesh_part.entity_to_vertex[geom].Append(v, nv);
|
|
mesh_part.attributes[i] = elem->GetAttribute();
|
|
if (geom == Geometry::TETRAHEDRON)
|
|
{
|
|
// Create 'mesh_part.tet_refine_flags' but only if we find at least one
|
|
// non-zero flag in a tetrahedron.
|
|
const Tetrahedron *tet = static_cast<const Tetrahedron*>(elem);
|
|
const int ref_flag = tet->GetRefinementFlag();
|
|
if (mesh_part.tet_refine_flags.Size() == 0)
|
|
{
|
|
if (ref_flag)
|
|
{
|
|
// This is the first time we encounter non-zero 'ref_flag'
|
|
const int num_tets = mesh_part.entity_to_vertex[geom].Size()/nv;
|
|
mesh_part.tet_refine_flags.SetSize(num_tets, 0);
|
|
mesh_part.tet_refine_flags.Last() = ref_flag;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mesh_part.tet_refine_flags.Append(ref_flag);
|
|
}
|
|
}
|
|
if ((geom_marker & (1 << geom)) == 0)
|
|
{
|
|
geom_marker |= (1 << geom);
|
|
num_geom++;
|
|
}
|
|
}
|
|
MFEM_ASSERT(mesh_part.tet_refine_flags.Size() == 0 ||
|
|
mesh_part.tet_refine_flags.Size() ==
|
|
mesh_part.entity_to_vertex[Geometry::TETRAHEDRON].Size()/4,
|
|
"internal error");
|
|
// Initialize 'mesh_part.element_map' if needed
|
|
if (num_geom > 1)
|
|
{
|
|
int offsets[Geometry::NumGeom];
|
|
int offset = 0;
|
|
for (int g = Geometry::DimStart[dim]; g < Geometry::DimStart[dim+1]; g++)
|
|
{
|
|
offsets[g] = offset;
|
|
offset += mesh_part.entity_to_vertex[g].Size()/Geometry::NumVerts[g];
|
|
}
|
|
mesh_part.element_map.SetSize(num_elems);
|
|
for (int i = 0; i < num_elems; i++)
|
|
{
|
|
const int geom = mesh.GetElementGeometry(elem_list[i]);
|
|
mesh_part.element_map[i] = offsets[geom]++;
|
|
}
|
|
}
|
|
|
|
// Initialize:
|
|
// - 'mesh_part.entity_to_vertex' for the boundary elements; vertex ids are
|
|
// global at this point - they will be mapped to local ids later
|
|
// - 'mesh_part.bdr_attributes'
|
|
geom_marker = 0; num_geom = 0;
|
|
for (int i = 0; i < num_bdr_elems; i++)
|
|
{
|
|
const Element *bdr_elem = mesh.GetBdrElement(bdr_elem_list[i]);
|
|
const int geom = bdr_elem->GetGeometryType();
|
|
const int nv = Geometry::NumVerts[geom];
|
|
const int *v = bdr_elem->GetVertices();
|
|
MFEM_VERIFY(numeric_limits<int>::max() - nv >=
|
|
mesh_part.entity_to_vertex[geom].Size(),
|
|
"overflow in 'entity_to_vertex[geom]', geom: "
|
|
<< Geometry::Name[geom]);
|
|
mesh_part.entity_to_vertex[geom].Append(v, nv);
|
|
mesh_part.bdr_attributes[i] = bdr_elem->GetAttribute();
|
|
if ((geom_marker & (1 << geom)) == 0)
|
|
{
|
|
geom_marker |= (1 << geom);
|
|
num_geom++;
|
|
}
|
|
}
|
|
// Initialize 'mesh_part.boundary_map' if needed
|
|
if (num_geom > 1)
|
|
{
|
|
int offsets[Geometry::NumGeom];
|
|
int offset = 0;
|
|
for (int g = Geometry::DimStart[dim-1]; g < Geometry::DimStart[dim]; g++)
|
|
{
|
|
offsets[g] = offset;
|
|
offset += mesh_part.entity_to_vertex[g].Size()/Geometry::NumVerts[g];
|
|
}
|
|
mesh_part.boundary_map.SetSize(num_bdr_elems);
|
|
for (int i = 0; i < num_bdr_elems; i++)
|
|
{
|
|
const int geom = mesh.GetBdrElementGeometry(bdr_elem_list[i]);
|
|
mesh_part.boundary_map[i] = offsets[geom]++;
|
|
}
|
|
}
|
|
|
|
// Create the vertex id map, 'vertex_loc_to_glob', which maps local ids to
|
|
// global ones; the map is sorted, preserving the global ordering.
|
|
Array<int> vertex_loc_to_glob;
|
|
{
|
|
std::unordered_set<int> vertex_set;
|
|
for (int i = 0; i < num_elems; i++)
|
|
{
|
|
const Element *elem = mesh.GetElement(elem_list[i]);
|
|
const int geom = elem->GetGeometryType();
|
|
const int nv = Geometry::NumVerts[geom];
|
|
const int *v = elem->GetVertices();
|
|
vertex_set.insert(v, v + nv);
|
|
}
|
|
vertex_loc_to_glob.SetSize(static_cast<int>(vertex_set.size()));
|
|
std::copy(vertex_set.begin(), vertex_set.end(), // src
|
|
vertex_loc_to_glob.begin()); // dest
|
|
}
|
|
vertex_loc_to_glob.Sort();
|
|
|
|
// Initialize 'mesh_part.num_vertices'
|
|
mesh_part.num_vertices = vertex_loc_to_glob.Size();
|
|
|
|
// Update the vertex ids in the arrays 'mesh_part.entity_to_vertex' from
|
|
// global to local.
|
|
for (int g = 0; g < Geometry::NumGeom; g++)
|
|
{
|
|
Array<int> &vert_array = mesh_part.entity_to_vertex[g];
|
|
for (int i = 0; i < vert_array.Size(); i++)
|
|
{
|
|
const int glob_id = vert_array[i];
|
|
const int loc_id = vertex_loc_to_glob.FindSorted(glob_id);
|
|
MFEM_ASSERT(loc_id >= 0, "internal error: global vertex id not found");
|
|
vert_array[i] = loc_id;
|
|
}
|
|
}
|
|
|
|
// Initialize one of 'mesh_part.vertex_coordinates' or 'mesh_part.nodes'
|
|
if (!mesh.GetNodes())
|
|
{
|
|
MFEM_VERIFY(numeric_limits<int>::max()/sdim >= vertex_loc_to_glob.Size(),
|
|
"overflow in 'vertex_coordinates', num_vertices = "
|
|
<< vertex_loc_to_glob.Size() << ", sdim = " << sdim);
|
|
mesh_part.vertex_coordinates.SetSize(sdim*vertex_loc_to_glob.Size());
|
|
for (int i = 0; i < vertex_loc_to_glob.Size(); i++)
|
|
{
|
|
const real_t *coord = mesh.GetVertex(vertex_loc_to_glob[i]);
|
|
for (int d = 0; d < sdim; d++)
|
|
{
|
|
mesh_part.vertex_coordinates[i*sdim+d] = coord[d];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const GridFunction &glob_nodes = *mesh.GetNodes();
|
|
mesh_part.nodal_fes = ExtractFESpace(mesh_part, *glob_nodes.FESpace());
|
|
// Initialized 'mesh_part.mesh'.
|
|
// Note: the nodes of 'mesh_part.mesh' are not set.
|
|
|
|
mesh_part.nodes = ExtractGridFunction(mesh_part, glob_nodes,
|
|
*mesh_part.nodal_fes);
|
|
|
|
// Attach the 'mesh_part.nodes' to the 'mesh_part.mesh'.
|
|
mesh_part.mesh->NewNodes(*mesh_part.nodes, /* make_owner: */ false);
|
|
// Note: the vertices of 'mesh_part.mesh' are not set.
|
|
}
|
|
|
|
// Begin constructing the "neighbor" groups, i.e. the groups that contain
|
|
// 'part_id'.
|
|
ListOfIntegerSets groups;
|
|
{
|
|
// the first group is the local one
|
|
IntegerSet group;
|
|
group.Recreate(1, &part_id);
|
|
groups.Insert(group);
|
|
}
|
|
|
|
// 'shared_faces' : shared face id -> (global_face_id, group_id)
|
|
// Note: 'shared_faces' will be sorted by 'global_face_id'.
|
|
Array<Pair<int,int>> shared_faces;
|
|
|
|
// Add "neighbor" groups defined by faces
|
|
// Construct 'shared_faces'.
|
|
if (dim >= 3)
|
|
{
|
|
std::unordered_set<int> face_set;
|
|
// Construct 'face_set'
|
|
const Table &elem_to_face = mesh.ElementToFaceTable();
|
|
for (int loc_elem_id = 0; loc_elem_id < num_elems; loc_elem_id++)
|
|
{
|
|
const int glob_elem_id = elem_list[loc_elem_id];
|
|
const int nfaces = elem_to_face.RowSize(glob_elem_id);
|
|
const int *faces = elem_to_face.GetRow(glob_elem_id);
|
|
face_set.insert(faces, faces + nfaces);
|
|
}
|
|
// Construct 'shared_faces'; add "neighbor" groups defined by faces.
|
|
IntegerSet group;
|
|
for (int glob_face_id : face_set)
|
|
{
|
|
int el[2];
|
|
mesh.GetFaceElements(glob_face_id, &el[0], &el[1]);
|
|
if (el[1] < 0) { continue; }
|
|
el[0] = partitioning[el[0]];
|
|
el[1] = partitioning[el[1]];
|
|
MFEM_ASSERT(el[0] == part_id || el[1] == part_id, "internal error");
|
|
if (el[0] != part_id || el[1] != part_id)
|
|
{
|
|
group.Recreate(2, el);
|
|
const int group_id = groups.Insert(group);
|
|
shared_faces.Append(Pair<int,int>(glob_face_id, group_id));
|
|
}
|
|
}
|
|
shared_faces.Sort(); // sort the shared faces by 'glob_face_id'
|
|
}
|
|
|
|
// 'shared_edges' : shared edge id -> (global_edge_id, group_id)
|
|
// Note: 'shared_edges' will be sorted by 'global_edge_id'.
|
|
Array<Pair<int,int>> shared_edges;
|
|
|
|
// Add "neighbor" groups defined by edges.
|
|
// Construct 'shared_edges'.
|
|
if (dim >= 2)
|
|
{
|
|
std::unordered_set<int> edge_set;
|
|
// Construct 'edge_set'
|
|
const Table &elem_to_edge = mesh.ElementToEdgeTable();
|
|
for (int loc_elem_id = 0; loc_elem_id < num_elems; loc_elem_id++)
|
|
{
|
|
const int glob_elem_id = elem_list[loc_elem_id];
|
|
const int nedges = elem_to_edge.RowSize(glob_elem_id);
|
|
const int *edges = elem_to_edge.GetRow(glob_elem_id);
|
|
edge_set.insert(edges, edges + nedges);
|
|
}
|
|
// Construct 'shared_edges'; add "neighbor" groups defined by edges.
|
|
IntegerSet group;
|
|
for (int glob_edge_id : edge_set)
|
|
{
|
|
const int nelem = edge_to_element.RowSize(glob_edge_id);
|
|
const int *elem = edge_to_element.GetRow(glob_edge_id);
|
|
Array<int> &gr = group; // reference to the 'group' internal Array
|
|
gr.SetSize(nelem);
|
|
for (int j = 0; j < nelem; j++)
|
|
{
|
|
gr[j] = partitioning[elem[j]];
|
|
}
|
|
gr.Sort();
|
|
gr.Unique();
|
|
MFEM_ASSERT(gr.FindSorted(part_id) >= 0, "internal error");
|
|
if (group.Size() > 1)
|
|
{
|
|
const int group_id = groups.Insert(group);
|
|
shared_edges.Append(Pair<int,int>(glob_edge_id, group_id));
|
|
}
|
|
}
|
|
shared_edges.Sort(); // sort the shared edges by 'glob_edge_id'
|
|
}
|
|
|
|
// 'shared_verts' : shared vertex id -> (global_vertex_id, group_id)
|
|
// Note: 'shared_verts' will be sorted by 'global_vertex_id'.
|
|
Array<Pair<int,int>> shared_verts;
|
|
|
|
// Add "neighbor" groups defined by vertices.
|
|
// Construct 'shared_verts'.
|
|
{
|
|
IntegerSet group;
|
|
for (int i = 0; i < vertex_loc_to_glob.Size(); i++)
|
|
{
|
|
// 'vertex_to_element' maps global vertex ids to global element ids
|
|
const int glob_vertex_id = vertex_loc_to_glob[i];
|
|
const int nelem = vertex_to_element.RowSize(glob_vertex_id);
|
|
const int *elem = vertex_to_element.GetRow(glob_vertex_id);
|
|
Array<int> &gr = group; // reference to the 'group' internal Array
|
|
gr.SetSize(nelem);
|
|
for (int j = 0; j < nelem; j++)
|
|
{
|
|
gr[j] = partitioning[elem[j]];
|
|
}
|
|
gr.Sort();
|
|
gr.Unique();
|
|
MFEM_ASSERT(gr.FindSorted(part_id) >= 0, "internal error");
|
|
if (group.Size() > 1)
|
|
{
|
|
const int group_id = groups.Insert(group);
|
|
shared_verts.Append(Pair<int,int>(glob_vertex_id, group_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Done constructing the "neighbor" groups in 'groups'.
|
|
const int num_groups = groups.Size();
|
|
|
|
// Define 'mesh_part.my_groups'
|
|
groups.AsTable(mesh_part.my_groups);
|
|
|
|
// Construct 'mesh_part.group_shared_entity_to_vertex[Geometry::POINT]'
|
|
Table &group__shared_vertex_to_vertex =
|
|
mesh_part.group_shared_entity_to_vertex[Geometry::POINT];
|
|
group__shared_vertex_to_vertex.MakeI(num_groups);
|
|
for (int sv = 0; sv < shared_verts.Size(); sv++)
|
|
{
|
|
const int group_id = shared_verts[sv].two;
|
|
group__shared_vertex_to_vertex.AddAColumnInRow(group_id);
|
|
}
|
|
group__shared_vertex_to_vertex.MakeJ();
|
|
for (int sv = 0; sv < shared_verts.Size(); sv++)
|
|
{
|
|
const int glob_vertex_id = shared_verts[sv].one;
|
|
const int group_id = shared_verts[sv].two;
|
|
const int loc_vertex_id = vertex_loc_to_glob.FindSorted(glob_vertex_id);
|
|
MFEM_ASSERT(loc_vertex_id >= 0, "internal error");
|
|
group__shared_vertex_to_vertex.AddConnection(group_id, loc_vertex_id);
|
|
}
|
|
group__shared_vertex_to_vertex.ShiftUpI();
|
|
|
|
// Construct 'mesh_part.group_shared_entity_to_vertex[Geometry::SEGMENT]'
|
|
if (dim >= 2)
|
|
{
|
|
Table &group__shared_edge_to_vertex =
|
|
mesh_part.group_shared_entity_to_vertex[Geometry::SEGMENT];
|
|
group__shared_edge_to_vertex.MakeI(num_groups);
|
|
for (int se = 0; se < shared_edges.Size(); se++)
|
|
{
|
|
const int group_id = shared_edges[se].two;
|
|
group__shared_edge_to_vertex.AddColumnsInRow(group_id, 2);
|
|
}
|
|
group__shared_edge_to_vertex.MakeJ();
|
|
const Table &edge_to_vertex = *mesh.GetEdgeVertexTable();
|
|
for (int se = 0; se < shared_edges.Size(); se++)
|
|
{
|
|
const int glob_edge_id = shared_edges[se].one;
|
|
const int group_id = shared_edges[se].two;
|
|
const int *v = edge_to_vertex.GetRow(glob_edge_id);
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
const int loc_vertex_id = vertex_loc_to_glob.FindSorted(v[i]);
|
|
MFEM_ASSERT(loc_vertex_id >= 0, "internal error");
|
|
group__shared_edge_to_vertex.AddConnection(group_id, loc_vertex_id);
|
|
}
|
|
}
|
|
group__shared_edge_to_vertex.ShiftUpI();
|
|
}
|
|
|
|
// Construct 'mesh_part.group_shared_entity_to_vertex[Geometry::TRIANGLE]'
|
|
// and 'mesh_part.group_shared_entity_to_vertex[Geometry::SQUARE]'.
|
|
if (dim >= 3)
|
|
{
|
|
Table &group__shared_tria_to_vertex =
|
|
mesh_part.group_shared_entity_to_vertex[Geometry::TRIANGLE];
|
|
Table &group__shared_quad_to_vertex =
|
|
mesh_part.group_shared_entity_to_vertex[Geometry::SQUARE];
|
|
Array<int> vertex_ids;
|
|
group__shared_tria_to_vertex.MakeI(num_groups);
|
|
group__shared_quad_to_vertex.MakeI(num_groups);
|
|
for (int sf = 0; sf < shared_faces.Size(); sf++)
|
|
{
|
|
const int glob_face_id = shared_faces[sf].one;
|
|
const int group_id = shared_faces[sf].two;
|
|
const int geom = mesh.GetFaceGeometry(glob_face_id);
|
|
mesh_part.group_shared_entity_to_vertex[geom].
|
|
AddColumnsInRow(group_id, Geometry::NumVerts[geom]);
|
|
}
|
|
group__shared_tria_to_vertex.MakeJ();
|
|
group__shared_quad_to_vertex.MakeJ();
|
|
for (int sf = 0; sf < shared_faces.Size(); sf++)
|
|
{
|
|
const int glob_face_id = shared_faces[sf].one;
|
|
const int group_id = shared_faces[sf].two;
|
|
const int geom = mesh.GetFaceGeometry(glob_face_id);
|
|
mesh.GetFaceVertices(glob_face_id, vertex_ids);
|
|
// Rotate shared triangles that have an adjacent tetrahedron with a
|
|
// nonzero refinement flag.
|
|
// See also ParMesh::BuildSharedFaceElems.
|
|
if (geom == Geometry::TRIANGLE)
|
|
{
|
|
int glob_el_id[2];
|
|
mesh.GetFaceElements(glob_face_id, &glob_el_id[0], &glob_el_id[1]);
|
|
int side = 0;
|
|
const Element *el = mesh.GetElement(glob_el_id[0]);
|
|
const Tetrahedron *tet = nullptr;
|
|
if (el->GetGeometryType() == Geometry::TETRAHEDRON)
|
|
{
|
|
tet = static_cast<const Tetrahedron*>(el);
|
|
}
|
|
else
|
|
{
|
|
side = 1;
|
|
el = mesh.GetElement(glob_el_id[1]);
|
|
if (el->GetGeometryType() == Geometry::TETRAHEDRON)
|
|
{
|
|
tet = static_cast<const Tetrahedron*>(el);
|
|
}
|
|
}
|
|
if (tet && tet->GetRefinementFlag())
|
|
{
|
|
// mark the shared face for refinement by reorienting
|
|
// it according to the refinement flag in the tetrahedron
|
|
// to which this shared face belongs to.
|
|
int info[2];
|
|
mesh.GetFaceInfos(glob_face_id, &info[0], &info[1]);
|
|
tet->GetMarkedFace(info[side]/64, &vertex_ids[0]);
|
|
}
|
|
}
|
|
for (int i = 0; i < vertex_ids.Size(); i++)
|
|
{
|
|
const int glob_id = vertex_ids[i];
|
|
const int loc_id = vertex_loc_to_glob.FindSorted(glob_id);
|
|
MFEM_ASSERT(loc_id >= 0, "internal error");
|
|
vertex_ids[i] = loc_id;
|
|
}
|
|
mesh_part.group_shared_entity_to_vertex[geom].
|
|
AddConnections(group_id, vertex_ids, vertex_ids.Size());
|
|
}
|
|
group__shared_tria_to_vertex.ShiftUpI();
|
|
group__shared_quad_to_vertex.ShiftUpI();
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<FiniteElementSpace>
|
|
MeshPartitioner::ExtractFESpace(MeshPart &mesh_part,
|
|
const FiniteElementSpace &global_fespace) const
|
|
{
|
|
mesh_part.GetMesh(); // initialize 'mesh_part.mesh'
|
|
// Note: the nodes of 'mesh_part.mesh' are not set by GetMesh() unless they
|
|
// were already constructed, e.g. by ExtractPart().
|
|
|
|
return std::unique_ptr<FiniteElementSpace>(
|
|
new FiniteElementSpace(mesh_part.mesh.get(),
|
|
global_fespace.FEColl(),
|
|
global_fespace.GetVDim(),
|
|
global_fespace.GetOrdering()));
|
|
}
|
|
|
|
std::unique_ptr<GridFunction>
|
|
MeshPartitioner::ExtractGridFunction(const MeshPart &mesh_part,
|
|
const GridFunction &global_gf,
|
|
FiniteElementSpace &local_fespace) const
|
|
{
|
|
std::unique_ptr<GridFunction> local_gf(new GridFunction(&local_fespace));
|
|
|
|
// Transfer data from 'global_gf' to 'local_gf'.
|
|
Array<int> gvdofs, lvdofs;
|
|
Vector loc_vals;
|
|
const int part_id = mesh_part.my_part_id;
|
|
const int num_elems = part_to_element.RowSize(part_id);
|
|
const int *elem_list = part_to_element.GetRow(part_id); // sorted
|
|
for (int loc_elem_id = 0; loc_elem_id < num_elems; loc_elem_id++)
|
|
{
|
|
const int glob_elem_id = elem_list[loc_elem_id];
|
|
DofTransformation glob_dt, local_dt;
|
|
global_gf.FESpace()->GetElementVDofs(glob_elem_id, gvdofs, glob_dt);
|
|
global_gf.GetSubVector(gvdofs, loc_vals);
|
|
glob_dt.InvTransformPrimal(loc_vals);
|
|
local_fespace.GetElementVDofs(loc_elem_id, lvdofs, local_dt);
|
|
local_dt.TransformPrimal(loc_vals);
|
|
local_gf->SetSubVector(lvdofs, loc_vals);
|
|
}
|
|
return local_gf;
|
|
}
|
|
|
|
|
|
GeometricFactors::GeometricFactors(const Mesh *mesh, const IntegrationRule &ir,
|
|
int flags, MemoryType d_mt)
|
|
{
|
|
this->mesh = mesh;
|
|
IntRule = &ir;
|
|
computed_factors = flags;
|
|
|
|
MFEM_ASSERT(mesh->GetNumGeometries(mesh->Dimension()) <= 1,
|
|
"mixed meshes are not supported!");
|
|
MFEM_ASSERT(mesh->GetNodes(), "meshes without nodes are not supported!");
|
|
|
|
Compute(*mesh->GetNodes(), d_mt);
|
|
}
|
|
|
|
GeometricFactors::GeometricFactors(const GridFunction &nodes,
|
|
const IntegrationRule &ir,
|
|
int flags, MemoryType d_mt)
|
|
{
|
|
this->mesh = nodes.FESpace()->GetMesh();
|
|
IntRule = &ir;
|
|
computed_factors = flags;
|
|
|
|
Compute(nodes, d_mt);
|
|
}
|
|
|
|
void GeometricFactors::Compute(const GridFunction &nodes,
|
|
MemoryType d_mt)
|
|
{
|
|
|
|
const FiniteElementSpace *fespace = nodes.FESpace();
|
|
const FiniteElement *fe = fespace->GetTypicalFE();
|
|
const int dim = fe->GetDim();
|
|
const int vdim = fespace->GetVDim();
|
|
const int NE = fespace->GetNE();
|
|
const int ND = fe->GetDof();
|
|
const int NQ = IntRule->GetNPoints();
|
|
|
|
unsigned eval_flags = 0;
|
|
MemoryType my_d_mt = (d_mt != MemoryType::DEFAULT) ? d_mt :
|
|
Device::GetDeviceMemoryType();
|
|
if (computed_factors & GeometricFactors::COORDINATES)
|
|
{
|
|
X.SetSize(vdim*NQ*NE, my_d_mt); // NQ x SDIM x NE
|
|
eval_flags |= QuadratureInterpolator::VALUES;
|
|
}
|
|
if (computed_factors & GeometricFactors::JACOBIANS)
|
|
{
|
|
J.SetSize(dim*vdim*NQ*NE, my_d_mt); // NQ x SDIM x DIM x NE
|
|
eval_flags |= QuadratureInterpolator::DERIVATIVES;
|
|
}
|
|
if (computed_factors & GeometricFactors::DETERMINANTS)
|
|
{
|
|
detJ.SetSize(NQ*NE, my_d_mt); // NQ x NE
|
|
eval_flags |= QuadratureInterpolator::DETERMINANTS;
|
|
}
|
|
|
|
const QuadratureInterpolator *qi = fespace->GetQuadratureInterpolator(*IntRule);
|
|
// All X, J, and detJ use this layout:
|
|
qi->SetOutputLayout(QVectorLayout::byNODES);
|
|
|
|
const bool use_tensor_products = UsesTensorBasis(*fespace);
|
|
|
|
qi->DisableTensorProducts(!use_tensor_products);
|
|
const ElementDofOrdering e_ordering = use_tensor_products ?
|
|
ElementDofOrdering::LEXICOGRAPHIC :
|
|
ElementDofOrdering::NATIVE;
|
|
const Operator *elem_restr = fespace->GetElementRestriction(e_ordering);
|
|
|
|
if (elem_restr) // Always true as of 2021-04-27
|
|
{
|
|
Vector Enodes(vdim*ND*NE, my_d_mt);
|
|
elem_restr->Mult(nodes, Enodes);
|
|
qi->Mult(Enodes, eval_flags, X, J, detJ);
|
|
}
|
|
else
|
|
{
|
|
qi->Mult(nodes, eval_flags, X, J, detJ);
|
|
}
|
|
}
|
|
|
|
FaceGeometricFactors::FaceGeometricFactors(const Mesh *mesh,
|
|
const IntegrationRule &ir,
|
|
int flags, FaceType type,
|
|
MemoryType d_mt)
|
|
: type(type)
|
|
{
|
|
this->mesh = mesh;
|
|
IntRule = &ir;
|
|
computed_factors = flags;
|
|
|
|
const GridFunction *nodes = mesh->GetNodes();
|
|
const FiniteElementSpace *fespace = nodes->FESpace();
|
|
const int vdim = fespace->GetVDim();
|
|
const int NF = fespace->GetNFbyType(type);
|
|
const int NQ = ir.GetNPoints();
|
|
|
|
const FaceRestriction *face_restr = fespace->GetFaceRestriction(
|
|
ElementDofOrdering::LEXICOGRAPHIC,
|
|
type,
|
|
L2FaceValues::SingleValued);
|
|
|
|
|
|
MemoryType my_d_mt = (d_mt != MemoryType::DEFAULT) ? d_mt :
|
|
Device::GetDeviceMemoryType();
|
|
|
|
Vector Fnodes(face_restr->Height(), my_d_mt);
|
|
face_restr->Mult(*nodes, Fnodes);
|
|
|
|
unsigned eval_flags = 0;
|
|
|
|
if (flags & FaceGeometricFactors::COORDINATES)
|
|
{
|
|
X.SetSize(vdim*NQ*NF, my_d_mt);
|
|
eval_flags |= FaceQuadratureInterpolator::VALUES;
|
|
}
|
|
if (flags & FaceGeometricFactors::JACOBIANS)
|
|
{
|
|
J.SetSize(vdim*(mesh->Dimension() - 1)*NQ*NF, my_d_mt);
|
|
eval_flags |= FaceQuadratureInterpolator::DERIVATIVES;
|
|
}
|
|
if (flags & FaceGeometricFactors::DETERMINANTS)
|
|
{
|
|
detJ.SetSize(NQ*NF, my_d_mt);
|
|
eval_flags |= FaceQuadratureInterpolator::DETERMINANTS;
|
|
}
|
|
if (flags & FaceGeometricFactors::NORMALS)
|
|
{
|
|
normal.SetSize(vdim*NQ*NF, my_d_mt);
|
|
eval_flags |= FaceQuadratureInterpolator::NORMALS;
|
|
}
|
|
|
|
const FaceQuadratureInterpolator *qi =
|
|
fespace->GetFaceQuadratureInterpolator(ir, type);
|
|
// All face data vectors assume layout byNODES.
|
|
qi->SetOutputLayout(QVectorLayout::byNODES);
|
|
const bool use_tensor_products = UsesTensorBasis(*fespace);
|
|
qi->DisableTensorProducts(!use_tensor_products);
|
|
|
|
qi->Mult(Fnodes, eval_flags, X, J, detJ, normal);
|
|
}
|
|
|
|
NodeExtrudeCoefficient::NodeExtrudeCoefficient(const int dim, const int n_,
|
|
const real_t s_)
|
|
: VectorCoefficient(dim), n(n_), s(s_), tip(p, dim-1)
|
|
{
|
|
}
|
|
|
|
void NodeExtrudeCoefficient::Eval(Vector &V, ElementTransformation &T,
|
|
const IntegrationPoint &ip)
|
|
{
|
|
V.SetSize(vdim);
|
|
T.Transform(ip, tip);
|
|
V(0) = p[0];
|
|
if (vdim == 2)
|
|
{
|
|
V(1) = s * ((ip.y + layer) / n);
|
|
}
|
|
else
|
|
{
|
|
V(1) = p[1];
|
|
V(2) = s * ((ip.z + layer) / n);
|
|
}
|
|
}
|
|
|
|
|
|
Mesh *Extrude1D(Mesh *mesh, const int ny, const real_t sy, const bool closed)
|
|
{
|
|
if (mesh->Dimension() != 1)
|
|
{
|
|
mfem::err << "Extrude1D : Not a 1D mesh!" << endl;
|
|
mfem_error();
|
|
}
|
|
|
|
int nvy = (closed) ? (ny) : (ny + 1);
|
|
int nvt = mesh->GetNV() * nvy;
|
|
|
|
Mesh *mesh2d;
|
|
|
|
if (closed)
|
|
{
|
|
mesh2d = new Mesh(2, nvt, mesh->GetNE()*ny, mesh->GetNBE()*ny);
|
|
}
|
|
else
|
|
mesh2d = new Mesh(2, nvt, mesh->GetNE()*ny,
|
|
mesh->GetNBE()*ny+2*mesh->GetNE());
|
|
|
|
// vertices
|
|
real_t vc[2];
|
|
for (int i = 0; i < mesh->GetNV(); i++)
|
|
{
|
|
vc[0] = mesh->GetVertex(i)[0];
|
|
for (int j = 0; j < nvy; j++)
|
|
{
|
|
vc[1] = sy * (real_t(j) / ny);
|
|
mesh2d->AddVertex(vc);
|
|
}
|
|
}
|
|
// elements
|
|
Array<int> vert;
|
|
for (int i = 0; i < mesh->GetNE(); i++)
|
|
{
|
|
const Element *elem = mesh->GetElement(i);
|
|
elem->GetVertices(vert);
|
|
const int attr = elem->GetAttribute();
|
|
for (int j = 0; j < ny; j++)
|
|
{
|
|
int qv[4];
|
|
qv[0] = vert[0] * nvy + j;
|
|
qv[1] = vert[1] * nvy + j;
|
|
qv[2] = vert[1] * nvy + (j + 1) % nvy;
|
|
qv[3] = vert[0] * nvy + (j + 1) % nvy;
|
|
|
|
mesh2d->AddQuad(qv, attr);
|
|
}
|
|
}
|
|
// 2D boundary from the 1D boundary
|
|
for (int i = 0; i < mesh->GetNBE(); i++)
|
|
{
|
|
const Element *elem = mesh->GetBdrElement(i);
|
|
elem->GetVertices(vert);
|
|
const int attr = elem->GetAttribute();
|
|
for (int j = 0; j < ny; j++)
|
|
{
|
|
int sv[2];
|
|
sv[0] = vert[0] * nvy + j;
|
|
sv[1] = vert[0] * nvy + (j + 1) % nvy;
|
|
|
|
if (attr%2)
|
|
{
|
|
Swap<int>(sv[0], sv[1]);
|
|
}
|
|
|
|
mesh2d->AddBdrSegment(sv, attr);
|
|
}
|
|
}
|
|
|
|
if (!closed)
|
|
{
|
|
// 2D boundary from the 1D elements (bottom + top)
|
|
int nba = (mesh->bdr_attributes.Size() > 0 ?
|
|
mesh->bdr_attributes.Max() : 0);
|
|
for (int i = 0; i < mesh->GetNE(); i++)
|
|
{
|
|
const Element *elem = mesh->GetElement(i);
|
|
elem->GetVertices(vert);
|
|
const int attr = nba + elem->GetAttribute();
|
|
int sv[2];
|
|
sv[0] = vert[0] * nvy;
|
|
sv[1] = vert[1] * nvy;
|
|
|
|
mesh2d->AddBdrSegment(sv, attr);
|
|
|
|
sv[0] = vert[1] * nvy + ny;
|
|
sv[1] = vert[0] * nvy + ny;
|
|
|
|
mesh2d->AddBdrSegment(sv, attr);
|
|
}
|
|
}
|
|
|
|
mesh2d->FinalizeQuadMesh(1, 0, false);
|
|
|
|
GridFunction *nodes = mesh->GetNodes();
|
|
if (nodes)
|
|
{
|
|
// duplicate the fec of the 1D mesh so that it can be deleted safely
|
|
// along with its nodes, fes and fec
|
|
FiniteElementCollection *fec2d = NULL;
|
|
FiniteElementSpace *fes2d;
|
|
const char *name = nodes->FESpace()->FEColl()->Name();
|
|
string cname = name;
|
|
if (cname == "Linear")
|
|
{
|
|
fec2d = new LinearFECollection;
|
|
}
|
|
else if (cname == "Quadratic")
|
|
{
|
|
fec2d = new QuadraticFECollection;
|
|
}
|
|
else if (cname == "Cubic")
|
|
{
|
|
fec2d = new CubicFECollection;
|
|
}
|
|
else if (!strncmp(name, "H1_", 3))
|
|
{
|
|
fec2d = new H1_FECollection(atoi(name + 7), 2);
|
|
}
|
|
else if (!strncmp(name, "L2_T", 4))
|
|
{
|
|
fec2d = new L2_FECollection(atoi(name + 10), 2, atoi(name + 4));
|
|
}
|
|
else if (!strncmp(name, "L2_", 3))
|
|
{
|
|
fec2d = new L2_FECollection(atoi(name + 7), 2);
|
|
}
|
|
else
|
|
{
|
|
delete mesh2d;
|
|
mfem::err << "Extrude1D : The mesh uses unknown FE collection : "
|
|
<< cname << endl;
|
|
mfem_error();
|
|
}
|
|
fes2d = new FiniteElementSpace(mesh2d, fec2d, 2);
|
|
mesh2d->SetNodalFESpace(fes2d);
|
|
GridFunction *nodes2d = mesh2d->GetNodes();
|
|
nodes2d->MakeOwner(fec2d);
|
|
|
|
NodeExtrudeCoefficient ecoeff(2, ny, sy);
|
|
Vector lnodes;
|
|
Array<int> vdofs2d;
|
|
for (int i = 0; i < mesh->GetNE(); i++)
|
|
{
|
|
ElementTransformation &T = *mesh->GetElementTransformation(i);
|
|
for (int j = ny-1; j >= 0; j--)
|
|
{
|
|
fes2d->GetElementVDofs(i*ny+j, vdofs2d);
|
|
lnodes.SetSize(vdofs2d.Size());
|
|
ecoeff.SetLayer(j);
|
|
fes2d->GetFE(i*ny+j)->Project(ecoeff, T, lnodes);
|
|
nodes2d->SetSubVector(vdofs2d, lnodes);
|
|
}
|
|
}
|
|
}
|
|
return mesh2d;
|
|
}
|
|
|
|
Mesh *Extrude2D(Mesh *mesh, const int nz, const real_t sz)
|
|
{
|
|
if (mesh->Dimension() != 2)
|
|
{
|
|
mfem::err << "Extrude2D : Not a 2D mesh!" << endl;
|
|
mfem_error();
|
|
}
|
|
|
|
int nvz = nz + 1;
|
|
int nvt = mesh->GetNV() * nvz;
|
|
|
|
Mesh *mesh3d = new Mesh(3, nvt, mesh->GetNE()*nz,
|
|
mesh->GetNBE()*nz+2*mesh->GetNE());
|
|
|
|
bool wdgMesh = false;
|
|
bool hexMesh = false;
|
|
|
|
// vertices
|
|
real_t vc[3];
|
|
for (int i = 0; i < mesh->GetNV(); i++)
|
|
{
|
|
vc[0] = mesh->GetVertex(i)[0];
|
|
vc[1] = mesh->GetVertex(i)[1];
|
|
for (int j = 0; j < nvz; j++)
|
|
{
|
|
vc[2] = sz * (real_t(j) / nz);
|
|
mesh3d->AddVertex(vc);
|
|
}
|
|
}
|
|
// elements
|
|
Array<int> vert;
|
|
for (int i = 0; i < mesh->GetNE(); i++)
|
|
{
|
|
const Element *elem = mesh->GetElement(i);
|
|
elem->GetVertices(vert);
|
|
const int attr = elem->GetAttribute();
|
|
Geometry::Type geom = elem->GetGeometryType();
|
|
switch (geom)
|
|
{
|
|
case Geometry::TRIANGLE:
|
|
wdgMesh = true;
|
|
for (int j = 0; j < nz; j++)
|
|
{
|
|
int pv[6];
|
|
pv[0] = vert[0] * nvz + j;
|
|
pv[1] = vert[1] * nvz + j;
|
|
pv[2] = vert[2] * nvz + j;
|
|
pv[3] = vert[0] * nvz + (j + 1) % nvz;
|
|
pv[4] = vert[1] * nvz + (j + 1) % nvz;
|
|
pv[5] = vert[2] * nvz + (j + 1) % nvz;
|
|
|
|
mesh3d->AddWedge(pv, attr);
|
|
}
|
|
break;
|
|
case Geometry::SQUARE:
|
|
hexMesh = true;
|
|
for (int j = 0; j < nz; j++)
|
|
{
|
|
int hv[8];
|
|
hv[0] = vert[0] * nvz + j;
|
|
hv[1] = vert[1] * nvz + j;
|
|
hv[2] = vert[2] * nvz + j;
|
|
hv[3] = vert[3] * nvz + j;
|
|
hv[4] = vert[0] * nvz + (j + 1) % nvz;
|
|
hv[5] = vert[1] * nvz + (j + 1) % nvz;
|
|
hv[6] = vert[2] * nvz + (j + 1) % nvz;
|
|
hv[7] = vert[3] * nvz + (j + 1) % nvz;
|
|
|
|
mesh3d->AddHex(hv, attr);
|
|
}
|
|
break;
|
|
default:
|
|
mfem::err << "Extrude2D : Invalid 2D element type \'"
|
|
<< geom << "\'" << endl;
|
|
mfem_error();
|
|
break;
|
|
}
|
|
}
|
|
// 3D boundary from the 2D boundary
|
|
for (int i = 0; i < mesh->GetNBE(); i++)
|
|
{
|
|
const Element *elem = mesh->GetBdrElement(i);
|
|
elem->GetVertices(vert);
|
|
const int attr = elem->GetAttribute();
|
|
for (int j = 0; j < nz; j++)
|
|
{
|
|
int qv[4];
|
|
qv[0] = vert[0] * nvz + j;
|
|
qv[1] = vert[1] * nvz + j;
|
|
qv[2] = vert[1] * nvz + (j + 1) % nvz;
|
|
qv[3] = vert[0] * nvz + (j + 1) % nvz;
|
|
|
|
mesh3d->AddBdrQuad(qv, attr);
|
|
}
|
|
}
|
|
|
|
// 3D boundary from the 2D elements (bottom + top)
|
|
int nba = (mesh->bdr_attributes.Size() > 0 ?
|
|
mesh->bdr_attributes.Max() : 0);
|
|
for (int i = 0; i < mesh->GetNE(); i++)
|
|
{
|
|
const Element *elem = mesh->GetElement(i);
|
|
elem->GetVertices(vert);
|
|
const int attr = nba + elem->GetAttribute();
|
|
Geometry::Type geom = elem->GetGeometryType();
|
|
switch (geom)
|
|
{
|
|
case Geometry::TRIANGLE:
|
|
{
|
|
int tv[3];
|
|
tv[0] = vert[0] * nvz;
|
|
tv[1] = vert[2] * nvz;
|
|
tv[2] = vert[1] * nvz;
|
|
|
|
mesh3d->AddBdrTriangle(tv, attr);
|
|
|
|
tv[0] = vert[0] * nvz + nz;
|
|
tv[1] = vert[1] * nvz + nz;
|
|
tv[2] = vert[2] * nvz + nz;
|
|
|
|
mesh3d->AddBdrTriangle(tv, attr);
|
|
}
|
|
break;
|
|
case Geometry::SQUARE:
|
|
{
|
|
int qv[4];
|
|
qv[0] = vert[0] * nvz;
|
|
qv[1] = vert[3] * nvz;
|
|
qv[2] = vert[2] * nvz;
|
|
qv[3] = vert[1] * nvz;
|
|
|
|
mesh3d->AddBdrQuad(qv, attr);
|
|
|
|
qv[0] = vert[0] * nvz + nz;
|
|
qv[1] = vert[1] * nvz + nz;
|
|
qv[2] = vert[2] * nvz + nz;
|
|
qv[3] = vert[3] * nvz + nz;
|
|
|
|
mesh3d->AddBdrQuad(qv, attr);
|
|
}
|
|
break;
|
|
default:
|
|
mfem::err << "Extrude2D : Invalid 2D element type \'"
|
|
<< geom << "\'" << endl;
|
|
mfem_error();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( hexMesh && wdgMesh )
|
|
{
|
|
mesh3d->FinalizeMesh(0, false);
|
|
}
|
|
else if ( hexMesh )
|
|
{
|
|
mesh3d->FinalizeHexMesh(1, 0, false);
|
|
}
|
|
else if ( wdgMesh )
|
|
{
|
|
mesh3d->FinalizeWedgeMesh(1, 0, false);
|
|
}
|
|
|
|
GridFunction *nodes = mesh->GetNodes();
|
|
if (nodes)
|
|
{
|
|
// duplicate the fec of the 2D mesh so that it can be deleted safely
|
|
// along with its nodes, fes and fec
|
|
FiniteElementCollection *fec3d = NULL;
|
|
FiniteElementSpace *fes3d;
|
|
const char *name = nodes->FESpace()->FEColl()->Name();
|
|
string cname = name;
|
|
if (cname == "Linear")
|
|
{
|
|
fec3d = new LinearFECollection;
|
|
}
|
|
else if (cname == "Quadratic")
|
|
{
|
|
fec3d = new QuadraticFECollection;
|
|
}
|
|
else if (cname == "Cubic")
|
|
{
|
|
fec3d = new CubicFECollection;
|
|
}
|
|
else if (!strncmp(name, "H1_", 3))
|
|
{
|
|
fec3d = new H1_FECollection(atoi(name + 7), 3);
|
|
}
|
|
else if (!strncmp(name, "L2_T", 4))
|
|
{
|
|
fec3d = new L2_FECollection(atoi(name + 10), 3, atoi(name + 4));
|
|
}
|
|
else if (!strncmp(name, "L2_", 3))
|
|
{
|
|
fec3d = new L2_FECollection(atoi(name + 7), 3);
|
|
}
|
|
else
|
|
{
|
|
delete mesh3d;
|
|
mfem::err << "Extrude3D : The mesh uses unknown FE collection : "
|
|
<< cname << endl;
|
|
mfem_error();
|
|
}
|
|
fes3d = new FiniteElementSpace(mesh3d, fec3d, 3);
|
|
mesh3d->SetNodalFESpace(fes3d);
|
|
GridFunction *nodes3d = mesh3d->GetNodes();
|
|
nodes3d->MakeOwner(fec3d);
|
|
|
|
NodeExtrudeCoefficient ecoeff(3, nz, sz);
|
|
Vector lnodes;
|
|
Array<int> vdofs3d;
|
|
for (int i = 0; i < mesh->GetNE(); i++)
|
|
{
|
|
ElementTransformation &T = *mesh->GetElementTransformation(i);
|
|
for (int j = nz-1; j >= 0; j--)
|
|
{
|
|
fes3d->GetElementVDofs(i*nz+j, vdofs3d);
|
|
lnodes.SetSize(vdofs3d.Size());
|
|
ecoeff.SetLayer(j);
|
|
fes3d->GetFE(i*nz+j)->Project(ecoeff, T, lnodes);
|
|
nodes3d->SetSubVector(vdofs3d, lnodes);
|
|
}
|
|
}
|
|
}
|
|
return mesh3d;
|
|
}
|
|
|
|
Mesh PartitionMPI(int dim, int mpi_cnt, int elem_per_mpi, bool print,
|
|
int &par_ref, Array<int> &partitioning)
|
|
{
|
|
MFEM_VERIFY(dim > 1, "Not implemented for 1D meshes.");
|
|
|
|
// Closest int divisor to the cubit root, going down.
|
|
auto factor3 = [](int N)
|
|
{
|
|
for (int i = static_cast<int>(round(cbrt(N))); i > 0; i--)
|
|
{ if (N % i == 0) { return i; } }
|
|
return 1;
|
|
};
|
|
|
|
// Closest int divisor to the square root, going down.
|
|
auto factor2 = [](int N)
|
|
{
|
|
for (int i = static_cast<int>(round(sqrt(N))); i > 0; i--)
|
|
{ if (N % i == 0) { return i; } }
|
|
return 1;
|
|
};
|
|
|
|
par_ref = 0;
|
|
const int ref_factor = (dim == 2) ? 4 : 8;
|
|
|
|
// Elements per task before performing parallel refinements.
|
|
// This will be used to form the serial mesh.
|
|
int el0 = elem_per_mpi;
|
|
while (el0 % ref_factor == 0)
|
|
{
|
|
el0 /= ref_factor;
|
|
par_ref++;
|
|
}
|
|
|
|
// In the serial mesh we have:
|
|
// The number of MPI blocks is mpi_cnt = mp_x.mpy_y.mpy_z.
|
|
// The size of each MPI block is el0 = el0_x.el0_y.el0_z.
|
|
int mpi_x, mpi_y, mpi_z;
|
|
int el0_x, el0_y, el0_z;
|
|
if (dim == 2)
|
|
{
|
|
mpi_x = factor2(mpi_cnt);
|
|
mpi_y = mpi_cnt / mpi_x;
|
|
|
|
// Switch order for better balance.
|
|
el0_y = factor2(el0);
|
|
el0_x = el0 / el0_y;
|
|
}
|
|
else
|
|
{
|
|
mpi_x = factor3(mpi_cnt);
|
|
mpi_y = factor2(mpi_cnt / mpi_x);
|
|
mpi_z = mpi_cnt / mpi_x / mpi_y;
|
|
|
|
// Switch order for better balance.
|
|
el0_z = factor3(el0);
|
|
el0_y = factor2(el0 / el0_z);
|
|
el0_x = el0 / el0_y / el0_z;
|
|
}
|
|
|
|
if (print && dim == 2)
|
|
{
|
|
int elem_par_x = mpi_x * el0_x * pow(2, par_ref),
|
|
elem_par_y = mpi_y * el0_y * pow(2, par_ref);
|
|
|
|
mfem::out << "--- Mesh generation: \n";
|
|
mfem::out << "Par mesh: " << elem_par_x << " x " << elem_par_y
|
|
<< " (" << elem_par_x * elem_par_y << " elements)\n"
|
|
<< "Elem / task: "
|
|
<< el0_x * pow(2, par_ref) << " x "
|
|
<< el0_y * pow(2, par_ref)
|
|
<< " (" << el0_x * pow(2, 2*par_ref) * el0_y << " elements)\n"
|
|
<< "MPI blocks: " << mpi_x << " x " << mpi_y
|
|
<< " (" << mpi_x * mpi_y << " mpi tasks)\n" << "-\n"
|
|
<< "Serial mesh: "
|
|
<< mpi_x * el0_x << " x " << mpi_y * el0_y
|
|
<< " (" << mpi_x * el0_x * mpi_y * el0_y << " elements)\n"
|
|
<< "Elem / task: " << el0_x << " x " << el0_y << std::endl
|
|
<< "Par refine: " << par_ref << std::endl;
|
|
mfem::out << "--- \n";
|
|
}
|
|
|
|
if (print && dim == 3)
|
|
{
|
|
int elem_par_x = mpi_x * el0_x * pow(2, par_ref),
|
|
elem_par_y = mpi_y * el0_y * pow(2, par_ref),
|
|
elem_par_z = mpi_z * el0_z * pow(2, par_ref);
|
|
|
|
mfem::out << "--- Mesh generation: \n";
|
|
mfem::out << "Par mesh: "
|
|
<< elem_par_x << " x " << elem_par_y << " x " << elem_par_z
|
|
<< " (" << elem_par_x*elem_par_y*elem_par_z << " elements)\n"
|
|
<< "Elem / task: "
|
|
<< el0_x * pow(2, par_ref) << " x "
|
|
<< el0_y * pow(2, par_ref) << " x "
|
|
<< el0_z * pow(2, par_ref)
|
|
<< " (" << el0_x*pow(2, 3*par_ref)*el0_y*el0_z << " elements)\n"
|
|
<< "MPI blocks: " << mpi_x << " x " << mpi_y << " x " << mpi_z
|
|
<< " (" << mpi_x * mpi_y * mpi_z << " mpi tasks)\n" << "-\n"
|
|
<< "Serial mesh: "
|
|
<< mpi_x*el0_x << " x " << mpi_y*el0_y << " x " << mpi_z*el0_z
|
|
<< " (" << mpi_x*el0_x*mpi_y*el0_y*mpi_z*el0_z << " elements)\n"
|
|
<< "Elem / task: "
|
|
<< el0_x << " x " << el0_y << " x " << el0_z << std::endl
|
|
<< "Par refine: " << par_ref << std::endl;
|
|
mfem::out << "--- \n";
|
|
}
|
|
|
|
Mesh mesh;
|
|
int nxyz[3];
|
|
if (dim == 2)
|
|
{
|
|
mesh = Mesh::MakeCartesian2D(mpi_x * el0_x,
|
|
mpi_y * el0_y, Element::QUADRILATERAL, true);
|
|
nxyz[0] = mpi_x; nxyz[1] = mpi_y;
|
|
}
|
|
else
|
|
{
|
|
mesh = Mesh::MakeCartesian3D(mpi_x * el0_x,
|
|
mpi_y * el0_y,
|
|
mpi_z * el0_z, Element::HEXAHEDRON, true);
|
|
nxyz[0] = mpi_x; nxyz[1] = mpi_y; nxyz[2] = mpi_z;
|
|
}
|
|
|
|
const int NE = mesh.GetNE();
|
|
partitioning.SetSize(NE);
|
|
std::unique_ptr<int[]> p_raw(mesh.CartesianPartitioning(nxyz));
|
|
std::copy(p_raw.get(), p_raw.get() + NE, partitioning.GetData());
|
|
|
|
return mesh;
|
|
}
|
|
|
|
bool Mesh::Conforming() const
|
|
{
|
|
if (NURBSext)
|
|
{
|
|
// NURBS meshes are always conforming (element-wise). NURBS patch
|
|
// conformity is indicated by NURBSExtension::NonconformingPatches.
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return ncmesh == NULL;
|
|
}
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
void Mesh::DebugDump(std::ostream &os) const
|
|
{
|
|
// dump vertices and edges (NCMesh "nodes")
|
|
os << NumOfVertices + NumOfEdges << "\n";
|
|
for (int i = 0; i < NumOfVertices; i++)
|
|
{
|
|
const real_t *v = GetVertex(i);
|
|
os << i << " " << v[0] << " " << v[1] << " " << v[2]
|
|
<< " 0 0 " << i << " -1 0\n";
|
|
}
|
|
|
|
Array<int> ev;
|
|
for (int i = 0; i < NumOfEdges; i++)
|
|
{
|
|
GetEdgeVertices(i, ev);
|
|
real_t mid[3] = {0, 0, 0};
|
|
for (int j = 0; j < 2; j++)
|
|
{
|
|
for (int k = 0; k < spaceDim; k++)
|
|
{
|
|
mid[k] += GetVertex(ev[j])[k];
|
|
}
|
|
}
|
|
os << NumOfVertices+i << " "
|
|
<< mid[0]/2 << " " << mid[1]/2 << " " << mid[2]/2 << " "
|
|
<< ev[0] << " " << ev[1] << " -1 " << i << " 0\n";
|
|
}
|
|
|
|
// dump elements
|
|
os << NumOfElements << "\n";
|
|
for (int i = 0; i < NumOfElements; i++)
|
|
{
|
|
const Element* e = elements[i];
|
|
os << e->GetNVertices() << " ";
|
|
for (int j = 0; j < e->GetNVertices(); j++)
|
|
{
|
|
os << e->GetVertices()[j] << " ";
|
|
}
|
|
os << e->GetAttribute() << " 0 " << i << "\n";
|
|
}
|
|
|
|
// dump faces
|
|
os << "0\n";
|
|
}
|
|
#endif
|
|
|
|
}
|