Wildmeshing Toolkit
shortest_edge_collapse.cpp
Go to the documentation of this file.
2 
3 #include <wmtk/Mesh.hpp>
4 #include <wmtk/Scheduler.hpp>
22 #include <wmtk/utils/Logger.hpp>
23 
24 
26 
28 {
29  if (mesh_in.top_simplex_type() != PrimitiveType::Edge &&
33  "shortest edge collapse works only for edge, triangle, or tet meshes: {}",
35  }
36 
37  if (!mesh_in.is_multi_mesh_root()) {
38  log_and_throw_error("The mesh passed in shortest_edge_collapse must be the root mesh");
39  }
40 
41  attribute::MeshAttributeHandle position_handle = options.position_handle;
42  std::vector<attribute::MeshAttributeHandle> other_position_handles =
43  options.other_position_handles;
44 
45  Mesh& mesh = position_handle.mesh();
46 
47  std::vector<attribute::MeshAttributeHandle> inversion_position_handles;
48  if (options.check_inversions) {
49  if (position_handle.mesh().top_cell_dimension() == position_handle.dimension()) {
50  logger().info("Adding inversion check on collapsing mesh.");
51  inversion_position_handles.emplace_back(position_handle);
52  }
53  for (auto& h : other_position_handles) {
54  if (h.mesh().top_cell_dimension() == h.dimension()) {
55  logger().info("Adding inversion check on other mesh.");
56  inversion_position_handles.emplace_back(h);
57  }
58  }
59 
60  if (inversion_position_handles.empty()) {
61  logger().warn("Shortest-edge collapse should check for inversions but there was no "
62  "position handle that is valid for inversion checks.");
63  }
64  }
65 
66  std::vector<attribute::MeshAttributeHandle> pass_through_attributes =
68 
69  for (auto& h : other_position_handles) {
70  pass_through_attributes.emplace_back(h);
71  }
72 
74 
75  auto visited_edge_flag =
76  mesh.register_attribute<char>("visited_edge", PrimitiveType::Edge, 1, false, char(1));
77 
78  auto update_flag_func = [](Eigen::Ref<const Eigen::MatrixXd> P) -> Eigen::VectorX<char> {
79  assert(P.cols() == 2);
80  assert(P.rows() == 2 || P.rows() == 3);
81  return Eigen::VectorX<char>::Constant(1, char(1));
82  };
83  auto tag_update =
84  std::make_shared<wmtk::operations::SingleAttributeTransferStrategy<char, double>>(
85  visited_edge_flag,
86  position_handle,
87  update_flag_func);
88 
90  // Storing edge lengths
91  auto edge_length_attribute =
92  mesh.register_attribute<double>("edge_length", PrimitiveType::Edge, 1);
93  auto edge_length_accessor = mesh.create_accessor(edge_length_attribute.as<double>());
94  // Edge length update
95  auto compute_edge_length = [](Eigen::Ref<const Eigen::MatrixXd> P) -> Eigen::VectorXd {
96  assert(P.cols() == 2);
97  assert(P.rows() == 2 || P.rows() == 3);
98  return Eigen::VectorXd::Constant(1, (P.col(0) - P.col(1)).norm());
99  };
100  auto edge_length_update =
101  std::make_shared<wmtk::operations::SingleAttributeTransferStrategy<double, double>>(
102  edge_length_attribute,
103  position_handle,
104  compute_edge_length);
105  edge_length_update->run_on_all();
106 
108  // computing bbox diagonal
109  Eigen::VectorXd bmin(position_handle.dimension());
110  bmin.setConstant(std::numeric_limits<double>::max());
111  Eigen::VectorXd bmax(position_handle.dimension());
112  bmax.setConstant(std::numeric_limits<double>::lowest());
113 
114  auto pt_accessor = mesh.create_const_accessor<double>(position_handle);
115 
116  const auto vertices = mesh.get_all(PrimitiveType::Vertex);
117  for (const auto& v : vertices) {
118  const auto p = pt_accessor.vector_attribute(v);
119  for (int64_t d = 0; d < bmax.size(); ++d) {
120  bmin[d] = std::min(bmin[d], p[d]);
121  bmax[d] = std::max(bmax[d], p[d]);
122  }
123  }
124 
125  const double bbdiag = (bmax - bmin).norm();
126 
127  const double length_abs = bbdiag * options.length_rel;
128 
129  wmtk::logger().info(
130  "bbox max {}, bbox min {}, diag {}, target edge length {}",
131  bmax,
132  bmin,
133  bbdiag,
134  length_abs);
135 
136  auto short_edges_first_priority = [&](const simplex::Simplex& s) {
137  assert(s.primitive_type() == PrimitiveType::Edge);
138  return edge_length_accessor.const_scalar_attribute(s.tuple());
139  };
140  pass_through_attributes.push_back(edge_length_attribute);
141  auto todo = std::make_shared<TodoSmallerInvariant>(
142  mesh,
143  edge_length_attribute.as<double>(),
144  4. / 5. * length_abs); // MTAO: why is this 4/5?
145 
147 
148  auto invariant_link_condition = std::make_shared<MultiMeshLinkConditionInvariant>(mesh);
149 
150  auto invariant_interior_edge = std::make_shared<invariants::InvariantCollection>(mesh);
151  auto invariant_interior_vertex = std::make_shared<invariants::InvariantCollection>(mesh);
152 
153  auto set_all_invariants = [&](auto&& m) {
154  invariant_interior_edge->add(
155  std::make_shared<invariants::InteriorSimplexInvariant>(m, PrimitiveType::Edge));
156  invariant_interior_vertex->add(
157  std::make_shared<invariants::InteriorSimplexInvariant>(m, PrimitiveType::Vertex));
158  };
159  wmtk::multimesh::MultiMeshVisitor visitor(set_all_invariants);
160  visitor.execute_from_root(mesh);
161 
162  auto invariant_mm_map = std::make_shared<MultiMeshMapValidInvariant>(mesh);
163 
165  std::vector<attribute::MeshAttributeHandle> position_handles;
166  position_handles.emplace_back(position_handle);
167 
169  // collapse
170  auto collapse = std::make_shared<wmtk::operations::EdgeCollapse>(mesh);
171  collapse->add_invariant(todo);
172  collapse->add_invariant(invariant_link_condition);
173  collapse->add_invariant(invariant_mm_map);
174 
175 
176  if (options.envelope_size) {
177  const double env_size = bbdiag * options.envelope_size.value();
178  bool envelope_added = false;
179 
180  if (position_handle.mesh().top_cell_dimension() < position_handle.dimension()) {
181  logger().info("Adding envelope check on collapsing mesh.");
182  collapse->add_invariant(std::make_shared<wmtk::invariants::EnvelopeInvariant>(
183  position_handle,
184  env_size,
185  position_handle));
186  envelope_added = true;
187  }
188 
189  for (auto& h : other_position_handles) {
190  if (h.mesh().top_cell_dimension() < h.dimension()) {
191  logger().info("Adding envelope check on other mesh.");
192  collapse->add_invariant(
193  std::make_shared<wmtk::invariants::EnvelopeInvariant>(h, env_size, h));
194  envelope_added = true;
195  }
196  }
197 
198  if (!envelope_added) {
199  logger().warn("Shortest-edge collapse should check for inversion but there was no "
200  "position handle that is valid for inversion checks.");
201  }
202  }
203 
204  for (auto& h : inversion_position_handles) {
205  collapse->add_invariant(
206  std::make_shared<SimplexInversionInvariant<double>>(h.mesh(), h.as<double>()));
207  }
208 
209  collapse->set_new_attribute_strategy(
210  visited_edge_flag,
212  collapse->add_transfer_strategy(tag_update);
213 
214  collapse->set_priority(short_edges_first_priority);
215  collapse->add_transfer_strategy(edge_length_update);
216 
217  if (options.lock_boundary) {
218  if (mesh_in.top_simplex_type() != PrimitiveType::Edge) {
219  collapse->add_invariant(invariant_interior_edge);
220  }
221  // set collapse towards boundary
222  for (const auto& pos_handle : position_handles) {
223  auto pos_collapse_strategy =
224  std::make_shared<wmtk::operations::CollapseNewAttributeStrategy<double>>(
225  pos_handle);
226  pos_collapse_strategy->set_strategy(wmtk::operations::CollapseBasicStrategy::Default);
227  pos_collapse_strategy->set_simplex_predicate(
229  collapse->set_new_attribute_strategy(pos_handle, pos_collapse_strategy);
230  }
231  } else {
232  for (const auto& pos_handle : position_handles) {
233  collapse->set_new_attribute_strategy(
234  pos_handle,
236  }
237  }
238 
239 
240  for (const auto& attr : pass_through_attributes) {
241  collapse->set_new_attribute_strategy(attr);
242  }
243 
244  auto propagate_position = [](const Eigen::MatrixXd& P) -> Eigen::VectorXd { return P; };
245  for (auto& h : other_position_handles) {
246  auto transfer_position =
247  std::make_shared<operations::SingleAttributeTransferStrategy<double, double>>(
248  h,
249  position_handle,
250  propagate_position);
251  collapse->add_transfer_strategy(transfer_position);
252  }
253 
254 
256  Scheduler scheduler;
257  SchedulerStats pass_stats =
258  scheduler.run_operation_on_all(*collapse, visited_edge_flag.as<char>());
259 
260 
261  // // debug code
262  // for (const auto& e : mesh.get_all(PrimitiveType::Edge)) {
263  // if (mesh.is_boundary(PrimitiveType::Edge, e)) {
264  // logger().error("after sec mesh has nonmanifold edges");
265  // }
266  // }
267 
269 
270  logger().info(
271  "Executed {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, executing: {}",
272  pass_stats.number_of_performed_operations(),
273  pass_stats.number_of_successful_operations(),
274  pass_stats.number_of_failed_operations(),
275  pass_stats.collecting_time,
276  pass_stats.sorting_time,
277  pass_stats.executing_time);
278 }
279 
281  Mesh& mesh,
282  const attribute::MeshAttributeHandle& position_handle,
283  const double length_rel,
284  std::optional<bool> lock_boundary,
285  std::optional<double> envelope_size,
286  bool check_inversion,
287  const std::vector<attribute::MeshAttributeHandle>& pass_through)
288 {
290  options.position_handle = position_handle;
291  options.length_rel = length_rel;
292  if (lock_boundary) {
293  options.lock_boundary = lock_boundary.value();
294  }
295  options.envelope_size = envelope_size;
296 
297  options.pass_through_attributes = pass_through;
298  shortest_edge_collapse(mesh, options);
299 }
300 } // namespace wmtk::components::shortest_edge_collapse
attribute::MeshAttributeHandle register_attribute(const std::string &name, PrimitiveType type, int64_t size, bool replace=false, T default_value=T(0))
const attribute::Accessor< T, Mesh, D > create_const_accessor(const attribute::MeshAttributeHandle &handle) const
std::vector< Tuple > get_all(PrimitiveType type) const
Generate a vector of Tuples from global vertex/edge/triangle/tetrahedron index.
Definition: Mesh.cpp:18
bool is_multi_mesh_root() const
return true if this mesh is the root of a multimesh tree
int64_t top_cell_dimension() const
Definition: Mesh.hpp:992
attribute::Accessor< T, Mesh, D > create_accessor(const attribute::MeshAttributeHandle &handle)
PrimitiveType top_simplex_type() const
Definition: Mesh.hpp:996
SchedulerStats run_operation_on_all(operations::Operation &op)
Definition: Scheduler.cpp:33
int64_t number_of_failed_operations() const
Returns the number of failed operations performed by the scheduler.
Definition: Scheduler.hpp:23
int64_t number_of_performed_operations() const
Returns the number of performed operations performed by the scheduler.
Definition: Scheduler.hpp:30
int64_t number_of_successful_operations() const
Returns the number of successful operations performed by the scheduler.
Definition: Scheduler.hpp:16
void shortest_edge_collapse(Mesh &mesh_in, const ShortestEdgeCollapseOptions &options)
Perform shortest-edge collapse on a triangular surface mesh.
void consolidate(Mesh &mesh)
Definition: consolidate.cpp:13
std::vector< Tuple > vertices(const Mesh &m, const Simplex &simplex)
std::string_view primitive_type_name(PrimitiveType t)
void log_and_throw_error(const std::string &msg)
Definition: Logger.cpp:101
spdlog::logger & logger()
Retrieves the current logger.
Definition: Logger.cpp:58
std::optional< double > envelope_size
The envelope size relative to the AABB.
std::vector< attribute::MeshAttributeHandle > other_position_handles
If this mesh is part of a multimesh, specify the vertex positions of all other meshes here,...
std::vector< attribute::MeshAttributeHandle > pass_through_attributes
Any other attribute goes here.
attribute::MeshAttributeHandle position_handle
vertex positions (double)
bool check_inversions
If this attribute is specified, it is used to check for inversions.