// Copyright(C) 1999-2021, 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 "ED_SystemInterface.h" #include "Tolerance.h" #include "exoII_read.h" #include "exo_block.h" #include "exodusII.h" #include "fmt/ostream.h" #include "node_set.h" #include "side_set.h" #include "smart_assert.h" #include "stringx.h" namespace { template bool Check_Nodal(ExoII_Read &file1, ExoII_Read &file2, const std::vector &node_map, const INT *id_map, bool check_only); template bool Check_Element_Block(ExoII_Read &file1, ExoII_Read &file2, const std::vector &elmt_map, const std::vector &node_map); template bool Check_Nodeset(ExoII_Read &file1, ExoII_Read &file2, const std::vector &node_map, bool check_only); template bool Check_Sideset(ExoII_Read &file1, ExoII_Read &file2, const std::vector &elmt_map, bool check_only); template bool Check_Element_Block_Params(const Exo_Block *block1, const Exo_Block *block2); template bool Check_Element_Block_Connectivity(Exo_Block *block1, Exo_Block *block2, const std::vector &elmt_map, const std::vector &node_map); bool close_compare(const std::string &st1, const std::string &st2); } // namespace template bool Check_Global(ExoII_Read &file1, ExoII_Read &file2) { bool is_same = true; if (file1.Dimension() != file2.Dimension()) { Warning(".. Dimension doesn't agree.\n"); is_same = false; } if (file1.Num_Nodes() != file2.Num_Nodes()) { if (interFace.map_flag != MapType::PARTIAL) { Warning(".. Number of nodes doesn't agree.\n"); is_same = false; } } if (file1.Num_Elements() != file2.Num_Elements()) { if (interFace.map_flag != MapType::PARTIAL) { Warning(".. Number of elements doesn't agree.\n"); is_same = false; } } if (file1.Num_Element_Blocks() != file2.Num_Element_Blocks()) { if (interFace.map_flag != MapType::PARTIAL) { Warning(".. Number of element blocks doesn't agree.\n"); is_same = false; } } if (file1.Num_Times() != file2.Num_Times() && !interFace.quiet_flag && !interFace.ignore_steps) { Warning(fmt::format(".. First file has {} result times while the second file has {}.\n", file1.Num_Times(), file2.Num_Times())); } return is_same; } template void Check_Compatible_Meshes(ExoII_Read &file1, ExoII_Read &file2, bool check_only, const std::vector &node_map, const std::vector &elmt_map, const INT *node_id_map) { bool is_diff = false; // NOTE: Check_Global is called earlier. Don't repeat call here. if (!Check_Nodal(file1, file2, node_map, node_id_map, check_only)) { Warning(".. Differences found in mesh nodal coordinates.\n"); is_diff = true; } if (!Check_Element_Block(file1, file2, elmt_map, node_map)) { Warning(".. Differences found in element block metadata or connectivity.\n"); is_diff = true; } if (!Check_Nodeset(file1, file2, node_map, check_only)) { Warning(".. Differences found in node set metadata or node lists.\n"); is_diff = true; } if (!Check_Sideset(file1, file2, elmt_map, check_only)) { Warning(".. Differences found in side set metadata or side lists.\n"); is_diff = true; } if (is_diff) { Warning(".. Differences found in mesh (non-transient) data. Aborting...\n"); exit(1); } } namespace { template bool Check_Nodal(ExoII_Read &file1, ExoII_Read &file2, const std::vector &node_map, const INT *id_map, bool check_only) { bool is_same = true; if (interFace.coord_tol.type == ToleranceMode::IGNORE_ || !check_only) { return is_same; } file1.Load_Nodal_Coordinates(); file2.Load_Nodal_Coordinates(); const auto *x1 = file1.X_Coords(); const double *y1 = x1; const double *z1 = x1; if (file1.Dimension() > 1) { y1 = file1.Y_Coords(); } if (file1.Dimension() > 2) { z1 = file1.Z_Coords(); } const auto *x2 = file2.X_Coords(); const double *y2 = x2; const double *z2 = x2; if (file2.Dimension() > 1) { y2 = file2.Y_Coords(); } if (file2.Dimension() > 2) { z2 = file2.Z_Coords(); } double max = 0.0; double norm; for (size_t n = 0; n < file1.Num_Nodes() && (is_same || interFace.show_all_diffs); ++n) { // Should this node be processed... if (node_map.empty() || node_map[n] >= 0) { INT n2 = node_map.empty() ? n : node_map[n]; double dx = interFace.coord_tol.Delta(x1[n], x2[n2]); if (dx > interFace.coord_tol.value) { fmt::print(" x coord {} diff: {:14.7e} ~ {:14.7e} ={:12.5e} (node {})\n", interFace.coord_tol.abrstr(), x1[n], x2[n2], dx, (size_t)id_map[n]); is_same = false; } norm = (x1[n] - x2[n2]) * (x1[n] - x2[n2]); if (file1.Dimension() > 1 && file2.Dimension() > 1) { double dy = interFace.coord_tol.Delta(y1[n], y2[n2]); if (dy > interFace.coord_tol.value) { fmt::print(" y coord {} diff: {:14.7e} ~ {:14.7e} ={:12.5e} (node {})\n", interFace.coord_tol.abrstr(), y1[n], y2[n2], dy, (size_t)id_map[n]); is_same = false; } norm += (y1[n] - y2[n2]) * (y1[n] - y2[n2]); } if (file1.Dimension() > 2 && file2.Dimension() > 2) { double dz = interFace.coord_tol.Delta(z1[n], z2[n2]); if (dz > interFace.coord_tol.value) { fmt::print(" z coord {} diff: {:14.7e} ~ {:14.7e} ={:12.5e} (node {})\n", interFace.coord_tol.abrstr(), z1[n], z2[n2], dz, (size_t)id_map[n]); is_same = false; } norm += (z1[n] - z2[n2]) * (z1[n] - z2[n2]); } max = max < norm ? norm : max; } // End of node iteration... } if (!interFace.quiet_flag && is_same && max > 0.0) { max = sqrt(max); fmt::print("Maximum difference between nodal coordinates = {}\n", max); } file1.Free_Nodal_Coordinates(); file2.Free_Nodal_Coordinates(); return is_same; } template bool Check_Element_Block(ExoII_Read &file1, ExoII_Read &file2, const std::vector &elmt_map, const std::vector &node_map) { bool is_same = true; // Verify that element blocks match in the two files... for (size_t b = 0; b < file1.Num_Element_Blocks(); ++b) { Exo_Block *block1 = file1.Get_Element_Block_by_Index(b); Exo_Block *block2 = nullptr; if (interFace.map_flag != MapType::DISTANCE && interFace.map_flag != MapType::PARTIAL) { if (block1 != nullptr) { if (interFace.by_name) { block2 = file2.Get_Element_Block_by_Name(block1->Name()); } else { block2 = file2.Get_Element_Block_by_Id(block1->Id()); } if (block2 == nullptr) { Warning(fmt::format(".. Block id {} with name {} exists in first " "file but not the second.\n", block1->Id(), block1->Name())); is_same = false; } else { if (!Check_Element_Block_Params(block1, block2)) { is_same = false; } else { // Only do this check if Check_Element_Block_Params does not fail. // TODO(gdsjaar): Pass in node_map and node_id_map... if (!Check_Element_Block_Connectivity(block1, block2, elmt_map, node_map)) { is_same = false; } } } } } } return is_same; } template bool Check_Element_Block_Connectivity(Exo_Block *block1, Exo_Block *block2, const std::vector &elmt_map, const std::vector &node_map) { bool is_same = true; SMART_ASSERT(block1 != nullptr && block2 != nullptr); block1->Load_Connectivity(); block2->Load_Connectivity(); const auto &conn1 = block1->Connectivity(); const auto &conn2 = block2->Connectivity(); SMART_ASSERT(block1->Size() == 0 || block1->Num_Nodes_per_Element() == 0 || !conn1.empty()); SMART_ASSERT(block2->Size() == 0 || block2->Num_Nodes_per_Element() == 0 || !conn2.empty()); if (interFace.map_flag == MapType::FILE_ORDER || elmt_map.empty()) { size_t node_count = block1->Size() * block1->Num_Nodes_per_Element(); SMART_ASSERT(node_count == block2->Size() * block2->Num_Nodes_per_Element()); if (interFace.map_flag != MapType::FILE_ORDER && !node_map.empty()) { for (size_t e = 0; e < node_count; ++e) { if (node_map[conn1[e] - 1] + 1 != conn2[e]) { size_t elem = e / block2->Num_Nodes_per_Element(); size_t node = e % block2->Num_Nodes_per_Element(); Warning( fmt::format(".. Connectivities in block id {} are not the same.\n" " First difference is node {} of local element {}\n", block1->Id(), node + 1, elem + 1)); is_same = false; break; } } } else { for (size_t e = 0; e < node_count; ++e) { if (conn1[e] != conn2[e]) { size_t elem = e / block2->Num_Nodes_per_Element(); size_t node = e % block2->Num_Nodes_per_Element(); Warning( fmt::format(".. Connectivities in block id {} are not the same.\n" " First difference is node {} of local element {}\n", block1->Id(), node + 1, elem + 1)); is_same = false; break; } } } } else { auto offset1 = block1->offset(); auto offset2 = block2->offset(); size_t num_element = block1->Size(); size_t nnpe = block1->Num_Nodes_per_Element(); for (size_t e1 = 0; is_same && e1 < num_element; e1++) { for (size_t n = 0; is_same && n < nnpe; ++n) { size_t off1 = e1 * nnpe + n; auto e2 = elmt_map[offset1 + e1]; if (e2 >= 0) { // If doing partial map, not all elements have a match e2 -= offset2; size_t off2 = e2 * nnpe + n; auto n1 = conn1[off1]; auto map_n1 = node_map.empty() ? n1 : node_map[n1 - 1] + 1; if (map_n1 != conn2[off2]) { Warning( fmt::format(".. Connectivities in block id {} are not the same.\n" " First difference is node {} of local element {} " "(file1) {} (file2)\n", block1->Id(), n + 1, e1 + 1, e2 + 1)); is_same = false; break; } } } } } block2->Free_Connectivity(); block1->Free_Connectivity(); return is_same; } template bool Check_Element_Block_Params(const Exo_Block *block1, const Exo_Block *block2) { bool is_same = true; SMART_ASSERT(block1 && block2); if (!interFace.by_name && block1->Id() != block2->Id()) { Warning(fmt::format(".. Block ids don't agree ({} != {}\n", block1->Id(), block2->Id())); is_same = false; } if (interFace.by_name && block1->Name() != block2->Name()) { Warning( fmt::format(".. Block names don't agree ({} != {}).\n", block1->Name(), block2->Name())); is_same = false; } if (!(no_case_equals(block1->Element_Type(), block2->Element_Type()))) { if (!interFace.short_block_check || !close_compare(block1->Element_Type(), block2->Element_Type())) { Warning(fmt::format(".. Block {}: element types don't agree ({} != {}).\n", block1->Id(), block1->Element_Type(), block2->Element_Type())); is_same = false; } } if (block1->Size() != block2->Size()) { Warning(fmt::format(".. Block {}: number of elements doesn't agree ({} != {}).\n", block1->Id(), block1->Size(), block2->Size())); is_same = false; } if (block1->Num_Nodes_per_Element() != block2->Num_Nodes_per_Element()) { Warning(fmt::format(".. Block {}: number of nodes per element doesn't agree ({} != {}).\n", block1->Id(), block1->Num_Nodes_per_Element(), block2->Num_Nodes_per_Element())); is_same = false; } #if 0 if (block1->Num_Attributes() != block2->Num_Attributes()) { Warning(fmt::format(".. Block {}: number of attributes doesn't agree ({} != {}).\n" block1->Id(), block1->Num_Attributes(), block2->Num_Attributes()); is_same = false; } #endif return is_same; } template bool Check_Nodeset(ExoII_Read &file1, ExoII_Read &file2, const std::vector &node_map, bool /*unused*/) { // Currently don't set diff flag for most of these since we // can continue (somewhat) with these differences... // As get more usage of nodeset/sideset variables, may revisit // what is a diff. bool is_same = true; if (file1.Num_Node_Sets() != file2.Num_Node_Sets()) { if (interFace.map_flag != MapType::PARTIAL) { Warning(".. Number of nodesets doesn't agree...\n"); if (interFace.pedantic) { is_same = false; } } } // Check that the files both contain the same nodesets... for (size_t b = 0; b < file1.Num_Node_Sets(); ++b) { Node_Set *set1 = file1.Get_Node_Set_by_Index(b); Node_Set *set2 = nullptr; if (interFace.by_name) { set2 = file2.Get_Node_Set_by_Name(set1->Name()); } else { set2 = file2.Get_Node_Set_by_Id(set1->Id()); } if (set2 == nullptr) { Warning( fmt::format(".. Nodeset id {} exists in first file but not the second.\n", set1->Id())); if (interFace.pedantic) { is_same = false; } } else { if (set1->Size() != set2->Size()) { Warning(fmt::format( ".. The node count for nodeset id {} is not the same in the two files ({} != {}).\n", set1->Id(), set1->Size(), set2->Size())); if (interFace.pedantic) { is_same = false; } } } } // Check that can access all nodesets in file2. // This should never fail if the above tests pass... for (size_t b = 0; b < file2.Num_Node_Sets(); ++b) { Node_Set *set2 = file2.Get_Node_Set_by_Index(b); if (set2 == nullptr) { Warning( fmt::format(".. Could not access the Nodeset with index {} in the second file.\n", b)); if (interFace.pedantic) { is_same = false; } } } // Do the following check(s) only if there are nodeset variables... // For each nodeset, check that the order of the nodeset nodes is the same. // Eventually need to be able to map the order... if (!interFace.ns_var_names.empty() || interFace.pedantic) { for (size_t b = 0; b < file1.Num_Node_Sets(); ++b) { Node_Set *set1 = file1.Get_Node_Set_by_Index(b); Node_Set *set2 = nullptr; if (interFace.by_name) { set2 = file2.Get_Node_Set_by_Name(set1->Name()); } else { set2 = file2.Get_Node_Set_by_Id(set1->Id()); } if (set2 == nullptr) { continue; } if (!node_map.empty()) { set1->apply_map(node_map); } if ((interFace.pedantic || set1->var_count() > 0) && (set1->Size() == set2->Size())) { size_t node_count = set1->Size(); int64_t diff = -1; for (size_t i = 0; i < node_count; i++) { if (set1->Node_Id(i) != set2->Node_Id(i)) { diff = i; break; } } if (diff >= 0) { Warning(fmt::format( ".. The nodelists for nodeset id {} are not the same in the two files.\n" "\t\tThe first difference is at position {}: Node {} vs. Node {}.\n", set1->Id(), set1->Node_Index(diff) + 1, set1->Node_Id(diff), set2->Node_Id(diff))); if (interFace.map_flag != MapType::PARTIAL) { is_same = false; } else { Warning(".. The nodelist differences are ignored for the " "partial_map case.\n"); } } } } } return is_same; } template bool Check_Sideset(ExoII_Read &file1, ExoII_Read &file2, const std::vector &elmt_map, bool /*unused*/) { // Currently don't set diff flag for most of these since we // can continue (somewhat) with these differences... // As get more usage of nodeset/sideset variables, may revisit // what is a diff. bool is_same = true; if (file1.Num_Side_Sets() != file2.Num_Side_Sets()) { if (interFace.map_flag != MapType::PARTIAL) { Warning(".. Number of sidesets doesn't agree...\n"); if (interFace.pedantic) { is_same = false; } } } // Check that the files both contain the same sidesets... for (size_t b = 0; b < file1.Num_Side_Sets(); ++b) { Side_Set *set1 = file1.Get_Side_Set_by_Index(b); Side_Set *set2 = nullptr; if (interFace.by_name) { set2 = file2.Get_Side_Set_by_Name(set1->Name()); } else { set2 = file2.Get_Side_Set_by_Id(set1->Id()); } if (set2 == nullptr) { Warning( fmt::format(".. Sideset id {} exists in first file but not the second.\n", set1->Id())); if (interFace.pedantic) { is_same = false; } } else { if (set1->Size() != set2->Size()) { Warning(fmt::format( ".. The side count for sideset id {} is not the same in the two files ({} != {}).\n", set1->Id(), set1->Size(), set2->Size())); if (interFace.pedantic) { is_same = false; } } } } for (size_t b = 0; b < file2.Num_Side_Sets(); ++b) { Side_Set *set2 = file2.Get_Side_Set_by_Index(b); if (set2 == nullptr) { Warning( fmt::format(".. Could not access the Sideset with index {} in the second file.\n", b)); if (interFace.pedantic) { is_same = false; } } } // Do the following check(s) only if there are sideset variables... (or -pedantic) // For each sideset, check that the order of the sideset sides is the same. // Eventually need to be able to map the order... if (!interFace.ss_var_names.empty() || interFace.pedantic || !interFace.ignore_sideset_df) { for (size_t b = 0; b < file1.Num_Side_Sets(); ++b) { Side_Set *set1 = file1.Get_Side_Set_by_Index(b); Side_Set *set2 = nullptr; if (interFace.by_name) { set2 = file2.Get_Side_Set_by_Name(set1->Name()); } else { set2 = file2.Get_Side_Set_by_Id(set1->Id()); } if (set2 == nullptr) { continue; } if (!elmt_map.empty()) { set1->apply_map(elmt_map); } // Don't care if sidesets don't match if there are no variables... // If different sizes and pedantic, difference caught above. if ((interFace.pedantic || set1->var_count() > 0) && (set1->Size() == set2->Size())) { size_t side_count = set1->Size(); int64_t diff = -1; for (size_t i = 0; i < side_count; i++) { if (set1->Side_Id(i) != set2->Side_Id(i)) { diff = i; break; } } if (diff >= 0) { // If `elmt_map` is not null, then need to unmap the set1 ids to get the local id that // appears in the file. If don't do this, error message is very confusing for the // user... auto set1_id = set1->Side_Id(diff).first; if (!elmt_map.empty()) { // Iterate map to find an entry equal to `set1_id`. Its position is then the file1 id // of the element. for (size_t i = 0; i < file1.Num_Elements(); i++) { if (elmt_map[i] == set1_id - 1) { set1_id = i + 1; break; } } } Warning(fmt::format( ".. The sidelists for sideset id {} are not the same in the two files.\n" "\t\tThe first difference is at position {}: Side {}.{} .vs. Side {}.{}.\n", set1->Id(), set1->Side_Index(diff) + 1, set1_id, set1->Side_Id(diff).second, set2->Side_Id(diff).first, set2->Side_Id(diff).second)); if (interFace.map_flag != MapType::PARTIAL) { is_same = false; } else { Warning(".. The sidelist differences are ignored for the " "partial_map case.\n"); } } } } } return is_same; } bool close_compare(const std::string &st1, const std::string &st2) { auto len1 = st1.size(); auto len2 = st2.size(); // Check that digits (if any) at end of names match while ((isdigit(st1[len1 - 1]) != 0) && (isdigit(st2[len2 - 1]) != 0)) { if (st1[len1 - 1] != st2[len2 - 1]) { return false; } len1--; len2--; } // Skip any digits at the end. It's OK if only one name has // digits since we want 'tri' and 'triangle6' to match, but not // tri6 and tri3 nor quad9 and tri9 while (isdigit(st1[len1 - 1]) != 0) { len1--; } while (isdigit(st2[len2 - 1]) != 0) { len2--; } unsigned length = (len1 < len2) ? len1 : len2; for (unsigned i = 0; i < length; ++i) { if (toupper(st1[i]) != toupper(st2[i])) { return false; } } return true; } } // namespace template bool Check_Global(ExoII_Read &file1, ExoII_Read &file2); template void Check_Compatible_Meshes(ExoII_Read &file1, ExoII_Read &file2, bool check_only, const std::vector &node_map, const std::vector &elmt_map, const int *node_id_map); template bool Check_Global(ExoII_Read &file1, ExoII_Read &file2); template void Check_Compatible_Meshes(ExoII_Read &file1, ExoII_Read &file2, bool check_only, const std::vector &node_map, const std::vector &elmt_map, const int64_t *node_id_map);