1 // Copyright(C) 1999-2021 National Technology & Engineering Solutions 2 // of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with 3 // NTESS, the U.S. Government retains certain rights in this software. 4 // 5 // See packages/seacas/LICENSE for details 6 7 #include <Ioss_BoundingBox.h> 8 #include <Ioss_CodeTypes.h> 9 #include <Ioss_CommSet.h> 10 #include <Ioss_DBUsage.h> 11 #include <Ioss_DatabaseIO.h> 12 #include <Ioss_ElementTopology.h> 13 #include <Ioss_EntityBlock.h> 14 #include <Ioss_Field.h> 15 #include <Ioss_FileInfo.h> 16 #include <Ioss_GroupingEntity.h> 17 #include <Ioss_NodeBlock.h> 18 #include <Ioss_ParallelUtils.h> 19 #include <Ioss_Property.h> 20 #include <Ioss_Region.h> 21 #include <Ioss_SerializeIO.h> 22 #include <Ioss_SideBlock.h> 23 #include <Ioss_SideSet.h> 24 #include <Ioss_Sort.h> 25 #include <Ioss_State.h> 26 #include <Ioss_StructuredBlock.h> 27 #include <Ioss_SurfaceSplit.h> 28 #include <Ioss_Utils.h> 29 #include <algorithm> 30 #include <cassert> 31 #include <cfloat> 32 #include <cstddef> 33 #include <cstring> 34 #include <fmt/ostream.h> 35 #include <iomanip> 36 #include <iostream> 37 #include <iterator> 38 #include <set> 39 #include <string> 40 #include <tokenize.h> 41 #include <utility> 42 #include <vector> 43 44 #if defined SEACAS_HAVE_DATAWARP 45 extern "C" { 46 #include <datawarp.h> 47 } 48 #endif 49 50 namespace { 51 auto initial_time = std::chrono::steady_clock::now(); 52 53 void log_time(std::chrono::time_point<std::chrono::steady_clock> &start, 54 std::chrono::time_point<std::chrono::steady_clock> &finish, int current_state, 55 double state_time, bool is_input, bool single_proc_only, 56 const Ioss::ParallelUtils &util); 57 58 void log_field(const char *symbol, const Ioss::GroupingEntity *entity, const Ioss::Field &field, 59 bool single_proc_only, const Ioss::ParallelUtils &util); 60 61 #ifndef NDEBUG internal_parallel_consistent(bool single_proc_only,const Ioss::GroupingEntity * ge,const Ioss::Field & field,int in_out,const Ioss::ParallelUtils & util)62 bool internal_parallel_consistent(bool single_proc_only, const Ioss::GroupingEntity *ge, 63 const Ioss::Field &field, int in_out, 64 const Ioss::ParallelUtils &util) 65 { 66 if (single_proc_only) { 67 return true; 68 } 69 70 const std::string &field_name = field.get_name(); 71 unsigned int hash_code = ge->hash() + Ioss::Utils::hash(field_name); 72 unsigned int max_hash = util.global_minmax(hash_code, Ioss::ParallelUtils::DO_MAX); 73 unsigned int min_hash = util.global_minmax(hash_code, Ioss::ParallelUtils::DO_MIN); 74 if (max_hash != min_hash) { 75 const std::string &ge_name = ge->name(); 76 fmt::print(Ioss::WARNING(), 77 "[{}] Parallel inconsistency detected for {} field '{}' on entity '{}'. (Hash: {} " 78 "{} {})\n", 79 in_out == 0 ? "writing" : "reading", util.parallel_rank(), field_name, ge_name, 80 hash_code, min_hash, max_hash); 81 return false; 82 } 83 return true; 84 } 85 #endif my_min(double x1,double x2)86 double my_min(double x1, double x2) { return x1 < x2 ? x1 : x2; } 87 my_max(double x1,double x2)88 double my_max(double x1, double x2) { return x1 > x2 ? x1 : x2; } 89 90 template <typename INT> calc_bounding_box(size_t ndim,size_t node_count,std::vector<double> & coordinates,std::vector<INT> & connectivity,double & xmin,double & ymin,double & zmin,double & xmax,double & ymax,double & zmax)91 void calc_bounding_box(size_t ndim, size_t node_count, std::vector<double> &coordinates, 92 std::vector<INT> &connectivity, double &xmin, double &ymin, double &zmin, 93 double &xmax, double &ymax, double &zmax) 94 { 95 std::vector<int> elem_block_nodes(node_count); 96 for (auto &node : connectivity) { 97 elem_block_nodes[node - 1] = 1; 98 } 99 100 xmin = DBL_MAX; 101 ymin = DBL_MAX; 102 zmin = DBL_MAX; 103 104 xmax = -DBL_MAX; 105 ymax = -DBL_MAX; 106 zmax = -DBL_MAX; 107 108 for (size_t i = 0; i < node_count; i++) { 109 if (elem_block_nodes[i] == 1) { 110 xmin = my_min(xmin, coordinates[ndim * i + 0]); 111 xmax = my_max(xmax, coordinates[ndim * i + 0]); 112 113 if (ndim > 1) { 114 ymin = my_min(ymin, coordinates[ndim * i + 1]); 115 ymax = my_max(ymax, coordinates[ndim * i + 1]); 116 } 117 118 if (ndim > 2) { 119 zmin = my_min(zmin, coordinates[ndim * i + 2]); 120 zmax = my_max(zmax, coordinates[ndim * i + 2]); 121 } 122 } 123 } 124 if (ndim < 3) { 125 zmin = zmax = 0.0; 126 } 127 if (ndim < 2) { 128 ymin = ymax = 0.0; 129 } 130 } 131 } // namespace 132 133 namespace Ioss { DatabaseIO(Region * region,std::string filename,DatabaseUsage db_usage,MPI_Comm communicator,const PropertyManager & props)134 DatabaseIO::DatabaseIO(Region *region, std::string filename, DatabaseUsage db_usage, 135 MPI_Comm communicator, const PropertyManager &props) 136 : properties(props), DBFilename(std::move(filename)), dbUsage(db_usage), 137 util_(db_usage == WRITE_HISTORY || db_usage == WRITE_HEARTBEAT ? MPI_COMM_SELF 138 : communicator), 139 region_(region), isInput(is_input_event(db_usage)), 140 singleProcOnly(db_usage == WRITE_HISTORY || db_usage == WRITE_HEARTBEAT || 141 SerializeIO::isEnabled()) 142 { 143 isParallel = util_.parallel_size() > 1; 144 myProcessor = util_.parallel_rank(); 145 146 // Some operations modify DBFilename and there is a need to get 147 // back to the original filename... 148 originalDBFilename = DBFilename; 149 150 // Check environment variable IOSS_PROPERTIES. If it exists, parse 151 // the contents and add to the 'properties' map. 152 util_.add_environment_properties(properties); 153 154 Utils::check_set_bool_property(properties, "ENABLE_FIELD_RECOGNITION", enableFieldRecognition); 155 156 if (properties.exists("FIELD_SUFFIX_SEPARATOR")) { 157 std::string tmp = properties.get("FIELD_SUFFIX_SEPARATOR").get_string(); 158 fieldSeparator = tmp[0]; 159 } 160 161 if (properties.exists("INTEGER_SIZE_API")) { 162 int isize = properties.get("INTEGER_SIZE_API").get_int(); 163 if (isize == 8) { 164 set_int_byte_size_api(Ioss::USE_INT64_API); 165 } 166 } 167 168 if (properties.exists("SERIALIZE_IO")) { 169 int isize = properties.get("SERIALIZE_IO").get_int(); 170 Ioss::SerializeIO::setGroupFactor(isize); 171 if (isize > 0) { 172 singleProcOnly = true; 173 } 174 } 175 176 if (properties.exists("CYCLE_COUNT")) { 177 cycleCount = properties.get("CYCLE_COUNT").get_int(); 178 } 179 180 if (properties.exists("OVERLAY_COUNT")) { 181 overlayCount = properties.get("OVERLAY_COUNT").get_int(); 182 } 183 184 Utils::check_set_bool_property(properties, "ENABLE_TRACING", m_enableTracing); 185 Utils::check_set_bool_property(properties, "TIME_STATE_INPUT_OUTPUT", m_timeStateInOut); 186 { 187 bool logging; 188 if (Utils::check_set_bool_property(properties, "LOGGING", logging)) { 189 set_logging(logging); 190 } 191 } 192 193 Utils::check_set_bool_property(properties, "LOWER_CASE_VARIABLE_NAMES", lowerCaseVariableNames); 194 Utils::check_set_bool_property(properties, "USE_GENERIC_CANONICAL_NAMES", 195 useGenericCanonicalName); 196 Utils::check_set_bool_property(properties, "IGNORE_DATABASE_NAMES", ignoreDatabaseNames); 197 198 { 199 bool consistent; 200 if (Utils::check_set_bool_property(properties, "PARALLEL_CONSISTENCY", consistent)) { 201 set_parallel_consistency(consistent); 202 } 203 } 204 205 check_setDW(); 206 207 if (!is_input()) { 208 // Create full path to the output file at this point if it doesn't 209 // exist... 210 if (isParallel) { 211 Ioss::FileInfo::create_path(DBFilename, util().communicator()); 212 } 213 else { 214 Ioss::FileInfo::create_path(DBFilename); 215 } 216 } 217 } 218 219 DatabaseIO::~DatabaseIO() = default; 220 int_byte_size_api()221 int DatabaseIO::int_byte_size_api() const 222 { 223 if (dbIntSizeAPI == USE_INT32_API) { 224 return 4; 225 } 226 return 8; 227 } 228 229 /** \brief Set the number of bytes used to represent an integer. 230 * 231 * \param[in] size The number of bytes. This is 4 for INT32 or 8 for INT64. 232 */ set_int_byte_size_api(DataSize size)233 void DatabaseIO::set_int_byte_size_api(DataSize size) const 234 { 235 dbIntSizeAPI = size; // mutable 236 } 237 238 /** \brief Set the character used to separate a field suffix from the field basename 239 * when recognizing vector, tensor fields. 240 * 241 * \param[in] separator The separator character. 242 */ set_field_separator(const char separator)243 void DatabaseIO::set_field_separator(const char separator) 244 { 245 if (properties.exists("FIELD_SUFFIX_SEPARATOR")) { 246 properties.erase("FIELD_SUFFIX_SEPARATOR"); 247 } 248 char tmp[2] = {separator, '\0'}; 249 properties.add(Property("FIELD_SUFFIX_SEPARATOR", tmp)); 250 fieldSeparator = separator; 251 } 252 253 /** 254 * Check whether user wants to use Cray DataWarp. It will be enabled if: 255 * the `DW_JOB_STRIPED` or `DW_JOB_PRIVATE` environment variable 256 * is set by the queuing system during runtime and the IOSS property 257 * `ENABLE_DATAWARP` set to `YES`. 258 * 259 * We currently only want output files to be directed to BB. 260 */ check_setDW()261 void DatabaseIO::check_setDW() const 262 { 263 if (!is_input()) { 264 bool set_dw = false; 265 Utils::check_set_bool_property(properties, "ENABLE_DATAWARP", set_dw); 266 if (set_dw) { 267 std::string bb_path; 268 // Selected via `#DW jobdw type=scratch access_mode=striped` 269 util().get_environment("DW_JOB_STRIPED", bb_path, isParallel); 270 271 if (bb_path.empty()) { // See if using `private` mode... 272 // Selected via `#DW jobdw type=scratch access_mode=private` 273 util().get_environment("DW_JOB_PRIVATE", bb_path, isParallel); 274 } 275 if (!bb_path.empty()) { 276 usingDataWarp = true; 277 dwPath = bb_path; 278 if (myProcessor == 0) { 279 fmt::print(Ioss::OUTPUT(), "\nDataWarp Burst Buffer Enabled. Path = `{}`\n\n", dwPath); 280 } 281 } 282 else { 283 if (myProcessor == 0) { 284 fmt::print(Ioss::WARNING(), 285 "DataWarp enabled via Ioss property `ENABLE_DATAWARP`, but\n" 286 " burst buffer path was not specified via `DW_JOB_STRIPED` or " 287 "`DW_JOB_PRIVATE`\n" 288 " environment variables (typically set by queuing system)\n" 289 " DataWarp will *NOT* be enabled, but job will still run.\n\n"); 290 } 291 } 292 } 293 } 294 } 295 296 /** 297 * In this wrapper function we check if user intends to use Cray 298 * DataWarp(aka DW), which provides ability to use NVMe based flash 299 * storage available across all compute nodes accessible via high 300 * speed NIC. 301 */ openDW(const std::string & filename)302 void DatabaseIO::openDW(const std::string &filename) const 303 { 304 set_pfsname(filename); // Name on permanent-file-store 305 if (using_dw()) { // We are about to write to a output database in BB 306 Ioss::FileInfo path{filename}; 307 Ioss::FileInfo bb_file{get_dwPath() + path.tailname()}; 308 if (bb_file.exists() && !bb_file.is_writable()) { 309 // already existing file which has been closed If we can't 310 // write to the file on the BB, then it is a file which is 311 // being staged by datawarp system over to the permanent 312 // filesystem. Wait until staging has finished... stage wait 313 // returns 0 = success, -ENOENT or -errno 314 #if defined SEACAS_HAVE_DATAWARP 315 #if IOSS_DEBUG_OUTPUT 316 if (myProcessor == 0) { 317 fmt::print(Ioss::DEBUG(), "DW: dw_wait_file_stage({});\n", bb_file.filename()); 318 } 319 #endif 320 int dwret = dw_wait_file_stage(bb_file.filename().c_str()); 321 if (dwret < 0) { 322 std::ostringstream errmsg; 323 fmt::print(errmsg, "ERROR: failed waiting for file stage `{}`: {}\n", bb_file.filename(), 324 std::strerror(-dwret)); 325 IOSS_ERROR(errmsg); 326 } 327 #else 328 // Used to debug DataWarp logic on systems without DataWarp... 329 fmt::print(Ioss::DEBUG(), "DW: (FAKE) dw_wait_file_stage({});\n", bb_file.filename()); 330 #endif 331 } 332 set_dwname(bb_file.filename()); 333 } 334 else { 335 set_dwname(filename); 336 } 337 } 338 339 /** \brief This function gets called inside closeDatabase__(), which checks if Cray Datawarp (DW) 340 * is in use, if so, we want to call a stageout before actual close of this file. 341 */ closeDW()342 void DatabaseIO::closeDW() const 343 { 344 if (using_dw()) { 345 if (!using_parallel_io() || (using_parallel_io() && myProcessor == 0)) { 346 #if defined SEACAS_HAVE_DATAWARP 347 int complete = 0, pending = 0, deferred = 0, failed = 0; 348 dw_query_file_stage(get_dwname().c_str(), &complete, &pending, &deferred, &failed); 349 #if IOSS_DEBUG_OUTPUT 350 auto initial = std::chrono::steady_clock::now(); 351 fmt::print(Ioss::DEBUG(), "Query: {}, {}, {}, {}\n", complete, pending, deferred, failed); 352 #endif 353 if (pending > 0) { 354 int dwret = dw_wait_file_stage(get_dwname().c_str()); 355 if (dwret < 0) { 356 std::ostringstream errmsg; 357 fmt::print(errmsg, "ERROR: failed waiting for file stage `{}`: {}\n", get_dwname(), 358 std::strerror(-dwret)); 359 IOSS_ERROR(errmsg); 360 } 361 #if IOSS_DEBUG_OUTPUT 362 dw_query_file_stage(get_dwname().c_str(), &complete, &pending, &deferred, &failed); 363 fmt::print(Ioss::DEBUG(), "Query: {}, {}, {}, {}\n", complete, pending, deferred, failed); 364 #endif 365 } 366 367 #if IOSS_DEBUG_OUTPUT 368 fmt::print(Ioss::DEBUG(), "\nDW: BEGIN dw_stage_file_out({}, {}, DW_STAGE_IMMEDIATE);\n", 369 get_dwname(), get_pfsname()); 370 #endif 371 int ret = 372 dw_stage_file_out(get_dwname().c_str(), get_pfsname().c_str(), DW_STAGE_IMMEDIATE); 373 374 #if IOSS_DEBUG_OUTPUT 375 auto time_now = std::chrono::steady_clock::now(); 376 std::chrono::duration<double> diff = time_now - initial; 377 fmt::print(Ioss::DEBUG(), "\nDW: END dw_stage_file_out({})\n", diff.count()); 378 #endif 379 if (ret < 0) { 380 std::ostringstream errmsg; 381 fmt::print(errmsg, "ERROR: file staging of `{}` to `{}` failed at close: {}\n", 382 get_dwname(), get_pfsname(), std::strerror(-ret)); 383 IOSS_ERROR(errmsg); 384 } 385 #else 386 fmt::print(Ioss::DEBUG(), "\nDW: (FAKE) dw_stage_file_out({}, {}, DW_STAGE_IMMEDIATE);\n", 387 get_dwname(), get_pfsname()); 388 #endif 389 } 390 if (using_parallel_io()) { 391 util().barrier(); 392 } 393 } 394 } 395 openDatabase__()396 void DatabaseIO::openDatabase__() const { openDW(get_filename()); } 397 closeDatabase__()398 void DatabaseIO::closeDatabase__() const { closeDW(); } 399 open_create_behavior()400 IfDatabaseExistsBehavior DatabaseIO::open_create_behavior() const 401 { 402 IfDatabaseExistsBehavior exists = DB_OVERWRITE; 403 if (properties.exists("APPEND_OUTPUT")) { 404 exists = static_cast<IfDatabaseExistsBehavior>(properties.get("APPEND_OUTPUT").get_int()); 405 } 406 return exists; 407 } 408 decoded_filename()409 const std::string &DatabaseIO::decoded_filename() const 410 { 411 if (decodedFilename.empty()) { 412 if (isParallel) { 413 decodedFilename = util().decode_filename(get_filename(), isParallel && !usingParallelIO); 414 } 415 else if (properties.exists("processor_count") && properties.exists("my_processor")) { 416 int proc_count = properties.get("processor_count").get_int(); 417 int my_proc = properties.get("my_processor").get_int(); 418 decodedFilename = Ioss::Utils::decode_filename(get_filename(), my_proc, proc_count); 419 } 420 else { 421 decodedFilename = get_filename(); 422 } 423 424 openDW(decodedFilename); 425 if (using_dw()) { 426 // Note that if using_dw(), then we need to set the decodedFilename to the BB name. 427 decodedFilename = get_dwname(); 428 } 429 } 430 return decodedFilename; 431 } 432 verify_and_log(const GroupingEntity * ge,const Field & field,int in_out)433 void DatabaseIO::verify_and_log(const GroupingEntity *ge, const Field &field, int in_out) const 434 { 435 if (ge != nullptr) { 436 assert(!is_parallel_consistent() || 437 internal_parallel_consistent(singleProcOnly, ge, field, in_out, util_)); 438 } 439 if (get_logging()) { 440 log_field(in_out == 1 ? ">" : "<", ge, field, singleProcOnly, util_); 441 } 442 } 443 begin_state(int state,double time)444 bool DatabaseIO::begin_state(int state, double time) 445 { 446 IOSS_FUNC_ENTER(m_); 447 if (m_timeStateInOut) { 448 m_stateStart = std::chrono::steady_clock::now(); 449 } 450 return begin_state__(state, time); 451 } end_state(int state,double time)452 bool DatabaseIO::end_state(int state, double time) 453 { 454 IOSS_FUNC_ENTER(m_); 455 bool res = end_state__(state, time); 456 if (m_timeStateInOut) { 457 auto finish = std::chrono::steady_clock::now(); 458 log_time(m_stateStart, finish, state, time, is_input(), singleProcOnly, util_); 459 } 460 return res; 461 } 462 463 // Default versions do nothing... begin_state__(int,double)464 bool DatabaseIO::begin_state__(int /* state */, double /* time */) { return true; } 465 end_state__(int,double)466 bool DatabaseIO::end_state__(int /* state */, double /* time */) { return true; } 467 handle_groups()468 void DatabaseIO::handle_groups() 469 { 470 // Set Grouping requests are specified as properties... 471 // See if the property exists and decode... 472 // There is a property for each "type": 473 // GROUP_SIDESET, GROUP_NODESET, GROUP_EDGESET, GROUP_FACESET, 474 // GROUP_ELEMSET. 475 // Within the property, the "value" consists of multiple groups separated by 476 // ":" 477 // Within the group, the names are "," separated: 478 // 479 // new_surf1,member1,member2,member3:new_surf2,mem1,mem2,mem3,mem4:new_surf3,.... 480 // 481 // Currently does not check for duplicate entity membership in a set -- 482 // union 483 // with duplicates 484 // 485 create_groups("GROUP_SIDESET", SIDESET, "side", (SideSet *)nullptr); 486 create_groups("GROUP_NODESET", NODESET, "node", (NodeSet *)nullptr); 487 create_groups("GROUP_EDGESET", EDGESET, "edge", (EdgeSet *)nullptr); 488 create_groups("GROUP_FACESET", FACESET, "face", (FaceSet *)nullptr); 489 create_groups("GROUP_ELEMSET", ELEMENTSET, "elem", (ElementSet *)nullptr); 490 } 491 492 template <typename T> create_groups(const std::string & property_name,EntityType type,const std::string & type_name,const T * set_type)493 void DatabaseIO::create_groups(const std::string &property_name, EntityType type, 494 const std::string &type_name, const T *set_type) 495 { 496 if (!properties.exists(property_name)) { 497 return; 498 } 499 500 std::string prop = properties.get(property_name).get_string(); 501 std::vector<std::string> groups = tokenize(prop, ":"); 502 for (auto &group : groups) { 503 std::vector<std::string> group_spec = tokenize(group, ","); 504 505 // group_spec should contain the name of the new group as 506 // the first location and the members of the group as subsequent 507 // locations. OK to have a single member 508 if (group_spec.size() < 2) { 509 std::ostringstream errmsg; 510 fmt::print(errmsg, 511 "ERROR: Invalid {} group specification '{}'\n" 512 " Correct syntax is 'new_group,member1,...,memberN' and there must " 513 " be at least 1 member of the group", 514 type_name, group); 515 IOSS_ERROR(errmsg); 516 } 517 518 create_group(type, type_name, group_spec, set_type); 519 } 520 } 521 522 template <typename T> create_group(EntityType,const std::string & type_name,const std::vector<std::string> & group_spec,const T *)523 void DatabaseIO::create_group(EntityType /*type*/, const std::string &type_name, 524 const std::vector<std::string> &group_spec, const T * /*set_type*/) 525 { 526 fmt::print(Ioss::WARNING(), 527 "Grouping of {0} sets is not yet implemented.\n" 528 " Skipping the creation of {0} set '{1}'\n\n", 529 type_name, group_spec[0]); 530 } 531 532 template <> create_group(EntityType type,const std::string &,const std::vector<std::string> & group_spec,const SideSet *)533 void DatabaseIO::create_group(EntityType type, const std::string & /*type_name*/, 534 const std::vector<std::string> &group_spec, 535 const SideSet * /*set_type*/) 536 { 537 // Not generalized yet... This only works for T == SideSet 538 if (type != SIDESET) { 539 return; 540 } 541 542 int64_t entity_count = 0; 543 int64_t df_count = 0; 544 545 // Create the new set... 546 auto new_set = new SideSet(this, group_spec[0]); 547 548 get_region()->add(new_set); 549 550 // Find the member SideSets... 551 for (size_t i = 1; i < group_spec.size(); i++) { 552 SideSet *set = get_region()->get_sideset(group_spec[i]); 553 if (set != nullptr) { 554 const SideBlockContainer &side_blocks = set->get_side_blocks(); 555 for (auto &sbold : side_blocks) { 556 size_t side_count = sbold->entity_count(); 557 auto sbnew = new SideBlock(this, sbold->name(), sbold->topology()->name(), 558 sbold->parent_element_topology()->name(), side_count); 559 int64_t id = sbold->get_property("id").get_int(); 560 sbnew->property_add(Property("set_offset", entity_count)); 561 sbnew->property_add(Property("set_df_offset", df_count)); 562 sbnew->property_add(Property("id", id)); 563 sbnew->property_add(Property("id", id)); 564 sbnew->property_add(Property("guid", util().generate_guid(id))); 565 566 new_set->add(sbnew); 567 568 size_t old_df_count = sbold->get_property("distribution_factor_count").get_int(); 569 if (old_df_count > 0) { 570 std::string storage = "Real["; 571 storage += std::to_string(sbnew->topology()->number_nodes()); 572 storage += "]"; 573 sbnew->field_add( 574 Field("distribution_factors", Field::REAL, storage, Field::MESH, side_count)); 575 } 576 entity_count += side_count; 577 df_count += old_df_count; 578 } 579 } 580 else { 581 fmt::print(Ioss::WARNING(), 582 "While creating the grouped surface '{}', the surface '{}' does not exist. " 583 "This surface will skipped and not added to the group.\n\n", 584 group_spec[0], group_spec[i]); 585 } 586 } 587 } 588 589 // Utility function that may be used by derived classes. Determines 590 // whether all elements in the model have the same face topology. 591 // This can be used to speed-up certain algorithms since they don't 592 // have to check each face (or group of faces) individually. set_common_side_topology()593 void DatabaseIO::set_common_side_topology() const 594 { 595 auto *new_this = const_cast<DatabaseIO *>(this); 596 597 bool first = true; 598 const ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); 599 for (auto block : element_blocks) { 600 size_t element_count = block->entity_count(); 601 602 // Check face types. 603 if (element_count > 0) { 604 if (commonSideTopology != nullptr || first) { 605 first = false; 606 ElementTopology *side_type = block->topology()->boundary_type(); 607 if (commonSideTopology == nullptr) { // First block 608 new_this->commonSideTopology = side_type; 609 } 610 if (commonSideTopology != side_type) { // Face topologies differ in mesh 611 new_this->commonSideTopology = nullptr; 612 return; 613 } 614 } 615 } 616 } 617 } 618 619 /** \brief Add multiple information records (informative strings) to the database. 620 * 621 * \param[in] info The strings to add. 622 */ add_information_records(const std::vector<std::string> & info)623 void DatabaseIO::add_information_records(const std::vector<std::string> &info) 624 { 625 informationRecords.reserve(informationRecords.size() + info.size()); 626 informationRecords.insert(informationRecords.end(), info.begin(), info.end()); 627 } 628 629 /** \brief Add an information record (an informative string) to the database. 630 * 631 * \param[in] info The string to add. 632 */ add_information_record(const std::string & info)633 void DatabaseIO::add_information_record(const std::string &info) 634 { 635 informationRecords.push_back(info); 636 } 637 638 /** \brief Add a QA record, which consists of 4 strings, to the database 639 * 640 * The 4 function parameters correspond to the 4 QA record strings. 641 * 642 * \param[in] code A descriptive code name, such as the application that modified the database. 643 * \param[in] code_qa A descriptive string, such as the version of the application that modified 644 * the database. 645 * \param[in] date A relevant date, such as the date the database was modified. 646 * \param[in] time A relevant time, such as the time the database was modified. 647 */ add_qa_record(const std::string & code,const std::string & code_qa,const std::string & date,const std::string & time)648 void DatabaseIO::add_qa_record(const std::string &code, const std::string &code_qa, 649 const std::string &date, const std::string &time) 650 { 651 qaRecords.push_back(code); 652 qaRecords.push_back(code_qa); 653 qaRecords.push_back(date); 654 qaRecords.push_back(time); 655 } 656 set_block_omissions(const std::vector<std::string> & omissions,const std::vector<std::string> & inclusions)657 void DatabaseIO::set_block_omissions(const std::vector<std::string> &omissions, 658 const std::vector<std::string> &inclusions) 659 { 660 if (!omissions.empty()) { 661 blockOmissions.assign(omissions.cbegin(), omissions.cend()); 662 Ioss::sort(blockOmissions.begin(), blockOmissions.end()); 663 } 664 if (!inclusions.empty()) { 665 blockInclusions.assign(inclusions.cbegin(), inclusions.cend()); 666 Ioss::sort(blockInclusions.begin(), blockInclusions.end()); 667 } 668 } 669 670 // Check topology of all sides (face/edges) in model... check_side_topology()671 void DatabaseIO::check_side_topology() const 672 { 673 // The following code creates the sideTopology sets which contain 674 // a list of the side topologies in this model. 675 // 676 // If sideTopology.size() > 1 --> the model has sides with mixed 677 // topology (i.e., quads and tris). 678 // 679 // If sideTopology.size() == 1 --> the model has homogeneous sides 680 // and each side is of the topology type 'sideTopology[0]' 681 // 682 // This is used in other code speed up some tests. 683 684 // Spheres and Circle have no faces/edges, so handle them special... 685 bool all_sphere = true; 686 687 if (sideTopology.empty()) { 688 // Set contains (parent_element, boundary_topology) pairs... 689 std::set<std::pair<const ElementTopology *, const ElementTopology *>> side_topo; 690 691 const ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); 692 693 for (auto &block : element_blocks) { 694 const ElementTopology *elem_type = block->topology(); 695 const ElementTopology *side_type = elem_type->boundary_type(); 696 if (side_type == nullptr) { 697 // heterogeneous sides. Iterate through... 698 int size = elem_type->number_boundaries(); 699 for (int i = 1; i <= size; i++) { 700 side_type = elem_type->boundary_type(i); 701 side_topo.insert(std::make_pair(elem_type, side_type)); 702 all_sphere = false; 703 } 704 } 705 else { 706 // homogeneous sides. 707 side_topo.insert(std::make_pair(elem_type, side_type)); 708 all_sphere = false; 709 } 710 } 711 if (all_sphere) { 712 // If we end up here, the model either contains all spheres, 713 // or there are no element blocks in the model... 714 const ElementTopology *ftopo = ElementTopology::factory("unknown"); 715 if (element_blocks.empty()) { 716 side_topo.insert(std::make_pair(ftopo, ftopo)); 717 } 718 else { 719 const ElementBlock *block = *element_blocks.cbegin(); 720 side_topo.insert(std::make_pair(block->topology(), ftopo)); 721 } 722 } 723 assert(!side_topo.empty()); 724 assert(sideTopology.empty()); 725 // Copy into the sideTopology container... 726 auto *new_this = const_cast<DatabaseIO *>(this); 727 std::copy(side_topo.cbegin(), side_topo.cend(), std::back_inserter(new_this->sideTopology)); 728 } 729 assert(!sideTopology.empty()); 730 } 731 get_block_adjacencies__(const Ioss::ElementBlock * eb,std::vector<std::string> & block_adjacency)732 void DatabaseIO::get_block_adjacencies__(const Ioss::ElementBlock *eb, 733 std::vector<std::string> &block_adjacency) const 734 { 735 if (!blockAdjacenciesCalculated) { 736 compute_block_adjacencies(); 737 } 738 739 const Ioss::ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); 740 assert(Ioss::Utils::check_block_order(element_blocks)); 741 742 // Extract the computed block adjacency information for this 743 // element block: 744 int blk_position = 0; 745 if (eb->property_exists("original_block_order")) { 746 blk_position = eb->get_property("original_block_order").get_int(); 747 } 748 else { 749 for (const auto &leb : element_blocks) { 750 if (leb == eb) { 751 break; 752 } 753 blk_position++; 754 } 755 } 756 757 int lblk_position = -1; 758 for (const auto &leb : element_blocks) { 759 if (leb->property_exists("original_block_order")) { 760 lblk_position = leb->get_property("original_block_order").get_int(); 761 } 762 else { 763 lblk_position++; 764 } 765 766 if (blk_position != lblk_position && 767 static_cast<int>(blockAdjacency[blk_position][lblk_position]) == 1) { 768 block_adjacency.push_back(leb->name()); 769 } 770 } 771 } 772 773 // common compute_block_adjacencies()774 void DatabaseIO::compute_block_adjacencies() const 775 { 776 // Add a field to each element block specifying which other element 777 // blocks the block is adjacent to (defined as sharing nodes). 778 // This is only calculated on request... 779 780 blockAdjacenciesCalculated = true; 781 782 const Ioss::ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); 783 assert(Ioss::Utils::check_block_order(element_blocks)); 784 785 if (element_blocks.size() == 1) { 786 blockAdjacency.resize(1); 787 blockAdjacency[0].resize(1); 788 blockAdjacency[0][0] = false; 789 return; 790 } 791 792 // Each processor first calculates the adjacencies on their own 793 // processor... 794 795 std::vector<int64_t> node_used(nodeCount); 796 std::vector<std::vector<int>> inv_con(nodeCount); 797 798 { 799 Ioss::SerializeIO serializeIO__(this); 800 int blk_position = -1; 801 for (Ioss::ElementBlock *eb : element_blocks) { 802 if (eb->property_exists("original_block_order")) { 803 blk_position = eb->get_property("original_block_order").get_int(); 804 } 805 else { 806 blk_position++; 807 } 808 int64_t my_element_count = eb->entity_count(); 809 if (int_byte_size_api() == 8) { 810 std::vector<int64_t> conn; 811 eb->get_field_data("connectivity_raw", conn); 812 for (auto node : conn) { 813 assert(node > 0 && node - 1 < nodeCount); 814 node_used[node - 1] = blk_position + 1; 815 } 816 } 817 else { 818 std::vector<int> conn; 819 eb->get_field_data("connectivity_raw", conn); 820 for (auto node : conn) { 821 assert(node > 0 && node - 1 < nodeCount); 822 node_used[node - 1] = blk_position + 1; 823 } 824 } 825 826 if (my_element_count > 0) { 827 for (int64_t i = 0; i < nodeCount; i++) { 828 if (node_used[i] == blk_position + 1) { 829 inv_con[i].push_back(blk_position); 830 } 831 } 832 } 833 } 834 } 835 836 #ifdef SEACAS_HAVE_MPI 837 if (isParallel) { 838 // Get contributions from other processors... 839 // Get the communication map... 840 Ioss::CommSet *css = get_region()->get_commset("commset_node"); 841 Ioss::Utils::check_non_null(css, "communication map", "commset_node", __func__); 842 std::vector<std::pair<int, int>> proc_node; 843 { 844 std::vector<int> entity_processor; 845 css->get_field_data("entity_processor", entity_processor); 846 proc_node.reserve(entity_processor.size() / 2); 847 for (size_t i = 0; i < entity_processor.size(); i += 2) { 848 proc_node.emplace_back(entity_processor[i + 1], entity_processor[i]); 849 } 850 } 851 852 // Now sort by increasing processor number. 853 Ioss::sort(proc_node.begin(), proc_node.end()); 854 855 // Pack the data: global_node_id, bits for each block, ... 856 // Use 'int' as basic type... 857 size_t id_size = 1; 858 size_t word_size = sizeof(int) * 8; 859 size_t bits_size = (element_blocks.size() + word_size - 1) / word_size; 860 std::vector<unsigned> send(proc_node.size() * (id_size + bits_size)); 861 std::vector<unsigned> recv(proc_node.size() * (id_size + bits_size)); 862 863 std::vector<int> procs(util().parallel_size()); 864 size_t offset = 0; 865 for (const auto &pn : proc_node) { 866 int64_t glob_id = pn.second; 867 int proc = pn.first; 868 procs[proc]++; 869 send[offset++] = glob_id; 870 int64_t loc_id = nodeMap.global_to_local(glob_id, true) - 1; 871 for (int jblk : inv_con[loc_id]) { 872 size_t wrd_off = jblk / word_size; 873 size_t bit = jblk % word_size; 874 send[offset + wrd_off] |= (1 << bit); 875 } 876 offset += bits_size; 877 } 878 879 // Count nonzero entries in 'procs' array -- count of 880 // sends/receives 881 size_t non_zero = util().parallel_size() - std::count(procs.begin(), procs.end(), 0); 882 883 // Post all receives... 884 MPI_Request request_null = MPI_REQUEST_NULL; 885 std::vector<MPI_Request> request(non_zero, request_null); 886 std::vector<MPI_Status> status(non_zero); 887 888 int result = MPI_SUCCESS; 889 size_t req_cnt = 0; 890 offset = 0; 891 for (int i = 0; result == MPI_SUCCESS && i < util().parallel_size(); i++) { 892 if (procs[i] > 0) { 893 const unsigned size = procs[i] * (id_size + bits_size); 894 void *const recv_buf = &recv[offset]; 895 result = MPI_Irecv(recv_buf, size, MPI_INT, i, 10101, util().communicator(), 896 &request[req_cnt]); 897 req_cnt++; 898 offset += size; 899 } 900 } 901 assert(result != MPI_SUCCESS || non_zero == req_cnt); 902 903 if (result != MPI_SUCCESS) { 904 std::ostringstream errmsg; 905 fmt::print(errmsg, "ERROR: MPI_Irecv error on processor {} in {}", util().parallel_rank(), 906 __func__); 907 IOSS_ERROR(errmsg); 908 } 909 910 int local_error = (MPI_SUCCESS == result) ? 0 : 1; 911 int global_error = util().global_minmax(local_error, Ioss::ParallelUtils::DO_MAX); 912 913 if (global_error != 0) { 914 std::ostringstream errmsg; 915 fmt::print(errmsg, "ERROR: MPI_Irecv error on some processor in {}", __func__); 916 IOSS_ERROR(errmsg); 917 } 918 919 result = MPI_SUCCESS; 920 req_cnt = 0; 921 offset = 0; 922 for (int i = 0; result == MPI_SUCCESS && i < util().parallel_size(); i++) { 923 if (procs[i] > 0) { 924 const unsigned size = procs[i] * (id_size + bits_size); 925 void *const send_buf = &send[offset]; 926 result = MPI_Rsend(send_buf, size, MPI_INT, i, 10101, util().communicator()); 927 req_cnt++; 928 offset += size; 929 } 930 } 931 assert(result != MPI_SUCCESS || non_zero == req_cnt); 932 933 if (result != MPI_SUCCESS) { 934 std::ostringstream errmsg; 935 fmt::print(errmsg, "ERROR: MPI_Rsend error on processor {} in {}", util().parallel_rank(), 936 __func__); 937 IOSS_ERROR(errmsg); 938 } 939 940 local_error = (MPI_SUCCESS == result) ? 0 : 1; 941 global_error = util().global_minmax(local_error, Ioss::ParallelUtils::DO_MAX); 942 943 if (global_error != 0) { 944 std::ostringstream errmsg; 945 fmt::print(errmsg, "ERROR: MPI_Rsend error on some processor in {}", __func__); 946 IOSS_ERROR(errmsg); 947 } 948 949 result = MPI_Waitall(req_cnt, request.data(), status.data()); 950 951 if (result != MPI_SUCCESS) { 952 std::ostringstream errmsg; 953 fmt::print(errmsg, "ERROR: MPI_Waitall error on processor {} in {}", util().parallel_rank(), 954 __func__); 955 IOSS_ERROR(errmsg); 956 } 957 958 // Unpack the data and update the inv_con arrays for boundary 959 // nodes... 960 offset = 0; 961 for (size_t i = 0; i < proc_node.size(); i++) { 962 int64_t glob_id = recv[offset++]; 963 int64_t loc_id = nodeMap.global_to_local(glob_id, true) - 1; 964 for (size_t iblk = 0; iblk < element_blocks.size(); iblk++) { 965 size_t wrd_off = iblk / word_size; 966 size_t bit = iblk % word_size; 967 if (recv[offset + wrd_off] & (1 << bit)) { 968 inv_con[loc_id].push_back(iblk); // May result in duplicates, but that is OK. 969 } 970 } 971 offset += bits_size; 972 } 973 } 974 #endif 975 976 // Convert from inv_con arrays to block adjacency... 977 blockAdjacency.resize(element_blocks.size()); 978 for (auto &block : blockAdjacency) { 979 block.resize(element_blocks.size()); 980 } 981 982 for (int64_t i = 0; i < nodeCount; i++) { 983 for (size_t j = 0; j < inv_con[i].size(); j++) { 984 int jblk = inv_con[i][j]; 985 for (size_t k = j + 1; k < inv_con[i].size(); k++) { 986 int kblk = inv_con[i][k]; 987 blockAdjacency[jblk][kblk] = true; 988 blockAdjacency[kblk][jblk] = true; 989 } 990 } 991 } 992 993 #ifdef SEACAS_HAVE_MPI 994 if (isParallel) { 995 // Sync across all processors... 996 size_t word_size = sizeof(int) * 8; 997 size_t bits_size = (element_blocks.size() + word_size - 1) / word_size; 998 999 std::vector<unsigned> data(element_blocks.size() * bits_size); 1000 int64_t offset = 0; 1001 for (size_t jblk = 0; jblk < element_blocks.size(); jblk++) { 1002 for (size_t iblk = 0; iblk < element_blocks.size(); iblk++) { 1003 if (blockAdjacency[jblk][iblk] == 1) { 1004 size_t wrd_off = iblk / word_size; 1005 size_t bit = iblk % word_size; 1006 data[offset + wrd_off] |= (1 << bit); 1007 } 1008 } 1009 offset += bits_size; 1010 } 1011 1012 std::vector<unsigned> out_data(element_blocks.size() * bits_size); 1013 MPI_Allreduce((void *)data.data(), out_data.data(), static_cast<int>(data.size()), 1014 MPI_UNSIGNED, MPI_BOR, util().communicator()); 1015 1016 offset = 0; 1017 for (size_t jblk = 0; jblk < element_blocks.size(); jblk++) { 1018 for (size_t iblk = 0; iblk < element_blocks.size(); iblk++) { 1019 if (blockAdjacency[jblk][iblk] == 0) { 1020 size_t wrd_off = iblk / word_size; 1021 size_t bit = iblk % word_size; 1022 if (out_data[offset + wrd_off] & (1 << bit)) { 1023 blockAdjacency[jblk][iblk] = 1; 1024 } 1025 } 1026 } 1027 offset += bits_size; 1028 } 1029 } 1030 #endif 1031 1032 // Make it symmetric... (TODO: this probably isn't needed...) 1033 for (size_t iblk = 0; iblk < element_blocks.size(); iblk++) { 1034 for (size_t jblk = iblk; jblk < element_blocks.size(); jblk++) { 1035 blockAdjacency[jblk][iblk] = blockAdjacency[iblk][jblk]; 1036 } 1037 } 1038 } 1039 get_bounding_box(const Ioss::ElementBlock * eb)1040 AxisAlignedBoundingBox DatabaseIO::get_bounding_box(const Ioss::ElementBlock *eb) const 1041 { 1042 if (elementBlockBoundingBoxes.empty()) { 1043 // Calculate the bounding boxes for all element blocks... 1044 std::vector<double> coordinates; 1045 Ioss::NodeBlock * nb = get_region()->get_node_blocks()[0]; 1046 nb->get_field_data("mesh_model_coordinates", coordinates); 1047 ssize_t nnode = nb->entity_count(); 1048 ssize_t ndim = nb->get_property("component_degree").get_int(); 1049 1050 const Ioss::ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); 1051 size_t nblock = element_blocks.size(); 1052 std::vector<double> minmax; 1053 minmax.reserve(6 * nblock); 1054 1055 for (auto &block : element_blocks) { 1056 double xmin, ymin, zmin, xmax, ymax, zmax; 1057 if (block->get_database()->int_byte_size_api() == 8) { 1058 std::vector<int64_t> connectivity; 1059 block->get_field_data("connectivity_raw", connectivity); 1060 calc_bounding_box(ndim, nnode, coordinates, connectivity, xmin, ymin, zmin, xmax, ymax, 1061 zmax); 1062 } 1063 else { 1064 std::vector<int> connectivity; 1065 block->get_field_data("connectivity_raw", connectivity); 1066 calc_bounding_box(ndim, nnode, coordinates, connectivity, xmin, ymin, zmin, xmax, ymax, 1067 zmax); 1068 } 1069 1070 minmax.push_back(xmin); 1071 minmax.push_back(ymin); 1072 minmax.push_back(zmin); 1073 minmax.push_back(-xmax); 1074 minmax.push_back(-ymax); 1075 minmax.push_back(-zmax); 1076 } 1077 1078 util().global_array_minmax(minmax, Ioss::ParallelUtils::DO_MIN); 1079 1080 for (size_t i = 0; i < element_blocks.size(); i++) { 1081 Ioss::ElementBlock * block = element_blocks[i]; 1082 const std::string & name = block->name(); 1083 AxisAlignedBoundingBox bbox(minmax[6 * i + 0], minmax[6 * i + 1], minmax[6 * i + 2], 1084 -minmax[6 * i + 3], -minmax[6 * i + 4], -minmax[6 * i + 5]); 1085 elementBlockBoundingBoxes[name] = bbox; 1086 } 1087 } 1088 return elementBlockBoundingBoxes[eb->name()]; 1089 } 1090 get_bounding_box(const Ioss::StructuredBlock * sb)1091 AxisAlignedBoundingBox DatabaseIO::get_bounding_box(const Ioss::StructuredBlock *sb) const 1092 { 1093 ssize_t ndim = sb->get_property("component_degree").get_int(); 1094 1095 std::pair<double, double> xx; 1096 std::pair<double, double> yy; 1097 std::pair<double, double> zz; 1098 1099 std::vector<double> coordinates; 1100 sb->get_field_data("mesh_model_coordinates_x", coordinates); 1101 auto x = std::minmax_element(coordinates.cbegin(), coordinates.cend()); 1102 xx = std::make_pair(*(x.first), *(x.second)); 1103 1104 if (ndim > 1) { 1105 sb->get_field_data("mesh_model_coordinates_y", coordinates); 1106 auto y = std::minmax_element(coordinates.cbegin(), coordinates.cend()); 1107 yy = std::make_pair(*(y.first), *(y.second)); 1108 } 1109 1110 if (ndim > 2) { 1111 sb->get_field_data("mesh_model_coordinates_z", coordinates); 1112 auto z = std::minmax_element(coordinates.cbegin(), coordinates.cend()); 1113 zz = std::make_pair(*(z.first), *(z.second)); 1114 } 1115 1116 return {xx.first, yy.first, zz.first, xx.second, yy.second, zz.second}; 1117 } 1118 } // namespace Ioss 1119 1120 namespace { log_time(std::chrono::time_point<std::chrono::steady_clock> & start,std::chrono::time_point<std::chrono::steady_clock> & finish,int current_state,double state_time,bool is_input,bool single_proc_only,const Ioss::ParallelUtils & util)1121 void log_time(std::chrono::time_point<std::chrono::steady_clock> &start, 1122 std::chrono::time_point<std::chrono::steady_clock> &finish, int current_state, 1123 double state_time, bool is_input, bool single_proc_only, 1124 const Ioss::ParallelUtils &util) 1125 { 1126 std::vector<double> all_times; 1127 double duration = std::chrono::duration<double, std::milli>(finish - start).count(); 1128 if (single_proc_only) { 1129 all_times.push_back(duration); 1130 } 1131 else { 1132 util.gather(duration, all_times); 1133 } 1134 1135 if (util.parallel_rank() == 0 || single_proc_only) { 1136 std::ostringstream strm; 1137 fmt::print(strm, "\nIOSS: Time to {} state {}, time {} is ", (is_input ? "read " : "write"), 1138 current_state, state_time); 1139 1140 double total = 0.0; 1141 for (auto &p_time : all_times) { 1142 total += p_time; 1143 } 1144 1145 // Now append each processors time onto the stream... 1146 if (util.parallel_size() == 1) { 1147 fmt::print(strm, "{} (ms)\n", total); 1148 } 1149 else if (util.parallel_size() > 4) { 1150 Ioss::sort(all_times.begin(), all_times.end()); 1151 fmt::print(strm, " Min: {}\tMax: {}\tMed: {}", all_times.front(), all_times.back(), 1152 all_times[all_times.size() / 2]); 1153 } 1154 else { 1155 char sep = (util.parallel_size() > 1) ? ':' : ' '; 1156 for (auto &p_time : all_times) { 1157 fmt::print(strm, "{:8d}{}", p_time, sep); 1158 } 1159 } 1160 if (util.parallel_size() > 1) { 1161 fmt::print(strm, "\tTot: {} (ms)\n", total); 1162 } 1163 fmt::print(Ioss::DEBUG(), "{}", strm.str()); 1164 } 1165 } 1166 log_field(const char * symbol,const Ioss::GroupingEntity * entity,const Ioss::Field & field,bool single_proc_only,const Ioss::ParallelUtils & util)1167 void log_field(const char *symbol, const Ioss::GroupingEntity *entity, const Ioss::Field &field, 1168 bool single_proc_only, const Ioss::ParallelUtils &util) 1169 { 1170 if (entity != nullptr) { 1171 std::vector<int64_t> all_sizes; 1172 if (single_proc_only) { 1173 all_sizes.push_back(field.get_size()); 1174 } 1175 else { 1176 util.gather(static_cast<int64_t>(field.get_size()), all_sizes); 1177 } 1178 1179 if (util.parallel_rank() == 0 || single_proc_only) { 1180 const std::string & name = entity->name(); 1181 std::ostringstream strm; 1182 auto now = std::chrono::steady_clock::now(); 1183 std::chrono::duration<double> diff = now - initial_time; 1184 fmt::print(strm, "{} [{:.3f}]\t", symbol, diff.count()); 1185 1186 int64_t total = 0; 1187 for (auto &p_size : all_sizes) { 1188 total += p_size; 1189 } 1190 // Now append each processors size onto the stream... 1191 if (util.parallel_size() > 4) { 1192 auto min_max = std::minmax_element(all_sizes.cbegin(), all_sizes.cend()); 1193 fmt::print(strm, " m: {:8d} M: {:8d} A: {:8d}", *min_max.first, *min_max.second, 1194 total / all_sizes.size()); 1195 } 1196 else { 1197 for (auto &p_size : all_sizes) { 1198 fmt::print(strm, "{:8d}:", p_size); 1199 } 1200 } 1201 if (util.parallel_size() > 1) { 1202 fmt::print(strm, " T:{:8d}", total); 1203 } 1204 fmt::print(strm, "\t{}/{}\n", name, field.get_name()); 1205 fmt::print(Ioss::DEBUG(), "{}", strm.str()); 1206 } 1207 } 1208 else { 1209 if (!single_proc_only) { 1210 util.barrier(); 1211 } 1212 if (util.parallel_rank() == 0 || single_proc_only) { 1213 auto time_now = std::chrono::steady_clock::now(); 1214 std::chrono::duration<double> diff = time_now - initial_time; 1215 fmt::print("{} [{:.3f}]\n", symbol, diff.count()); 1216 } 1217 } 1218 } 1219 } // namespace 1220