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 
8 #include <Ioss_Assembly.h>
9 #include <Ioss_Beam2.h>
10 #include <Ioss_Beam3.h>
11 #include <Ioss_FaceGenerator.h>
12 #include <Ioss_Hex20.h>
13 #include <Ioss_Hex27.h>
14 #include <Ioss_Hex8.h>
15 #include <Ioss_Node.h>
16 #include <Ioss_Pyramid13.h>
17 #include <Ioss_Pyramid14.h>
18 #include <Ioss_Pyramid5.h>
19 #include <Ioss_Quad4.h>
20 #include <Ioss_Quad8.h>
21 #include <Ioss_Quad9.h>
22 #include <Ioss_Sort.h>
23 #include <Ioss_Spring2.h>
24 #include <Ioss_Spring3.h>
25 #include <Ioss_StructuredBlock.h>
26 #include <Ioss_Tet10.h>
27 #include <Ioss_Tet4.h>
28 #include <Ioss_Tri3.h>
29 #include <Ioss_Tri4.h>
30 #include <Ioss_Tri6.h>
31 #include <Ioss_Unknown.h>
32 #include <Ioss_Utils.h>
33 #include <Ioss_Wedge15.h>
34 #include <Ioss_Wedge18.h>
35 #include <Ioss_Wedge6.h>
36 
37 #include <fmt/color.h>
38 #include <fmt/ostream.h>
39 #include <numeric>
40 #include <set>
41 #include <tokenize.h>
42 
43 #include <cgns/Iocgns_StructuredZoneData.h>
44 #include <cgns/Iocgns_Utils.h>
45 
46 #include <vtk_cgns.h> // xxx(kitware)
47 #include VTK_CGNS(cgnsconfig.h)
48 #if CG_BUILD_PARALLEL
49 #include VTK_CGNS(pcgnslib.h)
50 #else
51 #include VTK_CGNS(cgnslib.h)
52 #endif
53 
54 #include <cgns/Iocgns_Defines.h>
55 
56 #if defined(_WIN32)
57 #include <algorithm>
to_lower(std::string s)58 static std::string to_lower(std::string s)
59 {
60   std::transform(s.begin(), s.end(), s.begin(),
61       [](char c){ return static_cast<char>(std::tolower(c)); });
62   return s;
63 }
strcasestr(const char * haystack,const char * needle)64 static const char* strcasestr(const char* haystack, const char* needle)
65 {
66   std::string lneedle(to_lower(needle));
67   std::string lhaystack(to_lower(haystack));
68 
69   auto pos = lhaystack.find(lneedle);
70   return pos != std::string::npos ? haystack + pos : nullptr;
71 }
72 #endif
73 
74 #define CGERR(funcall)                                                                             \
75   if ((funcall) != CG_OK) {                                                                        \
76     Iocgns::Utils::cgns_error(file_ptr, __FILE__, __func__, __LINE__, -1);                         \
77   }
78 
79 #ifdef _WIN32
80 #ifdef _MSC_VER
81 #define strncasecmp strnicmp
82 #endif
strcasestr(char * haystack,const char * needle)83 char *strcasestr(char *haystack, const char *needle)
84 {
85   char *c;
86   for (c = haystack; *c; c++)
87     if (!strncasecmp(c, needle, strlen(needle)))
88       return c;
89   return 0;
90 }
91 #endif
92 
93 namespace {
power_2(int count)94   int power_2(int count)
95   {
96     // Return the maximum power of two which is less than or equal to 'count'
97     // count = 15 -> returns 8
98     // count = 16 -> returns 16
99     // count = 17 -> returns 16
100 
101     // Use brute force...
102     int pow2 = 1;
103     while (pow2 <= count) {
104       pow2 *= 2;
105     }
106     if (pow2 > count) {
107       pow2 /= 2;
108     }
109     return pow2;
110   }
111 
112   struct Range
113   {
RangeRange114     Range(int a, int b) : m_beg(a < b ? a : b), m_end(a < b ? b : a), m_reversed(b < a) {}
115 
116     int  m_beg;
117     int  m_end;
118     bool m_reversed;
119   };
120 
overlaps(const Range & a,const Range & b)121   bool overlaps(const Range &a, const Range &b) { return a.m_beg <= b.m_end && b.m_beg <= a.m_end; }
122 
subset_range(const Range & a,const Range & b)123   Range subset_range(const Range &a, const Range &b)
124   {
125     Range ret(std::max(a.m_beg, b.m_beg), std::min(a.m_end, b.m_end));
126     ret.m_reversed = a.m_reversed || b.m_reversed;
127     return ret;
128   }
129 
bc_subset_range(const Ioss::StructuredBlock * block,Ioss::BoundaryCondition & bc)130   void bc_subset_range(const Ioss::StructuredBlock *block, Ioss::BoundaryCondition &bc)
131   {
132     Ioss::IJK_t ordinal;
133     ordinal[0] = block->get_property("ni").get_int();
134     ordinal[1] = block->get_property("nj").get_int();
135     ordinal[2] = block->get_property("nk").get_int();
136 
137     Ioss::IJK_t offset;
138     offset[0] = block->get_property("offset_i").get_int();
139     offset[1] = block->get_property("offset_j").get_int();
140     offset[2] = block->get_property("offset_k").get_int();
141 
142     // NOTE: Updates the range in bc
143 
144     // Note that block range is nodes and m_ordinal[] is cells, so need to add 1 to range.
145     Range z_i(1 + offset[0], ordinal[0] + offset[0] + 1);
146     Range z_j(1 + offset[1], ordinal[1] + offset[1] + 1);
147     Range z_k(1 + offset[2], ordinal[2] + offset[2] + 1);
148 
149     Range gc_i(bc.m_rangeBeg[0], bc.m_rangeEnd[0]);
150     Range gc_j(bc.m_rangeBeg[1], bc.m_rangeEnd[1]);
151     Range gc_k(bc.m_rangeBeg[2], bc.m_rangeEnd[2]);
152 
153     Range gc_ii = subset_range(z_i, gc_i);
154     Range gc_jj = subset_range(z_j, gc_j);
155     Range gc_kk = subset_range(z_k, gc_k);
156 
157     if (overlaps(z_i, gc_i) && overlaps(z_j, gc_j) && overlaps(z_k, gc_k)) {
158       bc.m_rangeBeg[0] = gc_ii.m_reversed ? gc_ii.m_end : gc_ii.m_beg;
159       bc.m_rangeEnd[0] = gc_ii.m_reversed ? gc_ii.m_beg : gc_ii.m_end;
160       bc.m_rangeBeg[1] = gc_jj.m_reversed ? gc_jj.m_end : gc_jj.m_beg;
161       bc.m_rangeEnd[1] = gc_jj.m_reversed ? gc_jj.m_beg : gc_jj.m_end;
162       bc.m_rangeBeg[2] = gc_kk.m_reversed ? gc_kk.m_end : gc_kk.m_beg;
163       bc.m_rangeEnd[2] = gc_kk.m_reversed ? gc_kk.m_beg : gc_kk.m_end;
164     }
165     else {
166       bc.m_rangeBeg = {{0, 0, 0}};
167       bc.m_rangeEnd = {{0, 0, 0}};
168     }
169   }
170 
extract_trailing_int(const char * name)171   int extract_trailing_int(const char *name)
172   {
173     // 'name' consists of an arbitrary number of characters followed by
174     // zero or more digits.  Return the integer value of the contiguous
175     // set of trailing digits.
176     // Example: Name42 returns 42;  Name_52or_perhaps_3_43 returns 43.
177 
178     size_t len = std::strlen(name);
179     int    val = 0;
180     int    mul = 1;
181     for (size_t d = len; d > 0; d--) {
182       if (isdigit(name[d - 1])) {
183         val += mul * (name[d - 1] - '0');
184         mul *= 10;
185       }
186       else {
187         break;
188       }
189     }
190     return val;
191   }
192 
proc_with_minimum_work(Iocgns::StructuredZoneData * zone,const std::vector<size_t> & work,std::set<std::pair<int,int>> & proc_adam_map)193   ssize_t proc_with_minimum_work(Iocgns::StructuredZoneData *zone, const std::vector<size_t> &work,
194                                  std::set<std::pair<int, int>> &proc_adam_map)
195   {
196     size_t  min_work = std::numeric_limits<size_t>::max();
197     ssize_t min_proc = -1;
198     for (ssize_t i = 0; i < (ssize_t)work.size(); i++) {
199       if (work[i] < min_work &&
200           proc_adam_map.find(std::make_pair(zone->m_adam->m_zone, static_cast<int>(i))) == proc_adam_map.end()) {
201         min_work = work[i];
202         min_proc = i;
203         if (min_work == 0) {
204           break;
205         }
206       }
207     }
208     return min_proc;
209   }
add_bc_to_block(Ioss::StructuredBlock * block,const std::string & boco_name,const std::string & fam_name,int ibc,cgsize_t * range,CGNS_ENUMT (BCType_t)bocotype,bool is_parallel_io)210   void add_bc_to_block(Ioss::StructuredBlock *block, const std::string &boco_name,
211                        const std::string &fam_name, int ibc, cgsize_t *range, CGNS_ENUMT(BCType_t) bocotype,
212                        bool is_parallel_io)
213   {
214     Ioss::SideSet *sset = block->get_database()->get_region()->get_sideset(fam_name);
215     if (sset == nullptr) {
216       if (block->get_database()->parallel_rank() == 0) {
217         fmt::print(Ioss::WARNING(),
218                    "On block '{}', found the boundary condition named '{}' in family '{}'.\n"
219                    "         This family was not previously defined at the top-level of the file"
220                    " which is not normal.\n"
221                    "         Check your file to make sure this does not "
222                    "indicate a problem with the mesh.\n",
223                    block->name(), boco_name, fam_name);
224       }
225 
226       // Need to create a new sideset since didn't see this earlier.
227       auto *db = block->get_database();
228       sset     = new Ioss::SideSet(db, fam_name);
229 
230       // Get all previous sidesets to make sure we set a unique id...
231       int64_t     max_id   = 0;
232       const auto &sidesets = db->get_region()->get_sidesets();
233       for (const auto &ss : sidesets) {
234         if (ss->property_exists("id")) {
235           auto id = ss->get_property("id").get_int();
236           max_id  = (id > max_id) ? id : max_id;
237         }
238       }
239       sset->property_add(Ioss::Property("id", max_id + 10));
240       sset->property_add(Ioss::Property("guid", db->util().generate_guid(max_id + 10)));
241       db->get_region()->add(sset);
242     }
243 
244     assert(sset != nullptr);
245 
246     Ioss::IJK_t range_beg{{(int)std::min(range[0], range[3]), (int)std::min(range[1], range[4]),
247                            (int)std::min(range[2], range[5])}};
248 
249     Ioss::IJK_t range_end{{(int)std::max(range[0], range[3]), (int)std::max(range[1], range[4]),
250                            (int)std::max(range[2], range[5])}};
251 
252     // Determine overlap of surface with block (in parallel, a block may
253     // be split among multiple processors and the block face this is applied
254     // to may not exist on this decomposed block)
255     auto        bc   = Ioss::BoundaryCondition(boco_name, fam_name, range_beg, range_end);
256     std::string name = std::string(boco_name) + "/" + block->name();
257 
258     bc_subset_range(block, bc);
259     if (!is_parallel_io && !bc.is_valid()) {
260       bc.m_rangeBeg = {{0, 0, 0}};
261       bc.m_rangeEnd = {{0, 0, 0}};
262     }
263     block->m_boundaryConditions.push_back(bc);
264     auto sb = new Ioss::SideBlock(block->get_database(), name, Ioss::Quad4::name, Ioss::Hex8::name,
265                                   block->m_boundaryConditions.back().get_face_count());
266     sb->set_parent_block(block);
267     sset->add(sb);
268 
269     int base = block->get_property("base").get_int();
270     int zone = block->get_property("zone").get_int();
271     sb->property_add(Ioss::Property("base", base));
272     sb->property_add(Ioss::Property("zone", zone));
273     sb->property_add(Ioss::Property("section", ibc + 1));
274     sb->property_add(Ioss::Property("id", sset->get_property("id").get_int()));
275     sb->property_add(Ioss::Property(
276         "guid", block->get_database()->util().generate_guid(sset->get_property("id").get_int())));
277 
278     // Set a property on the sideset specifying the boundary condition type (bocotype)
279     // In CGNS, the bocotype is an enum; we store it as the integer value of the enum.
280     if (sset->property_exists("bc_type")) {
281       // Check that the 'bocotype' value matches the value of the property.
282       auto old_bocotype = sset->get_property("bc_type").get_int();
283       if (old_bocotype != bocotype && bocotype != CGNS_ENUMV(FamilySpecified)) {
284         fmt::print(Ioss::WARNING(),
285                    "On sideset '{}', the boundary condition type was previously set to {}"
286                    " which does not match the current value of {}. It will keep the old value.\n",
287                    sset->name(), old_bocotype, bocotype);
288       }
289     }
290     else {
291       sset->property_add(Ioss::Property("bc_type", (int)bocotype));
292     }
293   }
294 
sync_transient_variables_fpp(Ioss::Region * region)295   void sync_transient_variables_fpp(Ioss::Region *region)
296   {
297     // With an fpp read, certain blocks may only be on certain
298     // processors -- This consistency is addressed elsewhere; however,
299     // if a block is not on a processor, then that block will not have
300     // any transient fields.  Need to sync across all processors such
301     // that a block has the same fields on all processors.
302     //
303     // ASSUME: A block will have the same fields in the same order on
304     // all processors on which it exists.
305     //
306     // Do the gather all metadata to proc 0; consolidate and then
307     // broadcast back...
308     // Need: 'name' and 'VariableType'.  Assume all are double and the
309     // size will be processor dependent.
310     auto &           sblocks = region->get_structured_blocks();
311     std::vector<int> fld_count;
312     fld_count.reserve(sblocks.size());
313     for (const auto &block : sblocks) {
314       fld_count.push_back(block->field_count(Ioss::Field::TRANSIENT));
315     }
316     auto par = region->get_database()->util();
317     par.global_array_minmax(fld_count, Ioss::ParallelUtils::DO_MAX);
318 
319     // Determine total number of fields on all blocks...
320     int tot_fld = std::accumulate(fld_count.begin(), fld_count.end(), 0);
321     // Assuming fields are the same on all processors that have fields...
322     std::vector<char> fld_names(tot_fld * 2 * (CGNS_MAX_NAME_LENGTH + 1), 0);
323 
324     size_t offset = 0;
325     for (size_t i = 0; i < sblocks.size(); i++) {
326       const auto &   block = sblocks[i];
327       Ioss::NameList fields;
328       block->field_describe(Ioss::Field::TRANSIENT, &fields);
329       if (!fields.empty()) {
330         for (const auto &field_name : fields) {
331           const Ioss::Field &field = block->get_fieldref(field_name);
332           std::string        type  = field.raw_storage()->name();
333           Ioss::Utils::copy_string(&fld_names[offset], field_name, CGNS_MAX_NAME_LENGTH + 1);
334           offset += CGNS_MAX_NAME_LENGTH + 1;
335           Ioss::Utils::copy_string(&fld_names[offset], type, CGNS_MAX_NAME_LENGTH + 1);
336           offset += CGNS_MAX_NAME_LENGTH + 1;
337         }
338       }
339       else {
340         offset += (CGNS_MAX_NAME_LENGTH + 1) * 2 * fld_count[i];
341       }
342     }
343 
344     par.global_array_minmax(fld_names, Ioss::ParallelUtils::DO_MAX);
345 
346     // Each processor now should have a consistent list of the field
347     // names.  Now need to add the missing fields to the blocks that
348     // are not 'native' to this processor...
349     //
350     for (size_t i = 0; i < sblocks.size(); i++) {
351       auto &block = sblocks[i];
352       if (block->field_count(Ioss::Field::TRANSIENT) != (size_t)fld_count[i]) {
353         // Verify that either has 0 or correct number of fields...
354         assert(block->field_count(Ioss::Field::TRANSIENT) == 0);
355 
356         // Extract the field name and storage type...
357         offset = (CGNS_MAX_NAME_LENGTH + 1) * 2 * i;
358 
359         for (int nf = 0; nf < fld_count[i]; nf++) {
360           std::string fld_name(&fld_names[offset]);
361           offset += CGNS_MAX_NAME_LENGTH + 1;
362           std::string fld_type(&fld_names[offset]);
363           offset += CGNS_MAX_NAME_LENGTH + 1;
364 
365           block->field_add(
366               Ioss::Field(fld_name, Ioss::Field::DOUBLE, fld_type, Ioss::Field::TRANSIENT, 0));
367         }
368       }
369       assert(block->field_count(Ioss::Field::TRANSIENT) == (size_t)fld_count[i]);
370     }
371   }
372 
373 #if IOSS_DEBUG_OUTPUT
output_table(const Ioss::ElementBlockContainer & ebs,std::map<std::string,Ioss::FaceUnorderedSet> & boundary_faces)374   void output_table(const Ioss::ElementBlockContainer &            ebs,
375                     std::map<std::string, Ioss::FaceUnorderedSet> &boundary_faces)
376   {
377     // Get maximum name and face_count length...
378     size_t max_name = std::string("Block Name").length();
379     size_t max_face = std::string("Face Count").length();
380     for (auto eb : ebs) {
381       const std::string &name = eb->name();
382       if (name.length() > max_name) {
383         max_name = name.length();
384       }
385       size_t face_width = Ioss::Utils::number_width(boundary_faces[name].size());
386       max_face          = face_width > max_face ? face_width : max_face;
387     }
388     max_name += 4; // Padding
389     max_face += 4;
390 
391     fmt::print("\t+{2:-^{0}}+{2:-^{1}}+\n", max_name, max_face, "");
392     fmt::print("\t|{2:^{0}}|{3:^{1}}|\n", max_name, max_face, "Block Name", "Face Count");
393     fmt::print("\t+{2:-^{0}}+{2:-^{1}}+\n", max_name, max_face, "");
394     for (auto eb : ebs) {
395       const std::string &name = eb->name();
396       fmt::print("\t|{2:^{0}}|{3:{1}L}  |\n", max_name, max_face - 2, name,
397                  boundary_faces[name].size());
398     }
399     fmt::print("\t+{2:-^{0}}+{2:-^{1}}+\n", max_name, max_face, "");
400   }
401 #endif
402 } // namespace
403 
decompose_name(const std::string & name,bool is_parallel)404 std::pair<std::string, int> Iocgns::Utils::decompose_name(const std::string &name, bool is_parallel)
405 {
406   int         proc = is_parallel ? -1 : 0;
407   std::string zname{name};
408 
409   if (is_parallel) {
410     // Name should/might be of the form `basename_proc-#`.  Strip
411     // off the `_proc-#` portion and return just the basename.
412     auto tokens = Ioss::tokenize(zname, "_");
413     zname       = tokens[0];
414     if (tokens.size() >= 2) {
415       size_t idx = tokens.size() - 1;
416       if (tokens[idx].substr(0, 5) == "proc-") {
417         auto ptoken = Ioss::tokenize(tokens[idx], "-");
418         proc        = std::stoi(ptoken[1]);
419         idx--;
420         zname = tokens[idx];
421       }
422     }
423   }
424   return std::make_pair(zname, proc);
425 }
426 
decompose_sb_name(const std::string & name)427 std::string Iocgns::Utils::decompose_sb_name(const std::string &name)
428 {
429   std::string zname{name};
430 
431   // Name should/might be of the form `zonename/sb_name`.  Extract
432   // the 'sb_name' and return that
433   auto tokens = Ioss::tokenize(zname, "/");
434   if (tokens.size() >= 2) {
435     zname = tokens.back();
436   }
437   return zname;
438 }
439 
cgns_error(int cgnsid,const char * file,const char * function,int lineno,int processor)440 void Iocgns::Utils::cgns_error(int cgnsid, const char *file, const char *function, int lineno,
441                                int processor)
442 {
443   std::ostringstream errmsg;
444   fmt::print(errmsg, "CGNS error '{}' at line {} in file '{}' in function '{}'", cg_get_error(),
445              lineno, file, function);
446   if (processor >= 0) {
447     fmt::print(errmsg, " on processor {}", processor);
448   }
449   fmt::print(errmsg, ". Please report to gdsjaar@sandia.gov if you need help.");
450   if (cgnsid > 0) {
451 #if CG_BUILD_PARALLEL
452     // This can cause a hang if not all processors call this routine
453     // and then the error is not output...
454     //    cgp_close(cgnsid);
455 #else
456     cg_close(cgnsid);
457 #endif
458   }
459   IOSS_ERROR(errmsg);
460 }
461 
check_mesh_type(int cgns_file_ptr)462 Ioss::MeshType Iocgns::Utils::check_mesh_type(int cgns_file_ptr)
463 {
464   // ========================================================================
465   // Get the number of zones (element/structured blocks) in the mesh...
466   int base      = 1;
467   int num_zones = 0;
468   CGCHECKNP(cg_nzones(cgns_file_ptr, base, &num_zones));
469 
470   CGNS_ENUMT(ZoneType_t) common_zone_type = CGNS_ENUMV(ZoneTypeNull);
471 
472   for (int zone = 1; zone <= num_zones; zone++) {
473     CGNS_ENUMT(ZoneType_t) zone_type;
474     CGCHECKNP(cg_zone_type(cgns_file_ptr, base, zone, &zone_type));
475 
476     if (common_zone_type == CGNS_ENUMV(ZoneTypeNull)) {
477       common_zone_type = zone_type;
478     }
479 
480     if (common_zone_type != zone_type) {
481 #if IOSS_ENABLE_HYBRID
482       common_zone_type = CGNS_ENUMV(ZoneTypeUserDefined); // This is how we represent hybrid...
483       break;
484 #else
485       std::ostringstream errmsg;
486       fmt::print(errmsg,
487                  "ERROR: CGNS: Zone {} is not the same zone type as previous zones."
488                  " This is currently not allowed or supported (hybrid mesh).",
489                  zone);
490       IOSS_ERROR(errmsg);
491 #endif
492     }
493   }
494 
495   switch (common_zone_type) {
496   case CGNS_ENUMV(ZoneTypeUserDefined): return Ioss::MeshType::HYBRID;
497   case CGNS_ENUMV(Structured): return Ioss::MeshType::STRUCTURED;
498   case CGNS_ENUMV(Unstructured): return Ioss::MeshType::UNSTRUCTURED;
499   default: return Ioss::MeshType::UNKNOWN;
500   }
501 }
502 
update_db_zone_property(int cgns_file_ptr,const Ioss::Region * region,int myProcessor,bool is_parallel,bool is_parallel_io)503 void Iocgns::Utils::update_db_zone_property(int cgns_file_ptr, const Ioss::Region *region,
504                                             int myProcessor, bool is_parallel, bool is_parallel_io)
505 {
506   // If an output file is closed/opened, make sure that the zones in the Region
507   // match the zones on the database (file). CGNS likes to sort the zones, so they
508   // might be in a different order after reopening.  Update the 'db_zone_id' property...
509   int num_zones = 0;
510   int base      = 1;
511   CGCHECK(cg_nzones(cgns_file_ptr, base, &num_zones));
512 
513   // Read each zone and put names in a map indexed by zone id.
514   // Then iterate all of the region's structured blocks and element blocks
515   // and make sure that the zone exists in the map and then update the 'db_zone'
516   std::map<std::string, int> zones;
517 
518   for (int zone = 1; zone <= num_zones; zone++) {
519     cgsize_t size[9];
520     char     zname[CGNS_MAX_NAME_LENGTH + 1];
521     CGCHECK(cg_zone_read(cgns_file_ptr, base, zone, zname, size));
522     auto name_proc         = decompose_name(std::string(zname), is_parallel && !is_parallel_io);
523     zones[name_proc.first] = zone;
524   }
525 
526   const auto &sblocks = region->get_structured_blocks();
527   for (const auto &block : sblocks) {
528     if (is_parallel_io || block->is_active()) {
529       const std::string &name = block->name();
530       auto               iter = zones.find(name);
531       if (iter != zones.end()) {
532         auto db_zone = (*iter).second;
533         block->property_update("db_zone", db_zone);
534       }
535       else {
536         std::ostringstream errmsg;
537         fmt::print(errmsg,
538                    "ERROR: CGNS: Structured Block '{}' was not found on the CGNS database on "
539                    "processor {}.",
540                    name, myProcessor);
541         IOSS_ERROR(errmsg);
542       }
543     }
544   }
545 
546   const auto &eblocks = region->get_element_blocks();
547   for (const auto &block : eblocks) {
548     const std::string &name = block->name();
549     auto               iter = zones.find(name);
550     if (iter != zones.end()) {
551       auto db_zone = (*iter).second;
552       block->property_update("db_zone", db_zone);
553     }
554     else {
555       std::ostringstream errmsg;
556       fmt::print(
557           errmsg,
558           "ERROR: CGNS: Element Block '{}' was not found on the CGNS database on processor {}.",
559           name, myProcessor);
560       IOSS_ERROR(errmsg);
561     }
562   }
563 }
564 
get_db_zone(const Ioss::GroupingEntity * entity)565 int Iocgns::Utils::get_db_zone(const Ioss::GroupingEntity *entity)
566 {
567   // Returns the zone of the entity as it appears on the cgns database.
568   // Usually, but not always the same as the IOSS zone...
569   // Can differ on fpp reads and maybe writes.
570   if (entity->property_exists("db_zone")) {
571     return entity->get_property("db_zone").get_int();
572   }
573   if (entity->property_exists("zone")) {
574     return entity->get_property("zone").get_int();
575   }
576   std::ostringstream errmsg;
577   fmt::print(errmsg,
578              "ERROR: CGNS: Entity '{}' of type '{}' does not have the 'zone' property assigned.",
579              entity->name(), entity->type_string());
580   IOSS_ERROR(errmsg);
581 }
582 
583 namespace {
584   const size_t CG_CELL_CENTER_FIELD_ID = 1ul << 30;
585   const size_t CG_VERTEX_FIELD_ID      = 1ul << 31;
586 }
587 
index(const Ioss::Field & field)588 size_t Iocgns::Utils::index(const Ioss::Field &field) { return field.get_index() & 0x00ffffff; }
589 
set_field_index(const Ioss::Field & field,size_t index,CGNS_ENUMT (GridLocation_t)location)590 void Iocgns::Utils::set_field_index(const Ioss::Field &field, size_t index,
591                                     CGNS_ENUMT(GridLocation_t) location)
592 {
593   if (location == CGNS_ENUMV(CellCenter)) {
594     index |= CG_CELL_CENTER_FIELD_ID;
595   }
596   if (location == CGNS_ENUMV(Vertex)) {
597     index |= CG_VERTEX_FIELD_ID;
598   }
599   field.set_index(index);
600 }
601 
is_cell_field(const Ioss::Field & field)602 bool Iocgns::Utils::is_cell_field(const Ioss::Field &field)
603 {
604   size_t index = field.get_index();
605   if (index & CG_VERTEX_FIELD_ID) {
606     return false;
607   }
608   if (index & CG_CELL_CENTER_FIELD_ID) {
609     return true;
610   }
611   return !(field.get_name() == "mesh_model_coordinates" ||
612            field.get_name() == "mesh_model_coordinates_x" ||
613            field.get_name() == "mesh_model_coordinates_y" ||
614            field.get_name() == "mesh_model_coordinates_z" ||
615            field.get_name() == "cell_node_ids"); // Default to cell field...
616 }
617 
618 namespace {
619 #if CG_BUILD_PARALLEL
union_zgc_range(Ioss::ZoneConnectivity & zgc_i,const Ioss::ZoneConnectivity & zgc_j)620   void union_zgc_range(Ioss::ZoneConnectivity &zgc_i, const Ioss::ZoneConnectivity &zgc_j)
621   {
622     assert(zgc_i.m_transform == zgc_j.m_transform);
623     for (int i = 0; i < 3; i++) {
624       if (zgc_i.m_ownerRangeBeg[i] <= zgc_i.m_ownerRangeEnd[i]) {
625         zgc_i.m_ownerRangeBeg[i] = std::min(zgc_i.m_ownerRangeBeg[i], zgc_j.m_ownerRangeBeg[i]);
626         zgc_i.m_ownerRangeEnd[i] = std::max(zgc_i.m_ownerRangeEnd[i], zgc_j.m_ownerRangeEnd[i]);
627       }
628       else {
629         zgc_i.m_ownerRangeBeg[i] = std::max(zgc_i.m_ownerRangeBeg[i], zgc_j.m_ownerRangeBeg[i]);
630         zgc_i.m_ownerRangeEnd[i] = std::min(zgc_i.m_ownerRangeEnd[i], zgc_j.m_ownerRangeEnd[i]);
631       }
632 
633       if (zgc_i.m_donorRangeBeg[i] <= zgc_i.m_donorRangeEnd[i]) {
634         zgc_i.m_donorRangeBeg[i] = std::min(zgc_i.m_donorRangeBeg[i], zgc_j.m_donorRangeBeg[i]);
635         zgc_i.m_donorRangeEnd[i] = std::max(zgc_i.m_donorRangeEnd[i], zgc_j.m_donorRangeEnd[i]);
636       }
637       else {
638         zgc_i.m_donorRangeBeg[i] = std::max(zgc_i.m_donorRangeBeg[i], zgc_j.m_donorRangeBeg[i]);
639         zgc_i.m_donorRangeEnd[i] = std::min(zgc_i.m_donorRangeEnd[i], zgc_j.m_donorRangeEnd[i]);
640       }
641     }
642   }
643 #endif
644 
consolidate_zgc(const Ioss::Region & region)645   void consolidate_zgc(const Ioss::Region &region)
646   {
647     // In parallel, the zgc are not necessarily consistent across processors...
648     // and the owner/donor ranges are processor specific.
649     // Need to make sure all processors have a consistent list of zgc and the
650     // owner/donor ranges contain the union of the ranges on each
651     // processor.
652     // ...Could do this on a per sb basis, but better to do all at once...
653     // Data:
654     // CGNS_MAX_NAME_LENGTH - connectionName -- CGNS_MAX_NAME_LENGTH char max
655     // 1 - int zone
656     // 1 - int donor_zone -- get by mapping donorName to zone
657     // 6 cgsize_t[6] ownerRange (can probably use 32-bit int...)
658     // 6 cgsize_t[6] donorRange (can probably use 32-bit int...)
659     // 3 int[3] transform; (values range from -3 to +3 (could store as single int)
660     // CGNS_MAX_NAME_LENGTH characters + 17 ints / connection.
661 
662     PAR_UNUSED(region);
663 #if CG_BUILD_PARALLEL
664     const int BYTE_PER_NAME = CGNS_MAX_NAME_LENGTH;
665     const int INT_PER_ZGC   = 17;
666     // Gather all to processor 0, consolidate, and then scatter back...
667     int         my_count          = 0;
668     const auto &structured_blocks = region.get_structured_blocks();
669     for (const auto &sb : structured_blocks) {
670       my_count += std::count_if(
671           sb->m_zoneConnectivity.begin(), sb->m_zoneConnectivity.end(),
672           [](const Ioss::ZoneConnectivity &z) { return !z.is_from_decomp() && z.is_active(); });
673     }
674 
675     std::vector<int> rcv_data_cnt;
676     region.get_database()->util().all_gather(
677         my_count, rcv_data_cnt); // Allgather instead of gather so can bail if count=0
678     int count = std::accumulate(rcv_data_cnt.begin(), rcv_data_cnt.end(), 0);
679     if (count == 0) {
680       for (auto &sb : structured_blocks) {
681         sb->m_zoneConnectivity.clear();
682       }
683       return;
684     }
685 
686     std::vector<char> snd_zgc_name(my_count * BYTE_PER_NAME);
687     std::vector<int>  snd_zgc_data(my_count * INT_PER_ZGC);
688 
689     // Pack data for gathering to processor 0...
690     int off_name = 0;
691     int off_data = 0;
692     int off_cnt  = 0;
693 
694     // ========================================================================
695     auto pack_lambda = [&off_data, &off_name, &off_cnt, &snd_zgc_data,
696                         &snd_zgc_name](const std::vector<Ioss::ZoneConnectivity> &zgc) {
697       for (const auto &z : zgc) {
698         if (!z.is_from_decomp() && z.is_active()) {
699           Ioss::Utils::copy_string(&snd_zgc_name[off_name], z.m_connectionName, BYTE_PER_NAME);
700           off_cnt++;
701           off_name += BYTE_PER_NAME;
702 
703           snd_zgc_data[off_data++] = z.m_ownerZone;
704           snd_zgc_data[off_data++] = z.m_donorZone;
705 
706           snd_zgc_data[off_data++] = z.m_ownerRangeBeg[0];
707           snd_zgc_data[off_data++] = z.m_ownerRangeBeg[1];
708           snd_zgc_data[off_data++] = z.m_ownerRangeBeg[2];
709           snd_zgc_data[off_data++] = z.m_ownerRangeEnd[0];
710           snd_zgc_data[off_data++] = z.m_ownerRangeEnd[1];
711           snd_zgc_data[off_data++] = z.m_ownerRangeEnd[2];
712 
713           snd_zgc_data[off_data++] = z.m_donorRangeBeg[0];
714           snd_zgc_data[off_data++] = z.m_donorRangeBeg[1];
715           snd_zgc_data[off_data++] = z.m_donorRangeBeg[2];
716           snd_zgc_data[off_data++] = z.m_donorRangeEnd[0];
717           snd_zgc_data[off_data++] = z.m_donorRangeEnd[1];
718           snd_zgc_data[off_data++] = z.m_donorRangeEnd[2];
719 
720           snd_zgc_data[off_data++] = z.m_transform[0];
721           snd_zgc_data[off_data++] = z.m_transform[1];
722           snd_zgc_data[off_data++] = z.m_transform[2];
723         }
724       }
725     };
726     // ========================================================================
727 
728     off_data = off_name = off_cnt = 0;
729     for (const auto &sb : structured_blocks) {
730       pack_lambda(sb->m_zoneConnectivity);
731     }
732     assert(off_cnt == my_count);
733     assert(my_count == 0 || (off_data % my_count == 0));
734     assert(my_count == 0 || (off_data / my_count == INT_PER_ZGC));
735     assert(my_count == 0 || (off_name % my_count == 0 && off_name / my_count == BYTE_PER_NAME));
736 
737     std::vector<char> rcv_zgc_name;
738     std::vector<int>  rcv_zgc_data;
739     region.get_database()->util().gather(my_count, BYTE_PER_NAME, snd_zgc_name, rcv_zgc_name);
740     region.get_database()->util().gather(my_count, INT_PER_ZGC, snd_zgc_data, rcv_zgc_data);
741 
742     // Processor 0 now has all the zgc instances from all blocks on all processors.
743     std::vector<Ioss::ZoneConnectivity> zgc;
744     if (region.get_database()->util().parallel_rank() == 0) {
745       zgc.reserve(count);
746 
747       // Unpack data...
748       off_data = 0;
749       off_name = 0;
750       for (int i = 0; i < count; i++) {
751         std::string name{&rcv_zgc_name[off_name]};
752         off_name += BYTE_PER_NAME;
753         int         zone  = rcv_zgc_data[off_data++];
754         int         donor = rcv_zgc_data[off_data++];
755         Ioss::IJK_t range_beg{
756             {rcv_zgc_data[off_data++], rcv_zgc_data[off_data++], rcv_zgc_data[off_data++]}};
757         Ioss::IJK_t range_end{
758             {rcv_zgc_data[off_data++], rcv_zgc_data[off_data++], rcv_zgc_data[off_data++]}};
759         Ioss::IJK_t donor_beg{
760             {rcv_zgc_data[off_data++], rcv_zgc_data[off_data++], rcv_zgc_data[off_data++]}};
761         Ioss::IJK_t donor_end{
762             {rcv_zgc_data[off_data++], rcv_zgc_data[off_data++], rcv_zgc_data[off_data++]}};
763         Ioss::IJK_t transform{
764             {rcv_zgc_data[off_data++], rcv_zgc_data[off_data++], rcv_zgc_data[off_data++]}};
765         zgc.emplace_back(name, zone, "", donor, transform, range_beg, range_end, donor_beg,
766                          donor_end);
767       }
768       assert(off_data % count == 0);
769       assert(off_data / count == INT_PER_ZGC);
770       assert(off_name % count == 0 && off_name / count == BYTE_PER_NAME);
771 
772 #if IOSS_DEBUG_OUTPUT
773       fmt::print(Ioss::DEBUG(), "ZGC_CONSOLIDATE: Before consolidation: ({})\n", zgc.size());
774       for (const auto &z : zgc) {
775         fmt::print(Ioss::DEBUG(), "\tOZ {}{}\n", z.m_ownerZone, z);
776       }
777 #endif
778 
779       // Consolidate down to the minimum set that has the union of all ranges.
780       for (size_t i = 0; i < zgc.size(); i++) {
781         if (zgc[i].is_active()) {
782           auto owner_zone = zgc[i].m_ownerZone;
783           auto donor_zone = zgc[i].m_donorZone;
784 
785           for (size_t j = i + 1; j < zgc.size(); j++) {
786             if (zgc[j].is_active() && zgc[j].m_connectionName == zgc[i].m_connectionName &&
787                 zgc[j].m_ownerZone == owner_zone) {
788               if (zgc[j].m_donorZone == donor_zone) {
789                 // Found another instance of the "same" zgc...  Union the ranges
790                 union_zgc_range(zgc[i], zgc[j]);
791                 assert(zgc[i].is_valid());
792 
793                 // Flag the 'j' instance so it is processed only this time.
794                 zgc[j].m_isActive = false;
795               }
796               else {
797                 // We have a bad zgc -- name and owner_zone match, but not donor_zone.
798                 std::ostringstream errmsg;
799                 fmt::print(errmsg,
800                            "ERROR: CGNS: Found zgc named '{}' on zone {} which has two different "
801                            "donor zones: {} and {}\n",
802                            zgc[i].m_connectionName, owner_zone, donor_zone, zgc[j].m_donorZone);
803                 IOSS_ERROR(errmsg);
804               }
805             }
806           }
807         }
808       }
809 
810       // Cull out all 'non-active' zgc instances (owner and donor zone <= 0)
811       zgc.erase(std::remove_if(zgc.begin(), zgc.end(),
812                                [](Ioss::ZoneConnectivity &z) { return !z.is_active(); }),
813                 zgc.end());
814 
815       count = (int)zgc.size();
816       snd_zgc_name.resize(count * BYTE_PER_NAME);
817       snd_zgc_data.resize(count * INT_PER_ZGC);
818       // Now have a unique set of zgc over all processors with a union
819       // of the ranges on each individual processor.  Pack the data
820       // and broadcast back to all processors so all processors can
821       // output the same data for Zone Connectivity.
822       off_data = off_name = off_cnt = 0;
823       pack_lambda(zgc);
824 
825       assert(off_cnt == count);
826       assert(off_data % count == 0);
827       assert(off_data / count == INT_PER_ZGC);
828       assert(off_name % count == 0 && off_name / count == BYTE_PER_NAME);
829 
830 #if IOSS_DEBUG_OUTPUT
831       fmt::print(Ioss::DEBUG(), "ZGC_CONSOLIDATE: After consolidation: ({})\n", zgc.size());
832       for (const auto &z : zgc) {
833         fmt::print(Ioss::DEBUG(), "\tOZ {}{}\n", z.m_ownerZone, z);
834       }
835 #endif
836     } // End of processor 0 only processing...
837 
838     // Send the list of unique zgc instances to all processors so they can all output.
839     MPI_Bcast(&count, 1, MPI_INT, 0, region.get_database()->util().communicator());
840     snd_zgc_name.resize(count * BYTE_PER_NAME);
841     snd_zgc_data.resize(count * INT_PER_ZGC);
842     MPI_Bcast(snd_zgc_name.data(), (int)snd_zgc_name.size(), MPI_BYTE, 0,
843               region.get_database()->util().communicator());
844     MPI_Bcast(snd_zgc_data.data(), (int)snd_zgc_data.size(), MPI_INT, 0,
845               region.get_database()->util().communicator());
846 
847     // Now clean out existing ZGC lists for all blocks and add on the consolidated instances.
848     // Also create a vector for mapping from zone to sb name.
849     std::vector<std::string> sb_names(structured_blocks.size() + 1);
850     for (auto &sb : structured_blocks) {
851       sb->m_zoneConnectivity.clear();
852       auto zone = sb->get_property("zone").get_int();
853       assert(zone < (int)sb_names.size());
854       sb_names[zone] = sb->name();
855     }
856 
857     // Unpack data and apply to the correct structured block.
858     off_data = 0;
859     off_name = 0;
860     for (int i = 0; i < count; i++) {
861       std::string name{&snd_zgc_name[off_name]};
862       off_name += BYTE_PER_NAME;
863       int zone = snd_zgc_data[off_data++];
864       assert(zone < (int)sb_names.size());
865       int donor = snd_zgc_data[off_data++];
866       assert(donor < (int)sb_names.size());
867       Ioss::IJK_t range_beg{
868           {snd_zgc_data[off_data++], snd_zgc_data[off_data++], snd_zgc_data[off_data++]}};
869       Ioss::IJK_t range_end{
870           {snd_zgc_data[off_data++], snd_zgc_data[off_data++], snd_zgc_data[off_data++]}};
871       Ioss::IJK_t donor_beg{
872           {snd_zgc_data[off_data++], snd_zgc_data[off_data++], snd_zgc_data[off_data++]}};
873       Ioss::IJK_t donor_end{
874           {snd_zgc_data[off_data++], snd_zgc_data[off_data++], snd_zgc_data[off_data++]}};
875       Ioss::IJK_t transform{
876           {snd_zgc_data[off_data++], snd_zgc_data[off_data++], snd_zgc_data[off_data++]}};
877 
878       auto sb = structured_blocks[zone - 1];
879       assert(sb->get_property("zone").get_int() == zone);
880       sb->m_zoneConnectivity.emplace_back(name, zone, sb_names[donor], donor, transform, range_beg,
881                                           range_end, donor_beg, donor_end);
882     }
883 #endif
884   }
885 } // namespace
886 
output_assembly(int file_ptr,const Ioss::Assembly * assembly,bool is_parallel_io,bool appending)887 void Iocgns::Utils::output_assembly(int file_ptr, const Ioss::Assembly *assembly,
888                                     bool is_parallel_io, bool appending)
889 {
890   int base = 1;
891   int fam  = 0;
892   CGERR(cg_family_write(file_ptr, base, assembly->name().c_str(), &fam));
893 
894   int64_t id = assembly->get_optional_property("id", 0);
895   CGERR(cg_goto(file_ptr, base, "Family_t", fam, nullptr));
896   CGERR(cg_descriptor_write("FamVC_TypeId", "0"));
897   CGERR(cg_descriptor_write("FamVC_TypeName", "Unspecified"));
898   CGERR(cg_descriptor_write("FamVC_UserId", std::to_string(id).c_str()));
899   CGERR(cg_descriptor_write("FamVC_UserName", assembly->name().c_str()));
900 
901   const auto &members = assembly->get_members();
902   // Now, iterate the members of the assembly and add the reference to the structured block
903   if (assembly->get_member_type() == Ioss::STRUCTUREDBLOCK) {
904     for (const auto &mem : members) {
905       base           = mem->get_property("base").get_int();
906       const auto *sb = dynamic_cast<const Ioss::StructuredBlock *>(mem);
907       Ioss::Utils::check_dynamic_cast(sb);
908       if (is_parallel_io || sb->is_active()) {
909         int db_zone = get_db_zone(sb);
910         if (cg_goto(file_ptr, base, "Zone_t", db_zone, "end") == CG_OK) {
911           CGERR(cg_famname_write(assembly->name().c_str()));
912         }
913       }
914     }
915   }
916   else if (assembly->get_member_type() == Ioss::ELEMENTBLOCK) {
917     for (const auto &mem : members) {
918       if (appending) {
919         // Modifying an existing database so the element blocks
920         // should exist on the output database...
921         int db_zone = get_db_zone(mem);
922         if (cg_goto(file_ptr, base, "Zone_t", db_zone, "end") == CG_OK) {
923           CGERR(cg_famname_write(assembly->name().c_str()));
924         }
925       }
926       else {
927         // The element blocks have not yet been output.  To make
928         // it easier when they are written, add a property that
929         // specifies what assembly they are in.  Currently, the way
930         // CGNS represents assemblies limits membership to at most one
931         // assembly.
932         auto *new_mem = const_cast<Ioss::GroupingEntity *>(mem);
933         new_mem->property_add(Ioss::Property("assembly", assembly->name()));
934       }
935     }
936   }
937 }
938 
output_assemblies(int file_ptr,const Ioss::Region & region,bool is_parallel_io)939 void Iocgns::Utils::output_assemblies(int file_ptr, const Ioss::Region &region, bool is_parallel_io)
940 {
941   region.get_database()->progress("\tOutput Assemblies");
942   const auto &assemblies = region.get_assemblies();
943   for (const auto &assem : assemblies) {
944     output_assembly(file_ptr, assem, is_parallel_io);
945   }
946 }
write_state_meta_data(int file_ptr,const Ioss::Region & region,bool is_parallel_io)947 void Iocgns::Utils::write_state_meta_data(int file_ptr, const Ioss::Region &region,
948                                           bool is_parallel_io)
949 {
950   // Write the metadata to the state file...
951   // Not everything is needed, we just need the zones output so we can put the FlowSolutionPointers
952   // node under the Zone nodes.
953   int base           = 0;
954   int phys_dimension = region.get_property("spatial_dimension").get_int();
955   CGERR(cg_base_write(file_ptr, "Base", phys_dimension, phys_dimension, &base));
956 
957   region.get_database()->progress("\tElement Blocks");
958   const Ioss::ElementBlockContainer &ebs = region.get_element_blocks();
959   for (auto eb : ebs) {
960     const std::string &name    = eb->name();
961     int                db_zone = 0;
962     cgsize_t           size[3] = {0, 0, 0};
963     size[1]                    = eb->get_property("zone_element_count").get_int();
964     size[0]                    = eb->get_property("zone_node_count").get_int();
965 
966     if (is_parallel_io) {
967     }
968 
969     CGERR(cg_zone_write(file_ptr, base, name.c_str(), size, CGNS_ENUMV(Unstructured), &db_zone));
970     int prev_db_zone = get_db_zone(eb);
971     if (db_zone != prev_db_zone) {
972       std::ostringstream errmsg;
973       fmt::print(
974           errmsg,
975           "ERROR: CGNS: The 'db_zone' does not match in the state file {} and the base file {}.",
976           db_zone, prev_db_zone);
977       IOSS_ERROR(errmsg);
978     }
979   }
980 
981   region.get_database()->progress("\tStructured Blocks");
982   const auto &structured_blocks = region.get_structured_blocks();
983 
984   // If `is_parallel` and `!is_parallel_io`, then writing file-per-processor
985   bool is_parallel = region.get_database()->util().parallel_size() > 1;
986   int  rank        = region.get_database()->util().parallel_rank();
987   for (const auto &sb : structured_blocks) {
988     cgsize_t size[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
989     if (is_parallel_io) {
990       size[3] = sb->get_property("ni_global").get_int();
991       size[4] = sb->get_property("nj_global").get_int();
992       size[5] = sb->get_property("nk_global").get_int();
993     }
994     else {
995       size[3] = sb->get_property("ni").get_int();
996       size[4] = sb->get_property("nj").get_int();
997       size[5] = sb->get_property("nk").get_int();
998     }
999     size[0] = size[3] + 1;
1000     size[1] = size[4] + 1;
1001     size[2] = size[5] + 1;
1002 
1003     if (is_parallel_io || sb->is_active()) {
1004       std::string name = sb->name();
1005       if (is_parallel && !is_parallel_io) {
1006         name += "_proc-";
1007         name += std::to_string(rank);
1008       }
1009       int db_zone = 0;
1010       CGERR(cg_zone_write(file_ptr, base, name.c_str(), size, CGNS_ENUMV(Structured), &db_zone));
1011       if (db_zone != sb->get_property("db_zone").get_int()) {
1012         std::ostringstream errmsg;
1013         fmt::print(
1014             errmsg,
1015             "ERROR: CGNS: The 'db_zone' does not match in the state file {} and the base file {}.",
1016             db_zone, sb->get_property("db_zone").get_int());
1017         IOSS_ERROR(errmsg);
1018       }
1019     }
1020   }
1021 }
1022 
common_write_meta_data(int file_ptr,const Ioss::Region & region,std::vector<size_t> & zone_offset,bool is_parallel_io)1023 size_t Iocgns::Utils::common_write_meta_data(int file_ptr, const Ioss::Region &region,
1024                                              std::vector<size_t> &zone_offset, bool is_parallel_io)
1025 {
1026 #if !IOSS_ENABLE_HYBRID
1027   // Make sure mesh is not hybrid...
1028   if (region.mesh_type() == Ioss::MeshType::HYBRID) {
1029     std::ostringstream errmsg;
1030     fmt::print(errmsg,
1031                "ERROR: CGNS: The mesh on region '{}' is of type 'hybrid'."
1032                " This is currently not allowed or supported.",
1033                region.name());
1034     IOSS_ERROR(errmsg);
1035   }
1036 #endif
1037 
1038   region.get_database()->progress("\tEnter common_write_meta_data");
1039   int base           = 0;
1040   int phys_dimension = region.get_property("spatial_dimension").get_int();
1041   CGERR(cg_base_write(file_ptr, "Base", phys_dimension, phys_dimension, &base));
1042 
1043   CGERR(cg_goto(file_ptr, base, "end"));
1044   std::string version = "IOSS: CGNS Writer version " + std::string{__DATE__} + ", " +
1045                         Ioss::Utils::platform_information();
1046 
1047 #if CG_BUILD_PARALLEL
1048   if (is_parallel_io) {
1049     // Need to make sure the version string is the same on all
1050     // processors since they are all writing to the same file.  There
1051     // was a difficult to track bug in which the
1052     // platform_information() contained different node info ("ser9"
1053     // and "ser43") on certain ranks which caused an HDF5 failure way
1054     // downstream -- basically at file close.
1055     char tmp[2048];
1056     Ioss::Utils::copy_string(tmp, version, 2048);
1057     MPI_Bcast(tmp, (int)version.size() + 1, MPI_BYTE, 0,
1058               region.get_database()->util().communicator());
1059     version = std::string{tmp};
1060   }
1061 #endif
1062 
1063   CGERR(cg_descriptor_write("Information", version.c_str()));
1064   CGERR(cg_goto(file_ptr, base, "end"));
1065   CGERR(cg_dataclass_write(CGNS_ENUMV(Dimensional)));
1066   CGERR(cg_units_write(CGNS_ENUMV(MassUnitsUserDefined), CGNS_ENUMV(LengthUnitsUserDefined),
1067                        CGNS_ENUMV(TimeUnitsUserDefined), CGNS_ENUMV(TemperatureUnitsUserDefined),
1068                        CGNS_ENUMV(AngleUnitsUserDefined)))
1069 
1070   // Output the sidesets as Family_t nodes
1071   region.get_database()->progress("\tOutput Sidesets");
1072   const auto &sidesets = region.get_sidesets();
1073   for (const auto &ss : sidesets) {
1074     int fam = 0;
1075     CGERR(cg_family_write(file_ptr, base, ss->name().c_str(), &fam));
1076 
1077     int         bc_index = 0;
1078     CGNS_ENUMT(BCType_t) bocotype = CGNS_ENUMV(BCTypeNull);
1079     if (ss->property_exists("bc_type")) {
1080       bocotype = (CGNS_ENUMT(BCType_t))ss->get_property("bc_type").get_int();
1081     }
1082 
1083     int64_t id = ss->get_optional_property("id", fam);
1084 
1085     CGERR(cg_fambc_write(file_ptr, base, fam, "FamBC", bocotype, &bc_index));
1086     CGERR(cg_goto(file_ptr, base, "Family_t", fam, nullptr));
1087     CGERR(cg_descriptor_write("FamBC_TypeId", std::to_string(bocotype).c_str()));
1088     CGERR(cg_descriptor_write("FamBC_TypeName", cg_BCTypeName(bocotype)));
1089     CGERR(cg_descriptor_write("FamBC_UserId", std::to_string(id).c_str()));
1090     CGERR(cg_descriptor_write("FamBC_UserName", ss->name().c_str()));
1091   }
1092 
1093   // NOTE: Element Block zone write is deferred to put_field_internal so can
1094   // generate the node count based on connectivity traversal...
1095   // Just getting processor element count here...
1096   region.get_database()->progress("\tElement Blocks");
1097   const auto &element_blocks = region.get_element_blocks();
1098 
1099   size_t element_count = 0;
1100   for (const auto &eb : element_blocks) {
1101     int64_t local_count = eb->entity_count();
1102 #if CG_BUILD_PARALLEL
1103     if (is_parallel_io) {
1104       int64_t start = 0;
1105       MPI_Exscan(&local_count, &start, 1, Ioss::mpi_type(start), MPI_SUM,
1106                  region.get_database()->util().communicator());
1107       // Of the cells/elements in this zone, this proc handles
1108       // those starting at 'proc_offset+1' to 'proc_offset+num_entity'
1109       eb->property_update("proc_offset", start);
1110     }
1111 #endif
1112     element_count += (size_t)local_count;
1113   }
1114 
1115   region.get_database()->progress("\tStructured Blocks");
1116   const auto &structured_blocks = region.get_structured_blocks();
1117 
1118   // If `is_parallel` and `!is_parallel_io`, then writing file-per-processor
1119   bool is_parallel = region.get_database()->util().parallel_size() > 1;
1120   int  rank        = region.get_database()->util().parallel_rank();
1121   int  zone        = 0;
1122   for (const auto &sb : structured_blocks) {
1123     cgsize_t size[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
1124     if (is_parallel_io) {
1125       size[3] = sb->get_property("ni_global").get_int();
1126       size[4] = sb->get_property("nj_global").get_int();
1127       size[5] = sb->get_property("nk_global").get_int();
1128     }
1129     else {
1130       size[3] = sb->get_property("ni").get_int();
1131       size[4] = sb->get_property("nj").get_int();
1132       size[5] = sb->get_property("nk").get_int();
1133     }
1134     size[0] = size[3] + 1;
1135     size[1] = size[4] + 1;
1136     size[2] = size[5] + 1;
1137 
1138     if (is_parallel_io || sb->is_active()) {
1139       std::string name = sb->name();
1140       if (is_parallel && !is_parallel_io) {
1141         name += "_proc-";
1142         name += std::to_string(rank);
1143       }
1144       int db_zone = 0;
1145       CGERR(cg_zone_write(file_ptr, base, name.c_str(), size, CGNS_ENUMV(Structured), &db_zone));
1146       sb->property_update("db_zone", db_zone);
1147       // Add GridCoordinates Node...
1148       int grid_idx = 0;
1149       CGERR(cg_grid_write(file_ptr, base, db_zone, "GridCoordinates", &grid_idx));
1150     }
1151     else {
1152       sb->property_update("db_zone", -1);
1153     }
1154     zone++;
1155     assert(zone > 0);
1156     zone_offset[zone] = zone_offset[zone - 1] + sb->get_property("cell_count").get_int();
1157     sb->property_update("zone", zone);
1158     sb->property_update("base", base);
1159   }
1160 
1161   // TODO: Are there multi-level assemblies in CGNS?
1162   // Output the assembly data.
1163   // The assembly itself is Family data at top level.
1164   // For each assembly, iterate members and add the 'FamilyName' node linking it to the Assembly
1165   output_assemblies(file_ptr, region, is_parallel_io);
1166 
1167   region.get_database()->progress("\tMapping sb_name to zone");
1168   if (is_parallel_io || !is_parallel) { // Only for single file output or serial...
1169     // Create a vector for mapping from sb_name to zone -- used to update zgc instances
1170     std::map<std::string, int> sb_zone;
1171     for (const auto &sb : structured_blocks) {
1172       zone                = sb->get_property("zone").get_int();
1173       sb_zone[sb->name()] = zone;
1174     }
1175 
1176     // Update zgc instances to make sure the ownerZone and donorZone are
1177     // consistent with the zones on the output database (from cg_zone_write call)
1178     for (const auto &sb : structured_blocks) {
1179       int owner_zone = sb->get_property("zone").get_int();
1180       for (auto &zgc : sb->m_zoneConnectivity) {
1181         int donor_zone  = sb_zone[zgc.m_donorName];
1182         zgc.m_ownerZone = owner_zone;
1183         zgc.m_ownerGUID = region.get_database()->util().generate_guid(owner_zone);
1184         zgc.m_donorZone = donor_zone;
1185         zgc.m_donorGUID = region.get_database()->util().generate_guid(donor_zone);
1186       }
1187     }
1188   }
1189 
1190   region.get_database()->progress("\tConsolidate zgc");
1191   if (is_parallel_io) {
1192     consolidate_zgc(region);
1193   }
1194 
1195   region.get_database()->progress("\tStructured Block Loop");
1196   for (const auto &sb : structured_blocks) {
1197     if (!is_parallel_io && !sb->is_active()) {
1198       continue;
1199     }
1200 
1201     auto        db_zone = get_db_zone(sb);
1202     std::string name    = sb->name();
1203     if (is_parallel && !is_parallel_io) {
1204       name += "_proc-";
1205       name += std::to_string(rank);
1206     }
1207 
1208     // Transfer boundary condition nodes...
1209     // The bc.m_ownerRange argument needs to be the union of the size on all processors
1210     // Instead of requiring that of the caller, do the union in this routine.
1211     // TODO: Calculate it outside of the loop...
1212     // Need to handle possible range == 0,0,0.  Only affects the beg data...
1213     if (is_parallel_io) {
1214       region.get_database()->progress("\t\tBoundary Conditions");
1215     }
1216     CGNSIntVector bc_range(sb->m_boundaryConditions.size() * 6);
1217     size_t        idx = 0;
1218     for (const auto &bc : sb->m_boundaryConditions) {
1219       for (size_t i = 0; i < 3; i++) {
1220         if (bc.m_rangeBeg[i] == 0) {
1221           bc_range[idx++] = std::numeric_limits<int>::min();
1222         }
1223         else {
1224           bc_range[idx++] = -bc.m_rangeBeg[i];
1225         }
1226       }
1227       for (size_t i = 0; i < 3; i++) {
1228         bc_range[idx++] = bc.m_rangeEnd[i];
1229       }
1230     }
1231 
1232     if (is_parallel_io) {
1233       region.get_database()->util().global_array_minmax(bc_range, Ioss::ParallelUtils::DO_MAX);
1234     }
1235 
1236     for (idx = 0; idx < bc_range.size(); idx += 6) {
1237       bc_range[idx + 0] = -bc_range[idx + 0];
1238       bc_range[idx + 1] = -bc_range[idx + 1];
1239       bc_range[idx + 2] = -bc_range[idx + 2];
1240     }
1241 
1242     Ioss::IJK_t offset;
1243     offset[0] = sb->get_property("offset_i").get_int();
1244     offset[1] = sb->get_property("offset_j").get_int();
1245     offset[2] = sb->get_property("offset_k").get_int();
1246 
1247     idx = 0;
1248     for (const auto &bc : sb->m_boundaryConditions) {
1249       int bc_idx = 0;
1250       if (!is_parallel_io) {
1251         bc_range[idx + 0] -= offset[0];
1252         bc_range[idx + 1] -= offset[1];
1253         bc_range[idx + 2] -= offset[2];
1254         bc_range[idx + 3] -= offset[0];
1255         bc_range[idx + 4] -= offset[1];
1256         bc_range[idx + 5] -= offset[2];
1257       }
1258 
1259       if (is_parallel_io ||
1260           (bc_range[idx + 3] > 0 && bc_range[idx + 4] > 0 && bc_range[idx + 5] > 0)) {
1261         CGERR(cg_boco_write(file_ptr, base, db_zone, bc.m_bcName.c_str(), CGNS_ENUMV(FamilySpecified),
1262                             CGNS_ENUMV(PointRange), 2, &bc_range[idx], &bc_idx));
1263         CGERR(
1264             cg_goto(file_ptr, base, name.c_str(), 0, "ZoneBC_t", 1, bc.m_bcName.c_str(), 0, "end"));
1265         CGERR(cg_famname_write(bc.m_famName.c_str()));
1266         CGERR(cg_boco_gridlocation_write(file_ptr, base, db_zone, bc_idx, CGNS_ENUMV(Vertex)));
1267       }
1268       idx += 6;
1269     }
1270     // Transfer Zone Grid Connectivity...
1271     if (is_parallel_io) {
1272       region.get_database()->progress("\t\tZone Grid Connectivity");
1273     }
1274 
1275     // Used to detect duplicate zgc names in parallel but non-parallel-io case
1276     std::set<std::string> zgc_names;
1277 
1278     for (const auto &zgc : sb->m_zoneConnectivity) {
1279       if (zgc.is_valid() && zgc.is_active()) {
1280         int                     zgc_idx = 0;
1281         std::array<cgsize_t, 6> owner_range{{zgc.m_ownerRangeBeg[0], zgc.m_ownerRangeBeg[1],
1282                                              zgc.m_ownerRangeBeg[2], zgc.m_ownerRangeEnd[0],
1283                                              zgc.m_ownerRangeEnd[1], zgc.m_ownerRangeEnd[2]}};
1284         std::array<cgsize_t, 6> donor_range{{zgc.m_donorRangeBeg[0], zgc.m_donorRangeBeg[1],
1285                                              zgc.m_donorRangeBeg[2], zgc.m_donorRangeEnd[0],
1286                                              zgc.m_donorRangeEnd[1], zgc.m_donorRangeEnd[2]}};
1287 
1288         std::string donor_name    = zgc.m_donorName;
1289         std::string connect_name  = zgc.m_connectionName;
1290         std::string original_name = zgc.m_connectionName;
1291         if (is_parallel && !is_parallel_io) {
1292           if (zgc.is_from_decomp()) {
1293             connect_name = std::to_string(zgc.m_ownerGUID) + "--" + std::to_string(zgc.m_donorGUID);
1294           }
1295           else {
1296             auto iter = zgc_names.insert(connect_name);
1297             if (!iter.second) {
1298               // Name collision...
1299               for (char c = 'A'; c <= 'Z'; c++) {
1300                 std::string potential = connect_name + c;
1301                 iter                  = zgc_names.insert(potential);
1302                 if (iter.second) {
1303                   connect_name = potential;
1304                   break;
1305                 }
1306               }
1307               if (connect_name == zgc.m_connectionName) {
1308                 bool done = false;
1309                 for (char c1 = 'A'; c1 <= 'Z' && !done; c1++) {
1310                   for (char c2 = 'A'; c2 <= 'Z' && !done; c2++) {
1311                     std::string potential = connect_name + c1 + c2;
1312                     iter                  = zgc_names.insert(potential);
1313                     if (iter.second) {
1314                       connect_name = potential;
1315                       done         = true;
1316                     }
1317                   }
1318                 }
1319                 if (connect_name == zgc.m_connectionName) {
1320                   std::ostringstream errmsg;
1321                   fmt::print(errmsg,
1322                              "ERROR: CGNS: Duplicate ZGC Name '{}' on zone '{}', processor {}\n",
1323                              zgc.m_connectionName, sb->name(), zgc.m_ownerProcessor);
1324                   IOSS_ERROR(errmsg);
1325                 }
1326               }
1327             }
1328           }
1329           donor_name += "_proc-";
1330           donor_name += std::to_string(zgc.m_donorProcessor);
1331           owner_range[0] -= zgc.m_ownerOffset[0];
1332           owner_range[1] -= zgc.m_ownerOffset[1];
1333           owner_range[2] -= zgc.m_ownerOffset[2];
1334           owner_range[3] -= zgc.m_ownerOffset[0];
1335           owner_range[4] -= zgc.m_ownerOffset[1];
1336           owner_range[5] -= zgc.m_ownerOffset[2];
1337 
1338           donor_range[0] -= zgc.m_donorOffset[0];
1339           donor_range[1] -= zgc.m_donorOffset[1];
1340           donor_range[2] -= zgc.m_donorOffset[2];
1341           donor_range[3] -= zgc.m_donorOffset[0];
1342           donor_range[4] -= zgc.m_donorOffset[1];
1343           donor_range[5] -= zgc.m_donorOffset[2];
1344         }
1345         CGERR(cg_1to1_write(file_ptr, base, db_zone, connect_name.c_str(), donor_name.c_str(),
1346                             owner_range.data(), donor_range.data(), zgc.m_transform.data(),
1347                             &zgc_idx));
1348 
1349         if (zgc.is_from_decomp()) {
1350           CGERR(cg_goto(file_ptr, base, "Zone_t", db_zone, "ZoneGridConnectivity", 0,
1351                         "GridConnectivity1to1_t", zgc_idx, "end"));
1352           CGERR(cg_descriptor_write("Decomp", "is_decomp"));
1353         }
1354         else if (original_name != connect_name) {
1355           CGERR(cg_goto(file_ptr, base, "Zone_t", db_zone, "ZoneGridConnectivity", 0,
1356                         "GridConnectivity1to1_t", zgc_idx, "end"));
1357           CGERR(cg_descriptor_write("OriginalName", original_name.c_str()));
1358         }
1359       }
1360     }
1361   }
1362 
1363   region.get_database()->progress("\tReturn from common_write_meta_data");
1364   return element_count;
1365 }
1366 
map_cgns_to_topology_type(CGNS_ENUMT (ElementType_t)type)1367 std::string Iocgns::Utils::map_cgns_to_topology_type(CGNS_ENUMT(ElementType_t) type)
1368 {
1369   std::string topology = "unknown";
1370   switch (type) {
1371   case CGNS_ENUMV(NODE): topology = Ioss::Node::name; break;
1372   case CGNS_ENUMV(BAR_2): topology = Ioss::Beam2::name; break;
1373   case CGNS_ENUMV(BAR_3): topology = Ioss::Beam3::name; break;
1374   case CGNS_ENUMV(TRI_3): topology = Ioss::Tri3::name; break;
1375   case CGNS_ENUMV(TRI_6): topology = Ioss::Tri6::name; break;
1376   case CGNS_ENUMV(QUAD_4): topology = Ioss::Quad4::name; break;
1377   case CGNS_ENUMV(QUAD_8): topology = Ioss::Quad8::name; break;
1378   case CGNS_ENUMV(QUAD_9): topology = Ioss::Quad9::name; break;
1379   case CGNS_ENUMV(TETRA_4): topology = Ioss::Tet4::name; break;
1380   case CGNS_ENUMV(TETRA_10): topology = Ioss::Tet10::name; break;
1381   case CGNS_ENUMV(PYRA_5): topology = Ioss::Pyramid5::name; break;
1382   case CGNS_ENUMV(PYRA_13): topology = Ioss::Pyramid13::name; break;
1383   case CGNS_ENUMV(PYRA_14): topology = Ioss::Pyramid14::name; break;
1384   case CGNS_ENUMV(PENTA_6): topology = Ioss::Wedge6::name; break;
1385   case CGNS_ENUMV(PENTA_15): topology = Ioss::Wedge15::name; break;
1386   case CGNS_ENUMV(PENTA_18): topology = Ioss::Wedge18::name; break;
1387   case CGNS_ENUMV(HEXA_8): topology = Ioss::Hex8::name; break;
1388   case CGNS_ENUMV(HEXA_20): topology = Ioss::Hex20::name; break;
1389   case CGNS_ENUMV(HEXA_27): topology = Ioss::Hex27::name; break;
1390   default:
1391     fmt::print(Ioss::WARNING(), "Found topology of type {} which is not currently supported.\n",
1392                cg_ElementTypeName(type));
1393     topology = Ioss::Unknown::name;
1394   }
1395   return topology;
1396 }
1397 
CGNS_ENUMT(ElementType_t)1398 CGNS_ENUMT(ElementType_t) Iocgns::Utils::map_topology_to_cgns(const std::string &name)
1399 {
1400   CGNS_ENUMT(ElementType_t) topo = CGNS_ENUMV(ElementTypeNull);
1401   if (name == Ioss::Node::name) {
1402     topo = CGNS_ENUMV(NODE);
1403   }
1404   else if (name == Ioss::Spring2::name) {
1405     topo = CGNS_ENUMV(BAR_2);
1406   }
1407   else if (name == Ioss::Spring3::name) {
1408     topo = CGNS_ENUMV(BAR_3);
1409   }
1410   else if (name == Ioss::Beam2::name) {
1411     topo = CGNS_ENUMV(BAR_2);
1412   }
1413   else if (name == Ioss::Beam3::name) {
1414     topo = CGNS_ENUMV(BAR_3);
1415   }
1416   else if (name == Ioss::Tri3::name) {
1417     topo = CGNS_ENUMV(TRI_3);
1418   }
1419   else if (name == Ioss::Tri6::name) {
1420     topo = CGNS_ENUMV(TRI_6);
1421   }
1422   else if (name == Ioss::Quad4::name) {
1423     topo = CGNS_ENUMV(QUAD_4);
1424   }
1425   else if (name == Ioss::Quad8::name) {
1426     topo = CGNS_ENUMV(QUAD_8);
1427   }
1428   else if (name == Ioss::Quad9::name) {
1429     topo = CGNS_ENUMV(QUAD_9);
1430   }
1431   else if (name == Ioss::Tet4::name) {
1432     topo = CGNS_ENUMV(TETRA_4);
1433   }
1434   else if (name == Ioss::Tet10::name) {
1435     topo = CGNS_ENUMV(TETRA_10);
1436   }
1437   else if (name == Ioss::Pyramid5::name) {
1438     topo = CGNS_ENUMV(PYRA_5);
1439   }
1440   else if (name == Ioss::Pyramid13::name) {
1441     topo = CGNS_ENUMV(PYRA_13);
1442   }
1443   else if (name == Ioss::Pyramid14::name) {
1444     topo = CGNS_ENUMV(PYRA_14);
1445   }
1446   else if (name == Ioss::Wedge6::name) {
1447     topo = CGNS_ENUMV(PENTA_6);
1448   }
1449   else if (name == Ioss::Wedge15::name) {
1450     topo = CGNS_ENUMV(PENTA_15);
1451   }
1452   else if (name == Ioss::Wedge18::name) {
1453     topo = CGNS_ENUMV(PENTA_18);
1454   }
1455   else if (name == Ioss::Hex8::name) {
1456     topo = CGNS_ENUMV(HEXA_8);
1457   }
1458   else if (name == Ioss::Hex20::name) {
1459     topo = CGNS_ENUMV(HEXA_20);
1460   }
1461   else if (name == Ioss::Hex27::name) {
1462     topo = CGNS_ENUMV(HEXA_27);
1463   }
1464   else {
1465     fmt::print(Ioss::WARNING(), "Found topology of type {} which is not currently supported.\n",
1466                name);
1467   }
1468   return topo;
1469 }
1470 
write_flow_solution_metadata(int file_ptr,int base_ptr,Ioss::Region * region,int state,const int * vertex_solution_index,const int * cell_center_solution_index,bool is_parallel_io)1471 void Iocgns::Utils::write_flow_solution_metadata(int file_ptr, int base_ptr, Ioss::Region *region,
1472                                                  int state, const int *vertex_solution_index,
1473                                                  const int *cell_center_solution_index,
1474                                                  bool       is_parallel_io)
1475 {
1476   std::string c_name = fmt::format("CellCenterSolutionAtStep{:05}", state);
1477   std::string v_name = fmt::format("VertexSolutionAtStep{:05}", state);
1478   std::string step   = std::to_string(state);
1479 
1480   const auto &nblocks                 = region->get_node_blocks();
1481   const auto &nblock                  = nblocks[0];
1482   bool        global_has_nodal_fields = nblock->field_count(Ioss::Field::TRANSIENT) > 0;
1483   bool        is_file_per_state       = (base_ptr >= 0);
1484 
1485   // IF the `base_ptr` is positive, then we are in file-per-state option.
1486   // `file_ptr` points to the linked-to file where the state data is being
1487   // written and `base_ptr` points to the "base" file which has the mesh
1488   // metadata and links to the solution data "state" files.
1489   std::string linked_file_name;
1490   if (is_file_per_state) {
1491     linked_file_name = region->get_database()->get_filename();
1492   }
1493 
1494   // Create a lambda to avoid code duplication for similar treatment
1495   // of structured blocks and element blocks.
1496   auto sol_lambda = [=](Ioss::EntityBlock *block, bool has_nodal_fields) {
1497     int base = block->get_property("base").get_int();
1498     int zone = get_db_zone(block);
1499     if (has_nodal_fields) {
1500       if (is_file_per_state) {
1501         // We are using file-per-state and we need to add a link from the base file (base_ptr)
1502         // to the state file (file_ptr).
1503         CGERR(cg_goto(base_ptr, base, "Zone_t", zone, "end"));
1504         std::string linkpath = "/Base/" + block->name() + "/" + v_name;
1505         CGERR(cg_link_write(v_name.c_str(), linked_file_name.c_str(), linkpath.c_str()));
1506       }
1507       CGERR(cg_sol_write(file_ptr, base, zone, v_name.c_str(), CGNS_ENUMV(Vertex),
1508                          (int *)vertex_solution_index));
1509       CGERR(
1510           cg_goto(file_ptr, base, "Zone_t", zone, "FlowSolution_t", *vertex_solution_index, "end"));
1511       CGERR(cg_gridlocation_write(CGNS_ENUMV(Vertex)));
1512       CGERR(cg_descriptor_write("Step", step.c_str()));
1513     }
1514     if (block->field_count(Ioss::Field::TRANSIENT) > 0) {
1515       if (is_file_per_state) {
1516         // We are using file-per-state and we need to add a link from the base file (base_ptr)
1517         // to the state file (file_ptr).
1518         CGERR(cg_goto(base_ptr, base, "Zone_t", zone, "end"));
1519         std::string linkpath = "/Base/" + block->name() + "/" + c_name;
1520         CGERR(cg_link_write(c_name.c_str(), linked_file_name.c_str(), linkpath.c_str()));
1521       }
1522       CGERR(cg_sol_write(file_ptr, base, zone, c_name.c_str(), CGNS_ENUMV(CellCenter),
1523                          (int *)cell_center_solution_index));
1524       CGERR(cg_goto(file_ptr, base, "Zone_t", zone, "FlowSolution_t", *cell_center_solution_index,
1525                     "end"));
1526       CGERR(cg_descriptor_write("Step", step.c_str()));
1527     }
1528   };
1529 
1530   // Use the lambda
1531   const auto &sblocks = region->get_structured_blocks();
1532   for (auto &block : sblocks) {
1533     if (is_parallel_io || block->is_active()) {
1534       const auto &nb        = block->get_node_block();
1535       bool has_nodal_fields = global_has_nodal_fields || nb.field_count(Ioss::Field::TRANSIENT) > 0;
1536       sol_lambda(block, has_nodal_fields);
1537     }
1538   }
1539   // Use the lambda
1540   const auto &eblocks = region->get_element_blocks();
1541   for (auto &block : eblocks) {
1542     sol_lambda(block, global_has_nodal_fields);
1543   }
1544 }
1545 
find_solution_index(int cgns_file_ptr,int base,int zone,int step,CGNS_ENUMT (GridLocation_t)location)1546 int Iocgns::Utils::find_solution_index(int cgns_file_ptr, int base, int zone, int step,
1547                                        CGNS_ENUMT(GridLocation_t) location)
1548 {
1549   auto str_step = std::to_string(step);
1550   int  nsols    = 0;
1551   CGCHECKNP(cg_nsols(cgns_file_ptr, base, zone, &nsols));
1552   bool location_matches = false;
1553   for (int i = 0; i < nsols; i++) {
1554     CGNS_ENUMT(GridLocation_t) db_location;
1555     char              db_name[CGNS_MAX_NAME_LENGTH + 1];
1556     CGCHECKNP(cg_sol_info(cgns_file_ptr, base, zone, i + 1, db_name, &db_location));
1557     if (location == db_location) {
1558       location_matches = true;
1559       // Check if steps match.
1560       // NOTE: Using non-standard "Descriptor_t" node in FlowSolution_t
1561       CGCHECKNP(cg_goto(cgns_file_ptr, base, "Zone_t", zone, "FlowSolution_t", i + 1, "end"));
1562       int descriptor_count = 0;
1563       CGCHECKNP(cg_ndescriptors(&descriptor_count));
1564 
1565       bool found_step_descriptor = false;
1566       for (int d = 0; d < descriptor_count; d++) {
1567         char *db_step = nullptr;
1568         char  name[CGNS_MAX_NAME_LENGTH + 1];
1569         CGCHECKNP(cg_descriptor_read(d + 1, name, &db_step));
1570         if (strcmp(name, "step") == 0) {
1571           found_step_descriptor = true;
1572           if (str_step == db_step) {
1573             cg_free(db_step);
1574             return i + 1;
1575           }
1576 
1577           cg_free(db_step);
1578           break; // Found "step" descriptor, but wasn't correct step...
1579         }
1580         cg_free(db_step);
1581       }
1582       if (!found_step_descriptor) {
1583         // There was no Descriptor_t node with the name "step",
1584         // Try to decode the step from the FlowSolution_t name.
1585         // If `db_name` does not have `Step` or `step` in name,
1586         // then don't search
1587         if (strcasestr(db_name, "step") != nullptr) {
1588           int nstep = extract_trailing_int(db_name);
1589           if (nstep == step) {
1590             return i + 1;
1591           }
1592         }
1593       }
1594     }
1595   }
1596 
1597   if (location_matches) {
1598     return step;
1599   }
1600 
1601   fmt::print(Ioss::WARNING(),
1602              "CGNS: Could not find valid solution index for step {}, zone {}, and location {}\n",
1603              step, zone, cg_GridLocationName(location));
1604   return 0;
1605 }
1606 
add_sidesets(int cgns_file_ptr,Ioss::DatabaseIO * db)1607 void Iocgns::Utils::add_sidesets(int cgns_file_ptr, Ioss::DatabaseIO *db)
1608 {
1609   int base         = 1;
1610   int num_families = 0;
1611   CGCHECKNP(cg_nfamilies(cgns_file_ptr, base, &num_families));
1612 
1613   for (int family = 1; family <= num_families; family++) {
1614     char        name[CGNS_MAX_NAME_LENGTH + 1];
1615     CGNS_ENUMT(BCType_t) bocotype;
1616     int         num_bc  = 0;
1617     int         num_geo = 0;
1618     CGCHECKNP(cg_family_read(cgns_file_ptr, base, family, name, &num_bc, &num_geo));
1619 
1620 #if IOSS_DEBUG_OUTPUT
1621     if (db->parallel_rank() == 0) {
1622       fmt::print(Ioss::DEBUG(), "Family {} named {} has {} BC, and {} geometry references.\n",
1623                  family, name, num_bc, num_geo);
1624     }
1625 #endif
1626     if (num_bc > 0) {
1627       // Create a sideset...
1628       std::string ss_name(name); // Use name here before cg_fambc_read call overwrites it...
1629 
1630       CGCHECKNP(cg_fambc_read(cgns_file_ptr, base, family, 1, name, &bocotype));
1631 
1632       CGCHECKNP(cg_goto(cgns_file_ptr, base, "Family_t", family, "end"));
1633       int ndescriptors = 0;
1634       int id           = 0;
1635       CGCHECKNP(cg_ndescriptors(&ndescriptors));
1636       if (ndescriptors > 0) {
1637         for (int ndesc = 1; ndesc <= ndescriptors; ndesc++) {
1638           char  dname[CGNS_MAX_NAME_LENGTH + 1];
1639           char *dtext;
1640           CGCHECKNP(cg_descriptor_read(ndesc, dname, &dtext));
1641           if (strcmp(dname, "FamBC_UserId") == 0) {
1642             // Convert text in `dtext` to integer...
1643             id = Ioss::Utils::get_number(dtext);
1644             cg_free(dtext);
1645             break;
1646           }
1647           cg_free(dtext);
1648         }
1649       }
1650       if (id == 0) {
1651         id = Ioss::Utils::extract_id(ss_name);
1652       }
1653       if (id != 0) {
1654         auto *ss = new Ioss::SideSet(db, ss_name);
1655         ss->property_add(Ioss::Property("id", id));
1656         ss->property_add(Ioss::Property("guid", db->util().generate_guid(id)));
1657         ss->property_add(Ioss::Property("bc_type", bocotype));
1658         db->get_region()->add(ss);
1659       }
1660       else {
1661         if (db->parallel_rank() == 0) {
1662           fmt::print(Ioss::WARNING(),
1663                      "Skipping BC with name '{}' since FamBC_UserId is equal to 0.\n\n", ss_name);
1664         }
1665       }
1666     }
1667   }
1668 }
1669 
add_assemblies(int cgns_file_ptr,Ioss::DatabaseIO * db)1670 void Iocgns::Utils::add_assemblies(int cgns_file_ptr, Ioss::DatabaseIO *db)
1671 {
1672   int base         = 1;
1673   int num_families = 0;
1674   CGCHECKNP(cg_nfamilies(cgns_file_ptr, base, &num_families));
1675 
1676   for (int family = 1; family <= num_families; family++) {
1677     char name[CGNS_MAX_NAME_LENGTH + 1];
1678     int  num_bc  = 0;
1679     int  num_geo = 0;
1680     CGCHECKNP(cg_family_read(cgns_file_ptr, base, family, name, &num_bc, &num_geo));
1681 
1682     if (num_bc == 0 && num_geo == 0) {
1683       // See if this is an assembly -- will contain a 'FamVC_UserName' Descriptor_t node
1684       // The `Node Data` for that node will be the name of the assembly.
1685       // Assemblies will be created empty and then blocks/zones will be added during
1686       // the parsing of the zones.
1687       CGCHECKNP(cg_goto(cgns_file_ptr, base, "Family_t", family, "end"));
1688 
1689       int ndescriptors = 0;
1690       CGCHECKNP(cg_ndescriptors(&ndescriptors));
1691       if (ndescriptors > 0) {
1692         int         id = -1;
1693         std::string assem_name;
1694         for (int ndesc = 1; ndesc <= ndescriptors; ndesc++) {
1695           char  dname[CGNS_MAX_NAME_LENGTH + 1];
1696           char *dtext = nullptr;
1697           CGCHECKNP(cg_descriptor_read(ndesc, dname, &dtext));
1698           if (strcmp(dname, "FamVC_UserId") == 0) {
1699             // Convert text in `dtext` to integer...
1700             id = Ioss::Utils::get_number(dtext);
1701           }
1702           else if (strcmp(dname, "FamVC_UserName") == 0) {
1703             assem_name = dtext;
1704           }
1705           cg_free(dtext);
1706         }
1707         if (!assem_name.empty()) {
1708           // Create an assembly with this name...
1709           auto *assem = new Ioss::Assembly(db, assem_name);
1710           db->get_region()->add(assem);
1711           if (id >= 0) {
1712             assem->property_add(Ioss::Property("id", id));
1713           }
1714 
1715 #if IOSS_DEBUG_OUTPUT
1716           if (db->parallel_rank() == 0) {
1717             fmt::print(Ioss::DEBUG(),
1718                        "Adding Family {} named {} as an assembly named {} with id {}.\n", family,
1719                        name, assem_name, id);
1720           }
1721 #endif
1722         }
1723       }
1724     }
1725   }
1726 }
1727 
resolve_nodes(Ioss::Region & region,int my_processor,bool is_parallel)1728 size_t Iocgns::Utils::resolve_nodes(Ioss::Region &region, int my_processor, bool is_parallel)
1729 {
1730   // Each structured block has its own set of "cell_nodes"
1731   // At block boundaries, there are duplicate nodes which need to be resolved for the
1732   // unstructured mesh output.
1733 
1734   // We need to iterate all of the blocks and then each blocks zgc to determine
1735   // which nodes are shared between blocks. For all shared nodes, the node in the lowest
1736   // numbered zone is considered the "owner" and all other nodes are shared.
1737 
1738   // At the end of the routine, each block knows where its nodes fit
1739   // into the implicit ordering of nodes on this processor. This is
1740   // given by:
1741   // implicit_location = block->m_blockLocalNodeIndex[i] (0 <= i < #nodes_in_block)
1742   // Where 0 <= implicit_location < #nodes_on_processor
1743 
1744   // Create a vector of size which is the sum of the on-processor cell_nodes size for each block
1745   size_t num_total_cell_nodes = 0;
1746   auto & blocks               = region.get_structured_blocks();
1747   for (auto &block : blocks) {
1748     size_t node_count = block->get_property("node_count").get_int();
1749     num_total_cell_nodes += node_count;
1750   }
1751 
1752   ssize_t              ss_max = std::numeric_limits<ssize_t>::max();
1753   std::vector<ssize_t> cell_node_map(num_total_cell_nodes, ss_max);
1754 
1755   // Each cell_node location in the cell_node_map is currently initialized to ss_max.
1756   // Iterate each block and then each blocks non-intra-block (i.e., not
1757   // due to proc decomps) zgc instances and update cell_node_map
1758   // such that for each shared node, it points to the owner nodes
1759   // location.
1760   for (auto &owner_block : blocks) {
1761     for (const auto &zgc : owner_block->m_zoneConnectivity) {
1762       if (!zgc.is_from_decomp() &&
1763           zgc.is_active()) { // Not due to processor decomposition and has faces.
1764         // NOTE: In parallel, the owner block should exist, but may not have
1765         // any cells on this processor.  We can access its global i,j,k, but
1766         // don't store or access any "bulk" data on it.
1767         auto donor_block = region.get_structured_block(zgc.m_donorName);
1768         assert(donor_block != nullptr);
1769 
1770         std::vector<int> i_range = zgc.get_range(1);
1771         std::vector<int> j_range = zgc.get_range(2);
1772         std::vector<int> k_range = zgc.get_range(3);
1773         for (auto &k : k_range) {
1774           for (auto &j : j_range) {
1775             for (auto &i : i_range) {
1776               Ioss::IJK_t owner_index{{i, j, k}};
1777               Ioss::IJK_t donor_index = zgc.transform(owner_index);
1778 
1779               // The nodes as 'index' and 'owner' are contiguous and
1780               // should refer to the same node. 'owner' should be
1781               // the owner (unless it is already owned by another
1782               // block)
1783 
1784               ssize_t owner_global_offset = owner_block->get_global_node_offset(owner_index);
1785               ssize_t donor_global_offset = donor_block->get_global_node_offset(donor_index);
1786 
1787               if (owner_global_offset > donor_global_offset) {
1788                 if (is_parallel && (zgc.m_donorProcessor != my_processor)) {
1789                   size_t owner_block_local_offset =
1790                       owner_block->get_block_local_node_offset(owner_index);
1791                   owner_block->m_globalIdMap.emplace_back(owner_block_local_offset,
1792                                                           donor_global_offset + 1);
1793                 }
1794                 else if (!is_parallel || (zgc.m_ownerProcessor != my_processor)) {
1795                   size_t  owner_local_offset = owner_block->get_local_node_offset(owner_index);
1796                   ssize_t donor_local_offset = donor_block->get_local_node_offset(donor_index);
1797 
1798                   if (cell_node_map[owner_local_offset] == ss_max) {
1799                     cell_node_map[owner_local_offset] = donor_local_offset;
1800                   }
1801                 }
1802               }
1803             }
1804           }
1805         }
1806       }
1807     }
1808   }
1809 
1810   // Now iterate cell_node_map.  If an entry == ss_max, then it is
1811   // an owned node and needs to have its index into the unstructed
1812   // mesh node map set; otherwise, the value points to the owner
1813   // node, so the index at this location should be set to the owner
1814   // nodes index.
1815   size_t index = 0;
1816   for (auto &node : cell_node_map) {
1817     if (node == ss_max) {
1818       node = index++;
1819     }
1820     else {
1821       node = -node;
1822     }
1823   }
1824 
1825   for (auto &node : cell_node_map) {
1826     if (node < 0) {
1827       node = cell_node_map[-node];
1828     }
1829   }
1830 
1831   for (auto &block : blocks) {
1832     size_t node_count = block->get_property("node_count").get_int();
1833     block->m_blockLocalNodeIndex.resize(node_count);
1834 
1835     size_t beg = block->get_node_offset();
1836     size_t end = beg + node_count;
1837     for (size_t idx = beg, i = 0; idx < end; idx++) {
1838       block->m_blockLocalNodeIndex[i++] = cell_node_map[idx];
1839     }
1840   }
1841   return index;
1842 }
1843 
1844 std::vector<std::vector<std::pair<size_t, size_t>>>
resolve_processor_shared_nodes(Ioss::Region & region,int my_processor)1845 Iocgns::Utils::resolve_processor_shared_nodes(Ioss::Region &region, int my_processor)
1846 {
1847   // Determine which nodes are shared across processor boundaries.
1848   // Only need to check on block boundaries..
1849 
1850   // We need to iterate all of the blocks and then each blocks zgc to determine
1851   // which nodes are shared between processors. For all shared nodes, the node in the lowest
1852   // numbered zone is considered the "owner" and all other nodes are shared.
1853 
1854   auto &blocks = region.get_structured_blocks();
1855 
1856   std::vector<std::vector<std::pair<size_t, size_t>>> shared_nodes(blocks.size() + 1);
1857 
1858   for (auto &owner_block : blocks) {
1859     int  owner_zone = owner_block->get_property("zone").get_int();
1860     auto owner_ids  = owner_block->get_cell_node_ids(true);
1861     for (const auto &zgc : owner_block->m_zoneConnectivity) {
1862       assert(zgc.m_donorProcessor >= 0);
1863       assert(zgc.m_ownerProcessor >= 0);
1864 
1865       if (zgc.is_active() &&
1866           (zgc.m_donorProcessor != my_processor || zgc.m_ownerProcessor != my_processor)) {
1867         // NOTE: In parallel, the donor block should exist, but may not have
1868         // any cells on this processor.  We can access its global i,j,k, but
1869         // don't store or access any "bulk" data on it.
1870         auto donor_block = region.get_structured_block(zgc.m_donorName);
1871         assert(donor_block != nullptr);
1872         int donor_zone = donor_block->get_property("zone").get_int();
1873 
1874         auto donor_ids = donor_block->get_cell_node_ids(true);
1875 
1876         std::vector<int> i_range = zgc.get_range(1);
1877         std::vector<int> j_range = zgc.get_range(2);
1878         std::vector<int> k_range = zgc.get_range(3);
1879         for (auto &k : k_range) {
1880           for (auto &j : j_range) {
1881             for (auto &i : i_range) {
1882               Ioss::IJK_t owner_index{{i, j, k}};
1883               Ioss::IJK_t donor_index = zgc.transform(owner_index);
1884 
1885               // The nodes as 'index' and 'owner' are contiguous and
1886               // should refer to the same node.
1887 
1888               if (my_processor == zgc.m_ownerProcessor) {
1889                 ssize_t owner_offset = owner_block->get_block_local_node_offset(owner_index);
1890                 shared_nodes[owner_zone].emplace_back(owner_offset, zgc.m_donorProcessor);
1891               }
1892               else if (my_processor == zgc.m_donorProcessor) {
1893                 ssize_t donor_offset = donor_block->get_block_local_node_offset(donor_index);
1894                 shared_nodes[donor_zone].emplace_back(donor_offset, zgc.m_ownerProcessor);
1895               }
1896             }
1897           }
1898         }
1899       }
1900     }
1901 #if IOSS_DEBUG_OUTPUT
1902     fmt::print(Ioss::DEBUG(), "P{}, Block {} Shared Nodes: {}\n", my_processor, owner_block->name(),
1903                shared_nodes[owner_zone].size());
1904 #endif
1905   }
1906   return shared_nodes;
1907 }
1908 
add_to_assembly(int cgns_file_ptr,Ioss::Region * region,Ioss::EntityBlock * block,int base,int zone)1909 void Iocgns::Utils::add_to_assembly(int cgns_file_ptr, Ioss::Region *region,
1910                                     Ioss::EntityBlock *block, int base, int zone)
1911 {
1912   // See if there is a 'FamilyName' node...
1913   if (cg_goto(cgns_file_ptr, base, "Zone_t", zone, "end") == CG_OK) {
1914     char name[CGNS_MAX_NAME_LENGTH + 1];
1915     if (cg_famname_read(name) == CG_OK) {
1916       auto *assem = region->get_assembly(name);
1917       if (assem != nullptr) {
1918         assem->add(block);
1919         block->property_add(Ioss::Property("assembly", assem->name()));
1920       }
1921     }
1922   }
1923 }
1924 
add_structured_boundary_conditions(int cgns_file_ptr,Ioss::StructuredBlock * block,bool is_parallel_io)1925 void Iocgns::Utils::add_structured_boundary_conditions(int                    cgns_file_ptr,
1926                                                        Ioss::StructuredBlock *block,
1927                                                        bool                   is_parallel_io)
1928 {
1929   // `is_parallel_io` is true if all processors reading single file.
1930   // `is_parallel_io` is false if serial, or each processor reading its own file (fpp)
1931   if (is_parallel_io) {
1932     add_structured_boundary_conditions_pio(cgns_file_ptr, block);
1933   }
1934   else {
1935     add_structured_boundary_conditions_fpp(cgns_file_ptr, block);
1936   }
1937 }
1938 
add_structured_boundary_conditions_pio(int cgns_file_ptr,Ioss::StructuredBlock * block)1939 void Iocgns::Utils::add_structured_boundary_conditions_pio(int                    cgns_file_ptr,
1940                                                            Ioss::StructuredBlock *block)
1941 {
1942   int base = block->get_property("base").get_int();
1943   int zone = get_db_zone(block);
1944 
1945   // Called by Parallel run reading single file only.
1946   // Data needed:
1947   // * boco_name (CGNS_MAX_NAME_LENGTH + 1 chars)
1948   // * fam_name  (CGNS_MAX_NAME_LENGTH + 1 chars)
1949   // * data     (cgsize_t * 7) (bocotype + range[6])
1950 
1951   int num_bcs = 0;
1952 
1953   CGCHECKNP(cg_nbocos(cgns_file_ptr, base, zone, &num_bcs));
1954 
1955   std::vector<int>  bc_data(7 * num_bcs);
1956   std::vector<char> bc_names(2 * (CGNS_MAX_NAME_LENGTH + 1) * num_bcs);
1957 
1958   for (int ibc = 0; ibc < num_bcs; ibc++) {
1959     cgsize_t          range[6];
1960     char              boco_name[CGNS_MAX_NAME_LENGTH + 1];
1961     char              fam_name[CGNS_MAX_NAME_LENGTH + 1];
1962     CGNS_ENUMT(BCType_t)       bocotype;
1963     CGNS_ENUMT(PointSetType_t) ptset_type;
1964     cgsize_t          npnts;
1965     cgsize_t          NormalListSize;
1966     CGNS_ENUMT(DataType_t)     NormalDataType;
1967     int               ndataset;
1968 
1969     // All we really want from this is 'boco_name'
1970     CGCHECKNP(cg_boco_info(cgns_file_ptr, base, zone, ibc + 1, boco_name, &bocotype, &ptset_type,
1971                            &npnts, nullptr, &NormalListSize, &NormalDataType, &ndataset));
1972 
1973     if (bocotype == CGNS_ENUMV(FamilySpecified)) {
1974       // Get family name associated with this boco_name
1975       CGCHECKNP(
1976           cg_goto(cgns_file_ptr, base, "Zone_t", zone, "ZoneBC_t", 1, "BC_t", ibc + 1, "end"));
1977       CGCHECKNP(cg_famname_read(fam_name));
1978     }
1979     else {
1980       Ioss::Utils::copy_string(fam_name, boco_name);
1981     }
1982 
1983     CGCHECKNP(cg_boco_read(cgns_file_ptr, base, zone, ibc + 1, range, nullptr));
1984 
1985     // There are some BC that are applied on an edge or a vertex;
1986     // Don't want those (yet?), so filter them out at this time...
1987     {
1988       int same_count = (range[0] == range[3] ? 1 : 0) + (range[1] == range[4] ? 1 : 0) +
1989                        (range[2] == range[5] ? 1 : 0);
1990       if (same_count != 1) {
1991         fmt::print(Ioss::WARNING(),
1992                    "CGNS: Skipping Boundary Condition '{}' on block '{}'. It is applied to "
1993                    "{}. This code only supports surfaces.\n",
1994                    boco_name, block->name(), (same_count == 2 ? "an edge" : "a vertex"));
1995         continue;
1996       }
1997     }
1998 
1999     bool is_parallel_io = true;
2000     add_bc_to_block(block, boco_name, fam_name, ibc, range, bocotype, is_parallel_io);
2001   }
2002 }
2003 
generate_boundary_faces(Ioss::Region * region,std::map<std::string,Ioss::FaceUnorderedSet> & boundary_faces,Ioss::Field::BasicType field_type)2004 void Iocgns::Utils::generate_boundary_faces(
2005     Ioss::Region *region, std::map<std::string, Ioss::FaceUnorderedSet> &boundary_faces,
2006     Ioss::Field::BasicType field_type)
2007 {
2008   // See if we already generated the faces for this model...
2009   Ioss::FaceGenerator face_generator(*region);
2010   if (field_type == Ioss::Field::INT32) {
2011     face_generator.generate_faces((int)0, true);
2012   }
2013   else {
2014     face_generator.generate_faces((int64_t)0, true);
2015   }
2016   const Ioss::ElementBlockContainer &ebs = region->get_element_blocks();
2017   for (auto eb : ebs) {
2018     const std::string &name     = eb->name();
2019     auto &             boundary = boundary_faces[name];
2020     auto &             faces    = face_generator.faces(name);
2021     for (auto &face : faces) {
2022       if (face.elementCount_ == 1) {
2023         boundary.insert(face);
2024       }
2025     }
2026   }
2027 #if IOSS_DEBUG_OUTPUT
2028   output_table(ebs, boundary_faces);
2029 #endif
2030 }
2031 
add_structured_boundary_conditions_fpp(int cgns_file_ptr,Ioss::StructuredBlock * block)2032 void Iocgns::Utils::add_structured_boundary_conditions_fpp(int                    cgns_file_ptr,
2033                                                            Ioss::StructuredBlock *block)
2034 {
2035   int base = block->get_property("base").get_int();
2036   int zone = get_db_zone(block);
2037 
2038   // Called by both parallel fpp and serial runs.
2039   // In parallel, the 'cgns_file_ptr' is specific for each processor
2040 
2041   int num_bcs = 0;
2042   CGCHECKNP(cg_nbocos(cgns_file_ptr, base, zone, &num_bcs));
2043 
2044   for (int ibc = 0; ibc < num_bcs; ibc++) {
2045     char              boco_name[CGNS_MAX_NAME_LENGTH + 1];
2046     char              fam_name[CGNS_MAX_NAME_LENGTH + 1];
2047     CGNS_ENUMT(BCType_t)       bocotype;
2048     CGNS_ENUMT(PointSetType_t) ptset_type;
2049     cgsize_t          npnts;
2050     cgsize_t          NormalListSize;
2051     CGNS_ENUMT(DataType_t)     NormalDataType;
2052     int               ndataset;
2053     cgsize_t          range[6];
2054 
2055     // All we really want from this is 'boco_name'
2056     CGCHECKNP(cg_boco_info(cgns_file_ptr, base, zone, ibc + 1, boco_name, &bocotype, &ptset_type,
2057                            &npnts, nullptr, &NormalListSize, &NormalDataType, &ndataset));
2058 
2059     if (bocotype == CGNS_ENUMV(FamilySpecified)) {
2060       // Get family name associated with this boco_name
2061       CGCHECKNP(
2062           cg_goto(cgns_file_ptr, base, "Zone_t", zone, "ZoneBC_t", 1, "BC_t", ibc + 1, "end"));
2063       CGCHECKNP(cg_famname_read(fam_name));
2064     }
2065     else {
2066       Ioss::Utils::copy_string(fam_name, boco_name);
2067     }
2068 
2069     CGCHECKNP(cg_boco_read(cgns_file_ptr, base, zone, ibc + 1, range, nullptr));
2070 
2071     // There are some BC that are applied on an edge or a vertex;
2072     // Don't want those (yet?), so filter them out at this time...
2073     int same_count = (range[0] == range[3] ? 1 : 0) + (range[1] == range[4] ? 1 : 0) +
2074                      (range[2] == range[5] ? 1 : 0);
2075     if (same_count != 1) {
2076       fmt::print(Ioss::WARNING(),
2077                  "CGNS: Skipping Boundary Condition '{}' on block '{}'. It is applied to "
2078                  "{}. This code only supports surfaces.\n",
2079                  boco_name, block->name(), (same_count == 2 ? "an edge" : "a vertex"));
2080       continue;
2081     }
2082 
2083     int num_proc = block->get_database()->util().parallel_size();
2084     if (num_proc > 1) {
2085       // Need to modify range with block offset to put into global space
2086       Ioss::IJK_t offset;
2087       offset[0] = block->get_property("offset_i").get_int();
2088       offset[1] = block->get_property("offset_j").get_int();
2089       offset[2] = block->get_property("offset_k").get_int();
2090       range[0] += offset[0];
2091       range[1] += offset[1];
2092       range[2] += offset[2];
2093       range[3] += offset[0];
2094       range[4] += offset[1];
2095       range[5] += offset[2];
2096     }
2097 
2098     bool is_parallel_io = false;
2099     add_bc_to_block(block, boco_name, fam_name, ibc, range, bocotype, is_parallel_io);
2100   }
2101 }
2102 
finalize_database(int cgns_file_ptr,const std::vector<double> & timesteps,Ioss::Region * region,int myProcessor,bool is_parallel_io)2103 void Iocgns::Utils::finalize_database(int cgns_file_ptr, const std::vector<double> &timesteps,
2104                                       Ioss::Region *region, int myProcessor, bool is_parallel_io)
2105 {
2106   int base = 1;
2107   CGCHECK(cg_biter_write(cgns_file_ptr, base, "TimeIterValues", timesteps.size()));
2108 
2109   // Now write the timestep time values...
2110   CGCHECK(cg_goto(cgns_file_ptr, base, "BaseIterativeData_t", 1, "end"));
2111   cgsize_t dimtv[1] = {(cgsize_t)timesteps.size()};
2112   CGCHECK(cg_array_write("TimeValues", CGNS_ENUMV(RealDouble), 1, dimtv, timesteps.data()));
2113 
2114   // Output the ZoneIterativeData which maps a zones flow solutions to timesteps.
2115   // One per zone and the number of entries matches the number of timesteps...
2116   const auto &nblocks = region->get_node_blocks();
2117   auto &      nblock  = nblocks[0];
2118 
2119   bool has_nodal_fields = nblock->field_count(Ioss::Field::TRANSIENT) > 0;
2120 
2121   // Create a lambda to avoid code duplication for similar treatment
2122   // of structured blocks and element blocks.
2123   auto ziter = [=](Ioss::EntityBlock *block) {
2124     int              zone = get_db_zone(block);
2125     std::vector<int> indices(timesteps.size());
2126     bool             has_cell_center_fields = block->field_count(Ioss::Field::TRANSIENT) > 0;
2127     std::string      base_type;
2128     if (has_nodal_fields && !has_cell_center_fields) {
2129       base_type = "VertexSolutionAtStep";
2130     }
2131     else if (!has_nodal_fields && has_cell_center_fields) {
2132       base_type = "CellCenterSolutionAtStep";
2133     }
2134     else {
2135       base_type = "SolutionAtStep";
2136     }
2137 
2138     std::vector<char> names(32 * timesteps.size(), ' ');
2139     for (size_t state = 0; state < timesteps.size(); state++) {
2140       // This name is the "postfix" or common portion of all FlowSolution names...
2141       std::string name = fmt::format("{}{:05}", base_type, state + 1);
2142       Ioss::Utils::copy_string(&names[state * 32], name, 32);
2143       for (size_t i = name.size(); i < 32; i++) {
2144         names[state * 32 + i] = ' ';
2145       }
2146     }
2147 
2148     cgsize_t dim[2] = {32, (cgsize_t)timesteps.size()};
2149     if (has_cell_center_fields || has_nodal_fields) {
2150       CGCHECK(cg_ziter_write(cgns_file_ptr, base, zone, "ZoneIterativeData"));
2151       CGCHECK(cg_goto(cgns_file_ptr, base, "Zone_t", zone, "ZoneIterativeData_t", 1, "end"));
2152       CGCHECK(cg_array_write("FlowSolutionPointers", CGNS_ENUMV(Character), 2, dim, names.data()));
2153 
2154       if (has_nodal_fields) {
2155         int index     = 1;
2156         int increment = has_cell_center_fields ? 2 : 1;
2157         for (size_t state = 0; state < timesteps.size(); state++) {
2158           indices[state] = index;
2159           index += increment;
2160         }
2161 
2162         CGCHECK(cg_array_write("VertexSolutionIndices", CGNS_ENUMV(Integer), 1, &dim[1], indices.data()));
2163         CGCHECK(cg_descriptor_write("VertexPrefix", "Vertex"));
2164       }
2165       if (has_cell_center_fields) {
2166         int index     = has_nodal_fields ? 2 : 1;
2167         int increment = has_nodal_fields ? 2 : 1;
2168         for (size_t state = 0; state < timesteps.size(); state++) {
2169           indices[state] = index;
2170           index += increment;
2171         }
2172 
2173         CGCHECK(cg_array_write("CellCenterIndices", CGNS_ENUMV(Integer), 1, &dim[1], indices.data()));
2174         CGCHECK(cg_descriptor_write("CellCenterPrefix", "CellCenter"));
2175       }
2176     }
2177   };
2178 
2179   // Use the lambda...
2180   const auto &sblocks = region->get_structured_blocks();
2181   for (auto &block : sblocks) {
2182     if (is_parallel_io || block->is_active()) {
2183       ziter(block);
2184     }
2185   }
2186 
2187   // Use the lambda...
2188   const auto &eblocks = region->get_element_blocks();
2189   for (auto &block : eblocks) {
2190     ziter(block);
2191   }
2192 }
2193 
add_transient_variables(int cgns_file_ptr,const std::vector<double> & timesteps,Ioss::Region * region,bool enable_field_recognition,char suffix_separator,int myProcessor,bool is_parallel_io)2194 void Iocgns::Utils::add_transient_variables(int cgns_file_ptr, const std::vector<double> &timesteps,
2195                                             Ioss::Region *region, bool enable_field_recognition,
2196                                             char suffix_separator, int myProcessor,
2197                                             bool is_parallel_io)
2198 {
2199   // ==========================================
2200   // Add transient variables (if any) to all zones...
2201   // Create a lambda to avoid code duplication for similar treatment
2202   // of structured blocks and element blocks.
2203 
2204   // Assuming that the fields on all steps are the same, but can vary
2205   // from zone to zone.
2206   auto sol_iter = [=](Ioss::EntityBlock *block) {
2207     int b = block->get_property("base").get_int();
2208     int z = get_db_zone(block);
2209 
2210     int sol_count = 0;
2211     CGCHECK(cg_nsols(cgns_file_ptr, b, z, &sol_count));
2212     int sol_per_step = sol_count / (int)timesteps.size();
2213     assert(sol_count % (int)timesteps.size() == 0);
2214 
2215     for (int sol = 1; sol <= sol_per_step; sol++) {
2216       char              solution_name[CGNS_MAX_NAME_LENGTH + 1];
2217       CGNS_ENUMT(GridLocation_t) grid_loc;
2218       CGCHECK(cg_sol_info(cgns_file_ptr, b, z, sol, solution_name, &grid_loc));
2219 
2220       int field_count = 0;
2221       CGCHECK(cg_nfields(cgns_file_ptr, b, z, sol, &field_count));
2222 
2223       char **field_names = Ioss::Utils::get_name_array(field_count, CGNS_MAX_NAME_LENGTH);
2224       for (int field = 1; field <= field_count; field++) {
2225         CGNS_ENUMT(DataType_t) data_type;
2226         char          field_name[CGNS_MAX_NAME_LENGTH + 1];
2227         CGCHECK(cg_field_info(cgns_file_ptr, b, z, sol, field, &data_type, field_name));
2228         Ioss::Utils::copy_string(field_names[field - 1], field_name, CGNS_MAX_NAME_LENGTH + 1);
2229       }
2230 
2231       // Convert raw field names into composite fields (a_x, a_y, a_z ==> 3D vector 'a')
2232       std::vector<Ioss::Field> fields;
2233       if (grid_loc == CGNS_ENUMV(CellCenter)) {
2234         size_t entity_count = block->entity_count();
2235         Ioss::Utils::get_fields(entity_count, field_names, field_count, Ioss::Field::TRANSIENT,
2236                                 enable_field_recognition, suffix_separator, nullptr, fields);
2237         size_t index = 1;
2238         for (const auto &field : fields) {
2239           Utils::set_field_index(field, index, grid_loc);
2240           index += field.raw_storage()->component_count();
2241           block->field_add(field);
2242         }
2243       }
2244       else {
2245         assert(grid_loc == CGNS_ENUMV(Vertex));
2246         const Ioss::NodeBlock *cnb =
2247             (block->type() == Ioss::STRUCTUREDBLOCK)
2248                 ? &(dynamic_cast<Ioss::StructuredBlock *>(block)->get_node_block())
2249                 : region->get_node_blocks()[0];
2250         auto * nb           = const_cast<Ioss::NodeBlock *>(cnb);
2251         size_t entity_count = nb->entity_count();
2252         Ioss::Utils::get_fields(entity_count, field_names, field_count, Ioss::Field::TRANSIENT,
2253                                 enable_field_recognition, suffix_separator, nullptr, fields);
2254         size_t index = 1;
2255         for (const auto &field : fields) {
2256           Utils::set_field_index(field, index, grid_loc);
2257           index += field.raw_storage()->component_count();
2258           nb->field_add(field);
2259         }
2260       }
2261 
2262       Ioss::Utils::delete_name_array(field_names, field_count);
2263     }
2264   };
2265   // ==========================================
2266 
2267   if (!timesteps.empty()) {
2268     const auto &sblocks = region->get_structured_blocks();
2269     for (auto &block : sblocks) {
2270       if (is_parallel_io || block->is_active()) {
2271         sol_iter(block);
2272       }
2273     }
2274     const auto &eblocks = region->get_element_blocks();
2275     for (auto &block : eblocks) {
2276       sol_iter(block);
2277     }
2278     bool is_parallel = region->get_database()->util().parallel_size() > 1;
2279     if (is_parallel && !is_parallel_io) {
2280       sync_transient_variables_fpp(region);
2281     }
2282   }
2283 }
2284 
get_step_times(int cgns_file_ptr,std::vector<double> & timesteps,Ioss::Region * region,double timeScaleFactor,int myProcessor)2285 int Iocgns::Utils::get_step_times(int cgns_file_ptr, std::vector<double> &timesteps,
2286                                   Ioss::Region *region, double timeScaleFactor, int myProcessor)
2287 {
2288   int  base          = 1;
2289   int  num_timesteps = 0;
2290   char bitername[CGNS_MAX_NAME_LENGTH + 1];
2291   int  ierr = cg_biter_read(cgns_file_ptr, base, bitername, &num_timesteps);
2292   if (ierr == CG_NODE_NOT_FOUND) {
2293     return num_timesteps;
2294   }
2295   if (ierr == CG_ERROR) {
2296     Utils::cgns_error(cgns_file_ptr, __FILE__, __func__, __LINE__, myProcessor);
2297   }
2298 
2299   if (num_timesteps <= 0) {
2300     return num_timesteps;
2301   }
2302 
2303   // Read the timestep time values.
2304   CGCHECK(cg_goto(cgns_file_ptr, base, "BaseIterativeData_t", 1, "end"));
2305   std::vector<double> times(num_timesteps);
2306   CGCHECK(cg_array_read_as(1, CGNS_ENUMV(RealDouble), times.data()));
2307 
2308   timesteps.reserve(num_timesteps);
2309   for (int i = 0; i < num_timesteps; i++) {
2310     region->add_state(times[i] * timeScaleFactor);
2311     timesteps.push_back(times[i]);
2312   }
2313   return num_timesteps;
2314 }
2315 
set_line_decomposition(int cgns_file_ptr,const std::string & line_decomposition,std::vector<Iocgns::StructuredZoneData * > & zones,int rank,bool verbose)2316 void Iocgns::Utils::set_line_decomposition(int cgns_file_ptr, const std::string &line_decomposition,
2317                                            std::vector<Iocgns::StructuredZoneData *> &zones,
2318                                            int rank, bool verbose)
2319 {
2320   // The "line_decomposition" string is a list of 0 or more BC
2321   // (Family) names.  For all structured zones which this BC
2322   // touches, the ordinal of the face (i,j,k) will be set such that
2323   // a parallel decomposition will not split the zone along this
2324   // ordinal.  For example, if the BC "wall1" has the definition
2325   // [1->1, 1->5, 1->8], then it is on the constant 'i' face of the
2326   // zone and therefore, the zone will *not* be split along the 'i'
2327   // ordinal.
2328 
2329   // Get names of all valid 'bcs' on the mesh
2330   int base         = 1;
2331   int num_families = 0;
2332   CGCHECKNP(cg_nfamilies(cgns_file_ptr, base, &num_families));
2333 
2334   std::vector<std::string> families;
2335   families.reserve(num_families);
2336   for (int family = 1; family <= num_families; family++) {
2337     char name[CGNS_MAX_NAME_LENGTH + 1];
2338     int  num_bc  = 0;
2339     int  num_geo = 0;
2340     CGCHECKNP(cg_family_read(cgns_file_ptr, base, family, name, &num_bc, &num_geo));
2341     if (num_bc > 0) {
2342       Ioss::Utils::fixup_name(name);
2343       families.emplace_back(name);
2344     }
2345   }
2346 
2347   // Slit into fields using the commas as delimiters
2348   auto bcs = Ioss::tokenize(line_decomposition, ",");
2349   for (auto &bc : bcs) {
2350     Ioss::Utils::fixup_name(bc);
2351     if (std::find(families.begin(), families.end(), bc) == families.end()) {
2352       std::ostringstream errmsg;
2353       fmt::print(errmsg,
2354                  "ERROR: CGNS: The family/bc name '{}' specified as a line decomposition surface "
2355                  "does not exist on this CGNS file.\n"
2356                  "             Valid names are: ",
2357                  bc);
2358       for (const auto &fam : families) {
2359         fmt::print(errmsg, "'{}', ", fam);
2360       }
2361       IOSS_ERROR(errmsg);
2362     }
2363   }
2364 
2365   for (auto zone : zones) {
2366     // Read BCs applied to this zone and see if they match any of
2367     // the BCs in 'bcs' list.  If so, determine the face the BC is
2368     // applied to and set the m_lineOrdinal to the ordinal
2369     // perpendicular to this face.
2370     int izone = zone->m_zone;
2371     int num_bcs;
2372     CGCHECKNP(cg_nbocos(cgns_file_ptr, base, izone, &num_bcs));
2373 
2374     for (int ibc = 0; ibc < num_bcs; ibc++) {
2375       char              boconame[CGNS_MAX_NAME_LENGTH + 1];
2376       CGNS_ENUMT(BCType_t)       bocotype;
2377       CGNS_ENUMT(PointSetType_t) ptset_type;
2378       cgsize_t          npnts;
2379       cgsize_t          NormalListSize;
2380       CGNS_ENUMT(DataType_t)     NormalDataType;
2381       int               ndataset;
2382 
2383       // All we really want from this is 'boconame'
2384       CGCHECKNP(cg_boco_info(cgns_file_ptr, base, izone, ibc + 1, boconame, &bocotype, &ptset_type,
2385                              &npnts, nullptr, &NormalListSize, &NormalDataType, &ndataset));
2386 
2387       if (bocotype == CGNS_ENUMV(FamilySpecified)) {
2388         // Need to get boconame from cg_famname_read
2389         CGCHECKNP(
2390             cg_goto(cgns_file_ptr, base, "Zone_t", izone, "ZoneBC_t", 1, "BC_t", ibc + 1, "end"));
2391         CGCHECKNP(cg_famname_read(boconame));
2392       }
2393 
2394       Ioss::Utils::fixup_name(boconame);
2395       if (std::find(bcs.begin(), bcs.end(), boconame) != bcs.end()) {
2396         cgsize_t range[6];
2397         CGCHECKNP(cg_boco_read(cgns_file_ptr, base, izone, ibc + 1, range, nullptr));
2398 
2399         // There are some BC that are applied on an edge or a vertex;
2400         // Don't want those, so filter them out at this time...
2401         bool i = range[0] == range[3];
2402         bool j = range[1] == range[4];
2403         bool k = range[2] == range[5];
2404 
2405         int sum = (i ? 1 : 0) + (j ? 1 : 0) + (k ? 1 : 0);
2406         // Only set m_lineOrdinal if only a single ordinal selected.
2407         if (sum == 1) {
2408           unsigned int ordinal = 0;
2409           if (i) {
2410             ordinal = Ordinal::I;
2411           }
2412           else if (j) {
2413             ordinal = Ordinal::J;
2414           }
2415           else if (k) {
2416             ordinal = Ordinal::K;
2417           }
2418           zone->m_lineOrdinal |= ordinal;
2419           if (verbose && rank == 0) {
2420             fmt::print(Ioss::DEBUG(), "Setting line ordinal to {} on {} for surface: {}\n",
2421                        zone->m_lineOrdinal, zone->m_name, boconame);
2422           }
2423         }
2424       }
2425     }
2426   }
2427 }
2428 
decompose_model(std::vector<Iocgns::StructuredZoneData * > & zones,int proc_count,int rank,double load_balance_threshold,bool verbose)2429 void Iocgns::Utils::decompose_model(std::vector<Iocgns::StructuredZoneData *> &zones,
2430                                     int proc_count, int rank, double load_balance_threshold,
2431                                     bool verbose)
2432 {
2433   size_t work = 0;
2434   for (const auto &z : zones) {
2435     work += z->work();
2436     assert(z->is_active());
2437   }
2438 
2439   double avg_work = (double)work / proc_count;
2440 
2441   if (verbose) {
2442     auto num_active = zones.size();
2443     if (rank == 0) {
2444       fmt::print(
2445           Ioss::OUTPUT(),
2446           "Decomposing structured mesh with {} zones for {} processors.\nAverage workload is {:L}, "
2447           "Load Balance Threshold is {}, Work range {:L} to {:L}\n",
2448           num_active, proc_count, (size_t)avg_work, load_balance_threshold,
2449           (size_t)(avg_work / load_balance_threshold), (size_t)(avg_work * load_balance_threshold));
2450     }
2451   }
2452 
2453   if (avg_work < 1.0) {
2454     std::ostringstream errmsg;
2455     fmt::print(errmsg, "ERROR: Model size too small to distribute over {} processors.\n",
2456                proc_count);
2457     IOSS_ERROR(errmsg);
2458   }
2459 
2460   if (verbose) {
2461     if (rank == 0) {
2462       fmt::print(Ioss::DEBUG(),
2463                  "========================================================================\n");
2464       fmt::print(Ioss::DEBUG(), "Pre-Splitting: (Average = {:L}, LB Threshold = {}\n",
2465                  (size_t)avg_work, load_balance_threshold);
2466     }
2467   }
2468   // Split all blocks where block->work() > avg_work * load_balance_threshold
2469   size_t new_zone_id =
2470       Utils::pre_split(zones, avg_work, load_balance_threshold, rank, proc_count, verbose);
2471 
2472   // At this point, there should be no zone with block->work() > avg_work * load_balance_threshold
2473   if (verbose) {
2474     if (rank == 0) {
2475       fmt::print(Ioss::DEBUG(),
2476                  "========================================================================\n");
2477     }
2478   }
2479   size_t num_split = 0;
2480   size_t px        = 0;
2481   do {
2482     std::vector<size_t> work_vector(proc_count);
2483     Utils::assign_zones_to_procs(zones, work_vector, verbose);
2484 
2485     // Calculate workload ratio for each processor...
2486     px = 0; // Number of processors where workload ratio exceeds threshold.
2487     std::vector<bool> exceeds(proc_count);
2488     for (size_t i = 0; i < work_vector.size(); i++) {
2489       double workload_ratio = double(work_vector[i]) / avg_work;
2490       if (workload_ratio > load_balance_threshold) {
2491         exceeds[i] = true;
2492         px++;
2493         if (verbose && rank == 0) {
2494           fmt::print(Ioss::DEBUG(), "{}",
2495                      fmt::format(fg(fmt::color::red),
2496                                  "\nProcessor {} work: {:L}, workload ratio: {} (exceeds)", i,
2497                                  work_vector[i], workload_ratio));
2498         }
2499       }
2500       else {
2501         if (verbose && rank == 0) {
2502           fmt::print(Ioss::DEBUG(), "\nProcessor {} work: {:L}, workload ratio: {}", i,
2503                      work_vector[i], workload_ratio);
2504         }
2505       }
2506     }
2507     if (verbose && rank == 0) {
2508       fmt::print(Ioss::DEBUG(), "\n\nWorkload threshold exceeded on {} processors.\n", px);
2509     }
2510     bool single_zone = zones.size() == 1;
2511     if (single_zone) {
2512       auto active = std::count_if(zones.begin(), zones.end(),
2513                                   [](Iocgns::StructuredZoneData *a) { return a->is_active(); });
2514       if (active >= proc_count) {
2515         px = 0;
2516       }
2517     }
2518     num_split = 0;
2519     if (px > 0) {
2520       auto zone_new(zones);
2521       for (auto zone : zones) {
2522         if (zone->is_active() && exceeds[zone->m_proc]) {
2523           // Since 'zones' is sorted from most work to least,
2524           // we just iterate zones and check whether the zone
2525           // is on a proc where the threshold was exceeded.
2526           // if so, split the block and set exceeds[proc] to false;
2527           // Exit the loop when num_split >= px.
2528           auto children = zone->split(static_cast<int>(new_zone_id), zone->work() / 2.0, rank, verbose);
2529           if (children.first != nullptr && children.second != nullptr) {
2530             zone_new.push_back(children.first);
2531             zone_new.push_back(children.second);
2532 
2533             new_zone_id += 2;
2534             exceeds[zone->m_proc] = false;
2535             num_split++;
2536             if (num_split >= px) {
2537               break;
2538             }
2539           }
2540         }
2541       }
2542       std::swap(zone_new, zones);
2543     }
2544     if (verbose) {
2545       auto active = std::count_if(zones.begin(), zones.end(),
2546                                   [](Iocgns::StructuredZoneData *a) { return a->is_active(); });
2547       if (rank == 0) {
2548         fmt::print(Ioss::DEBUG(), "Number of active zones = {}, average work = {:L}\n", active,
2549                    (size_t)avg_work);
2550         fmt::print(Ioss::DEBUG(),
2551                    "========================================================================\n");
2552       }
2553     }
2554   } while (px > 0 && num_split > 0);
2555 }
2556 
assign_zones_to_procs(std::vector<Iocgns::StructuredZoneData * > & all_zones,std::vector<size_t> & work_vector,bool verbose)2557 void Iocgns::Utils::assign_zones_to_procs(std::vector<Iocgns::StructuredZoneData *> &all_zones,
2558                                           std::vector<size_t> &work_vector, bool verbose)
2559 {
2560   for (auto &zone : all_zones) {
2561     zone->m_proc = -1;
2562   }
2563 
2564   // Sort zones based on work.  Most work first.. Filtered to active only...
2565   std::vector<Iocgns::StructuredZoneData *> zones;
2566   std::copy_if(all_zones.begin(), all_zones.end(), std::back_inserter(zones),
2567                [](Iocgns::StructuredZoneData *z) { return z->is_active(); });
2568 
2569   Ioss::sort(zones.begin(), zones.end(),
2570              [](Iocgns::StructuredZoneData *a, Iocgns::StructuredZoneData *b) {
2571                return a->work() > b->work();
2572              });
2573 
2574   std::set<std::pair<int, int>> proc_adam_map;
2575 
2576   // On first entry, work_vector will be all zeros.  To avoid any
2577   // searching, assign the first `nproc` zones to the `nproc` entries
2578   // in `work_vector`.  Avoids searching...
2579   if (zones.size() < work_vector.size()) {
2580     std::ostringstream errmsg;
2581     fmt::print(errmsg,
2582                "IOCGNS error: Could not decompose mesh across {} processors based on constraints.",
2583                work_vector.size());
2584     IOSS_ERROR(errmsg);
2585   }
2586   assert(zones.size() >= work_vector.size());
2587   size_t i = 0;
2588   for (; i < work_vector.size(); i++) {
2589     auto &zone   = zones[i];
2590     zone->m_proc = static_cast<int>(i);
2591     if (verbose) {
2592       fmt::print(
2593           Ioss::DEBUG(),
2594           "Assigning zone '{}' with work {:L} to processor {}. Changing work from {:L} to {:L}\n",
2595           zone->m_name, zone->work(), zone->m_proc, work_vector[i], zone->work() + work_vector[i]);
2596     }
2597     work_vector[i] += zone->work();
2598     proc_adam_map.insert(std::make_pair(zone->m_adam->m_zone, zone->m_proc));
2599   }
2600 
2601   for (; i < zones.size(); i++) {
2602     auto &zone = zones[i];
2603 
2604     // Assign zone to processor with minimum work that does not already have a zone with the same
2605     // adam zone...
2606     ssize_t proc = proc_with_minimum_work(zone, work_vector, proc_adam_map);
2607 
2608     // See if any other zone on this processor has the same adam zone...
2609     if (proc >= 0) {
2610       auto success = proc_adam_map.insert(std::make_pair(zone->m_adam->m_zone, static_cast<int>(proc)));
2611       if (success.second) {
2612         zone->m_proc = static_cast<int>(proc);
2613         if (verbose) {
2614           fmt::print(Ioss::DEBUG(),
2615                      "Assigning zone '{}' with work {:L} to processor {}. Changing work from {:L} "
2616                      "to {:L}\n",
2617                      zone->m_name, zone->work(), zone->m_proc, work_vector[proc],
2618                      zone->work() + work_vector[proc]);
2619         }
2620         work_vector[proc] += zone->work();
2621       }
2622       else {
2623         std::ostringstream errmsg;
2624         fmt::print(errmsg, "IOCGNS error: Could not assign zones to processors in {}", __func__);
2625         IOSS_ERROR(errmsg);
2626       }
2627     }
2628     else {
2629       std::ostringstream errmsg;
2630       fmt::print(errmsg, "IOCGNS error: Could not assign zones to processors in {}", __func__);
2631       IOSS_ERROR(errmsg);
2632     }
2633   }
2634 }
2635 
pre_split(std::vector<Iocgns::StructuredZoneData * > & zones,double avg_work,double load_balance,int proc_rank,int proc_count,bool verbose)2636 size_t Iocgns::Utils::pre_split(std::vector<Iocgns::StructuredZoneData *> &zones, double avg_work,
2637                                 double load_balance, int proc_rank, int proc_count, bool verbose)
2638 {
2639   auto original_zones(zones); // In case we need to call this again...
2640 
2641   auto   new_zones(zones);
2642   size_t new_zone_id = zones.size() + 1;
2643 
2644   // See if can split each zone over a set of procs...
2645   double           total_work = 0.0;
2646   std::vector<int> splits(zones.size());
2647 
2648   for (size_t i = 0; i < zones.size(); i++) {
2649     auto   zone = zones[i];
2650     double work = static_cast<double>(zone->work());
2651     total_work += work;
2652     if (load_balance <= 1.2) {
2653       splits[i] = int(std::ceil(work / avg_work));
2654     }
2655     else {
2656       splits[i] = int(std::round(work / avg_work + 0.2));
2657     }
2658     splits[i] = splits[i] == 0 ? 1 : splits[i];
2659   }
2660 
2661   int  num_splits        = std::accumulate(splits.begin(), splits.end(), 0);
2662   int  diff              = proc_count - num_splits;
2663   bool adjustment_needed = diff > 0;
2664 
2665   while (diff != 0) {
2666     // Adjust splits so sum is equal to proc_count.
2667     // Adjust the largest split count(s)
2668     int    step      = diff < 0 ? -1 : 1;
2669     size_t min_z     = 0;
2670     double min_delta = 1.0e27;
2671     for (size_t i = 0; i < zones.size(); i++) {
2672       auto   zone = zones[i];
2673       double work = static_cast<double>(zone->work());
2674 
2675       if (splits[i] == 0) {
2676         continue;
2677       }
2678       if ((splits[i] + step) > 0) {
2679         double delta = std::abs(avg_work - work / (double)(splits[i] + step));
2680         if (delta < min_delta) {
2681           min_delta = delta;
2682           min_z     = i;
2683         }
2684       }
2685     }
2686     splits[min_z] += step;
2687     diff -= step;
2688   }
2689   assert(diff == 0);
2690   assert(std::accumulate(splits.begin(), splits.end(), 0) == proc_count);
2691 
2692   // See if splits result in avg_work for all zones in range...
2693   double min_avg      = avg_work / load_balance;
2694   double max_avg      = avg_work * load_balance;
2695   bool   adaptive_avg = true;
2696   if (!adjustment_needed) {
2697     for (size_t i = 0; i < zones.size(); i++) {
2698       auto   zone = zones[i];
2699       double work = static_cast<double>(zone->work());
2700       if (splits[i] == 0) {
2701         adaptive_avg = false;
2702         break;
2703       }
2704       double zone_avg = work / (double)splits[i];
2705       if (zone_avg < min_avg || zone_avg > max_avg) {
2706         adaptive_avg = false;
2707         break;
2708       }
2709     }
2710   }
2711 
2712   if (adaptive_avg) {
2713     for (size_t i = 0; i < zones.size(); i++) {
2714       auto zone       = zones[i];
2715       int  num_active = 0;
2716 
2717       auto work_average = avg_work;
2718       int  split_cnt    = splits[i];
2719       if (split_cnt == 1) {
2720         continue;
2721       }
2722 
2723       std::vector<std::pair<int, Iocgns::StructuredZoneData *>> active;
2724       active.emplace_back(split_cnt, zone);
2725       do {
2726         assert(!active.empty());
2727         split_cnt = active.back().first;
2728         zone      = active.back().second;
2729         active.pop_back();
2730 
2731         if (zone->is_active()) {
2732           if (split_cnt != 1) {
2733             int max_power_2 = power_2(split_cnt);
2734             if (max_power_2 == split_cnt) {
2735               work_average = zone->work() / 2.0;
2736               max_power_2 /= 2;
2737             }
2738             else {
2739               work_average = zone->work() / (double(split_cnt) / double(max_power_2));
2740             }
2741 
2742             auto children = zone->split(static_cast<int>(new_zone_id), work_average, proc_rank, verbose);
2743             if (children.first != nullptr && children.second != nullptr) {
2744               new_zones.push_back(children.first);
2745               new_zones.push_back(children.second);
2746               new_zone_id += 2;
2747               active.emplace_back(split_cnt - max_power_2, children.second);
2748               active.emplace_back(max_power_2, children.first);
2749               num_active++;
2750             }
2751           }
2752         }
2753         if (num_active >=
2754             proc_count) { // Don't split a single zone into more than `proc_count` pieces
2755           break;
2756         }
2757       } while (!active.empty());
2758     }
2759   }
2760   else {
2761     for (auto zone : zones) {
2762       int num_active = 0;
2763       if (zone->work() <= max_avg) {
2764         // This zone is already in `new_zones`; just skip doing anything else with it.
2765       }
2766       else {
2767         std::vector<std::pair<int, Iocgns::StructuredZoneData *>> active;
2768 
2769         double work      = static_cast<double>(zone->work());
2770         int    split_cnt = int(work / avg_work);
2771 
2772         // Find modulus of work % avg_work and split off that amount
2773         // which will be < avg_work.
2774         double mod_work = work - avg_work * split_cnt;
2775         if (mod_work > max_avg - avg_work) {
2776           auto children = zone->split(static_cast<int>(new_zone_id), mod_work, proc_rank, verbose);
2777           if (children.first != nullptr && children.second != nullptr) {
2778             new_zones.push_back(children.first);
2779             new_zones.push_back(children.second);
2780             new_zone_id += 2;
2781             num_active++;
2782             active.emplace_back(split_cnt, children.second);
2783           }
2784           else {
2785             active.emplace_back(split_cnt, zone);
2786           }
2787         }
2788         else {
2789           active.emplace_back(split_cnt, zone);
2790         }
2791 
2792         // The work remaining on this zone should be approximately
2793         // equally divided among `split_cnt` processors.
2794         do {
2795           assert(!active.empty());
2796           split_cnt = active.back().first;
2797           zone      = active.back().second;
2798           active.pop_back();
2799 
2800           if (zone->is_active()) {
2801             int    max_power_2  = power_2(split_cnt);
2802             double work_average = 0.0;
2803             if (max_power_2 == split_cnt) {
2804               work_average = zone->work() / 2.0;
2805             }
2806             else {
2807               work_average = zone->work() / (double(split_cnt) / double(max_power_2));
2808             }
2809 
2810             if (max_power_2 != 1) {
2811               if (max_power_2 == split_cnt) {
2812                 max_power_2 /= 2;
2813               }
2814               auto children = zone->split(static_cast<int>(new_zone_id), work_average, proc_rank, verbose);
2815               if (children.first != nullptr && children.second != nullptr) {
2816                 new_zones.push_back(children.first);
2817                 new_zones.push_back(children.second);
2818                 new_zone_id += 2;
2819                 active.emplace_back(split_cnt - max_power_2, children.second);
2820                 active.emplace_back(max_power_2, children.first);
2821                 num_active++;
2822               }
2823             }
2824           }
2825           if (num_active >=
2826               proc_count) { // Don't split a single zone into more than `proc_count` pieces
2827             break;
2828           }
2829         } while (!active.empty());
2830       }
2831     }
2832   }
2833   std::swap(new_zones, zones);
2834   size_t active = std::count_if(zones.begin(), zones.end(),
2835                                 [](const Iocgns::StructuredZoneData *z) { return z->is_active(); });
2836 
2837   if (active < (size_t)proc_count && load_balance > 1.05) {
2838     // Tighten up the load_balance factor to get some decomposition going...
2839     double new_load_balance = (1.0 + load_balance) / 2.0;
2840 
2841     // If any of the original zones were split the first time we called this routine,
2842     // we need to delete the zones created via splitting.
2843     // Also reset the parent zone to not have any children...
2844     for (auto &zone : zones) {
2845       if (!zone->is_active()) {
2846         zone->m_child1 = nullptr;
2847         zone->m_child2 = nullptr;
2848       }
2849       if (zone->m_adam != zone) {
2850         // Created via a split; delete...
2851         delete zone;
2852       }
2853     }
2854 
2855     // Revert `zones` back to original version (with no zones split)
2856     zones       = original_zones;
2857     new_zone_id = pre_split(zones, avg_work, new_load_balance, proc_rank, proc_count, verbose);
2858   }
2859   return new_zone_id;
2860 }
2861 
parse_zonebc_sideblocks(int cgns_file_ptr,int base,int zone,int myProcessor)2862 std::vector<Iocgns::ZoneBC> Iocgns::Utils::parse_zonebc_sideblocks(int cgns_file_ptr, int base,
2863                                                                    int zone, int myProcessor)
2864 {
2865   int num_bc;
2866   CGCHECK(cg_nbocos(cgns_file_ptr, base, zone, &num_bc));
2867 
2868   std::vector<Iocgns::ZoneBC> zonebc;
2869   zonebc.reserve(num_bc);
2870 
2871   for (int i = 0; i < num_bc; i++) {
2872     char              boco_name[CGNS_MAX_NAME_LENGTH + 1];
2873     CGNS_ENUMT(BCType_t)       boco_type;
2874     CGNS_ENUMT(PointSetType_t) ptset_type;
2875     cgsize_t          num_pnts;
2876     cgsize_t          normal_list_size; // ignore
2877     CGNS_ENUMT(DataType_t)     normal_data_type; // ignore
2878     int               num_dataset;      // ignore
2879     CGCHECK(cg_boco_info(cgns_file_ptr, base, zone, i + 1, boco_name, &boco_type, &ptset_type,
2880                          &num_pnts, nullptr, &normal_list_size, &normal_data_type, &num_dataset));
2881 
2882     if (num_pnts != 2 || ptset_type != CGNS_ENUMV(PointRange)) {
2883       std::ostringstream errmsg;
2884       fmt::print(
2885           errmsg,
2886           "CGNS: In Zone {}, boundary condition '{}' has a PointSetType of '{}' and {} points.\n"
2887           "      The type must be 'PointRange' and there must be 2 points.",
2888           zone, boco_name, cg_PointSetTypeName(ptset_type), num_pnts);
2889       IOSS_ERROR(errmsg);
2890     }
2891 
2892     std::array<cgsize_t, 2> point_range;
2893     CGCHECK(cg_boco_read(cgns_file_ptr, base, zone, i + 1, point_range.data(), nullptr));
2894     zonebc.emplace_back(boco_name, point_range);
2895   }
2896   return zonebc;
2897 }
2898 
2899 #ifdef CG_BUILD_HDF5
2900 // xxx(kitware)
2901 // extern "C" int H5get_libversion(unsigned *, unsigned *, unsigned *);
2902 #endif
2903 
show_config()2904 std::string Iocgns::Utils::show_config()
2905 {
2906   std::stringstream config;
2907   fmt::print(config, "\tCGNS Library Version: {}\n", CGNS_DOTVERS);
2908 #if CG_BUILD_64BIT
2909   fmt::print(config, "\t\tDefault integer size is 64-bit.\n");
2910 #else
2911   fmt::print(config, "\t\tDefault integer size is 32-bit.\n");
2912 #endif
2913 #if defined(CGNS_SCOPE_ENUMS)
2914   fmt::print(config, "\t\tScoped Enums enabled\n");
2915 #else
2916   fmt::print(config, "\t\tScoped Enums NOT enabled\n");
2917 #endif
2918 #if defined(CG_COMPACT)
2919   fmt::print(config, "\t\tCompact Storage enabled\n");
2920 #else
2921   fmt::print(config, "\t\tCompact Storage NOT enabled\n");
2922 #endif
2923 #if CG_BUILD_PARALLEL
2924   fmt::print(config, "\t\tParallel enabled\n");
2925 #else
2926   fmt::print(config, "\t\tParallel NOT enabled\n");
2927 #endif
2928 #if CG_BUILD_HDF5
2929   // xxx(kitware)
2930   // unsigned major;
2931   // unsigned minor;
2932   // unsigned release;
2933   // H5get_libversion(&major, &minor, &release);
2934   // fmt::print(config, "\t\tHDF5 enabled ({}.{}.{})\n", major, minor, release);
2935 #else
2936 #error "Not defined..."
2937 #endif
2938 #if HDF5_HAVE_COLL_METADATA
2939   fmt::print(config, "\t\tUsing HDF5 Collective Metadata.\n");
2940 #else
2941   fmt::print(config, "\t\tHDF5 Collective Metadata NOT Available.\n");
2942 #endif
2943 #if HDF5_HAVE_MULTI_DATASET
2944   fmt::print(config, "\t\tHDF5 Multi-Dataset Available.\n\n");
2945 #else
2946   fmt::print(config, "\t\tHDF5 Multi-Dataset NOT Available.\n\n");
2947 #endif
2948   return config.str();
2949 }
2950 
2951 namespace {
create_face(Ioss::FaceUnorderedSet & faces,size_t id,std::array<size_t,4> & conn,size_t element,int local_face)2952   void create_face(Ioss::FaceUnorderedSet &faces, size_t id, std::array<size_t, 4> &conn,
2953                    size_t element, int local_face)
2954   {
2955     Ioss::Face face(id, conn);
2956     auto       face_iter = faces.insert(face);
2957 
2958     (*(face_iter.first)).add_element(element * 10 + local_face);
2959   }
2960 } // namespace
2961 
2962 template <typename INT>
generate_block_faces(Ioss::ElementTopology * topo,size_t num_elem,const std::vector<INT> & connectivity,Ioss::FaceUnorderedSet & boundary,const std::vector<INT> & zone_local_zone_global)2963 void Iocgns::Utils::generate_block_faces(Ioss::ElementTopology *topo, size_t num_elem,
2964                                          const std::vector<INT> &connectivity,
2965                                          Ioss::FaceUnorderedSet &boundary,
2966                                          const std::vector<INT> &zone_local_zone_global)
2967 {
2968   // Only handle continuum elements at this time...
2969   if (topo->parametric_dimension() != 3) {
2970     return;
2971   }
2972 
2973   int num_face_per_elem = topo->number_faces();
2974   assert(num_face_per_elem <= 6);
2975   std::array<Ioss::IntVector, 6> face_conn;
2976   std::array<int, 6>             face_node_count{};
2977   for (int face = 0; face < num_face_per_elem; face++) {
2978     face_conn[face]       = topo->face_connectivity(face + 1);
2979     face_node_count[face] = topo->face_type(face + 1)->number_corner_nodes();
2980   }
2981 
2982   Ioss::FaceUnorderedSet all_faces;
2983   int                    num_node_per_elem = topo->number_nodes();
2984   for (size_t elem = 0, offset = 0; elem < num_elem; elem++, offset += num_node_per_elem) {
2985     for (int face = 0; face < num_face_per_elem; face++) {
2986       size_t id = 0;
2987       assert(face_node_count[face] <= 4);
2988       std::array<size_t, 4> conn = {{0, 0, 0, 0}};
2989       for (int j = 0; j < face_node_count[face]; j++) {
2990         size_t fnode = offset + face_conn[face][j];
2991         size_t lnode = connectivity[fnode]; // local since "connectivity_raw"
2992         conn[j]      = lnode;
2993         id += Ioss::FaceGenerator::id_hash(lnode);
2994       }
2995       auto elem_id = zone_local_zone_global[elem];
2996       create_face(all_faces, id, conn, elem_id, face);
2997     }
2998   }
2999 
3000   // All faces generated for this element block; now extract boundary faces...
3001   for (auto &face : all_faces) {
3002     if (face.elementCount_ == 1) {
3003       boundary.insert(face);
3004     }
3005   }
3006 }
3007 
3008 template void Iocgns::Utils::generate_block_faces<int>(
3009     Ioss::ElementTopology *topo, size_t num_elem, const std::vector<int> &connectivity,
3010     Ioss::FaceUnorderedSet &boundary, const std::vector<int> &zone_local_zone_global);
3011 template void Iocgns::Utils::generate_block_faces<int64_t>(
3012     Ioss::ElementTopology *topo, size_t num_elem, const std::vector<int64_t> &connectivity,
3013     Ioss::FaceUnorderedSet &boundary, const std::vector<int64_t> &zone_local_zone_global);
3014