1 // Copyright(C) 1999-2021 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_CodeTypes.h>
8 #include <Ioss_SubSystem.h>
9 #include <Ioss_Utils.h>
10 
11 #include <algorithm>
12 #include <cassert>
13 #include <cctype>
14 #include <chrono>
15 #include <cstdint>
16 #include <cstdlib>
17 #include <cstring>
18 #include <fmt/chrono.h>
19 #include <fmt/format.h>
20 #include <fmt/ostream.h>
21 #include <fstream>
22 #include <sstream>
23 #include <string>
24 #include <tokenize.h>
25 #include <vector>
26 
27 #ifndef _WIN32
28 #include <sys/ioctl.h>
29 #include <sys/utsname.h>
30 #endif
31 
32 #ifndef _POSIX_SOURCE
33 #define _POSIX_SOURCE
34 #endif
35 #include <cstdio>
36 
37 #if defined(_MSC_VER)
38 #include <io.h>
39 #define isatty _isatty
40 #endif
41 
42 // For memory utilities...
43 #if defined(_WIN32)
44 #if 0
45 #define WIN32_LEAN_AND_MEAN
46 #define NOMINMAX
47 #include <psapi.h>
48 #include <windows.h>
49 #endif
50 
51 #elif defined(__unix__) || defined(__unix) || defined(unix) ||                                     \
52     (defined(__APPLE__) && defined(__MACH__))
53 #include <sys/resource.h>
54 #include <unistd.h>
55 
56 #if defined(__APPLE__) && defined(__MACH__)
57 #include <mach/mach.h>
58 
59 #elif (defined(_AIX) || defined(__TOS__AIX__)) ||                                                  \
60     (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
61 #include <fcntl.h>
62 #include <procfs.h>
63 
64 #elif defined(__linux__) || defined(__linux) || defined(linux) || defined(__gnu_linux__)
65 #include <stdio.h>
66 #endif
67 #endif
68 
69 #if defined(BGQ_LWK) && defined(__linux__)
70 #include <spi/include/kernel/location.h>
71 #include <spi/include/kernel/memory.h>
72 #endif
73 
74 std::ostream *Ioss::Utils::m_warningStream  = &std::cerr;
75 std::ostream *Ioss::Utils::m_debugStream    = &std::cerr;
76 std::ostream *Ioss::Utils::m_outputStream   = &std::cerr;
77 std::string   Ioss::Utils::m_preWarningText = "\nIOSS WARNING: ";
78 
79 namespace {
80   auto initial_time = std::chrono::steady_clock::now();
81 
82   template <typename INT>
83   void set_owned_node_count(Ioss::Region &region, int my_processor, INT dummy);
84 
85   ////////////////////////////////////////////////////////////////////////
is_separator(const char separator,const char value)86   bool is_separator(const char separator, const char value) { return separator == value; }
87 
match(const char * name1,const char * name2)88   size_t match(const char *name1, const char *name2)
89   {
90     size_t l1  = std::strlen(name1);
91     size_t l2  = std::strlen(name2);
92     size_t len = l1 < l2 ? l1 : l2;
93     for (size_t i = 0; i < len; i++) {
94       if (name1[i] != name2[i]) {
95         while (i > 0 && (isdigit(name1[i - 1]) != 0) && (isdigit(name2[i - 1]) != 0)) {
96           i--;
97           // Back up to first non-digit so to handle "evar0000, evar0001, ..., evar 1123"
98         }
99         return i;
100       }
101     }
102     return len;
103   }
104 
105   // Split 'str' into 'tokens' based on the 'separator' character.
106   // If 'str' starts with 1 or more 'separator', they are part of the
107   // first token and not used for splitting.  If there are multiple
108   // 'separator' characters in a row, then the first is used to split
109   // and the subsequent 'separator' characters are put as leading
110   // characters of the next token.
111   // `__this___is_a_string__for_tokens` will split to 6 tokens:
112   // `__this` `__is` `a` `string` `_for` `tokens`
field_tokenize(const std::string & str,const char separator,std::vector<std::string> & tokens)113   void field_tokenize(const std::string &str, const char separator,
114                       std::vector<std::string> &tokens)
115   {
116     std::string curr_token;
117     // Skip leading separators...
118     size_t i = 0;
119     while (i < str.length() && is_separator(separator, str[i])) {
120       curr_token += str[i++];
121     }
122     for (; i < str.length(); ++i) {
123       char curr_char = str[i];
124 
125       // determine if current character is a separator
126       bool is_sep = is_separator(separator, curr_char);
127       if (is_sep && curr_token != "") {
128         // we just completed a token
129         tokens.push_back(curr_token);
130         curr_token.clear();
131         while (i++ < str.length() && is_separator(separator, str[i])) {
132           curr_token += str[i];
133         }
134         i--;
135       }
136       else if (!is_sep) {
137         curr_token += curr_char;
138       }
139     }
140     if (curr_token != "") {
141       tokens.push_back(curr_token);
142     }
143   }
144 } // namespace
145 
time_and_date(char * time_string,char * date_string,size_t length)146 void Ioss::Utils::time_and_date(char *time_string, char *date_string, size_t length)
147 {
148   std::time_t t    = std::time(nullptr);
149   std::string time = fmt::format("{:%H:%M:%S}", fmt::localtime(t));
150   std::string date;
151   if (length >= 10) {
152     date = fmt::format("{:%Y/%m/%d}", fmt::localtime(t));
153   }
154   else {
155     date = fmt::format("{:%y/%m/%d}", fmt::localtime(t));
156   }
157   copy_string(time_string, time, 9);
158   copy_string(date_string, date, length + 1);
159 }
160 
check_non_null(void * ptr,const char * type,const std::string & name,const std::string & func)161 void Ioss::Utils::check_non_null(void *ptr, const char *type, const std::string &name,
162                                  const std::string &func)
163 {
164   if (ptr == nullptr) {
165     std::ostringstream errmsg;
166     fmt::print(errmsg,
167                "INTERNAL ERROR: Could not find {} '{}'. Something is wrong in {}. Please report.\n",
168                type, name, func);
169     IOSS_ERROR(errmsg);
170   }
171 }
172 
decode_filename(const std::string & filename,int processor,int num_processors)173 std::string Ioss::Utils::decode_filename(const std::string &filename, int processor,
174                                          int num_processors)
175 {
176   // Current format for per-processor file names is:
177   // PREFIX/basename.num_proc.cur_proc
178   // the 'cur_proc' field is padded to be the same width as
179   // the 'num_proc' field
180   // Examples: basename.8.1, basename.64.03, basename.128.001
181 
182   // Create a std::string containing the total number of processors
183   if (num_processors > 1) {
184     size_t proc_width = number_width(num_processors);
185 
186     std::string decoded_filename =
187         fmt::format("{}.{}.{:0{}}", filename, num_processors, processor, proc_width);
188     return decoded_filename;
189   }
190   return filename;
191 }
192 
get_number(const std::string & suffix)193 size_t Ioss::Utils::get_number(const std::string &suffix)
194 {
195   int  N       = 0;
196   bool all_dig = suffix.find_first_not_of("0123456789") == std::string::npos;
197   if (all_dig) {
198     N = std::stoi(suffix);
199   }
200   return N;
201 }
202 
extract_id(const std::string & name_id)203 int64_t Ioss::Utils::extract_id(const std::string &name_id)
204 {
205   int64_t id = 0;
206 
207   std::vector<std::string> tokens = Ioss::tokenize(name_id, "_");
208   if (tokens.size() > 1) {
209     // Check whether last token is an integer...
210     std::string str_id = tokens.back();
211     id                 = get_number(str_id);
212   }
213   return id;
214 }
215 
format_id_list(const std::vector<size_t> & ids,const std::string & rng_sep,const std::string & seq_sep)216 std::string Ioss::Utils::format_id_list(const std::vector<size_t> &ids, const std::string &rng_sep,
217                                         const std::string &seq_sep)
218 {
219   // Based on function from cubit (but I wrote original cubit version long time ago... ;-)
220   if (ids.empty()) {
221     return "";
222   }
223 
224   // PRECONDITION: `ids` is monotonically increasing -- will throw IOSS_ERROR if violated.
225   if (!std::is_sorted(ids.begin(), ids.end(), [](size_t a, size_t b) { return a <= b; })) {
226     std::ostringstream errmsg;
227     fmt::print(errmsg,
228                "INTERNAL ERROR: ({}) The `ids` vector is not in monotonically increasing order as "
229                "required.\n",
230                __func__);
231     IOSS_ERROR(errmsg);
232   }
233 
234   size_t             num = 0;
235   std::ostringstream ret_str;
236   while (num < ids.size()) {
237     fmt::print(ret_str, "{}{}", num == 0 ? "" : seq_sep, ids[num]);
238     size_t begin    = ids[num]; // first id in range of 1 or more ids
239     size_t previous = ids[num]; // last id in range of 1 or more ids
240     // Gather a range or single value... (begin .. previous)
241     while (previous == ids[num] && ++num < ids.size() && ids[num] == previous + 1) {
242       previous++;
243     }
244 
245     if (begin != previous) {
246       fmt::print(ret_str, "{}{}", previous == begin + 1 ? seq_sep : rng_sep, previous);
247     }
248   }
249   return ret_str.str();
250 }
251 
encode_entity_name(const std::string & entity_type,int64_t id)252 std::string Ioss::Utils::encode_entity_name(const std::string &entity_type, int64_t id)
253 {
254   // ExodusII stores block, nodeset, and sideset ids as integers
255   // Sierra   stores these as std::strings. The string is created by
256   // concatenating the type, the character '_' and the id.
257 
258   return fmt::format("{}_{}", entity_type, id);
259 }
260 
get_name_array(size_t count,int size)261 char **Ioss::Utils::get_name_array(size_t count, int size)
262 {
263   auto names = new char *[count];
264   for (size_t i = 0; i < count; i++) {
265     names[i] = new char[size + 1];
266     std::memset(names[i], '\0', size + 1);
267   }
268   return names;
269 }
270 
delete_name_array(char ** names,int count)271 void Ioss::Utils::delete_name_array(char **names, int count)
272 {
273   for (int i = 0; i < count; i++) {
274     delete[] names[i];
275   }
276   delete[] names;
277 }
278 
fixup_type(const std::string & base,int nodes_per_element,int spatial)279 std::string Ioss::Utils::fixup_type(const std::string &base, int nodes_per_element, int spatial)
280 {
281   std::string type = base;
282   Ioss::Utils::fixup_name(type); // Convert to lowercase; replace spaces with '_'
283 
284   // Fixup an exodusII kluge/ambiguity.
285   // The element block type does not fully define the element. For
286   // example, a block of type 'triangle' may have either 3 or 6
287   // nodes.  To fix this, check the block type name and see if it
288   // ends with a number.  If it does, assume it is OK; if not, append
289   // the 'nodes_per_element'.
290   if (isdigit(*(type.rbegin())) == 0) {
291     if (nodes_per_element > 1) {
292       type += std::to_string(nodes_per_element);
293     }
294   }
295 
296   // Fixup an exodusII kludge.  For triangular elements, the same
297   // name is used for 2D elements and 3D shell elements.  Convert
298   // to unambiguous names for the IO Subsystem.  The 2D name
299   // stays the same, the 3D name becomes 'trishell#'
300   if (spatial == 3) {
301     if (type == "triangle3") {
302       type = "trishell3";
303     }
304     else if (type == "triangle4") {
305       type = "trishell4";
306     }
307     else if (type == "triangle6") {
308       type = "trishell6";
309     }
310     else if (type == "tri3") {
311       type = "trishell3";
312     }
313     else if (type == "tri4") {
314       type = "trishell4";
315     }
316     else if (type == "tri6") {
317       type = "trishell6";
318     }
319   }
320 
321   if (spatial == 2) {
322     if (type == "shell2") {
323       type = "shellline2d2";
324     }
325     else if (type == "rod2" || type == "bar2" || type == "truss2") {
326       type = "rod2d2";
327     }
328     else if (type == "shell3") {
329       type = "shellline2d3";
330     }
331     else if (type == "bar3" || type == "rod3" || type == "truss3") {
332       type = "rod2d3";
333     }
334     else if (type == "bar4" || type == "rod4" || type == "truss4") {
335       type = "rod2d4";
336     }
337   }
338 
339   if (Ioss::Utils::substr_equal("super", type)) {
340     // A super element can have a varying number of nodes.  Create
341     // an IO element type for this super element just so the IO
342     // system can read a mesh containing super elements.  This
343     // allows the "omit volume" command to be used in the Sierra
344     // applications to skip creating a corresponding element block
345     // in the application.
346     type = "super" + std::to_string(nodes_per_element);
347   }
348   return type;
349 }
350 
local_filename(const std::string & relative_filename,const std::string & type,const std::string & working_directory)351 std::string Ioss::Utils::local_filename(const std::string &relative_filename,
352                                         const std::string &type,
353                                         const std::string &working_directory)
354 {
355   if (relative_filename[0] == '/' || type == "generated" || working_directory.empty()) {
356     return relative_filename;
357   }
358   std::string filename = working_directory;
359   filename += relative_filename;
360   return filename;
361 }
362 
field_warning(const Ioss::GroupingEntity * ge,const Ioss::Field & field,const std::string & inout)363 int Ioss::Utils::field_warning(const Ioss::GroupingEntity *ge, const Ioss::Field &field,
364                                const std::string &inout)
365 {
366   fmt::print(Ioss::WARNING(), "{} '{}'. Unknown {} field '{}'\n", ge->type_string(), ge->name(),
367              inout, field.get_name());
368   return -4;
369 }
370 
371 namespace {
match_composite_field(char ** names,Ioss::IntVector & which_names,const char suffix_separator)372   const Ioss::VariableType *match_composite_field(char **names, Ioss::IntVector &which_names,
373                                                   const char suffix_separator)
374   {
375     // ASSUME: Fields are in order...
376     // The field we are trying to match will be a composite field of
377     // type base_x_1, base_y_1, base_z_1, ...., base_y_3, base_z_3.
378     // The composite field type currently always has a numeric Real[N]
379     // type field as the last suffix and the other field as the first
380     // suffix.
381     // If we take the last suffix of the last name, it should give us
382     // the 'N' in the Real[N] field.  Dividing 'which_names.size()' by
383     // 'N' will give the number of components in the inner field.
384 
385     char suffix[2] = {suffix_separator, '\0'};
386 
387     std::vector<std::string> tokens =
388         Ioss::tokenize(names[which_names[which_names.size() - 1]], suffix);
389 
390     if (tokens.size() <= 2) {
391       return nullptr;
392     }
393 
394     assert(tokens.size() > 2);
395 
396     // Check that suffix is a number -- all digits
397     size_t N = Ioss::Utils::get_number(tokens[tokens.size() - 1]);
398 
399     if (N == 0) {
400       return nullptr;
401     }
402 
403     if (which_names.size() % N != 0) {
404       return nullptr;
405     }
406 
407     size_t inner_token = tokens.size() - 2;
408     size_t inner_comp  = which_names.size() / N;
409 
410     // Gather the first 'inner_ccomp' inner field suffices...
411     std::vector<Ioss::Suffix> suffices;
412     for (size_t i = 0; i < inner_comp; i++) {
413       std::vector<std::string> ltokens = Ioss::tokenize(names[which_names[i]], suffix);
414       // The second-last token is the suffix for this component...
415       Ioss::Suffix tmp(ltokens[inner_token]);
416       suffices.push_back(tmp);
417     }
418 
419     // check that the suffices on the next copies of the inner field
420     // match the first copy...
421     size_t j = inner_comp;
422     for (size_t copy = 1; copy < N; copy++) {
423       for (size_t i = 0; i < inner_comp; i++) {
424         std::vector<std::string> ltokens = Ioss::tokenize(names[which_names[j++]], suffix);
425         // The second-last token is the suffix for this component...
426         if (suffices[i] != ltokens[inner_token]) {
427           return nullptr;
428         }
429       }
430     }
431 
432     // All 'N' copies of the inner field match, now see the
433     // suffices actually defines a field...
434     const Ioss::VariableType *type = Ioss::VariableType::factory(suffices);
435     if (type != nullptr) {
436       type = Ioss::VariableType::factory(type->name(), static_cast<int>(N));
437     }
438     return type;
439   }
440 
match_single_field(char ** names,Ioss::IntVector & which_names,const char suffix_separator)441   const Ioss::VariableType *match_single_field(char **names, Ioss::IntVector &which_names,
442                                                const char suffix_separator)
443   {
444     // Strip off the suffix from each name indexed in 'which_names'
445     // and see if it defines a valid type...
446     std::vector<Ioss::Suffix> suffices;
447 
448     char suffix[2] = {suffix_separator, '\0'};
449 
450     for (int which_name : which_names) {
451       std::vector<std::string> tokens     = Ioss::tokenize(names[which_name], suffix);
452       size_t                   num_tokens = tokens.size();
453 
454       // The last token is the suffix for this component...
455       Ioss::Suffix tmp(tokens[num_tokens - 1]);
456       suffices.push_back(tmp);
457     }
458     const Ioss::VariableType *type = Ioss::VariableType::factory(suffices);
459     return type;
460   }
461 
get_next_field(char ** names,int num_names,size_t count,Ioss::Field::RoleType fld_role,const char suffix_separator,const int * truth_table)462   Ioss::Field get_next_field(char **names, int num_names, size_t count,
463                              Ioss::Field::RoleType fld_role, const char suffix_separator,
464                              const int *truth_table)
465   {
466     // NOTE: 'names' are all lowercase at this point.
467 
468     // Assumption 1: To convert to a non-SCALAR type, the variable name
469     // must have an field_suffix_sep in the name separating the suffixes from
470     // the main name.
471 
472     // Find first unused name (used names have '\0' as first character...
473     int  index       = 0;
474     bool found_valid = false;
475     for (index = 0; index < num_names; index++) {
476       assert(truth_table == nullptr || truth_table[index] == 1 || truth_table[index] == 0);
477       if ((truth_table == nullptr || truth_table[index] == 1) && names[index][0] != '\0') {
478         found_valid = true;
479         break;
480       }
481     }
482 
483     if (!found_valid) {
484       // Return an invalid field...
485       return Ioss::Field("", Ioss::Field::INVALID, IOSS_SCALAR(), fld_role, 1);
486     }
487 
488     // At this point, name[index] should be a valid potential field
489     // name and all names[i] with i < index are either already used or
490     // not valid for this grouping entity (truth_table entry == 0).
491     assert(index < num_names && names[index][0] != '\0' &&
492            (truth_table == nullptr || truth_table[index] == 1));
493     char *name = names[index];
494 
495     // Split the name up into tokens separated by the
496     // 'suffix_separator'.  Note that the basename itself could
497     // contain a suffix_separator (back_stress_xx or
498     // back_stress_xx_01). Need to ignore embedded separators
499     // (back_stress) and also recognize composite variable types
500     // (back_stress_xx_01). At the current time, a composite variable
501     // type can only contain two non-composite variable types, so we
502     // only need to look to be concerned with the last 1 or 2 tokens...
503     std::vector<std::string> tokens;
504     field_tokenize(name, suffix_separator, tokens);
505     size_t num_tokens = tokens.size();
506 
507     // Check that tokenizer did not return empty tokens...
508     bool invalid = tokens[0].empty() || tokens[num_tokens - 1].empty();
509     if (num_tokens == 1 || invalid) {
510       // It is not a (Sierra-generated) name for a non-SCALAR variable
511       // Return a SCALAR field
512       Ioss::Field field(name, Ioss::Field::REAL, IOSS_SCALAR(), fld_role, count);
513       field.set_index(index);
514       names[index][0] = '\0';
515       return field;
516     }
517 
518     // KNOW: num_tokens > 1 at this point.  Possible that we still
519     // just have a scalar with one or more embedded separator characters...
520     int suffix_size = 1;
521     if (num_tokens > 2) {
522       suffix_size = 2;
523     }
524 
525     // If num_tokens > 2, then we can potentially have a composite
526     // variable type which would have a double suffix (_xx_01).
527 
528     // Gather all names which match in the first
529     // (num_tokens-suffix_size) tokens and see if their suffices form
530     // a valid variable type...
531     while (suffix_size > 0) {
532       Ioss::IntVector which_names; // Contains index of names that
533       // potentially match as components
534       // of a higher-order type.
535 
536       std::string base_name = tokens[0];
537       for (size_t i = 1; i < num_tokens - suffix_size; i++) {
538         base_name += suffix_separator;
539         base_name += tokens[i];
540       }
541       base_name += suffix_separator;
542       size_t bn_len = base_name.length(); // Length of basename portion only
543       size_t length = std::strlen(name);  // Length of total name (with suffix)
544 
545       // Add the current name...
546       which_names.push_back(index);
547 
548       // Gather all other names that are valid for this entity, and
549       // have the same overall length and match in the first 'bn_len'
550       // characters.
551       //
552       // Check that they have the same number of tokens,
553       // It is possible that the first name(s) that match with two
554       // suffices have a basename that match other names with only a
555       // single suffix lc_cam_x, lc_cam_y, lc_sfarea.
556       for (int i = index + 1; i < num_names; i++) {
557         char *                   tst_name = names[i];
558         std::vector<std::string> subtokens;
559         field_tokenize(tst_name, suffix_separator, subtokens);
560         if ((truth_table == nullptr || truth_table[i] == 1) && // Defined on this entity
561             std::strlen(tst_name) == length &&                 // names must be same length
562             std::strncmp(name, tst_name, bn_len) == 0 &&       // base portion must match
563             subtokens.size() == num_tokens) {
564           which_names.push_back(i);
565         }
566       }
567 
568       const Ioss::VariableType *type = nullptr;
569       if (suffix_size == 2) {
570         if (which_names.size() > 1) {
571           type = match_composite_field(names, which_names, suffix_separator);
572         }
573       }
574       else {
575         assert(suffix_size == 1);
576         type = match_single_field(names, which_names, suffix_separator);
577       }
578 
579       if (type != nullptr) {
580         // A valid variable type was recognized.
581         // Mark the names which were used so they aren't used for another field on this entity.
582         // Create a field of that variable type.
583         assert(type->component_count() == static_cast<int>(which_names.size()));
584         Ioss::Field field(base_name.substr(0, bn_len - 1), Ioss::Field::REAL, type, fld_role,
585                           count);
586         field.set_index(index);
587         for (const auto &which_name : which_names) {
588           names[which_name][0] = '\0';
589         }
590         return field;
591       }
592       if (suffix_size == 1) {
593         Ioss::Field field(name, Ioss::Field::REAL, IOSS_SCALAR(), fld_role, count);
594         field.set_index(index);
595         names[index][0] = '\0';
596         return field;
597       }
598 
599       suffix_size--;
600     }
601     return Ioss::Field("", Ioss::Field::INVALID, IOSS_SCALAR(), fld_role, 1);
602   }
603 
604   // common
define_field(size_t nmatch,size_t match_length,char ** names,std::vector<Ioss::Suffix> & suffices,size_t entity_count,Ioss::Field::RoleType fld_role,std::vector<Ioss::Field> & fields)605   bool define_field(size_t nmatch, size_t match_length, char **names,
606                     std::vector<Ioss::Suffix> &suffices, size_t entity_count,
607                     Ioss::Field::RoleType fld_role, std::vector<Ioss::Field> &fields)
608   {
609     // Try to define a field of size 'nmatch' with the suffices in 'suffices'.
610     // If this doesn't define a known field, then assume it is a scalar instead
611     // and return false.
612     if (nmatch > 1) {
613       const Ioss::VariableType *type = Ioss::VariableType::factory(suffices);
614       if (type == nullptr) {
615         nmatch = 1;
616       }
617       else {
618         char *name         = names[0];
619         name[match_length] = '\0';
620         Ioss::Field field(name, Ioss::Field::REAL, type, fld_role, entity_count);
621         if (field.is_valid()) {
622           fields.push_back(field);
623         }
624         for (size_t j = 0; j < nmatch; j++) {
625           names[j][0] = '\0';
626         }
627         return true;
628       }
629     }
630 
631     // NOTE: nmatch could be reset inside previous if block.
632     // This is not an 'else' block, it is a new if block.
633     if (nmatch == 1) {
634       Ioss::Field field(names[0], Ioss::Field::REAL, IOSS_SCALAR(), fld_role, entity_count);
635       if (field.is_valid()) {
636         fields.push_back(field);
637       }
638       names[0][0] = '\0';
639       return false;
640     }
641     return false; // Can't get here...  Quiet the compiler
642   }
643 } // namespace
644 // Read scalar fields off an input database and determine whether
645 // they are components of a higher order type (vector, tensor, ...).
646 // This routine is used if there is no field component separator.  E.g.,
647 // fieldx, fieldy, fieldz instead of field_x field_y field_z
648 
get_fields(int64_t entity_count,char ** names,size_t num_names,Ioss::Field::RoleType fld_role,bool enable_field_recognition,const char suffix_separator,int * local_truth,std::vector<Ioss::Field> & fields)649 void Ioss::Utils::get_fields(int64_t entity_count, // The number of objects in this entity.
650                              char ** names,        // Raw list of field names from exodus
651                              size_t  num_names,    // Number of names in list
652                              Ioss::Field::RoleType fld_role, // Role of field
653                              bool enable_field_recognition, const char suffix_separator,
654                              int *local_truth, // Truth table for this entity;
655                              // null if not applicable.
656                              std::vector<Ioss::Field> &fields) // The fields that were found.
657 {
658   if (!enable_field_recognition) {
659     // Create a separate field for each name.
660     for (size_t i = 0; i < num_names; i++) {
661       if (local_truth == nullptr || local_truth[i] == 1) {
662         Ioss::Field field(names[i], Ioss::Field::REAL, IOSS_SCALAR(), fld_role, entity_count);
663         fields.push_back(field);
664         names[i][0] = '\0';
665       }
666     }
667   }
668   else if (suffix_separator != 0) {
669     while (true) {
670       // NOTE: 'get_next_field' determines storage type (vector, tensor,...)
671       Ioss::Field field =
672           get_next_field(names, static_cast<int>(num_names), entity_count, fld_role, suffix_separator, local_truth);
673       if (field.is_valid()) {
674         fields.push_back(field);
675       }
676       else {
677         break;
678       }
679     }
680   }
681   else {
682     size_t                    nmatch = 1;
683     size_t                    ibeg   = 0;
684     size_t                    pmat   = 0;
685     std::vector<Ioss::Suffix> suffices;
686   top:
687 
688     while (ibeg + nmatch < num_names) {
689       if (local_truth != nullptr) {
690         while (ibeg < num_names && local_truth[ibeg] == 0) {
691           ibeg++;
692         }
693       }
694       for (size_t i = ibeg + 1; i < num_names; i++) {
695         size_t mat = match(names[ibeg], names[i]);
696         if (local_truth != nullptr && local_truth[i] == 0) {
697           mat = 0;
698         }
699 
700         // For all fields, the total length of the name is the same
701         // for all components of that field.  The 'basename' of the
702         // field will also be the same for all cases.
703         //
704         // It is possible that the length of the match won't be the
705         // same for all components of a field since the match may
706         // include a portion of the suffix; (sigxx, sigxy, sigyy
707         // should match only 3 characters of the basename (sig), but
708         // sigxx and sigxy will match 4 characters) so consider a
709         // valid match if the match length is >= previous match length.
710         if ((std::strlen(names[ibeg]) == std::strlen(names[i])) && mat > 0 &&
711             (pmat == 0 || mat >= pmat)) {
712           nmatch++;
713           if (nmatch == 2) {
714             // Get suffix for first field in the match
715             pmat = mat;
716             Ioss::Suffix tmp(&names[ibeg][pmat]);
717             suffices.push_back(tmp);
718           }
719           // Get suffix for next fields in the match
720           Ioss::Suffix tmp(&names[i][pmat]);
721           suffices.push_back(tmp);
722         }
723         else {
724 
725           bool multi_component =
726               define_field(nmatch, pmat, &names[ibeg], suffices, entity_count, fld_role, fields);
727           if (!multi_component) {
728             // Although we matched multiple suffices, it wasn't a
729             // higher-order field, so we only used 1 name instead of
730             // the 'nmatch' we thought we might use.
731             i = ibeg + 1;
732           }
733 
734           // Cleanout the suffices vector.
735           clear(suffices);
736 
737           // Reset for the next time through the while loop...
738           nmatch = 1;
739           pmat   = 0;
740           ibeg   = i;
741           break;
742         }
743       }
744     }
745     // We've gone through the entire list of names; see if what we
746     // have forms a multi-component field; if not, then define a
747     // scalar field and jump up to the loop again to handle the others
748     // that had been gathered.
749     if (ibeg < num_names) {
750       if (local_truth == nullptr || local_truth[ibeg] == 1) {
751         bool multi_component =
752             define_field(nmatch, pmat, &names[ibeg], suffices, entity_count, fld_role, fields);
753         clear(suffices);
754         if (nmatch > 1 && !multi_component) {
755           ibeg++;
756           goto top;
757         }
758       }
759       else {
760         ibeg++;
761         goto top;
762       }
763     }
764   }
765 }
766 
platform_information()767 std::string Ioss::Utils::platform_information()
768 {
769 #ifndef _WIN32
770   struct utsname sys_info
771   {
772   };
773   uname(&sys_info);
774   std::string info =
775       fmt::format("Node: {0}, OS: {1} {2}, {3}, Machine: {4}", sys_info.nodename, sys_info.sysname,
776                   sys_info.release, sys_info.version, sys_info.machine);
777 #else
778   std::string                 info = "Node: Unknown, OS: Unknown, Machine: Unknown";
779 #endif
780   return info;
781 }
782 
get_memory_info()783 size_t Ioss::Utils::get_memory_info()
784 {
785   // Code from http://nadeausoftware.com/sites/NadeauSoftware.com/files/getRSS.c
786   size_t memory_usage = 0;
787 #if defined(_WIN32)
788 #if 0
789   /* Windows -------------------------------------------------- */
790   PROCESS_MEMORY_COUNTERS info;
791   GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
792   memory_usage = (size_t)info.WorkingSetSize;
793 #else
794   memory_usage = 0;
795 #endif
796 
797 #elif defined(__APPLE__) && defined(__MACH__)
798   kern_return_t               error;
799   mach_msg_type_number_t      outCount;
800   mach_task_basic_info_data_t taskinfo{};
801 
802   taskinfo.virtual_size = 0;
803   outCount              = MACH_TASK_BASIC_INFO_COUNT;
804   error                 = task_info(mach_task_self(), MACH_TASK_BASIC_INFO,
805                     reinterpret_cast<task_info_t>(&taskinfo), &outCount);
806   if (error == KERN_SUCCESS) {
807     memory_usage = taskinfo.resident_size;
808   }
809 #elif __linux__
810 #if defined(BGQ_LWK)
811   uint64_t heap;
812   Kernel_GetMemorySize(KERNEL_MEMSIZE_HEAP, &heap);
813   memory_usage = heap;
814 #else
815   // On Linux, the /proc pseudo-file system contains a directory for
816   // each running or zombie process. The /proc/[pid]/stat,
817   // /proc/[pid]/statm, and /proc/[pid]/status pseudo-files for the
818   // process with id [pid] all contain a process's current resident
819   // set size, among other things. But the /proc/[pid]/statm
820   // pseudo-file is the easiest to read since it contains a single
821   // line of text with white-space delimited values:
822   //
823   // * total program size
824   // * resident set size
825   // * shared pages
826   // * text (code) size
827   // * library size
828   // * data size (heap + stack)
829   // * dirty pages
830   //
831   // The second value provides the process's current resident set size
832   // in pages. To get the field for the current process, open
833   // /proc/self/statm and parse the second integer value. Multiply the
834   // field by the page size from sysconf( ).
835 
836   long  rss = 0L;
837   FILE *fp  = NULL;
838   if ((fp = fopen("/proc/self/statm", "r")) == NULL)
839     return (size_t)0L; // Can't open? */
840   if (fscanf(fp, "%*s%ld", &rss) != 1) {
841     fclose(fp);
842     return (size_t)0L; // Can't read? */
843   }
844   fclose(fp);
845   memory_usage = (size_t)rss * (size_t)sysconf(_SC_PAGESIZE);
846 #endif
847 #endif
848   return memory_usage;
849 }
850 
get_hwm_memory_info()851 size_t Ioss::Utils::get_hwm_memory_info()
852 {
853   // Code from http://nadeausoftware.com/sites/NadeauSoftware.com/files/getRSS.c
854   size_t memory_usage = 0;
855 #if defined(_WIN32)
856 #if 0
857   /* Windows -------------------------------------------------- */
858   PROCESS_MEMORY_COUNTERS info;
859   GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info));
860   memory_usage = (size_t)info.PeakWorkingSetSize;
861 #else
862   memory_usage = 0;
863 #endif
864 
865 #elif (defined(_AIX) || defined(__TOS__AIX__)) ||                                                  \
866     (defined(__sun__) || defined(__sun) || defined(sun) && (defined(__SVR4) || defined(__svr4__)))
867   /* AIX and Solaris ------------------------------------------ */
868   struct psinfo psinfo;
869   int           fd = -1;
870   if ((fd = open("/proc/self/psinfo", O_RDONLY)) == -1)
871     return (size_t)0L; /* Can't open? */
872   if (read(fd, &psinfo, sizeof(psinfo)) != sizeof(psinfo)) {
873     close(fd);
874     return (size_t)0L; /* Can't read? */
875   }
876   close(fd);
877   memory_usage = (size_t)(psinfo.pr_rssize * 1024L);
878 
879 #elif (defined(__APPLE__) && defined(__MACH__)) || (defined(__linux__) && !defined(BGQ_LWK))
880   /* BSD, Linux, and OSX -------------------------------------- */
881   struct rusage rusage;
882   getrusage(RUSAGE_SELF, &rusage);
883 #if defined(__APPLE__) && defined(__MACH__)
884   memory_usage = (size_t)rusage.ru_maxrss;
885 #else
886   memory_usage = (size_t)(rusage.ru_maxrss * 1024L);
887 #endif
888 #endif
889   return memory_usage;
890 }
891 
block_is_omitted(Ioss::GroupingEntity * block)892 bool Ioss::Utils::block_is_omitted(Ioss::GroupingEntity *block)
893 {
894   bool omitted = block->get_optional_property("omitted", 0) == 1;
895   return omitted;
896 }
897 
calculate_sideblock_membership(IntVector & face_is_member,const Ioss::SideBlock * ef_blk,size_t int_byte_size,const void * element,const void * sides,int64_t number_sides,const Ioss::Region * region)898 void Ioss::Utils::calculate_sideblock_membership(IntVector &            face_is_member,
899                                                  const Ioss::SideBlock *ef_blk,
900                                                  size_t int_byte_size, const void *element,
901                                                  const void *sides, int64_t number_sides,
902                                                  const Ioss::Region *region)
903 {
904   assert(ef_blk != nullptr);
905 
906   face_is_member.reserve(number_sides);
907 
908   const ElementTopology *unknown = Ioss::ElementTopology::factory("unknown");
909 
910   // Topology of faces in this face block...
911   const ElementTopology *ftopo = ef_blk->topology();
912 
913   // Topology of parent element for faces in this face block
914   const ElementTopology *parent_topo = ef_blk->parent_element_topology();
915 
916   // If split by element block then parent_block will be non-nullptr
917   const ElementBlock *parent_block = ef_blk->parent_element_block();
918 
919   // The element block containing the face we are working on...
920   Ioss::ElementBlock *block = nullptr;
921 
922   // Topology of face/edge in current element block
923   const ElementTopology *common_ftopo = nullptr;
924 
925   // Topology of elements in the element block containing this element
926   const ElementTopology *block_topo = nullptr;
927 
928   // Topology of the face we are currently working with...
929   const ElementTopology *topo = nullptr;
930 
931   // The element side that the current face is on the element...
932   int64_t current_side = -1;
933 
934   if (number_sides > 0 && (element == nullptr || sides == nullptr)) {
935     std::ostringstream errmsg;
936     fmt::print(errmsg, "INTERNAL ERROR: null element or sides pointer passed to {}.", __func__);
937     IOSS_ERROR(errmsg);
938   }
939 
940   for (int64_t iel = 0; iel < number_sides; iel++) {
941     int64_t elem_id = 0;
942     int64_t side_id = 0;
943     if (int_byte_size == 4) {
944       elem_id = ((int *)element)[iel];
945       side_id = ((int *)sides)[iel];
946     }
947     else {
948       elem_id = ((int64_t *)element)[iel];
949       side_id = ((int64_t *)sides)[iel];
950     }
951 
952     // Get the element block containing this face...
953     if (block == nullptr || !block->contains(elem_id)) {
954       block      = region->get_element_block(elem_id);
955       block_topo = block->topology();
956       // nullptr if hetero face/edge on element
957       common_ftopo = block->topology()->boundary_type(0);
958       if (common_ftopo != nullptr) {
959         topo = common_ftopo;
960       }
961       current_side = -1;
962     }
963 
964     // If the element topology of the element block containing this
965     // face has heterogeneous topology (eg. wedge), then determine the
966     // topology corresponding to the current side..
967     if (common_ftopo == nullptr && side_id != current_side) {
968       current_side = side_id;
969       topo         = block->topology()->boundary_type(static_cast<int>(side_id));
970     }
971 
972     bool face_topo_match  = ftopo == unknown || topo == ftopo;
973     bool block_topo_match = parent_topo == unknown || block_topo == parent_topo;
974     // See if the face topology and the parent element topology for
975     // the current face match the topology associated with this face block.
976     if (face_topo_match && block_topo_match && (parent_block == nullptr || parent_block == block) &&
977         !block_is_omitted(block)) {
978       // This face/edge  belongs in the face/edge block
979       face_is_member.push_back(1);
980     }
981     else {
982       face_is_member.push_back(0);
983     }
984   }
985 }
986 
get_side_offset(const Ioss::SideBlock * sb)987 int64_t Ioss::Utils::get_side_offset(const Ioss::SideBlock *sb)
988 {
989 
990   const Ioss::ElementTopology *side_topo   = sb->topology();
991   const Ioss::ElementTopology *parent_topo = sb->parent_element_topology();
992   int64_t                      side_offset = 0;
993   if ((side_topo != nullptr) && (parent_topo != nullptr)) {
994     int side_topo_dim = side_topo->parametric_dimension();
995     int elem_topo_dim = parent_topo->parametric_dimension();
996     int elem_spat_dim = parent_topo->spatial_dimension();
997 
998     if (side_topo_dim + 1 < elem_spat_dim && side_topo_dim < elem_topo_dim) {
999       side_offset = parent_topo->number_faces();
1000     }
1001   }
1002   return side_offset;
1003 }
1004 
hash(const std::string & name)1005 unsigned int Ioss::Utils::hash(const std::string &name)
1006 {
1007   // Hash function from Aho, Sethi, Ullman "Compilers: Principles,
1008   // Techniques, and Tools.  Page 436
1009 
1010   const char * symbol = name.c_str();
1011   unsigned int hashval;
1012   unsigned int g;
1013   for (hashval = 0; *symbol != '\0'; symbol++) {
1014     hashval = (hashval << 4) + *symbol;
1015     g       = hashval & 0xf0000000;
1016     if (g != 0) {
1017       hashval = hashval ^ (g >> 24);
1018       hashval = hashval ^ g;
1019     }
1020   }
1021   return hashval;
1022 }
1023 
timer()1024 double Ioss::Utils::timer()
1025 {
1026   auto now = std::chrono::steady_clock::now();
1027   return std::chrono::duration<double>(now - initial_time).count();
1028 }
1029 
input_file(const std::string & file_name,std::vector<std::string> * lines,size_t max_line_length)1030 void Ioss::Utils::input_file(const std::string &file_name, std::vector<std::string> *lines,
1031                              size_t max_line_length)
1032 {
1033   // Create an ifstream for the input file. This does almost the same
1034   // function as sierra::Env::input() except this is for a single
1035   // processor and the sierra::Env::input() is for parallel...
1036 
1037   if (!file_name.empty()) {
1038     // Open the file and read into the vector...
1039     std::string   input_line;
1040     std::ifstream infile(file_name);
1041     lines->push_back(file_name.substr(0, max_line_length));
1042     while (!std::getline(infile, input_line).fail()) {
1043       if (max_line_length == 0 || input_line.length() <= max_line_length) {
1044         lines->push_back(input_line);
1045       }
1046       else {
1047         // Split the line into pieces of length "max_line_length-1"
1048         // and append a "\" to all but the last. Don't worry about
1049         // splitting at whitespace...
1050         size_t ibeg = 0;
1051         do {
1052           std::string sub = input_line.substr(ibeg, max_line_length - 1);
1053           if (ibeg + max_line_length - 1 < input_line.length()) {
1054             sub += "\\";
1055           }
1056           lines->push_back(sub);
1057           ibeg += max_line_length - 1;
1058         } while (ibeg < input_line.length());
1059       }
1060     }
1061   }
1062 }
1063 
str_equal(const std::string & s1,const std::string & s2)1064 bool Ioss::Utils::str_equal(const std::string &s1, const std::string &s2)
1065 {
1066   return (s1.size() == s2.size()) &&
1067          std::equal(s1.begin(), s1.end(), s2.begin(),
1068                     [](char a, char b) { return std::tolower(a) == std::tolower(b); });
1069 }
1070 
substr_equal(const std::string & prefix,const std::string & str)1071 bool Ioss::Utils::substr_equal(const std::string &prefix, const std::string &str)
1072 {
1073   return (str.size() >= prefix.size()) && str_equal(prefix, str.substr(0, prefix.size()));
1074 }
1075 
uppercase(std::string name)1076 std::string Ioss::Utils::uppercase(std::string name)
1077 {
1078   std::transform(name.begin(), name.end(), name.begin(), [](char c) { return static_cast<char>(std::toupper(c)); });
1079   return name;
1080 }
1081 
lowercase(std::string name)1082 std::string Ioss::Utils::lowercase(std::string name)
1083 {
1084   std::transform(name.begin(), name.end(), name.begin(), [](char c) { return static_cast<char>(std::tolower(c)); });
1085   return name;
1086 }
1087 
check_set_bool_property(const Ioss::PropertyManager & properties,const std::string & prop_name,bool & prop_value)1088 bool Ioss::Utils::check_set_bool_property(const Ioss::PropertyManager &properties,
1089                                           const std::string &prop_name, bool &prop_value)
1090 {
1091   bool found_property = false;
1092   if (properties.exists(prop_name)) {
1093     found_property = true;
1094     if (properties.get(prop_name).get_type() == Ioss::Property::INTEGER) {
1095       prop_value = properties.get(prop_name).get_int() != 0;
1096     }
1097     else {
1098       std::string yesno = Ioss::Utils::uppercase(properties.get(prop_name).get_string());
1099       if (yesno == "TRUE" || yesno == "YES" || yesno == "ON") {
1100         prop_value = true;
1101       }
1102       else if (yesno == "FALSE" || yesno == "NO" || yesno == "OFF") {
1103         prop_value = false;
1104       }
1105       else {
1106         std::ostringstream errmsg;
1107         fmt::print(errmsg,
1108                    "ERROR: Unrecognized value found for {}. "
1109                    "Found '{}' which is not one of TRUE|FALSE|YES|NO|ON|OFF",
1110                    prop_name, yesno);
1111         IOSS_ERROR(errmsg);
1112       }
1113     }
1114   }
1115   return found_property;
1116 }
1117 
fixup_name(char * name)1118 void Ioss::Utils::fixup_name(char *name)
1119 {
1120   assert(name != nullptr);
1121 
1122   size_t len = std::strlen(name);
1123   for (size_t i = 0; i < len; i++) {
1124     name[i] = static_cast<char>(tolower(name[i])); // guaranteed(?) to be ascii...
1125     if (name[i] == ' ') {
1126       name[i] = '_';
1127     }
1128   }
1129 }
1130 
fixup_name(std::string & name)1131 void Ioss::Utils::fixup_name(std::string &name)
1132 {
1133   name = Ioss::Utils::lowercase(name);
1134 
1135   size_t len = name.length();
1136   for (size_t i = 0; i < len; i++) {
1137     if (name[i] == ' ') {
1138       name[i] = '_';
1139     }
1140   }
1141 }
1142 
1143 namespace {
1144 
1145   /** \brief Hash function from Aho, Sethi, Ullman "Compilers: Principles,
1146    *         Techniques, and Tools.  Page 436
1147    */
two_letter_hash(const char * symbol)1148   std::string two_letter_hash(const char *symbol)
1149   {
1150     const int    HASHSIZE = 673; // Largest prime less than 676 (26*26)
1151     unsigned int hashval;
1152     for (hashval = 0; *symbol != '\0'; symbol++) {
1153       hashval        = (hashval << 4) + *symbol;
1154       unsigned int g = hashval & 0xf0000000;
1155       if (g != 0) {
1156         hashval = hashval ^ (g >> 24);
1157         hashval = hashval ^ g;
1158       }
1159     }
1160 
1161     // Convert to base-26 'number'
1162     hashval %= HASHSIZE;
1163     char word[3] = {char(hashval / 26 + 'a'), char(hashval % 26 + 'a'), '\0'};
1164     return (std::string(word));
1165   }
1166 } // namespace
1167 
variable_name_kluge(const std::string & name,size_t component_count,size_t copies,size_t max_var_len)1168 std::string Ioss::Utils::variable_name_kluge(const std::string &name, size_t component_count,
1169                                              size_t copies, size_t max_var_len)
1170 {
1171 
1172   // Width = 'max_var_len'.
1173   // Reserve space for suffix '_00...'
1174   // Reserve 3 for hash   '.xx'
1175   int hash_len = 3;
1176   int comp_len = 3; // _00
1177   int copy_len = 0;
1178 
1179   if (copies > 1) {
1180     assert(component_count % copies == 0);
1181     component_count /= copies;
1182   }
1183 
1184   if (component_count <= 1) {
1185     comp_len = 0;
1186   }
1187   else {
1188     comp_len = number_width(component_count) + 1; // _00000
1189   }
1190 
1191   if (copies <= 1) {
1192     copy_len = 0;
1193   }
1194   else {
1195     copy_len = number_width(copies) + 1; // _00000
1196   }
1197 
1198   size_t maxlen = max_var_len - comp_len - copy_len;
1199 
1200   std::string new_str = name;
1201   if (name.length() <= maxlen) {
1202     // If name fits without kluging, then just use name as it is
1203     // without adding on the hash...
1204     return lowercase(new_str);
1205   }
1206   // Know that the name is too long, try to shorten. Need room for
1207   // hash now.
1208   maxlen -= hash_len;
1209   int len = static_cast<int>(name.length());
1210 
1211   // Take last 'maxlen' characters.  Motivation for this is that the
1212   // beginning of the composed (or generated) variable name is the
1213   // names of the mechanics and mechanics instances in which this
1214   // variable is nested, so they will be similar for all variables at
1215   // the same scope and the differences will occur at the variable
1216   // name level...
1217   //
1218   // However, there will likely be variables at the
1219   // same level but in different scope that have the same name which
1220   // would cause a clash, so we *hope* that the hash will make those
1221   // scope names unique...
1222   std::string s = std::string(name).substr(len - maxlen, len);
1223   assert(s.length() <= maxlen);
1224   new_str = s;
1225 
1226   // NOTE: The hash is not added if the name is not shortened.
1227   std::string hash_string = two_letter_hash(name.c_str());
1228   new_str += std::string(".");
1229   new_str += hash_string;
1230   return lowercase(new_str);
1231 }
1232 
generate_history_mesh(Ioss::Region * region)1233 void Ioss::Utils::generate_history_mesh(Ioss::Region *region)
1234 {
1235   Ioss::DatabaseIO *db = region->get_database();
1236   if (db->parallel_rank() == 0) {
1237     region->begin_mode(Ioss::STATE_DEFINE_MODEL);
1238 
1239     // Node Block
1240     auto *nb = new Ioss::NodeBlock(db, "nodeblock_1", 1, 3);
1241     region->add(nb);
1242 
1243     // Element Block
1244     auto *eb = new Ioss::ElementBlock(db, "e1", "sphere", 1);
1245     eb->property_add(Ioss::Property("id", 1));
1246     eb->property_add(Ioss::Property("guid", 1));
1247     region->add(eb);
1248     region->end_mode(Ioss::STATE_DEFINE_MODEL);
1249 
1250     region->begin_mode(Ioss::STATE_MODEL);
1251     static double coord[3] = {1.1, 2.2, 3.3};
1252     static int    ids[1]   = {1};
1253     nb->put_field_data("ids", ids, sizeof(int));
1254     nb->put_field_data("mesh_model_coordinates", coord, 3 * sizeof(double));
1255 
1256     static int connect[1] = {1};
1257     eb->put_field_data("ids", ids, sizeof(int));
1258     eb->put_field_data("connectivity", connect, 1 * sizeof(int));
1259 
1260     region->end_mode(Ioss::STATE_MODEL);
1261   }
1262 }
1263 
1264 // Safer than Ioss::Utils::copy_string -- guarantees null termination
copy_string(char * dest,char const * source,size_t elements)1265 void Ioss::Utils::copy_string(char *dest, char const *source, size_t elements)
1266 {
1267   char *d;
1268   for (d = dest; d + 1 < dest + elements && *source; d++, source++) {
1269     *d = *source;
1270   }
1271   *d = '\0';
1272 }
1273 
1274 namespace {
1275   const int tab64[64] = {63, 0,  58, 1,  59, 47, 53, 2,  60, 39, 48, 27, 54, 33, 42, 3,
1276                          61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4,
1277                          62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
1278                          56, 45, 25, 31, 35, 16, 9,  12, 44, 24, 15, 8,  23, 7,  6,  5};
1279 } // namespace
1280 
log_power_2(uint64_t value)1281 int Ioss::Utils::log_power_2(uint64_t value)
1282 {
1283   assert(value > 0);
1284   value = (value << 1) - 1;
1285   value |= value >> 1;
1286   value |= value >> 2;
1287   value |= value >> 4;
1288   value |= value >> 8;
1289   value |= value >> 16;
1290   value |= value >> 32;
1291   return tab64[(((value - (value >> 1)) * 0x07EDD5E59A4E28C2)) >> 58];
1292 }
1293 
term_width()1294 int Ioss::Utils::term_width()
1295 {
1296   int cols = 0;
1297   if (isatty(fileno(stdout))) {
1298 #if defined(TIOCGWINSZ)
1299     struct winsize ts;
1300     ioctl(STDOUT_FILENO, TIOCGWINSZ, &ts);
1301     cols = ts.ws_col;
1302 #elif TIOCGSIZE
1303     struct ttysize ts;
1304     ioctl(STDOUT_FILENO, TIOCGSIZE, &ts);
1305     cols = ts.ts_cols;
1306 #endif /* TIOCGSIZE */
1307   }
1308   return cols != 0 ? cols : 100;
1309 }
1310 
info_fields(const Ioss::GroupingEntity * ige,Ioss::Field::RoleType role,const std::string & header,const std::string & suffix)1311 void Ioss::Utils::info_fields(const Ioss::GroupingEntity *ige, Ioss::Field::RoleType role,
1312                               const std::string &header, const std::string &suffix)
1313 {
1314   Ioss::NameList fields;
1315   ige->field_describe(role, &fields);
1316 
1317   if (fields.empty()) {
1318     return;
1319   }
1320 
1321   if (!header.empty()) {
1322     fmt::print("{}{}", header, suffix);
1323   }
1324   // Iterate through results fields and transfer to output
1325   // database...
1326   // Get max width of a name...
1327   int max_width = 0;
1328   for (const auto &field_name : fields) {
1329     max_width = max_width > static_cast<int>(field_name.length()) ? max_width : static_cast<int>(field_name.length());
1330   }
1331 
1332   auto width = Ioss::Utils::term_width();
1333   if (width == 0) {
1334     width = 80;
1335   }
1336   int cur_out = 8; // Tab width...
1337   if (!header.empty()) {
1338     cur_out = static_cast<int>(header.size() + suffix.size() + 16); // Assume 2 tabs...
1339   }
1340   for (const auto &field_name : fields) {
1341     const Ioss::VariableType *var_type   = ige->get_field(field_name).raw_storage();
1342     int                       comp_count = var_type->component_count();
1343     fmt::print("{1:>{0}s}:{2}  ", max_width, field_name, comp_count);
1344     cur_out += max_width + 4;
1345     if (cur_out + max_width >= width) {
1346       fmt::print("\n\t");
1347       cur_out = 8;
1348     }
1349   }
1350   if (!header.empty()) {
1351     fmt::print("\n");
1352   }
1353 }
1354 
info_property(const Ioss::GroupingEntity * ige,Ioss::Property::Origin origin,const std::string & header,const std::string & suffix,bool print_empty)1355 void Ioss::Utils::info_property(const Ioss::GroupingEntity *ige, Ioss::Property::Origin origin,
1356                                 const std::string &header, const std::string &suffix,
1357                                 bool print_empty)
1358 {
1359   Ioss::NameList properties;
1360   ige->property_describe(origin, &properties);
1361 
1362   if (properties.empty()) {
1363     if (print_empty && !header.empty()) {
1364       fmt::print("{}{} *** No attributes ***\n", header, suffix);
1365     }
1366     return;
1367   }
1368 
1369   if (!header.empty()) {
1370     fmt::print("{}{}", header, suffix);
1371   }
1372 
1373   int num_out = 0;
1374   for (const auto &property_name : properties) {
1375     fmt::print("{:>s}: ", property_name);
1376     auto prop = ige->get_property(property_name);
1377     switch (prop.get_type()) {
1378     case Ioss::Property::BasicType::REAL: fmt::print("{}\t", prop.get_real()); break;
1379     case Ioss::Property::BasicType::INTEGER: fmt::print("{}\t", prop.get_int()); break;
1380     case Ioss::Property::BasicType::STRING: fmt::print("'{}'\t", prop.get_string()); break;
1381     case Ioss::Property::BasicType::VEC_INTEGER:
1382       fmt::print("{}\t", fmt::join(prop.get_vec_int(), "  "));
1383       break;
1384     case Ioss::Property::BasicType::VEC_DOUBLE:
1385       fmt::print("{}\t", fmt::join(prop.get_vec_double(), "  "));
1386       break;
1387     default:; // Do nothing
1388     }
1389     num_out++;
1390     if (num_out >= 3) {
1391       fmt::print("\n\t");
1392       num_out = 0;
1393     }
1394   }
1395   if (!header.empty()) {
1396     fmt::print("\n");
1397   }
1398 }
1399