From 5e63f4d790042ea47acd35f3598ee2daa9bd2ab7 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 31 Jan 2025 18:02:45 -0500 Subject: [PATCH 01/47] Prepare for SubMesh. --- src/wmtk/Mesh.hpp | 2 +- src/wmtk/SubMesh.hpp | 52 ++++++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 1 + tests/submesh/CMakeLists.txt | 5 ++++ tests/submesh/submesh.cpp | 22 +++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/wmtk/SubMesh.hpp create mode 100644 tests/submesh/CMakeLists.txt create mode 100644 tests/submesh/submesh.cpp diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index cfbef455ef..dccc412764 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -358,7 +358,7 @@ class Mesh : public std::enable_shared_from_this, public wmtk::utils::Merk public: /** * @brief switch the orientation of the Tuple of the given dimension - * @note this is not doen in place. Return a new Tuple of the switched state + * @note this is not done in place. Return a new Tuple of the switched state * * @param m * @param type d-0 -> switch vertex diff --git a/src/wmtk/SubMesh.hpp b/src/wmtk/SubMesh.hpp new file mode 100644 index 0000000000..035b25229e --- /dev/null +++ b/src/wmtk/SubMesh.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "Mesh.hpp" + + +namespace wmtk { + +/** + * Mesh m; + * SubMesh s = m.add_sub_mesh(); + * s.add_simplex(IdSimplex); + * s.add_from_tag(tag_handle, tag_value); + */ +class SubMesh +{ +public: + SubMesh() = delete; + SubMesh(const SubMesh&) = delete; + SubMesh& operator=(const SubMesh&) = delete; + SubMesh(SubMesh&&) = delete; + SubMesh& operator=(SubMesh&&) = delete; + + // throws if `type` is larger than the substructure max dimension of tuple + Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const; + + // call open_star_single_dimension if `type` is larger or equal than max dim, use `switch_tuple` + // otherwise + std::vector switch_tuple_vector(const Tuple& tuple, PrimitiveType type) const; + + // 1. get max dim in open star + // 2. check if any incident max dim facet has less than two neighbors + bool is_boundary(PrimitiveType pt, const Tuple& tuple) const; + + // This is going to be some ugly recursive stuff I guess... + bool is_manifold(PrimitiveType pt, const Tuple& tuple) const; + + // Check if sub mesh contains the simplex + bool contains(PrimitiveType pt, const Tuple& tuple) const; + + // must be part of the construction + void initialize( + const std::map>& tag_attributes, + const int64_t tag_value); + + // forward to Mesh + int64_t id(const Tuple& tuple, PrimitiveType type) const; + +private: + Mesh& m_mesh; +}; + +} // namespace wmtk diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cb5320a681..f6747df540 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -104,6 +104,7 @@ add_subdirectory_with_source_group(multimesh) add_subdirectory_with_source_group(operations) add_subdirectory_with_source_group(attributes) add_subdirectory_with_source_group(autogen) +add_subdirectory_with_source_group(submesh) diff --git a/tests/submesh/CMakeLists.txt b/tests/submesh/CMakeLists.txt new file mode 100644 index 0000000000..6c2701d86c --- /dev/null +++ b/tests/submesh/CMakeLists.txt @@ -0,0 +1,5 @@ +# Sources +set(TEST_SOURCES + submesh.cpp +) +target_sources(wmtk_tests PRIVATE ${TEST_SOURCES}) diff --git a/tests/submesh/submesh.cpp b/tests/submesh/submesh.cpp new file mode 100644 index 0000000000..80e06ac09a --- /dev/null +++ b/tests/submesh/submesh.cpp @@ -0,0 +1,22 @@ +#include + +#include +#include +#include + +#include "tools/DEBUG_TriMesh.hpp" +#include "tools/TriMesh_examples.hpp" + +#include "tools/DEBUG_EdgeMesh.hpp" + +using namespace wmtk; + +constexpr PrimitiveType PV = PrimitiveType::Vertex; +constexpr PrimitiveType PE = PrimitiveType::Edge; +constexpr PrimitiveType PF = PrimitiveType::Triangle; +constexpr PrimitiveType PT = PrimitiveType::Tetrahedron; + +TEST_CASE("sub_mesh", "[mesh][sub_mesh]") +{ + // basic test for implementing +} From a14266c30dd6ae6fc2d609cfd8e8a34b61b7c826 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Sun, 2 Feb 2025 17:48:30 -0500 Subject: [PATCH 02/47] Add Embedding and start working on SubMesh registration. --- src/wmtk/CMakeLists.txt | 1 + src/wmtk/submesh/CMakeLists.txt | 7 ++++ src/wmtk/submesh/Embedding.cpp | 62 ++++++++++++++++++++++++++++++ src/wmtk/submesh/Embedding.hpp | 39 +++++++++++++++++++ src/wmtk/submesh/SubMesh.cpp | 18 +++++++++ src/wmtk/{ => submesh}/SubMesh.hpp | 26 ++++++++----- tests/submesh/submesh.cpp | 2 +- 7 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 src/wmtk/submesh/CMakeLists.txt create mode 100644 src/wmtk/submesh/Embedding.cpp create mode 100644 src/wmtk/submesh/Embedding.hpp create mode 100644 src/wmtk/submesh/SubMesh.cpp rename src/wmtk/{ => submesh}/SubMesh.hpp (64%) diff --git a/src/wmtk/CMakeLists.txt b/src/wmtk/CMakeLists.txt index 74643cefbb..2d82f81986 100644 --- a/src/wmtk/CMakeLists.txt +++ b/src/wmtk/CMakeLists.txt @@ -47,3 +47,4 @@ add_subdirectory(multimesh) # add_subdirectory(function) +add_subdirectory(submesh) \ No newline at end of file diff --git a/src/wmtk/submesh/CMakeLists.txt b/src/wmtk/submesh/CMakeLists.txt new file mode 100644 index 0000000000..7b2c985922 --- /dev/null +++ b/src/wmtk/submesh/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SRC_FILES + Embedding.hpp + Embedding.cpp + SubMesh.hpp + SubMesh.cpp +) +target_sources(wildmeshing_toolkit PRIVATE ${SRC_FILES}) \ No newline at end of file diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp new file mode 100644 index 0000000000..789be2c1b8 --- /dev/null +++ b/src/wmtk/submesh/Embedding.cpp @@ -0,0 +1,62 @@ +#include "Embedding.hpp" + +#include +#include + +#include "SubMesh.hpp" + +namespace wmtk::submesh { + +Embedding::Embedding(const std::shared_ptr& mesh) + : m_mesh(mesh) +{ + m_tag_attribute_name[PrimitiveType::Vertex] = "WMTK_submesh_tag_v"; + m_tag_attribute_name[PrimitiveType::Edge] = "WMTK_submesh_tag_e"; + m_tag_attribute_name[PrimitiveType::Triangle] = "WMTK_submesh_tag_f"; + m_tag_attribute_name[PrimitiveType::Tetrahedron] = "WMTK_submesh_tag_t"; + + Mesh& m = *m_mesh; + + // register tag attributes + for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { + if (m.has_attribute(m_tag_attribute_name[pt], pt)) { + log_and_throw_error( + "Cannot create embedding. Mesh already has an attribute with name {}", + m_tag_attribute_name[pt]); + } + + m_tag_handle[pt] = m.register_attribute_typed(m_tag_attribute_name[pt], pt, 1); + } +} + +std::shared_ptr Embedding::add_submesh() +{ + if (m_submesh_counter == 63) { + log_and_throw_error("An embedding can only hold up to 63 submeshes"); + } + + std::shared_ptr sub = std::make_shared(*this, m_submesh_counter); + m_submeshes.emplace_back(sub); + + ++m_submesh_counter; + + return sub; +} + +Mesh& Embedding::mesh() +{ + return *m_mesh; +} + +attribute::TypedAttributeHandle& Embedding::tag_handle(const PrimitiveType pt) +{ + return m_tag_handle.at(pt); +} + +attribute::Accessor Embedding::tag_accessor(const PrimitiveType pt) +{ + return m_mesh->create_accessor(m_tag_handle.at(pt)); +} + + +} // namespace wmtk::submesh diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp new file mode 100644 index 0000000000..0f1a038ddc --- /dev/null +++ b/src/wmtk/submesh/Embedding.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace wmtk::submesh { + +class SubMesh; + +/** + * The embedding is a wrapper for the embedding mesh for the submeshes. It contains a pointer to the + * mesh, the attribute for the submesh tags, and a factory for SubMeshes. + */ +class Embedding +{ +public: + Embedding(const std::shared_ptr& mesh); + + std::shared_ptr add_submesh(); + + Mesh& mesh(); + + attribute::TypedAttributeHandle& tag_handle(const PrimitiveType pt); + attribute::Accessor tag_accessor(const PrimitiveType pt); + +private: + std::shared_ptr m_mesh; + + std::map m_tag_attribute_name; + std::map> m_tag_handle; + + std::vector> m_submeshes; + int64_t m_submesh_counter = 0; +}; + +} // namespace wmtk::submesh \ No newline at end of file diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp new file mode 100644 index 0000000000..fb7811e400 --- /dev/null +++ b/src/wmtk/submesh/SubMesh.cpp @@ -0,0 +1,18 @@ +#include "SubMesh.hpp" + +#include "Embedding.hpp" + +namespace wmtk::submesh { + +SubMesh::SubMesh(Embedding& embedding, int64_t submesh_id) + : m_embedding(embedding) + , m_submesh_id(submesh_id) +{} + +void SubMesh::add_simplex(const Tuple& tuple, PrimitiveType pt) +{ + auto acc = m_embedding.tag_accessor(pt); + acc.scalar_attribute(tuple) |= (int64_t)1 << m_submesh_id; +} + +} // namespace wmtk::submesh diff --git a/src/wmtk/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp similarity index 64% rename from src/wmtk/SubMesh.hpp rename to src/wmtk/submesh/SubMesh.hpp index 035b25229e..545dec87ae 100644 --- a/src/wmtk/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -1,12 +1,16 @@ #pragma once -#include "Mesh.hpp" +#include +#include +#include +#include +namespace wmtk::submesh { -namespace wmtk { +class Embedding; /** - * Mesh m; + * Embedding m; * SubMesh s = m.add_sub_mesh(); * s.add_simplex(IdSimplex); * s.add_from_tag(tag_handle, tag_value); @@ -14,18 +18,21 @@ namespace wmtk { class SubMesh { public: + SubMesh(Embedding& embedding, int64_t submesh_id); SubMesh() = delete; SubMesh(const SubMesh&) = delete; SubMesh& operator=(const SubMesh&) = delete; SubMesh(SubMesh&&) = delete; SubMesh& operator=(SubMesh&&) = delete; + void add_simplex(const Tuple& tuple, PrimitiveType pt); + // throws if `type` is larger than the substructure max dimension of tuple - Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const; + Tuple switch_tuple(const Tuple& tuple, PrimitiveType pt) const; - // call open_star_single_dimension if `type` is larger or equal than max dim, use `switch_tuple` + // call open_star_single_dimension if `pt` is larger or equal than max dim, use `switch_tuple` // otherwise - std::vector switch_tuple_vector(const Tuple& tuple, PrimitiveType type) const; + std::vector switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const; // 1. get max dim in open star // 2. check if any incident max dim facet has less than two neighbors @@ -43,10 +50,11 @@ class SubMesh const int64_t tag_value); // forward to Mesh - int64_t id(const Tuple& tuple, PrimitiveType type) const; + int64_t id(const Tuple& tuple, PrimitiveType pt) const; private: - Mesh& m_mesh; + Embedding& m_embedding; + const int64_t m_submesh_id; }; -} // namespace wmtk +} // namespace wmtk::submesh diff --git a/tests/submesh/submesh.cpp b/tests/submesh/submesh.cpp index 80e06ac09a..8d42e9cf8a 100644 --- a/tests/submesh/submesh.cpp +++ b/tests/submesh/submesh.cpp @@ -16,7 +16,7 @@ constexpr PrimitiveType PE = PrimitiveType::Edge; constexpr PrimitiveType PF = PrimitiveType::Triangle; constexpr PrimitiveType PT = PrimitiveType::Tetrahedron; -TEST_CASE("sub_mesh", "[mesh][sub_mesh]") +TEST_CASE("submesh", "[mesh][submesh]") { // basic test for implementing } From 4d647f3ee098f35a5c5425915c7a2f07d7714f5a Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Mon, 3 Feb 2025 14:36:32 -0500 Subject: [PATCH 03/47] Add functionality to submesh. --- src/wmtk/submesh/Embedding.cpp | 5 +++ src/wmtk/submesh/Embedding.hpp | 1 + src/wmtk/submesh/SubMesh.cpp | 78 ++++++++++++++++++++++++++++++++++ src/wmtk/submesh/SubMesh.hpp | 28 +++++++++++- tests/submesh/CMakeLists.txt | 2 +- tests/submesh/submesh.cpp | 22 ---------- tests/submesh/test_submesh.cpp | 52 +++++++++++++++++++++++ 7 files changed, 163 insertions(+), 25 deletions(-) delete mode 100644 tests/submesh/submesh.cpp create mode 100644 tests/submesh/test_submesh.cpp diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index 789be2c1b8..ff32d75294 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -48,6 +48,11 @@ Mesh& Embedding::mesh() return *m_mesh; } +const Mesh& Embedding::mesh() const +{ + return *m_mesh; +} + attribute::TypedAttributeHandle& Embedding::tag_handle(const PrimitiveType pt) { return m_tag_handle.at(pt); diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index 0f1a038ddc..0538721c74 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -22,6 +22,7 @@ class Embedding std::shared_ptr add_submesh(); Mesh& mesh(); + const Mesh& mesh() const; attribute::TypedAttributeHandle& tag_handle(const PrimitiveType pt); attribute::Accessor tag_accessor(const PrimitiveType pt); diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index fb7811e400..d2f8c51f7b 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -2,6 +2,11 @@ #include "Embedding.hpp" +#include +#include +#include +#include + namespace wmtk::submesh { SubMesh::SubMesh(Embedding& embedding, int64_t submesh_id) @@ -9,10 +14,83 @@ SubMesh::SubMesh(Embedding& embedding, int64_t submesh_id) , m_submesh_id(submesh_id) {} +Mesh& SubMesh::mesh() +{ + return m_embedding.mesh(); +} + +const Mesh& SubMesh::mesh() const +{ + return m_embedding.mesh(); +} + void SubMesh::add_simplex(const Tuple& tuple, PrimitiveType pt) { auto acc = m_embedding.tag_accessor(pt); acc.scalar_attribute(tuple) |= (int64_t)1 << m_submesh_id; + + const simplex::Simplex s(mesh(), pt, tuple); + + auto faces = simplex::faces(mesh(), s); + + for (const simplex::Simplex& f : faces) { + auto a = m_embedding.tag_accessor(f.primitive_type()); + a.scalar_attribute(f.tuple()) |= (int64_t)1 << m_submesh_id; + } +} + +void SubMesh::add_simplex(const simplex::IdSimplex& simplex) +{ + const PrimitiveType& pt = simplex.primitive_type(); + const Tuple t = mesh().get_tuple_from_id_simplex(simplex); + add_simplex(t, pt); +} + +PrimitiveType SubMesh::top_simplex_type(const Tuple& tuple) const +{ + const Mesh& m = mesh(); + + for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { + if (contains(tuple, pt)) { + return pt; + } + } + + log_and_throw_error("No simplex of the tuple contains the submesh tag."); +} + +PrimitiveType SubMesh::top_simplex_type() const +{ + const Mesh& m = mesh(); + + for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { + const auto tuples = m.get_all(pt); + for (const Tuple& t : tuples) { + if (contains(t, pt)) { + return pt; + } + } + } + + log_and_throw_error("No simplex of the tuple contains the submesh tag."); +} + +Tuple SubMesh::switch_tuple(const Tuple& tuple, PrimitiveType pt) const +{ + const int8_t pt_id = get_primitive_type_id(pt); + const int8_t max_pt_id = get_primitive_type_id(top_simplex_type(tuple)); + + if (pt_id >= max_pt_id) { + log_and_throw_error("Submesh `switch_tuple` cannot be used for cell switches."); + } + + return mesh().switch_tuple(tuple, pt); +} + +bool SubMesh::contains(const Tuple& tuple, PrimitiveType pt) const +{ + const auto acc = m_embedding.tag_accessor(pt); + return (acc.const_scalar_attribute(tuple) & ((int64_t)1 << m_submesh_id)) != 0; } } // namespace wmtk::submesh diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 545dec87ae..02c34bbe87 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -5,6 +5,12 @@ #include #include +namespace wmtk { +namespace simplex { +class IdSimplex; +} +} // namespace wmtk + namespace wmtk::submesh { class Embedding; @@ -25,9 +31,27 @@ class SubMesh SubMesh(SubMesh&&) = delete; SubMesh& operator=(SubMesh&&) = delete; + Mesh& mesh(); + const Mesh& mesh() const; + void add_simplex(const Tuple& tuple, PrimitiveType pt); + void add_simplex(const simplex::IdSimplex& simplex); + + /** + * @brief Get the maximum primitive type that has a tag for a given tuple. + */ + PrimitiveType top_simplex_type(const Tuple& tuple) const; + + /** + * @brief Get the maximum primitive type that has a tag in the entire mesh. + */ + PrimitiveType top_simplex_type() const; - // throws if `type` is larger than the substructure max dimension of tuple + /** + * @brief Can only perform local switches! + * + * Throws if `pt` is larger or equal to top_simplex_type(tuple) + */ Tuple switch_tuple(const Tuple& tuple, PrimitiveType pt) const; // call open_star_single_dimension if `pt` is larger or equal than max dim, use `switch_tuple` @@ -42,7 +66,7 @@ class SubMesh bool is_manifold(PrimitiveType pt, const Tuple& tuple) const; // Check if sub mesh contains the simplex - bool contains(PrimitiveType pt, const Tuple& tuple) const; + bool contains(const Tuple& tuple, PrimitiveType pt) const; // must be part of the construction void initialize( diff --git a/tests/submesh/CMakeLists.txt b/tests/submesh/CMakeLists.txt index 6c2701d86c..f6c22241fe 100644 --- a/tests/submesh/CMakeLists.txt +++ b/tests/submesh/CMakeLists.txt @@ -1,5 +1,5 @@ # Sources set(TEST_SOURCES - submesh.cpp + test_submesh.cpp ) target_sources(wmtk_tests PRIVATE ${TEST_SOURCES}) diff --git a/tests/submesh/submesh.cpp b/tests/submesh/submesh.cpp deleted file mode 100644 index 8d42e9cf8a..0000000000 --- a/tests/submesh/submesh.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include - -#include -#include -#include - -#include "tools/DEBUG_TriMesh.hpp" -#include "tools/TriMesh_examples.hpp" - -#include "tools/DEBUG_EdgeMesh.hpp" - -using namespace wmtk; - -constexpr PrimitiveType PV = PrimitiveType::Vertex; -constexpr PrimitiveType PE = PrimitiveType::Edge; -constexpr PrimitiveType PF = PrimitiveType::Triangle; -constexpr PrimitiveType PT = PrimitiveType::Tetrahedron; - -TEST_CASE("submesh", "[mesh][submesh]") -{ - // basic test for implementing -} diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp new file mode 100644 index 0000000000..e1b8c858b7 --- /dev/null +++ b/tests/submesh/test_submesh.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "tools/DEBUG_TriMesh.hpp" +#include "tools/TriMesh_examples.hpp" + +#include "tools/DEBUG_EdgeMesh.hpp" + +using namespace wmtk; +using namespace submesh; + +constexpr PrimitiveType PV = PrimitiveType::Vertex; +constexpr PrimitiveType PE = PrimitiveType::Edge; +constexpr PrimitiveType PF = PrimitiveType::Triangle; +constexpr PrimitiveType PT = PrimitiveType::Tetrahedron; + +TEST_CASE("submesh_init", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + const Tuple edge45 = m.edge_tuple_from_vids(4, 5); + + Embedding emb(mesh_in); + std::shared_ptr sub_ptr = emb.add_submesh(); + SubMesh& sub = *sub_ptr; + + CHECK_THROWS(sub.top_simplex_type()); + + sub.add_simplex(edge45, PE); + + CHECK(sub.contains(edge45, PrimitiveType::Edge)); + CHECK(sub.contains(edge45, PrimitiveType::Vertex)); + CHECK(sub.contains(sub.switch_tuple(edge45, PV), PrimitiveType::Vertex)); + CHECK(sub.top_simplex_type(edge45) == PrimitiveType::Edge); + + { + ParaviewWriter writer("submesh_init", "vertices", m, false, true, true, false); + CHECK_NOTHROW(m.serialize(writer)); + } +} From ffdfe7cc7edb883ba984d754dbf4a8d364de666c Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Mon, 3 Feb 2025 18:19:47 -0500 Subject: [PATCH 04/47] Add switch_tuple_vector. --- src/wmtk/submesh/SubMesh.cpp | 41 +++++++++++++++++++- src/wmtk/submesh/SubMesh.hpp | 3 ++ tests/submesh/test_submesh.cpp | 70 ++++++++++++++++++++++++++++++++-- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index d2f8c51f7b..d2a3744305 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -3,6 +3,7 @@ #include "Embedding.hpp" #include +#include #include #include #include @@ -84,7 +85,40 @@ Tuple SubMesh::switch_tuple(const Tuple& tuple, PrimitiveType pt) const log_and_throw_error("Submesh `switch_tuple` cannot be used for cell switches."); } - return mesh().switch_tuple(tuple, pt); + return local_switch_tuple(tuple, pt); +} + +std::vector SubMesh::switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const +{ + const int8_t pt_id = get_primitive_type_id(pt); + const int8_t max_pt_id = get_primitive_type_id(top_simplex_type(tuple)); + + if (pt_id > max_pt_id) { + log_and_throw_error("Required PrimitiveType switch does not exist in submesh."); + } + if (pt_id == 0) { + log_and_throw_error("Cannot perform global switches for vertex PrimitiveType. Use " + "`switch_tuple` instead of `switch_tuple_vector`."); + } + + assert(pt_id <= max_pt_id); + + const PrimitiveType pt_face = get_primitive_type_from_id(pt_id - 1); + + const simplex::Simplex s_face(mesh(), pt_face, tuple); + + std::vector neighs; + neighs.reserve(2); + for (const Tuple& t : simplex::cofaces_single_dimension_iterable(mesh(), s_face, pt)) { + if (contains(t, pt)) { + neighs.emplace_back(t); + } + } + + assert(!neighs.empty()); + assert(neighs[0] == tuple); + + return neighs; } bool SubMesh::contains(const Tuple& tuple, PrimitiveType pt) const @@ -93,4 +127,9 @@ bool SubMesh::contains(const Tuple& tuple, PrimitiveType pt) const return (acc.const_scalar_attribute(tuple) & ((int64_t)1 << m_submesh_id)) != 0; } +Tuple SubMesh::local_switch_tuple(const Tuple& tuple, PrimitiveType pt) const +{ + return mesh().switch_tuple(tuple, pt); +} + } // namespace wmtk::submesh diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 02c34bbe87..6f1d4540a9 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -79,6 +79,9 @@ class SubMesh private: Embedding& m_embedding; const int64_t m_submesh_id; + +private: + Tuple local_switch_tuple(const Tuple& tuple, PrimitiveType pt) const; }; } // namespace wmtk::submesh diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index e1b8c858b7..6a231c0b2f 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -40,10 +40,72 @@ TEST_CASE("submesh_init", "[mesh][submesh]") sub.add_simplex(edge45, PE); - CHECK(sub.contains(edge45, PrimitiveType::Edge)); - CHECK(sub.contains(edge45, PrimitiveType::Vertex)); - CHECK(sub.contains(sub.switch_tuple(edge45, PV), PrimitiveType::Vertex)); - CHECK(sub.top_simplex_type(edge45) == PrimitiveType::Edge); + CHECK(sub.contains(edge45, PE)); + CHECK(sub.contains(edge45, PV)); + CHECK(sub.contains(sub.switch_tuple(edge45, PV), PV)); + CHECK(sub.top_simplex_type(edge45) == PE); + + // Test switch_tuple_vector + const Tuple edge34 = m.edge_tuple_from_vids(3, 4); + sub.add_simplex(edge34, PE); + CHECK(sub.contains(edge34, PE)); + CHECK(sub.contains(edge34, PV)); + CHECK(sub.contains(sub.switch_tuple(edge34, PV), PV)); + CHECK(sub.top_simplex_type(edge34) == PE); + + // switch from edge 45 to 43 + { + CHECK_THROWS(sub.switch_tuple(edge34, PE)); + CHECK_THROWS(sub.switch_tuple_vector(edge45, PV).size() == 1); + std::vector v4_edges; + REQUIRE_NOTHROW(v4_edges = sub.switch_tuple_vector(edge45, PE)); + CHECK(v4_edges.size() == 2); + CHECK(v4_edges[0] == edge45); + CHECK(m.get_id_simplex(v4_edges[1], PE) == m.get_id_simplex(edge34, PE)); + CHECK(m.get_id_simplex(v4_edges[1], PV) == m.get_id_simplex(edge45, PV)); + } + + const Tuple edge04 = m.edge_tuple_from_vids(0, 4); + sub.add_simplex(edge04, PE); + // switch from edge 45 to all others + { + std::vector v4_edges; + REQUIRE_NOTHROW(v4_edges = sub.switch_tuple_vector(edge45, PE)); + CHECK(v4_edges.size() == 3); + CHECK(v4_edges[0] == edge45); + for (const Tuple& t : v4_edges) { + CHECK(m.get_id_simplex(t, PV) == m.get_id_simplex(edge45, PV)); + } + } + + const Tuple edge54 = sub.switch_tuple(edge45, PV); + { + std::vector v5_edges; + REQUIRE_NOTHROW(v5_edges = sub.switch_tuple_vector(edge54, PE)); + CHECK(v5_edges.size() == 1); + CHECK(v5_edges[0] == edge54); + } + + const Tuple face596 = m.face_tuple_from_vids(5, 9, 6); + sub.add_simplex(face596, PF); + CHECK(sub.top_simplex_type() == PF); + const Tuple edge59 = m.edge_tuple_with_vs_and_t(5, 9, 9); + { + std::vector v5_edges; + REQUIRE_NOTHROW(v5_edges = sub.switch_tuple_vector(edge59, PE)); + CHECK(v5_edges.size() == 3); + CHECK(v5_edges[0] == edge59); + for (const Tuple& t : v5_edges) { + CHECK(m.get_id_simplex(t, PV) == m.get_id_simplex(edge59, PV)); + } + + // local switch + CHECK_NOTHROW(sub.switch_tuple(edge59, PE)); + + // face switch + CHECK(sub.switch_tuple_vector(face596, PF).size() == 1); + } + { ParaviewWriter writer("submesh_init", "vertices", m, false, true, true, false); From 46e8efc9f0c3ec95de2c8dc35d75fd7a52c6af82 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 4 Feb 2025 18:26:49 -0500 Subject: [PATCH 05/47] More SubMesh functionality. --- src/wmtk/submesh/SubMesh.cpp | 103 +++++++++++++++++++++++++++++++++ src/wmtk/submesh/SubMesh.hpp | 33 ++++++++--- tests/submesh/test_submesh.cpp | 39 +++++++++++++ 3 files changed, 166 insertions(+), 9 deletions(-) diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index d2a3744305..f5573f2330 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -3,8 +3,12 @@ #include "Embedding.hpp" #include +#include #include #include +#include +#include +#include #include #include @@ -47,6 +51,18 @@ void SubMesh::add_simplex(const simplex::IdSimplex& simplex) add_simplex(t, pt); } +std::vector SubMesh::get_all(const PrimitiveType pt) const +{ + const auto all = mesh().get_all(pt); + std::vector sub; + for (const Tuple& t : all) { + if (contains(t, pt)) { + sub.emplace_back(t); + } + } + return sub; +} + PrimitiveType SubMesh::top_simplex_type(const Tuple& tuple) const { const Mesh& m = mesh(); @@ -121,15 +137,102 @@ std::vector SubMesh::switch_tuple_vector(const Tuple& tuple, PrimitiveTyp return neighs; } +bool SubMesh::is_boundary(const Tuple& tuple, PrimitiveType pt) const +{ + if (!contains(tuple, pt)) { + log_and_throw_error("Cannot check for boundary if simplex is not contained in submesh"); + } + + const simplex::Simplex sim(pt, tuple); + // 1. get max dim in open star + int8_t top_dim = get_primitive_type_id(pt); + for (const simplex::IdSimplex& s : simplex::open_star_iterable(mesh(), sim)) { + if (!contains(s)) { + continue; + } + const int8_t dim = get_primitive_type_id(s.primitive_type()); + top_dim = std::max(top_dim, dim); + } + + if (top_dim == 0) { + // simplex is an isolated vertex + return true; + } + + // 2. check if any incident max dim facet has less than two neighbors + const PrimitiveType top_pt = get_primitive_type_from_id(top_dim); + const PrimitiveType face_pt = get_primitive_type_from_id(top_dim - 1); + + for (const simplex::Simplex& face_simplex : + simplex::neighbors_single_dimension(mesh(), sim, face_pt)) { + // const simplex::Simplex face_simplex(top_pt, face_tuple); + if (!contains(face_simplex)) { + continue; + } + + int64_t cell_counter = 0; + for (const Tuple& cell_tuple : + simplex::cofaces_single_dimension_iterable(mesh(), face_simplex, top_pt)) { + if (contains(cell_tuple, top_pt)) { + ++cell_counter; + } + } + if (cell_counter < 2) { + return true; + } + } + + return false; +} + bool SubMesh::contains(const Tuple& tuple, PrimitiveType pt) const { const auto acc = m_embedding.tag_accessor(pt); return (acc.const_scalar_attribute(tuple) & ((int64_t)1 << m_submesh_id)) != 0; } +bool SubMesh::contains(const simplex::IdSimplex& s) const +{ + const auto acc = m_embedding.tag_accessor(s.primitive_type()); + return (acc.const_scalar_attribute(s) & ((int64_t)1 << m_submesh_id)) != 0; +} + +bool SubMesh::contains(const simplex::Simplex& s) const +{ + return contains(s.tuple(), s.primitive_type()); +} + +int64_t SubMesh::id(const Tuple& tuple, PrimitiveType pt) const +{ + return mesh().id(tuple, pt); +} + Tuple SubMesh::local_switch_tuple(const Tuple& tuple, PrimitiveType pt) const { return mesh().switch_tuple(tuple, pt); } +template +void SubMesh::add_from_tag_attribute( + const attribute::TypedAttributeHandle& tag_attribute, + const T tag_value) +{ + const attribute::Accessor acc = mesh().create_const_accessor(tag_attribute); + const PrimitiveType pt = tag_attribute.primitive_type(); + + const auto tuples = mesh().get_all(pt); + for (const Tuple& t : tuples) { + if (acc.const_scalar_attribute(t) == tag_value) { + add_simplex(t, pt); + } + } +} + +template void SubMesh::add_from_tag_attribute( + const attribute::TypedAttributeHandle&, + const int64_t); +template void SubMesh::add_from_tag_attribute( + const attribute::TypedAttributeHandle&, + const char); + } // namespace wmtk::submesh diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 6f1d4540a9..a80379bb62 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -8,7 +8,8 @@ namespace wmtk { namespace simplex { class IdSimplex; -} +class Simplex; +} // namespace simplex } // namespace wmtk namespace wmtk::submesh { @@ -37,6 +38,8 @@ class SubMesh void add_simplex(const Tuple& tuple, PrimitiveType pt); void add_simplex(const simplex::IdSimplex& simplex); + std::vector get_all(const PrimitiveType pt) const; + /** * @brief Get the maximum primitive type that has a tag for a given tuple. */ @@ -58,22 +61,34 @@ class SubMesh // otherwise std::vector switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const; - // 1. get max dim in open star - // 2. check if any incident max dim facet has less than two neighbors - bool is_boundary(PrimitiveType pt, const Tuple& tuple) const; + /** + * @brief Is the given simplex on the boundary? + * + * This check is more complex than the one of the mesh itself but follows a similar idea. First, + * the top simplex type in the open star is determined. Next, we check if any facet in the star + * has less than two top simplices incident. + * + * Note that the behavior might be unexpected for non-homogenuous or non-manifold simplicial + * complexes! + */ + bool is_boundary(const Tuple& tuple, PrimitiveType pt) const; // This is going to be some ugly recursive stuff I guess... bool is_manifold(PrimitiveType pt, const Tuple& tuple) const; // Check if sub mesh contains the simplex bool contains(const Tuple& tuple, PrimitiveType pt) const; + bool contains(const simplex::IdSimplex& s) const; + bool contains(const simplex::Simplex& s) const; - // must be part of the construction - void initialize( - const std::map>& tag_attributes, - const int64_t tag_value); + template + void add_from_tag_attribute( + const attribute::TypedAttributeHandle& tag_attribute, + const T tag_value); - // forward to Mesh + /** + * Wrapping the simplex id function of Mesh. + */ int64_t id(const Tuple& tuple, PrimitiveType pt) const; private: diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 6a231c0b2f..41203118f5 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -43,6 +43,9 @@ TEST_CASE("submesh_init", "[mesh][submesh]") CHECK(sub.contains(edge45, PE)); CHECK(sub.contains(edge45, PV)); CHECK(sub.contains(sub.switch_tuple(edge45, PV), PV)); + CHECK(sub.is_boundary(edge45, PE)); + CHECK(sub.is_boundary(edge45, PV)); + CHECK(sub.is_boundary(sub.switch_tuple(edge45, PV), PV)); CHECK(sub.top_simplex_type(edge45) == PE); // Test switch_tuple_vector @@ -51,6 +54,8 @@ TEST_CASE("submesh_init", "[mesh][submesh]") CHECK(sub.contains(edge34, PE)); CHECK(sub.contains(edge34, PV)); CHECK(sub.contains(sub.switch_tuple(edge34, PV), PV)); + CHECK(sub.is_boundary(edge34, PV)); + CHECK(!sub.is_boundary(sub.switch_tuple(edge34, PV), PV)); CHECK(sub.top_simplex_type(edge34) == PE); // switch from edge 45 to 43 @@ -67,6 +72,8 @@ TEST_CASE("submesh_init", "[mesh][submesh]") const Tuple edge04 = m.edge_tuple_from_vids(0, 4); sub.add_simplex(edge04, PE); + CHECK(sub.is_boundary(edge04, PV)); + CHECK(!sub.is_boundary(sub.switch_tuple(edge04, PV), PV)); // switch from edge 45 to all others { std::vector v4_edges; @@ -89,6 +96,9 @@ TEST_CASE("submesh_init", "[mesh][submesh]") const Tuple face596 = m.face_tuple_from_vids(5, 9, 6); sub.add_simplex(face596, PF); CHECK(sub.top_simplex_type() == PF); + CHECK(sub.id(face596, PF) == 9); + CHECK(sub.is_boundary(face596, PF)); + CHECK(sub.is_boundary(m.vertex_tuple_from_id(5), PV)); const Tuple edge59 = m.edge_tuple_with_vs_and_t(5, 9, 9); { std::vector v5_edges; @@ -112,3 +122,32 @@ TEST_CASE("submesh_init", "[mesh][submesh]") CHECK_NOTHROW(m.serialize(writer)); } } + +TEST_CASE("submesh_init_from_tag", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + + Embedding emb(mesh_in); + std::shared_ptr sub_ptr = emb.add_submesh(); + SubMesh& sub = *sub_ptr; + + // register tag attribute + { + auto tag_handle = m.register_attribute("tag", PF, 1); + auto acc = m.create_accessor(tag_handle); + acc.scalar_attribute(m.tuple_from_face_id(0)) = 1; + acc.scalar_attribute(m.tuple_from_face_id(2)) = 1; + + sub.add_from_tag_attribute(tag_handle.as(), 1); + } + + CHECK(sub.top_simplex_type() == PF); + CHECK(sub.get_all(PF).size() == 2); + CHECK(sub.get_all(PE).size() == 6); + CHECK(sub.get_all(PV).size() == 5); +} From 399e6c9931951b5444d117bf889ed08156ecdcfa Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 5 Feb 2025 16:55:58 -0500 Subject: [PATCH 06/47] Add MeshBase. --- src/wmtk/CMakeLists.txt | 1 + src/wmtk/Mesh.hpp | 5 +++- src/wmtk/MeshBase.hpp | 49 ++++++++++++++++++++++++++++++++++++ src/wmtk/submesh/SubMesh.hpp | 3 ++- 4 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 src/wmtk/MeshBase.hpp diff --git a/src/wmtk/CMakeLists.txt b/src/wmtk/CMakeLists.txt index 2d82f81986..06390543f5 100644 --- a/src/wmtk/CMakeLists.txt +++ b/src/wmtk/CMakeLists.txt @@ -2,6 +2,7 @@ set(SRC_FILES Cell.cpp Cell.hpp + MeshBase.hpp Mesh.cpp Mesh_attributes.cpp #the attribute parts of Mesh class Mesh_construction.cpp #the construction/destruction parts of Mesh class diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index dccc412764..a265b9f3bc 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -18,6 +18,7 @@ // basic data for the class #include +#include "MeshBase.hpp" #include "Tuple.hpp" #include "Types.hpp" #include "attribute/AttributeManager.hpp" @@ -103,7 +104,9 @@ class TupleTag; // * Mesh.cpp // * Mesh_attributes.cpp // * Mesh_construction.cpp -class Mesh : public std::enable_shared_from_this, public wmtk::utils::MerkleTreeInteriorNode +class Mesh : public std::enable_shared_from_this, + public MeshBase, + public wmtk::utils::MerkleTreeInteriorNode { public: friend class tests::tools::TestTools; diff --git a/src/wmtk/MeshBase.hpp b/src/wmtk/MeshBase.hpp new file mode 100644 index 0000000000..86a5cf8067 --- /dev/null +++ b/src/wmtk/MeshBase.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include "PrimitiveType.hpp" +#include "Tuple.hpp" + +namespace wmtk { + + +namespace simplex { +class Simplex; +class IdSimplex; +} // namespace simplex + + +class MeshBase +{ +private: + /* data */ +public: + // virtual std::vector get_all(PrimitiveType type) const = 0; + + /** + * @brief switch the orientation of the Tuple of the given dimension + * @note this is not done in place. Return a new Tuple of the switched state + * + * @param m + * @param type d-0 -> switch vertex + d-1 -> switch edge + d-2 -> switch face + d-3 -> switch tetrahedron + */ + virtual Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const = 0; + + /** + * @brief check if a simplex (encoded as a tuple/primitive pair) lies on a boundary or not + * + * @param simplex + * @return true if this simplex lies on the boundary of the mesh + * @return false otherwise + */ + virtual bool is_boundary(PrimitiveType, const Tuple& tuple) const = 0; + + virtual int64_t id(const simplex::Simplex& s) const = 0; + virtual int64_t id(const Tuple& tuple, PrimitiveType pt) const = 0; +}; + +} // namespace wmtk \ No newline at end of file diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index a80379bb62..8d22061b79 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -22,7 +23,7 @@ class Embedding; * s.add_simplex(IdSimplex); * s.add_from_tag(tag_handle, tag_value); */ -class SubMesh +class SubMesh : public std::enable_shared_from_this { public: SubMesh(Embedding& embedding, int64_t submesh_id); From ce6cdf38b58b95b3aa8975dbfbd2a795b3c0f04c Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 5 Feb 2025 22:41:57 -0500 Subject: [PATCH 07/47] Updating switch code (without testing it). --- src/wmtk/MeshBase.hpp | 4 +-- src/wmtk/submesh/SubMesh.cpp | 63 +++++++++++++++++++++++++++------- src/wmtk/submesh/SubMesh.hpp | 19 ++++++++-- tests/submesh/test_submesh.cpp | 23 +++++++++++++ 4 files changed, 91 insertions(+), 18 deletions(-) diff --git a/src/wmtk/MeshBase.hpp b/src/wmtk/MeshBase.hpp index 86a5cf8067..830dee85b3 100644 --- a/src/wmtk/MeshBase.hpp +++ b/src/wmtk/MeshBase.hpp @@ -16,11 +16,11 @@ class IdSimplex; class MeshBase { -private: - /* data */ public: // virtual std::vector get_all(PrimitiveType type) const = 0; + virtual int64_t top_cell_dimension() const = 0; + /** * @brief switch the orientation of the Tuple of the given dimension * @note this is not done in place. Return a new Tuple of the switched state diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index f5573f2330..6d7ea44938 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -31,6 +31,9 @@ const Mesh& SubMesh::mesh() const void SubMesh::add_simplex(const Tuple& tuple, PrimitiveType pt) { + const int64_t pt_dim = get_primitive_type_id(pt); + m_top_cell_dimension = std::max(m_top_cell_dimension, pt_dim); + auto acc = m_embedding.tag_accessor(pt); acc.scalar_attribute(tuple) |= (int64_t)1 << m_submesh_id; @@ -78,18 +81,31 @@ PrimitiveType SubMesh::top_simplex_type(const Tuple& tuple) const PrimitiveType SubMesh::top_simplex_type() const { - const Mesh& m = mesh(); - - for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { - const auto tuples = m.get_all(pt); - for (const Tuple& t : tuples) { - if (contains(t, pt)) { - return pt; - } - } + // const Mesh& m = mesh(); + // + // for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { + // const auto tuples = m.get_all(pt); + // for (const Tuple& t : tuples) { + // if (contains(t, pt)) { + // return pt; + // } + // } + // } + // + // log_and_throw_error("No simplex of the tuple contains the submesh tag."); + + if (m_top_cell_dimension < 0) { + log_and_throw_error("No simplex of the tuple contains the submesh tag."); } - log_and_throw_error("No simplex of the tuple contains the submesh tag."); + return get_primitive_type_from_id(m_top_cell_dimension); +} + +int64_t SubMesh::top_cell_dimension() const +{ + assert(m_top_cell_dimension >= 0); + assert(m_top_cell_dimension < 4); + return m_top_cell_dimension; } Tuple SubMesh::switch_tuple(const Tuple& tuple, PrimitiveType pt) const @@ -97,10 +113,21 @@ Tuple SubMesh::switch_tuple(const Tuple& tuple, PrimitiveType pt) const const int8_t pt_id = get_primitive_type_id(pt); const int8_t max_pt_id = get_primitive_type_id(top_simplex_type(tuple)); - if (pt_id >= max_pt_id) { - log_and_throw_error("Submesh `switch_tuple` cannot be used for cell switches."); + if (pt_id > max_pt_id) { + // invalid switch + log_and_throw_error("Required PrimitiveType switch does not exist in submesh."); + } + if (pt_id == max_pt_id) { + // global switch + const auto vec = switch_tuple_vector(tuple, pt); + if (vec.size() != 2) { + log_and_throw_error( + "SubMesh `switch_tuple` cannot be used on non-manifold or boundary simplices."); + } + return vec[1]; } + // local switch return local_switch_tuple(tuple, pt); } @@ -137,7 +164,7 @@ std::vector SubMesh::switch_tuple_vector(const Tuple& tuple, PrimitiveTyp return neighs; } -bool SubMesh::is_boundary(const Tuple& tuple, PrimitiveType pt) const +bool SubMesh::is_boundary(PrimitiveType pt, const Tuple& tuple) const { if (!contains(tuple, pt)) { log_and_throw_error("Cannot check for boundary if simplex is not contained in submesh"); @@ -185,6 +212,11 @@ bool SubMesh::is_boundary(const Tuple& tuple, PrimitiveType pt) const return false; } +bool SubMesh::is_boundary(const Tuple& tuple, PrimitiveType pt) const +{ + return is_boundary(pt, tuple); +} + bool SubMesh::contains(const Tuple& tuple, PrimitiveType pt) const { const auto acc = m_embedding.tag_accessor(pt); @@ -207,6 +239,11 @@ int64_t SubMesh::id(const Tuple& tuple, PrimitiveType pt) const return mesh().id(tuple, pt); } +int64_t SubMesh::id(const simplex::Simplex& s) const +{ + return id(s.tuple(), s.primitive_type()); +} + Tuple SubMesh::local_switch_tuple(const Tuple& tuple, PrimitiveType pt) const { return mesh().switch_tuple(tuple, pt); diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 8d22061b79..81dab8132e 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -23,7 +24,7 @@ class Embedding; * s.add_simplex(IdSimplex); * s.add_from_tag(tag_handle, tag_value); */ -class SubMesh : public std::enable_shared_from_this +class SubMesh : public std::enable_shared_from_this, public MeshBase { public: SubMesh(Embedding& embedding, int64_t submesh_id); @@ -36,6 +37,12 @@ class SubMesh : public std::enable_shared_from_this Mesh& mesh(); const Mesh& mesh() const; + /** + * @brief Adds a simpex to the submesh. + * + * The top simplex type of the mesh is updated if the added simplex is of higher dimension than + * any other simplex of the submesh. + */ void add_simplex(const Tuple& tuple, PrimitiveType pt); void add_simplex(const simplex::IdSimplex& simplex); @@ -51,12 +58,14 @@ class SubMesh : public std::enable_shared_from_this */ PrimitiveType top_simplex_type() const; + int64_t top_cell_dimension() const; + /** * @brief Can only perform local switches! * * Throws if `pt` is larger or equal to top_simplex_type(tuple) */ - Tuple switch_tuple(const Tuple& tuple, PrimitiveType pt) const; + Tuple switch_tuple(const Tuple& tuple, PrimitiveType pt) const override; // call open_star_single_dimension if `pt` is larger or equal than max dim, use `switch_tuple` // otherwise @@ -72,6 +81,7 @@ class SubMesh : public std::enable_shared_from_this * Note that the behavior might be unexpected for non-homogenuous or non-manifold simplicial * complexes! */ + bool is_boundary(PrimitiveType pt, const Tuple& tuple) const override; bool is_boundary(const Tuple& tuple, PrimitiveType pt) const; // This is going to be some ugly recursive stuff I guess... @@ -90,12 +100,15 @@ class SubMesh : public std::enable_shared_from_this /** * Wrapping the simplex id function of Mesh. */ - int64_t id(const Tuple& tuple, PrimitiveType pt) const; + int64_t id(const Tuple& tuple, PrimitiveType pt) const override; + int64_t id(const simplex::Simplex& s) const override; private: Embedding& m_embedding; const int64_t m_submesh_id; + int64_t m_top_cell_dimension = -1; + private: Tuple local_switch_tuple(const Tuple& tuple, PrimitiveType pt) const; }; diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 41203118f5..b0854ea5b8 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -151,3 +152,25 @@ TEST_CASE("submesh_init_from_tag", "[mesh][submesh]") CHECK(sub.get_all(PE).size() == 6); CHECK(sub.get_all(PV).size() == 5); } + +TEST_CASE("submesh_top_dimension_cofaces", "[mesh][submesh]") +{ + REQUIRE(false); // test not implemented + + // logger().set_level(spdlog::level::off); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + const Tuple edge45 = m.edge_tuple_from_vids(4, 5); + + Embedding emb(mesh_in); + std::shared_ptr sub_ptr = emb.add_submesh(); + SubMesh& sub = *sub_ptr; + + CHECK_THROWS(sub.top_simplex_type()); + + sub.add_simplex(edge45, PE); +} \ No newline at end of file From d56d64b205934f376eb6f650265dfb72862c847f Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 6 Feb 2025 18:19:26 -0500 Subject: [PATCH 08/47] Fix clang compilation issues. --- src/wmtk/EdgeMesh.hpp | 2 +- src/wmtk/Mesh.hpp | 12 ++++++------ src/wmtk/MeshBase.hpp | 4 +++- src/wmtk/MeshCRTP.hpp | 8 +++++--- src/wmtk/PointMesh.hpp | 2 +- src/wmtk/TetMesh.hpp | 2 +- src/wmtk/TriMesh.hpp | 2 +- src/wmtk/submesh/SubMesh.hpp | 5 +++-- 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/wmtk/EdgeMesh.hpp b/src/wmtk/EdgeMesh.hpp index 98fb6e6868..0202daebbb 100644 --- a/src/wmtk/EdgeMesh.hpp +++ b/src/wmtk/EdgeMesh.hpp @@ -57,7 +57,7 @@ class EdgeMesh : public MeshCRTP std::vector orient_vertices(const Tuple& tuple) const override; protected: - int64_t id(const Tuple& tuple, PrimitiveType type) const; + int64_t id(const Tuple& tuple, PrimitiveType type) const final override; using MeshCRTP::id; // getting the (simplex) prototype int64_t id_vertex(const Tuple& tuple) const { return id(tuple, PrimitiveType::Vertex); } diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index a265b9f3bc..c6aca799c6 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -144,7 +144,7 @@ class Mesh : public std::enable_shared_from_this, friend class operations::internal::CollapseAlternateFacetData; - int64_t top_cell_dimension() const; + int64_t top_cell_dimension() const override; PrimitiveType top_simplex_type() const; bool is_free() const; @@ -170,7 +170,7 @@ class Mesh : public std::enable_shared_from_this, * @param type the type of tuple, can be vertex/edge/triangle/tetrahedron * @return vector of Tuples referring to each type */ - std::vector get_all(PrimitiveType type) const; + std::vector get_all(PrimitiveType type) const override; std::vector get_all_id_simplex(PrimitiveType type) const; /** @@ -369,7 +369,7 @@ class Mesh : public std::enable_shared_from_this, d-2 -> switch face d-3 -> switch tetrahedron */ - virtual Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const = 0; + virtual Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const override = 0; // NOTE: adding per-simplex utility functions here is _wrong_ and will be removed @@ -432,7 +432,7 @@ class Mesh : public std::enable_shared_from_this, * @return true if this simplex lies on the boundary of the mesh * @return false otherwise */ - virtual bool is_boundary(PrimitiveType, const Tuple& tuple) const = 0; + virtual bool is_boundary(PrimitiveType, const Tuple& tuple) const override = 0; bool is_hash_valid(const Tuple& tuple, const attribute::Accessor& hash_accessor) const; @@ -811,14 +811,14 @@ class Mesh : public std::enable_shared_from_this, d-3 -> tetrahedron * @return int64_t id of the entity */ - int64_t id(const Tuple& tuple, PrimitiveType type) const; + int64_t id(const Tuple& tuple, PrimitiveType type) const override; int64_t id(const simplex::NavigatableSimplex& s) const { return s.index(); } int64_t id(const simplex::IdSimplex& s) const { return s.index(); } protected: /// Forwarding version of id on simplices that does id caching - virtual int64_t id(const simplex::Simplex& s) const = 0; + virtual int64_t id(const simplex::Simplex& s) const override = 0; /// Internal utility to allow id to be virtual with a non-virtual overload in derived -Mesh classes. /// Mesh::id invokes Mesh::id_virtual which is final overriden by MeshCRTP::id_virtual, which in turn invokes MeshCRTP::id, and then TriMesh::id. /// This circuitous mechanism makes MeshCRTP::id and TriMesh::id fully inlineable, so code that wants to take in any derived class can get optimized results with MeshCRTP, or for cases where classes want to utilize just TriMesh they can get inline/accelerated usage as well. diff --git a/src/wmtk/MeshBase.hpp b/src/wmtk/MeshBase.hpp index 830dee85b3..66aab2855a 100644 --- a/src/wmtk/MeshBase.hpp +++ b/src/wmtk/MeshBase.hpp @@ -17,7 +17,9 @@ class IdSimplex; class MeshBase { public: - // virtual std::vector get_all(PrimitiveType type) const = 0; + virtual ~MeshBase() = default; + + virtual std::vector get_all(PrimitiveType type) const = 0; virtual int64_t top_cell_dimension() const = 0; diff --git a/src/wmtk/MeshCRTP.hpp b/src/wmtk/MeshCRTP.hpp index 9468e1e02b..ba0f87f6dc 100644 --- a/src/wmtk/MeshCRTP.hpp +++ b/src/wmtk/MeshCRTP.hpp @@ -100,7 +100,10 @@ class MeshCRTP : public Mesh protected: /// Returns the id of a simplex encoded in a tuple - int64_t id(const Tuple& tuple, PrimitiveType type) const { return derived().id(tuple, type); } + int64_t id(const Tuple& tuple, PrimitiveType type) const override + { + return derived().id(tuple, type); + } /// internal utility for overriding the mesh class's id function without having the final override block the derived class's override /// (we can't have Mesh::id be virtual, MeshCRTP::id final override, and TriMesh::id. This indirection pushes the final override to this other function int64_t id_virtual(const Tuple& tuple, PrimitiveType type) const final override @@ -111,8 +114,7 @@ class MeshCRTP : public Mesh /// variant of id that can cache internally held values int64_t id(const simplex::Simplex& s) const final override { - - return id(s.tuple(),s.primitive_type()); + return id(s.tuple(), s.primitive_type()); } // catch any other Mesh id methods that might emerge by default diff --git a/src/wmtk/PointMesh.hpp b/src/wmtk/PointMesh.hpp index d59872e52c..01f0ca3dc8 100644 --- a/src/wmtk/PointMesh.hpp +++ b/src/wmtk/PointMesh.hpp @@ -45,7 +45,7 @@ class PointMesh : public MeshCRTP protected: using MeshCRTP::id; // getting the (simplex) prototype - int64_t id(const Tuple& tuple, PrimitiveType type) const; + int64_t id(const Tuple& tuple, PrimitiveType type) const override; /** * @brief internal function that returns the tuple of requested type, and has the global index diff --git a/src/wmtk/TetMesh.hpp b/src/wmtk/TetMesh.hpp index b93516a756..25ce6ba14f 100644 --- a/src/wmtk/TetMesh.hpp +++ b/src/wmtk/TetMesh.hpp @@ -63,7 +63,7 @@ class TetMesh : public MeshCRTP protected: void make_cached_accessors(); - int64_t id(const Tuple& tuple, PrimitiveType type) const; + int64_t id(const Tuple& tuple, PrimitiveType type) const override; using MeshCRTP::id; // getting the (simplex) prototype diff --git a/src/wmtk/TriMesh.hpp b/src/wmtk/TriMesh.hpp index d1b184326e..da5ddd9c9a 100644 --- a/src/wmtk/TriMesh.hpp +++ b/src/wmtk/TriMesh.hpp @@ -68,7 +68,7 @@ class TriMesh : public MeshCRTP std::vector orient_vertices(const Tuple& t) const override; protected: - int64_t id(const Tuple& tuple, PrimitiveType type) const; + int64_t id(const Tuple& tuple, PrimitiveType type) const override; using MeshCRTP::id; // getting the (simplex) prototype int64_t id_vertex(const Tuple& tuple) const { return id(tuple, PrimitiveType::Vertex); } diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 81dab8132e..c606ac1996 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -33,6 +33,7 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase SubMesh& operator=(const SubMesh&) = delete; SubMesh(SubMesh&&) = delete; SubMesh& operator=(SubMesh&&) = delete; + ~SubMesh() override = default; Mesh& mesh(); const Mesh& mesh() const; @@ -46,7 +47,7 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase void add_simplex(const Tuple& tuple, PrimitiveType pt); void add_simplex(const simplex::IdSimplex& simplex); - std::vector get_all(const PrimitiveType pt) const; + std::vector get_all(const PrimitiveType pt) const override; /** * @brief Get the maximum primitive type that has a tag for a given tuple. @@ -58,7 +59,7 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase */ PrimitiveType top_simplex_type() const; - int64_t top_cell_dimension() const; + int64_t top_cell_dimension() const override; /** * @brief Can only perform local switches! From ed1e650666189359ecd4b37f03304816e4bb2fe7 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 6 Feb 2025 18:20:30 -0500 Subject: [PATCH 09/47] Fix some weird behavior for SubMesh. --- src/wmtk/submesh/SubMesh.cpp | 121 ++++++++++++++++++--------------- src/wmtk/submesh/SubMesh.hpp | 8 ++- tests/submesh/test_submesh.cpp | 53 +++++---------- 3 files changed, 90 insertions(+), 92 deletions(-) diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index 6d7ea44938..03fbd7c7f3 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -32,7 +32,10 @@ const Mesh& SubMesh::mesh() const void SubMesh::add_simplex(const Tuple& tuple, PrimitiveType pt) { const int64_t pt_dim = get_primitive_type_id(pt); - m_top_cell_dimension = std::max(m_top_cell_dimension, pt_dim); + if (m_top_cell_dimension < pt_dim) { + logger().trace("Top cell dimension changed from {} to {}", m_top_cell_dimension, pt_dim); + m_top_cell_dimension = pt_dim; + } auto acc = m_embedding.tag_accessor(pt); acc.scalar_attribute(tuple) |= (int64_t)1 << m_submesh_id; @@ -119,50 +122,64 @@ Tuple SubMesh::switch_tuple(const Tuple& tuple, PrimitiveType pt) const } if (pt_id == max_pt_id) { // global switch - const auto vec = switch_tuple_vector(tuple, pt); - if (vec.size() != 2) { + const PrimitiveType pt_face = get_primitive_type_from_id(pt_id - 1); + const simplex::Simplex s_face(mesh(), pt_face, tuple); + + Tuple other; + int64_t other_counter = 0; + for (const Tuple& t : simplex::cofaces_single_dimension_iterable(mesh(), s_face, pt)) { + if (contains(t, pt)) { + ++other_counter; + if (other_counter == 2) { + other = t; + } + } + } + + if (other_counter != 2) { log_and_throw_error( "SubMesh `switch_tuple` cannot be used on non-manifold or boundary simplices."); } - return vec[1]; + return other; } // local switch return local_switch_tuple(tuple, pt); } -std::vector SubMesh::switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const -{ - const int8_t pt_id = get_primitive_type_id(pt); - const int8_t max_pt_id = get_primitive_type_id(top_simplex_type(tuple)); - - if (pt_id > max_pt_id) { - log_and_throw_error("Required PrimitiveType switch does not exist in submesh."); - } - if (pt_id == 0) { - log_and_throw_error("Cannot perform global switches for vertex PrimitiveType. Use " - "`switch_tuple` instead of `switch_tuple_vector`."); - } - - assert(pt_id <= max_pt_id); - - const PrimitiveType pt_face = get_primitive_type_from_id(pt_id - 1); - - const simplex::Simplex s_face(mesh(), pt_face, tuple); - - std::vector neighs; - neighs.reserve(2); - for (const Tuple& t : simplex::cofaces_single_dimension_iterable(mesh(), s_face, pt)) { - if (contains(t, pt)) { - neighs.emplace_back(t); - } - } - - assert(!neighs.empty()); - assert(neighs[0] == tuple); - - return neighs; -} +// std::vector SubMesh::switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const +//{ +// const int8_t pt_id = get_primitive_type_id(pt); +// const int8_t max_pt_id = get_primitive_type_id(top_simplex_type(tuple)); +// +// if (pt_id > max_pt_id) { +// log_and_throw_error("Required PrimitiveType switch does not exist in submesh."); +// } +// if (pt_id < max_pt_id) { +// // log_and_throw_error("Cannot perform global switches for vertex PrimitiveType. Use " +// // "`switch_tuple` instead of `switch_tuple_vector`."); +// return {local_switch_tuple(tuple, pt)}; +// } +// +// assert(pt_id <= max_pt_id); +// +// const PrimitiveType pt_face = get_primitive_type_from_id(pt_id - 1); +// +// const simplex::Simplex s_face(mesh(), pt_face, tuple); +// +// std::vector neighs; +// neighs.reserve(2); +// for (const Tuple& t : simplex::cofaces_single_dimension_iterable(mesh(), s_face, pt)) { +// if (contains(t, pt)) { +// neighs.emplace_back(t); +// } +// } +// +// assert(!neighs.empty()); +// assert(neighs[0] == tuple); +// +// return neighs; +//} bool SubMesh::is_boundary(PrimitiveType pt, const Tuple& tuple) const { @@ -170,32 +187,23 @@ bool SubMesh::is_boundary(PrimitiveType pt, const Tuple& tuple) const log_and_throw_error("Cannot check for boundary if simplex is not contained in submesh"); } - const simplex::Simplex sim(pt, tuple); - // 1. get max dim in open star - int8_t top_dim = get_primitive_type_id(pt); - for (const simplex::IdSimplex& s : simplex::open_star_iterable(mesh(), sim)) { - if (!contains(s)) { - continue; - } - const int8_t dim = get_primitive_type_id(s.primitive_type()); - top_dim = std::max(top_dim, dim); - } + const simplex::Simplex s(pt, tuple); - if (top_dim == 0) { - // simplex is an isolated vertex + const PrimitiveType top_pt = top_simplex_type(); + if (top_pt == PrimitiveType::Vertex) { return true; } + const PrimitiveType face_pt = get_primitive_type_from_id(top_cell_dimension() - 1); - // 2. check if any incident max dim facet has less than two neighbors - const PrimitiveType top_pt = get_primitive_type_from_id(top_dim); - const PrimitiveType face_pt = get_primitive_type_from_id(top_dim - 1); + const auto neighbors = simplex::neighbors_single_dimension(mesh(), s, face_pt); - for (const simplex::Simplex& face_simplex : - simplex::neighbors_single_dimension(mesh(), sim, face_pt)) { - // const simplex::Simplex face_simplex(top_pt, face_tuple); + // check if any incident facet has less than two neighbors + int64_t n_neighbors = 0; + for (const simplex::Simplex& face_simplex : neighbors) { if (!contains(face_simplex)) { continue; } + ++n_neighbors; int64_t cell_counter = 0; for (const Tuple& cell_tuple : @@ -209,6 +217,11 @@ bool SubMesh::is_boundary(PrimitiveType pt, const Tuple& tuple) const } } + if (n_neighbors == 0) { + // this simplex has no cell incident and is therefore considered boundary + return true; + } + return false; } diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 81dab8132e..b49d10ac36 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -67,9 +67,11 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase */ Tuple switch_tuple(const Tuple& tuple, PrimitiveType pt) const override; - // call open_star_single_dimension if `pt` is larger or equal than max dim, use `switch_tuple` - // otherwise - std::vector switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const; + /** + * This function does not make sense. The proper way to navigate is using + * cofaces_single_dimension. + */ + // std::vector switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const; /** * @brief Is the given simplex on the boundary? diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index b0854ea5b8..41e791665c 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -25,6 +25,7 @@ constexpr PrimitiveType PT = PrimitiveType::Tetrahedron; TEST_CASE("submesh_init", "[mesh][submesh]") { // logger().set_level(spdlog::level::off); + logger().set_level(spdlog::level::trace); // basic test for implementing std::shared_ptr mesh_in = @@ -40,6 +41,8 @@ TEST_CASE("submesh_init", "[mesh][submesh]") CHECK_THROWS(sub.top_simplex_type()); sub.add_simplex(edge45, PE); + CHECK(sub.top_simplex_type() == PrimitiveType::Edge); + CHECK(sub.top_cell_dimension() == 1); CHECK(sub.contains(edge45, PE)); CHECK(sub.contains(edge45, PV)); @@ -57,42 +60,28 @@ TEST_CASE("submesh_init", "[mesh][submesh]") CHECK(sub.contains(sub.switch_tuple(edge34, PV), PV)); CHECK(sub.is_boundary(edge34, PV)); CHECK(!sub.is_boundary(sub.switch_tuple(edge34, PV), PV)); + CHECK(!sub.is_boundary(edge45, PV)); CHECK(sub.top_simplex_type(edge34) == PE); // switch from edge 45 to 43 { CHECK_THROWS(sub.switch_tuple(edge34, PE)); - CHECK_THROWS(sub.switch_tuple_vector(edge45, PV).size() == 1); - std::vector v4_edges; - REQUIRE_NOTHROW(v4_edges = sub.switch_tuple_vector(edge45, PE)); - CHECK(v4_edges.size() == 2); - CHECK(v4_edges[0] == edge45); - CHECK(m.get_id_simplex(v4_edges[1], PE) == m.get_id_simplex(edge34, PE)); - CHECK(m.get_id_simplex(v4_edges[1], PV) == m.get_id_simplex(edge45, PV)); + CHECK_NOTHROW(sub.switch_tuple(edge45, PE)); + Tuple sw_edge45; + REQUIRE_NOTHROW(sw_edge45 = sub.switch_tuple(edge45, PE)); // global switch + CHECK(m.get_id_simplex(sw_edge45, PE) == m.get_id_simplex(edge34, PE)); + CHECK(m.get_id_simplex(sw_edge45, PV) == m.get_id_simplex(edge45, PV)); } const Tuple edge04 = m.edge_tuple_from_vids(0, 4); sub.add_simplex(edge04, PE); CHECK(sub.is_boundary(edge04, PV)); CHECK(!sub.is_boundary(sub.switch_tuple(edge04, PV), PV)); - // switch from edge 45 to all others - { - std::vector v4_edges; - REQUIRE_NOTHROW(v4_edges = sub.switch_tuple_vector(edge45, PE)); - CHECK(v4_edges.size() == 3); - CHECK(v4_edges[0] == edge45); - for (const Tuple& t : v4_edges) { - CHECK(m.get_id_simplex(t, PV) == m.get_id_simplex(edge45, PV)); - } - } const Tuple edge54 = sub.switch_tuple(edge45, PV); - { - std::vector v5_edges; - REQUIRE_NOTHROW(v5_edges = sub.switch_tuple_vector(edge54, PE)); - CHECK(v5_edges.size() == 1); - CHECK(v5_edges[0] == edge54); - } + CHECK_THROWS(sub.switch_tuple(edge54, PE)); + CHECK(m.get_id_simplex(sub.switch_tuple(edge54, PV), PV) == m.get_id_simplex(edge45, PV)); + const Tuple face596 = m.face_tuple_from_vids(5, 9, 6); sub.add_simplex(face596, PF); @@ -100,21 +89,15 @@ TEST_CASE("submesh_init", "[mesh][submesh]") CHECK(sub.id(face596, PF) == 9); CHECK(sub.is_boundary(face596, PF)); CHECK(sub.is_boundary(m.vertex_tuple_from_id(5), PV)); + // after adding a face, all edges and vertices that are not incident to a face, are boundary + CHECK(sub.is_boundary(edge45, PE)); + CHECK(sub.is_boundary(edge45, PV)); const Tuple edge59 = m.edge_tuple_with_vs_and_t(5, 9, 9); { - std::vector v5_edges; - REQUIRE_NOTHROW(v5_edges = sub.switch_tuple_vector(edge59, PE)); - CHECK(v5_edges.size() == 3); - CHECK(v5_edges[0] == edge59); - for (const Tuple& t : v5_edges) { - CHECK(m.get_id_simplex(t, PV) == m.get_id_simplex(edge59, PV)); - } - - // local switch + // local edge switch CHECK_NOTHROW(sub.switch_tuple(edge59, PE)); - - // face switch - CHECK(sub.switch_tuple_vector(face596, PF).size() == 1); + // global face switch + CHECK_THROWS(sub.switch_tuple(face596, PF)); } From 3b129b10006e899eb22310403fcd172626c20c4f Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 6 Feb 2025 18:34:26 -0500 Subject: [PATCH 10/47] Tiny clean-up --- src/wmtk/multimesh/utils/extract_child_mesh_from_tag.cpp | 1 - src/wmtk/multimesh/utils/internal/TupleTag.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/wmtk/multimesh/utils/extract_child_mesh_from_tag.cpp b/src/wmtk/multimesh/utils/extract_child_mesh_from_tag.cpp index d64c2cbcd8..c5ef7a680f 100644 --- a/src/wmtk/multimesh/utils/extract_child_mesh_from_tag.cpp +++ b/src/wmtk/multimesh/utils/extract_child_mesh_from_tag.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include diff --git a/src/wmtk/multimesh/utils/internal/TupleTag.cpp b/src/wmtk/multimesh/utils/internal/TupleTag.cpp index d07824280c..cf719e355b 100644 --- a/src/wmtk/multimesh/utils/internal/TupleTag.cpp +++ b/src/wmtk/multimesh/utils/internal/TupleTag.cpp @@ -1,6 +1,6 @@ #include "TupleTag.hpp" +#include #include -#include namespace wmtk::multimesh::utils::internal { TupleTag::TupleTag(Mesh& mesh, const std::set& critical_points) : m_mesh(mesh) From cafb861783d718feeccd4cb02c7775833ef37214 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 7 Feb 2025 10:34:06 -0500 Subject: [PATCH 11/47] Make Embedding inherit from MeshBase. --- src/wmtk/Mesh.hpp | 3 ++- src/wmtk/MeshCRTP.hpp | 15 ++++++++------- src/wmtk/submesh/Embedding.cpp | 32 +++++++++++++++++++++++++++++++- src/wmtk/submesh/Embedding.hpp | 13 ++++++++++++- 4 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index c6aca799c6..29b77e6bb8 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -816,9 +816,10 @@ class Mesh : public std::enable_shared_from_this, int64_t id(const simplex::NavigatableSimplex& s) const { return s.index(); } int64_t id(const simplex::IdSimplex& s) const { return s.index(); } -protected: /// Forwarding version of id on simplices that does id caching virtual int64_t id(const simplex::Simplex& s) const override = 0; + +protected: /// Internal utility to allow id to be virtual with a non-virtual overload in derived -Mesh classes. /// Mesh::id invokes Mesh::id_virtual which is final overriden by MeshCRTP::id_virtual, which in turn invokes MeshCRTP::id, and then TriMesh::id. /// This circuitous mechanism makes MeshCRTP::id and TriMesh::id fully inlineable, so code that wants to take in any derived class can get optimized results with MeshCRTP, or for cases where classes want to utilize just TriMesh they can get inline/accelerated usage as well. diff --git a/src/wmtk/MeshCRTP.hpp b/src/wmtk/MeshCRTP.hpp index ba0f87f6dc..39242d52fb 100644 --- a/src/wmtk/MeshCRTP.hpp +++ b/src/wmtk/MeshCRTP.hpp @@ -98,18 +98,11 @@ class MeshCRTP : public Mesh return create_const_accessor(handle.as()); } -protected: /// Returns the id of a simplex encoded in a tuple int64_t id(const Tuple& tuple, PrimitiveType type) const override { return derived().id(tuple, type); } - /// internal utility for overriding the mesh class's id function without having the final override block the derived class's override - /// (we can't have Mesh::id be virtual, MeshCRTP::id final override, and TriMesh::id. This indirection pushes the final override to this other function - int64_t id_virtual(const Tuple& tuple, PrimitiveType type) const final override - { - return id(tuple, type); - } /// variant of id that can cache internally held values int64_t id(const simplex::Simplex& s) const final override @@ -120,6 +113,14 @@ class MeshCRTP : public Mesh // catch any other Mesh id methods that might emerge by default using Mesh::id; +protected: + /// internal utility for overriding the mesh class's id function without having the final override block the derived class's override + /// (we can't have Mesh::id be virtual, MeshCRTP::id final override, and TriMesh::id. This indirection pushes the final override to this other function + int64_t id_virtual(const Tuple& tuple, PrimitiveType type) const final override + { + return id(tuple, type); + } + protected: Tuple tuple_from_id(const PrimitiveType type, const int64_t gid) const override diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index ff32d75294..7203514cc2 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -60,7 +60,37 @@ attribute::TypedAttributeHandle& Embedding::tag_handle(const PrimitiveT attribute::Accessor Embedding::tag_accessor(const PrimitiveType pt) { - return m_mesh->create_accessor(m_tag_handle.at(pt)); + return mesh().create_accessor(m_tag_handle.at(pt)); +} + +std::vector Embedding::get_all(PrimitiveType type) const +{ + return mesh().get_all(type); +} + +int64_t Embedding::top_cell_dimension() const +{ + return mesh().top_cell_dimension(); +} + +Tuple Embedding::switch_tuple(const Tuple& tuple, PrimitiveType type) const +{ + return mesh().switch_tuple(tuple, type); +} + +bool Embedding::is_boundary(PrimitiveType pt, const Tuple& tuple) const +{ + return mesh().is_boundary(pt, tuple); +} + +int64_t Embedding::id(const simplex::Simplex& s) const +{ + return mesh().id(s); +} + +int64_t Embedding::id(const Tuple& tuple, PrimitiveType pt) const +{ + return mesh().id(tuple, pt); } diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index 0538721c74..dfc50a46cf 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -1,8 +1,10 @@ #pragma once #include +#include #include #include +#include #include #include @@ -14,7 +16,7 @@ class SubMesh; * The embedding is a wrapper for the embedding mesh for the submeshes. It contains a pointer to the * mesh, the attribute for the submesh tags, and a factory for SubMeshes. */ -class Embedding +class Embedding : public std::enable_shared_from_this, public MeshBase { public: Embedding(const std::shared_ptr& mesh); @@ -27,6 +29,15 @@ class Embedding attribute::TypedAttributeHandle& tag_handle(const PrimitiveType pt); attribute::Accessor tag_accessor(const PrimitiveType pt); + std::vector get_all(PrimitiveType type) const; + + int64_t top_cell_dimension() const; + + Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const; + bool is_boundary(PrimitiveType, const Tuple& tuple) const; + int64_t id(const simplex::Simplex& s) const; + int64_t id(const Tuple& tuple, PrimitiveType pt) const; + private: std::shared_ptr m_mesh; From a9d6d5176fec518defac32b64f2e1ce8b9185516 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 7 Feb 2025 12:28:49 -0500 Subject: [PATCH 12/47] Add filter to ParaviewWriter. --- src/wmtk/io/ParaviewWriter.cpp | 71 +++++++++++++++++----------------- src/wmtk/io/ParaviewWriter.hpp | 21 +++++++++- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/src/wmtk/io/ParaviewWriter.cpp b/src/wmtk/io/ParaviewWriter.cpp index 8f71e6c39a..c0f219173a 100644 --- a/src/wmtk/io/ParaviewWriter.cpp +++ b/src/wmtk/io/ParaviewWriter.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -96,7 +97,8 @@ ParaviewWriter::ParaviewWriter( bool write_points, bool write_edges, bool write_faces, - bool write_tetrahedra) + bool write_tetrahedra, + const std::function& filter) : m_vertices_name(vertices_name) { m_enabled[0] = write_points; @@ -106,41 +108,38 @@ ParaviewWriter::ParaviewWriter( std::array cells; - for (size_t i = 0; i < 4; ++i) { - const auto pt = PrimitiveType(i); - if (m_enabled[i]) { - // include deleted tuples so that attributes are aligned - const auto tuples = mesh.get_all(pt, true); - cells[i].resize(tuples.size(), i + 1); - - for (size_t j = 0; j < tuples.size(); ++j) { - const auto& t = tuples[j]; - if (t.is_null()) { - for (size_t d = 0; d < cells[i].cols(); ++d) { - cells[i](j, d) = 0; - } - } else { - int64_t vid = mesh.id(t, PrimitiveType::Vertex); - cells[i](j, 0) = vid; - if (i > 0) { - auto t1 = mesh.switch_tuple(t, PrimitiveType::Vertex); - - cells[i](j, 1) = mesh.id(t1, PrimitiveType::Vertex); - } - if (i > 1) { - auto t1 = mesh.switch_tuple(t, PrimitiveType::Edge); - auto t2 = mesh.switch_tuple(t1, PrimitiveType::Vertex); - - cells[i](j, 2) = mesh.id(t2, PrimitiveType::Vertex); - } - if (i > 2) { - auto t1 = mesh.switch_tuple(t, PrimitiveType::Triangle); - auto t2 = mesh.switch_tuple(t1, PrimitiveType::Edge); - auto t3 = mesh.switch_tuple(t2, PrimitiveType::Vertex); - - cells[i](j, 3) = mesh.id(t3, PrimitiveType::Vertex); - } - } + for (size_t dim = 0; dim < 4; ++dim) { + const PrimitiveType pt = PrimitiveType(dim); + if (!m_enabled[dim]) { + continue; + } + // include deleted simplices so that attributes are aligned + + const auto simplices = mesh.get_all_id_simplex(pt, true); + + cells[dim].resize(simplices.size(), dim + 1); + + for (int64_t s_id = 0; s_id < simplices.size(); ++s_id) { + const simplex::IdSimplex& s = simplices[s_id]; + + if (s.index() != s_id || (filter != nullptr && !filter(s))) { + // deleted simplex + cells[dim].row(s_id).setZero(); + continue; + } + + if (dim == 0) { + // vertex + cells[dim](s_id, 0) = s.index(); + continue; + } + + // edge, triangle, tetrahedron + const auto vertices = + simplex::faces_single_dimension(mesh, mesh.get_simplex(s), PrimitiveType::Vertex); + assert(vertices.size() == dim + 1); + for (int64_t i = 0; i < vertices.size(); ++i) { + cells[dim](s_id, i) = mesh.id(vertices.simplex_vector()[i]); } } } diff --git a/src/wmtk/io/ParaviewWriter.hpp b/src/wmtk/io/ParaviewWriter.hpp index 925bc2868d..9fb8c6cf41 100644 --- a/src/wmtk/io/ParaviewWriter.hpp +++ b/src/wmtk/io/ParaviewWriter.hpp @@ -4,6 +4,7 @@ #include #include +#include namespace paraviewo { class ParaviewWriter; @@ -11,6 +12,9 @@ class ParaviewWriter; namespace wmtk { class Mesh; +namespace simplex { +class IdSimplex; +} namespace io { class ParaviewWriter : public MeshWriter @@ -51,6 +55,20 @@ class ParaviewWriter : public MeshWriter }; public: + /** + * @brief Write in VTU format. + * + * The writer generates one file for each simplex dimension and attaches all the attributes for + * the corresponding simplex dimension. All higher dimensions also contain the simplex + * attributes. + * + * The writer stores ALL simplices, even those that are invalid. This helps with debugging, as + * the IDs in the VTU file correspond to those in the code. All invalid simplices will contain 0 + * vertex IDs, so in case one vertex looks strange in Paraview, that might be because of that. + * + * The filter function can be used to treat simplices as invalid ones. A simplex is treated as + * invalid if the filter function returns false. + */ ParaviewWriter( const std::filesystem::path& filename, const std::string& vertices_name, @@ -58,7 +76,8 @@ class ParaviewWriter : public MeshWriter bool write_points = true, bool write_edges = true, bool write_faces = true, - bool write_tetrahedra = true); + bool write_tetrahedra = true, + const std::function& filter = {}); bool write(const int dim) override { return dim == 0 || m_enabled[dim]; } From e024125a32138e672bde4f4842e314321641107e Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 7 Feb 2025 12:29:30 -0500 Subject: [PATCH 13/47] Add write util functions for SubMesh and Embedding. --- src/wmtk/Mesh.hpp | 2 +- src/wmtk/MeshBase.hpp | 1 + src/wmtk/submesh/CMakeLists.txt | 3 ++ src/wmtk/submesh/Embedding.cpp | 5 ++++ src/wmtk/submesh/Embedding.hpp | 3 +- src/wmtk/submesh/SubMesh.cpp | 12 ++++++++ src/wmtk/submesh/SubMesh.hpp | 1 + src/wmtk/submesh/utils/write.cpp | 51 ++++++++++++++++++++++++++++++++ src/wmtk/submesh/utils/write.hpp | 31 +++++++++++++++++++ tests/submesh/test_submesh.cpp | 35 ++++++++++++++++++++++ 10 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/wmtk/submesh/utils/write.cpp create mode 100644 src/wmtk/submesh/utils/write.hpp diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index 29b77e6bb8..7544f2e301 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -172,7 +172,7 @@ class Mesh : public std::enable_shared_from_this, */ std::vector get_all(PrimitiveType type) const override; - std::vector get_all_id_simplex(PrimitiveType type) const; + std::vector get_all_id_simplex(PrimitiveType type) const override; /** * @brief Retrieve the IdSimplex that is represented by the tuple and primitive type. */ diff --git a/src/wmtk/MeshBase.hpp b/src/wmtk/MeshBase.hpp index 66aab2855a..efb4caa127 100644 --- a/src/wmtk/MeshBase.hpp +++ b/src/wmtk/MeshBase.hpp @@ -20,6 +20,7 @@ class MeshBase virtual ~MeshBase() = default; virtual std::vector get_all(PrimitiveType type) const = 0; + virtual std::vector get_all_id_simplex(PrimitiveType type) const = 0; virtual int64_t top_cell_dimension() const = 0; diff --git a/src/wmtk/submesh/CMakeLists.txt b/src/wmtk/submesh/CMakeLists.txt index 7b2c985922..7ceb9298e8 100644 --- a/src/wmtk/submesh/CMakeLists.txt +++ b/src/wmtk/submesh/CMakeLists.txt @@ -3,5 +3,8 @@ set(SRC_FILES Embedding.cpp SubMesh.hpp SubMesh.cpp + + utils/write.hpp + utils/write.cpp ) target_sources(wildmeshing_toolkit PRIVATE ${SRC_FILES}) \ No newline at end of file diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index 7203514cc2..c505de0852 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -68,6 +68,11 @@ std::vector Embedding::get_all(PrimitiveType type) const return mesh().get_all(type); } +std::vector Embedding::get_all_id_simplex(PrimitiveType type) const +{ + return mesh().get_all_id_simplex(type); +} + int64_t Embedding::top_cell_dimension() const { return mesh().top_cell_dimension(); diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index dfc50a46cf..fdc13dbac6 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -29,7 +29,8 @@ class Embedding : public std::enable_shared_from_this, public MeshBas attribute::TypedAttributeHandle& tag_handle(const PrimitiveType pt); attribute::Accessor tag_accessor(const PrimitiveType pt); - std::vector get_all(PrimitiveType type) const; + std::vector get_all(PrimitiveType type) const override; + std::vector get_all_id_simplex(PrimitiveType type) const override; int64_t top_cell_dimension() const; diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index 03fbd7c7f3..f650565bd3 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -69,6 +69,18 @@ std::vector SubMesh::get_all(const PrimitiveType pt) const return sub; } +std::vector SubMesh::get_all_id_simplex(PrimitiveType pt) const +{ + const auto all = mesh().get_all_id_simplex(pt); + std::vector sub; + for (const simplex::IdSimplex& s : all) { + if (contains(s)) { + sub.emplace_back(s); + } + } + return sub; +} + PrimitiveType SubMesh::top_simplex_type(const Tuple& tuple) const { const Mesh& m = mesh(); diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 899dabc407..d5c48a9313 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -48,6 +48,7 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase void add_simplex(const simplex::IdSimplex& simplex); std::vector get_all(const PrimitiveType pt) const override; + std::vector get_all_id_simplex(PrimitiveType type) const override; /** * @brief Get the maximum primitive type that has a tag for a given tuple. diff --git a/src/wmtk/submesh/utils/write.cpp b/src/wmtk/submesh/utils/write.cpp new file mode 100644 index 0000000000..82850ca41b --- /dev/null +++ b/src/wmtk/submesh/utils/write.cpp @@ -0,0 +1,51 @@ +#include "write.hpp" + +#include +#include +#include +#include + +void wmtk::submesh::utils::write( + const std::filesystem::path& filename, + const std::string& vertices_name, + const SubMesh& sub, + bool write_points, + bool write_edges, + bool write_faces, + bool write_tetrahedra) +{ + const Mesh& m = sub.mesh(); + + ParaviewWriter writer( + filename, + vertices_name, + m, + write_points, + write_edges, + write_faces, + write_tetrahedra, + [&sub](const simplex::IdSimplex& s) { return sub.contains(s); }); + m.serialize(writer); +} + +void wmtk::submesh::utils::write( + const std::filesystem::path& filename, + const std::string& vertices_name, + const Embedding& emb, + bool write_points, + bool write_edges, + bool write_faces, + bool write_tetrahedra) +{ + const Mesh& m = emb.mesh(); + + ParaviewWriter writer( + filename, + vertices_name, + m, + write_points, + write_edges, + write_faces, + write_tetrahedra); + m.serialize(writer); +} diff --git a/src/wmtk/submesh/utils/write.hpp b/src/wmtk/submesh/utils/write.hpp new file mode 100644 index 0000000000..82e6e97f6e --- /dev/null +++ b/src/wmtk/submesh/utils/write.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace wmtk::submesh { +class SubMesh; +class Embedding; +} // namespace wmtk::submesh + + +namespace wmtk::submesh::utils { + +void write( + const std::filesystem::path& filename, + const std::string& vertices_name, + const SubMesh& sub, + bool write_points = true, + bool write_edges = true, + bool write_faces = true, + bool write_tetrahedra = true); + +void write( + const std::filesystem::path& filename, + const std::string& vertices_name, + const Embedding& emb, + bool write_points = true, + bool write_edges = true, + bool write_faces = true, + bool write_tetrahedra = true); + +} // namespace wmtk::submesh::utils diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 41e791665c..52b24be4f0 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "tools/DEBUG_TriMesh.hpp" @@ -156,4 +157,38 @@ TEST_CASE("submesh_top_dimension_cofaces", "[mesh][submesh]") CHECK_THROWS(sub.top_simplex_type()); sub.add_simplex(edge45, PE); +} + +TEST_CASE("submesh_init_multi", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + logger().set_level(spdlog::level::trace); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + const Tuple edge45 = m.edge_tuple_from_vids(4, 5); + + Embedding emb(mesh_in); + std::shared_ptr sub1_ptr = emb.add_submesh(); + SubMesh& sub1 = *sub1_ptr; + + std::shared_ptr sub2_ptr = emb.add_submesh(); + SubMesh& sub2 = *sub2_ptr; + + sub1.add_simplex(m.face_tuple_from_vids(0, 3, 4), PF); + sub1.add_simplex(m.face_tuple_from_vids(1, 4, 5), PF); + + sub2.add_simplex(m.face_tuple_from_vids(1, 4, 5), PF); + + { + using submesh::utils::write; + CHECK_NOTHROW(write("submesh_init_multi", "vertices", emb, false, false, true, false)); + CHECK_NOTHROW( + write("submesh_init_multi_sub1", "vertices", sub1, false, false, true, false)); + CHECK_NOTHROW( + write("submesh_init_multi_sub2", "vertices", sub2, false, false, true, false)); + } } \ No newline at end of file From 8f2c29fa9cdd23bdc0e2804ccb232c602192bd65 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 7 Feb 2025 17:26:40 -0500 Subject: [PATCH 14/47] Ignore removed fids when retrieving edge through vertex IDs. --- tests/tools/DEBUG_TriMesh.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/tools/DEBUG_TriMesh.cpp b/tests/tools/DEBUG_TriMesh.cpp index c2098bf5c5..13e62e518c 100644 --- a/tests/tools/DEBUG_TriMesh.cpp +++ b/tests/tools/DEBUG_TriMesh.cpp @@ -77,6 +77,9 @@ auto DEBUG_TriMesh::edge_tuple_from_vids(const int64_t v1, const int64_t v2) con const attribute::Accessor fv = create_const_accessor(m_fv_handle); auto fv_base = create_base_accessor(m_fv_handle); for (int64_t fid = 0; fid < capacity(PrimitiveType::Triangle); ++fid) { + if (is_removed(fid)) { + continue; + } Tuple face = face_tuple_from_id(fid); auto fv0 = fv.const_vector_attribute(face); int64_t local_vid1 = -1, local_vid2 = -1; @@ -89,11 +92,7 @@ auto DEBUG_TriMesh::edge_tuple_from_vids(const int64_t v1, const int64_t v2) con } } if (local_vid1 != -1 && local_vid2 != -1) { - return Tuple( - local_vid1, - (3 - local_vid1 - local_vid2) % 3, - -1, - fid); + return Tuple(local_vid1, (3 - local_vid1 - local_vid2) % 3, -1, fid); } } return Tuple(); @@ -158,8 +157,7 @@ void DEBUG_TriMesh::reserve_attributes(PrimitiveType type, int64_t size) } -auto DEBUG_TriMesh::get_tmoe(const Tuple& t) - -> TriMeshOperationExecutor +auto DEBUG_TriMesh::get_tmoe(const Tuple& t) -> TriMeshOperationExecutor { return TriMeshOperationExecutor(*this, t); } From d1123eace07511a44f1cf7d2ddeeb643b648e700 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 7 Feb 2025 17:43:24 -0500 Subject: [PATCH 15/47] Add necessary strategies for split and collapse. --- src/wmtk/submesh/Embedding.cpp | 135 +++++++++++++++++++++++++++- src/wmtk/submesh/Embedding.hpp | 30 ++++++- tests/submesh/test_submesh.cpp | 157 ++++++++++++++++++++++++++++++++- 3 files changed, 313 insertions(+), 9 deletions(-) diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index c505de0852..12b29cd12a 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -1,5 +1,8 @@ #include "Embedding.hpp" +#include +#include +#include #include #include @@ -26,20 +29,105 @@ Embedding::Embedding(const std::shared_ptr& mesh) } m_tag_handle[pt] = m.register_attribute_typed(m_tag_attribute_name[pt], pt, 1); + + + attribute::MeshAttributeHandle h(m, m_tag_handle.at(pt)); + + m_split_new[pt] = std::make_shared>(h); + auto& split_new_strat = *(m_split_new[pt]); + + split_new_strat.set_strategy(operations::SplitBasicStrategy::Copy); + split_new_strat.set_rib_strategy(operations::SplitRibBasicStrategy::None); + + m_collapse_new[pt] = std::make_shared>(h); + auto& collapse_new_strat = *(m_collapse_new[pt]); + + auto collapse_new_func = [](const VectorX& a, + const VectorX& b, + const std::bitset<2>&) -> VectorX { + VectorX r(a.rows()); + + assert(a.rows() == b.rows()); + assert(a.rows() == r.rows()); + + for (int64_t i = 0; i < a.rows(); ++i) { + r[i] = a[i] | b[i]; + } + + return r; + }; + collapse_new_strat.set_strategy(collapse_new_func); } + + + m_substructure_predicate = [this](const simplex::Simplex& s) -> bool { + return simplex_is_in_submesh(s); + }; + + auto update_tag_func = [this]( + const Eigen::MatrixX& P, + const std::vector& tuples) -> Eigen::VectorX { + // transfer from vertices (P.cols()) to top_simplex + assert(P.rows() == 1); // rows --> attribute dimension + // cols --> number of input simplices (vertices) + + const simplex::Simplex cell(m_mesh->top_simplex_type(), tuples[0]); + + assert(cell.primitive_type() != PrimitiveType::Vertex); + + // transfer from cell to facets + int64_t cell_tag; + { + auto tag_cell_acc = tag_accessor(cell.primitive_type()); + cell_tag = tag_cell_acc.const_scalar_attribute(cell); + + auto tag_facet_acc = tag_accessor(cell.primitive_type() - 1); + const auto facets = + simplex::faces_single_dimension_tuples(*m_mesh, cell, cell.primitive_type() - 1); + + for (const Tuple& f : facets) { + tag_facet_acc.scalar_attribute(f) |= cell_tag; + } + } + + if (cell.primitive_type() != PrimitiveType::Edge) { + // cell is triangle or tet + for (const PrimitiveType& pt : + utils::primitive_range(cell.primitive_type() - 1, PrimitiveType::Edge)) { + auto s_acc = tag_accessor(pt); + auto f_acc = tag_accessor(pt - 1); + const auto simplices = simplex::faces_single_dimension(*m_mesh, cell, pt); + for (const simplex::Simplex& s : simplices) { + const auto faces = simplex::faces_single_dimension_tuples(*m_mesh, s, pt - 1); + for (const Tuple& f : faces) { + f_acc.scalar_attribute(f) |= s_acc.const_scalar_attribute(s); + } + } + } + } + + return Eigen::VectorX::Constant(1, cell_tag); + }; + + attribute::MeshAttributeHandle h_v(m, m_tag_handle.at(PrimitiveType::Vertex)); + attribute::MeshAttributeHandle h_c(m, m_tag_handle.at(m.top_simplex_type())); + + m_transfer = + std::make_shared>( + h_c, + h_v, + update_tag_func); } std::shared_ptr Embedding::add_submesh() { - if (m_submesh_counter == 63) { + if (m_submeshes.size() == 63) { log_and_throw_error("An embedding can only hold up to 63 submeshes"); } - std::shared_ptr sub = std::make_shared(*this, m_submesh_counter); + std::shared_ptr sub = std::make_shared(*this, m_submeshes.size()); m_submeshes.emplace_back(sub); - ++m_submesh_counter; - return sub; } @@ -63,6 +151,11 @@ attribute::Accessor Embedding::tag_accessor(const PrimitiveType pt) return mesh().create_accessor(m_tag_handle.at(pt)); } +const attribute::Accessor Embedding::tag_accessor(const PrimitiveType pt) const +{ + return mesh().create_const_accessor(m_tag_handle.at(pt)); +} + std::vector Embedding::get_all(PrimitiveType type) const { return mesh().get_all(type); @@ -98,5 +191,39 @@ int64_t Embedding::id(const Tuple& tuple, PrimitiveType pt) const return mesh().id(tuple, pt); } +bool Embedding::has_child_mesh() const +{ + return !m_submeshes.empty(); +} + +bool Embedding::simplex_is_in_submesh(const simplex::Simplex& s) const +{ + const auto acc = tag_accessor(s.primitive_type()); + return acc.const_scalar_attribute(s.tuple()) > 0; +} + +void Embedding::set_split_strategies(operations::EdgeSplit& split) const +{ + for (const auto& [pt, strat] : m_split_new) { + attribute::MeshAttributeHandle h(*m_mesh, m_tag_handle.at(pt)); + split.set_new_attribute_strategy(h, strat); + } + + split.add_transfer_strategy(m_transfer); +} + +void Embedding::set_collapse_strategies(operations::EdgeCollapse& collapse) const +{ + for (const auto& [pt, strat] : m_collapse_new) { + attribute::MeshAttributeHandle h(*m_mesh, m_tag_handle.at(pt)); + collapse.set_new_attribute_strategy(h, strat); + } +} + +std::function Embedding::substructure_predicate() const +{ + return m_substructure_predicate; +} + } // namespace wmtk::submesh diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index fdc13dbac6..3941104b5d 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -7,6 +8,14 @@ #include #include #include +#include +#include +#include + +namespace wmtk::operations { +class EdgeSplit; +class EdgeCollapse; +} // namespace wmtk::operations namespace wmtk::submesh { @@ -28,6 +37,7 @@ class Embedding : public std::enable_shared_from_this, public MeshBas attribute::TypedAttributeHandle& tag_handle(const PrimitiveType pt); attribute::Accessor tag_accessor(const PrimitiveType pt); + const attribute::Accessor tag_accessor(const PrimitiveType pt) const; std::vector get_all(PrimitiveType type) const override; std::vector get_all_id_simplex(PrimitiveType type) const override; @@ -39,6 +49,16 @@ class Embedding : public std::enable_shared_from_this, public MeshBas int64_t id(const simplex::Simplex& s) const; int64_t id(const Tuple& tuple, PrimitiveType pt) const; + bool has_child_mesh() const; + + bool simplex_is_in_submesh(const simplex::Simplex& s) const; + + void set_split_strategies(operations::EdgeSplit& split) const; + + void set_collapse_strategies(operations::EdgeCollapse& collapse) const; + + std::function substructure_predicate() const; + private: std::shared_ptr m_mesh; @@ -46,7 +66,15 @@ class Embedding : public std::enable_shared_from_this, public MeshBas std::map> m_tag_handle; std::vector> m_submeshes; - int64_t m_submesh_counter = 0; + + std::map>> + m_split_new; + std::map>> + m_collapse_new; + + std::shared_ptr> m_transfer; + + std::function m_substructure_predicate; }; } // namespace wmtk::submesh \ No newline at end of file diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 52b24be4f0..26b1ecb402 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -1,9 +1,10 @@ #include -#include #include -#include #include +#include +#include +#include #include #include #include @@ -13,7 +14,6 @@ #include "tools/DEBUG_TriMesh.hpp" #include "tools/TriMesh_examples.hpp" -#include "tools/DEBUG_EdgeMesh.hpp" using namespace wmtk; using namespace submesh; @@ -169,15 +169,18 @@ TEST_CASE("submesh_init_multi", "[mesh][submesh]") std::make_shared(tests::edge_region_with_position()); tests::DEBUG_TriMesh& m = *mesh_in; - const Tuple edge45 = m.edge_tuple_from_vids(4, 5); Embedding emb(mesh_in); + CHECK_FALSE(emb.has_child_mesh()); + std::shared_ptr sub1_ptr = emb.add_submesh(); SubMesh& sub1 = *sub1_ptr; std::shared_ptr sub2_ptr = emb.add_submesh(); SubMesh& sub2 = *sub2_ptr; + CHECK(emb.has_child_mesh()); + sub1.add_simplex(m.face_tuple_from_vids(0, 3, 4), PF); sub1.add_simplex(m.face_tuple_from_vids(1, 4, 5), PF); @@ -191,4 +194,150 @@ TEST_CASE("submesh_init_multi", "[mesh][submesh]") CHECK_NOTHROW( write("submesh_init_multi_sub2", "vertices", sub2, false, false, true, false)); } +} + +TEST_CASE("submesh_split", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + // logger().set_level(spdlog::level::trace); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + + const auto pos = m.get_attribute_handle("vertices", PrimitiveType::Vertex); + + Embedding emb(mesh_in); + CHECK_FALSE(emb.has_child_mesh()); + + std::shared_ptr sub1_ptr = emb.add_submesh(); + sub1_ptr->add_simplex(m.face_tuple_from_vids(1, 4, 5), PF); + + std::shared_ptr sub2_ptr = emb.add_submesh(); + sub2_ptr->add_simplex(m.face_tuple_from_vids(4, 5, 8), PF); + + CHECK(emb.has_child_mesh()); + + + operations::EdgeSplit split(m); + emb.set_split_strategies(split); + split.set_new_attribute_strategy(pos); + + const simplex::Simplex edge45(PE, m.edge_tuple_from_vids(4, 5)); + split(edge45); + + CHECK(sub1_ptr->contains(m.vertex_tuple_from_id(4), PV)); + CHECK(sub1_ptr->contains(m.edge_tuple_from_vids(4, 10), PE)); + CHECK(sub1_ptr->contains(m.edge_tuple_from_vids(5, 10), PE)); + CHECK(sub1_ptr->contains(m.face_tuple_from_vids(4, 10, 1), PF)); + CHECK(sub1_ptr->contains(m.face_tuple_from_vids(5, 10, 1), PF)); + CHECK(sub2_ptr->contains(m.vertex_tuple_from_id(4), PV)); + CHECK(sub2_ptr->contains(m.edge_tuple_from_vids(4, 10), PE)); + CHECK(sub2_ptr->contains(m.edge_tuple_from_vids(5, 10), PE)); + CHECK(sub2_ptr->contains(m.face_tuple_from_vids(4, 10, 8), PF)); + CHECK(sub2_ptr->contains(m.face_tuple_from_vids(5, 10, 8), PF)); + + { + using submesh::utils::write; + CHECK_NOTHROW(write("submesh_split", "vertices", emb, true, true, true, false)); + } +} + +TEST_CASE("submesh_collapse", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + // logger().set_level(spdlog::level::trace); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + + const auto pos = m.get_attribute_handle("vertices", PrimitiveType::Vertex); + + Embedding emb(mesh_in); + CHECK_FALSE(emb.has_child_mesh()); + + std::shared_ptr sub1_ptr = emb.add_submesh(); + sub1_ptr->add_simplex(m.face_tuple_from_vids(1, 4, 5), PF); + + std::shared_ptr sub2_ptr = emb.add_submesh(); + sub2_ptr->add_simplex(m.face_tuple_from_vids(4, 5, 8), PF); + + CHECK(emb.has_child_mesh()); + + + operations::EdgeCollapse collapse(m); + emb.set_collapse_strategies(collapse); + collapse.set_new_attribute_strategy(pos); + + const simplex::Simplex edge45(PE, m.edge_tuple_from_vids(4, 5)); + collapse(edge45); + + CHECK(sub1_ptr->contains(m.vertex_tuple_from_id(5), PV)); + CHECK(sub1_ptr->contains(m.edge_tuple_from_vids(5, 1), PE)); + CHECK(sub2_ptr->contains(m.vertex_tuple_from_id(5), PV)); + CHECK(sub2_ptr->contains(m.edge_tuple_from_vids(5, 8), PE)); + + { + using submesh::utils::write; + CHECK_NOTHROW(write("submesh_collapse", "vertices", emb, true, true, true, false)); + } +} + +TEST_CASE("submesh_collapse_towards_submesh", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + // logger().set_level(spdlog::level::trace); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + + const auto pos = m.get_attribute_handle("vertices", PrimitiveType::Vertex); + const auto pos_acc = m.create_const_accessor(pos); + + Embedding emb(mesh_in); + CHECK_FALSE(emb.has_child_mesh()); + + std::shared_ptr sub1_ptr = emb.add_submesh(); + sub1_ptr->add_simplex(m.edge_tuple_from_vids(1, 4), PE); + sub1_ptr->add_simplex(m.edge_tuple_from_vids(4, 8), PE); + + // std::shared_ptr sub2_ptr = emb.add_submesh(); + // sub2_ptr->add_simplex(m.face_tuple_from_vids(4, 5, 8), PF); + Eigen::VectorXd p4 = pos_acc.const_vector_attribute(m.vertex_tuple_from_id(4)); + Eigen::VectorXd p5 = pos_acc.const_vector_attribute(m.vertex_tuple_from_id(5)); + CHECK(p4 != p5); + + CHECK(emb.has_child_mesh()); + + + operations::EdgeCollapse collapse(m); + auto clps_strat = std::make_shared>(pos); + clps_strat->set_simplex_predicate(emb.substructure_predicate()); + clps_strat->set_strategy(operations::CollapseBasicStrategy::Default); + + emb.set_collapse_strategies(collapse); + collapse.set_new_attribute_strategy(pos, clps_strat); + + const simplex::Simplex edge45(PE, m.edge_tuple_from_vids(4, 5)); + collapse(edge45); + + CHECK(sub1_ptr->contains(m.vertex_tuple_from_id(5), PV)); + CHECK(sub1_ptr->contains(m.edge_tuple_from_vids(5, 1), PE)); + + p5 = pos_acc.const_vector_attribute(m.vertex_tuple_from_id(5)); + CHECK(p4 == p5); + + { + using submesh::utils::write; + CHECK_NOTHROW( + write("submesh_collapse_towards_submesh", "vertices", emb, true, true, true, false)); + } } \ No newline at end of file From b4c24d36242d9dad3e48afcca77bbf50583d7322 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 11 Feb 2025 10:16:08 -0500 Subject: [PATCH 16/47] Add operation constructors for Embedding. --- src/wmtk/operations/EdgeCollapse.cpp | 8 ++++++++ src/wmtk/operations/EdgeCollapse.hpp | 5 +++++ src/wmtk/operations/EdgeSplit.cpp | 7 +++++++ src/wmtk/operations/EdgeSplit.hpp | 5 +++++ tests/submesh/test_submesh.cpp | 10 ++++++---- 5 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/wmtk/operations/EdgeCollapse.cpp b/src/wmtk/operations/EdgeCollapse.cpp index 78ad76851f..f5f455d533 100644 --- a/src/wmtk/operations/EdgeCollapse.cpp +++ b/src/wmtk/operations/EdgeCollapse.cpp @@ -8,6 +8,8 @@ #include #include "utils/multi_mesh_edge_collapse.hpp" +#include + namespace wmtk::operations { @@ -53,6 +55,12 @@ EdgeCollapse::EdgeCollapse(Mesh& m) custom_attribute_collector.execute_from_root(m); } +EdgeCollapse::EdgeCollapse(submesh::Embedding& m) + : EdgeCollapse(m.mesh()) +{ + m.set_collapse_strategies(*this); +} + std::vector EdgeCollapse::execute(const simplex::Simplex& simplex) { return utils::multi_mesh_edge_collapse_with_modified_simplices( diff --git a/src/wmtk/operations/EdgeCollapse.hpp b/src/wmtk/operations/EdgeCollapse.hpp index 4b92136f59..da409afe2c 100644 --- a/src/wmtk/operations/EdgeCollapse.hpp +++ b/src/wmtk/operations/EdgeCollapse.hpp @@ -3,12 +3,17 @@ #include "Operation.hpp" #include "attribute_new/CollapseNewAttributeStrategy.hpp" +namespace wmtk::submesh { +class Embedding; +} + namespace wmtk::operations { class EdgeCollapse : public Operation { public: // constructor for default factory pattern construction EdgeCollapse(Mesh& m); + EdgeCollapse(submesh::Embedding& m); PrimitiveType primitive_type() const override { return PrimitiveType::Edge; } diff --git a/src/wmtk/operations/EdgeSplit.cpp b/src/wmtk/operations/EdgeSplit.cpp index eae7e549c6..d81615cfb8 100644 --- a/src/wmtk/operations/EdgeSplit.cpp +++ b/src/wmtk/operations/EdgeSplit.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,12 @@ EdgeSplit::EdgeSplit(Mesh& m) custom_attribute_collector.execute_from_root(m); } +EdgeSplit::EdgeSplit(submesh::Embedding& m) + : EdgeSplit(m.mesh()) +{ + m.set_split_strategies(*this); +} + std::vector EdgeSplit::execute(const simplex::Simplex& simplex) { return utils::multi_mesh_edge_split_with_modified_simplices( diff --git a/src/wmtk/operations/EdgeSplit.hpp b/src/wmtk/operations/EdgeSplit.hpp index 15a0e35b27..75d99a86a8 100644 --- a/src/wmtk/operations/EdgeSplit.hpp +++ b/src/wmtk/operations/EdgeSplit.hpp @@ -4,11 +4,16 @@ #include "attribute_new/SplitNewAttributeStrategy.hpp" +namespace wmtk::submesh { +class Embedding; +} + namespace wmtk::operations { class EdgeSplit : public Operation { public: EdgeSplit(Mesh& m); + EdgeSplit(submesh::Embedding& m); PrimitiveType primitive_type() const override { return PrimitiveType::Edge; } diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 26b1ecb402..37f69e8449 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -221,8 +221,9 @@ TEST_CASE("submesh_split", "[mesh][submesh]") CHECK(emb.has_child_mesh()); - operations::EdgeSplit split(m); - emb.set_split_strategies(split); + // operations::EdgeSplit split(m); + // emb.set_split_strategies(split); + operations::EdgeSplit split(emb); split.set_new_attribute_strategy(pos); const simplex::Simplex edge45(PE, m.edge_tuple_from_vids(4, 5)); @@ -270,8 +271,9 @@ TEST_CASE("submesh_collapse", "[mesh][submesh]") CHECK(emb.has_child_mesh()); - operations::EdgeCollapse collapse(m); - emb.set_collapse_strategies(collapse); + // operations::EdgeCollapse collapse(m); + // emb.set_collapse_strategies(collapse); + operations::EdgeCollapse collapse(emb); collapse.set_new_attribute_strategy(pos); const simplex::Simplex edge45(PE, m.edge_tuple_from_vids(4, 5)); From 85847f711e98e1cec479681600ecd9749c5ea01d Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 11 Feb 2025 15:04:19 -0500 Subject: [PATCH 17/47] Add collapse test. --- tests/submesh/test_submesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 37f69e8449..fbeb6ac341 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -320,12 +320,12 @@ TEST_CASE("submesh_collapse_towards_submesh", "[mesh][submesh]") CHECK(emb.has_child_mesh()); - operations::EdgeCollapse collapse(m); + operations::EdgeCollapse collapse(emb); + // emb.set_collapse_strategies(collapse); auto clps_strat = std::make_shared>(pos); clps_strat->set_simplex_predicate(emb.substructure_predicate()); clps_strat->set_strategy(operations::CollapseBasicStrategy::Default); - emb.set_collapse_strategies(collapse); collapse.set_new_attribute_strategy(pos, clps_strat); const simplex::Simplex edge45(PE, m.edge_tuple_from_vids(4, 5)); From ab2c4ddec5860a28a55f80e787542955e937977d Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 13 Feb 2025 15:10:56 -0500 Subject: [PATCH 18/47] Prepare triwild based on submesh. --- applications/CMakeLists.txt | 1 + applications/triwild_submesh/CMakeLists.txt | 19 ++ applications/triwild_submesh/triwild_grid.cpp | 66 +++++ applications/triwild_submesh/triwild_grid.hpp | 14 + .../triwild_submesh/triwild_submesh_main.cpp | 240 ++++++++++++++++++ .../triwild_submesh/triwild_submesh_spec.hpp | 96 +++++++ .../triwild_submesh_test_config.json | 9 + src/wmtk/attribute/AttributePrototypeDZ.hpp | 116 +++++++++ 8 files changed, 561 insertions(+) create mode 100644 applications/triwild_submesh/CMakeLists.txt create mode 100644 applications/triwild_submesh/triwild_grid.cpp create mode 100644 applications/triwild_submesh/triwild_grid.hpp create mode 100644 applications/triwild_submesh/triwild_submesh_main.cpp create mode 100644 applications/triwild_submesh/triwild_submesh_spec.hpp create mode 100644 applications/triwild_submesh/triwild_submesh_test_config.json create mode 100644 src/wmtk/attribute/AttributePrototypeDZ.hpp diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt index e4a06d0c4b..011d2f74d7 100644 --- a/applications/CMakeLists.txt +++ b/applications/CMakeLists.txt @@ -30,6 +30,7 @@ add_application(isotropic_remeshing OFF) add_application(tetwild_msh_converter ON) add_application(tetwild_simplification ON) add_application(triwild ON) +add_application(triwild_submesh ON) add_application(tetwild ON) add_application(cdt_opt ON) add_application(shortest_edge_collapse ON) diff --git a/applications/triwild_submesh/CMakeLists.txt b/applications/triwild_submesh/CMakeLists.txt new file mode 100644 index 0000000000..ffd800d9d8 --- /dev/null +++ b/applications/triwild_submesh/CMakeLists.txt @@ -0,0 +1,19 @@ +wmtk_add_application(triwild_submesh_app + triwild_submesh_main.cpp + triwild_submesh_spec.hpp + triwild_grid.hpp + triwild_grid.cpp + ) + +target_link_libraries(triwild_submesh_app PRIVATE +wmtk::input +wmtk::procedural +wmtk::edge_insertion +wmtk::wildmeshing +wmtk::winding_number +wmtk::output) + +# wmtk_register_integration_test(EXEC_NAME triwild_submesh_app +# CONFIG_FILE ${CMAKE_CURRENT_SOURCE_DIR}/triwild_test_config.json +# GIT_REPOSITORY "https://github.com/wildmeshing/data.git" +# GIT_TAG 82ea9d55c901bbc86d48de39383e9c85d8f67686) diff --git a/applications/triwild_submesh/triwild_grid.cpp b/applications/triwild_submesh/triwild_grid.cpp new file mode 100644 index 0000000000..ccf52c50ca --- /dev/null +++ b/applications/triwild_submesh/triwild_grid.cpp @@ -0,0 +1,66 @@ +#include "triwild_grid.hpp" + +#include +#include + +namespace wmtk::triwild { + +wmtk::TriMesh generate_bg_grid( + const double x_min, + const double y_min, + const double x_max, + const double y_max, + const double length_rel, + const double margin_eps) +{ + const double input_diag = + (Eigen::Vector2d(x_min, y_min) - Eigen::Vector2d(x_max, y_max)).norm(); + const double target_length = length_rel * input_diag; + const double y_start = y_min - input_diag * margin_eps; + const double x_start = x_min - input_diag * margin_eps; + const double y_end = y_max + input_diag * margin_eps; + const double x_end = x_max + input_diag * margin_eps; + + const int64_t x_grid_size = floor((x_end - x_start) / target_length); + const int64_t y_grid_size = floor((y_end - y_start) / target_length); + const double x_space = (x_end - x_start) / x_grid_size; + const double y_space = (y_end - y_start) / y_grid_size; + + Eigen::MatrixX V; + V.resize((x_grid_size + 1) * (y_grid_size + 1), 2); + + for (int64_t i = 0; i < y_grid_size + 1; ++i) { + for (int64_t j = 0; j < x_grid_size + 1; ++j) { + // V(i * (x_grid_size + 1) + j, 0) = x_min + j * x_space; + // V(i * (x_grid_size + 1) + j, 1) = y_min + i * y_space; + V.row(i * (x_grid_size + 1) + j) << x_start + j * x_space, y_start + i * y_space; + } + } + + RowVectors3l F; + F.resize(x_grid_size * y_grid_size * 2, 3); + + for (int64_t i = 0; i < y_grid_size; ++i) { + for (int64_t j = 0; j < x_grid_size; ++j) { + F.row((i * x_grid_size + j) * 2) << i * (x_grid_size + 1) + j, + i * (x_grid_size + 1) + j + 1, (i + 1) * (x_grid_size + 1) + j + 1; + + // std::cout << (i * x_grid_size + j) * 2 << ": " << i * x_grid_size + j << " " + // << i * x_grid_size + j + 1 << " " << (i + 1) * x_grid_size + j + 1 + // << std::endl; + + F.row((i * x_grid_size + j) * 2 + 1) << i * (x_grid_size + 1) + j, + (i + 1) * (x_grid_size + 1) + j + 1, (i + 1) * (x_grid_size + 1) + j; + } + } + + // std::cout << F << std::endl; + + wmtk::TriMesh mesh; + mesh.initialize(F, false); + wmtk::mesh_utils::set_matrix_attribute(V, "vertices", PrimitiveType::Vertex, mesh); + + return mesh; +} + +} // namespace wmtk::triwild \ No newline at end of file diff --git a/applications/triwild_submesh/triwild_grid.hpp b/applications/triwild_submesh/triwild_grid.hpp new file mode 100644 index 0000000000..3e2147f940 --- /dev/null +++ b/applications/triwild_submesh/triwild_grid.hpp @@ -0,0 +1,14 @@ +#include +#include + +namespace wmtk::triwild { + +wmtk::TriMesh generate_bg_grid( + const double x_min, + const double y_min, + const double x_max, + const double y_max, + const double length_rel, + const double margin_eps = 0.1); + +} // namespace wmtk::triwild \ No newline at end of file diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp new file mode 100644 index 0000000000..08177ac0f3 --- /dev/null +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "triwild_grid.hpp" +#include "triwild_submesh_spec.hpp" + +using namespace wmtk; +using namespace wmtk::components; +namespace fs = std::filesystem; + +using wmtk::components::utils::resolve_paths; + +int main(int argc, char* argv[]) +{ + opt_logger().set_level(spdlog::level::off); + CLI::App app{argv[0]}; + + app.ignore_case(); + + fs::path json_input_file; + app.add_option("-j, --json", json_input_file, "json specification file") + ->required(true) + ->check(CLI::ExistingFile); + CLI11_PARSE(app, argc, argv); + + nlohmann::json j; + { + std::ifstream ifs(json_input_file); + j = nlohmann::json::parse(ifs); + + jse::JSE spec_engine; + bool r = spec_engine.verify_json(j, triwild_submesh_spec); + if (!r) { + wmtk::logger().error("{}", spec_engine.log2str()); + return 1; + } else { + j = spec_engine.inject_defaults(j, triwild_submesh_spec); + } + } + + fs::path input_file = resolve_paths(json_input_file, {j["root"], j["input"]}); + + auto mesh = wmtk::components::input::input(input_file, true); + wmtk::logger().info( + "mesh has {} vertices and {} edges", + mesh->get_all(PrimitiveType::Vertex).size(), + mesh->get_all(PrimitiveType::Edge).size()); + + // TODO: use procedural + + // get bbox; + double x_min, y_min, x_max, y_max; + x_min = std::numeric_limits::max(); + y_min = std::numeric_limits::max(); + x_max = std::numeric_limits::lowest(); + y_max = std::numeric_limits::lowest(); + + auto mesh_pt_handle = mesh->get_attribute_handle("vertices", PrimitiveType::Vertex); + auto mesh_pt_accessor = mesh->create_const_accessor(mesh_pt_handle); + + for (const auto& v : mesh->get_all(PrimitiveType::Vertex)) { + x_min = std::min(x_min, mesh_pt_accessor.const_vector_attribute(v)[0]); + x_max = std::max(x_max, mesh_pt_accessor.const_vector_attribute(v)[0]); + y_min = std::min(y_min, mesh_pt_accessor.const_vector_attribute(v)[1]); + y_max = std::max(y_max, mesh_pt_accessor.const_vector_attribute(v)[1]); + } + + // const double diag = (Eigen::Vector2d(x_min, y_min) - Eigen::Vector2d(x_max, y_max)).norm(); + // const double eps = 0.2; // TODO: change with length_rel + // x_min -= diag * eps; + // y_min -= diag * eps; + // x_max += diag * eps; + // y_max += diag * eps; + + // MatrixX V; + // V.resize(4, 2); + // V.row(0) = Vector2r(x_min, y_min); + // V.row(1) = Vector2r(x_max, y_min); + // V.row(2) = Vector2r(x_max, y_max); + // V.row(3) = Vector2r(x_min, y_max); + + // RowVectors3l F; + // F.resize(2, 3); + // F.row(0) << 0, 1, 2; + // F.row(1) << 0, 2, 3; + + // wmtk::TriMesh bg_mesh; + // bg_mesh.initialize(F, false); + // mesh_utils::set_matrix_attribute(V, "vertices", PrimitiveType::Vertex, bg_mesh); + + auto bg_mesh = + wmtk::triwild::generate_bg_grid(x_min, y_min, x_max, y_max, j["target_edge_length"]); + + wmtk::components::output::output(bg_mesh, "bg_mesh", "vertices"); + + wmtk::logger().info("generated bg mesh"); + + // auto em = static_cast(*mesh); + + wmtk::components::EdgeInsertionMeshes eim = + wmtk::components::edge_insertion(static_cast(*mesh), bg_mesh); + + wmtk::logger().info("finised edge insertion"); + + std::vector pass_through; + auto trimesh = eim.tri_mesh; + auto edgemesh = eim.inserted_edge_mesh; + auto bboxmesh = eim.bbox_mesh; + + std::string output_file = j["output"]; + + wmtk::components::output::output(*trimesh, output_file + "_after_insertion", "vertices"); + wmtk::components::output::output( + *edgemesh, + output_file + "_after_insertion_edge_mesh", + "vertices"); + + auto input_handle = trimesh->get_attribute_handle("input", PrimitiveType::Edge); + pass_through.push_back(input_handle); + auto bbox_handle = trimesh->get_attribute_handle("bbox", PrimitiveType::Edge); + pass_through.push_back(bbox_handle); + + + // TODO: add open vertex boundary + std::vector enves; + + wmtk::components::EnvelopeOptions e; + e.envelope_name = "input"; + e.envelope_constrained_mesh = edgemesh; + e.envelope_geometry_mesh = edgemesh; + e.constrained_position_name = "vertices"; + e.geometry_position_name = "vertices"; + e.thickness = j["envelope_size"]; + + if (e.envelope_name == "input") { + e.envelope_geometry_mesh = mesh; // set as input + } + + enves.push_back(e); + + wmtk::components::EnvelopeOptions e2; + e2.envelope_name = "bbox"; + e2.envelope_constrained_mesh = bboxmesh; + e2.envelope_geometry_mesh = bboxmesh; + e2.constrained_position_name = "vertices"; + e2.geometry_position_name = "vertices"; + e2.thickness = 0.0001; + + enves.push_back(e2); + + + wmtk::components::WildMeshingOptions wmo; + wmo.input_mesh = trimesh; + wmo.input_mesh_position = "vertices"; + wmo.target_edge_length = j["target_edge_length"]; + wmo.target_max_amips = j["target_max_amips"]; + wmo.max_passes = j["max_passes"]; + wmo.intermediate_output = j["intermediate_output"]; + wmo.replace_double_coordinate = false; + wmo.scheduler_update_frequency = 0; + wmo.intermediate_output_path = ""; + wmo.intermediate_output_name = j["output"]; + wmo.envelopes = enves; + wmo.pass_through = pass_through; + wmo.skip_split = j["skip_split"]; + wmo.skip_collapse = j["skip_collapse"]; + wmo.skip_swap = j["skip_swap"]; + wmo.skip_smooth = j["skip_smooth"]; + + auto meshes_after_tetwild = wildmeshing(wmo); + auto main_mesh = meshes_after_tetwild[0].first; + + wmtk::components::output::output(*main_mesh, output_file, "vertices"); + + std::shared_ptr input_mesh; + for (int64_t i = 1; i < meshes_after_tetwild.size(); ++i) { + // output child meshes + wmtk::components::output::output( + *(meshes_after_tetwild[i].first), + output_file + "_" + meshes_after_tetwild[i].second, + "vertices"); + + if (meshes_after_tetwild[i].second == "input") { + input_mesh = meshes_after_tetwild[i].first; + } + } + + const std::string report = j["report"]; + if (!report.empty()) { + nlohmann::json out_json; + out_json["vertices"] = main_mesh->get_all(PrimitiveType::Vertex).size(); + out_json["edges"] = main_mesh->get_all(PrimitiveType::Edge).size(); + out_json["cells"] = main_mesh->get_all(PrimitiveType::Triangle).size(); + + out_json["input"] = j; + + std::ofstream ofs(report); + ofs << out_json; + } + + // auto mesh_after_winding_number = winding_number(main_mesh, input_mesh); + + // wmtk::components::output(*mesh_after_winding_number, output_file, "vertices"); + + // const std::string report = j["report"]; + // if (!report.empty()) { + // nlohmann::json out_json; + // out_json["vertices"] = mesh_after_winding_number->get_all(PrimitiveType::Vertex).size(); + // out_json["edges"] = mesh_after_winding_number->get_all(PrimitiveType::Edge).size(); + // out_json["faces"] = mesh_after_winding_number->get_all(PrimitiveType::Triangle).size(); + // out_json["cells"] = + // mesh_after_winding_number->get_all(PrimitiveType::Tetrahedron).size(); + + // out_json["input"] = j; + + // std::ofstream ofs(report); + // ofs << out_json; + // } + + return 0; +} \ No newline at end of file diff --git a/applications/triwild_submesh/triwild_submesh_spec.hpp b/applications/triwild_submesh/triwild_submesh_spec.hpp new file mode 100644 index 0000000000..8d4659062a --- /dev/null +++ b/applications/triwild_submesh/triwild_submesh_spec.hpp @@ -0,0 +1,96 @@ +#pragma once +#include +namespace { + +nlohmann::json triwild_submesh_spec = R"( +[ + { + "pointer": "/", + "type": "object", + "required": ["input", "output"], + "optional": [ + "root", + "report", + "envelope_size", + "target_edge_length", + "target_max_amips", + "max_passes", + "intermediate_output", + "skip_split", + "skip_collapse", + "skip_swap", + "skip_smooth" + ] +}, +{ + "pointer": "/input", + "type": "string" +}, +{ + "pointer": "/envelope_size", + "type": "float", + "default": 0.001 +}, +{ + "pointer": "/length_rel", + "type": "float", + "default": 0.1 +}, +{ + "pointer": "/intermediate_output", + "type": "bool", + "default": false +}, +{ + "pointer": "/skip_split", + "type": "bool", + "default": false +}, +{ + "pointer": "/skip_collapse", + "type": "bool", + "default": false +}, +{ + "pointer": "/skip_swap", + "type": "bool", + "default": false +}, +{ + "pointer": "/skip_smooth", + "type": "bool", + "default": false +}, +{ + "pointer": "/target_max_amips", + "type": "float", + "default": 10.0 +}, +{ + "pointer": "/target_edge_length", + "type": "float", + "default": 0.05 +}, +{ + "pointer": "/max_passes", + "type": "int", + "default": 10 +}, +{ + "pointer": "/output", + "type": "string" +}, +{ + "pointer": "/report", + "type": "string", + "default": "" +}, +{ + "pointer": "/root", + "type": "string", + "default": "" +} +] +)"_json; + +} \ No newline at end of file diff --git a/applications/triwild_submesh/triwild_submesh_test_config.json b/applications/triwild_submesh/triwild_submesh_test_config.json new file mode 100644 index 0000000000..10d836ab5a --- /dev/null +++ b/applications/triwild_submesh/triwild_submesh_test_config.json @@ -0,0 +1,9 @@ +{ + "test_directory": "unit_test", + "tests": ["triwild_siggraph_icon_out.json"], + "input_tag": "input", + "oracle_tag": "report", + "input_directory_tag": "root", + "platform": "Linux", + "checks": [] +} \ No newline at end of file diff --git a/src/wmtk/attribute/AttributePrototypeDZ.hpp b/src/wmtk/attribute/AttributePrototypeDZ.hpp new file mode 100644 index 0000000000..d666c66d51 --- /dev/null +++ b/src/wmtk/attribute/AttributePrototypeDZ.hpp @@ -0,0 +1,116 @@ +template +class Attribute +{ +public: + using MapResult = internal::MapResult; + using ConstMapResult = internal::ConstMapResult; + + template + friend class AccessorBase; + friend class internal::AttributeTransactionStack; + + void serialize(const std::string& name, const int dim, MeshWriter& writer) const; + + Attribute(const std::string& name, int64_t dimension, T default_value = T(0), int64_t size = 0); + + Attribute(Attribute&& o); + ~Attribute(); + Attribute& operator=(Attribute&& o); + + ConstMapResult vector_attribute(const int64_t index) const; + MapResult vector_attribute(const int64_t index); + + T scalar_attribute(const int64_t index) const; + T& scalar_attribute(const int64_t index); + + int64_t reserved_size() const; + + int64_t dimension() const; + void reserve(const int64_t size); + + const T& default_value() const; + + bool operator==(const Attribute& o) const; + + void push_scope(); + void pop_scope(bool apply_updates); + void rollback_current_scope(); + + const AttributeTransactionStack& get_local_scope_stack() const; + AttributeTransactionStack& get_local_scope_stack(); + + void consolidate(const std::vector& new2old); + + void index_remap(const std::vector& old2new); + void index_remap(const std::vector& old2new, const std::vector& cols); + + + /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////// + // new stuff + + std::string name() const; + + struct SplitStrategies + { + SplitFuncType split_spine_strategy; + SplitRibFuncType split_rib_strategy; + }; + + void set_split_strategies(const SplitStrategies& strategies); + const SplitStrategies& get_split_strategies() const; + + struct CollapseStrategies + { + CollapseFuncType collapse_strategy; + }; + + void set_collapse_strategies(const CollapseStrategies& strategies); + const CollapseStrategies& get_collapse_strategies() const; + + struct SwapStrategies + { + SplitFuncType split_spine_strategy; + SplitRibFuncType split_rib_strategy; + CollapseFuncType collapse_strategy; + }; + + void set_swap_strategies(const SwapStrategies& strategies); + const SwapStrategies& get_swap_strategies() const; + + struct FaceSplitStrategies + { + SplitFuncType split_1_spine_strategy; + SplitRibFuncType split_1_rib_strategy; + SplitFuncType split_2_spine_strategy; + SplitRibFuncType split_2_rib_strategy; + CollapseFuncType collapse_strategy; + }; + + void set_face_split_strategies(const FaceSplitStrategies& strategies); + const FaceSplitStrategies& get_face_split_strategies() const; + + // set strategies to make it behave like a position + void set_default_position_strategies(); + // set strategies to make it behave like a tag + void set_default_tag_strategies(); + // set none strategies + void set_none_strategies(); + + // check if all strategies were set and print info + bool validate_strategies(const spdlog::log_level& l = spdlog::log_level::NONE); + + +private: + std::vector m_data; + PerThreadAttributeScopeStacks m_scope_stacks; + int64_t m_dimension = -1; + T m_default_value = T(0); + std::string m_name; + + SplitStrategies m_split_strategies; + CollapseStrategies m_collapse_strategies; + SwapStrategies m_swap_strategies; + FaceSplitStrategies m_face_split_strategies; +}; \ No newline at end of file From b334c85c9c37910ec1dfd6e0e7b876c42a809431 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 14 Feb 2025 15:47:19 -0500 Subject: [PATCH 19/47] Preparations for submesh wildmeshing. --- .../triwild_submesh/triwild_submesh_main.cpp | 61 +- .../components/wildmeshing/CMakeLists.txt | 5 +- .../internal/WildmeshingEmbeddingOptions.hpp | 40 + .../internal/wildmeshing_embedding_2d.cpp | 1310 +++++++++++++++++ .../internal/wildmeshing_embedding_2d.hpp | 44 + .../internal/wildmeshing_utils.cpp | 31 +- .../internal/wildmeshing_utils.hpp | 8 + .../components/wildmeshing/wildmeshing.cpp | 6 + .../components/wildmeshing/wildmeshing.hpp | 4 +- 9 files changed, 1443 insertions(+), 66 deletions(-) create mode 100644 components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp create mode 100644 components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp create mode 100644 components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.hpp diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp index 08177ac0f3..786695423c 100644 --- a/applications/triwild_submesh/triwild_submesh_main.cpp +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -65,8 +65,6 @@ int main(int argc, char* argv[]) mesh->get_all(PrimitiveType::Vertex).size(), mesh->get_all(PrimitiveType::Edge).size()); - // TODO: use procedural - // get bbox; double x_min, y_min, x_max, y_max; x_min = std::numeric_limits::max(); @@ -84,29 +82,6 @@ int main(int argc, char* argv[]) y_max = std::max(y_max, mesh_pt_accessor.const_vector_attribute(v)[1]); } - // const double diag = (Eigen::Vector2d(x_min, y_min) - Eigen::Vector2d(x_max, y_max)).norm(); - // const double eps = 0.2; // TODO: change with length_rel - // x_min -= diag * eps; - // y_min -= diag * eps; - // x_max += diag * eps; - // y_max += diag * eps; - - // MatrixX V; - // V.resize(4, 2); - // V.row(0) = Vector2r(x_min, y_min); - // V.row(1) = Vector2r(x_max, y_min); - // V.row(2) = Vector2r(x_max, y_max); - // V.row(3) = Vector2r(x_min, y_max); - - // RowVectors3l F; - // F.resize(2, 3); - // F.row(0) << 0, 1, 2; - // F.row(1) << 0, 2, 3; - - // wmtk::TriMesh bg_mesh; - // bg_mesh.initialize(F, false); - // mesh_utils::set_matrix_attribute(V, "vertices", PrimitiveType::Vertex, bg_mesh); - auto bg_mesh = wmtk::triwild::generate_bg_grid(x_min, y_min, x_max, y_max, j["target_edge_length"]); @@ -114,14 +89,11 @@ int main(int argc, char* argv[]) wmtk::logger().info("generated bg mesh"); - // auto em = static_cast(*mesh); - wmtk::components::EdgeInsertionMeshes eim = wmtk::components::edge_insertion(static_cast(*mesh), bg_mesh); wmtk::logger().info("finised edge insertion"); - std::vector pass_through; auto trimesh = eim.tri_mesh; auto edgemesh = eim.inserted_edge_mesh; auto bboxmesh = eim.bbox_mesh; @@ -134,13 +106,13 @@ int main(int argc, char* argv[]) output_file + "_after_insertion_edge_mesh", "vertices"); - auto input_handle = trimesh->get_attribute_handle("input", PrimitiveType::Edge); - pass_through.push_back(input_handle); - auto bbox_handle = trimesh->get_attribute_handle("bbox", PrimitiveType::Edge); - pass_through.push_back(bbox_handle); - + // clean up + { + auto pos_handle = + trimesh->get_attribute_handle("vertices", PrimitiveType::Vertex); + trimesh->clear_attributes({pos_handle}); + } - // TODO: add open vertex boundary std::vector enves; wmtk::components::EnvelopeOptions e; @@ -180,7 +152,7 @@ int main(int argc, char* argv[]) wmo.intermediate_output_path = ""; wmo.intermediate_output_name = j["output"]; wmo.envelopes = enves; - wmo.pass_through = pass_through; + // wmo.pass_through = pass_through; wmo.skip_split = j["skip_split"]; wmo.skip_collapse = j["skip_collapse"]; wmo.skip_swap = j["skip_swap"]; @@ -217,24 +189,5 @@ int main(int argc, char* argv[]) ofs << out_json; } - // auto mesh_after_winding_number = winding_number(main_mesh, input_mesh); - - // wmtk::components::output(*mesh_after_winding_number, output_file, "vertices"); - - // const std::string report = j["report"]; - // if (!report.empty()) { - // nlohmann::json out_json; - // out_json["vertices"] = mesh_after_winding_number->get_all(PrimitiveType::Vertex).size(); - // out_json["edges"] = mesh_after_winding_number->get_all(PrimitiveType::Edge).size(); - // out_json["faces"] = mesh_after_winding_number->get_all(PrimitiveType::Triangle).size(); - // out_json["cells"] = - // mesh_after_winding_number->get_all(PrimitiveType::Tetrahedron).size(); - - // out_json["input"] = j; - - // std::ofstream ofs(report); - // ofs << out_json; - // } - return 0; } \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt index 150297afae..21a4e0ccc0 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt +++ b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt @@ -14,6 +14,9 @@ set(SRC_FILES internal/wildmeshing3d.cpp internal/wildmeshing_utils.hpp internal/wildmeshing_utils.cpp - internal/WildmeshingOptions.hpp) + internal/WildmeshingOptions.hpp + internal/wildmeshing_embedding_2d.hpp + internal/wildmeshing_embedding_2d.cpp + internal/WildmeshingEmbeddingOptions.hpp) target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp new file mode 100644 index 0000000000..4f4c9fa3a2 --- /dev/null +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +namespace wmtk::components { + +struct EmbeddingEnvelopeOptions +{ + std::string envelope_name; + std::shared_ptr envelope_constrained_mesh; + std::shared_ptr envelope_geometry_mesh; + std::string constrained_position_name; + std::string geometry_position_name; + double thickness; +}; + + +struct WildMeshingEmbeddingOptions +{ + std::shared_ptr input_mesh; + std::string input_mesh_position; + double target_edge_length; + double target_max_amips; + double max_passes; + bool intermediate_output; + bool replace_double_coordinate; + size_t scheduler_update_frequency; + std::string intermediate_output_path; + std::string intermediate_output_name; + + bool skip_split = false; + bool skip_collapse = false; + bool skip_swap = false; + bool skip_smooth = false; + + std::vector envelopes; + std::vector pass_through; +}; + +} // namespace wmtk::components \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp new file mode 100644 index 0000000000..5cdeeca006 --- /dev/null +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -0,0 +1,1310 @@ +#include "wildmeshing2d.hpp" + +#include "WildmeshingOptions.hpp" +#include "wildmeshing_utils.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include +namespace wmtk::components::internal { + +using namespace simplex; +using namespace operations; +using namespace operations::tri_mesh; +using namespace operations::tet_mesh; +using namespace operations::composite; +using namespace function; +using namespace invariants; + +std::vector, std::string>> wildmeshing_embedding_2d( + const WildMeshingOptions& options) +{ + auto& mesh = *options.input_mesh; + + if (!mesh.is_connectivity_valid()) { + log_and_throw_error("input mesh for wildmeshing connectivity invalid"); + } + + wmtk::logger().trace("Getting rational point handle"); + + ////////////////////////////////// + // Retriving vertices + // + if (options.replace_double_coordinate) { + wmtk::logger().trace("Found double attribute"); + auto pt_double_attribute = + mesh.get_attribute_handle(options.input_mesh_position, PrimitiveType::Vertex); + + if (!mesh.has_attribute(options.input_mesh_position, PrimitiveType::Vertex)) { + wmtk::utils::cast_attribute( + pt_double_attribute, + mesh, + options.input_mesh_position); + + + } else { + auto pt_attribute = mesh.get_attribute_handle( + options.input_mesh_position, + PrimitiveType::Vertex); + wmtk::utils::cast_attribute(pt_double_attribute, pt_attribute); + } + mesh.delete_attribute(pt_double_attribute); + } + auto pt_attribute = + mesh.get_attribute_handle(options.input_mesh_position, PrimitiveType::Vertex); + wmtk::logger().trace("Getting rational point accessor"); + auto pt_accessor = mesh.create_accessor(pt_attribute.as()); + + wmtk::logger().trace("Computing bounding box diagonal"); + ////////////////////////////////// + // computing bbox diagonal + Eigen::VectorXd bmin(mesh.top_cell_dimension()); + bmin.setConstant(std::numeric_limits::max()); + Eigen::VectorXd bmax(mesh.top_cell_dimension()); + bmax.setConstant(std::numeric_limits::lowest()); + + const auto vertices = mesh.get_all(PrimitiveType::Vertex); + for (const auto& v : vertices) { + const auto p = pt_accessor.vector_attribute(v).cast(); + for (int64_t d = 0; d < bmax.size(); ++d) { + bmin[d] = std::min(bmin[d], p[d]); + bmax[d] = std::max(bmax[d], p[d]); + } + } + + + const double bbdiag = (bmax - bmin).norm(); + + wmtk::logger().info("bbox max {}, bbox min {}, diag {}", bmax, bmin, bbdiag); + + const double target_edge_length = options.target_edge_length * bbdiag; + + // const double target_edge_length = + // options.target_edge_length * bbdiag / + // std::min( + // 2.0, + // 1 + options.target_edge_length * 2); // min to prevent bad option.target_edge_length + + wmtk::logger().info("target edge length: {}", target_edge_length); + + ////////////////////////////////// + // store amips + auto amips_attribute = + mesh.register_attribute("wildmeshing_amips", mesh.top_simplex_type(), 1); + auto amips_accessor = mesh.create_accessor(amips_attribute.as()); + // amips update + auto compute_amips = [](const Eigen::MatrixX& P) -> Eigen::VectorXd { + assert(P.rows() == 2 || P.rows() == 3); // rows --> attribute dimension + assert(P.cols() == P.rows() + 1); + if (P.cols() == 3) { + // triangle + assert(P.rows() == 2); + std::array pts; + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 2; ++j) { + pts[2 * i + j] = P(j, i).to_double(); + } + } + const double a = Tri_AMIPS_energy(pts); + return Eigen::VectorXd::Constant(1, a); + } else { + // tet + assert(P.rows() == 3); + std::array pts; + for (size_t i = 0; i < 4; ++i) { + for (size_t j = 0; j < 3; ++j) { + pts[3 * i + j] = P(j, i).to_double(); + } + } + const double a = Tet_AMIPS_energy(pts); + return Eigen::VectorXd::Constant(1, a); + } + }; + auto amips_update = + std::make_shared>( + amips_attribute, + pt_attribute, + compute_amips); + amips_update->run_on_all(); + + double max_amips = std::numeric_limits::lowest(); + double min_amips = std::numeric_limits::max(); + + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); + double e = amips_accessor.scalar_attribute(t); + max_amips = std::max(max_amips, e); + min_amips = std::min(min_amips, e); + } + + logger().info("Initial Max AMIPS Energy: {}, Min AMIPS Energy: {}", max_amips, min_amips); + + ////////////////////////////////// + // sizing field scalar + ////////////////////////////////// + + // TODO: add sizing field for 2d + + // auto sizing_field_scalar_attribute = mesh.register_attribute( + // "sizing_field_scalar", + // PrimitiveType::Vertex, + // 1, + // false, + // 1); // defaults to 1 + + + ////////////////////////////////// + // Storing target edge length + auto target_edge_length_attribute = mesh.register_attribute( + "wildmeshing_target_edge_length", + PrimitiveType::Edge, + 1, + false, + target_edge_length); // defaults to target edge length + + // Target edge length update + const double min_edge_length = [&]() -> double { + if (options.envelopes.empty()) { + return 1e-6; // some default value if no envelope exists + } else { + // use envelope thickness if available + double r = 0; + for (const auto& e : options.envelopes) { + r = std::max(r, e.thickness); + } + assert(r > 0); + return r; + } + }(); + const double target_max_amips = options.target_max_amips; + + + ////////////////////////////////// + // Storing edge lengths + auto edge_length_attribute = + mesh.register_attribute("edge_length", PrimitiveType::Edge, 1); + auto edge_length_accessor = mesh.create_accessor(edge_length_attribute.as()); + // Edge length update + auto compute_edge_length = [](const Eigen::MatrixX& P) -> Eigen::VectorXd { + assert(P.cols() == 2); + assert(P.rows() == 2 || P.rows() == 3); + return Eigen::VectorXd::Constant(1, sqrt((P.col(0) - P.col(1)).squaredNorm().to_double())); + }; + auto edge_length_update = + std::make_shared>( + edge_length_attribute, + pt_attribute, + compute_edge_length); + edge_length_update->run_on_all(); + + + ////////////////////////////////// + // compute frozen vertices + ////////////////////////////////// + auto frozen_vertex_attribute = + mesh.register_attribute("frozen_vertex", PrimitiveType::Vertex, 1); + auto frozen_vertex_accessor = mesh.create_accessor(frozen_vertex_attribute.as()); + + auto input_ptr = mesh.get_child_meshes().front(); + + int64_t frozen_cnt = 0; + for (const auto& v : input_ptr->get_all(PrimitiveType::Vertex)) { + if (input_ptr->is_boundary(PrimitiveType::Vertex, v)) { + const auto& parent_v = + input_ptr->map_to_parent(simplex::Simplex::vertex(*input_ptr, v)); + frozen_vertex_accessor.scalar_attribute(parent_v) = 1; + frozen_cnt++; + } else { + // frozen_vertex_accessor.scalar_attribute(parent_v) = 0; // redundant, just for safe + } + } + + frozen_cnt = 0; + + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + if (frozen_vertex_accessor.scalar_attribute(v) == 1) { + frozen_cnt++; + } + } + + wmtk::logger().info("mesh has {} frozen vertices", frozen_cnt); + + ////////////////////////////////// + // default transfer + auto pass_through_attributes = options.pass_through; + pass_through_attributes.push_back(edge_length_attribute); + pass_through_attributes.push_back(amips_attribute); + // pass_through_attributes.push_back(target_edge_length_attribute); + + + ////////////////////////////////// + // Lambdas for priority + ////////////////////////////////// + auto long_edges_first = [&](const simplex::Simplex& s) { + assert(s.primitive_type() == PrimitiveType::Edge); + return -edge_length_accessor.scalar_attribute(s.tuple()); + }; + auto short_edges_first = [&](const simplex::Simplex& s) { + assert(s.primitive_type() == PrimitiveType::Edge); + return edge_length_accessor.scalar_attribute(s.tuple()); + }; + + + ////////////////////////////////// + // envelopes + ////////////////////////////////// + + using MeshConstrainPair = ProjectOperation::MeshConstrainPair; + + auto envelope_invariant = std::make_shared(mesh); + std::vector> update_child_position, + update_parent_position; + std::vector> envelopes; + std::vector mesh_constraint_pairs; + + std::vector, std::string>> multimesh_meshes; + + for (const auto& e : options.envelopes) { + auto& constrained_mesh = *e.envelope_constrained_mesh; + auto& geometry_mesh = *e.envelope_geometry_mesh; + + wmtk::logger().info( + "wildmeshing2d: registered {} mesh as envelope constraints", + e.envelope_name); + + const bool geometry_has_double_pos = + geometry_mesh.has_attribute(e.geometry_position_name, PrimitiveType::Vertex); + const bool geometry_has_rational_pos = + geometry_mesh.has_attribute(e.geometry_position_name, PrimitiveType::Vertex); + assert(geometry_has_double_pos || geometry_has_rational_pos); + + auto geometry_pt_handle = geometry_has_double_pos + ? geometry_mesh.get_attribute_handle( + e.geometry_position_name, + PrimitiveType::Vertex) + : geometry_mesh.get_attribute_handle( + e.geometry_position_name, + PrimitiveType::Vertex); + + auto constrained_pt_handle = constrained_mesh.get_attribute_handle( + e.constrained_position_name, + PrimitiveType::Vertex); + + multimesh_meshes.push_back(std::make_pair(e.envelope_constrained_mesh, e.envelope_name)); + pass_through_attributes.emplace_back(constrained_pt_handle); + + mesh_constraint_pairs.emplace_back(geometry_pt_handle, constrained_pt_handle); + + envelope_invariant->add(std::make_shared( + geometry_pt_handle, + e.thickness * bbdiag, + constrained_pt_handle)); + + update_parent_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/constrained_pt_handle, + /*target=*/pt_attribute)); + + update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/pt_attribute, + /*target=*/constrained_pt_handle)); + } + + + ////////////////////////////////// + // Invariants + ////////////////////////////////// + + wmtk::logger().trace("Going through invariants"); + auto inversion_invariant = + std::make_shared>(mesh, pt_attribute.as()); + + std::shared_ptr amips = + std::make_shared(mesh, pt_attribute); + // auto function_invariant = std::make_shared(mesh.top_simplex_type(), + // amips); + auto function_invariant = + std::make_shared(mesh.top_simplex_type(), amips); + + auto link_condition = std::make_shared(mesh); + + auto todo_larger = std::make_shared( + mesh, + edge_length_attribute.as(), + target_edge_length_attribute.as(), + 4.0 / 3.0); + + auto todo_smaller = std::make_shared( + mesh, + edge_length_attribute.as(), + target_edge_length_attribute.as(), + 4.0 / 5.0); + + + auto interior_edge = std::make_shared(mesh); + + for (const auto& em : multimesh_meshes) { + interior_edge->add_boundary(*(em.first)); + } + + auto invariant_separate_substructures = + std::make_shared(mesh); + + auto frozen_vertex_invariant = std::make_shared( + mesh, + frozen_vertex_attribute.as()); + auto frozen_opp_vertex_invariant = std::make_shared( + mesh, + frozen_vertex_attribute.as()); + + ////////////////////////////////// + // renew flags + ////////////////////////////////// + auto visited_edge_flag = + mesh.register_attribute("visited_edge", PrimitiveType::Edge, 1, false, char(1)); + + auto update_flag_func = [](const Eigen::MatrixX& P) -> Eigen::VectorX { + assert(P.cols() == 2); + assert(P.rows() == 2 || P.rows() == 3); + return Eigen::VectorX::Constant(1, char(1)); + }; + auto tag_update = + std::make_shared>( + visited_edge_flag, + pt_attribute, + update_flag_func); + + ////////////////////////////////// + // sizing field update flags + ////////////////////////////////// + + + // TODO: add with sizing field + // auto visited_vertex_flag = + // mesh.register_attribute("visited_vertex", PrimitiveType::Vertex, 1, false, + // char(1)); + // pass_through_attributes.push_back(visited_vertex_flag); + + ////////////////////////////////// + // energy filter flag + ////////////////////////////////// + + // TODO: add energy filter + + // auto energy_filter_attribute = + // mesh.register_attribute("energy_filter", PrimitiveType::Vertex, 1, false, + // char(1)); + + // auto energy_filter_accessor = mesh.create_accessor(energy_filter_attribute); + + // auto update_energy_filter_func = [](const Eigen::MatrixX& P) -> + // Eigen::VectorX { + // // assert(P.cols() == 2); + // // assert(P.rows() == 2 || P.rows() == 3); + // return Eigen::VectorX::Constant(1, char(1)); + // }; + // auto energy_filter_update = + // std::make_shared>( + // energy_filter_attribute, + // pt_attribute, + // update_energy_filter_func); + + // pass_through_attributes.push_back(energy_filter_attribute); + + ////////////////////////////////// + // Creation of the 4 ops + ////////////////////////////////// + std::vector> ops; + std::vector ops_name; + + ////////////////////////////////// + // 0) Rounding + ////////////////////////////////// + auto rounding_pt_attribute = mesh.get_attribute_handle_typed( + options.input_mesh_position, + PrimitiveType::Vertex); + auto rounding = std::make_shared(mesh, rounding_pt_attribute); + rounding->add_invariant( + std::make_shared(mesh, pt_attribute.as(), true)); + rounding->add_invariant(inversion_invariant); + + + ////////////////////////////////// + // 1) EdgeSplit + ////////////////////////////////// + auto split = std::make_shared(mesh); + split->set_priority(long_edges_first); + + split->add_invariant(todo_larger); + split->add_invariant(inversion_invariant); + + split->set_new_attribute_strategy(pt_attribute); + // split->set_new_attribute_strategy(sizing_field_scalar_attribute); + split->set_new_attribute_strategy( + visited_edge_flag, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + + split->set_new_attribute_strategy( + frozen_vertex_attribute, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + split->set_new_attribute_strategy( + target_edge_length_attribute, + wmtk::operations::SplitBasicStrategy::Copy, + wmtk::operations::SplitRibBasicStrategy::Mean); + + for (const auto& attr : pass_through_attributes) { + split->set_new_attribute_strategy( + attr, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + } + + // split->add_transfer_strategy(amips_update); + split->add_transfer_strategy(edge_length_update); + split->add_transfer_strategy(tag_update); // for renew the queue + // split->add_transfer_strategy(energy_filter_update); + + // split->add_transfer_strategy(target_edge_length_update); + + auto split_then_round = std::make_shared(mesh); + split_then_round->add_operation(split); + split_then_round->add_operation(rounding); + + for (auto& s : update_child_position) { + split_then_round->add_transfer_strategy(s); + } + + // split unrounded + auto split_unrounded = std::make_shared(mesh); + split_unrounded->set_priority(long_edges_first); + + split_unrounded->add_invariant(todo_larger); + + auto split_unrounded_transfer_strategy = + std::make_shared>(pt_attribute); + split_unrounded_transfer_strategy->set_strategy( + [](const Eigen::VectorX& a, const std::bitset<2>&) { + return std::array, 2>{{a, a}}; + }); + split_unrounded_transfer_strategy->set_rib_strategy( + [](const Eigen::VectorX& p0_d, + const Eigen::VectorX& p1_d, + const std::bitset<2>& bs) -> Eigen::VectorX { + Eigen::VectorX p0(p0_d.size()); + Eigen::VectorX p1(p1_d.size()); + for (int i = 0; i < p0_d.size(); ++i) { + p0[i] = Rational(p0_d[i], false); + p1[i] = Rational(p1_d[i], false); + } + if (bs[0] == bs[1]) { + return (p0 + p1) / Rational(2, false); + } else if (bs[0]) { + return p0; + + } else { + return p1; + } + }); + + split_unrounded->set_new_attribute_strategy(pt_attribute, split_unrounded_transfer_strategy); + // split_unrounded->set_new_attribute_strategy(sizing_field_scalar_attribute); + split_unrounded->set_new_attribute_strategy( + visited_edge_flag, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + split_unrounded->set_new_attribute_strategy( + frozen_vertex_attribute, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + for (const auto& attr : pass_through_attributes) { + split_unrounded->set_new_attribute_strategy( + attr, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + } + split_unrounded->set_new_attribute_strategy( + target_edge_length_attribute, + wmtk::operations::SplitBasicStrategy::Copy, + wmtk::operations::SplitRibBasicStrategy::Mean); + + split_unrounded->add_transfer_strategy(amips_update); + split_unrounded->add_transfer_strategy(edge_length_update); + split_unrounded->add_transfer_strategy(tag_update); // for renew the queue + // split_unrounded->add_transfer_strategy(energy_filter_update); + + // split->add_transfer_strategy(target_edge_length_update); + + auto split_sequence = std::make_shared(mesh); + split_sequence->add_operation(split_then_round); + split_sequence->add_operation(split_unrounded); + // split_sequence->add_invariant( + // std::make_shared(mesh, energy_filter_attribute.as())); + + split_sequence->set_priority(long_edges_first); + + + if (!options.skip_split) { + ops.emplace_back(split_sequence); + ops_name.emplace_back("SPLIT"); + + ops.emplace_back(rounding); + ops_name.emplace_back("rounding"); + } + + + ////////////////////////////////// + // collapse transfer + ////////////////////////////////// + auto clps_strat1 = std::make_shared>(pt_attribute); + // clps_strat1->set_simplex_predicate(BasicSimplexPredicate::IsInterior); + // clps_strat1->set_strategy(CollapseBasicStrategy::Default); + clps_strat1->set_strategy(CollapseBasicStrategy::CopyOther); + + auto clps_strat2 = std::make_shared>(pt_attribute); + // clps_strat2->set_simplex_predicate(BasicSimplexPredicate::IsInterior); + // clps_strat2->set_strategy(CollapseBasicStrategy::Default); + clps_strat2->set_strategy(CollapseBasicStrategy::CopyTuple); + + ////////////////////////////////// + // 2) EdgeCollapse + ////////////////////////////////// + + auto setup_collapse = [&](std::shared_ptr& collapse) { + collapse->add_invariant(invariant_separate_substructures); + collapse->add_invariant(std::make_shared(mesh)); + collapse->add_invariant(link_condition); + collapse->add_invariant(inversion_invariant); + collapse->add_invariant(function_invariant); + collapse->add_invariant(envelope_invariant); + + collapse->set_new_attribute_strategy( + visited_edge_flag, + wmtk::operations::CollapseBasicStrategy::None); + + collapse->add_transfer_strategy(tag_update); + // collapse->add_transfer_strategy(energy_filter_update); + for (const auto& attr : pass_through_attributes) { + collapse->set_new_attribute_strategy( + attr, + wmtk::operations::CollapseBasicStrategy::None); + } + collapse->set_new_attribute_strategy( + target_edge_length_attribute, + wmtk::operations::CollapseBasicStrategy::None); + // THis triggers a segfault in release + // solved somehow + // collapse->set_priority(short_edges_first); + + collapse->add_transfer_strategy(amips_update); + collapse->add_transfer_strategy(edge_length_update); + // collapse->add_transfer_strategy(target_edge_length_update); + + for (auto& s : update_child_position) { + collapse->add_transfer_strategy(s); + } + }; + + auto collapse1 = std::make_shared(mesh); + + // TODO: implement for 2d + // collapse1->add_invariant(std::make_shared( + // mesh, + // pt_attribute.as(), + // amips_attribute.as(), + // 1)); + + collapse1->add_invariant(frozen_vertex_invariant); + collapse1->set_new_attribute_strategy(pt_attribute, clps_strat1); + collapse1->set_new_attribute_strategy( + frozen_vertex_attribute, + CollapseBasicStrategy::CopyOther); + // collapse1->set_new_attribute_strategy(sizing_field_scalar_attribute, clps_strat1); + setup_collapse(collapse1); + + auto collapse2 = std::make_shared(mesh); + // collapse2->add_invariant(std::make_shared( + // mesh, + // pt_attribute.as(), + // amips_attribute.as(), + // 0)); + + collapse2->add_invariant(frozen_opp_vertex_invariant); + collapse2->set_new_attribute_strategy(pt_attribute, clps_strat2); + collapse2->set_new_attribute_strategy( + frozen_vertex_attribute, + CollapseBasicStrategy::CopyTuple); + // collapse2->set_new_attribute_strategy(sizing_field_scalar_attribute, clps_strat2); + setup_collapse(collapse2); + + auto collapse = std::make_shared(mesh); + collapse->add_operation(collapse1); + collapse->add_operation(collapse2); + collapse->add_invariant(todo_smaller); + + auto collapse_then_round = std::make_shared(mesh); + collapse_then_round->add_operation(collapse); + collapse_then_round->add_operation(rounding); + + collapse_then_round->set_priority(short_edges_first); + // collapse_then_round->add_invariant( + // std::make_shared(mesh, energy_filter_attribute.as())); + + + for (auto& s : update_child_position) { + collapse_then_round->add_transfer_strategy(s); + } + + if (!options.skip_collapse) { + ops.emplace_back(collapse_then_round); + ops_name.emplace_back("COLLAPSE"); + + ops.emplace_back(rounding); + ops_name.emplace_back("rounding"); + } + + ////////////////////////////////// + // 3) Swap + ////////////////////////////////// + + auto setup_swap = [&](Operation& op, + EdgeCollapse& collapse, + EdgeSplit& split, + std::shared_ptr simplex_invariant, + bool is_edge = true) { + if (is_edge) op.set_priority(long_edges_first); + + op.add_invariant(simplex_invariant); + op.add_invariant( + std::make_shared(mesh, pt_attribute.as())); + op.add_invariant(inversion_invariant); + op.add_invariant(function_invariant); + + op.add_transfer_strategy(amips_update); + op.add_transfer_strategy(edge_length_update); + op.add_transfer_strategy(tag_update); + // op.add_transfer_strategy(target_edge_length_update); + // for (auto& s : update_child_position) { + // op.add_transfer_strategy(s); + // } + + collapse.add_invariant(invariant_separate_substructures); + collapse.add_invariant(link_condition); + + + collapse.set_new_attribute_strategy(pt_attribute, CollapseBasicStrategy::CopyOther); + split.set_new_attribute_strategy(pt_attribute); + + split.set_new_attribute_strategy( + visited_edge_flag, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + + collapse.set_new_attribute_strategy( + visited_edge_flag, + wmtk::operations::CollapseBasicStrategy::None); + + split.set_new_attribute_strategy( + frozen_vertex_attribute, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + + collapse.set_new_attribute_strategy( + frozen_vertex_attribute, + CollapseBasicStrategy::CopyOther); + + split.set_new_attribute_strategy( + target_edge_length_attribute, + wmtk::operations::SplitBasicStrategy::Copy, + wmtk::operations::SplitRibBasicStrategy::Mean); + collapse.set_new_attribute_strategy( + target_edge_length_attribute, + wmtk::operations::CollapseBasicStrategy::None); + + // this might not be necessary + // for (auto& s : update_child_position) { + // collapse.add_transfer_strategy(s); + // split.add_transfer_strategy(s); + // } + + + for (const auto& attr : pass_through_attributes) { + split.set_new_attribute_strategy( + attr, + wmtk::operations::SplitBasicStrategy::None, + wmtk::operations::SplitRibBasicStrategy::None); + collapse.set_new_attribute_strategy( + attr, + wmtk::operations::CollapseBasicStrategy::None); + } + }; + + + auto swap = std::make_shared(mesh); + setup_swap(*swap, swap->collapse(), swap->split(), interior_edge); + + if (!options.skip_swap) { + ops.push_back(swap); + ops_name.push_back("swap"); + + ops.emplace_back(rounding); + ops_name.emplace_back("rounding"); + } + + // 4) Smoothing + // ////////////////////////////////////// + // // special smoothing on surface + // ////////////////////////////////////// + + // if (mesh.top_simplex_type() == PrimitiveType::Tetrahedron) { + // for (auto& pair : mesh_constraint_pairs) { + // if (pair.second.mesh().top_simplex_type() != PrimitiveType::Triangle) continue; + + // auto& child_mesh = pair.second.mesh(); + // auto& child_position_handle = pair.second; + + // auto lap_smoothing = std::make_shared( + // child_mesh, + // child_position_handle.as()); + // lap_smoothing->add_invariant(std::make_shared( + // child_mesh, + // child_position_handle.as())); + // lap_smoothing->add_invariant(inversion_invariant); + + // auto proj_lap_smoothing = + // std::make_shared(lap_smoothing, mesh_constraint_pairs); + // proj_lap_smoothing->use_random_priority() = true; + + // proj_lap_smoothing->add_invariant(envelope_invariant); + // proj_lap_smoothing->add_invariant(inversion_invariant); + // proj_lap_smoothing->add_invariant( + // std::make_shared(mesh, + // energy_filter_attribute.as())); + + // proj_lap_smoothing->add_transfer_strategy(amips_update); + // proj_lap_smoothing->add_transfer_strategy(edge_length_update); + // for (auto& s : update_parent_position) { // TODO::this should from only one child + // proj_lap_smoothing->add_transfer_strategy(s); + // } + // for (auto& s : update_child_position) { + // proj_lap_smoothing->add_transfer_strategy(s); + // } + + // ops.push_back(proj_lap_smoothing); + // ops_name.push_back("LAPLACIAN SMOOTHING"); + // } + // } + + // auto energy = + // std::make_shared(mesh, pt_attribute, *amips); + // auto smoothing = std::make_shared(energy); + auto smoothing = std::make_shared(mesh, pt_attribute); + smoothing->add_invariant(std::make_shared(mesh, pt_attribute.as())); + smoothing->add_invariant(frozen_vertex_invariant); + smoothing->add_invariant(inversion_invariant); + for (auto& s : update_child_position) { + smoothing->add_transfer_strategy(s); + } + + // test code + // smoothing->add_invariant(envelope_invariant); + // smoothing->add_transfer_strategy(edge_length_update); + // ops.push_back(smoothing); + // ops_name.push_back("SMOOTHING"); + // ops.emplace_back(rounding); + // ops_name.emplace_back("rounding"); + //-------- + + auto proj_smoothing = std::make_shared(smoothing, mesh_constraint_pairs); + proj_smoothing->use_random_priority() = true; + proj_smoothing->add_invariant(frozen_vertex_invariant); + proj_smoothing->add_invariant(envelope_invariant); + proj_smoothing->add_invariant(inversion_invariant); + // proj_smoothing->add_invariant( + // std::make_shared(mesh, energy_filter_attribute.as())); + + proj_smoothing->add_transfer_strategy(amips_update); + proj_smoothing->add_transfer_strategy(edge_length_update); + for (auto& s : update_parent_position) { + proj_smoothing->add_transfer_strategy(s); + } + + for (auto& s : update_child_position) { + proj_smoothing->add_transfer_strategy(s); + } + // proj_smoothing->add_transfer_strategy(target_edge_length_update); + + if (!options.skip_smooth) { + for (int i = 0; i < 1; ++i) { + // some old code to do smoothing several times, maybe useful later + ops.push_back(proj_smoothing); + ops_name.push_back("SMOOTHING"); + } + + ops.emplace_back(rounding); + ops_name.emplace_back("rounding"); + } + + write( + mesh, + options.intermediate_output_path, + options.intermediate_output_name, + options.input_mesh_position, + 0, + options.intermediate_output); + + ////////////////////////////////// + // Running all ops in order n times + Scheduler scheduler; + { + const size_t freq = options.scheduler_update_frequency; + scheduler.set_update_frequency(freq == 0 ? std::optional{} : freq); + } + int64_t success = 10; + + ////////////////////////////////// + // preprocessing + ////////////////////////////////// + + // debug code + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + const auto vertices = mesh.orient_vertices(t); + std::vector pos; + for (int i = 0; i < vertices.size(); ++i) { + pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); + } + if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { + wmtk::logger().error("Flipped triangle!"); + } + } + + SchedulerStats pre_stats; + + logger().info("----------------------- Preprocess Collapse -----------------------"); + + logger().info("Executing collapse ..."); + + + wmtk::attribute::TypedAttributeHandle visited_edge_flag_t = visited_edge_flag.as(); + + pre_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag_t); + logger().info( + "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, " + "executing: {}", + "preprocessing collapse", + pre_stats.number_of_performed_operations(), + pre_stats.number_of_successful_operations(), + pre_stats.number_of_failed_operations(), + pre_stats.collecting_time, + pre_stats.sorting_time, + pre_stats.executing_time); + + success = pre_stats.number_of_successful_operations(); + + // verbose logger, can be removed + int64_t unrounded = 0; + int64_t frozen = 0; + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + const auto p = pt_accessor.vector_attribute(v); + for (int64_t d = 0; d < 2; ++d) { + if (!p[d].is_rounded()) { + ++unrounded; + break; + } + } + + if (frozen_vertex_accessor.scalar_attribute(v) == 1) { + frozen++; + } + } + + logger().info("Mesh has {} unrounded vertices", unrounded); + logger().error("Mesh has {} frozen vertices", frozen); + + // debug code + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + const auto vertices = mesh.orient_vertices(t); + std::vector pos; + for (int i = 0; i < vertices.size(); ++i) { + pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); + } + if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { + wmtk::logger().error("Flipped triangle!"); + } + } + + + // compute max energy + double max_energy = std::numeric_limits::lowest(); + double min_energy = std::numeric_limits::max(); + double avg_energy = 0; + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); + double e = amips_accessor.scalar_attribute(t); + max_energy = std::max(max_energy, e); + min_energy = std::min(min_energy, e); + avg_energy += e; + } + + avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); + + logger().info( + "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", + max_energy, + min_energy, + avg_energy); + + // std::ofstream file0("quality_plot_pre.csv"); + // file0 << "tid, quality" << std::endl; + // int64_t t_cnt = 0; + // for (const auto& t : mesh.get_all(PrimitiveType::Tetrahedron)) { + // t_cnt++; + // file0 << t_cnt << ", " << amips_accessor.scalar_attribute(t) << std::endl; + // } + + + double old_max_energy = max_energy; + double old_avg_energy = avg_energy; + int iii = 0; + bool is_double = false; + for (int64_t i = 0; i < options.max_passes; ++i) { + logger().info("--------------------------- Pass {} ---------------------------", i); + + SchedulerStats pass_stats; + int jj = 0; + for (auto& op : ops) { + logger().info("Executing {} ...", ops_name[jj]); + SchedulerStats stats; + if (op->primitive_type() == PrimitiveType::Edge) { + stats = scheduler.run_operation_on_all(*op, visited_edge_flag_t); + // } else if (ops_name[jj] == "SMOOTHING") { + // // stats = scheduler.run_operation_on_all(*op); + // stats = + // scheduler.run_operation_on_all_coloring(*op, + // coloring_attribute.as()); + } else { + stats = scheduler.run_operation_on_all(*op); + } + pass_stats += stats; + logger().info( + "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, " + "executing: {}", + ops_name[jj], + stats.number_of_performed_operations(), + stats.number_of_successful_operations(), + stats.number_of_failed_operations(), + stats.collecting_time, + stats.sorting_time, + stats.executing_time); + + success = stats.number_of_successful_operations(); + + // verbose logger, can be removed + int64_t unrounded = 0; + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + const auto p = pt_accessor.vector_attribute(v); + for (int64_t d = 0; d < 2; ++d) { + if (!p[d].is_rounded()) { + ++unrounded; + break; + } + } + } + + logger().info("Mesh has {} unrounded vertices", unrounded); + + // debug code + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + const auto vertices = mesh.orient_vertices(t); + std::vector pos; + for (int i = 0; i < vertices.size(); ++i) { + pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); + } + if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { + wmtk::logger().error("Flipped triangle!"); + } + } + + avg_energy = 0; + + // compute max energy + max_energy = std::numeric_limits::lowest(); + min_energy = std::numeric_limits::max(); + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); + double e = amips_accessor.scalar_attribute(t); + max_energy = std::max(max_energy, e); + min_energy = std::min(min_energy, e); + avg_energy += e; + } + + avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); + + logger().info( + "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", + max_energy, + min_energy, + avg_energy); + + + ++jj; + } + + logger().info( + "Executed {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, executing: {}", + pass_stats.number_of_performed_operations(), + pass_stats.number_of_successful_operations(), + pass_stats.number_of_failed_operations(), + pass_stats.collecting_time, + pass_stats.sorting_time, + pass_stats.executing_time); + + multimesh::consolidate(mesh); + + write( + mesh, + options.intermediate_output_path, + options.intermediate_output_name, + options.input_mesh_position, + i + 1, + options.intermediate_output); + + assert(mesh.is_connectivity_valid()); + + // compute max energy + max_energy = std::numeric_limits::lowest(); + min_energy = std::numeric_limits::max(); + avg_energy = 0; + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); + double e = amips_accessor.scalar_attribute(t); + max_energy = std::max(max_energy, e); + min_energy = std::min(min_energy, e); + avg_energy += e; + } + + avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); + + int64_t unrounded = 0; + if (!is_double) { + bool rational = false; + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + const auto p = pt_accessor.vector_attribute(v); + for (int64_t d = 0; d < bmax.size(); ++d) { + if (!p[d].is_rounded()) { + rational = true; + ++unrounded; + break; + } + } + } + + is_double = !rational; + } + + logger().info("Mesh has {} unrounded vertices", unrounded); + logger().info( + "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", + max_energy, + min_energy, + avg_energy); + + + // adjust sizing field + // if (i > 0 && old_max_energy - max_energy < 5e-1 && + // (old_avg_energy - avg_energy) / avg_energy < 0.1) { + // wmtk::logger().info("adjusting sizing field ..."); + + // adjust_sizing_field( + // mesh, + // pt_attribute.as(), + // edge_length_attribute.as(), + // sizing_field_scalar_attribute.as(), + // amips_attribute.as(), + // target_edge_length_attribute.as(), + // visited_vertex_flag.as(), + // target_max_amips, + // max_energy, + // target_edge_length, + // min_edge_length); + + // wmtk::logger().info("adjusting sizing field finished"); + + // // wmtk::logger().info("setting energy filter ..."); + // // set_operation_energy_filter_after_sizing_field( + // // mesh, + // // pt_attribute.as(), + // // amips_attribute.as(), + // // energy_filter_attribute.as(), + // // visited_vertex_flag.as(), + // // target_max_amips, + // // max_energy, + // // target_edge_length); + // // wmtk::logger().info("setting energy filter finished"); + + // // int64_t e_cnt = 0; + // // for (const auto& e : mesh.get_all(PrimitiveType::Edge)) { + // // if (energy_filter_accessor.scalar_attribute(e) == char(1) || + // // energy_filter_accessor.scalar_attribute( + // // mesh.switch_tuple(e, PrimitiveType::Vertex)) == char(1)) { + // // e_cnt++; + // // } + // // } + // // wmtk::logger().info( + // // "{} edges are going to be executed out of {}", + // // e_cnt, + // // mesh.get_all(PrimitiveType::Edge).size()); + + // for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + // energy_filter_accessor.scalar_attribute(v) = char(1); + // } + // wmtk::logger().info("reset energy filter"); + // } else { + // wmtk::logger().info("setting energy filter ..."); + // set_operation_energy_filter( + // mesh, + // pt_attribute.as(), + // amips_attribute.as(), + // energy_filter_attribute.as(), + // visited_vertex_flag.as(), + // target_max_amips, + // max_energy, + // target_edge_length); + // wmtk::logger().info("setting energy filter finished"); + + // int64_t e_cnt = 0; + // for (const auto& e : mesh.get_all(PrimitiveType::Edge)) { + // if (energy_filter_accessor.scalar_attribute(e) == char(1) || + // energy_filter_accessor.scalar_attribute( + // mesh.switch_tuple(e, PrimitiveType::Vertex)) == char(1)) { + // e_cnt++; + // } + // } + // wmtk::logger().info( + // "{} edges are going to be executed out of {}", + // e_cnt, + // mesh.get_all(PrimitiveType::Edge).size()); + // } + + old_max_energy = max_energy; + old_avg_energy = avg_energy; + + // stop at good quality + if (max_energy <= target_max_amips && is_double) break; + } + + logger().info("----------------------- Postprocess Collapse -----------------------"); + + logger().info("Executing collapse ..."); + + auto post_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag_t); + logger().info( + "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, " + "executing: {}", + "preprocessing collapse", + post_stats.number_of_performed_operations(), + post_stats.number_of_successful_operations(), + post_stats.number_of_failed_operations(), + post_stats.collecting_time, + post_stats.sorting_time, + post_stats.executing_time); + + // compute max energy + max_energy = std::numeric_limits::lowest(); + min_energy = std::numeric_limits::max(); + avg_energy = 0; + for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); + double e = amips_accessor.scalar_attribute(t); + max_energy = std::max(max_energy, e); + min_energy = std::min(min_energy, e); + avg_energy += e; + } + + avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); + + logger().info( + "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", + max_energy, + min_energy, + avg_energy); + + multimesh::consolidate(mesh); + + std::vector, std::string>> all_meshes; + all_meshes.push_back(std::make_pair(options.input_mesh, "main")); + + for (const auto& p : multimesh_meshes) { + all_meshes.push_back(p); + } + + return all_meshes; +} + +} // namespace wmtk::components::internal diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.hpp new file mode 100644 index 0000000000..f9c91ab8e4 --- /dev/null +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include "WildmeshingOptions.hpp" + +namespace wmtk::components::internal { + +std::vector, std::string>> wildmeshing_embedding_2d( + const WildMeshingOptions& options); + +// void adjust_sizing_field( +// Mesh& m, +// const TypedAttributeHandle& coordinate_handle, +// const TypedAttributeHandle& edge_length_handle, +// const TypedAttributeHandle& sizing_field_scalar_handle, +// const TypedAttributeHandle& energy_handle, +// const TypedAttributeHandle& target_edge_length_handle, +// const TypedAttributeHandle& visited_handle, +// const double stop_energy, +// const double current_max_energy, +// const double initial_target_edge_length, +// const double min_target_edge_length); + +// void set_operation_energy_filter( +// Mesh& m, +// const TypedAttributeHandle& coordinate_handle, +// const TypedAttributeHandle& energy_handle, +// const TypedAttributeHandle& energy_filter_handle, +// const TypedAttributeHandle& visited_handle, +// const double stop_energy, +// const double current_max_energy, +// const double initial_target_edge_length); + +// void set_operation_energy_filter_after_sizing_field( +// Mesh& m, +// const TypedAttributeHandle& coordinate_handle, +// const TypedAttributeHandle& energy_handle, +// const TypedAttributeHandle& energy_filter_handle, +// const TypedAttributeHandle& visited_handle, +// const double stop_energy, +// const double current_max_energy, +// const double initial_target_edge_length); + +} // namespace wmtk::components::internal \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.cpp index 65fc759895..b15b9217ee 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.cpp @@ -4,7 +4,7 @@ namespace wmtk::components::internal { void write( - const std::shared_ptr& mesh, + const Mesh& mesh, const std::string& out_dir, const std::string& name, const std::string& vname, @@ -12,43 +12,54 @@ void write( const bool intermediate_output) { if (intermediate_output) { - if (mesh->top_simplex_type() == PrimitiveType::Triangle) { + if (mesh.top_simplex_type() == PrimitiveType::Triangle) { // write trimesh const std::filesystem::path data_dir = ""; wmtk::io::ParaviewWriter writer( data_dir / (name + "_" + std::to_string(index)), vname, - *mesh, + mesh, true, true, true, false); - mesh->serialize(writer); - } else if (mesh->top_simplex_type() == PrimitiveType::Tetrahedron) { + mesh.serialize(writer); + } else if (mesh.top_simplex_type() == PrimitiveType::Tetrahedron) { // write tetmesh const std::filesystem::path data_dir = ""; wmtk::io::ParaviewWriter writer( data_dir / (name + "_" + std::to_string(index)), vname, - *mesh, + mesh, true, true, true, true); - mesh->serialize(writer); - } else if (mesh->top_simplex_type() == PrimitiveType::Edge) { + mesh.serialize(writer); + } else if (mesh.top_simplex_type() == PrimitiveType::Edge) { // write edgemesh const std::filesystem::path data_dir = ""; wmtk::io::ParaviewWriter writer( data_dir / (name + "_" + std::to_string(index)), vname, - *mesh, + mesh, true, true, false, false); - mesh->serialize(writer); + mesh.serialize(writer); } } } + +void write( + const std::shared_ptr& mesh, + const std::string& out_dir, + const std::string& name, + const std::string& vname, + const int64_t index, + const bool intermediate_output) +{ + write(*mesh, out_dir, name, vname, index, intermediate_output); +} } // namespace wmtk::components::internal \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.hpp index 4ad4961dfc..4ea06b4c69 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.hpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_utils.hpp @@ -6,6 +6,14 @@ namespace wmtk::components::internal { +void write( + const Mesh& mesh, + const std::string& out_dir, + const std::string& name, + const std::string& vname, + const int64_t index, + const bool intermediate_output); + void write( const std::shared_ptr& mesh, const std::string& out_dir, diff --git a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp index 274db376fe..58ee20431b 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp @@ -3,6 +3,8 @@ #include "internal/wildmeshing2d.hpp" #include "internal/wildmeshing3d.hpp" +#include + namespace wmtk::components { using namespace internal; @@ -20,4 +22,8 @@ std::vector, std::string>> wildmeshing( return {}; } +void wildmeshing(const WildMeshingEmbeddingOptions& options) { + log_and_throw_error("not implemented"); +} + } // namespace wmtk::components diff --git a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp index df1ac057e8..45d4fff3e1 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp @@ -3,12 +3,14 @@ #include #include +#include "internal/WildmeshingEmbeddingOptions.hpp" #include "internal/WildmeshingOptions.hpp" - namespace wmtk::components { std::vector, std::string>> wildmeshing( const WildMeshingOptions& option); +void wildmeshing(const WildMeshingEmbeddingOptions& options); + } // namespace wmtk::components From 5194f4d81e0df70156ec0cfc75abc013a1ff6519 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 18 Feb 2025 15:13:46 +0100 Subject: [PATCH 20/47] Clean up wildmeshing code. --- .../triwild_submesh/triwild_submesh_main.cpp | 4 +- .../components/wildmeshing/CMakeLists.txt | 3 +- .../internal/WildmeshingEmbeddingOptions.hpp | 40 -- .../internal/WildmeshingOptions.hpp | 30 +- .../internal/wildmeshing_embedding_2d.cpp | 572 ++++-------------- .../components/wildmeshing/wildmeshing.cpp | 22 +- .../components/wildmeshing/wildmeshing.hpp | 5 +- src/wmtk/utils/CMakeLists.txt | 5 + src/wmtk/utils/bbox_from_mesh.cpp | 59 ++ src/wmtk/utils/bbox_from_mesh.hpp | 23 + src/wmtk/utils/bvh_from_mesh.cpp | 217 +++++++ src/wmtk/utils/bvh_from_mesh.hpp | 26 + 12 files changed, 481 insertions(+), 525 deletions(-) delete mode 100644 components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp create mode 100644 src/wmtk/utils/bbox_from_mesh.cpp create mode 100644 src/wmtk/utils/bbox_from_mesh.hpp create mode 100644 src/wmtk/utils/bvh_from_mesh.cpp create mode 100644 src/wmtk/utils/bvh_from_mesh.hpp diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp index 786695423c..f72c5a8fc6 100644 --- a/applications/triwild_submesh/triwild_submesh_main.cpp +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -146,7 +146,6 @@ int main(int argc, char* argv[]) wmo.target_edge_length = j["target_edge_length"]; wmo.target_max_amips = j["target_max_amips"]; wmo.max_passes = j["max_passes"]; - wmo.intermediate_output = j["intermediate_output"]; wmo.replace_double_coordinate = false; wmo.scheduler_update_frequency = 0; wmo.intermediate_output_path = ""; @@ -157,6 +156,7 @@ int main(int argc, char* argv[]) wmo.skip_collapse = j["skip_collapse"]; wmo.skip_swap = j["skip_swap"]; wmo.skip_smooth = j["skip_smooth"]; + wmo.use_embedding = true; auto meshes_after_tetwild = wildmeshing(wmo); auto main_mesh = meshes_after_tetwild[0].first; @@ -186,7 +186,7 @@ int main(int argc, char* argv[]) out_json["input"] = j; std::ofstream ofs(report); - ofs << out_json; + ofs << std::setw(4) << out_json; } return 0; diff --git a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt index 21a4e0ccc0..03af095829 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt +++ b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt @@ -16,7 +16,6 @@ set(SRC_FILES internal/wildmeshing_utils.cpp internal/WildmeshingOptions.hpp internal/wildmeshing_embedding_2d.hpp - internal/wildmeshing_embedding_2d.cpp - internal/WildmeshingEmbeddingOptions.hpp) + internal/wildmeshing_embedding_2d.cpp) target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp deleted file mode 100644 index 4f4c9fa3a2..0000000000 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingEmbeddingOptions.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#include -#include - -namespace wmtk::components { - -struct EmbeddingEnvelopeOptions -{ - std::string envelope_name; - std::shared_ptr envelope_constrained_mesh; - std::shared_ptr envelope_geometry_mesh; - std::string constrained_position_name; - std::string geometry_position_name; - double thickness; -}; - - -struct WildMeshingEmbeddingOptions -{ - std::shared_ptr input_mesh; - std::string input_mesh_position; - double target_edge_length; - double target_max_amips; - double max_passes; - bool intermediate_output; - bool replace_double_coordinate; - size_t scheduler_update_frequency; - std::string intermediate_output_path; - std::string intermediate_output_name; - - bool skip_split = false; - bool skip_collapse = false; - bool skip_swap = false; - bool skip_smooth = false; - - std::vector envelopes; - std::vector pass_through; -}; - -} // namespace wmtk::components \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp index 539667f031..10ae11ac23 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp @@ -17,23 +17,29 @@ struct EnvelopeOptions struct WildMeshingOptions { std::shared_ptr input_mesh; - std::string input_mesh_position; - double target_edge_length; - double target_max_amips; - double max_passes; - bool intermediate_output; - bool replace_double_coordinate; - size_t scheduler_update_frequency; - std::string intermediate_output_path; + std::string input_mesh_position = "vertices"; + double target_edge_length = 0.05; + double target_max_amips = 10.0; + double max_passes = 10; + bool intermediate_output = false; + bool replace_double_coordinate = false; + size_t scheduler_update_frequency = 0; + std::string intermediate_output_path = ""; std::string intermediate_output_name; - bool skip_split; - bool skip_collapse; - bool skip_swap; - bool skip_smooth; + bool skip_split = false; + bool skip_collapse = false; + bool skip_swap = false; + bool skip_smooth = false; std::vector envelopes; std::vector pass_through; + + /** + * Convert the multimesh into tags und use Embedding/SubMesh. + * For now, no topologica checks on the submesh will be performed. + */ + bool use_embedding = false; }; } // namespace wmtk::components \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 5cdeeca006..d240ed0f7e 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -62,6 +62,7 @@ #include #include +#include #include #include @@ -82,6 +83,54 @@ using namespace operations::composite; using namespace function; using namespace invariants; +std::tuple min_max_avg_amips( + const Mesh& mesh, + const attribute::MeshAttributeHandle& amips_handle) +{ + const auto amips_accessor = mesh.create_const_accessor(amips_handle); + + // compute max energy + double max_energy = std::numeric_limits::lowest(); + double min_energy = std::numeric_limits::max(); + double avg_energy = 0; + for (const Tuple& t : mesh.get_all(mesh.top_simplex_type())) { + double e = amips_accessor.const_scalar_attribute(t); + max_energy = std::max(max_energy, e); + min_energy = std::min(min_energy, e); + avg_energy += e; + } + + avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); + + logger().info( + "Max AMIPS Energy: {:>6.2f}, Min AMIPS Energy: {:>6.2f}, Avg AMIPS Energy: {:>6.2f}", + max_energy, + min_energy, + avg_energy); + + return std::make_tuple(min_energy, max_energy, avg_energy); +} + +void print_stats(const wmtk::SchedulerStats& stats, const std::string& name = "") +{ + if (name.empty()) { + logger().info( + "Executed {} ops (S/F) {}/{}. Time executing: {:.4f}", + stats.number_of_performed_operations(), + stats.number_of_successful_operations(), + stats.number_of_failed_operations(), + stats.executing_time); + } else { + logger().info( + "Executed {}, {} ops (S/F) {}/{}. Time executing: {:.4f}", + name, + stats.number_of_performed_operations(), + stats.number_of_successful_operations(), + stats.number_of_failed_operations(), + stats.executing_time); + } +} + std::vector, std::string>> wildmeshing_embedding_2d( const WildMeshingOptions& options) { @@ -91,13 +140,13 @@ std::vector, std::string>> wildmeshing_embedding log_and_throw_error("input mesh for wildmeshing connectivity invalid"); } - wmtk::logger().trace("Getting rational point handle"); + logger().trace("Getting rational point handle"); ////////////////////////////////// // Retriving vertices // if (options.replace_double_coordinate) { - wmtk::logger().trace("Found double attribute"); + logger().trace("Found double attribute"); auto pt_double_attribute = mesh.get_attribute_handle(options.input_mesh_position, PrimitiveType::Vertex); @@ -118,40 +167,16 @@ std::vector, std::string>> wildmeshing_embedding } auto pt_attribute = mesh.get_attribute_handle(options.input_mesh_position, PrimitiveType::Vertex); - wmtk::logger().trace("Getting rational point accessor"); + logger().trace("Getting rational point accessor"); auto pt_accessor = mesh.create_accessor(pt_attribute.as()); - wmtk::logger().trace("Computing bounding box diagonal"); ////////////////////////////////// // computing bbox diagonal - Eigen::VectorXd bmin(mesh.top_cell_dimension()); - bmin.setConstant(std::numeric_limits::max()); - Eigen::VectorXd bmax(mesh.top_cell_dimension()); - bmax.setConstant(std::numeric_limits::lowest()); - - const auto vertices = mesh.get_all(PrimitiveType::Vertex); - for (const auto& v : vertices) { - const auto p = pt_accessor.vector_attribute(v).cast(); - for (int64_t d = 0; d < bmax.size(); ++d) { - bmin[d] = std::min(bmin[d], p[d]); - bmax[d] = std::max(bmax[d], p[d]); - } - } - - - const double bbdiag = (bmax - bmin).norm(); - - wmtk::logger().info("bbox max {}, bbox min {}, diag {}", bmax, bmin, bbdiag); + const double bbdiag = wmtk::utils::bbox_diagonal_from_mesh(pt_attribute); + logger().info("bbox diag = {}", bbdiag); const double target_edge_length = options.target_edge_length * bbdiag; - - // const double target_edge_length = - // options.target_edge_length * bbdiag / - // std::min( - // 2.0, - // 1 + options.target_edge_length * 2); // min to prevent bad option.target_edge_length - - wmtk::logger().info("target edge length: {}", target_edge_length); + logger().info("target edge length: {}", target_edge_length); ////////////////////////////////// // store amips @@ -193,31 +218,11 @@ std::vector, std::string>> wildmeshing_embedding compute_amips); amips_update->run_on_all(); - double max_amips = std::numeric_limits::lowest(); - double min_amips = std::numeric_limits::max(); - - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); - double e = amips_accessor.scalar_attribute(t); - max_amips = std::max(max_amips, e); - min_amips = std::min(min_amips, e); - } - - logger().info("Initial Max AMIPS Energy: {}, Min AMIPS Energy: {}", max_amips, min_amips); - - ////////////////////////////////// - // sizing field scalar - ////////////////////////////////// - - // TODO: add sizing field for 2d - - // auto sizing_field_scalar_attribute = mesh.register_attribute( - // "sizing_field_scalar", - // PrimitiveType::Vertex, - // 1, - // false, - // 1); // defaults to 1 + double max_amips; + double min_amips; + double avg_amips; + std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); ////////////////////////////////// // Storing target edge length @@ -235,14 +240,14 @@ std::vector, std::string>> wildmeshing_embedding } else { // use envelope thickness if available double r = 0; - for (const auto& e : options.envelopes) { + for (const EnvelopeOptions& e : options.envelopes) { r = std::max(r, e.thickness); } assert(r > 0); return r; } }(); - const double target_max_amips = options.target_max_amips; + const double& target_max_amips = options.target_max_amips; ////////////////////////////////// @@ -273,34 +278,29 @@ std::vector, std::string>> wildmeshing_embedding auto input_ptr = mesh.get_child_meshes().front(); - int64_t frozen_cnt = 0; - for (const auto& v : input_ptr->get_all(PrimitiveType::Vertex)) { + for (const Tuple& v : input_ptr->get_all(PrimitiveType::Vertex)) { if (input_ptr->is_boundary(PrimitiveType::Vertex, v)) { const auto& parent_v = input_ptr->map_to_parent(simplex::Simplex::vertex(*input_ptr, v)); frozen_vertex_accessor.scalar_attribute(parent_v) = 1; - frozen_cnt++; - } else { - // frozen_vertex_accessor.scalar_attribute(parent_v) = 0; // redundant, just for safe } } - frozen_cnt = 0; - - for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { - if (frozen_vertex_accessor.scalar_attribute(v) == 1) { - frozen_cnt++; + { + int64_t frozen_cnt = 0; + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + if (frozen_vertex_accessor.scalar_attribute(v) == 1) { + frozen_cnt++; + } } + logger().info("mesh has {} frozen vertices", frozen_cnt); } - wmtk::logger().info("mesh has {} frozen vertices", frozen_cnt); - ////////////////////////////////// // default transfer auto pass_through_attributes = options.pass_through; pass_through_attributes.push_back(edge_length_attribute); pass_through_attributes.push_back(amips_attribute); - // pass_through_attributes.push_back(target_edge_length_attribute); ////////////////////////////////// @@ -324,33 +324,30 @@ std::vector, std::string>> wildmeshing_embedding auto envelope_invariant = std::make_shared(mesh); std::vector> update_child_position, - update_parent_position; + update_parent_position; // TODO remove for submesh std::vector> envelopes; std::vector mesh_constraint_pairs; std::vector, std::string>> multimesh_meshes; - for (const auto& e : options.envelopes) { - auto& constrained_mesh = *e.envelope_constrained_mesh; - auto& geometry_mesh = *e.envelope_geometry_mesh; + for (const EnvelopeOptions& e : options.envelopes) { + Mesh& constrained_mesh = *e.envelope_constrained_mesh; + Mesh& geometry_mesh = *e.envelope_geometry_mesh; - wmtk::logger().info( - "wildmeshing2d: registered {} mesh as envelope constraints", - e.envelope_name); + logger().info("wildmeshing2d: registered {} mesh as envelope constraints", e.envelope_name); - const bool geometry_has_double_pos = + const bool has_double_pos = geometry_mesh.has_attribute(e.geometry_position_name, PrimitiveType::Vertex); - const bool geometry_has_rational_pos = + const bool has_rational_pos = geometry_mesh.has_attribute(e.geometry_position_name, PrimitiveType::Vertex); - assert(geometry_has_double_pos || geometry_has_rational_pos); + assert(has_double_pos ^ has_rational_pos); - auto geometry_pt_handle = geometry_has_double_pos - ? geometry_mesh.get_attribute_handle( - e.geometry_position_name, - PrimitiveType::Vertex) - : geometry_mesh.get_attribute_handle( - e.geometry_position_name, - PrimitiveType::Vertex); + auto geometry_pt_handle = has_double_pos ? geometry_mesh.get_attribute_handle( + e.geometry_position_name, + PrimitiveType::Vertex) + : geometry_mesh.get_attribute_handle( + e.geometry_position_name, + PrimitiveType::Vertex); auto constrained_pt_handle = constrained_mesh.get_attribute_handle( e.constrained_position_name, @@ -368,11 +365,11 @@ std::vector, std::string>> wildmeshing_embedding update_parent_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( /*source=*/constrained_pt_handle, - /*target=*/pt_attribute)); + /*target=*/pt_attribute)); // TODO remove for submesh update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( /*source=*/pt_attribute, - /*target=*/constrained_pt_handle)); + /*target=*/constrained_pt_handle)); // TODO remove for submesh } @@ -380,7 +377,7 @@ std::vector, std::string>> wildmeshing_embedding // Invariants ////////////////////////////////// - wmtk::logger().trace("Going through invariants"); + logger().trace("Going through invariants"); auto inversion_invariant = std::make_shared>(mesh, pt_attribute.as()); @@ -413,7 +410,8 @@ std::vector, std::string>> wildmeshing_embedding } auto invariant_separate_substructures = - std::make_shared(mesh); + std::make_shared( + mesh); // TODO remove for submesh auto frozen_vertex_invariant = std::make_shared( mesh, @@ -439,42 +437,6 @@ std::vector, std::string>> wildmeshing_embedding pt_attribute, update_flag_func); - ////////////////////////////////// - // sizing field update flags - ////////////////////////////////// - - - // TODO: add with sizing field - // auto visited_vertex_flag = - // mesh.register_attribute("visited_vertex", PrimitiveType::Vertex, 1, false, - // char(1)); - // pass_through_attributes.push_back(visited_vertex_flag); - - ////////////////////////////////// - // energy filter flag - ////////////////////////////////// - - // TODO: add energy filter - - // auto energy_filter_attribute = - // mesh.register_attribute("energy_filter", PrimitiveType::Vertex, 1, false, - // char(1)); - - // auto energy_filter_accessor = mesh.create_accessor(energy_filter_attribute); - - // auto update_energy_filter_func = [](const Eigen::MatrixX& P) -> - // Eigen::VectorX { - // // assert(P.cols() == 2); - // // assert(P.rows() == 2 || P.rows() == 3); - // return Eigen::VectorX::Constant(1, char(1)); - // }; - // auto energy_filter_update = - // std::make_shared>( - // energy_filter_attribute, - // pt_attribute, - // update_energy_filter_func); - - // pass_through_attributes.push_back(energy_filter_attribute); ////////////////////////////////// // Creation of the 4 ops @@ -504,7 +466,6 @@ std::vector, std::string>> wildmeshing_embedding split->add_invariant(inversion_invariant); split->set_new_attribute_strategy(pt_attribute); - // split->set_new_attribute_strategy(sizing_field_scalar_attribute); split->set_new_attribute_strategy( visited_edge_flag, wmtk::operations::SplitBasicStrategy::None, @@ -526,12 +487,8 @@ std::vector, std::string>> wildmeshing_embedding wmtk::operations::SplitRibBasicStrategy::None); } - // split->add_transfer_strategy(amips_update); split->add_transfer_strategy(edge_length_update); split->add_transfer_strategy(tag_update); // for renew the queue - // split->add_transfer_strategy(energy_filter_update); - - // split->add_transfer_strategy(target_edge_length_update); auto split_then_round = std::make_shared(mesh); split_then_round->add_operation(split); @@ -574,7 +531,6 @@ std::vector, std::string>> wildmeshing_embedding }); split_unrounded->set_new_attribute_strategy(pt_attribute, split_unrounded_transfer_strategy); - // split_unrounded->set_new_attribute_strategy(sizing_field_scalar_attribute); split_unrounded->set_new_attribute_strategy( visited_edge_flag, wmtk::operations::SplitBasicStrategy::None, @@ -597,15 +553,10 @@ std::vector, std::string>> wildmeshing_embedding split_unrounded->add_transfer_strategy(amips_update); split_unrounded->add_transfer_strategy(edge_length_update); split_unrounded->add_transfer_strategy(tag_update); // for renew the queue - // split_unrounded->add_transfer_strategy(energy_filter_update); - - // split->add_transfer_strategy(target_edge_length_update); auto split_sequence = std::make_shared(mesh); split_sequence->add_operation(split_then_round); split_sequence->add_operation(split_unrounded); - // split_sequence->add_invariant( - // std::make_shared(mesh, energy_filter_attribute.as())); split_sequence->set_priority(long_edges_first); @@ -623,13 +574,9 @@ std::vector, std::string>> wildmeshing_embedding // collapse transfer ////////////////////////////////// auto clps_strat1 = std::make_shared>(pt_attribute); - // clps_strat1->set_simplex_predicate(BasicSimplexPredicate::IsInterior); - // clps_strat1->set_strategy(CollapseBasicStrategy::Default); clps_strat1->set_strategy(CollapseBasicStrategy::CopyOther); auto clps_strat2 = std::make_shared>(pt_attribute); - // clps_strat2->set_simplex_predicate(BasicSimplexPredicate::IsInterior); - // clps_strat2->set_strategy(CollapseBasicStrategy::Default); clps_strat2->set_strategy(CollapseBasicStrategy::CopyTuple); ////////////////////////////////// @@ -648,8 +595,6 @@ std::vector, std::string>> wildmeshing_embedding visited_edge_flag, wmtk::operations::CollapseBasicStrategy::None); - collapse->add_transfer_strategy(tag_update); - // collapse->add_transfer_strategy(energy_filter_update); for (const auto& attr : pass_through_attributes) { collapse->set_new_attribute_strategy( attr, @@ -658,13 +603,10 @@ std::vector, std::string>> wildmeshing_embedding collapse->set_new_attribute_strategy( target_edge_length_attribute, wmtk::operations::CollapseBasicStrategy::None); - // THis triggers a segfault in release - // solved somehow - // collapse->set_priority(short_edges_first); + collapse->add_transfer_strategy(tag_update); collapse->add_transfer_strategy(amips_update); collapse->add_transfer_strategy(edge_length_update); - // collapse->add_transfer_strategy(target_edge_length_update); for (auto& s : update_child_position) { collapse->add_transfer_strategy(s); @@ -673,34 +615,20 @@ std::vector, std::string>> wildmeshing_embedding auto collapse1 = std::make_shared(mesh); - // TODO: implement for 2d - // collapse1->add_invariant(std::make_shared( - // mesh, - // pt_attribute.as(), - // amips_attribute.as(), - // 1)); - collapse1->add_invariant(frozen_vertex_invariant); collapse1->set_new_attribute_strategy(pt_attribute, clps_strat1); collapse1->set_new_attribute_strategy( frozen_vertex_attribute, CollapseBasicStrategy::CopyOther); - // collapse1->set_new_attribute_strategy(sizing_field_scalar_attribute, clps_strat1); setup_collapse(collapse1); auto collapse2 = std::make_shared(mesh); - // collapse2->add_invariant(std::make_shared( - // mesh, - // pt_attribute.as(), - // amips_attribute.as(), - // 0)); collapse2->add_invariant(frozen_opp_vertex_invariant); collapse2->set_new_attribute_strategy(pt_attribute, clps_strat2); collapse2->set_new_attribute_strategy( frozen_vertex_attribute, CollapseBasicStrategy::CopyTuple); - // collapse2->set_new_attribute_strategy(sizing_field_scalar_attribute, clps_strat2); setup_collapse(collapse2); auto collapse = std::make_shared(mesh); @@ -713,9 +641,6 @@ std::vector, std::string>> wildmeshing_embedding collapse_then_round->add_operation(rounding); collapse_then_round->set_priority(short_edges_first); - // collapse_then_round->add_invariant( - // std::make_shared(mesh, energy_filter_attribute.as())); - for (auto& s : update_child_position) { collapse_then_round->add_transfer_strategy(s); @@ -749,10 +674,6 @@ std::vector, std::string>> wildmeshing_embedding op.add_transfer_strategy(amips_update); op.add_transfer_strategy(edge_length_update); op.add_transfer_strategy(tag_update); - // op.add_transfer_strategy(target_edge_length_update); - // for (auto& s : update_child_position) { - // op.add_transfer_strategy(s); - // } collapse.add_invariant(invariant_separate_substructures); collapse.add_invariant(link_condition); @@ -787,13 +708,6 @@ std::vector, std::string>> wildmeshing_embedding target_edge_length_attribute, wmtk::operations::CollapseBasicStrategy::None); - // this might not be necessary - // for (auto& s : update_child_position) { - // collapse.add_transfer_strategy(s); - // split.add_transfer_strategy(s); - // } - - for (const auto& attr : pass_through_attributes) { split.set_new_attribute_strategy( attr, @@ -817,53 +731,9 @@ std::vector, std::string>> wildmeshing_embedding ops_name.emplace_back("rounding"); } + ///////////////////////////////////////// // 4) Smoothing - // ////////////////////////////////////// - // // special smoothing on surface - // ////////////////////////////////////// - - // if (mesh.top_simplex_type() == PrimitiveType::Tetrahedron) { - // for (auto& pair : mesh_constraint_pairs) { - // if (pair.second.mesh().top_simplex_type() != PrimitiveType::Triangle) continue; - - // auto& child_mesh = pair.second.mesh(); - // auto& child_position_handle = pair.second; - - // auto lap_smoothing = std::make_shared( - // child_mesh, - // child_position_handle.as()); - // lap_smoothing->add_invariant(std::make_shared( - // child_mesh, - // child_position_handle.as())); - // lap_smoothing->add_invariant(inversion_invariant); - - // auto proj_lap_smoothing = - // std::make_shared(lap_smoothing, mesh_constraint_pairs); - // proj_lap_smoothing->use_random_priority() = true; - - // proj_lap_smoothing->add_invariant(envelope_invariant); - // proj_lap_smoothing->add_invariant(inversion_invariant); - // proj_lap_smoothing->add_invariant( - // std::make_shared(mesh, - // energy_filter_attribute.as())); - - // proj_lap_smoothing->add_transfer_strategy(amips_update); - // proj_lap_smoothing->add_transfer_strategy(edge_length_update); - // for (auto& s : update_parent_position) { // TODO::this should from only one child - // proj_lap_smoothing->add_transfer_strategy(s); - // } - // for (auto& s : update_child_position) { - // proj_lap_smoothing->add_transfer_strategy(s); - // } - - // ops.push_back(proj_lap_smoothing); - // ops_name.push_back("LAPLACIAN SMOOTHING"); - // } - // } - - // auto energy = - // std::make_shared(mesh, pt_attribute, *amips); - // auto smoothing = std::make_shared(energy); + ///////////////////////////////////////// auto smoothing = std::make_shared(mesh, pt_attribute); smoothing->add_invariant(std::make_shared(mesh, pt_attribute.as())); smoothing->add_invariant(frozen_vertex_invariant); @@ -872,22 +742,11 @@ std::vector, std::string>> wildmeshing_embedding smoothing->add_transfer_strategy(s); } - // test code - // smoothing->add_invariant(envelope_invariant); - // smoothing->add_transfer_strategy(edge_length_update); - // ops.push_back(smoothing); - // ops_name.push_back("SMOOTHING"); - // ops.emplace_back(rounding); - // ops_name.emplace_back("rounding"); - //-------- - auto proj_smoothing = std::make_shared(smoothing, mesh_constraint_pairs); - proj_smoothing->use_random_priority() = true; + // proj_smoothing->use_random_priority() = true; proj_smoothing->add_invariant(frozen_vertex_invariant); proj_smoothing->add_invariant(envelope_invariant); proj_smoothing->add_invariant(inversion_invariant); - // proj_smoothing->add_invariant( - // std::make_shared(mesh, energy_filter_attribute.as())); proj_smoothing->add_transfer_strategy(amips_update); proj_smoothing->add_transfer_strategy(edge_length_update); @@ -898,11 +757,9 @@ std::vector, std::string>> wildmeshing_embedding for (auto& s : update_child_position) { proj_smoothing->add_transfer_strategy(s); } - // proj_smoothing->add_transfer_strategy(target_edge_length_update); if (!options.skip_smooth) { for (int i = 0; i < 1; ++i) { - // some old code to do smoothing several times, maybe useful later ops.push_back(proj_smoothing); ops_name.push_back("SMOOTHING"); } @@ -926,7 +783,7 @@ std::vector, std::string>> wildmeshing_embedding const size_t freq = options.scheduler_update_frequency; scheduler.set_update_frequency(freq == 0 ? std::optional{} : freq); } - int64_t success = 10; + // int64_t success = 10; ////////////////////////////////// // preprocessing @@ -940,7 +797,7 @@ std::vector, std::string>> wildmeshing_embedding pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); } if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - wmtk::logger().error("Flipped triangle!"); + logger().error("Flipped triangle!"); } } @@ -954,18 +811,9 @@ std::vector, std::string>> wildmeshing_embedding wmtk::attribute::TypedAttributeHandle visited_edge_flag_t = visited_edge_flag.as(); pre_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag_t); - logger().info( - "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, " - "executing: {}", - "preprocessing collapse", - pre_stats.number_of_performed_operations(), - pre_stats.number_of_successful_operations(), - pre_stats.number_of_failed_operations(), - pre_stats.collecting_time, - pre_stats.sorting_time, - pre_stats.executing_time); - - success = pre_stats.number_of_successful_operations(); + print_stats(pre_stats, "preprocessing collapse"); + + // int64_t success = pre_stats.number_of_successful_operations(); // verbose logger, can be removed int64_t unrounded = 0; @@ -995,42 +843,14 @@ std::vector, std::string>> wildmeshing_embedding pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); } if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - wmtk::logger().error("Flipped triangle!"); + logger().error("Flipped triangle!"); } } + std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); - // compute max energy - double max_energy = std::numeric_limits::lowest(); - double min_energy = std::numeric_limits::max(); - double avg_energy = 0; - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); - double e = amips_accessor.scalar_attribute(t); - max_energy = std::max(max_energy, e); - min_energy = std::min(min_energy, e); - avg_energy += e; - } - - avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); - - logger().info( - "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", - max_energy, - min_energy, - avg_energy); - - // std::ofstream file0("quality_plot_pre.csv"); - // file0 << "tid, quality" << std::endl; - // int64_t t_cnt = 0; - // for (const auto& t : mesh.get_all(PrimitiveType::Tetrahedron)) { - // t_cnt++; - // file0 << t_cnt << ", " << amips_accessor.scalar_attribute(t) << std::endl; - // } - - - double old_max_energy = max_energy; - double old_avg_energy = avg_energy; + double old_max_energy = max_amips; + double old_avg_energy = min_amips; int iii = 0; bool is_double = false; for (int64_t i = 0; i < options.max_passes; ++i) { @@ -1043,27 +863,11 @@ std::vector, std::string>> wildmeshing_embedding SchedulerStats stats; if (op->primitive_type() == PrimitiveType::Edge) { stats = scheduler.run_operation_on_all(*op, visited_edge_flag_t); - // } else if (ops_name[jj] == "SMOOTHING") { - // // stats = scheduler.run_operation_on_all(*op); - // stats = - // scheduler.run_operation_on_all_coloring(*op, - // coloring_attribute.as()); } else { stats = scheduler.run_operation_on_all(*op); } pass_stats += stats; - logger().info( - "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, " - "executing: {}", - ops_name[jj], - stats.number_of_performed_operations(), - stats.number_of_successful_operations(), - stats.number_of_failed_operations(), - stats.collecting_time, - stats.sorting_time, - stats.executing_time); - - success = stats.number_of_successful_operations(); + print_stats(stats, ops_name[jj]); // verbose logger, can be removed int64_t unrounded = 0; @@ -1087,43 +891,17 @@ std::vector, std::string>> wildmeshing_embedding pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); } if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - wmtk::logger().error("Flipped triangle!"); + logger().error("Flipped triangle!"); } } - avg_energy = 0; - - // compute max energy - max_energy = std::numeric_limits::lowest(); - min_energy = std::numeric_limits::max(); - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); - double e = amips_accessor.scalar_attribute(t); - max_energy = std::max(max_energy, e); - min_energy = std::min(min_energy, e); - avg_energy += e; - } - - avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); - - logger().info( - "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", - max_energy, - min_energy, - avg_energy); + std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); ++jj; } - logger().info( - "Executed {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, executing: {}", - pass_stats.number_of_performed_operations(), - pass_stats.number_of_successful_operations(), - pass_stats.number_of_failed_operations(), - pass_stats.collecting_time, - pass_stats.sorting_time, - pass_stats.executing_time); + print_stats(pass_stats); multimesh::consolidate(mesh); @@ -1137,26 +915,12 @@ std::vector, std::string>> wildmeshing_embedding assert(mesh.is_connectivity_valid()); - // compute max energy - max_energy = std::numeric_limits::lowest(); - min_energy = std::numeric_limits::max(); - avg_energy = 0; - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); - double e = amips_accessor.scalar_attribute(t); - max_energy = std::max(max_energy, e); - min_energy = std::min(min_energy, e); - avg_energy += e; - } - - avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); - int64_t unrounded = 0; if (!is_double) { bool rational = false; for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { const auto p = pt_accessor.vector_attribute(v); - for (int64_t d = 0; d < bmax.size(); ++d) { + for (int64_t d = 0; d < pt_accessor.dimension(); ++d) { if (!p[d].is_rounded()) { rational = true; ++unrounded; @@ -1169,94 +933,15 @@ std::vector, std::string>> wildmeshing_embedding } logger().info("Mesh has {} unrounded vertices", unrounded); - logger().info( - "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", - max_energy, - min_energy, - avg_energy); - - - // adjust sizing field - // if (i > 0 && old_max_energy - max_energy < 5e-1 && - // (old_avg_energy - avg_energy) / avg_energy < 0.1) { - // wmtk::logger().info("adjusting sizing field ..."); - - // adjust_sizing_field( - // mesh, - // pt_attribute.as(), - // edge_length_attribute.as(), - // sizing_field_scalar_attribute.as(), - // amips_attribute.as(), - // target_edge_length_attribute.as(), - // visited_vertex_flag.as(), - // target_max_amips, - // max_energy, - // target_edge_length, - // min_edge_length); - - // wmtk::logger().info("adjusting sizing field finished"); - - // // wmtk::logger().info("setting energy filter ..."); - // // set_operation_energy_filter_after_sizing_field( - // // mesh, - // // pt_attribute.as(), - // // amips_attribute.as(), - // // energy_filter_attribute.as(), - // // visited_vertex_flag.as(), - // // target_max_amips, - // // max_energy, - // // target_edge_length); - // // wmtk::logger().info("setting energy filter finished"); - - // // int64_t e_cnt = 0; - // // for (const auto& e : mesh.get_all(PrimitiveType::Edge)) { - // // if (energy_filter_accessor.scalar_attribute(e) == char(1) || - // // energy_filter_accessor.scalar_attribute( - // // mesh.switch_tuple(e, PrimitiveType::Vertex)) == char(1)) { - // // e_cnt++; - // // } - // // } - // // wmtk::logger().info( - // // "{} edges are going to be executed out of {}", - // // e_cnt, - // // mesh.get_all(PrimitiveType::Edge).size()); - - // for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { - // energy_filter_accessor.scalar_attribute(v) = char(1); - // } - // wmtk::logger().info("reset energy filter"); - // } else { - // wmtk::logger().info("setting energy filter ..."); - // set_operation_energy_filter( - // mesh, - // pt_attribute.as(), - // amips_attribute.as(), - // energy_filter_attribute.as(), - // visited_vertex_flag.as(), - // target_max_amips, - // max_energy, - // target_edge_length); - // wmtk::logger().info("setting energy filter finished"); - - // int64_t e_cnt = 0; - // for (const auto& e : mesh.get_all(PrimitiveType::Edge)) { - // if (energy_filter_accessor.scalar_attribute(e) == char(1) || - // energy_filter_accessor.scalar_attribute( - // mesh.switch_tuple(e, PrimitiveType::Vertex)) == char(1)) { - // e_cnt++; - // } - // } - // wmtk::logger().info( - // "{} edges are going to be executed out of {}", - // e_cnt, - // mesh.get_all(PrimitiveType::Edge).size()); - // } - - old_max_energy = max_energy; - old_avg_energy = avg_energy; + std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); + + old_max_energy = max_amips; + old_avg_energy = avg_amips; // stop at good quality - if (max_energy <= target_max_amips && is_double) break; + if (max_amips <= target_max_amips && is_double) { + break; + } } logger().info("----------------------- Postprocess Collapse -----------------------"); @@ -1264,36 +949,9 @@ std::vector, std::string>> wildmeshing_embedding logger().info("Executing collapse ..."); auto post_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag_t); - logger().info( - "Executed {}, {} ops (S/F) {}/{}. Time: collecting: {}, sorting: {}, " - "executing: {}", - "preprocessing collapse", - post_stats.number_of_performed_operations(), - post_stats.number_of_successful_operations(), - post_stats.number_of_failed_operations(), - post_stats.collecting_time, - post_stats.sorting_time, - post_stats.executing_time); - - // compute max energy - max_energy = std::numeric_limits::lowest(); - min_energy = std::numeric_limits::max(); - avg_energy = 0; - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - // double e = amips->get_value(simplex::Simplex(mesh.top_simplex_type(), t)); - double e = amips_accessor.scalar_attribute(t); - max_energy = std::max(max_energy, e); - min_energy = std::min(min_energy, e); - avg_energy += e; - } - - avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); + print_stats(post_stats, "preprocessing collapse"); - logger().info( - "Max AMIPS Energy: {}, Min AMIPS Energy: {}, Avg AMIPS Energy: {}", - max_energy, - min_energy, - avg_energy); + std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); multimesh::consolidate(mesh); diff --git a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp index 58ee20431b..47edf2f9eb 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.cpp @@ -2,6 +2,7 @@ #include "internal/wildmeshing2d.hpp" #include "internal/wildmeshing3d.hpp" +#include "internal/wildmeshing_embedding_2d.hpp" #include @@ -10,20 +11,25 @@ namespace wmtk::components { using namespace internal; std::vector, std::string>> wildmeshing( - const WildMeshingOptions& option) + const WildMeshingOptions& options) { - if (option.input_mesh->top_simplex_type() == PrimitiveType::Triangle) { - return wildmeshing2d(option); + if (options.use_embedding) { + if (options.input_mesh->top_simplex_type() == PrimitiveType::Triangle) { + return wildmeshing_embedding_2d(options); + } else { + log_and_throw_error("3D embedding not implemented yet."); + // return wildmeshing3d(option); + } + } + + if (options.input_mesh->top_simplex_type() == PrimitiveType::Triangle) { + return wildmeshing2d(options); } else { - return wildmeshing3d(option); + return wildmeshing3d(options); } assert(false); return {}; } -void wildmeshing(const WildMeshingEmbeddingOptions& options) { - log_and_throw_error("not implemented"); -} - } // namespace wmtk::components diff --git a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp index 45d4fff3e1..2b5ea50dbb 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/wildmeshing.hpp @@ -3,14 +3,11 @@ #include #include -#include "internal/WildmeshingEmbeddingOptions.hpp" #include "internal/WildmeshingOptions.hpp" namespace wmtk::components { std::vector, std::string>> wildmeshing( - const WildMeshingOptions& option); - -void wildmeshing(const WildMeshingEmbeddingOptions& options); + const WildMeshingOptions& options); } // namespace wmtk::components diff --git a/src/wmtk/utils/CMakeLists.txt b/src/wmtk/utils/CMakeLists.txt index a72bb1a053..825e2e1372 100644 --- a/src/wmtk/utils/CMakeLists.txt +++ b/src/wmtk/utils/CMakeLists.txt @@ -72,6 +72,11 @@ set(SRC_FILES DynamicArray.hpp DynamicArray.hxx + + bbox_from_mesh.hpp + bbox_from_mesh.cpp + bvh_from_mesh.hpp + bvh_from_mesh.cpp ) target_sources(wildmeshing_toolkit PRIVATE ${SRC_FILES}) diff --git a/src/wmtk/utils/bbox_from_mesh.cpp b/src/wmtk/utils/bbox_from_mesh.cpp new file mode 100644 index 0000000000..0e17c12140 --- /dev/null +++ b/src/wmtk/utils/bbox_from_mesh.cpp @@ -0,0 +1,59 @@ +#include "bbox_from_mesh.hpp" + +#include + +#include +#include + +namespace wmtk::utils { + + +Eigen::MatrixXd bbox_from_mesh(const attribute::MeshAttributeHandle& position_handle) +{ + const Mesh& m = position_handle.mesh(); + + const int64_t dim = position_handle.dimension(); + + Eigen::MatrixXd bbox; + bbox.resize(2, dim); + for (int64_t d = 0; d < dim; ++d) { + bbox(0, d) = std::numeric_limits::max(); + bbox(1, d) = std::numeric_limits::lowest(); + } + + std::visit( + [&bbox, &dim, &m](const auto& v) -> void { + using T = typename std::decay_t::Type; + const auto p_acc = m.create_const_accessor(v); + for (const Tuple& t : m.get_all(PrimitiveType::Vertex)) { + const auto p = p_acc.const_vector_attribute(t); + for (int64_t d = 0; d < dim; ++d) { + if constexpr (std::is_same_v) { + bbox(0, d) = std::min(bbox(0, d), p[d]); + bbox(1, d) = std::max(bbox(1, d), p[d]); + } else if constexpr (std::is_same_v) { + bbox(0, d) = std::min(bbox(0, d), p[d].to_double()); + bbox(1, d) = std::max(bbox(1, d), p[d].to_double()); + } else { + log_and_throw_error("Cannot compute bbox. Unknown position handle type."); + } + } + } + }, + position_handle.handle()); + + // std::cout << "BBOX:\n" << bbox << std::endl; + + return bbox; +} + +double bbox_diagonal_from_mesh(const attribute::MeshAttributeHandle& position_handle) +{ + const Eigen::MatrixXd bbox = bbox_from_mesh(position_handle); + + const double d = (bbox.row(0) - bbox.row(1)).norm(); + + return d; +} + +} // namespace wmtk::utils \ No newline at end of file diff --git a/src/wmtk/utils/bbox_from_mesh.hpp b/src/wmtk/utils/bbox_from_mesh.hpp new file mode 100644 index 0000000000..3d850cba73 --- /dev/null +++ b/src/wmtk/utils/bbox_from_mesh.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace wmtk::utils { + +/** + * @brief Compute the bounding box of a mesh. + * + * @param position_handle The position handle, which also contains a pointer to the mesh. + * @return A matrix where the first row contains all minimal coordinates and the second row contains + * all maximal coordinates. + */ +Eigen::MatrixXd bbox_from_mesh(const attribute::MeshAttributeHandle& position_handle); + +/** + * @brief Compute the diagonal of the bounding box of a mesh. + * @param position_handle The position handle, which also contains a pointer to the mesh. + * @return The diagonal of the bounding box. + */ +double bbox_diagonal_from_mesh(const attribute::MeshAttributeHandle& position_handle); + +} // namespace wmtk::utils \ No newline at end of file diff --git a/src/wmtk/utils/bvh_from_mesh.cpp b/src/wmtk/utils/bvh_from_mesh.cpp new file mode 100644 index 0000000000..ccb1866aa1 --- /dev/null +++ b/src/wmtk/utils/bvh_from_mesh.cpp @@ -0,0 +1,217 @@ +#include "bvh_from_mesh.hpp" + +#include +#include +#include + +namespace wmtk::components::internal::utils { + + +std::shared_ptr bvh_from_mesh(const attribute::MeshAttributeHandle& position_handle) +{ + const Mesh& mesh = position_handle.mesh(); + + constexpr PrimitiveType PV = PrimitiveType::Vertex; + constexpr PrimitiveType PE = PrimitiveType::Edge; + + const attribute::Accessor accessor = + mesh.create_const_accessor(position_handle.as()); + + Eigen::MatrixXd V; + Eigen::MatrixXi F; + + if (mesh.top_simplex_type() == PrimitiveType::Triangle) { + int64_t count = 0; + assert(accessor.dimension() <= 3); + + const std::vector& face_tuples = mesh.get_all(PrimitiveType::Triangle); + + V.resize(3 * face_tuples.size(), accessor.dimension()); + V.setZero(); + F.resize(face_tuples.size(), 3); + + for (const Tuple& f : face_tuples) { + const int64_t fid = f.global_cid(); + + auto p0 = accessor.const_vector_attribute(f); + auto p1 = accessor.const_vector_attribute(mesh.switch_tuple(f, PV)); + auto p2 = accessor.const_vector_attribute(mesh.switch_tuples(f, {PE, PV})); + + F.row(fid) = Eigen::Vector3i(count, count + 1, count + 2); + V.row(3 * fid) = p0; + V.row(3 * fid + 1) = p1; + V.row(3 * fid + 2) = p2; + + count += 3; + } + + + } else if (mesh.top_simplex_type() == PrimitiveType::Edge) { + int64_t count = 0; + + const std::vector& edge_tuples = mesh.get_all(PrimitiveType::Edge); + + V.resize(2 * edge_tuples.size(), accessor.dimension()); + F.resize(edge_tuples.size(), 2); + + for (const Tuple& e : edge_tuples) { + const int64_t eid = e.global_cid(); + + auto p0 = accessor.const_vector_attribute(e); + auto p1 = accessor.const_vector_attribute(mesh.switch_tuple(e, PV)); + + F.row(eid) = Eigen::Vector2i(count, count + 1); + V.row(2 * eid) = p0; + V.row(2 * eid + 1) = p1; + + count += 2; + } + + } else { + log_and_throw_error("bvh_from_mesh works only for tri/edges meshes"); + } + + + std::shared_ptr bvh = std::make_shared(); + bvh->init(V, F, 1e-10); + return bvh; +} + +std::shared_ptr bvh_from_mesh( + const attribute::MeshAttributeHandle& position_handle, + const PrimitiveType pt) + +{ + const Mesh& mesh = position_handle.mesh(); + + constexpr PrimitiveType PV = PrimitiveType::Vertex; + constexpr PrimitiveType PE = PrimitiveType::Edge; + + const attribute::Accessor accessor = + mesh.create_const_accessor(position_handle.as()); + + Eigen::MatrixXd V; + Eigen::MatrixXi F; + + if (pt == PrimitiveType::Triangle) { + int64_t count = 0; + assert(accessor.dimension() <= 3); + + const std::vector& face_tuples = mesh.get_all(PrimitiveType::Triangle); + + V.resize(3 * face_tuples.size(), accessor.dimension()); + V.setZero(); + F.resize(face_tuples.size(), 3); + + for (int64_t fid = 0; fid < face_tuples.size(); ++fid) { + const Tuple& f = face_tuples[fid]; + + auto p0 = accessor.const_vector_attribute(f); + auto p1 = accessor.const_vector_attribute(mesh.switch_tuple(f, PV)); + auto p2 = accessor.const_vector_attribute(mesh.switch_tuples(f, {PE, PV})); + + F.row(fid) = Eigen::Vector3i(count, count + 1, count + 2); + V.row(3 * fid) = p0; + V.row(3 * fid + 1) = p1; + V.row(3 * fid + 2) = p2; + + count += 3; + } + + + } else if (pt == PrimitiveType::Edge) { + int64_t count = 0; + + const std::vector& edge_tuples = mesh.get_all(PrimitiveType::Edge); + + V.resize(2 * edge_tuples.size(), accessor.dimension()); + F.resize(edge_tuples.size(), 2); + + for (int64_t eid = 0; eid < edge_tuples.size(); ++eid) { + const Tuple& e = edge_tuples[eid]; + + auto p0 = accessor.const_vector_attribute(e); + auto p1 = accessor.const_vector_attribute(mesh.switch_tuple(e, PV)); + + F.row(eid) = Eigen::Vector2i(count, count + 1); + V.row(2 * eid) = p0; + V.row(2 * eid + 1) = p1; + + count += 2; + } + + } else { + log_and_throw_error("bvh_from_mesh works only for tri/edges meshes"); + } + + + std::shared_ptr bvh = std::make_shared(); + bvh->init(V, F, 1e-10); + return bvh; +} + +std::shared_ptr bvh_from_mesh( + attribute::MeshAttributeHandle& position_handle, + attribute::MeshAttributeHandle& label_handle, + const int64_t label_value) +{ + const Mesh& mesh = position_handle.mesh(); + + std::shared_ptr bvh; + + const auto p_acc = mesh.create_const_accessor(position_handle); + const auto tag_acc = mesh.create_const_accessor(label_handle); + + std::vector pts; + + for (const Tuple& t : mesh.get_all(PrimitiveType::Triangle)) { + const simplex::Simplex tri(mesh, PrimitiveType::Triangle, t); + + if (tag_acc.primitive_type() == PrimitiveType::Tetrahedron) { + const auto tets = simplex::top_dimension_cofaces(mesh, tri); + if (tets.size() != 2) { + continue; + } + + // find tets that are on the boundary of the tagged region + if ((tag_acc.const_scalar_attribute(tets.simplex_vector()[0]) == label_value) == + (tag_acc.const_scalar_attribute(tets.simplex_vector()[1]) == label_value)) { + continue; + } + } else { + // find tagged triangles + if (tag_acc.const_scalar_attribute(tri) != label_value) { + continue; + } + } + + const auto vertices = simplex::faces_single_dimension(mesh, tri, PrimitiveType::Vertex); + pts.emplace_back(p_acc.const_vector_attribute(vertices.simplex_vector()[0])); + pts.emplace_back(p_acc.const_vector_attribute(vertices.simplex_vector()[1])); + pts.emplace_back(p_acc.const_vector_attribute(vertices.simplex_vector()[2])); + } + + if (pts.empty()) { + log_and_throw_error("Seems like the input mesh does not contain any tagged elements."); + } + + Eigen::MatrixXd V; + Eigen::MatrixXi F; + V.resize(pts.size(), 3); + F.resize(pts.size() / 3, 3); + for (size_t i = 0; i < F.rows(); ++i) { + const size_t v0 = 3 * i + 0; + const size_t v1 = 3 * i + 1; + const size_t v2 = 3 * i + 2; + V.row(v0) = pts[v0]; + V.row(v1) = pts[v1]; + V.row(v2) = pts[v2]; + F.row(i) = Eigen::Vector3i(v0, v1, v2); + } + bvh = std::make_shared(); + bvh->init(V, F, 1e-10); + + return bvh; +} + +} // namespace wmtk::components::internal::utils \ No newline at end of file diff --git a/src/wmtk/utils/bvh_from_mesh.hpp b/src/wmtk/utils/bvh_from_mesh.hpp new file mode 100644 index 0000000000..cf0e946b00 --- /dev/null +++ b/src/wmtk/utils/bvh_from_mesh.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +namespace wmtk::components::internal::utils { + +std::shared_ptr bvh_from_mesh( + const attribute::MeshAttributeHandle& position_handle); + +std::shared_ptr bvh_from_mesh( + const attribute::MeshAttributeHandle& position_handle, + const PrimitiveType pt); + +/** + * @brief Generate a bvh from tagged simplices. + * + * This method assumes that the bvh should contain triangles. If the labels are on tetrahedra, the + * boundary of those is used. + */ +std::shared_ptr bvh_from_mesh( + attribute::MeshAttributeHandle& position_handle, + attribute::MeshAttributeHandle& label_handle, + const int64_t label_value); + +} // namespace wmtk::components::internal::utils \ No newline at end of file From 2c87b7fa47fc246f1864225af6548d14bc12163f Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 19 Feb 2025 15:34:16 -0500 Subject: [PATCH 21/47] minor change --- .../internal/wildmeshing_embedding_2d.cpp | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index d240ed0f7e..35412773f1 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -186,30 +186,17 @@ std::vector, std::string>> wildmeshing_embedding // amips update auto compute_amips = [](const Eigen::MatrixX& P) -> Eigen::VectorXd { assert(P.rows() == 2 || P.rows() == 3); // rows --> attribute dimension - assert(P.cols() == P.rows() + 1); - if (P.cols() == 3) { - // triangle - assert(P.rows() == 2); - std::array pts; - for (size_t i = 0; i < 3; ++i) { - for (size_t j = 0; j < 2; ++j) { - pts[2 * i + j] = P(j, i).to_double(); - } - } - const double a = Tri_AMIPS_energy(pts); - return Eigen::VectorXd::Constant(1, a); - } else { - // tet - assert(P.rows() == 3); - std::array pts; - for (size_t i = 0; i < 4; ++i) { - for (size_t j = 0; j < 3; ++j) { - pts[3 * i + j] = P(j, i).to_double(); - } + assert(P.cols() == 2); + // triangle + assert(P.rows() == 2); + std::array pts; + for (size_t i = 0; i < 3; ++i) { + for (size_t j = 0; j < 2; ++j) { + pts[2 * i + j] = P(j, i).to_double(); } - const double a = Tet_AMIPS_energy(pts); - return Eigen::VectorXd::Constant(1, a); } + const double a = Tri_AMIPS_energy(pts); + return Eigen::VectorXd::Constant(1, a); }; auto amips_update = std::make_shared>( From af3a8930655430b3c914b0cc094747416cbc9344 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 19 Feb 2025 15:56:13 -0500 Subject: [PATCH 22/47] Use iterable in AMIPS smoothing. --- .../operations/AMIPSOptimizationSmoothing.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/wmtk/operations/AMIPSOptimizationSmoothing.cpp b/src/wmtk/operations/AMIPSOptimizationSmoothing.cpp index b12a76f377..847ade60dd 100644 --- a/src/wmtk/operations/AMIPSOptimizationSmoothing.cpp +++ b/src/wmtk/operations/AMIPSOptimizationSmoothing.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -259,7 +260,7 @@ std::vector AMIPSOptimizationSmoothing::execute(const simplex: if (m_coordinate_handle.holds()) { auto accessor = mesh().create_accessor(m_coordinate_handle.as()); - const auto neighs = wmtk::simplex::cofaces_single_dimension_simplices( + auto neighs = wmtk::simplex::cofaces_single_dimension_iterable( mesh(), simplex, mesh().top_simplex_type()); @@ -267,7 +268,8 @@ std::vector AMIPSOptimizationSmoothing::execute(const simplex: if (mesh().top_simplex_type() == PrimitiveType::Triangle) { std::vector> cells; - for (const simplex::Simplex& cell : neighs) { + for (const Tuple& cell_tuple : neighs) { + const simplex::Simplex cell(mesh().top_simplex_type(), cell_tuple); cells.emplace_back(m_amips.get_raw_coordinates<3, 2>(cell, simplex)); } WMTKAMIPSProblem<6> problem(cells); @@ -287,7 +289,8 @@ std::vector AMIPSOptimizationSmoothing::execute(const simplex: std::vector> cells; - for (simplex::Simplex cell : neighs) { + for (const Tuple& cell_tuple : neighs) { + simplex::Simplex cell(mesh().top_simplex_type(), cell_tuple); // auto vertices = mesh().orient_vertices(cell.tuple()); // int idx = -1; // for (int i = 0; i < 4; ++i) { @@ -399,7 +402,7 @@ std::vector AMIPSOptimizationSmoothing::execute(const simplex: } else { auto accessor = mesh().create_accessor(m_coordinate_handle.as()); - const auto neighs = wmtk::simplex::cofaces_single_dimension_simplices( + auto neighs = wmtk::simplex::cofaces_single_dimension_iterable( mesh(), simplex, mesh().top_simplex_type()); @@ -407,7 +410,8 @@ std::vector AMIPSOptimizationSmoothing::execute(const simplex: if (mesh().top_simplex_type() == PrimitiveType::Triangle) { std::vector> cells; - for (const simplex::Simplex& cell : neighs) { + for (const Tuple& cell_tuple : neighs) { + const simplex::Simplex cell(mesh().top_simplex_type(), cell_tuple); cells.emplace_back(m_amips.get_raw_coordinates<3, 2>(cell, simplex)); } WMTKAMIPSProblem<6> problem(cells); @@ -427,7 +431,9 @@ std::vector AMIPSOptimizationSmoothing::execute(const simplex: std::vector> cells; - for (simplex::Simplex cell : neighs) { + for (const Tuple& cell_tuple : neighs) { + simplex::Simplex cell(mesh().top_simplex_type(), cell_tuple); + if (!mesh().is_ccw(cell.tuple())) { // switch any local id but NOT the vertex cell = simplex::Simplex( From 56061250ddf995787489adab369c4fe489510c49 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 19 Feb 2025 15:56:42 -0500 Subject: [PATCH 23/47] Add missing AMIPS transfer in submesh triwild. --- .../internal/wildmeshing_embedding_2d.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 35412773f1..328331281e 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -474,9 +474,6 @@ std::vector, std::string>> wildmeshing_embedding wmtk::operations::SplitRibBasicStrategy::None); } - split->add_transfer_strategy(edge_length_update); - split->add_transfer_strategy(tag_update); // for renew the queue - auto split_then_round = std::make_shared(mesh); split_then_round->add_operation(split); split_then_round->add_operation(rounding); @@ -537,16 +534,16 @@ std::vector, std::string>> wildmeshing_embedding wmtk::operations::SplitBasicStrategy::Copy, wmtk::operations::SplitRibBasicStrategy::Mean); - split_unrounded->add_transfer_strategy(amips_update); - split_unrounded->add_transfer_strategy(edge_length_update); - split_unrounded->add_transfer_strategy(tag_update); // for renew the queue - auto split_sequence = std::make_shared(mesh); split_sequence->add_operation(split_then_round); split_sequence->add_operation(split_unrounded); split_sequence->set_priority(long_edges_first); + split_sequence->add_transfer_strategy(amips_update); + split_sequence->add_transfer_strategy(edge_length_update); + split_sequence->add_transfer_strategy(tag_update); // for renew the queue + if (!options.skip_split) { ops.emplace_back(split_sequence); @@ -882,6 +879,7 @@ std::vector, std::string>> wildmeshing_embedding } } + amips_update->run_on_all(); std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); From c7d375bf4e97a34a1e091251dbf99fa4d0e62014 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 19 Feb 2025 17:11:30 -0500 Subject: [PATCH 24/47] Fix unrounded vertices bug. --- .../internal/wildmeshing_embedding_2d.cpp | 58 +++++++++---------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 328331281e..273c90e3a6 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -310,8 +310,8 @@ std::vector, std::string>> wildmeshing_embedding using MeshConstrainPair = ProjectOperation::MeshConstrainPair; auto envelope_invariant = std::make_shared(mesh); - std::vector> update_child_position, - update_parent_position; // TODO remove for submesh + std::vector> + update_child_position; // TODO remove for submesh std::vector> envelopes; std::vector mesh_constraint_pairs; @@ -350,10 +350,6 @@ std::vector, std::string>> wildmeshing_embedding e.thickness * bbdiag, constrained_pt_handle)); - update_parent_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( - /*source=*/constrained_pt_handle, - /*target=*/pt_attribute)); // TODO remove for submesh - update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( /*source=*/pt_attribute, /*target=*/constrained_pt_handle)); // TODO remove for submesh @@ -441,6 +437,9 @@ std::vector, std::string>> wildmeshing_embedding rounding->add_invariant( std::make_shared(mesh, pt_attribute.as(), true)); rounding->add_invariant(inversion_invariant); + for (auto& s : update_child_position) { + rounding->add_transfer_strategy(s); + } ////////////////////////////////// @@ -734,9 +733,6 @@ std::vector, std::string>> wildmeshing_embedding proj_smoothing->add_transfer_strategy(amips_update); proj_smoothing->add_transfer_strategy(edge_length_update); - for (auto& s : update_parent_position) { - proj_smoothing->add_transfer_strategy(s); - } for (auto& s : update_child_position) { proj_smoothing->add_transfer_strategy(s); @@ -844,6 +840,7 @@ std::vector, std::string>> wildmeshing_embedding int jj = 0; for (auto& op : ops) { logger().info("Executing {} ...", ops_name[jj]); + SchedulerStats stats; if (op->primitive_type() == PrimitiveType::Edge) { stats = scheduler.run_operation_on_all(*op, visited_edge_flag_t); @@ -854,32 +851,31 @@ std::vector, std::string>> wildmeshing_embedding print_stats(stats, ops_name[jj]); // verbose logger, can be removed - int64_t unrounded = 0; - for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { - const auto p = pt_accessor.vector_attribute(v); - for (int64_t d = 0; d < 2; ++d) { - if (!p[d].is_rounded()) { - ++unrounded; - break; + { + int64_t unrounded = 0; + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + const auto p = pt_accessor.const_vector_attribute(v); + for (int64_t d = 0; d < pt_accessor.dimension(); ++d) { + if (!p[d].is_rounded()) { + ++unrounded; + } } } + logger().info("Mesh has {} unrounded vertices", unrounded); } - logger().info("Mesh has {} unrounded vertices", unrounded); - - // debug code - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - const auto vertices = mesh.orient_vertices(t); - std::vector pos; - for (int i = 0; i < vertices.size(); ++i) { - pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); - } - if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - logger().error("Flipped triangle!"); - } - } - - amips_update->run_on_all(); + //// debug code + // for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { + // const auto vertices = mesh.orient_vertices(t); + // std::vector pos; + // for (int i = 0; i < vertices.size(); ++i) { + // pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); + // } + // if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { + // logger().error("Flipped triangle!"); + // } + // } + // std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); From 94c057670946c0713b8a79beee827be53dcaaaaa Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 19 Feb 2025 19:21:58 -0500 Subject: [PATCH 25/47] Add some documentation to EnvelopeInvariant. --- src/wmtk/invariants/EnvelopeInvariant.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/wmtk/invariants/EnvelopeInvariant.hpp b/src/wmtk/invariants/EnvelopeInvariant.hpp index ea9f6b4fcf..b363009b68 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.hpp +++ b/src/wmtk/invariants/EnvelopeInvariant.hpp @@ -19,6 +19,16 @@ namespace wmtk::invariants { class EnvelopeInvariant : public Invariant { public: + /** + * @brief Creates an envelope for checking if vertices are inside or outside of it. + * + * The check is performed on the `coordinate`, the envelope is constructed from + * `envelope_mesh_coordinate`. + * + * @param envelope_mesh_coordinate Used for constructing the envelope. + * @param envelope_size + * @param coordinate This position handle represents the mesh the envelope is tested on. + */ EnvelopeInvariant( const attribute::MeshAttributeHandle& envelope_mesh_coordinate, double envelope_size, From 678101415af6adb2b37a9b224ad09543f9234917 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 20 Feb 2025 15:05:18 -0500 Subject: [PATCH 26/47] Add log_assert to wmtk::logger() --- src/wmtk/utils/Logger.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/wmtk/utils/Logger.hpp b/src/wmtk/utils/Logger.hpp index d8aa7498c4..655f8e0302 100644 --- a/src/wmtk/utils/Logger.hpp +++ b/src/wmtk/utils/Logger.hpp @@ -45,4 +45,15 @@ template { log_and_throw_error(fmt::format(fmt::runtime(msg), args...)); } + +/** + * @brief Calls log_and_throw_error if check is false. + */ +template +[[noreturn]] void log_assert(const bool check, const std::string& msg, const Args&... args) +{ + if (!check) { + log_and_throw_error(msg, args...); + } +} } // namespace wmtk From 0a0482f70dce189b1624bdacd8e931043361b2b9 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 20 Feb 2025 15:06:43 -0500 Subject: [PATCH 27/47] Add util function submesh_from_multimesh. --- src/wmtk/submesh/CMakeLists.txt | 2 + src/wmtk/submesh/Embedding.cpp | 5 ++ src/wmtk/submesh/Embedding.hpp | 3 ++ src/wmtk/submesh/SubMesh.cpp | 5 ++ src/wmtk/submesh/SubMesh.hpp | 1 + .../submesh/utils/submesh_from_multimesh.cpp | 29 +++++++++++ .../submesh/utils/submesh_from_multimesh.hpp | 9 ++++ tests/submesh/test_submesh.cpp | 49 +++++++++++++++++++ 8 files changed, 103 insertions(+) create mode 100644 src/wmtk/submesh/utils/submesh_from_multimesh.cpp create mode 100644 src/wmtk/submesh/utils/submesh_from_multimesh.hpp diff --git a/src/wmtk/submesh/CMakeLists.txt b/src/wmtk/submesh/CMakeLists.txt index 7ceb9298e8..db8bce5608 100644 --- a/src/wmtk/submesh/CMakeLists.txt +++ b/src/wmtk/submesh/CMakeLists.txt @@ -6,5 +6,7 @@ set(SRC_FILES utils/write.hpp utils/write.cpp + utils/submesh_from_multimesh.hpp + utils/submesh_from_multimesh.cpp ) target_sources(wildmeshing_toolkit PRIVATE ${SRC_FILES}) \ No newline at end of file diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index 12b29cd12a..c8e45422bc 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -191,6 +191,11 @@ int64_t Embedding::id(const Tuple& tuple, PrimitiveType pt) const return mesh().id(tuple, pt); } +std::vector> Embedding::get_child_meshes() const +{ + return m_submeshes; +} + bool Embedding::has_child_mesh() const { return !m_submeshes.empty(); diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index 3941104b5d..2f48c39431 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -49,6 +49,8 @@ class Embedding : public std::enable_shared_from_this, public MeshBas int64_t id(const simplex::Simplex& s) const; int64_t id(const Tuple& tuple, PrimitiveType pt) const; + std::vector> get_child_meshes() const; + bool has_child_mesh() const; bool simplex_is_in_submesh(const simplex::Simplex& s) const; @@ -59,6 +61,7 @@ class Embedding : public std::enable_shared_from_this, public MeshBas std::function substructure_predicate() const; + private: std::shared_ptr m_mesh; diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index f650565bd3..b82f29ea71 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -50,6 +50,11 @@ void SubMesh::add_simplex(const Tuple& tuple, PrimitiveType pt) } } +void SubMesh::add_simplex(const simplex::Simplex& s) +{ + add_simplex(s.tuple(), s.primitive_type()); +} + void SubMesh::add_simplex(const simplex::IdSimplex& simplex) { const PrimitiveType& pt = simplex.primitive_type(); diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index d5c48a9313..db28251f9f 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -45,6 +45,7 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase * any other simplex of the submesh. */ void add_simplex(const Tuple& tuple, PrimitiveType pt); + void add_simplex(const simplex::Simplex& s); void add_simplex(const simplex::IdSimplex& simplex); std::vector get_all(const PrimitiveType pt) const override; diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp new file mode 100644 index 0000000000..e3e9ea4eed --- /dev/null +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp @@ -0,0 +1,29 @@ +#include "submesh_from_multimesh.hpp" + +#include +#include + +namespace wmtk::submesh::utils { + +std::shared_ptr submesh_from_multimesh(const std::shared_ptr& mesh) +{ + log_assert(mesh->is_multi_mesh_root(), "submesh_from_multimesh must be called on root mesh"); + + std::shared_ptr emb_ptr = std::make_shared(mesh); + Embedding& emb = *emb_ptr; + + for (const auto& child_mesh_ptr : mesh->get_child_meshes()) { + auto sub_ptr = emb.add_submesh(); + + const Mesh& cm = *child_mesh_ptr; + for (const Tuple& child_tuple : cm.get_all(cm.top_simplex_type())) { + const simplex::Simplex child_simplex(cm.top_simplex_type(), child_tuple); + const simplex::Simplex parent_simplex = cm.map_to_parent(child_simplex); + sub_ptr->add_simplex(parent_simplex); + } + } + + return emb_ptr; +} + +} // namespace wmtk::submesh::utils \ No newline at end of file diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.hpp b/src/wmtk/submesh/utils/submesh_from_multimesh.hpp new file mode 100644 index 0000000000..71eaa978dc --- /dev/null +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace wmtk::submesh::utils { + +std::shared_ptr submesh_from_multimesh(const std::shared_ptr& mesh); + +} // namespace wmtk::submesh::utils diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index fbeb6ac341..c3010f6049 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -342,4 +343,52 @@ TEST_CASE("submesh_collapse_towards_submesh", "[mesh][submesh]") CHECK_NOTHROW( write("submesh_collapse_towards_submesh", "vertices", emb, true, true, true, false)); } +} + +TEST_CASE("submesh_from_multimesh", "[mesh][submesh]") +{ + // logger().set_level(spdlog::level::off); + // logger().set_level(spdlog::level::trace); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + // add child + { + std::shared_ptr child_ptr = + std::make_shared(tests::single_triangle()); + + const Tuple child_tuple = child_ptr->face_tuple_from_vids(0, 1, 2); + const Tuple parent_tuple = mesh_in->face_tuple_from_vids(8, 9, 5); + std::vector> map_tuples; + map_tuples.emplace_back(std::array{child_tuple, parent_tuple}); + mesh_in->register_child_mesh(child_ptr, map_tuples); + } + // add child + { + std::shared_ptr child_ptr = + std::make_shared(tests::single_triangle()); + + const Tuple child_tuple = child_ptr->face_tuple_from_vids(0, 1, 2); + const Tuple parent_tuple = mesh_in->face_tuple_from_vids(0, 3, 4); + std::vector> map_tuples; + map_tuples.emplace_back(std::array{child_tuple, parent_tuple}); + mesh_in->register_child_mesh(child_ptr, map_tuples); + } + + tests::DEBUG_TriMesh& m = *mesh_in; + + const auto pos = m.get_attribute_handle("vertices", PrimitiveType::Vertex); + const auto pos_acc = m.create_const_accessor(pos); + + auto emb_ptr = submesh::utils::submesh_from_multimesh(mesh_in); + + Embedding& emb = *emb_ptr; + CHECK(emb.has_child_mesh()); + CHECK(emb.get_child_meshes().size() == 2); + { + using submesh::utils::write; + CHECK_NOTHROW(write("submesh_from_multimesh", "vertices", emb, true, true, true, false)); + } } \ No newline at end of file From dd4fb6f4da47e19a1ebefb7ee975735cddeb30d1 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 20 Feb 2025 19:49:20 -0500 Subject: [PATCH 28/47] A bit more clean-up. --- .../internal/wildmeshing_embedding_2d.cpp | 180 ++++++------------ 1 file changed, 62 insertions(+), 118 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 273c90e3a6..5a043f693b 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -83,9 +83,7 @@ using namespace operations::composite; using namespace function; using namespace invariants; -std::tuple min_max_avg_amips( - const Mesh& mesh, - const attribute::MeshAttributeHandle& amips_handle) +double compute_max_amips(const Mesh& mesh, const attribute::MeshAttributeHandle& amips_handle) { const auto amips_accessor = mesh.create_const_accessor(amips_handle); @@ -108,7 +106,7 @@ std::tuple min_max_avg_amips( min_energy, avg_energy); - return std::make_tuple(min_energy, max_energy, avg_energy); + return max_energy; } void print_stats(const wmtk::SchedulerStats& stats, const std::string& name = "") @@ -131,6 +129,43 @@ void print_stats(const wmtk::SchedulerStats& stats, const std::string& name = "" } } +void print_unrounded_vertices(const Mesh& mesh, const attribute::MeshAttributeHandle& pt_handle) +{ + const auto pt_accessor = mesh.create_const_accessor(pt_handle); + + // verbose logger, can be removed + int64_t unrounded = 0; + for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { + const auto p = pt_accessor.const_vector_attribute(v); + for (int64_t d = 0; d < 2; ++d) { + if (!p[d].is_rounded()) { + ++unrounded; + break; + } + } + } + + if (unrounded > 0) { + logger().info("Mesh has {} unrounded vertices", unrounded); + } +} + +void test_flipped_triangles(const Mesh& mesh, const attribute::MeshAttributeHandle& pt_handle) +{ + const auto pt_accessor = mesh.create_const_accessor(pt_handle); + + for (const Tuple& t : mesh.get_all(mesh.top_simplex_type())) { + const auto vertices = mesh.orient_vertices(t); + std::vector pos; + for (int i = 0; i < vertices.size(); ++i) { + pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); + } + if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { + logger().error("Flipped triangle!"); + } + } +} + std::vector, std::string>> wildmeshing_embedding_2d( const WildMeshingOptions& options) { @@ -206,10 +241,7 @@ std::vector, std::string>> wildmeshing_embedding amips_update->run_on_all(); double max_amips; - double min_amips; - double avg_amips; - - std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); + max_amips = compute_max_amips(mesh, amips_attribute); ////////////////////////////////// // Storing target edge length @@ -770,67 +802,22 @@ std::vector, std::string>> wildmeshing_embedding ////////////////////////////////// // debug code - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - const auto vertices = mesh.orient_vertices(t); - std::vector pos; - for (int i = 0; i < vertices.size(); ++i) { - pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); - } - if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - logger().error("Flipped triangle!"); - } - } - - SchedulerStats pre_stats; - - logger().info("----------------------- Preprocess Collapse -----------------------"); - - logger().info("Executing collapse ..."); - - - wmtk::attribute::TypedAttributeHandle visited_edge_flag_t = visited_edge_flag.as(); - - pre_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag_t); - print_stats(pre_stats, "preprocessing collapse"); - - // int64_t success = pre_stats.number_of_successful_operations(); - - // verbose logger, can be removed - int64_t unrounded = 0; - int64_t frozen = 0; - for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { - const auto p = pt_accessor.vector_attribute(v); - for (int64_t d = 0; d < 2; ++d) { - if (!p[d].is_rounded()) { - ++unrounded; - break; - } - } + test_flipped_triangles(mesh, pt_attribute); - if (frozen_vertex_accessor.scalar_attribute(v) == 1) { - frozen++; - } - } + { + logger().info("----------------------- Preprocess Collapse -----------------------"); + // logger().info("Executing collapse ..."); - logger().info("Mesh has {} unrounded vertices", unrounded); - logger().error("Mesh has {} frozen vertices", frozen); + SchedulerStats pre_stats = + scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag.as()); + print_stats(pre_stats, "preprocessing collapse"); - // debug code - for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - const auto vertices = mesh.orient_vertices(t); - std::vector pos; - for (int i = 0; i < vertices.size(); ++i) { - pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); - } - if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - logger().error("Flipped triangle!"); - } + // verbose logger, can be removed + print_unrounded_vertices(mesh, pt_attribute); + test_flipped_triangles(mesh, pt_attribute); + max_amips = compute_max_amips(mesh, amips_attribute); } - std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); - - double old_max_energy = max_amips; - double old_avg_energy = min_amips; int iii = 0; bool is_double = false; for (int64_t i = 0; i < options.max_passes; ++i) { @@ -839,11 +826,11 @@ std::vector, std::string>> wildmeshing_embedding SchedulerStats pass_stats; int jj = 0; for (auto& op : ops) { - logger().info("Executing {} ...", ops_name[jj]); + // logger().info("Executing {} ...", ops_name[jj]); SchedulerStats stats; if (op->primitive_type() == PrimitiveType::Edge) { - stats = scheduler.run_operation_on_all(*op, visited_edge_flag_t); + stats = scheduler.run_operation_on_all(*op, visited_edge_flag.as()); } else { stats = scheduler.run_operation_on_all(*op); } @@ -851,33 +838,9 @@ std::vector, std::string>> wildmeshing_embedding print_stats(stats, ops_name[jj]); // verbose logger, can be removed - { - int64_t unrounded = 0; - for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { - const auto p = pt_accessor.const_vector_attribute(v); - for (int64_t d = 0; d < pt_accessor.dimension(); ++d) { - if (!p[d].is_rounded()) { - ++unrounded; - } - } - } - logger().info("Mesh has {} unrounded vertices", unrounded); - } - - //// debug code - // for (const auto& t : mesh.get_all(mesh.top_simplex_type())) { - // const auto vertices = mesh.orient_vertices(t); - // std::vector pos; - // for (int i = 0; i < vertices.size(); ++i) { - // pos.push_back(pt_accessor.const_vector_attribute(vertices[i])); - // } - // if (wmtk::utils::wmtk_orient2d(pos[0], pos[1], pos[2]) <= 0) { - // logger().error("Flipped triangle!"); - // } - // } - // - std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); - + print_unrounded_vertices(mesh, pt_attribute); + test_flipped_triangles(mesh, pt_attribute); + max_amips = compute_max_amips(mesh, amips_attribute); ++jj; } @@ -896,28 +859,8 @@ std::vector, std::string>> wildmeshing_embedding assert(mesh.is_connectivity_valid()); - int64_t unrounded = 0; - if (!is_double) { - bool rational = false; - for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { - const auto p = pt_accessor.vector_attribute(v); - for (int64_t d = 0; d < pt_accessor.dimension(); ++d) { - if (!p[d].is_rounded()) { - rational = true; - ++unrounded; - break; - } - } - } - - is_double = !rational; - } - - logger().info("Mesh has {} unrounded vertices", unrounded); - std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); - - old_max_energy = max_amips; - old_avg_energy = avg_amips; + print_unrounded_vertices(mesh, pt_attribute); + max_amips = compute_max_amips(mesh, amips_attribute); // stop at good quality if (max_amips <= target_max_amips && is_double) { @@ -927,12 +870,13 @@ std::vector, std::string>> wildmeshing_embedding logger().info("----------------------- Postprocess Collapse -----------------------"); - logger().info("Executing collapse ..."); + // logger().info("Executing collapse ..."); - auto post_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag_t); + auto post_stats = + scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag.as()); print_stats(post_stats, "preprocessing collapse"); - std::tie(min_amips, max_amips, avg_amips) = min_max_avg_amips(mesh, amips_attribute); + max_amips = compute_max_amips(mesh, amips_attribute); multimesh::consolidate(mesh); From fd00c2cc6b6ab11e6aacaea0cbbdee7b44a1e727 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 20 Feb 2025 21:12:20 -0500 Subject: [PATCH 29/47] Starting to add SubMesh to triwild. --- .../internal/wildmeshing_embedding_2d.cpp | 45 +++- src/wmtk/invariants/EnvelopeInvariant.cpp | 216 +++++++++--------- .../submesh/utils/submesh_from_multimesh.cpp | 2 +- src/wmtk/utils/Logger.cpp | 12 +- src/wmtk/utils/Logger.hpp | 5 + 5 files changed, 157 insertions(+), 123 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 5a043f693b..79a18065ee 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -59,6 +59,10 @@ #include #include +#include +#include +#include + #include #include @@ -69,6 +73,7 @@ #include #include +#include #include #include @@ -221,7 +226,7 @@ std::vector, std::string>> wildmeshing_embedding // amips update auto compute_amips = [](const Eigen::MatrixX& P) -> Eigen::VectorXd { assert(P.rows() == 2 || P.rows() == 3); // rows --> attribute dimension - assert(P.cols() == 2); + assert(P.cols() == 3); // triangle assert(P.rows() == 2); std::array pts; @@ -287,6 +292,11 @@ std::vector, std::string>> wildmeshing_embedding compute_edge_length); edge_length_update->run_on_all(); + ////////////////////////////////// + // substructures + ////////////////////////////////// + auto emb_ptr = submesh::utils::submesh_from_multimesh(options.input_mesh); + submesh::Embedding emb = *emb_ptr; ////////////////////////////////// // compute frozen vertices @@ -295,16 +305,34 @@ std::vector, std::string>> wildmeshing_embedding mesh.register_attribute("frozen_vertex", PrimitiveType::Vertex, 1); auto frozen_vertex_accessor = mesh.create_accessor(frozen_vertex_attribute.as()); - auto input_ptr = mesh.get_child_meshes().front(); - - for (const Tuple& v : input_ptr->get_all(PrimitiveType::Vertex)) { - if (input_ptr->is_boundary(PrimitiveType::Vertex, v)) { - const auto& parent_v = - input_ptr->map_to_parent(simplex::Simplex::vertex(*input_ptr, v)); - frozen_vertex_accessor.scalar_attribute(parent_v) = 1; + for (const auto& sub_ptr : emb.get_child_meshes()) { + submesh::SubMesh& sub = *sub_ptr; + for (const simplex::IdSimplex& v : sub.get_all_id_simplex(PrimitiveType::Vertex)) { + int64_t counter = 0; + for (const Tuple& cof : cofaces_single_dimension_iterable( + mesh, + mesh.get_simplex(v), + sub.top_simplex_type())) { + if (sub.contains(cof, sub.top_simplex_type())) { + ++counter; + } + } + if (counter != 2) { + frozen_vertex_accessor.scalar_attribute(v) = 1; + } } } + // auto input_ptr = mesh.get_child_meshes().front(); + // + // for (const Tuple& v : input_ptr->get_all(PrimitiveType::Vertex)) { + // if (input_ptr->is_boundary(PrimitiveType::Vertex, v)) { + // const auto& parent_v = + // input_ptr->map_to_parent(simplex::Simplex::vertex(*input_ptr, v)); + // frozen_vertex_accessor.scalar_attribute(parent_v) = 1; + // } + // } + { int64_t frozen_cnt = 0; for (const auto& v : mesh.get_all(PrimitiveType::Vertex)) { @@ -795,7 +823,6 @@ std::vector, std::string>> wildmeshing_embedding const size_t freq = options.scheduler_update_frequency; scheduler.set_update_frequency(freq == 0 ? std::optional{} : freq); } - // int64_t success = 10; ////////////////////////////////// // preprocessing diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index 98a0ea9ce9..ab259695cb 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -20,149 +20,143 @@ namespace wmtk { - -constexpr PrimitiveType PV = PrimitiveType::Vertex; +constexpr PrimitiveType PF = PrimitiveType::Triangle; constexpr PrimitiveType PE = PrimitiveType::Edge; -} // namespace wmtk -namespace wmtk::invariants { +constexpr PrimitiveType PV = PrimitiveType::Vertex; -EnvelopeInvariant::EnvelopeInvariant( +template +std::shared_ptr init_fast_envelope( const attribute::MeshAttributeHandle& envelope_mesh_coordinate, - double envelope_size, - const attribute::MeshAttributeHandle& coordinate) - : Invariant(coordinate.mesh()) - , m_coordinate_handle(coordinate) - , m_envelope_size(envelope_size) + const double envelope_size) { const auto& envelope_mesh = envelope_mesh_coordinate.mesh(); - assert(envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds()); + const attribute::Accessor accessor = + envelope_mesh.create_const_accessor(envelope_mesh_coordinate.as()); - if (envelope_mesh_coordinate.holds()) { - // for rational - const attribute::Accessor accessor = - envelope_mesh.create_const_accessor(envelope_mesh_coordinate.as()); + std::vector vertices; + std::vector faces; + int count = 0; + assert(accessor.dimension() == 3); - if (envelope_mesh.top_simplex_type() == PrimitiveType::Triangle) { - std::vector vertices; - std::vector faces; + const std::vector& facest = envelope_mesh.get_all(PF); + for (const auto& f : facest) { + if constexpr (std::is_same()) { + Eigen::Vector3d p0 = accessor.const_vector_attribute(f).cast(); + Eigen::Vector3d p1 = + accessor.const_vector_attribute(envelope_mesh.switch_tuple(f, PV)).cast(); + Eigen::Vector3d p2 = + accessor.const_vector_attribute(envelope_mesh.switch_tuples(f, {PE, PV})) + .cast(); - int count = 0; - assert(accessor.dimension() == 3); + vertices.push_back(p0); + vertices.push_back(p1); + vertices.push_back(p2); + } else { + Eigen::Vector3d p0 = accessor.const_vector_attribute(f); + Eigen::Vector3d p1 = accessor.const_vector_attribute(envelope_mesh.switch_tuple(f, PV)); + Eigen::Vector3d p2 = + accessor.const_vector_attribute(envelope_mesh.switch_tuples(f, {PE, PV})); + + vertices.push_back(p0); + vertices.push_back(p1); + vertices.push_back(p2); + } + faces.emplace_back(count, count + 1, count + 2); - const std::vector& facest = envelope_mesh.get_all(wmtk::PrimitiveType::Triangle); - for (const auto& f : facest) { - Eigen::Vector3d p0 = accessor.const_vector_attribute(f).cast(); - Eigen::Vector3d p1 = - accessor.const_vector_attribute(envelope_mesh.switch_tuple(f, PV)) - .cast(); - Eigen::Vector3d p2 = - accessor.const_vector_attribute(envelope_mesh.switch_tuples(f, {PE, PV})) - .cast(); - - faces.emplace_back(count, count + 1, count + 2); - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - - count += 3; - } + count += 3; + } - m_envelope = - std::make_shared(vertices, faces, envelope_size); + return std::make_shared(vertices, faces, envelope_size); +} - } else if (envelope_mesh.top_simplex_type() == PrimitiveType::Edge) { - logger().warn("Envelope for edge mesh is using sampling"); +template +std::shared_ptr init_bvh( + const attribute::MeshAttributeHandle& envelope_mesh_coordinate) +{ + const auto& envelope_mesh = envelope_mesh_coordinate.mesh(); - int64_t count = 0; - int64_t index = 0; + const attribute::Accessor accessor = + envelope_mesh.create_const_accessor(envelope_mesh_coordinate.as()); - const std::vector& edgest = envelope_mesh.get_all(wmtk::PrimitiveType::Edge); + logger().warn("Envelope for edge mesh is using sampling"); - Eigen::MatrixXd vertices(2 * edgest.size(), accessor.dimension()); - Eigen::MatrixXi edges(edgest.size(), 2); + int64_t count = 0; + int64_t index = 0; - for (const auto& e : edgest) { - auto p0 = accessor.const_vector_attribute(e).cast(); - auto p1 = accessor.const_vector_attribute(envelope_mesh.switch_tuple(e, PV)) - .cast(); + const std::vector& edgest = envelope_mesh.get_all(PE); - edges.row(index) << count, count + 1; - vertices.row(2 * index) = p0; - vertices.row(2 * index + 1) = p1; + Eigen::MatrixXd vertices(2 * edgest.size(), accessor.dimension()); + Eigen::MatrixXi edges(edgest.size(), 2); - count += 2; - ++index; - } + for (const Tuple& e : edgest) { + if constexpr (std::is_same()) { + const auto p0 = accessor.const_vector_attribute(e).cast(); + const auto p1 = + accessor.const_vector_attribute(envelope_mesh.switch_tuple(e, PV)).cast(); - m_bvh = std::make_shared(); - m_bvh->init(vertices, edges, 1e-10); + vertices.row(2 * index) = p0; + vertices.row(2 * index + 1) = p1; } else { - throw std::runtime_error("Envelope works only for tri/edges meshes"); - } - } else if (envelope_mesh_coordinate.holds()) { - // for double - const attribute::Accessor accessor = - envelope_mesh.create_const_accessor(envelope_mesh_coordinate.as()); + const auto p0 = accessor.const_vector_attribute(e); + const auto p1 = accessor.const_vector_attribute(envelope_mesh.switch_tuple(e, PV)); + vertices.row(2 * index) = p0; + vertices.row(2 * index + 1) = p1; + } + edges.row(index) << count, count + 1; - if (envelope_mesh.top_simplex_type() == PrimitiveType::Triangle) { - std::vector vertices; - std::vector faces; - - int count = 0; - assert(accessor.dimension() == 3); - - const std::vector& facest = envelope_mesh.get_all(wmtk::PrimitiveType::Triangle); - for (const auto& f : facest) { - Eigen::Vector3d p0 = accessor.const_vector_attribute(f); - Eigen::Vector3d p1 = - accessor.const_vector_attribute(envelope_mesh.switch_tuple(f, PV)); - Eigen::Vector3d p2 = - accessor.const_vector_attribute(envelope_mesh.switch_tuples(f, {PE, PV})); - - faces.emplace_back(count, count + 1, count + 2); - vertices.push_back(p0); - vertices.push_back(p1); - vertices.push_back(p2); - - count += 3; - } - - m_envelope = - std::make_shared(vertices, faces, envelope_size); + count += 2; + ++index; + } - } else if (envelope_mesh.top_simplex_type() == PrimitiveType::Edge) { - logger().warn("Envelope for edge mesh is using sampling"); + std::shared_ptr bvh = std::make_shared(); + bvh->init(vertices, edges, 1e-10); + return bvh; +} - int64_t count = 0; - int64_t index = 0; +} // namespace wmtk +namespace wmtk::invariants { - const std::vector& edgest = envelope_mesh.get_all(wmtk::PrimitiveType::Edge); +EnvelopeInvariant::EnvelopeInvariant( + const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + double envelope_size, + const attribute::MeshAttributeHandle& coordinate) + : Invariant(coordinate.mesh()) + , m_coordinate_handle(coordinate) + , m_envelope_size(envelope_size) +{ + const auto& envelope_mesh = envelope_mesh_coordinate.mesh(); - Eigen::MatrixXd vertices(2 * edgest.size(), accessor.dimension()); - Eigen::MatrixXi edges(edgest.size(), 2); + // log_assert( + // envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds(), + // "Envelope mesh handle type invalid"); - for (const auto& e : edgest) { - auto p0 = accessor.const_vector_attribute(e); - auto p1 = accessor.const_vector_attribute(envelope_mesh.switch_tuple(e, PV)); + if (!(envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds())) { + log_and_throw_error("Envelope mesh handle type invalid"); + } - edges.row(index) << count, count + 1; - vertices.row(2 * index) = p0; - vertices.row(2 * index + 1) = p1; + // log_assert( + // envelope_mesh.top_simplex_type() == PF || envelope_mesh.top_simplex_type() == PE, + // "Envelope works only for tri/edges meshes"); - count += 2; - ++index; - } + if (!(envelope_mesh.top_simplex_type() == PF || envelope_mesh.top_simplex_type() == PE)) { + log_and_throw_error("Envelope works only for tri/edges meshes"); + } - m_bvh = std::make_shared(); - m_bvh->init(vertices, edges, 1e-10); - } else { - throw std::runtime_error("Envelope works only for tri/edges meshes"); + if (envelope_mesh_coordinate.holds()) { + if (envelope_mesh.top_simplex_type() == PF) { + m_envelope = init_fast_envelope(envelope_mesh_coordinate, envelope_size); + } else if (envelope_mesh.top_simplex_type() == PE) { + m_bvh = init_bvh(envelope_mesh_coordinate); + } + } else if (envelope_mesh_coordinate.holds()) { + if (envelope_mesh.top_simplex_type() == PF) { + m_envelope = init_fast_envelope(envelope_mesh_coordinate, envelope_size); + } else if (envelope_mesh.top_simplex_type() == PE) { + m_bvh = init_bvh(envelope_mesh_coordinate); } - } else { - throw std::runtime_error("Envelope mesh handle type invlid"); } } diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp index e3e9ea4eed..692f367c92 100644 --- a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp @@ -7,7 +7,7 @@ namespace wmtk::submesh::utils { std::shared_ptr submesh_from_multimesh(const std::shared_ptr& mesh) { - log_assert(mesh->is_multi_mesh_root(), "submesh_from_multimesh must be called on root mesh"); + // log_assert(mesh->is_multi_mesh_root(), "submesh_from_multimesh must be called on root mesh"); std::shared_ptr emb_ptr = std::make_shared(mesh); Embedding& emb = *emb_ptr; diff --git a/src/wmtk/utils/Logger.cpp b/src/wmtk/utils/Logger.cpp index aa7fcc8214..c99a8b8a92 100644 --- a/src/wmtk/utils/Logger.cpp +++ b/src/wmtk/utils/Logger.cpp @@ -6,9 +6,10 @@ #include namespace wmtk { -bool has_user_overloaded_logger_level() { +bool has_user_overloaded_logger_level() +{ const char* val = std::getenv("WMTK_LOGGER_LEVEL"); - if(val == nullptr) { + if (val == nullptr) { return false; } std::string env_val = val; @@ -104,4 +105,11 @@ void log_and_throw_error(const std::string& msg) throw std::runtime_error(msg); } +void log_assert(const bool check, const std::string& msg) +{ + if (!check) { + log_and_throw_error(msg); + } +} + } // namespace wmtk diff --git a/src/wmtk/utils/Logger.hpp b/src/wmtk/utils/Logger.hpp index 655f8e0302..e59dd23549 100644 --- a/src/wmtk/utils/Logger.hpp +++ b/src/wmtk/utils/Logger.hpp @@ -46,6 +46,11 @@ template log_and_throw_error(fmt::format(fmt::runtime(msg), args...)); } +/** + * @brief Calls log_and_throw_error if check is false. + */ +[[noreturn]] void log_assert(const bool check, const std::string& msg); + /** * @brief Calls log_and_throw_error if check is false. */ From dfdcecebe4fe7be5268048eab263e08b65f9ae36 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Fri, 21 Feb 2025 18:47:41 -0500 Subject: [PATCH 30/47] Add MeshType and a pointer from Mesh to its Embedding. --- src/wmtk/Mesh.hpp | 18 +++++++++++++++++ src/wmtk/MeshBase.hpp | 11 +++++++++++ src/wmtk/Mesh_multimesh.cpp | 25 +++++++++++++++++------- src/wmtk/submesh/Embedding.cpp | 7 +++++++ src/wmtk/submesh/Embedding.hpp | 2 ++ src/wmtk/submesh/SubMesh.cpp | 5 +++++ src/wmtk/submesh/SubMesh.hpp | 2 ++ tests/submesh/test_submesh.cpp | 35 ++++++++++++++++++++++++++++++++++ 8 files changed, 98 insertions(+), 7 deletions(-) diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index 4d6f1a016d..02f2b36c5e 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -86,6 +86,10 @@ class TupleTag; } // namespace utils } // namespace multimesh +namespace submesh { +class Embedding; +} + // NOTE: the implementation of this class is split into several files to improve clang-format // performance @@ -118,6 +122,7 @@ class Mesh : public std::enable_shared_from_this, friend class multimesh::MultiMeshVisitorExecutor; friend class multimesh::attribute::AttributeScopeHandle; friend class multimesh::utils::internal::TupleTag; + friend class submesh::Embedding; friend class operations::utils::UpdateEdgeOperationMultiMeshMapFunctor; friend class operations::Operation; friend class operations::EdgeCollapse; @@ -130,6 +135,8 @@ class Mesh : public std::enable_shared_from_this, PrimitiveType top_simplex_type() const; bool is_free() const; + MeshType mesh_type() const; + // attribute directly hashes its "children" components so it overrides "child_hashes" std::map child_hashables() const override; std::map child_hashes() const override; @@ -776,6 +783,10 @@ class Mesh : public std::enable_shared_from_this, **/ bool is_from_same_multi_mesh_structure(const Mesh& other) const; + bool has_embedding() const; + + submesh::Embedding& get_embedding() const; + protected: // creates a scope as int64_t as the AttributeScopeHandle exists [[nodiscard]] attribute::AttributeScopeHandle create_single_mesh_scope(); @@ -829,6 +840,8 @@ class Mesh : public std::enable_shared_from_this, multimesh::MultiMeshManager m_multi_mesh_manager; + submesh::Embedding* m_embedding = nullptr; // a pointer to the embedding (if there is any) + int64_t m_top_cell_dimension = -1; // assumes no adjacency data exists @@ -978,6 +991,11 @@ inline bool Mesh::is_free() const return m_is_free; } +inline MeshType Mesh::mesh_type() const +{ + return MeshType::Mesh; +} + inline int64_t Mesh::top_cell_dimension() const { return m_top_cell_dimension; diff --git a/src/wmtk/MeshBase.hpp b/src/wmtk/MeshBase.hpp index efb4caa127..0692291cb8 100644 --- a/src/wmtk/MeshBase.hpp +++ b/src/wmtk/MeshBase.hpp @@ -13,6 +13,15 @@ class Simplex; class IdSimplex; } // namespace simplex +/** + * This enum is used to perform casts at runtime from the MeshBase type to the specific mesh type. + * + * Something like: + * if(emb_ptr->mesh_type() == MeshType::Embedding) { + * Embedding& emb = static_cast(*emb_ptr); + * } + */ +enum class MeshType { Mesh, Embedding, SubMesh }; class MeshBase { @@ -24,6 +33,8 @@ class MeshBase virtual int64_t top_cell_dimension() const = 0; + virtual MeshType mesh_type() const = 0; + /** * @brief switch the orientation of the Tuple of the given dimension * @note this is not done in place. Return a new Tuple of the switched state diff --git a/src/wmtk/Mesh_multimesh.cpp b/src/wmtk/Mesh_multimesh.cpp index 166f2ec389..932d96716e 100644 --- a/src/wmtk/Mesh_multimesh.cpp +++ b/src/wmtk/Mesh_multimesh.cpp @@ -1,6 +1,6 @@ #include "Mesh.hpp" -#include #include +#include #include #include @@ -36,6 +36,17 @@ bool Mesh::is_from_same_multi_mesh_structure(const Mesh& other) const return &get_multi_mesh_root() == &other.get_multi_mesh_root(); } +bool Mesh::has_embedding() const +{ + return m_embedding; +} + +submesh::Embedding& Mesh::get_embedding() const +{ + assert(has_embedding()); + return *m_embedding; +} + bool Mesh::can_map(const Mesh& other_mesh, const simplex::Simplex& my_simplex) const { if (!is_from_same_multi_mesh_structure(other_mesh)) { @@ -244,15 +255,15 @@ std::vector> Mesh::get_all_meshes() const auto meshes2 = get_all_child_meshes(); std::vector> meshes; meshes.emplace_back(shared_from_this()); - for(const auto& m: meshes2) { + for (const auto& m : meshes2) { meshes.emplace_back(m); } return meshes; - //std::queue> queue; + // std::queue> queue; ////std::queue queue; - //meshes.emplace_back(this); - //while(!queue.empty()) { - // const auto& cur = queue.front(); + // meshes.emplace_back(this); + // while(!queue.empty()) { + // const auto& cur = queue.front(); // //Mesh const* cur = queue.front(); // queue.pop(); // meshes.emplace_back(cur->shared_from_this()); @@ -260,6 +271,6 @@ std::vector> Mesh::get_all_meshes() const // queue.emplace(m.get()); // } //} - //return meshes; + // return meshes; } } // namespace wmtk diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index c8e45422bc..a46831617a 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -13,6 +13,8 @@ namespace wmtk::submesh { Embedding::Embedding(const std::shared_ptr& mesh) : m_mesh(mesh) { + m_mesh->m_embedding = this; + m_tag_attribute_name[PrimitiveType::Vertex] = "WMTK_submesh_tag_v"; m_tag_attribute_name[PrimitiveType::Edge] = "WMTK_submesh_tag_e"; m_tag_attribute_name[PrimitiveType::Triangle] = "WMTK_submesh_tag_f"; @@ -171,6 +173,11 @@ int64_t Embedding::top_cell_dimension() const return mesh().top_cell_dimension(); } +MeshType Embedding::mesh_type() const +{ + return MeshType::Embedding; +} + Tuple Embedding::switch_tuple(const Tuple& tuple, PrimitiveType type) const { return mesh().switch_tuple(tuple, type); diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index 2f48c39431..1568f14a8c 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -44,6 +44,8 @@ class Embedding : public std::enable_shared_from_this, public MeshBas int64_t top_cell_dimension() const; + MeshType mesh_type() const; + Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const; bool is_boundary(PrimitiveType, const Tuple& tuple) const; int64_t id(const simplex::Simplex& s) const; diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index b82f29ea71..ed60ae664e 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -99,6 +99,11 @@ PrimitiveType SubMesh::top_simplex_type(const Tuple& tuple) const log_and_throw_error("No simplex of the tuple contains the submesh tag."); } +MeshType SubMesh::mesh_type() const +{ + return MeshType::SubMesh; +} + PrimitiveType SubMesh::top_simplex_type() const { // const Mesh& m = mesh(); diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index db28251f9f..05698ad5fb 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -56,6 +56,8 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase */ PrimitiveType top_simplex_type(const Tuple& tuple) const; + MeshType mesh_type() const; + /** * @brief Get the maximum primitive type that has a tag in the entire mesh. */ diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index c3010f6049..08893cb69a 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -391,4 +391,39 @@ TEST_CASE("submesh_from_multimesh", "[mesh][submesh]") using submesh::utils::write; CHECK_NOTHROW(write("submesh_from_multimesh", "vertices", emb, true, true, true, false)); } +} + +TEST_CASE("submesh_type_casts", "[mesh][submesh]") +{ + logger().set_level(spdlog::level::off); + // logger().set_level(spdlog::level::trace); + + // basic test for implementing + std::shared_ptr mesh_in = + std::make_shared(tests::edge_region_with_position()); + + tests::DEBUG_TriMesh& m = *mesh_in; + CHECK_FALSE(m.has_embedding()); + const Tuple edge45 = m.edge_tuple_from_vids(4, 5); + + std::shared_ptr emb_ptr = std::make_shared(mesh_in); + CHECK(m.has_embedding()); + REQUIRE(emb_ptr->mesh_type() == MeshType::Embedding); + Embedding& emb = static_cast(*emb_ptr); + + std::shared_ptr sub_ptr = emb.add_submesh(); + CHECK(emb.get_child_meshes().size() == 1); + REQUIRE(sub_ptr->mesh_type() == MeshType::SubMesh); + SubMesh& sub = static_cast(*sub_ptr); + + sub.add_simplex(edge45, PE); + CHECK(sub.contains(edge45, PE)); + + // test linking of embedding in mesh + { + Mesh& emb_mesh = emb.mesh(); + REQUIRE(emb_mesh.has_embedding()); + Embedding& mesh_emb = emb_mesh.get_embedding(); + CHECK(&mesh_emb == &emb); + } } \ No newline at end of file From d105f586d804e9e51171587ad4fdbf09085b7c2d Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Sun, 23 Feb 2025 18:53:29 -0500 Subject: [PATCH 31/47] Add EnvelopeInvariant constructor for SubMesh. The `after` function still needs modification. --- .../internal/wildmeshing_embedding_2d.cpp | 73 ++------- src/wmtk/invariants/EnvelopeInvariant.cpp | 139 +++++++++++++++++- src/wmtk/invariants/EnvelopeInvariant.hpp | 8 + src/wmtk/submesh/SubMesh.cpp | 11 ++ src/wmtk/submesh/SubMesh.hpp | 3 + 5 files changed, 171 insertions(+), 63 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 79a18065ee..eaeb906466 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -5,18 +5,15 @@ #include #include -#include #include #include -#include #include #include #include #include -#include #include #include @@ -26,10 +23,7 @@ #include #include #include -#include #include -#include -#include #include @@ -63,8 +57,6 @@ #include #include -#include - #include #include #include @@ -72,12 +64,8 @@ #include #include -#include #include -#include -#include -#include namespace wmtk::components::internal { using namespace simplex; @@ -370,10 +358,9 @@ std::vector, std::string>> wildmeshing_embedding using MeshConstrainPair = ProjectOperation::MeshConstrainPair; auto envelope_invariant = std::make_shared(mesh); - std::vector> - update_child_position; // TODO remove for submesh + std::vector> envelopes; - std::vector mesh_constraint_pairs; + std::vector mesh_constraint_pairs; // TODO remove std::vector, std::string>> multimesh_meshes; @@ -383,36 +370,19 @@ std::vector, std::string>> wildmeshing_embedding logger().info("wildmeshing2d: registered {} mesh as envelope constraints", e.envelope_name); - const bool has_double_pos = - geometry_mesh.has_attribute(e.geometry_position_name, PrimitiveType::Vertex); - const bool has_rational_pos = - geometry_mesh.has_attribute(e.geometry_position_name, PrimitiveType::Vertex); - assert(has_double_pos ^ has_rational_pos); - - auto geometry_pt_handle = has_double_pos ? geometry_mesh.get_attribute_handle( - e.geometry_position_name, - PrimitiveType::Vertex) - : geometry_mesh.get_attribute_handle( - e.geometry_position_name, - PrimitiveType::Vertex); - - auto constrained_pt_handle = constrained_mesh.get_attribute_handle( - e.constrained_position_name, - PrimitiveType::Vertex); + // auto constrained_pt_handle = constrained_mesh.get_attribute_handle( + // e.constrained_position_name, + // PrimitiveType::Vertex); - multimesh_meshes.push_back(std::make_pair(e.envelope_constrained_mesh, e.envelope_name)); - pass_through_attributes.emplace_back(constrained_pt_handle); + // multimesh_meshes.push_back(std::make_pair(e.envelope_constrained_mesh, e.envelope_name)); + // pass_through_attributes.emplace_back(constrained_pt_handle); - mesh_constraint_pairs.emplace_back(geometry_pt_handle, constrained_pt_handle); + // mesh_constraint_pairs.emplace_back(geometry_pt_handle, constrained_pt_handle); - envelope_invariant->add(std::make_shared( - geometry_pt_handle, - e.thickness * bbdiag, - constrained_pt_handle)); + submesh::SubMesh& sub = *emb.get_child_meshes()[0]; - update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( - /*source=*/pt_attribute, - /*target=*/constrained_pt_handle)); // TODO remove for submesh + envelope_invariant->add( + std::make_shared(pt_attribute, e.thickness * bbdiag, sub)); } @@ -497,9 +467,6 @@ std::vector, std::string>> wildmeshing_embedding rounding->add_invariant( std::make_shared(mesh, pt_attribute.as(), true)); rounding->add_invariant(inversion_invariant); - for (auto& s : update_child_position) { - rounding->add_transfer_strategy(s); - } ////////////////////////////////// @@ -537,10 +504,6 @@ std::vector, std::string>> wildmeshing_embedding split_then_round->add_operation(split); split_then_round->add_operation(rounding); - for (auto& s : update_child_position) { - split_then_round->add_transfer_strategy(s); - } - // split unrounded auto split_unrounded = std::make_shared(mesh); split_unrounded->set_priority(long_edges_first); @@ -650,10 +613,6 @@ std::vector, std::string>> wildmeshing_embedding collapse->add_transfer_strategy(tag_update); collapse->add_transfer_strategy(amips_update); collapse->add_transfer_strategy(edge_length_update); - - for (auto& s : update_child_position) { - collapse->add_transfer_strategy(s); - } }; auto collapse1 = std::make_shared(mesh); @@ -685,10 +644,6 @@ std::vector, std::string>> wildmeshing_embedding collapse_then_round->set_priority(short_edges_first); - for (auto& s : update_child_position) { - collapse_then_round->add_transfer_strategy(s); - } - if (!options.skip_collapse) { ops.emplace_back(collapse_then_round); ops_name.emplace_back("COLLAPSE"); @@ -781,9 +736,6 @@ std::vector, std::string>> wildmeshing_embedding smoothing->add_invariant(std::make_shared(mesh, pt_attribute.as())); smoothing->add_invariant(frozen_vertex_invariant); smoothing->add_invariant(inversion_invariant); - for (auto& s : update_child_position) { - smoothing->add_transfer_strategy(s); - } auto proj_smoothing = std::make_shared(smoothing, mesh_constraint_pairs); // proj_smoothing->use_random_priority() = true; @@ -794,9 +746,6 @@ std::vector, std::string>> wildmeshing_embedding proj_smoothing->add_transfer_strategy(amips_update); proj_smoothing->add_transfer_strategy(edge_length_update); - for (auto& s : update_child_position) { - proj_smoothing->add_transfer_strategy(s); - } if (!options.skip_smooth) { for (int i = 0; i < 1; ++i) { diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index ab259695cb..da05211608 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -116,6 +116,97 @@ std::shared_ptr init_bvh( return bvh; } +template +std::shared_ptr init_fast_envelope( + const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + const double envelope_size, + const submesh::SubMesh& sub) +{ + const Mesh& m = envelope_mesh_coordinate.mesh(); + + const attribute::Accessor accessor = + m.create_const_accessor(envelope_mesh_coordinate.as()); + + std::vector vertices; + std::vector faces; + + int count = 0; + assert(accessor.dimension() == 3); + + const std::vector& facest = sub.get_all(PF); + for (const auto& f : facest) { + if constexpr (std::is_same()) { + Eigen::Vector3d p0 = accessor.const_vector_attribute(f).cast(); + Eigen::Vector3d p1 = + accessor.const_vector_attribute(sub.switch_tuple(f, PV)).cast(); + Eigen::Vector3d p2 = + accessor.const_vector_attribute(sub.switch_tuples(f, {PE, PV})).cast(); + + vertices.push_back(p0); + vertices.push_back(p1); + vertices.push_back(p2); + } else { + Eigen::Vector3d p0 = accessor.const_vector_attribute(f); + Eigen::Vector3d p1 = accessor.const_vector_attribute(sub.switch_tuple(f, PV)); + Eigen::Vector3d p2 = accessor.const_vector_attribute(sub.switch_tuples(f, {PE, PV})); + + vertices.push_back(p0); + vertices.push_back(p1); + vertices.push_back(p2); + } + faces.emplace_back(count, count + 1, count + 2); + + count += 3; + } + + return std::make_shared(vertices, faces, envelope_size); +} + +template +std::shared_ptr init_bvh( + const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + const submesh::SubMesh& sub) +{ + const Mesh& m = envelope_mesh_coordinate.mesh(); + + const attribute::Accessor accessor = + m.create_const_accessor(envelope_mesh_coordinate.as()); + + logger().warn("Envelope for edge mesh is using sampling"); + + int64_t count = 0; + int64_t index = 0; + + const std::vector& edgest = sub.get_all(PE); + + Eigen::MatrixXd vertices(2 * edgest.size(), accessor.dimension()); + Eigen::MatrixXi edges(edgest.size(), 2); + + for (const Tuple& e : edgest) { + if constexpr (std::is_same()) { + const auto p0 = accessor.const_vector_attribute(e).cast(); + const auto p1 = accessor.const_vector_attribute(sub.switch_tuple(e, PV)).cast(); + + vertices.row(2 * index) = p0; + vertices.row(2 * index + 1) = p1; + } else { + const auto p0 = accessor.const_vector_attribute(e); + const auto p1 = accessor.const_vector_attribute(sub.switch_tuple(e, PV)); + + vertices.row(2 * index) = p0; + vertices.row(2 * index + 1) = p1; + } + edges.row(index) << count, count + 1; + + count += 2; + ++index; + } + + std::shared_ptr bvh = std::make_shared(); + bvh->init(vertices, edges, 1e-10); + return bvh; +} + } // namespace wmtk namespace wmtk::invariants { @@ -123,7 +214,7 @@ EnvelopeInvariant::EnvelopeInvariant( const attribute::MeshAttributeHandle& envelope_mesh_coordinate, double envelope_size, const attribute::MeshAttributeHandle& coordinate) - : Invariant(coordinate.mesh()) + : Invariant(coordinate.mesh(), false, false, true) , m_coordinate_handle(coordinate) , m_envelope_size(envelope_size) { @@ -160,10 +251,56 @@ EnvelopeInvariant::EnvelopeInvariant( } } +EnvelopeInvariant::EnvelopeInvariant( + const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + double envelope_size, + const submesh::SubMesh& sub) + : Invariant(envelope_mesh_coordinate.mesh(), false, false, true) + , m_coordinate_handle(envelope_mesh_coordinate) + , m_envelope_size(envelope_size) + , m_submesh(&sub) +{ + // log_assert( + // envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds(), + // "Envelope mesh handle type invalid"); + + if (!(envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds())) { + log_and_throw_error("Envelope mesh handle type invalid"); + } + + // log_assert( + // envelope_mesh.top_simplex_type() == PF || envelope_mesh.top_simplex_type() == PE, + // "Envelope works only for tri/edges meshes"); + + if (!(sub.top_simplex_type() == PF || sub.top_simplex_type() == PE)) { + log_and_throw_error("Envelope works only for tri/edges meshes"); + } + + if (envelope_mesh_coordinate.holds()) { + if (sub.top_simplex_type() == PF) { + m_envelope = init_fast_envelope(envelope_mesh_coordinate, envelope_size, sub); + } else if (sub.top_simplex_type() == PE) { + m_bvh = init_bvh(envelope_mesh_coordinate, sub); + } + } else if (envelope_mesh_coordinate.holds()) { + if (sub.top_simplex_type() == PF) { + m_envelope = init_fast_envelope(envelope_mesh_coordinate, envelope_size, sub); + } else if (sub.top_simplex_type() == PE) { + m_bvh = init_bvh(envelope_mesh_coordinate, sub); + } + } +} + bool EnvelopeInvariant::after( const std::vector& top_dimension_tuples_before, const std::vector& top_dimension_tuples_after) const { + /* + Modification for submesh: + - Get faces of top_dimension_tuples_before that are of type sub.top_simplex_type() + - Filter by sub.contains(tuple, pt_top) + */ + if (top_dimension_tuples_after.empty()) return true; assert(m_coordinate_handle.holds() || m_coordinate_handle.holds()); diff --git a/src/wmtk/invariants/EnvelopeInvariant.hpp b/src/wmtk/invariants/EnvelopeInvariant.hpp index b363009b68..b539c87287 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.hpp +++ b/src/wmtk/invariants/EnvelopeInvariant.hpp @@ -5,6 +5,7 @@ #include #include +#include #include namespace fastEnvelope { @@ -34,6 +35,11 @@ class EnvelopeInvariant : public Invariant double envelope_size, const attribute::MeshAttributeHandle& coordinate); + EnvelopeInvariant( + const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + double envelope_size, + const submesh::SubMesh& sub); + bool after( const std::vector& top_dimension_tuples_before, const std::vector& top_dimension_tuples_after) const override; @@ -43,6 +49,8 @@ class EnvelopeInvariant : public Invariant std::shared_ptr m_bvh = nullptr; const attribute::MeshAttributeHandle m_coordinate_handle; + const submesh::SubMesh* m_submesh = nullptr; + const double m_envelope_size; }; } // namespace wmtk::invariants diff --git a/src/wmtk/submesh/SubMesh.cpp b/src/wmtk/submesh/SubMesh.cpp index ed60ae664e..04c898d9d1 100644 --- a/src/wmtk/submesh/SubMesh.cpp +++ b/src/wmtk/submesh/SubMesh.cpp @@ -169,6 +169,17 @@ Tuple SubMesh::switch_tuple(const Tuple& tuple, PrimitiveType pt) const return local_switch_tuple(tuple, pt); } +Tuple SubMesh::switch_tuples( + const Tuple& tuple, + const std::initializer_list& op_sequence) const +{ + Tuple r = tuple; + for (const PrimitiveType& primitive : op_sequence) { + r = switch_tuple(r, primitive); + } + return r; +} + // std::vector SubMesh::switch_tuple_vector(const Tuple& tuple, PrimitiveType pt) const //{ // const int8_t pt_id = get_primitive_type_id(pt); diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index 05698ad5fb..b24e04921c 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -72,6 +72,9 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase */ Tuple switch_tuple(const Tuple& tuple, PrimitiveType pt) const override; + Tuple switch_tuples(const Tuple& tuple, const std::initializer_list& op_sequence) + const; + /** * This function does not make sense. The proper way to navigate is using * cofaces_single_dimension. From ea850a9e4de74e8d6e44b256b939a039524f8466 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Mon, 24 Feb 2025 16:35:15 -0500 Subject: [PATCH 32/47] Refactor envelope invariant + add unit tests. --- src/wmtk/invariants/EnvelopeInvariant.cpp | 471 ++++++++++--------- src/wmtk/invariants/EnvelopeInvariant.hpp | 12 +- tests/CMakeLists.txt | 1 + tests/invariants/CMakeLists.txt | 5 + tests/invariants/test_envelope_invariant.cpp | 279 +++++++++++ tests/tools/TriMesh_examples.cpp | 20 + tests/tools/TriMesh_examples.hpp | 2 + 7 files changed, 567 insertions(+), 223 deletions(-) create mode 100644 tests/invariants/CMakeLists.txt create mode 100644 tests/invariants/test_envelope_invariant.cpp diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index da05211608..fb8c63c8a6 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -252,11 +252,11 @@ EnvelopeInvariant::EnvelopeInvariant( } EnvelopeInvariant::EnvelopeInvariant( - const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + const attribute::MeshAttributeHandle& pt_attribute, double envelope_size, const submesh::SubMesh& sub) - : Invariant(envelope_mesh_coordinate.mesh(), false, false, true) - , m_coordinate_handle(envelope_mesh_coordinate) + : Invariant(pt_attribute.mesh(), false, false, true) + , m_coordinate_handle(pt_attribute) , m_envelope_size(envelope_size) , m_submesh(&sub) { @@ -264,7 +264,7 @@ EnvelopeInvariant::EnvelopeInvariant( // envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds(), // "Envelope mesh handle type invalid"); - if (!(envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds())) { + if (!(pt_attribute.holds() || pt_attribute.holds())) { log_and_throw_error("Envelope mesh handle type invalid"); } @@ -276,17 +276,17 @@ EnvelopeInvariant::EnvelopeInvariant( log_and_throw_error("Envelope works only for tri/edges meshes"); } - if (envelope_mesh_coordinate.holds()) { + if (pt_attribute.holds()) { if (sub.top_simplex_type() == PF) { - m_envelope = init_fast_envelope(envelope_mesh_coordinate, envelope_size, sub); + m_envelope = init_fast_envelope(pt_attribute, envelope_size, sub); } else if (sub.top_simplex_type() == PE) { - m_bvh = init_bvh(envelope_mesh_coordinate, sub); + m_bvh = init_bvh(pt_attribute, sub); } - } else if (envelope_mesh_coordinate.holds()) { + } else if (pt_attribute.holds()) { if (sub.top_simplex_type() == PF) { - m_envelope = init_fast_envelope(envelope_mesh_coordinate, envelope_size, sub); + m_envelope = init_fast_envelope(pt_attribute, envelope_size, sub); } else if (sub.top_simplex_type() == PE) { - m_bvh = init_bvh(envelope_mesh_coordinate, sub); + m_bvh = init_bvh(pt_attribute, sub); } } } @@ -297,267 +297,294 @@ bool EnvelopeInvariant::after( { /* Modification for submesh: - - Get faces of top_dimension_tuples_before that are of type sub.top_simplex_type() + - Get faces of top_dimension_tuples_after that are of type sub.top_simplex_type() - Filter by sub.contains(tuple, pt_top) */ - if (top_dimension_tuples_after.empty()) return true; + if (top_dimension_tuples_after.empty()) { + return true; + } assert(m_coordinate_handle.holds() || m_coordinate_handle.holds()); + assert(m_envelope || m_bvh); - if (m_coordinate_handle.holds()) { - const attribute::Accessor accessor = - mesh().create_const_accessor(m_coordinate_handle.as()); - const PrimitiveType type = mesh().top_simplex_type(); + if (m_envelope) { + return after_with_envelope(top_dimension_tuples_after); + } + if (m_bvh) { + return after_with_bvh(top_dimension_tuples_after); + } + assert(false); // this code should be unreachable + return false; +} - if (m_envelope) { - assert(accessor.dimension() == 3); - std::vector faces; +bool EnvelopeInvariant::after_with_envelope( + const std::vector& top_dimension_tuples_after) const +{ + assert(m_envelope); + assert(m_coordinate_handle.dimension() == 3); - if (type == PrimitiveType::Triangle) { - std::array triangle; + const PrimitiveType pt_top = mesh().top_simplex_type(); + if (pt_top == PrimitiveType::Triangle) { + return after_with_envelope_triangle(top_dimension_tuples_after); + } + if (pt_top == PrimitiveType::Edge) { + return after_with_envelope_edge(top_dimension_tuples_after); + } + if (pt_top == PrimitiveType::Vertex) { + return after_with_envelope_vertex(top_dimension_tuples_after); + } + log_and_throw_error("Invalid mesh type"); +} - for (const Tuple& tuple : top_dimension_tuples_after) { - faces = faces_single_dimension_tuples( - mesh(), - simplex::Simplex(mesh(), type, tuple), - PrimitiveType::Vertex); +bool EnvelopeInvariant::after_with_envelope_triangle( + const std::vector& top_dimension_tuples_after) const +{ + assert(m_envelope); + assert(mesh().top_simplex_type() == PrimitiveType::Triangle); - triangle[0] = accessor.const_vector_attribute(faces[0]).cast(); - triangle[1] = accessor.const_vector_attribute(faces[1]).cast(); - triangle[2] = accessor.const_vector_attribute(faces[2]).cast(); + const bool res = std::visit( + [&](auto&& tah) noexcept { + using HandleType = typename std::decay_t; + using AttributeType = typename HandleType::Type; - if (m_envelope->is_outside(triangle)) { - wmtk::logger().debug("fail envelope check 1"); - return false; - } - } + const auto accessor = mesh().create_const_accessor(tah); - return true; - } else if (type == PrimitiveType::Edge) { - for (const Tuple& tuple : top_dimension_tuples_after) { - faces = faces_single_dimension_tuples( - mesh(), - simplex::Simplex(mesh(), type, tuple), - PrimitiveType::Vertex); - - Eigen::Vector3d p0 = accessor.const_vector_attribute(faces[0]).cast(); - Eigen::Vector3d p1 = accessor.const_vector_attribute(faces[1]).cast(); - - if (m_envelope->is_outside(p0, p1)) { - wmtk::logger().debug("fail envelope check 2"); - return false; - } - } + std::array triangle; - return true; - } else if (type == PrimitiveType::Vertex) { - for (const Tuple& tuple : top_dimension_tuples_after) { - Eigen::Vector3d p = accessor.const_vector_attribute(tuple).cast(); + for (const Tuple& tuple : top_dimension_tuples_after) { + const auto faces = faces_single_dimension_tuples( + mesh(), + simplex::Simplex(PrimitiveType::Triangle, tuple), + PrimitiveType::Vertex); - if (m_envelope->is_outside(p)) { - wmtk::logger().debug("fail envelope check 3"); - return false; - } + if constexpr (std::is_same_v) { + triangle[0] = accessor.const_vector_attribute(faces[0]); + triangle[1] = accessor.const_vector_attribute(faces[1]); + triangle[2] = accessor.const_vector_attribute(faces[2]); + } + if constexpr (std::is_same_v) { + triangle[0] = accessor.const_vector_attribute(faces[0]).cast(); + triangle[1] = accessor.const_vector_attribute(faces[1]).cast(); + triangle[2] = accessor.const_vector_attribute(faces[2]).cast(); } - return true; - } else { - throw std::runtime_error("Invalid mesh type"); + if (m_envelope->is_outside(triangle)) { + wmtk::logger().debug("fail envelope check 1"); + return false; + } } return true; - } else { - assert(m_bvh); - - SimpleBVH::VectorMax3d nearest_point; - double sq_dist; - - const double d = m_envelope_size; - const double real_envelope = m_envelope_size - d / sqrt(accessor.dimension()); - const double real_envelope_2 = real_envelope * real_envelope; + }, + m_coordinate_handle.handle()); - if (type == PrimitiveType::Edge) { - std::vector pts; - - for (const Tuple& tuple : top_dimension_tuples_after) { - const auto p0 = accessor.const_vector_attribute(tuple).cast().eval(); - const auto p1 = accessor.const_vector_attribute(mesh().switch_tuple(tuple, PV)) - .cast() - .eval(); - - const int64_t N = (p0 - p1).norm() / d + 1; - pts.reserve(pts.size() + N); + return res; +} - for (int64_t n = 0; n <= N; n++) { - auto tmp = p0 * (double(n) / N) + p1 * (N - double(n)) / N; - pts.push_back(tmp); - } +bool EnvelopeInvariant::after_with_envelope_edge( + const std::vector& top_dimension_tuples_after) const +{ + assert(m_envelope); + assert(mesh().top_simplex_type() == PrimitiveType::Edge); + + const bool res = std::visit( + [&](auto&& tah) noexcept { + using HandleType = typename std::decay_t; + using AttributeType = typename HandleType::Type; + + const auto accessor = mesh().create_const_accessor(tah); + + std::array triangle; + const PrimitiveType pt_top = mesh().top_simplex_type(); + + for (const Tuple& tuple : top_dimension_tuples_after) { + const auto faces = faces_single_dimension_tuples( + mesh(), + simplex::Simplex(mesh(), pt_top, tuple), + PrimitiveType::Vertex); + + Eigen::Vector3d p0; + Eigen::Vector3d p1; + + if constexpr (std::is_same_v) { + p0 = accessor.const_vector_attribute(faces[0]); + p1 = accessor.const_vector_attribute(faces[1]); + } else if constexpr (std::is_same_v) { + p0 = accessor.const_vector_attribute(faces[0]).cast(); + p1 = accessor.const_vector_attribute(faces[1]).cast(); + } else { + log_and_throw_error("Unknown attribute type"); } - auto current_point = pts[0]; - - int prev_facet = m_bvh->nearest_facet(current_point, nearest_point, sq_dist); - if (sq_dist > real_envelope_2) { - wmtk::logger().debug("fail envelope check 4"); + if (m_envelope->is_outside(p0, p1)) { + wmtk::logger().debug("fail envelope check 2"); return false; } - - for (const auto& v : pts) { - sq_dist = (v - nearest_point).squaredNorm(); - m_bvh->nearest_facet_with_hint(v, prev_facet, nearest_point, sq_dist); - if (sq_dist > real_envelope_2) { - wmtk::logger().debug("fail envelope check 5"); - return false; - } - } - - return true; - } else if (type == PrimitiveType::Vertex) { - for (const Tuple& tuple : top_dimension_tuples_after) { - Eigen::Vector3d p = accessor.const_vector_attribute(tuple).cast(); - m_bvh->nearest_facet(p, nearest_point, sq_dist); - if (sq_dist > m_envelope_size * m_envelope_size) { - wmtk::logger().debug("fail envelope check 6"); - return false; - } - } - - return true; - } else { - throw std::runtime_error("Invalid mesh type"); } - return true; - } - } else if (m_coordinate_handle.holds()) { - const attribute::Accessor accessor = - mesh().create_const_accessor(m_coordinate_handle.as()); - const auto type = mesh().top_simplex_type(); - - if (m_envelope) { - assert(accessor.dimension() == 3); - - std::vector faces; - - if (type == PrimitiveType::Triangle) { - std::array triangle; - for (const Tuple& tuple : top_dimension_tuples_after) { - faces = faces_single_dimension_tuples( - mesh(), - simplex::Simplex(mesh(), type, tuple), - PrimitiveType::Vertex); - - triangle[0] = accessor.const_vector_attribute(faces[0]); - triangle[1] = accessor.const_vector_attribute(faces[1]); - triangle[2] = accessor.const_vector_attribute(faces[2]); + return true; + }, + m_coordinate_handle.handle()); - if (m_envelope->is_outside(triangle)) { - wmtk::logger().debug("fail envelope check 7"); - return false; - } - } + return res; +} - return true; - } else if (type == PrimitiveType::Edge) { - for (const Tuple& tuple : top_dimension_tuples_after) { - faces = faces_single_dimension_tuples( - mesh(), - simplex::Simplex(mesh(), type, tuple), - PrimitiveType::Vertex); - - Eigen::Vector3d p0 = accessor.const_vector_attribute(faces[0]); - Eigen::Vector3d p1 = accessor.const_vector_attribute(faces[1]); - - if (m_envelope->is_outside(p0, p1)) { - wmtk::logger().debug("fail envelope check 8"); - return false; - } +bool EnvelopeInvariant::after_with_envelope_vertex( + const std::vector& top_dimension_tuples_after) const +{ + assert(m_envelope); + assert(mesh().top_simplex_type() == PrimitiveType::Vertex); + + const bool res = std::visit( + [&](auto&& tah) noexcept { + using HandleType = typename std::decay_t; + using AttributeType = typename HandleType::Type; + + const auto accessor = mesh().create_const_accessor(tah); + + for (const Tuple& tuple : top_dimension_tuples_after) { + Eigen::Vector3d p; + if constexpr (std::is_same_v) { + p = accessor.const_vector_attribute(tuple); + } else if constexpr (std::is_same_v) { + p = accessor.const_vector_attribute(tuple).cast(); + } else { + log_and_throw_error("Unknown attribute type"); } - return true; - } else if (type == PrimitiveType::Vertex) { - for (const Tuple& tuple : top_dimension_tuples_after) { - Eigen::Vector3d p = accessor.const_vector_attribute(tuple); - - if (m_envelope->is_outside(p)) { - wmtk::logger().debug("fail envelope check 9"); - return false; - } + if (m_envelope->is_outside(p)) { + wmtk::logger().debug("fail envelope check 3"); + return false; } - - return true; - } else { - throw std::runtime_error("Invalid mesh type"); } - return true; - } else { - assert(m_bvh); - SimpleBVH::VectorMax3d nearest_point; - double sq_dist; + return true; + }, + m_coordinate_handle.handle()); - const double d = m_envelope_size; - const double real_envelope = m_envelope_size - d / sqrt(accessor.dimension()); - const double real_envelope_2 = real_envelope * real_envelope; + return res; +} - if (type == PrimitiveType::Edge) { - std::vector pts; +bool EnvelopeInvariant::after_with_bvh(const std::vector& top_dimension_tuples_after) const +{ + assert(m_bvh); - for (const Tuple& tuple : top_dimension_tuples_after) { - SimpleBVH::VectorMax3d p0 = - accessor.const_vector_attribute(tuple).cast(); - SimpleBVH::VectorMax3d p1 = - accessor.const_vector_attribute(mesh().switch_tuple(tuple, PV)) - .cast(); + const PrimitiveType type = mesh().top_simplex_type(); + if (type == PrimitiveType::Edge) { + return after_with_bvh_edge(top_dimension_tuples_after); + } + if (type == PrimitiveType::Vertex) { + return after_with_bvh_vertex(top_dimension_tuples_after); + } - const int64_t N = (p0 - p1).norm() / d + 1; - pts.reserve(pts.size() + N); + log_and_throw_error("Invalid mesh type"); +} - for (int64_t n = 0; n <= N; n++) { - auto tmp = p0 * (double(n) / N) + p1 * (N - double(n)) / N; - pts.push_back(tmp); - } +bool EnvelopeInvariant::after_with_bvh_edge( + const std::vector& top_dimension_tuples_after) const +{ + assert(m_bvh); + assert(mesh().top_simplex_type() == PrimitiveType::Edge); + + SimpleBVH::VectorMax3d nearest_point; + double sq_dist; + + const double d = m_envelope_size; + const double real_envelope = m_envelope_size - d / sqrt(m_coordinate_handle.dimension()); + const double real_envelope_2 = real_envelope * real_envelope; + + std::vector pts; + + std::visit( + [&](auto&& tah) noexcept { + using HandleType = typename std::decay_t; + using AttributeType = typename HandleType::Type; + + const auto accessor = mesh().create_const_accessor(tah); + + for (const Tuple& tuple : top_dimension_tuples_after) { + SimpleBVH::VectorMax3d p0; + SimpleBVH::VectorMax3d p1; + if constexpr (std::is_same_v) { + p0 = accessor.const_vector_attribute(tuple); + p1 = accessor.const_vector_attribute(mesh().switch_tuple(tuple, PV)); + } else if constexpr (std::is_same_v) { + p0 = accessor.const_vector_attribute(tuple).cast(); + p1 = accessor.const_vector_attribute(mesh().switch_tuple(tuple, PV)) + .cast(); + } else { + log_and_throw_error("Unknown attribute type"); } - auto current_point = pts[0]; + const int64_t N = (p0 - p1).norm() / d + 1; + pts.reserve(pts.size() + N); - int prev_facet = m_bvh->nearest_facet(current_point, nearest_point, sq_dist); - if (sq_dist > real_envelope_2) { - wmtk::logger().debug("fail envelope check 10"); - return false; + for (int64_t n = 0; n <= N; n++) { + auto tmp = p0 * (double(n) / N) + p1 * (N - double(n)) / N; + pts.push_back(tmp); } + } + }, + m_coordinate_handle.handle()); - for (const auto& v : pts) { - sq_dist = (v - nearest_point).squaredNorm(); - m_bvh->nearest_facet_with_hint(v, prev_facet, nearest_point, sq_dist); - if (sq_dist > real_envelope_2) { - wmtk::logger().debug("fail envelope check 11"); - return false; - } - } + SimpleBVH::VectorMax3d current_point = pts[0]; - return true; - } else if (type == PrimitiveType::Vertex) { - for (const Tuple& tuple : top_dimension_tuples_after) { - Eigen::Vector3d p = accessor.const_vector_attribute(tuple).cast(); - m_bvh->nearest_facet(p, nearest_point, sq_dist); - if (sq_dist > m_envelope_size * m_envelope_size) { - wmtk::logger().debug("fail envelope check 12"); - return false; - } - } + int prev_facet = m_bvh->nearest_facet(current_point, nearest_point, sq_dist); + if (sq_dist > real_envelope_2) { + wmtk::logger().debug("fail envelope check 4"); + return false; + } - return true; - } else { - throw std::runtime_error("Invalid mesh type"); - } - return true; + for (const auto& v : pts) { + sq_dist = (v - nearest_point).squaredNorm(); + m_bvh->nearest_facet_with_hint(v, prev_facet, nearest_point, sq_dist); + if (sq_dist > real_envelope_2) { + wmtk::logger().debug("fail envelope check 5"); + return false; } - } else { - throw std::runtime_error("Envelope mesh handle type invlid"); } + + return true; } +bool EnvelopeInvariant::after_with_bvh_vertex( + const std::vector& top_dimension_tuples_after) const +{ + assert(m_bvh); + assert(mesh().top_simplex_type() == PrimitiveType::Vertex); + + SimpleBVH::VectorMax3d nearest_point; + double sq_dist; + + for (const Tuple& tuple : top_dimension_tuples_after) { + SimpleBVH::VectorMax3d p = std::visit( + [&](auto&& tah) noexcept { + using HandleType = typename std::decay_t; + using AttributeType = typename HandleType::Type; + + const auto accessor = mesh().create_const_accessor(tah); + + SimpleBVH::VectorMax3d p; + if constexpr (std::is_same_v) { + p = accessor.const_vector_attribute(tuple); + } else if constexpr (std::is_same_v) { + p = accessor.const_vector_attribute(tuple).cast(); + } else { + log_and_throw_error("Unknown attribute type"); + } + return p; + }, + m_coordinate_handle.handle()); + + m_bvh->nearest_facet(p, nearest_point, sq_dist); + if (sq_dist > m_envelope_size * m_envelope_size) { + wmtk::logger().debug("fail envelope check 6"); + return false; + } + } + + return true; +} } // namespace wmtk::invariants diff --git a/src/wmtk/invariants/EnvelopeInvariant.hpp b/src/wmtk/invariants/EnvelopeInvariant.hpp index b539c87287..570e1fb603 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.hpp +++ b/src/wmtk/invariants/EnvelopeInvariant.hpp @@ -36,7 +36,7 @@ class EnvelopeInvariant : public Invariant const attribute::MeshAttributeHandle& coordinate); EnvelopeInvariant( - const attribute::MeshAttributeHandle& envelope_mesh_coordinate, + const attribute::MeshAttributeHandle& pt_attribute, double envelope_size, const submesh::SubMesh& sub); @@ -44,6 +44,16 @@ class EnvelopeInvariant : public Invariant const std::vector& top_dimension_tuples_before, const std::vector& top_dimension_tuples_after) const override; +private: + bool after_with_envelope(const std::vector& top_dimension_tuples_after) const; + bool after_with_envelope_triangle(const std::vector& top_dimension_tuples_after) const; + bool after_with_envelope_edge(const std::vector& top_dimension_tuples_after) const; + bool after_with_envelope_vertex(const std::vector& top_dimension_tuples_after) const; + + bool after_with_bvh(const std::vector& top_dimension_tuples_after) const; + bool after_with_bvh_edge(const std::vector& top_dimension_tuples_after) const; + bool after_with_bvh_vertex(const std::vector& top_dimension_tuples_after) const; + private: std::shared_ptr m_envelope = nullptr; std::shared_ptr m_bvh = nullptr; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dc12c47c4f..bfda30bc2a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -103,6 +103,7 @@ add_subdirectory_with_source_group(operations) add_subdirectory_with_source_group(attributes) add_subdirectory_with_source_group(autogen) add_subdirectory_with_source_group(submesh) +add_subdirectory_with_source_group(invariants) diff --git a/tests/invariants/CMakeLists.txt b/tests/invariants/CMakeLists.txt new file mode 100644 index 0000000000..7f0ee256ba --- /dev/null +++ b/tests/invariants/CMakeLists.txt @@ -0,0 +1,5 @@ +# Sources +set(TEST_SOURCES + test_envelope_invariant.cpp +) +target_sources(wmtk_tests PRIVATE ${TEST_SOURCES}) diff --git a/tests/invariants/test_envelope_invariant.cpp b/tests/invariants/test_envelope_invariant.cpp new file mode 100644 index 0000000000..8ab4c0c872 --- /dev/null +++ b/tests/invariants/test_envelope_invariant.cpp @@ -0,0 +1,279 @@ +#include + +#include +#include +#include +#include +#include "tools/DEBUG_EdgeMesh.hpp" +#include "tools/DEBUG_PointMesh.hpp" +#include "tools/DEBUG_TriMesh.hpp" +#include "tools/EdgeMesh_examples.hpp" +#include "tools/TriMesh_examples.hpp" + +#include + +using namespace wmtk; +using namespace tests; +using namespace invariants; + +constexpr PrimitiveType PV = PrimitiveType::Vertex; +constexpr PrimitiveType PE = PrimitiveType::Edge; +constexpr PrimitiveType PF = PrimitiveType::Triangle; + +void positions_as_rational(Mesh& mesh) +{ + auto pt_double_attribute = mesh.get_attribute_handle("vertices", PrimitiveType::Vertex); + wmtk::utils::cast_attribute(pt_double_attribute, mesh, "vertices"); + mesh.delete_attribute(pt_double_attribute); +} + +std::shared_ptr construct_edge_45(DEBUG_TriMesh& m) +{ + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + auto acc = m.create_const_accessor(env_pos_handle); + + std::shared_ptr em_ptr = std::make_shared(single_line()); + Eigen::MatrixXd V; + V.resize(2, 3); + V.row(0) = acc.const_vector_attribute(m.vertex_tuple_from_id(4)); + V.row(1) = acc.const_vector_attribute(m.vertex_tuple_from_id(5)); + mesh_utils::set_matrix_attribute(V, "vertices", PrimitiveType::Vertex, *em_ptr); + + return em_ptr; +} + +std::shared_ptr construct_point_4(DEBUG_TriMesh& m) +{ + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + auto acc = m.create_const_accessor(env_pos_handle); + + std::shared_ptr pm_ptr = std::make_shared(1); + Eigen::MatrixXd V; + V.resize(1, 3); + V.row(0) = acc.const_vector_attribute(m.vertex_tuple_from_id(4)); + mesh_utils::set_matrix_attribute(V, "vertices", PrimitiveType::Vertex, *pm_ptr); + + return pm_ptr; +} + +TEST_CASE("envelope_invariant_envelope", "[invariants][envelope]") +{ + std::shared_ptr mesh_in = + std::make_shared(edge_region_with_position()); + DEBUG_TriMesh& m = *mesh_in; + + + SECTION("double_triangle") + { + using T = double; + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, env_pos_handle); + + const Tuple v4 = m.vertex_tuple_from_id(4); + const simplex::Simplex s4(PV, v4); + + const auto tops = simplex::top_dimension_cofaces_tuples(m, s4); + CHECK(env_inv.after({}, tops)); + + auto acc = m.create_accessor(env_pos_handle); + acc.vector_attribute(s4)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("rational_triangle") + { + using T = Rational; + positions_as_rational(m); + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, env_pos_handle); + + const Tuple v4 = m.vertex_tuple_from_id(4); + const simplex::Simplex s4(PV, v4); + + const auto tops = simplex::top_dimension_cofaces_tuples(m, s4); + CHECK(env_inv.after({}, tops)); + + auto acc = m.create_accessor(env_pos_handle); + acc.vector_attribute(s4)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("double_edge") + { + std::shared_ptr em_ptr = construct_edge_45(m); + DEBUG_EdgeMesh& em = *em_ptr; + + using T = double; + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + auto query_pos_handle = em.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, query_pos_handle); + + const Tuple v0 = em.tuple_from_edge_id(0); + const simplex::Simplex s0(PV, v0); + + + const auto tops = simplex::top_dimension_cofaces_tuples(em, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = em.create_accessor(query_pos_handle); + acc.vector_attribute(s0)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("rational_edge") + { + std::shared_ptr em_ptr = construct_edge_45(m); + DEBUG_EdgeMesh& em = *em_ptr; + + using T = Rational; + positions_as_rational(em); + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + auto query_pos_handle = em.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, query_pos_handle); + + const Tuple v0 = em.tuple_from_edge_id(0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(em, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = em.create_accessor(query_pos_handle); + acc.vector_attribute(s0)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("double_point") + { + std::shared_ptr pm_ptr = construct_point_4(m); + DEBUG_PointMesh& pm = *pm_ptr; + + using T = double; + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + auto query_pos_handle = pm.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, query_pos_handle); + + const Tuple v0(-1, -1, -1, 0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(pm, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = pm.create_accessor(query_pos_handle); + acc.vector_attribute(s0)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("rational_point") + { + std::shared_ptr pm_ptr = construct_point_4(m); + DEBUG_PointMesh& pm = *pm_ptr; + + using T = Rational; + positions_as_rational(pm); + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + auto query_pos_handle = pm.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, query_pos_handle); + + const Tuple v0(-1, -1, -1, 0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(pm, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = pm.create_accessor(query_pos_handle); + acc.vector_attribute(s0)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } +} + +TEST_CASE("envelope_invariant_bvh", "[invariants][envelope]") +{ + std::shared_ptr em_ptr = std::make_shared(single_line()); + DEBUG_EdgeMesh& em = *em_ptr; + { + Eigen::MatrixXd V; + V.resize(2, 2); + V.row(0) << 0, 0; + V.row(1) << 1, 0; + mesh_utils::set_matrix_attribute(V, "vertices", PV, em); + } + + SECTION("double_edge") + { + using T = double; + + auto env_pos_handle = em.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, env_pos_handle); + + const Tuple v0 = em.tuple_from_edge_id(0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(em, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = em.create_accessor(env_pos_handle); + acc.vector_attribute(s0)[1] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("rational_edge") + { + using T = Rational; + positions_as_rational(em); + + auto env_pos_handle = em.get_attribute_handle("vertices", PV); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, env_pos_handle); + + const Tuple v0 = em.tuple_from_edge_id(0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(em, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = em.create_accessor(env_pos_handle); + acc.vector_attribute(s0)[1] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("double_point") + { + std::shared_ptr pm_ptr = std::make_shared(1); + DEBUG_PointMesh& pm = *pm_ptr; + + using T = double; + + auto env_pos_handle = em.get_attribute_handle("vertices", PV); + auto query_pos_handle = pm.register_attribute("vertices", PV, 2); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, query_pos_handle); + + const Tuple v0(-1, -1, -1, 0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(pm, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = pm.create_accessor(query_pos_handle); + acc.vector_attribute(s0)[1] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("rational_point") + { + std::shared_ptr pm_ptr = std::make_shared(1); + DEBUG_PointMesh& pm = *pm_ptr; + + using T = Rational; + + auto env_pos_handle = em.get_attribute_handle("vertices", PV); + auto query_pos_handle = pm.register_attribute("vertices", PV, 2); + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, query_pos_handle); + + const Tuple v0(-1, -1, -1, 0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(pm, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = pm.create_accessor(query_pos_handle); + acc.vector_attribute(s0)[1] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } +} \ No newline at end of file diff --git a/tests/tools/TriMesh_examples.cpp b/tests/tools/TriMesh_examples.cpp index 2b59810063..774a348d2f 100644 --- a/tests/tools/TriMesh_examples.cpp +++ b/tests/tools/TriMesh_examples.cpp @@ -287,6 +287,26 @@ TriMesh edge_region_with_position() return m; } +TriMesh edge_region_with_position_2D() +{ + TriMesh m = edge_region(); + + Eigen::MatrixXd V; + V.resize(10, 2); + V.row(0) << 0.5, 1; + V.row(1) << 1.5, 1; + V.row(2) << 2.5, 1; + V.row(3) << 0, 0; + V.row(4) << 1, 0; + V.row(5) << 2, 0; + V.row(6) << 3, 0; + V.row(7) << 0.5, -1; + V.row(8) << 1.5, -1; + V.row(9) << 2.5, -1; + mesh_utils::set_matrix_attribute(V, "vertices", PrimitiveType::Vertex, m); + return m; +} + TriMesh embedded_diamond() { // 0---1 diff --git a/tests/tools/TriMesh_examples.hpp b/tests/tools/TriMesh_examples.hpp index e996639e74..72477d032f 100644 --- a/tests/tools/TriMesh_examples.hpp +++ b/tests/tools/TriMesh_examples.hpp @@ -176,6 +176,8 @@ TriMesh nine_triangles_with_a_hole(); TriMesh ten_triangles_with_position(int dimension); TriMesh edge_region_with_position(); + +TriMesh edge_region_with_position_2D(); // 0---1 // / \ / \ . // 2---3---4 From e0036cd0be934f2986553192e3cc84ea5fd2bb62 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Mon, 24 Feb 2025 17:25:25 -0500 Subject: [PATCH 33/47] Extend envelope for submeshes. --- src/wmtk/invariants/EnvelopeInvariant.cpp | 68 ++++++++++++--- src/wmtk/simplex/IdSimplexCollection.cpp | 13 +++ src/wmtk/simplex/IdSimplexCollection.hpp | 1 + tests/invariants/test_envelope_invariant.cpp | 92 ++++++++++++++++++++ 4 files changed, 161 insertions(+), 13 deletions(-) diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index fb8c63c8a6..80d2b2b026 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -9,6 +9,7 @@ #include "EnvelopeInvariant.hpp" #include +#include #include #include #include @@ -295,18 +296,59 @@ bool EnvelopeInvariant::after( const std::vector& top_dimension_tuples_before, const std::vector& top_dimension_tuples_after) const { + if (top_dimension_tuples_after.empty()) { + return true; + } + + assert(m_coordinate_handle.holds() || m_coordinate_handle.holds()); + assert(m_envelope || m_bvh); + /* Modification for submesh: - Get faces of top_dimension_tuples_after that are of type sub.top_simplex_type() - Filter by sub.contains(tuple, pt_top) */ - if (top_dimension_tuples_after.empty()) { - return true; - } + if (m_submesh) { + const submesh::SubMesh& sub = *m_submesh; - assert(m_coordinate_handle.holds() || m_coordinate_handle.holds()); - assert(m_envelope || m_bvh); + const PrimitiveType pt_top = sub.top_simplex_type(); + + simplex::IdSimplexCollection tops(mesh()); + for (const Tuple& t : top_dimension_tuples_after) { + if (mesh().top_simplex_type() == pt_top) { + if (sub.contains(t, pt_top)) { + tops.add(pt_top, t); + } + continue; + } + + const auto faces = simplex::faces_single_dimension_tuples( + mesh(), + simplex::Simplex(mesh().top_simplex_type(), t), + pt_top); + + for (const Tuple& f : faces) { + if (sub.contains(f, pt_top)) { + tops.add(pt_top, f); + } + } + } + tops.sort_and_clean(); + + if (tops.size() == 0) { + return true; + } + + if (m_envelope) { + return after_with_envelope(tops.simplex_vector_tuples()); + } + if (m_bvh) { + return after_with_bvh(tops.simplex_vector_tuples()); + } + assert(false); // this code should be unreachable + return false; + } if (m_envelope) { return after_with_envelope(top_dimension_tuples_after); @@ -325,7 +367,9 @@ bool EnvelopeInvariant::after_with_envelope( assert(m_envelope); assert(m_coordinate_handle.dimension() == 3); - const PrimitiveType pt_top = mesh().top_simplex_type(); + const PrimitiveType pt_top = + m_submesh ? m_submesh->top_simplex_type() : mesh().top_simplex_type(); + if (pt_top == PrimitiveType::Triangle) { return after_with_envelope_triangle(top_dimension_tuples_after); } @@ -342,7 +386,6 @@ bool EnvelopeInvariant::after_with_envelope_triangle( const std::vector& top_dimension_tuples_after) const { assert(m_envelope); - assert(mesh().top_simplex_type() == PrimitiveType::Triangle); const bool res = std::visit( [&](auto&& tah) noexcept { @@ -470,11 +513,13 @@ bool EnvelopeInvariant::after_with_bvh(const std::vector& top_dimension_t { assert(m_bvh); - const PrimitiveType type = mesh().top_simplex_type(); - if (type == PrimitiveType::Edge) { + const PrimitiveType pt_top = + m_submesh ? m_submesh->top_simplex_type() : mesh().top_simplex_type(); + + if (pt_top == PrimitiveType::Edge) { return after_with_bvh_edge(top_dimension_tuples_after); } - if (type == PrimitiveType::Vertex) { + if (pt_top == PrimitiveType::Vertex) { return after_with_bvh_vertex(top_dimension_tuples_after); } @@ -484,9 +529,6 @@ bool EnvelopeInvariant::after_with_bvh(const std::vector& top_dimension_t bool EnvelopeInvariant::after_with_bvh_edge( const std::vector& top_dimension_tuples_after) const { - assert(m_bvh); - assert(mesh().top_simplex_type() == PrimitiveType::Edge); - SimpleBVH::VectorMax3d nearest_point; double sq_dist; diff --git a/src/wmtk/simplex/IdSimplexCollection.cpp b/src/wmtk/simplex/IdSimplexCollection.cpp index e5ace4a375..fea82a3d48 100644 --- a/src/wmtk/simplex/IdSimplexCollection.cpp +++ b/src/wmtk/simplex/IdSimplexCollection.cpp @@ -40,6 +40,19 @@ std::vector IdSimplexCollection::simplex_vector_tuples(PrimitiveType ptyp return tuples; } +std::vector IdSimplexCollection::simplex_vector_tuples() const +{ + std::vector tuples; + tuples.reserve(m_simplices.size()); + + // add simplices to the vector + for (const IdSimplex& s : m_simplices) { + tuples.emplace_back(m_mesh.get_tuple_from_id_simplex(s)); + } + + return tuples; +} + void IdSimplexCollection::add(const IdSimplex& simplex) { m_simplices.push_back(simplex); diff --git a/src/wmtk/simplex/IdSimplexCollection.hpp b/src/wmtk/simplex/IdSimplexCollection.hpp index d232608cb9..d16f25d3d5 100644 --- a/src/wmtk/simplex/IdSimplexCollection.hpp +++ b/src/wmtk/simplex/IdSimplexCollection.hpp @@ -30,6 +30,7 @@ class IdSimplexCollection * @brief Return vector of all simplices of the requested type, as tuples */ std::vector simplex_vector_tuples(PrimitiveType ptype) const; + std::vector simplex_vector_tuples() const; /** * @brief Add simplex to the collection. diff --git a/tests/invariants/test_envelope_invariant.cpp b/tests/invariants/test_envelope_invariant.cpp index 8ab4c0c872..f56c420fc8 100644 --- a/tests/invariants/test_envelope_invariant.cpp +++ b/tests/invariants/test_envelope_invariant.cpp @@ -1,6 +1,8 @@ #include #include +#include +#include #include #include #include @@ -58,6 +60,8 @@ std::shared_ptr construct_point_4(DEBUG_TriMesh& m) TEST_CASE("envelope_invariant_envelope", "[invariants][envelope]") { + logger().set_level(spdlog::level::off); + std::shared_ptr mesh_in = std::make_shared(edge_region_with_position()); DEBUG_TriMesh& m = *mesh_in; @@ -189,6 +193,8 @@ TEST_CASE("envelope_invariant_envelope", "[invariants][envelope]") TEST_CASE("envelope_invariant_bvh", "[invariants][envelope]") { + logger().set_level(spdlog::level::off); + std::shared_ptr em_ptr = std::make_shared(single_line()); DEBUG_EdgeMesh& em = *em_ptr; { @@ -276,4 +282,90 @@ TEST_CASE("envelope_invariant_bvh", "[invariants][envelope]") acc.vector_attribute(s0)[1] = 1; CHECK_FALSE(env_inv.after({}, tops)); } +} + +TEST_CASE("envelope_invariant_submesh_edge", "[invariants][envelope]") +{ + std::shared_ptr mesh_in = + std::make_shared(edge_region_with_position()); + DEBUG_TriMesh& m = *mesh_in; + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + + submesh::Embedding emb(mesh_in); + + auto sub1_ptr = emb.add_submesh(); + submesh::SubMesh& sub1 = *sub1_ptr; + sub1.add_simplex(m.edge_tuple_from_vids(4, 5), PE); + + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, sub1); + + // SECTION("4") + { + const Tuple v4 = m.vertex_tuple_from_id(4); + const simplex::Simplex s4(PV, v4); + + const auto tops = simplex::top_dimension_cofaces_tuples(m, s4); + CHECK(env_inv.after({}, tops)); + + auto acc = m.create_accessor(env_pos_handle); + acc.vector_attribute(s4)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + // SECTION("0") + { + const Tuple v0 = m.vertex_tuple_from_id(0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(m, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = m.create_accessor(env_pos_handle); + + acc.vector_attribute(s0)[2] = 1; + CHECK(env_inv.after({}, tops)); + } +} + +TEST_CASE("envelope_invariant_submesh_triangle", "[invariants][envelope]") +{ + std::shared_ptr mesh_in = + std::make_shared(edge_region_with_position()); + DEBUG_TriMesh& m = *mesh_in; + + auto env_pos_handle = m.get_attribute_handle("vertices", PV); + + submesh::Embedding emb(mesh_in); + + auto sub_ptr = emb.add_submesh(); + submesh::SubMesh& sub = *sub_ptr; + sub.add_simplex(m.face_tuple_from_vids(4, 5, 1), PF); + + EnvelopeInvariant env_inv(env_pos_handle, 1e-2, sub); + + SECTION("4") + { + const Tuple v4 = m.vertex_tuple_from_id(4); + const simplex::Simplex s4(PV, v4); + + const auto tops = simplex::top_dimension_cofaces_tuples(m, s4); + CHECK(env_inv.after({}, tops)); + + auto acc = m.create_accessor(env_pos_handle); + acc.vector_attribute(s4)[2] = 1; + CHECK_FALSE(env_inv.after({}, tops)); + } + SECTION("0") + { + const Tuple v0 = m.vertex_tuple_from_id(0); + const simplex::Simplex s0(PV, v0); + + const auto tops = simplex::top_dimension_cofaces_tuples(m, s0); + CHECK(env_inv.after({}, tops)); + + auto acc = m.create_accessor(env_pos_handle); + + acc.vector_attribute(s0)[2] = 1; + CHECK(env_inv.after({}, tops)); + } } \ No newline at end of file From c808ac971e6c91b77aae5a2e793fb158439409f6 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Mon, 24 Feb 2025 17:45:05 -0500 Subject: [PATCH 34/47] Add envelope support to submesh triwild. Submesh triwild is still not complete. --- .../triwild_submesh/triwild_submesh_main.cpp | 43 +++--- .../tests/wildmeshing/test_wildmeshing.cpp | 123 ++++++++++++++++++ .../components/wildmeshing/CMakeLists.txt | 3 + .../internal/WildmeshingOptions.hpp | 56 +++++++- .../internal/wildmeshing_embedding_2d.cpp | 22 ++-- .../submesh/utils/submesh_from_multimesh.cpp | 10 ++ .../submesh/utils/submesh_from_multimesh.hpp | 5 + 7 files changed, 224 insertions(+), 38 deletions(-) create mode 100644 components/tests/wildmeshing/test_wildmeshing.cpp diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp index f72c5a8fc6..788d5c562f 100644 --- a/applications/triwild_submesh/triwild_submesh_main.cpp +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -114,30 +114,31 @@ int main(int argc, char* argv[]) } std::vector enves; + { + wmtk::components::EnvelopeOptions e; + e.envelope_name = "input"; + e.envelope_constrained_mesh = edgemesh; + e.envelope_geometry_mesh = edgemesh; + e.constrained_position_name = "vertices"; + e.geometry_position_name = "vertices"; + e.thickness = j["envelope_size"]; + + if (e.envelope_name == "input") { + e.envelope_geometry_mesh = mesh; // set as input + } - wmtk::components::EnvelopeOptions e; - e.envelope_name = "input"; - e.envelope_constrained_mesh = edgemesh; - e.envelope_geometry_mesh = edgemesh; - e.constrained_position_name = "vertices"; - e.geometry_position_name = "vertices"; - e.thickness = j["envelope_size"]; - - if (e.envelope_name == "input") { - e.envelope_geometry_mesh = mesh; // set as input - } - - enves.push_back(e); + enves.push_back(e); - wmtk::components::EnvelopeOptions e2; - e2.envelope_name = "bbox"; - e2.envelope_constrained_mesh = bboxmesh; - e2.envelope_geometry_mesh = bboxmesh; - e2.constrained_position_name = "vertices"; - e2.geometry_position_name = "vertices"; - e2.thickness = 0.0001; + wmtk::components::EnvelopeOptions e2; + e2.envelope_name = "bbox"; + e2.envelope_constrained_mesh = bboxmesh; + e2.envelope_geometry_mesh = bboxmesh; + e2.constrained_position_name = "vertices"; + e2.geometry_position_name = "vertices"; + e2.thickness = 0.0001; - enves.push_back(e2); + enves.push_back(e2); + } wmtk::components::WildMeshingOptions wmo; diff --git a/components/tests/wildmeshing/test_wildmeshing.cpp b/components/tests/wildmeshing/test_wildmeshing.cpp new file mode 100644 index 0000000000..0dda5cee3e --- /dev/null +++ b/components/tests/wildmeshing/test_wildmeshing.cpp @@ -0,0 +1,123 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +using namespace wmtk; +using namespace components; + +constexpr PrimitiveType PV = PrimitiveType::Vertex; +constexpr PrimitiveType PE = PrimitiveType::Edge; +constexpr PrimitiveType PF = PrimitiveType::Triangle; + +const std::filesystem::path data_dir = WMTK_DATA_DIR; + +TEST_CASE("test_wildmeshing_submesh", "[wildmeshing][.]") +{ + // logger().set_level(spdlog::level::off); + logger().set_level(spdlog::level::trace); + + std::shared_ptr mesh_ptr = + std::make_shared(tests::edge_region_with_position_2D()); + tests::DEBUG_TriMesh& m = *mesh_ptr; + + // add input + std::shared_ptr child_input_ptr; + { + child_input_ptr = std::make_shared(tests::single_line()); + + const Tuple child_tuple = child_input_ptr->edge_tuple_from_vids(0, 1); + const Tuple parent_tuple = m.edge_tuple_from_vids(4, 5); + std::vector> map_tuples; + map_tuples.emplace_back(std::array{child_tuple, parent_tuple}); + m.register_child_mesh(child_input_ptr, map_tuples); + } + // add boundary + std::shared_ptr child_bnd_ptr; + { + child_bnd_ptr = std::make_shared(); + tests::DEBUG_EdgeMesh& c = *child_bnd_ptr; + + RowVectors2l edges; + edges.resize(8, 2); + edges.row(0) << 0, 1; + edges.row(1) << 1, 2; + edges.row(2) << 2, 3; + edges.row(3) << 3, 4; + edges.row(4) << 4, 5; + edges.row(5) << 5, 6; + edges.row(6) << 6, 7; + edges.row(7) << 7, 0; + c.initialize(edges); + + std::vector> map_tuples; + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(0, 1), m.edge_tuple_from_vids(0, 1)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(1, 2), m.edge_tuple_from_vids(1, 2)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(2, 3), m.edge_tuple_from_vids(2, 6)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(3, 4), m.edge_tuple_from_vids(6, 9)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(4, 5), m.edge_tuple_from_vids(9, 8)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(5, 6), m.edge_tuple_from_vids(8, 7)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(6, 7), m.edge_tuple_from_vids(7, 3)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(7, 0), m.edge_tuple_from_vids(3, 0)}); + m.register_child_mesh(child_bnd_ptr, map_tuples); + } + + // envelopes + std::vector enves; + { + wmtk::components::EnvelopeOptions e; + e.envelope_name = "input"; + e.envelope_constrained_mesh = child_input_ptr; + e.envelope_geometry_mesh = child_input_ptr; + e.thickness = 1e-3; + + enves.push_back(e); + + wmtk::components::EnvelopeOptions e2; + e2.envelope_name = "bbox"; + e2.envelope_constrained_mesh = child_bnd_ptr; + e2.envelope_geometry_mesh = child_bnd_ptr; + e2.thickness = 1e-4; + + enves.push_back(e2); + } + + wmtk::components::WildMeshingOptions wmo; + wmo.input_mesh = mesh_ptr; + wmo.target_edge_length = 0.05; + wmo.max_passes = 5; + wmo.replace_double_coordinate = true; + wmo.intermediate_output_path = ""; + wmo.intermediate_output_name = "out"; + wmo.envelopes = enves; + wmo.use_embedding = true; + + auto meshes_after_tetwild = wildmeshing(wmo); + + // CHECK(emb.has_child_mesh()); + // CHECK(emb.get_child_meshes().size() == 2); + //{ + // using submesh::utils::write; + // CHECK_NOTHROW(write("wildmeshing_submesh", "vertices", emb, true, true, true, false)); + // } +} \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt index 03af095829..2f5f4fc192 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt +++ b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt @@ -19,3 +19,6 @@ set(SRC_FILES internal/wildmeshing_embedding_2d.cpp) target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) + +add_component_test(wmtk::${COMPONENT_NAME} + ${PROJECT_SOURCE_DIR}/components/tests/wildmeshing/test_wildmeshing.cpp) \ No newline at end of file diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp index 10ae11ac23..ff8a7962d0 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp @@ -5,24 +5,68 @@ namespace wmtk::components { struct EnvelopeOptions { + /** + * Name of the envelope. Mostly used for debugging. + */ std::string envelope_name; + /** + * The mesh that is used to construct the envelope. + */ std::shared_ptr envelope_constrained_mesh; + /** + * The mesh that is checked if it is within the envelope. + */ std::shared_ptr envelope_geometry_mesh; - std::string constrained_position_name; - std::string geometry_position_name; - double thickness; + /** + * The position attribute name of the envelope-construction mesh. + */ + std::string constrained_position_name = "vertices"; + /** + * The position attribute name for the mesh that must remain within the envelope. + */ + std::string geometry_position_name = "vertices"; + /** + * The envelope thickness, relative to the bounding box of the embedding. + */ + double thickness = 1e-6; }; struct WildMeshingOptions { + /** + * The mesh that is embedding the submeshes (represented by the envelope options). + */ std::shared_ptr input_mesh; + /** + * The position attribute of the input mesh, i.e., the embedding. + */ std::string input_mesh_position = "vertices"; + /** + * Target edge length, relative to the bounding box of the input mesh. + */ double target_edge_length = 0.05; + /** + * The targeted max amips. The optimization will continue until every cell has an amips below + * the max or until it runs out of iterations. + */ double target_max_amips = 10.0; + /** + * The maximum number of iterations of the optimization. + */ double max_passes = 10; + /** + * Debugging output. + */ bool intermediate_output = false; + /** + * Replace the `double` vertex positions by `Rational` positions. Use this if your mesh has its + * positions stored as `double`. + */ bool replace_double_coordinate = false; + /** + * TODO document (I don't know what this is good for) + */ size_t scheduler_update_frequency = 0; std::string intermediate_output_path = ""; std::string intermediate_output_name; @@ -32,7 +76,13 @@ struct WildMeshingOptions bool skip_swap = false; bool skip_smooth = false; + /** + * The envelopes ensure that substructures remain in their position up to a given error. + */ std::vector envelopes; + /** + * Further attributes that will be handled with default behavior. + */ std::vector pass_through; /** diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index eaeb906466..17dcc75639 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -195,8 +195,6 @@ std::vector, std::string>> wildmeshing_embedding } auto pt_attribute = mesh.get_attribute_handle(options.input_mesh_position, PrimitiveType::Vertex); - logger().trace("Getting rational point accessor"); - auto pt_accessor = mesh.create_accessor(pt_attribute.as()); ////////////////////////////////// // computing bbox diagonal @@ -213,10 +211,9 @@ std::vector, std::string>> wildmeshing_embedding auto amips_accessor = mesh.create_accessor(amips_attribute.as()); // amips update auto compute_amips = [](const Eigen::MatrixX& P) -> Eigen::VectorXd { - assert(P.rows() == 2 || P.rows() == 3); // rows --> attribute dimension + assert(P.rows() == 2); // rows --> attribute dimension assert(P.cols() == 3); // triangle - assert(P.rows() == 2); std::array pts; for (size_t i = 0; i < 3; ++i) { for (size_t j = 0; j < 2; ++j) { @@ -283,7 +280,8 @@ std::vector, std::string>> wildmeshing_embedding ////////////////////////////////// // substructures ////////////////////////////////// - auto emb_ptr = submesh::utils::submesh_from_multimesh(options.input_mesh); + std::map, std::shared_ptr> mm_to_submesh_map; + auto emb_ptr = submesh::utils::submesh_from_multimesh(options.input_mesh, mm_to_submesh_map); submesh::Embedding emb = *emb_ptr; ////////////////////////////////// @@ -370,16 +368,12 @@ std::vector, std::string>> wildmeshing_embedding logger().info("wildmeshing2d: registered {} mesh as envelope constraints", e.envelope_name); - // auto constrained_pt_handle = constrained_mesh.get_attribute_handle( - // e.constrained_position_name, - // PrimitiveType::Vertex); - - // multimesh_meshes.push_back(std::make_pair(e.envelope_constrained_mesh, e.envelope_name)); - // pass_through_attributes.emplace_back(constrained_pt_handle); - - // mesh_constraint_pairs.emplace_back(geometry_pt_handle, constrained_pt_handle); + if (&constrained_mesh != &geometry_mesh) { + log_and_throw_error( + "wildmeshing2d: constrained and geometry mesh must be the same for submesh usage."); + } - submesh::SubMesh& sub = *emb.get_child_meshes()[0]; + submesh::SubMesh& sub = *mm_to_submesh_map.at(e.envelope_geometry_mesh); envelope_invariant->add( std::make_shared(pt_attribute, e.thickness * bbdiag, sub)); diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp index 692f367c92..a5ae0f84d4 100644 --- a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp @@ -6,6 +6,14 @@ namespace wmtk::submesh::utils { std::shared_ptr submesh_from_multimesh(const std::shared_ptr& mesh) +{ + std::map, std::shared_ptr> smm; + return submesh_from_multimesh(mesh, smm); +} + +std::shared_ptr submesh_from_multimesh( + const std::shared_ptr& mesh, + std::map, std::shared_ptr>& submesh_map) { // log_assert(mesh->is_multi_mesh_root(), "submesh_from_multimesh must be called on root mesh"); @@ -15,6 +23,8 @@ std::shared_ptr submesh_from_multimesh(const std::shared_ptr& m for (const auto& child_mesh_ptr : mesh->get_child_meshes()) { auto sub_ptr = emb.add_submesh(); + submesh_map[child_mesh_ptr] = sub_ptr; + const Mesh& cm = *child_mesh_ptr; for (const Tuple& child_tuple : cm.get_all(cm.top_simplex_type())) { const simplex::Simplex child_simplex(cm.top_simplex_type(), child_tuple); diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.hpp b/src/wmtk/submesh/utils/submesh_from_multimesh.hpp index 71eaa978dc..671b56479e 100644 --- a/src/wmtk/submesh/utils/submesh_from_multimesh.hpp +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.hpp @@ -1,9 +1,14 @@ #pragma once #include +#include namespace wmtk::submesh::utils { std::shared_ptr submesh_from_multimesh(const std::shared_ptr& mesh); +std::shared_ptr submesh_from_multimesh( + const std::shared_ptr& mesh, + std::map, std::shared_ptr>& submesh_map); + } // namespace wmtk::submesh::utils From 5fd86036625f769b3abeafa5aa774f2c0b961eda Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Mon, 17 Mar 2025 16:09:57 -0400 Subject: [PATCH 35/47] More updates to wildmeshing. ProjectionOperation needs to be extended for submesh. --- .../internal/wildmeshing_embedding_2d.cpp | 32 ++++++++++--------- src/wmtk/operations/composite/TriEdgeSwap.cpp | 7 ++++ src/wmtk/operations/composite/TriEdgeSwap.hpp | 5 +++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 17dcc75639..f7f9d492cc 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -303,7 +303,7 @@ std::vector, std::string>> wildmeshing_embedding ++counter; } } - if (counter != 2) { + if (counter < 2) { frozen_vertex_accessor.scalar_attribute(v) = 1; } } @@ -412,13 +412,13 @@ std::vector, std::string>> wildmeshing_embedding auto interior_edge = std::make_shared(mesh); - for (const auto& em : multimesh_meshes) { - interior_edge->add_boundary(*(em.first)); - } + // for (const auto& em : multimesh_meshes) { + // interior_edge->add_boundary(*(em.first)); + // } - auto invariant_separate_substructures = - std::make_shared( - mesh); // TODO remove for submesh + // auto invariant_separate_substructures = + // std::make_shared( + // mesh); // TODO remove for submesh auto frozen_vertex_invariant = std::make_shared( mesh, @@ -466,7 +466,7 @@ std::vector, std::string>> wildmeshing_embedding ////////////////////////////////// // 1) EdgeSplit ////////////////////////////////// - auto split = std::make_shared(mesh); + auto split = std::make_shared(emb); split->set_priority(long_edges_first); split->add_invariant(todo_larger); @@ -499,7 +499,7 @@ std::vector, std::string>> wildmeshing_embedding split_then_round->add_operation(rounding); // split unrounded - auto split_unrounded = std::make_shared(mesh); + auto split_unrounded = std::make_shared(emb); split_unrounded->set_priority(long_edges_first); split_unrounded->add_invariant(todo_larger); @@ -584,7 +584,7 @@ std::vector, std::string>> wildmeshing_embedding ////////////////////////////////// auto setup_collapse = [&](std::shared_ptr& collapse) { - collapse->add_invariant(invariant_separate_substructures); + // collapse->add_invariant(invariant_separate_substructures); collapse->add_invariant(std::make_shared(mesh)); collapse->add_invariant(link_condition); collapse->add_invariant(inversion_invariant); @@ -609,7 +609,7 @@ std::vector, std::string>> wildmeshing_embedding collapse->add_transfer_strategy(edge_length_update); }; - auto collapse1 = std::make_shared(mesh); + auto collapse1 = std::make_shared(emb); collapse1->add_invariant(frozen_vertex_invariant); collapse1->set_new_attribute_strategy(pt_attribute, clps_strat1); @@ -618,7 +618,7 @@ std::vector, std::string>> wildmeshing_embedding CollapseBasicStrategy::CopyOther); setup_collapse(collapse1); - auto collapse2 = std::make_shared(mesh); + auto collapse2 = std::make_shared(emb); collapse2->add_invariant(frozen_opp_vertex_invariant); collapse2->set_new_attribute_strategy(pt_attribute, clps_strat2); @@ -667,7 +667,7 @@ std::vector, std::string>> wildmeshing_embedding op.add_transfer_strategy(edge_length_update); op.add_transfer_strategy(tag_update); - collapse.add_invariant(invariant_separate_substructures); + // collapse.add_invariant(invariant_separate_substructures); collapse.add_invariant(link_condition); @@ -712,7 +712,7 @@ std::vector, std::string>> wildmeshing_embedding }; - auto swap = std::make_shared(mesh); + auto swap = std::make_shared(emb); setup_swap(*swap, swap->collapse(), swap->split(), interior_edge); if (!options.skip_swap) { @@ -731,7 +731,9 @@ std::vector, std::string>> wildmeshing_embedding smoothing->add_invariant(frozen_vertex_invariant); smoothing->add_invariant(inversion_invariant); - auto proj_smoothing = std::make_shared(smoothing, mesh_constraint_pairs); + auto proj_smoothing = std::make_shared( + smoothing, + mesh_constraint_pairs); // TODO adapt for embedding // proj_smoothing->use_random_priority() = true; proj_smoothing->add_invariant(frozen_vertex_invariant); proj_smoothing->add_invariant(envelope_invariant); diff --git a/src/wmtk/operations/composite/TriEdgeSwap.cpp b/src/wmtk/operations/composite/TriEdgeSwap.cpp index 99935fb082..f49e4367a5 100644 --- a/src/wmtk/operations/composite/TriEdgeSwap.cpp +++ b/src/wmtk/operations/composite/TriEdgeSwap.cpp @@ -1,6 +1,7 @@ #include "TriEdgeSwap.hpp" #include +#include namespace wmtk::operations::composite { @@ -10,6 +11,12 @@ TriEdgeSwap::TriEdgeSwap(Mesh& m) , m_collapse(m) {} +TriEdgeSwap::TriEdgeSwap(submesh::Embedding& m) + : Operation(m.mesh()) + , m_split(m) + , m_collapse(m) +{} + std::vector TriEdgeSwap::execute(const simplex::Simplex& simplex) { diff --git a/src/wmtk/operations/composite/TriEdgeSwap.hpp b/src/wmtk/operations/composite/TriEdgeSwap.hpp index 2e35b040f4..9c77ca99fc 100644 --- a/src/wmtk/operations/composite/TriEdgeSwap.hpp +++ b/src/wmtk/operations/composite/TriEdgeSwap.hpp @@ -4,6 +4,10 @@ #include #include +namespace wmtk::submesh { +class Embedding; +} + namespace wmtk::operations::composite { /** * Performs an edge swap, implemented as a combination of swap and collapse. @@ -41,6 +45,7 @@ class TriEdgeSwap : public Operation { public: TriEdgeSwap(Mesh& m); + TriEdgeSwap(submesh::Embedding& m); PrimitiveType primitive_type() const override { return PrimitiveType::Edge; } From 952844c204f4f20f566a44f32f4e08a0c54eb4e6 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 20 Mar 2025 15:09:59 -0400 Subject: [PATCH 36/47] Setting up a test for ProjectionOperation --- .../operations/composite/ProjectOperation.cpp | 71 +++++++- .../operations/composite/ProjectOperation.hpp | 16 +- tests/operations/CMakeLists.txt | 1 + .../operations/test_projection_operation.cpp | 167 ++++++++++++++++++ 4 files changed, 246 insertions(+), 9 deletions(-) create mode 100644 tests/operations/test_projection_operation.cpp diff --git a/src/wmtk/operations/composite/ProjectOperation.cpp b/src/wmtk/operations/composite/ProjectOperation.cpp index daf81341fe..fc8afcaefe 100644 --- a/src/wmtk/operations/composite/ProjectOperation.cpp +++ b/src/wmtk/operations/composite/ProjectOperation.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include @@ -18,8 +21,15 @@ ProjectOperation::ProjectOperation( ProjectOperation::ProjectOperation( std::shared_ptr main_op, const std::vector& mesh_constaint_pairs) - : AttributesUpdate(main_op->mesh()) - , m_main_op(main_op) + : ProjectOperation(main_op->mesh(), mesh_constaint_pairs) +{ + m_main_op = main_op; +} + +ProjectOperation::ProjectOperation( + Mesh& mesh, + const std::vector& mesh_constaint_pairs) + : AttributesUpdate(mesh) { for (auto& pair : mesh_constaint_pairs) { int64_t count = 0; @@ -84,13 +94,60 @@ ProjectOperation::ProjectOperation( } } +ProjectOperation::ProjectOperation( + std::shared_ptr main_op, + const submesh::Embedding& emb, + const attribute::MeshAttributeHandle& pos_handle) + : AttributesUpdate(main_op->mesh()) + , m_main_op(main_op) +{ + const Mesh& m = emb.mesh(); + assert(&m == &pos_handle.mesh()); + + log_and_throw_error("incomplete implementation"); + + // Wrapper for the position accessor that works for double and Rational. Probably not the most + // efficient code but good enough for what is required here + auto get_pos = [&pos_handle, &m](const simplex::IdSimplex& s) -> Eigen::VectorXd { + return std::visit( + [&m, &s](auto&& tah) noexcept -> Eigen::VectorXd { + using HandleType = typename std::decay_t; + using AttributeType = typename HandleType::Type; + + const auto accessor = m.create_const_accessor(tah); + if constexpr (std::is_same_v) { + return accessor.const_vector_attribute(s); + } + if constexpr (std::is_same_v) { + return accessor.const_vector_attribute(s).cast(); + } + log_and_throw_error("Position attribute must be double or rational"); + }, + pos_handle.handle()); + }; + + for (const auto& sub_ptr : emb.get_child_meshes()) { + const submesh::SubMesh& sub = *sub_ptr; + const PrimitiveType pt = sub.top_simplex_type(); + + for (const simplex::IdSimplex& cell : sub.get_all_id_simplex(pt)) { + // + } + } +} + std::vector ProjectOperation::execute(const simplex::Simplex& simplex) { - // mesh has to be the same as the main_op mesh - assert(&m_main_op->mesh() == &mesh()); - const auto main_simplices = (*m_main_op)(simplex); - if (main_simplices.empty()) return {}; - assert(main_simplices.size() == 1); + std::vector main_simplices; + if (m_main_op) { + // mesh has to be the same as the main_op mesh + assert(&m_main_op->mesh() == &mesh()); + main_simplices = (*m_main_op)(simplex); + if (main_simplices.empty()) return {}; + assert(main_simplices.size() == 1); + } else { + main_simplices.emplace_back(simplex); + } const auto main_tup = main_simplices.front().tuple(); diff --git a/src/wmtk/operations/composite/ProjectOperation.hpp b/src/wmtk/operations/composite/ProjectOperation.hpp index b6df673d38..36d5dc3df0 100644 --- a/src/wmtk/operations/composite/ProjectOperation.hpp +++ b/src/wmtk/operations/composite/ProjectOperation.hpp @@ -8,10 +8,15 @@ namespace SimpleBVH { class BVH; } +namespace wmtk::submesh { +class Embedding; +} + namespace wmtk::operations::composite { class ProjectOperation : public AttributesUpdate { public: + // first: construction, second: query using MeshConstrainPair = std::pair; @@ -24,15 +29,22 @@ class ProjectOperation : public AttributesUpdate std::shared_ptr main_op, const std::vector& mesh_constaint_pairs); + ProjectOperation(Mesh& mesh, const std::vector& mesh_constaint_pairs); + + ProjectOperation( + std::shared_ptr main_op, + const submesh::Embedding& emb, + const attribute::MeshAttributeHandle& pos_handle); + std::vector execute(const simplex::Simplex& simplex) override; - PrimitiveType primitive_type() const override { return m_main_op->primitive_type(); } + PrimitiveType primitive_type() const override { return PrimitiveType::Vertex; } private: using BVHConstrainPair = std::pair>; - const std::shared_ptr m_main_op; + std::shared_ptr m_main_op; std::vector m_bvh; }; } // namespace wmtk::operations::composite diff --git a/tests/operations/CMakeLists.txt b/tests/operations/CMakeLists.txt index 8ee8ac1c34..51ef97a39f 100644 --- a/tests/operations/CMakeLists.txt +++ b/tests/operations/CMakeLists.txt @@ -11,5 +11,6 @@ set(TEST_SOURCES test_attribute_transfer.cpp vertex_optimization.cpp edge_operation_data.cpp + test_projection_operation.cpp ) target_sources(wmtk_tests PRIVATE ${TEST_SOURCES}) diff --git a/tests/operations/test_projection_operation.cpp b/tests/operations/test_projection_operation.cpp new file mode 100644 index 0000000000..49eba59fd5 --- /dev/null +++ b/tests/operations/test_projection_operation.cpp @@ -0,0 +1,167 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace wmtk; +using namespace operations; +using namespace composite; + +void write(const Mesh& mesh, const std::string& name) +{ + if (mesh.top_simplex_type() == PrimitiveType::Triangle) { + wmtk::io::ParaviewWriter writer(name, "vertices", mesh, true, true, true, false); + mesh.serialize(writer); + } else if (mesh.top_simplex_type() == PrimitiveType::Edge) { + wmtk::io::ParaviewWriter writer(name, "vertices", mesh, true, true, false, false); + mesh.serialize(writer); + } +} + +attribute::MeshAttributeHandle register_and_transfer_positions( + attribute::MeshAttributeHandle parent_pos_handle, + Mesh& child) +{ + auto child_pos_handle = child.register_attribute( + "vertices", + PrimitiveType::Vertex, + parent_pos_handle.dimension()); + + attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/parent_pos_handle, + /*target=*/child_pos_handle) + ->run_on_all(); + + return child_pos_handle; +} + +TEST_CASE("test_projection_operation", "[operations][.]") +{ + // logger().set_level(spdlog::level::off); + logger().set_level(spdlog::level::trace); + + std::shared_ptr mesh_ptr = + std::make_shared(tests::edge_region_with_position_2D()); + tests::DEBUG_TriMesh& m = *mesh_ptr; + + // add input (edge 4-5) + std::shared_ptr child_input_ptr; + { + child_input_ptr = std::make_shared(tests::single_line()); + + const Tuple child_tuple = child_input_ptr->edge_tuple_from_vids(0, 1); + const Tuple parent_tuple = m.edge_tuple_from_vids(4, 5); + std::vector> map_tuples; + map_tuples.emplace_back(std::array{child_tuple, parent_tuple}); + m.register_child_mesh(child_input_ptr, map_tuples); + } + // add boundary + std::shared_ptr child_bnd_ptr; + { + child_bnd_ptr = std::make_shared(); + tests::DEBUG_EdgeMesh& c = *child_bnd_ptr; + + RowVectors2l edges; + edges.resize(8, 2); + edges.row(0) << 0, 1; + edges.row(1) << 1, 2; + edges.row(2) << 2, 3; + edges.row(3) << 3, 4; + edges.row(4) << 4, 5; + edges.row(5) << 5, 6; + edges.row(6) << 6, 7; + edges.row(7) << 7, 0; + c.initialize(edges); + + std::vector> map_tuples; + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(0, 1), m.edge_tuple_from_vids(0, 1)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(1, 2), m.edge_tuple_from_vids(1, 2)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(2, 3), m.edge_tuple_from_vids(2, 6)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(3, 4), m.edge_tuple_from_vids(6, 9)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(4, 5), m.edge_tuple_from_vids(9, 8)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(5, 6), m.edge_tuple_from_vids(8, 7)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(6, 7), m.edge_tuple_from_vids(7, 3)}); + map_tuples.emplace_back( + std::array{c.edge_tuple_from_vids(7, 0), m.edge_tuple_from_vids(3, 0)}); + m.register_child_mesh(child_bnd_ptr, map_tuples); + } + + attribute::MeshAttributeHandle pos_handle = + m.get_attribute_handle("vertices", PrimitiveType::Vertex); + + SECTION("multimesh") + { + // move vertices around + { + auto acc = m.create_accessor(pos_handle); + const Tuple t4 = m.vertex_tuple_from_id(4); + acc.vector_attribute(t4)[0] = 0.75; + acc.vector_attribute(t4)[1] = 0.9; + } + + auto write_all = [&]() { + static int64_t write_counter = 0; + write(m, fmt::format("test_proj_mm_parent_{}", write_counter)); + write(*child_input_ptr, fmt::format("test_proj_mm_input_{}", write_counter)); + write(*child_bnd_ptr, fmt::format("test_proj_mm_bnd_{}", write_counter)); + ++write_counter; + }; + + auto child_input_pos_handle = register_and_transfer_positions(pos_handle, *child_input_ptr); + auto child_bnd_pos_handle = register_and_transfer_positions(pos_handle, *child_bnd_ptr); + + std::vector> update_child_position; + update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/pos_handle, + /*target=*/child_input_pos_handle)); + update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/pos_handle, + /*target=*/child_bnd_pos_handle)); + + std::vector mesh_constraint_pairs; + mesh_constraint_pairs.emplace_back(child_input_pos_handle, pos_handle); + mesh_constraint_pairs.emplace_back(child_bnd_pos_handle, pos_handle); + + auto smoothing = std::make_shared(m, pos_handle); + for (auto& s : update_child_position) { + smoothing->add_transfer_strategy(s); + } + auto proj_smoothing = std::make_shared(m, mesh_constraint_pairs); + for (auto& s : update_child_position) { + proj_smoothing->add_transfer_strategy(s); + } + + const simplex::Simplex v1(PrimitiveType::Vertex, m.vertex_tuple_from_id(1)); + + write_all(); + (*smoothing)(v1); + write_all(); + (*proj_smoothing)(v1); + + write_all(); + } + + // CHECK(emb.has_child_mesh()); + // CHECK(emb.get_child_meshes().size() == 2); + //{ + // using submesh::utils::write; + // CHECK_NOTHROW(write("wildmeshing_submesh", "vertices", emb, true, true, true, false)); + // } +} \ No newline at end of file From c75dafc75a3088663156449f89c4e682fef34eab Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 20 Mar 2025 18:29:19 -0400 Subject: [PATCH 37/47] Fix projection operation mm test. --- tests/operations/test_projection_operation.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/operations/test_projection_operation.cpp b/tests/operations/test_projection_operation.cpp index 49eba59fd5..aab0c3b4a7 100644 --- a/tests/operations/test_projection_operation.cpp +++ b/tests/operations/test_projection_operation.cpp @@ -134,16 +134,26 @@ TEST_CASE("test_projection_operation", "[operations][.]") update_child_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( /*source=*/pos_handle, /*target=*/child_bnd_pos_handle)); + std::vector> update_parent_position; + update_parent_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/child_input_pos_handle, + /*target=*/pos_handle)); + update_parent_position.emplace_back(attribute_update::make_cast_attribute_transfer_strategy( + /*source=*/child_bnd_pos_handle, + /*target=*/pos_handle)); std::vector mesh_constraint_pairs; - mesh_constraint_pairs.emplace_back(child_input_pos_handle, pos_handle); - mesh_constraint_pairs.emplace_back(child_bnd_pos_handle, pos_handle); + mesh_constraint_pairs.emplace_back(child_input_pos_handle, child_input_pos_handle); + mesh_constraint_pairs.emplace_back(child_bnd_pos_handle, child_bnd_pos_handle); auto smoothing = std::make_shared(m, pos_handle); for (auto& s : update_child_position) { smoothing->add_transfer_strategy(s); } auto proj_smoothing = std::make_shared(m, mesh_constraint_pairs); + for (auto& s : update_parent_position) { + proj_smoothing->add_transfer_strategy(s); + } for (auto& s : update_child_position) { proj_smoothing->add_transfer_strategy(s); } From b95b07441144637b4baab333f3a6dfd50cc466b4 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 25 Mar 2025 09:08:25 +0100 Subject: [PATCH 38/47] Make projection operation also work for SubMesh. --- .../operations/composite/ProjectOperation.cpp | 126 ++++++++++++++---- .../operations/composite/ProjectOperation.hpp | 11 +- src/wmtk/submesh/Embedding.cpp | 9 ++ src/wmtk/submesh/Embedding.hpp | 7 + .../submesh/utils/submesh_from_multimesh.cpp | 1 + .../submesh/utils/submesh_from_multimesh.hpp | 17 +++ .../operations/test_projection_operation.cpp | 86 +++++++++--- 7 files changed, 215 insertions(+), 42 deletions(-) diff --git a/src/wmtk/operations/composite/ProjectOperation.cpp b/src/wmtk/operations/composite/ProjectOperation.cpp index fc8afcaefe..02fcf069d0 100644 --- a/src/wmtk/operations/composite/ProjectOperation.cpp +++ b/src/wmtk/operations/composite/ProjectOperation.cpp @@ -96,30 +96,35 @@ ProjectOperation::ProjectOperation( ProjectOperation::ProjectOperation( std::shared_ptr main_op, - const submesh::Embedding& emb, + submesh::Embedding& emb, const attribute::MeshAttributeHandle& pos_handle) - : AttributesUpdate(main_op->mesh()) - , m_main_op(main_op) + : ProjectOperation(emb, pos_handle) +{ + m_main_op = main_op; +} + +ProjectOperation::ProjectOperation( + submesh::Embedding& emb, + const attribute::MeshAttributeHandle& pos_handle) + : AttributesUpdate(emb.mesh()) + , m_embedding_pos_handle(pos_handle) { const Mesh& m = emb.mesh(); assert(&m == &pos_handle.mesh()); - - log_and_throw_error("incomplete implementation"); - // Wrapper for the position accessor that works for double and Rational. Probably not the most // efficient code but good enough for what is required here - auto get_pos = [&pos_handle, &m](const simplex::IdSimplex& s) -> Eigen::VectorXd { + auto get_pos = [&pos_handle, &m](const Tuple& _t) -> Eigen::VectorXd { return std::visit( - [&m, &s](auto&& tah) noexcept -> Eigen::VectorXd { + [&m, &_t](auto&& tah) noexcept -> Eigen::VectorXd { using HandleType = typename std::decay_t; using AttributeType = typename HandleType::Type; const auto accessor = m.create_const_accessor(tah); if constexpr (std::is_same_v) { - return accessor.const_vector_attribute(s); + return accessor.const_vector_attribute(_t); } if constexpr (std::is_same_v) { - return accessor.const_vector_attribute(s).cast(); + return accessor.const_vector_attribute(_t).cast(); } log_and_throw_error("Position attribute must be double or rational"); }, @@ -130,9 +135,40 @@ ProjectOperation::ProjectOperation( const submesh::SubMesh& sub = *sub_ptr; const PrimitiveType pt = sub.top_simplex_type(); - for (const simplex::IdSimplex& cell : sub.get_all_id_simplex(pt)) { - // + if (pt == PrimitiveType::Vertex) { + logger().info("Ignoring vertex submeshes in ProjectOperation"); + continue; + } + + int64_t count = 0; + int64_t index = 0; + + const std::vector facest = sub.get_all_id_simplex(pt); + + const int64_t dim = int64_t(pt) + 1; + + Eigen::MatrixXd vertices(dim * facest.size(), pos_handle.dimension()); + Eigen::MatrixXi faces(facest.size(), dim); + + for (const simplex::IdSimplex& cell : facest) { + const auto tmp = + faces_single_dimension_tuples(m, m.get_simplex(cell), PrimitiveType::Vertex); + + assert(tmp.size() == dim); + for (int64_t j = 0; j < tmp.size(); ++j) { + Eigen::VectorXd p = get_pos(tmp[j]); + faces(index, j) = count; + vertices.row(dim * index + j) = p; + + ++count; + } + ++index; } + + auto bvh = std::make_shared(); + bvh->init(vertices, faces, 1e-10); + + m_submesh_bvh.emplace_back(sub_ptr, bvh); } } @@ -151,27 +187,27 @@ std::vector ProjectOperation::execute(const simplex::Simplex& const auto main_tup = main_simplices.front().tuple(); - for (auto& pair : m_bvh) { + for (auto& [pos_attribute, bvh] : m_bvh) { const std::vector mapped_tuples_after = - mesh().map_tuples(pair.first.mesh(), primitive_type(), {main_tup}); + mesh().map_tuples(pos_attribute.mesh(), primitive_type(), {main_tup}); if (mapped_tuples_after.empty()) continue; - if (pair.first.holds()) { + if (pos_attribute.holds()) { wmtk::attribute::Accessor accessor = - pair.first.mesh().create_accessor(pair.first.as()); + pos_attribute.mesh().create_accessor(pos_attribute.as()); for (const auto& t : mapped_tuples_after) { auto p = accessor.vector_attribute(t); SimpleBVH::VectorMax3d nearest_point; double sq_dist; - pair.second->nearest_facet(p, nearest_point, sq_dist); + bvh->nearest_facet(p, nearest_point, sq_dist); p = nearest_point; } } else { - assert((pair.first.holds())); + assert((pos_attribute.holds())); wmtk::attribute::Accessor accessor = - pair.first.mesh().create_accessor(pair.first.as()); + pos_attribute.mesh().create_accessor(pos_attribute.as()); for (const auto& t : mapped_tuples_after) { auto p_map = accessor.vector_attribute(t); @@ -180,16 +216,16 @@ std::vector ProjectOperation::execute(const simplex::Simplex& const Eigen::Vector3d p = p_map.cast(); SimpleBVH::VectorMax3d nearest_point; double sq_dist; - pair.second->nearest_facet(p, nearest_point, sq_dist); - for (int64_t d = 0; d < pair.first.dimension(); ++d) { + bvh->nearest_facet(p, nearest_point, sq_dist); + for (int64_t d = 0; d < pos_attribute.dimension(); ++d) { p_map(d) = Rational(nearest_point[d], true); } } else if (p_map.rows() == 2) { const Eigen::Vector2d p = p_map.cast(); SimpleBVH::VectorMax3d nearest_point; double sq_dist; - pair.second->nearest_facet(p, nearest_point, sq_dist); - for (int64_t d = 0; d < pair.first.dimension(); ++d) { + bvh->nearest_facet(p, nearest_point, sq_dist); + for (int64_t d = 0; d < pos_attribute.dimension(); ++d) { p_map(d) = Rational(nearest_point[d], true); } } else { @@ -199,6 +235,50 @@ std::vector ProjectOperation::execute(const simplex::Simplex& } } + for (auto& [sub_ptr, bvh] : m_submesh_bvh) { + const submesh::SubMesh& sub = *sub_ptr; + if (!sub.contains(main_tup, primitive_type())) { + continue; + } + + if (m_embedding_pos_handle.holds()) { + wmtk::attribute::Accessor accessor = + mesh().create_accessor(m_embedding_pos_handle.as()); + + auto p = accessor.vector_attribute(main_tup); + SimpleBVH::VectorMax3d nearest_point; + double sq_dist; + bvh->nearest_facet(p, nearest_point, sq_dist); + p = nearest_point; + } else { + assert((m_embedding_pos_handle.holds())); + wmtk::attribute::Accessor accessor = + mesh().create_accessor(m_embedding_pos_handle.as()); + + auto p_map = accessor.vector_attribute(main_tup); + + if (p_map.rows() == 3) { + const Eigen::Vector3d p = p_map.cast(); + SimpleBVH::VectorMax3d nearest_point; + double sq_dist; + bvh->nearest_facet(p, nearest_point, sq_dist); + for (int64_t d = 0; d < m_embedding_pos_handle.dimension(); ++d) { + p_map(d) = Rational(nearest_point[d], true); + } + } else if (p_map.rows() == 2) { + const Eigen::Vector2d p = p_map.cast(); + SimpleBVH::VectorMax3d nearest_point; + double sq_dist; + bvh->nearest_facet(p, nearest_point, sq_dist); + for (int64_t d = 0; d < m_embedding_pos_handle.dimension(); ++d) { + p_map(d) = Rational(nearest_point[d], true); + } + } else { + throw std::runtime_error("wrong vector dimension"); + } + } + } + return main_simplices; } diff --git a/src/wmtk/operations/composite/ProjectOperation.hpp b/src/wmtk/operations/composite/ProjectOperation.hpp index 36d5dc3df0..bc5d880531 100644 --- a/src/wmtk/operations/composite/ProjectOperation.hpp +++ b/src/wmtk/operations/composite/ProjectOperation.hpp @@ -10,7 +10,8 @@ class BVH; namespace wmtk::submesh { class Embedding; -} +class SubMesh; +} // namespace wmtk::submesh namespace wmtk::operations::composite { class ProjectOperation : public AttributesUpdate @@ -33,9 +34,11 @@ class ProjectOperation : public AttributesUpdate ProjectOperation( std::shared_ptr main_op, - const submesh::Embedding& emb, + submesh::Embedding& emb, const attribute::MeshAttributeHandle& pos_handle); + ProjectOperation(submesh::Embedding& emb, const attribute::MeshAttributeHandle& pos_handle); + std::vector execute(const simplex::Simplex& simplex) override; PrimitiveType primitive_type() const override { return PrimitiveType::Vertex; } @@ -43,8 +46,12 @@ class ProjectOperation : public AttributesUpdate private: using BVHConstrainPair = std::pair>; + using BVHSubMeshConstraintPair = + std::pair, std::shared_ptr>; std::shared_ptr m_main_op; std::vector m_bvh; + std::vector m_submesh_bvh; + attribute::MeshAttributeHandle m_embedding_pos_handle; }; } // namespace wmtk::operations::composite diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index a46831617a..d0a54cbf36 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -237,5 +237,14 @@ std::function Embedding::substructure_predicate() return m_substructure_predicate; } +void Embedding::update_tag_attribute_handles() +{ + Mesh& m = *m_mesh; + for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { + const auto& name = m_tag_attribute_name[pt]; + m_tag_handle[pt] = m.get_attribute_handle_typed(name, pt); + } +} + } // namespace wmtk::submesh diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index 1568f14a8c..eb10bd790d 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -29,6 +29,7 @@ class Embedding : public std::enable_shared_from_this, public MeshBas { public: Embedding(const std::shared_ptr& mesh); + Embedding(Embedding&) = delete; std::shared_ptr add_submesh(); @@ -63,6 +64,12 @@ class Embedding : public std::enable_shared_from_this, public MeshBas std::function substructure_predicate() const; + /** + * @brief Update the tag attribute handles. + * + * This function must be called after removing attributes or deregistering child meshes. + */ + void update_tag_attribute_handles(); private: std::shared_ptr m_mesh; diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp index a5ae0f84d4..9748a87dd0 100644 --- a/src/wmtk/submesh/utils/submesh_from_multimesh.cpp +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.cpp @@ -21,6 +21,7 @@ std::shared_ptr submesh_from_multimesh( Embedding& emb = *emb_ptr; for (const auto& child_mesh_ptr : mesh->get_child_meshes()) { + logger().trace("Add submesh from multimesh"); auto sub_ptr = emb.add_submesh(); submesh_map[child_mesh_ptr] = sub_ptr; diff --git a/src/wmtk/submesh/utils/submesh_from_multimesh.hpp b/src/wmtk/submesh/utils/submesh_from_multimesh.hpp index 671b56479e..2bb1d86c1c 100644 --- a/src/wmtk/submesh/utils/submesh_from_multimesh.hpp +++ b/src/wmtk/submesh/utils/submesh_from_multimesh.hpp @@ -5,8 +5,25 @@ namespace wmtk::submesh::utils { +/** + * @brief Make the mesh an Embedding and construct SubMeshes from all children. + * + * The method does not delete any child meshes. + * + * @param mesh The root mesh that becomes the embedding mesh. + * @return Shared pointer of the Embedding. + */ std::shared_ptr submesh_from_multimesh(const std::shared_ptr& mesh); +/** + * @brief Make the mesh an Embedding and construct SubMeshes from all children. + * + * The method does not delete any child meshes. + * + * @param mesh The root mesh that becomes the embedding mesh. + * @param submesh_map A map from child meshes to the new SubMeshes. + * @return Shared pointer of the Embedding. + */ std::shared_ptr submesh_from_multimesh( const std::shared_ptr& mesh, std::map, std::shared_ptr>& submesh_map); diff --git a/tests/operations/test_projection_operation.cpp b/tests/operations/test_projection_operation.cpp index aab0c3b4a7..9332281407 100644 --- a/tests/operations/test_projection_operation.cpp +++ b/tests/operations/test_projection_operation.cpp @@ -47,8 +47,9 @@ attribute::MeshAttributeHandle register_and_transfer_positions( TEST_CASE("test_projection_operation", "[operations][.]") { - // logger().set_level(spdlog::level::off); - logger().set_level(spdlog::level::trace); + logger().set_level(spdlog::level::off); + // logger().set_level(spdlog::level::trace); + opt_logger().set_level(spdlog::level::off); std::shared_ptr mesh_ptr = std::make_shared(tests::edge_region_with_position_2D()); @@ -106,16 +107,16 @@ TEST_CASE("test_projection_operation", "[operations][.]") attribute::MeshAttributeHandle pos_handle = m.get_attribute_handle("vertices", PrimitiveType::Vertex); - SECTION("multimesh") + // move vertices around { - // move vertices around - { - auto acc = m.create_accessor(pos_handle); - const Tuple t4 = m.vertex_tuple_from_id(4); - acc.vector_attribute(t4)[0] = 0.75; - acc.vector_attribute(t4)[1] = 0.9; - } + auto acc = m.create_accessor(pos_handle); + const Tuple t4 = m.vertex_tuple_from_id(4); + acc.vector_attribute(t4)[0] = 0.75; + acc.vector_attribute(t4)[1] = 0.9; + } + SECTION("multimesh") + { auto write_all = [&]() { static int64_t write_counter = 0; write(m, fmt::format("test_proj_mm_parent_{}", write_counter)); @@ -163,15 +164,66 @@ TEST_CASE("test_projection_operation", "[operations][.]") write_all(); (*smoothing)(v1); write_all(); - (*proj_smoothing)(v1); + CHECK_NOTHROW((*proj_smoothing)(v1)); write_all(); } + SECTION("submesh") + { + std::map, std::shared_ptr> submesh_map; + auto emb_ptr = submesh::utils::submesh_from_multimesh(mesh_ptr, submesh_map); + submesh::Embedding& emb = *emb_ptr; + auto sub_input_ptr = submesh_map[child_input_ptr]; + auto sub_bnd_ptr = submesh_map[child_bnd_ptr]; + submesh::SubMesh& sub_input = *sub_input_ptr; + submesh::SubMesh& sub_bnd = *sub_bnd_ptr; + + auto write_all = [&]() { + static int64_t write_counter = 0; + submesh::utils::write( + fmt::format("test_proj_sub_parent_{}", write_counter), + "vertices", + emb, + false, + true, + true, + false); + submesh::utils::write( + fmt::format("test_proj_sub_input_{}", write_counter), + "vertices", + sub_input, + false, + true, + false, + false); + submesh::utils::write( + fmt::format("test_proj_sub_bnd_{}", write_counter), + "vertices", + sub_bnd, + false, + true, + false, + false); + ++write_counter; + }; + + // deregister all child meshes + for (const auto [child, sub] : submesh_map) { + m.deregister_child_mesh(child); + } + emb.update_tag_attribute_handles(); + + write_all(); + + auto smoothing = std::make_shared(m, pos_handle); + auto proj_smoothing = std::make_shared(emb, pos_handle); + + const simplex::Simplex v1(PrimitiveType::Vertex, m.vertex_tuple_from_id(1)); + + (*smoothing)(v1); + write_all(); + CHECK_NOTHROW((*proj_smoothing)(v1)); - // CHECK(emb.has_child_mesh()); - // CHECK(emb.get_child_meshes().size() == 2); - //{ - // using submesh::utils::write; - // CHECK_NOTHROW(write("wildmeshing_submesh", "vertices", emb, true, true, true, false)); - // } + write_all(); + } } \ No newline at end of file From ee8032970971409bde6dc51a5ee40a5fa2f61808 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 25 Mar 2025 10:57:31 +0100 Subject: [PATCH 39/47] Issues with attribute new that is really hard to debug. --- .../tests/wildmeshing/test_wildmeshing.cpp | 1 + .../internal/wildmeshing_embedding_2d.cpp | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/components/tests/wildmeshing/test_wildmeshing.cpp b/components/tests/wildmeshing/test_wildmeshing.cpp index 0dda5cee3e..7614c611b0 100644 --- a/components/tests/wildmeshing/test_wildmeshing.cpp +++ b/components/tests/wildmeshing/test_wildmeshing.cpp @@ -109,6 +109,7 @@ TEST_CASE("test_wildmeshing_submesh", "[wildmeshing][.]") wmo.replace_double_coordinate = true; wmo.intermediate_output_path = ""; wmo.intermediate_output_name = "out"; + wmo.intermediate_output = true; wmo.envelopes = enves; wmo.use_embedding = true; diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index f7f9d492cc..6d9820e55f 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -204,6 +204,18 @@ std::vector, std::string>> wildmeshing_embedding const double target_edge_length = options.target_edge_length * bbdiag; logger().info("target edge length: {}", target_edge_length); + ////////////////////////////////// + // substructures + ////////////////////////////////// + std::map, std::shared_ptr> mm_to_submesh_map; + auto emb_ptr = submesh::utils::submesh_from_multimesh(options.input_mesh, mm_to_submesh_map); + submesh::Embedding& emb = *emb_ptr; + // deregister all child meshes + for (const auto& [child, sub] : mm_to_submesh_map) { + mesh.deregister_child_mesh(child); + } + emb.update_tag_attribute_handles(); + ////////////////////////////////// // store amips auto amips_attribute = @@ -277,13 +289,6 @@ std::vector, std::string>> wildmeshing_embedding compute_edge_length); edge_length_update->run_on_all(); - ////////////////////////////////// - // substructures - ////////////////////////////////// - std::map, std::shared_ptr> mm_to_submesh_map; - auto emb_ptr = submesh::utils::submesh_from_multimesh(options.input_mesh, mm_to_submesh_map); - submesh::Embedding emb = *emb_ptr; - ////////////////////////////////// // compute frozen vertices ////////////////////////////////// @@ -292,7 +297,7 @@ std::vector, std::string>> wildmeshing_embedding auto frozen_vertex_accessor = mesh.create_accessor(frozen_vertex_attribute.as()); for (const auto& sub_ptr : emb.get_child_meshes()) { - submesh::SubMesh& sub = *sub_ptr; + const submesh::SubMesh& sub = *sub_ptr; for (const simplex::IdSimplex& v : sub.get_all_id_simplex(PrimitiveType::Vertex)) { int64_t counter = 0; for (const Tuple& cof : cofaces_single_dimension_iterable( @@ -731,9 +736,7 @@ std::vector, std::string>> wildmeshing_embedding smoothing->add_invariant(frozen_vertex_invariant); smoothing->add_invariant(inversion_invariant); - auto proj_smoothing = std::make_shared( - smoothing, - mesh_constraint_pairs); // TODO adapt for embedding + auto proj_smoothing = std::make_shared(smoothing, emb, pt_attribute); // proj_smoothing->use_random_priority() = true; proj_smoothing->add_invariant(frozen_vertex_invariant); proj_smoothing->add_invariant(envelope_invariant); From 6043affefb4c1fe2f36739e46711ac4d5f0907d0 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Tue, 25 Mar 2025 14:26:03 +0100 Subject: [PATCH 40/47] Fix update attribute update function in Embedding. --- src/wmtk/submesh/Embedding.cpp | 98 ++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/wmtk/submesh/Embedding.cpp b/src/wmtk/submesh/Embedding.cpp index d0a54cbf36..a94a3b791c 100644 --- a/src/wmtk/submesh/Embedding.cpp +++ b/src/wmtk/submesh/Embedding.cpp @@ -217,6 +217,7 @@ bool Embedding::simplex_is_in_submesh(const simplex::Simplex& s) const void Embedding::set_split_strategies(operations::EdgeSplit& split) const { for (const auto& [pt, strat] : m_split_new) { + assert(m_mesh->validate_handle(m_tag_handle.at(pt))); attribute::MeshAttributeHandle h(*m_mesh, m_tag_handle.at(pt)); split.set_new_attribute_strategy(h, strat); } @@ -240,9 +241,106 @@ std::function Embedding::substructure_predicate() void Embedding::update_tag_attribute_handles() { Mesh& m = *m_mesh; + for (const PrimitiveType& pt : utils::primitive_below(m.top_simplex_type())) { + if (!m.has_attribute(m_tag_attribute_name[pt], pt)) { + log_and_throw_error( + "Cannot update handles. Mesh already no attribute with name {}", + m_tag_attribute_name[pt]); + } const auto& name = m_tag_attribute_name[pt]; m_tag_handle[pt] = m.get_attribute_handle_typed(name, pt); + + + attribute::MeshAttributeHandle h(m, m_tag_handle.at(pt)); + + m_split_new[pt] = std::make_shared>(h); + auto& split_new_strat = *(m_split_new[pt]); + + split_new_strat.set_strategy(operations::SplitBasicStrategy::Copy); + split_new_strat.set_rib_strategy(operations::SplitRibBasicStrategy::None); + + m_collapse_new[pt] = std::make_shared>(h); + auto& collapse_new_strat = *(m_collapse_new[pt]); + + auto collapse_new_func = [](const VectorX& a, + const VectorX& b, + const std::bitset<2>&) -> VectorX { + VectorX r(a.rows()); + + assert(a.rows() == b.rows()); + assert(a.rows() == r.rows()); + + for (int64_t i = 0; i < a.rows(); ++i) { + r[i] = a[i] | b[i]; + } + + return r; + }; + collapse_new_strat.set_strategy(collapse_new_func); + } + + + m_substructure_predicate = [this](const simplex::Simplex& s) -> bool { + return simplex_is_in_submesh(s); + }; + + auto update_tag_func = [this]( + const Eigen::MatrixX& P, + const std::vector& tuples) -> Eigen::VectorX { + // transfer from vertices (P.cols()) to top_simplex + assert(P.rows() == 1); // rows --> attribute dimension + // cols --> number of input simplices (vertices) + + const simplex::Simplex cell(m_mesh->top_simplex_type(), tuples[0]); + + assert(cell.primitive_type() != PrimitiveType::Vertex); + + // transfer from cell to facets + int64_t cell_tag; + { + auto tag_cell_acc = tag_accessor(cell.primitive_type()); + cell_tag = tag_cell_acc.const_scalar_attribute(cell); + + auto tag_facet_acc = tag_accessor(cell.primitive_type() - 1); + const auto facets = + simplex::faces_single_dimension_tuples(*m_mesh, cell, cell.primitive_type() - 1); + + for (const Tuple& f : facets) { + tag_facet_acc.scalar_attribute(f) |= cell_tag; + } + } + + if (cell.primitive_type() != PrimitiveType::Edge) { + // cell is triangle or tet + for (const PrimitiveType& pt : + utils::primitive_range(cell.primitive_type() - 1, PrimitiveType::Edge)) { + auto s_acc = tag_accessor(pt); + auto f_acc = tag_accessor(pt - 1); + const auto simplices = simplex::faces_single_dimension(*m_mesh, cell, pt); + for (const simplex::Simplex& s : simplices) { + const auto faces = simplex::faces_single_dimension_tuples(*m_mesh, s, pt - 1); + for (const Tuple& f : faces) { + f_acc.scalar_attribute(f) |= s_acc.const_scalar_attribute(s); + } + } + } + } + + return Eigen::VectorX::Constant(1, cell_tag); + }; + + attribute::MeshAttributeHandle h_v(m, m_tag_handle.at(PrimitiveType::Vertex)); + attribute::MeshAttributeHandle h_c(m, m_tag_handle.at(m.top_simplex_type())); + + m_transfer = + std::make_shared>( + h_c, + h_v, + update_tag_func); + + for (const auto& [pt, h] : m_tag_handle) { + assert(m.validate_handle(h)); } } From 74d9658e5a137b4edca21adcdc79ca3e742667cd Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 26 Mar 2025 12:06:23 +0100 Subject: [PATCH 41/47] Add envelope to swap in triwild. --- .../triwild_submesh/triwild_submesh_main.cpp | 7 ++++--- components/tests/wildmeshing/test_wildmeshing.cpp | 3 ++- .../internal/wildmeshing_embedding_2d.cpp | 13 +++++++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp index 788d5c562f..7fe950216e 100644 --- a/applications/triwild_submesh/triwild_submesh_main.cpp +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -123,9 +123,9 @@ int main(int argc, char* argv[]) e.geometry_position_name = "vertices"; e.thickness = j["envelope_size"]; - if (e.envelope_name == "input") { - e.envelope_geometry_mesh = mesh; // set as input - } + // if (e.envelope_name == "input") { + // e.envelope_geometry_mesh = mesh; // set as input + //} enves.push_back(e); @@ -149,6 +149,7 @@ int main(int argc, char* argv[]) wmo.max_passes = j["max_passes"]; wmo.replace_double_coordinate = false; wmo.scheduler_update_frequency = 0; + wmo.intermediate_output = j["intermediate_output"]; wmo.intermediate_output_path = ""; wmo.intermediate_output_name = j["output"]; wmo.envelopes = enves; diff --git a/components/tests/wildmeshing/test_wildmeshing.cpp b/components/tests/wildmeshing/test_wildmeshing.cpp index 7614c611b0..39e9ed584c 100644 --- a/components/tests/wildmeshing/test_wildmeshing.cpp +++ b/components/tests/wildmeshing/test_wildmeshing.cpp @@ -27,7 +27,8 @@ const std::filesystem::path data_dir = WMTK_DATA_DIR; TEST_CASE("test_wildmeshing_submesh", "[wildmeshing][.]") { // logger().set_level(spdlog::level::off); - logger().set_level(spdlog::level::trace); + logger().set_level(spdlog::level::debug); + opt_logger().set_level(spdlog::level::warn); std::shared_ptr mesh_ptr = std::make_shared(tests::edge_region_with_position_2D()); diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 6d9820e55f..a84f98b76d 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -262,7 +262,7 @@ std::vector, std::string>> wildmeshing_embedding // use envelope thickness if available double r = 0; for (const EnvelopeOptions& e : options.envelopes) { - r = std::max(r, e.thickness); + r = std::max(r, e.thickness * bbdiag); } assert(r > 0); return r; @@ -674,6 +674,7 @@ std::vector, std::string>> wildmeshing_embedding // collapse.add_invariant(invariant_separate_substructures); collapse.add_invariant(link_condition); + collapse.add_invariant(envelope_invariant); collapse.set_new_attribute_strategy(pt_attribute, CollapseBasicStrategy::CopyOther); @@ -793,6 +794,14 @@ std::vector, std::string>> wildmeshing_embedding max_amips = compute_max_amips(mesh, amips_attribute); } + write( + mesh, + options.intermediate_output_path, + options.intermediate_output_name, + options.input_mesh_position, + 1, + options.intermediate_output); + int iii = 0; bool is_double = false; for (int64_t i = 0; i < options.max_passes; ++i) { @@ -829,7 +838,7 @@ std::vector, std::string>> wildmeshing_embedding options.intermediate_output_path, options.intermediate_output_name, options.input_mesh_position, - i + 1, + i + 2, options.intermediate_output); assert(mesh.is_connectivity_valid()); From 25b21f59e11edab3d5eeed3bc6f107083f1843fd Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 27 Mar 2025 16:25:58 +0100 Subject: [PATCH 42/47] More clean-up and fixes. --- .../triwild_submesh/triwild_submesh_main.cpp | 34 +++-- .../components/wildmeshing/CMakeLists.txt | 5 +- .../internal/WildmeshingOptions.hpp | 2 +- .../internal/wildmeshing_embedding_2d.cpp | 122 +++++++++--------- .../wildmeshing/utils/IntermediateWrite.cpp | 59 +++++++++ .../wildmeshing/utils/IntermediateWrite.hpp | 40 ++++++ src/wmtk/attribute/MeshAttributes.cpp | 6 +- 7 files changed, 185 insertions(+), 83 deletions(-) create mode 100644 components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.cpp create mode 100644 components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.hpp diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp index 7fe950216e..4396edee6d 100644 --- a/applications/triwild_submesh/triwild_submesh_main.cpp +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -59,6 +59,10 @@ int main(int argc, char* argv[]) fs::path input_file = resolve_paths(json_input_file, {j["root"], j["input"]}); + if (!fs::exists(input_file)) { + log_and_throw_error("File {} does not exist.", input_file.string()); + } + auto mesh = wmtk::components::input::input(input_file, true); wmtk::logger().info( "mesh has {} vertices and {} edges", @@ -85,7 +89,9 @@ int main(int argc, char* argv[]) auto bg_mesh = wmtk::triwild::generate_bg_grid(x_min, y_min, x_max, y_max, j["target_edge_length"]); - wmtk::components::output::output(bg_mesh, "bg_mesh", "vertices"); + if (j["intermediate_output"]) { + wmtk::components::output::output(bg_mesh, "bg_mesh", "vertices"); + } wmtk::logger().info("generated bg mesh"); @@ -100,11 +106,13 @@ int main(int argc, char* argv[]) std::string output_file = j["output"]; - wmtk::components::output::output(*trimesh, output_file + "_after_insertion", "vertices"); - wmtk::components::output::output( - *edgemesh, - output_file + "_after_insertion_edge_mesh", - "vertices"); + if (j["intermediate_output"]) { + wmtk::components::output::output(*trimesh, output_file + "_after_insertion", "vertices"); + wmtk::components::output::output( + *edgemesh, + output_file + "_after_insertion_edge_mesh", + "vertices"); + } // clean up { @@ -161,23 +169,11 @@ int main(int argc, char* argv[]) wmo.use_embedding = true; auto meshes_after_tetwild = wildmeshing(wmo); + assert(meshes_after_tetwild.size() == 1); auto main_mesh = meshes_after_tetwild[0].first; wmtk::components::output::output(*main_mesh, output_file, "vertices"); - std::shared_ptr input_mesh; - for (int64_t i = 1; i < meshes_after_tetwild.size(); ++i) { - // output child meshes - wmtk::components::output::output( - *(meshes_after_tetwild[i].first), - output_file + "_" + meshes_after_tetwild[i].second, - "vertices"); - - if (meshes_after_tetwild[i].second == "input") { - input_mesh = meshes_after_tetwild[i].first; - } - } - const std::string report = j["report"]; if (!report.empty()) { nlohmann::json out_json; diff --git a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt index 2f5f4fc192..0d52a73ffd 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt +++ b/components/wildmeshing/wmtk/components/wildmeshing/CMakeLists.txt @@ -16,7 +16,10 @@ set(SRC_FILES internal/wildmeshing_utils.cpp internal/WildmeshingOptions.hpp internal/wildmeshing_embedding_2d.hpp - internal/wildmeshing_embedding_2d.cpp) + internal/wildmeshing_embedding_2d.cpp + utils/IntermediateWrite.hpp + utils/IntermediateWrite.cpp +) target_sources(wmtk_${COMPONENT_NAME} PRIVATE ${SRC_FILES}) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp index ff8a7962d0..a906a39db0 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/WildmeshingOptions.hpp @@ -54,7 +54,7 @@ struct WildMeshingOptions /** * The maximum number of iterations of the optimization. */ - double max_passes = 10; + int64_t max_passes = 10; /** * Debugging output. */ diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index a84f98b76d..8c14b9e408 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -1,7 +1,7 @@ #include "wildmeshing2d.hpp" +#include #include "WildmeshingOptions.hpp" -#include "wildmeshing_utils.hpp" #include #include @@ -76,8 +76,12 @@ using namespace operations::composite; using namespace function; using namespace invariants; -double compute_max_amips(const Mesh& mesh, const attribute::MeshAttributeHandle& amips_handle) +double compute_max_amips( + const attribute::MeshAttributeHandle& amips_handle, + double& last_max_amips, + const bool verbose = true) { + const Mesh& mesh = amips_handle.mesh(); const auto amips_accessor = mesh.create_const_accessor(amips_handle); // compute max energy @@ -93,11 +97,18 @@ double compute_max_amips(const Mesh& mesh, const attribute::MeshAttributeHandle& avg_energy = avg_energy / mesh.get_all(mesh.top_simplex_type()).size(); - logger().info( - "Max AMIPS Energy: {:>6.2f}, Min AMIPS Energy: {:>6.2f}, Avg AMIPS Energy: {:>6.2f}", - max_energy, - min_energy, - avg_energy); + if (verbose) { + logger().info( + "Max AMIPS Energy: {:>6.2f}, Min AMIPS Energy: {:>6.2f}, Avg AMIPS Energy: {:>6.2f}", + max_energy, + min_energy, + avg_energy); + } + + if (max_energy > last_max_amips) { + logger().error("Max amips increased from {} to {}", last_max_amips, max_energy); + } + last_max_amips = max_energy; return max_energy; } @@ -122,8 +133,12 @@ void print_stats(const wmtk::SchedulerStats& stats, const std::string& name = "" } } -void print_unrounded_vertices(const Mesh& mesh, const attribute::MeshAttributeHandle& pt_handle) +int64_t count_unrounded_vertices( + const attribute::MeshAttributeHandle& pt_handle, + int64_t& last_num_unrounded, + const bool verbose = true) { + const Mesh& mesh = pt_handle.mesh(); const auto pt_accessor = mesh.create_const_accessor(pt_handle); // verbose logger, can be removed @@ -138,9 +153,19 @@ void print_unrounded_vertices(const Mesh& mesh, const attribute::MeshAttributeHa } } - if (unrounded > 0) { + if (unrounded > 0 && verbose) { logger().info("Mesh has {} unrounded vertices", unrounded); } + + if (unrounded > last_num_unrounded) { + logger().error( + "Number of unrounded vertices increased from {} to {}", + last_num_unrounded, + unrounded); + } + last_num_unrounded = unrounded; + + return unrounded; } void test_flipped_triangles(const Mesh& mesh, const attribute::MeshAttributeHandle& pt_handle) @@ -242,8 +267,8 @@ std::vector, std::string>> wildmeshing_embedding compute_amips); amips_update->run_on_all(); - double max_amips; - max_amips = compute_max_amips(mesh, amips_attribute); + double last_max_amips = std::numeric_limits::max(); + compute_max_amips(amips_attribute, last_max_amips); ////////////////////////////////// // Storing target edge length @@ -362,11 +387,6 @@ std::vector, std::string>> wildmeshing_embedding auto envelope_invariant = std::make_shared(mesh); - std::vector> envelopes; - std::vector mesh_constraint_pairs; // TODO remove - - std::vector, std::string>> multimesh_meshes; - for (const EnvelopeOptions& e : options.envelopes) { Mesh& constrained_mesh = *e.envelope_constrained_mesh; Mesh& geometry_mesh = *e.envelope_geometry_mesh; @@ -570,7 +590,7 @@ std::vector, std::string>> wildmeshing_embedding ops.emplace_back(split_sequence); ops_name.emplace_back("SPLIT"); - ops.emplace_back(rounding); + ops.emplace_back(rounding); // TODO split tries to round already ops_name.emplace_back("rounding"); } @@ -647,7 +667,7 @@ std::vector, std::string>> wildmeshing_embedding ops.emplace_back(collapse_then_round); ops_name.emplace_back("COLLAPSE"); - ops.emplace_back(rounding); + ops.emplace_back(rounding); // TODO collapse rounds already, why a second rounding? ops_name.emplace_back("rounding"); } @@ -672,7 +692,6 @@ std::vector, std::string>> wildmeshing_embedding op.add_transfer_strategy(edge_length_update); op.add_transfer_strategy(tag_update); - // collapse.add_invariant(invariant_separate_substructures); collapse.add_invariant(link_condition); collapse.add_invariant(envelope_invariant); @@ -742,6 +761,7 @@ std::vector, std::string>> wildmeshing_embedding proj_smoothing->add_invariant(frozen_vertex_invariant); proj_smoothing->add_invariant(envelope_invariant); proj_smoothing->add_invariant(inversion_invariant); + proj_smoothing->add_invariant(function_invariant); // TODO confirm that this should be here proj_smoothing->add_transfer_strategy(amips_update); proj_smoothing->add_transfer_strategy(edge_length_update); @@ -757,13 +777,14 @@ std::vector, std::string>> wildmeshing_embedding ops_name.emplace_back("rounding"); } - write( + wildmeshing::utils::IntermediateWrite interm_writer( mesh, options.intermediate_output_path, options.intermediate_output_name, options.input_mesh_position, - 0, options.intermediate_output); + interm_writer.disable_vertex_write(); + interm_writer.write(); ////////////////////////////////// // Running all ops in order n times @@ -780,37 +801,31 @@ std::vector, std::string>> wildmeshing_embedding // debug code test_flipped_triangles(mesh, pt_attribute); + int64_t last_num_unrounded = std::numeric_limits::max(); { logger().info("----------------------- Preprocess Collapse -----------------------"); - // logger().info("Executing collapse ..."); SchedulerStats pre_stats = scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag.as()); print_stats(pre_stats, "preprocessing collapse"); - // verbose logger, can be removed - print_unrounded_vertices(mesh, pt_attribute); - test_flipped_triangles(mesh, pt_attribute); - max_amips = compute_max_amips(mesh, amips_attribute); + count_unrounded_vertices(pt_attribute, last_num_unrounded, false); + // test_flipped_triangles(mesh, pt_attribute); + compute_max_amips(amips_attribute, last_max_amips, false); } - write( - mesh, - options.intermediate_output_path, - options.intermediate_output_name, - options.input_mesh_position, - 1, - options.intermediate_output); + interm_writer.write(); + - int iii = 0; - bool is_double = false; for (int64_t i = 0; i < options.max_passes; ++i) { logger().info("--------------------------- Pass {} ---------------------------", i); SchedulerStats pass_stats; - int jj = 0; - for (auto& op : ops) { - // logger().info("Executing {} ...", ops_name[jj]); + for (int64_t op_it = 0; op_it != ops.size(); ++op_it) { + auto& op = ops[op_it]; + auto& op_name = ops_name[op_it]; + + logger().debug("Executing {} ...", op_name); SchedulerStats stats; if (op->primitive_type() == PrimitiveType::Edge) { @@ -819,35 +834,26 @@ std::vector, std::string>> wildmeshing_embedding stats = scheduler.run_operation_on_all(*op); } pass_stats += stats; - print_stats(stats, ops_name[jj]); - - // verbose logger, can be removed - print_unrounded_vertices(mesh, pt_attribute); - test_flipped_triangles(mesh, pt_attribute); - max_amips = compute_max_amips(mesh, amips_attribute); + print_stats(stats, op_name); - ++jj; + count_unrounded_vertices(pt_attribute, last_num_unrounded, false); + // test_flipped_triangles(mesh, pt_attribute); + compute_max_amips(amips_attribute, last_max_amips, false); } print_stats(pass_stats); multimesh::consolidate(mesh); - write( - mesh, - options.intermediate_output_path, - options.intermediate_output_name, - options.input_mesh_position, - i + 2, - options.intermediate_output); + interm_writer.write(); assert(mesh.is_connectivity_valid()); - print_unrounded_vertices(mesh, pt_attribute); - max_amips = compute_max_amips(mesh, amips_attribute); + const int64_t num_unrounded = count_unrounded_vertices(pt_attribute, last_num_unrounded); + const double max_amips = compute_max_amips(amips_attribute, last_max_amips); // stop at good quality - if (max_amips <= target_max_amips && is_double) { + if (max_amips <= target_max_amips && num_unrounded == 0) { break; } } @@ -860,17 +866,13 @@ std::vector, std::string>> wildmeshing_embedding scheduler.run_operation_on_all(*collapse_then_round, visited_edge_flag.as()); print_stats(post_stats, "preprocessing collapse"); - max_amips = compute_max_amips(mesh, amips_attribute); + compute_max_amips(amips_attribute, last_max_amips); multimesh::consolidate(mesh); std::vector, std::string>> all_meshes; all_meshes.push_back(std::make_pair(options.input_mesh, "main")); - for (const auto& p : multimesh_meshes) { - all_meshes.push_back(p); - } - return all_meshes; } diff --git a/components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.cpp b/components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.cpp new file mode 100644 index 0000000000..72b7730213 --- /dev/null +++ b/components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.cpp @@ -0,0 +1,59 @@ +#include "IntermediateWrite.hpp" + +#include + +wmtk::components::wildmeshing::utils::IntermediateWrite::IntermediateWrite( + const Mesh& mesh, + const std::filesystem::path& out_dir, + const std::filesystem::path& name, + const std::string& vname, + const bool intermediate_output) + : m_mesh(mesh) + , m_name(out_dir / name) + , m_pos_attribute_name(vname) + , m_write(intermediate_output) +{ + switch (m_mesh.top_simplex_type()) { + case PrimitiveType::Tetrahedron: m_t = true; [[fallthrough]]; + case PrimitiveType::Triangle: m_f = true; [[fallthrough]]; + case PrimitiveType::Edge: m_e = true; [[fallthrough]]; + case PrimitiveType::Vertex: m_v = true; break; + default: log_and_throw_error("Unkown primitive type in IntermediateWrite"); + } +} + +void wmtk::components::wildmeshing::utils::IntermediateWrite::write() +{ + if (!m_write) { + return; + } + wmtk::io::ParaviewWriter writer( + fmt::format("{}_{}", m_name.string(), m_iter++), + m_pos_attribute_name, + m_mesh, + m_v, + m_e, + m_f, + m_t); + m_mesh.serialize(writer); +} + +void wmtk::components::wildmeshing::utils::IntermediateWrite::disable_vertex_write() +{ + m_v = false; +} + +void wmtk::components::wildmeshing::utils::IntermediateWrite::disable_edge_write() +{ + m_e = false; +} + +void wmtk::components::wildmeshing::utils::IntermediateWrite::disable_face_write() +{ + m_f = false; +} + +void wmtk::components::wildmeshing::utils::IntermediateWrite::disable_tetrahedron_write() +{ + m_t = false; +} diff --git a/components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.hpp b/components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.hpp new file mode 100644 index 0000000000..ad2a33c1ad --- /dev/null +++ b/components/wildmeshing/wmtk/components/wildmeshing/utils/IntermediateWrite.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace wmtk::components::wildmeshing::utils { + +class IntermediateWrite +{ +public: + IntermediateWrite( + const Mesh& mesh, + const std::filesystem::path& out_dir, + const std::filesystem::path& name, + const std::string& vname, + const bool intermediate_output); + + IntermediateWrite(IntermediateWrite&) = delete; + + void write(); + + void disable_vertex_write(); + void disable_edge_write(); + void disable_face_write(); + void disable_tetrahedron_write(); + +private: + const Mesh& m_mesh; + const std::filesystem::path m_name; + const std::string m_pos_attribute_name; + const bool m_write; + int64_t m_iter = 0; + + bool m_v = false; + bool m_e = false; + bool m_f = false; + bool m_t = false; +}; + +} // namespace wmtk::components::wildmeshing::utils \ No newline at end of file diff --git a/src/wmtk/attribute/MeshAttributes.cpp b/src/wmtk/attribute/MeshAttributes.cpp index a026401ee5..3376753e7d 100644 --- a/src/wmtk/attribute/MeshAttributes.cpp +++ b/src/wmtk/attribute/MeshAttributes.cpp @@ -286,15 +286,17 @@ void MeshAttributes::remove_attributes( template void MeshAttributes::remove_attribute(const AttributeHandle& attribute) { + const std::string name = m_attributes[attribute.index]->name(); + m_handles.erase(name); m_attributes[attribute.index].reset(); } template bool MeshAttributes::validate() const { - if (m_handles.size() != m_attributes.size()) { + if (m_handles.size() > m_attributes.size()) { logger().warn( - "Number of handles and attributes is not the same. Handles: {}, attributes: {}", + "More handles than attributes. Handles: {}, attributes: {}", m_handles.size(), m_attributes.size()); return false; From 9c60aeef9503b5c760ff3702387797fd8daf6ac6 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Thu, 27 Mar 2025 11:59:20 -0400 Subject: [PATCH 43/47] Fix Mac compilation issues. --- src/wmtk/Mesh.hpp | 2 +- src/wmtk/invariants/EnvelopeInvariant.cpp | 47 ++++++++++--------- .../operations/composite/ProjectOperation.cpp | 2 +- src/wmtk/submesh/Embedding.hpp | 12 ++--- src/wmtk/submesh/SubMesh.hpp | 2 +- src/wmtk/utils/Logger.hpp | 2 +- 6 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/wmtk/Mesh.hpp b/src/wmtk/Mesh.hpp index c5a26a5c7a..73f5459d5a 100644 --- a/src/wmtk/Mesh.hpp +++ b/src/wmtk/Mesh.hpp @@ -135,7 +135,7 @@ class Mesh : public std::enable_shared_from_this, PrimitiveType top_simplex_type() const; bool is_free() const; - MeshType mesh_type() const; + MeshType mesh_type() const override; // attribute directly hashes its "children" components so it overrides "child_hashes" std::map child_hashables() const override; diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index 80d2b2b026..294aa5c402 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -44,12 +44,12 @@ std::shared_ptr init_fast_envelope( const std::vector& facest = envelope_mesh.get_all(PF); for (const auto& f : facest) { if constexpr (std::is_same()) { - Eigen::Vector3d p0 = accessor.const_vector_attribute(f).cast(); - Eigen::Vector3d p1 = - accessor.const_vector_attribute(envelope_mesh.switch_tuple(f, PV)).cast(); + Eigen::Vector3d p0 = accessor.const_vector_attribute(f).template cast(); + Eigen::Vector3d p1 = accessor.const_vector_attribute(envelope_mesh.switch_tuple(f, PV)) + .template cast(); Eigen::Vector3d p2 = accessor.const_vector_attribute(envelope_mesh.switch_tuples(f, {PE, PV})) - .cast(); + .template cast(); vertices.push_back(p0); vertices.push_back(p1); @@ -93,9 +93,9 @@ std::shared_ptr init_bvh( for (const Tuple& e : edgest) { if constexpr (std::is_same()) { - const auto p0 = accessor.const_vector_attribute(e).cast(); - const auto p1 = - accessor.const_vector_attribute(envelope_mesh.switch_tuple(e, PV)).cast(); + const auto p0 = accessor.const_vector_attribute(e).template cast(); + const auto p1 = accessor.const_vector_attribute(envelope_mesh.switch_tuple(e, PV)) + .template cast(); vertices.row(2 * index) = p0; vertices.row(2 * index + 1) = p1; @@ -137,11 +137,11 @@ std::shared_ptr init_fast_envelope( const std::vector& facest = sub.get_all(PF); for (const auto& f : facest) { if constexpr (std::is_same()) { - Eigen::Vector3d p0 = accessor.const_vector_attribute(f).cast(); + Eigen::Vector3d p0 = accessor.const_vector_attribute(f).template cast(); Eigen::Vector3d p1 = - accessor.const_vector_attribute(sub.switch_tuple(f, PV)).cast(); - Eigen::Vector3d p2 = - accessor.const_vector_attribute(sub.switch_tuples(f, {PE, PV})).cast(); + accessor.const_vector_attribute(sub.switch_tuple(f, PV)).template cast(); + Eigen::Vector3d p2 = accessor.const_vector_attribute(sub.switch_tuples(f, {PE, PV})) + .template cast(); vertices.push_back(p0); vertices.push_back(p1); @@ -185,8 +185,9 @@ std::shared_ptr init_bvh( for (const Tuple& e : edgest) { if constexpr (std::is_same()) { - const auto p0 = accessor.const_vector_attribute(e).cast(); - const auto p1 = accessor.const_vector_attribute(sub.switch_tuple(e, PV)).cast(); + const auto p0 = accessor.const_vector_attribute(e).template cast(); + const auto p1 = + accessor.const_vector_attribute(sub.switch_tuple(e, PV)).template cast(); vertices.row(2 * index) = p0; vertices.row(2 * index + 1) = p1; @@ -258,8 +259,8 @@ EnvelopeInvariant::EnvelopeInvariant( const submesh::SubMesh& sub) : Invariant(pt_attribute.mesh(), false, false, true) , m_coordinate_handle(pt_attribute) - , m_envelope_size(envelope_size) , m_submesh(&sub) + , m_envelope_size(envelope_size) { // log_assert( // envelope_mesh_coordinate.holds() || envelope_mesh_coordinate.holds(), @@ -408,9 +409,9 @@ bool EnvelopeInvariant::after_with_envelope_triangle( triangle[2] = accessor.const_vector_attribute(faces[2]); } if constexpr (std::is_same_v) { - triangle[0] = accessor.const_vector_attribute(faces[0]).cast(); - triangle[1] = accessor.const_vector_attribute(faces[1]).cast(); - triangle[2] = accessor.const_vector_attribute(faces[2]).cast(); + triangle[0] = accessor.const_vector_attribute(faces[0]).template cast(); + triangle[1] = accessor.const_vector_attribute(faces[1]).template cast(); + triangle[2] = accessor.const_vector_attribute(faces[2]).template cast(); } if (m_envelope->is_outside(triangle)) { @@ -454,8 +455,8 @@ bool EnvelopeInvariant::after_with_envelope_edge( p0 = accessor.const_vector_attribute(faces[0]); p1 = accessor.const_vector_attribute(faces[1]); } else if constexpr (std::is_same_v) { - p0 = accessor.const_vector_attribute(faces[0]).cast(); - p1 = accessor.const_vector_attribute(faces[1]).cast(); + p0 = accessor.const_vector_attribute(faces[0]).template cast(); + p1 = accessor.const_vector_attribute(faces[1]).template cast(); } else { log_and_throw_error("Unknown attribute type"); } @@ -491,7 +492,7 @@ bool EnvelopeInvariant::after_with_envelope_vertex( if constexpr (std::is_same_v) { p = accessor.const_vector_attribute(tuple); } else if constexpr (std::is_same_v) { - p = accessor.const_vector_attribute(tuple).cast(); + p = accessor.const_vector_attribute(tuple).template cast(); } else { log_and_throw_error("Unknown attribute type"); } @@ -552,9 +553,9 @@ bool EnvelopeInvariant::after_with_bvh_edge( p0 = accessor.const_vector_attribute(tuple); p1 = accessor.const_vector_attribute(mesh().switch_tuple(tuple, PV)); } else if constexpr (std::is_same_v) { - p0 = accessor.const_vector_attribute(tuple).cast(); + p0 = accessor.const_vector_attribute(tuple).template cast(); p1 = accessor.const_vector_attribute(mesh().switch_tuple(tuple, PV)) - .cast(); + .template cast(); } else { log_and_throw_error("Unknown attribute type"); } @@ -611,7 +612,7 @@ bool EnvelopeInvariant::after_with_bvh_vertex( if constexpr (std::is_same_v) { p = accessor.const_vector_attribute(tuple); } else if constexpr (std::is_same_v) { - p = accessor.const_vector_attribute(tuple).cast(); + p = accessor.const_vector_attribute(tuple).template cast(); } else { log_and_throw_error("Unknown attribute type"); } diff --git a/src/wmtk/operations/composite/ProjectOperation.cpp b/src/wmtk/operations/composite/ProjectOperation.cpp index 02fcf069d0..ddf4067c39 100644 --- a/src/wmtk/operations/composite/ProjectOperation.cpp +++ b/src/wmtk/operations/composite/ProjectOperation.cpp @@ -124,7 +124,7 @@ ProjectOperation::ProjectOperation( return accessor.const_vector_attribute(_t); } if constexpr (std::is_same_v) { - return accessor.const_vector_attribute(_t).cast(); + return accessor.const_vector_attribute(_t).template cast(); } log_and_throw_error("Position attribute must be double or rational"); }, diff --git a/src/wmtk/submesh/Embedding.hpp b/src/wmtk/submesh/Embedding.hpp index eb10bd790d..52f6a79eaa 100644 --- a/src/wmtk/submesh/Embedding.hpp +++ b/src/wmtk/submesh/Embedding.hpp @@ -43,14 +43,14 @@ class Embedding : public std::enable_shared_from_this, public MeshBas std::vector get_all(PrimitiveType type) const override; std::vector get_all_id_simplex(PrimitiveType type) const override; - int64_t top_cell_dimension() const; + int64_t top_cell_dimension() const override; - MeshType mesh_type() const; + MeshType mesh_type() const override; - Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const; - bool is_boundary(PrimitiveType, const Tuple& tuple) const; - int64_t id(const simplex::Simplex& s) const; - int64_t id(const Tuple& tuple, PrimitiveType pt) const; + Tuple switch_tuple(const Tuple& tuple, PrimitiveType type) const override; + bool is_boundary(PrimitiveType, const Tuple& tuple) const override; + int64_t id(const simplex::Simplex& s) const override; + int64_t id(const Tuple& tuple, PrimitiveType pt) const override; std::vector> get_child_meshes() const; diff --git a/src/wmtk/submesh/SubMesh.hpp b/src/wmtk/submesh/SubMesh.hpp index b24e04921c..f280640b5f 100644 --- a/src/wmtk/submesh/SubMesh.hpp +++ b/src/wmtk/submesh/SubMesh.hpp @@ -56,7 +56,7 @@ class SubMesh : public std::enable_shared_from_this, public MeshBase */ PrimitiveType top_simplex_type(const Tuple& tuple) const; - MeshType mesh_type() const; + MeshType mesh_type() const override; /** * @brief Get the maximum primitive type that has a tag in the entire mesh. diff --git a/src/wmtk/utils/Logger.hpp b/src/wmtk/utils/Logger.hpp index e59dd23549..5e8c5b5b7e 100644 --- a/src/wmtk/utils/Logger.hpp +++ b/src/wmtk/utils/Logger.hpp @@ -49,7 +49,7 @@ template /** * @brief Calls log_and_throw_error if check is false. */ -[[noreturn]] void log_assert(const bool check, const std::string& msg); +void log_assert(const bool check, const std::string& msg); /** * @brief Calls log_and_throw_error if check is false. From 55a463ad4c89e2b0b0c6a05494fa138b525e8c65 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 9 Apr 2025 16:24:24 -0400 Subject: [PATCH 44/47] Removing bad invariant in smoothing. --- .../internal/wildmeshing_embedding_2d.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp index 8c14b9e408..2c83bfbb8e 100644 --- a/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp +++ b/components/wildmeshing/wmtk/components/wildmeshing/internal/wildmeshing_embedding_2d.cpp @@ -103,11 +103,12 @@ double compute_max_amips( max_energy, min_energy, avg_energy); - } - if (max_energy > last_max_amips) { - logger().error("Max amips increased from {} to {}", last_max_amips, max_energy); + if (max_energy > last_max_amips) { + logger().info("Max amips increased from {} to {}", last_max_amips, max_energy); + } } + last_max_amips = max_energy; return max_energy; @@ -610,7 +611,7 @@ std::vector, std::string>> wildmeshing_embedding auto setup_collapse = [&](std::shared_ptr& collapse) { // collapse->add_invariant(invariant_separate_substructures); - collapse->add_invariant(std::make_shared(mesh)); + // collapse->add_invariant(std::make_shared(mesh)); collapse->add_invariant(link_condition); collapse->add_invariant(inversion_invariant); collapse->add_invariant(function_invariant); @@ -761,7 +762,7 @@ std::vector, std::string>> wildmeshing_embedding proj_smoothing->add_invariant(frozen_vertex_invariant); proj_smoothing->add_invariant(envelope_invariant); proj_smoothing->add_invariant(inversion_invariant); - proj_smoothing->add_invariant(function_invariant); // TODO confirm that this should be here + //proj_smoothing->add_invariant(function_invariant); // TODO confirm that this should be here proj_smoothing->add_transfer_strategy(amips_update); proj_smoothing->add_transfer_strategy(edge_length_update); @@ -836,9 +837,9 @@ std::vector, std::string>> wildmeshing_embedding pass_stats += stats; print_stats(stats, op_name); - count_unrounded_vertices(pt_attribute, last_num_unrounded, false); + // count_unrounded_vertices(pt_attribute, last_num_unrounded, false); // test_flipped_triangles(mesh, pt_attribute); - compute_max_amips(amips_attribute, last_max_amips, false); + // compute_max_amips(amips_attribute, last_max_amips, false); } print_stats(pass_stats); From 24db387ff66c21d7c95d03ed7cbabf6e257a8cec Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 9 Apr 2025 16:44:12 -0400 Subject: [PATCH 45/47] Fix Linux compilation issue. --- src/wmtk/invariants/EnvelopeInvariant.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wmtk/invariants/EnvelopeInvariant.cpp b/src/wmtk/invariants/EnvelopeInvariant.cpp index 294aa5c402..95fb2c5240 100644 --- a/src/wmtk/invariants/EnvelopeInvariant.cpp +++ b/src/wmtk/invariants/EnvelopeInvariant.cpp @@ -608,15 +608,15 @@ bool EnvelopeInvariant::after_with_bvh_vertex( const auto accessor = mesh().create_const_accessor(tah); - SimpleBVH::VectorMax3d p; + SimpleBVH::VectorMax3d _p; if constexpr (std::is_same_v) { - p = accessor.const_vector_attribute(tuple); + _p = accessor.const_vector_attribute(tuple); } else if constexpr (std::is_same_v) { - p = accessor.const_vector_attribute(tuple).template cast(); + _p = accessor.const_vector_attribute(tuple).template cast(); } else { log_and_throw_error("Unknown attribute type"); } - return p; + return _p; }, m_coordinate_handle.handle()); From bdc7b20711663821a033ed8e8677d46f03635e1e Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 9 Apr 2025 17:46:01 -0400 Subject: [PATCH 46/47] Remove unfinished test from integration tests. --- tests/submesh/test_submesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/submesh/test_submesh.cpp b/tests/submesh/test_submesh.cpp index 08893cb69a..50bd65b021 100644 --- a/tests/submesh/test_submesh.cpp +++ b/tests/submesh/test_submesh.cpp @@ -138,7 +138,7 @@ TEST_CASE("submesh_init_from_tag", "[mesh][submesh]") CHECK(sub.get_all(PV).size() == 5); } -TEST_CASE("submesh_top_dimension_cofaces", "[mesh][submesh]") +TEST_CASE("submesh_top_dimension_cofaces", "[mesh][submesh][.]") { REQUIRE(false); // test not implemented From 895fbe4c03fcdb0664ea6b8d6c961d1e22b14c67 Mon Sep 17 00:00:00 2001 From: Daniel Zint Date: Wed, 28 May 2025 16:50:15 +0200 Subject: [PATCH 47/47] Add timings to triwild report. --- .../triwild_submesh/triwild_submesh_main.cpp | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/applications/triwild_submesh/triwild_submesh_main.cpp b/applications/triwild_submesh/triwild_submesh_main.cpp index 4396edee6d..d0371db8e5 100644 --- a/applications/triwild_submesh/triwild_submesh_main.cpp +++ b/applications/triwild_submesh/triwild_submesh_main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "triwild_grid.hpp" @@ -57,6 +58,10 @@ int main(int argc, char* argv[]) } } + nlohmann::json out_json; + + wmtk::utils::StopWatch sw_total("Triwild Total"); + fs::path input_file = resolve_paths(json_input_file, {j["root"], j["input"]}); if (!fs::exists(input_file)) { @@ -86,18 +91,28 @@ int main(int argc, char* argv[]) y_max = std::max(y_max, mesh_pt_accessor.const_vector_attribute(v)[1]); } + wmtk::utils::StopWatch sw_bg_mesh("Background Mesh"); + auto bg_mesh = wmtk::triwild::generate_bg_grid(x_min, y_min, x_max, y_max, j["target_edge_length"]); + sw_bg_mesh.stop(); + out_json["runtime_seconds"]["background_mesh"] = sw_bg_mesh.getElapsedTime(); + if (j["intermediate_output"]) { wmtk::components::output::output(bg_mesh, "bg_mesh", "vertices"); } wmtk::logger().info("generated bg mesh"); + wmtk::utils::StopWatch sw_edge_insertion("Edge Insertion"); + wmtk::components::EdgeInsertionMeshes eim = wmtk::components::edge_insertion(static_cast(*mesh), bg_mesh); + sw_edge_insertion.stop(); + out_json["runtime_seconds"]["edge_insertion"] = sw_edge_insertion.getElapsedTime(); + wmtk::logger().info("finised edge insertion"); auto trimesh = eim.tri_mesh; @@ -168,15 +183,24 @@ int main(int argc, char* argv[]) wmo.skip_smooth = j["skip_smooth"]; wmo.use_embedding = true; + + wmtk::utils::StopWatch sw_wildmeshing("Wildmeshing"); + auto meshes_after_tetwild = wildmeshing(wmo); + + sw_wildmeshing.stop(); + out_json["runtime_seconds"]["wildmeshing"] = sw_wildmeshing.getElapsedTime(); + assert(meshes_after_tetwild.size() == 1); auto main_mesh = meshes_after_tetwild[0].first; wmtk::components::output::output(*main_mesh, output_file, "vertices"); + sw_total.stop(); + out_json["runtime_seconds"]["total"] = sw_total.getElapsedTime(); + const std::string report = j["report"]; if (!report.empty()) { - nlohmann::json out_json; out_json["vertices"] = main_mesh->get_all(PrimitiveType::Vertex).size(); out_json["edges"] = main_mesh->get_all(PrimitiveType::Edge).size(); out_json["cells"] = main_mesh->get_all(PrimitiveType::Triangle).size();