// Copyright(C) 1999-2022 National Technology & Engineering Solutions // of Sandia, LLC (NTESS). Under the terms of Contract DE-NA0003525 with // NTESS, the U.S. Government retains certain rights in this software. // // See packages/seacas/LICENSE for details #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vtk_fmt.h" #include VTK_FMT(fmt/ostream.h) #include #include #include #include #include #include #include #include #include "Ioss_Assembly.h" #include "Ioss_Blob.h" #include "Ioss_CommSet.h" #include "Ioss_CoordinateFrame.h" #include "Ioss_DBUsage.h" #include "Ioss_DatabaseIO.h" #include "Ioss_EdgeBlock.h" #include "Ioss_EdgeSet.h" #include "Ioss_ElementBlock.h" #include "Ioss_ElementSet.h" #include "Ioss_EntityBlock.h" #include "Ioss_EntitySet.h" #include "Ioss_EntityType.h" #include "Ioss_FaceBlock.h" #include "Ioss_FaceSet.h" #include "Ioss_Field.h" #include "Ioss_GroupingEntity.h" #include "Ioss_Map.h" #include "Ioss_NodeBlock.h" #include "Ioss_NodeSet.h" #include "Ioss_Property.h" #include "Ioss_Region.h" #include "Ioss_SideBlock.h" #include "Ioss_SideSet.h" #include "Ioss_SmartAssert.h" #include "Ioss_State.h" #include "Ioss_VariableType.h" // Transitioning from treating global variables as Ioss::Field::TRANSIENT // to Ioss::Field::REDUCTION. To get the old behavior, define the value // below to '1'. #define GLOBALS_ARE_TRANSIENT 0 // ======================================================================== // Static internal helper functions // ======================================================================== namespace { static bool sixty_four_bit_message_output = false; std::vector exodus_types({EX_GLOBAL, EX_BLOB, EX_ASSEMBLY, EX_NODE_BLOCK, EX_EDGE_BLOCK, EX_FACE_BLOCK, EX_ELEM_BLOCK, EX_NODE_SET, EX_EDGE_SET, EX_FACE_SET, EX_ELEM_SET, EX_SIDE_SET}); const size_t max_line_length = MAX_LINE_LENGTH; const char *complex_suffix[] = {".re", ".im"}; void check_variable_consistency(const ex_var_params &exo_params, int my_processor, const std::string &filename, const Ioss::ParallelUtils &util); void check_attribute_index_order(Ioss::GroupingEntity *block); template void write_attribute_names(int exoid, ex_entity_type type, const std::vector &entities); template void generate_block_truth_table(Ioex::VariableNameMap &variables, Ioss::IntVector &truth_table, std::vector &blocks, char field_suffix_separator); void insert_sort_and_unique(const std::vector &src, std::vector &dest); class AssemblyTreeFilter { public: AssemblyTreeFilter() = delete; AssemblyTreeFilter(const AssemblyTreeFilter &) = delete; AssemblyTreeFilter(Ioss::Region *region, const Ioss::EntityType filterType, const std::vector &assemblies) : m_region(region), m_type(filterType), m_assemblies(assemblies), m_visitedAssemblies(assemblies.size(), false) { } void update_list_from_assembly_tree(size_t assemblyIndex, std::vector &list) { // Walk the tree without cyclic dependency if (assemblyIndex < m_assemblies.size()) { if (m_visitedAssemblies[assemblyIndex] == false) { m_visitedAssemblies[assemblyIndex] = true; const auto &assembly = m_assemblies[assemblyIndex]; const Ioss::EntityType assemblyType = Ioex::map_exodus_type(assembly.type); if (m_type == assemblyType) { for (int j = 0; j < assembly.entity_count; j++) { Ioss::GroupingEntity *ge = m_region->get_entity(assembly.entity_list[j], m_type); if (nullptr != ge) { list.push_back(ge->name()); } } } if (Ioss::ASSEMBLY == assemblyType) { for (int i = 0; i < assembly.entity_count; i++) { // Find the sub assembly with the same id int64_t subAssemblyId = assembly.entity_list[i]; bool found = false; for (size_t j = 0; j < m_assemblies.size(); j++) { if (m_assemblies[j].id == subAssemblyId) { found = true; update_list_from_assembly_tree(j, list); break; } } if(!found) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Could not find sub-assembly with id: {} and name: {}", assembly.id, assembly.name); IOSS_ERROR(errmsg); } } } } } } void update_assembly_filter_list(std::vector &assemblyFilterList) { for (size_t i = 0; i < m_assemblies.size(); ++i) { if (m_visitedAssemblies[i]) { assemblyFilterList.push_back(m_assemblies[i].name); } } std::sort(assemblyFilterList.begin(), assemblyFilterList.end(), std::less()); auto endIter = std::unique(assemblyFilterList.begin(), assemblyFilterList.end()); assemblyFilterList.resize(endIter - assemblyFilterList.begin()); } private: Ioss::Region *m_region = nullptr; Ioss::EntityType m_type = Ioss::INVALID_TYPE; const std::vector &m_assemblies; mutable std::vector m_visitedAssemblies; }; } // namespace namespace Ioex { BaseDatabaseIO::BaseDatabaseIO(Ioss::Region *region, const std::string &filename, Ioss::DatabaseUsage db_usage, Ioss_MPI_Comm communicator, const Ioss::PropertyManager &props) : Ioss::DatabaseIO(region, filename, db_usage, communicator, props) { m_groupCount[EX_GLOBAL] = 1; // To make some common code work more cleanly. m_groupCount[EX_NODE_BLOCK] = 1; // To make some common code work more cleanly. // A history file is only written on processor 0... if (db_usage == Ioss::WRITE_HISTORY) { isParallel = false; } timeLastFlush = time(nullptr); dbState = Ioss::STATE_UNKNOWN; // Set exodusII warning level. if (util().get_environment("EX_DEBUG", isParallel)) { fmt::print( Ioss::DEBUG(), "IOEX: Setting EX_VERBOSE|EX_DEBUG because EX_DEBUG environment variable is set.\n"); ex_opts(EX_VERBOSE | EX_DEBUG); } if (!is_input()) { if (util().get_environment("EX_MODE", exodusMode, isParallel)) { fmt::print( Ioss::OUTPUT(), "IOEX: Exodus create mode set to {} from value of EX_MODE environment variable.\n", exodusMode); } if (util().get_environment("EX_MINIMIZE_OPEN_FILES", isParallel)) { fmt::print(Ioss::OUTPUT(), "IOEX: Minimizing open files because EX_MINIMIZE_OPEN_FILES environment " "variable is set.\n"); minimizeOpenFiles = true; } else { Ioss::Utils::check_set_bool_property(properties, "MINIMIZE_OPEN_FILES", minimizeOpenFiles); } { bool file_per_state = false; Ioss::Utils::check_set_bool_property(properties, "FILE_PER_STATE", file_per_state); if (file_per_state) { set_file_per_state(true); } } } // See if there are any properties that need to (or can) be // handled prior to opening/creating database... bool compress = ((properties.exists("COMPRESSION_LEVEL") && properties.get("COMPRESSION_LEVEL").get_int() > 0) || (properties.exists("COMPRESSION_SHUFFLE") && properties.get("COMPRESSION_SHUFFLE").get_int() > 0)); if (compress) { exodusMode |= EX_NETCDF4; } if (properties.exists("FILE_TYPE")) { std::string type = properties.get("FILE_TYPE").get_string(); if (type == "netcdf4" || type == "netcdf-4" || type == "hdf5") { exodusMode |= EX_NETCDF4; } else if (type == "netcdf5" || type == "netcdf-5" || type == "cdf5") { exodusMode |= EX_64BIT_DATA; } } if (properties.exists("ENABLE_FILE_GROUPS")) { exodusMode |= EX_NETCDF4; exodusMode |= EX_NOCLASSIC; } if (properties.exists("MAXIMUM_NAME_LENGTH")) { maximumNameLength = properties.get("MAXIMUM_NAME_LENGTH").get_int(); } if (properties.exists("REAL_SIZE_DB")) { int rsize = properties.get("REAL_SIZE_DB").get_int(); if (rsize == 4) { dbRealWordSize = 4; // Only used for file create... } } if (properties.exists("INTEGER_SIZE_DB")) { int isize = properties.get("INTEGER_SIZE_DB").get_int(); if (isize == 8) { exodusMode |= EX_ALL_INT64_DB; } } if (properties.exists("INTEGER_SIZE_API")) { int isize = properties.get("INTEGER_SIZE_API").get_int(); if (isize == 8) { set_int_byte_size_api(Ioss::USE_INT64_API); } } if (!is_input()) { if (properties.exists("FLUSH_INTERVAL")) { int interval = properties.get("FLUSH_INTERVAL").get_int(); flushInterval = interval; } } // Don't open output files until they are actually going to be // written to. This is needed for proper support of the topology // files and auto restart so we don't overwrite a file with data we // need to save... } void BaseDatabaseIO::set_int_byte_size_api(Ioss::DataSize size) const { if (m_exodusFilePtr > 0) { int old_status = ex_int64_status(get_file_pointer()); if (size == 8) { ex_set_int64_status(get_file_pointer(), EX_ALL_INT64_API | old_status); } else { // Need to clear EX_ALL_INT64_API if set... if ((old_status & EX_ALL_INT64_API) != 0) { old_status &= ~EX_ALL_INT64_API; assert(!(old_status & EX_ALL_INT64_API)); ex_set_int64_status(m_exodusFilePtr, old_status); } } } else { if (size == 8) { exodusMode |= EX_ALL_INT64_API; } else { exodusMode &= ~EX_ALL_INT64_API; } } dbIntSizeAPI = size; // mutable } // Returns byte size of integers stored on the database... int BaseDatabaseIO::int_byte_size_db() const { int status = ex_int64_status(get_file_pointer()); if (status & EX_MAPS_INT64_DB || status & EX_IDS_INT64_DB || status & EX_BULK_INT64_DB) { return 8; } else { return 4; } } // common BaseDatabaseIO::~BaseDatabaseIO() { try { free_file_pointer(); } catch (...) { } } // common unsigned BaseDatabaseIO::entity_field_support() const { return Ioss::NODEBLOCK | Ioss::EDGEBLOCK | Ioss::FACEBLOCK | Ioss::ELEMENTBLOCK | Ioss::NODESET | Ioss::EDGESET | Ioss::FACESET | Ioss::ELEMENTSET | Ioss::SIDESET | Ioss::SIDEBLOCK | Ioss::REGION | Ioss::SUPERELEMENT; } // common int BaseDatabaseIO::get_file_pointer() const { // Returns the file_pointer used to access the file on disk. // Checks that the file is open and if not, opens it first. if (m_exodusFilePtr < 0) { bool write_message = true; bool abort_if_error = true; if (is_input()) { open_input_file(write_message, nullptr, nullptr, abort_if_error); } else { bool overwrite = true; handle_output_file(write_message, nullptr, nullptr, overwrite, abort_if_error); } if (!m_groupName.empty()) { ex_get_group_id(m_exodusFilePtr, m_groupName.c_str(), &m_exodusFilePtr); } } assert(m_exodusFilePtr >= 0); fileExists = true; return m_exodusFilePtr; } int BaseDatabaseIO::free_file_pointer() const { if (m_exodusFilePtr != -1) { bool do_timer = false; if (isParallel) { Ioss::Utils::check_set_bool_property(properties, "IOSS_TIME_FILE_OPEN_CLOSE", do_timer); } double t_begin = (do_timer ? Ioss::Utils::timer() : 0); ex_close(m_exodusFilePtr); closeDW(); if (do_timer && isParallel) { double t_end = Ioss::Utils::timer(); double duration = util().global_minmax(t_end - t_begin, Ioss::ParallelUtils::DO_MAX); if (myProcessor == 0) { fmt::print(Ioss::DEBUG(), "File Close Time = {}\n", duration); } } } m_exodusFilePtr = -1; return m_exodusFilePtr; } bool BaseDatabaseIO::ok__(bool write_message, std::string *error_message, int *bad_count) const { // For input, we try to open the existing file. // For output, we do not want to overwrite or clobber the output // file if it already exists since the app might be reading the restart // data from this file and then later clobbering it and then writing // restart data to the same file. So, for output, we first check // whether the file exists and if it it and is writable, assume // that we can later create a new or append to existing file. // Returns the number of processors on which this file is *NOT* ok in 'bad_count' if not null. // Will return 'true' only if file ok on all processors. if (fileExists) { // File has already been opened at least once... return dbState != Ioss::STATE_INVALID; } bool abort_if_error = false; bool is_ok; if (is_input()) { is_ok = open_input_file(write_message, error_message, bad_count, abort_if_error); } else { // See if file exists... Don't overwrite (yet) it it exists. bool overwrite = false; is_ok = handle_output_file(write_message, error_message, bad_count, overwrite, abort_if_error); // Close all open files... if (m_exodusFilePtr >= 0) { ex_close(m_exodusFilePtr); m_exodusFilePtr = -1; } } return is_ok; } void BaseDatabaseIO::finalize_file_open() const { assert(m_exodusFilePtr >= 0); // Check byte-size of integers stored on the database... if ((ex_int64_status(m_exodusFilePtr) & EX_ALL_INT64_DB) != 0) { if (myProcessor == 0 && !sixty_four_bit_message_output) { fmt::print(Ioss::DEBUG(), "IOSS: Input database contains 8-byte integers. Setting Ioss to use " "8-byte integers.\n"); sixty_four_bit_message_output = true; } ex_set_int64_status(m_exodusFilePtr, EX_ALL_INT64_API); set_int_byte_size_api(Ioss::USE_INT64_API); } // Check for maximum name length used on the input file. int max_name_length = ex_inquire_int(m_exodusFilePtr, EX_INQ_DB_MAX_USED_NAME_LENGTH); if (max_name_length > maximumNameLength) { maximumNameLength = max_name_length; } ex_set_max_name_length(m_exodusFilePtr, maximumNameLength); } bool BaseDatabaseIO::open_group__(const std::string &group_name) { // Get existing file pointer... bool success = false; int exoid = get_file_pointer(); m_groupName = group_name; ex_get_group_id(exoid, m_groupName.c_str(), &m_exodusFilePtr); if (m_exodusFilePtr < 0) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Could not open group named '{}' in file '{}'.\n", m_groupName, get_filename()); IOSS_ERROR(errmsg); } success = true; return success; } bool BaseDatabaseIO::create_subgroup__(const std::string &group_name) { bool success = false; if (!is_input()) { // Get existing file pointer... int exoid = get_file_pointer(); // Check name for '/' which is not allowed since it is the // separator character in a full group path if (group_name.find('/') != std::string::npos) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Invalid group name '{}' contains a '/' which is not allowed.\n", m_groupName); IOSS_ERROR(errmsg); } m_groupName = group_name; exoid = ex_create_group(exoid, m_groupName.c_str()); if (exoid < 0) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Could not create group named '{}' in file '{}'.\n", m_groupName, get_filename()); IOSS_ERROR(errmsg); } m_exodusFilePtr = exoid; success = true; } return success; } // common void BaseDatabaseIO::put_qa() { struct qa_element { char *qa_record[1][4]; }; size_t num_qa_records = qaRecords.size() / 4; auto *qa = new qa_element[num_qa_records + 1]; for (size_t i = 0; i < num_qa_records + 1; i++) { for (int j = 0; j < 4; j++) { qa[i].qa_record[0][j] = new char[MAX_STR_LENGTH + 1]; } } { int j = 0; for (size_t i = 0; i < num_qa_records; i++) { Ioss::Utils::copy_string(qa[i].qa_record[0][0], qaRecords[j++], MAX_STR_LENGTH + 1); Ioss::Utils::copy_string(qa[i].qa_record[0][1], qaRecords[j++], MAX_STR_LENGTH + 1); Ioss::Utils::copy_string(qa[i].qa_record[0][2], qaRecords[j++], MAX_STR_LENGTH + 1); Ioss::Utils::copy_string(qa[i].qa_record[0][3], qaRecords[j++], MAX_STR_LENGTH + 1); } } Ioss::Utils::time_and_date(qa[num_qa_records].qa_record[0][3], qa[num_qa_records].qa_record[0][2], MAX_STR_LENGTH); std::string codename = "unknown"; std::string version = "unknown"; if (get_region()->property_exists("code_name")) { codename = get_region()->get_property("code_name").get_string(); } if (get_region()->property_exists("code_version")) { version = get_region()->get_property("code_version").get_string(); } Ioss::Utils::copy_string(qa[num_qa_records].qa_record[0][0], codename, MAX_STR_LENGTH + 1); Ioss::Utils::copy_string(qa[num_qa_records].qa_record[0][1], version, MAX_STR_LENGTH + 1); int ierr = ex_put_qa(get_file_pointer(), num_qa_records + 1, qa[0].qa_record); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } for (size_t i = 0; i < num_qa_records + 1; i++) { for (int j = 0; j < 4; j++) { delete[] qa[i].qa_record[0][j]; } } delete[] qa; } // common void BaseDatabaseIO::put_info() { // dump info records, include the product_registry // See if the input file was specified as a property on the database... std::string filename; std::vector input_lines; if (get_region()->property_exists("input_file_name")) { filename = get_region()->get_property("input_file_name").get_string(); // Determine size of input file so can embed it in info records... Ioss::Utils::input_file(filename, &input_lines, max_line_length); } // Get configuration information for IOSS library. // Split into strings and remove empty lines... std::string config = Ioss::IOFactory::show_configuration(); std::replace(std::begin(config), std::end(config), '\t', ' '); auto lines = Ioss::tokenize(config, "\n"); lines.erase(std::remove_if(lines.begin(), lines.end(), [](const std::string &line) { return line == ""; }), lines.end()); // See if the client added any "information_records" size_t info_rec_size = informationRecords.size(); size_t in_lines = input_lines.size(); size_t qa_lines = 1; // Platform info size_t config_lines = lines.size(); size_t total_lines = in_lines + qa_lines + info_rec_size + config_lines; char **info = Ioss::Utils::get_name_array( total_lines, max_line_length); // 'total_lines' pointers to char buffers int i = 0; Ioss::Utils::copy_string(info[i++], Ioss::Utils::platform_information(), max_line_length + 1); // Copy input file lines into 'info' array... for (size_t j = 0; j < input_lines.size(); j++, i++) { Ioss::Utils::copy_string(info[i], input_lines[j], max_line_length + 1); } // Copy "information_records" property data ... for (size_t j = 0; j < informationRecords.size(); j++, i++) { Ioss::Utils::copy_string(info[i], informationRecords[j], max_line_length + 1); } for (size_t j = 0; j < lines.size(); j++, i++) { Ioss::Utils::copy_string(info[i], lines[j], max_line_length + 1); } int ierr = ex_put_info(get_file_pointer(), total_lines, info); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } Ioss::Utils::delete_name_array(info, total_lines); } // common int BaseDatabaseIO::get_current_state() const { int step = get_region()->get_current_state(); if (step <= 0) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: No currently active state. The calling code must call " "Ioss::Region::begin_state(int step)\n" " to set the database timestep from which to read the transient data.\n" " [{}]\n", get_filename()); IOSS_ERROR(errmsg); } return step; } void BaseDatabaseIO::update_block_omissions_from_assemblies() { Ioss::SerializeIO serializeIO__(this); if (!assemblyOmissions.empty()) { assert(blockInclusions.empty()); } if (!assemblyInclusions.empty()) { assert(blockOmissions.empty()); } std::vector exclusions; std::vector inclusions; // Query number of assemblies... int nassem = ex_inquire_int(get_file_pointer(), EX_INQ_ASSEMBLY); if (nassem > 0) { std::vector assemblies(nassem); int max_name_length = ex_inquire_int(m_exodusFilePtr, EX_INQ_DB_MAX_USED_NAME_LENGTH); for (auto &assembly : assemblies) { assembly.name = new char[max_name_length + 1]; } int ierr = ex_get_assemblies(get_file_pointer(), assemblies.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } // Now allocate space for member list and get assemblies again... for (auto &assembly : assemblies) { assembly.entity_list = new int64_t[assembly.entity_count]; } ierr = ex_get_assemblies(get_file_pointer(), assemblies.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } AssemblyTreeFilter inclusionFilter(get_region(), Ioss::ELEMENTBLOCK, assemblies); AssemblyTreeFilter exclusionFilter(get_region(), Ioss::ELEMENTBLOCK, assemblies); for (size_t i = 0; i < assemblies.size(); ++i) { const auto &assembly = assemblies[i]; bool omitAssembly = std::binary_search(assemblyOmissions.begin(), assemblyOmissions.end(), assembly.name); bool includeAssembly = std::binary_search(assemblyInclusions.begin(), assemblyInclusions.end(), assembly.name); if (omitAssembly) { exclusionFilter.update_list_from_assembly_tree(i, exclusions); } if (includeAssembly) { inclusionFilter.update_list_from_assembly_tree(i, inclusions); } } exclusionFilter.update_assembly_filter_list(assemblyOmissions); inclusionFilter.update_assembly_filter_list(assemblyInclusions); for (auto &assembly : assemblies) { delete[] assembly.entity_list; delete[] assembly.name; } insert_sort_and_unique(exclusions, blockOmissions); insert_sort_and_unique(inclusions, blockInclusions); } } void BaseDatabaseIO::get_assemblies() { Ioss::SerializeIO serializeIO__(this); // Query number of assemblies... int nassem = ex_inquire_int(get_file_pointer(), EX_INQ_ASSEMBLY); if (nassem > 0) { std::vector assemblies(nassem); int max_name_length = ex_inquire_int(m_exodusFilePtr, EX_INQ_DB_MAX_USED_NAME_LENGTH); for (auto &assembly : assemblies) { assembly.name = new char[max_name_length + 1]; } int ierr = ex_get_assemblies(get_file_pointer(), assemblies.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } // Now allocate space for member list and get assemblies again... for (auto &assembly : assemblies) { assembly.entity_list = new int64_t[assembly.entity_count]; } ierr = ex_get_assemblies(get_file_pointer(), assemblies.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } for (const auto &assembly : assemblies) { auto *assem = new Ioss::Assembly(get_region()->get_database(), assembly.name); assem->property_add(Ioss::Property("id", assembly.id)); get_region()->add(assem); } // Now iterate again and populate member lists... for (const auto &assembly : assemblies) { Ioss::Assembly *assem = get_region()->get_assembly(assembly.name); assert(assem != nullptr); auto type = Ioex::map_exodus_type(assembly.type); size_t num_added_entities = 0; for (int j = 0; j < assembly.entity_count; j++) { auto *ge = get_region()->get_entity(assembly.entity_list[j], type); if (ge == nullptr) { std::ostringstream errmsg; fmt::print(errmsg, "Error: Failed to find entity of type {} with id {} for assembly {}.\n", type, assembly.entity_list[j], assem->name()); IOSS_ERROR(errmsg); } if (!Ioss::Utils::block_is_omitted(ge)) { assem->add(ge); num_added_entities++; } } SMART_ASSERT(assem->member_count() == num_added_entities) (assem->member_count())(num_added_entities); add_mesh_reduction_fields(EX_ASSEMBLY, assembly.id, assem); // Check for additional variables. int attribute_count = assem->get_property("attribute_count").get_int(); add_attribute_fields(EX_ASSEMBLY, assem, attribute_count, "Assembly"); add_reduction_results_fields(EX_ASSEMBLY, assem); } // If there are any reduction results fields ("REDUCTION"), then need to // allocate space for the values to be stored on each timestep... if (!m_reductionVariables[EX_ASSEMBLY].empty()) { size_t size = m_reductionVariables[EX_ASSEMBLY].size(); for (const auto &assembly : assemblies) { m_reductionValues[EX_ASSEMBLY][assembly.id].resize(size); } } for (auto &assembly : assemblies) { delete[] assembly.entity_list; delete[] assembly.name; } assert(assemblyOmissions.empty() || assemblyInclusions.empty()); // Only one can be non-empty // Handle all assembly omissions or inclusions... if (!assemblyOmissions.empty()) { for (const auto &name : assemblyOmissions) { auto assembly = get_region()->get_assembly(name); if (assembly != nullptr) { assembly->property_add(Ioss::Property(std::string("omitted"), 1)); } } } if (!assemblyInclusions.empty()) { const auto &assemblies = get_region()->get_assemblies(); for (auto &assembly : assemblies) { assembly->property_add(Ioss::Property(std::string("omitted"), 1)); } // Now, erase the property on any assemblies in the inclusion list... for (const auto &name : assemblyInclusions) { auto assembly = get_region()->get_assembly(name); if (assembly != nullptr) { assembly->property_erase("omitted"); } } } } } void BaseDatabaseIO::get_blobs() { Ioss::SerializeIO serializeIO__(this); // Query number of blobs... int nblob = ex_inquire_int(get_file_pointer(), EX_INQ_BLOB); if (nblob > 0) { std::vector blobs(nblob); int max_name_length = ex_inquire_int(m_exodusFilePtr, EX_INQ_DB_MAX_USED_NAME_LENGTH); for (auto &bl : blobs) { bl.name = new char[max_name_length + 1]; } int ierr = ex_get_blobs(get_file_pointer(), blobs.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } for (const auto &bl : blobs) { #ifdef SEACAS_HAVE_MPI // Each blob is spread across all processors (should support a minimum size...) // Determine size of blob on each rank and offset from beginning of blob. size_t per_proc = bl.num_entry / parallel_size(); size_t extra = bl.num_entry % parallel_size(); size_t count = per_proc + (myProcessor < (int)extra ? 1 : 0); size_t offset = 0; if (myProcessor < (int)extra) { offset = (per_proc + 1) * myProcessor; } else { offset = (per_proc + 1) * extra + per_proc * (myProcessor - extra); } Ioss::Blob *blob = new Ioss::Blob(get_region()->get_database(), bl.name, count); blob->property_add(Ioss::Property("_processor_offset", (int64_t)offset)); blob->property_add(Ioss::Property("global_size", (int64_t)bl.num_entry)); #else auto *blob = new Ioss::Blob(get_region()->get_database(), bl.name, bl.num_entry); #endif blob->property_add(Ioss::Property("id", bl.id)); get_region()->add(blob); } // Now iterate again and populate member lists... int iblk = 0; for (const auto &bl : blobs) { Ioss::Blob *blob = get_region()->get_blob(bl.name); assert(blob != nullptr); add_mesh_reduction_fields(EX_BLOB, bl.id, blob); // Check for additional variables. int attribute_count = blob->get_property("attribute_count").get_int(); add_attribute_fields(EX_BLOB, blob, attribute_count, "Blob"); add_reduction_results_fields(EX_BLOB, blob); add_results_fields(EX_BLOB, blob, iblk++); } // If there are any reduction results fields ("REDUCTION"), then need to // allocate space for the values to be stored on each timestep... if (!m_reductionVariables[EX_BLOB].empty()) { size_t size = m_reductionVariables[EX_BLOB].size(); for (const auto &blob : blobs) { m_reductionValues[EX_BLOB][blob.id].resize(size); } } for (auto &bl : blobs) { delete[] bl.name; } } } // common void BaseDatabaseIO::get_nodeblocks() { // For exodusII, there is only a single node block which contains // all of the nodes. // The default id assigned is '1' and the name is 'nodeblock_1' std::string block_name = "nodeblock_1"; auto block = new Ioss::NodeBlock(this, block_name, nodeCount, spatialDimension); block->property_add(Ioss::Property("id", 1)); block->property_add(Ioss::Property("guid", util().generate_guid(1))); // Check for results variables. int num_attr = 0; { Ioss::SerializeIO serializeIO__(this); int ierr = ex_get_attr_param(get_file_pointer(), EX_NODE_BLOCK, 1, &num_attr); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } add_attribute_fields(EX_NODE_BLOCK, block, num_attr, ""); // Not supported on nodeblocks at this time // add_reduction_results_fields(EX_NODE_BLOCK, block); add_results_fields(EX_NODE_BLOCK, block); // If there are any reduction results fields ("REDUCTION"), then need to // allocate space for the values to be stored on each timestep... if (!m_reductionVariables[EX_NODE_BLOCK].empty()) { size_t size = m_reductionVariables[EX_NODE_BLOCK].size(); m_reductionValues[EX_NODE_BLOCK][1].resize(size); } bool added = get_region()->add(block); if (!added) { delete block; } } // common // common size_t BaseDatabaseIO::handle_block_ids(const Ioss::EntityBlock *eb, ex_entity_type map_type, Ioss::Map &entity_map, void *ids, size_t num_to_get, size_t offset) const { /*! * NOTE: "element" is generic for "element", "face", or "edge" * * There are two modes we need to support in this routine: * 1. Initial definition of element map (local->global) and * elemMap.reverse (global->local). * 2. Redefinition of element map via 'reordering' of the original * map when the elements on this processor are the same, but their * order is changed. * * So, there will be two maps the 'elemMap.map' map is a 'direct lookup' * map which maps current local position to global id and the * 'elemMap.reverse' is an associative lookup which maps the * global id to 'original local'. There is also a * 'elemMap.reorder' which is direct lookup and maps current local * position to original local. * The ids coming in are the global ids; their position is the * local id -1 (That is, data[0] contains the global id of local * element 1 in this element block). The 'model-local' id is * given by eb_offset + 1 + position: * * int local_position = elemMap.reverse[ElementMap[i+1]] * (the elemMap.map and elemMap.reverse are 1-based) * * But, this assumes 1..numel elements are being output at the same * time; we are actually outputting a blocks worth of elements at a * time, so we need to consider the block offsets. * So... local-in-block position 'i' is index 'eb_offset+i' in * 'elemMap.map' and the 'local_position' within the element * blocks data arrays is 'local_position-eb_offset'. With this, the * position within the data array of this element block is: * * int eb_position = * elemMap.reverse[elemMap.map[eb_offset+i+1]]-eb_offset-1 * * To determine which map to update on a call to this function, we * use the following hueristics: * -- If the database state is 'Ioss::STATE_MODEL:', then update the * 'elemMap.reverse'. * -- If the database state is not Ioss::STATE_MODEL, then leave * the 'elemMap.reverse' alone since it corresponds to the * information already written to the database. [May want to add * a Ioss::STATE_REDEFINE_MODEL] * -- Always update elemMap.map to match the passed in 'ids' * array. * * NOTE: the maps are built an element block at a time... * NOTE: The mapping is done on TRANSIENT fields only; MODEL fields * should be in the original order... */ // Overwrite this portion of the 'elemMap.map', but keep other // parts as they were. We are adding elements starting at position // 'eb_offset+offset' and ending at // 'eb_offset+offset+num_to_get'. If the entire block is being // processed, this reduces to the range 'eb_offset..eb_offset+my_element_count' bool in_define = (dbState == Ioss::STATE_MODEL) || (dbState == Ioss::STATE_DEFINE_MODEL); int64_t eb_offset = eb->get_offset(); if (int_byte_size_api() == 4) { entity_map.set_map(static_cast(ids), num_to_get, eb_offset, in_define); } else { entity_map.set_map(static_cast(ids), num_to_get, eb_offset, in_define); } // Now, if the state is Ioss::STATE_MODEL, output this portion of // the entity number map... if (in_define) { if (ex_put_partial_id_map(get_file_pointer(), map_type, offset + 1, num_to_get, ids) < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } return num_to_get; } // common void BaseDatabaseIO::compute_block_membership__(Ioss::SideBlock *efblock, std::vector &block_membership) const { const Ioss::ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); assert(Ioss::Utils::check_block_order(element_blocks)); Ioss::Int64Vector block_ids(element_blocks.size()); if (block_ids.size() == 1) { block_ids[0] = 1; } else { Ioss::Int64Vector element_side; if (int_byte_size_api() == 4) { Ioss::IntVector es32; efblock->get_field_data("element_side", es32); element_side.resize(es32.size()); std::copy(es32.begin(), es32.end(), element_side.begin()); } else { efblock->get_field_data("element_side", element_side); } size_t number_sides = element_side.size() / 2; Ioss::ElementBlock *block = nullptr; for (size_t iel = 0; iel < number_sides; iel++) { int64_t elem_id = element_side[2 * iel]; // Vector contains both element and side. elem_id = elemMap.global_to_local(elem_id); if (block == nullptr || !block->contains(elem_id)) { block = get_region()->get_element_block(elem_id); assert(block != nullptr); size_t block_order = block->get_property("original_block_order").get_int(); assert(block_order < block_ids.size()); block_ids[block_order] = 1; } } } // Synchronize among all processors.... if (isParallel) { util().global_array_minmax(block_ids, Ioss::ParallelUtils::DO_MAX); } for (const auto &block : element_blocks) { size_t block_order = block->get_property("original_block_order").get_int(); assert(block_order < block_ids.size()); if (block_ids[block_order] == 1) { if (!Ioss::Utils::block_is_omitted(block)) { block_membership.push_back(block->name()); } } } } // common int64_t BaseDatabaseIO::get_field_internal(const Ioss::Region * /* region */, const Ioss::Field &field, void *data, size_t data_size) const { // For now, assume that all TRANSIENT fields on a region // are REDUCTION fields (1 value). We need to gather these // and output them all at one time. The storage location is a // 'globalVariables' array { size_t num_to_get = field.verify(data_size); Ioss::SerializeIO serializeIO__(this); Ioss::Field::RoleType role = field.get_role(); if (role == Ioss::Field::TRANSIENT || role == Ioss::Field::REDUCTION) { get_reduction_field(EX_GLOBAL, field, get_region(), data); } else { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: Can not handle non-TRANSIENT or non-REDUCTION fields on regions"); IOSS_ERROR(errmsg); } return num_to_get; } } // common int64_t BaseDatabaseIO::put_field_internal(const Ioss::Region * /* region */, const Ioss::Field &field, void *data, size_t data_size) const { // For now, assume that all TRANSIENT fields on a region // are REDUCTION fields (1 value). We need to gather these // and output them all at one time. The storage location is a // 'globalVariables' array { Ioss::SerializeIO serializeIO__(this); Ioss::Field::RoleType role = field.get_role(); size_t num_to_get = field.verify(data_size); if ((role == Ioss::Field::TRANSIENT || role == Ioss::Field::REDUCTION) && num_to_get == 1) { store_reduction_field(EX_GLOBAL, field, get_region(), data); } else if (num_to_get != 1) { // There should have been a warning/error message printed to the // log file earlier for this, so we won't print anything else // here since it would be printed for each and every timestep.... ; } else { std::ostringstream errmsg; fmt::print( errmsg, "ERROR: The variable named '{}' is of the wrong type. A region variable must be of type" " TRANSIENT or REDUCTION.\n" "This is probably an internal error; please notify gdsjaar@sandia.gov", field.get_name()); IOSS_ERROR(errmsg); } return num_to_get; } } namespace { // common template void generate_block_truth_table(const VariableNameMap &variables, Ioss::IntVector &truth_table, std::vector &blocks, char field_suffix_separator) { size_t block_count = blocks.size(); size_t var_count = variables.size(); if (var_count == 0 || block_count == 0) { return; } truth_table.resize(block_count * var_count); // Fill in the truth table. It is conceptually a two-dimensional array of // the form 'array[num_blocks][num_element_var]'. In C++, // the values for the first block are first, followed by // next block, ... size_t offset = 0; for (const auto &block : blocks) { // Get names of all transient and reduction fields... Ioss::NameList results_fields = block->field_describe(Ioss::Field::TRANSIENT); block->field_describe(Ioss::Field::REDUCTION, &results_fields); for (const auto &fn : results_fields) { Ioss::Field field = block->get_field(fn); Ioss::Field::BasicType ioss_type = field.get_type(); int re_im = 1; if (ioss_type == Ioss::Field::COMPLEX) { re_im = 2; } for (int complex_comp = 0; complex_comp < re_im; complex_comp++) { std::string field_name = field.get_name(); if (re_im == 2) { field_name += complex_suffix[complex_comp]; } for (int i = 1; i <= field.get_component_count(Ioss::Field::InOut::INPUT); i++) { std::string var_string = field.get_component_name(i, Ioss::Field::InOut::INPUT, field_suffix_separator); // Find position of 'var_string' in 'variables' auto VN = variables.find(var_string); if (VN != variables.end()) { // Index '(*VN).second' is 1-based... truth_table[offset + (*VN).second - 1] = 1; } } } } offset += var_count; } assert(offset == var_count * block_count); } } // namespace // common void BaseDatabaseIO::store_reduction_field(ex_entity_type type, const Ioss::Field &field, const Ioss::GroupingEntity *ge, void *variables) const { Ioss::Field::BasicType ioss_type = field.get_type(); assert(ioss_type == Ioss::Field::REAL || ioss_type == Ioss::Field::INTEGER || ioss_type == Ioss::Field::INT64 || ioss_type == Ioss::Field::COMPLEX); auto *rvar = static_cast(variables); auto *ivar = static_cast(variables); auto *ivar64 = static_cast(variables); auto id = ge->get_optional_property("id", 0); // Note that if the field's basic type is COMPLEX, then each component of // the VariableType is a complex variable consisting of a real and // imaginary part. Since exodus cannot handle complex variables, // we have to output a (real and imaginary) X (number of // components) fields. For example, if V is a 3d vector of complex // data, the data in the 'variables' array are v_x, v.im_x, v_y, // v.im_y, v_z, v.im_z which need to be output in six separate // exodus fields. These fields were already defined in // "write_results_metadata". // get number of components, cycle through each component // and add suffix to base 'field_name'. Look up index // of this name in 'm_variables[EX_GLOBAL]' map int comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT); int var_index = 0; int re_im = 1; if (field.get_type() == Ioss::Field::COMPLEX) { re_im = 2; } for (int complex_comp = 0; complex_comp < re_im; complex_comp++) { std::string field_name = field.get_name(); if (re_im == 2) { field_name += complex_suffix[complex_comp]; } for (int i = 0; i < comp_count; i++) { std::string var_name = get_component_name(field, Ioss::Field::InOut::OUTPUT, i + 1); #if GLOBALS_ARE_TRANSIENT if (type == EX_GLOBAL) { SMART_ASSERT(m_variables[type].find(var_name) != m_variables[type].end())(type)(var_name); var_index = m_variables[type].find(var_name)->second; } else { SMART_ASSERT(m_reductionVariables[type].find(var_name) != m_reductionVariables[type].end()) (type)(var_name); var_index = m_reductionVariables[type].find(var_name)->second; } #else SMART_ASSERT(m_reductionVariables[type].find(var_name) != m_reductionVariables[type].end()) (type)(var_name); var_index = m_reductionVariables[type].find(var_name)->second; #endif SMART_ASSERT(static_cast(m_reductionValues[type][id].size()) >= var_index) (id)(m_reductionValues[type][id].size())(var_index); // Transfer from 'variables' array. if (ioss_type == Ioss::Field::REAL || ioss_type == Ioss::Field::COMPLEX) { m_reductionValues[type][id][var_index - 1] = rvar[i]; } else if (ioss_type == Ioss::Field::INTEGER) { m_reductionValues[type][id][var_index - 1] = ivar[i]; } else if (ioss_type == Ioss::Field::INT64) { m_reductionValues[type][id][var_index - 1] = ivar64[i]; // FIX 64 UNSAFE } } } } // common void BaseDatabaseIO::get_reduction_field(ex_entity_type type, const Ioss::Field &field, const Ioss::GroupingEntity *ge, void *variables) const { auto id = ge->get_optional_property("id", 0); Ioss::Field::BasicType ioss_type = field.get_type(); assert(ioss_type == Ioss::Field::REAL || ioss_type == Ioss::Field::INTEGER || ioss_type == Ioss::Field::INT64); auto *rvar = static_cast(variables); auto *ivar = static_cast(variables); auto *i64var = static_cast(variables); // get number of components, cycle through each component // and add suffix to base 'field_name'. Look up index // of this name in 'm_variables[type]' map int comp_count = field.get_component_count(Ioss::Field::InOut::INPUT); for (int i = 0; i < comp_count; i++) { int var_index = 0; std::string var_name = get_component_name(field, Ioss::Field::InOut::INPUT, i + 1); #if GLOBALS_ARE_TRANSIENT if (type == EX_GLOBAL) { assert(m_variables[type].find(var_name) != m_variables[type].end()); var_index = m_variables[type].find(var_name)->second; } else { assert(m_reductionVariables[type].find(var_name) != m_reductionVariables[type].end()); var_index = m_reductionVariables[type].find(var_name)->second; } assert(static_cast(m_reductionValues[type][id].size()) >= var_index); #else SMART_ASSERT(m_reductionVariables[type].find(var_name) != m_reductionVariables[type].end()) (type)(var_name); var_index = m_reductionVariables[type].find(var_name)->second; SMART_ASSERT(static_cast(m_reductionValues[type][id].size()) >= var_index); #endif // Transfer to 'variables' array. if (ioss_type == Ioss::Field::REAL) { rvar[i] = m_reductionValues[type][id][var_index - 1]; } else if (ioss_type == Ioss::Field::INT64) { i64var[i] = static_cast(m_reductionValues[type][id][var_index - 1]); } else if (ioss_type == Ioss::Field::INTEGER) { ivar[i] = static_cast(m_reductionValues[type][id][var_index - 1]); } } } // common void BaseDatabaseIO::write_reduction_fields() const { int step = get_current_state(); step = get_database_step(step); for (const auto &type : exodus_types) { auto &id_values = m_reductionValues[type]; for (const auto &values : id_values) { int64_t id = values.first; auto &vals = values.second; size_t count = vals.size(); if (count > 0) { int ierr = ex_put_reduction_vars(get_file_pointer(), step, type, id, count, vals.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } } } } // common void BaseDatabaseIO::read_reduction_fields() const { int step = get_current_state(); for (const auto &type : exodus_types) { auto &id_values = m_reductionValues[type]; for (const auto &values : id_values) { int64_t id = values.first; auto &vals = values.second; size_t count = vals.size(); if (count > 0) { int ierr = ex_get_reduction_vars(get_file_pointer(), step, type, id, count, (double *)vals.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } } } } // common bool BaseDatabaseIO::begin__(Ioss::State state) { dbState = state; return true; } // common bool BaseDatabaseIO::end__(Ioss::State state) { // Transitioning out of state 'state' assert(state == dbState); switch (state) { case Ioss::STATE_DEFINE_MODEL: if (!is_input()) { write_meta_data(open_create_behavior()); } break; case Ioss::STATE_DEFINE_TRANSIENT: if (!is_input()) { write_results_metadata(true, open_create_behavior()); } break; default: // ignore everything else... break; } { Ioss::SerializeIO serializeIO__(this); if (!is_input()) { ex_update(get_file_pointer()); if (minimizeOpenFiles) { free_file_pointer(); } } dbState = Ioss::STATE_UNKNOWN; } return true; } void BaseDatabaseIO::open_state_file(int state) { // Close current file... free_file_pointer(); // Update filename to append state count... decodedFilename.clear(); Ioss::FileInfo db(originalDBFilename); std::string new_filename; if (!db.pathname().empty()) { new_filename += db.pathname() + "/"; } if (get_cycle_count() >= 1) { static const std::string suffix{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"}; int index = (state - 1) % get_cycle_count(); new_filename += db.basename() + "-state-" + suffix[index] + "." + db.extension(); } else { new_filename += db.basename() + "-state-" + std::to_string(state) + "." + db.extension(); } DBFilename = new_filename; fileExists = false; ex_var_params exo_params{}; #if GLOBALS_ARE_TRANSIENT exo_params.num_glob = m_variables[EX_GLOBAL].size(); #else exo_params.num_glob = m_reductionVariables[EX_GLOBAL].size(); #endif exo_params.num_node = m_variables[EX_NODE_BLOCK].size(); exo_params.num_edge = m_variables[EX_EDGE_BLOCK].size(); exo_params.num_face = m_variables[EX_FACE_BLOCK].size(); exo_params.num_elem = m_variables[EX_ELEM_BLOCK].size(); exo_params.num_nset = m_variables[EX_NODE_SET].size(); exo_params.num_eset = m_variables[EX_EDGE_SET].size(); exo_params.num_fset = m_variables[EX_FACE_SET].size(); exo_params.num_sset = m_variables[EX_SIDE_SET].size(); exo_params.num_elset = m_variables[EX_ELEM_SET].size(); char the_title[max_line_length + 1]; // Title... if (get_region()->property_exists("title")) { std::string title_str = get_region()->get_property("title").get_string(); Ioss::Utils::copy_string(the_title, title_str); } else { Ioss::Utils::copy_string(the_title, "IOSS Default Output Title"); } Ioex::Mesh mesh(spatialDimension, the_title, util(), !usingParallelIO); mesh.populate(get_region()); // Write the metadata to the exodus file... Ioex::Internals data(get_file_pointer(), maximumNameLength, util()); int ierr = data.initialize_state_file(mesh, exo_params, originalDBFilename); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } bool BaseDatabaseIO::begin_state__(int state, double time) { Ioss::SerializeIO serializeIO__(this); time /= timeScaleFactor; if (!is_input()) { if (get_file_per_state()) { // Close current file; create new file and output transient metadata... open_state_file(state); write_results_metadata(false, open_create_behavior()); } int ierr = ex_put_time(get_file_pointer(), get_database_step(state), &time); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } // Zero global variable array... for (auto &type : exodus_types) { auto &id_values = m_reductionValues[type]; for (auto &values : id_values) { auto &vals = values.second; std::fill(vals.begin(), vals.end(), 0.0); } } } else { // Store reduction variables read_reduction_fields(); } return true; } // common bool BaseDatabaseIO::end_state__(int state, double time) { Ioss::SerializeIO serializeIO__(this); if (!is_input()) { write_reduction_fields(); time /= timeScaleFactor; finalize_write(state, time); if (minimizeOpenFiles) { free_file_pointer(); } } return true; } // common void BaseDatabaseIO::add_region_fields() { #if GLOBALS_ARE_TRANSIENT int field_count = add_results_fields(EX_GLOBAL, get_region()); #else int field_count = add_reduction_results_fields(EX_GLOBAL, get_region()); #endif m_reductionValues[EX_GLOBAL][0].resize(field_count); add_mesh_reduction_fields(EX_GLOBAL, 0, get_region()); } namespace { // Memory allocated in `ex_get_attributes`, this makes deletion cleaner... class EX_attribute : public ex_attribute { public: EX_attribute() { values = nullptr; } ~EX_attribute() { free(values); } }; } // namespace void BaseDatabaseIO::add_mesh_reduction_fields(ex_entity_type type, int64_t id, Ioss::GroupingEntity *entity) { // Get "global attributes" // These are single key-value per grouping entity // Stored as Ioss::Property with origin of ATTRIBUTE Ioss::SerializeIO serializeIO__(this); int att_count = ex_get_attribute_count(get_file_pointer(), type, id); if (att_count > 0) { std::vector attr(att_count); ex_get_attribute_param(get_file_pointer(), type, id, attr.data()); ex_get_attributes(get_file_pointer(), att_count, attr.data()); // Create a property on `entity` for each `attribute` for (const auto &att : attr) { if (att.value_count == 0) { // Just an attribute name. Give it an empty value... entity->property_add(Ioss::Property(att.name, "", Ioss::Property::ATTRIBUTE)); continue; } assert(att.values != nullptr); switch (att.type) { case EX_INTEGER: { const auto *idata = static_cast(att.values); if (att.value_count == 1) { entity->property_add(Ioss::Property(att.name, *idata, Ioss::Property::ATTRIBUTE)); } else { std::vector tmp(att.value_count); std::copy(idata, idata + att.value_count, tmp.begin()); entity->property_add(Ioss::Property(att.name, tmp, Ioss::Property::ATTRIBUTE)); } } break; case EX_DOUBLE: { const auto *ddata = static_cast(att.values); if (att.value_count == 1) { entity->property_add(Ioss::Property(att.name, *ddata, Ioss::Property::ATTRIBUTE)); } else { std::vector tmp(att.value_count); std::copy(ddata, ddata + att.value_count, tmp.begin()); entity->property_add(Ioss::Property(att.name, tmp, Ioss::Property::ATTRIBUTE)); } } break; case EX_CHAR: { const auto *cdata = static_cast(att.values); entity->property_add(Ioss::Property(att.name, cdata, Ioss::Property::ATTRIBUTE)); } break; } } } } // common int64_t BaseDatabaseIO::add_results_fields(ex_entity_type type, Ioss::GroupingEntity *entity, int64_t position) { return internal_add_results_fields(type, entity, position, m_groupCount[type], m_truthTable[type], m_variables[type]); } // common int64_t BaseDatabaseIO::internal_add_results_fields(ex_entity_type type, Ioss::GroupingEntity *entity, int64_t position, int64_t block_count, Ioss::IntVector &truth_table, Ioex::VariableNameMap &variables) { int nvar = 0; { Ioss::SerializeIO serializeIO__(this); int ierr = ex_get_variable_param(get_file_pointer(), type, &nvar); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } if (nvar > 0) { if (truth_table.empty()) { truth_table.resize(block_count * nvar); // Read and store the truth table (Should be there since we only // get to this routine if there are variables...) if (type == EX_NODE_BLOCK || type == EX_GLOBAL || type == EX_ASSEMBLY) { // These types don't have a truth table in the exodus api... // They do in Ioss just for some consistency... std::fill(truth_table.begin(), truth_table.end(), 1); } else { Ioss::SerializeIO serializeIO__(this); int ierr = ex_get_truth_table(get_file_pointer(), type, block_count, nvar, truth_table.data()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } // If parallel, then synchronize the truth table among all // processors... Need to know that block_X has variable_Y // even if block_X is empty on a specific processor... The // truth table contains 0 if the variable doesn't exist and 1 // if it does, so we just take the maximum at each location... // This is a collective call... Make sure not in Serialize if (isParallel) { util().global_array_minmax(truth_table, Ioss::ParallelUtils::DO_MAX); } } // Get the variable names and add as fields. Need to decode these // into vector/tensor/... eventually, for now store all as // scalars. char **names = Ioss::Utils::get_name_array(nvar, maximumNameLength); // Read the names... // (Currently, names are read for every block. We could save them...) { Ioss::SerializeIO serializeIO__(this); int ierr = ex_get_variable_names(get_file_pointer(), type, nvar, names); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } // Add to VariableNameMap so can determine exodusII index given a // Sierra field name. exodusII index is just 'i+1' for (int i = 0; i < nvar; i++) { if (lowerCaseVariableNames) { Ioss::Utils::fixup_name(names[i]); } variables.insert(VNMValuePair(std::string(names[i]), i + 1)); } int offset = position * nvar; int *local_truth = nullptr; if (!truth_table.empty()) { local_truth = &truth_table[offset]; } std::vector fields; int64_t count = entity->entity_count(); Ioss::Utils::get_fields(count, names, nvar, Ioss::Field::TRANSIENT, this, local_truth, fields); for (const auto &field : fields) { entity->field_add(field); } for (int i = 0; i < nvar; i++) { // Verify that all names were used for a field... assert(names[i][0] == '\0' || (local_truth && local_truth[i] == 0)); delete[] names[i]; } delete[] names; } } return nvar; } // common int64_t BaseDatabaseIO::add_reduction_results_fields(ex_entity_type type, Ioss::GroupingEntity *entity) { int nvar = 0; { Ioss::SerializeIO serializeIO__(this); int ierr = ex_get_reduction_variable_param(get_file_pointer(), type, &nvar); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } if (nvar > 0) { // Get the variable names and add as fields. Need to decode these // into vector/tensor/... eventually, for now store all as // scalars. char **names = Ioss::Utils::get_name_array(nvar, maximumNameLength); // Read the names... // (Currently, names are read for every block. We could save them...) { Ioss::SerializeIO serializeIO__(this); int ierr = ex_get_reduction_variable_names(get_file_pointer(), type, nvar, names); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } // Add to VariableNameMap so can determine exodusII index given a // Sierra field name. exodusII index is just 'i+1' auto &variables = m_reductionVariables[type]; for (int i = 0; i < nvar; i++) { if (lowerCaseVariableNames) { Ioss::Utils::fixup_name(names[i]); } variables.insert(VNMValuePair(std::string(names[i]), i + 1)); } int *local_truth = nullptr; std::vector fields; int64_t count = 1; Ioss::Utils::get_fields(count, names, nvar, Ioss::Field::REDUCTION, this, local_truth, fields); for (const auto &field : fields) { entity->field_add(field); } for (int i = 0; i < nvar; i++) { // Verify that all names were used for a field... assert(names[i][0] == '\0' || (local_truth && local_truth[i] == 0)); delete[] names[i]; } delete[] names; } } return nvar; } // common void BaseDatabaseIO::write_results_metadata(bool gather_data, Ioss::IfDatabaseExistsBehavior behavior) { if (gather_data) { int glob_index = 0; #if GLOBALS_ARE_TRANSIENT glob_index = gather_names(EX_GLOBAL, m_variables[EX_GLOBAL], get_region(), glob_index, true); #else glob_index = gather_names(EX_GLOBAL, m_reductionVariables[EX_GLOBAL], get_region(), glob_index, true); #endif m_reductionValues[EX_GLOBAL][0].resize(glob_index); const Ioss::NodeBlockContainer &node_blocks = get_region()->get_node_blocks(); assert(node_blocks.size() <= 1); internal_gather_results_metadata(EX_NODE_BLOCK, node_blocks); const Ioss::EdgeBlockContainer &edge_blocks = get_region()->get_edge_blocks(); internal_gather_results_metadata(EX_EDGE_BLOCK, edge_blocks); const Ioss::FaceBlockContainer &face_blocks = get_region()->get_face_blocks(); internal_gather_results_metadata(EX_FACE_BLOCK, face_blocks); const Ioss::ElementBlockContainer &element_blocks = get_region()->get_element_blocks(); internal_gather_results_metadata(EX_ELEM_BLOCK, element_blocks); const Ioss::NodeSetContainer &nodesets = get_region()->get_nodesets(); internal_gather_results_metadata(EX_NODE_SET, nodesets); const Ioss::EdgeSetContainer &edgesets = get_region()->get_edgesets(); internal_gather_results_metadata(EX_EDGE_SET, edgesets); const Ioss::FaceSetContainer &facesets = get_region()->get_facesets(); internal_gather_results_metadata(EX_FACE_SET, facesets); const Ioss::ElementSetContainer &elementsets = get_region()->get_elementsets(); internal_gather_results_metadata(EX_ELEM_SET, elementsets); const auto &blobs = get_region()->get_blobs(); internal_gather_results_metadata(EX_BLOB, blobs); const auto &assemblies = get_region()->get_assemblies(); internal_gather_results_metadata(EX_ASSEMBLY, assemblies); { int index = 0; const Ioss::SideSetContainer &sidesets = get_region()->get_sidesets(); for (const auto &sideset : sidesets) { const Ioss::SideBlockContainer &side_blocks = sideset->get_side_blocks(); for (const auto &block : side_blocks) { glob_index = gather_names(EX_SIDE_SET, m_reductionVariables[EX_SIDE_SET], block, glob_index, true); index = gather_names(EX_SIDE_SET, m_variables[EX_SIDE_SET], block, index, false); } } generate_sideset_truth_table(); } } if (behavior != Ioss::DB_APPEND && behavior != Ioss::DB_MODIFY) { ex_var_params exo_params{}; #if GLOBALS_ARE_TRANSIENT exo_params.num_glob = m_variables[EX_GLOBAL].size(); #else exo_params.num_glob = m_reductionVariables[EX_GLOBAL].size(); #endif exo_params.num_node = m_variables[EX_NODE_BLOCK].size(); exo_params.num_edge = m_variables[EX_EDGE_BLOCK].size(); exo_params.num_face = m_variables[EX_FACE_BLOCK].size(); exo_params.num_elem = m_variables[EX_ELEM_BLOCK].size(); exo_params.num_nset = m_variables[EX_NODE_SET].size(); exo_params.num_eset = m_variables[EX_EDGE_SET].size(); exo_params.num_fset = m_variables[EX_FACE_SET].size(); exo_params.num_sset = m_variables[EX_SIDE_SET].size(); exo_params.num_elset = m_variables[EX_ELEM_SET].size(); exo_params.edge_var_tab = m_truthTable[EX_EDGE_BLOCK].data(); exo_params.face_var_tab = m_truthTable[EX_FACE_BLOCK].data(); exo_params.elem_var_tab = m_truthTable[EX_ELEM_BLOCK].data(); exo_params.nset_var_tab = m_truthTable[EX_NODE_SET].data(); exo_params.eset_var_tab = m_truthTable[EX_EDGE_SET].data(); exo_params.fset_var_tab = m_truthTable[EX_FACE_SET].data(); exo_params.sset_var_tab = m_truthTable[EX_SIDE_SET].data(); exo_params.elset_var_tab = m_truthTable[EX_ELEM_SET].data(); if (isParallel) { // Check consistency among all processors. They should all // have the same number of each variable type... // The called function will throw an exception if the counts differ. check_variable_consistency(exo_params, myProcessor, get_filename(), util()); } { Ioss::SerializeIO serializeIO__(this); int ierr = ex_put_all_var_param_ext(get_file_pointer(), &exo_params); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } // Blob and Assembly not supported in ex_put_all_var_param_ext... if (!m_variables[EX_BLOB].empty()) { ierr = ex_put_variable_param(get_file_pointer(), EX_BLOB, m_variables[EX_BLOB].size()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } if (!m_variables[EX_ASSEMBLY].empty()) { ierr = ex_put_variable_param(get_file_pointer(), EX_ASSEMBLY, m_variables[EX_ASSEMBLY].size()); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } for (const auto &type : exodus_types) { output_results_names(type, m_variables[type], false); output_results_names(type, m_reductionVariables[type], true); } } } } // common template void BaseDatabaseIO::internal_gather_results_metadata(ex_entity_type type, std::vector entities) { int index = 0; int red_index = 0; for (const auto &entity : entities) { red_index = gather_names(type, m_reductionVariables[type], entity, red_index, true); index = gather_names(type, m_variables[type], entity, index, false); } #if GLOBALS_ARE_TRANSIENT size_t value_size = type == EX_GLOBAL ? m_variables[type].size() : m_reductionVariables[type].size(); #else size_t value_size = m_reductionVariables[type].size(); #endif for (const auto &entity : entities) { auto id = entity->get_optional_property("id", 0); m_reductionValues[type][id].resize(value_size); } generate_block_truth_table(m_variables[type], m_truthTable[type], entities, get_field_separator()); } // common int BaseDatabaseIO::gather_names(ex_entity_type type, VariableNameMap &variables, const Ioss::GroupingEntity *ge, int index, bool reduction) { int new_index = index; bool nblock = (type == EX_NODE_BLOCK); // Get names of all transient and reduction fields... Ioss::NameList results_fields; if (reduction) { ge->field_describe(Ioss::Field::REDUCTION, &results_fields); } if (!reduction || type == EX_GLOBAL) { ge->field_describe(Ioss::Field::TRANSIENT, &results_fields); } // NOTE: For exodusII, the convention is that the displacement // fields are the first 'ndim' fields in the file. // Try to find a likely displacement field std::string disp_name; bool has_disp = false; if (!reduction && nblock && new_index == 0) { has_disp = find_displacement_field(results_fields, ge, spatialDimension, &disp_name); if (has_disp) { new_index += spatialDimension; } } int save_index = 0; for (const auto &name : results_fields) { if (has_disp && name == disp_name && new_index != 0) { save_index = new_index; new_index = 0; } Ioss::Field field = ge->get_field(name); int re_im = 1; if (field.get_type() == Ioss::Field::COMPLEX) { re_im = 2; } for (int complex_comp = 0; complex_comp < re_im; complex_comp++) { std::string field_name = field.get_name(); if (re_im == 2) { field_name += complex_suffix[complex_comp]; } for (int i = 1; i <= field.get_component_count(Ioss::Field::InOut::OUTPUT); i++) { std::string var_string = get_component_name(field, Ioss::Field::InOut::OUTPUT, i); if (variables.find(var_string) == variables.end()) { variables.insert(VNMValuePair(var_string, ++new_index)); } } } if (has_disp && name == disp_name) { new_index = save_index; } } return new_index; } // common void BaseDatabaseIO::generate_sideset_truth_table() { size_t var_count = m_variables[EX_SIDE_SET].size(); if (var_count == 0 || m_groupCount[EX_SIDE_SET] == 0) { return; } // Member variable. Will be deleted in destructor... m_truthTable[EX_SIDE_SET].resize(m_groupCount[EX_SIDE_SET] * var_count); // Fill in the truth table. It is conceptually a two-dimensional array of // the form 'array[num_blocks][num_var]'. In C++, // the values for the first block are first, followed by // next block, ... size_t offset = 0; const Ioss::SideSetContainer &sidesets = get_region()->get_sidesets(); for (const auto &sideset : sidesets) { const Ioss::SideBlockContainer &side_blocks = sideset->get_side_blocks(); for (const auto &block : side_blocks) { // See if this sideblock has a corresponding entry in the sideset list. if (block->property_exists("invalid")) { continue; } // Get names of all transient and reduction fields... Ioss::NameList results_fields = block->field_describe(Ioss::Field::TRANSIENT); block->field_describe(Ioss::Field::REDUCTION, &results_fields); for (const auto &fn : results_fields) { Ioss::Field field = block->get_field(fn); Ioss::Field::BasicType ioss_type = field.get_type(); int re_im = 1; if (ioss_type == Ioss::Field::COMPLEX) { re_im = 2; } for (int complex_comp = 0; complex_comp < re_im; complex_comp++) { std::string field_name = field.get_name(); if (re_im == 2) { field_name += complex_suffix[complex_comp]; } for (int i = 1; i <= field.get_component_count(Ioss::Field::InOut::OUTPUT); i++) { std::string var_string = get_component_name(field, Ioss::Field::InOut::OUTPUT, i); // Find position of 'var_string' in 'm_variables[]' auto VN = m_variables[EX_SIDE_SET].find(var_string); if (VN != m_variables[EX_SIDE_SET].end()) { // Index '(*VN).second' is 1-based... m_truthTable[EX_SIDE_SET][offset + (*VN).second - 1] = 1; } } } } } offset += var_count; } assert(offset == var_count * m_groupCount[EX_SIDE_SET]); } // common void BaseDatabaseIO::output_results_names(ex_entity_type type, VariableNameMap &variables, bool reduction) const { bool lowercase_names = (properties.exists("VARIABLE_NAME_CASE") && Ioss::Utils::lowercase(properties.get("VARIABLE_NAME_CASE").get_string()) == "lower"); bool uppercase_names = (properties.exists("VARIABLE_NAME_CASE") && Ioss::Utils::lowercase(properties.get("VARIABLE_NAME_CASE").get_string()) == "upper"); size_t var_count = variables.size(); if (var_count > 0) { size_t name_length = 0; // Push into a char** array... std::vector var_names(var_count); std::vector variable_names(var_count); for (const auto &variable : variables) { size_t index = variable.second; assert(index > 0 && index <= var_count); variable_names[index - 1] = variable.first; if (uppercase_names) { variable_names[index - 1] = Ioss::Utils::uppercase(variable_names[index - 1]); } else if (lowercase_names) { variable_names[index - 1] = Ioss::Utils::lowercase(variable_names[index - 1]); } var_names[index - 1] = const_cast(variable_names[index - 1].c_str()); size_t name_len = variable_names[index - 1].length(); name_length = name_len > name_length ? name_len : name_length; } // Should handle this automatically, but by the time we get to defining transient fields, we // have already created the output database and populated the set/block names. At this point, // it is too late to change the size of the names stored on the output database... (I think... // try changing DIM_STR_NAME value and see if works...) if (name_length > static_cast(maximumNameLength)) { if (myProcessor == 0) { fmt::print(Ioss::WARNING(), "There are variables names whose name length ({0}) exceeds the current " "maximum name length ({1})\n set for this database ({2}).\n" " You should either reduce the length of the variable name, or " "set the 'MAXIMUM_NAME_LENGTH' property\n" " to at least {0}.\n Contact gdsjaar@sandia.gov for more " "information.\n\n", name_length, maximumNameLength, get_filename()); } } int ierr = 0; if (reduction) { ierr = ex_put_reduction_variable_names(get_file_pointer(), type, var_count, var_names.data()); } else { ierr = ex_put_variable_names(get_file_pointer(), type, var_count, var_names.data()); } if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } } // common // Handle special output time requests -- primarily restart (cycle, overwrite) // Given the global region step, return the step on the database... int BaseDatabaseIO::get_database_step(int global_step) const { if (get_file_per_state()) { return 1; } assert(overlayCount >= 0 && cycleCount >= 0); if (overlayCount == 0 && cycleCount == 0) { return global_step; } int local_step = global_step - 1; local_step /= (overlayCount + 1); if (cycleCount > 0) { local_step %= cycleCount; } return local_step + 1; } // common void BaseDatabaseIO::flush_database__() const { if (!is_input()) { if (isParallel || myProcessor == 0) { ex_update(get_file_pointer()); } } } void BaseDatabaseIO::finalize_write(int state, double sim_time) { // Attempt to ensure that all data written up to this point has // actually made it out to disk. We also write a special attribute // to the file to indicate that the current timestep should be // complete on the disk. // The attribute is a GLOBAL attribute named "last_written_time" // which is a double value which can be compared to the values in // the time array to make sure they match. If they don't, then // hopefully the "last_written_time" is smaller than the time // array value and indicates that the last step is corrupt. // Update the attribute. Ioex::update_last_time_attribute(get_file_pointer(), sim_time); // Flush the files buffer to disk... // If a history file, then only flush if there is more // than 10 seconds since the last flush to avoid // the flush eating up cpu time for small fast jobs... // NOTE: If decide to do this on all files, need to sync across // processors to make sure they all flush at same time. // GDS: 2011/03/30 -- Use for all non-parallel files, but shorten // time for non history files. Assume that can afford to lose ~10 // seconds worth of data... (Flush was taking long time on some // /scratch filesystems at SNL for short regression tests with // lots of steps) // GDS: 2011/07/27 -- shorten from 90 to 10. Developers running // small jobs were not able to view output until job // finished. Hopefully the netcdf no-fsync fix along with this fix // results in negligible impact on runtime with more syncs. // Need to be able to handle a flushInterval == 1 to force flush // every time step even in a serial run. // The default setting for flushInterval is 1, but in the past, // it was not checked for serial runs. Now, set the default to -1 // and if that is the value and serial, then do the time-based // check; otherwise, use flushInterval setting... bool do_flush = true; if (flushInterval == 1) { do_flush = true; } else if (flushInterval == 0) { do_flush = false; } else if (dbUsage == Ioss::WRITE_HISTORY || !isParallel) { assert(myProcessor == 0); time_t cur_time = time(nullptr); if (cur_time - timeLastFlush >= 10) { timeLastFlush = cur_time; do_flush = true; } else { do_flush = false; } } if (!do_flush && flushInterval > 0) { if (state % flushInterval == 0) { do_flush = true; } } if (do_flush) { flush_database__(); } } // common void Ioex::BaseDatabaseIO::add_attribute_fields(ex_entity_type entity_type, Ioss::GroupingEntity *block, int attribute_count, const std::string &type) { /// \todo REFACTOR Some of the attribute knowledge should be at /// the Ioss::ElementTopology level instead of here. That would /// make it easier for an application to register a new element /// type and its attributes. // Attribute "Conventions" to be used if there are no attribute names on the database: // from Table 1 in ExodusII manual // // Circle 1 Radius [Volume] // Sphere 1 Radius [Volume] // Truss 1 Area // 2D Beam 3 Area, I, J // 3D Beam 7 Area, I1, I2, J, V1, V2, V3 (V will be a 3D vector named "reference_axis") // Shell 1 Thickness // // Additional conventions not defined in ExodusII manual: // * If a "beam" has 1 attribute, call it "area" // * Treat "bar" and "rod" as aliases for "truss" // * Treat "trishell" as alias for "shell" // * All "shell" or "trishell" elements -- If #attributes == #node/element, the // attribute is "nodal_thickness" // // If there are attribute names on the database, use those names. // Always create a variable "attribute" which contains a single // field for all attributes... assert(block != nullptr); if (attribute_count > 0) { size_t my_element_count = block->entity_count(); // Get the attribute names. May not exist or may be blank... char **names = Ioss::Utils::get_name_array(attribute_count, maximumNameLength); int64_t id = block->get_property("id").get_int(); // Some older applications do not want to used named // attributes; in this case, just create a field for each // attribute named "attribute_1", "attribute_2", ..., "attribute_#" // This is controlled by the database property // "IGNORE_ATTRIBUTE_NAMES" bool attributes_named = true; // Possibly reset below; note that even if ignoring // attribute names, they are still 'named' if (properties.exists("IGNORE_ATTRIBUTE_NAMES")) { for (int i = 0; i < attribute_count; i++) { std::string tmp = fmt::format("attribute_{}", i + 1); Ioss::Utils::copy_string(names[i], tmp, maximumNameLength + 1); } } else { // Use attribute names if they exist. { Ioss::SerializeIO serializeIO__(this); if (block->entity_count() != 0) { int ierr = ex_get_attr_names(get_file_pointer(), entity_type, id, &names[0]); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } } // Sync names across processors... if (isParallel) { std::vector cname(attribute_count * (maximumNameLength + 1)); if (block->entity_count() != 0) { for (int i = 0; i < attribute_count; i++) { std::memcpy(&cname[i * (maximumNameLength + 1)], names[i], maximumNameLength + 1); } } util().attribute_reduction(attribute_count * (maximumNameLength + 1), cname.data()); for (int i = 0; i < attribute_count; i++) { std::memcpy(names[i], &cname[i * (maximumNameLength + 1)], maximumNameLength + 1); } } // Convert to lowercase. attributes_named = true; for (int i = 0; i < attribute_count; i++) { fix_bad_name(names[i]); Ioss::Utils::fixup_name(names[i]); if (names[i][0] == '\0' || (!(std::isalnum(names[i][0]) || names[i][0] == '_'))) { attributes_named = false; } } } if (attributes_named) { std::vector attributes; Ioss::Utils::get_fields(my_element_count, names, attribute_count, Ioss::Field::ATTRIBUTE, this, nullptr, attributes); int offset = 1; for (const auto &field : attributes) { if (block->field_exists(field.get_name())) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: In block '{}', attribute '{}' is defined multiple times which is " "not allowed.\n", block->name(), field.get_name()); IOSS_ERROR(errmsg); } block->field_add(field); const Ioss::Field &tmp_field = block->get_fieldref(field.get_name()); tmp_field.set_index(offset); offset += field.get_component_count(Ioss::Field::InOut::INPUT); } } else { // Attributes are not named.... // Try to assign some meaningful names based on conventions... int unknown_attributes = 0; if (type_match(type, "shell") || type_match(type, "trishell")) { if (attribute_count == block->get_property("topology_node_count").get_int()) { std::string att_name = "nodal_thickness"; std::string storage = fmt::format("Real[{}]", attribute_count); block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, storage, Ioss::Field::ATTRIBUTE, my_element_count, 1)); } else { std::string att_name = "thickness"; block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, 1)); unknown_attributes = attribute_count - 1; } } // NOTE: This must appear before the "sphere" check since // sphere is substring of "sphere-mass" // Want an exact match here, not substring match... else if (Ioss::Utils::str_equal(type, "sphere-mass")) { if (attribute_count != 10) { if (myProcessor == 0) { fmt::print(Ioss::WARNING(), "For element block '{}' of type '{}' there were {} attributes instead of " "the expected 10 attributes " "known to the IO Subsystem. " " The attributes can be accessed as the field named 'attribute'", block->name(), type, attribute_count); } } else { // First attribute is concentrated mass... size_t offset = 1; block->field_add(Ioss::Field("mass", Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, offset)); offset += 1; // Next six attributes are moment of inertia -- symmetric tensor block->field_add(Ioss::Field("inertia", Ioss::Field::REAL, IOSS_SYM_TENSOR(), Ioss::Field::ATTRIBUTE, my_element_count, offset)); offset += 6; // Next three attributes are offset from node to CG block->field_add(Ioss::Field("offset", Ioss::Field::REAL, IOSS_VECTOR_3D(), Ioss::Field::ATTRIBUTE, my_element_count, offset)); } } else if (type_match(type, "circle") || type_match(type, "sphere")) { std::string att_name = "radius"; size_t offset = 1; block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, offset++)); if (attribute_count > 1) { // Default second attribute (from sphgen3d) is "volume" // which is the volume of the cube which would surround a // sphere of the given radius. att_name = "volume"; block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, offset++)); } unknown_attributes = attribute_count - 2; } else if (type_match(type, "truss") || type_match(type, "bar") || type_match(type, "beam") || type_match(type, "rod")) { // Technically, truss, bar, rod should all only have 1 attribute; however, // there are some mesh generation codes that treat all of these types the // same and put "beam-type" attributes on bars... int index = 1; std::string att_name = "area"; block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, index++)); if (spatialDimension == 2 && attribute_count >= 3) { block->field_add(Ioss::Field("i", Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, index++)); block->field_add(Ioss::Field("j", Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, index++)); } else if (spatialDimension == 3 && attribute_count >= 7) { block->field_add(Ioss::Field("i1", Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, index++)); block->field_add(Ioss::Field("i2", Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, index++)); block->field_add(Ioss::Field("j", Ioss::Field::REAL, IOSS_SCALAR(), Ioss::Field::ATTRIBUTE, my_element_count, index++)); block->field_add(Ioss::Field("reference_axis", Ioss::Field::REAL, IOSS_VECTOR_3D(), Ioss::Field::ATTRIBUTE, my_element_count, index)); index += 3; if (attribute_count >= 10) { // Next three attributes would (hopefully) be offset vector... // This is typically from a NASGEN model. block->field_add(Ioss::Field("offset", Ioss::Field::REAL, IOSS_VECTOR_3D(), Ioss::Field::ATTRIBUTE, my_element_count, index)); index += 3; } } unknown_attributes = attribute_count - (index - 1); } else { unknown_attributes = attribute_count; } if (unknown_attributes > 0) { std::string att_name = "extra_attribute_"; att_name += std::to_string(unknown_attributes); std::string storage = fmt::format("Real[{}]", unknown_attributes); size_t index = attribute_count - unknown_attributes + 1; block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, storage, Ioss::Field::ATTRIBUTE, my_element_count, index)); } } // Always create a field called "attribute" containing data // for all attributes on the mesh std::string att_name = "attribute"; // Default std::string storage = fmt::format("Real[{}]", attribute_count); block->field_add(Ioss::Field(att_name, Ioss::Field::REAL, storage, Ioss::Field::ATTRIBUTE, my_element_count, 1)); // Release memory... Ioss::Utils::delete_name_array(names, attribute_count); } } void BaseDatabaseIO::common_write_meta_data(Ioss::IfDatabaseExistsBehavior behavior) { Ioss::Region *region = get_region(); // Verify that exodus supports the mesh_type... if (region->mesh_type() != Ioss::MeshType::UNSTRUCTURED) { std::ostringstream errmsg; fmt::print(errmsg, "ERROR: The mesh type is '{}' which Exodus does not support.\n" " Only 'Unstructured' is supported at this time.\n", region->mesh_type_string()); IOSS_ERROR(errmsg); } const Ioss::NodeBlockContainer &node_blocks = region->get_node_blocks(); assert(node_blocks.size() <= 1); if (!node_blocks.empty()) { Ioex::get_id(node_blocks[0], EX_NODE_BLOCK, &ids_); nodeCount = node_blocks[0]->entity_count(); spatialDimension = node_blocks[0]->get_property("component_degree").get_int(); } else { spatialDimension = 1; } // Assemblies -- { const auto &assemblies = region->get_assemblies(); if (behavior != Ioss::DB_MODIFY) { // Set ids of all entities that have "id" property... for (auto &assem : assemblies) { Ioex::set_id(assem, EX_ASSEMBLY, &ids_); } for (auto &assem : assemblies) { Ioex::get_id(assem, EX_ASSEMBLY, &ids_); } } m_groupCount[EX_ASSEMBLY] = assemblies.size(); } // Blobs -- { const auto &blobs = region->get_blobs(); // Set ids of all entities that have "id" property... if (behavior != Ioss::DB_MODIFY) { for (auto &blob : blobs) { Ioex::set_id(blob, EX_BLOB, &ids_); } for (auto &blob : blobs) { Ioex::get_id(blob, EX_BLOB, &ids_); } } m_groupCount[EX_BLOB] = blobs.size(); } // Edge Blocks -- { const Ioss::EdgeBlockContainer &edge_blocks = region->get_edge_blocks(); assert(Ioss::Utils::check_block_order(edge_blocks)); // Set ids of all entities that have "id" property... if (behavior != Ioss::DB_MODIFY) { for (auto &edge_block : edge_blocks) { Ioex::set_id(edge_block, EX_EDGE_BLOCK, &ids_); } edgeCount = 0; for (auto &edge_block : edge_blocks) { edgeCount += edge_block->entity_count(); // Set ids of all entities that do not have "id" property... Ioex::get_id(edge_block, EX_EDGE_BLOCK, &ids_); } } m_groupCount[EX_EDGE_BLOCK] = edge_blocks.size(); } // Face Blocks -- { const Ioss::FaceBlockContainer &face_blocks = region->get_face_blocks(); assert(Ioss::Utils::check_block_order(face_blocks)); // Set ids of all entities that have "id" property... if (behavior != Ioss::DB_MODIFY) { for (auto &face_block : face_blocks) { Ioex::set_id(face_block, EX_FACE_BLOCK, &ids_); } faceCount = 0; for (auto &face_block : face_blocks) { faceCount += face_block->entity_count(); // Set ids of all entities that do not have "id" property... Ioex::get_id(face_block, EX_FACE_BLOCK, &ids_); } } m_groupCount[EX_FACE_BLOCK] = face_blocks.size(); } // Element Blocks -- { const Ioss::ElementBlockContainer &element_blocks = region->get_element_blocks(); assert(Ioss::Utils::check_block_order(element_blocks)); if (behavior != Ioss::DB_MODIFY) { // Set ids of all entities that have "id" property... for (auto &element_block : element_blocks) { Ioex::set_id(element_block, EX_ELEM_BLOCK, &ids_); } } elementCount = 0; Ioss::Int64Vector element_counts; element_counts.reserve(element_blocks.size()); for (auto &element_block : element_blocks) { elementCount += element_block->entity_count(); element_counts.push_back(element_block->entity_count()); // Set ids of all entities that do not have "id" property... if (behavior != Ioss::DB_MODIFY) { Ioex::get_id(element_block, EX_ELEM_BLOCK, &ids_); } } m_groupCount[EX_ELEM_BLOCK] = element_blocks.size(); if (isParallel) { // Set "global_entity_count" property on all blocks. // Used to skip output on "globally" empty blocks. Ioss::Int64Vector global_counts(element_counts.size()); util().global_count(element_counts, global_counts); size_t idx = 0; for (auto &element_block : element_blocks) { element_block->property_add(Ioss::Property("global_entity_count", global_counts[idx++])); } } } // NodeSets ... { const Ioss::NodeSetContainer &nodesets = region->get_nodesets(); if (behavior != Ioss::DB_MODIFY) { for (auto &set : nodesets) { Ioex::set_id(set, EX_NODE_SET, &ids_); } for (auto &set : nodesets) { Ioex::get_id(set, EX_NODE_SET, &ids_); } } m_groupCount[EX_NODE_SET] = nodesets.size(); } // EdgeSets ... { const Ioss::EdgeSetContainer &edgesets = region->get_edgesets(); if (behavior != Ioss::DB_MODIFY) { for (auto &set : edgesets) { Ioex::set_id(set, EX_EDGE_SET, &ids_); } for (auto &set : edgesets) { Ioex::get_id(set, EX_EDGE_SET, &ids_); } } m_groupCount[EX_EDGE_SET] = edgesets.size(); } // FaceSets ... { const Ioss::FaceSetContainer &facesets = region->get_facesets(); if (behavior != Ioss::DB_MODIFY) { for (auto &set : facesets) { Ioex::set_id(set, EX_FACE_SET, &ids_); } for (auto &set : facesets) { Ioex::get_id(set, EX_FACE_SET, &ids_); } } m_groupCount[EX_FACE_SET] = facesets.size(); } // ElementSets ... { const Ioss::ElementSetContainer &elementsets = region->get_elementsets(); if (behavior != Ioss::DB_MODIFY) { for (auto &set : elementsets) { Ioex::set_id(set, EX_ELEM_SET, &ids_); } for (auto &set : elementsets) { Ioex::get_id(set, EX_ELEM_SET, &ids_); } } m_groupCount[EX_ELEM_SET] = elementsets.size(); } // SideSets ... { const Ioss::SideSetContainer &ssets = region->get_sidesets(); if (behavior != Ioss::DB_MODIFY) { for (auto &set : ssets) { Ioex::set_id(set, EX_SIDE_SET, &ids_); } } // Get entity counts for all face sets... Create SideSets. for (auto &set : ssets) { if (behavior != Ioss::DB_MODIFY) { Ioex::get_id(set, EX_SIDE_SET, &ids_); } int64_t id = set->get_property("id").get_int(); int64_t entity_count = 0; int64_t df_count = 0; const Ioss::SideBlockContainer &side_blocks = set->get_side_blocks(); for (auto &block : side_blocks) { // Add "*_offset" properties to specify at what offset // the data for this block appears in the containing set. auto *new_block = const_cast(block); new_block->property_add(Ioss::Property("set_offset", entity_count)); new_block->property_add(Ioss::Property("set_df_offset", df_count)); // If combining sideblocks into sidesets on output, then // the id of the sideblock must be the same as the sideset // id. new_block->property_update("id", id); new_block->property_update("guid", util().generate_guid(id)); entity_count += block->entity_count(); df_count += block->get_property("distribution_factor_count").get_int(); } auto *new_entity = const_cast(set); new_entity->property_add(Ioss::Property("entity_count", entity_count)); new_entity->property_add(Ioss::Property("distribution_factor_count", df_count)); } m_groupCount[EX_SIDE_SET] = ssets.size(); } } void BaseDatabaseIO::output_other_meta_data() { // Write attribute names (if any)... write_attribute_names(get_file_pointer(), EX_NODE_SET, get_region()->get_nodesets()); write_attribute_names(get_file_pointer(), EX_EDGE_SET, get_region()->get_edgesets()); write_attribute_names(get_file_pointer(), EX_FACE_SET, get_region()->get_facesets()); write_attribute_names(get_file_pointer(), EX_ELEM_SET, get_region()->get_elementsets()); write_attribute_names(get_file_pointer(), EX_NODE_BLOCK, get_region()->get_node_blocks()); write_attribute_names(get_file_pointer(), EX_EDGE_BLOCK, get_region()->get_edge_blocks()); write_attribute_names(get_file_pointer(), EX_FACE_BLOCK, get_region()->get_face_blocks()); write_attribute_names(get_file_pointer(), EX_ELEM_BLOCK, get_region()->get_element_blocks()); write_attribute_names(get_file_pointer(), EX_ASSEMBLY, get_region()->get_assemblies()); write_attribute_names(get_file_pointer(), EX_BLOB, get_region()->get_blobs()); // Write "reduction" attributes... std::vector regions; regions.push_back(get_region()); Ioex::write_reduction_attributes(get_file_pointer(), regions); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_nodesets()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_nodesets()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_edgesets()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_facesets()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_elementsets()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_node_blocks()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_edge_blocks()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_face_blocks()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_element_blocks()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_assemblies()); Ioex::write_reduction_attributes(get_file_pointer(), get_region()->get_blobs()); // Write coordinate names... if (!get_region()->get_node_blocks().empty()) { char const *labels[3]; labels[0] = "x"; labels[1] = "y"; labels[2] = "z"; int ierr = ex_put_coord_names(get_file_pointer(), (char **)labels); if (ierr < 0) { Ioex::exodus_error(get_file_pointer(), __LINE__, __func__, __FILE__); } } // Write coordinate frame data... write_coordinate_frames(get_file_pointer(), get_region()->get_coordinate_frames()); } } // namespace Ioex namespace { template void write_attribute_names(int exoid, ex_entity_type type, const std::vector &entities) { // For the entity, determine the attribute fields and the correct // order. Write the names of these fields. However, be aware that // the field "attribute" always exists to contain all attributes // and its name should not be used even if it is the only // attribute field. for (const auto &ge : entities) { int attribute_count = ge->get_property("attribute_count").get_int(); if (attribute_count > 0) { check_attribute_index_order(ge); std::vector names(attribute_count); std::vector names_str(attribute_count); // Get the attribute fields... Ioss::NameList results_fields = ge->field_describe(Ioss::Field::ATTRIBUTE); for (const auto &field_name : results_fields) { const Ioss::Field &field = ge->get_fieldref(field_name); assert(field.get_index() != 0); if (field_name == "attribute") { field.set_index(1); continue; } int comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT); int field_offset = field.get_index(); for (int i = 0; i < comp_count; i++) { names_str[field_offset - 1 + i] = ge->get_database()->get_component_name(field, Ioss::Field::InOut::OUTPUT, i + 1); names[field_offset - 1 + i] = const_cast(names_str[field_offset - 1 + i].c_str()); } } size_t ge_id = ge->get_property("id").get_int(); int ierr = ex_put_attr_names(exoid, type, ge_id, names.data()); if (ierr < 0) { Ioex::exodus_error(exoid, __LINE__, __func__, __FILE__); } } } } // common void check_attribute_index_order(Ioss::GroupingEntity *block) { int attribute_count = block->get_property("attribute_count").get_int(); if (attribute_count == 0) { return; } int component_sum = 0; std::vector attributes(attribute_count + 1); // Get the attribute fields... Ioss::NameList results_fields = block->field_describe(Ioss::Field::ATTRIBUTE); bool all_attributes_indexed = true; bool some_attributes_indexed = false; for (const auto &field_name : results_fields) { const Ioss::Field &field = block->get_fieldref(field_name); if (field_name == "attribute") { field.set_index(1); if (results_fields.size() == 1) { return; } continue; } int field_offset = field.get_index(); if (field_offset == 0) { all_attributes_indexed = false; } else { some_attributes_indexed = true; } int comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT); component_sum += comp_count; if (field_offset == 0) { continue; } if (field_offset + comp_count - 1 > attribute_count) { std::ostringstream errmsg; fmt::print( errmsg, "INTERNAL ERROR: For block '{}', attribute '{}', the indexing is incorrect.\n" "Something is wrong in the Ioex::BaseDatabaseIO class, function {}. Please report.\n", block->name(), field_name, __func__); IOSS_ERROR(errmsg); } for (int i = field_offset; i < field_offset + comp_count; i++) { if (attributes[i] != 0) { std::ostringstream errmsg; fmt::print( errmsg, "INTERNAL ERROR: For block '{}', attribute '{}', indexes into the same location as a " "previous attribute.\n" "Something is wrong in the Ioex::BaseDatabaseIO class, function {}. Please report.\n", block->name(), field_name, __func__); IOSS_ERROR(errmsg); } attributes[i] = 1; } } if (component_sum > attribute_count) { std::ostringstream errmsg; fmt::print( errmsg, "INTERNAL ERROR: Block '{}' is supposed to have {} attributes, but {} attributes " "were counted.\n" "Something is wrong in the Ioex::BaseDatabaseIO class, function {}. Please report.\n", block->name(), attribute_count, component_sum, __func__); IOSS_ERROR(errmsg); } // Take care of the easy cases first... if (all_attributes_indexed) { // Check that all attributes are defined. This should have // caught above in the duplicate index check. for (int i = 1; i <= attribute_count; i++) { if (attributes[i] == 0) { std::ostringstream errmsg; fmt::print( errmsg, "INTERNAL ERROR: Block '{}' has an incomplete set of attributes.\n" "Something is wrong in the Ioex::BaseDatabaseIO class, function {}. Please report.\n", block->name(), __func__); IOSS_ERROR(errmsg); } } return; } if (!some_attributes_indexed) { // Index was not set for any of the attributes; set them all... size_t offset = 1; for (const auto &field_name : results_fields) { const Ioss::Field &field = block->get_fieldref(field_name); if (field_name == "attribute") { field.set_index(1); continue; } int comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT); assert(field.get_index() == 0); field.set_index(offset); offset += comp_count; } assert((int)offset == attribute_count + 1); return; } // At this point, we have a partially indexed set of attributes. Some have an index and some // don't // The easy case is if the missing indices are at the end of the list... assert(!all_attributes_indexed && some_attributes_indexed); int last_defined = 0; for (int i = 1; i < attribute_count + 1; i++) { if (attributes[i] != 0) { last_defined = i; } } int first_undefined = attribute_count; for (int i = attribute_count; i > 0; i--) { if (attributes[i] == 0) { first_undefined = i; } } if (last_defined < first_undefined) { for (const auto &field_name : results_fields) { const Ioss::Field &field = block->get_fieldref(field_name); if (field_name == "attribute") { field.set_index(1); continue; } if (field.get_index() == 0) { field.set_index(first_undefined); int comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT); first_undefined += comp_count; } } assert(first_undefined == attribute_count + 1); return; } // Take the easy way out... Just reindex all attributes. size_t offset = 1; for (const auto &field_name : results_fields) { const Ioss::Field &field = block->get_fieldref(field_name); if (field_name == "attribute") { field.set_index(1); continue; } int comp_count = field.get_component_count(Ioss::Field::InOut::OUTPUT); assert(field.get_index() == 0); field.set_index(offset); offset += comp_count; } assert((int)offset == attribute_count + 1); } void check_variable_consistency(const ex_var_params &exo_params, int my_processor, const std::string &filename, const Ioss::ParallelUtils &util) { PAR_UNUSED(exo_params); PAR_UNUSED(my_processor); PAR_UNUSED(filename); PAR_UNUSED(util); #ifdef SEACAS_HAVE_MPI const int num_types = 10; std::vector var_counts(num_types); var_counts[0] = exo_params.num_glob; var_counts[1] = exo_params.num_node; var_counts[2] = exo_params.num_edge; var_counts[3] = exo_params.num_face; var_counts[4] = exo_params.num_elem; var_counts[5] = exo_params.num_nset; var_counts[6] = exo_params.num_eset; var_counts[7] = exo_params.num_fset; var_counts[8] = exo_params.num_sset; var_counts[9] = exo_params.num_elset; Ioss::IntVector all_counts; util.gather(var_counts, all_counts); bool any_diff = false; std::ostringstream errmsg; if (my_processor == 0) { bool diff[num_types]; // See if any differ... for (int iv = 0; iv < 10; iv++) { diff[iv] = false; std::string type; switch (iv) { case 0: type = "global"; break; case 1: type = "nodal"; break; case 2: type = "edge"; break; case 3: type = "face"; break; case 4: type = "element"; break; case 5: type = "nodeset"; break; case 6: type = "edgeset"; break; case 7: type = "faceset"; break; case 8: type = "sideset"; break; case 9: type = "elementset"; break; } for (int ip = 1; ip < util.parallel_size(); ip++) { if (var_counts[iv] != all_counts[ip * num_types + iv]) { any_diff = true; if (!diff[iv]) { Ioss::FileInfo db(filename); diff[iv] = true; fmt::print(errmsg, "\nERROR: Number of {} variables is not consistent on all processors.\n" " Database: '{}'\n" "\tProcessor 0 count = {}\n", type, db.tailname(), var_counts[iv]); } fmt::print(errmsg, "\tProcessor {} count = {}\n", ip, all_counts[ip * num_types + iv]); } } } } else { // Give the other processors something to say... fmt::print(errmsg, "ERROR: Variable type counts are inconsistent. See processor 0 output for " "more details.\n"); } int idiff = any_diff ? 1 : 0; util.broadcast(idiff); any_diff = idiff == 1; if (any_diff) { std::runtime_error x(errmsg.str()); throw x; } #endif } void insert_sort_and_unique(const std::vector &src, std::vector &dest) { dest.insert(dest.end(), src.begin(), src.end()); std::sort(dest.begin(), dest.end(), std::less()); auto endIter = std::unique(dest.begin(), dest.end()); dest.resize(endIter - dest.begin()); } } // namespace