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