1 // Copyright(C) 1999-2020 National Technology & Engineering Solutions
2 // of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
3 // NTESS, the U.S. Government retains certain rights in this software.
4 //
5 // See packages/seacas/LICENSE for details
6 
7 #include <Ioss_Field.h> // for Field, etc
8 #include <Ioss_Map.h>
9 #include <Ioss_SmartAssert.h>
10 #include <Ioss_Sort.h>
11 #include <Ioss_Utils.h> // for IOSS_ERROR
12 #include <cstddef>      // for size_t
13 #include <fmt/ostream.h>
14 #include <iterator> // for insert_iterator, inserter
15 #include <numeric>
16 #include <sstream>
17 #include <string>
18 #include <sys/types.h> // for ssize_t
19 #include <vector>      // for vector, vector<>::iterator, etc
20 
21 namespace {
is_one2one(INT * ids,size_t num_to_get,size_t offset)22   template <typename INT> bool is_one2one(INT *ids, size_t num_to_get, size_t offset)
23   {
24     bool one2one    = true;
25     INT  map_offset = num_to_get > 0 ? ids[0] - 1 - offset : 0;
26     for (size_t i = 0; i < num_to_get; i++) {
27       if ((size_t)ids[i] != i + offset + 1 + map_offset) {
28         one2one = false;
29         break;
30       }
31     }
32     return one2one;
33   }
34 } // namespace
35 
release_memory()36 void Ioss::Map::release_memory()
37 {
38   IOSS_FUNC_ENTER(m_);
39   MapContainer().swap(m_map);
40   MapContainer().swap(m_reorder);
41   ReverseMapContainer().swap(m_reverse);
42 }
43 
44 // Determines whether the input map is sequential (m_map[i] == i)
is_sequential(bool check_all)45 bool Ioss::Map::is_sequential(bool check_all) const
46 {
47   // Assumes the_map runs from [1..size) Slot zero will contain -1 if the
48   // vector is sequential; 1 if not sequential, and 0 if it has not
49   // yet been determined...
50   // Once the the_map has been determined to be sequential/not-sequential,
51   // slot zero is set appropriately.
52   // 'sequential' is defined here to mean i==the_map[i] for all
53   // 0<i<the_map.size()
54 
55   // Arguably, an empty map is sequential...
56   if (m_map.empty()) {
57     return true;
58   }
59 
60   if (!check_all) {
61     // Check slot zero...
62     if (m_map[0] == -1) {
63       return true;
64     }
65     if (m_map[0] == 1) {
66       return false;
67     }
68   }
69 
70   IOSS_FUNC_ENTER(m_);
71   auto & new_map  = const_cast<Ioss::MapContainer &>(m_map);
72   size_t map_size = m_map.size();
73   for (int64_t i = 1; i < (int64_t)map_size; i++) {
74     if (m_map[i] != i + m_offset) {
75       new_map[0] = 1;
76       return false;
77     }
78   }
79   new_map[0] = -1;
80   return true;
81 }
82 
set_size(size_t entity_count)83 void Ioss::Map::set_size(size_t entity_count)
84 {
85   IOSS_FUNC_ENTER(m_);
86   if (m_map.empty()) {
87     m_map.resize(entity_count + 1);
88     set_is_sequential(true);
89   }
90 }
91 
build_reverse_map()92 void Ioss::Map::build_reverse_map() { build_reverse_map(m_map.size() - 1, 0); }
build_reverse_map_no_lock()93 void Ioss::Map::build_reverse_map_no_lock() { build_reverse_map__(m_map.size() - 1, 0); }
build_reverse_map(int64_t num_to_get,int64_t offset)94 void Ioss::Map::build_reverse_map(int64_t num_to_get, int64_t offset)
95 {
96   IOSS_FUNC_ENTER(m_);
97   build_reverse_map__(num_to_get, offset);
98 }
99 
build_reverse_map__(int64_t num_to_get,int64_t offset)100 void Ioss::Map::build_reverse_map__(int64_t num_to_get, int64_t offset)
101 {
102   // Stored as an unordered map -- key:global_id, value:local_id
103   if (is_sequential()) {
104     return;
105   }
106 
107   if (m_reverse.empty()) {
108     // This is first time that the m_reverse map is being built..
109     // m_map is no longer  1-to-1.
110     // Just iterate m_map and add all values that are non-zero
111     m_reverse.reserve(m_map.size());
112     for (size_t i = 1; i < m_map.size(); i++) {
113       if (m_map[i] != 0) {
114         bool ok = m_reverse.insert({m_map[i], i}).second;
115         if (!ok) {
116           std::ostringstream errmsg;
117           fmt::print(errmsg,
118                      "\nERROR: Duplicate {0} global id detected on processor {1}, filename '{2}'.\n"
119                      "       Global id {3} assigned to local {0}s {4} and {5}.\n",
120                      m_entityType, m_myProcessor, m_filename, m_map[i], i, m_reverse[m_map[i]]);
121           IOSS_ERROR(errmsg);
122         }
123       }
124     }
125   }
126   else {
127     m_reverse.reserve(m_reverse.size() + num_to_get);
128     for (int64_t i = 0; i < num_to_get; i++) {
129       int64_t local_id = offset + i + 1;
130       bool    ok       = m_reverse.insert({m_map[local_id], local_id}).second;
131       if (!ok) {
132         if (local_id != m_reverse[m_map[local_id]]) {
133           std::ostringstream errmsg;
134           fmt::print(errmsg,
135                      "\nERROR: Duplicate {0} global id detected on processor {1}, filename '{2}'.\n"
136                      "       Global id {3} assigned to local {0}s {4} and {5}.\n",
137                      m_entityType, m_myProcessor, m_filename, m_map[local_id], local_id,
138                      m_reverse[m_map[local_id]]);
139           IOSS_ERROR(errmsg);
140         }
141       }
142 
143       if (m_map[local_id] <= 0) {
144         std::ostringstream errmsg;
145         fmt::print(errmsg,
146                    "\nERROR: {0} map detected non-positive global id {1} for {0} with local id {2} "
147                    "on processor {3}.\n",
148                    m_entityType, m_map[local_id], local_id, m_myProcessor);
149         IOSS_ERROR(errmsg);
150       }
151     }
152   }
153 }
154 
155 template bool Ioss::Map::set_map(int *ids, size_t count, size_t offset, bool in_define_mode);
156 template bool Ioss::Map::set_map(int64_t *ids, size_t count, size_t offset, bool in_define_mode);
157 
158 template <typename INT>
set_map(INT * ids,size_t count,size_t offset,bool in_define_mode)159 bool Ioss::Map::set_map(INT *ids, size_t count, size_t offset, bool in_define_mode)
160 {
161   IOSS_FUNC_ENTER(m_);
162   if (in_define_mode && is_sequential()) {
163     // If the current map is one-to-one, check whether it will be one-to-one
164     // after adding these ids...
165     bool one2one = is_one2one(ids, count, offset);
166     if (one2one) {
167       // Further checks on how ids fit into previously set m_map entries (if any)
168       if (count > 0) {
169         INT tmp_offset = ids[0] - 1 - offset;
170         if (tmp_offset < 0 || (m_offset >= 0 && tmp_offset != m_offset)) {
171           one2one = false;
172         }
173       }
174     }
175 
176     if (!one2one) {
177       // Up to this point, the id map has been one-to-one.  Once we
178       // apply these `ids` to `m_map`, the map will no
179       // longer be one-to-one. The main consequence of this is that we
180       // now need an explicit reverseMap.  The reverseMap is built
181       // incrementally with the current range of 'ids', but before
182       // that can be done, need to build a reverseMap of the current
183       // one-to-one data...
184       set_is_sequential(false);
185       build_reverse_map__(m_map.size() - 1, 0);
186       m_offset = 0;
187     }
188     else {
189       // Map is sequential beginning at ids[0]
190       if (count > 0) {
191         m_offset = ids[0] - 1 - offset;
192       }
193     }
194   }
195 
196   bool changed = false; // True if redefining an entry
197   for (size_t i = 0; i < count; i++) {
198     int64_t local_id = offset + i + 1;
199     SMART_ASSERT((size_t)local_id < m_map.size())(local_id)(m_map.size());
200     if (m_map[local_id] > 0 && m_map[local_id] != ids[i]) {
201       changed = true;
202     }
203     m_map[local_id] = ids[i];
204     if (local_id != ids[i] - m_offset) {
205       set_is_sequential(false);
206     }
207     if (ids[i] <= 0) {
208       std::ostringstream errmsg;
209       fmt::print(errmsg,
210                  "\nERROR: {} mapping routines detected non-positive global id {}"
211                  " for local id {} on processor {}, filename '{}'.\n",
212                  m_entityType, ids[i], local_id, m_myProcessor, m_filename);
213       IOSS_ERROR(errmsg);
214     }
215   }
216 
217   if (in_define_mode) {
218     if (changed) {
219       m_reverse.clear();
220     }
221     build_reverse_map__(count, offset);
222   }
223   else if (changed) {
224     // Build the reorderEntityMap which does a direct mapping from
225     // the current topologies local order to the local order
226     // stored in the database if these two orders are different, that
227     // is if the ids order was redefined after the STATE_MODEL
228     // phase... This is 0-based and used for
229     // remapping output and input TRANSIENT fields.
230     build_reorder_map__(offset, count);
231   }
232   return changed;
233 }
234 
set_default(size_t count,size_t offset)235 void Ioss::Map::set_default(size_t count, size_t offset)
236 {
237   IOSS_FUNC_ENTER(m_);
238   m_map.resize(count + 1);
239   for (size_t i = 1; i <= count; i++) {
240     m_map[i] = i + offset;
241   }
242   set_is_sequential(true);
243 }
244 
245 #ifndef DOXYGEN_SKIP_THIS
246 template void Ioss::Map::reverse_map_data(int *data, size_t count) const;
247 template void Ioss::Map::reverse_map_data(int64_t *data, size_t count) const;
248 #endif
249 
reverse_map_data(INT * data,size_t count)250 template <typename INT> void Ioss::Map::reverse_map_data(INT *data, size_t count) const
251 {
252   IOSS_FUNC_ENTER(m_);
253   if (!is_sequential()) {
254     for (size_t i = 0; i < count; i++) {
255       INT global_id = data[i];
256       data[i]       = global_to_local__(global_id, true);
257     }
258   }
259   else if (m_offset != 0) {
260     for (size_t i = 0; i < count; i++) {
261       data[i] -= m_offset;
262     }
263   }
264 }
265 
reverse_map_data(void * data,const Ioss::Field & field,size_t count)266 void Ioss::Map::reverse_map_data(void *data, const Ioss::Field &field, size_t count) const
267 {
268   if (field.get_type() == Ioss::Field::INTEGER) {
269     int *connect = static_cast<int *>(data);
270     reverse_map_data(connect, count);
271   }
272   else {
273     auto *connect = static_cast<int64_t *>(data);
274     reverse_map_data(connect, count);
275   }
276 }
277 
278 #ifndef DOXYGEN_SKIP_THIS
279 template void Ioss::Map::map_data(int *data, size_t count) const;
280 template void Ioss::Map::map_data(int64_t *data, size_t count) const;
281 #endif
282 
map_data(INT * data,size_t count)283 template <typename INT> void Ioss::Map::map_data(INT *data, size_t count) const
284 {
285   IOSS_FUNC_ENTER(m_);
286   if (!is_sequential()) {
287     for (size_t i = 0; i < count; i++) {
288       data[i] = m_map[data[i]];
289     }
290   }
291   else if (m_offset != 0) {
292     for (size_t i = 0; i < count; i++) {
293       data[i] += m_offset;
294     }
295   }
296 }
297 
map_data(void * data,const Ioss::Field & field,size_t count)298 void Ioss::Map::map_data(void *data, const Ioss::Field &field, size_t count) const
299 {
300   if (field.get_type() == Ioss::Field::INTEGER) {
301     int *datum = static_cast<int *>(data);
302     map_data(datum, count);
303   }
304   else {
305     auto *datum = static_cast<int64_t *>(data);
306     map_data(datum, count);
307   }
308 }
309 
310 #ifndef DOXYGEN_SKIP_THIS
311 template void Ioss::Map::map_implicit_data(int *data, size_t count, size_t offset) const;
312 template void Ioss::Map::map_implicit_data(int64_t *data, size_t count, size_t offset) const;
313 #endif
314 
315 template <typename INT>
map_implicit_data(INT * ids,size_t count,size_t offset)316 void Ioss::Map::map_implicit_data(INT *ids, size_t count, size_t offset) const
317 {
318   // Map the "local" ids (offset+1..offset+count) to the global ids. The local
319   // ids are implicit
320   if (is_sequential()) {
321     for (size_t i = 0; i < count; i++) {
322       ids[i] = m_offset + offset + 1 + i;
323     }
324   }
325   else {
326     for (size_t i = 0; i < count; i++) {
327       ids[i] = m_map[offset + 1 + i];
328     }
329   }
330 }
331 
map_implicit_data(void * data,const Ioss::Field & field,size_t count,size_t offset)332 void Ioss::Map::map_implicit_data(void *data, const Ioss::Field &field, size_t count,
333                                   size_t offset) const
334 {
335   IOSS_FUNC_ENTER(m_);
336   if (field.get_type() == Ioss::Field::INTEGER) {
337     map_implicit_data(static_cast<int *>(data), count, offset);
338   }
339   else {
340     map_implicit_data(static_cast<int64_t *>(data), count, offset);
341   }
342 }
343 
344 template size_t Ioss::Map::map_field_to_db_scalar_order(double *             variables,
345                                                         std::vector<double> &db_var,
346                                                         size_t begin_offset, size_t count,
347                                                         size_t stride, size_t offset);
348 template size_t Ioss::Map::map_field_to_db_scalar_order(int *variables, std::vector<double> &db_var,
349                                                         size_t begin_offset, size_t count,
350                                                         size_t stride, size_t offset);
351 template size_t Ioss::Map::map_field_to_db_scalar_order(int64_t *            variables,
352                                                         std::vector<double> &db_var,
353                                                         size_t begin_offset, size_t count,
354                                                         size_t stride, size_t offset);
355 
356 template <typename T>
map_field_to_db_scalar_order(T * variables,std::vector<double> & db_var,size_t begin_offset,size_t count,size_t stride,size_t offset)357 size_t Ioss::Map::map_field_to_db_scalar_order(T *variables, std::vector<double> &db_var,
358                                                size_t begin_offset, size_t count, size_t stride,
359                                                size_t offset)
360 {
361   IOSS_FUNC_ENTER(m_);
362   size_t num_out = 0;
363   if (!m_reorder.empty()) {
364     size_t k = offset;
365     for (size_t j = begin_offset; j < count * stride; j += stride) {
366       // Map to storage location.
367       ssize_t where = m_reorder[k++] - offset;
368       if (where >= 0) {
369         SMART_ASSERT(where < (ssize_t)count)(where)(count);
370         db_var[where] = variables[j];
371         num_out++;
372       }
373     }
374   }
375   else {
376     size_t k = 0;
377     for (size_t j = begin_offset; j < count * stride; j += stride) {
378       // Map to storage location.
379       db_var[k++] = variables[j];
380     }
381     num_out = count;
382   }
383   return num_out;
384 }
385 
build_reorder_map__(int64_t start,int64_t count)386 void Ioss::Map::build_reorder_map__(int64_t start, int64_t count)
387 {
388   // This routine builds a map that relates the current node id order
389   // to the original node ordering in affect at the time the file was
390   // created. That is, the node map used to define the topology of the
391   // model.  Now, if there are changes in node ordering at the
392   // application level, we build the node reorder map to map the
393   // current order into the original order.  An added complication is
394   // that this is more than just a reordering... It may be that the
395   // application has 'ghosted' nodes that it doesn't want to put out on
396   // the database, so the reorder map must handle a node that is not
397   // in the original mesh and map that to an invalid value (currently
398   // using -1 as invalid value...)
399 
400   // Note: To further add confusion, the reorder map is 0-based
401   // and the reverse map and 'map' are 1-baed. This is
402   // just a consequence of how they are intended to be used...
403   //
404   // start is based on a 0-based array -- start of the reorderMap to build.
405 
406   if (m_reorder.empty()) {
407     // See if actually need a reorder map first...
408     bool    need_reorder_map = false;
409     int64_t my_end           = start + count;
410     for (int64_t i = start; i < my_end; i++) {
411       int64_t global_id     = m_map[i + 1];
412       int64_t orig_local_id = global_to_local__(global_id) - 1;
413 
414       // The reordering should only be a permutation of the original
415       // ordering within this entity block...
416       SMART_ASSERT(orig_local_id >= start && orig_local_id <= my_end)(orig_local_id)(start)(my_end);
417       if (i != orig_local_id) {
418         need_reorder_map = true;
419         break;
420       }
421     }
422     if (need_reorder_map) {
423       int64_t map_size = m_map.size() - 1;
424       m_reorder.resize(map_size);
425       // If building a partial reorder map, assume all entries
426       // are a direct 1-1 and then let the partial fills overwrite
427       // if needed.
428       std::iota(m_reorder.begin(), m_reorder.end(), 0);
429     }
430     else {
431       return;
432     }
433   }
434 
435   int64_t my_end = start + count;
436   for (int64_t i = start; i < my_end; i++) {
437     int64_t global_id     = m_map[i + 1];
438     int64_t orig_local_id = global_to_local__(global_id) - 1;
439 
440     // The reordering should only be a permutation of the original
441     // ordering within this entity block...
442     SMART_ASSERT(orig_local_id >= start && orig_local_id <= my_end)(orig_local_id)(start)(my_end);
443     m_reorder[i] = orig_local_id;
444   }
445 }
446 
447 // Node and Element mapping functions.  The ExodusII database
448 // stores ids in a local-id system (1..NUMNP), (1..NUMEL) but
449 // Sierra wants entities in a global system. These routines
450 // take care of the mapping from local <-> global
451 
global_to_local(int64_t global,bool must_exist)452 int64_t Ioss::Map::global_to_local(int64_t global, bool must_exist) const
453 {
454   IOSS_FUNC_ENTER(m_);
455   return global_to_local__(global, must_exist);
456 }
457 
global_to_local__(int64_t global,bool must_exist)458 int64_t Ioss::Map::global_to_local__(int64_t global, bool must_exist) const
459 {
460   int64_t local = global;
461   if (!is_sequential() && !m_reverse.empty()) {
462     // Possible for !is_sequential() which means non-one-to-one, but
463     // reverseMap is empty (which implied one-to-one) if the ORIGINAL mapping defined
464     // during dbState == STATE_MODEL was one-to-one, but there is a
465     // reordering which is due to new id ordering defined after STATE_MODEL...
466     auto iter = m_reverse.find(global);
467     if (iter != m_reverse.end()) {
468       local = iter->second;
469     }
470     else {
471       local = 0;
472     }
473   }
474   else if (!must_exist && global > static_cast<int64_t>(m_map.size()) - 1) {
475     local = 0;
476   }
477   else {
478     local = global - m_offset;
479   }
480   if (local > static_cast<int64_t>(m_map.size()) - 1) {
481     std::ostringstream errmsg;
482     fmt::print(errmsg,
483                "ERROR: Ioss Mapping routines detected {0} with global id equal to {1} returns a "
484                "local id of {2} which is\n"
485                "larger than the local {0} count {5} on processor {3}, filename '{4}'.\n"
486                "This should not happen, please report.\n",
487                m_entityType, global, local, m_myProcessor, m_filename, m_map.size() - 1);
488     IOSS_ERROR(errmsg);
489   }
490   else if (local <= 0 && must_exist) {
491     std::ostringstream errmsg;
492     fmt::print(errmsg,
493                "ERROR: Ioss Mapping routines could not find a {0} with global id equal to {1} in "
494                "the {0} map\n"
495                "on processor {2}, filename '{3}'.\n"
496                "This should not happen, please report.\n",
497                m_entityType, global, m_myProcessor, m_filename);
498     IOSS_ERROR(errmsg);
499   }
500   return local;
501 }
502