libMesh
disjoint_neighbor_test.C
Go to the documentation of this file.
1 #include <libmesh/dirichlet_boundaries.h>
2 #include <libmesh/dof_map.h>
3 #include <libmesh/elem.h>
4 #include <libmesh/equation_systems.h>
5 #include <libmesh/exodusII_io.h>
6 #include <libmesh/function_base.h>
7 #include <libmesh/linear_implicit_system.h>
8 #include <libmesh/mesh.h>
9 #include <libmesh/mesh_generation.h>
10 #include <libmesh/numeric_vector.h>
11 #include <libmesh/quadrature_gauss.h>
12 #include <libmesh/sparse_matrix.h>
13 #include <libmesh/wrapped_function.h>
14 
15 #include "test_comm.h"
16 #include "libmesh_cppunit.h"
17 
18 #include "libmesh/mesh_refinement.h"
19 #include "libmesh/periodic_boundaries.h"
20 #include "libmesh/periodic_boundary_base.h"
21 
22 using namespace libMesh;
23 
24 static const Real b = 1.0;
25 static const Real Cgap = 1.0;
26 
27 // Boundary IDs (counterclockwise): bottom=0, right=1, top=2, left=3
28 static const boundary_id_type left_id = 3;
29 static const boundary_id_type right_id = 1;
32 
39 
40 static const int Nx = 2, Ny = 2;
41 
42 Number
43 heat_exact (const Point & p,
44  const Parameters &,
45  const std::string &,
46  const std::string &)
47 {
48  return (p(0) < 0.5) ? p(0) : p(0) + b;
49 }
50 
52  const Parameters &,
53  const std::string &,
54  const std::string &)
55 {
56  return 0.0;
57 }
58 
60  const Parameters & params,
61  const std::string &,
62  const std::string &)
63 {
64  const Real b = params.get<Real>("b");
65  return 1.0 + b;
66 }
67 
68 // Assemble system with temperature jump interface conditions
70  const std::string & /*system_name*/)
71  {
72  const MeshBase &mesh = es.get_mesh();
73  LinearImplicitSystem &system = es.get_system<LinearImplicitSystem>("TempJump");
74  const DofMap &dof_map = system.get_dof_map();
75  FEType fe_type = dof_map.variable_type(0);
76 
77  // FE objects for volume and face integration
78  auto fe = FEBase::build(mesh.mesh_dimension(), fe_type);
79  auto fe_face_L = FEBase::build(mesh.mesh_dimension(), fe_type);
80  auto fe_face_R = FEBase::build(mesh.mesh_dimension(), fe_type);
81 
82  // Quadrature rules
84  QGauss qface(mesh.mesh_dimension() - 1, fe_type.default_quadrature_order());
85  fe->attach_quadrature_rule(&qrule);
86  fe_face_L->attach_quadrature_rule(&qface);
87  fe_face_R->attach_quadrature_rule(&qface);
88 
89  const auto &JxW_vol = fe->get_JxW();
90  const auto &dphi_vol = fe->get_dphi();
91  const auto &phi_L = fe_face_L->get_phi();
92  const auto &phi_R = fe_face_R->get_phi();
93  const auto &JxW_face = fe_face_L->get_JxW();
94 
97  std::vector<dof_id_type> dof_indices;
98 
99  const BoundaryInfo &boundary = mesh.get_boundary_info();
100  const double conductance = Cgap * b; // interface conductance
101 
102  for (const Elem *elem : mesh.active_local_element_ptr_range())
103  {
104  dof_map.dof_indices(elem, dof_indices);
105  const unsigned int n_dofs = dof_indices.size();
106 
107  Ke.resize(n_dofs, n_dofs);
108  Fe.resize(n_dofs);
109  Ke.zero();
110  Fe.zero();
111 
112  // --- Volume contribution ---
113  fe->reinit(elem);
114  for (unsigned int qp = 0; qp < qrule.n_points(); qp++)
115  for (unsigned int i = 0; i < n_dofs; i++)
116  for (unsigned int j = 0; j < n_dofs; j++)
117  Ke(i, j) += conductance * JxW_vol[qp] * (dphi_vol[i][qp] * dphi_vol[j][qp]);
118 
119  // --- Left-side interface ---
120  unsigned int side = boundary.side_with_boundary_id(elem, interface_left_id);
121  if (side != libMesh::invalid_uint)
122  {
123  fe_face_L->reinit(elem, side);
124  const Elem *neighbor = elem->neighbor_ptr(side);
125  libmesh_assert(neighbor);
126  unsigned int n_side = neighbor->which_neighbor_am_i(elem);
127  fe_face_R->reinit(neighbor, n_side);
128 
129  std::vector<dof_id_type> dofs_L, dofs_R;
130  dof_map.dof_indices(elem, dofs_L);
131  dof_map.dof_indices(neighbor, dofs_R);
132 
133  DenseMatrix<Number> Ke_ll(n_dofs, n_dofs);
134  DenseMatrix<Number> Ke_lr(n_dofs, dofs_R.size());
135 
136  for (unsigned int qp = 0; qp < qface.n_points(); qp++)
137  {
138  for (unsigned int i = 0; i < n_dofs; i++)
139  for (unsigned int j = 0; j < n_dofs; j++)
140  Ke_ll(i, j) += Cgap * phi_L[i][qp] * phi_L[j][qp] * JxW_face[qp];
141 
142  for (unsigned int i = 0; i < n_dofs; i++)
143  for (unsigned int j = 0; j < dofs_R.size(); j++)
144  Ke_lr(i, j) += -Cgap * phi_L[i][qp] * phi_R[j][qp] * JxW_face[qp];
145  }
146 
147  Ke += Ke_ll;
148  system.matrix->add_matrix(Ke_lr, dofs_L, dofs_R);
149  }
150 
151  // --- Right-side interface ---
152  side = boundary.side_with_boundary_id(elem, interface_right_id);
153  if (side != libMesh::invalid_uint)
154  {
155  fe_face_R->reinit(elem, side);
156  const Elem *neighbor = elem->neighbor_ptr(side);
157  libmesh_assert(neighbor);
158  unsigned int n_side = neighbor->which_neighbor_am_i(elem);
159  fe_face_L->reinit(neighbor, n_side);
160 
161  std::vector<dof_id_type> dofs_R, dofs_L;
162  dof_map.dof_indices(elem, dofs_R);
163  dof_map.dof_indices(neighbor, dofs_L);
164 
165  DenseMatrix<Number> Ke_rr(n_dofs, n_dofs);
166  DenseMatrix<Number> Ke_rl(n_dofs, dofs_L.size());
167 
168  for (unsigned int qp = 0; qp < qface.n_points(); qp++)
169  {
170  for (unsigned int i = 0; i < n_dofs; i++)
171  for (unsigned int j = 0; j < n_dofs; j++)
172  Ke_rr(i, j) += Cgap * phi_R[i][qp] * phi_R[j][qp] * JxW_face[qp];
173 
174  for (unsigned int i = 0; i < n_dofs; i++)
175  for (unsigned int j = 0; j < dofs_L.size(); j++)
176  Ke_rl(i, j) += -Cgap * phi_R[i][qp] * phi_L[j][qp] * JxW_face[qp];
177  }
178 
179  Ke += Ke_rr;
180  system.matrix->add_matrix(Ke_rl, dofs_R, dofs_L);
181  }
182 
183  // Apply constraints and add local contributions
184  dof_map.heterogenously_constrain_element_matrix_and_vector(Ke, Fe, dof_indices);
185  system.matrix->add_matrix(Ke, dof_indices);
186  system.rhs->add_vector(Fe, dof_indices);
187  }
188 
189  system.matrix->close();
190  system.rhs->close();
191  }
192 
193 class DisjointNeighborTest : public CppUnit::TestCase {
194 public:
195  LIBMESH_CPPUNIT_TEST_SUITE( DisjointNeighborTest );
196 #ifdef LIBMESH_ENABLE_PERIODIC
197 #if defined(LIBMESH_HAVE_SOLVER)
198  CPPUNIT_TEST( testTempJump );
199  CPPUNIT_TEST( testTempJumpRefine );
200 #endif
201 #if defined(LIBMESH_ENABLE_AMR) && defined(LIBMESH_ENABLE_EXCEPTIONS)
202  // // This test intentionally triggers find_neighbors() consistency check
203  // // failure after AMR refinement across disjoint interfaces.
204  // // Expected: libmesh_assert_valid_neighbors() fails.
205  CPPUNIT_TEST(testTempJumpLocalRefineFail);
206 #endif
207  CPPUNIT_TEST( testCloneEquality );
208  CPPUNIT_TEST( testStitchingDiscontinuousBoundaries );
209  CPPUNIT_TEST( testPreserveDisjointNeighborPairsAfterStitch );
210  CPPUNIT_TEST( testStitchCrossMesh );
211 #ifdef LIBMESH_ENABLE_EXCEPTIONS
212  CPPUNIT_TEST( testDisjointNeighborConflictError );
213 #endif // LIBMESH_ENABLE_EXCEPTIONS
214 #endif
215 
216  CPPUNIT_TEST_SUITE_END();
217 
218 private:
219 
221  {
222  LOG_UNIT_TEST;
223 
224  Mesh mesh(*TestCommWorld, 2);
225  build_two_disjoint_elems(mesh);
226 
227  // Check elem 0 (the left element)
228  if (const Elem * elem_left = mesh.query_elem_ptr(0))
229  {
230  // This processor owns elem 0, so we can safely check its neighbor
231  const Elem * elem_right_neighbor = elem_left->neighbor_ptr(1);
232 
233  // The neighbor relationship should have been set up by prepare_for_use()
234  libmesh_assert(elem_right_neighbor);
235 
236  // Verify the neighbor is indeed elem 1
237  LIBMESH_ASSERT_NUMBERS_EQUAL(elem_right_neighbor->id(), 1, 1e-15);
238  }
239 
240  // Check elem 1 (the right element)
241  if (const Elem * elem_right = mesh.query_elem_ptr(1))
242  {
243  // This processor owns elem 1, so we can safely check its neighbor
244  const Elem * elem_left_neighbor = elem_right->neighbor_ptr(3);
245 
246  // The neighbor relationship should be valid
247  libmesh_assert(elem_left_neighbor);
248 
249  // Verify the neighbor is indeed elem 0
250  LIBMESH_ASSERT_NUMBERS_EQUAL(elem_left_neighbor->id(), 0, 1e-15);
251  }
252 
253  EquationSystems es(mesh);
254  LinearImplicitSystem & sys =
255  es.add_system<LinearImplicitSystem>("TempJump");
256 
257  const unsigned int u_var = sys.add_variable("u", FEType(FIRST, LAGRANGE));
258 
259  std::set<boundary_id_type> left_bdy { left_id };
260  std::set<boundary_id_type> right_bdy { right_id };
261  std::vector<unsigned int> vars (1, u_var);
262 
263  Parameters params;
264  params.set<Real>("b") = b;
266  WrappedFunction<Number> right_val(sys, &right_solution_fn, &params);
267  DirichletBoundary bc_left (left_bdy, vars, left_val);
268  DirichletBoundary bc_right(right_bdy, vars, right_val);
269 
270  sys.get_dof_map().add_dirichlet_boundary(bc_left);
271  sys.get_dof_map().add_dirichlet_boundary(bc_right);
272 
274 
275  // Ensure the DofMap creates sparsity entries between neighboring elements.
276  // Without this, PETSc may report "New nonzero at (a,b) caused a malloc."
278 
279  es.init();
280  sys.solve();
281 
282  // ExodusII_IO(mesh).write_equation_systems("temperature_jump.e", es);
283 
284  for (Real x=0.; x<=1.; x+=0.05)
285  {
286  if (std::abs(x - 0.5) < 1e-12) continue; // skip interface
287  for (Real y=0.; y<=1.; y+=0.05)
288  {
289  Point p(x,y);
290  const Number exact = heat_exact(p,params,"","");
291  const Number approx = sys.point_value(0,p);
292  LIBMESH_ASSERT_NUMBERS_EQUAL(exact, approx, 1e-2);
293  }
294  }
295  }
296 
297 
299  {
300  LOG_UNIT_TEST;
301 
302  Mesh mesh(*TestCommWorld, 2);
303  build_two_disjoint_elems(mesh);
304 
305  std::unique_ptr<MeshBase> mesh_clone = mesh.clone();
306  CPPUNIT_ASSERT(*mesh_clone == mesh);
307  }
308 
310  {
311  LOG_UNIT_TEST;
312 
313  Mesh mesh(*TestCommWorld, 2);
314  build_two_disjoint_elems(mesh);
315 
316  CPPUNIT_ASSERT(mesh.parallel_n_nodes() == 8);
317 
318  mesh.stitch_surfaces(interface_left_id, interface_right_id, 1e-8 /*tolerance*/,
319  true/*clear_stitched_boundary_ids*/, false /*verbose*/,false/*use_binary_search*/,
320  false /*enforce_all_nodes_match_on_boundaries*/, false /*merge_boundary_nodes_all_or_nothing*/,
321  false /*prepare_after_stitching*/);
322 
323  // ExodusII_IO(mesh).write("stitched_disjoint_neighbor_boundary_pairs.e");
324  CPPUNIT_ASSERT(mesh.parallel_n_nodes() == 6);
325 
326  Mesh mesh2(*TestCommWorld, 2);
327  build_two_disjoint_elems(mesh2);
328 
329  CPPUNIT_ASSERT(mesh2.parallel_n_nodes() == 8);
330 
331  mesh2.stitch_surfaces(interface_left_id, interface_right_id, 1e-8 /*tolerance*/,
332  true/*clear_stitched_boundary_ids*/, false /*verbose*/,false/*use_binary_search*/,
333  false /*enforce_all_nodes_match_on_boundaries*/, false /*merge_boundary_nodes_all_or_nothing*/,
334  true /*prepare_after_stitching*/);
335 
336  CPPUNIT_ASSERT(mesh2.parallel_n_nodes() == 6);
337 
338  // ExodusII_IO(mesh2).write("stitched_disjoint_neighbor_boundary_pairs_2.e");
339  }
340 
342  {
343  LOG_UNIT_TEST;
344 
345  Mesh mesh(*TestCommWorld, 2);
346  build_split_mesh_with_interface(mesh, Nx, Ny);
347 
348  EquationSystems es(mesh);
349  LinearImplicitSystem & sys =
350  es.add_system<LinearImplicitSystem>("TempJump");
351 
352  const unsigned int u_var = sys.add_variable("u", FEType(FIRST, LAGRANGE));
353 
354  std::set<boundary_id_type> left_bdy { left_id };
355  std::set<boundary_id_type> right_bdy { right_id };
356  std::vector<unsigned int> vars (1, u_var);
357 
358  Parameters params;
359  params.set<Real>("b") = b;
361  WrappedFunction<Number> right_val(sys, &right_solution_fn, &params);
362  DirichletBoundary bc_left (left_bdy, vars, left_val);
363  DirichletBoundary bc_right(right_bdy, vars, right_val);
364 
365  sys.get_dof_map().add_dirichlet_boundary(bc_left);
366  sys.get_dof_map().add_dirichlet_boundary(bc_right);
367 
369 
370  // Ensure the DofMap creates sparsity entries between neighboring elements.
371  // Without this, PETSc may report "New nonzero at (a,b) caused a malloc."
373 
374  es.init();
375  sys.solve();
376 
377  // ExodusII_IO(mesh).write_equation_systems("temperature_jump_refine.e", es);
378 
379  for (Real x=0.; x<=1.; x+=0.05)
380  {
381  if (std::abs(x - 0.5) < 1e-12) continue; // skip interface
382  for (Real y=0.; y<=1.; y+=0.05)
383  {
384  Point p(x,y);
385  const Number exact = heat_exact(p,params,"","");
386  const Number approx = sys.point_value(0,p);
387  LIBMESH_ASSERT_NUMBERS_EQUAL(exact, approx, 1e-2);
388  }
389  }
390  }
391 
392 
393 #ifdef LIBMESH_ENABLE_EXCEPTIONS
395  {
396  LOG_UNIT_TEST;
397 
398  try
399  {
400  Mesh mesh(*TestCommWorld, 2);
401  build_split_mesh_with_interface(mesh, Nx, Ny, true);
402  }
403  catch (const std::exception &e)
404  {
405  CPPUNIT_ASSERT(std::string(e.what()).find("Mesh refinement is not yet implemented") != std::string::npos);
406  }
407  }
408 #endif // LIBMESH_ENABLE_EXCEPTIONS
409 
411  {
412  LOG_UNIT_TEST;
413  Mesh mesh(*TestCommWorld, 2);
414  build_four_disjoint_elems(mesh);
415  // ExodusII_IO(mesh).write("four_elem_one_row.e");
416  CPPUNIT_ASSERT(mesh.parallel_n_nodes() == 16);
417 
418  mesh.stitch_surfaces(interface2_left_id,
420  1e-8 /* tolerance */,
421  true /* clear_stitched_boundary_ids */,
422  false /* verbose */,
423  false /* use_binary_search */,
424  false /* enforce_all_nodes_match_on_boundaries */,
425  false /* merge_boundary_nodes_all_or_nothing */,
426  true /* prepare_after_stitching */);
427 
428  // ExodusII_IO(mesh).write("four_elem_one_row_after_stitching.e");
429  CPPUNIT_ASSERT(mesh.parallel_n_nodes() == 14);
430  const auto * disjoint_pairs = mesh.get_disjoint_neighbor_boundary_pairs();
431 
432  // make sure that both the interface1 and the interface3 pairing still exist afterward.
433  CPPUNIT_ASSERT(disjoint_pairs->size() == 4);
434  for (const auto & [bid, pb_ptr] : *disjoint_pairs)
435  {
436  const auto & pb = *pb_ptr;
437  const boundary_id_type a = pb.myboundary;
438  const boundary_id_type b = pb.pairedboundary;
439  if ((a == interface1_left_id && b == interface1_right_id) ||
443  continue;
444  else
445  CPPUNIT_FAIL("Disjoint neighbor boundary pair missing after stitching!");
446  }
447 
448  }
449 
451  {
452  LOG_UNIT_TEST;
453 
454  // Create a mesh with interface boundaries but without pairing
455  Mesh mesh_Y(*TestCommWorld, 2);
456  build_two_disjoint_elems(mesh_Y);
457 
458  // Create another mesh that defines the same pair properly
459  auto left_id_x = left_id + 10; // to avoid boundary ID conflict with mesh_Y
460  auto right_id_x = right_id + 10;
461  auto interface_left_id_x = interface_left_id + 10;
462  auto interface_right_id_x = interface_right_id + 10;
463 
464  Mesh mesh_X(*TestCommWorld, 2);
465  const auto translate_vec = Point(1.0, 0.0, 0.0);
466  // Left subdomain nodes
467  mesh_X.add_point(Point(0.0, 0.0) + translate_vec, 0); // bottom-left corner
468  mesh_X.add_point(Point(0.5, 0.0) + translate_vec, 1); // bottom-right corner of left element (interface node)
469  mesh_X.add_point(Point(0.0, 1.0) + translate_vec, 2); // top-left corner
470  mesh_X.add_point(Point(0.5, 1.0) + translate_vec, 3); // top-right corner of left element (interface node)
471 
472  // Right subdomain nodes (duplicated interface points)
473  mesh_X.add_point(Point(0.5, 0.0) + translate_vec, 4); // bottom-left corner of right element (same coords as node 1) (interface node)
474  mesh_X.add_point(Point(1.0, 0.0) + translate_vec, 5); // bottom-right corner
475  mesh_X.add_point(Point(0.5, 1.0) + translate_vec, 6); // top-left corner of right element (same coords as node 3) (interface node)
476  mesh_X.add_point(Point(1.0, 1.0) + translate_vec, 7); // top-right corner
477 
478 
479  // ---- Define elements ----
480  BoundaryInfo & boundary = mesh_X.get_boundary_info();
481  // Left element (element ID = 0)
482  {
483  Elem * elem = mesh_X.add_elem(Elem::build_with_id(QUAD4, 0));
484  elem->set_node(0, mesh_X.node_ptr(0)); // bottom-left (0,0)
485  elem->set_node(1, mesh_X.node_ptr(1)); // bottom-right (0.5,0)
486  elem->set_node(2, mesh_X.node_ptr(3)); // top-right (0.5,1)
487  elem->set_node(3, mesh_X.node_ptr(2)); // top-left (0,1)
488  boundary.add_side(elem, 3, left_id_x); // left boundary
489  boundary.add_side(elem, 1, interface_left_id_x);
490  boundary.sideset_name(left_id_x) = "left_boundary_x";
491  boundary.sideset_name(interface_left_id_x) = "interface_left_x";
492  }
493 
494  // Right element (element ID = 1)
495  {
496  Elem * elem = mesh_X.add_elem(Elem::build_with_id(QUAD4, 1));
497  elem->set_node(0, mesh_X.node_ptr(4)); // bottom-left (0.5,0)_R
498  elem->set_node(1, mesh_X.node_ptr(5)); // bottom-right (1,0)
499  elem->set_node(2, mesh_X.node_ptr(7)); // top-right (1,1)
500  elem->set_node(3, mesh_X.node_ptr(6)); // top-left (0.5,1)_R
501  boundary.add_side(elem, 1, right_id_x); // right boundary
502  boundary.add_side(elem, 3, interface_right_id_x);
503  boundary.sideset_name(right_id_x) = "right_boundary_x";
504  boundary.sideset_name(interface_right_id_x) = "interface_right_x";
505  }
506 
507  mesh_X.add_disjoint_neighbor_boundary_pairs(interface_left_id_x, interface_right_id_x, RealVectorValue(0.0, 0.0, 0.0));
508 
509  // libMesh shouldn't renumber, or our based-on-initial-id
510  // assertions later may fail.
511  mesh_X.allow_renumbering(false);
512 
513  mesh_X.prepare_for_use();
514 
515  // Stitching should trigger an error since Y already has the same
516  // boundary IDs but not registered as a pair
517  mesh_Y.stitch_meshes(mesh_X,
518  right_id, left_id_x,
519  1e-8,
520  true, // clear_stitched_boundary_ids
521  false, // verbose
522  false, // use_binary_search
523  false, // enforce_all_nodes_match_on_boundaries
524  false, // merge_boundary_nodes_all_or_nothing
525  false); // prepare_after_stitching
526 
527  CPPUNIT_ASSERT(mesh_Y.parallel_n_nodes() == 14);
528  const auto * disjoint_pairs = mesh_Y.get_disjoint_neighbor_boundary_pairs();
529 
530  // ExodusII_IO(mesh_Y).write("mesh_Y_after_stitching.e");
531  // make sure that both the interface and the interface_x pairing still exist afterward.
532  CPPUNIT_ASSERT(disjoint_pairs->size() == 4);
533  for (const auto & [bid, pb_ptr] : *disjoint_pairs)
534  {
535  const auto & pb = *pb_ptr;
536  const boundary_id_type a = pb.myboundary;
537  const boundary_id_type b = pb.pairedboundary;
538  if ((a == interface_left_id_x && b == interface_right_id_x) ||
539  (a == interface_right_id_x && b == interface_left_id_x) ||
540  (a == interface_left_id && b == interface_right_id) ||
542  continue;
543  else
544  CPPUNIT_FAIL("Disjoint neighbor boundary pair missing after stitching!");
545  }
546 
547 
548  }
549 
550 
551 #ifdef LIBMESH_ENABLE_EXCEPTIONS
553  {
554  LOG_UNIT_TEST;
555 
556  try
557  {
558  Mesh mesh_Y(*TestCommWorld, 2);
559  build_two_disjoint_elems(mesh_Y, false);
561 
562  // Create another mesh that defines the same pair properly
563  auto right_id_x = right_id + 10;
564  auto interface_left_id_x = interface_left_id + 10;
565  auto interface_right_id_x = interface_right_id + 10;
566 
567  Mesh mesh_X(*TestCommWorld, 2);
568  const auto translate_vec = Point(1.0, 0.0, 0.0);
569  // Left subdomain nodes
570  mesh_X.add_point(Point(0.0, 0.0) + translate_vec, 0); // bottom-left corner
571  mesh_X.add_point(Point(0.5, 0.0) + translate_vec, 1); // bottom-right corner of left element (interface node)
572  mesh_X.add_point(Point(0.0, 1.0) + translate_vec, 2); // top-left corner
573  mesh_X.add_point(Point(0.5, 1.0) + translate_vec, 3); // top-right corner of left element (interface node)
574 
575  // Right subdomain nodes (duplicated interface points)
576  mesh_X.add_point(Point(0.5, 0.0) + translate_vec, 4); // bottom-left corner of right element (same coords as node 1) (interface node)
577  mesh_X.add_point(Point(1.0, 0.0) + translate_vec, 5); // bottom-right corner
578  mesh_X.add_point(Point(0.5, 1.0) + translate_vec, 6); // top-left corner of right element (same coords as node 3) (interface node)
579  mesh_X.add_point(Point(1.0, 1.0) + translate_vec, 7); // top-right corner
580 
581 
582  // ---- Define elements ----
583  BoundaryInfo & boundary = mesh_X.get_boundary_info();
584  // Left element (element ID = 0)
585  {
586  Elem * elem = mesh_X.add_elem(Elem::build_with_id(QUAD4, 0));
587  elem->set_node(0, mesh_X.node_ptr(0)); // bottom-left (0,0)
588  elem->set_node(1, mesh_X.node_ptr(1)); // bottom-right (0.5,0)
589  elem->set_node(2, mesh_X.node_ptr(3)); // top-right (0.5,1)
590  elem->set_node(3, mesh_X.node_ptr(2)); // top-left (0,1)
591  boundary.add_side(elem, 3, right_id); // left boundary -> use the same ID as mesh_Y to trigger conflict
592  boundary.add_side(elem, 1, interface_left_id_x);
593  boundary.sideset_name(right_id) = "left_boundary_x";
594  boundary.sideset_name(interface_left_id_x) = "interface_left_x";
595  }
596 
597  // Right element (element ID = 1)
598  {
599  Elem * elem = mesh_X.add_elem(Elem::build_with_id(QUAD4, 1));
600  elem->set_node(0, mesh_X.node_ptr(4)); // bottom-left (0.5,0)_R
601  elem->set_node(1, mesh_X.node_ptr(5)); // bottom-right (1,0)
602  elem->set_node(2, mesh_X.node_ptr(7)); // top-right (1,1)
603  elem->set_node(3, mesh_X.node_ptr(6)); // top-left (0.5,1)_R
604  boundary.add_side(elem, 1, right_id_x); // right boundary
605  boundary.add_side(elem, 3, interface_right_id_x);
606  boundary.sideset_name(right_id_x) = "right_boundary_x";
607  boundary.sideset_name(interface_right_id_x) = "interface_right_x";
608  }
609 
610  mesh_X.add_disjoint_neighbor_boundary_pairs(right_id, right_id_x, RealVectorValue(1.0, 0.0, 0.0));
611 
612  // libMesh shouldn't renumber, or our based-on-initial-id
613  // assertions later may fail.
614  mesh_X.allow_renumbering(false);
615 
616  mesh_X.prepare_for_use();
617 
618  // Stitching should trigger an error since Y already has the same
619  // boundary IDs but not registered as a pair
620  mesh_Y.stitch_meshes(mesh_X,
621  right_id, right_id_x,
622  1e-8,
623  true, // clear_stitched_boundary_ids
624  false, // verbose
625  false, // use_binary_search
626  false, // enforce_all_nodes_match_on_boundaries
627  false, // merge_boundary_nodes_all_or_nothing
628  false); // prepare_after_stitching
629  }
630  catch (const std::exception &e)
631  {
632  CPPUNIT_ASSERT(std::string(e.what()).find("Disjoint neighbor boundary pairing mismatch") != std::string::npos);
633  }
634  }
635 #endif // LIBMESH_ENABLE_EXCEPTIONS
636 
637 
638 
639  void build_two_disjoint_elems(Mesh &mesh, bool add_pair = true, const Point &translate_vec = Point(0.0, 0.0, 0.0))
640  {
641  // Domain: x in (0, 1), y in (0, 1)
642  // Split into two subdomains:
643  // Left subdomain: 0 <= x <= 0.5
644  // Right subdomain: 0.5 <= x <= 1
645  //
646  // Note: Points at x = 0.5 are duplicated (same coordinates but different node IDs)
647  // to represent an interface or discontinuity between the two subdomains.
648  //
649  // Coordinates layout:
650  //
651  // (0,1) (0.5,1)_L (0.5,1)_R (1,1)
652  // x--------x x--------x
653  // | | | |
654  // | Left | Interface | Right |
655  // | | | |
656  // x--------x x--------x
657  // (0,0) (0.5,0)_L (0.5,0)_R (1,0)
658 
659 
660  // ---- Define points ----
661 
662  // Left subdomain nodes
663  mesh.add_point(Point(0.0, 0.0) + translate_vec, 0); // bottom-left corner
664  mesh.add_point(Point(0.5, 0.0) + translate_vec, 1); // bottom-right corner of left element (interface node)
665  mesh.add_point(Point(0.0, 1.0) + translate_vec, 2); // top-left corner
666  mesh.add_point(Point(0.5, 1.0) + translate_vec, 3); // top-right corner of left element (interface node)
667 
668  // Right subdomain nodes (duplicated interface points)
669  mesh.add_point(Point(0.5, 0.0) + translate_vec, 4); // bottom-left corner of right element (same coords as node 1) (interface node)
670  mesh.add_point(Point(1.0, 0.0) + translate_vec, 5); // bottom-right corner
671  mesh.add_point(Point(0.5, 1.0) + translate_vec, 6); // top-left corner of right element (same coords as node 3) (interface node)
672  mesh.add_point(Point(1.0, 1.0) + translate_vec, 7); // top-right corner
673 
674 
675  // ---- Define elements ----
676  BoundaryInfo & boundary = mesh.get_boundary_info();
677  // Left element (element ID = 0)
678  {
680  elem->set_node(0, mesh.node_ptr(0)); // bottom-left (0,0)
681  elem->set_node(1, mesh.node_ptr(1)); // bottom-right (0.5,0)
682  elem->set_node(2, mesh.node_ptr(3)); // top-right (0.5,1)
683  elem->set_node(3, mesh.node_ptr(2)); // top-left (0,1)
684  boundary.add_side(elem, 3, left_id); // left boundary
685  boundary.add_side(elem, 1, interface_left_id);
686  boundary.sideset_name(left_id) = "left_boundary";
687  boundary.sideset_name(interface_left_id) = "interface_left";
688  }
689 
690  // Right element (element ID = 1)
691  {
693  elem->set_node(0, mesh.node_ptr(4)); // bottom-left (0.5,0)_R
694  elem->set_node(1, mesh.node_ptr(5)); // bottom-right (1,0)
695  elem->set_node(2, mesh.node_ptr(7)); // top-right (1,1)
696  elem->set_node(3, mesh.node_ptr(6)); // top-left (0.5,1)_R
697  boundary.add_side(elem, 1, right_id); // right boundary
698  boundary.add_side(elem, 3, interface_right_id);
699  boundary.sideset_name(right_id) = "right_boundary";
700  boundary.sideset_name(interface_right_id) = "interface_right";
701  }
702 
703  // This is the key testing step: inform libMesh about the disjoint neighbor boundary pairs
704  // And, in `prepare_for_use()`, libMesh will set up the disjoint neighbor relationships.
705  if (add_pair)
707 
708  // libMesh shouldn't renumber, or our based-on-initial-id
709  // assertions later may fail.
710  mesh.allow_renumbering(false);
711 
713  }
714 
715  // The interface is located at x = 0.5; nx must be even (split evenly into left and right subdomains)
716  void build_split_mesh_with_interface(Mesh &mesh, unsigned nx, unsigned ny, bool test_local_refinement = false)
717  {
718  // Ensure nx is even so the interface aligns with element boundaries
719  libmesh_error_msg_if(nx % 2 != 0, "nx must be even!");
720 
721  mesh.clear();
723 
724  const unsigned nxL = nx / 2; // Number of elements in x-direction (left half)
725  const unsigned nxR = nx / 2; // Number of elements in x-direction (right half)
726  const double dx = 1.0 / static_cast<double>(nx);
727  const double dy = 1.0 / static_cast<double>(ny);
728 
729  // --- Generate points for the left subdomain [0, 0.5] ---
730  // Each row of the left subdomain has (nxL + 1) nodes.
731  // Total nodes = (nxL + 1) * (ny + 1).
732  auto nidL = [&](unsigned i, unsigned j) {
733  return static_cast<dof_id_type>(j * (nxL + 1) + i);
734  };
735 
736  for (unsigned j = 0; j <= ny; ++j)
737  {
738  const double y = j * dy;
739  for (unsigned i = 0; i <= nxL; ++i)
740  {
741  const double x = i * dx; // At i = nxL, x = 0.5 (interface)
742  mesh.add_point(Point(x, y), nidL(i, j));
743  }
744  }
745 
746  // --- Generate points for the right subdomain [0.5, 1.0] ---
747  // Interface nodes at x = 0.5 are duplicated with new node IDs.
748  const dof_id_type baseR = static_cast<dof_id_type>((nxL + 1) * (ny + 1));
749 
750  auto nidR = [&](unsigned i, unsigned j) {
751  return static_cast<dof_id_type>(baseR + j * (nxR + 1) + i);
752  };
753 
754  for (unsigned j = 0; j <= ny; ++j)
755  {
756  const double y = j * dy;
757  for (unsigned i = 0; i <= nxR; ++i)
758  {
759  const double x = 0.5 + i * dx; // At i = 0, x = 0.5 (same coords as left interface, different ID)
760  mesh.add_point(Point(x, y), nidR(i, j));
761  }
762  }
763 
764  BoundaryInfo &boundary = mesh.get_boundary_info();
765 
766  // --- Create left subdomain elements (QUAD4) ---
767  // Node order: 0 = BL, 1 = BR, 2 = TR, 3 = TL
768  // Side order: 0 = bottom(0-1), 1 = right(1-2), 2 = top(2-3), 3 = left(3-0)
769  dof_id_type eid = 0;
770  for (unsigned j = 0; j < ny; ++j)
771  {
772  for (unsigned i = 0; i < nxL; ++i)
773  {
775  e->set_node(0, mesh.node_ptr(nidL(i, j)));
776  e->set_node(1, mesh.node_ptr(nidL(i+1, j)));
777  e->set_node(2, mesh.node_ptr(nidL(i+1, j+1)));
778  e->set_node(3, mesh.node_ptr(nidL(i, j+1)));
779 
780  // Left outer boundary (i == 0 -> side 3)
781  if (i == 0)
782  boundary.add_side(e, 3, left_id);
783 
784  // Interface (left side) boundary (i == nxL - 1 -> side 1)
785  if (i == nxL - 1)
786  boundary.add_side(e, 1, interface_left_id);
787  }
788  }
789 
790  // --- Create right subdomain elements (QUAD4) ---
791  for (unsigned j = 0; j < ny; ++j)
792  {
793  for (unsigned i = 0; i < nxR; ++i)
794  {
796  e->set_node(0, mesh.node_ptr(nidR(i, j)));
797  e->set_node(1, mesh.node_ptr(nidR(i+1, j)));
798  e->set_node(2, mesh.node_ptr(nidR(i+1, j+1)));
799  e->set_node(3, mesh.node_ptr(nidR(i, j+1)));
800 
801  // Interface (right side) boundary (i == 0 -> side 3)
802  if (i == 0)
803  boundary.add_side(e, 3, interface_right_id);
804 
805  // Right outer boundary (i == nxR - 1 -> side 1)
806  if (i == nxR - 1)
807  boundary.add_side(e, 1, right_id);
808  }
809  }
810 
811  // --- Assign human-readable boundary names ---
812  boundary.sideset_name(left_id) = "left_boundary";
813  boundary.sideset_name(right_id) = "right_boundary";
814  boundary.sideset_name(interface_left_id) = "interface_left";
815  boundary.sideset_name(interface_right_id) = "interface_right";
816 
817 
818  // This is the key testing step: inform libMesh about the disjoint neighbor boundary pairs
819  // And, in `prepare_for_use()`, libMesh will set up the disjoint neighbor relationships.
821 
823 
824 #if LIBMESH_ENABLE_AMR
825  if (test_local_refinement)
826  {
827  // set refinement flags
828  for (auto & elem : mesh.element_ptr_range())
829  {
830  const Real xmid = elem->vertex_average()(0);
831  if ((xmid < 0.5 && xmid > 0.5 - dx) ||
832  (xmid > 0.5 && xmid < 0.5 + dx))
833  elem->set_refinement_flag(Elem::REFINE);
834  }
835 
836  // refine
838  }
839 #endif
840  }
841 
843 {
844  // Clear any existing data and initialize the mesh
845  mesh.clear();
847 
848  BoundaryInfo &boundary = mesh.get_boundary_info();
849 
850  // --------------------------------------------------------------------
851  // Domain layout:
852  // 4 elements aligned in a single row, each of width = 0.25
853  //
854  // y=1: x0---x1_L---x1_R---x2_L---x2_R---x3_L---x3_R---x4
855  //
856  // y=0: x0---x1_L---x1_R---x2_L---x2_R---x3_L---x3_R---x4
857  //
858  // The interfaces between adjacent elements are duplicated:
859  // for each internal interface, left and right sides have distinct nodes
860  // (geometrically coincident but topologically disconnected).
861  // --------------------------------------------------------------------
862  const Real dx = 0.25;
863 
864  // ---- Add nodal coordinates ----
865  // Each interface has duplicated nodes (left and right) to represent discontinuity.
866  unsigned id = 0;
867  for (unsigned i = 0; i < 4; ++i)
868  {
869  Real xL = i * dx;
870  Real xR = (i + 1) * dx;
871 
872  // Left nodes of the element
873  mesh.add_point(Point(xL, 0.0), id++); // bottom-left
874  mesh.add_point(Point(xL, 1.0), id++); // top-left
875 
876  // Right nodes (duplicated interface nodes)
877  mesh.add_point(Point(xR, 0.0), id++); // bottom-right
878  mesh.add_point(Point(xR, 1.0), id++); // top-right
879  }
880 
881  // Add one final pair of rightmost boundary nodes
882  mesh.add_point(Point(1.0, 0.0), id++);
883  mesh.add_point(Point(1.0, 1.0), id++);
884 
885  // ---- Create four QUAD4 elements ----
886  // Node ordering: 0 = BL, 1 = BR, 2 = TR, 3 = TL
887  unsigned elem_id = 0;
888  for (unsigned i = 0; i < 4; ++i)
889  {
890  Elem *elem = mesh.add_elem(Elem::build_with_id(QUAD4, elem_id++));
891 
892  unsigned base = i * 4;
893  elem->set_node(0, mesh.node_ptr(base)); // bottom-left
894  elem->set_node(1, mesh.node_ptr(base + 2)); // bottom-right
895  elem->set_node(2, mesh.node_ptr(base + 3)); // top-right
896  elem->set_node(3, mesh.node_ptr(base + 1)); // top-left
897 
898  // Add outer boundaries
899  if (i == 0)
900  boundary.add_side(elem, 3, left_id); // left outer boundary
901  if (i == 3)
902  boundary.add_side(elem, 1, right_id); // right outer boundary
903  }
904 
905  // ---- Define internal interface sides ----
906  // Element side 1 (right) connects to the next element’s side 3 (left)
907  // Each interface is treated as a disjoint (non-conforming) boundary pair
908  boundary.add_side(mesh.elem_ptr(0), 1, interface1_left_id);
909  boundary.add_side(mesh.elem_ptr(1), 3, interface1_right_id);
910 
911  boundary.add_side(mesh.elem_ptr(1), 1, interface2_left_id);
912  boundary.add_side(mesh.elem_ptr(2), 3, interface2_right_id);
913 
914  boundary.add_side(mesh.elem_ptr(2), 1, interface3_left_id);
915  boundary.add_side(mesh.elem_ptr(3), 3, interface3_right_id);
916 
917  // ---- Register disjoint neighbor boundary pairs ----
918  // These pairs inform libMesh that the specified sides are geometrically
919  // adjacent but topologically disconnected.
923 
924  // ---- Assign descriptive names to boundary IDs ----
925  boundary.sideset_name(left_id) = "left_boundary";
926  boundary.sideset_name(right_id) = "right_boundary";
927 
928  boundary.sideset_name(interface1_left_id) = "interface1_left";
929  boundary.sideset_name(interface1_right_id) = "interface1_right";
930  boundary.sideset_name(interface2_left_id) = "interface2_left";
931  boundary.sideset_name(interface2_right_id) = "interface2_right";
932  boundary.sideset_name(interface3_left_id) = "interface3_left";
933  boundary.sideset_name(interface3_right_id) = "interface3_right";
934 
935  // ---- Finalize mesh setup ----
936  mesh.allow_renumbering(false);
938 }
939 
940 };
941 
static const boundary_id_type interface1_left_id
static const boundary_id_type right_id
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition: fe_type.h:196
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) override final
functions for adding /deleting nodes elements.
void build_two_disjoint_elems(Mesh &mesh, bool add_pair=true, const Point &translate_vec=Point(0.0, 0.0, 0.0))
Wrap a libMesh-style function pointer into a FunctionBase object.
CPPUNIT_TEST_SUITE_REGISTRATION(DisjointNeighborTest)
virtual dof_id_type parallel_n_nodes() const override
This is the EquationSystems class.
virtual Elem * add_elem(Elem *e) override final
Add elem e to the end of the element array.
virtual Node *& set_node(const unsigned int i)
Definition: elem.h:2564
Number right_solution_fn(const Point &, const Parameters &params, const std::string &, const std::string &)
virtual void zero() override final
Set every element in the vector to 0.
Definition: dense_vector.h:420
const unsigned int invalid_uint
A number which is used quite often to represent an invalid or uninitialized value for an unsigned int...
Definition: libmesh.h:303
This class provides the ability to map between arbitrary, user-defined strings and several data types...
Definition: parameters.h:74
void allow_renumbering(bool allow)
If false is passed in then this mesh will no longer be renumbered when being prepared for use...
Definition: mesh_base.h:1345
static const boundary_id_type interface1_right_id
VectorValue< Real > RealVectorValue
Useful typedefs to allow transparent switching between Real and Complex data types.
virtual void zero() override final
Sets all elements of the matrix to 0 and resets any decomposition flag which may have been previously...
Definition: dense_matrix.h:911
libMesh::Parallel::Communicator * TestCommWorld
Definition: driver.C:218
static const boundary_id_type interface3_right_id
void set_implicit_neighbor_dofs(bool implicit_neighbor_dofs)
Allow the implicit_neighbor_dofs flag to be set programmatically.
Definition: dof_map.C:1891
Number left_solution_fn(const Point &, const Parameters &, const std::string &, const std::string &)
bool refine_elements()
Only refines the user-requested elements.
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
static const boundary_id_type interface_right_id
void resize(const unsigned int n)
Resize the vector.
Definition: dense_vector.h:396
virtual void add_vector(const T *v, const std::vector< numeric_index_type > &dof_indices)
Computes , where v is a pointer and each dof_indices[i] specifies where to add value v[i]...
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
unsigned int side_with_boundary_id(const Elem *const elem, const boundary_id_type boundary_id) const
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
void testPreserveDisjointNeighborPairsAfterStitch()
Number point_value(unsigned int var, const Point &p, const bool insist_on_success=true, const NumericVector< Number > *sol=nullptr) const
Definition: system.C:2219
NumericVector< Number > * rhs
The system matrix.
This class allows one to associate Dirichlet boundary values with a given set of mesh boundary ids an...
Order default_quadrature_order() const
Definition: fe_type.h:415
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
virtual void solve() override
Assembles & solves the linear system A*x=b.
const T_sys & get_system(std::string_view name) const
virtual std::unique_ptr< MeshBase > clone() const =0
Virtual "copy constructor".
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.
This is the MeshBase class.
Definition: mesh_base.h:80
Number heat_exact(const Point &p, const Parameters &, const std::string &, const std::string &)
This class handles the numbering of degrees of freedom on a mesh.
Definition: dof_map.h:179
void build_split_mesh_with_interface(Mesh &mesh, unsigned nx, unsigned ny, bool test_local_refinement=false)
static const boundary_id_type interface3_left_id
virtual void add_matrix(const DenseMatrix< T > &dm, const std::vector< numeric_index_type > &rows, const std::vector< numeric_index_type > &cols)=0
Add the full matrix dm to the SparseMatrix.
void add_disjoint_neighbor_boundary_pairs(const boundary_id_type b1, const boundary_id_type b2, const RealVectorValue &translation)
Register a pair of boundaries as disjoint neighbor boundary pairs.
Definition: mesh_base.C:2232
const T & get(std::string_view) const
Definition: parameters.h:451
Implements (adaptive) mesh refinement algorithms for a MeshBase.
int8_t boundary_id_type
Definition: id_types.h:51
void build_four_disjoint_elems(Mesh &mesh)
dof_id_type id() const
Definition: dof_object.h:819
unsigned int which_neighbor_am_i(const Elem *e) const
This function tells you which neighbor e is.
Definition: elem.h:2933
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
PeriodicBoundaries * get_disjoint_neighbor_boundary_pairs()
Definition: mesh_base.C:2256
static const Real Cgap
static const boundary_id_type interface2_left_id
static std::unique_ptr< FEGenericBase > build(const unsigned int dim, const FEType &type)
Builds a specific finite element type.
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
libmesh_assert(ctx)
static const boundary_id_type interface2_right_id
unsigned int add_variable(std::string_view var, const FEType &type, const std::set< subdomain_id_type > *const active_subdomains=nullptr)
Adds the variable var to the list of variables for this system.
Definition: system.C:1344
static const boundary_id_type interface_left_id
void set_mesh_dimension(unsigned char d)
Resets the logical dimension of the mesh.
Definition: mesh_base.h:413
virtual dof_id_type parallel_n_nodes() const =0
void attach_assemble_function(void fptr(EquationSystems &es, const std::string &name))
Register a user function to use in assembling the system matrix and RHS.
Definition: system.C:1959
virtual void clear()
Deletes all the element and node data that is currently stored.
Definition: mesh_base.C:1029
virtual void close()=0
Calls the NumericVector&#39;s internal assembly routines, ensuring that the values are consistent across ...
std::string & sideset_name(boundary_id_type id)
void assemble_temperature_jump(EquationSystems &es, const std::string &)
virtual const Elem * elem_ptr(const dof_id_type i) const =0
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2612
virtual void close()=0
Calls the SparseMatrix&#39;s internal assembly routines, ensuring that the values are consistent across p...
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual const Elem * query_elem_ptr(const dof_id_type i) const =0
T & set(const std::string &)
Definition: parameters.h:494
static const Real b
SparseMatrix< Number > * matrix
The system matrix.
static const int Ny
static std::unique_ptr< Elem > build_with_id(const ElemType type, dof_id_type id)
Calls the build() method above with a nullptr parent, and additionally sets the newly-created Elem&#39;s ...
Definition: elem.C:556
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
Add side side of element number elem with boundary id id to the boundary information data structure...
const MeshBase & get_mesh() const
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Definition: dense_matrix.h:895
This class implements specific orders of Gauss quadrature.
unsigned int mesh_dimension() const
Definition: mesh_base.C:430
void add_dirichlet_boundary(const DirichletBoundary &dirichlet_boundary)
Adds a copy of the specified Dirichlet boundary to the system.
virtual void init()
Initialize all the systems.
virtual System & add_system(std::string_view system_type, std::string_view name)
Add the system of type system_type named name to the systems array.
virtual const Node * node_ptr(const dof_id_type i) const =0
The Mesh class is a thin wrapper, around the ReplicatedMesh class by default.
Definition: mesh.h:50
const DofMap & get_dof_map() const
Definition: system.h:2417
static const boundary_id_type left_id
static const int Nx
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
std::size_t stitch_meshes(const MeshBase &other_mesh, boundary_id_type this_mesh_boundary, boundary_id_type other_mesh_boundary, Real tol=TOLERANCE, bool clear_stitched_boundary_ids=false, bool verbose=true, bool use_binary_search=true, bool enforce_all_nodes_match_on_boundaries=false, bool merge_boundary_nodes_all_or_nothing=false, bool remap_subdomain_ids=false, bool prepare_after_stitching=true)
Stitch other_mesh to this mesh so that this mesh is the union of the two meshes.
uint8_t dof_id_type
Definition: id_types.h:67
virtual const Node * node_ptr(const dof_id_type i) const override final