Cloned SEACAS for EXODUS library with extra build files for internal package management.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

702 lines
29 KiB

/*
* Copyright(C) 1999-2022 National Technology & Engineering Solutions
* of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with
* NTESS, the U.S. Government retains certain rights in this software.
*
* See packages/seacas/LICENSE for details
*/
/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* Functions contained in this file:
* generate_graph()
* find_surnd_elems()
* find_adjacency()
*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
#include "copy_string_cpp.h"
#include "elb.h" // for Problem_Description, etc
#include "elb_elem.h" // for get_elem_info, NNODES, etc
#include "elb_err.h" // for Gen_Error
#include "elb_graph.h"
#include "elb_util.h" // for in_list, find_inter
#include <cassert> // for assert
#include <cstddef> // for size_t
#include <cstdlib>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <sstream>
#include <vector> // for vector
extern int is_hex(E_Type etype);
extern int is_tet(E_Type etype);
extern int is_3d_element(E_Type etype);
/* Local function prototypes */
namespace {
template <typename INT>
int find_surnd_elems(Mesh_Description<INT> * /*mesh*/, Graph_Description<INT> * /*graph*/);
template <typename INT>
int find_adjacency(Problem_Description * /*problem*/, Mesh_Description<INT> * /*mesh*/,
Graph_Description<INT> * /*graph*/, Weight_Description<INT> * /*weight*/,
Sphere_Info * /*sphere*/);
} // namespace
/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
/* Function generate_graph() begins:
*----------------------------------------------------------------------------
* This function does the work to generate the graph from the FE mesh.
*****************************************************************************/
template int generate_graph(Problem_Description *problem, Mesh_Description<int> *mesh,
Graph_Description<int> *graph, Weight_Description<int> *weight,
Sphere_Info *sphere);
template int generate_graph(Problem_Description *problem, Mesh_Description<int64_t> *mesh,
Graph_Description<int64_t> *graph, Weight_Description<int64_t> *weight,
Sphere_Info *sphere);
template <typename INT>
int generate_graph(Problem_Description *problem, Mesh_Description<INT> *mesh,
Graph_Description<INT> *graph, Weight_Description<INT> *weight,
Sphere_Info *sphere)
{
double time1 = get_time();
/* Find the elements surrounding a node */
if (!find_surnd_elems(mesh, graph)) {
Gen_Error(0, "fatal: could not find surrounding elements");
return 0;
}
double time2 = get_time();
fmt::print(stderr, "Time to find surrounding elements: {}s\n", time2 - time1);
/* Find the adjacency, if required */
if (problem->alloc_graph == ELB_TRUE) {
if (!find_adjacency(problem, mesh, graph, weight, sphere)) {
Gen_Error(0, "fatal: could not find adjacency");
return 0;
}
time1 = get_time();
fmt::print(stderr, "Time to find the adjacency: {}s\n", time2 - time1);
}
return 1;
}
namespace {
/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
/* Function find_surnd_elems() begins:
*----------------------------------------------------------------------------
* This function finds the elements surrounding a given FEM node. In other
* words, this function generates a list of elements containing a given
* FEM node.
*****************************************************************************/
template <typename INT>
int find_surnd_elems(Mesh_Description<INT> *mesh, Graph_Description<INT> *graph)
{
std::vector<int> surround_count(mesh->num_nodes);
std::vector<size_t> last_element(mesh->num_nodes);
size_t sur_elem_total_size = 0;
/* Find the count of surrounding elements for each node in the mesh */
// The hope is that this code will speed up the entire routine even
// though we are iterating the complete connectivity array twice.
for (size_t ecnt = 0; ecnt < mesh->num_elems; ecnt++) {
int nnodes = get_elem_info(NNODES, mesh->elem_type[ecnt]);
for (int ncnt = 0; ncnt < nnodes; ncnt++) {
INT node = mesh->connect[ecnt][ncnt];
assert(node < (INT)mesh->num_nodes);
/*
* in the case of degenerate elements, where a node can be
* entered into the connect table twice, need to check to
* make sure that this element is not already listed as
* surrounding this node
*/
if (last_element[node] != ecnt + 1) {
last_element[node] = ecnt + 1;
surround_count[node]++;
sur_elem_total_size++;
}
}
} /* End "for(ecnt=0; ecnt < mesh->num_elems; ecnt++)" */
size_t v_size = sizeof(std::vector<INT>);
size_t vv_size = sizeof(std::vector<std::vector<INT>>);
size_t total = vv_size + mesh->num_nodes * v_size + sur_elem_total_size * sizeof(INT);
fmt::print(stderr, "\ttotal size of reverse connectivity array: {} entries ({} bytes).\n",
fmt::group_digits(sur_elem_total_size), fmt::group_digits(total));
vec_free(last_element);
// Attempt to reserve an array with this size...
double time1 = get_time();
graph->sur_elem.resize(mesh->num_nodes);
for (size_t ncnt = 0; ncnt < mesh->num_nodes; ncnt++) {
if (surround_count[ncnt] == 0) {
fmt::print(stderr, "WARNING: Node = {} has no elements\n", ncnt + 1);
}
else {
graph->sur_elem[ncnt].reserve(surround_count[ncnt]);
graph->max_nsur =
surround_count[ncnt] > graph->max_nsur ? surround_count[ncnt] : graph->max_nsur;
}
}
double time2 = get_time();
fmt::print(stderr,
"\tmemory allocated...({} seconds)\n"
"\tmax of {} elements per node\n",
time2 - time1, graph->max_nsur);
/* Find the surrounding elements for each node in the mesh */
for (size_t ecnt = 0; ecnt < mesh->num_elems; ecnt++) {
int nnodes = get_elem_info(NNODES, mesh->elem_type[ecnt]);
for (int ncnt = 0; ncnt < nnodes; ncnt++) {
INT node = mesh->connect[ecnt][ncnt];
/*
* in the case of degenerate elements, where a node can be
* entered into the connect table twice, need to check to
* make sure that this element is not already listed as
* surrounding this node
*/
if (graph->sur_elem[node].empty() || ecnt != (size_t)graph->sur_elem[node].back()) {
/* Add the element to the list */
graph->sur_elem[node].push_back(ecnt);
}
}
} /* End "for(ecnt=0; ecnt < mesh->num_elems; ecnt++)" */
#ifndef NDEBUG
for (size_t ncnt = 0; ncnt < mesh->num_nodes; ncnt++) {
assert(graph->sur_elem[ncnt].size() == (size_t)surround_count[ncnt]);
}
#endif
return 1;
}
/*****************************************************************************/
/*****************************************************************************/
/*****************************************************************************/
/* Function find_adjacency() begins:
*----------------------------------------------------------------------------
* This function finds adjacency (or graph) of the problem.
*****************************************************************************/
template <typename INT>
int find_adjacency(Problem_Description *problem, Mesh_Description<INT> *mesh,
Graph_Description<INT> *graph, Weight_Description<INT> *weight,
Sphere_Info *sphere)
{
std::vector<INT> pt_list;
std::vector<INT> hold_elem;
INT side_nodes[MAX_SIDE_NODES + 2];
INT mirror_nodes[MAX_SIDE_NODES + 2];
static int count = 0;
int hflag1;
int hflag2;
int tflag1;
int tflag2;
/*-----------------------------Execution Begins------------------------------*/
/* Allocate memory necessary for the adjacency */
graph->start.resize(problem->num_vertices + 1);
for (int i = 0; i < MAX_SIDE_NODES + 2; i++) {
side_nodes[i] = -999;
mirror_nodes[i] = -999;
}
/* Find the adjacency for a nodal based decomposition */
if (problem->type == NODAL) {
graph->nadj = 0;
for (size_t ncnt = 0; ncnt < mesh->num_nodes; ncnt++) {
graph->start[ncnt] = graph->nadj;
assert(graph->nadj == graph->adj.size());
for (size_t ecnt = 0; ecnt < graph->sur_elem[ncnt].size(); ecnt++) {
size_t elem = graph->sur_elem[ncnt][ecnt];
int nnodes = get_elem_info(NNODES, mesh->elem_type[elem]);
for (int i = 0; i < nnodes; i++) {
INT entry = mesh->connect[elem][i];
if (ncnt != (size_t)entry && in_list(entry, graph->adj.size() - graph->start[ncnt],
&graph->adj[graph->start[ncnt]]) < 0) {
graph->adj.push_back(entry);
graph->nadj++;
}
}
} /* End "for(ecnt=0; ecnt < graph->nsur_elem[ncnt]; ecnt++)" */
} /* End "for(ncnt=0; ncnt < mesh->num_nodes; ncnt++)" */
}
/* Find the adjacency for a elemental based decomposition */
else {
/* for face adjacencies, need to allocate some memory */
if (problem->face_adj) {
/* allocate space to hold info about surrounding elements */
pt_list.resize(graph->max_nsur);
hold_elem.resize(graph->max_nsur);
}
graph->nadj = 0;
size_t cnt = 0;
/* tmp_element used to speed up the in_list calc */
std::vector<int> tmp_element(mesh->num_elems, -1);
/* Cycle through the elements */
int element_3d = 0;
int nnodes = mesh->num_dims;
int nsides = 0;
for (size_t ecnt = 0; ecnt < mesh->num_elems; ecnt++) {
E_Type etype = mesh->elem_type[ecnt];
E_Type etype_last = NULL_EL;
if (etype != etype_last) {
etype_last = etype;
element_3d = is_3d_element(mesh->elem_type[ecnt]);
if (problem->face_adj == 0) {
nnodes = get_elem_info(NNODES, etype);
}
nsides = get_elem_info(NSIDES, etype);
}
if (etype != SPHERE || problem->no_sph == 1) {
graph->start[cnt] = graph->nadj;
assert(graph->nadj == graph->adj.size());
/*
* now have to decide how to determine adjacency
* !face_adj - any element that connects to any node in this
* element is an adjacent element
* face_adj - a) for 3D elements only those that share an
* entire face with this element are considered
* adjacent
* b) do not connect 1D/2D elements to 3D elements
* c) 1D and 2D elements can connect to each other
*/
/* If not forcing face adjaceny */
if (problem->face_adj == 0) {
/* ncnt = 0,...,7 for hex */
for (int ncnt = 0; ncnt < nnodes; ncnt++) {
/* node is the node number 'ncnt' of element 'ecnt' */
size_t node = mesh->connect[ecnt][ncnt];
/* i varies from 0 -> # of elements touching 'node' */
for (size_t i = 0; i < graph->sur_elem[node].size(); i++) {
/* 'entry' is element # i touching node 'node' */
INT entry = graph->sur_elem[node][i];
/* make sure we're not checking if the element
is connected to itself */
if (ecnt != (size_t)entry && mesh->elem_type[entry] != SPHERE) {
/* If tmp_element[entry] != ecnt, then entry is not in list... */
if ((size_t)tmp_element[entry] != ecnt) {
#if 0
assert(in_list(entry, (graph->nadj)-(graph->start[cnt]),
(graph->adj)+(graph->start[cnt])) < 0);
#endif
tmp_element[entry] = ecnt;
(graph->nadj)++;
graph->adj.push_back(entry);
if (weight->type & EDGE_WGT) {
weight->edges.push_back(1.0);
}
}
else if (weight->type & EDGE_WGT) {
int iret = in_list(entry, (graph->nadj) - (graph->start[cnt]),
&graph->adj[graph->start[cnt]]);
assert(iret >= 0);
weight->edges[iret + (graph->start[cnt])] += 1.0F;
}
}
}
} /* End "for(ncnt=0; ...)" */
} /* End: "if (problem->face_adj == 0)" */
/* So if this is a 3-d element and we're forcing face
* adjacency, if it gets to this else below
*
* if this element is 1d/2d allow connections to 1d and 2d
* elements but not to 3d elements
*
*/
else {
if (element_3d) {
/* need to check for hex's or tet's */
/*
* If the first element is a hex or tet, set flags
* hflag1/tflag1 to 1
*/
hflag1 = is_hex(etype);
tflag1 = is_tet(etype);
/* check each side of this element */
for (int nscnt = 0; nscnt < nsides; nscnt++) {
/* get the list of nodes on this side set */
int side_cnt = ss_to_node_list(etype, mesh->connect[ecnt], (nscnt + 1), side_nodes);
/*
* now I need to determine how many side set nodes I
* need to use to determine if there is an element
* connected to this side.
*
* 2-D - need two nodes, so find one intersection
* 3-D - need three nodes, so find two intersections
* NOTE: must check to make sure that this number is not
* larger than the number of nodes on the sides (ie - SHELL).
*/
nnodes = mesh->num_dims;
/*
* In case the number of nodes on this side are less
* than the minimum number, set nnodes to side_cnt,
* i.e., if a 3-D mesh contains a bar, then nnodes=3,
* and side_cnt = 2
*/
if (nnodes > side_cnt) {
nnodes = side_cnt;
}
nnodes--; /* decrement to find the number of intersections */
size_t nelem = 0; /* reset this in case no intersections are needed */
/* copy the first array into temp storage */
#if 0
/* nhold is the number of elements touching node 0 on
the side of this element */
size_t nhold = graph->sur_elem[side_nodes[0]].size();
/* Now that we have the number of elements touching
side 0, get their element ids and store them in hold_elem */
for (size_t ncnt = 0; ncnt < nhold; ncnt++)
hold_elem[ncnt] = graph->sur_elem[side_nodes[0]][ncnt];
#endif
/*
* need to handle hex's differently because of
* the tet/hex combination
*/
if (!hflag1) {
/* Get the number of elements ( and their ids )
that touch node (ncnt+1) and see if any elements touch
both node 0 and node (ncnt+1), and if so, return to nelem
the number of elements touching both nodes and their
indices in pt_list. When ncnt != 0, hold_elem and nhold
change */
size_t nhold = graph->sur_elem[side_nodes[0]].size();
for (size_t ncnt = 0; ncnt < nhold; ncnt++) {
hold_elem[ncnt] = graph->sur_elem[side_nodes[0]][ncnt];
}
for (int ncnt = 0; ncnt < nnodes; ncnt++) {
nelem = find_inter(
hold_elem.data(), &graph->sur_elem[side_nodes[(ncnt + 1)]][0], nhold,
graph->sur_elem[side_nodes[(ncnt + 1)]].size(), pt_list.data());
/* If less than 2 ( 0 or 1 ) elements only
touch nodes 0 and ncnt+1 then try next side node, i.e.,
repeat loop ncnt */
if (nelem < 2) {
break;
}
nhold = nelem;
for (size_t i = 0; i < nelem; i++) {
hold_elem[i] = hold_elem[pt_list[i]];
}
}
}
/* If this element is a hex type */
else {
/*
* To handle hex's, check opposite corners. First check
* 1 and 3 and then 2 and 4. Only an element connected
* to this face will be connected to both corners. If there
* are tet's connected to this face, both will show up in
* one of the intersections (nothing will show up in the
* other intersection).
*/
/* See if hexes share nodes 0 and nodes (ncnt+2) */
int inode = 0;
for (int ncnt = 0; ncnt < nnodes; ncnt++) {
nelem =
find_inter(&graph->sur_elem[side_nodes[inode]][0],
&graph->sur_elem[side_nodes[(ncnt + 2)]][0],
graph->sur_elem[side_nodes[inode]].size(),
graph->sur_elem[side_nodes[(ncnt + 2)]].size(), pt_list.data());
/*
* If there are multiple elements in the intersection, then
* they must share the face, since the intersection is between
* the corner nodes. No element could connect with both of
* those nodes without being connected elsewhere.
*/
if (nelem > 1) {
/* Then get the correct elements out of the hold array */
for (size_t i = 0; i < nelem; i++) {
hold_elem[i] = graph->sur_elem[side_nodes[inode]][pt_list[i]];
}
break;
}
/*
* if there aren't multiple elements in the intersection,
* then check the opposite corners (1 & 3)
*/
inode = 1;
}
} /* "if (!hflag)" */
/*
* if there is an element on this side of ecnt, then there
* will be at least two elements in the intersection (one
* will be ecnt)
*/
if (nelem > 1) {
/*
* now go through and check each element in the list
* to see if it is different than ecnt.
*/
for (size_t i = 0; i < nelem; i++) {
size_t entry = hold_elem[i];
if (ecnt != entry) {
/*
* Need to verify that this side of ecnt is actually
* connected to a face of entry. The problem case is
* when an entire face of a shell (one of the ends)
* is connected to only an edge of a quad/tet
*/
E_Type etype2 = mesh->elem_type[entry];
/* make sure this is a 3d element*/
if (is_3d_element(etype2)) {
/* need to check for hex's */
hflag2 = is_hex(etype2);
/* TET10 cannot connect to a HEX */
tflag2 = is_tet(etype2);
/* check here for tet/hex combinations */
int sid;
if ((tflag1 && hflag2) || (hflag1 && tflag2)) {
/*
* have to call a special function to get the side id
* in these cases. In both cases, the number of side
* nodes for the element will not be consistent with
* side_cnt, and:
*
* TET/HEX - side_nodes only contains three of the
* the side nodes of the hex.
*
* HEX/TET - Have to check that this tet shares a side
* with the hex.
*/
sid = get_side_id_hex_tet(mesh->elem_type[entry], mesh->connect[entry],
side_cnt, side_nodes);
}
else {
/*
* get the side id of elem. Make sure that ecnt is
* trying to communicate to a valid side of elem
*/
side_cnt = get_ss_mirror(etype, side_nodes, (nscnt + 1), mirror_nodes);
/*
* small kludge to handle 6 node faces butted up against
* 4 node faces
*/
/* if this element 1 is a hexshell, then only
require 4 of the 6 nodes to match between elements
1 and 2 */
if (etype == HEXSHELL && side_cnt == 6) {
side_cnt = 4;
}
/* side_cnt is the number of nodes on the face
of a particular element. This number is passed
to get_side_id and the error with two hexes
only sharing 3 nodes is in get_side_id
Additional comments can be found there */
/*
* in order to get the correct side order for elem,
* get the mirror of the side of ecnt
*/
/* Based on elements intersecting, get the side
of element 1 that is connected to the element in the list
which it intersects with. The two elements must have
(originally) side_cnt nodes in common */
sid =
get_side_id(mesh->elem_type[entry], mesh->connect[entry], side_cnt,
mirror_nodes, problem->skip_checks, problem->partial_adj);
}
if (sid > 0) {
(graph->nadj)++;
graph->adj.push_back(entry);
if (weight->type & EDGE_WGT) {
/*
* the edge weight is the number of nodes in the
* connecting face
*/
weight->edges.push_back(side_cnt);
/*
* have to put a kluge in here for the
* tet/hex problem
*/
if (hflag1 && tflag2) {
(weight->edges.back())--;
}
}
}
else if ((sid < 0) && (!problem->skip_checks)) {
/*
* too many errors with bad meshes, print out
* more information here for diagnostics
*/
std::string tmpstr;
std::string cmesg;
cmesg = "Error returned while getting side id for communication map.";
Gen_Error(0, cmesg);
cmesg = fmt::format("Element 1: {}", (ecnt + 1));
Gen_Error(0, cmesg);
nnodes = get_elem_info(NNODES, etype);
cmesg = "connect table:";
for (int ii = 0; ii < nnodes; ii++) {
tmpstr = fmt::format(" {}", (size_t)(mesh->connect[ecnt][ii] + 1));
cmesg += tmpstr;
}
Gen_Error(0, cmesg);
cmesg = fmt::format("side id: {}", (nscnt + 1));
Gen_Error(0, cmesg);
cmesg = "side nodes:";
for (int ii = 0; ii < side_cnt; ii++) {
tmpstr = fmt::format(" {}", (size_t)(side_nodes[ii] + 1));
cmesg += tmpstr;
}
Gen_Error(0, cmesg);
cmesg = fmt::format("Element 2: {}", (entry + 1));
Gen_Error(0, cmesg);
nnodes = get_elem_info(NNODES, etype2);
cmesg = "connect table:";
for (int ii = 0; ii < nnodes; ii++) {
tmpstr = fmt::format(" {}", (size_t)(mesh->connect[entry][ii] + 1));
cmesg += tmpstr;
}
Gen_Error(0, cmesg);
count++;
fmt::print("Now we have {} bad element connections.\n", count);
} /* End "if (sid > 0)" */
} /* End: "if(ecnt != entry)" */
}
} /* End: "for(i=0; i < nelem; i++)" */
} /* End: "if (nelem > 1)" */
} /* End: "for (nscnt = 0; nscnt < nsides; nscnt++)" */
} /* End: "if(element_3d)" */
else {
/* this is either a 2d or 1d element. Only allow attachments to other
* 1d or 2d elements
*/
nnodes = get_elem_info(NNODES, mesh->elem_type[ecnt]);
for (int ncnt = 0; ncnt < nnodes; ncnt++) {
/* node is the node number 'ncnt' of element 'ecnt' */
size_t node = mesh->connect[ecnt][ncnt];
/* i varies from 0 -> # of elements touching 'node' */
for (size_t i = 0; i < graph->sur_elem[node].size(); i++) {
/* 'entry' is element # i touching node 'node' */
INT entry = graph->sur_elem[node][i];
/* make sure we're not checking if the element
is connected to itself */
if (ecnt != (size_t)entry) {
/* now make sure that the entry is not a 3d element */
if (!is_3d_element(mesh->elem_type[entry])) {
int iret;
if ((iret = in_list(entry, graph->adj.size() - graph->start[cnt],
&graph->adj[graph->start[cnt]])) < 0) {
(graph->nadj)++;
graph->adj.push_back(entry);
if (weight->type & EDGE_WGT) {
weight->edges.push_back(1.0);
}
}
else if (weight->type & EDGE_WGT) {
weight->edges[iret + (graph->start[cnt])] += 1.0F;
}
}
} /* End: if(ecnt != entry) */
} /* for(i=0; i < graph->nsur_elem[node]; i++) */
} /* End "for(ncnt=0; ...)" */
} /* End: "else" (if !element_3d) */
} /* End: "else" (if face_adj != 0) */
cnt++;
} /* End "if(etype != SPHERE)" */
} /* End "for(ecnt=0; ecnt < mesh->num_elems; ecnt++)" */
}
graph->start[problem->num_vertices] = graph->adj.size();
graph->nadj = graph->adj.size();
if ((size_t)graph->start[problem->num_vertices] != graph->nadj) {
// Possibly an integer overflow... Output error message and stop.
std::ostringstream errmsg;
fmt::print(errmsg,
"fatal: Graph adjacency edge count ({}) exceeds chaco 32-bit integer range.\n",
graph->nadj);
Gen_Error(0, errmsg.str());
return 0;
}
/* Adjust for a mesh with spheres */
if (problem->type == ELEMENTAL && sphere->num) {
/* Decrement adjacency entries */
for (auto &elem : graph->adj) {
for (size_t ecnt = 0; ecnt < mesh->num_el_blks; ecnt++) {
if (elem >= sphere->begin[ecnt] && elem < sphere->end[ecnt]) {
elem -= sphere->adjust[ecnt];
break;
}
}
}
}
return 1;
}
} // namespace