Files
mfem/mesh/pncmesh.cpp
T
2020-03-09 15:36:41 -07:00

2971 lines
83 KiB
C++

// Copyright (c) 2010-2020, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "../config/config.hpp"
#ifdef MFEM_USE_MPI
#include "mesh_headers.hpp"
#include "pncmesh.hpp"
#include "../general/binaryio.hpp"
#include <map>
#include <climits> // INT_MIN, INT_MAX
namespace mfem
{
using namespace bin_io;
ParNCMesh::ParNCMesh(MPI_Comm comm, const NCMesh &ncmesh, int *part)
: NCMesh(ncmesh)
{
MyComm = comm;
MPI_Comm_size(MyComm, &NRanks);
MPI_Comm_rank(MyComm, &MyRank);
// assign leaf elements to the processors by simply splitting the
// sequence of leaf elements into 'NRanks' parts
for (int i = 0; i < leaf_elements.Size(); i++)
{
elements[leaf_elements[i]].rank = part ? part[i] : InitialPartition(i);
}
Update();
// note that at this point all processors still have all the leaf elements;
// we however may now start pruning the refinement tree to get rid of
// branches that only contain someone else's leaves (see Prune())
}
ParNCMesh::ParNCMesh(const ParNCMesh &other)
// copy primary data only
: NCMesh(other)
, MyComm(other.MyComm)
, NRanks(other.NRanks)
, MyRank(other.MyRank)
{
Update(); // mark all secondary stuff for recalculation
}
ParNCMesh::~ParNCMesh()
{
ClearAuxPM();
}
void ParNCMesh::Update()
{
NCMesh::Update();
groups.clear();
group_id.clear();
CommGroup self;
self.push_back(MyRank);
groups.push_back(self);
group_id[self] = 0;
for (int i = 0; i < 3; i++)
{
entity_owner[i].DeleteAll();
entity_pmat_group[i].DeleteAll();
entity_index_rank[i].DeleteAll();
}
shared_vertices.Clear();
shared_edges.Clear();
shared_faces.Clear();
element_type.SetSize(0);
ghost_layer.SetSize(0);
boundary_layer.SetSize(0);
}
void ParNCMesh::AssignLeafIndices()
{
// This is an override of NCMesh::AssignLeafIndices(). The difference is
// that we shift all elements we own to the beginning of the array
// 'leaf_elements' and assign all ghost elements indices >= NElements.
// Also note that the ordering of ghosts and non-ghosts is preserved here,
// which is important for ParNCMesh::GetFaceNeighbors.
// We store the original leaf ordering in 'leaf_glob_order'. This is later
// used (and deleted) in GetConformingSharedStructures
NCMesh::AssignLeafIndices(); // original numbering, for 'leaf_glob_order'
int nleafs = leaf_elements.Size();
Array<int> ghosts;
ghosts.Reserve(nleafs);
NElements = 0;
for (int i = 0; i < nleafs; i++)
{
int elem = leaf_elements[i];
if (elements[elem].rank == MyRank)
{
leaf_elements[NElements++] = elem;
}
else
{
ghosts.Append(elem);
}
}
NGhostElements = ghosts.Size();
leaf_elements.SetSize(NElements);
leaf_elements.Append(ghosts);
// store original (globally consistent) numbering in 'leaf_glob_order'
leaf_glob_order.SetSize(nleafs);
for (int i = 0; i < nleafs; i++)
{
leaf_glob_order[i] = elements[leaf_elements[i]].index;
}
// new numbering with ghost shifted to the back
NCMesh::AssignLeafIndices();
}
void ParNCMesh::UpdateVertices()
{
// This is an override of NCMesh::UpdateVertices. This version first
// assigns vert_index to vertices of elements of our rank. Only these
// vertices then make it to the Mesh in NCMesh::GetMeshComponents.
// The remaining (ghost) vertices are assigned indices greater or equal to
// Mesh::GetNV().
for (node_iterator node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex()) { node->vert_index = -1; }
}
NVertices = 0;
for (int i = 0; i < leaf_elements.Size(); i++)
{
Element &el = elements[leaf_elements[i]];
if (el.rank == MyRank)
{
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
int &vindex = nodes[el.node[j]].vert_index;
if (vindex < 0) { vindex = NVertices++; }
}
}
}
vertex_nodeId.SetSize(NVertices);
for (node_iterator node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex() && node->vert_index >= 0)
{
vertex_nodeId[node->vert_index] = node.index();
}
}
NGhostVertices = 0;
for (node_iterator node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasVertex() && node->vert_index < 0)
{
node->vert_index = NVertices + (NGhostVertices++);
}
}
}
void ParNCMesh::OnMeshUpdated(Mesh *mesh)
{
// This is an override (or extension of) NCMesh::OnMeshUpdated().
// In addition to getting edge/face indices from 'mesh', we also
// assign indices to ghost edges/faces that don't exist in the 'mesh'.
// clear edge_index and Face::index
for (node_iterator node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasEdge()) { node->edge_index = -1; }
}
for (face_iterator face = faces.begin(); face != faces.end(); ++face)
{
face->index = -1;
}
// go assign existing edge/face indices
NCMesh::OnMeshUpdated(mesh);
// count ghost edges and assign their indices
NEdges = mesh->GetNEdges();
NGhostEdges = 0;
for (node_iterator node = nodes.begin(); node != nodes.end(); ++node)
{
if (node->HasEdge() && node->edge_index < 0)
{
node->edge_index = NEdges + (NGhostEdges++);
}
}
// count ghost faces
NFaces = mesh->GetNumFaces();
NGhostFaces = 0;
for (face_iterator face = faces.begin(); face != faces.end(); ++face)
{
if (face->index < 0) { NGhostFaces++; }
}
if (Dim == 2)
{
// in 2D we have fake faces because of DG
MFEM_ASSERT(NFaces == NEdges, "");
MFEM_ASSERT(NGhostFaces == NGhostEdges, "");
}
// resize face_geom (default_geom is for slave faces beyond the ghost layer)
Geometry::Type default_geom = Geometry::SQUARE;
face_geom.SetSize(NFaces + NGhostFaces, default_geom);
// update 'face_geom' for ghost faces, assign ghost face indices
int nghosts = 0;
for (int i = 0; i < NGhostElements; i++)
{
Element &el = elements[leaf_elements[NElements + i]]; // ghost element
GeomInfo &gi = GI[el.Geom()];
for (int j = 0; j < gi.nf; j++)
{
const int *fv = gi.faces[j];
Face* face = faces.Find(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
MFEM_ASSERT(face, "face not found!");
if (face->index < 0)
{
face->index = NFaces + (nghosts++);
// store the face geometry
static const Geometry::Type types[5] =
{
Geometry::INVALID, Geometry::INVALID,
Geometry::SEGMENT, Geometry::TRIANGLE, Geometry::SQUARE
};
face_geom[face->index] = types[gi.nfv[j]];
}
}
}
// assign valid indices also to faces beyond the ghost layer
for (face_iterator face = faces.begin(); face != faces.end(); ++face)
{
if (face->index < 0) { face->index = NFaces + (nghosts++); }
}
MFEM_ASSERT(nghosts == NGhostFaces, "");
}
void ParNCMesh::ElementSharesFace(int elem, int local, int face)
{
// Analogous to ElementSharesEdge.
Element &el = elements[elem];
int f_index = faces[face].index;
int &owner = tmp_owner[f_index];
owner = std::min(owner, el.rank);
char &flag = tmp_shared_flag[f_index];
flag |= (el.rank == MyRank) ? 0x1 : 0x2;
entity_index_rank[2].Append(Connection(f_index, el.rank));
// derive globally consistent face ID from the global element sequence
int &el_loc = entity_elem_local[2][f_index];
if (el_loc < 0 || leaf_glob_order[el.index] < leaf_glob_order[(el_loc >> 4)])
{
el_loc = (el.index << 4) | local;
}
}
void ParNCMesh::BuildFaceList()
{
// This is an extension of NCMesh::BuildFaceList() which also determines
// face ownership and prepares face processor groups.
face_list.Clear();
if (Dim < 3) { return; }
int nfaces = NFaces + NGhostFaces;
tmp_owner.SetSize(nfaces);
tmp_owner = INT_MAX;
tmp_shared_flag.SetSize(nfaces);
tmp_shared_flag = 0;
entity_index_rank[2].SetSize(6*leaf_elements.Size() * 3/2);
entity_index_rank[2].SetSize(0);
entity_elem_local[2].SetSize(nfaces);
entity_elem_local[2] = -1;
NCMesh::BuildFaceList();
InitOwners(nfaces, entity_owner[2]);
MakeSharedList(face_list, shared_faces);
tmp_owner.DeleteAll();
tmp_shared_flag.DeleteAll();
// create simple conforming (cut-mesh) groups now
CreateGroups(NFaces, entity_index_rank[2], entity_conf_group[2]);
// NOTE: entity_index_rank[2] is not deleted until CalculatePMatrixGroups
CalcFaceOrientations();
}
void ParNCMesh::ElementSharesEdge(int elem, int local, int enode)
{
// Called by NCMesh::BuildEdgeList when an edge is visited in a leaf element.
// This allows us to determine edge ownership and whether it is shared
// without duplicating all the HashTable lookups in NCMesh::BuildEdgeList().
Element &el= elements[elem];
int e_index = nodes[enode].edge_index;
int &owner = tmp_owner[e_index];
owner = std::min(owner, el.rank);
char &flag = tmp_shared_flag[e_index];
flag |= (el.rank == MyRank) ? 0x1 : 0x2;
entity_index_rank[1].Append(Connection(e_index, el.rank));
// derive globally consistent edge ID from the global element sequence
int &el_loc = entity_elem_local[1][e_index];
if (el_loc < 0 || leaf_glob_order[el.index] < leaf_glob_order[(el_loc >> 4)])
{
el_loc = (el.index << 4) | local;
}
}
void ParNCMesh::BuildEdgeList()
{
// This is an extension of NCMesh::BuildEdgeList() which also determines
// edge ownership and prepares edge processor groups.
int nedges = NEdges + NGhostEdges;
tmp_owner.SetSize(nedges);
tmp_owner = INT_MAX;
tmp_shared_flag.SetSize(nedges);
tmp_shared_flag = 0;
entity_index_rank[1].SetSize(12*leaf_elements.Size() * 3/2);
entity_index_rank[1].SetSize(0);
entity_elem_local[1].SetSize(nedges);
entity_elem_local[1] = -1;
NCMesh::BuildEdgeList();
InitOwners(nedges, entity_owner[1]);
MakeSharedList(edge_list, shared_edges);
tmp_owner.DeleteAll();
tmp_shared_flag.DeleteAll();
// create simple conforming (cut-mesh) groups now
CreateGroups(NEdges, entity_index_rank[1], entity_conf_group[1]);
// NOTE: entity_index_rank[1] is not deleted until CalculatePMatrixGroups
}
void ParNCMesh::ElementSharesVertex(int elem, int local, int vnode)
{
// Analogous to ElementSharesEdge.
Element &el = elements[elem];
int v_index = nodes[vnode].vert_index;
int &owner = tmp_owner[v_index];
owner = std::min(owner, el.rank);
char &flag = tmp_shared_flag[v_index];
flag |= (el.rank == MyRank) ? 0x1 : 0x2;
entity_index_rank[0].Append(Connection(v_index, el.rank));
// derive globally consistent vertex ID from the global element sequence
int &el_loc = entity_elem_local[0][v_index];
if (el_loc < 0 || leaf_glob_order[el.index] < leaf_glob_order[(el_loc >> 4)])
{
el_loc = (el.index << 4) | local;
}
}
void ParNCMesh::BuildVertexList()
{
// This is an extension of NCMesh::BuildVertexList() which also determines
// vertex ownership and creates vertex processor groups.
int nvertices = NVertices + NGhostVertices;
tmp_owner.SetSize(nvertices);
tmp_owner = INT_MAX;
tmp_shared_flag.SetSize(nvertices);
tmp_shared_flag = 0;
entity_index_rank[0].SetSize(8*leaf_elements.Size());
entity_index_rank[0].SetSize(0);
entity_elem_local[0].SetSize(nvertices);
entity_elem_local[0] = -1;
NCMesh::BuildVertexList();
InitOwners(nvertices, entity_owner[0]);
MakeSharedList(vertex_list, shared_vertices);
tmp_owner.DeleteAll();
tmp_shared_flag.DeleteAll();
// create simple conforming (cut-mesh) groups now
CreateGroups(NVertices, entity_index_rank[0], entity_conf_group[0]);
// NOTE: entity_index_rank[0] is not deleted until CalculatePMatrixGroups
}
void ParNCMesh::InitOwners(int num, Array<GroupId> &entity_owner)
{
entity_owner.SetSize(num);
for (int i = 0; i < num; i++)
{
entity_owner[i] =
(tmp_owner[i] != INT_MAX) ? GetSingletonGroup(tmp_owner[i]) : 0;
}
}
void ParNCMesh::MakeSharedList(const NCList &list, NCList &shared)
{
MFEM_VERIFY(tmp_shared_flag.Size(), "wrong code path");
// combine flags of masters and slaves
for (unsigned i = 0; i < list.masters.size(); i++)
{
const Master &master = list.masters[i];
char &master_flag = tmp_shared_flag[master.index];
char master_old_flag = master_flag;
for (int j = master.slaves_begin; j < master.slaves_end; j++)
{
int si = list.slaves[j].index;
if (si >= 0)
{
char &slave_flag = tmp_shared_flag[si];
master_flag |= slave_flag;
slave_flag |= master_old_flag;
}
else // special case: prism edge-face constraint
{
if (entity_owner[1][-1-si] != MyRank)
{
master_flag |= 0x2;
}
}
}
}
shared.Clear();
for (unsigned i = 0; i < list.conforming.size(); i++)
{
if (tmp_shared_flag[list.conforming[i].index] == 0x3)
{
shared.conforming.push_back(list.conforming[i]);
}
}
for (unsigned i = 0; i < list.masters.size(); i++)
{
if (tmp_shared_flag[list.masters[i].index] == 0x3)
{
shared.masters.push_back(list.masters[i]);
}
}
for (unsigned i = 0; i < list.slaves.size(); i++)
{
int si = list.slaves[i].index;
if (si >= 0 && tmp_shared_flag[si] == 0x3)
{
shared.slaves.push_back(list.slaves[i]);
}
}
}
bool operator<(const ParNCMesh::CommGroup &lhs, const ParNCMesh::CommGroup &rhs)
{
if (lhs.size() == rhs.size())
{
for (unsigned i = 0; i < lhs.size(); i++)
{
if (lhs[i] < rhs[i]) { return true; }
}
return false;
}
return lhs.size() < rhs.size();
}
#ifdef MFEM_DEBUG
static bool group_sorted(const ParNCMesh::CommGroup &group)
{
for (unsigned i = 1; i < group.size(); i++)
{
if (group[i] <= group[i-1]) { return false; }
}
return true;
}
#endif
ParNCMesh::GroupId ParNCMesh::GetGroupId(const CommGroup &group)
{
if (group.size() == 1 && group[0] == MyRank)
{
return 0;
}
MFEM_ASSERT(group_sorted(group), "invalid group");
GroupId &id = group_id[group];
if (!id)
{
id = groups.size();
groups.push_back(group);
}
return id;
}
ParNCMesh::GroupId ParNCMesh::GetSingletonGroup(int rank)
{
MFEM_ASSERT(rank != INT_MAX, "invalid rank");
static std::vector<int> group;
group.resize(1);
group[0] = rank;
return GetGroupId(group);
}
bool ParNCMesh::GroupContains(GroupId id, int rank) const
{
// TODO: would std::lower_bound() pay off here? Groups are usually small.
const CommGroup &group = groups[id];
for (unsigned i = 0; i < group.size(); i++)
{
if (group[i] == rank) { return true; }
}
return false;
}
void ParNCMesh::CreateGroups(int nentities, Array<Connection> &index_rank,
Array<GroupId> &entity_group)
{
index_rank.Sort();
index_rank.Unique();
entity_group.SetSize(nentities);
entity_group = 0;
CommGroup group;
group.reserve(128);
int begin = 0, end = 0;
while (begin < index_rank.Size())
{
int index = index_rank[begin].from;
if (index >= nentities)
{
break; // probably creating entity_conf_group (no ghosts)
}
while (end < index_rank.Size() && index_rank[end].from == index)
{
end++;
}
group.resize(end - begin);
for (int i = begin; i < end; i++)
{
group[i - begin] = index_rank[i].to;
}
entity_group[index] = GetGroupId(group);
begin = end;
}
}
void ParNCMesh::AddConnections(int entity, int index, const Array<int> &ranks)
{
for (int i = 0; i < ranks.Size(); i++)
{
entity_index_rank[entity].Append(Connection(index, ranks[i]));
}
}
void ParNCMesh::CalculatePMatrixGroups()
{
// make sure all entity_index_rank[i] arrays are filled
GetSharedVertices();
GetSharedEdges();
GetSharedFaces();
int v[4], e[4], eo[4];
Array<int> ranks;
ranks.Reserve(256);
// connect slave edges to master edges and their vertices
for (unsigned i = 0; i < shared_edges.masters.size(); i++)
{
const Master &master_edge = shared_edges.masters[i];
ranks.SetSize(0);
for (int j = master_edge.slaves_begin; j < master_edge.slaves_end; j++)
{
int owner = entity_owner[1][edge_list.slaves[j].index];
ranks.Append(groups[owner][0]);
}
ranks.Sort();
ranks.Unique();
AddConnections(1, master_edge.index, ranks);
GetEdgeVertices(master_edge, v);
for (int j = 0; j < 2; j++)
{
AddConnections(0, v[j], ranks);
}
}
// connect slave faces to master faces and their edges and vertices
for (unsigned i = 0; i < shared_faces.masters.size(); i++)
{
const Master &master_face = shared_faces.masters[i];
ranks.SetSize(0);
for (int j = master_face.slaves_begin; j < master_face.slaves_end; j++)
{
int si = face_list.slaves[j].index;
int owner = (si >= 0) ? entity_owner[2][si] // standard face dependency
/* */ : entity_owner[1][-1 - si]; // prism edge-face dep
ranks.Append(groups[owner][0]);
}
ranks.Sort();
ranks.Unique();
AddConnections(2, master_face.index, ranks);
int nfv = GetFaceVerticesEdges(master_face, v, e, eo);
for (int j = 0; j < nfv; j++)
{
AddConnections(0, v[j], ranks);
AddConnections(1, e[j], ranks);
}
}
int nentities[3] =
{
NVertices + NGhostVertices,
NEdges + NGhostEdges,
NFaces + NGhostFaces
};
// compress the index-rank arrays into group representation
for (int i = 0; i < 3; i++)
{
CreateGroups(nentities[i], entity_index_rank[i], entity_pmat_group[i]);
entity_index_rank[i].DeleteAll();
}
}
int ParNCMesh::get_face_orientation(Face &face, Element &e1, Element &e2,
int local[2])
{
// Return face orientation in e2, assuming the face has orientation 0 in e1.
int ids[2][4];
Element* e[2] = { &e1, &e2 };
for (int i = 0; i < 2; i++)
{
// get local face number (remember that p1, p2, p3 are not in order, and
// p4 is not stored)
int lf = find_local_face(e[i]->Geom(),
find_node(*e[i], face.p1),
find_node(*e[i], face.p2),
find_node(*e[i], face.p3));
// optional output
if (local) { local[i] = lf; }
// get node IDs for the face as seen from e[i]
const int* fv = GI[e[i]->Geom()].faces[lf];
for (int j = 0; j < 4; j++)
{
ids[i][j] = e[i]->node[fv[j]];
}
}
return (ids[0][3] >= 0) ? Mesh::GetQuadOrientation(ids[0], ids[1])
/* */ : Mesh::GetTriOrientation(ids[0], ids[1]);
}
void ParNCMesh::CalcFaceOrientations()
{
if (Dim < 3) { return; }
// Calculate orientation of shared conforming faces.
// NOTE: face orientation is calculated relative to its lower rank element.
// Thanks to the ghost layer this can be done locally, without communication.
face_orient.SetSize(NFaces);
face_orient = 0;
for (face_iterator face = faces.begin(); face != faces.end(); ++face)
{
if (face->elem[0] >= 0 && face->elem[1] >= 0 && face->index < NFaces)
{
Element *e1 = &elements[face->elem[0]];
Element *e2 = &elements[face->elem[1]];
if (e1->rank == e2->rank) { continue; }
if (e1->rank > e2->rank) { std::swap(e1, e2); }
face_orient[face->index] = get_face_orientation(*face, *e1, *e2);
}
}
}
void ParNCMesh::GetBoundaryClosure(const Array<int> &bdr_attr_is_ess,
Array<int> &bdr_vertices,
Array<int> &bdr_edges)
{
NCMesh::GetBoundaryClosure(bdr_attr_is_ess, bdr_vertices, bdr_edges);
int i, j;
// filter out ghost vertices
for (i = j = 0; i < bdr_vertices.Size(); i++)
{
if (bdr_vertices[i] < NVertices) { bdr_vertices[j++] = bdr_vertices[i]; }
}
bdr_vertices.SetSize(j);
// filter out ghost edges
for (i = j = 0; i < bdr_edges.Size(); i++)
{
if (bdr_edges[i] < NEdges) { bdr_edges[j++] = bdr_edges[i]; }
}
bdr_edges.SetSize(j);
}
//// Neighbors /////////////////////////////////////////////////////////////////
void ParNCMesh::UpdateLayers()
{
if (element_type.Size()) { return; }
int nleaves = leaf_elements.Size();
element_type.SetSize(nleaves);
for (int i = 0; i < nleaves; i++)
{
element_type[i] = (elements[leaf_elements[i]].rank == MyRank) ? 1 : 0;
}
// determine the ghost layer
Array<char> ghost_set;
FindSetNeighbors(element_type, NULL, &ghost_set);
// find the neighbors of the ghost layer
Array<char> boundary_set;
FindSetNeighbors(ghost_set, NULL, &boundary_set);
ghost_layer.SetSize(0);
boundary_layer.SetSize(0);
for (int i = 0; i < nleaves; i++)
{
char &etype = element_type[i];
if (ghost_set[i])
{
etype = 2;
ghost_layer.Append(leaf_elements[i]);
}
else if (boundary_set[i] && etype)
{
etype = 3;
boundary_layer.Append(leaf_elements[i]);
}
}
}
bool ParNCMesh::CheckElementType(int elem, int type)
{
Element &el = elements[elem];
if (!el.ref_type)
{
return (element_type[el.index] == type);
}
else
{
for (int i = 0; i < 8 && el.child[i] >= 0; i++)
{
if (!CheckElementType(el.child[i], type)) { return false; }
}
return true;
}
}
void ParNCMesh::ElementNeighborProcessors(int elem, Array<int> &ranks)
{
ranks.SetSize(0); // preserve capacity
// big shortcut: there are no neighbors if element_type == 1
if (CheckElementType(elem, 1)) { return; }
// ok, we do need to look for neighbors;
// at least we can only search in the ghost layer
tmp_neighbors.SetSize(0);
FindNeighbors(elem, tmp_neighbors, &ghost_layer);
// return a list of processors
for (int i = 0; i < tmp_neighbors.Size(); i++)
{
ranks.Append(elements[tmp_neighbors[i]].rank);
}
ranks.Sort();
ranks.Unique();
}
template<class T>
static void set_to_array(const std::set<T> &set, Array<T> &array)
{
array.Reserve(set.size());
array.SetSize(0);
for (std::set<int>::iterator it = set.begin(); it != set.end(); ++it)
{
array.Append(*it);
}
}
void ParNCMesh::NeighborProcessors(Array<int> &neighbors)
{
UpdateLayers();
// TODO: look at groups instead?
std::set<int> ranks;
for (int i = 0; i < ghost_layer.Size(); i++)
{
ranks.insert(elements[ghost_layer[i]].rank);
}
set_to_array(ranks, neighbors);
}
//// ParMesh compatibility /////////////////////////////////////////////////////
void ParNCMesh::MakeSharedTable(int ngroups, int ent, Array<int> &shared_local,
Table &group_shared, Array<char> *entity_geom,
char geom)
{
const Array<GroupId> &conf_group = entity_conf_group[ent];
group_shared.MakeI(ngroups-1);
// count shared entities
int num_shared = 0;
for (int i = 0; i < conf_group.Size(); i++)
{
if (conf_group[i])
{
if (entity_geom && (*entity_geom)[i] != geom) { continue; }
num_shared++;
group_shared.AddAColumnInRow(conf_group[i]-1);
}
}
shared_local.SetSize(num_shared);
group_shared.MakeJ();
// fill shared_local and group_shared
for (int i = 0, j = 0; i < conf_group.Size(); i++)
{
if (conf_group[i])
{
if (entity_geom && (*entity_geom)[i] != geom) { continue; }
shared_local[j] = i;
group_shared.AddConnection(conf_group[i]-1, j);
j++;
}
}
group_shared.ShiftUpI();
// sort the groups consistently across processors
for (int i = 0; i < group_shared.Size(); i++)
{
int size = group_shared.RowSize(i);
int *row = group_shared.GetRow(i);
Array<int> ref_row(row, size);
ref_row.Sort([&](const int a, const int b)
{
int el_loc_a = entity_elem_local[ent][shared_local[a]];
int el_loc_b = entity_elem_local[ent][shared_local[b]];
int lgo_a = leaf_glob_order[el_loc_a >> 4];
int lgo_b = leaf_glob_order[el_loc_b >> 4];
if (lgo_a != lgo_b) { return lgo_a < lgo_b; }
return (el_loc_a & 0xf) < (el_loc_b & 0xf);
});
}
}
void ParNCMesh::GetConformingSharedStructures(ParMesh &pmesh)
{
// make sure we have entity_conf_group[x] and the ordering arrays
if (leaf_elements.Size())
{
for (int ent = 0; ent < Dim; ent++)
{
GetSharedList(ent);
MFEM_VERIFY(entity_conf_group[ent].Size(), "internal error");
MFEM_VERIFY(entity_elem_local[ent].Size(), "internal error");
}
MFEM_VERIFY(leaf_glob_order.Size(), "internal error");
}
// create ParMesh groups, and the map (ncmesh_group -> pmesh_group)
Array<int> group_map(groups.size());
{
group_map = 0;
IntegerSet iset;
ListOfIntegerSets int_groups;
for (unsigned i = 0; i < groups.size(); i++)
{
if (groups[i].size() > 1 || !i) // skip singleton groups
{
iset.Recreate(groups[i].size(), groups[i].data());
group_map[i] = int_groups.Insert(iset);
}
}
pmesh.gtopo.Create(int_groups, 822);
}
// renumber groups in entity_conf_group[] (due to missing singletons)
for (int ent = 0; ent < 3; ent++)
{
for (int i = 0; i < entity_conf_group[ent].Size(); i++)
{
GroupId &ecg = entity_conf_group[ent][i];
ecg = group_map[ecg];
}
}
// create shared to local index mappings and group tables
int ng = pmesh.gtopo.NGroups();
MakeSharedTable(ng, 0, pmesh.svert_lvert, pmesh.group_svert);
MakeSharedTable(ng, 1, pmesh.sedge_ledge, pmesh.group_sedge);
Array<int> slt, slq;
MakeSharedTable(ng, 2, slt, pmesh.group_stria, &face_geom, Geometry::TRIANGLE);
MakeSharedTable(ng, 2, slq, pmesh.group_squad, &face_geom, Geometry::SQUARE);
pmesh.sface_lface = slt;
pmesh.sface_lface.Append(slq);
// create shared_edges
for (int i = 0; i < pmesh.shared_edges.Size(); i++)
{
delete pmesh.shared_edges[i];
}
pmesh.shared_edges.SetSize(pmesh.sedge_ledge.Size());
for (int i = 0; i < pmesh.shared_edges.Size(); i++)
{
int el_loc = entity_elem_local[1][pmesh.sedge_ledge[i]];
MeshId edge_id(-1, leaf_elements[(el_loc >> 4)], (el_loc & 0xf));
int v[2];
GetEdgeVertices(edge_id, v, false);
pmesh.shared_edges[i] = new Segment(v, 1);
}
// create shared_trias
pmesh.shared_trias.SetSize(slt.Size());
for (int i = 0; i < slt.Size(); i++)
{
int el_loc = entity_elem_local[2][slt[i]];
MeshId face_id(-1, leaf_elements[(el_loc >> 4)], (el_loc & 0xf));
int v[4], e[4], eo[4];
GetFaceVerticesEdges(face_id, v, e, eo);
pmesh.shared_trias[i].Set(v);
}
// create shared_quads
pmesh.shared_quads.SetSize(slq.Size());
for (int i = 0; i < slq.Size(); i++)
{
int el_loc = entity_elem_local[2][slq[i]];
MeshId face_id(-1, leaf_elements[(el_loc >> 4)], (el_loc & 0xf));
int e[4], eo[4];
GetFaceVerticesEdges(face_id, pmesh.shared_quads[i].v, e, eo);
}
// free the arrays, they're not needed anymore (until next mesh update)
for (int ent = 0; ent < Dim; ent++)
{
entity_conf_group[ent].DeleteAll();
entity_elem_local[ent].DeleteAll();
}
leaf_glob_order.DeleteAll();
}
void ParNCMesh::GetFaceNeighbors(ParMesh &pmesh)
{
ClearAuxPM();
const NCList &shared = (Dim == 3) ? GetSharedFaces() : GetSharedEdges();
const NCList &full_list = (Dim == 3) ? GetFaceList() : GetEdgeList();
Array<Element*> fnbr;
Array<Connection> send_elems;
int bound = shared.conforming.size() + shared.slaves.size();
fnbr.Reserve(bound);
send_elems.Reserve(bound);
// go over all shared faces and collect face neighbor elements
for (unsigned i = 0; i < shared.conforming.size(); i++)
{
const MeshId &cf = shared.conforming[i];
Face* face = GetFace(elements[cf.element], cf.local);
MFEM_ASSERT(face != NULL, "");
MFEM_ASSERT(face->elem[0] >= 0 && face->elem[1] >= 0, "");
Element* e[2] = { &elements[face->elem[0]], &elements[face->elem[1]] };
if (e[0]->rank == MyRank) { std::swap(e[0], e[1]); }
MFEM_ASSERT(e[0]->rank != MyRank && e[1]->rank == MyRank, "");
fnbr.Append(e[0]);
send_elems.Append(Connection(e[0]->rank, e[1]->index));
}
for (unsigned i = 0; i < shared.masters.size(); i++)
{
const Master &mf = shared.masters[i];
for (int j = mf.slaves_begin; j < mf.slaves_end; j++)
{
const Slave &sf = full_list.slaves[j];
if (sf.index < 0) { continue; }
MFEM_ASSERT(mf.element >= 0 && sf.element >= 0, "");
Element* e[2] = { &elements[mf.element], &elements[sf.element] };
bool loc0 = (e[0]->rank == MyRank);
bool loc1 = (e[1]->rank == MyRank);
if (loc0 == loc1) { continue; }
if (loc0) { std::swap(e[0], e[1]); }
fnbr.Append(e[0]);
send_elems.Append(Connection(e[0]->rank, e[1]->index));
}
}
MFEM_ASSERT(fnbr.Size() <= bound, "oops, bad upper bound");
// remove duplicate face neighbor elements and sort them by rank & index
// (note that the send table is sorted the same way and the order is also the
// same on different processors, this is important for ExchangeFaceNbrData)
fnbr.Sort();
fnbr.Unique();
fnbr.Sort([](const Element* a, const Element* b)
{
return (a->rank != b->rank) ? a->rank < b->rank
/* */ : a->index < b->index;
});
// put the ranks into 'face_nbr_group'
for (int i = 0; i < fnbr.Size(); i++)
{
if (!i || fnbr[i]->rank != pmesh.face_nbr_group.Last())
{
pmesh.face_nbr_group.Append(fnbr[i]->rank);
}
}
int nranks = pmesh.face_nbr_group.Size();
// create a new mfem::Element for each face neighbor element
pmesh.face_nbr_elements.SetSize(0);
pmesh.face_nbr_elements.Reserve(fnbr.Size());
pmesh.face_nbr_elements_offset.SetSize(0);
pmesh.face_nbr_elements_offset.Reserve(pmesh.face_nbr_group.Size()+1);
Array<int> fnbr_index(NGhostElements);
fnbr_index = -1;
std::map<int, int> vert_map;
for (int i = 0; i < fnbr.Size(); i++)
{
Element* elem = fnbr[i];
mfem::Element* fne = NewMeshElement(elem->geom);
fne->SetAttribute(elem->attribute);
pmesh.face_nbr_elements.Append(fne);
GeomInfo& gi = GI[(int) elem->geom];
for (int k = 0; k < gi.nv; k++)
{
int &v = vert_map[elem->node[k]];
if (!v) { v = vert_map.size(); }
fne->GetVertices()[k] = v-1;
}
if (!i || elem->rank != fnbr[i-1]->rank)
{
pmesh.face_nbr_elements_offset.Append(i);
}
MFEM_ASSERT(elem->index >= NElements, "not a ghost element");
fnbr_index[elem->index - NElements] = i;
}
pmesh.face_nbr_elements_offset.Append(fnbr.Size());
// create vertices in 'face_nbr_vertices'
{
pmesh.face_nbr_vertices.SetSize(vert_map.size());
tmp_vertex = new TmpVertex[nodes.NumIds()]; // TODO: something cheaper?
std::map<int, int>::iterator it;
for (it = vert_map.begin(); it != vert_map.end(); ++it)
{
pmesh.face_nbr_vertices[it->second-1].SetCoords(
spaceDim, CalcVertexPos(it->first));
}
delete [] tmp_vertex;
}
// make the 'send_face_nbr_elements' table
send_elems.Sort();
send_elems.Unique();
for (int i = 0, last_rank = -1; i < send_elems.Size(); i++)
{
Connection &c = send_elems[i];
if (c.from != last_rank)
{
// renumber rank to position in 'face_nbr_group'
last_rank = c.from;
c.from = pmesh.face_nbr_group.Find(c.from);
}
else
{
c.from = send_elems[i-1].from; // avoid search
}
}
pmesh.send_face_nbr_elements.MakeFromList(nranks, send_elems);
// go over the shared faces again and modify their Mesh::FaceInfo
for (unsigned i = 0; i < shared.conforming.size(); i++)
{
const MeshId &cf = shared.conforming[i];
Face* face = GetFace(elements[cf.element], cf.local);
Element* e[2] = { &elements[face->elem[0]], &elements[face->elem[1]] };
if (e[0]->rank == MyRank) { std::swap(e[0], e[1]); }
Mesh::FaceInfo &fi = pmesh.faces_info[cf.index];
fi.Elem2No = -1 - fnbr_index[e[0]->index - NElements];
if (Dim == 3)
{
int local[2];
int o = get_face_orientation(*face, *e[1], *e[0], local);
fi.Elem2Inf = 64*local[1] + o;
}
else
{
fi.Elem2Inf = 64*find_element_edge(*e[0], face->p1, face->p3) + 1;
}
}
if (shared.slaves.size())
{
int nfaces = NFaces, nghosts = NGhostFaces;
if (Dim <= 2) { nfaces = NEdges, nghosts = NGhostEdges; }
// enlarge Mesh::faces_info for ghost slaves
MFEM_ASSERT(pmesh.faces_info.Size() == nfaces, "");
MFEM_ASSERT(pmesh.GetNumFaces() == nfaces, "");
pmesh.faces_info.SetSize(nfaces + nghosts);
for (int i = nfaces; i < pmesh.faces_info.Size(); i++)
{
Mesh::FaceInfo &fi = pmesh.faces_info[i];
fi.Elem1No = fi.Elem2No = -1;
fi.Elem1Inf = fi.Elem2Inf = -1;
fi.NCFace = -1;
}
// Note that some of the indices i >= nfaces in pmesh.faces_info will
// remain untouched below and they will have Elem1No == -1, in particular.
// fill in FaceInfo for shared slave faces
for (unsigned i = 0; i < shared.masters.size(); i++)
{
const Master &mf = shared.masters[i];
for (int j = mf.slaves_begin; j < mf.slaves_end; j++)
{
const Slave &sf = full_list.slaves[j];
if (sf.index < 0) { continue; }
MFEM_ASSERT(sf.element >= 0 && mf.element >= 0, "");
Element &sfe = elements[sf.element];
Element &mfe = elements[mf.element];
bool sloc = (sfe.rank == MyRank);
bool mloc = (mfe.rank == MyRank);
if (sloc == mloc) { continue; }
Mesh::FaceInfo &fi = pmesh.faces_info[sf.index];
fi.Elem1No = sfe.index;
fi.Elem2No = mfe.index;
fi.Elem1Inf = 64 * sf.local;
fi.Elem2Inf = 64 * mf.local;
if (!sloc)
{
// 'fi' is the info for a ghost slave face with index:
// sf.index >= nfaces
std::swap(fi.Elem1No, fi.Elem2No);
std::swap(fi.Elem1Inf, fi.Elem2Inf);
// After the above swap, Elem1No refers to the local, master-side
// element. In other words, side 1 IS NOT the side that generated
// the face.
}
else
{
// 'fi' is the info for a local slave face with index:
// sf.index < nfaces
// Here, Elem1No refers to the local, slave-side element.
// In other words, side 1 IS the side that generated the face.
}
MFEM_ASSERT(fi.Elem2No >= NElements, "");
fi.Elem2No = -1 - fnbr_index[fi.Elem2No - NElements];
const DenseMatrix* pm = &sf.point_matrix;
if (!sloc && Dim == 3)
{
// ghost slave in 3D needs flipping orientation
DenseMatrix* pm2 = new DenseMatrix(*pm);
std::swap((*pm2)(0,1), (*pm2)(0,3));
std::swap((*pm2)(1,1), (*pm2)(1,3));
aux_pm_store.Append(pm2);
fi.Elem2Inf ^= 1;
pm = pm2;
// The problem is that sf.point_matrix is designed for P matrix
// construction and always has orientation relative to the slave
// face. In ParMesh::GetSharedFaceTransformations the result
// would therefore be the same on both processors, which is not
// how that function works for conforming faces. The orientation
// of Loc1, Loc2 and Face needs to always be relative to Element
// 1, which is the element containing the slave face on one
// processor, but on the other it is the element containing the
// master face. In the latter case we need to flip the pm.
}
MFEM_ASSERT(fi.NCFace < 0, "");
fi.NCFace = pmesh.nc_faces_info.Size();
pmesh.nc_faces_info.Append(Mesh::NCFaceInfo(true, sf.master, pm));
}
}
}
// NOTE: this function skips ParMesh::send_face_nbr_vertices and
// ParMesh::face_nbr_vertices_offset, these are not used outside of ParMesh
}
void ParNCMesh::ClearAuxPM()
{
for (int i = 0; i < aux_pm_store.Size(); i++)
{
delete aux_pm_store[i];
}
aux_pm_store.DeleteAll();
}
//// Prune, Refine, Derefine ///////////////////////////////////////////////////
bool ParNCMesh::PruneTree(int elem)
{
Element &el = elements[elem];
if (el.ref_type)
{
bool remove[8];
bool removeAll = true;
// determine which subtrees can be removed (and whether it's all of them)
for (int i = 0; i < 8; i++)
{
remove[i] = false;
if (el.child[i] >= 0)
{
remove[i] = PruneTree(el.child[i]);
if (!remove[i]) { removeAll = false; }
}
}
// all children can be removed, let the (maybe indirect) parent do it
if (removeAll) { return true; }
// not all children can be removed, but remove those that can be
for (int i = 0; i < 8; i++)
{
if (remove[i]) { DerefineElement(el.child[i]); }
}
return false; // need to keep this element and up
}
else
{
// return true if this leaf can be removed
return el.rank < 0;
}
}
void ParNCMesh::Prune()
{
if (!Iso && Dim == 3)
{
if (MyRank == 0)
{
MFEM_WARNING("Can't prune 3D aniso meshes yet.");
}
return;
}
UpdateLayers();
for (int i = 0; i < leaf_elements.Size(); i++)
{
// rank of elements beyond the ghost layer is unknown / not updated
if (element_type[i] == 0)
{
elements[leaf_elements[i]].rank = -1;
// NOTE: rank == -1 will make the element disappear from leaf_elements
// on next Update, see NCMesh::CollectLeafElements
}
}
// derefine subtrees whose leaves are all unneeded
for (int i = 0; i < root_state.Size(); i++)
{
if (PruneTree(i)) { DerefineElement(i); }
}
Update();
}
void ParNCMesh::Refine(const Array<Refinement> &refinements)
{
if (NRanks == 1)
{
NCMesh::Refine(refinements);
return;
}
for (int i = 0; i < refinements.Size(); i++)
{
const Refinement &ref = refinements[i];
MFEM_VERIFY(ref.ref_type == 7 || Dim < 3,
"anisotropic parallel refinement not supported yet in 3D.");
}
MFEM_VERIFY(Iso || Dim < 3,
"parallel refinement of 3D aniso meshes not supported yet.");
NeighborRefinementMessage::Map send_ref;
// create refinement messages to all neighbors (NOTE: some may be empty)
Array<int> neighbors;
NeighborProcessors(neighbors);
for (int i = 0; i < neighbors.Size(); i++)
{
send_ref[neighbors[i]].SetNCMesh(this);
}
// populate messages: all refinements that occur next to the processor
// boundary need to be sent to the adjoining neighbors so they can keep
// their ghost layer up to date
Array<int> ranks;
ranks.Reserve(64);
for (int i = 0; i < refinements.Size(); i++)
{
const Refinement &ref = refinements[i];
MFEM_ASSERT(ref.index < NElements, "");
int elem = leaf_elements[ref.index];
ElementNeighborProcessors(elem, ranks);
for (int j = 0; j < ranks.Size(); j++)
{
send_ref[ranks[j]].AddRefinement(elem, ref.ref_type);
}
}
// send the messages (overlap with local refinements)
NeighborRefinementMessage::IsendAll(send_ref, MyComm);
// do local refinements
for (int i = 0; i < refinements.Size(); i++)
{
const Refinement &ref = refinements[i];
NCMesh::RefineElement(leaf_elements[ref.index], ref.ref_type);
}
// receive (ghost layer) refinements from all neighbors
for (int j = 0; j < neighbors.Size(); j++)
{
int rank, size;
NeighborRefinementMessage::Probe(rank, size, MyComm);
NeighborRefinementMessage msg;
msg.SetNCMesh(this);
msg.Recv(rank, size, MyComm);
// do the ghost refinements
for (int i = 0; i < msg.Size(); i++)
{
NCMesh::RefineElement(msg.elements[i], msg.values[i]);
}
}
Update();
// make sure we can delete the send buffers
NeighborRefinementMessage::WaitAllSent(send_ref);
}
void ParNCMesh::LimitNCLevel(int max_nc_level)
{
MFEM_VERIFY(max_nc_level >= 1, "'max_nc_level' must be 1 or greater.");
while (1)
{
Array<Refinement> refinements;
GetLimitRefinements(refinements, max_nc_level);
long size = refinements.Size(), glob_size;
MPI_Allreduce(&size, &glob_size, 1, MPI_LONG, MPI_SUM, MyComm);
if (!glob_size) { break; }
Refine(refinements);
}
}
void ParNCMesh::Derefine(const Array<int> &derefs)
{
MFEM_VERIFY(Dim < 3 || Iso,
"derefinement of 3D anisotropic meshes not implemented yet.");
InitDerefTransforms();
// store fine element ranks
old_index_or_rank.SetSize(leaf_elements.Size());
for (int i = 0; i < leaf_elements.Size(); i++)
{
old_index_or_rank[i] = elements[leaf_elements[i]].rank;
}
// back up the leaf_elements array
Array<int> old_elements;
leaf_elements.Copy(old_elements);
// *** STEP 1: redistribute elements to avoid complex derefinements ***
Array<int> new_ranks(leaf_elements.Size());
for (int i = 0; i < leaf_elements.Size(); i++)
{
new_ranks[i] = elements[leaf_elements[i]].rank;
}
// make the lowest rank get all the fine elements for each derefinement
for (int i = 0; i < derefs.Size(); i++)
{
int row = derefs[i];
MFEM_VERIFY(row >= 0 && row < derefinements.Size(),
"invalid derefinement number.");
const int* fine = derefinements.GetRow(row);
int size = derefinements.RowSize(row);
int coarse_rank = INT_MAX;
for (int j = 0; j < size; j++)
{
int fine_rank = elements[leaf_elements[fine[j]]].rank;
coarse_rank = std::min(coarse_rank, fine_rank);
}
for (int j = 0; j < size; j++)
{
new_ranks[fine[j]] = coarse_rank;
}
}
int target_elements = 0;
for (int i = 0; i < new_ranks.Size(); i++)
{
if (new_ranks[i] == MyRank) { target_elements++; }
}
// redistribute elements slightly to get rid of complex derefinements
// straddling processor boundaries *and* update the ghost layer
RedistributeElements(new_ranks, target_elements, false);
// *** STEP 2: derefine now, communication similar to Refine() ***
NeighborDerefinementMessage::Map send_deref;
// create derefinement messages to all neighbors (NOTE: some may be empty)
Array<int> neighbors;
NeighborProcessors(neighbors);
for (int i = 0; i < neighbors.Size(); i++)
{
send_deref[neighbors[i]].SetNCMesh(this);
}
// derefinements that occur next to the processor boundary need to be sent
// to the adjoining neighbors to keep their ghost layers in sync
Array<int> ranks;
ranks.Reserve(64);
for (int i = 0; i < derefs.Size(); i++)
{
const int* fine = derefinements.GetRow(derefs[i]);
int parent = elements[old_elements[fine[0]]].parent;
// send derefinement to neighbors
ElementNeighborProcessors(parent, ranks);
for (int j = 0; j < ranks.Size(); j++)
{
send_deref[ranks[j]].AddDerefinement(parent, new_ranks[fine[0]]);
}
}
NeighborDerefinementMessage::IsendAll(send_deref, MyComm);
// restore old (pre-redistribution) element indices, for SetDerefMatrixCodes
for (int i = 0; i < leaf_elements.Size(); i++)
{
elements[leaf_elements[i]].index = -1;
}
for (int i = 0; i < old_elements.Size(); i++)
{
elements[old_elements[i]].index = i;
}
// do local derefinements
Array<int> coarse;
old_elements.Copy(coarse);
for (int i = 0; i < derefs.Size(); i++)
{
const int* fine = derefinements.GetRow(derefs[i]);
int parent = elements[old_elements[fine[0]]].parent;
// record the relation of the fine elements to their parent
SetDerefMatrixCodes(parent, coarse);
NCMesh::DerefineElement(parent);
}
// receive ghost layer derefinements from all neighbors
for (int j = 0; j < neighbors.Size(); j++)
{
int rank, size;
NeighborDerefinementMessage::Probe(rank, size, MyComm);
NeighborDerefinementMessage msg;
msg.SetNCMesh(this);
msg.Recv(rank, size, MyComm);
// do the ghost derefinements
for (int i = 0; i < msg.Size(); i++)
{
int elem = msg.elements[i];
if (elements[elem].ref_type)
{
SetDerefMatrixCodes(elem, coarse);
NCMesh::DerefineElement(elem);
}
elements[elem].rank = msg.values[i];
}
}
// update leaf_elements, Element::index etc.
Update();
UpdateLayers();
// link old fine elements to the new coarse elements
for (int i = 0; i < coarse.Size(); i++)
{
int index = elements[coarse[i]].index;
if (element_type[index] == 0)
{
// this coarse element will get pruned, encode who owns it now
index = -1 - elements[coarse[i]].rank;
}
transforms.embeddings[i].parent = index;
}
leaf_elements.Copy(old_elements);
Prune();
// renumber coarse element indices after pruning
for (int i = 0; i < coarse.Size(); i++)
{
int &index = transforms.embeddings[i].parent;
if (index >= 0)
{
index = elements[old_elements[index]].index;
}
}
// make sure we can delete all send buffers
NeighborDerefinementMessage::WaitAllSent(send_deref);
}
template<typename Type>
void ParNCMesh::SynchronizeDerefinementData(Array<Type> &elem_data,
const Table &deref_table)
{
const MPI_Datatype datatype = MPITypeMap<Type>::mpi_type;
Array<MPI_Request*> requests;
Array<int> neigh;
requests.Reserve(64);
neigh.Reserve(8);
// make room for ghost values (indices beyond NumElements)
elem_data.SetSize(leaf_elements.Size(), 0);
for (int i = 0; i < deref_table.Size(); i++)
{
const int* fine = deref_table.GetRow(i);
int size = deref_table.RowSize(i);
MFEM_ASSERT(size <= 8, "");
int ranks[8], min_rank = INT_MAX, max_rank = INT_MIN;
for (int j = 0; j < size; j++)
{
ranks[j] = elements[leaf_elements[fine[j]]].rank;
min_rank = std::min(min_rank, ranks[j]);
max_rank = std::max(max_rank, ranks[j]);
}
// exchange values for derefinements that straddle processor boundaries
if (min_rank != max_rank)
{
neigh.SetSize(0);
for (int j = 0; j < size; j++)
{
if (ranks[j] != MyRank) { neigh.Append(ranks[j]); }
}
neigh.Sort();
neigh.Unique();
for (int j = 0; j < size; j++/*pass*/)
{
Type *data = &elem_data[fine[j]];
int rnk = ranks[j], len = 1; /*j;
do { j++; } while (j < size && ranks[j] == rnk);
len = j - len;*/
if (rnk == MyRank)
{
for (int k = 0; k < neigh.Size(); k++)
{
MPI_Request* req = new MPI_Request;
MPI_Isend(data, len, datatype, neigh[k], 292, MyComm, req);
requests.Append(req);
}
}
else
{
MPI_Request* req = new MPI_Request;
MPI_Irecv(data, len, datatype, rnk, 292, MyComm, req);
requests.Append(req);
}
}
}
}
for (int i = 0; i < requests.Size(); i++)
{
MPI_Wait(requests[i], MPI_STATUS_IGNORE);
delete requests[i];
}
}
// instantiate SynchronizeDerefinementData for int and double
template void
ParNCMesh::SynchronizeDerefinementData<int>(Array<int> &, const Table &);
template void
ParNCMesh::SynchronizeDerefinementData<double>(Array<double> &, const Table &);
void ParNCMesh::CheckDerefinementNCLevel(const Table &deref_table,
Array<int> &level_ok, int max_nc_level)
{
Array<int> leaf_ok(leaf_elements.Size());
leaf_ok = 1;
// check elements that we own
for (int i = 0; i < deref_table.Size(); i++)
{
const int *fine = deref_table.GetRow(i),
size = deref_table.RowSize(i);
int parent = elements[leaf_elements[fine[0]]].parent;
Element &pa = elements[parent];
for (int j = 0; j < size; j++)
{
int child = leaf_elements[fine[j]];
if (elements[child].rank == MyRank)
{
int splits[3];
CountSplits(child, splits);
for (int k = 0; k < Dim; k++)
{
if ((pa.ref_type & (1 << k)) &&
splits[k] >= max_nc_level)
{
leaf_ok[fine[j]] = 0; break;
}
}
}
}
}
SynchronizeDerefinementData(leaf_ok, deref_table);
level_ok.SetSize(deref_table.Size());
level_ok = 1;
for (int i = 0; i < deref_table.Size(); i++)
{
const int* fine = deref_table.GetRow(i),
size = deref_table.RowSize(i);
for (int j = 0; j < size; j++)
{
if (!leaf_ok[fine[j]])
{
level_ok[i] = 0; break;
}
}
}
}
//// Rebalance /////////////////////////////////////////////////////////////////
void ParNCMesh::Rebalance(const Array<int> *custom_partition)
{
send_rebalance_dofs.clear();
recv_rebalance_dofs.clear();
Array<int> old_elements;
leaf_elements.GetSubArray(0, NElements, old_elements);
if (!custom_partition) // SFC based partitioning
{
Array<int> new_ranks(leaf_elements.Size());
new_ranks = -1;
// figure out new assignments for Element::rank
long local_elems = NElements, total_elems = 0;
MPI_Allreduce(&local_elems, &total_elems, 1, MPI_LONG, MPI_SUM, MyComm);
long first_elem_global = 0;
MPI_Scan(&local_elems, &first_elem_global, 1, MPI_LONG, MPI_SUM, MyComm);
first_elem_global -= local_elems;
for (int i = 0, j = 0; i < leaf_elements.Size(); i++)
{
if (elements[leaf_elements[i]].rank == MyRank)
{
new_ranks[i] = Partition(first_elem_global + (j++), total_elems);
}
}
int target_elements = PartitionFirstIndex(MyRank+1, total_elems)
- PartitionFirstIndex(MyRank, total_elems);
// assign the new ranks and send elements (plus ghosts) to new owners
RedistributeElements(new_ranks, target_elements, true);
}
else // whatever partitioning the user has passed
{
MFEM_VERIFY(custom_partition->Size() == NElements,
"Size of the partition array must match the number "
"of local mesh elements (ParMesh::GetNE()).");
Array<int> new_ranks;
custom_partition->Copy(new_ranks);
new_ranks.SetSize(leaf_elements.Size(), -1); // make room for ghosts
RedistributeElements(new_ranks, -1, true);
}
// set up the old index array
old_index_or_rank.SetSize(NElements);
old_index_or_rank = -1;
for (int i = 0; i < old_elements.Size(); i++)
{
Element &el = elements[old_elements[i]];
if (el.rank == MyRank) { old_index_or_rank[el.index] = i; }
}
// get rid of elements beyond the new ghost layer
Prune();
}
void ParNCMesh::RedistributeElements(Array<int> &new_ranks, int target_elements,
bool record_comm)
{
bool sfc = (target_elements >= 0);
UpdateLayers();
// *** STEP 1: communicate new rank assignments for the ghost layer ***
NeighborElementRankMessage::Map send_ghost_ranks, recv_ghost_ranks;
ghost_layer.Sort([&](const int a, const int b)
{
return elements[a].rank < elements[b].rank;
});
{
Array<int> rank_neighbors;
// loop over neighbor ranks and their elements
int begin = 0, end = 0;
while (end < ghost_layer.Size())
{
// find range of elements belonging to one rank
int rank = elements[ghost_layer[begin]].rank;
while (end < ghost_layer.Size() &&
elements[ghost_layer[end]].rank == rank) { end++; }
Array<int> rank_elems;
rank_elems.MakeRef(&ghost_layer[begin], end - begin);
// find elements within boundary_layer that are neighbors to 'rank'
rank_neighbors.SetSize(0);
NeighborExpand(rank_elems, rank_neighbors, &boundary_layer);
// send a message with new rank assignments within 'rank_neighbors'
NeighborElementRankMessage& msg = send_ghost_ranks[rank];
msg.SetNCMesh(this);
msg.Reserve(rank_neighbors.Size());
for (int i = 0; i < rank_neighbors.Size(); i++)
{
int elem = rank_neighbors[i];
msg.AddElementRank(elem, new_ranks[elements[elem].index]);
}
msg.Isend(rank, MyComm);
// prepare to receive a message from the neighbor too, these will
// be new the new rank assignments for our ghost layer
recv_ghost_ranks[rank].SetNCMesh(this);
begin = end;
}
}
NeighborElementRankMessage::RecvAll(recv_ghost_ranks, MyComm);
// read new ranks for the ghost layer from messages received
NeighborElementRankMessage::Map::iterator it;
for (it = recv_ghost_ranks.begin(); it != recv_ghost_ranks.end(); ++it)
{
NeighborElementRankMessage &msg = it->second;
for (int i = 0; i < msg.Size(); i++)
{
int ghost_index = elements[msg.elements[i]].index;
MFEM_ASSERT(element_type[ghost_index] == 2, "");
new_ranks[ghost_index] = msg.values[i];
}
}
recv_ghost_ranks.clear();
// *** STEP 2: send elements that no longer belong to us to new assignees ***
/* The result thus far is just the array 'new_ranks' containing new owners
for elements that we currently own plus new owners for the ghost layer.
Next we keep elements that still belong to us and send ElementSets with
the remaining elements to their new owners. Each batch of elements needs
to be sent together with their neighbors so the receiver also gets a
ghost layer that is up to date (this is why we needed Step 1). */
int received_elements = 0;
for (int i = 0; i < leaf_elements.Size(); i++)
{
Element &el = elements[leaf_elements[i]];
if (el.rank == MyRank && new_ranks[i] == MyRank)
{
received_elements++; // initialize to number of elements we're keeping
}
el.rank = new_ranks[i];
}
int nsent = 0, nrecv = 0; // for debug check
RebalanceMessage::Map send_elems;
{
// sort elements we own by the new rank
Array<int> owned_elements;
owned_elements.MakeRef(leaf_elements.GetData(), NElements);
owned_elements.Sort([&](const int a, const int b)
{
return elements[a].rank < elements[b].rank;
});
Array<int> batch;
batch.Reserve(1024);
// send elements to new owners
int begin = 0, end = 0;
while (end < NElements)
{
// find range of elements belonging to one rank
int rank = elements[owned_elements[begin]].rank;
while (end < owned_elements.Size() &&
elements[owned_elements[end]].rank == rank) { end++; }
if (rank != MyRank)
{
Array<int> rank_elems;
rank_elems.MakeRef(&owned_elements[begin], end - begin);
// expand the 'rank_elems' set by its neighbor elements (ghosts)
batch.SetSize(0);
NeighborExpand(rank_elems, batch);
// send the batch
RebalanceMessage &msg = send_elems[rank];
msg.SetNCMesh(this);
msg.Reserve(batch.Size());
for (int i = 0; i < batch.Size(); i++)
{
int elem = batch[i];
Element &el = elements[elem];
if ((element_type[el.index] & 1) || el.rank != rank)
{
msg.AddElementRank(elem, el.rank);
}
// NOTE: we skip 'ghosts' that are of the receiver's rank because
// they are not really ghosts and would get sent multiple times,
// disrupting the termination mechanism in Step 4.
}
if (sfc)
{
msg.Isend(rank, MyComm);
}
else
{
// custom partitioning needs synchronous sends
msg.Issend(rank, MyComm);
}
nsent++;
// also: record what elements we sent (excluding the ghosts)
// so that SendRebalanceDofs can later send data for them
if (record_comm)
{
send_rebalance_dofs[rank].SetElements(rank_elems, this);
}
}
begin = end;
}
}
// *** STEP 3: receive elements from others ***
RebalanceMessage msg;
msg.SetNCMesh(this);
if (sfc)
{
/* We don't know from whom we're going to receive, so we need to probe.
However, for the default SFC partitioning, we do know how many elements
we're going to own eventually, so the termination condition is easy. */
while (received_elements < target_elements)
{
int rank, size;
RebalanceMessage::Probe(rank, size, MyComm);
// receive message; note: elements are created as the message is decoded
msg.Recv(rank, size, MyComm);
nrecv++;
for (int i = 0; i < msg.Size(); i++)
{
int elem_rank = msg.values[i];
elements[msg.elements[i]].rank = elem_rank;
if (elem_rank == MyRank) { received_elements++; }
}
// save the ranks we received from, for later use in RecvRebalanceDofs
if (record_comm)
{
recv_rebalance_dofs[rank].SetNCMesh(this);
}
}
Update();
RebalanceMessage::WaitAllSent(send_elems);
}
else
{
/* The case (target_elements < 0) is used for custom partitioning.
Here we need to employ the "non-blocking consensus" algorithm
(https://scorec.rpi.edu/REPORTS/2015-9.pdf) to determine when the
element exchange is finished. The algorithm uses a non-blocking
barrier. */
MPI_Request barrier = MPI_REQUEST_NULL;
int done = 0;
while (!done)
{
int rank, size;
while (RebalanceMessage::IProbe(rank, size, MyComm))
{
// receive message; note: elements are created as the msg is decoded
msg.Recv(rank, size, MyComm);
nrecv++;
for (int i = 0; i < msg.Size(); i++)
{
elements[msg.elements[i]].rank = msg.values[i];
}
// save the ranks we received from, for later use in RecvRebalanceDofs
if (record_comm)
{
recv_rebalance_dofs[rank].SetNCMesh(this);
}
}
if (barrier != MPI_REQUEST_NULL)
{
MPI_Test(&barrier, &done, MPI_STATUS_IGNORE);
}
else
{
if (RebalanceMessage::TestAllSent(send_elems))
{
int err = MPI_Ibarrier(MyComm, &barrier);
MFEM_VERIFY(err == MPI_SUCCESS, "");
MFEM_VERIFY(barrier != MPI_REQUEST_NULL, "");
}
}
}
Update();
}
NeighborElementRankMessage::WaitAllSent(send_ghost_ranks);
#ifdef MFEM_DEBUG
int glob_sent, glob_recv;
MPI_Reduce(&nsent, &glob_sent, 1, MPI_INT, MPI_SUM, 0, MyComm);
MPI_Reduce(&nrecv, &glob_recv, 1, MPI_INT, MPI_SUM, 0, MyComm);
if (MyRank == 0)
{
MFEM_ASSERT(glob_sent == glob_recv,
"(glob_sent, glob_recv) = ("
<< glob_sent << ", " << glob_recv << ")");
}
#endif
}
void ParNCMesh::SendRebalanceDofs(int old_ndofs,
const Table &old_element_dofs,
long old_global_offset,
FiniteElementSpace *space)
{
Array<int> dofs;
int vdim = space->GetVDim();
// fill messages (prepared by Rebalance) with element DOFs
RebalanceDofMessage::Map::iterator it;
for (it = send_rebalance_dofs.begin(); it != send_rebalance_dofs.end(); ++it)
{
RebalanceDofMessage &msg = it->second;
msg.dofs.clear();
int ne = msg.elem_ids.size();
if (ne)
{
msg.dofs.reserve(old_element_dofs.RowSize(msg.elem_ids[0]) * ne * vdim);
}
for (int i = 0; i < ne; i++)
{
old_element_dofs.GetRow(msg.elem_ids[i], dofs);
space->DofsToVDofs(dofs, old_ndofs);
msg.dofs.insert(msg.dofs.end(), dofs.begin(), dofs.end());
}
msg.dof_offset = old_global_offset;
}
// send the DOFs to element recipients from last Rebalance()
RebalanceDofMessage::IsendAll(send_rebalance_dofs, MyComm);
}
void ParNCMesh::RecvRebalanceDofs(Array<int> &elements, Array<long> &dofs)
{
// receive from the same ranks as in last Rebalance()
RebalanceDofMessage::RecvAll(recv_rebalance_dofs, MyComm);
// count the size of the result
int ne = 0, nd = 0;
RebalanceDofMessage::Map::iterator it;
for (it = recv_rebalance_dofs.begin(); it != recv_rebalance_dofs.end(); ++it)
{
RebalanceDofMessage &msg = it->second;
ne += msg.elem_ids.size();
nd += msg.dofs.size();
}
elements.SetSize(ne);
dofs.SetSize(nd);
// copy element indices and their DOFs
ne = nd = 0;
for (it = recv_rebalance_dofs.begin(); it != recv_rebalance_dofs.end(); ++it)
{
RebalanceDofMessage &msg = it->second;
for (unsigned i = 0; i < msg.elem_ids.size(); i++)
{
elements[ne++] = msg.elem_ids[i];
}
for (unsigned i = 0; i < msg.dofs.size(); i++)
{
dofs[nd++] = msg.dof_offset + msg.dofs[i];
}
}
RebalanceDofMessage::WaitAllSent(send_rebalance_dofs);
}
//// ElementSet ////////////////////////////////////////////////////////////////
ParNCMesh::ElementSet::ElementSet(const ElementSet &other)
: ncmesh(other.ncmesh), include_ref_types(other.include_ref_types)
{
other.data.Copy(data);
}
void ParNCMesh::ElementSet::WriteInt(int value)
{
// helper to put an int to the data array
data.Append(value & 0xff);
data.Append((value >> 8) & 0xff);
data.Append((value >> 16) & 0xff);
data.Append((value >> 24) & 0xff);
}
int ParNCMesh::ElementSet::GetInt(int pos) const
{
// helper to get an int from the data array
return (int) data[pos] +
((int) data[pos+1] << 8) +
((int) data[pos+2] << 16) +
((int) data[pos+3] << 24);
}
void ParNCMesh::ElementSet::FlagElements(const Array<int> &elements, char flag)
{
for (int i = 0; i < elements.Size(); i++)
{
int elem = elements[i];
while (elem >= 0)
{
Element &el = ncmesh->elements[elem];
if (el.flag == flag) { break; }
el.flag = flag;
elem = el.parent;
}
}
}
void ParNCMesh::ElementSet::EncodeTree(int elem)
{
Element &el = ncmesh->elements[elem];
if (!el.ref_type)
{
// we reached a leaf, mark this as zero child mask
data.Append(0);
}
else
{
// check which subtrees contain marked elements
int mask = 0;
for (int i = 0; i < 8; i++)
{
if (el.child[i] >= 0 && ncmesh->elements[el.child[i]].flag)
{
mask |= 1 << i;
}
}
// write the bit mask and visit the subtrees
data.Append(mask);
if (include_ref_types)
{
data.Append(el.ref_type);
}
for (int i = 0; i < 8; i++)
{
if (mask & (1 << i))
{
EncodeTree(el.child[i]);
}
}
}
}
void ParNCMesh::ElementSet::Encode(const Array<int> &elements)
{
FlagElements(elements, 1);
// Each refinement tree that contains at least one element from the set
// is encoded as HEADER + TREE, where HEADER is the root element number and
// TREE is the output of EncodeTree().
for (int i = 0; i < ncmesh->root_state.Size(); i++)
{
if (ncmesh->elements[i].flag)
{
WriteInt(i);
EncodeTree(i);
}
}
WriteInt(-1); // mark end of data
FlagElements(elements, 0);
}
#ifdef MFEM_DEBUG
std::string ParNCMesh::ElementSet::RefPath() const
{
std::ostringstream oss;
for (int i = 0; i < ref_path.Size(); i++)
{
oss << " elem " << ref_path[i] << " (";
const Element &el = ncmesh->elements[ref_path[i]];
for (int j = 0; j < GI[el.Geom()].nv; j++)
{
if (j) { oss << ", "; }
oss << ncmesh->RetrieveNode(el, j);
}
oss << ")\n";
}
return oss.str();
}
#endif
void ParNCMesh::ElementSet::DecodeTree(int elem, int &pos,
Array<int> &elements) const
{
#ifdef MFEM_DEBUG
ref_path.Append(elem);
#endif
int mask = data[pos++];
if (!mask)
{
elements.Append(elem);
}
else
{
Element &el = ncmesh->elements[elem];
if (include_ref_types)
{
int ref_type = data[pos++];
if (!el.ref_type)
{
ncmesh->RefineElement(elem, ref_type);
}
else { MFEM_ASSERT(ref_type == el.ref_type, "") }
}
else
{
MFEM_ASSERT(el.ref_type != 0, "Path not found:\n"
<< RefPath() << " mask = " << mask);
}
for (int i = 0; i < 8; i++)
{
if (mask & (1 << i))
{
DecodeTree(el.child[i], pos, elements);
}
}
}
#ifdef MFEM_DEBUG
ref_path.DeleteLast();
#endif
}
void ParNCMesh::ElementSet::Decode(Array<int> &elements) const
{
int root, pos = 0;
while ((root = GetInt(pos)) >= 0)
{
pos += 4;
DecodeTree(root, pos, elements);
}
}
void ParNCMesh::ElementSet::Dump(std::ostream &os) const
{
write<int>(os, data.Size());
os.write((const char*) data.GetData(), data.Size());
}
void ParNCMesh::ElementSet::Load(std::istream &is)
{
data.SetSize(read<int>(is));
is.read((char*) data.GetData(), data.Size());
}
//// EncodeMeshIds/DecodeMeshIds ///////////////////////////////////////////////
void ParNCMesh::AdjustMeshIds(Array<MeshId> ids[], int rank)
{
GetSharedVertices();
GetSharedEdges();
GetSharedFaces();
if (!shared_edges.masters.size() &&
!shared_faces.masters.size()) { return; }
Array<bool> contains_rank(groups.size());
for (unsigned i = 0; i < groups.size(); i++)
{
contains_rank[i] = GroupContains(i, rank);
}
Array<Pair<int, int> > find_v(ids[0].Size());
for (int i = 0; i < ids[0].Size(); i++)
{
find_v[i].one = ids[0][i].index;
find_v[i].two = i;
}
find_v.Sort();
// find vertices of master edges shared with 'rank', and modify their
// MeshIds so their element/local matches the element of the master edge
for (unsigned i = 0; i < shared_edges.masters.size(); i++)
{
const MeshId &edge_id = shared_edges.masters[i];
if (contains_rank[entity_pmat_group[1][edge_id.index]])
{
int v[2], pos, k;
GetEdgeVertices(edge_id, v);
for (int j = 0; j < 2; j++)
{
if ((pos = find_v.FindSorted(Pair<int, int>(v[j], 0))) != -1)
{
// switch to an element/local that is safe for 'rank'
k = find_v[pos].two;
ChangeVertexMeshIdElement(ids[0][k], edge_id.element);
ChangeRemainingMeshIds(ids[0], pos, find_v);
}
}
}
}
if (!shared_faces.masters.size()) { return; }
Array<Pair<int, int> > find_e(ids[1].Size());
for (int i = 0; i < ids[1].Size(); i++)
{
find_e[i].one = ids[1][i].index;
find_e[i].two = i;
}
find_e.Sort();
// find vertices/edges of master faces shared with 'rank', and modify their
// MeshIds so their element/local matches the element of the master face
for (unsigned i = 0; i < shared_faces.masters.size(); i++)
{
const MeshId &face_id = shared_faces.masters[i];
if (contains_rank[entity_pmat_group[2][face_id.index]])
{
int v[4], e[4], eo[4], pos, k;
int nfv = GetFaceVerticesEdges(face_id, v, e, eo);
for (int j = 0; j < nfv; j++)
{
if ((pos = find_v.FindSorted(Pair<int, int>(v[j], 0))) != -1)
{
k = find_v[pos].two;
ChangeVertexMeshIdElement(ids[0][k], face_id.element);
ChangeRemainingMeshIds(ids[0], pos, find_v);
}
if ((pos = find_e.FindSorted(Pair<int, int>(e[j], 0))) != -1)
{
k = find_e[pos].two;
ChangeEdgeMeshIdElement(ids[1][k], face_id.element);
ChangeRemainingMeshIds(ids[1], pos, find_e);
}
}
}
}
}
void ParNCMesh::ChangeVertexMeshIdElement(NCMesh::MeshId &id, int elem)
{
Element &el = elements[elem];
MFEM_ASSERT(el.ref_type == 0, "");
GeomInfo& gi = GI[el.Geom()];
for (int i = 0; i < gi.nv; i++)
{
if (nodes[el.node[i]].vert_index == id.index)
{
id.local = i;
id.element = elem;
return;
}
}
MFEM_ABORT("Vertex not found.");
}
void ParNCMesh::ChangeEdgeMeshIdElement(NCMesh::MeshId &id, int elem)
{
Element &old = elements[id.element];
const int *ev = GI[old.Geom()].edges[(int) id.local];
Node* node = nodes.Find(old.node[ev[0]], old.node[ev[1]]);
MFEM_ASSERT(node != NULL, "Edge not found.");
Element &el = elements[elem];
MFEM_ASSERT(el.ref_type == 0, "");
GeomInfo& gi = GI[el.Geom()];
for (int i = 0; i < gi.ne; i++)
{
const int* ev = gi.edges[i];
if ((el.node[ev[0]] == node->p1 && el.node[ev[1]] == node->p2) ||
(el.node[ev[1]] == node->p1 && el.node[ev[0]] == node->p2))
{
id.local = i;
id.element = elem;
return;
}
}
MFEM_ABORT("Edge not found.");
}
void ParNCMesh::ChangeRemainingMeshIds(Array<MeshId> &ids, int pos,
const Array<Pair<int, int> > &find)
{
const MeshId &first = ids[find[pos].two];
while (++pos < find.Size() && ids[find[pos].two].index == first.index)
{
MeshId &other = ids[find[pos].two];
other.element = first.element;
other.local = first.local;
}
}
void ParNCMesh::EncodeMeshIds(std::ostream &os, Array<MeshId> ids[])
{
std::map<int, int> stream_id;
// get a list of elements involved, dump them to 'os' and create the mapping
// element_id: (Element index -> stream ID)
{
Array<int> elements;
for (int type = 0; type < 3; type++)
{
for (int i = 0; i < ids[type].Size(); i++)
{
elements.Append(ids[type][i].element);
}
}
ElementSet eset(this);
eset.Encode(elements);
eset.Dump(os);
Array<int> decoded;
decoded.Reserve(elements.Size());
eset.Decode(decoded);
for (int i = 0; i < decoded.Size(); i++)
{
stream_id[decoded[i]] = i;
}
}
// write the IDs as element/local pairs
for (int type = 0; type < 3; type++)
{
write<int>(os, ids[type].Size());
for (int i = 0; i < ids[type].Size(); i++)
{
const MeshId& id = ids[type][i];
write<int>(os, stream_id[id.element]); // TODO: variable 1-4 bytes
write<char>(os, id.local);
}
}
}
void ParNCMesh::DecodeMeshIds(std::istream &is, Array<MeshId> ids[])
{
// read the list of elements
ElementSet eset(this);
eset.Load(is);
Array<int> elems;
eset.Decode(elems);
// read vertex/edge/face IDs
for (int type = 0; type < 3; type++)
{
int ne = read<int>(is);
ids[type].SetSize(ne);
for (int i = 0; i < ne; i++)
{
int el_num = read<int>(is);
int elem = elems[el_num];
Element &el = elements[elem];
MFEM_VERIFY(!el.ref_type, "not a leaf element: " << el_num);
MeshId &id = ids[type][i];
id.element = elem;
id.local = read<char>(is);
// find vertex/edge/face index
GeomInfo &gi = GI[el.Geom()];
switch (type)
{
case 0:
{
id.index = nodes[el.node[(int) id.local]].vert_index;
break;
}
case 1:
{
const int* ev = gi.edges[(int) id.local];
Node* node = nodes.Find(el.node[ev[0]], el.node[ev[1]]);
MFEM_ASSERT(node && node->HasEdge(), "edge not found.");
id.index = node->edge_index;
break;
}
default:
{
const int* fv = gi.faces[(int) id.local];
Face* face = faces.Find(el.node[fv[0]], el.node[fv[1]],
el.node[fv[2]], el.node[fv[3]]);
MFEM_ASSERT(face, "face not found.");
id.index = face->index;
}
}
}
}
}
void ParNCMesh::EncodeGroups(std::ostream &os, const Array<GroupId> &ids)
{
// get a list of unique GroupIds
std::map<GroupId, GroupId> stream_id;
for (int i = 0; i < ids.Size(); i++)
{
if (i && ids[i] == ids[i-1]) { continue; }
unsigned size = stream_id.size();
GroupId &sid = stream_id[ids[i]];
if (size != stream_id.size()) { sid = size; }
}
// write the unique groups
write<short>(os, stream_id.size());
for (std::map<GroupId, GroupId>::iterator
it = stream_id.begin(); it != stream_id.end(); ++it)
{
write<GroupId>(os, it->second);
if (it->first >= 0)
{
const CommGroup &group = groups[it->first];
write<short>(os, group.size());
for (unsigned i = 0; i < group.size(); i++)
{
write<int>(os, group[i]);
}
}
else
{
// special "invalid" group, marks forwarded rows
write<short>(os, -1);
}
}
// write the list of all GroupIds
write<int>(os, ids.Size());
for (int i = 0; i < ids.Size(); i++)
{
write<GroupId>(os, stream_id[ids[i]]);
}
}
void ParNCMesh::DecodeGroups(std::istream &is, Array<GroupId> &ids)
{
int ngroups = read<short>(is);
Array<GroupId> groups(ngroups);
// read stream groups, convert to our groups
CommGroup ranks;
ranks.reserve(128);
for (int i = 0; i < ngroups; i++)
{
int id = read<GroupId>(is);
int size = read<short>(is);
if (size >= 0)
{
ranks.resize(size);
for (int i = 0; i < size; i++)
{
ranks[i] = read<int>(is);
}
groups[id] = GetGroupId(ranks);
}
else
{
groups[id] = -1; // forwarded
}
}
// read the list of IDs
ids.SetSize(read<int>(is));
for (int i = 0; i < ids.Size(); i++)
{
ids[i] = groups[read<GroupId>(is)];
}
}
//// Messages //////////////////////////////////////////////////////////////////
template<class ValueType, bool RefTypes, int Tag>
void ParNCMesh::ElementValueMessage<ValueType, RefTypes, Tag>::Encode(int)
{
std::ostringstream ostream;
Array<int> tmp_elements;
tmp_elements.MakeRef(elements.data(), elements.size());
ElementSet eset(pncmesh, RefTypes);
eset.Encode(tmp_elements);
eset.Dump(ostream);
// decode the element set to obtain a local numbering of elements
Array<int> decoded;
decoded.Reserve(tmp_elements.Size());
eset.Decode(decoded);
std::map<int, int> element_index;
for (int i = 0; i < decoded.Size(); i++)
{
element_index[decoded[i]] = i;
}
write<int>(ostream, values.size());
MFEM_ASSERT(elements.size() == values.size(), "");
for (unsigned i = 0; i < values.size(); i++)
{
write<int>(ostream, element_index[elements[i]]); // element number
write<ValueType>(ostream, values[i]);
}
ostream.str().swap(data);
}
template<class ValueType, bool RefTypes, int Tag>
void ParNCMesh::ElementValueMessage<ValueType, RefTypes, Tag>::Decode(int)
{
std::istringstream istream(data);
ElementSet eset(pncmesh, RefTypes);
eset.Load(istream);
Array<int> tmp_elements;
eset.Decode(tmp_elements);
int* el = tmp_elements.GetData();
elements.assign(el, el + tmp_elements.Size());
values.resize(elements.size());
int count = read<int>(istream);
for (int i = 0; i < count; i++)
{
int index = read<int>(istream);
MFEM_ASSERT(index >= 0 && (size_t) index < values.size(), "");
values[index] = read<ValueType>(istream);
}
// no longer need the raw data
data.clear();
}
void ParNCMesh::RebalanceDofMessage::SetElements(const Array<int> &elems,
NCMesh *ncmesh)
{
eset.SetNCMesh(ncmesh);
eset.Encode(elems);
Array<int> decoded;
decoded.Reserve(elems.Size());
eset.Decode(decoded);
elem_ids.resize(decoded.Size());
for (int i = 0; i < decoded.Size(); i++)
{
elem_ids[i] = eset.GetNCMesh()->elements[decoded[i]].index;
}
}
static void write_dofs(std::ostream &os, const std::vector<int> &dofs)
{
write<int>(os, dofs.size());
// TODO: we should compress the ints, mostly they are contiguous ranges
os.write((const char*) dofs.data(), dofs.size() * sizeof(int));
}
static void read_dofs(std::istream &is, std::vector<int> &dofs)
{
dofs.resize(read<int>(is));
is.read((char*) dofs.data(), dofs.size() * sizeof(int));
}
void ParNCMesh::RebalanceDofMessage::Encode(int)
{
std::ostringstream stream;
eset.Dump(stream);
write<long>(stream, dof_offset);
write_dofs(stream, dofs);
stream.str().swap(data);
}
void ParNCMesh::RebalanceDofMessage::Decode(int)
{
std::istringstream stream(data);
eset.Load(stream);
dof_offset = read<long>(stream);
read_dofs(stream, dofs);
data.clear();
Array<int> elems;
eset.Decode(elems);
elem_ids.resize(elems.Size());
for (int i = 0; i < elems.Size(); i++)
{
elem_ids[i] = eset.GetNCMesh()->elements[elems[i]].index;
}
}
//// Utility ///////////////////////////////////////////////////////////////////
void ParNCMesh::GetDebugMesh(Mesh &debug_mesh) const
{
// create a serial NCMesh containing all our elements (ghosts and all)
NCMesh* copy = new NCMesh(*this);
Array<int> &cle = copy->leaf_elements;
for (int i = 0; i < cle.Size(); i++)
{
Element &el = copy->elements[cle[i]];
el.attribute = el.rank + 1;
}
debug_mesh.InitFromNCMesh(*copy);
debug_mesh.SetAttributes();
debug_mesh.ncmesh = copy;
}
void ParNCMesh::Trim()
{
NCMesh::Trim();
shared_vertices.Clear(true);
shared_edges.Clear(true);
shared_faces.Clear(true);
for (int i = 0; i < 3; i++)
{
entity_owner[i].DeleteAll();
entity_pmat_group[i].DeleteAll();
entity_index_rank[i].DeleteAll();
}
send_rebalance_dofs.clear();
recv_rebalance_dofs.clear();
old_index_or_rank.DeleteAll();
ClearAuxPM();
}
long ParNCMesh::RebalanceDofMessage::MemoryUsage() const
{
return (elem_ids.capacity() + dofs.capacity()) * sizeof(int);
}
template<typename K, typename V>
static long map_memory_usage(const std::map<K, V> &map)
{
long result = 0;
for (typename std::map<K, V>::const_iterator
it = map.begin(); it != map.end(); ++it)
{
result += it->second.MemoryUsage();
result += sizeof(std::pair<K, V>) + 3*sizeof(void*) + sizeof(bool);
}
return result;
}
long ParNCMesh::GroupsMemoryUsage() const
{
long groups_size = groups.capacity() * sizeof(CommGroup);
for (unsigned i = 0; i < groups.size(); i++)
{
groups_size += groups[i].capacity() * sizeof(int);
}
const int approx_node_size =
sizeof(std::pair<CommGroup, GroupId>) + 3*sizeof(void*) + sizeof(bool);
return groups_size + group_id.size() * approx_node_size;
}
template<typename Type, int Size>
static long arrays_memory_usage(const Array<Type> (&arrays)[Size])
{
long total = 0;
for (int i = 0; i < Size; i++)
{
total += arrays[i].MemoryUsage();
}
return total;
}
long ParNCMesh::MemoryUsage(bool with_base) const
{
long total_groups_owners = 0;
for (int i = 0; i < 3; i++)
{
total_groups_owners += entity_owner[i].MemoryUsage() +
entity_pmat_group[i].MemoryUsage() +
entity_index_rank[i].MemoryUsage();
}
return (with_base ? NCMesh::MemoryUsage() : 0) +
GroupsMemoryUsage() +
arrays_memory_usage(entity_owner) +
arrays_memory_usage(entity_pmat_group) +
arrays_memory_usage(entity_conf_group) +
leaf_glob_order.MemoryUsage() +
arrays_memory_usage(entity_elem_local) +
shared_vertices.MemoryUsage() +
shared_edges.MemoryUsage() +
shared_faces.MemoryUsage() +
face_orient.MemoryUsage() +
element_type.MemoryUsage() +
ghost_layer.MemoryUsage() +
boundary_layer.MemoryUsage() +
tmp_owner.MemoryUsage() +
tmp_shared_flag.MemoryUsage() +
arrays_memory_usage(entity_index_rank) +
tmp_neighbors.MemoryUsage() +
map_memory_usage(send_rebalance_dofs) +
map_memory_usage(recv_rebalance_dofs) +
old_index_or_rank.MemoryUsage() +
aux_pm_store.MemoryUsage() +
sizeof(ParNCMesh) - sizeof(NCMesh);
}
int ParNCMesh::PrintMemoryDetail(bool with_base) const
{
if (with_base) { NCMesh::PrintMemoryDetail(); }
mfem::out << GroupsMemoryUsage() << " groups\n"
<< arrays_memory_usage(entity_owner) << " entity_owner\n"
<< arrays_memory_usage(entity_pmat_group) << " entity_pmat_group\n"
<< arrays_memory_usage(entity_conf_group) << " entity_conf_group\n"
<< leaf_glob_order.MemoryUsage() << " leaf_glob_order\n"
<< arrays_memory_usage(entity_elem_local) << " entity_elem_local\n"
<< shared_vertices.MemoryUsage() << " shared_vertices\n"
<< shared_edges.MemoryUsage() << " shared_edges\n"
<< shared_faces.MemoryUsage() << " shared_faces\n"
<< face_orient.MemoryUsage() << " face_orient\n"
<< element_type.MemoryUsage() << " element_type\n"
<< ghost_layer.MemoryUsage() << " ghost_layer\n"
<< boundary_layer.MemoryUsage() << " boundary_layer\n"
<< tmp_owner.MemoryUsage() << " tmp_owner\n"
<< tmp_shared_flag.MemoryUsage() << " tmp_shared_flag\n"
<< arrays_memory_usage(entity_index_rank) << " entity_index_rank\n"
<< tmp_neighbors.MemoryUsage() << " tmp_neighbors\n"
<< map_memory_usage(send_rebalance_dofs) << " send_rebalance_dofs\n"
<< map_memory_usage(recv_rebalance_dofs) << " recv_rebalance_dofs\n"
<< old_index_or_rank.MemoryUsage() << " old_index_or_rank\n"
<< aux_pm_store.MemoryUsage() << " aux_pm_store\n"
<< sizeof(ParNCMesh) - sizeof(NCMesh) << " ParNCMesh" << std::endl;
return leaf_elements.Size();
}
} // namespace mfem
#endif // MFEM_USE_MPI