libMesh
Loading...
Searching...
No Matches
variational_smoother_constraint.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18// Local Includes
19#include "libmesh/variational_smoother_constraint.h"
20#include "libmesh/mesh_tools.h"
21#include "libmesh/boundary_info.h"
22
23namespace libMesh
24{
25
26// Helper function to orient a vector in the positive x/y/z direction
27auto get_positive_vector = [](const Point & vec) -> Point {
28 libmesh_error_msg_if(vec.norm() < TOLERANCE,
29 "Can't define a positively-oriented vector with a zero vector.");
30
31 // Choose sign such that direction vector points in the positive x/y/z
32 // direction This helps to eliminate duplicate lines/planes
33 Point canonical{0, 0, 0};
34 // Choose the canonical dimension to ensure the dot product below is nonzero
35 for (const auto dim_id : make_range(3))
36 if (!absolute_fuzzy_equals(vec(dim_id), 0.))
37 {
38 canonical(dim_id) = 1.;
39 break;
40 }
41
42 const auto dot_prod = vec * canonical;
44
45 return (dot_prod > 0) ? vec.unit() : -vec.unit();
46};
47
48PointConstraint::PointConstraint(const Point & point, const Real & tol) : _point(point), _tol(tol)
49{
50}
51
53{
54 if (*this == other)
55 return false;
56
57 return _point < other.point();
58}
59
61{
62 return _point.absolute_fuzzy_equals(other.point(), _tol);
63}
64
66{
67 // using visit to resolve the variant to its actual type
68 return std::visit(
69 [&](auto && o) -> ConstraintVariant {
70 if (!o.contains_point(*this))
71 // Point is not on the constraint
72 return InvalidConstraint();
73
74 return *this;
75 },
76 other);
77}
78
79LineConstraint::LineConstraint(const Point & point, const Point & direction, const Real & tol)
80 : _point(point), _direction(get_positive_vector(direction)), _tol(tol)
81{
82 libmesh_error_msg_if(_direction.norm() < _tol,
83 "Can't define a line with zero magnitude direction vector.");
84}
85
87{
88 if (*this == other)
89 return false;
90
92 return _direction < other.direction();
93 return (_direction * _point) < (other.direction() * other.point());
94}
95
97{
99 return false;
100 return this->contains_point(other.point());
101}
102
104{
105 // If the point lies on the line, then the vector p - point is parallel to the
106 // line In that case, the cross product of p - point with the line's direction
107 // will be zero.
108 return _direction.cross(p.point() - _point).norm() < _tol;
109}
110
115
117{
118 return _direction * p.normal() < _tol;
119}
120
122{
123 // using visit to resolve the variant to its actual type
124 return std::visit(
125 [&](auto && o) -> ConstraintVariant {
126 // Use std::decay_t to strip references/const/volatile from o's type,
127 // so we can match the actual stored variant type in std::is_same_v.
128 using T = std::decay_t<decltype(o)>;
129 if constexpr (std::is_same_v<T, LineConstraint>)
130 {
131 if (*this == o)
132 return *this;
133
134 if (this->is_parallel(o))
135 // Lines are parallel and do not intersect
136 return InvalidConstraint();
137
138 // Solve for t in the equation p1 + t·d1 = p2 + s·d2
139 // The shortest vector between skew lines lies along the normal vector
140 // (d1 × d2). Projecting the vector (p2 - p1) onto this normal gives a
141 // scalar proportional to the distance. This is equivalent to solving:
142 // ((p2 - p1) × d2) · (d1 × d2) = t · |d1 × d2|²
143 // ⇒ t = ((delta × d2) · (d1 × d2)) / |d1 × d2|²
144
145 const Point delta = o.point() - _point;
146 const Point cross_d1_d2 = _direction.cross(o.direction());
147 const Real cross_dot = (delta.cross(o.direction())) * cross_d1_d2;
148 const Real denom = cross_d1_d2.norm_sq();
149
150 const Real t = cross_dot / denom;
151 const Point intersection = _point + t * _direction;
152
153 // Verify that intersection lies on both lines
154 if (o.direction().cross(intersection - o.point()).norm() > _tol)
155 // Lines do not intersect at a single point
156 return InvalidConstraint();
157
158 return PointConstraint{intersection};
159 }
160
161 else if constexpr (std::is_same_v<T, PlaneConstraint>)
162 return o.intersect(*this);
163
164 else if constexpr (std::is_same_v<T, PointConstraint>)
165 {
166 if (!this->contains_point(o))
167 // Point is not on the line
168 return InvalidConstraint();
169
170 return o;
171 }
172
173 else
174 libmesh_error_msg("Unsupported constraint type in Line::intersect.");
175 },
176 other);
177}
178
179PlaneConstraint::PlaneConstraint(const Point & point, const Point & normal, const Real & tol)
180 : _point(point), _normal(get_positive_vector(normal)), _tol(tol)
181{
182 libmesh_error_msg_if(_normal.norm() < _tol,
183 "Can't define a plane with zero magnitude direction vector.");
184}
185
187{
188 if (*this == other)
189 return false;
190
191 if (!(_normal.absolute_fuzzy_equals(other.normal(), _tol)))
192 return _normal < other.normal();
193 return (_normal * _point) < (other.normal() * other.point());
194}
195
197{
198 if (!(_normal.absolute_fuzzy_equals(other.normal(), _tol)))
199 return false;
200 return this->contains_point(other.point());
201}
202
207
208bool PlaneConstraint::is_parallel(const LineConstraint & l) const { return l.is_parallel(*this); }
209
211{
212 // distance between the point and the plane
213 const Real dist = (p.point() - _point) * _normal;
214 return std::abs(dist) < _tol;
215}
216
218{
219 const bool base_on_plane = this->contains_point(PointConstraint(l.point()));
220 const bool dir_orthogonal = std::abs(_normal * l.direction()) < _tol;
221 return base_on_plane && dir_orthogonal;
222}
223
225{
226 // using visit to resolve the variant to its actual type
227 return std::visit(
228 [&](auto && o) -> ConstraintVariant {
229 // Use std::decay_t to strip references/const/volatile from o's type,
230 // so we can match the actual stored variant type in std::is_same_v.
231 using T = std::decay_t<decltype(o)>;
232 if constexpr (std::is_same_v<T, PlaneConstraint>)
233 {
234 // If planes are identical, return one of them
235 if (*this == o)
236 return *this;
237
238 if (this->is_parallel(o))
239 // Planes are parallel and do not intersect
240 return InvalidConstraint();
241
242 // Solve for a point on the intersection line of two planes.
243 // Given planes:
244 // Plane 1: n1 · (x - p1) = 0
245 // Plane 2: n2 · (x - p2) = 0
246 // The line of intersection has direction dir = n1 × n2.
247 // To find a point on this line, we assume:
248 // x = p1 + s·n1 = p2 + t·n2
249 // ⇒ p1 - p2 = t·n2 - s·n1
250 // Taking dot products with n1 and n2 leads to:
251 // [-n1·n1 n1·n2] [s] = [n1 · (p1 - p2)]
252 // [-n1·n2 n2·n2] [t] [n2 · (p1 - p2)]
253
254 const Point dir = this->_normal.cross(o.normal()); // direction of line of intersection
255 libmesh_assert(dir.norm() > _tol);
256 const Point w = _point - o.point();
257
258 // Dot product terms used in 2x2 system
259 const Real n1_dot_n1 = _normal * _normal;
260 const Real n1_dot_n2 = _normal * o.normal();
261 const Real n2_dot_n2 = o.normal() * o.normal();
262 const Real n1_dot_w = _normal * w;
263 const Real n2_dot_w = o.normal() * w;
264
265 const Real denom = -(n1_dot_n1 * n2_dot_n2 - n1_dot_n2 * n1_dot_n2);
266 libmesh_assert(std::abs(denom) > _tol);
267
268 const Real s = -(n1_dot_n2 * n2_dot_w - n2_dot_n2 * n1_dot_w) / denom;
269 const Point p0 = _point + s * _normal;
270
271 return LineConstraint{p0, dir};
272 }
273
274 else if constexpr (std::is_same_v<T, LineConstraint>)
275 {
276 if (this->contains_line(o))
277 return o;
278
279 if (this->is_parallel(o))
280 // Line is parallel and does not intersect the plane
281 return InvalidConstraint();
282
283 // Solve for t in the parametric equation:
284 // p(t) = point + t·d
285 // such that this point also satisfies the plane equation:
286 // n · (p(t) - p0) = 0
287 // which leads to:
288 // t = (n · (p0 - point)) / (n · d)
289
290 const Real denom = _normal * o.direction();
291 libmesh_assert(std::abs(denom) > _tol);
292 const Real t = (_normal * (_point - o.point())) / denom;
293 return PointConstraint{o.point() + t * o.direction()};
294 }
295
296 else if constexpr (std::is_same_v<T, PointConstraint>)
297 {
298 if (!this->contains_point(o))
299 // Point is not on the plane
300 return InvalidConstraint();
301
302 return o;
303 }
304
305 else
306 libmesh_error_msg("Unsupported constraint type in Plane::intersect.");
307 },
308 other);
309}
310
311std::ostream &operator<<(std::ostream &os, const ConstraintVariant & c)
312{
313 if (std::holds_alternative<PointConstraint>(c))
314 os << "point constraint: point=" << std::get<PointConstraint>(c).point();
315
316 else if (std::holds_alternative<LineConstraint>(c))
317 {
318 const auto & line = std::get<LineConstraint>(c);
319 os << "line constraint: point=" << line.point() << ", direction=" << line.direction();
320 }
321
322 else if (std::holds_alternative<PlaneConstraint>(c))
323 {
324 const auto & plane = std::get<PlaneConstraint>(c);
325 os << "plane constraint: point=" << plane.point() << ", normal=" << plane.normal();
326 }
327
328 else if (std::holds_alternative<InvalidConstraint>(c))
329 os << "invalid constraint";
330
331 return os;
332}
333
335 System & sys, const bool & preserve_subdomain_boundaries, const unsigned int verbosity)
336 : Constraint(), _verbosity(verbosity), _sys(sys), _preserve_subdomain_boundaries(preserve_subdomain_boundaries) {
337}
338
340
342{
343 const auto & mesh = _sys.get_mesh();
344 const auto dim = mesh.mesh_dimension();
345 const auto & proc_id = mesh.processor_id();
346
347 // Only compute the node to elem map once
348 std::unordered_map<dof_id_type, std::vector<const Elem *>> nodes_to_elem_map;
349 MeshTools::build_nodes_to_elem_map(mesh, nodes_to_elem_map);
350
351 const auto & boundary_info = mesh.get_boundary_info();
352
353 const auto boundary_node_ids = MeshTools::find_boundary_nodes(mesh);
354
355 // Identify/constrain subdomain boundary nodes, if requested
356 std::unordered_map<dof_id_type, ConstraintVariant> subdomain_boundary_map;
358 {
359 for (const auto * elem : mesh.active_element_ptr_range())
360 {
361 const auto & sub_id1 = elem->subdomain_id();
362 for (const auto side : elem->side_index_range())
363 {
364 const auto * neighbor = elem->neighbor_ptr(side);
365 if (neighbor == nullptr)
366 continue;
367
368 const auto & sub_id2 = neighbor->subdomain_id();
369 if (sub_id1 == sub_id2)
370 continue;
371
372 // elem and neighbor are in different subdomains, and share nodes
373 // that need to be constrained
374 for (const auto local_node_id : elem->nodes_on_side(side))
375 {
376 const auto & node = mesh.node_ref(elem->node_id(local_node_id));
377 // Make sure we haven't already processed this node.
378 // We leave the responsibility of computing the constraint to
379 // the proc that owns the node.
380 if (subdomain_boundary_map.count(node.id()) ||
381 node.processor_id() != proc_id)
382 continue;
383
384 // Get the relevant nodal neighbors for the subdomain constraint
385 const auto side_grouped_boundary_neighbors =
387 mesh, node, sub_id1, nodes_to_elem_map);
388
389 // Determine which constraint should be imposed
390 const auto subdomain_constraint =
391 determine_constraint(node, dim, side_grouped_boundary_neighbors);
392
393 // This subdomain boundary node does not lie on an external boundary,
394 // go ahead and impose constraint
395 if (boundary_node_ids.find(node.id()) == boundary_node_ids.end())
396 {
397 if (_verbosity > 20)
398 libMesh::out << "Imposing subdomain constraint on "
399 << std::endl << " " << node << std::endl
400 << " " << subdomain_constraint << std::endl;
401
402 this->impose_constraint(node, subdomain_constraint);
403 }
404
405 // This subdomain boundary node could lie on an external boundary, save it
406 // for later to combine with the external boundary constraint.
407 // We also save constraints for non-boundary nodes so we don't try to
408 // re-constrain the node when accessed from the neighboring elem.
409 // See subdomain_boundary_map.count call above.
410 subdomain_boundary_map[node.id()] = subdomain_constraint;
411
412 } // for local_node_id
413
414 } // for side
415 } // for elem
416 }
417
418 // Loop through boundary nodes and impose constraints
419 for (const auto & bid : boundary_node_ids)
420 {
421 const auto & node = mesh.node_ref(bid);
422 // We leave the responsibility of computing the constraint to the proc
423 // that owns the node.
424 if (node.processor_id() != proc_id)
425 continue;
426
427 // Get the relevant nodal neighbors for the boundary constraint
428 const auto side_grouped_boundary_neighbors = get_neighbors_for_boundary_constraint(
429 mesh, node, boundary_node_ids, boundary_info, nodes_to_elem_map);
430
431 // Determine which constraint should be imposed
432 const auto boundary_constraint =
433 determine_constraint(node, dim, side_grouped_boundary_neighbors);
434
435 // Check for the case where this boundary node is also part of a subdomain id boundary
436 if (const auto it = subdomain_boundary_map.find(bid); it != subdomain_boundary_map.end())
437 {
438 const auto & subdomain_constraint = it->second;
439 // Combine current boundary constraint with previously determined
440 // subdomain_constraint
441 auto combined_constraint = intersect_constraints(subdomain_constraint, boundary_constraint);
442
443 // This will catch cases where constraints have no intersection
444 // Fall back to fixed node constraint
445 if (std::holds_alternative<InvalidConstraint>(combined_constraint))
446 combined_constraint = PointConstraint(node);
447
448 if (_verbosity > 20)
449 libMesh::out << "Imposing boundary/subdomain constraint on "
450 << std::endl << " " << node << std::endl
451 << " " << combined_constraint << std::endl;
452
453 this->impose_constraint(node, combined_constraint);
454 }
455
456 else
457 {
458 if (_verbosity > 20)
459 libMesh::out << "Imposing boundary constraint on "
460 << std::endl << node << std::endl
461 << " " << boundary_constraint << std::endl;
462
463 this->impose_constraint(node, boundary_constraint);
464 }
465
466 } // end bid
467}
468
470{
471 for (const auto d : make_range(_sys.get_mesh().mesh_dimension()))
472 {
473 const auto constrained_dof_index = node.dof_number(_sys.number(), d, 0);
474 DofConstraintRow constraint_row;
475 // Leave the constraint row as all zeros so this dof is independent from other dofs
476 const auto constrained_value = node(d);
477 // Simply constrain this dof to retain it's current value
479 constrained_dof_index, constraint_row, constrained_value, true);
480 }
481}
482
484 const Point & ref_normal_vec)
485{
486 const auto dim = _sys.get_mesh().mesh_dimension();
487 // determine equation of plane: c_x * x + c_y * y + c_z * z + c = 0
488 std::vector<Real> xyz_coefs; // vector to hold c_x, c_y, c_z
489 Real c = 0.;
490
491 // We choose to constrain the dimension with the largest magnitude coefficient
492 // This approach ensures the coefficients added to the constraint_row
493 // (i.e., -c_xyz / c_max) have as small magnitude as possible
494
495 // We initialize this to avoid maybe-uninitialized compiler error
496 unsigned int constrained_dim = 0;
497
498 // Let's assert that we have a nonzero normal to ensure that constrained_dim
499 // is always set
500 libmesh_assert(ref_normal_vec.norm() > TOLERANCE);
501
502 Real max_abs_coef = 0.;
503 for (const auto d : make_range(dim))
504 {
505 const auto coef = ref_normal_vec(d);
506 xyz_coefs.push_back(coef);
507 c -= coef * node(d);
508
509 const auto coef_abs = std::abs(coef);
510 if (coef_abs > max_abs_coef)
511 {
512 max_abs_coef = coef_abs;
513 constrained_dim = d;
514 }
515 }
516
517 DofConstraintRow constraint_row;
518 for (const auto free_dim : make_range(dim))
519 {
520 if (free_dim == constrained_dim)
521 continue;
522
523 const auto free_dof_index = node.dof_number(_sys.number(), free_dim, 0);
524 constraint_row[free_dof_index] = -xyz_coefs[free_dim] / xyz_coefs[constrained_dim];
525 }
526
527 const auto inhomogeneous_part = -c / xyz_coefs[constrained_dim];
528 const auto constrained_dof_index = node.dof_number(_sys.number(), constrained_dim, 0);
530 constrained_dof_index, constraint_row, inhomogeneous_part, true);
531}
532
534 const Point & line_vec)
535{
536 const auto dim = _sys.get_mesh().mesh_dimension();
537
538 // We will free the dimension most parallel to line_vec to keep the
539 // constraint coefficients small
540 const std::vector<Real> line_vec_coefs{line_vec(0), line_vec(1), line_vec(2)};
541 auto it = std::max_element(line_vec_coefs.begin(), line_vec_coefs.end(), [](double a, double b) {
542 return std::abs(a) < std::abs(b);
543 });
544 const unsigned int free_dim = std::distance(line_vec_coefs.begin(), it);
545 const auto free_dof_index = node.dof_number(_sys.number(), free_dim, 0);
546
547 // A line is parameterized as r(t) = node + t * line_vec, so
548 // x(t) = node(x) + t * line_vec(x)
549 // y(t) = node(y) + t * line_vec(y)
550 // z(t) = node(z) + t * line_vec(z)
551 // Let's say we leave x free. Then t = (x(t) - node(x)) / line_vec(x)
552 // Then y and z can be constrained as
553 // y = node(y) + line_vec_y * (x(t) - node(x)) / line_vec(x)
554 // = x(t) * line_vec(y) / line_vec(x) + (node(y) - node(x) * line_vec(y) / line_vec(x))
555 // z = x(t) * line_vec(z) / line_vec(x) + (node(z) - node(x) * line_vec(z) / line_vec(x))
556
557 libmesh_assert(!relative_fuzzy_equals(line_vec(free_dim), 0.));
558 for (const auto constrained_dim : make_range(dim))
559 {
560 if (constrained_dim == free_dim)
561 continue;
562
563 DofConstraintRow constraint_row;
564 constraint_row[free_dof_index] = line_vec(constrained_dim) / line_vec(free_dim);
565 const auto inhomogeneous_part =
566 node(constrained_dim) - node(free_dim) * line_vec(constrained_dim) / line_vec(free_dim);
567 const auto constrained_dof_index = node.dof_number(_sys.number(), constrained_dim, 0);
569 constrained_dof_index, constraint_row, inhomogeneous_part, true);
570 }
571}
572
573// Utility function to determine whether two nodes share a boundary ID.
574// The motivation for this is that a sliding boundary node on a triangular
575// element can have a neighbor boundary node in the same element that is not
576// part of the same boundary
577// Consider the below example with nodes A, C, D, F, G that comprise elements
578// E1, E2, E3, with boundaries B1, B2, B3, B4. To determine the constraint
579// equations for the sliding node C, the neighboring nodes A and D need to
580// be identified to construct the line that C is allowed to slide along.
581// Note that neighbors A and D both share the boundary B1 with C.
582// Without ensuring that neighbors share the same boundary as the current
583// node, a neighboring node that does not lie on the same boundary
584// (i.e. F and G) might be selected to define the constraining line,
585// resulting in an incorrect constraint.
586// Note that if, for example, boundaries B1 and B2 were to be combined
587// into boundary B12, the node F would share a boundary id with node C
588// and result in an incorrect constraint. It would be useful to design
589// additional checks to detect cases like this.
590
591// B3
592// G-----------F
593// | \ / |
594// B4 | \ E2 / | B2
595// | \ / |
596// | E1 \ / E3 |
597// A-----C-----D
598// B1
599
601 const Node & neighbor_node,
602 const Elem & containing_elem,
603 const BoundaryInfo & boundary_info)
604{
605 bool nodes_share_bid = false;
606
607 // Node ids local to containing_elem
608 const auto node_id = containing_elem.get_node_index(&boundary_node);
609 const auto neighbor_id = containing_elem.get_node_index(&neighbor_node);
610
611 for (const auto side_id : containing_elem.side_index_range())
612 {
613 // We don't care about this side if it doesn't contain our boundary and neighbor nodes
614 if (!(containing_elem.is_node_on_side(node_id, side_id) &&
615 containing_elem.is_node_on_side(neighbor_id, side_id)))
616 continue;
617
618 // If the current side, containing boundary_node and neighbor_node, lies on a boundary,
619 // we can say that boundary_node and neighbor_node have a common boundary id.
620 std::vector<boundary_id_type> boundary_ids;
621 boundary_info.boundary_ids(&containing_elem, side_id, boundary_ids);
622 if (boundary_ids.size())
623 {
624 nodes_share_bid = true;
625 break;
626 }
627 }
628 return nodes_share_bid;
629}
630
631std::set<std::set<const Node *>>
633 const MeshBase & mesh,
634 const Node & node,
635 const subdomain_id_type sub_id,
636 const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map)
637{
638
639 // Find all the nodal neighbors... that is the nodes directly connected
640 // to this node through one edge, or if none exists, use other nodes on the
641 // containing face
642 std::vector<const Node *> neighbors;
644 mesh, node, nodes_to_elem_map, neighbors);
645
646 // Each constituent set corresponds to neighbors sharing a face on the
647 // subdomain boundary
648 std::set<std::set<const Node *>> side_grouped_boundary_neighbors;
649
650 for (const auto * neigh : neighbors)
651 {
652 // Determine whether the neighbor is on the subdomain boundary
653 // First, find the common elements that both node and neigh belong to
654 const auto & elems_containing_node = libmesh_map_find(nodes_to_elem_map, node.id());
655 const auto & elems_containing_neigh = libmesh_map_find(nodes_to_elem_map, neigh->id());
656 const Elem * common_elem = nullptr;
657 for (const auto * neigh_elem : elems_containing_neigh)
658 {
659 if ((std::find(elems_containing_node.begin(), elems_containing_node.end(), neigh_elem) !=
660 elems_containing_node.end())
661 // We should be able to find a common element on the sub_id boundary
662 && (neigh_elem->subdomain_id() == sub_id))
663 common_elem = neigh_elem;
664 else
665 continue;
666
667 // Now, determine whether node and neigh are on a side coincident
668 // with the subdomain boundary
669 for (const auto common_side : common_elem->side_index_range())
670 {
671 bool node_found_on_side = false;
672 bool neigh_found_on_side = false;
673 for (const auto local_node_id : common_elem->nodes_on_side(common_side))
674 {
675 if (common_elem->node_id(local_node_id) == node.id())
676 node_found_on_side = true;
677 else if (common_elem->node_id(local_node_id) == neigh->id())
678 neigh_found_on_side = true;
679 }
680
681 if (!(node_found_on_side && neigh_found_on_side &&
682 common_elem->neighbor_ptr(common_side)))
683 continue;
684
685 const auto matched_side = common_side;
686 // There could be multiple matched sides, so keep this next part
687 // inside the common_side loop
688 //
689 // Does matched_side, containing both node and neigh, lie on the
690 // sub_id subdomain boundary?
691 const auto matched_neighbor_sub_id =
692 common_elem->neighbor_ptr(matched_side)->subdomain_id();
693 const bool is_matched_side_on_subdomain_boundary = matched_neighbor_sub_id != sub_id;
694
695 if (is_matched_side_on_subdomain_boundary)
696 {
697 // Store all nodes that live on this side
698 const auto nodes_on_side = common_elem->nodes_on_side(common_side);
699 std::set<const Node *> node_ptrs_on_side;
700 for (const auto local_node_id : nodes_on_side)
701 node_ptrs_on_side.insert(common_elem->node_ptr(local_node_id));
702 node_ptrs_on_side.erase(node_ptrs_on_side.find(&node));
703 side_grouped_boundary_neighbors.insert(node_ptrs_on_side);
704
705 continue;
706 }
707
708 } // for common_side
709
710 } // for neigh_elem
711 }
712
713 return side_grouped_boundary_neighbors;
714}
715
716std::set<std::set<const Node *>>
718 const MeshBase & mesh,
719 const Node & node,
720 const std::unordered_set<dof_id_type> & boundary_node_ids,
721 const BoundaryInfo & boundary_info,
722 const std::unordered_map<dof_id_type, std::vector<const Elem *>> & nodes_to_elem_map)
723{
724
725 // Find all the nodal neighbors... that is the nodes directly connected
726 // to this node through one edge, or if none exists, use other nodes on the
727 // containing face
728 std::vector<const Node *> neighbors;
730 mesh, node, nodes_to_elem_map, neighbors);
731
732 // Each constituent set corresponds to neighbors sharing a face on the
733 // boundary
734 std::set<std::set<const Node *>> side_grouped_boundary_neighbors;
735
736 for (const auto * neigh : neighbors)
737 {
738 const bool is_neighbor_boundary_node =
739 boundary_node_ids.find(neigh->id()) != boundary_node_ids.end();
740 if (!is_neighbor_boundary_node)
741 continue;
742
743 // Determine whether nodes share a common boundary id
744 // First, find the common element that both node and neigh belong to
745 const auto & elems_containing_node = libmesh_map_find(nodes_to_elem_map, node.id());
746 const auto & elems_containing_neigh = libmesh_map_find(nodes_to_elem_map, neigh->id());
747 const Elem * common_elem = nullptr;
748 for (const auto * neigh_elem : elems_containing_neigh)
749 {
750 const bool is_neigh_common =
751 std::find(elems_containing_node.begin(), elems_containing_node.end(), neigh_elem) !=
752 elems_containing_node.end();
753 if (!is_neigh_common)
754 continue;
755 common_elem = neigh_elem;
756 // Keep this in the neigh_elem loop because there can be multiple common
757 // elements Now, determine whether node and neigh share a common boundary
758 // id
759 const bool nodes_have_common_bid = VariationalSmootherConstraint::nodes_share_boundary_id(
760 node, *neigh, *common_elem, boundary_info);
761 if (nodes_have_common_bid)
762 {
763 // Find the side coinciding with the shared boundary
764 for (const auto side : common_elem->side_index_range())
765 {
766 // We only care about external boundaries here, make sure side doesn't
767 // have a neighbor
768 if (common_elem->neighbor_ptr(side))
769 continue;
770
771 bool node_found_on_side = false;
772 bool neigh_found_on_side = false;
773 const auto nodes_on_side = common_elem->nodes_on_side(side);
774 for (const auto local_node_id : nodes_on_side)
775 {
776 if (common_elem->node_id(local_node_id) == node.id())
777 node_found_on_side = true;
778 else if (common_elem->node_id(local_node_id) == neigh->id())
779 neigh_found_on_side = true;
780 }
781 if (!(node_found_on_side && neigh_found_on_side))
782 continue;
783
784 std::set<const Node *> node_ptrs_on_side;
785 for (const auto local_node_id : nodes_on_side)
786 node_ptrs_on_side.insert(common_elem->node_ptr(local_node_id));
787 node_ptrs_on_side.erase(node_ptrs_on_side.find(&node));
788 side_grouped_boundary_neighbors.insert(node_ptrs_on_side);
789 }
790 continue;
791 }
792 }
793 }
794
795 return side_grouped_boundary_neighbors;
796}
797
799 const Node & node,
800 const unsigned int dim,
801 const std::set<std::set<const Node *>> & side_grouped_boundary_neighbors)
802{
803 // Determines the appropriate geometric constraint for a node based on its
804 // neighbors.
805
806 // Extract neighbors in flat vector
807 std::vector<const Node *> neighbors;
808 for (const auto & side : side_grouped_boundary_neighbors)
809 neighbors.insert(neighbors.end(), side.begin(), side.end());
810
811 // Constrain the node to it's current location
812 // Note that neighbors.size() <= 1 is used here instead of == 1 so we don't
813 // crash when trying to access *neighbors[0] a few lines below in the
814 // dim == 2 case
815 if (dim == 1 || neighbors.size() <= 1)
816 return PointConstraint{node};
817
818 if (dim == 2 || neighbors.size() == 2)
819 {
820 // Determine whether node + all neighbors are colinear
821 bool all_colinear = true;
822 const Point ref_dir = (*neighbors[0] - node).unit();
823 for (const auto i : make_range(std::size_t(1), neighbors.size()))
824 {
825 const Point delta = *(neighbors[i]) - node;
826 libmesh_assert(delta.norm() >= TOLERANCE);
827 const Point dir = delta.unit();
828 if (!dir.relative_fuzzy_equals(ref_dir) && !dir.relative_fuzzy_equals(-ref_dir))
829 {
830 all_colinear = false;
831 break;
832 }
833 }
834
835 if (all_colinear)
836 return LineConstraint{node, ref_dir};
837
838 return PointConstraint{node};
839 }
840
841 // dim == 3, neighbors.size() >= 3
842 std::set<PlaneConstraint> valid_planes;
843 for (const auto & side_nodes : side_grouped_boundary_neighbors)
844 {
845 std::vector<const Node *> side_nodes_vec(side_nodes.begin(), side_nodes.end());
846 for (const auto i : index_range(side_nodes_vec))
847 {
848 const Point vec_i = (*side_nodes_vec[i] - node);
849 for (const auto j : make_range(i))
850 {
851 const Point vec_j = (*side_nodes_vec[j] - node);
852 Point candidate_normal = vec_i.cross(vec_j);
853 if (candidate_normal.norm() <= TOLERANCE)
854 continue;
855
856 const PlaneConstraint candidate_plane{node, candidate_normal};
857 valid_planes.emplace(candidate_plane);
858 }
859 }
860 }
861
862 // Fall back to point constraint
863 if (valid_planes.empty())
864 return PointConstraint(node);
865
866 // Combine all the planes together to get a common constraint
867 auto it = valid_planes.begin();
868 ConstraintVariant current = *it++;
869 for (; it != valid_planes.end(); ++it)
870 {
871 current = intersect_constraints(current, *it);
872
873 // This will catch cases where constraints have no intersection
874 // (i.e., the element surface is non-planar)
875 // Fall back to fixed node constraint
876 if (std::holds_alternative<InvalidConstraint>(current))
877 {
878 current = PointConstraint(node);
879 break;
880 }
881 }
882
883 return current;
884}
885
886// Applies the computed constraint (PointConstraint, LineConstraint, or
887// PlaneConstraint) to a node.
889 const ConstraintVariant & constraint)
890{
891
892 libmesh_assert_msg(!std::holds_alternative<InvalidConstraint>(constraint),
893 "Cannot impose constraint using InvalidConstraint.");
894
895 if (std::holds_alternative<PointConstraint>(constraint))
896 fix_node(node);
897 else if (std::holds_alternative<LineConstraint>(constraint))
898 constrain_node_to_line(node, std::get<LineConstraint>(constraint).direction());
899 else if (std::holds_alternative<PlaneConstraint>(constraint))
900 constrain_node_to_plane(node, std::get<PlaneConstraint>(constraint).normal());
901
902 else
903 libmesh_assert_msg(false, "Unknown constraint type.");
904}
905
906} // namespace libMesh
unsigned int dim
The BoundaryInfo class contains information relevant to boundary conditions including storing faces,...
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 add_constraint_row(const dof_id_type dof_number, const DofConstraintRow &constraint_row, const Number constraint_rhs, const bool forbid_constraint_overwrite)
Adds a copy of the user-defined row to the constraint matrix, using an inhomogeneous right-hand-side ...
dof_id_type dof_number(const unsigned int s, const unsigned int var, const unsigned int comp) const
dof_id_type id() const
Definition dof_object.h:819
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const =0
virtual std::vector< unsigned int > nodes_on_side(const unsigned int) const =0
subdomain_id_type subdomain_id() const
Definition elem.h:2591
const Node * node_ptr(const unsigned int i) const
Definition elem.h:2516
unsigned int get_node_index(const Node *node_ptr) const
Definition elem.h:2554
dof_id_type node_id(const unsigned int i) const
Definition elem.h:2484
const Elem * neighbor_ptr(unsigned int i) const
Definition elem.h:2615
IntRange< unsigned short > side_index_range() const
Definition elem.h:2727
Represents an invalid constraint (i.e., when the two constraints don't intersect)
Represents a line constraint defined by a base point and direction vector.
bool contains_point(const PointConstraint &p) const
Query whether a point lies on the line.
const Point & direction() const
Const getter for the _direction attribute.
bool operator<(const LineConstraint &other) const
Comparison operator for ordering LineConstraint objects.
bool operator==(const LineConstraint &other) const
Equality operator.
bool is_parallel(const LineConstraint &l) const
Query whether a line is parallel to this line.
const Point & point() const
Const getter for the _point attribute.
Point _direction
Direction of the constraining line.
Real _tol
Tolerance to use for numerical comparisons.
ConstraintVariant intersect(const ConstraintVariant &other) const
Computes the intersection of this line with another constraint.
Point _point
A point on the constraining line.
This is the MeshBase class.
Definition mesh_base.h:81
unsigned int mesh_dimension() const
Definition mesh_base.C:430
A Node is like a Point, but with more information.
Definition node.h:55
Represents a plane constraint defined by a point and normal vector.
const Point & point() const
Const getter for the _point attribute.
const Point & normal() const
Const getter for the _normal attribute.
bool operator==(const PlaneConstraint &other) const
Equality operator.
Point _point
A point on the constraining plane.
Point _normal
The direction normal to the constraining plane.
ConstraintVariant intersect(const ConstraintVariant &other) const
Computes the intersection of this plane with another constraint.
bool is_parallel(const PlaneConstraint &p) const
Query whether a plane is parallel to this plane.
bool contains_line(const LineConstraint &l) const
Query whether a line lies on the plane.
bool contains_point(const PointConstraint &p) const
Query whether a point lies on the plane.
Real _tol
Tolerance to use for numerical comparisons.
bool operator<(const PlaneConstraint &other) const
Comparison operator for ordering PlaneConstraint objects.
Represents a fixed point constraint.
bool operator==(const PointConstraint &other) const
Equality operator.
const Point & point() const
Const getter for the _point attribute.
bool operator<(const PointConstraint &other) const
Comparison operator for ordering PointConstraint objects.
Point _point
Location of constraint.
ConstraintVariant intersect(const ConstraintVariant &other) const
Computes the intersection of this point with another constraint.
Real _tol
Tolerance to use for numerical comparisons.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition system.h:100
const DofMap & get_dof_map() const
Definition system.h:2417
unsigned int number() const
Definition system.h:2393
const MeshBase & get_mesh() const
Definition system.h:2401
bool absolute_fuzzy_equals(const TypeVector< T > &rhs, Real tol=TOLERANCE) const
auto norm_sq() const
TypeVector< typename CompareTypes< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const
bool relative_fuzzy_equals(const TypeVector< T > &rhs, Real tol=TOLERANCE) const
auto norm() const
TypeVector< T > unit() const
static std::set< std::set< const Node * > > get_neighbors_for_subdomain_constraint(const MeshBase &mesh, const Node &node, const subdomain_id_type sub_id, const std::unordered_map< dof_id_type, std::vector< const Elem * > > &nodes_to_elem_map)
Get the relevant nodal neighbors for a subdomain constraint.
const unsigned int _verbosity
Verbosity setting.
void fix_node(const Node &node)
Constrain (i.e., fix) a node to not move during mesh smoothing.
void impose_constraint(const Node &node, const ConstraintVariant &constraint)
Applies a given constraint to a node (e.g., fixing it, restricting it to a line or plane).
virtual void constrain() override
Constraint function.
void constrain_node_to_plane(const Node &node, const Point &ref_normal_vec)
Constrain a node to remain in the given plane during mesh smoothing.
VariationalSmootherConstraint(System &sys, const bool &preserve_subdomain_boundaries, const unsigned int verbosity)
Constructor.
static bool nodes_share_boundary_id(const Node &boundary_node, const Node &neighbor_node, const Elem &containing_elem, const BoundaryInfo &boundary_info)
Determines whether two neighboring nodes share a common boundary id.
virtual ~VariationalSmootherConstraint() override
const bool _preserve_subdomain_boundaries
Whether nodes on subdomain boundaries are subject to change via smoothing.
static ConstraintVariant determine_constraint(const Node &node, const unsigned int dim, const std::set< std::set< const Node * > > &side_grouped_boundary_neighbors)
Determines the appropriate constraint (PointConstraint, LineConstraint, or PlaneConstraint) for a nod...
void constrain_node_to_line(const Node &node, const Point &line_vec)
Constrain a node to remain on the given line during mesh smoothing.
static std::set< std::set< const Node * > > get_neighbors_for_boundary_constraint(const MeshBase &mesh, const Node &node, const std::unordered_set< dof_id_type > &boundary_node_ids, const BoundaryInfo &boundary_info, const std::unordered_map< dof_id_type, std::vector< const Elem * > > &nodes_to_elem_map)
Get the relevant nodal neighbors for an external boundary constraint.
static const Real b
MeshBase & mesh
void build_nodes_to_elem_map(const MeshBase &mesh, std::vector< std::vector< dof_id_type > > &nodes_to_elem_map)
After calling this function the input vector nodes_to_elem_map will contain the node to element conne...
Definition mesh_tools.C:456
void find_nodal_or_face_neighbors(const MeshBase &mesh, const Node &node, const std::unordered_map< dof_id_type, std::vector< const Elem * > > &nodes_to_elem_map, std::vector< const Node * > &neighbors)
Given a mesh and a node in the mesh, the vector will be filled with every node directly attached to t...
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
The libMesh namespace provides an interface to certain functionality in the library.
bool relative_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
Function to check whether two variables are equal within a relative tolerance.
bool absolute_fuzzy_equals(const T &var1, const T2 &var2, const Real tol=TOLERANCE *TOLERANCE)
Function to check whether two variables are equal within an absolute tolerance.
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
libmesh_assert(ctx)
std::ostream & operator<<(std::ostream &os, const OrderWrapper &order)
Overload stream operators.
Definition fe_type.h:182
OStreamProxy out
std::variant< PointConstraint, LineConstraint, PlaneConstraint, InvalidConstraint > ConstraintVariant
Type used to store a constraint that may be a PlaneConstraint, LineConstraint, or PointConstraint.
std::map< dof_id_type, Real, std::less< dof_id_type >, Threads::scalable_allocator< std::pair< const dof_id_type, Real > > > DofConstraintRow
A row of the Dof constraint matrix.
Definition dof_map.h:100
static constexpr Real TOLERANCE
uint8_t dof_id_type
Definition id_types.h:67
ConstraintVariant intersect_constraints(const ConstraintVariant &a, const ConstraintVariant &b)
Dispatch intersection between two constraint variants.
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
const boundary_id_type side_id