libMesh
Loading...
Searching...
No Matches
Functions
libMesh::MeshTools::Modification Namespace Reference

Tools for Mesh modification. More...

Functions

void distort (MeshBase &mesh, const Real factor, const bool perturb_boundary=false)
 Randomly perturb the nodal locations.
 
void permute_elements (MeshBase &mesh)
 Randomly permute the nodal ordering of each element (without twisting the element mapping).
 
void orient_elements (MeshBase &mesh)
 Redo the nodal ordering of each element as necessary to give the element Jacobian a positive orientation.
 
void redistribute (MeshBase &mesh, const FunctionBase< Real > &mapfunc)
 Deterministically perturb the nodal locations.
 
void translate (MeshBase &mesh, const Real xt=0., const Real yt=0., const Real zt=0.)
 Translates the mesh.
 
RealTensorValue rotate (MeshBase &mesh, const Real phi, const Real theta=0., const Real psi=0.)
 Rotates the mesh in 3D space.
 
void scale (MeshBase &mesh, const Real xs, const Real ys=0., const Real zs=0.)
 Scales the mesh.
 
void all_tri (MeshBase &mesh)
 Subdivides any non-simplex elements in a Mesh to produce simplex (triangular in 2D, tetrahedral in 3D) elements.
 
void all_rbb (MeshBase &mesh)
 Converts all element geometric mappings from the default Lagrange to the more flexible Rational-Bezier-Bernstein.
 
void smooth (MeshBase &, unsigned int, Real)
 Smooth the mesh with a simple Laplace smoothing algorithm.
 
void interpolate_surface (MeshBase &mesh, const Surface &surface, std::set< std::size_t > ids={}, bool use_boundary_nodes=true)
 Move nodes in mesh to their closest points on the specified surface.
 
void flatten (MeshBase &mesh)
 Removes all the refinement tree structure of Mesh, leaving only the highest-level (most-refined) elements.
 
void change_boundary_id (MeshBase &mesh, const boundary_id_type old_id, const boundary_id_type new_id)
 Finds any boundary ids that are currently old_id, changes them to new_id.
 
void change_subdomain_id (MeshBase &mesh, const subdomain_id_type old_id, const subdomain_id_type new_id)
 Finds any subdomain ids that are currently old_id, changes them to new_id.
 

Detailed Description

Tools for Mesh modification.

Author
Benjamin S. Kirk
Date
2004

Function Documentation

◆ all_rbb()

void libMesh::MeshTools::Modification::all_rbb ( MeshBase mesh)

Converts all element geometric mappings from the default Lagrange to the more flexible Rational-Bezier-Bernstein.

When elements have curved edges and/or faces, node weights are chosen so that the new edges interpolate the old edge node locations with a circular arc.

Definition at line 1260 of file mesh_modification.C.

1261{
1262 LOG_SCOPE("all_rbb()", "MeshTools::Modification");
1263
1264 // By default, use 1.0 as the weight on every RATIONAL_BERNSTEIN
1265 // mapped node
1266 const Real default_weight = 1.0;
1267
1268 const auto weight_index =
1269 (mesh.add_node_datum<Real>("rational_weight", true,
1270 &default_weight));
1271
1273 mesh.set_default_mapping_data(weight_index);
1274
1275 // Out of loop to reduce heap allocations
1276 std::unique_ptr<Elem> edge_ptr, face_ptr;
1277
1278 for (auto & elem : mesh.element_ptr_range())
1279 {
1280 if (elem->level())
1281 libmesh_not_implemented_msg
1282 ("all_rbb() currently only supports flat meshes with no refinement levels");
1283
1284#ifdef LIBMESH_ENABLE_INFINITE_ELEMENTS
1285 if (elem->infinite())
1286 libmesh_not_implemented_msg
1287 ("all_rbb() currently only supports finite geometric elements");
1288#endif
1289
1290 elem->set_mapping_type(RATIONAL_BERNSTEIN_MAP);
1291 elem->set_mapping_data(weight_index);
1292
1293 // Nothing to do unless we have curves to correct
1294 if (elem->default_order() == FIRST)
1295 continue;
1296
1297 // Modify the center node of an "edge" - possibly an actual edge
1298 // element's node, possibly a center node between points on a
1299 // face's or cell's edge - for RBB interpolation. This should fit
1300 // a circular curve exactly in cases where the original nodes are
1301 // equispaced and the outer nodes' weights are equal, and should
1302 // be a good fit otherwise.
1303 //
1304 // We want to use this to interpolate "internal" conceptual
1305 // edges of a Hex27 too, so we'll handle the cases where w0 and
1306 // w1 aren't 1, as well as the cases where the Nodes n0 and n1
1307 // are already control points which don't match their
1308 // corresponding physical points.
1309 auto make_edge_rbb = [default_weight, weight_index]
1310 (const Node & n0, const Node & n1, Node & n_center,
1311 const Point & p0, const Point & p1)
1312 {
1313 // Skip edges we've already modified; the center node for
1314 // these is no longer at the curve point we wish to
1315 // interpolate, it should already be at the control point that
1316 // accomplishes the interpolation.
1317 const Real old_weight = n_center.get_extra_datum<Real>(weight_index);
1318 if (old_weight != default_weight)
1319 return;
1320
1321 Point & p2 = n_center;
1322
1323 const Real w0 = n0.get_extra_datum<Real>(weight_index);
1324 const Real w1 = n1.get_extra_datum<Real>(weight_index);
1325
1326 const Point e02 = p2-p0,
1327 e21 = p1-p2;
1328 const Real chord_02_len_sq = e02.norm_sq(),
1329 chord_21_len_sq = e21.norm_sq();
1330
1331 // First find the cosine of phi, the angle between our two
1332 // subchords (turning from the direction of one to the
1333 // direction of the other; this is the supplementary angle to
1334 // the angle at the midpoint). This is the same as half of
1335 // the angle of our circular arc, which nicely enough is also
1336 // the angle we take cos and sec of in NURBS formulae
1337 const Real cos_phi = (e02*e21)/std::sqrt(chord_02_len_sq*chord_21_len_sq);
1338
1339 // There's a way to do really large arcs using negative
1340 // weights, but we're going to get lousy approximation quality
1341 // from isoparametric elements if we go too low, as well as
1342 // bad numerics here, so let's just disallow it.
1343 if (cos_phi < 0.5)
1344 libmesh_not_implemented_msg
1345 ("all_rbb() is not recommended for extremely sharp curves on one edge");
1346
1347 const Real w_center = cos_phi*std::sqrt(w0*w1);
1348
1349 n_center.set_extra_datum<Real>(weight_index, w_center);
1350
1351 // Now let's get the control point location. This comes from
1352 // a lot of back-and-forth with Gemini, but fortunately I'm
1353 // rewriting it after I've already added unit tests that
1354 // should scream if it's badly wrong.
1355 const Real w_mid = w0/4 + w1/4 + w_center/2;
1356 n_center *= 2*w_mid;
1357 n_center -= (w0 * p0 + w1 * p1)/2;
1358 n_center /= w_center;
1359 };
1360
1361 auto make_face_rbb = [weight_index] (Elem & face)
1362 {
1363 // Prisms and pyramids may need to skip some faces while
1364 // adjusting others
1365 if (face.type() == TRI6)
1366 return;
1367
1368 if (face.type() != QUAD9)
1369 libmesh_not_implemented_msg
1370 ("all_rbb() currently only supports mid-face nodes on Quad9 faces");
1371
1372 // We only use [4,8) but matching indices is nice and stack is
1373 // cheap.
1374 Real w[9];
1375
1376 for (unsigned int i : make_range(4u, 8u))
1377 w[i] = face.node_ref(i).get_extra_datum<Real>(weight_index);
1378
1379 // We can't currently handle arbitrary vertex weights
1380#ifndef NDEBUG
1381 for (unsigned int i : make_range(4u))
1382 libmesh_assert_equal_to
1383 (face.node_ref(i).get_extra_datum<Real>(weight_index), 1);
1384#endif
1385
1386 // For the mid-face point, if we want to exactly match
1387 // any cylinders and cones and spheres, we're actually already
1388 // entirely constrained by the other points.
1389 //
1390 // This formula gives the minimum-energy Steiner surface based
1391 // on the outer 8 points.
1392 //
1393 // That's an isogeometric representation of a cylinder aligned
1394 // to either axis, or of a sphere where the quad edges are on
1395 // latitude/longitude lines, or of a cone where two edges are
1396 // segments of cone generating lines and the other two are
1397 // arcs perpendicular to the axis.
1398 //
1399 // It's not perfectly isogeometric for the spheres we generate
1400 // (where the quad edges are all great circles), but it should
1401 // still converge asymptotically faster than non-rational
1402 // quadratic Lagrange.
1403 const Point xi_avg = (face.point(7) + face.point(5))/2;
1404 const Point eta_avg = (face.point(4) + face.point(6))/2;
1405 const Point vertex_avg = (face.point(0) + face.point(1) +
1406 face.point(2) + face.point(3))/4;
1407
1408 const Real w_xi = (w[7] + w[5])/2;
1409 const Real w_eta = (w[4] + w[6])/2;
1410 const Real w_mid = w_xi * w_eta;
1411
1412 Node & midnode = face.node_ref(8);
1413 midnode.set_extra_datum<Real>(weight_index, w_mid);
1414 midnode = ((1+w_mid)/(w_xi+w_eta) * (w_xi*xi_avg + w_eta*eta_avg) - vertex_avg)/w_mid;
1415 };
1416
1417 // If we're on a Hex27, our formula for the mid-volume node
1418 // relies on the locations of the mid-face points. We could
1419 // re-calculate those later but let's just save them now.
1420 Point midfacepts[6];
1421 if (elem->type() == HEX27)
1422 for (auto i : make_range(6))
1423 midfacepts[i] = elem->point(20+i);
1424
1425 // Check each edge for a curve, and adjust it if needed.
1426 for (auto e : elem->edge_index_range())
1427 {
1428 elem->build_edge_ptr(edge_ptr, e);
1429
1430 // We should add EDGE4 once we have QUAD16/TRI10/HEX64 to
1431 // use it
1432 if (edge_ptr->type() != EDGE3)
1433 libmesh_not_implemented_msg
1434 ("all_rbb() currently only supports meshes with 2- and/or 3-node edges");
1435
1436 make_edge_rbb(edge_ptr->node_ref(0), edge_ptr->node_ref(1),
1437 edge_ptr->node_ref(2),
1438 edge_ptr->node_ref(0), edge_ptr->node_ref(1));
1439
1440 }
1441
1442 // If we're in 3D, we may have face nodes that also need to be
1443 // adjusted to replace an interpolated curve with a spline
1444 // curve. We know what to do with a quad face, but we'll have
1445 // to scream and die if we see a Tri7 face node.
1446 bool check_face_points = (elem->dim() > 2) &&
1447 (elem->n_nodes() > elem->n_edges() + elem->n_vertices());
1448
1449 if (check_face_points)
1450 for (auto f : elem->side_index_range())
1451 {
1452 // Prisms and pyramids may need to skip some faces while
1453 // adjusting others
1454 if (elem->side_type(f) == TRI6)
1455 continue;
1456
1457 elem->build_side_ptr(face_ptr, f);
1458
1459 make_face_rbb(*face_ptr);
1460 }
1461
1462 bool check_interior_points =
1463 elem->n_nodes() > elem->n_edges() + elem->n_vertices() + elem->n_faces();
1464
1465 if (check_interior_points)
1466 {
1467 if (elem->type() == EDGE3)
1468 {
1469 make_edge_rbb(elem->node_ref(0), elem->node_ref(1),
1470 elem->node_ref(2),
1471 elem->node_ref(0), elem->node_ref(1));
1472 }
1473 else if (elem->dim() == 2)
1474 {
1475 make_face_rbb(*elem);
1476 }
1477 else if (elem->type() == HEX27)
1478 {
1479 // We still have the midnode left to go. We want
1480 // something here that will preserve the tensor product
1481 // structure for 2.5D extrusions of IGA faces, but also
1482 // be at least near to the minimum-energy control point
1483 // and weight for general cases. We'll treat opposing
1484 // mid-face nodes as the endpoints of a (more general
1485 // than our edges, since they might have non-1 weights)
1486 // Edge3, and see what we'd need on the midnode to
1487 // interpolate the center point with them. If we've got
1488 // something isogeometric like an extrusion then our
1489 // results should agree; for a quick-but-good output in
1490 // general we'll take an average.
1491 const int opposite_sides[3][2] = {{0,5}, {1,3}, {2,4}};
1492
1493 Node & midnode = elem->node_ref(26);
1494 const Point original_midpoint = midnode;
1495
1496 // Averaging in projective space
1497 Point sum_weighted_point = 0;
1498 Real sum_weight = 0;
1499
1500 for (int i : make_range(3))
1501 {
1502 Node & n0 = elem->node_ref(20+opposite_sides[i][0]);
1503 Node & n1 = elem->node_ref(20+opposite_sides[i][1]);
1504
1505 make_edge_rbb(n0, n1, midnode,
1506 midfacepts[opposite_sides[i][0]],
1507 midfacepts[opposite_sides[i][1]]);
1508
1509 const Real midweight =
1510 midnode.get_extra_datum<Real>(weight_index);
1511 sum_weight += midweight;
1512 sum_weighted_point += midweight * midnode;
1513
1514 // Reset for next run
1515 midnode = original_midpoint;
1516 midnode.set_extra_datum<Real>(weight_index,
1517 default_weight);
1518
1519 }
1520
1521 const Real midweight = sum_weight/3;
1522 midnode.set_extra_datum<Real>(weight_index,
1523 midweight);
1524
1525 midnode = sum_weighted_point / 3 / midweight;
1526 }
1527 else
1528 libmesh_not_implemented_msg
1529 ("all_rbb() doesn't yet support " << elem->type());
1530 }
1531 }
1532}
T get_extra_datum(const unsigned int index) const
Gets the value on this object of the extra datum associated with index, which should have been obtain...
void set_extra_datum(const unsigned int index, const T value)
Sets the value on this object of the extra datum associated with index, which should have been obtain...
This is the base class from which all geometric element types are derived.
Definition elem.h:96
void set_default_mapping_data(const unsigned char data)
Set the default master space to physical space mapping basis functions to be used on newly added elem...
Definition mesh_base.h:968
void set_default_mapping_type(const ElemMappingType type)
Set the default master space to physical space mapping basis functions to be used on newly added elem...
Definition mesh_base.h:950
unsigned int add_node_datum(const std::string &name, bool allocate_data=true, const T *default_value=nullptr)
Register a datum (of type T) to be added to each node in the mesh.
Definition mesh_base.h:2679
A Node is like a Point, but with more information.
Definition node.h:55
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
auto norm_sq() const
MeshBase & mesh
@ RATIONAL_BERNSTEIN_MAP
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176

