// Copyright(C) 1999-2023 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 #include "add_to_log.h" #include "adler.h" #include "copy_string_cpp.h" #include "format_time.h" #include "sys_info.h" #if !USE_STD_SORT #include "pdqsort.h" #endif #include "smart_assert.h" #include "time_stamp.h" #include #if EX_API_VERS_NODOT <= 467 #error "Requires exodusII version 4.68 or later" #endif #include "CJ_ExodusEntity.h" #include "CJ_ExodusFile.h" #include "CJ_Internals.h" #include "CJ_ObjectType.h" #include "CJ_SystemInterface.h" #include "CJ_Variables.h" #include "CJ_Version.h" namespace { template void clear(std::vector &vec) { vec.clear(); vec.shrink_to_fit(); SMART_ASSERT(vec.capacity() == 0); } template bool approx_equal(T v1, T v2) { #if 0 static const T tolerance = 100.0 * std::numeric_limits::epsilon(); return std::fabs(v1 - v2) <= std::fabs(v1+v2)*tolerance; #else return (float)v1 == (float)v2; #endif } } // namespace struct NodeInfo { NodeInfo() = default; NodeInfo(size_t id_, double x_, double y_, double z_) : id(id_), x(x_), y(y_), z(z_) {} size_t id{0}; double x{0.0}; double y{0.0}; double z{0.0}; bool operator==(const NodeInfo &other) const { return id == other.id && approx_equal(x, other.x) && approx_equal(y, other.y) && approx_equal(z, other.z); } bool operator!=(const NodeInfo &other) const { return !(*this == other); } bool operator<(const NodeInfo &other) const { if (id < other.id) { return true; } if (id > other.id) { return false; } SMART_ASSERT(id == other.id); if (!approx_equal(x, other.x) && x < other.x) { return true; } if (!approx_equal(x, other.x) && x > other.x) { return false; } SMART_ASSERT(approx_equal(x, other.x)); if (!approx_equal(y, other.y) && y < other.y) { return true; } if (!approx_equal(y, other.y) && y > other.y) { return false; } SMART_ASSERT(approx_equal(y, other.y)); if (!approx_equal(z, other.z) && z < other.z) { return true; } return false; } }; using GlobalMap = std::vector; using GMapIter = GlobalMap::iterator; extern double seacas_timer(); namespace { template struct TimeStepMap { TimeStepMap(size_t part, int step, T time) : partNumber(part), localStepNumber(step), timeValue(time) { } size_t partNumber; int localStepNumber; T timeValue; }; int get_width(size_t max_value); ex_entity_type exodus_object_type(const Excn::ObjectType &conjoin_type) { switch (conjoin_type) { case Excn::ObjectType::EBLK: return EX_ELEM_BLOCK; case Excn::ObjectType::SSET: return EX_SIDE_SET; case Excn::ObjectType::NSET: return EX_NODE_SET; default: throw std::runtime_error("Invalid Object Type in exodus_object_type: " + std::to_string(static_cast(conjoin_type))); } } char **get_name_array(int size, size_t length) { char **names = nullptr; if (size > 0) { names = new char *[size]; for (int i = 0; i < size; i++) { names[i] = new char[length + 1]; std::memset(names[i], '\0', length + 1); } } return names; } void free_name_array(char **names, int size) { for (int i = 0; i < size; i++) { delete[] names[i]; } delete[] names; names = nullptr; } } // namespace std::string tsFormat = "[{:%H:%M:%S}] "; // prototypes // The main program templated to permit float/double transfer. template int conjoin(Excn::SystemInterface &interFace, T /* dummy */, INT /* dummy int */); namespace { void sort_file_times(StringVector &input_files); void compress_white_space(char *str); void add_info_record(char *info_record, int size); template void put_mesh_summary(const Excn::Mesh &mesh); template void get_put_qa(int id, int id_out, const std::vector> &global_times, Excn::SystemInterface &interFace); int get_put_coordinate_names(int in, int out, int dimensionality); template int get_put_coordinates(Excn::Mesh &global, size_t part_count, std::vector> &local_mesh, T dummy); template int get_coordinates(int id, int dimensionality, size_t num_nodes, std::vector local_node_to_global, size_t part, std::vector &x, std::vector &y, std::vector &z); StringVector get_exodus_variable_names(int id, ex_entity_type elType, size_t var_count); template void filter_truth_table(int id, Excn::Mesh &global, std::vector &glob_blocks, Excn::Variables &vars, const StringIdVector &variable_names); template void get_truth_table(Excn::Mesh &global, std::vector> &blocks, std::vector &glob_blocks, Excn::Variables &vars, int debug); template void create_output_truth_table(std::vector &global_sets, Excn::Variables &vars, std::vector &truth_table); template int read_write_master_values(Excn::Variables &vars, const Excn::Mesh &global, std::vector &global_sets, std::vector> &local_mesh, std::vector> &local_sets, std::vector &values, size_t p, Excn::ExodusFile &id, int time_step, int time_step_out); void get_variable_params(int id, Excn::Variables &vars, const StringIdVector &variable_list); void get_put_variable_names(int id, int out, Excn::Variables &vars, Excn::SystemInterface &si, int *combined_status_variable_index = nullptr); template void build_reverse_node_map(std::vector> &local_mesh, Excn::Mesh *global, size_t part_count, GlobalMap &global_node_map); template void build_reverse_node_map(std::vector> &local_mesh, Excn::Mesh *global, size_t part_count, std::vector &global_node_map); template void build_reverse_element_map(std::vector> &local_mesh, std::vector> &blocks, std::vector &glob_blocks, Excn::Mesh *global, size_t part_count, std::vector> &global_element_map); template void get_nodesets(size_t total_node_count, std::vector> &local_mesh, std::vector>> &nodesets, std::vector> &glob_sets); template void get_element_blocks(const std::vector> &local_mesh, const Excn::Mesh &global, std::vector> &blocks, std::vector &glob_blocks); template void put_element_blocks(std::vector> &local_mesh, std::vector> &blocks, std::vector &glob_blocks, T single_or_double); template void put_nodesets(std::vector> &glob_sets); template void get_sideset_metadata(std::vector> &local_mesh, std::vector>> &sets, std::vector> &glob_ssets); template void get_put_sidesets(std::vector> &glob_ssets); template void add_status_variable(int id_out, const Excn::Mesh &global, const std::vector &blocks, const std::vector &glob_blocks, const std::vector &local_element_to_global, int step, int variable, T alive, int combined_variable_index); template size_t find_max_entity_count(size_t part_count, std::vector> &local_mesh, const Excn::Mesh &global, std::vector> &blocks, std::vector>> &nodesets, std::vector>> &sidesets); int case_compare(const std::string &s1, const std::string &s2); template void verify_set_position_mapping(const std::string &type, size_t part_count, const std::vector &global_sets, const std::vector> &sets) { bool problem = false; for (size_t i = 0; i < global_sets.size(); i++) { size_t loc_pos = global_sets[i].position_; for (size_t p = 0; p < part_count; p++) { if (global_sets[i].id != sets[p][loc_pos].id || case_compare(global_sets[i].name_, sets[p][loc_pos].name_) != 0 || sets[p][loc_pos].position_ != i) { problem = true; fmt::print(stderr, "\nERROR: Mismatch for global {0} at position {1} and local {0} in part {2} " "position {3}\n", type, i, p + 1, loc_pos); global_sets[i].dump(); sets[p][loc_pos].dump(); } } } if (problem) { throw std::runtime_error(type + " mismatch"); } } // SEE: http://lemire.me/blog/2017/04/10/removing-duplicates-from-lists-quickly template size_t unique(std::vector &out) { if (out.empty()) return 0; size_t pos = 1; T oldv = out[0]; for (size_t i = 1; i < out.size(); ++i) { T newv = out[i]; out[pos] = newv; pos += (newv != oldv); oldv = newv; } return pos; } template void uniquify(std::vector &vec) { #if USE_STD_SORT std::sort(vec.begin(), vec.end()); #else pdqsort(vec.begin(), vec.end()); #endif vec.resize(unique(vec)); vec.shrink_to_fit(); } } // namespace unsigned int debug_level = 0; int main(int argc, char *argv[]) { try { time_t begin_time = time(nullptr); Excn::SystemInterface::show_version(); Excn::SystemInterface interFace; bool ok = interFace.parse_options(argc, argv); if (!ok) { fmt::print(stderr, "\nERROR: Problems parsing command line arguments.\n\n"); exit(EXIT_FAILURE); } debug_level = interFace.debug(); if ((debug_level & 64) != 0U) { ex_opts(EX_VERBOSE | EX_DEBUG); } else { ex_opts(0); } int error = 0; if (interFace.sort_times()) { sort_file_times(interFace.inputFiles_); } if (!Excn::ExodusFile::initialize(interFace)) { fmt::print(stderr, "ERROR: Problem initializing input and/or output files.\n"); exit(EXIT_FAILURE); } int int_byte_size = 4; if (interFace.ints_64_bit()) { int_byte_size = 8; } if (Excn::ExodusFile::io_word_size() == 4) { if (int_byte_size == 4) { error = conjoin(interFace, static_cast(0.0), 0); } else { error = conjoin(interFace, static_cast(0.0), static_cast(0)); } } else { if (int_byte_size == 4) { error = conjoin(interFace, 0.0, 0); } else { error = conjoin(interFace, 0.0, static_cast(0)); } } Excn::ExodusFile::close_all(); time_t end_time = time(nullptr); add_to_log(argv[0], end_time - begin_time); return error; } catch (std::exception &e) { fmt::print(stderr, "ERROR: Standard exception: {}\n", e.what()); } } template int conjoin(Excn::SystemInterface &interFace, T /* dummy */, INT /* dummy int */) { SMART_ASSERT(sizeof(T) == Excn::ExodusFile::io_word_size()); const T alive = interFace.alive_value(); size_t part_count = interFace.inputFiles_.size(); auto mytitle = new char[MAX_LINE_LENGTH + 1]; memset(mytitle, '\0', MAX_LINE_LENGTH + 1); Excn::Mesh global; std::vector> local_mesh(part_count); // ****************************************************************** // 1. Read global info int error = 0; if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } std::string title0; for (size_t p = 0; p < part_count; p++) { ex_init_params info{}; error += ex_get_init_ext(Excn::ExodusFile(p), &info); local_mesh[p].title = info.title; local_mesh[p].dimensionality = info.num_dim; local_mesh[p].nodeCount = info.num_nodes; local_mesh[p].elementCount = info.num_elem; local_mesh[p].blockCount = info.num_elem_blk; local_mesh[p].nodesetCount = info.num_node_sets; local_mesh[p].sidesetCount = info.num_side_sets; if (p == 0) { global.title = mytitle; global.dimensionality = local_mesh[p].count(Excn::ObjectType::DIM); global.blockCount = local_mesh[p].count(Excn::ObjectType::EBLK); } else { SMART_ASSERT(global.count(Excn::ObjectType::DIM) == local_mesh[p].count(Excn::ObjectType::DIM)) (global.count(Excn::ObjectType::DIM))(local_mesh[p].count(Excn::ObjectType::DIM))(p); SMART_ASSERT(global.count(Excn::ObjectType::EBLK) == local_mesh[p].count(Excn::ObjectType::EBLK)) (global.count(Excn::ObjectType::EBLK))(local_mesh[p].count(Excn::ObjectType::EBLK))(p); } local_mesh[p].localNodeToGlobal.resize(local_mesh[p].count(Excn::ObjectType::NODE)); local_mesh[p].localElementToGlobal.resize(local_mesh[p].count(Excn::ObjectType::ELEM)); } // end for (p=0..part_count) delete[] mytitle; if (interFace.omit_nodesets()) { global.nodesetCount = 0; } if (interFace.omit_sidesets()) { global.sidesetCount = 0; } // Get database times... Save mapping from time step to part providing that time step. std::vector> global_times; double t_min = FLT_MAX; for (size_t p = part_count; p > 0; p--) { Excn::ExodusFile id(p - 1); bool used = false; int nts = ex_inquire_int(id, EX_INQ_TIME); if (nts == 0) { std::string part = "Part " + std::to_string(p) + ": "; part += interFace.inputFiles_[p - 1]; fmt::print(stderr, "\nWARNING: '{}'\n\tdoes not contain any time steps so it will not be in the " "conjoined file.\n", part); local_mesh[p - 1].isActive = false; } else { std::vector times(nts); ex_get_all_times(id, times.data()); // A database will include all times from step 0 up to the // last time that is less than t_min on the following database. // Note that we are iterating through the databases from last to first... // Check delta between the last used timestep on this database // and the first time on the previous (later in time) database // (which is t_min). It must be > user-specified minimum delta. // Set by -interpart_minimum_time_delta {delta} command line option. // NOTE that this is not the delta between individual timesteps // within a database, it is the delta between the last timestep // on one database and the first on the next database. int i = 0; for (i = nts; i > 0; i--) { if (times[i - 1] < t_min) { if (used || t_min - times[i - 1] >= interFace.interpart_minimum_time_delta()) { used = true; global_times.push_back(TimeStepMap(p - 1, i - 1, times[i - 1])); } } } local_mesh[p - 1].timestepCount = i; t_min = t_min < times[0] ? t_min : times[0]; if (!used) { std::string part = "Part " + std::to_string(p) + ": "; part += interFace.inputFiles_[p - 1]; fmt::print(stderr, "\nWARNING: '{}'\n\tdoes not contain any time steps which will be used in " "conjoined file.\n" "\tCurrent minimum time = {}, timestep range on this part is {} to {}\n", part, t_min, times[0], times[nts - 1]); local_mesh[p - 1].isActive = false; } } } global.timestepCount = global_times.size(); std::reverse(global_times.begin(), global_times.end()); // Need these throughout run, so declare outside of this block... // TODO(gdsjaar): Add these to the "Mesh" class. std::vector glob_blocks(global.count(Excn::ObjectType::EBLK)); std::vector> blocks(part_count); std::vector> glob_ssets; std::vector>> sidesets(part_count); std::vector> glob_nsets; std::vector>> nodesets(part_count); { // Now, build the reverse global node map which permits access of the // local id given the global id. std::vector global_node_map; if (interFace.ignore_coordinates()) { build_reverse_node_map(local_mesh, &global, part_count, global_node_map); } else { GlobalMap global_node_coord_map; build_reverse_node_map(local_mesh, &global, part_count, global_node_coord_map); global_node_map.reserve(global_node_coord_map.size()); for (const auto &ni : global_node_coord_map) { global_node_map.push_back(ni.id); } } if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } // **************************************************************************** // 5. Get Block information including element attributes // must check for zero length blocks get_element_blocks(local_mesh, global, blocks, glob_blocks); std::vector> global_element_map(global.count(Excn::ObjectType::ELEM)); build_reverse_element_map(local_mesh, blocks, glob_blocks, &global, part_count, global_element_map); // // NOTE: Node set/side set information can be different for each part /************************************************************************/ // 7. Get Side sets if (!interFace.omit_sidesets()) { if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } get_sideset_metadata(local_mesh, sidesets, glob_ssets); if (global.count(Excn::ObjectType::SSET) != glob_ssets.size()) { global.sidesetCount = glob_ssets.size(); } } /************************************************************************/ // 6. Get Node sets if (!interFace.omit_nodesets()) { if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } get_nodesets(global.count(Excn::ObjectType::NODE), local_mesh, nodesets, glob_nsets); if (global.count(Excn::ObjectType::NSET) != glob_nsets.size()) { global.nodesetCount = glob_nsets.size(); } } /************************************************************************/ // Start writing the output file... if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } Excn::CommunicationMetaData comm_data; // Create the output file... Excn::ExodusFile::create_output(interFace); put_mesh_summary(global); get_put_qa(Excn::ExodusFile(0), Excn::ExodusFile::output(), global_times, interFace); Excn::Internals exodus(Excn::ExodusFile::output(), Excn::ExodusFile::max_name_length()); exodus.write_meta_data(global, glob_blocks, glob_nsets, glob_ssets, comm_data); // Output bulk mesh data.... put_nodesets(glob_nsets); // c.2. Write Global Node Number Map if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } error = ex_put_id_map(Excn::ExodusFile::output(), EX_NODE_MAP, global_node_map.data()); if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } if (!global_element_map.empty()) { std::vector global_map(global.count(Excn::ObjectType::ELEM)); for (size_t i = 0; i < global.count(Excn::ObjectType::ELEM); i++) { global_map[i] = global_element_map[i].first; } ex_put_id_map(Excn::ExodusFile::output(), EX_ELEM_MAP, global_map.data()); } T dummy = 0; put_element_blocks(local_mesh, blocks, glob_blocks, dummy); get_put_sidesets(glob_ssets); } // ************************************************************************ // 2. Get Coordinate Info. { if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } error += get_put_coordinates(global, part_count, local_mesh, (T)0); if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } fmt::print("Wrote coordinate information...\n"); } // ####################TRANSIENT DATA SECTION########################### // *********************************************************************** // 9. Get Variable Information and names if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } // I. read number of variables for each type. Note that exodusII does not // provide for history variables // NOTE: it is assumed that every part has the same global, nodal, // and element lists bool add_n_status = interFace.nodal_status_variable() != "NONE"; bool add_e_status = interFace.element_status_variable() != "NONE"; Excn::Variables global_vars(Excn::ObjectType::GLOBAL); Excn::Variables nodal_vars(Excn::ObjectType::NODE, add_n_status); Excn::Variables element_vars(Excn::ObjectType::EBLK, add_e_status); Excn::Variables nodeset_vars(Excn::ObjectType::NSET); Excn::Variables sideset_vars(Excn::ObjectType::SSET); { Excn::ExodusFile id(0); get_variable_params(id, global_vars, interFace.global_var_names()); get_variable_params(id, nodal_vars, interFace.node_var_names()); get_variable_params(id, element_vars, interFace.elem_var_names()); get_variable_params(id, nodeset_vars, interFace.nset_var_names()); get_variable_params(id, sideset_vars, interFace.sset_var_names()); get_truth_table(global, blocks, glob_blocks, element_vars, 4); filter_truth_table(id, global, glob_blocks, element_vars, interFace.elem_var_names()); get_truth_table(global, nodesets, glob_nsets, nodeset_vars, 32); filter_truth_table(id, global, glob_nsets, nodeset_vars, interFace.nset_var_names()); get_truth_table(global, sidesets, glob_ssets, sideset_vars, 16); filter_truth_table(id, global, glob_ssets, sideset_vars, interFace.sset_var_names()); } // There is a slightly tricky situation here. The truthTable block order // is based on the ordering of the blocks on the input databases. // These blocks may have been reordered on output to make the 'offset' // variables line up correctly when the element ids are mapped back to // the "overall global" order. There is not much problem since most // calls outputting block-related items pass the id and don't rely // on ordering. However, the truth table is one of the exceptions // and we need to reorder the truth table to match the output block // order. After this call, we can use the original ordering, so just // need a temporary vector here... if (global_vars.count(Excn::InOut::OUT_) + nodal_vars.count(Excn::InOut::OUT_) + element_vars.count(Excn::InOut::OUT_) + nodeset_vars.count(Excn::InOut::OUT_) + sideset_vars.count(Excn::InOut::OUT_) > 0) { std::vector elem_truth_table; std::vector nset_truth_table; std::vector sset_truth_table; create_output_truth_table(glob_blocks, element_vars, elem_truth_table); create_output_truth_table(glob_nsets, nodeset_vars, nset_truth_table); create_output_truth_table(glob_ssets, sideset_vars, sset_truth_table); ex_put_all_var_param(Excn::ExodusFile::output(), global_vars.count(Excn::InOut::OUT_), nodal_vars.count(Excn::InOut::OUT_), element_vars.count(Excn::InOut::OUT_), elem_truth_table.data(), nodeset_vars.count(Excn::InOut::OUT_), nset_truth_table.data(), sideset_vars.count(Excn::InOut::OUT_), sset_truth_table.data()); } // II. read/write the variable names int combined_status_variable_index = 0; { Excn::ExodusFile id(0); get_put_variable_names(id, Excn::ExodusFile::output(), global_vars, interFace); get_put_variable_names(id, Excn::ExodusFile::output(), nodal_vars, interFace); get_put_variable_names(id, Excn::ExodusFile::output(), element_vars, interFace, &combined_status_variable_index); get_put_variable_names(id, Excn::ExodusFile::output(), nodeset_vars, interFace); get_put_variable_names(id, Excn::ExodusFile::output(), sideset_vars, interFace); } ex_update(Excn::ExodusFile::output()); /**********************************************************************/ // 10. Get Transient Data // This routine reads in a time dump from an EXODUSII file size_t num_time_steps = global.count(Excn::ObjectType::TIME); if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } std::vector global_values(global_vars.count(Excn::InOut::IN_)); std::vector output_global_values(global_vars.count(Excn::InOut::OUT_)); // Determine maximum number of entities on any part auto max_ent = find_max_entity_count(part_count, local_mesh, global, blocks, nodesets, sidesets); std::vector values(max_ent); // Stage II. Extracting transient variable data. // loop over time steps // Determine if user wants a subset of timesteps transferred to the output file. // Time steps for output file double start_time = seacas_timer(); // Used for output formatting at end of loop. int percent_width = 0; int field_width = 3; if (num_time_steps > 100) { percent_width = 1; field_width = 5; } int element_width = get_width(global.count(Excn::ObjectType::ELEM)); int step_width = get_width(num_time_steps); step_width += step_width / 3; // For commas -- 1,234,456 int part_width = get_width(part_count + 1); int loc_step_width = 0; for (auto &global_time : global_times) { if (loc_step_width < global_time.localStepNumber + 1) { loc_step_width = global_time.localStepNumber + 1; } } loc_step_width = get_width(loc_step_width); loc_step_width += loc_step_width / 3; size_t time_step_out = 0; for (size_t time_step = 0; time_step < num_time_steps; time_step++) { time_step_out++; T time_val = 0; size_t p = global_times[time_step].partNumber; Excn::ExodusFile id(p); // read in and write out the time step information error += ex_get_time(id, global_times[time_step].localStepNumber + 1, &time_val); SMART_ASSERT(time_val == global_times[time_step].timeValue) (time_step)(time_val)(global_times[time_step].timeValue); error += ex_put_time(Excn::ExodusFile::output(), time_step_out, &time_val); if (global_vars.count(Excn::InOut::OUT_) > 0) { if (debug_level & 1) { fmt::print("{}Global Variables...\n", time_stamp(tsFormat)); } error += ex_get_var(id, global_times[time_step].localStepNumber + 1, EX_GLOBAL, 0, 0, global_vars.count(), (void *)global_values.data()); // Map ... for (int ig = 0; ig < global_vars.count(Excn::InOut::IN_); ig++) { if (global_vars.index_[ig] > 0) { SMART_ASSERT(ig < (int)global_values.size()); output_global_values[global_vars.index_[ig] - 1] = global_values[ig]; } } error += ex_put_var(Excn::ExodusFile::output(), time_step_out, EX_GLOBAL, 1, 0, global_vars.count(Excn::InOut::OUT_), output_global_values.data()); } // ======================================================================== // Nodal Values... if (debug_level & 1) { fmt::print("{}Nodal Variables...\n", time_stamp(tsFormat)); } if (nodal_vars.count(Excn::InOut::OUT_) > 0) { size_t node_count = local_mesh[p].count(Excn::ObjectType::NODE); std::vector master_nodal_values(global.count(Excn::ObjectType::NODE)); int offset = nodal_vars.addStatus ? 1 : 0; for (int i = 0; i < nodal_vars.count(Excn::InOut::OUT_) - offset; i++) { // Last output variable may be status for (int j = 0; j < nodal_vars.count(Excn::InOut::IN_); j++) { if (nodal_vars.index_[j] - 1 == i) { std::fill(master_nodal_values.begin(), master_nodal_values.end(), T(0.0)); error += ex_get_var(id, global_times[time_step].localStepNumber + 1, EX_NODAL, j + 1, 0, node_count, values.data()); // copy values to master nodal value information for (size_t jj = 0; jj < node_count; jj++) { // Map local nodal value to global location... size_t nodal_value = local_mesh[p].localNodeToGlobal[jj]; master_nodal_values[nodal_value] = values[jj]; } error += ex_put_var(Excn::ExodusFile::output(), time_step_out, EX_NODAL, i + 1, 0, global.count(Excn::ObjectType::NODE), master_nodal_values.data()); break; } } // Fill "node_status" variable -- 'alive' for alive; 1-alive for dead. // It is the last output variable... if (nodal_vars.addStatus) { SMART_ASSERT(alive == 0.0 || alive == 1.0)(alive); std::fill(master_nodal_values.begin(), master_nodal_values.end(), T((1.0 - alive))); for (size_t j = 0; j < node_count; j++) { // Map local nodal value to global location... size_t nodal_value = local_mesh[p].localNodeToGlobal[j]; master_nodal_values[nodal_value] = alive; } error += ex_put_var(Excn::ExodusFile::output(), time_step_out, EX_NODAL, nodal_vars.count(Excn::InOut::OUT_), 0, global.count(Excn::ObjectType::NODE), master_nodal_values.data()); } } } // ======================================================================== // Extracting element transient variable data if (debug_level & 1) { fmt::print("{}Element Variables...\n", time_stamp(tsFormat)); } if (element_vars.count(Excn::InOut::IN_) > 0) { read_write_master_values(element_vars, global, glob_blocks, local_mesh, blocks, values, p, id, global_times[time_step].localStepNumber, time_step_out); } // Add element status variable... // Use the output time step for writing data if (interFace.element_status_variable() != "NONE") { add_status_variable(Excn::ExodusFile::output(), global, blocks[p], glob_blocks, local_mesh[p].localElementToGlobal, time_step_out, element_vars.index_[element_vars.count(Excn::InOut::IN_)], alive, combined_status_variable_index); } // ======================================================================== // Extracting sideset transient variable data if (debug_level & 1) { fmt::print("{}Sideset Variables...\n", time_stamp(tsFormat)); } if (sideset_vars.count(Excn::InOut::IN_) > 0) { read_write_master_values(sideset_vars, global, glob_ssets, local_mesh, sidesets, values, p, id, global_times[time_step].localStepNumber, time_step_out); } // ======================================================================== // Extracting nodeset transient variable data if (debug_level & 1) { fmt::print("{}Nodeset Variables...\n", time_stamp(tsFormat)); } if (nodeset_vars.count(Excn::InOut::IN_) > 0) { read_write_master_values(nodeset_vars, global, glob_nsets, local_mesh, nodesets, values, p, id, global_times[time_step].localStepNumber, time_step_out); } // ======================================================================== ex_update(Excn::ExodusFile::output()); if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } fmt::print("Step {:{}}/{}, time {:.4e} (Part {:{}}/{}, step {:{}}) Active Elem: {:{}}", fmt::group_digits(time_step + 1), step_width, fmt::group_digits(num_time_steps), time_val, p + 1, part_width, part_count, fmt::group_digits(global_times[time_step].localStepNumber + 1), loc_step_width, fmt::group_digits(local_mesh[p].count(Excn::ObjectType::ELEM)), element_width); double cur_time = seacas_timer(); double elapsed = cur_time - start_time; double time_per_step = elapsed / time_step_out; double percentage_done = (time_step_out * 100.0) / global.count(Excn::ObjectType::TIME); double estimated_remaining = time_per_step * (global.count(Excn::ObjectType::TIME) - time_step_out); fmt::print(" [{:{}.{}f}%, Elapsed={}, ETA={}]\n", percentage_done, field_width, percent_width, format_time(elapsed), format_time(estimated_remaining)); if (debug_level & 1) { fmt::print("\n"); } } /*************************************************************************/ // EXIT program if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } fmt::print("******* END *******\n"); return error; } namespace { template void get_put_qa(int id, int id_out, const std::vector> & /*global_times*/, Excn::SystemInterface & /*interFace*/) { // NOTE: Assuming info and QA records for all parts int error = 0; int info_string_len = MAX_LINE_LENGTH; size_t num_info_records = ex_inquire_int(id, EX_INQ_INFO); // size_t extra_info = global_times.size() + 2 + 1; size_t extra_info = 2 + 1; char **info_records = get_name_array(num_info_records + extra_info, info_string_len); if (num_info_records > 0) { error += ex_get_info(id, info_records); } // Add an info record for CONJOIN add_info_record(info_records[num_info_records], MAX_LINE_LENGTH); #if 0 // Add time/part mapping... for (size_t i=0; i < global_times.size(); i++) { std::ostringstream os; fmt::print(os, "Step {:2}, time {:.4e} (Part {}, step {}) File: {}", i+1, global_times[i].timeValue, global_times[i].partNumber+1, global_times[i].localStepNumber+1, interFace.inputFiles_[global_times[i].partNumber]); copy_string(info_records[num_info_records+1+i], os.str(), MAX_LINE_LENGTH + 1); } #endif error += ex_put_info(id_out, num_info_records + extra_info, info_records); free_name_array(info_records, num_info_records + extra_info); // II. Get and store QA records, if they exist struct qa_element { char *qa_record[1][4]; }; int num_qa_records = ex_inquire_int(id, EX_INQ_QA); auto qaRecord = new qa_element[num_qa_records + 1]; for (int i = 0; i < num_qa_records + 1; i++) { for (int j = 0; j < 4; j++) { qaRecord[i].qa_record[0][j] = new char[MAX_STR_LENGTH + 1]; qaRecord[i].qa_record[0][j][0] = '\0'; } } if (num_qa_records) { error += ex_get_qa(id, qaRecord[0].qa_record); } char buffer[MAX_STR_LENGTH + 1]; copy_string(qaRecord[num_qa_records].qa_record[0][0], qainfo[0], MAX_STR_LENGTH + 1); // Code copy_string(qaRecord[num_qa_records].qa_record[0][1], qainfo[2], MAX_STR_LENGTH + 1); // Version time_t date_time = time(nullptr); strftime(buffer, MAX_STR_LENGTH, "%Y/%m/%d", localtime(&date_time)); copy_string(qaRecord[num_qa_records].qa_record[0][2], buffer, MAX_STR_LENGTH + 1); strftime(buffer, MAX_STR_LENGTH, "%H:%M:%S", localtime(&date_time)); copy_string(qaRecord[num_qa_records].qa_record[0][3], buffer, MAX_STR_LENGTH + 1); error += ex_put_qa(id_out, num_qa_records + 1, qaRecord[0].qa_record); for (int i = 0; i < num_qa_records + 1; i++) { for (int j = 0; j < 4; j++) { delete[] qaRecord[i].qa_record[0][j]; } } delete[] qaRecord; } template int get_put_coordinates(Excn::Mesh &global, size_t part_count, std::vector> &local_mesh, T /* dummy */) { SMART_ASSERT(sizeof(T) == Excn::ExodusFile::io_word_size()); std::vector x(global.count(Excn::ObjectType::NODE)); std::vector y(global.count(Excn::ObjectType::NODE)); std::vector z(global.count(Excn::ObjectType::NODE)); if (debug_level & 8) { const T FILL_VALUE = FLT_MAX; std::fill(x.begin(), x.end(), FILL_VALUE); std::fill(y.begin(), y.end(), FILL_VALUE); std::fill(z.begin(), z.end(), FILL_VALUE); } int error = 0; for (size_t p = 0; p < part_count; p++) { error += get_coordinates(Excn::ExodusFile(p), global.count(Excn::ObjectType::DIM), local_mesh[p].count(Excn::ObjectType::NODE), local_mesh[p].localNodeToGlobal, p, x, y, z); } // end for p=0..part_count // Get Coordinate Names // NOTE: Assuming coordinate names should be // the same for all files/parts therefore, only one // file/part needs to be loaded error += get_put_coordinate_names(Excn::ExodusFile(0), Excn::ExodusFile::output(), global.count(Excn::ObjectType::DIM)); // Write out coordinate information error += ex_put_coord(Excn::ExodusFile::output(), x.data(), y.data(), z.data()); return error; } int get_put_coordinate_names(int in, int out, int dimensionality) { int error = 0; char **coordinate_names = get_name_array(dimensionality, Excn::ExodusFile::max_name_length()); error += ex_get_coord_names(in, coordinate_names); error += ex_put_coord_names(out, coordinate_names); fmt::print("Wrote coordinate names...\n"); free_name_array(coordinate_names, dimensionality); return error; } template int get_coordinates(int id, int dimensionality, size_t num_nodes, std::vector local_node_to_global, size_t part, std::vector &x, std::vector &y, std::vector &z) { SMART_ASSERT(sizeof(T) == Excn::ExodusFile::io_word_size()); SMART_ASSERT(local_node_to_global.size() == num_nodes); int error = 0; std::vector local_x(num_nodes); std::vector local_y(num_nodes); std::vector local_z(num_nodes); error += ex_get_coord(id, local_x.data(), local_y.data(), local_z.data()); // Check for 2D or 3D coordinates if (dimensionality == 3) { if (debug_level & 8) { for (size_t i = 0; i < num_nodes; i++) { INT node = local_node_to_global[i]; const T FILL_VALUE = FLT_MAX; if (x[node] != FILL_VALUE && y[node] != FILL_VALUE && z[node] != FILL_VALUE) { if (!approx_equal(x[node], local_x[i]) || !approx_equal(y[node], local_y[i]) || !approx_equal(z[node], local_z[i])) { fmt::print( stderr, "\nWARNING: Node {} has different coordinates in at least two parts.\n" " this may indicate that this id has been reused in the current part.\n" " cur value = {:14.6e} {:14.6e} {:14.6e}\n" " new value = {:14.6e} {:14.6e} {:14.6e} from part {}\n", node + 1, x[node], y[node], z[node], local_x[i], local_y[i], local_z[i], part); } } } } for (size_t i = 0; i < num_nodes; i++) { // The following WILL overwrite x[node],y[node],z[node] if node is the // same for different parts INT node = local_node_to_global[i]; x[node] = local_x[i]; y[node] = local_y[i]; z[node] = local_z[i]; } } else { if (debug_level & 8) { for (size_t i = 0; i < num_nodes; i++) { INT node = local_node_to_global[i]; const T FILL_VALUE = FLT_MAX; if (x[node] != FILL_VALUE && y[node] != FILL_VALUE) { if (!approx_equal(x[node], local_x[i]) || !approx_equal(y[node], local_y[i])) { fmt::print( stderr, "\nWARNING: Node {} has different coordinates in at least two parts.\n" " this may indicate that this id has been reused in the current part.\n" " cur value = {:14.6e} {:14.6e}\n" " new value = {:14.6e} {:14.6e} from part {}\n", node + 1, x[node], y[node], local_x[i], local_y[i], part); } } } } for (size_t i = 0; i < num_nodes; i++) { // The following WILL overwrite x[node],y[node] if node is the same for // different parts INT node = local_node_to_global[i]; x[node] = local_x[i]; y[node] = local_y[i]; } } return error; } template void get_element_blocks(const std::vector> &local_mesh, const Excn::Mesh &global, std::vector> &blocks, std::vector &glob_blocks) { size_t part_count = local_mesh.size(); for (size_t ip = 0; ip < part_count; ip++) { blocks[ip].resize(local_mesh[ip].count(Excn::ObjectType::EBLK)); } std::vector block_id(global.count(Excn::ObjectType::EBLK)); int error = 0; for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); error += ex_get_ids(id, EX_ELEM_BLOCK, block_id.data()); if (p == 0) { for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { glob_blocks[b].id = block_id[b]; } } for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { ex_block block_param{}; block_param.id = glob_blocks[b].id; block_param.type = EX_ELEM_BLOCK; error += ex_get_block_param(id, &block_param); std::vector name(Excn::ExodusFile::max_name_length() + 1); ex_get_name(id, EX_ELEM_BLOCK, glob_blocks[b].id, name.data()); blocks[p][b].id = glob_blocks[b].id; if (name[0] != '\0') { blocks[p][b].name_ = name.data(); if (p == 0) { glob_blocks[b].name_ = name.data(); } } // Find position of this block id in the local mesh for (size_t lb = 0; lb < global.count(Excn::ObjectType::EBLK); lb++) { if (block_id[lb] == glob_blocks[b].id) { blocks[p][b].position_ = lb; break; } } if (block_param.num_entry > 0) { blocks[p][b].elementCount = block_param.num_entry; blocks[p][b].nodesPerElement = block_param.num_nodes_per_entry; blocks[p][b].attributeCount = block_param.num_attribute; blocks[p][b].offset_ = block_param.num_entry; blocks[p][b].elType = block_param.topology; // NOTE: This is not correct, but fixed below. glob_blocks[b].elementCount += block_param.num_entry; if (glob_blocks[b].nodesPerElement == 0) { glob_blocks[b].nodesPerElement = block_param.num_nodes_per_entry; } if (glob_blocks[b].attributeCount == 0) { glob_blocks[b].attributeCount = block_param.num_attribute; } glob_blocks[b].position_ = b; glob_blocks[b].elType = block_param.topology; } if (block_param.num_attribute > 0 && glob_blocks[b].attributeNames.empty()) { // Get attribute names. Assume the same on all parts // on which the block exists. char **names = get_name_array(block_param.num_attribute, Excn::ExodusFile::max_name_length()); ex_get_attr_names(id, EX_ELEM_BLOCK, block_id[b], names); for (int i = 0; i < block_param.num_attribute; i++) { glob_blocks[b].attributeNames.emplace_back(names[i]); } free_name_array(names, block_param.num_attribute); } } } // end for p=0..part_count // Convert block_offset from elements/block/part to true offset for (size_t p = 0; p < part_count; p++) { if (debug_level & 4) { fmt::print("\nElement block info for part {}...\n", p); } // Number of elements per block in local block order... std::vector local_order_entity_count(global.count(Excn::ObjectType::EBLK)); for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { int local_order = blocks[p][b].position_; local_order_entity_count[local_order] = blocks[p][b].entity_count(); } size_t sum = 0; for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { size_t save = local_order_entity_count[b]; local_order_entity_count[b] = sum; sum += save; } for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { int local_order = blocks[p][b].position_; blocks[p][b].offset_ = local_order_entity_count[local_order]; if (debug_level & 4) { fmt::print("\tBlock {}, Id = {}, Name = '{}', Elements = {:12}, Nodes/element = {}, " "Attributes = {}, Position = {}, Offset = {}\n", b, glob_blocks[b].id, blocks[p][b].name_, fmt::group_digits(blocks[p][b].entity_count()), blocks[p][b].nodesPerElement, blocks[p][b].attributeCount, blocks[p][b].position_, blocks[p][b].offset_); } } } } template void put_element_blocks(std::vector> &local_mesh, std::vector> &blocks, std::vector &glob_blocks, T /* dummy */) { SMART_ASSERT(sizeof(T) == Excn::ExodusFile::io_word_size()); int global_num_blocks = glob_blocks.size(); if (debug_level & 1) { fmt::print("{}", time_stamp(tsFormat)); } fmt::print("\nReading and Writing element connectivity & attributes\n"); for (int b = 0; b < global_num_blocks; b++) { if (debug_level & 4) { fmt::print("\nOutput element block info for...\n" "Block {}, Id = {}, Name = '{}', Elements = {:12}, Nodes/element = {}, " "Attributes = {}\n", b, glob_blocks[b].id, glob_blocks[b].name_, fmt::group_digits(glob_blocks[b].entity_count()), glob_blocks[b].nodesPerElement, glob_blocks[b].attributeCount); } if (debug_level & 4) { fmt::print("B{}:\t", b); } size_t max_nodes = glob_blocks[b].entity_count(); max_nodes *= glob_blocks[b].nodesPerElement; std::vector block_linkage(max_nodes); // Initialize attributes list, if it exists std::vector attributes(glob_blocks[b].attributeCount * glob_blocks[b].entity_count()); int error = 0; size_t part_count = local_mesh.size(); for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); size_t global_pos; size_t global_block_pos; if (blocks[p][b].entity_count() > 0) { // non-zero length block if (debug_level & 4) { fmt::print("#"); } size_t maximum_nodes = blocks[p][b].entity_count(); maximum_nodes *= blocks[p][b].nodesPerElement; std::vector local_linkage(maximum_nodes); INT bid = blocks[p][b].id; error = ex_get_conn(id, EX_ELEM_BLOCK, bid, local_linkage.data(), nullptr, nullptr); if (error < 0) { fmt::print(stderr, "ERROR: Cannot get element block connectivity for block {} on part {}.\n", bid, p); } size_t pos = 0; size_t goffset = glob_blocks[b].offset_; size_t element_count = blocks[p][b].entity_count(); size_t boffset = blocks[p][b].offset_; size_t npe = blocks[p][b].nodesPerElement; INT *part_loc_elem_to_global = local_mesh[p].localElementToGlobal.data(); INT *part_loc_node_to_global = local_mesh[p].localNodeToGlobal.data(); for (size_t e = 0; e < element_count; e++) { global_block_pos = part_loc_elem_to_global[(e + boffset)] - goffset; global_pos = global_block_pos * npe; for (size_t n = 0; n < npe; n++) { size_t node = part_loc_node_to_global[local_linkage[pos++] - 1]; if (debug_level & 4) { SMART_ASSERT(block_linkage[global_pos] == (int)node + 1 || block_linkage[global_pos] == 0); } block_linkage[global_pos++] = node + 1; } } // Get attributes list, if it exists if (blocks[p][b].attributeCount > 0) { SMART_ASSERT(blocks[p][b].attributeCount == glob_blocks[b].attributeCount) (p)(b)(blocks[p][b].attributeCount)(glob_blocks[b].attributeCount); size_t max_attr = blocks[p][b].entity_count() * blocks[p][b].attributeCount; std::vector local_attr(max_attr); error += ex_get_attr(id, EX_ELEM_BLOCK, blocks[p][b].id, local_attr.data()); pos = 0; size_t att_count = blocks[p][b].attributeCount; for (size_t e = 0; e < element_count; e++) { // global_pos is global position within this element block... global_block_pos = local_mesh[p].localElementToGlobal[(e + boffset)] - goffset; global_pos = global_block_pos * att_count; for (size_t n = 0; n < att_count; n++) { attributes[global_pos++] = local_attr[pos++]; } } } } // end if blocks[p][b].entity_count() (non-zero length block) else if (debug_level & 4) { fmt::print("."); } } // end for p=0..part_count-1 // Verify that connectivity has been set for all elements... for (size_t i = 0; i < block_linkage.size(); i++) { SMART_ASSERT(block_linkage[i] > 0)(block_linkage[i])(i)(b)(glob_blocks[b].id); } // Write out block info int id_out = Excn::ExodusFile::output(); // output file identifier if (!block_linkage.empty()) { error += ex_put_conn(id_out, EX_ELEM_BLOCK, glob_blocks[b].id, block_linkage.data(), nullptr, nullptr); } // Write out attributes list if it exists if (glob_blocks[b].attributeCount > 0) { error += ex_put_attr(id_out, EX_ELEM_BLOCK, glob_blocks[b].id, attributes.data()); } // end for b=0..global_num_blocks-1 if (debug_level & 4) { fmt::print("\n"); } } fmt::print("\n"); } template void build_reverse_element_map(std::vector> &local_mesh, std::vector> &blocks, std::vector &glob_blocks, Excn::Mesh *global, size_t part_count, std::vector> &global_element_map) { int error = 0; // Create the map that maps from a local part element to the // global map. This combines the mapping local part element to // 'global id' and then 'global id' to global position. The // mapping is now a direct lookup instead of a lookup followed by // a reverse map. // // We iterate through each element block a part at a time to // determine which elements are in each block. Note that an // element will possibly exist in multiple parts, so we need to do // the sort/uniquify/shrink on each element block. The operations // for each element block will be: // 1. get elements for the block for each part into a vector. // 2. sort/uniquify/shrink that vector and copy into the // 'global_element_map' in the positions following the previous block. // // This is similar to what was done above in a global sense, // except it is done an element block at a time. The only need for // doing them both is that it lets us detect whether an element id // was reused and appears in both blocks. Currently, just error // out if that happens... In the future, change the id to an // unused value and continue.... size_t tot_size = 0; std::vector>> global_element_numbers(part_count); for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); global_element_numbers[p].resize(local_mesh[p].count(Excn::ObjectType::ELEM)); std::vector ids(local_mesh[p].count(Excn::ObjectType::ELEM)); error += ex_get_id_map(id, EX_ELEM_MAP, ids.data()); for (size_t i = 0; i < local_mesh[p].count(Excn::ObjectType::ELEM); i++) { global_element_numbers[p][i] = std::make_pair(ids[i], size_t(0)); } tot_size += local_mesh[p].count(Excn::ObjectType::ELEM); } global_element_map.resize(tot_size); size_t goffset = 0; for (auto &glob_block : glob_blocks) { size_t block_size = 0; for (size_t p = 0; p < part_count; p++) { size_t lb = 0; for (; lb < blocks[0].size(); lb++) { if (glob_block.id == blocks[p][lb].id) { break; } } SMART_ASSERT(glob_block.id == blocks[p][lb].id); block_size += blocks[p][lb].entity_count(); } std::vector> block_element_map(block_size); size_t poffset = 0; for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); size_t lb = 0; for (; lb < blocks[0].size(); lb++) { if (glob_block.id == blocks[p][lb].id) { break; } } SMART_ASSERT(glob_block.id == blocks[p][lb].id); // Get connectivity array for this element block... size_t element_count = blocks[p][lb].entity_count(); size_t nodes_per_elem = blocks[p][lb].nodesPerElement; size_t maximum_nodes = element_count * nodes_per_elem; std::vector local_linkage(maximum_nodes); size_t bid = blocks[p][lb].id; error = ex_get_conn(id, EX_ELEM_BLOCK, bid, local_linkage.data(), nullptr, nullptr); if (error < 0) { fmt::print(stderr, "ERROR: Cannot get element block connectivity for block {} on part {}.\n", bid, p); } // Convert connectivity to global node numbers. for (size_t i = 0; i < element_count * nodes_per_elem; i++) { INT local_node = local_linkage[i]; INT global_node = local_mesh[p].localNodeToGlobal[local_node - 1] + 1; local_linkage[i] = global_node; } // Have element global ids for all elements in this block, // and connectivity. Can now create out "eleminfo" for these elements. size_t con_offset = 0; size_t boffset = blocks[p][lb].offset_; for (size_t i = 0; i < element_count; i++) { size_t adler_crc = adler(0, &local_linkage[con_offset], nodes_per_elem * sizeof(int)); global_element_numbers[p][boffset + i].second = adler_crc; block_element_map[poffset + i] = global_element_numbers[p][boffset + i]; con_offset += nodes_per_elem; } poffset += element_count; } // Sort, uniquify, shrink 'block_element_map' and the result // then contains the list of elements in this block... uniquify(block_element_map); size_t block_total_num_elements = block_element_map.size(); glob_block.elementCount = block_total_num_elements; glob_block.offset_ = goffset; // Copy into the global_element_map... std::copy(block_element_map.begin(), block_element_map.end(), &global_element_map[goffset]); goffset += block_total_num_elements; } global->elementCount = goffset; global_element_map.resize(goffset); size_t max_id = global_element_map[global->elementCount - 1].first; bool is_contiguous = max_id == global_element_map.size(); // fmt::print("Element id map {}.\n", (is_contiguous ? "is" : "is not")); // The global_element_map may or may not be globally sorted; however, each // block is sorted, so if we do the iteration by blocks, we can // use lower_bound instead of doing global searches... for (auto &glob_block : glob_blocks) { auto gm_begin = global_element_map.begin() + glob_block.offset_; auto gm_end = gm_begin + glob_block.elementCount; auto cur_pos = gm_begin; for (size_t p = 0; p < part_count; p++) { size_t lb = 0; for (; lb < blocks[0].size(); lb++) { if (glob_block.id == blocks[p][lb].id) { break; } } SMART_ASSERT(glob_block.id == blocks[p][lb].id); size_t element_count = blocks[p][lb].entity_count(); size_t boffset = blocks[p][lb].offset_; for (size_t i = 0; i < element_count; i++) { std::pair global_element = global_element_numbers[p][boffset + i]; if (cur_pos == gm_end || *cur_pos != global_element) { auto iter = std::lower_bound(gm_begin, gm_end, global_element); SMART_ASSERT(iter != gm_end); cur_pos = iter; } size_t element_value = cur_pos - gm_begin; local_mesh[p].localElementToGlobal[i + boffset] = element_value + glob_block.offset_; ++cur_pos; } } } // Update the element ids to give a unique, non-repeating set. If // contiguous, then there is nothing to do. If not contiguous, // then need to determine if there are any repeats (id reuse) and // if so, generate a new id for the repeated uses. Note that // there is a possibility that elements in two or more element // blocks will have the same element id, so we generate a vector // containing a pair, sort it, // and then use it to detect duplicates and map them to a new id. if (!is_contiguous) { std::vector> id_pos(global_element_map.size()); for (size_t i = 0; i < global->elementCount; i++) { id_pos[i].first = global_element_map[i].first; id_pos[i].second = i; } #if USE_STD_SORT std::sort(id_pos.begin(), id_pos.end()); #else pdqsort(id_pos.begin(), id_pos.end()); #endif max_id = id_pos.back().first; // Check again for contiguous ids since we now have a sorted list... is_contiguous = max_id == global_element_map.size(); if (!is_contiguous) { int repeat_found = 0; size_t id_last = id_pos[0].first; for (size_t i = 1; i < global->elementCount; i++) { if (id_pos[i].first == id_last) { global_element_map[id_pos[i].second].first = ++max_id; repeat_found++; } else { id_last = id_pos[i].first; } } if (repeat_found > 0) { fmt::print(stderr, "WARNING: {} duplicate element ids were found. Their ids have been " "renumbered to remove duplicates.\n", repeat_found); } } } } template void build_reverse_node_map(std::vector> &local_mesh, Excn::Mesh *global, size_t part_count, GlobalMap &global_node_map) { // Append all local node maps to the global node map. // Sort the global node map // Remove duplicates. // Position within map is now the map... // When building the local-part node to global id, use binary_search... // Global node map and count. std::vector> global_nodes(part_count); size_t tot_size = 0; for (size_t p = 0; p < part_count; p++) { tot_size += local_mesh[p].count(Excn::ObjectType::NODE); global_nodes[p].resize(local_mesh[p].count(Excn::ObjectType::NODE)); } global_node_map.resize(tot_size); size_t offset = 0; for (size_t p = 0; p < part_count; p++) { std::vector x(local_mesh[p].count(Excn::ObjectType::NODE)); std::vector y(local_mesh[p].count(Excn::ObjectType::NODE)); std::vector z(local_mesh[p].count(Excn::ObjectType::NODE)); std::vector nid(local_mesh[p].count(Excn::ObjectType::NODE)); Excn::ExodusFile id(p); ex_get_id_map(id, EX_NODE_MAP, nid.data()); ex_get_coord(id, x.data(), y.data(), z.data()); for (size_t i = 0; i < local_mesh[p].count(Excn::ObjectType::NODE); i++) { global_nodes[p][i] = NodeInfo(nid[i], x[i], y[i], z[i]); } std::copy(global_nodes[p].begin(), global_nodes[p].end(), &global_node_map[offset]); offset += local_mesh[p].count(Excn::ObjectType::NODE); } // Now, sort the global_node_map array and remove duplicates... uniquify(global_node_map); global->nodeCount = global_node_map.size(); // See whether the node numbers are contiguous. If so, we can map // the nodes back to their original location. Since the nodes are // sorted and there are no duplicates, we just need to see if the id // at global_node_map.size() == global_node_map.size(); INT max_id = global_node_map[global->nodeCount - 1].id; bool is_contiguous = (int64_t)max_id == static_cast(global_node_map.size()); fmt::print("Node map {} contiguous.\n", (is_contiguous ? "is" : "is not")); // Create the map that maps from a local part node to the // global map. This combines the mapping local part node to // 'global id' and then 'global id' to global position. The // mapping is now a direct lookup instead of a lookup followed by // a reverse map. if (is_contiguous) { for (size_t p = 0; p < part_count; p++) { size_t node_count = local_mesh[p].count(Excn::ObjectType::NODE); for (size_t i = 0; i < node_count; i++) { const NodeInfo &global_node = global_nodes[p][i]; local_mesh[p].localNodeToGlobal[i] = global_node.id - 1; } } } else { auto cur_pos = global_node_map.begin(); for (size_t p = 0; p < part_count; p++) { size_t node_count = local_mesh[p].count(Excn::ObjectType::NODE); for (size_t i = 0; i < node_count; i++) { NodeInfo global_node = global_nodes[p][i]; if (cur_pos == global_node_map.end() || *cur_pos != global_node) { auto iter = std::lower_bound(global_node_map.begin(), global_node_map.end(), global_node); if (iter == global_node_map.end()) { NodeInfo n = global_node; fmt::print(stderr, "ERROR: Bad Node in build_reverse_node_map: {}\tat location: {}\t{}\t{}\n", n.id, n.x, n.y, n.z); exit(EXIT_FAILURE); } cur_pos = iter; } size_t nodal_value = cur_pos - global_node_map.begin(); local_mesh[p].localNodeToGlobal[i] = nodal_value; ++cur_pos; } } } // Update the nodal ids to give a unique, non-repeating set. If contiguous, then // there is nothing to do. If not contiguous, then need to determine if there are any // repeats (id reuse) and if so, generate a new id for the repeated uses. // A duplicate id would have the same id, but different x y z position. if (!is_contiguous) { bool repeat_found = false; size_t id_last = global_node_map[0].id; for (size_t i = 1; i < global->nodeCount; i++) { if (global_node_map[i].id == id_last) { global_node_map[i].id = ++max_id; repeat_found = true; } else { id_last = global_node_map[i].id; } } if (repeat_found) { fmt::print( stderr, "WARNING: Duplicate node ids were found. Their ids have been renumbered to remove " "duplicates. If the part meshes should be identical, maybe use the " "--ignore_coordinate option.\n"); } } } template void build_reverse_node_map(std::vector> &local_mesh, Excn::Mesh *global, size_t part_count, std::vector &global_node_map) { // Append all local node maps to the global node map. // Sort the global node map // Remove duplicates. // Position within map is now the map... // When building the local-part node to global id, use binary_search... // Global node map and count. std::vector> global_nodes(part_count); size_t tot_size = 0; for (size_t p = 0; p < part_count; p++) { tot_size += local_mesh[p].count(Excn::ObjectType::NODE); global_nodes[p].resize(local_mesh[p].count(Excn::ObjectType::NODE)); } global_node_map.resize(tot_size); size_t offset = 0; for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); ex_get_id_map(id, EX_NODE_MAP, global_nodes[p].data()); std::copy(global_nodes[p].begin(), global_nodes[p].end(), &global_node_map[offset]); offset += local_mesh[p].count(Excn::ObjectType::NODE); } // Now, sort the global_node_map array and remove duplicates... uniquify(global_node_map); global->nodeCount = global_node_map.size(); // See whether the node numbers are contiguous. If so, we can map // the nodes back to their original location. Since the nodes are // sorted and there are no duplicates, we just need to see if the id // at global_node_map.size() == global_node_map.size(); INT max_id = global_node_map[global->nodeCount - 1]; bool is_contiguous = (int64_t)max_id == static_cast(global_node_map.size()); fmt::print("Node map {} contiguous.\n", (is_contiguous ? "is" : "is not")); // Create the map that maps from a local part node to the // global map. This combines the mapping local part node to // 'global id' and then 'global id' to global position. The // mapping is now a direct lookup instead of a lookup followed by // a reverse map. if (is_contiguous) { for (size_t p = 0; p < part_count; p++) { size_t node_count = local_mesh[p].count(Excn::ObjectType::NODE); for (size_t i = 0; i < node_count; i++) { INT global_node = global_nodes[p][i]; local_mesh[p].localNodeToGlobal[i] = global_node - 1; } } } else { auto cur_pos = global_node_map.begin(); for (size_t p = 0; p < part_count; p++) { size_t node_count = local_mesh[p].count(Excn::ObjectType::NODE); for (size_t i = 0; i < node_count; i++) { INT global_node = global_nodes[p][i]; if (cur_pos == global_node_map.end() || *cur_pos != global_node) { auto iter = std::lower_bound(global_node_map.begin(), global_node_map.end(), global_node); if (iter == global_node_map.end()) { INT n = global_node; fmt::print(stderr, "ERROR: Bad Node in build_reverse_node_map: {}\n", n); exit(EXIT_FAILURE); } cur_pos = iter; } size_t nodal_value = cur_pos - global_node_map.begin(); local_mesh[p].localNodeToGlobal[i] = nodal_value; ++cur_pos; } } } } void get_put_variable_names(int id, int out, Excn::Variables &vars, Excn::SystemInterface &si, int *combined_status_variable_index) { if (vars.count(Excn::InOut::OUT_) > 0) { char **output_name_list = get_name_array(vars.count(Excn::InOut::OUT_), Excn::ExodusFile::max_name_length()); int num_vars = vars.index_.size(); int extra = vars.addStatus ? 1 : 0; int num_input_vars = num_vars - extra; char **input_name_list = get_name_array(num_vars, Excn::ExodusFile::max_name_length()); if (num_input_vars > 0) { int error = ex_get_variable_names(id, vars.type(), num_input_vars, input_name_list); if (error != EX_NOERR) { fmt::print(stderr, "ERROR: Cannot get {} variable names\n", vars.label()); exit(EXIT_FAILURE); } } std::string status; if (vars.type() == EX_ELEM_BLOCK || vars.type() == EX_NODAL) { if (vars.type() == EX_ELEM_BLOCK) { status = si.element_status_variable(); if (status != "NONE") { copy_string(input_name_list[num_vars - 1], status, Excn::ExodusFile::max_name_length() + 1); } } else if (vars.type() == EX_NODAL) { status = si.nodal_status_variable(); if (status != "NONE") { copy_string(input_name_list[num_vars - 1], status, Excn::ExodusFile::max_name_length() + 1); } } } // Iterate through the 'var_index' and transfer // Assume that the number of pointers is limited to // the number of results variables size_t maxlen = 0; for (int i = 0; i < num_vars; i++) { if (vars.index_[i] > 0) { copy_string(output_name_list[vars.index_[i] - 1], input_name_list[i], Excn::ExodusFile::max_name_length() + 1); if (strlen(input_name_list[i]) > maxlen) { maxlen = strlen(input_name_list[i]); } } } // See if any of the variable names conflict with the status variable name... if (status != "NONE") { for (size_t i = 0; i < static_cast(vars.count(Excn::InOut::OUT_)) - 1; i++) { if (case_compare(output_name_list[i], status) == 0) { // Error -- duplicate element variable names on output database. fmt::print(stderr, "\nERROR: A {} variable already exists on the input database with the " "same name as the status variable '{}'. This is not allowed.\n\n", vars.label(), status); exit(EXIT_FAILURE); } } } maxlen += 2; // Assume 8 characters for initial tab... int width = si.screen_width(); int nfield = (width - 8) / maxlen; if (nfield < 1) { nfield = 1; } fmt::print("Found {} {} variables.\n\t", vars.count(Excn::InOut::OUT_), vars.label()); { int i = 0; int ifld = 1; while (i < vars.count(Excn::InOut::OUT_)) { fmt::print("{:<{}}", output_name_list[i++], maxlen); if (++ifld > nfield && i < vars.count(Excn::InOut::OUT_)) { fmt::print("\n\t"); ifld = 1; } } fmt::print("\n\n"); } ex_put_variable_names(out, vars.type(), vars.count(Excn::InOut::OUT_), output_name_list); // KLUGE: Handle finding combined status index variable here since it is // the only place we have the list of output variable names... if (vars.type() == EX_ELEM_BLOCK && (combined_status_variable_index != nullptr)) { *combined_status_variable_index = 0; const std::string &comb_stat = si.combined_mesh_status_variable(); if (!comb_stat.empty()) { for (int i = 0; i < vars.count(Excn::InOut::OUT_); i++) { if (case_compare(comb_stat, output_name_list[i]) == 0) { *combined_status_variable_index = i + 1; break; } } } } free_name_array(output_name_list, vars.count(Excn::InOut::OUT_)); free_name_array(input_name_list, num_vars); } } void get_variable_params(int id, Excn::Variables &vars, const StringIdVector &variable_list) { // Determines the number of variables of type 'type()' that will // be written to the output database. The 'variable_list' vector // specifies a possibly empty list of variable names that the user // wants transferred to the output database. If 'variable_list' is // empty, then all variables of that type will be transferred; if // the 'variable_list' size is 1 and it contains the string 'NONE', // then no variables of that type will be transferred; if size is 1 // and it contains the string 'ALL', then all variables of that type // will be transferred. // // Returns the number of variables which will be output Also creates // a 'var_index'. The var_index is zero-based and of size // 'input_variable_count'. If: // var_index[i] ==0, variable not written to output database // var_index[i] > 0, variable written; is variable 'var_index[i]' // If 'type' is ELEMENT or NODE, then reserve space for the 'status' variable. int extra = vars.addStatus ? 1 : 0; int num_vars; ex_get_variable_param(id, vars.type(), &num_vars); vars.index_.resize(num_vars + extra); // Create initial index which defaults to no output... std::fill(vars.index_.begin(), vars.index_.end(), 0); // ...Except for the status variable (if any) if (extra == 1) { vars.index_[num_vars] = 1; } // If 'variable_list' is empty or specified 'ALL', then all // variables are to be output if (variable_list.empty() || (variable_list.size() == 1 && case_compare(variable_list[0].first, "all") == 0)) { std::iota(vars.index_.begin(), vars.index_.end(), 1); vars.outputCount = num_vars + extra; return; } // Another possibility is user specifies "NONE" for the variable // list so no variables will be written. Just return 0. if (variable_list.size() == 1 && case_compare(variable_list[0].first, "none") == 0) { vars.outputCount = extra; return; } // At this point, the variable_list specifies at least one // variable to be output to the database. // Get the list that the user entered and the list of files // from the input database... { StringVector exo_names = get_exodus_variable_names(id, vars.type(), num_vars); // Iterate 'variable_list' and find position in 'exo_names'. If // not found, there is an error -- set index to -1; otherwise set // index to position (1-based) in variable_list. Others are // already set to '0' by default initialization. // The variable_list may contain multiple entries for each // variable if the user is specifying output only on certain // element blocks... std::string var_name; int var_count = 0; for (auto &elem : variable_list) { if (var_name == elem.first) { continue; } var_name = elem.first; bool found = false; for (size_t j = 0; j < exo_names.size() && !found; j++) { if (case_compare(exo_names[j], var_name) == 0) { found = true; vars.index_[j] = ++var_count; } } if (!found) { fmt::print(stderr, "ERROR: Variable '{}' is not valid.\n", elem.first); exit(EXIT_FAILURE); } } // Count non-zero entries in var_index; int nz_count = 0; for (auto &elem : vars.index_) { if (elem > 0) { nz_count++; } } SMART_ASSERT(nz_count == var_count + extra)(nz_count)(var_count); if (vars.addStatus) { vars.index_[num_vars] = nz_count; // Already counted above... } vars.outputCount = nz_count; return; } } template void put_mesh_summary(const Excn::Mesh &mesh) { // Write out Mesh info fmt::print(" Title: {}\n\n", mesh.title); fmt::print(" Number of coordinates per node ={:14}\n", fmt::group_digits(mesh.count(Excn::ObjectType::DIM))); fmt::print(" Number of nodes ={:14}\n", fmt::group_digits(mesh.count(Excn::ObjectType::NODE))); fmt::print(" Number of elements ={:14}\n", fmt::group_digits(mesh.count(Excn::ObjectType::ELEM))); fmt::print(" Number of element blocks ={:14}\n", fmt::group_digits(mesh.count(Excn::ObjectType::EBLK))); fmt::print(" Number of nodal point sets ={:14}\n", fmt::group_digits(mesh.count(Excn::ObjectType::NSET))); fmt::print(" Number of element side sets ={:14}\n", fmt::group_digits(mesh.count(Excn::ObjectType::SSET))); } template void get_nodesets(size_t total_node_count, std::vector> &local_mesh, std::vector>> &nodesets, std::vector> &glob_sets) { // Find number of nodesets in the global model... std::set set_ids; std::vector ids; size_t part_count = local_mesh.size(); int bad_ns = 0; { int ns_count; for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); ns_count = ex_inquire_int(id, EX_INQ_NODE_SETS); // Get the ids for these ids.resize(ns_count); ex_get_ids(id, EX_NODE_SET, ids.data()); for (int iset = 0; iset < ns_count; iset++) { if (ids[iset] != 0) { set_ids.insert(ids[iset]); } else { bad_ns++; } } } } if (bad_ns != 0) { fmt::print(stderr, "ERROR: There were {} nodesets (counting all files) which had an id equal to " "0 which is not allowed.\n", bad_ns); } if (set_ids.empty()) { return; } // set_ids now contains the union of all nodeset ids... glob_sets.resize(set_ids.size()); size_t gnset_size = set_ids.size(); ids.resize(gnset_size); { size_t i = 0; for (auto &set_id : set_ids) { glob_sets[i].id = set_id; glob_sets[i].position_ = i; i++; } } { for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); nodesets[p].resize(set_ids.size()); // Get the ids again so we can map current order back to file order... ex_get_ids(id, EX_NODE_SET, ids.data()); int ns_count = ex_inquire_int(id, EX_INQ_NODE_SETS); for (int i = 0; i < ns_count; i++) { nodesets[p][i].id = ids[i]; // Find which global nodeset this id corresponds to... size_t gi = gnset_size; for (size_t j = 0; j < gnset_size; j++) { if (ids[i] == glob_sets[j].id) { gi = j; break; } } SMART_ASSERT(gi != gnset_size); glob_sets[gi].position_ = i; nodesets[p][i].position_ = gi; // Get the parameters for this nodeset... if (ex_int64_status(id) & EX_BULK_INT64_API) { int64_t node_count; int64_t df_count; ex_get_set_param(id, EX_NODE_SET, nodesets[p][i].id, &node_count, &df_count); nodesets[p][i].nodeCount = node_count; nodesets[p][i].dfCount = df_count; } else { int node_count; int df_count; ex_get_set_param(id, EX_NODE_SET, nodesets[p][i].id, &node_count, &df_count); nodesets[p][i].nodeCount = node_count; nodesets[p][i].dfCount = df_count; } std::vector name(Excn::ExodusFile::max_name_length() + 1); ex_get_name(id, EX_NODE_SET, nodesets[p][i].id, name.data()); if (name[0] != '\0') { nodesets[p][i].name_ = name.data(); if (glob_sets[gi].name_.empty()) { glob_sets[gi].name_ = name.data(); } } if (debug_level & 32) { fmt::print("Part {} ", p + 1); nodesets[p][i].dump(); } } } } verify_set_position_mapping("nodeset", part_count, glob_sets, nodesets); { // Now get the nodeset nodes and df. // Currently ignore the DF. Could add if people need it... // This is inefficient since the part loop is on // the inside... The other ordering would use more memory... std::vector ns_nodes; for (size_t ns = 0; ns < set_ids.size(); ns++) { std::vector glob_ns_nodes(total_node_count + 1); std::fill(glob_ns_nodes.begin(), glob_ns_nodes.end(), 0); size_t lns = glob_sets[ns].position_; for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); glob_sets[ns].name_ = nodesets[p][lns].name_; size_t size = nodesets[p][lns].entity_count(); if (size > 0) { ns_nodes.resize(size); ex_get_set(id, EX_NODE_SET, nodesets[p][lns].id, ns_nodes.data(), nullptr); // The node ids are in local space -- map to global for (size_t iset = 0; iset < size; iset++) { size_t global_node = local_mesh[p].localNodeToGlobal[ns_nodes[iset] - 1] + 1; glob_ns_nodes[global_node] = 1; } } } // Count number of nonzero entries and transfer to the // output nodeset // NOTE: global_node above is 1-based. glob_sets[ns].nodeCount = std::accumulate(glob_ns_nodes.begin(), glob_ns_nodes.end(), INT(0)); glob_sets[ns].nodeSetNodes.resize(glob_sets[ns].entity_count()); glob_sets[ns].dfCount = 0; size_t j = 0; for (size_t i = 1; i <= total_node_count; i++) { if (glob_ns_nodes[i] == 1) { glob_sets[ns].nodeSetNodes[j++] = i; glob_ns_nodes[i] = j; } } SMART_ASSERT(j == glob_sets[ns].entity_count())(j)(glob_sets[ns].entity_count())(ns); // See if we need a map from local nodeset position to global nodeset position // Only needed if there are nodeset variables (or dist factors). // Assume all files have same number of variables... int num_vars; { Excn::ExodusFile id(0); ex_get_variable_param(id, EX_NODE_SET, &num_vars); } if (num_vars > 0) { for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); // Get the nodelist, but store it in nodeOrderMap. // global_pos = nodeOrderMap[i] Excn::NodeSet &nset = nodesets[p][ns]; size_t nnodes = nset.entity_count(); nset.nodeOrderMap.resize(nnodes); ex_get_set(id, EX_NODE_SET, nset.id, nset.nodeOrderMap.data(), nullptr); for (size_t i = 0; i < nnodes; i++) { size_t local_node = nset.nodeOrderMap[i]; // 1-based size_t global_node = local_mesh[p].localNodeToGlobal[local_node - 1] + 1; // 1-based size_t global_pos = glob_ns_nodes[global_node]; // 1-based nset.nodeOrderMap[i] = global_pos - 1; } #if 0 if (debug_level & 32) nset.dump_order(); #endif } } } } } template void put_nodesets(std::vector> &glob_sets) { int exoid = Excn::ExodusFile::output(); if (debug_level & 32) { fmt::print("\nOutput NodeSets:\n"); } for (auto &glob_set : glob_sets) { ex_put_set(exoid, EX_NODE_SET, glob_set.id, glob_set.nodeSetNodes.data(), nullptr); // ex_put_node_set_dist_fact(exoid, glob_sets[ns].id, &glob_sets[ns].distFactors[0]); // Done with the memory; clear out the vector containing the bulk data nodes and distFactors. clear(glob_set.nodeSetNodes); clear(glob_set.distFactors); if (debug_level & 32) { glob_set.dump(); } } } template void get_sideset_metadata(std::vector> &local_mesh, std::vector>> &sets, std::vector> &glob_ssets) { // Find number of sidesets in the global model... std::set set_ids; std::vector ids; size_t part_count = local_mesh.size(); int bad_ss = 0; { for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); int ss_count = ex_inquire_int(id, EX_INQ_SIDE_SETS); // Get the ids for these ids.resize(ss_count); ex_get_ids(id, EX_SIDE_SET, ids.data()); for (int i = 0; i < ss_count; i++) { if (ids[i] != 0) { set_ids.insert(ids[i]); } else { bad_ss++; } } } } if (bad_ss != 0) { fmt::print(stderr, "ERROR: There were {} sidesets (counting all files) which had an id equal to 0 " "which is not allowed.\n", bad_ss); } if (set_ids.empty()) { return; } // set_ids now contains the union of all sideset ids... glob_ssets.resize(set_ids.size()); size_t gsset_size = set_ids.size(); ids.resize(gsset_size); { size_t i = 0; for (auto set_id : set_ids) { glob_ssets[i].id = set_id; glob_ssets[i].position_ = i; i++; } } { std::vector name(Excn::ExodusFile::max_name_length() + 1); for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); sets[p].resize(set_ids.size()); // Get the ids again so we can map current order back to file order... ex_get_ids(id, EX_SIDE_SET, ids.data()); int ss_count = ex_inquire_int(id, EX_INQ_SIDE_SETS); for (int i = 0; i < ss_count; i++) { sets[p][i].id = ids[i]; // Find which global sideset this id corresponds to... size_t gi = gsset_size; for (size_t j = 0; j < gsset_size; j++) { if (ids[i] == glob_ssets[j].id) { gi = j; break; } } SMART_ASSERT(gi != gsset_size); glob_ssets[gi].position_ = i; sets[p][i].position_ = gi; // Get the parameters for this sideset... ex_set set{}; set.type = EX_SIDE_SET; set.id = sets[p][i].id; set.entry_list = nullptr; set.extra_list = nullptr; set.distribution_factor_list = nullptr; int error = ex_get_sets(id, 1, &set); if (error != EX_NOERR) { fmt::print(stderr, "ERROR: Cannot get side set with id {}\n", set.id); exit(EXIT_FAILURE); } sets[p][i].sideCount = set.num_entry; sets[p][i].dfCount = set.num_distribution_factor; glob_ssets[gi].sideCount += sets[p][i].entity_count(); glob_ssets[gi].dfCount += sets[p][i].dfCount; ex_get_name(id, EX_SIDE_SET, sets[p][i].id, name.data()); if (name[0] != '\0') { sets[p][i].name_ = name.data(); glob_ssets[gi].name_ = name.data(); } } } verify_set_position_mapping("sideset", part_count, glob_ssets, sets); // See if we need a map from local sideset position to global sideset position // Only needed if there are sideeset variables (or dist factors which are currently ignored). // Assume all files have same number of variables... bool need_sideset_map = false; { int num_vars; Excn::ExodusFile id(0); ex_get_variable_param(id, EX_SIDE_SET, &num_vars); need_sideset_map = num_vars > 0; } // Now get the sideset elements/sides. Ignoring DF for now... for (size_t ss = 0; ss < set_ids.size(); ss++) { // This is maximum possible size; will probably be reduced by duplicate elimination... using ElemSideMap = std::vector>; ElemSideMap elem_side(glob_ssets[ss].sideCount); int ss_id = glob_ssets[ss].id; size_t offset = 0; size_t lss = glob_ssets[ss].position_; for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); sets[p][lss].elems.resize(sets[p][lss].sideCount); sets[p][lss].sides.resize(sets[p][lss].sideCount); ex_get_set(id, EX_SIDE_SET, ss_id, sets[p][lss].elems.data(), sets[p][lss].sides.data()); // Add these to the elem_side vector... for (size_t i = 0; i < sets[p][lss].sideCount; i++) { size_t global_elem = local_mesh[p].localElementToGlobal[sets[p][lss].elems[i] - 1] + 1; elem_side[offset + i] = std::make_pair((INT)global_elem, (INT)sets[p][lss].sides[i]); } offset += sets[p][lss].sideCount; } uniquify(elem_side); // Set the output sideset definition... glob_ssets[ss].sideCount = elem_side.size(); glob_ssets[ss].dfCount = 0; glob_ssets[ss].elems.resize(elem_side.size()); glob_ssets[ss].sides.resize(elem_side.size()); // Populate the global sideset elements and sides... for (size_t i = 0; i < elem_side.size(); i++) { std::tie(glob_ssets[ss].elems[i], glob_ssets[ss].sides[i]) = elem_side[i]; } if (need_sideset_map) { // For this sideset in each part, figure out the mapping for // its (elem, side, variable) position into the corresponding // global sideset position... // Try the lower_bound searching of elem_side for now. If // inefficient, fix later... for (size_t p = 0; p < part_count; p++) { sets[p][lss].elemOrderMap.resize(sets[p][lss].sideCount); for (size_t i = 0; i < sets[p][lss].sideCount; i++) { size_t global_elem = local_mesh[p].localElementToGlobal[sets[p][lss].elems[i] - 1] + 1; std::pair es = std::make_pair((INT)global_elem, (INT)sets[p][lss].sides[i]); auto iter = std::lower_bound(elem_side.begin(), elem_side.end(), es); size_t pos = iter - elem_side.begin(); sets[p][lss].elemOrderMap[i] = pos; } } } } // Calculate sideset offset for (size_t b = 0; b < glob_ssets.size(); b++) { size_t sum = 0; for (size_t p = 0; p < part_count; p++) { sets[p][b].offset_ = sum; sum += sets[p][b].entity_count(); if (debug_level & 16) { fmt::print("Part {} ", p + 1); sets[p][b].dump(); } } } // Free some memory which is no longer needed... // (Could move up into sideset loop above) for (size_t p = 0; p < part_count; p++) { for (auto &elem : sets[p]) { clear(elem.elems); clear(elem.sides); clear(elem.distFactors); } } } } template void get_put_sidesets(std::vector> &glob_ssets) { int exoid = Excn::ExodusFile::output(); // output file identifier for (auto &glob_sset : glob_ssets) { ex_put_set(exoid, EX_SIDE_SET, glob_sset.id, const_cast(glob_sset.elems.data()), const_cast(&glob_sset.sides[0])); if (glob_sset.dfCount > 0) { ex_put_set_dist_fact(exoid, EX_SIDE_SET, glob_sset.id, reinterpret_cast(glob_sset.distFactors.data())); } } for (auto &glob_sset : glob_ssets) { clear(glob_sset.elems); clear(glob_sset.sides); clear(glob_sset.distFactors); } } template void add_status_variable(int id_out, const Excn::Mesh &global, const std::vector &blocks, const std::vector &glob_blocks, const std::vector &local_element_to_global, int step, int variable, T alive, int combined_variable_index) { SMART_ASSERT(sizeof(T) == Excn::ExodusFile::io_word_size()); std::vector status; SMART_ASSERT(alive == 0.0 || alive == 1.0)(alive); for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { status.resize(glob_blocks[b].entity_count()); std::fill(status.begin(), status.end(), (1.0 - alive)); size_t boffset = blocks[b].offset_; size_t goffset = glob_blocks[b].offset_; size_t element_count = blocks[b].entity_count(); for (size_t e = 0; e < element_count; e++) { size_t global_block_pos = local_element_to_global[(e + boffset)] - goffset; status[global_block_pos] = alive; } // If combining this status variable with a mesh status // variable, do that now... if (combined_variable_index > 0) { std::vector mesh_status(glob_blocks[b].entity_count()); ex_get_var(id_out, step, EX_ELEM_BLOCK, combined_variable_index, glob_blocks[b].id, glob_blocks[b].entity_count(), mesh_status.data()); if (alive == 1.0) { for (size_t i = 0; i < glob_blocks[b].entity_count(); i++) { status[i] = status[i] * mesh_status[i]; } } else { SMART_ASSERT(alive == 0.0); for (size_t i = 0; i < glob_blocks[b].entity_count(); i++) { status[i] = 1.0 - ((1.0 - status[i]) * (1.0 - mesh_status[i])); } } } ex_put_var(id_out, step, EX_ELEM_BLOCK, variable, glob_blocks[b].id, glob_blocks[b].entity_count(), status.data()); } } StringVector get_exodus_variable_names(int id, ex_entity_type elType, size_t var_count) { // Allocate space for variable names... char **name_list = get_name_array(var_count, Excn::ExodusFile::max_name_length()); int error = ex_get_variable_names(id, elType, var_count, name_list); if (error != EX_NOERR) { fmt::print(stderr, "ERROR: Cannot get variable names\n"); exit(EXIT_FAILURE); } StringVector names(var_count); for (size_t j = 0; j < var_count; j++) { compress_white_space(name_list[j]); names[j] = std::string(name_list[j]); } free_name_array(name_list, var_count); return names; } template void filter_truth_table(int id, Excn::Mesh &global, std::vector &glob_blocks, Excn::Variables &vars, const StringIdVector &variable_names) { // This routine checks the 'variable_names' list to see if the user // has restricted the output of certain variables to certain element // blocks. If so, then the truth table is modified to match the // users request. if (variable_names.empty()) { return; } // Check for a non-zero id entry in the variable_names list which // will signify a user-specified element block. bool found_it = false; for (size_t i = 0; i < variable_names.size() && !found_it; i++) { if (variable_names[i].second > 0) { found_it = true; } } if (!found_it) { return; } // At this point, know that there is at least one block-restricted // variable output specification. For each one, modify the global // truth table to match the specification. StringVector exo_names = get_exodus_variable_names(id, vars.type(), vars.count()); std::string var_name; int out_position = -1; for (auto [v_name, v_blkid] : variable_names) { if (v_blkid > 0) { if (var_name != v_name) { var_name = v_name; // Find which exodus variable matches this name out_position = -1; for (size_t j = 0; j < exo_names.size(); j++) { if (case_compare(exo_names[j], var_name) == 0) { out_position = vars.index_[j] - 1; break; } } if (out_position < 0) { fmt::print(stderr, "ERROR: Variable '{}' does not exist on any block in this database.\n", v_name); exit(EXIT_FAILURE); } // Set all truth table entries for this variable to negative // of current value and then iterate over specified blocks and // set those positive. This way can make sure that the // variable truly exists for the block that the user specified. found_it = false; for (size_t b = 0; b < global.count(vars.objectType); b++) { if (glob_blocks[b].id == v_blkid) { if (glob_blocks[b].truthTable[out_position] == 0) { fmt::print(stderr, "ERROR: Variable '{}' does not exist on block {}.\n", v_name, v_blkid); exit(EXIT_FAILURE); } else { found_it = true; break; } } else { // User does not want this variable output on these blocks... glob_blocks[b].truthTable[out_position] = 0; } } if (!found_it) { fmt::print(stderr, "ERROR: User-specified block id of {} for variable '{}' does not exist.\n", v_blkid, v_name); exit(EXIT_FAILURE); } } } } } template void get_truth_table(Excn::Mesh &global, std::vector> &blocks, std::vector &glob_blocks, Excn::Variables &vars, int debug) { // read truth table - sum across all parts since many will // have values set to zero for zero length blocks the element // variable truth table organized as a 2D array: // [global.count(Excn::EBLK)][num_elem_vars] Excn::ObjectType object_type = vars.objectType; if (vars.count(Excn::InOut::OUT_) > 0) { // For each input exodus file, get it's truth table and fill // in the location in the output truth table... size_t part_count = blocks.size(); for (size_t p = 0; p < part_count; p++) { Excn::ExodusFile id(p); for (size_t b = 0; b < global.count(object_type); b++) { size_t gb = 0; for (; gb < global.count(object_type); gb++) { if (glob_blocks[gb].id == blocks[p][b].id) { break; } } SMART_ASSERT(glob_blocks[gb].id == blocks[p][b].id); if (p == 0) { glob_blocks[gb].truthTable.resize(vars.count(Excn::InOut::OUT_)); } if (vars.count(Excn::InOut::IN_) > 0) { blocks[p][b].truthTable.resize(vars.count(Excn::InOut::IN_)); ex_get_object_truth_vector(id, vars.type(), blocks[p][b].id, vars.count(Excn::InOut::IN_), blocks[p][b].truthTable.data()); // Find global block corresponding to this block. (Ids match) for (int j = 0; j < vars.count(Excn::InOut::IN_); j++) { if (vars.index_[j] > 0) { glob_blocks[gb].truthTable[vars.index_[j] - 1] += blocks[p][b].truthTable[j]; } } } if (vars.addStatus) { glob_blocks[gb].truthTable[vars.count(Excn::InOut::OUT_) - 1] = 1; } } } // reset truth table values that may be greater than 1 for (size_t b = 0; b < global.count(object_type); b++) { for (int j = 0; j < vars.count(Excn::InOut::OUT_); j++) { if (glob_blocks[b].truthTable[j] > 0) { glob_blocks[b].truthTable[j] = 1; } } } if (debug_level & debug) { fmt::print("Truth table for {}\t{} variables\t{} sets\n", vars.label(), vars.count(Excn::InOut::OUT_), global.count(object_type)); for (size_t b = 0; b < global.count(object_type); b++) { for (int j = 0; j < vars.count(Excn::InOut::OUT_); j++) { fmt::print("{}", glob_blocks[b].truthTable[j]); } fmt::print("\n"); } } } } int case_compare(const std::string &s1, const std::string &s2) { const char *c1 = s1.c_str(); const char *c2 = s2.c_str(); for (;;) { if (::toupper(*c1) != ::toupper(*c2)) { return ::toupper(*c1) - ::toupper(*c2); } if (*c1 == '\0') { return 0; } c1++; c2++; } } void add_info_record(char *info_record, int size) { // Add 'uname' output to the passed in character string. // Maximum size of string is 'size' (not including terminating nullptr) // This is used as information data in the concatenated results file // to help in tracking when/where/... the file was created auto info = sys_info("CONJOIN"); copy_string(info_record, info, size + 1); } inline bool is_whitespace(char c) { static char white_space[] = {' ', '\t', '\n', '\r', ',', '\0'}; return std::strchr(white_space, c) != nullptr; } void compress_white_space(char *str) { char *ibuf = str; char *obuf = str; int i = 0; int cnt = 0; // Don't process an empty string. if (str == nullptr) { return; } // Skip leading... while (*ibuf != 0 && is_whitespace(*ibuf)) { ++ibuf; } while (*ibuf != 0) { if (is_whitespace(*ibuf) && cnt > 0) { ibuf++; } else { if (!is_whitespace(*ibuf)) { cnt = 0; } else { *ibuf = ' '; cnt = 1; } obuf[i++] = *ibuf++; } } obuf[i--] = '\0'; // Skip trailing whitespace while (i > 0 && is_whitespace(obuf[i])) { obuf[i--] = '\0'; } } int get_width(size_t max_value) { // Returns the field width which will accommodate the // largest value. int width = 0; if (max_value >= 10) { width = int(log10(static_cast(max_value))); } return width + 1; } template void map_element_vars(size_t loffset, size_t goffset, size_t entity_count, std::vector &values, std::vector &global_values, INT *part_loc_elem_to_global) { // copy values to master element value information for (size_t j = 0; j < entity_count; j++) { size_t global_block_pos = part_loc_elem_to_global[(j + loffset)] - goffset; global_values[global_block_pos] = values[j]; } } template void map_sideset_vars(U & /*unused*/, size_t /*unused*/, std::vector & /*unused*/, std::vector & /*unused*/) { throw std::runtime_error("Internal Error."); } template void map_sideset_vars(Excn::SideSet &local_set, size_t entity_count, std::vector &values, std::vector &global_values) { // copy values to master nodeset value information for (size_t j = 0; j < entity_count; j++) { size_t global_loc = local_set.elemOrderMap[j]; SMART_ASSERT(global_loc < global_values.size()); global_values[global_loc] = values[j]; } } template void map_sideset_vars(Excn::SideSet &local_set, size_t entity_count, std::vector &values, std::vector &global_values) { // copy values to master nodeset value information for (size_t j = 0; j < entity_count; j++) { size_t global_loc = local_set.elemOrderMap[j]; SMART_ASSERT(global_loc < global_values.size()); global_values[global_loc] = values[j]; } } template void map_nodeset_vars(U & /*unused*/, size_t /*unused*/, std::vector & /*unused*/, std::vector & /*unused*/) { throw std::runtime_error("Internal Error."); } template void map_nodeset_vars(Excn::NodeSet &local_set, size_t entity_count, std::vector &values, std::vector &global_values) { // copy values to master nodeset value information for (size_t j = 0; j < entity_count; j++) { size_t global_loc = local_set.nodeOrderMap[j]; SMART_ASSERT(global_loc < global_values.size()); global_values[global_loc] = values[j]; } } template void map_nodeset_vars(Excn::NodeSet &local_set, size_t entity_count, std::vector &values, std::vector &global_values) { // copy values to master nodeset value information for (size_t j = 0; j < entity_count; j++) { size_t global_loc = local_set.nodeOrderMap[j]; SMART_ASSERT(global_loc < global_values.size()); global_values[global_loc] = values[j]; } } template int read_write_master_values(Excn::Variables &vars, const Excn::Mesh &global, std::vector &global_sets, std::vector> &local_mesh, std::vector> &local_sets, std::vector &values, size_t p, Excn::ExodusFile &id, int time_step, int time_step_out) { int error = 0; int id_out = Excn::ExodusFile::output(); // output file identifier size_t max_size = 0; for (size_t b = 0; b < global.count(vars.objectType); b++) { if (max_size < global_sets[b].entity_count()) { max_size = global_sets[b].entity_count(); } } std::vector master_values(max_size); // Only needed for element, but haven't cleaned this up yet... INT *part_loc_elem_to_global = local_mesh[p].localElementToGlobal.data(); for (int i = 0; i < vars.count(Excn::InOut::IN_); i++) { if (vars.index_[i] > 0) { int ivar = vars.index_[i] - 1; for (size_t b = 0; b < global.count(vars.objectType); b++) { size_t lb = 0; for (; lb < global.count(vars.objectType); lb++) { if (global_sets[b].id == local_sets[p][lb].id) { break; } } SMART_ASSERT(global_sets[b].id == local_sets[p][lb].id); if (global_sets[b].truthTable[ivar] && local_sets[p][lb].entity_count() > 0) { int entity_count = local_sets[p][lb].entity_count(); if (local_sets[p][lb].truthTable[i] > 0) { error += ex_get_var(id, time_step + 1, exodus_object_type(vars.objectType), i + 1, local_sets[p][lb].id, entity_count, values.data()); switch (vars.objectType) { case Excn::ObjectType::EBLK: map_element_vars(local_sets[p][lb].offset_, global_sets[b].offset_, entity_count, values, master_values, part_loc_elem_to_global); break; case Excn::ObjectType::SSET: map_sideset_vars(local_sets[p][lb], entity_count, values, master_values); break; case Excn::ObjectType::NSET: map_nodeset_vars(local_sets[p][lb], entity_count, values, master_values); break; default: break; } } ex_put_var(id_out, time_step_out, exodus_object_type(vars.objectType), ivar + 1, global_sets[b].id, global_sets[b].entity_count(), master_values.data()); } } } } return error; } template void create_output_truth_table(std::vector &global_sets, Excn::Variables &vars, std::vector &truth_table) { truth_table.resize(global_sets.size() * vars.count(Excn::InOut::OUT_)); for (auto &global_set : global_sets) { int bout = global_set.position_; SMART_ASSERT(bout >= 0); for (int j = 0; j < vars.count(Excn::InOut::OUT_); j++) { int out_ttable_loc = (bout * vars.count(Excn::InOut::OUT_)) + j; truth_table[out_ttable_loc] = global_set.truthTable[j]; } } } template size_t find_max_entity_count(size_t part_count, std::vector> &local_mesh, const Excn::Mesh &global, std::vector> &blocks, std::vector>> &nodesets, std::vector>> &sidesets) { size_t max_ent = local_mesh[0].count(Excn::ObjectType::NODE); for (size_t p = 1; p < part_count; p++) { if ((size_t)local_mesh[p].count(Excn::ObjectType::NODE) > max_ent) { max_ent = local_mesh[p].count(Excn::ObjectType::NODE); } } for (size_t p = 0; p < part_count; p++) { for (size_t b = 0; b < global.count(Excn::ObjectType::EBLK); b++) { if (blocks[p][b].entity_count() > max_ent) { max_ent = blocks[p][b].entity_count(); } } } // Nodesets... for (size_t p = 0; p < part_count; p++) { for (size_t b = 0; b < global.count(Excn::ObjectType::NSET); b++) { if (nodesets[p][b].entity_count() > max_ent) { max_ent = nodesets[p][b].entity_count(); } } } // Sidesets... for (size_t p = 0; p < part_count; p++) { for (size_t b = 0; b < global.count(Excn::ObjectType::SSET); b++) { if (sidesets[p][b].entity_count() > max_ent) { max_ent = sidesets[p][b].entity_count(); } } } return max_ent; } void sort_file_times(StringVector &input_files) { // Sort files based on minimum timestep time std::vector> file_time_name; file_time_name.reserve(input_files.size()); for (auto &filename : input_files) { float version = 0.0; int cpu_word_size = sizeof(float); int io_wrd_size = 0; int exoid = ex_open(filename.c_str(), EX_READ, &cpu_word_size, &io_wrd_size, &version); if (exoid < 0) { fmt::print(stderr, "ERROR: Cannot open file '{}'\n", filename); exit(EXIT_FAILURE); } int nts = ex_inquire_int(exoid, EX_INQ_TIME); double time = 0.0; if (nts > 0) { ex_get_time(exoid, 1, &time); } file_time_name.emplace_back(time, filename); ex_close(exoid); } std::sort(file_time_name.begin(), file_time_name.end()); input_files.clear(); input_files.reserve(file_time_name.size()); for (const auto &entry : file_time_name) { input_files.push_back(entry.second); } } } // namespace