// 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 "ED_SystemInterface.h" // for SystemInterface, interFace #include "Tolerance.h" // for Tolerance, etc #include "exo_entity.h" // for Exo_Entity, EXOTYPE #include "exodusII.h" #include "fmt/color.h" #include "fmt/ostream.h" #include "smart_assert.h" // for SMART_ASSERT #include "stringx.h" // for find_string, etc #include "util.h" #include // for size_t #include // for nullptr #include // for string, char_traits, etc #include // for vector template class ExoII_Read; namespace { void build_variable_names(const char *type, std::vector &names, std::vector &tols, const Tolerance &default_tol, bool do_all_flag, const std::vector &var_names1, const std::vector &var_names2, bool *diff_found); template void build_truth_table(EXOTYPE type, const char *label, std::vector &names, size_t num_entity, ExoII_Read &file1, ExoII_Read &file2, const std::vector &var_names1, const std::vector &var_names2, std::vector &truth_tab, bool quiet_flag, bool *diff_found); void output_exodus_names(int file_id, EXOTYPE type, const std::vector &names); void output_diff_names(const char *type, const std::vector &names); void output_compare_names(const char *type, const std::vector &names, const std::vector &tol, int num_vars1, int num_vars2); } // namespace template void Build_Variable_Names(ExoII_Read &file1, ExoII_Read &file2, bool *diff_found) { // Build (and compare) global variable names. build_variable_names("global", interFace.glob_var_names, interFace.glob_var, interFace.glob_var_default, interFace.glob_var_do_all_flag, file1.Global_Var_Names(), file2.Global_Var_Names(), diff_found); // Build (and compare) nodal variable names. build_variable_names("nodal", interFace.node_var_names, interFace.node_var, interFace.node_var_default, interFace.node_var_do_all_flag, file1.Nodal_Var_Names(), file2.Nodal_Var_Names(), diff_found); // Build (and compare) element variable names. build_variable_names("element", interFace.elmt_var_names, interFace.elmt_var, interFace.elmt_var_default, interFace.elmt_var_do_all_flag, file1.Element_Var_Names(), file2.Element_Var_Names(), diff_found); // Build (and compare) element variable names. if (!interFace.ignore_attributes) { build_variable_names("element attribute", interFace.elmt_att_names, interFace.elmt_att, interFace.elmt_att_default, interFace.elmt_att_do_all_flag, file1.Element_Att_Names(), file2.Element_Att_Names(), diff_found); } // Build (and compare) nodeset variable names. build_variable_names("nodeset", interFace.ns_var_names, interFace.ns_var, interFace.ns_var_default, interFace.ns_var_do_all_flag, file1.NS_Var_Names(), file2.NS_Var_Names(), diff_found); // Build (and compare) sideset variable names. build_variable_names("sideset", interFace.ss_var_names, interFace.ss_var, interFace.ss_var_default, interFace.ss_var_do_all_flag, file1.SS_Var_Names(), file2.SS_Var_Names(), diff_found); // Build (and compare) edgeblock variable names. build_variable_names("edgeblock", interFace.eb_var_names, interFace.eb_var, interFace.eb_var_default, interFace.eb_var_do_all_flag, file1.EB_Var_Names(), file2.EB_Var_Names(), diff_found); // Build (and compare) faceblock variable names. build_variable_names("faceblock", interFace.fb_var_names, interFace.fb_var, interFace.fb_var_default, interFace.fb_var_do_all_flag, file1.FB_Var_Names(), file2.FB_Var_Names(), diff_found); } template int Create_File(ExoII_Read &file1, ExoII_Read &file2, const std::string &diffile_name, bool *diff_found) { // Multiple modes: // summary_flag == true --> Single file, output summary and variable names, return // diffile_name == "" --> Dual file, output summary, variable names, check compatibility, // diffile_name != "" --> Three files (2 in, 1 out) // create output file which is diff of input. // output summary, variable names, check compatibility // quiet_flag == true --> don't output summary information SMART_ASSERT(!interFace.summary_flag); //======================================================================== // From here on down, have two input files and possibly 1 output file... // Create output file. int out_file_id = -1; if (!diffile_name.empty()) { // Take minimum word size for output file. int iows = file1.IO_Word_Size() < file2.IO_Word_Size() ? file1.IO_Word_Size() : file2.IO_Word_Size(); int compws = sizeof(double); int mode = EX_CLOBBER; if (sizeof(INT) == 8) { mode |= EX_ALL_INT64_DB; mode |= EX_ALL_INT64_API; } out_file_id = ex_create(diffile_name.c_str(), mode, &compws, &iows); if (out_file_id < 0) { Error(fmt::format("Couldn't create output file \"{}\".\n", diffile_name)); } ex_copy(file1.File_ID(), out_file_id); } if (!interFace.quiet_flag) { if (out_file_id >= 0) { // The files are to be differenced .. just list names. if (interFace.coord_tol.type != ToleranceMode::IGNORE_) { fmt::print("Coordinates: tol: {:8g} {}, floor: {:8g}\n", interFace.coord_tol.value, interFace.coord_tol.typestr(), interFace.coord_tol.floor); } else { fmt::print("Locations of nodes will not be considered.\n"); } if (interFace.time_tol.type != ToleranceMode::IGNORE_) { fmt::print("Time step values: tol: {:8g} {}, floor: {:8g}\n", interFace.time_tol.value, interFace.time_tol.typestr(), interFace.time_tol.floor); } else { fmt::print("Time step time values will not be differenced.\n"); } output_diff_names("Global", interFace.glob_var_names); output_diff_names("Nodal", interFace.node_var_names); output_diff_names("Element", interFace.elmt_var_names); output_diff_names("Element Attribute", interFace.elmt_att_names); output_diff_names("Nodeset", interFace.ns_var_names); output_diff_names("Sideset", interFace.ss_var_names); output_diff_names("Edgeblock", interFace.eb_var_names); output_diff_names("Faceblock", interFace.fb_var_names); } else { // The files are to be compared .. echo additional info. if (Tolerance::use_old_floor) { std::ostringstream info; fmt::print(info, "INFO: Using old definition of floor tolerance. |a-b| 0 && file2.Num_Side_Sets() > 0) { fmt::print("Sideset Distribution Factors will be compared:\n {:<{}} tol: {:8g} ({}), " "floor: {:8g}\n", "...", name_length(), interFace.ss_df_tol.value, interFace.ss_df_tol.typestr(), interFace.ss_df_tol.floor); } else { if (interFace.ignore_sideset_df || interFace.ss_df_tol.type == ToleranceMode::IGNORE_) { std::ostringstream info; fmt::print(info, "Sideset Distribution Factors will not be compared.\n"); DIFF_OUT(info, fmt::color::yellow); } else { fmt::print("No Sideset Distribution Factors on either file.\n"); } } output_compare_names("Edgeblock", interFace.eb_var_names, interFace.eb_var, file1.Num_EB_Vars(), file2.Num_EB_Vars()); output_compare_names("Faceblock", interFace.fb_var_names, interFace.fb_var, file1.Num_FB_Vars(), file2.Num_FB_Vars()); } } std::vector truth_tab; build_truth_table(EX_ELEM_BLOCK, "Element Block", interFace.elmt_var_names, file1.Num_Element_Blocks(), file1, file2, file1.Element_Var_Names(), file2.Element_Var_Names(), truth_tab, interFace.quiet_flag, diff_found); std::vector ns_truth_tab; build_truth_table(EX_NODE_SET, "Nodeset", interFace.ns_var_names, file1.Num_Node_Sets(), file1, file2, file1.NS_Var_Names(), file2.NS_Var_Names(), ns_truth_tab, interFace.quiet_flag, diff_found); std::vector ss_truth_tab; build_truth_table(EX_SIDE_SET, "Sideset", interFace.ss_var_names, file1.Num_Side_Sets(), file1, file2, file1.SS_Var_Names(), file2.SS_Var_Names(), ss_truth_tab, interFace.quiet_flag, diff_found); std::vector eb_truth_tab; build_truth_table(EX_EDGE_BLOCK, "Edgeblock", interFace.eb_var_names, file1.Num_Edge_Blocks(), file1, file2, file1.EB_Var_Names(), file2.EB_Var_Names(), eb_truth_tab, interFace.quiet_flag, diff_found); std::vector fb_truth_tab; build_truth_table(EX_FACE_BLOCK, "Faceblock", interFace.fb_var_names, file1.Num_Face_Blocks(), file1, file2, file1.FB_Var_Names(), file2.FB_Var_Names(), fb_truth_tab, interFace.quiet_flag, diff_found); // Put out the concatenated variable parameters here and then // put out the names.... if (out_file_id >= 0) { ex_put_all_var_param(out_file_id, interFace.glob_var_names.size(), interFace.node_var_names.size(), interFace.elmt_var_names.size(), truth_tab.data(), interFace.ns_var_names.size(), ns_truth_tab.data(), interFace.ss_var_names.size(), ss_truth_tab.data()); output_exodus_names(out_file_id, EX_GLOBAL, interFace.glob_var_names); output_exodus_names(out_file_id, EX_NODAL, interFace.node_var_names); output_exodus_names(out_file_id, EX_ELEM_BLOCK, interFace.elmt_var_names); output_exodus_names(out_file_id, EX_NODE_SET, interFace.ns_var_names); output_exodus_names(out_file_id, EX_SIDE_SET, interFace.ss_var_names); output_exodus_names(out_file_id, EX_EDGE_BLOCK, interFace.eb_var_names); output_exodus_names(out_file_id, EX_FACE_BLOCK, interFace.fb_var_names); } return out_file_id; } namespace { void output_exodus_names(int file_id, EXOTYPE type, const std::vector &names) { if (!names.empty()) { std::vector vars(names.size()); for (unsigned i = 0; i < names.size(); ++i) { vars[i] = const_cast(names[i].c_str()); SMART_ASSERT(vars[i] != nullptr); } ex_put_variable_names(file_id, type, names.size(), vars.data()); } } void output_compare_names(const char *type, const std::vector &names, const std::vector &tol, int num_vars1, int num_vars2) { if (!names.empty()) { fmt::print("{} variables to be compared:\n", type); for (unsigned v = 0; v < names.size(); ++v) { if (v == 0) { fmt::print(" {:<{}} tol: {:8g} ({}), floor: {:8g}\n", names[v], name_length(), tol[v].value, tol[v].typestr(), tol[v].floor); } else { fmt::print(" {:<{}} {:8g} ({}), {:8g}\n", names[v], name_length(), tol[v].value, tol[v].typestr(), tol[v].floor); } } } else if (num_vars1 == 0 && num_vars2 == 0) { fmt::print("No {} variables on either file.\n", type); } else { fmt::print("{} variables will not be compared.\n", type); } } void output_diff_names(const char *type, const std::vector &names) { if (!names.empty()) { fmt::print("{} variables to be differenced:\n", type); for (auto &name : names) { fmt::print("\t{}\n", name); } } else { fmt::print("No {} variables will be differenced.\n", type); } } void build_variable_names(const char *type, std::vector &names, std::vector &tols, const Tolerance &default_tol, bool do_all_flag, const std::vector &var_names1, const std::vector &var_names2, bool *diff_found) { std::vector x_list; // exclusion list for (auto name : names) { chop_whitespace(name); SMART_ASSERT(!name.empty()); if (name[0] == '!') { x_list.push_back(extract_token(name, "!")); // remove "!" & add } } if (do_all_flag) { auto length_name = var_names1.size(); for (size_t n = 0; n < length_name; ++n) { const std::string &name = var_names1[n]; if (!interFace.summary_flag && find_string(var_names2, name, interFace.nocase_var_names) < 0) { if (find_string(x_list, name, interFace.nocase_var_names) < 0) { if (interFace.allowNameMismatch) { x_list.push_back(name); } else { *diff_found = true; if (!interFace.quiet_flag) { std::ostringstream diff; fmt::print(diff, "exodiff: DIFFERENCE .. The {} variable \"{}\" is in the first file but " "not the second.\n", type, name); DIFF_OUT(diff); } continue; } } } if (find_string(names, name, interFace.nocase_var_names) < 0 && find_string(x_list, name, interFace.nocase_var_names) < 0) { names.push_back(name); tols.push_back(default_tol); SMART_ASSERT(names.size() == tols.size())(names.size())(tols.size()); } } if (!interFace.noSymmetricNameCheck) { length_name = var_names2.size(); for (size_t n = 0; n < length_name; ++n) { const std::string &name = var_names2[n]; if (!interFace.summary_flag && find_string(var_names1, name, interFace.nocase_var_names) < 0) { if (find_string(x_list, name, interFace.nocase_var_names) < 0) { *diff_found = true; if (!interFace.quiet_flag) { std::ostringstream diff; fmt::print(diff, "exodiff: DIFFERENCE .. The {} variable \"{}\" is in the second file " "but not the first.\n", type, name); DIFF_OUT(diff); } continue; } } SMART_ASSERT(find_string(names, name, interFace.nocase_var_names) >= 0 || find_string(x_list, name, interFace.nocase_var_names) >= 0); } } } std::vector tmp_list; std::vector tmp_tols; for (size_t n = 0; n < names.size(); ++n) { std::string name = names[n]; chop_whitespace(name); if (name[0] == '!') { continue; } int idx = find_string(var_names1, name, interFace.nocase_var_names); if (idx >= 0) { if (interFace.summary_flag || find_string(var_names2, name, interFace.nocase_var_names) >= 0) { tmp_tols.push_back(tols[n]); tmp_list.push_back(var_names1[idx]); } else { *diff_found = true; if (!interFace.quiet_flag) { std::ostringstream diff; fmt::print(diff, "exodiff: DIFFERENCE .. The {} variable \"{}\" is not in the second file.\n", type, name); DIFF_OUT(diff); } } } else { *diff_found = true; if (!interFace.quiet_flag) { std::ostringstream diff; fmt::print( diff, "exodiff: DIFFERENCE .. Specified {} variable \"{}\" is not in the first file.\n", type, name); DIFF_OUT(diff); } } } names = tmp_list; tols = tmp_tols; SMART_ASSERT(names.size() == tols.size())(names.size())(tols.size()); } template void build_truth_table(EXOTYPE type, const char *label, std::vector &names, size_t num_entity, ExoII_Read &file1, ExoII_Read &file2, const std::vector &var_names1, const std::vector &var_names2, std::vector &truth_tab, bool quiet_flag, bool *diff_found) { if (!names.empty()) { int num_vars = names.size(); truth_tab.resize(num_vars * num_entity); for (int i = num_vars * num_entity - 1; i >= 0; --i) { truth_tab[i] = 0; } for (size_t b = 0; b < num_entity; ++b) { Exo_Entity *set1 = file1.Get_Entity_by_Index(type, b); Exo_Entity *set2 = nullptr; if (interFace.by_name) { set2 = file2.Get_Entity_by_Name(type, set1->Name()); } else { set2 = file2.Get_Entity_by_Id(type, set1->Id()); } if (set2 == nullptr) { if (interFace.map_flag != MapType::PARTIAL) { *diff_found = true; std::ostringstream diff; fmt::print(diff, "exodiff: DIFFERENCE {} id {} exists in first file but not the second...\n", label, set1->Id()); DIFF_OUT(diff); } continue; } for (int out_idx = 0; out_idx < num_vars; ++out_idx) { const std::string &name = names[out_idx]; int idx1 = find_string(var_names1, name, interFace.nocase_var_names); int idx2 = find_string(var_names2, name, interFace.nocase_var_names); if (idx1 < 0 || idx2 < 0) { Error(fmt::format("Unable to find variable named '{}' on database.\n", name)); } if (set1->is_valid_var(idx1)) { if (set2->is_valid_var(idx2)) { truth_tab[b * num_vars + out_idx] = 1; } else if (!quiet_flag) { std::ostringstream diff; fmt::print(diff, "exodiff: INFO {0} variable \"{1}\" is not saved for {0}" " id {2} in the second file but is in the first (by virtue of the truth " "tables). " "This variable won't be considered for this {0}.\n", label, name, set1->Id()); DIFF_OUT(diff, fmt::color::yellow); } } else if (set2->is_valid_var(idx2) && !quiet_flag) { std::ostringstream diff; fmt::print( diff, "exodiff: INFO {0} variable \"{1}\" is not saved for {0}" " id {2} in the first file but is in the second (by virtue of the truth tables). " "This variable won't be considered for this {0}.\n", label, name, set1->Id()); DIFF_OUT(diff, fmt::color::yellow); } } } } } } // End of namespace template int Create_File(ExoII_Read &file1, ExoII_Read &file2, const std::string &diffile_name, bool *diff_found); template void Build_Variable_Names(ExoII_Read &file1, ExoII_Read &file2, bool *diff_found); template int Create_File(ExoII_Read &file1, ExoII_Read &file2, const std::string &diffile_name, bool *diff_found); template void Build_Variable_Names(ExoII_Read &file1, ExoII_Read &file2, bool *diff_found);