// Copyright(C) 1999-2021 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 // for Field, etc #include #include #include #include // for IOSS_ERROR #include // for size_t #include "vtk_fmt.h" #include VTK_FMT(fmt/ostream.h) #include // for insert_iterator, inserter #include #include #include #include // for vector, vector<>::iterator, etc namespace { template bool is_one2one(INT *ids, size_t num_to_get, size_t offset) { bool one2one = true; INT map_offset = num_to_get > 0 ? ids[0] - 1 - offset : 0; for (size_t i = 0; i < num_to_get; i++) { if ((size_t)ids[i] != i + offset + 1 + map_offset) { one2one = false; break; } } return one2one; } } // namespace void Ioss::Map::release_memory() { IOSS_FUNC_ENTER(m_); MapContainer().swap(m_map); MapContainer().swap(m_reorder); ReverseMapContainer().swap(m_reverse); } // Determines whether the input map is sequential (m_map[i] == i) bool Ioss::Map::is_sequential(bool check_all) const { // Assumes the_map runs from [1..size) Slot zero will contain -1 if the // vector is sequential; 1 if not sequential, and 0 if it has not // yet been determined... // Once the the_map has been determined to be sequential/not-sequential, // slot zero is set appropriately. // 'sequential' is defined here to mean i==the_map[i] for all // 0(m_map); size_t map_size = m_map.size(); for (int64_t i = 1; i < (int64_t)map_size; i++) { if (m_map[i] != i + m_offset) { new_map[0] = 1; return false; } } new_map[0] = -1; return true; } void Ioss::Map::set_size(size_t entity_count) { IOSS_FUNC_ENTER(m_); if (m_map.empty()) { m_map.resize(entity_count + 1); set_is_sequential(true); } } void Ioss::Map::build_reverse_map() { build_reverse_map(m_map.size() - 1, 0); } void Ioss::Map::build_reverse_map_no_lock() { build_reverse_map__(m_map.size() - 1, 0); } void Ioss::Map::build_reverse_map(int64_t num_to_get, int64_t offset) { IOSS_FUNC_ENTER(m_); build_reverse_map__(num_to_get, offset); } void Ioss::Map::build_reverse_map__(int64_t num_to_get, int64_t offset) { // Stored as an unordered map -- key:global_id, value:local_id if (is_sequential()) { return; } if (m_reverse.empty()) { // This is first time that the m_reverse map is being built.. // m_map is no longer 1-to-1. // Just iterate m_map and add all values that are non-zero m_reverse.reserve(m_map.size()); for (size_t i = 1; i < m_map.size(); i++) { if (m_map[i] != 0) { bool ok = m_reverse.insert({m_map[i], i}).second; if (!ok) { std::ostringstream errmsg; fmt::print(errmsg, "\nERROR: Duplicate {0} global id detected on processor {1}, filename '{2}'.\n" " Global id {3} assigned to local {0}s {4} and {5}.\n", m_entityType, m_myProcessor, m_filename, m_map[i], i, m_reverse[m_map[i]]); IOSS_ERROR(errmsg); } } } } else { m_reverse.reserve(m_reverse.size() + num_to_get); for (int64_t i = 0; i < num_to_get; i++) { int64_t local_id = offset + i + 1; bool ok = m_reverse.insert({m_map[local_id], local_id}).second; if (!ok) { if (local_id != m_reverse[m_map[local_id]]) { std::ostringstream errmsg; fmt::print(errmsg, "\nERROR: Duplicate {0} global id detected on processor {1}, filename '{2}'.\n" " Global id {3} assigned to local {0}s {4} and {5}.\n", m_entityType, m_myProcessor, m_filename, m_map[local_id], local_id, m_reverse[m_map[local_id]]); IOSS_ERROR(errmsg); } } if (m_map[local_id] <= 0) { std::ostringstream errmsg; fmt::print(errmsg, "\nERROR: {0} map detected non-positive global id {1} for {0} with local id {2} " "on processor {3}.\n", m_entityType, m_map[local_id], local_id, m_myProcessor); IOSS_ERROR(errmsg); } } } } template bool Ioss::Map::set_map(int *ids, size_t count, size_t offset, bool in_define_mode); template bool Ioss::Map::set_map(int64_t *ids, size_t count, size_t offset, bool in_define_mode); template bool Ioss::Map::set_map(INT *ids, size_t count, size_t offset, bool in_define_mode) { IOSS_FUNC_ENTER(m_); if (in_define_mode && is_sequential()) { // If the current map is one-to-one, check whether it will be one-to-one // after adding these ids... bool one2one = is_one2one(ids, count, offset); if (one2one) { // Further checks on how ids fit into previously set m_map entries (if any) if (count > 0) { INT tmp_offset = ids[0] - 1 - offset; if (tmp_offset < 0 || (m_offset >= 0 && tmp_offset != m_offset)) { one2one = false; } } } if (!one2one) { // Up to this point, the id map has been one-to-one. Once we // apply these `ids` to `m_map`, the map will no // longer be one-to-one. The main consequence of this is that we // now need an explicit reverseMap. The reverseMap is built // incrementally with the current range of 'ids', but before // that can be done, need to build a reverseMap of the current // one-to-one data... set_is_sequential(false); build_reverse_map__(m_map.size() - 1, 0); m_offset = 0; } else { // Map is sequential beginning at ids[0] if (count > 0) { m_offset = ids[0] - 1 - offset; } } } bool changed = false; // True if redefining an entry for (size_t i = 0; i < count; i++) { int64_t local_id = offset + i + 1; SMART_ASSERT((size_t)local_id < m_map.size())(local_id)(m_map.size()); if (m_map[local_id] > 0 && m_map[local_id] != ids[i]) { changed = true; } m_map[local_id] = ids[i]; if (local_id != ids[i] - m_offset) { set_is_sequential(false); } if (ids[i] <= 0) { std::ostringstream errmsg; fmt::print(errmsg, "\nERROR: {} mapping routines detected non-positive global id {}" " for local id {} on processor {}, filename '{}'.\n", m_entityType, ids[i], local_id, m_myProcessor, m_filename); IOSS_ERROR(errmsg); } } if (in_define_mode) { if (changed) { m_reverse.clear(); } build_reverse_map__(count, offset); } else if (changed) { // Build the reorderEntityMap which does a direct mapping from // the current topologies local order to the local order // stored in the database if these two orders are different, that // is if the ids order was redefined after the STATE_MODEL // phase... This is 0-based and used for // remapping output and input TRANSIENT fields. build_reorder_map__(offset, count); } return changed; } void Ioss::Map::set_default(size_t count, size_t offset) { IOSS_FUNC_ENTER(m_); m_map.resize(count + 1); for (size_t i = 1; i <= count; i++) { m_map[i] = i + offset; } set_is_sequential(true); } #ifndef DOXYGEN_SKIP_THIS template void Ioss::Map::reverse_map_data(int *data, size_t count) const; template void Ioss::Map::reverse_map_data(int64_t *data, size_t count) const; #endif template void Ioss::Map::reverse_map_data(INT *data, size_t count) const { IOSS_FUNC_ENTER(m_); if (!is_sequential()) { for (size_t i = 0; i < count; i++) { INT global_id = data[i]; data[i] = global_to_local__(global_id, true); } } else if (m_offset != 0) { for (size_t i = 0; i < count; i++) { data[i] -= m_offset; } } } void Ioss::Map::reverse_map_data(void *data, const Ioss::Field &field, size_t count) const { if (field.get_type() == Ioss::Field::INTEGER) { int *connect = static_cast(data); reverse_map_data(connect, count); } else { auto *connect = static_cast(data); reverse_map_data(connect, count); } } #ifndef DOXYGEN_SKIP_THIS template void Ioss::Map::map_data(int *data, size_t count) const; template void Ioss::Map::map_data(int64_t *data, size_t count) const; #endif template void Ioss::Map::map_data(INT *data, size_t count) const { IOSS_FUNC_ENTER(m_); if (!is_sequential()) { for (size_t i = 0; i < count; i++) { data[i] = m_map[data[i]]; } } else if (m_offset != 0) { for (size_t i = 0; i < count; i++) { data[i] += m_offset; } } } void Ioss::Map::map_data(void *data, const Ioss::Field &field, size_t count) const { if (field.get_type() == Ioss::Field::INTEGER) { int *datum = static_cast(data); map_data(datum, count); } else { auto *datum = static_cast(data); map_data(datum, count); } } #ifndef DOXYGEN_SKIP_THIS template void Ioss::Map::map_implicit_data(int *data, size_t count, size_t offset) const; template void Ioss::Map::map_implicit_data(int64_t *data, size_t count, size_t offset) const; #endif template void Ioss::Map::map_implicit_data(INT *ids, size_t count, size_t offset) const { // Map the "local" ids (offset+1..offset+count) to the global ids. The local // ids are implicit if (is_sequential()) { for (size_t i = 0; i < count; i++) { ids[i] = m_offset + offset + 1 + i; } } else { for (size_t i = 0; i < count; i++) { ids[i] = m_map[offset + 1 + i]; } } } void Ioss::Map::map_implicit_data(void *data, const Ioss::Field &field, size_t count, size_t offset) const { IOSS_FUNC_ENTER(m_); if (field.get_type() == Ioss::Field::INTEGER) { map_implicit_data(static_cast(data), count, offset); } else { map_implicit_data(static_cast(data), count, offset); } } template size_t Ioss::Map::map_field_to_db_scalar_order(double *variables, std::vector &db_var, size_t begin_offset, size_t count, size_t stride, size_t offset); template size_t Ioss::Map::map_field_to_db_scalar_order(int *variables, std::vector &db_var, size_t begin_offset, size_t count, size_t stride, size_t offset); template size_t Ioss::Map::map_field_to_db_scalar_order(int64_t *variables, std::vector &db_var, size_t begin_offset, size_t count, size_t stride, size_t offset); template size_t Ioss::Map::map_field_to_db_scalar_order(T *variables, std::vector &db_var, size_t begin_offset, size_t count, size_t stride, size_t offset) { IOSS_FUNC_ENTER(m_); size_t num_out = 0; if (!m_reorder.empty()) { size_t k = offset; for (size_t j = begin_offset; j < count * stride; j += stride) { // Map to storage location. int64_t where = m_reorder[k++] - offset; if (where >= 0) { SMART_ASSERT(where < (int64_t)count)(where)(count); db_var[where] = variables[j]; num_out++; } } } else { size_t k = 0; for (size_t j = begin_offset; j < count * stride; j += stride) { // Map to storage location. db_var[k++] = variables[j]; } num_out = count; } return num_out; } void Ioss::Map::build_reorder_map__(int64_t start, int64_t count) { // This routine builds a map that relates the current node id order // to the original node ordering in affect at the time the file was // created. That is, the node map used to define the topology of the // model. Now, if there are changes in node ordering at the // application level, we build the node reorder map to map the // current order into the original order. An added complication is // that this is more than just a reordering... It may be that the // application has 'ghosted' nodes that it doesn't want to put out on // the database, so the reorder map must handle a node that is not // in the original mesh and map that to an invalid value (currently // using -1 as invalid value...) // Note: To further add confusion, the reorder map is 0-based // and the reverse map and 'map' are 1-baed. This is // just a consequence of how they are intended to be used... // // start is based on a 0-based array -- start of the reorderMap to build. if (m_reorder.empty()) { // See if actually need a reorder map first... bool need_reorder_map = false; int64_t my_end = start + count; for (int64_t i = start; i < my_end; i++) { int64_t global_id = m_map[i + 1]; int64_t orig_local_id = global_to_local__(global_id) - 1; // The reordering should only be a permutation of the original // ordering within this entity block... SMART_ASSERT(orig_local_id >= start && orig_local_id <= my_end)(orig_local_id)(start)(my_end); if (i != orig_local_id) { need_reorder_map = true; break; } } if (need_reorder_map) { int64_t map_size = m_map.size() - 1; m_reorder.resize(map_size); // If building a partial reorder map, assume all entries // are a direct 1-1 and then let the partial fills overwrite // if needed. std::iota(m_reorder.begin(), m_reorder.end(), 0); } else { return; } } int64_t my_end = start + count; for (int64_t i = start; i < my_end; i++) { int64_t global_id = m_map[i + 1]; int64_t orig_local_id = global_to_local__(global_id) - 1; // The reordering should only be a permutation of the original // ordering within this entity block... SMART_ASSERT(orig_local_id >= start && orig_local_id <= my_end)(orig_local_id)(start)(my_end); m_reorder[i] = orig_local_id; } } // Node and Element mapping functions. The ExodusII database // stores ids in a local-id system (1..NUMNP), (1..NUMEL) but // Sierra wants entities in a global system. These routines // take care of the mapping from local <-> global int64_t Ioss::Map::global_to_local(int64_t global, bool must_exist) const { IOSS_FUNC_ENTER(m_); return global_to_local__(global, must_exist); } int64_t Ioss::Map::global_to_local__(int64_t global, bool must_exist) const { int64_t local = global; if (!is_sequential() && !m_reverse.empty()) { // Possible for !is_sequential() which means non-one-to-one, but // reverseMap is empty (which implied one-to-one) if the ORIGINAL mapping defined // during dbState == STATE_MODEL was one-to-one, but there is a // reordering which is due to new id ordering defined after STATE_MODEL... auto iter = m_reverse.find(global); if (iter != m_reverse.end()) { local = iter->second; } else { local = 0; } } else if (!must_exist && global > static_cast(m_map.size()) - 1) { local = 0; } else { local = global - m_offset; } if (local > static_cast(m_map.size()) - 1) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Ioss Mapping routines detected {0} with global id equal to {1} returns a " "local id of {2} which is\n" "larger than the local {0} count {5} on processor {3}, filename '{4}'.\n" "This should not happen, please report.\n", m_entityType, global, local, m_myProcessor, m_filename, m_map.size() - 1); IOSS_ERROR(errmsg); } else if (local <= 0 && must_exist) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Ioss Mapping routines could not find a {0} with global id equal to {1} in " "the {0} map\n" "on processor {2}, filename '{3}'.\n" "This should not happen, please report.\n", m_entityType, global, m_myProcessor, m_filename); IOSS_ERROR(errmsg); } return local; }