239 auto & boundary_info =
mesh->get_boundary_info();
240 auto side_tuples = boundary_info.build_side_list();
242 for (
const auto bid : boundary_info.get_boundary_ids())
246 std::set<std::pair<subdomain_id_type, subdomain_id_type>> block_neighbors;
247 for (
const auto index : index_range(side_tuples))
249 if (std::get<2>(side_tuples[index]) != bid)
251 const auto elem_ptr =
mesh->elem_ptr(std::get<0>(side_tuples[index]));
252 if (elem_ptr->neighbor_ptr(std::get<1>(side_tuples[index])))
253 block_neighbors.insert(std::make_pair(
254 elem_ptr->subdomain_id(),
255 elem_ptr->neighbor_ptr(std::get<1>(side_tuples[index]))->subdomain_id()));
259 std::set<std::pair<subdomain_id_type, subdomain_id_type>> flipped_pairs;
260 for (
const auto & block_pair_1 : block_neighbors)
261 for (
const auto & block_pair_2 : block_neighbors)
262 if (block_pair_1 != block_pair_2)
263 if (block_pair_1.first == block_pair_2.second &&
264 block_pair_1.second == block_pair_2.first)
265 flipped_pairs.insert(block_pair_1);
268 const std::string sideset_full_name =
269 boundary_info.sideset_name(bid) +
" (" + std::to_string(bid) +
")";
270 if (!flipped_pairs.empty())
272 std::string block_pairs_string =
"";
273 for (
const auto & pair : flipped_pairs)
274 block_pairs_string +=
275 " [" +
mesh->subdomain_name(pair.first) +
" (" + std::to_string(pair.first) +
"), " +
276 mesh->subdomain_name(pair.second) +
" (" + std::to_string(pair.second) +
")]";
277 message =
"Inconsistent orientation of sideset " + sideset_full_name +
278 " with regards to subdomain pairs" + block_pairs_string;
281 message =
"Sideset " + sideset_full_name +
282 " is consistently oriented with regards to the blocks it neighbors";
289 unsigned int num_normals_flipping = 0;
290 Real steepest_side_angles = 0;
291 for (
const auto & [elem_id,
side_id, side_bid] : side_tuples)
295 const auto & elem_ptr =
mesh->elem_ptr(elem_id);
298 const std::unique_ptr<const Elem> face = elem_ptr->build_side_ptr(
side_id);
299 std::unique_ptr<libMesh::FEBase> fe(
302 fe->attach_quadrature_rule(&qface);
303 const auto & normals = fe->get_normals();
305 mooseAssert(normals.size() == 1,
"We expected only one normal here");
306 const auto & side_normal = normals[0];
309 for (
const auto neighbor : elem_ptr->neighbor_ptr_range())
311 for (
const auto neigh_side_index : neighbor->side_index_range())
314 if (!boundary_info.has_boundary_id(neighbor, neigh_side_index, bid))
321 fe_neighbor->attach_quadrature_rule(&qface);
322 const auto & neigh_normals = fe_neighbor->get_normals();
323 fe_neighbor->reinit(neighbor, neigh_side_index);
324 mooseAssert(neigh_normals.size() == 1,
"We expected only one normal here");
325 const auto & neigh_side_normal = neigh_normals[0];
328 if (neigh_side_normal * side_normal <= 0)
330 num_normals_flipping++;
331 steepest_side_angles =
332 std::max(std::acos(neigh_side_normal * side_normal), steepest_side_angles);
334 _console <<
"Side normals changed by more than pi/2 for sideset "
335 << sideset_full_name <<
" between side " <<
side_id <<
" of element "
336 << elem_ptr->id() <<
" and side " << neigh_side_index
337 <<
" of neighbor element " << neighbor->id() << std::endl;
339 _console <<
"Maximum output reached for sideset normal flipping check. Silencing "
346 if (num_normals_flipping)
347 message =
"Sideset " + sideset_full_name +
348 " has two neighboring sides with a very large angle. Largest angle detected: " +
349 std::to_string(steepest_side_angles) +
" rad (" +
350 std::to_string(steepest_side_angles * 180 /
libMesh::pi) +
" degrees).";
352 message =
"Sideset " + sideset_full_name +
353 " does not appear to have side-to-neighbor-side orientation flips. All neighbor "
354 "sides normal differ by less than pi/2";
817 const std::unique_ptr<MeshBase> & mesh)
const
819 unsigned int num_likely_AMR_created_nonconformality = 0;
820 auto pl =
mesh->sub_point_locator();
826 auto mesh_copy =
mesh->clone();
830 for (
auto & node :
mesh->node_ptr_range())
833 std::set<const Elem *> elements_around;
834 (*pl)(*node, elements_around);
837 std::set<const Elem *> fine_elements;
838 std::set<const Elem *> coarse_elements;
841 for (
auto elem : elements_around)
845 bool node_on_elem =
false;
851 if (!elem->is_vertex(elem->get_node_index(node)))
858 fine_elements.insert(elem);
862 coarse_elements.insert(elem);
869 if (fine_elements.size() == elements_around.size())
872 if (fine_elements.empty())
879 const auto elem_type = (*fine_elements.begin())->
type();
880 if ((elem_type == QUAD4 || elem_type == QUAD8 || elem_type == QUAD9) &&
881 fine_elements.size() != 2)
883 else if ((elem_type == HEX8 || elem_type == HEX20 || elem_type == HEX27) &&
884 fine_elements.size() != 4)
886 else if ((elem_type == TRI3 || elem_type == TRI6 || elem_type == TRI7) &&
887 fine_elements.size() != 3)
889 else if ((elem_type == TET4 || elem_type == TET10 || elem_type == TET14) &&
890 (fine_elements.size() % 2 != 0))
897 if (elem_type != TET4 && elem_type != TET10 && elem_type != TET14 && coarse_elements.size() > 1)
904 std::vector<const Node *> tentative_coarse_nodes;
908 if (elem_type == QUAD4 || elem_type == QUAD8 || elem_type == QUAD9 || elem_type == HEX8 ||
909 elem_type == HEX20 || elem_type == HEX27)
911 const auto elem = *fine_elements.begin();
914 std::vector<Elem *> node_on_sides;
915 unsigned int side_inside_parent = std::numeric_limits<unsigned int>::max();
916 for (
auto i : make_range(elem->n_sides()))
918 const auto side = elem->side_ptr(i);
919 std::vector<const Node *> other_nodes_on_side;
920 bool node_on_side =
false;
921 for (
const auto & elem_node : side->node_ref_range())
923 if (*node == elem_node)
926 other_nodes_on_side.emplace_back(&elem_node);
934 bool all_side_nodes_are_shared =
true;
935 for (
const auto & other_node : other_nodes_on_side)
937 bool shared_with_a_fine_elem =
false;
938 for (
const auto & other_elem : fine_elements)
939 if (other_elem != elem &&
941 shared_with_a_fine_elem =
true;
943 if (!shared_with_a_fine_elem)
944 all_side_nodes_are_shared =
false;
946 if (all_side_nodes_are_shared)
948 side_inside_parent = i;
954 if (side_inside_parent == std::numeric_limits<unsigned int>::max())
961 const auto interior_side = elem->side_ptr(side_inside_parent);
962 const Node * interior_node =
nullptr;
963 for (
const auto & other_node : interior_side->node_ref_range())
965 if (other_node == *node)
967 bool in_all_node_neighbor_elements =
true;
968 for (
auto other_elem : fine_elements)
971 in_all_node_neighbor_elements =
false;
973 if (in_all_node_neighbor_elements)
975 interior_node = &other_node;
984 std::set<const Elem *> all_elements;
985 elem->find_point_neighbors(*interior_node, all_elements);
987 if (elem_type == QUAD4 || elem_type == QUAD8 || elem_type == QUAD9)
990 *interior_node, *node, *elem, tentative_coarse_nodes, fine_elements);
1000 const auto & coarse_elem = *coarse_elements.begin();
1001 unsigned short coarse_side_i = 0;
1002 for (
const auto & coarse_side_index : coarse_elem->side_index_range())
1004 const auto coarse_side_ptr = coarse_elem->side_ptr(coarse_side_index);
1010 coarse_side_i = coarse_side_index;
1014 const auto coarse_side = coarse_elem->side_ptr(coarse_side_i);
1027 tentative_coarse_nodes.resize(4);
1028 for (
const auto & elem_1 : fine_elements)
1029 for (
const auto & coarse_node : elem_1->node_ref_range())
1031 bool node_shared =
false;
1032 for (
const auto & elem_2 : fine_elements)
1034 if (elem_2 != elem_1)
1042 elem_1->is_vertex(elem_1->get_node_index(&coarse_node)))
1043 tentative_coarse_nodes[i++] = &coarse_node;
1044 mooseAssert(i <= 5,
"We went too far in this index");
1054 Point axis = *interior_node - *node;
1055 const auto start_circle = elem->vertex_average();
1057 tentative_coarse_nodes, *interior_node, start_circle, axis);
1058 tentative_coarse_nodes.resize(8);
1062 for (
const auto & elem : fine_elements)
1065 unsigned int node_index = 0;
1066 for (
const auto & coarse_node : tentative_coarse_nodes)
1074 for (
const auto & neighbor : elem->neighbor_ptr_range())
1075 if (all_elements.count(neighbor) && !fine_elements.count(neighbor))
1078 const Node * coarse_elem_node =
nullptr;
1079 for (
const auto & fine_node : neighbor->node_ref_range())
1081 if (!neighbor->is_vertex(neighbor->get_node_index(&fine_node)))
1083 bool node_shared =
false;
1084 for (
const auto & elem_2 : all_elements)
1085 if (elem_2 != neighbor &&
1090 coarse_elem_node = &fine_node;
1095 tentative_coarse_nodes[node_index + 4] = coarse_elem_node;
1096 mooseAssert(node_index + 4 < tentative_coarse_nodes.size(),
"Indexed too far");
1097 mooseAssert(coarse_elem_node,
"Did not find last coarse element node");
1103 fine_elements = all_elements;
1107 else if (elem_type == TRI3 || elem_type == TRI6 || elem_type == TRI7)
1112 const Elem * center_elem =
nullptr;
1113 for (
const auto refined_elem_1 : fine_elements)
1115 unsigned int num_neighbors = 0;
1116 for (
const auto refined_elem_2 : fine_elements)
1118 if (refined_elem_1 == refined_elem_2)
1120 if (refined_elem_1->has_neighbor(refined_elem_2))
1123 if (num_neighbors >= 2)
1124 center_elem = refined_elem_1;
1130 for (
const auto refined_elem : fine_elements)
1132 if (refined_elem == center_elem)
1134 for (
const auto & other_node : refined_elem->node_ref_range())
1136 refined_elem->is_vertex(refined_elem->get_node_index(&other_node)))
1137 tentative_coarse_nodes.push_back(&other_node);
1142 unsigned int center_side_opposite_node = std::numeric_limits<unsigned int>::max();
1143 for (
auto side_index : center_elem->side_index_range())
1145 center_side_opposite_node = side_index;
1146 const auto neighbor_on_other_side_of_opposite_center_side =
1147 center_elem->neighbor_ptr(center_side_opposite_node);
1150 if (!neighbor_on_other_side_of_opposite_center_side)
1153 fine_elements.insert(neighbor_on_other_side_of_opposite_center_side);
1154 for (
const auto & tri_node : neighbor_on_other_side_of_opposite_center_side->node_ref_range())
1155 if (neighbor_on_other_side_of_opposite_center_side->is_vertex(
1156 neighbor_on_other_side_of_opposite_center_side->get_node_index(&tri_node)) &&
1157 center_elem->side_ptr(center_side_opposite_node)->get_node_index(&tri_node) ==
1159 tentative_coarse_nodes.push_back(&tri_node);
1161 mooseAssert(center_side_opposite_node != std::numeric_limits<unsigned int>::max(),
1162 "Did not find the side opposite the non-conformality");
1163 mooseAssert(tentative_coarse_nodes.size() == 3,
1164 "We are forming a coarsened triangle element");
1168 else if (elem_type == TET4 || elem_type == TET10 || elem_type == TET14)
1172 std::set<const Elem *> tips_tets;
1173 std::set<const Elem *> inside_tets;
1176 const Elem * coarse_elem =
nullptr;
1177 std::set<const Elem *> fine_tets;
1178 for (
auto & coarse_one : coarse_elements)
1180 for (
const auto & elem : fine_elements)
1183 if (elem->has_neighbor(coarse_one))
1184 fine_tets.insert(elem);
1186 if (fine_tets.size())
1188 coarse_elem = coarse_one;
1197 for (
const auto & elem : fine_elements)
1199 int num_face_neighbors = 0;
1200 for (
const auto & tet : fine_tets)
1201 if (tet->has_neighbor(elem))
1202 num_face_neighbors++;
1203 if (num_face_neighbors == 2)
1205 fine_tets.insert(elem);
1213 std::set<const Node *> other_nodes;
1214 for (
const auto & tet_1 : fine_tets)
1216 for (
const auto & node_1 : tet_1->node_ref_range())
1218 if (&node_1 == node)
1220 if (!tet_1->is_vertex(tet_1->get_node_index(&node_1)))
1222 for (
const auto & tet_2 : fine_tets)
1229 other_nodes.insert(&node_1);
1233 mooseAssert(other_nodes.size() == 2,
1234 "Should find only two extra non-conformal nodes near the coarse element");
1237 for (
const auto & tet_1 : fine_tets)
1239 for (
const auto & neighbor : tet_1->neighbor_ptr_range())
1241 neighbor->is_vertex(neighbor->get_node_index(*other_nodes.begin())) &&
1243 neighbor->is_vertex(neighbor->get_node_index(*other_nodes.rbegin())))
1244 fine_tets.insert(neighbor);
1247 for (
const auto & tet_1 : fine_tets)
1249 for (
const auto & neighbor : tet_1->neighbor_ptr_range())
1251 neighbor->is_vertex(neighbor->get_node_index(*other_nodes.begin())) &&
1253 neighbor->is_vertex(neighbor->get_node_index(*other_nodes.rbegin())))
1254 fine_tets.insert(neighbor);
1258 for (
const auto & tet_1 : fine_tets)
1259 for (
const auto & neighbor : tet_1->neighbor_ptr_range())
1260 for (
const auto & tet_2 : fine_tets)
1261 if (tet_1 != tet_2 && tet_2->has_neighbor(neighbor) && neighbor != coarse_elem)
1262 fine_tets.insert(neighbor);
1265 for (
const auto & tet_1 : fine_tets)
1267 unsigned int unshared_nodes = 0;
1268 for (
const auto & other_node : tet_1->node_ref_range())
1270 if (!tet_1->is_vertex(tet_1->get_node_index(&other_node)))
1272 bool node_shared =
false;
1273 for (
const auto & tet_2 : fine_tets)
1279 if (unshared_nodes == 1)
1280 tips_tets.insert(tet_1);
1281 else if (unshared_nodes == 0)
1282 inside_tets.insert(tet_1);
1284 mooseError(
"Did not expect a tet to have two unshared vertex nodes here");
1291 for (
const auto & tet : inside_tets)
1293 for (
const auto & neighbor : tet->neighbor_ptr_range())
1296 bool shared_with_another_tet =
false;
1297 for (
const auto & tet_2 : fine_tets)
1301 if (tet_2->has_neighbor(neighbor))
1302 shared_with_another_tet =
true;
1304 if (shared_with_another_tet)
1308 std::vector<const Node *> tip_nodes_shared;
1309 unsigned int unshared_nodes = 0;
1310 for (
const auto & other_node : neighbor->node_ref_range())
1312 if (!neighbor->is_vertex(neighbor->get_node_index(&other_node)))
1316 for (
const auto & tip_tet : tips_tets)
1318 if (neighbor == tip_tet)
1323 tip_nodes_shared.push_back(&other_node);
1326 bool node_shared =
false;
1327 for (
const auto & tet_2 : fine_tets)
1333 if (tip_nodes_shared.size() == 3 && unshared_nodes == 1)
1334 tips_tets.insert(neighbor);
1341 fine_elements.clear();
1342 for (
const auto & elem : tips_tets)
1343 fine_elements.insert(elem);
1344 for (
const auto & elem : inside_tets)
1345 fine_elements.insert(elem);
1348 for (
const auto & tip : tips_tets)
1350 for (
const auto & node : tip->node_ref_range())
1352 bool outside =
true;
1354 const auto id = tip->get_node_index(&node);
1355 if (!tip->is_vertex(
id))
1357 for (
const auto & tet : inside_tets)
1362 tentative_coarse_nodes.push_back(&node);
1369 std::sort(tentative_coarse_nodes.begin(), tentative_coarse_nodes.end());
1370 tentative_coarse_nodes.erase(
1371 std::unique(tentative_coarse_nodes.begin(), tentative_coarse_nodes.end()),
1372 tentative_coarse_nodes.end());
1376 if (tentative_coarse_nodes.size() != 4)
1383 ". Skipping detection for this node and all future nodes near only this "
1389 for (
auto elem : fine_elements)
1390 if (elem->type() != elem_type)
1394 for (
const auto & check_node : tentative_coarse_nodes)
1395 if (check_node ==
nullptr)
1399 std::unique_ptr<Elem> parent = Elem::build(Elem::first_order_equivalent_type(elem_type));
1400 auto parent_ptr = mesh_copy->add_elem(parent.release());
1403 for (
auto i : index_range(tentative_coarse_nodes))
1404 parent_ptr->set_node(i, mesh_copy->node_ptr(tentative_coarse_nodes[i]->id()));
1407 parent_ptr->set_refinement_flag(Elem::REFINE);
1408 parent_ptr->refine(mesh_refiner);
1409 const auto num_children = parent_ptr->n_children();
1417 unsigned int num_children_match = 0;
1418 for (
const auto & child : parent_ptr->child_ref_range())
1420 for (
const auto & potential_children : fine_elements)
1421 if (MooseUtils::absoluteFuzzyEqual(child.vertex_average()(0),
1422 potential_children->vertex_average()(0),
1424 MooseUtils::absoluteFuzzyEqual(child.vertex_average()(1),
1425 potential_children->vertex_average()(1),
1427 MooseUtils::absoluteFuzzyEqual(child.vertex_average()(2),
1428 potential_children->vertex_average()(2),
1431 num_children_match++;
1436 if (num_children_match == num_children ||
1437 ((elem_type == TET4 || elem_type == TET10 || elem_type == TET14) &&
1438 num_children_match == 4))
1440 num_likely_AMR_created_nonconformality++;
1441 if (num_likely_AMR_created_nonconformality <
_num_outputs)
1443 _console <<
"Detected non-conformality likely created by AMR near" << *node
1445 <<
" elements that could be merged into a coarse element:" << std::endl;
1446 for (
const auto & elem : fine_elements)
1450 else if (num_likely_AMR_created_nonconformality ==
_num_outputs)
1451 _console <<
"Maximum log output reached, silencing output" << std::endl;
1456 "Number of non-conformal nodes likely due to mesh refinement detected by heuristic: " +
1459 num_likely_AMR_created_nonconformality);
1460 pl->unset_close_to_point_tol();
1603 if (
mesh->mesh_dimension() != 3)
1605 mooseWarning(
"The edge intersection algorithm only works with 3D meshes. "
1606 "'examine_non_matching_edges' is skipped");
1609 if (!
mesh->is_serial())
1610 mooseError(
"Only serialized/replicated meshes are supported");
1611 unsigned int num_intersecting_edges = 0;
1615 std::unordered_map<Elem *, BoundingBox> bounding_box_map;
1616 for (
const auto elem :
mesh->active_element_ptr_range())
1618 const auto boundingBox = elem->loose_bounding_box();
1619 bounding_box_map.insert({elem, boundingBox});
1622 std::unique_ptr<PointLocatorBase> point_locator =
mesh->sub_point_locator();
1623 std::set<std::array<dof_id_type, 4>> overlapping_edges_nodes;
1624 for (
const auto elem :
mesh->active_element_ptr_range())
1627 std::set<const Elem *> candidate_elems;
1628 std::set<const Elem *> nearby_elems;
1629 for (
unsigned int i = 0; i < elem->n_nodes(); i++)
1631 (*point_locator)(elem->point(i), candidate_elems);
1632 nearby_elems.insert(candidate_elems.begin(), candidate_elems.end());
1634 std::vector<std::unique_ptr<const Elem>> elem_edges(elem->n_edges());
1635 for (
auto i : elem->edge_index_range())
1636 elem_edges[i] = elem->build_edge_ptr(i);
1637 for (
const auto other_elem : nearby_elems)
1640 if (elem->id() >= other_elem->id())
1643 std::vector<std::unique_ptr<const Elem>> other_edges(other_elem->n_edges());
1644 for (
auto j : other_elem->edge_index_range())
1645 other_edges[j] = other_elem->build_edge_ptr(j);
1646 for (
auto & edge : elem_edges)
1648 for (
auto & other_edge : other_edges)
1651 const Node * n1 = edge->get_nodes()[0];
1652 const Node * n2 = edge->get_nodes()[1];
1653 const Node * n3 = other_edge->get_nodes()[0];
1654 const Node * n4 = other_edge->get_nodes()[1];
1657 std::array<dof_id_type, 4> node_id_array = {n1->id(), n2->id(), n3->id(), n4->id()};
1658 std::sort(node_id_array.begin(), node_id_array.end());
1661 if (overlapping_edges_nodes.count(node_id_array))
1667 if (edge->type() != EDGE2)
1670 " was found in cell " + std::to_string(elem->id()) +
1671 " which is of type " +
1673 "The edge intersection check only works for EDGE2 "
1674 "elements.\nThis message will not be output again";
1675 mooseDoOnce(
_console << element_message << std::endl);
1678 if (other_edge->type() != EDGE2)
1682 Point intersection_coords;
1688 overlapping_edges_nodes.insert(node_id_array);
1689 num_intersecting_edges += 2;
1693 std::string elem_id = std::to_string(elem->id());
1694 std::string other_elem_id = std::to_string(other_elem->id());
1695 std::string x_coord = std::to_string(intersection_coords(0));
1696 std::string y_coord = std::to_string(intersection_coords(1));
1697 std::string z_coord = std::to_string(intersection_coords(2));
1698 std::string message =
"Intersecting edges found between elements " + elem_id +
1699 " and " + other_elem_id +
" near point (" + x_coord +
", " +
1700 y_coord +
", " + z_coord +
")";
1711 num_intersecting_edges);