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 ®ion)
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 ®ion, 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 ®ion,
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 ®ion,
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 ®ion, 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 ®ion, 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> ×teps,
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> ×teps,
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> ×teps,
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