// Copyright(C) 1999-2023 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 #include "EJ_mapping.h" #include "Ioss_ElementBlock.h" // for ElementBlock #include "Ioss_GroupingEntity.h" // for GroupingEntity #include "Ioss_NodeBlock.h" // for NodeBlock #include "Ioss_Property.h" // for Property #include "Ioss_Region.h" // for Region, etc #include "Ioss_SmartAssert.h" #include "Ioss_Sort.h" #include // for sort, unique #include // for size_t #include #include #include // for make_pair, pair namespace { bool entity_is_omitted(const Ioss::GroupingEntity *block) { bool omitted = block->get_optional_property("omitted", 0) == 1; return omitted; } } // namespace template void eliminate_omitted_nodes(RegionVector &part_mesh, std::vector &global_node_map, std::vector &local_node_map, bool fill_global) { size_t offset = 0; size_t j = 0; size_t part_count = part_mesh.size(); for (size_t p = 0; p < part_count; p++) { bool has_omissions = part_mesh[p]->get_property("block_omission_count").get_int() > 0; const Ioss::NodeBlock *nb = part_mesh[p]->get_node_blocks()[0]; size_t loc_size = nb->entity_count(); if (has_omissions) { // If there are any omitted element blocks for this part, don't // map the nodes that are only connected to omitted element // blocks. std::vector node_status; nb->get_field_data("node_connectivity_status", node_status); for (size_t i = 0; i < node_status.size(); i++) { if (node_status[i] != 1) { local_node_map[offset + i] = j; if (fill_global) { global_node_map.push_back(j + 1); } j++; } else { local_node_map[offset + i] = -1; } } } else { for (size_t i = 0; i < loc_size; i++) { local_node_map[offset + i] = j; if (fill_global) { global_node_map.push_back(j + 1); } j++; } } offset += loc_size; } } template void eliminate_omitted_nodes(RegionVector &part_mesh, std::vector &global_node_map, std::vector &local_node_map, bool fill_global); template void eliminate_omitted_nodes(RegionVector &part_mesh, std::vector &global_node_map, std::vector &local_node_map, bool fill_global); template void build_reverse_node_map(Ioss::Region & /*global*/, RegionVector &part_mesh, std::vector &global_node_map, std::vector &local_node_map) { // Instead of using and , consider using a sorted vector... // Append all local node maps to the global node map. // Sort the global node map // Remove duplicates. // Position within map is now the map... // When building the local-part node to global id, use binary_search... size_t part_count = part_mesh.size(); // Global node map and count. std::vector> global_nodes(part_count); size_t tot_size = 0; for (size_t p = 0; p < part_count; p++) { const Ioss::NodeBlock *nb = part_mesh[p]->get_node_blocks()[0]; size_t loc_size = nb->entity_count(); tot_size += loc_size; global_nodes[p].resize(loc_size); } global_node_map.resize(tot_size); size_t offset = 0; bool any_omitted_nodes = false; for (size_t p = 0; p < part_count; p++) { const Ioss::NodeBlock *nb = part_mesh[p]->get_node_blocks()[0]; nb->get_field_data("ids", global_nodes[p]); // If there are any omitted element blocks for this part, set // the global id of any nodes that are only connected to omitted // element blocks to 0. bool has_omissions = part_mesh[p]->get_property("block_omission_count").get_int() > 0; if (has_omissions) { std::vector node_status; nb->get_field_data("node_connectivity_status", node_status); for (size_t i = 0; i < node_status.size(); i++) { if (node_status[i] == 1) { any_omitted_nodes = true; global_nodes[p][i] = 0; } } } std::copy(global_nodes[p].begin(), global_nodes[p].end(), &global_node_map[offset]); offset += global_nodes[p].size(); } // Now, sort the global_node_map array and remove duplicates... Ioss::Utils::uniquify(global_node_map); // If any omitted nodes, remove them from the global_node_map. // The id will be 0 if (any_omitted_nodes) { auto pos = std::remove(global_node_map.begin(), global_node_map.end(), 0); global_node_map.erase(pos, global_node_map.end()); } size_t output_node_count = global_node_map.size(); // See whether the node numbers are contiguous. If so, we can map // the nodes back to their original location. Since the nodes are // sorted and there are no duplicates, we just need to see if the id // at global_node_map.size() == global_node_map.size(); size_t max_id = global_node_map[output_node_count - 1]; bool is_contiguous = max_id == output_node_count; fmt::print("Node map {} contiguous.\n", (is_contiguous ? "is" : "is not")); // Create the map that maps from a local part node to the // global map. This combines the mapping local part node to // 'global id' and then 'global id' to global position. The // mapping is now a direct lookup instead of a lookup followed by // a reverse map. auto cur_pos = global_node_map.begin(); for (size_t p = 0; p < part_count; p++) { size_t noffset = part_mesh[p]->get_property("node_offset").get_int(); size_t node_count = global_nodes[p].size(); for (size_t i = 0; i < node_count; i++) { INT global_node = global_nodes[p][i]; if (global_node > 0) { if (cur_pos == global_node_map.end() || *cur_pos != global_node) { auto iter = std::lower_bound(global_node_map.begin(), global_node_map.end(), global_node); if (iter == global_node_map.end()) { fmt::print("{}\n", fmt::group_digits(global_node)); SMART_ASSERT(iter != global_node_map.end()); } cur_pos = iter; } size_t nodal_value = cur_pos - global_node_map.begin(); local_node_map[noffset + i] = nodal_value; ++cur_pos; } else { local_node_map[noffset + i] = -1; } } } // Update the nodal ids to give a unique, non-repeating set. If contiguous, then // there is nothing to do. If not contiguous, then need to determine if there are any // repeats (id reuse) and if so, generate a new id for the repeated uses. if (!is_contiguous) { bool repeat_found = false; INT id_last = global_node_map[0]; for (size_t i = 1; i < output_node_count; i++) { if (global_node_map[i] == id_last) { global_node_map[i] = ++max_id; repeat_found = true; } else { id_last = global_node_map[i]; } } if (repeat_found) { fmt::print("Duplicate node ids were found. Their ids have been renumbered to remove " "duplicates.\n"); } } } template void build_reverse_node_map(Ioss::Region &global, RegionVector &part_mesh, std::vector &global_node_map, std::vector &local_node_map); template void build_reverse_node_map(Ioss::Region &global, RegionVector &part_mesh, std::vector &global_node_map, std::vector &local_node_map); template void build_local_element_map(RegionVector &part_mesh, std::vector &local_element_map) { size_t global = 0; size_t offset = 0; for (auto &p : part_mesh) { const Ioss::ElementBlockContainer &ebs = p->get_element_blocks(); Ioss::ElementBlockContainer::const_iterator i = ebs.begin(); while (i != ebs.end()) { const auto *eb = *i++; size_t num_elem = eb->entity_count(); if (entity_is_omitted(eb)) { // Fill local_element_map with -1 for the omitted elements. for (size_t j = 0; j < num_elem; j++) { local_element_map[offset + j] = -1; } } else { for (size_t j = 0; j < num_elem; j++) { local_element_map[offset + j] = global++; } } offset += num_elem; } } } template void build_local_element_map(RegionVector &part_mesh, std::vector &local_element_map); template void build_local_element_map(RegionVector &part_mesh, std::vector &local_element_map); template void generate_element_ids(RegionVector &part_mesh, const std::vector &local_element_map, std::vector &global_element_map) { // Follow same logic as 'build_local_element_map' to ensure elements // are processed in same order. // Many models do not use the element number map at all, so they // will have a 1..numel map. If all parts have that, then we don't // want to do any fancy duplicate removal and other processing, just // output a 1..numel map for the output mesh... We still generate // the global_element_map, but check whether any of the part blocks // have a non-1..numel map... bool has_map = false; size_t offset = 0; for (auto &p : part_mesh) { const Ioss::ElementBlockContainer &ebs = p->get_element_blocks(); Ioss::ElementBlockContainer::const_iterator i = ebs.begin(); while (i != ebs.end()) { Ioss::ElementBlock *eb = *i++; INT num_elem = eb->entity_count(); if (!entity_is_omitted(eb)) { std::vector part_ids; eb->get_field_data("ids", part_ids); if (!has_map) { INT eb_offset = eb->get_offset(); for (INT j = 0; j < num_elem; j++) { if (part_ids[j] != eb_offset + j + 1) { has_map = true; break; } } } for (INT j = 0; j < num_elem; j++) { INT gpos = local_element_map[offset + j]; if (gpos >= 0) { global_element_map[gpos] = part_ids[j]; } } } offset += num_elem; } } // Check for duplicates... // NOTE: Used to use an indexed sort here, but if there was a // duplicate id, it didn't really care whether part 1 or part N's // index came first which causes really screwy element maps. // Instead, lets sort a vector containing pairs of where // the index will always? increase for increasing part numbers... if (has_map) { std::vector> index(global_element_map.size()); for (size_t i = 0; i < index.size(); i++) { index[i] = std::make_pair(global_element_map[i], (INT)i); } Ioss::sort(index.begin(), index.end()); INT max_id = index[index.size() - 1].first + 1; size_t beg = 0; for (size_t i = 1; i < index.size(); i++) { if (index[beg].first == index[i].first) { // Duplicate found... Assign it a new id greater than any // existing id... (What happens if we exceed INT_MAX?) global_element_map[index[i].second] = max_id++; // Keep 'beg' the same in case multiple duplicate of this value. } else { beg = i; } } } else { INT one = 1; std::iota(global_element_map.begin(), global_element_map.end(), one); } } template void generate_element_ids(RegionVector &part_mesh, const std::vector &local_element_map, std::vector &global_element_map); template void generate_element_ids(RegionVector &part_mesh, const std::vector &local_element_map, std::vector &global_element_map);