References libMesh::MeshBase::add_node_datum(), libMesh::Elem::build_edge_ptr(), libMesh::Elem::build_side_ptr(), libMesh::Elem::default_order(), libMesh::Elem::dim(), libMesh::EDGE3, libMesh::Elem::edge_index_range(), libMesh::FIRST, libMesh::DofObject::get_extra_datum(), libMesh::HEX27, libMesh::Elem::infinite(), libMesh::Elem::level(), libMesh::make_range(), mesh, libMesh::Elem::n_edges(), libMesh::Elem::n_faces(), libMesh::Elem::n_nodes(), libMesh::Elem::n_vertices(), libMesh::Elem::node_ref(), libMesh::TypeVector< T >::norm_sq(), libMesh::Elem::point(), libMesh::QUAD9, libMesh::RATIONAL_BERNSTEIN_MAP, libMesh::Real, libMesh::MeshBase::set_default_mapping_data(), libMesh::MeshBase::set_default_mapping_type(), libMesh::DofObject::set_extra_datum(), libMesh::Elem::set_mapping_data(), libMesh::Elem::set_mapping_type(), libMesh::Elem::side_index_range(), libMesh::Elem::side_type(), libMesh::TRI6, and libMesh::Elem::type().

Referenced by AllRBBTest::test_box(), AllRBBTest::test_circle(), AllRBBTest::test_cylinder(), AllRBBTest::test_disk(), and AllRBBTest::test_sphere().

◆ all_tri()

void libMesh::MeshTools::Modification::all_tri ( MeshBase mesh)

Subdivides any non-simplex elements in a Mesh to produce simplex (triangular in 2D, tetrahedral in 3D) elements.

Note
Only supports coarse / unrefined meshes. A uniformly refined mesh can be used only after a flatten() removes its coarser layers.

Definition at line 449 of file mesh_modification.C.

