Wildmeshing Toolkit
test_component_isotropic_remeshing.cpp
Go to the documentation of this file.
1 #include <catch2/catch_test_macros.hpp>
2 #include <nlohmann/json.hpp>
3 #include <tools/DEBUG_EdgeMesh.hpp>
4 #include <tools/DEBUG_TriMesh.hpp>
5 #include <tools/DEBUG_Tuple.hpp>
6 #include <tools/EdgeMesh_examples.hpp>
7 #include <tools/TriMesh_examples.hpp>
8 #include <wmtk/Scheduler.hpp>
9 #include <wmtk/Types.hpp>
11 #include <wmtk/components/isotropic_remeshing/internal/IsotropicRemeshing.hpp>
12 #include <wmtk/components/isotropic_remeshing/internal/IsotropicRemeshingOptions.hpp>
15 #include <wmtk/components/utils/Paths.hpp>
21 #include <wmtk/io/Cache.hpp>
34 #include <wmtk/simplex/link.hpp>
36 
37 using namespace wmtk::components::utils;
38 
40 using namespace wmtk;
41 using namespace wmtk::tests;
42 using namespace wmtk::simplex;
43 
44 const std::filesystem::path data_dir = WMTK_DATA_DIR;
45 
46 void print_tuple_map_iso(const DEBUG_TriMesh& parent, const DEBUG_MultiMeshManager& p_mul_manager)
47 {
48  int64_t child_id = 0;
49  for (auto& child_data : p_mul_manager.children()) {
50  PrimitiveType map_ptype = child_data.mesh->top_simplex_type();
51  auto parent_to_child_accessor =
52  parent.create_const_accessor<int64_t>(child_data.map_handle);
53  for (int64_t parent_gid = 0; parent_gid < parent.capacity(map_ptype); ++parent_gid) {
54  auto parent_to_child_data = parent_to_child_accessor.const_vector_attribute(
55  parent.tuple_from_id(map_ptype, parent_gid));
56  auto [parent_tuple, child_tuple] =
57  wmtk::multimesh::utils::vectors_to_tuples(parent_to_child_data);
58  }
59  }
60 }
61 
62 TEST_CASE("smoothing_mesh", "[components][isotropic_remeshing][2D]")
63 {
64  using namespace operations;
65 
66  wmtk::io::Cache cache("wmtk_cache", ".");
67 
68  // input
69  json input_component_json = {
70  {"type", "input"},
71  {"name", "input_mesh"},
72  {"cell_dimension", 2},
73  {"file", (data_dir / "bumpyDice.msh").string()},
74  {"ignore_z", false},
75  {"tetrahedron_attributes", json::array()}};
76  wmtk::components::input(Paths(), input_component_json, cache);
77 
78 
79  auto mesh_in = cache.read_mesh(input_component_json["name"]);
80 
81  TriMesh& m = static_cast<TriMesh&>(*mesh_in);
82 
84  m.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
85 
86  AttributesUpdateWithFunction op(m);
87  op.set_function(VertexLaplacianSmooth(pos_attribute));
88 
89  op.add_invariant(
90  std::make_shared<invariants::InteriorSimplexInvariant>(m, PrimitiveType::Vertex));
91 
92  Scheduler scheduler;
93 
94  for (int i = 0; i < 3; ++i) {
95  scheduler.run_operation_on_all(op);
96  }
97 
98  // output
99  {
100  ParaviewWriter writer(
101  cache.get_cache_path() / "mesh_smooth",
102  "vertices",
103  m,
104  false,
105  false,
106  true,
107  false);
108  m.serialize(writer);
109  }
110 }
111 
112 TEST_CASE("smoothing_simple_examples", "[components][isotropic_remeshing][2D]")
113 {
114  using namespace operations;
115  using namespace tri_mesh;
116 
117  SECTION("hex_plus_two")
118  {
119  DEBUG_TriMesh mesh = wmtk::tests::hex_plus_two_with_position();
120 
122  mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
123 
124  // offset interior vertex
125  auto pos = mesh.create_accessor<double>(pos_attribute);
126  Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
127  pos.vector_attribute(v4) = Eigen::Vector3d{0.6, 0.9, 0};
128 
129  AttributesUpdateWithFunction op(mesh);
130  op.set_function(VertexLaplacianSmooth(pos_attribute));
131  op.add_invariant(
132  std::make_shared<invariants::InteriorSimplexInvariant>(mesh, PrimitiveType::Vertex));
133 
134  Scheduler scheduler;
135  scheduler.run_operation_on_all(op);
136 
137  v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
138  Eigen::Vector3d after_smooth = pos.vector_attribute(v4);
139  CHECK((after_smooth - Eigen::Vector3d{1, 0, 0}).squaredNorm() == 0);
140  }
141 
142  SECTION("edge_region")
143  {
144  DEBUG_TriMesh mesh = wmtk::tests::edge_region_with_position();
145 
147  mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
148 
149  // offset interior vertex
150  auto pos = mesh.create_accessor<double>(pos_attribute);
151  Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
152  Tuple v5 = mesh.tuple_from_id(PrimitiveType::Vertex, 5);
153  pos.vector_attribute(v4) = Eigen::Vector3d{0.6, 0.9, 0};
154  pos.vector_attribute(v5) = Eigen::Vector3d{1.4, -0.9, 0};
155 
156  AttributesUpdateWithFunction op(mesh);
157  op.set_function(VertexLaplacianSmooth(pos_attribute));
158  op.add_invariant(
159  std::make_shared<invariants::InteriorSimplexInvariant>(mesh, PrimitiveType::Vertex));
160 
161  Scheduler scheduler;
162 
163  for (size_t i = 0; i < 10; ++i) {
164  scheduler.run_operation_on_all(op);
165  // v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
166  // /*Eigen::Vector3d p4_after_smooth =*/pos.vector_attribute(v4);
167  }
168 
169  v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
170  Eigen::Vector3d p4_after_smooth = pos.vector_attribute(v4);
171  CHECK((p4_after_smooth - Eigen::Vector3d{1, 0, 0}).squaredNorm() < 1e-10);
172 
173  v5 = mesh.tuple_from_id(PrimitiveType::Vertex, 5);
174  Eigen::Vector3d p5_after_smooth = pos.vector_attribute(v5);
175  CHECK((p5_after_smooth - Eigen::Vector3d{2, 0, 0}).squaredNorm() < 1e-10);
176  }
177 }
178 
179 TEST_CASE("tangential_smoothing", "[components][isotropic_remeshing][2D]")
180 {
181  using namespace operations;
182 
183  DEBUG_TriMesh mesh = wmtk::tests::hex_plus_two_with_position();
184 
186  mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
187 
188  // offset interior vertex
189  auto pos = mesh.create_accessor<double>(pos_attribute);
190  Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
191 
192  Eigen::Vector3d p_init;
193  SECTION("1_0_1")
194  {
195  p_init = Eigen::Vector3d{1, 0, 1};
196  }
197  SECTION("0.5_0.5_1")
198  {
199  p_init = Eigen::Vector3d{0.5, 0.5, 1};
200  }
201  SECTION("0_0_7")
202  {
203  p_init = Eigen::Vector3d{0, 0, 7};
204  }
205 
206  pos.vector_attribute(v4) = p_init;
207 
208  AttributesUpdateWithFunction op(mesh);
209  op.set_function(VertexTangentialLaplacianSmooth(pos_attribute));
210  op.add_invariant(
211  std::make_shared<invariants::InteriorSimplexInvariant>(mesh, PrimitiveType::Vertex));
212 
213  Scheduler scheduler;
214  scheduler.run_operation_on_all(op);
215 
216  v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
217  Eigen::Vector3d after_smooth = pos.vector_attribute(v4);
218  Eigen::Vector3d target = Eigen::Vector3d{1, 0, p_init[2]};
219  CHECK((after_smooth - target).squaredNorm() == 0);
220 }
221 
222 TEST_CASE("tangential_smoothing_boundary", "[components][isotropic_remeshing][2D]")
223 {
224  using namespace operations;
225  using namespace tri_mesh;
226 
227  DEBUG_TriMesh mesh = wmtk::tests::hex_plus_two_with_position();
228 
230  mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
231 
232  // offset interior vertex
233  auto pos = mesh.create_accessor<double>(pos_attribute);
234  Tuple v1 = mesh.tuple_from_id(PrimitiveType::Vertex, 1);
235 
236  Eigen::Vector3d p_init;
237  SECTION("1.7_1.1_0")
238  {
239  p_init = Eigen::Vector3d{1.7, 1.1, 0};
240  }
241  SECTION("2.2_2_0")
242  {
243  p_init = Eigen::Vector3d{2.2, 2, 0};
244  }
245  SECTION("2.2_2_5")
246  {
247  p_init = Eigen::Vector3d{2.2, 2, 5};
248  }
249 
250  pos.vector_attribute(v1) = p_init;
251 
252  AttributesUpdateWithFunction op(mesh);
253  op.set_function(VertexTangentialLaplacianSmooth(pos_attribute));
254 
255  const bool success = !op(Simplex::vertex(mesh, v1)).empty();
256  REQUIRE(success);
257 
258  v1 = mesh.tuple_from_id(PrimitiveType::Vertex, 1);
259  Eigen::Vector3d after_smooth = pos.vector_attribute(v1);
260  CHECK((after_smooth - Eigen::Vector3d{1.5, p_init[1], p_init[2]}).squaredNorm() == 0);
261 }
262 
263 TEST_CASE("split_long_edges", "[components][isotropic_remeshing][split][2D]")
264 {
265  using namespace operations;
266 
267  // This test does not fully work yet
268 
269  DEBUG_TriMesh mesh = wmtk::tests::edge_region_with_position();
270 
272  mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
273 
274  EdgeSplit op(mesh);
275  op.set_new_attribute_strategy(
276  pos_attribute,
277  SplitBasicStrategy::None,
278  SplitRibBasicStrategy::Mean);
279 
280  {
281  auto pos = mesh.create_accessor<double>(pos_attribute);
282  const Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
283  const Tuple v5 = mesh.tuple_from_id(PrimitiveType::Vertex, 5);
284  // reposition interior vertices
285  pos.vector_attribute(v4) = Eigen::Vector3d{0.6, 0.9, 0};
286  pos.vector_attribute(v5) = Eigen::Vector3d{2.4, -0.9, 0};
287  // << std::endl;
288  }
289 
290  double min_split_length_squared = -1;
291 
292  SECTION("6.4")
293  {
294  min_split_length_squared = 6.4;
295  op.add_invariant(std::make_shared<MinEdgeLengthInvariant>(
296  mesh,
297  pos_attribute.as<double>(),
298  min_split_length_squared));
299 
300  Scheduler scheduler;
301 
302  size_t n_vertices = mesh.get_all(PrimitiveType::Vertex).size();
303  size_t n_iterations = 0;
304  for (; n_iterations < 10; ++n_iterations) {
305  scheduler.run_operation_on_all(op);
306 
307  const size_t n_vertices_new = mesh.get_all(PrimitiveType::Vertex).size();
308  if (n_vertices_new == n_vertices) {
309  break;
310  } else {
311  n_vertices = n_vertices_new;
312  }
313  }
314 
315  CHECK(n_iterations == 1);
316  REQUIRE(n_vertices == 11);
317 
318  // check position of new vertex
319  auto pos = mesh.create_accessor<double>(pos_attribute);
320  const Tuple v10 = mesh.tuple_from_id(PrimitiveType::Vertex, 10);
321  CHECK((pos.vector_attribute(v10) - Eigen::Vector3d{1.5, 0, 0}).squaredNorm() == 0);
322  }
323  SECTION("3.5")
324  {
325  min_split_length_squared = 3.5;
326  op.add_invariant(std::make_shared<MinEdgeLengthInvariant>(
327  mesh,
328  pos_attribute.as<double>(),
329  min_split_length_squared));
330 
331  Scheduler scheduler;
332 
333  size_t n_vertices = mesh.get_all(PrimitiveType::Vertex).size();
334  size_t n_iterations = 0;
335  for (; n_iterations < 10; ++n_iterations) {
336  scheduler.run_operation_on_all(op);
337 
338  const size_t n_vertices_new = mesh.get_all(PrimitiveType::Vertex).size();
339  if (n_vertices_new == n_vertices) {
340  break;
341  } else {
342  n_vertices = n_vertices_new;
343  }
344  }
345 
346  CHECK(n_iterations < 5);
347  CHECK(n_vertices == 15);
348  }
349 
350  // check edge lengths
351  auto pos = mesh.create_accessor<double>(pos_attribute);
352  for (const Tuple& e : mesh.get_all(PrimitiveType::Edge)) {
353  const Eigen::Vector3d p0 = pos.vector_attribute(e);
354  const Eigen::Vector3d p1 = pos.vector_attribute(mesh.switch_vertex(e));
355  const double l_squared = (p1 - p0).squaredNorm();
356  CHECK(l_squared < min_split_length_squared);
357  }
358 }
359 
360 TEST_CASE("collapse_short_edges", "[components][isotropic_remeshing][collapse][2D]")
361 {
362  using namespace operations;
363 
364  DEBUG_TriMesh mesh = wmtk::tests::edge_region_with_position();
365 
366  auto pos_attribute = mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
367 
368 
369  EdgeCollapse op(mesh);
370  op.add_invariant(std::make_shared<MultiMeshLinkConditionInvariant>(mesh));
371  op.set_new_attribute_strategy(pos_attribute, CollapseBasicStrategy::Mean);
372 
373  SECTION("interior")
374  {
375  {
376  auto pos = mesh.create_accessor<double>(pos_attribute);
377  const Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
378  const Tuple v5 = mesh.tuple_from_id(PrimitiveType::Vertex, 5);
379  // reposition interior vertices
380  pos.vector_attribute(v4) = Eigen::Vector3d{1.4, 0, 0};
381  pos.vector_attribute(v5) = Eigen::Vector3d{1.6, 0, 0};
382  }
383 
384  op.add_invariant(
385  std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<double>(), 0.1));
386 
387  Scheduler scheduler;
388 
389  size_t n_vertices = mesh.get_all(PrimitiveType::Vertex).size();
390  size_t n_iterations = 0;
391  for (; n_iterations < 10; ++n_iterations) {
392  scheduler.run_operation_on_all(op);
393 
394  const size_t n_vertices_new = mesh.get_all(PrimitiveType::Vertex).size();
395  if (n_vertices_new == n_vertices) {
396  break;
397  } else {
398  n_vertices = n_vertices_new;
399  }
400  }
401 
402  REQUIRE(n_iterations == 1);
403  REQUIRE(n_vertices == 9);
404 
405  // CHECK_THROWS(mesh.tuple_from_id(PrimitiveType::Vertex, 4));
406  const Tuple v5 = mesh.tuple_from_id(PrimitiveType::Vertex, 5);
407 #if defined(WMTK_ENABLE_HASH_UPDATE)
408  REQUIRE(mesh.is_valid_with_hash(v5));
409 #else
410  REQUIRE(mesh.is_valid(v5));
411 #endif
412 
413  auto pos = mesh.create_accessor<double>(pos_attribute);
414  Eigen::Vector3d p5 = pos.vector_attribute(v5);
415  CHECK((p5 - Eigen::Vector3d{1.5, 0, 0}).squaredNorm() == 0);
416  }
417  SECTION("towards_boundary_true")
418  {
419  {
420  auto pos = mesh.create_accessor<double>(pos_attribute);
421  const Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
422  // reposition vertex
423  pos.vector_attribute(v4) = Eigen::Vector3d{0.6, 0.9, 0};
424  }
425 
426  // set collapse towards boundary
427  {
428  auto tmp = std::make_shared<CollapseNewAttributeStrategy<double>>(pos_attribute);
429  tmp->set_strategy(CollapseBasicStrategy::Mean);
430  tmp->set_simplex_predicate(BasicSimplexPredicate::IsInterior);
431  op.set_new_attribute_strategy(pos_attribute, tmp);
432  }
433 
434  op.add_invariant(
435  std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<double>(), 0.1));
436 
437  Scheduler scheduler;
438 
439  size_t n_vertices = mesh.get_all(PrimitiveType::Vertex).size();
440  size_t n_iterations = 0;
441  for (; n_iterations < 10; ++n_iterations) {
442  scheduler.run_operation_on_all(op);
443 
444  const size_t n_vertices_new = mesh.get_all(PrimitiveType::Vertex).size();
445  if (n_vertices_new == n_vertices) {
446  break;
447  } else {
448  n_vertices = n_vertices_new;
449  }
450  }
451 
452  REQUIRE(n_iterations == 1);
453  REQUIRE(n_vertices == 9);
454 
455  const Tuple v0 = mesh.tuple_from_id(PrimitiveType::Vertex, 0);
456 #if defined(WMTK_ENABLE_HASH_UPDATE)
457  REQUIRE(mesh.is_valid_with_hash(v0));
458 #else
459  REQUIRE(mesh.is_valid(v0));
460 #endif
461 
462  auto pos = mesh.create_accessor<double>(pos_attribute);
463  Eigen::Vector3d p0 = pos.vector_attribute(v0);
464  CHECK((p0 - Eigen::Vector3d{0.5, 1, 0}).squaredNorm() == 0);
465  }
466  SECTION("towards_boundary_false")
467  {
468  {
469  auto pos = mesh.create_accessor<double>(pos_attribute);
470  const Tuple v4 = mesh.tuple_from_id(PrimitiveType::Vertex, 4);
471  // reposition vertex
472  pos.vector_attribute(v4) = Eigen::Vector3d{0.6, 0.9, 0};
473  }
474 
475  op.add_invariant(
476  std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<double>(), 0.1));
477  // op_settings.collapse_towards_boundary = false; <-- invariant missing
478 
479 
480  Scheduler scheduler;
481 
482  size_t n_vertices = mesh.get_all(PrimitiveType::Vertex).size();
483  size_t n_iterations = 0;
484  for (; n_iterations < 10; ++n_iterations) {
485  scheduler.run_operation_on_all(op);
486 
487  const size_t n_vertices_new = mesh.get_all(PrimitiveType::Vertex).size();
488  if (n_vertices_new == n_vertices) {
489  break;
490  } else {
491  n_vertices = n_vertices_new;
492  }
493  }
494 
495  REQUIRE(n_iterations == 1);
496  REQUIRE(n_vertices == 9);
497 
498  const Tuple v0 = mesh.tuple_from_id(PrimitiveType::Vertex, 0);
499 #if defined(WMTK_ENABLE_HASH_UPDATE)
500  REQUIRE(mesh.is_valid_with_hash(v0));
501 #else
502  REQUIRE(mesh.is_valid(v0));
503 #endif
504 
505  auto pos = mesh.create_accessor<double>(pos_attribute);
506  Eigen::Vector3d p0 = pos.vector_attribute(v0);
507  CHECK((p0 - Eigen::Vector3d{0.55, 0.95, 0}).squaredNorm() == 0);
508  }
509  SECTION("collapse_boundary_true")
510  {
511  {
512  auto pos = mesh.create_accessor<double>(pos_attribute);
513  const Tuple v1 = mesh.tuple_from_id(PrimitiveType::Vertex, 1);
514  // reposition vertex
515  pos.vector_attribute(v1) = Eigen::Vector3d{0.6, 1, 0};
516  }
517 
518  op.add_invariant(
519  std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<double>(), 0.1));
520 
521  Scheduler scheduler;
522 
523  scheduler.run_operation_on_all(op);
524 
525  REQUIRE(mesh.get_all(PrimitiveType::Vertex).size() == 9);
526 
527  const Tuple v0 = mesh.tuple_from_id(PrimitiveType::Vertex, 0);
528 #if defined(WMTK_ENABLE_HASH_UPDATE)
529  REQUIRE(mesh.is_valid_with_hash(v0));
530 #else
531  REQUIRE(mesh.is_valid(v0));
532 #endif
533 
534  auto pos = mesh.create_accessor<double>(pos_attribute);
535  Eigen::Vector3d p0 = pos.vector_attribute(v0);
536  CHECK((p0 - Eigen::Vector3d{0.55, 1, 0}).squaredNorm() == 0);
537  }
538  SECTION("collapse_boundary_false")
539  {
540  {
541  auto pos = mesh.create_accessor<double>(pos_attribute);
542  const Tuple v1 = mesh.tuple_from_id(PrimitiveType::Vertex, 1);
543  // reposition vertex
544  pos.vector_attribute(v1) = Eigen::Vector3d{0.6, 1, 0};
545  }
546 
547  op.add_invariant(
548  std::make_shared<MaxEdgeLengthInvariant>(mesh, pos_attribute.as<double>(), 0.1));
549  op.add_invariant(
550  std::make_shared<invariants::InteriorSimplexInvariant>(mesh, PrimitiveType::Edge));
551 
552  Scheduler scheduler;
553 
554  scheduler.run_operation_on_all(op);
555 
556  REQUIRE(mesh.get_all(PrimitiveType::Vertex).size() == 10);
557  }
558 }
559 
560 TEST_CASE("swap_edge_for_valence", "[components][isotropic_remeshing][swap][2D]")
561 {
562  using namespace operations;
563 
564  DEBUG_TriMesh mesh = wmtk::tests::embedded_diamond();
565 
566  composite::TriEdgeSwap op(mesh);
567  op.add_invariant(
568  std::make_shared<invariants::InteriorSimplexInvariant>(mesh, PrimitiveType::Edge));
569  op.collapse().add_invariant(std::make_shared<MultiMeshLinkConditionInvariant>(mesh));
570 
571  Tuple swap_edge = mesh.edge_tuple_with_vs_and_t(6, 7, 5);
572 
573  auto vertex_one_ring = [](TriMesh& m, const Tuple& t) {
574  return simplex::link(m, simplex::Simplex::vertex(m, t))
576  };
577 
578  SECTION("single_op_fail")
579  {
580  op.add_invariant(std::make_shared<invariants::ValenceImprovementInvariant>(mesh));
581  CHECK(op(Simplex::edge(mesh, swap_edge)).empty());
582  }
583  SECTION("swap_success")
584  {
585  // swap edge to create inbalence in valence
586  {
587  const auto ret = op(Simplex::edge(mesh, swap_edge));
588  REQUIRE(!ret.empty());
589  const Tuple& return_tuple = ret[0].tuple();
590  swap_edge = return_tuple;
591  long id0 = mesh.id_vertex(swap_edge);
592  long id1 = mesh.id_vertex(mesh.switch_vertex(swap_edge));
593 
594  // check valence
595  const Tuple v3 = mesh.tuple_from_id(PrimitiveType::Vertex, 3);
596  const Tuple v6 = mesh.tuple_from_id(PrimitiveType::Vertex, 6);
597  const Tuple v7 = mesh.tuple_from_id(PrimitiveType::Vertex, 7);
598  const Tuple v10 = mesh.tuple_from_id(PrimitiveType::Vertex, 10);
599  CHECK(vertex_one_ring(mesh, v3).size() == 7);
600  CHECK(vertex_one_ring(mesh, v10).size() == 7);
601  CHECK(vertex_one_ring(mesh, v6).size() == 5);
602  CHECK(vertex_one_ring(mesh, v7).size() == 5);
603  }
604 
605  op.add_invariant(std::make_shared<invariants::ValenceImprovementInvariant>(mesh));
606 
607 
608  SECTION("single_op")
609  {
610  const auto ret = op(Simplex::edge(mesh, swap_edge));
611  REQUIRE(!ret.empty());
612  const Tuple& return_tuple = ret[0].tuple();
613  swap_edge = return_tuple;
614  CHECK(mesh.id(Simplex::vertex(mesh, swap_edge)) == 7);
615  CHECK(mesh.id(Simplex::vertex(mesh, mesh.switch_vertex(swap_edge))) == 6);
616  }
617  SECTION("with_scheduler")
618  {
619  Scheduler scheduler;
620  scheduler.run_operation_on_all(op);
621  }
622 
623 
624  // check valence
625  {
626  const Tuple v3 = mesh.tuple_from_id(PrimitiveType::Vertex, 3);
627  const Tuple v6 = mesh.tuple_from_id(PrimitiveType::Vertex, 6);
628  const Tuple v7 = mesh.tuple_from_id(PrimitiveType::Vertex, 7);
629  const Tuple v10 = mesh.tuple_from_id(PrimitiveType::Vertex, 10);
630  CHECK(vertex_one_ring(mesh, v3).size() == 6);
631  CHECK(vertex_one_ring(mesh, v10).size() == 6);
632  CHECK(vertex_one_ring(mesh, v6).size() == 6);
633  CHECK(vertex_one_ring(mesh, v7).size() == 6);
634  }
635  }
636  SECTION("swap_fail")
637  {
638  const Tuple e = mesh.edge_tuple_with_vs_and_t(6, 7, 5);
639  op.add_invariant(std::make_shared<invariants::ValenceImprovementInvariant>(mesh));
640  const bool success = !op(Simplex::edge(mesh, e)).empty();
641  CHECK(!success);
642  }
643 }
644 
645 TEST_CASE("component_isotropic_remeshing", "[components][isotropic_remeshing][2D]")
646 {
647  io::Cache cache("wmtk_cache", ".");
648 
649  if constexpr (true) {
650  const std::filesystem::path input_file = data_dir / "small.msh";
651  json input_component_json = {
652  {"name", "input_mesh"},
653  {"file", input_file.string()},
654  {"ignore_z", false},
655  {"tetrahedron_attributes", json::array()}};
656  REQUIRE_NOTHROW(wmtk::components::input(Paths(), input_component_json, cache));
657  }
658 
659  if constexpr (true) {
660  json mesh_isotropic_remeshing_json = R"({
661  "input": "input_mesh",
662  "output": "output_mesh",
663  "attributes": {"position": "vertices", "inversion_position": [], "other_positions": [], "update_other_positions": false},
664  "pass_through": [],
665  "iterations": 1,
666  "length_abs": 0.003,
667  "length_rel": -1,
668  "lock_boundary": true,
669  "use_for_periodic": false,
670  "dont_disable_split": false
671  })"_json;
672  REQUIRE_NOTHROW(
673  wmtk::components::isotropic_remeshing(Paths(), mesh_isotropic_remeshing_json, cache));
674  }
675  if constexpr (true) {
676  json component_json = R"({
677  "input": "output_mesh",
678  "attributes": {"position": "vertices"},
679  "file": "bunny_isotropic_remeshing"
680  })"_json;
681 
682  CHECK_NOTHROW(wmtk::components::output(Paths(), component_json, cache));
683 
684  // auto mesh_in = cache.read_mesh("output_mesh");
685  // TriMesh& m = static_cast<TriMesh&>(*mesh_in);
686  // ParaviewWriter writer(
687  // cache.get_cache_path() / "isotropic_remeshing_output",
688  // "vertices",
689  // m,
690  // false,
691  // false,
692  // true,
693  // false);
694  // m.serialize(writer);
695  }
696 }
697 
698 TEST_CASE("remeshing_tetrahedron", "[components][isotropic_remeshing][2D]")
699 {
700  using namespace wmtk::components::internal;
701 
702  io::Cache cache("wmtk_cache", ".");
703 
704  // input
705  DEBUG_TriMesh mesh = tetrahedron_with_position();
706 
707  auto pos_handle = mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
708  std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
709  pass_through_attributes.emplace_back(
710  mesh.register_attribute<int64_t>("dummy_tag", PrimitiveType::Triangle, 1));
711 
712  {
713  auto acc = mesh.create_accessor<int64_t>(pass_through_attributes[0]);
714  acc.scalar_attribute(mesh.face_tuple_from_vids(0, 1, 2)) = 1;
715  acc.scalar_attribute(mesh.face_tuple_from_vids(0, 1, 3)) = 1;
716  }
717 
718 
720  pos_handle,
721  pass_through_attributes,
722  0.1,
723  true,
724  false,
725  false,
726  false,
727  10));
728 
729  {
730  ParaviewWriter writer(
731  cache.get_cache_path() / "tet_remeshing",
732  "vertices",
733  mesh,
734  false,
735  false,
736  true,
737  false);
738  mesh.serialize(writer);
739  }
740 }
741 
742 TEST_CASE("remeshing_with_boundary", "[components][isotropic_remeshing][2D]")
743 {
744  using namespace wmtk::components::internal;
745 
746  io::Cache cache("wmtk_cache", ".");
747 
748  // input
749  TriMesh mesh = edge_region_with_position();
750  auto pos_handle = mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
751  std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
752 
753  SECTION("lock_boundary_false")
754  {
756  pos_handle,
757  pass_through_attributes,
758  0.5,
759  false,
760  false,
761  false,
762  false,
763  5);
764 
765  size_t n_boundary_edges = 0;
766  for (const Tuple& e : mesh.get_all(PrimitiveType::Edge)) {
767  if (mesh.is_boundary_edge(e)) {
768  ++n_boundary_edges;
769  }
770  }
771  CHECK(n_boundary_edges > 8);
772 
773  {
774  ParaviewWriter writer(
775  cache.get_cache_path() / "w_bd_remeshing_lock_false",
776  "vertices",
777  mesh,
778  false,
779  false,
780  true,
781  false);
782  mesh.serialize(writer);
783  }
784  }
785 
786  SECTION("lock_boundary_true")
787  {
789  pos_handle,
790  pass_through_attributes,
791  0.5,
792  true,
793  false,
794  false,
795  false,
796  5);
797 
798 
799  size_t n_boundary_edges = 0;
800  for (const Tuple& e : mesh.get_all(PrimitiveType::Edge)) {
801  if (mesh.is_boundary_edge(e)) {
802  ++n_boundary_edges;
803  }
804  }
805  CHECK(n_boundary_edges == 8);
806 
807  {
808  ParaviewWriter writer(
809  cache.get_cache_path() / "w_bd_remeshing_lock_true",
810  "vertices",
811  mesh,
812  false,
813  false,
814  true,
815  false);
816  mesh.serialize(writer);
817  }
818  }
819 }
820 
821 TEST_CASE("remeshing_preserve_topology", "[components][isotropic_remeshing][2D][.]")
822 {
823  using namespace wmtk::components::internal;
824 
825  // input
826  DEBUG_TriMesh mesh = edge_region_with_position();
827  // DEBUG_TriMesh mesh = hex_plus_two_with_position();
828 
829  auto pos_handle = mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
830  std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
831 
832  auto tag_handle = mesh.register_attribute<int64_t>("is_boundary", wmtk::PrimitiveType::Edge, 1);
833  auto tag_accessor = mesh.create_accessor<int64_t>(tag_handle);
834  for (const Tuple& e : mesh.get_all(PrimitiveType::Edge)) {
835  if (mesh.is_boundary_edge(e)) {
836  tag_accessor.scalar_attribute(e) = 1;
837  } else {
838  tag_accessor.scalar_attribute(e) = 0;
839  }
840  }
841  std::shared_ptr<Mesh> child_ptr =
843  mesh,
844  "is_boundary",
845  1,
847 
848  REQUIRE(mesh.get_child_meshes().size() == 1);
849  mesh.multi_mesh_manager().check_map_valid(mesh);
850  const auto& child_mesh = *child_ptr;
851  CHECK(child_mesh.get_all(PrimitiveType::Edge).size() == 8);
852  CHECK(child_mesh.get_all(PrimitiveType::Vertex).size() == 8);
853 
854 
856  pos_handle,
857  pass_through_attributes,
858  0.5,
859  /*lock_boundary*/ false,
860  true,
861  true,
862  false,
863  5);
864 
865  REQUIRE(mesh.is_connectivity_valid());
866  mesh.multi_mesh_manager().check_map_valid(mesh);
867 
868 
869  size_t n_boundary_edges = 0;
870  for (const Tuple& e : mesh.get_all(PrimitiveType::Edge)) {
871  if (mesh.is_boundary_edge(e)) {
872  ++n_boundary_edges;
873  }
874  }
875  // CHECK(n_boundary_edges > 8);
876 
877  // output
878  {
879  ParaviewWriter writer("remeshing_test", "vertices", mesh, true, true, true, false);
880  mesh.serialize(writer);
881  }
882 }
883 
884 TEST_CASE("remeshing_preserve_topology_realmesh", "[components][isotropic_remeshing][2D][.]")
885 {
886  using namespace wmtk::components::internal;
887  using namespace operations;
888 
889  wmtk::io::Cache cache("wmtk_cache", ".");
890 
891  // input
892  // TODO: What is the default attribute for "vertices". From the reader it seems to be
893  // "vertices". need change "vertices" to "vertices" isotropic_remeshing.hpp
894  json input_component_json = {
895  {"type", "input"},
896  {"name", "input_mesh"},
897  {"cell_dimension", 2},
898  {"file", (data_dir / "circle.msh").string()},
899  {"ignore_z", false}};
900  wmtk::components::input(Paths(), input_component_json, cache);
901 
902  auto m = cache.read_mesh(input_component_json["name"]);
903  tests::DEBUG_TriMesh& mesh = static_cast<tests::DEBUG_TriMesh&>(*m);
904 
905  auto pos_handle = mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
906  std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
907 
908  auto tag_handle = mesh.register_attribute<int64_t>("is_boundary", wmtk::PrimitiveType::Edge, 1);
909  auto tag_accessor = mesh.create_accessor<int64_t>(tag_handle);
910  for (const Tuple& e : mesh.get_all(PrimitiveType::Edge)) {
911  if (mesh.is_boundary_edge(e)) {
912  tag_accessor.scalar_attribute(e) = 1;
913  } else {
914  tag_accessor.scalar_attribute(e) = 0;
915  }
916  }
917  std::shared_ptr<Mesh> child_ptr =
919  mesh,
920  "is_boundary",
921  1,
923 
924  REQUIRE(mesh.get_child_meshes().size() == 1);
925  // mesh.multi_mesh_manager().check_map_valid(mesh);
926  // const auto& child_mesh = *child_ptr;
927 
928 
929  // IsotropicRemeshing isotropicRemeshing(mesh, 0.5, false, false, false);
930 
931  for (int i = 0; i < 25; i++) {
933  pos_handle,
934  pass_through_attributes,
935  0.05,
936  false,
937  true,
938  false,
939  false,
940  1);
941  REQUIRE(mesh.is_connectivity_valid());
942  mesh.multi_mesh_manager().check_map_valid(mesh);
943  }
944 
945 
946  auto child_vertex_handle =
947  child_ptr->register_attribute<double>("vertices", wmtk::PrimitiveType::Vertex, 3);
948  auto child_vertex_accessor = child_ptr->create_accessor<int64_t>(child_vertex_handle);
949 
950  auto parent_vertex_handle =
951  mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
952  auto parent_vertex_accessor = mesh.create_accessor<int64_t>(parent_vertex_handle);
953 
954  for (const auto& v : child_ptr->get_all(PrimitiveType::Vertex)) {
955  auto parent_v = child_ptr->map_to_root_tuple(Simplex(*child_ptr, PrimitiveType::Vertex, v));
956  child_vertex_accessor.vector_attribute(v) =
957  parent_vertex_accessor.vector_attribute(parent_v);
958  }
959 
960  // output
961  {
962  ParaviewWriter writer(
963  cache.get_cache_path() / "remeshing_test_circle_final",
964  "vertices",
965  mesh,
966  true,
967  true,
968  true,
969  false);
970  mesh.serialize(writer);
971 
972  ParaviewWriter writer2(
973  cache.get_cache_path() / "remeshing_test_circle_child_mesh_final",
974  "vertices",
975  *child_ptr,
976  true,
977  true,
978  false,
979  false);
980  child_ptr->serialize(writer2);
981  }
982 }
983 
984 TEST_CASE("remeshing_realmesh", "[components][isotropic_remeshing][2D][.]")
985 {
986  using namespace wmtk::components::internal;
987  using namespace operations;
988 
989  wmtk::io::Cache cache("wmtk_cache", ".");
990 
991  // input
992  json input_component_json = {
993  {"type", "input"},
994  {"name", "input_mesh"},
995  {"cell_dimension", 2},
996  {"file", (data_dir / "circle.msh").string()},
997  {"ignore_z", false}};
998  wmtk::components::input(Paths(), input_component_json, cache);
999 
1000  auto m = cache.read_mesh(input_component_json["name"]);
1001  TriMesh& mesh = static_cast<TriMesh&>(*m);
1002 
1003  auto pos_handle = mesh.get_attribute_handle<double>("vertices", PrimitiveType::Vertex);
1004  std::vector<attribute::MeshAttributeHandle> pass_through_attributes;
1005 
1006  // auto tag_handle = mesh.register_attribute<int64_t>("is_boundary", wmtk::PrimitiveType::Edge,
1007  // 1); auto tag_accessor = mesh.create_accessor<int64_t>(tag_handle); for (const Tuple& e :
1008  // mesh.get_all(PrimitiveType::Edge)) {
1009  // if (mesh.is_boundary_edge(e)) {
1010  // tag_accessor.scalar_attribute(e) = 1;
1011  // } else {
1012  // tag_accessor.scalar_attribute(e) = 0;
1013  // }
1014  // }
1015  // std::shared_ptr<Mesh> child_ptr =
1016  // wmtk::multimesh::utils::extract_and_register_child_mesh_from_tag(
1017  // mesh,
1018  // "is_boundary",
1019  // 1,
1020  // PrimitiveType::Edge);
1021 
1022  // REQUIRE(mesh.get_child_meshes().size() == 1);
1023  // mesh.multi_mesh_manager().check_map_valid(mesh);
1024  // const auto& child_mesh = *child_ptr;
1025 
1027  pos_handle,
1028  pass_through_attributes,
1029  0.5,
1030  false,
1031  true,
1032  false,
1033  false,
1034  25);
1035 
1036  REQUIRE(mesh.is_connectivity_valid());
1037  // mesh.multi_mesh_manager().check_map_valid(mesh);
1038 
1039  // output
1040  {
1041  ParaviewWriter writer(
1042  cache.get_cache_path() / "remeshing_test_circle_no_nultimesh",
1043  "vertices",
1044  mesh,
1045  true,
1046  true,
1047  true,
1048  false);
1049  mesh.serialize(writer);
1050  }
1051 }
void serialize(MeshWriter &writer, const Mesh *local_root=nullptr) const
Definition: Mesh.cpp:93
attribute::MeshAttributeHandle get_attribute_handle(const std::string &name, const PrimitiveType ptype) const
Definition: Mesh.hpp:918
std::vector< Tuple > get_all(PrimitiveType type) const
Generate a vector of Tuples from global vertex/edge/triangle/tetrahedron index.
Definition: Mesh.cpp:18
SchedulerStats run_operation_on_all(operations::Operation &op)
Definition: Scheduler.cpp:33
bool is_connectivity_valid() const final override
Definition: TriMesh.cpp:359
bool is_boundary_edge(const Tuple &tuple) const
Definition: TriMesh.cpp:72
auto as() const -> const held_handle_type< held_type_from_primitive< T >()> &
std::filesystem::path get_cache_path() const
Get the path of the cache folder.
Definition: Cache.cpp:146
std::shared_ptr< Mesh > read_mesh(const std::string &name) const
Load a mesh from cache.
Definition: Cache.cpp:171
const std::vector< Simplex > & simplex_vector() const
Return const reference to the simplex vector.
static Simplex edge(const Mesh &m, const Tuple &t)
Definition: Simplex.hpp:61
static Simplex vertex(const Mesh &m, const Tuple &t)
Definition: Simplex.hpp:56
std::shared_ptr< Mesh > input(const std::filesystem::path &file, const bool ignore_z_if_zero, const std::vector< std::string > &tetrahedron_attributes)
Definition: input.cpp:12
void isotropic_remeshing(const IsotropicRemeshingOptions &options)
std::shared_ptr< Mesh > extract_and_register_child_mesh_from_tag(Mesh &m, const std::string &tag, const int64_t tag_value, const PrimitiveType pt, bool child_is_free)
extract a child mesh based on the given tag and tag value, and register it to the input (parent) mesh
std::tuple< Tuple, Tuple > vectors_to_tuples(const Eigen::Ref< const TwoTupleVector > &v)
SimplexCollection link(const Mesh &mesh, const simplex::Simplex &simplex, const bool sort_and_clean)
Definition: link.cpp:84
Definition: Accessor.hpp:6
Vector< double, 3 > Vector3d
Definition: Types.hpp:39
void print_tuple_map_iso(const DEBUG_TriMesh &parent, const DEBUG_MultiMeshManager &p_mul_manager)
const std::filesystem::path data_dir
nlohmann::json json
TEST_CASE("smoothing_mesh", "[components][isotropic_remeshing][2D]")
nlohmann::json json
Definition: input.cpp:9