libMesh
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 
23 namespace libMesh
24 {
25 
26 // Helper function to orient a vector in the positive x/y/z direction
27 auto 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;
43  libmesh_assert(!absolute_fuzzy_equals(dot_prod, 0.));
44 
45  return (dot_prod > 0) ? vec.unit() : -vec.unit();
46 };
47 
48 PointConstraint::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 
79 LineConstraint::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 
86 bool LineConstraint::operator<(const LineConstraint & other) const
87 {
88  if (*this == other)
89  return false;
90 
92  return _direction < other.direction();
93  return (_direction * _point) < (other.direction() * other.point());
94 }
95 
96 bool LineConstraint::operator==(const LineConstraint & other) const
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 
112 {
114 }
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 
179 PlaneConstraint::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 
204 {
206 }
207 
208 bool 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 
311 std::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 
631 std::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 
716 std::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
bool operator<(const LineConstraint &other) const
Comparison operator for ordering LineConstraint objects.
const Point & normal() const
Const getter for the _normal attribute.
dof_id_type dof_number(const unsigned int s, const unsigned int var, const unsigned int comp) const
Definition: dof_object.h:1008
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.
A Node is like a Point, but with more information.
Definition: node.h:52
auto norm() const
Definition: type_vector.h:908
unsigned int get_node_index(const Node *node_ptr) const
Definition: elem.h:2551
bool contains_point(const PointConstraint &p) const
Query whether a point lies on the plane.
IntRange< unsigned short > side_index_range() const
Definition: elem.h:2724
static constexpr Real TOLERANCE
unsigned int dim
const boundary_id_type side_id
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
Real _tol
Tolerance to use for numerical comparisons.
std::ostream & operator<<(std::ostream &os, const OrderWrapper &order)
Overload stream operators.
Definition: fe_type.h:182
virtual bool is_node_on_side(const unsigned int n, const unsigned int s) const =0
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 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.
bool operator==(const PointConstraint &other) const
Equality operator.
ConstraintVariant intersect(const ConstraintVariant &other) const
Computes the intersection of this line with another constraint.
The libMesh namespace provides an interface to certain functionality in the library.
Point _point
A point on the constraining plane.
bool contains_line(const LineConstraint &l) const
Query whether a line lies on the plane.
const MeshBase & get_mesh() const
Definition: system.h:2401
Real distance(const Point &p)
This is the MeshBase class.
Definition: mesh_base.h:80
Point _normal
The direction normal to the constraining plane.
std::unordered_set< dof_id_type > find_boundary_nodes(const MeshBase &mesh)
Calling this function on a 2D mesh will convert all the elements to triangles.
Definition: mesh_tools.C:524
ConstraintVariant intersect_constraints(const ConstraintVariant &a, const ConstraintVariant &b)
Dispatch intersection between two constraint variants.
bool is_parallel(const LineConstraint &l) const
Query whether a line is parallel to this line.
bool operator<(const PlaneConstraint &other) const
Comparison operator for ordering PlaneConstraint objects.
TypeVector< T > unit() const
Definition: type_vector.h:1141
unsigned int number() const
Definition: system.h:2393
auto norm_sq() const
Definition: type_vector.h:928
Represents an invalid constraint (i.e., when the two constraints don&#39;t intersect) ...
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.
Point _point
A point on the constraining line.
Point _direction
Direction of the constraining line.
dof_id_type id() const
Definition: dof_object.h:819
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 ...
VariationalSmootherConstraint(System &sys, const bool &preserve_subdomain_boundaries, const unsigned int verbosity)
Constructor.
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 Point & direction() const
Const getter for the _direction attribute.
void constrain_node_to_line(const Node &node, const Point &line_vec)
Constrain a node to remain on the given line during mesh smoothing.
Manages consistently variables, degrees of freedom, and coefficient vectors.
Definition: system.h:98
const Point & point() const
Const getter for the _point attribute.
Represents a plane constraint defined by a point and normal vector.
virtual ~VariationalSmootherConstraint() override
bool operator<(const PointConstraint &other) const
Comparison operator for ordering PointConstraint objects.
Real _tol
Tolerance to use for numerical comparisons.
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
libmesh_assert(ctx)
const unsigned int _verbosity
Verbosity setting.
bool operator==(const LineConstraint &other) const
Equality operator.
const bool _preserve_subdomain_boundaries
Whether nodes on subdomain boundaries are subject to change via smoothing.
TypeVector< typename CompareTypes< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const
Definition: type_vector.h:885
bool absolute_fuzzy_equals(const TypeVector< T > &rhs, Real tol=TOLERANCE) const
Definition: type_vector.h:975
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.
Point _point
Location of constraint.
virtual void constrain() override
Constraint function.
ConstraintVariant intersect(const ConstraintVariant &other) const
Computes the intersection of this point with another constraint.
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2612
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.
Definition: fuzzy_equals.h:64
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
subdomain_id_type subdomain_id() const
Definition: elem.h:2588
auto norm(const T &a)
Definition: tensor_tools.h:74
const Point & point() const
Const getter for the _point attribute.
static const Real b
OStreamProxy out
Represents a fixed point constraint.
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
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
Real _tol
Tolerance to use for numerical comparisons.
unsigned int mesh_dimension() const
Definition: mesh_base.C:430
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 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)...
Represents a line constraint defined by a base point and direction vector.
const Point & point() const
Const getter for the _point attribute.
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.
Definition: fuzzy_equals.h:78
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...
Definition: mesh_tools.C:1092
std::variant< PointConstraint, LineConstraint, PlaneConstraint, InvalidConstraint > ConstraintVariant
Type used to store a constraint that may be a PlaneConstraint, LineConstraint, or PointConstraint...
bool is_parallel(const PlaneConstraint &p) const
Query whether a plane is parallel to this plane.
const DofMap & get_dof_map() const
Definition: system.h:2417
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
bool operator==(const PlaneConstraint &other) const
Equality operator.
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
void fix_node(const Node &node)
Constrain (i.e., fix) a node to not move during mesh smoothing.
bool contains_point(const PointConstraint &p) const
Query whether a point lies on the line.
uint8_t dof_id_type
Definition: id_types.h:67
ConstraintVariant intersect(const ConstraintVariant &other) const
Computes the intersection of this plane with another constraint.