450{
451 LOG_SCOPE("all_tri()", "MeshTools::Modification");
452
453 if (!mesh.is_replicated() && !mesh.is_prepared())
455
456 // The number of elements in the original mesh before any additions
457 // or deletions.
458 const dof_id_type n_orig_elem = mesh.n_elem();
459 const dof_id_type max_orig_id = mesh.max_elem_id();
460
461 // We store pointers to the newly created elements in a vector
462 // until they are ready to be added to the mesh. This is because
463 // adding new elements on the fly can cause reallocation and invalidation
464 // of existing mesh element_iterators.
465 std::vector<std::unique_ptr<Elem>> new_elements;
466
467 unsigned int max_subelems = 1; // in 1D nothing needs to change
468 if (mesh.mesh_dimension() == 2) // in 2D quads can split into 2 tris
469 max_subelems = 2;
470 if (mesh.mesh_dimension() == 3) // in 3D hexes can split into 6 tets
471 max_subelems = 6;
472
473 // 2D polygons and 3D polyhedra can be split into an arbitrary
474 // number of triangles/tetrahedra depending on their topology, so we
475 // have to scan the mesh to find the largest split we will need.
476 for (const Elem * elem : mesh.element_ptr_range())
477 {
478 if (const Polygon * poly = dynamic_cast<const Polygon *>(elem))
479 max_subelems = std::max(max_subelems, poly->n_subtriangles());
480 else if (const Polyhedron * polyhedron = dynamic_cast<const Polyhedron *>(elem))
481 max_subelems = std::max(max_subelems, polyhedron->n_subelements());
482 }
483 mesh.comm().max(max_subelems);
484
485 new_elements.reserve (max_subelems*n_orig_elem);
486
487 // If the original mesh has *side* boundary data, we carry that over
488 // to the new mesh with triangular elements. We currently only
489 // support bringing over side-based BCs to the all-tri mesh, but
490 // that could probably be extended to node and edge-based BCs as
491 // well.
492 const bool mesh_has_boundary_data = (mesh.get_boundary_info().n_boundary_conds() > 0);
493
494 // Temporary vectors to store the new boundary element pointers, side numbers, and boundary ids
495 std::vector<Elem *> new_bndry_elements;
496 std::vector<unsigned short int> new_bndry_sides;
497 std::vector<boundary_id_type> new_bndry_ids;
498
499 // We may need to add new points if we run into a 1.5th order
500 // element; if we do that on a DistributedMesh in a ghost element then
501 // we will need to fix their ids / unique_ids
502 bool added_new_ghost_point = false;
503
504 // Iterate over the elements, splitting:
505 // QUADs into pairs of conforming triangles
506 // PYRAMIDs into pairs of conforming tets,
507 // PRISMs into triplets of conforming tets, and
508 // HEXs into quintets or sextets of conforming tets.
509 // We split on the shortest diagonal to give us better
510 // triangle quality in 2D, and we split based on node ids
511 // to guarantee consistency in 3D.
512 // C0POLYGONs into their sub-triangles
513 // C0POLYHEDRA into their sub-elements (currently only tets)
514
515 // FIXME: This algorithm does not work on refined grids!
516 {
517#ifdef LIBMESH_ENABLE_UNIQUE_ID
519#endif
520
521 // For avoiding extraneous allocation when building side elements
522 std::unique_ptr<const Elem> elem_side, subside_elem;
523
524 for (auto & elem : mesh.element_ptr_range())
525 {
526 const ElemType etype = elem->type();
527
528 // all_tri currently only works on coarse meshes
529 if (elem->parent())
530 libmesh_not_implemented_msg("Cannot convert a refined element into simplices\n");
531
532 // The new elements we will split the original into. Reserving
533 // for the maximum number of sub-elements created for each element
534 std::vector<std::unique_ptr<Elem>> subelem(max_subelems);
535
536 auto set_nodes = [&elem, &subelem]
537 (const std::initializer_list<std::initializer_list<int>> & node_ids) {
538 int i=0;
539 for (auto row : node_ids)
540 {
541 int j=0;
542 Elem * sub = subelem[i++].get();
543 libmesh_assert(sub);
544 for (auto node_id : row)
545 sub->set_node(j++, elem->node_ptr(node_id));
546 }
547 };
548
549 switch (etype)
550 {
551 case QUAD4:
552 {
553 subelem[0] = Elem::build(TRI3);
554 subelem[1] = Elem::build(TRI3);
555
556 // Check for possible edge swap
557 if ((elem->point(0) - elem->point(2)).norm() <
558 (elem->point(1) - elem->point(3)).norm())
559 set_nodes({{0,1,2},{0,2,3}});
560 else
561 set_nodes({{0,1,3},{1,2,3}});
562
563 break;
564 }
565
566 case QUAD8:
567 {
568 if (elem->processor_id() != mesh.processor_id())
569 added_new_ghost_point = true;
570
571 subelem[0] = Elem::build(TRI6);
572 subelem[1] = Elem::build(TRI6);
573
574 // Add a new node at the center (vertex average) of the element.
575 Node * new_node = mesh.add_point((mesh.point(elem->node_id(0)) +
576 mesh.point(elem->node_id(1)) +
577 mesh.point(elem->node_id(2)) +
578 mesh.point(elem->node_id(3)))/4,
580 elem->processor_id());
581
582 // Check for possible edge swap
583 if ((elem->point(0) - elem->point(2)).norm() <
584 (elem->point(1) - elem->point(3)).norm())
585 {
586 set_nodes({{0,1,2,4,5},{0,2,3,3,6,7}});
587 subelem[0]->set_node(5, new_node);
588 subelem[1]->set_node(3, new_node);
589 }
590 else
591 {
592 set_nodes({{3,0,1,7,4},{1,2,3,5,6}});
593 subelem[0]->set_node(5, new_node);
594 subelem[1]->set_node(5, new_node);
595 }
596
597 break;
598 }
599
600 case QUAD9:
601 {
602 subelem[0] = Elem::build(TRI6);
603 subelem[1] = Elem::build(TRI6);
604
605 // Check for possible edge swap
606 if ((elem->point(0) - elem->point(2)).norm() <
607 (elem->point(1) - elem->point(3)).norm())
608 set_nodes({{0,1,2,4,5,8},{0,2,3,8,6,7}});
609 else
610 set_nodes({{0,1,3,4,8,7},{1,2,3,5,6,8}});
611
612 break;
613 }
614
615 case HEX8:
616 {
617 BoundaryInfo & boundary_info = mesh.get_boundary_info();
618
619 // Hexes all split into six tetrahedra
620 subelem[0] = Elem::build(TET4);
621 subelem[1] = Elem::build(TET4);
622 subelem[2] = Elem::build(TET4);
623 subelem[3] = Elem::build(TET4);
624 subelem[4] = Elem::build(TET4);
625 subelem[5] = Elem::build(TET4);
626
627 // On faces, we choose the node with the highest
628 // global id, and we split on the diagonal which
629 // includes that node. This ensures that (even in
630 // parallel, even on distributed meshes) the same
631 // diagonal split will be chosen for elements on either
632 // side of the same quad face.
633 const unsigned int highest_n = highest_vertex_on(elem);
634
635 // opposing_node[n] is the local node number of the node
636 // on the farthest corner of a hex8 from local node n
637 static const std::array<unsigned int, 8> opposing_node =
638 {6, 7, 4, 5, 2, 3, 0, 1};
639
640 static const std::vector<std::vector<unsigned int>> sides_opposing_highest =
641 {{2,3,5},{3,4,5},{1,4,5},{1,2,5},{0,2,3},{0,3,4},{0,1,4},{0,1,2}};
642 static const std::vector<std::vector<unsigned int>> nodes_neighboring_highest =
643 {{1,3,4},{0,2,5},{1,3,6},{0,2,7},{0,5,7},{1,4,6},{2,5,7},{3,4,6}};
644
645 // Start by looking in three directions away from the
646 // highest-id node. In each direction there will be two
647 // different possibilities for the split depending on
648 // how the opposing face nodes are numbered.
649 //
650 // This is tricky enough that I'm not going to worry
651 // about manually keeping tets oriented; we'll just call
652 // orient() on each as we go.
653
654 unsigned int next_subelem = 0;
655 for (auto side : sides_opposing_highest[highest_n])
656 {
657 const std::vector<unsigned int> nodes_on_side =
658 elem->nodes_on_side(side);
659
660 auto [dn, dn2] = split_diagonal(elem, nodes_on_side);
661
662 unsigned int split_on_neighbor = false;
663 for (auto n : nodes_neighboring_highest[highest_n])
664 if (dn == n || dn2 == n)
665 {
666 split_on_neighbor = true;
667 break;
668 }
669
670 // Add one or two elements for each opposing side,
671 // depending on whether the diagonal split there
672 // connects to the neighboring diagonal split or
673 // not.
674 if (split_on_neighbor)
675 {
676 subelem[next_subelem]->set_node(0, elem->node_ptr(highest_n));
677 subelem[next_subelem]->set_node(1, elem->node_ptr(dn));
678 subelem[next_subelem]->set_node(2, elem->node_ptr(dn2));
679 for (auto n : nodes_on_side)
680 if (n != dn && n != dn2)
681 {
682 subelem[next_subelem]->set_node(3, elem->node_ptr(n));
683 break;
684 }
685 subelem[next_subelem]->orient(&boundary_info);
686 ++next_subelem;
687
688 subelem[next_subelem]->set_node(0, elem->node_ptr(highest_n));
689 subelem[next_subelem]->set_node(1, elem->node_ptr(dn));
690 subelem[next_subelem]->set_node(2, elem->node_ptr(dn2));
691 for (auto n : reverse(nodes_on_side))
692 if (n != dn && n != dn2)
693 {
694 subelem[next_subelem]->set_node(3, elem->node_ptr(n));
695 break;
696 }
697 subelem[next_subelem]->orient(&boundary_info);
698 ++next_subelem;
699 }
700 else
701 {
702 subelem[next_subelem]->set_node(0, elem->node_ptr(highest_n));
703 subelem[next_subelem]->set_node(1, elem->node_ptr(dn));
704 subelem[next_subelem]->set_node(2, elem->node_ptr(dn2));
705 for (auto n : nodes_on_side)
706 for (auto n2 : nodes_neighboring_highest[highest_n])
707 if (n == n2)
708 {
709 subelem[next_subelem]->set_node(3, elem->node_ptr(n));
710 goto break_both_loops;
711 }
712
713 break_both_loops:
714 subelem[next_subelem]->orient(&boundary_info);
715 ++next_subelem;
716 }
717 }
718
719 // At this point we've created between 3 and 6 tets.
720 // What's left to do depends on how many.
721
722 // If we just chopped off three vertices into three
723 // tets, then the best way to split this hex would be
724 // the symmetric five-split. Chop off the opposing
725 // vertex too, and then the remaining interior is our
726 // final tet.
727 if (next_subelem == 3)
728 {
729 subelem[next_subelem]->set_node(0, elem->node_ptr(opposing_nodes[highest_n][0]));
730 subelem[next_subelem]->set_node(1, elem->node_ptr(opposing_nodes[highest_n][1]));
731 subelem[next_subelem]->set_node(2, elem->node_ptr(opposing_nodes[highest_n][2]));
732 subelem[next_subelem]->set_node(3, elem->node_ptr(opposing_node[highest_n]));
733 subelem[next_subelem]->orient(&boundary_info);
734 ++next_subelem;
735
736 subelem[next_subelem]->set_node(0, elem->node_ptr(opposing_nodes[highest_n][0]));
737 subelem[next_subelem]->set_node(1, elem->node_ptr(opposing_nodes[highest_n][1]));
738 subelem[next_subelem]->set_node(2, elem->node_ptr(opposing_nodes[highest_n][2]));
739 subelem[next_subelem]->set_node(3, elem->node_ptr(highest_n));
740 subelem[next_subelem]->orient(&boundary_info);
741 ++next_subelem;
742
743 // We don't need the 6th tet after all
744 subelem[next_subelem].reset();
745 ++next_subelem;
746 }
747
748 // If we just chopped off one (or two) vertices into
749 // tets, then the remaining gap is best (or only) filled
750 // by pairing another tet with each.
751 if (next_subelem == 4 ||
752 next_subelem == 5)
753 {
754 for (auto side : sides_opposing_highest[highest_n])
755 {
756 const std::vector<unsigned int> nodes_on_side =
757 elem->nodes_on_side(side);
758
759 auto [dn, dn2] = split_diagonal(elem, nodes_on_side);
760
761 unsigned int split_on_neighbor = false;
762 for (auto n : nodes_neighboring_highest[highest_n])
763 if (dn == n || dn2 == n)
764 {
765 split_on_neighbor = true;
766 break;
767 }
768
769 // The two !split_on_neighbor sides are where we
770 // need the two remaining tets
771 if (!split_on_neighbor)
772 {
773 subelem[next_subelem]->set_node(0, elem->node_ptr(highest_n));
774 subelem[next_subelem]->set_node(1, elem->node_ptr(dn));
775 subelem[next_subelem]->set_node(2, elem->node_ptr(dn2));
776 subelem[next_subelem]->set_node(3, elem->node_ptr(opposing_node[highest_n]));
777 subelem[next_subelem]->orient(&boundary_info);
778 ++next_subelem;
779 }
780 }
781 }
782
783 // Whether we got there by creating six tets from the
784 // first for loop or by patching up the split afterward,
785 // we should have considered six tets (possibly
786 // including one deleted one...) at this point.
787 libmesh_assert(next_subelem == 6);
788
789 break;
790 }
791
792 case PRISM6:
793 {
794 // Prisms all split into three tetrahedra
795 subelem[0] = Elem::build(TET4);
796 subelem[1] = Elem::build(TET4);
797 subelem[2] = Elem::build(TET4);
798
799 // Triangular faces are not split.
800
801 // On quad faces, we choose the node with the highest
802 // global id, and we split on the diagonal which
803 // includes that node. This ensures that (even in
804 // parallel, even on distributed meshes) the same
805 // diagonal split will be chosen for elements on either
806 // side of the same quad face. It also ensures that we
807 // always have a mix of "clockwise" and
808 // "counterclockwise" split faces (two of one and one
809 // of the other on each prism; this is useful since the
810 // alternative all-clockwise or all-counterclockwise
811 // face splittings can't be turned into tets without
812 // adding more nodes
813
814 // Split on 0-4 diagonal
815 if (split_first_diagonal(elem, 0,4, 1,3))
816 {
817 // Split on 0-5 diagonal
818 if (split_first_diagonal(elem, 0,5, 2,3))
819 {
820 // Split on 1-5 diagonal
821 if (split_first_diagonal(elem, 1,5, 2,4))
822 set_nodes({{0,4,5,3},{0,4,1,5},{0,1,2,5}});
823 else // Split on 2-4 diagonal
824 {
825 libmesh_assert (split_first_diagonal(elem, 2,4, 1,5));
826 set_nodes({{0,4,5,3},{0,4,2,5},{0,1,2,4}});
827 }
828 }
829 else // Split on 2-3 diagonal
830 {
831 libmesh_assert (split_first_diagonal(elem, 2,3, 0,5));
832
833 // 0-4 and 2-3 split implies 2-4 split
834 libmesh_assert (split_first_diagonal(elem, 2,4, 1,5));
835
836 set_nodes({{0,4,2,3},{3,4,2,5},{0,1,2,4}});
837 }
838 }
839 else // Split on 1-3 diagonal
840 {
841 libmesh_assert (split_first_diagonal(elem, 1,3, 0,4));
842
843 // Split on 0-5 diagonal
844 if (split_first_diagonal(elem, 0,5, 2,3))
845 {
846 // 1-3 and 0-5 split implies 1-5 split
847 libmesh_assert (split_first_diagonal(elem, 1,5, 2,4));
848
849 set_nodes({{1,3,4,5},{1,0,3,5},{0,1,2,5}});
850 }
851 else // Split on 2-3 diagonal
852 {
853 libmesh_assert (split_first_diagonal(elem, 2,3, 0,5));
854
855 // Split on 1-5 diagonal
856 if (split_first_diagonal(elem, 1,5, 2,4))
857 set_nodes({{0,1,2,3},{3,1,2,5},{1,3,4,5}});
858 else // Split on 2-4 diagonal
859 {
860 libmesh_assert (split_first_diagonal(elem, 2,4, 1,5));
861 set_nodes({{0,1,2,3},{2,3,4,5},{3,1,2,4}});
862 }
863 }
864 }
865
866 break;
867 }
868
869 case PRISM20:
870 case PRISM21:
871 libmesh_experimental(); // We should upgrade this to TET14...
872 libmesh_fallthrough();
873 case PRISM18:
874 {
875 subelem[0] = Elem::build(TET10);
876 subelem[1] = Elem::build(TET10);
877 subelem[2] = Elem::build(TET10);
878
879 // Split on 0-4 diagonal
880 if (split_first_diagonal(elem, 0,4, 1,3))
881 {
882 // Split on 0-5 diagonal
883 if (split_first_diagonal(elem, 0,5, 2,3))
884 {
885 // Split on 1-5 diagonal
886 if (split_first_diagonal(elem, 1,5, 2,4))
887 set_nodes({{0,4,5,3,15,13,17,9,12,14},
888 {0,4,1,5,15,10,6,17,13,16},
889 {0,1,2,5,6,7,8,17,16,11}});
890 else // Split on 2-4 diagonal
891 {
892 libmesh_assert (split_first_diagonal(elem, 2,4, 1,5));
893
894 set_nodes({{0,4,5,3,15,13,17,9,12,14},
895 {0,4,2,5,15,16,8,17,13,11},
896 {0,1,2,4,6,7,8,15,10,16}});
897 }
898 }
899 else // Split on 2-3 diagonal
900 {
901 libmesh_assert (split_first_diagonal(elem, 2,3, 0,5));
902
903 // 0-4 and 2-3 split implies 2-4 split
904 libmesh_assert (split_first_diagonal(elem, 2,4, 1,5));
905
906 set_nodes({{0,4,2,3,15,16,8,9,12,17},
907 {3,4,2,5,12,16,17,14,13,11},
908 {0,1,2,4,6,7,8,15,10,16}});
909 }
910 }
911 else // Split on 1-3 diagonal
912 {
913 libmesh_assert (split_first_diagonal(elem, 1,3, 0,4));
914
915 // Split on 0-5 diagonal
916 if (split_first_diagonal(elem, 0,5, 2,3))
917 {
918 // 1-3 and 0-5 split implies 1-5 split
919 libmesh_assert (split_first_diagonal(elem, 1,5, 2,4));
920
921 set_nodes({{1,3,4,5,15,12,10,16,14,13},
922 {1,0,3,5,6,9,15,16,17,14},
923 {0,1,2,5,6,7,8,17,16,11}});
924 }
925 else // Split on 2-3 diagonal
926 {
927 libmesh_assert (split_first_diagonal(elem, 2,3, 0,5));
928
929 // Split on 1-5 diagonal
930 if (split_first_diagonal(elem, 1,5, 2,4))
931 set_nodes({{0,1,2,3,6,7,8,9,15,17},
932 {3,1,2,5,15,7,17,14,16,11},
933 {1,3,4,5,15,12,10,16,14,13}});
934 else // Split on 2-4 diagonal
935 {
936 libmesh_assert (split_first_diagonal(elem, 2,4, 1,5));
937
938 set_nodes({{0,1,2,3,6,7,8,9,15,17},
939 {2,3,4,5,17,12,16,11,14,13},
940 {3,1,2,4,15,7,17,12,10,16}});
941 }
942 }
943 }
944
945 break;
946 }
947
948 case PYRAMID5:
949 {
950 // Pyramids all split into two tetrahedra
951 subelem[0] = Elem::build(TET4);
952 subelem[1] = Elem::build(TET4);
953
954 // Choose how to split the quad face in a way that will
955 // be consistent from possibly-different-type elements
956 // splitting from the other side
957 //
958 // Split on 0-2 diagonal
959 if (split_first_diagonal(elem, 0,2, 1,3))
960 set_nodes({{0,1,2,4},{0,2,3,4}});
961 // Split on 1-3 diagonal
962 else
963 {
964 libmesh_assert (split_first_diagonal(elem, 1,3, 0,2));
965 set_nodes({{0,1,3,4},{1,2,3,4}});
966 }
967
968 break;
969 }
970
971 case PYRAMID14:
972 {
973 // Pyramids all split into two tetrahedra
974 subelem[0] = Elem::build(TET10);
975 subelem[1] = Elem::build(TET10);
976
977 // Choose how to split the quad face in a way that will
978 // be consistent from possibly-different-type elements
979 // splitting from the other side
980 //
981 // Split on 0-2 diagonal
982 if (split_first_diagonal(elem, 0,2, 1,3))
983 set_nodes({{0,1,2,4,5,6,13,9,10,11},
984 {0,2,3,4,13,7,8,9,11,12}});
985 // Split on 1-3 diagonal
986 else
987 {
988 libmesh_assert (split_first_diagonal(elem, 1,3, 0,2));
989 set_nodes({{0,1,3,4,5,13,8,9,10,12},
990 {1,2,3,4,6,7,13,10,11,12}});
991 }
992
993 break;
994 }
995
996 case C0POLYGON:
997 {
998 // Split a C0Polygon into the triangles defined by its
999 // current triangulation. This relies on the user having
1000 // a valid triangulation (the constructor sets a default
1001 // one, and the user can refresh it via retriangulate()
1002 // after moving nodes).
1003 const C0Polygon * polygon = cast_ptr<const C0Polygon *>(elem);
1004 const unsigned int n_subtri = polygon->n_subtriangles();
1005 for (unsigned int t = 0; t != n_subtri; ++t)
1006 {
1007 const std::array<int, 3> tri = polygon->subtriangle(t);
1008 if (tri[0] < 0 || tri[1] < 0 || tri[2] < 0)
1009 libmesh_not_implemented_msg
1010 ("Cannot convert a C0Polygon whose triangulation\n"
1011 "introduces special (non-vertex) points");
1012 subelem[t] = Elem::build(TRI3);
1013 subelem[t]->set_node(0, elem->node_ptr(tri[0]));
1014 subelem[t]->set_node(1, elem->node_ptr(tri[1]));
1015 subelem[t]->set_node(2, elem->node_ptr(tri[2]));
1016 }
1017
1018 break;
1019 }
1020
1021 case C0POLYHEDRON:
1022 {
1023 // Split a C0Polyhedron into the tetrahedra defined by its
1024 // current tetrahedralization. If the polyhedron required
1025 // a mid-element node, the user is expected to have added
1026 // that node to the mesh during construction; we just
1027 // reference it via the polyhedron's node pointers.
1028 const C0Polyhedron * polyhedron =
1029 cast_ptr<const C0Polyhedron *>(elem);
1030 const unsigned int n_sub = polyhedron->n_subelements();
1031 for (unsigned int t = 0; t != n_sub; ++t)
1032 {
1033 const std::array<int, 4> tet = polyhedron->subelement(t);
1034 if (tet[0] < 0 || tet[1] < 0 || tet[2] < 0 || tet[3] < 0)
1035 libmesh_not_implemented_msg
1036 ("Cannot convert a C0Polyhedron whose triangulation\n"
1037 "introduces special (non-vertex) points");
1038 subelem[t] = Elem::build(TET4);
1039 subelem[t]->set_node(0, elem->node_ptr(tet[0]));
1040 subelem[t]->set_node(1, elem->node_ptr(tet[1]));
1041 subelem[t]->set_node(2, elem->node_ptr(tet[2]));
1042 subelem[t]->set_node(3, elem->node_ptr(tet[3]));
1043 }
1044 // There is a concern that two neighbor polyhedra might have
1045 // a triangulation of a side that does not match. But the
1046 // default triangulation is based on the side's triangulation
1047 // and the side element is supposed to be shared (that's why
1048 // shared pointers to polygons are used to build the polyhedra).
1049 // So the default one should work.
1050
1051 break;
1052 }
1053
1054 // No need to split elements that are already simplicial:
1055 case EDGE2:
1056 case EDGE3:
1057 case EDGE4:
1058 case TRI3:
1059 case TRI6:
1060 case TRI7:
1061 case TET4:
1062 case TET10:
1063 case TET14:
1064 case INFEDGE2:
1065 // No way to split infinite quad/prism elements, so
1066 // hopefully no need to
1067 case INFQUAD4:
1068 case INFQUAD6:
1069 case INFPRISM6:
1070 case INFPRISM12:
1071 continue;
1072 // If we're left with an unimplemented element we're
1073 // probably out of luck. TODO: implement hex20, hex27,
1074 // pyramid13,...
1075 default:
1076 libmesh_not_implemented_msg
1077 ("Error, encountered unimplemented element "
1078 << Utility::enum_to_string<ElemType>(etype)
1079 << " in MeshTools::Modification::all_tri()...");
1080 } // end switch (etype)
1081
1082 // Be sure the correct data is set for all subelems.
1083 const unsigned int nei = elem->n_extra_integers();
1084 for (unsigned int i=0; i != max_subelems; ++i)
1085 if (subelem[i]) {
1086 subelem[i]->processor_id() = elem->processor_id();
1087 subelem[i]->subdomain_id() = elem->subdomain_id();
1088
1089 // Copy any extra element data. Since the subelements
1090 // haven't been added to the mesh yet any allocation has
1091 // to be done manually.
1092 subelem[i]->add_extra_integers(nei);
1093 for (unsigned int ei=0; ei != nei; ++ei)
1094 subelem[ei]->set_extra_integer(ei, elem->get_extra_integer(ei));
1095
1096
1097 // Copy any mapping data.
1098 subelem[i]->set_mapping_type(elem->mapping_type());
1099 subelem[i]->set_mapping_data(elem->mapping_data());
1100 }
1101
1102 // On a mesh with boundary data, we need to move that data to
1103 // the new elements.
1104
1105 // On a mesh which is distributed, we need to move
1106 // remote_elem links to the new elements.
1107 bool mesh_is_serial = mesh.is_serial();
1108
1109 if (mesh_has_boundary_data || !mesh_is_serial)
1110 {
1111 // Container to key boundary IDs handed back by the BoundaryInfo object.
1112 std::vector<boundary_id_type> bc_ids;
1113
1114 for (auto sn : elem->side_index_range())
1115 {
1116 mesh.get_boundary_info().boundary_ids(elem, sn, bc_ids);
1117
1118 if (bc_ids.empty() && elem->neighbor_ptr(sn) != remote_elem)
1119 continue;
1120
1121 // Make a sorted list of node ids for elem->side(sn)
1122 elem->build_side_ptr(elem_side, sn);
1123 std::vector<dof_id_type> elem_side_nodes(elem_side->n_nodes());
1124 for (unsigned int esn=0,
1125 n_esn = cast_int<unsigned int>(elem_side_nodes.size());
1126 esn != n_esn; ++esn)
1127 elem_side_nodes[esn] = elem_side->node_id(esn);
1128 std::sort(elem_side_nodes.begin(), elem_side_nodes.end());
1129
1130 for (unsigned int i=0; i != max_subelems; ++i)
1131 if (subelem[i])
1132 {
1133 for (auto subside : subelem[i]->side_index_range())
1134 {
1135 subelem[i]->build_side_ptr(subside_elem, subside);
1136
1137 // Make a list of *vertex* node ids for this subside, see if they are all present
1138 // in elem->side(sn). Note 1: we can't just compare elem->key(sn) to
1139 // subelem[i]->key(subside) in the Prism cases, since the new side is
1140 // a different type. Note 2: we only use vertex nodes since, in the future,
1141 // a Hex20 or Prism15's QUAD8 face may be split into two Tri6 faces, and the
1142 // original face will not contain the mid-edge node.
1143 std::vector<dof_id_type> subside_nodes(subside_elem->n_vertices());
1144 for (unsigned int ssn=0,
1145 n_ssn = cast_int<unsigned int>(subside_nodes.size());
1146 ssn != n_ssn; ++ssn)
1147 subside_nodes[ssn] = subside_elem->node_id(ssn);
1148 std::sort(subside_nodes.begin(), subside_nodes.end());
1149
1150 // std::includes returns true if every element of the second sorted range is
1151 // contained in the first sorted range.
1152 if (std::includes(elem_side_nodes.begin(), elem_side_nodes.end(),
1153 subside_nodes.begin(), subside_nodes.end()))
1154 {
1155 for (const auto & b_id : bc_ids)
1156 if (b_id != BoundaryInfo::invalid_id)
1157 {
1158 new_bndry_ids.push_back(b_id);
1159 new_bndry_elements.push_back(subelem[i].get());
1160 new_bndry_sides.push_back(subside);
1161 }
1162
1163 // If the original element had a RemoteElem neighbor on side 'sn',
1164 // then the subelem has one on side 'subside'.
1165 if (elem->neighbor_ptr(sn) == remote_elem)
1166 subelem[i]->set_neighbor(subside, const_cast<RemoteElem*>(remote_elem));
1167 }
1168 }
1169 } // end for loop over subelem
1170 } // end for loop over sides
1171
1172 // Remove the original element from the BoundaryInfo structure.
1174
1175 } // end if (mesh_has_boundary_data)
1176
1177 // Determine new IDs for the split elements which will be
1178 // the same on all processors, therefore keeping the Mesh
1179 // in sync. Note: we offset the new IDs by max_orig_id to
1180 // avoid overwriting any of the original IDs.
1181 for (unsigned int i=0; i != max_subelems; ++i)
1182 if (subelem[i])
1183 {
1184 // Determine new IDs for the split elements which will be
1185 // the same on all processors, therefore keeping the Mesh
1186 // in sync. Note: we offset the new IDs by the max of the
1187 // pre-existing ids to avoid conflicting with originals.
1188 subelem[i]->set_id( max_orig_id + max_subelems*elem->id() + i );
1189
1190#ifdef LIBMESH_ENABLE_UNIQUE_ID
1191 subelem[i]->set_unique_id(max_unique_id + max_subelems*elem->unique_id() + i);
1192#endif
1193
1194 // Prepare to add the newly-created simplices
1195 new_elements.push_back(std::move(subelem[i]));
1196 }
1197
1198 // Delete the original element
1199 mesh.delete_elem(elem);
1200 } // End for loop over elements
1201 } // end scope
1202
1203
1204 // Now, iterate over the new elements vector, and add them each to
1205 // the Mesh.
1206 for (auto & elem : new_elements)
1207 mesh.add_elem(std::move(elem));
1208
1209 if (mesh_has_boundary_data)
1210 {
1211 // If the old mesh had boundary data, the new mesh better have
1212 // some. However, we can't assert that the size of
1213 // new_bndry_elements vector is > 0, since we may not have split
1214 // any elements actually on the boundary. We also can't assert
1215 // that the original number of boundary sides is equal to the
1216 // sum of the boundary sides currently in the mesh and the
1217 // newly-added boundary sides, since in 3D, we may have split a
1218 // boundary QUAD into two boundary TRIs. Therefore, we won't be
1219 // too picky about the actual number of BCs, and just assert that
1220 // there are some, somewhere.
1221#ifndef NDEBUG
1222 bool nbe_nonempty = new_bndry_elements.size();
1223 mesh.comm().max(nbe_nonempty);
1224 libmesh_assert(nbe_nonempty ||
1226#endif
1227
1228 // We should also be sure that the lengths of the new boundary data vectors
1229 // are all the same.
1230 libmesh_assert_equal_to (new_bndry_elements.size(), new_bndry_sides.size());
1231 libmesh_assert_equal_to (new_bndry_sides.size(), new_bndry_ids.size());
1232
1233 // Add the new boundary info to the mesh
1234 for (auto s : index_range(new_bndry_elements))
1235 mesh.get_boundary_info().add_side(new_bndry_elements[s],
1236 new_bndry_sides[s],
1237 new_bndry_ids[s]);
1238 }
1239
1240 // In a DistributedMesh any newly added ghost node ids may be
1241 // inconsistent, and unique_ids of newly added ghost nodes remain
1242 // unset.
1243 // make_nodes_parallel_consistent() will fix all this.
1244 if (!mesh.is_serial())
1245 {
1246 mesh.comm().max(added_new_ghost_point);
1247
1248 if (added_new_ghost_point)
1250 }
1251
1252 // Prepare the newly created mesh for use.
1254
1255 // Let the new_elements and new_bndry_elements vectors go out of scope.
1256}
void max(const T &r, T &o, Request &req) const
The BoundaryInfo class contains information relevant to boundary conditions including storing faces,...
std::size_t n_boundary_conds() const
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
Fills a user-provided std::vector with the boundary ids associated with Node node.
void remove(const Node *node)
Removes the boundary conditions associated with node node, if any exist.
The C0Polygon is an element in 2D with an arbitrary (but fixed) number of first-order (EDGE2) sides.
The C0Polyhedron is an element in 3D with an arbitrary (but fixed) number of polygonal first-order (C...
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
static std::unique_ptr< Elem > build(const ElemType type, Elem *p=nullptr)
Definition elem.C:442
virtual const Point & point(const dof_id_type i) const =0
virtual bool is_serial() const
Definition mesh_base.h:357
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
bool is_prepared() const
Definition mesh_base.C:1064
unsigned int mesh_dimension() const
Definition mesh_base.C:430
virtual dof_id_type n_elem() const =0
virtual bool is_replicated() const
Definition mesh_base.h:379
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition mesh_base.C:824
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
virtual dof_id_type max_elem_id() const =0
virtual unique_id_type parallel_max_unique_id() const =0
This is the MeshCommunication class.
void make_nodes_parallel_consistent(MeshBase &)
Copy processor_ids and ids on ghost nodes from their local processors.
processor_id_type processor_id() const
const Parallel::Communicator & comm() const
The Polygon is an element in 2D with an arbitrary (but fixed) number of sides.
virtual std::array< int, 3 > subtriangle(unsigned int i) const
unsigned int n_subtriangles() const
The Polyhedron is an element in 3D with an arbitrary number of polygonal faces.
virtual std::array< int, 4 > subelement(unsigned int i) const
unsigned int n_subelements() const
In parallel meshes where a ghost element has neighbors which do not exist on the local processor,...
Definition remote_elem.h:61
const Elem & get(const ElemType type_in)
uint8_t unique_id_type
Definition id_types.h:86
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition int_range.h:153
ElemType
Defines an enum for geometric element types.
libmesh_assert(ctx)
const RemoteElem * remote_elem
Definition remote_elem.C:57
uint8_t dof_id_type
Definition id_types.h:67

References libMesh::MeshBase::add_elem(), libMesh::MeshBase::add_point(), libMesh::BoundaryInfo::add_side(), libMesh::BoundaryInfo::boundary_ids(), libMesh::Elem::build(), libMesh::Elem::build_side_ptr(), libMesh::C0POLYGON, libMesh::C0POLYHEDRON, libMesh::ParallelObject::comm(), libMesh::MeshBase::delete_elem(), libMesh::EDGE2, libMesh::EDGE3, libMesh::EDGE4, libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::get_extra_integer(), libMesh::HEX8, libMesh::DofObject::id(), libMesh::index_range(), libMesh::INFEDGE2, libMesh::INFPRISM12, libMesh::INFPRISM6, libMesh::INFQUAD4, libMesh::INFQUAD6, libMesh::DofObject::invalid_id, libMesh::BoundaryInfo::invalid_id, libMesh::MeshBase::is_prepared(), libMesh::MeshBase::is_replicated(), libMesh::MeshBase::is_serial(), libMesh::libmesh_assert(), libMesh::MeshCommunication::make_nodes_parallel_consistent(), libMesh::Elem::mapping_data(), libMesh::Elem::mapping_type(), libMesh::Parallel::Communicator::max(), libMesh::MeshBase::max_elem_id(), mesh, libMesh::MeshBase::mesh_dimension(), libMesh::BoundaryInfo::n_boundary_conds(), libMesh::MeshBase::n_elem(), libMesh::DofObject::n_extra_integers(), libMesh::Polyhedron::n_subelements(), libMesh::Polygon::n_subtriangles(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_id(), libMesh::Elem::node_ptr(), libMesh::Elem::nodes_on_side(), libMesh::MeshBase::parallel_max_unique_id(), libMesh::Elem::parent(), libMesh::MeshBase::point(), libMesh::Elem::point(), libMesh::MeshBase::prepare_for_use(), libMesh::PRISM18, libMesh::PRISM20, libMesh::PRISM21, libMesh::PRISM6, libMesh::DofObject::processor_id(), libMesh::ParallelObject::processor_id(), libMesh::PYRAMID14, libMesh::PYRAMID5, libMesh::QUAD4, libMesh::QUAD8, libMesh::QUAD9, libMesh::remote_elem, libMesh::BoundaryInfo::remove(), libMesh::Elem::set_node(), libMesh::Elem::side_index_range(), libMesh::Elem::subdomain_id(), libMesh::Polyhedron::subelement(), libMesh::Polygon::subtriangle(), libMesh::TET10, libMesh::TET14, libMesh::TET4, libMesh::TRI3, libMesh::TRI6, libMesh::TRI7, libMesh::Elem::type(), and libMesh::DofObject::unique_id().

Referenced by OverlappingFunctorTest::checkCouplingFunctorTri(), OverlappingFunctorTest::checkCouplingFunctorTriUnifRef(), main(), AllTriTest::test_helper(), AllTriTest::test_helper_c0polyhedron(), AllRBBTest::test_sphere(), AllTriTest::testAllTriC0Polygon(), AllTriTest::testAllTriC0PolygonOctagon(), and libMesh::MeshTetInterface::volume_to_surface_mesh().

◆ change_boundary_id()

void libMesh::MeshTools::Modification::change_boundary_id ( MeshBase mesh,
const boundary_id_type  old_id,
const boundary_id_type  new_id 
)

Finds any boundary ids that are currently old_id, changes them to new_id.

Definition at line 1921 of file mesh_modification.C.

1924{
1925 // This is just a shim around the member implementation, now
1926 mesh.get_boundary_info().renumber_id(old_id, new_id);
1927}
void renumber_id(boundary_id_type old_id, boundary_id_type new_id)
Changes all entities (nodes, sides, edges, shellfaces) with boundary id old_id to instead be labeled ...

References libMesh::MeshBase::get_boundary_info(), mesh, and libMesh::BoundaryInfo::renumber_id().

Referenced by MeshStitchTest::renameAndShift().

◆ change_subdomain_id()

void libMesh::MeshTools::Modification::change_subdomain_id ( MeshBase mesh,
const subdomain_id_type  old_id,
const subdomain_id_type  new_id 
)

Finds any subdomain ids that are currently old_id, changes them to new_id.

Definition at line 1931 of file mesh_modification.C.

1934{
1935 if (old_id == new_id)
1936 {
1937 // If the IDs are the same, this is a no-op.
1938 return;
1939 }
1940
1943 [old_id, new_id](const ElemRange & range)
1944 {
1945 for (Elem * elem : range)
1946 if (elem->subdomain_id() == old_id)
1947 elem->subdomain_id() = new_id;
1948 });
1949
1950 // We just invalidated mesh.get_subdomain_ids(), but it might not be
1951 // efficient to fix that here.
1953}
const ElemRange & element_stored_range()
Definition mesh_base.C:1939
void unset_has_cached_elem_data()
Tells this we have done some operation (e.g.
Definition mesh_base.h:281
The StoredRange class defines a contiguous, divisible set of objects.
void parallel_for(const Range &range, const Body &body, unsigned int n_threads=libMesh::n_threads())
Execute the provided function object in parallel on the specified range.

References libMesh::MeshBase::element_stored_range(), mesh, libMesh::Threads::parallel_for(), and libMesh::MeshBase::unset_has_cached_elem_data().

◆ distort()

void libMesh::MeshTools::Modification::distort ( MeshBase mesh,
const Real  factor,
const bool  perturb_boundary = false 
)

Randomly perturb the nodal locations.

This function will move each node factor fraction of its minimum neighboring node separation distance. Nodes on the boundary are not moved by default, however they may be by setting the flag perturb_boundary true.

Definition at line 148 of file mesh_modification.C.

151{
154 libmesh_assert ((factor >= 0.) && (factor <= 1.));
155
156 LOG_SCOPE("distort()", "MeshTools::Modification");
157
158 // If we are not perturbing boundary nodes, make a
159 // quickly-searchable list of node ids we can check against.
160 std::unordered_set<dof_id_type> boundary_node_ids;
161 if (!perturb_boundary)
162 boundary_node_ids = MeshTools::find_boundary_nodes (mesh);
163
164 // Now calculate the minimum distance to
165 // neighboring nodes for each node.
166 // hmin holds these distances.
167 std::vector<float> hmin (mesh.max_node_id(),
168 std::numeric_limits<float>::max());
169
170 for (const auto & elem : mesh.active_element_ptr_range())
171 for (auto & n : elem->node_ref_range())
172 hmin[n.id()] = std::min(hmin[n.id()],
173 static_cast<float>(elem->hmin()));
174
175 // Now actually move the nodes
176 {
177 const unsigned int seed = 123456;
178
179 // seed the random number generator.
180 // We'll loop from 1 to n_nodes on every processor, even those
181 // that don't have a particular node, so that the pseudorandom
182 // numbers will be the same everywhere.
183 std::srand(seed);
184
185 // If the node is on the boundary or
186 // the node is not used by any element (hmin[n]<1.e20)
187 // then we should not move it.
188 // [Note: Testing for (in)equality might be wrong
189 // (different types, namely float and double)]
190 for (auto n : make_range(mesh.max_node_id()))
191 if ((perturb_boundary || !boundary_node_ids.count(n)) && hmin[n] < 1.e20)
192 {
193 // the direction, random but unit normalized
194 Point dir (static_cast<Real>(std::rand())/static_cast<Real>(RAND_MAX),
195 (mesh.mesh_dimension() > 1) ? static_cast<Real>(std::rand())/static_cast<Real>(RAND_MAX) : 0.,
196 ((mesh.mesh_dimension() == 3) ? static_cast<Real>(std::rand())/static_cast<Real>(RAND_MAX) : 0.));
197
198 dir(0) = (dir(0)-.5)*2.;
199#if LIBMESH_DIM > 1
200 if (mesh.mesh_dimension() > 1)
201 dir(1) = (dir(1)-.5)*2.;
202#endif
203#if LIBMESH_DIM > 2
204 if (mesh.mesh_dimension() == 3)
205 dir(2) = (dir(2)-.5)*2.;
206#endif
207
208 dir = dir.unit();
209
210 Node * node = mesh.query_node_ptr(n);
211 if (!node)
212 continue;
213
214 (*node)(0) += dir(0)*factor*hmin[n];
215#if LIBMESH_DIM > 1
216 if (mesh.mesh_dimension() > 1)
217 (*node)(1) += dir(1)*factor*hmin[n];
218#endif
219#if LIBMESH_DIM > 2
220 if (mesh.mesh_dimension() == 3)
221 (*node)(2) += dir(2)*factor*hmin[n];
222#endif
223 }
224 }
225
226 // We haven't changed any topology, but just changing geometry could
227 // have invalidated a point locator.
229}
virtual dof_id_type n_nodes() const =0
virtual dof_id_type max_node_id() const =0
virtual const Node * query_node_ptr(const dof_id_type i) const =0
void clear_point_locator()
Releases the current PointLocator object.
Definition mesh_base.C:1866
TypeVector< T > unit() const
std::unordered_set< dof_id_type > find_boundary_nodes(const MeshBase &mesh)
Returns a std::set containing Node IDs for all of the boundary nodes.
Definition mesh_tools.C:524

References libMesh::MeshBase::clear_point_locator(), libMesh::MeshTools::find_boundary_nodes(), libMesh::Elem::hmin(), libMesh::libmesh_assert(), libMesh::make_range(), libMesh::MeshBase::max_node_id(), mesh, libMesh::MeshBase::mesh_dimension(), libMesh::MeshBase::n_elem(), libMesh::MeshBase::n_nodes(), libMesh::Elem::node_ref_range(), libMesh::MeshBase::query_node_ptr(), libMesh::Real, and libMesh::TypeVector< T >::unit().

Referenced by main(), DistortTest::perturb_and_check(), VolumeTest::test_true_centroid_and_volume(), and VolumeTest::testQuad4TrueCentroid().

◆ flatten()

void libMesh::MeshTools::Modification::flatten ( MeshBase mesh)

Removes all the refinement tree structure of Mesh, leaving only the highest-level (most-refined) elements.

This is useful when you want to write out a uniformly-refined grid to be treated later as an initial mesh.

Note
Many functions in LibMesh assume a conforming (with no hanging nodes) grid exists at some level, so you probably only want to do this on meshes which have been uniformly refined.

Definition at line 1795 of file mesh_modification.C.

1796{
1798
1799 // Algorithm:
1800 // .) For each active element in the mesh: construct a
1801 // copy which is the same in every way *except* it is
1802 // a level 0 element. Store the pointers to these in
1803 // a separate vector. Save any boundary information as well.
1804 // Delete the active element from the mesh.
1805 // .) Loop over all (remaining) elements in the mesh, delete them.
1806 // .) Add the level-0 copies back to the mesh
1807
1808 // Temporary storage for new element pointers
1809 std::vector<std::unique_ptr<Elem>> new_elements;
1810
1811 // BoundaryInfo Storage for element ids, sides, and BC ids
1812 std::vector<Elem *> saved_boundary_elements;
1813 std::vector<boundary_id_type> saved_bc_ids;
1814 std::vector<unsigned short int> saved_bc_sides;
1815
1816 // Container to catch boundary ids passed back by BoundaryInfo
1817 std::vector<boundary_id_type> bc_ids;
1818
1819 // Reserve a reasonable amt. of space for each
1820 new_elements.reserve(mesh.n_active_elem());
1821 saved_boundary_elements.reserve(mesh.get_boundary_info().n_boundary_conds());
1822 saved_bc_ids.reserve(mesh.get_boundary_info().n_boundary_conds());
1823 saved_bc_sides.reserve(mesh.get_boundary_info().n_boundary_conds());
1824
1825 for (auto & elem : mesh.active_element_ptr_range())
1826 {
1827 // Make a new element of the same type
1828 auto copy = Elem::build(elem->type());
1829
1830 // Set node pointers (they still point to nodes in the original mesh)
1831 for (auto n : elem->node_index_range())
1832 copy->set_node(n, elem->node_ptr(n));
1833
1834 // Copy over ids
1835 copy->processor_id() = elem->processor_id();
1836 copy->subdomain_id() = elem->subdomain_id();
1837
1838 // Retain the original element's ID(s) as well, otherwise
1839 // the Mesh may try to create them for you...
1840 copy->set_id( elem->id() );
1841#ifdef LIBMESH_ENABLE_UNIQUE_ID
1842 copy->set_unique_id(elem->unique_id());
1843#endif
1844
1845 // This element could have boundary info or DistributedMesh
1846 // remote_elem links as well. We need to save the (elem,
1847 // side, bc_id) triples and those links
1848 for (auto s : elem->side_index_range())
1849 {
1850 if (elem->neighbor_ptr(s) == remote_elem)
1851 copy->set_neighbor(s, const_cast<RemoteElem *>(remote_elem));
1852
1853 mesh.get_boundary_info().boundary_ids(elem, s, bc_ids);
1854 for (const auto & bc_id : bc_ids)
1855 if (bc_id != BoundaryInfo::invalid_id)
1856 {
1857 saved_boundary_elements.push_back(copy.get());
1858 saved_bc_ids.push_back(bc_id);
1859 saved_bc_sides.push_back(s);
1860 }
1861 }
1862
1863 // Copy any extra element data. Since the copy hasn't been
1864 // added to the mesh yet any allocation has to be done manually.
1865 const unsigned int nei = elem->n_extra_integers();
1866 copy->add_extra_integers(nei);
1867 for (unsigned int i=0; i != nei; ++i)
1868 copy->set_extra_integer(i, elem->get_extra_integer(i));
1869
1870 // Copy any mapping data.
1871 copy->set_mapping_type(elem->mapping_type());
1872 copy->set_mapping_data(elem->mapping_data());
1873
1874 // We're done with this element
1875 mesh.delete_elem(elem);
1876
1877 // But save the copy
1878 new_elements.push_back(std::move(copy));
1879 }
1880
1881 // Make sure we saved the same number of boundary conditions
1882 // in each vector.
1883 libmesh_assert_equal_to (saved_boundary_elements.size(), saved_bc_ids.size());
1884 libmesh_assert_equal_to (saved_bc_ids.size(), saved_bc_sides.size());
1885
1886 // Loop again, delete any remaining elements
1887 for (auto & elem : mesh.element_ptr_range())
1888 mesh.delete_elem(elem);
1889
1890 // Add the copied (now level-0) elements back to the mesh
1891 for (auto & new_elem : new_elements)
1892 {
1893 // Save the original ID, because the act of adding the Elem can
1894 // change new_elem's id!
1895 dof_id_type orig_id = new_elem->id();
1896
1897 Elem * added_elem = mesh.add_elem(std::move(new_elem));
1898
1899 // If the Elem, as it was re-added to the mesh, now has a
1900 // different ID (this is unlikely, so it's just an assert)
1901 // the boundary information will no longer be correct.
1902 libmesh_assert_equal_to (orig_id, added_elem->id());
1903
1904 // Avoid compiler warnings in opt mode.
1905 libmesh_ignore(added_elem, orig_id);
1906 }
1907
1908 // Finally, also add back the saved boundary information
1909 for (auto e : index_range(saved_boundary_elements))
1910 mesh.get_boundary_info().add_side(saved_boundary_elements[e],
1911 saved_bc_sides[e],
1912 saved_bc_ids[e]);
1913
1914 // Trim unused and renumber nodes and elements
1916}
dof_id_type id() const
Definition dof_object.h:819
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
virtual dof_id_type n_active_elem() const =0
void libmesh_ignore(const Args &...)

References libMesh::MeshBase::add_elem(), libMesh::BoundaryInfo::add_side(), libMesh::BoundaryInfo::boundary_ids(), libMesh::Elem::build(), libMesh::MeshBase::delete_elem(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::get_extra_integer(), libMesh::DofObject::id(), libMesh::index_range(), libMesh::BoundaryInfo::invalid_id, libMesh::MeshBase::is_prepared(), libMesh::MeshBase::is_replicated(), libMesh::libmesh_assert(), libMesh::libmesh_ignore(), libMesh::Elem::mapping_data(), libMesh::Elem::mapping_type(), mesh, libMesh::MeshBase::n_active_elem(), libMesh::BoundaryInfo::n_boundary_conds(), libMesh::DofObject::n_extra_integers(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_index_range(), libMesh::Elem::node_ptr(), libMesh::MeshBase::prepare_for_use(), libMesh::DofObject::processor_id(), libMesh::remote_elem, libMesh::Elem::side_index_range(), libMesh::Elem::subdomain_id(), libMesh::Elem::type(), and libMesh::DofObject::unique_id().

Referenced by main().

◆ interpolate_surface()

void libMesh::MeshTools::Modification::interpolate_surface ( MeshBase mesh,
const Surface surface,
std::set< std::size_t >  ids = {},
bool  use_boundary_nodes = true 
)

Move nodes in mesh to their closest points on the specified surface.

If use_boundary_nodes is true (the default), then ids is interpreted as a set of boundary side ids, and nodes are moved iff they are on element sides which have those boundary ids. We do not consider nodal boundary ids, which are for discrete points, which cannot be continuously moved without moving a neighborhood around them.

If use_boundary_nodes is false, then ids is interpreted as a set of subdomain ids, and nodes are moved iff they are on elements which are on those subdomains.

If ids is empty, then rather than "do nothing" we interpret that as "do everything". If use_boundary_nodes is true then we move all nodes on domain boundary sides (where elements have null neighbors), and if false then we move all nodes.

Definition at line 1695 of file mesh_modification.C.

1699{
1700 const bool is_serial = mesh.is_serial();
1701 const processor_id_type mesh_pid = mesh.processor_id();
1702
1703 // We might have to move ghost nodes on a distributed mesh if their
1704 // owners don't see a requisite element or boundary they're on.
1705 std::unordered_set<dof_id_type> moved_ghost_nodes;
1706
1707 auto move_node = [& moved_ghost_nodes, & surface, is_serial, mesh_pid]
1708 (Node & node) {
1709 node = surface.closest_point(node);
1710
1711 if (!is_serial && node.processor_id() != mesh_pid)
1712 moved_ghost_nodes.insert(node.id());
1713 };
1714
1715 const bool no_ids = ids.empty();
1716 const BoundaryInfo & boundary_info = mesh.get_boundary_info();
1717
1718 for (const auto & elem : mesh.active_element_ptr_range())
1719 {
1720 if (elem->mapping_type() != LAGRANGE_MAP)
1721 libmesh_not_implemented();
1722
1723 if (use_boundary_nodes)
1724 {
1725 for (auto s : elem->side_index_range())
1726 {
1727 if (no_ids)
1728 {
1729 // If we're not using boundary ids, we're
1730 // interpolating all external and no internal
1731 // boundaries
1732 if (elem->neighbor_ptr(s))
1733 continue;
1734 }
1735 else
1736 {
1737 if (std::none_of(ids.begin(), ids.end(),
1738 [&boundary_info,elem,s](std::size_t bcid)
1739 {return boundary_info.has_boundary_id(elem, s, bcid);}))
1740 continue;
1741 }
1742
1743 for (auto n : elem->nodes_on_side(s))
1744 move_node(elem->node_ref(n));
1745 }
1746 }
1747 else
1748 {
1749 if (no_ids || ids.count(elem->subdomain_id()))
1750 for (Node & node : elem->node_ref_range())
1751 move_node(node);
1752 }
1753 }
1754
1755 if (!is_serial)
1756 {
1757 std::map<processor_id_type, std::vector<dof_id_type>> moved_nodes_map;
1758 for (auto id : moved_ghost_nodes)
1759 {
1760 const Node & node = mesh.node_ref(id);
1761 moved_nodes_map[node.processor_id()].push_back(node.id());
1762 }
1763
1764 auto action_functor =
1765 [& mesh, & surface]
1766 (processor_id_type /* pid */,
1767 const std::vector<dof_id_type> & my_moved_nodes)
1768 {
1769 for (auto id : my_moved_nodes)
1770 {
1771 Node & node = mesh.node_ref(id);
1772 node = surface.closest_point(node);
1773 }
1774 };
1775
1776 // First get new node positions to their owners
1777 Parallel::push_parallel_vector_data
1778 (mesh.comm(), moved_nodes_map, action_functor);
1779
1780 // Then get node positions to anyone else with them ghosted
1781 SyncNodalPositions sync_object(mesh);
1783 (mesh.comm(), mesh.nodes_begin(), mesh.nodes_end(),
1784 sync_object);
1785 }
1786
1787 // We haven't changed any topology, but just changing geometry could
1788 // have invalidated a point locator.
1790}
processor_id_type processor_id() const
Definition dof_object.h:881
virtual const Node & node_ref(const dof_id_type i) const
Definition mesh_base.h:745
void sync_dofobject_data_by_id(const Communicator &comm, const Iterator &range_begin, const Iterator &range_end, SyncFunctor &sync)
Request data about a range of ghost dofobjects uniquely identified by their id.
uint8_t processor_id_type
Definition id_types.h:104

References libMesh::MeshBase::clear_point_locator(), libMesh::Surface::closest_point(), libMesh::ParallelObject::comm(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::MeshBase::is_serial(), libMesh::LAGRANGE_MAP, libMesh::Elem::mapping_type(), mesh, libMesh::Elem::neighbor_ptr(), libMesh::MeshBase::node_ref(), libMesh::Elem::node_ref(), libMesh::Elem::node_ref_range(), libMesh::Elem::nodes_on_side(), libMesh::DofObject::processor_id(), libMesh::ParallelObject::processor_id(), TIMPI::push_parallel_vector_data(), libMesh::Elem::side_index_range(), libMesh::Elem::subdomain_id(), and libMesh::Parallel::sync_dofobject_data_by_id().

◆ orient_elements()

void libMesh::MeshTools::Modification::orient_elements ( MeshBase mesh)

Redo the nodal ordering of each element as necessary to give the element Jacobian a positive orientation.

This function does not currently handle meshes with any element refinement.

Definition at line 273 of file mesh_modification.C.

274{
275 LOG_SCOPE("orient_elements()", "MeshTools::Modification");
276
277 // We don't yet support doing orient() on a parent element, which
278 // would require us to consistently orient all its children and
279 // give them different local child numbers.
280 unsigned int n_levels = MeshTools::n_levels(mesh);
281 if (n_levels > 1)
282 libmesh_not_implemented_msg("orient_elements() does not support refined meshes");
283
284 BoundaryInfo & boundary_info = mesh.get_boundary_info();
285 for (auto elem : mesh.element_ptr_range())
286 elem->orient(&boundary_info);
287}
unsigned int n_levels(const MeshBase &mesh)
Definition mesh_tools.C:826

References libMesh::MeshBase::get_boundary_info(), mesh, libMesh::MeshTools::n_levels(), and libMesh::Elem::orient().

Referenced by ElemTest< elem_type >::test_orient_elements().

◆ permute_elements()

void libMesh::MeshTools::Modification::permute_elements ( MeshBase mesh)

Randomly permute the nodal ordering of each element (without twisting the element mapping).

This is useful for regression testing with a variety of element orientations.

This function does not currently handle meshes with any element refinement.

This function does not currently permute BoundaryInfo data associated with element sides, which will likely be scrambled by the permutation.

Definition at line 233 of file mesh_modification.C.

234{
235 LOG_SCOPE("permute_elements()", "MeshTools::Modification");
236
237 // We don't yet support doing permute() on a parent element, which
238 // would require us to consistently permute all its children and
239 // give them different local child numbers.
240 unsigned int n_levels = MeshTools::n_levels(mesh);
241 if (n_levels > 1)
242 libmesh_error();
243
244 const unsigned int seed = 123456;
245
246 // seed the random number generator.
247 // We'll loop from 1 to max_elem_id on every processor, even those
248 // that don't have a particular element, so that the pseudorandom
249 // numbers will be the same everywhere.
250 std::srand(seed);
251
252
253 for (auto e_id : make_range(mesh.max_elem_id()))
254 {
255 int my_rand = std::rand();
256
257 Elem * elem = mesh.query_elem_ptr(e_id);
258
259 if (!elem)
260 continue;
261
262 const unsigned int max_permutation = elem->n_permutations();
263 if (!max_permutation)
264 continue;
265
266 const unsigned int perm = my_rand % max_permutation;
267
268 elem->permute(perm);
269 }
270}
virtual unsigned int n_permutations() const =0
Returns the number of independent permutations of element nodes - e.g.
virtual void permute(unsigned int perm_num)=0
Permutes the element (by swapping node and neighbor pointers) according to the specified index.
virtual const Elem * query_elem_ptr(const dof_id_type i) const =0

References libMesh::make_range(), libMesh::MeshBase::max_elem_id(), mesh, libMesh::MeshTools::n_levels(), libMesh::Elem::n_permutations(), libMesh::Elem::permute(), and libMesh::MeshBase::query_elem_ptr().

Referenced by main(), and FETestBase< order, family, elem_type, build_nx, CaseName >::setUp().

◆ redistribute()

void libMesh::MeshTools::Modification::redistribute ( MeshBase mesh,
const FunctionBase< Real > &  mapfunc 
)

Deterministically perturb the nodal locations.

This function will move each node from it's current x/y/z coordinates to a new x/y/z coordinate given by the first LIBMESH_DIM components of the specified function mapfunc

Nodes on the boundary are also moved.

Currently, non-vertex nodes are moved in the same way as vertex nodes, according to (newx,newy,newz) = mapfunc(x,y,z). This behavior is often suboptimal for higher order geometries and may be subject to change in future libMesh versions.

Definition at line 291 of file mesh_modification.C.

293{
296
297 LOG_SCOPE("redistribute()", "MeshTools::Modification");
298
299 DenseVector<Real> output_vec(LIBMESH_DIM);
300
301 // FIXME - we should thread this later.
302 std::unique_ptr<FunctionBase<Real>> myfunc = mapfunc.clone();
303
304 for (auto & node : mesh.node_ptr_range())
305 {
306 (*myfunc)(*node, output_vec);
307
308 (*node)(0) = output_vec(0);
309#if LIBMESH_DIM > 1
310 (*node)(1) = output_vec(1);
311#endif
312#if LIBMESH_DIM > 2
313 (*node)(2) = output_vec(2);
314#endif
315 }
316
317 // If we just moved a mesh in or out out of the X axis or XY plane
318 // then we might have changed its spatial_dimension()
320
321 // We haven't changed any topology, but just changing geometry could
322 // have invalidated a point locator.
324}
Defines a dense vector for use in Finite Element-type computations.
virtual std::unique_ptr< FunctionBase< Output > > clone() const =0

References libMesh::MeshBase::clear_point_locator(), libMesh::FunctionBase< Output >::clone(), libMesh::libmesh_assert(), mesh, libMesh::MeshBase::n_elem(), libMesh::MeshBase::n_nodes(), and libMesh::MeshBase::unset_has_cached_elem_data().

Referenced by libMesh::MeshTools::Generation::build_cube(), FETestBase< order, family, elem_type, build_nx, CaseName >::setUp(), MeshSmootherTest::testLaplaceSmoother(), MeshSmootherTest::testVariationalSmoother(), and MeshSmootherTest::testVariationalSmootherRegression().

◆ rotate()

RealTensorValue libMesh::MeshTools::Modification::rotate ( MeshBase mesh,
const Real  phi,
const Real  theta = 0.,
const Real  psi = 0. 
)

Rotates the mesh in 3D space.

Here the standard Euler angles are adopted (http://mathworld.wolfram.com/EulerAngles.html) The angles are in degrees (360 make a full circle)

Returns
the 3x3 rotation matrix implied by (phi, theta, psi)

Definition at line 371 of file mesh_modification.C.

375{
376 // We won't change any topology, but just changing geometry could
377 // invalidate a point locator.
379
380#if LIBMESH_DIM == 3
381 const auto R = RealTensorValue::intrinsic_rotation_matrix(phi, theta, psi);
382
383 for (auto & node : mesh.node_ptr_range())
384 {
385 Point & pt = *node;
386 pt = R * pt;
387 }
388
389 // If we just moved a mesh in or out out of the X axis or XY plane
390 // then we might have changed its spatial_dimension()
392
393 return R;
394
395#else
396 libmesh_ignore(mesh, phi, theta, psi);
397 libmesh_error_msg("MeshTools::Modification::rotate() requires libMesh to be compiled with LIBMESH_DIM==3");
398 // We'll never get here
399 return RealTensorValue();
400#endif
401}
static TensorValue< Real > intrinsic_rotation_matrix(Real phi, Real theta, Real psi)
Generate the intrinsic rotation matrix associated with the provided Euler angles.
TensorValue< Real > RealTensorValue
Useful typedefs to allow transparent switching between Real and Complex data types.

References libMesh::MeshBase::clear_point_locator(), libMesh::TensorValue< T >::intrinsic_rotation_matrix(), libMesh::libmesh_ignore(), mesh, and libMesh::MeshBase::unset_has_cached_elem_data().

Referenced by main(), and FETestBase< order, family, elem_type, build_nx, CaseName >::setUp().

◆ scale()

void libMesh::MeshTools::Modification::scale ( MeshBase mesh,
const Real  xs,
const Real  ys = 0.,
const Real  zs = 0. 
)

Scales the mesh.

The grid points are scaled in the x direction by xs, in the y direction by ys, etc... If only xs is specified then the scaling is assumed uniform in all directions.

Definition at line 404 of file mesh_modification.C.

408{
409 const Real x_scale = xs;
410 Real y_scale = ys;
411 Real z_scale = zs;
412
413 if (ys == 0.)
414 {
415 libmesh_assert_equal_to (zs, 0.);
416
417 y_scale = z_scale = x_scale;
418 }
419
420 // Scale the x coordinate in all dimensions
421 for (auto & node : mesh.node_ptr_range())
422 (*node)(0) *= x_scale;
423
424 // Only scale the y coordinate in 2 and 3D
425 if (LIBMESH_DIM < 2)
426 return;
427
428 for (auto & node : mesh.node_ptr_range())
429 (*node)(1) *= y_scale;
430
431 // Only scale the z coordinate in 3D
432 if (LIBMESH_DIM < 3)
433 return;
434
435 for (auto & node : mesh.node_ptr_range())
436 (*node)(2) *= z_scale;
437
438 // If we just collapsed a manifold onto the X axis or XY plane
439 // then we might have changed its spatial_dimension()
441
442 // We haven't changed any topology, but just changing geometry could
443 // have invalidated a point locator.
445}

References libMesh::MeshBase::clear_point_locator(), mesh, libMesh::Real, and libMesh::MeshBase::unset_has_cached_elem_data().

Referenced by main().

◆ smooth()

void libMesh::MeshTools::Modification::smooth ( MeshBase mesh,
unsigned int  n_iterations,
Real  power 
)

Smooth the mesh with a simple Laplace smoothing algorithm.

The mesh is smoothed n_iterations times. If the parameter power is 0, each node is moved to the average position of the neighboring connected nodes. If power > 0, the node positions are weighted by their distance. The positions of higher order nodes, and nodes living in refined elements, are calculated from the vertex positions of their parent nodes. Only works in 2D.

Author
Martin Luthi (luthi.nosp@m.@gi..nosp@m.alask.nosp@m.a.ed.nosp@m.u)
Date
2005

This implementation assumes every element "side" has only 2 nodes.

Definition at line 1536 of file mesh_modification.C.

1539{
1543 libmesh_assert_equal_to (mesh.mesh_dimension(), 2);
1544
1545 /*
1546 * Create a quickly-searchable list of boundary nodes.
1547 */
1548 std::unordered_set<dof_id_type> boundary_node_ids =
1550
1551 // For avoiding extraneous element side allocation
1552 ElemSideBuilder side_builder;
1553
1554 for (unsigned int iter=0; iter<n_iterations; iter++)
1555 {
1556 /*
1557 * loop over the mesh refinement level
1558 */
1559 unsigned int n_levels = MeshTools::n_levels(mesh);
1560 for (unsigned int refinement_level=0; refinement_level != n_levels;
1561 refinement_level++)
1562 {
1563 // initialize the storage (have to do it on every level to get empty vectors
1564 std::vector<Point> new_positions;
1565 std::vector<Real> weight;
1566 new_positions.resize(mesh.n_nodes());
1567 weight.resize(mesh.n_nodes());
1568
1569 {
1570 // Loop over the elements to calculate new node positions
1571 for (const auto & elem : as_range(mesh.level_elements_begin(refinement_level),
1572 mesh.level_elements_end(refinement_level)))
1573 {
1574 /*
1575 * We relax all nodes on level 0 first
1576 * If the element is refined (level > 0), we interpolate the
1577 * parents nodes with help of the embedding matrix
1578 */
1579 if (refinement_level == 0)
1580 {
1581 for (auto s : elem->side_index_range())
1582 {
1583 /*
1584 * Only operate on sides which are on the
1585 * boundary or for which the current element's
1586 * id is greater than its neighbor's.
1587 * Sides get only built once.
1588 */
1589 if ((elem->neighbor_ptr(s) != nullptr) &&
1590 (elem->id() > elem->neighbor_ptr(s)->id()))
1591 {
1592 const Elem & side = side_builder(*elem, s);
1593 const Node & node0 = side.node_ref(0);
1594 const Node & node1 = side.node_ref(1);
1595
1596 Real node_weight = 1.;
1597 // calculate the weight of the nodes
1598 if (power > 0)
1599 {
1600 Point diff = node0-node1;
1601 node_weight = std::pow(diff.norm(), power);
1602 }
1603
1604 const dof_id_type id0 = node0.id(), id1 = node1.id();
1605 new_positions[id0].add_scaled( node1, node_weight );
1606 new_positions[id1].add_scaled( node0, node_weight );
1607 weight[id0] += node_weight;
1608 weight[id1] += node_weight;
1609 }
1610 } // element neighbor loop
1611 }
1612#ifdef LIBMESH_ENABLE_AMR
1613 else // refinement_level > 0
1614 {
1615 /*
1616 * Find the positions of the hanging nodes of refined elements.
1617 * We do this by calculating their position based on the parent
1618 * (one level less refined) element, and the embedding matrix
1619 */
1620
1621 const Elem * parent = elem->parent();
1622
1623 /*
1624 * find out which child I am
1625 */
1626 unsigned int c = parent->which_child_am_i(elem);
1627 /*
1628 *loop over the childs (that is, the current elements) nodes
1629 */
1630 for (auto nc : elem->node_index_range())
1631 {
1632 /*
1633 * the new position of the node
1634 */
1635 Point point;
1636 for (auto n : parent->node_index_range())
1637 {
1638 /*
1639 * The value from the embedding matrix
1640 */
1641 const Real em_val = parent->embedding_matrix(c,nc,n);
1642
1643 if (em_val != 0.)
1644 point.add_scaled (parent->point(n), em_val);
1645 }
1646
1647 const dof_id_type id = elem->node_ptr(nc)->id();
1648 new_positions[id] = point;
1649 weight[id] = 1.;
1650 }
1651 } // if element refinement_level
1652#endif // #ifdef LIBMESH_ENABLE_AMR
1653
1654 } // element loop
1655
1656 /*
1657 * finally reposition the vertex nodes
1658 */
1659 for (auto nid : make_range(mesh.n_nodes()))
1660 if (!boundary_node_ids.count(nid) && weight[nid] > 0.)
1661 mesh.node_ref(nid) = new_positions[nid]/weight[nid];
1662 }
1663
1664 // Now handle the additional second_order nodes by calculating
1665 // their position based on the vertex positions
1666 // we do a second loop over the level elements
1667 for (auto & elem : as_range(mesh.level_elements_begin(refinement_level),
1668 mesh.level_elements_end(refinement_level)))
1669 {
1670 const unsigned int son_begin = elem->n_vertices();
1671 const unsigned int son_end = elem->n_nodes();
1672 for (unsigned int n=son_begin; n<son_end; n++)
1673 {
1674 const unsigned int n_adjacent_vertices =
1675 elem->n_second_order_adjacent_vertices(n);
1676
1677 Point point;
1678 for (unsigned int v=0; v<n_adjacent_vertices; v++)
1679 point.add(elem->point( elem->second_order_adjacent_vertex(n,v) ));
1680
1681 const dof_id_type id = elem->node_ptr(n)->id();
1682 mesh.node_ref(id) = point/n_adjacent_vertices;
1683 }
1684 }
1685 } // refinement_level loop
1686 } // end iteration
1687
1688 // We haven't changed any topology, but just changing geometry could
1689 // have invalidated a point locator.
1691}
Helper for building element sides that minimizes the construction of new elements.
const Point & point(const unsigned int i) const
Definition elem.h:2462
const Node & node_ref(const unsigned int i) const
Definition elem.h:2538
const Elem * parent() const
Definition elem.h:3047
virtual Real embedding_matrix(const unsigned int child_num, const unsigned int child_node_num, const unsigned int parent_node_num) const =0
unsigned int which_child_am_i(const Elem *e) const
Definition elem.h:3209
void add(const TypeVector< T2 > &)
Add to this vector without creating a temporary.
auto norm() const
void add_scaled(const TypeVector< T2 > &, const T &)
Add a scaled value to this vector without creating a temporary.
dof_id_type weight(const MeshBase &mesh, const processor_id_type pid)
Definition mesh_tools.C:444
SimpleRange< IndexType > as_range(const std::pair< IndexType, IndexType > &p)
Helper function that allows us to treat a homogenous pair as a range.
const dof_id_type n_nodes
Definition tecplot_io.C:67

References libMesh::TypeVector< T >::add(), libMesh::TypeVector< T >::add_scaled(), libMesh::as_range(), libMesh::MeshBase::clear_point_locator(), libMesh::Elem::embedding_matrix(), libMesh::MeshTools::find_boundary_nodes(), libMesh::DofObject::id(), libMesh::make_range(), mesh, libMesh::MeshBase::mesh_dimension(), libMesh::MeshTools::n_levels(), libMesh::Elem::n_nodes(), libMesh::MeshBase::n_nodes(), libMesh::Elem::n_second_order_adjacent_vertices(), libMesh::Elem::n_vertices(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_index_range(), libMesh::Elem::node_ptr(), libMesh::MeshBase::node_ref(), libMesh::Elem::node_ref(), libMesh::TypeVector< T >::norm(), libMesh::Elem::parent(), libMesh::Elem::point(), libMesh::Real, libMesh::Elem::second_order_adjacent_vertex(), libMesh::Elem::side_index_range(), libMesh::MeshTools::weight(), and libMesh::Elem::which_child_am_i().

◆ translate()

void libMesh::MeshTools::Modification::translate ( MeshBase mesh,
const Real  xt = 0.,
const Real  yt = 0.,
const Real  zt = 0. 
)

Translates the mesh.

The grid points are translated in the x direction by xt, in the y direction by yt, etc...

Definition at line 328 of file mesh_modification.C.

332{
333 const Point p(xt, yt, zt);
334
335 for (auto & node : mesh.node_ptr_range())
336 *node += p;
337
338 // If we just moved a mesh in or out out of the X axis or XY plane
339 // then we might have changed its spatial_dimension()
341
342 // We haven't changed any topology, but just changing geometry could
343 // have invalidated a point locator.
345}

References libMesh::MeshBase::clear_point_locator(), mesh, and libMesh::MeshBase::unset_has_cached_elem_data().

Referenced by MeshStitchTest::testMeshStitchElemsets(), and MeshTriangulationTest::testTriangulatorRoundHole().